Compare commits

..

3 commits

Author SHA1 Message Date
cd1ca94b11 Ajout section Explorer sous le diaporama /photo et factorisation HeroViewport
La page /photo affiche maintenant une flèche de scroll invitant à découvrir
une section de navigation avec les catégories et le fil photo en dessous du
diaporama, avec un dégradé progressif entre les deux.

Le pattern "hero viewport + scroll indicator" est factorisé dans un composant
HeroViewport réutilisable, utilisé par /photo (mode transparent), les albums
de catégories et les posts du fil photo.
2026-02-18 10:28:57 +01:00
c80e2bd386 Suppression du CSS mort de l'ancien toggle dark mode (#darkToggle, animations sun/moon) 2026-02-18 09:26:00 +01:00
98778965d2 Remplacement du div onclick par un button pour le backdrop du menu mobile (accessibilité)
Le backdrop utilisait un div avec onclick inline. Remplacé par un <button> avec aria-label et le listener déplacé dans un script du composant.
2026-02-18 08:54:28 +01:00
7 changed files with 229 additions and 94 deletions

View file

@ -7,62 +7,3 @@
.prose img { .prose img {
border-radius: 30px; border-radius: 30px;
} }
#sun {
transform: translate3d(0, 0px, 0);
}
#moon {
transform: translate3d(0, 0px, 0);
}
#darkToggle:hover #sun {
transform: translate3d(0, 10px, 0);
}
#darkToggle:hover #moon {
transform: translate3d(0, 10px, 0);
}
html.dark #darkToggle:hover .horizon {
border-color: #718096 !important;
}
.horizon .setting {
animation: 1s ease 0s 1 setting;
}
.horizon .rising {
animation: 1s ease 0s 1 rising;
}
@keyframes setting {
0% {
transform: translate3d(0, 10px, 0)
}
40% {
transform: translate3d(0, -2px, 0)
}
to {
transform: translate3d(0, 30px, 0)
}
}
@keyframes rising {
0% {
opacity: 0;
transform: translate3d(0, 30px, 0)
}
40% {
opacity: 1;
transform: translate3d(0, -2px, 0)
}
to {
opacity: 1;
transform: translate3d(0, 10, 0)
}
}

View file

@ -31,12 +31,13 @@ const currentMenus = menus[currentLang] || menus.fr;
class="flex items-center justify-between h-full max-w-5xl pl-6 pr-4 mx-auto border-b border-l-0 border-r-0 border-transparent select-none lg:border-r lg:border-l lg:rounded-b-xl" class="flex items-center justify-between h-full max-w-5xl pl-6 pr-4 mx-auto border-b border-l-0 border-r-0 border-transparent select-none lg:border-r lg:border-l lg:rounded-b-xl"
> >
<Logo /> <Logo />
<div <button
id="mobileMenuBackground" id="mobileMenuBackground"
onclick="closeMobileMenu()" type="button"
class="fixed inset-0 z-20 hidden w-screen h-screen duration-300 ease-out bg-white/90 dark:bg-neutral-950/90" aria-label="Fermer le menu"
class="fixed inset-0 z-20 hidden w-screen h-screen duration-300 ease-out bg-white/90 dark:bg-neutral-950/90 appearance-none border-0 p-0 cursor-default"
> >
</div> </button>
<nav <nav
class="relative z-30 flex flex-row-reverse justify-start w-full text-sm sm:justify-end text-neutral-500 dark:text-neutral-400 sm:flex-row" class="relative z-30 flex flex-row-reverse justify-start w-full text-sm sm:justify-end text-neutral-500 dark:text-neutral-400 sm:flex-row"
> >
@ -94,3 +95,9 @@ const currentMenus = menus[currentLang] || menus.fr;
</nav> </nav>
</div> </div>
</header> </header>
<script is:inline>
document.getElementById('mobileMenuBackground').addEventListener('click', function () {
window.closeMobileMenu();
});
</script>

View file

