Index > filter menu fonctionnel + router URL + opti images
All checks were successful
Deploy / Deploy to Production (push) Successful in 2s
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:
parent
444b07ccad
commit
40366f617b
11 changed files with 139 additions and 23 deletions
|
|
@ -1,3 +1,7 @@
|
|||
.projects-index summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.projects-index li.filtered-out {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
@font-face {
|
||||
font-family: "neue-haas-grotesk-display";
|
||||
src:
|
||||
url("https://use.typekit.net/af/174ae3/00000000000000007735bb5a/31/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n5&v=3")
|
||||
src: url("https://use.typekit.net/af/174ae3/00000000000000007735bb5a/31/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n5&v=3")
|
||||
format("woff2"),
|
||||
url("https://use.typekit.net/af/174ae3/00000000000000007735bb5a/31/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n5&v=3")
|
||||
format("woff"),
|
||||
|
|
@ -17,8 +16,7 @@
|
|||
|
||||
@font-face {
|
||||
font-family: "neue-haas-grotesk-display";
|
||||
src:
|
||||
url("https://use.typekit.net/af/db1ce7/00000000000000007735bb5e/31/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=i5&v=3")
|
||||
src: url("https://use.typekit.net/af/db1ce7/00000000000000007735bb5e/31/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=i5&v=3")
|
||||
format("woff2"),
|
||||
url("https://use.typekit.net/af/db1ce7/00000000000000007735bb5e/31/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=i5&v=3")
|
||||
format("woff"),
|
||||
|
|
@ -121,6 +119,10 @@ nav {
|
|||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button.active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,6 +140,10 @@ nav {
|
|||
margin: var(--body-margin) var(--body-margin) var(--body-margin) 0;
|
||||
left: calc(var(--index-width) + var(--body-margin));
|
||||
text-align: right;
|
||||
|
||||
picture {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
|
||||
button.prev {
|
||||
|
|
@ -267,9 +273,8 @@ button.next {
|
|||
display: grid;
|
||||
grid-template-columns:
|
||||
calc(3 / 6 * var(--index-width) - var(--body-margin))
|
||||
calc(2 / 6 * var(--index-width) - var(--body-margin)) calc(
|
||||
1 / 6 * var(--index-width) - var(--body-margin)
|
||||
);
|
||||
calc(2 / 6 * var(--index-width) - var(--body-margin)) calc(1 / 6 *
|
||||
var(--index-width) - var(--body-margin));
|
||||
gap: var(--body-margin);
|
||||
pointer-events: all;
|
||||
padding-bottom: var(--line-height-S);
|
||||
|
|
|
|||
|
|
@ -118,6 +118,9 @@ nav.filter-menu {
|
|||
nav.filter-menu a {
|
||||
text-decoration: none;
|
||||
}
|
||||
nav.filter-menu button.active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* PROJECT SLIDESHOW */
|
||||
.project-slideshow {
|
||||
|
|
@ -134,6 +137,9 @@ nav.filter-menu a {
|
|||
left: calc(var(--index-width) + var(--body-margin));
|
||||
text-align: right;
|
||||
}
|
||||
.project-slideshow picture {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
button.prev {
|
||||
position: absolute;
|
||||
|
|
@ -582,6 +588,14 @@ dialog#mobile-menu-content::backdrop {
|
|||
float: left;
|
||||
}
|
||||
|
||||
.projects-index summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.projects-index li.filtered-out {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
:root {
|
||||
--slider-height: 50vh;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -2,4 +2,5 @@
|
|||
@import "src/_main.scss";
|
||||
@import "src/_nav.scss";
|
||||
@import "src/_home.scss";
|
||||
@import "src/_index.scss";
|
||||
@import "src/_mobile.scss";
|
||||
|
|
|
|||
28
assets/js/filter-menu.js
Normal file
28
assets/js/filter-menu.js
Normal 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"));
|
||||
|
|
@ -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
18
assets/js/router.js
Normal 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"));
|
||||
});
|
||||
|
|
@ -10,6 +10,7 @@ return [
|
|||
'quality' => 80,
|
||||
'srcsets' => array_merge(
|
||||
require __DIR__ . '/thumbs/home-slideshow.php',
|
||||
require __DIR__ . '/thumbs/project-slideshow.php',
|
||||
),
|
||||
],
|
||||
];
|
||||
20
site/config/thumbs/project-slideshow.php
Normal file
20
site/config/thumbs/project-slideshow.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
// Slideshow projet : 50vw desktop (> 1000px), 100vw mobile
|
||||
// Widths couvrent mobile 3x jusqu'à desktop 4K 2x
|
||||
return [
|
||||
'project-slideshow' => [
|
||||
'500w' => ['width' => 500],
|
||||
'800w' => ['width' => 800],
|
||||
'1100w' => ['width' => 1100],
|
||||
'1400w' => ['width' => 1400],
|
||||
'1800w' => ['width' => 1800],
|
||||
],
|
||||
'project-slideshow-webp' => [
|
||||
'500w' => ['width' => 500, 'format' => 'webp'],
|
||||
'800w' => ['width' => 800, 'format' => 'webp'],
|
||||
'1100w' => ['width' => 1100, 'format' => 'webp'],
|
||||
'1400w' => ['width' => 1400, 'format' => 'webp'],
|
||||
'1800w' => ['width' => 1800, 'format' => 'webp'],
|
||||
],
|
||||
];
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
<nav class="filter-menu">
|
||||
<?php foreach ($categories as $key => $category): ?>
|
||||
<a href=""><?= $category ?></a>
|
||||
<button data-filter="<?= Str::slug($category) ?>"><?= $category ?></button>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<ul class="projects-index">
|
||||
<?php foreach ($page->children()->listed()->sortBy('date', 'desc') as $project): ?>
|
||||
<li>
|
||||
<li data-category="<?= Str::slug($project->category()) ?>" data-slug="<?= $project->slug() ?>">
|
||||
<div class="fix">
|
||||
<button class="project-toggler grid">
|
||||
<span> <?= $project->shownTitle() ?> </span>
|
||||
|
|
@ -19,7 +19,13 @@
|
|||
<section class="toggle">
|
||||
<div class="project-slideshow">
|
||||
<?php foreach ($project->images() as $image): ?>
|
||||
<img <?= e($image->indexOf() === 0, 'class="active"') ?> src="<?= $image->url() ?>">
|
||||
<?php snippet('picture', [
|
||||
'file' => $image,
|
||||
'srcsetName' => 'project-slideshow',
|
||||
'sizes' => '(min-width: 1000px) 50vw, 100vw',
|
||||
'lazy' => $image->indexOf() !== 0,
|
||||
'class' => $image->indexOf() === 0 ? 'active' : null,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
<button class="prev"></button>
|
||||
<button class="next"></button>
|
||||
|
|
@ -32,7 +38,9 @@
|
|||
<button class="all-projects-closer"></button>
|
||||
</ul>
|
||||
|
||||
<script src="/assets/js/router.js"></script>
|
||||
<script src="/assets/js/project-slideshow.js"></script>
|
||||
<script src="/assets/js/project-toggle.js"></script>
|
||||
<script src="/assets/js/filter-menu.js"></script>
|
||||
<?php snippet('footer') ?>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue