Initial commit
1233
static/css/style.css
Normal file
BIN
static/cv_alexandre_gut.pdf
Normal file
BIN
static/images/ACSIA.jpeg
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
static/images/Debbie.jpeg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
static/images/Debbie2.jpeg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
static/images/Deshumidificateur.jpeg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
static/images/Deshumidificateur2.jpeg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
static/images/Deshumidificateur3.jpeg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
static/images/Elsa.jpeg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
static/images/IAMS.jpeg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
static/images/IAMS2.jpeg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
static/images/IAMS3.jpeg
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
static/images/IAMS4.jpeg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
static/images/Imprimante.jpeg
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
static/images/Imprimante2.jpeg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
static/images/Instacook.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
static/images/Instacook2.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
static/images/Prodigy.jpeg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
static/images/Prodigy2.jpeg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
static/images/Pélican.jpeg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
static/images/Pélican2.jpeg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
static/images/Shift_ui.jpeg
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
static/images/Smartmotor.jpg
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
static/images/Smartmotor2.jpg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
static/images/Smartmotor3.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
static/images/Stitch.jpeg
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
static/images/VialFinder.jpeg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
static/images/Zelda.jpeg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
static/images/Zelda2.jpeg
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
static/images/assistant.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
static/images/assistant2.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
static/images/assistant3.png
Normal file
|
After Width: | Height: | Size: 457 KiB |
BIN
static/images/assistant4.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
static/images/assistant5.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
static/images/pybash.jpeg
Normal file
|
After Width: | Height: | Size: 77 KiB |
130
static/js/main.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Portfolio Alexandre — Scripts JS
|
||||
* Filtres dynamiques, navigation mobile, lightbox
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// ─── Navigation mobile ─────────────────────────────────────────────────
|
||||
const navToggle = document.getElementById('navToggle');
|
||||
const navLinks = document.querySelector('.nav-links');
|
||||
|
||||
if (navToggle && navLinks) {
|
||||
navToggle.addEventListener('click', () => {
|
||||
navLinks.classList.toggle('open');
|
||||
});
|
||||
// Fermer le menu au clic sur un lien
|
||||
navLinks.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.addEventListener('click', () => navLinks.classList.remove('open'));
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Filtres dynamiques (page projets) ────────────────────────────────
|
||||
const filterPills = document.querySelectorAll('.filter-pill:not(.pill-more)');
|
||||
|
||||
filterPills.forEach(pill => {
|
||||
pill.addEventListener('click', () => {
|
||||
const filterType = pill.dataset.filter; // 'category' ou 'tech'
|
||||
const filterValue = pill.dataset.value;
|
||||
|
||||
// Mettre à jour l'input hidden correspondant
|
||||
const inputId = filterType === 'category' ? 'categoryInput' : 'techInput';
|
||||
const hiddenInput = document.getElementById(inputId);
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = filterValue;
|
||||
}
|
||||
|
||||
// Mettre à jour l'apparence des pills du même groupe
|
||||
const group = pill.closest('.filter-pills');
|
||||
if (group) {
|
||||
group.querySelectorAll('.filter-pill').forEach(p => p.classList.remove('active'));
|
||||
pill.classList.add('active');
|
||||
}
|
||||
|
||||
// Soumettre le formulaire automatiquement
|
||||
const form = document.getElementById('filtersForm');
|
||||
if (form) form.submit();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Smooth scroll pour ancres internes ───────────────────────────────
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', (e) => {
|
||||
const targetId = anchor.getAttribute('href').slice(1);
|
||||
const target = document.getElementById(targetId);
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
const navHeight = parseInt(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--nav-height')
|
||||
) || 68;
|
||||
const top = target.getBoundingClientRect().top + window.scrollY - navHeight - 16;
|
||||
window.scrollTo({ top, behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Animation d'apparition des cartes ────────────────────────────────
|
||||
const cards = document.querySelectorAll('.project-card');
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry, i) => {
|
||||
if (entry.isIntersecting) {
|
||||
setTimeout(() => {
|
||||
entry.target.style.opacity = '1';
|
||||
entry.target.style.transform = 'translateY(0)';
|
||||
}, i * 80);
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
cards.forEach(card => {
|
||||
card.style.opacity = '0';
|
||||
card.style.transform = 'translateY(20px)';
|
||||
card.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
|
||||
observer.observe(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Navbar : réduire au scroll ───────────────────────────────────────
|
||||
const navbar = document.querySelector('.navbar');
|
||||
let lastScrollY = window.scrollY;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.scrollY > 80) {
|
||||
navbar?.classList.add('scrolled');
|
||||
} else {
|
||||
navbar?.classList.remove('scrolled');
|
||||
}
|
||||
lastScrollY = window.scrollY;
|
||||
}, { passive: true });
|
||||
|
||||
});
|
||||
|
||||
// ─── Lightbox (fonctions globales appelées depuis HTML) ──────────────────
|
||||
function openLightbox(src) {
|
||||
const lightbox = document.getElementById('lightbox');
|
||||
const lightboxImg = document.getElementById('lightboxImg');
|
||||
if (lightbox && lightboxImg) {
|
||||
lightboxImg.src = src;
|
||||
lightbox.classList.add('open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
function closeLightbox() {
|
||||
const lightbox = document.getElementById('lightbox');
|
||||
if (lightbox) {
|
||||
lightbox.classList.remove('open');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Fermer la lightbox avec Échap
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') closeLightbox();
|
||||
});
|
||||