@ -1,6 +1,6 @@
--- ---
import { Picture } from 'astro:assets'; import { Picture } from 'astro:assets';
import ScrollIndicator from './ScrollIndicator.astro'; import HeroViewport from './HeroViewport.astro';
interface Props { interface Props {
title: string; title: string;
@ -15,6 +15,7 @@ const { title, description, date, tags, coverImage, scrollTarget = '.info-sectio
--- ---
{coverImage && ( {coverImage && (
<HeroViewport targetSelector={scrollTarget}>
<div class="hero-image"> <div class="hero-image">
<Picture src={coverImage} alt={title} widths={[800, 1200, 1920]} formats={['webp']} /> <Picture src={coverImage} alt={title} widths={[800, 1200, 1920]} formats={['webp']} />
<div class="hero-overlay"> <div class="hero-overlay">
@ -31,9 +32,9 @@ const { title, description, date, tags, coverImage, scrollTarget = '.info-sectio
</time> </time>
)} )}
</div> </div>
<ScrollIndicator targetSelector={scrollTarget} />
</div> </div>
</div> </div>
</HeroViewport>
)} )}
{tags && tags.length > 0 && ( {tags && tags.length > 0 && (
@ -47,10 +48,13 @@ const { title, description, date, tags, coverImage, scrollTarget = '.info-sectio
)} )}
<style> <style>
:global(.hero-viewport) {
--hero-height: calc(100vh - var(--header-height, 53px) - var(--footer-height, 54px));
}
.hero-image { .hero-image {
width: 100%; width: 100%;
height: calc(100vh - var(--header-height, 53px) - var(--footer-height, 54px)); height: 100%;
overflow: hidden;
position: relative; position: relative;
} }

View file

@ -1,6 +1,6 @@
--- ---
import CategoryNav from './CategoryNav.astro'; import CategoryNav from './CategoryNav.astro';
import ScrollIndicator from './ScrollIndicator.astro'; import HeroViewport from './HeroViewport.astro';
import Lightbox from './Lightbox.astro'; import Lightbox from './Lightbox.astro';
import { Picture } from 'astro:assets'; import { Picture } from 'astro:assets';
import { getEntry } from 'astro:content'; import { getEntry } from 'astro:content';
@ -49,7 +49,7 @@ const lightboxImages = images.map(img => ({
<CategoryNav currentCategory={category} opaque={true} /> <CategoryNav currentCategory={category} opaque={true} />
<!-- Image hero avec titre en overlay --> <!-- Image hero avec titre en overlay -->
<header class="hero-cover"> <HeroViewport targetSelector="#thumbnails">
<div class="hero-image"> <div class="hero-image">
{images[0] && <Picture src={images[0].src} alt={images[0].alt} widths={[800, 1200, 1920]} formats={['webp']} class="hero-bg" />} {images[0] && <Picture src={images[0].src} alt={images[0].alt} widths={[800, 1200, 1920]} formats={['webp']} class="hero-bg" />}
<div class="hero-overlay"> <div class="hero-overlay">
@ -61,10 +61,9 @@ const lightboxImages = images.map(img => ({
<p class="hero-subtitle">{categoryData.data.subtitle}</p> <p class="hero-subtitle">{categoryData.data.subtitle}</p>
)} )}
</div> </div>
<ScrollIndicator targetSelector="#thumbnails" />
</div> </div>
</div> </div>
</header> </HeroViewport>
<!-- Grille de thumbnails --> <!-- Grille de thumbnails -->
<div id="thumbnails" class="thumbnails-grid"> <div id="thumbnails" class="thumbnails-grid">
@ -144,11 +143,8 @@ document.addEventListener('scroll', onScroll);
color: #ffffff; color: #ffffff;
} }
.hero-cover { .category-container :global(.hero-viewport) {
position: relative; --hero-height: calc(100vh - var(--header-height) - var(--footer-height));
width: 100%;
height: calc(100vh - var(--header-height) - var(--footer-height));
overflow: hidden;
margin-top: var(--header-height); margin-top: var(--header-height);
} }

View file

@ -0,0 +1,150 @@
---
import { getCollection } from 'astro:content';
const photoCategories = await getCollection('photoCategories');
const sortedCategories = photoCategories.sort((a, b) => (a.data.order || 99) - (b.data.order || 99));
---
<section id="explore-section" class="explore-section">
<div class="explore-content">
<div class="explore-card">
<h2 class="explore-card-title">Catégories</h2>
<p class="explore-card-desc">Parcourir les photos par thème</p>
<ul class="category-list">
{sortedCategories.map(cat => (
<li>
<a href={`/photo/albums/${cat.id}`} class="category-link">
{cat.data.title}
</a>
</li>
))}
</ul>
</div>
<div class="explore-card">
<h2 class="explore-card-title">Fil Photo</h2>
<p class="explore-card-desc">Parcourir les séries chronologiques, reportages et histoires en images</p>
<a href="/photo/blog" class="explore-cta">
Voir le fil
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="12" x2="19" y2="12"/>
<polyline points="12 5 19 12 12 19"/>
</svg>
</a>
</div>
</div>
</section>
<style>
.explore-section {
position: relative;
background: #000;
z-index: 20;
padding: 4rem 2rem;
padding-top: 8rem;
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
}
.explore-section::before {
content: '';
position: absolute;
top: -30vh;
left: 0;
right: 0;
height: 30vh;
background: linear-gradient(to bottom, transparent, #000);
pointer-events: none;
}
.explore-content {
display: flex;
gap: 3rem;
max-width: 900px;
width: 100%;
}
.explore-card {
flex: 1;
padding: 2rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
}
.explore-card-title {
margin: 0 0 0.5rem;
font-size: 1.5rem;
font-weight: 700;
color: white;
}
.explore-card-desc {
margin: 0 0 1.5rem;
font-size: 0.95rem;
color: rgba(255, 255, 255, 0.6);
line-height: 1.5;
}
.category-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.category-link {
display: inline-block;
padding: 0.4rem 0.9rem;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 20px;
color: rgba(255, 255, 255, 0.8);
text-decoration: none;
font-size: 0.9rem;
transition: background 0.2s ease, color 0.2s ease;
}
.category-link:hover {
background: rgba(255, 255, 255, 0.15);
color: white;
}
.explore-cta {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.6rem 1.2rem;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: white;
text-decoration: none;
font-size: 0.95rem;
font-weight: 500;
transition: background 0.2s ease;
}
.explore-cta:hover {
background: rgba(255, 255, 255, 0.2);
}
@media (max-width: 768px) {
.explore-section {
padding: 3rem 1.25rem;
}
.explore-content {
flex-direction: column;
gap: 2rem;
}
.explore-card {
padding: 1.5rem;
}
}
</style>

View file

@ -0,0 +1,33 @@
---
import ScrollIndicator from './ScrollIndicator.astro';
interface Props {
targetSelector?: string;
transparent?: boolean;
}
const { targetSelector = '#content', transparent = false } = Astro.props;
---
<div class:list={['hero-viewport', { transparent }]}>
<slot />
<ScrollIndicator targetSelector={targetSelector} />
</div>
<style>
.hero-viewport {
position: relative;
height: var(--hero-height, 100vh);
overflow: hidden;
}
.hero-viewport.transparent {
z-index: 25;
pointer-events: none;
}
.hero-viewport.transparent :global(.scroll-indicator) {
pointer-events: auto;
bottom: 100px;
}
</style>

View file

@ -1,10 +1,14 @@
--- ---
import PhotoLayout from '../layouts/PhotoLayout.astro'; import PhotoLayout from '../layouts/PhotoLayout.astro';
import PhotoGallery from '../components/photo/PhotoGallery.astro'; import PhotoGallery from '../components/photo/PhotoGallery.astro';
import HeroViewport from '../components/photo/HeroViewport.astro';
import ExploreSection from '../components/photo/ExploreSection.astro';
const title = "Galerie Photo - Jalil Arfaoui"; const title = "Galerie Photo - Jalil Arfaoui";
--- ---
<PhotoLayout title={title}> <PhotoLayout title={title} enableScroll={true}>
<PhotoGallery category="all" /> <PhotoGallery category="all" />
<HeroViewport targetSelector="#explore-section" transparent />
<ExploreSection />
</PhotoLayout> </PhotoLayout>