Index > filter menu fonctionnel + router URL + opti images
All checks were successful
Deploy / Deploy to Production (push) Successful in 2s

- Filtre actif reflété dans ?filter= (pushState), restauré au chargement et sur popstate
- Projet ouvert reflété dans ?project= (pushState), restauré au chargement et sur popstate
- router.js : helpers setParam/getParam + dispatch routechange sur popstate
- Opti images projet : snippet picture, srcset 500-1800w JPEG+WebP, sizes 50vw/>1000px / 100vw mobile
- SCSS : picture { display: contents } dans .project-slideshow, .filtered-out, button.active

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-06-04 16:02:20 +02:00
parent 444b07ccad
commit 40366f617b
11 changed files with 139 additions and 23 deletions

28
assets/js/filter-menu.js Normal file
View file

@ -0,0 +1,28 @@
const filterButtons = document.querySelectorAll(".filter-menu button");
const projectItems = document.querySelectorAll(".projects-index li");
function applyFilter(filter) {
filterButtons.forEach((b) => b.classList.remove("active"));
if (!filter) {
projectItems.forEach((item) => item.classList.remove("filtered-out"));
return;
}
const btn = [...filterButtons].find((b) => b.dataset.filter === filter);
if (btn) btn.classList.add("active");
projectItems.forEach((item) => {
item.classList.toggle("filtered-out", item.dataset.category !== filter);
});
}
filterButtons.forEach((btn) => {
btn.addEventListener("click", () => {
const isActive = btn.classList.contains("active");
const newFilter = isActive ? null : btn.dataset.filter;
applyFilter(newFilter);
Router.setParam("filter", newFilter);
});
});
window.addEventListener("routechange", () => applyFilter(Router.getParam("filter")));
applyFilter(Router.getParam("filter"));

View file

@ -1,13 +1,29 @@
const allProjects = document.querySelectorAll(".projects-index li");
function selectProject(slug) {
if (!slug) {
allProjects.forEach((p) => p.classList.remove("selected", "unselected"));
return;
}
const target = [...allProjects].find((p) => p.dataset.slug === slug);
if (!target) return;
allProjects.forEach((p) => {
p.classList.add("unselected");
p.classList.remove("selected");
});
target.classList.add("selected");
target.classList.remove("unselected");
}
allProjects.forEach((project) => {
project.addEventListener("click", () => {
allProjects.forEach((unselected) => {
unselected.classList.add("unselected");
unselected.classList.remove("selected");
allProjects.forEach((p) => {
p.classList.add("unselected");
p.classList.remove("selected");
});
project.classList.add("selected");
project.classList.remove("unselected");
Router.setParam("project", project.dataset.slug);
});
});
@ -18,19 +34,20 @@ projectTogglers.forEach((button) => {
const parentLi = button.closest("li");
if (parentLi.classList.contains("selected")) {
event.stopPropagation();
allProjects.forEach((project) => {
project.classList.remove("unselected");
parentLi.classList.remove("selected");
});
allProjects.forEach((p) => p.classList.remove("unselected"));
parentLi.classList.remove("selected");
Router.setParam("project", null);
}
});
});
const allProjectsCloser = document.querySelector(".all-projects-closer");
allProjectsCloser.addEventListener("click", (event) => {
allProjects.forEach((project) => {
project.classList.remove("selected");
project.classList.remove("unselected");
});
allProjectsCloser.addEventListener("click", () => {
allProjects.forEach((p) => p.classList.remove("selected", "unselected"));
Router.setParam("project", null);
});
window.addEventListener("routechange", () => selectProject(Router.getParam("project")));
selectProject(Router.getParam("project"));

18
assets/js/router.js Normal file
View file

@ -0,0 +1,18 @@
const Router = {
getParam(key) {
return new URLSearchParams(window.location.search).get(key);
},
setParam(key, value) {
const url = new URL(window.location);
if (value != null) {
url.searchParams.set(key, value);
} else {
url.searchParams.delete(key);
}
history.pushState({}, "", url);
},
};
window.addEventListener("popstate", () => {
window.dispatchEvent(new CustomEvent("routechange"));
});