/* ══════════════════════════════════════════════════════════════════════════
   Maël Fabre Photographie — styles applicatifs.

   Reprend le <style> du prototype (lignes 15-73) à l'identique pour la partie
   layout/animations, et ajoute les classes qui, dans le prototype, étaient
   générées en chaînes de style inline par le JS (chips de filtre, états
   actifs, boutons de photo). Les tokens viennent de organic.css, chargé avant.
   ══════════════════════════════════════════════════════════════════════ */

html { scroll-behavior: smooth; }

body {
  margin: 0;
  text-wrap: pretty;
  overflow-x: clip;
  background: var(--color-bg);
}

/* ═══════════ Fond technique « studio » ═══════════
   UNE seule couche fixe derrière tout le contenu (z-index négatif) : grille
   d'ingénieur (fine + large), courbes de niveau facon topographie, halo
   terracotta et vignettage — tout empilé en `background-image`.

   PERF : l'élément est promu sur sa propre couche GPU (`translateZ(0)`) et ne
   porte NI `mask-image` NI animation. C'est décisif : un élément `fixed`
   plein écran avec un masque n'est pas compositable, donc le navigateur le
   RE-PEINT à chaque frame de défilement (d'où les ~5 fps). Promu et sans
   masque, il est peint une fois puis simplement recomposé : le scroll ne le
   touche plus. Le fondu que faisait le masque est désormais intégré au halo.
   Purement décoratif : pointer-events coupés, invisible au lecteur d'écran. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  transform: translateZ(0); /* → couche GPU dédiée, plus de repaint au scroll */
  background-image:
    /* halo terracotta en haut + vignettage : peints d'abord (dessous) */
    radial-gradient(90% 50% at 50% -6%, color-mix(in srgb, var(--color-accent) 11%, transparent), transparent 68%),
    radial-gradient(70% 55% at 88% 106%, color-mix(in srgb, var(--color-accent-2) 7%, transparent), transparent 70%),
    radial-gradient(135% 125% at 50% 38%, transparent 52%, rgba(0, 0, 0, .5) 100%),
    /* courbes de niveau — un seul foyer, moins de calcul */
    repeating-radial-gradient(circle at 84% 10%,
      transparent 0 64px,
      color-mix(in srgb, var(--color-accent) 5%, transparent) 64px 65px,
      transparent 65px 130px),
    /* grille large + fine */
    linear-gradient(color-mix(in srgb, var(--color-text) 4%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--color-text) 4%, transparent) 1px, transparent 1px),
    linear-gradient(color-mix(in srgb, var(--color-text) 2.5%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--color-text) 2.5%, transparent) 1px, transparent 1px);
  background-size:
    100% 100%, 100% 100%, 100% 100%,
    100% 100%,
    168px 168px, 168px 168px,
    34px 34px, 34px 34px;
}

.wrap {
  max-width: 1240px;
  margin: 0 auto;
  /* Gouttière latérale forcée en `!important` : de nombreuses sections posent
     un padding EN LIGNE en raccourci (`style="padding:40px 0 20px"`) dont les
     côtés valent 0 — ce qui écrasait la gouttière. Sur desktop le centrage par
     max-width masquait le problème ; sur mobile (pleine largeur) tout collait
     aux bords. Le `!important` (auteur) l'emporte sur le style en ligne normal
     et rétablit la marge partout, sans toucher aux 12+ gabarits concernés.
     Le padding vertical des sections, lui, reste piloté par leur style en ligne. */
  padding-left: clamp(18px, 5vw, 104px) !important;
  padding-right: clamp(18px, 5vw, 104px) !important;
}

.page-shell { min-height: 100vh; display: flex; flex-direction: column; }
.page-main  { flex: 1; }

/* Lien d'évitement — visible uniquement au clavier. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--color-accent); color: var(--color-bg);
  padding: 10px 18px; border-radius: 0 0 999px 0; font-size: 14px;
}
.skip-link:focus { left: 0; }

/* — barre de navigation — */
.navbar {
  position: sticky; top: 0; z-index: 40;
  /* Fond quasi opaque plutôt qu'un `backdrop-filter: blur` : le flou d'arrière-
     plan sur une barre COLLÉE se recalcule à chaque frame de scroll — une des
     premières causes de saccade. Sur sombre, un fond dense rend aussi bien. */
  background: color-mix(in srgb, var(--color-bg) 96%, transparent);
  border-bottom: 1px solid var(--color-divider);
}
.nav-inner {
  display: flex; align-items: center; gap: clamp(10px, 2vw, 30px);
  padding-top: max(15px, env(safe-area-inset-top)); padding-bottom: 15px;
}
.nav-links   { display: flex; align-items: center; gap: clamp(12px, 1.8vw, 28px); }
.nav-actions { display: flex; align-items: center; gap: 10px; }
.hamburger   { display: none; }

.brand {
  display: flex; flex-direction: column; line-height: 1;
  margin-right: auto; text-align: left; text-decoration: none; color: inherit;
}
.brand-name { font-family: var(--font-heading); font-size: 22px; letter-spacing: -.01em; }
.brand-role {
  font-size: 10px; letter-spacing: .28em; text-transform: uppercase;
  color: var(--color-accent-700); margin-top: 3px;
}

.nav-link { font-size: 14px; text-decoration: none; color: inherit; }
.nav-link:hover { color: var(--color-accent); }
/* L'onglet courant est marqué par aria-current, pas par une classe : une
   seule source de vérité, lisible par les lecteurs d'écran comme par le CSS. */
.nav-link[aria-current="page"] { color: var(--color-accent); font-weight: 700; }

.lang-toggle {
  font-size: 12px; font-weight: 700; letter-spacing: .06em;
  border: 1px solid var(--color-divider); border-radius: 999px;
  padding: 6px 11px; text-decoration: none; color: inherit;
}
.lang-toggle:hover { border-color: var(--color-accent); color: var(--color-accent); }

.cart-count {
  margin-left: 2px; background: var(--color-bg); color: var(--color-accent-700);
  border-radius: 999px; font-size: 11px; font-weight: 700;
  min-width: 18px; height: 18px;
  display: inline-flex; align-items: center; justify-content: center; padding: 0 5px;
}
/* Un `display` explicite l'emporte sur la règle par défaut de [hidden] :
   sans cette ligne, le badge affiche « 0 » sur un panier vide. */
.cart-count[hidden] { display: none; }

/* — menu plein écran — */
.menu-overlay {
  position: fixed; inset: 0; z-index: 80; background: var(--color-bg);
  display: flex; flex-direction: column; overflow: hidden;
  animation: fade-in .28s ease;
}
.menu-overlay[hidden] { display: none; }
.menu-blob { position: absolute; border-radius: 50%; z-index: -1; }
.menu-blob-1 { top: -130px; right: -120px; width: 360px; height: 360px; background: var(--color-accent-2-100); }
.menu-blob-2 { bottom: -160px; left: -140px; width: 320px; height: 320px; background: var(--color-accent-100); }

.menu-head {
  display: flex; align-items: center; justify-content: space-between;
  padding-top: max(15px, env(safe-area-inset-top)); padding-bottom: 15px;
}
.menu-nav { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 4px; }
.menu-link {
  font-family: var(--font-heading); font-size: clamp(40px, 10vw, 68px);
  line-height: 1.02; letter-spacing: -.025em; color: var(--color-text);
  background: none; border: 0; cursor: pointer; text-align: left; text-decoration: none;
  padding: 8px 0; display: flex; align-items: center; justify-content: space-between; gap: 18px;
  opacity: 0; animation: menu-item-in .55s cubic-bezier(.2,.7,.2,1) forwards;
}
.menu-link:hover { color: var(--color-accent); }
.menu-link .arw { width: .5em; height: .5em; opacity: .3; transition: transform .3s, opacity .3s; }
.menu-link:hover .arw { opacity: 1; transform: translate(5px, -5px); }

.menu-foot {
  padding-bottom: max(34px, calc(env(safe-area-inset-bottom) + 18px));
  display: flex; flex-direction: column; gap: 20px;
}
.menu-contact {
  display: flex; flex-direction: column; gap: 8px; font-size: 14px;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  border-top: 1px solid var(--color-divider); padding-top: 20px;
}
.menu-contact span { display: flex; align-items: center; gap: 8px; }

@keyframes menu-item-in { from { opacity: 0; transform: translateY(26px); } to { opacity: 1; transform: none; } }
@keyframes menu-item-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(-14px); } }
@keyframes menu-out { from { opacity: 1; } to { opacity: 0; } }
.menu-overlay.closing { animation: menu-out .3s cubic-bezier(.4,0,1,1) forwards; }
.menu-overlay.closing .menu-link { animation: menu-item-out .24s ease forwards !important; }

/* — panier tiroir — */
.cart-portal { position: fixed; inset: 0; z-index: 60; display: flex; justify-content: flex-end; }
.cart-portal[hidden] { display: none; }
.cart-backdrop {
  position: absolute; inset: 0; border: 0; padding: 0; cursor: pointer;
  background: color-mix(in srgb, var(--color-neutral-900) 45%, transparent);
  animation: fade-in .25s ease;
}
.cart-aside {
  position: relative; width: min(420px, 100%); background: var(--color-bg);
  height: 100%; display: flex; flex-direction: column; box-shadow: var(--shadow-lg);
  animation: drawer-in .34s cubic-bezier(.2,.7,.2,1);
}
.cart-portal.closing .cart-backdrop { animation: fade-out .3s ease forwards; }
.cart-portal.closing .cart-aside    { animation: drawer-out .32s cubic-bezier(.4,0,1,1) forwards; }

.cart-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: max(22px, env(safe-area-inset-top)) 24px 22px;
  border-bottom: 1px solid var(--color-divider);
}
.cart-head h2 { font-family: var(--font-heading); font-size: 24px; margin: 0; }
.cart-body { flex: 1; overflow-y: auto; padding: 16px 24px; display: flex; flex-direction: column; gap: 12px; }
/* Sans ça, les enfants d'un conteneur flex en colonne se COMPRESSENT pour tenir
   dans la hauteur visible (flex-shrink:1 par défaut) : un pack de 10 se faisait
   écraser puis rogner par son `overflow:hidden`, et le panier ne défilait plus.
   En bloquant le rétrécissement, le contenu déborde → `overflow-y:auto` défile. */
.cart-body > * { flex: none; }
.cart-item { display: flex; gap: 12px; align-items: center; }
.cart-item figure { margin: 0; width: 52px; height: 60px; border-radius: 12px; overflow: hidden; flex: none; }
.cart-foot {
  padding: 20px 24px max(20px, calc(env(safe-area-inset-bottom) + 8px));
  border-top: 1px solid var(--color-divider); background: var(--color-surface);
}
.cart-row { display: flex; justify-content: space-between; margin-bottom: 6px; font-size: 14px; }
.cart-total { display: flex; justify-content: space-between; margin-bottom: 16px; font-family: var(--font-heading); font-size: 22px; }
.cart-total span:last-child { color: var(--color-accent); }
.cart-empty { flex: 1; display: grid; place-content: center; text-align: center; padding: 40px; gap: 12px; }

@keyframes drawer-in  { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes drawer-out { from { transform: translateX(0); } to { transform: translateX(100%); } }
@keyframes fade-in    { from { opacity: 0; } to { opacity: 1; } }
@keyframes fade-out   { from { opacity: 1; } to { opacity: 0; } }

/* — accueil : éventail de photos qui dérive — */
.hero-fan { overflow-x: clip; overflow-y: visible; padding: 40px 0 64px; }
.hero-track {
  display: flex; gap: clamp(10px, 1.4vw, 20px); width: max-content;
  align-items: flex-end; margin: 0 auto;
  /* Plus d'animation CSS continue : la bande avance d'une carte a la fois,
     pilotee par app.js. Un defilement d'un trait donne une impression de
     bande passante, alors qu'un pas cadence laisse le temps de regarder
     chaque photo. */
  will-change: transform;
}
.hero-track.is-stepping {
  /* Courbe a leger depassement : la bande arrive, depasse d'un cheveu, puis
     revient — c'est ce rebond qui donne la sensation d'inertie plutot que
     de translation mecanique. */
  transition: transform .78s cubic-bezier(.34, 1.18, .42, 1);
}

/* ── Couche 1 : l'elan ──────────────────────────────────────────────────
   Une carte posee sur un plateau qu'on tire vers la gauche traine derriere
   lui, puis le rattrape en depassant legerement. C'est ce retard-la qu'on
   simule : pendant le pas la carte se decale vers la DROITE (elle est en
   retard sur la bande), et quand la bande s'arrete elle revient a sa place
   avec un depassement.

   Duree et retard sont propres a chaque carte (variables posees par la vue) :
   sinon les dix-huit cartes oscilleraient a l'unisson, ce qui ressemble a une
   secousse de la page entiere plutot qu'a de l'inertie. */
.hero-slot {
  flex: none;
  transition: transform var(--elanDur, .8s)
              cubic-bezier(.16, 1.68, .38, 1) var(--elanDelay, 0s);
}
/* Classe distincte de `is-stepping`, et retiree AVANT elle (au tiers du
   glissement, cf. app.js) : la traine doit se resorber pendant que la bande
   freine encore. Accrochee au meme etat que le glissement, elle ne partait
   qu'une fois la bande immobile et se lisait comme un recalage. */
.hero-track.is-lagging .hero-slot {
  transform: translateX(var(--elan, 10px));
  /* Le retard s'installe vite, il se resorbe lentement : l'inverse donnerait
     un flottement mou, sans depart franc. */
  transition: transform .22s cubic-bezier(.4, 0, .7, .3);
}

/* ── Couche 2 : le flottement ───────────────────────────────────────────
   Un leger va-et-vient permanent, pour que la bande ne soit jamais tout a
   fait immobile entre deux pas. Les `animation-delay` negatifs demarrent
   chaque carte a un point different du cycle : sans eux, les huit
   temperaments partiraient malgre tout tous ensemble au chargement. */
.hero-drift {
  height: 100%;
  animation: hero-float var(--flotDur, 5s) var(--flotDelay, 0s)
             ease-in-out infinite alternate;
}
@keyframes hero-float {
  from { transform: translateY(calc(var(--flotY, 8px) * -.55))
                    rotate(calc(var(--flotRot, 1deg) * -1)); }
  to   { transform: translateY(var(--flotY, 8px)) rotate(var(--flotRot, 1deg)); }
}

/* ── Couche 3 : l'eventail et le survol ─────────────────────────────── */
.hero-frame {
  height: 100%; border-radius: 24px; overflow: hidden; margin: 0;
  box-shadow: var(--shadow-md);
  transform: var(--fan, none);
  transition: transform .42s cubic-bezier(.34, 1.32, .42, 1),
              opacity .28s ease, box-shadow .28s ease, filter .28s ease;
}
.hero-frame a { display: block; height: 100%; }
.hero-frame img { width: 100%; height: 100%; object-fit: cover; }

/* Au survol de la bande, les autres cartes s'effacent : c'est le retrait des
   voisines qui fait ressortir celle qu'on regarde, plus que son propre
   agrandissement. */
.hero-track:hover .hero-frame { opacity: .42; }

/* Specificite superieure a la regle ci-dessus (0,3,1 contre 0,2,1), sans
   recourir a !important. */
.hero-track:hover .hero-slot:hover .hero-frame {
  opacity: 1;
  /* La carte se redresse : elle quitte l'eventail et se presente de face. */
  transform: translateY(-18px) scale(1.09);
  box-shadow: var(--shadow-lg);
}
/* La carte visee cesse de flotter : on ne vise pas une cible qui bouge. */
.hero-slot:hover .hero-drift { animation-play-state: paused; }
.hero-slot:hover { position: relative; z-index: 3; }

/* ───────── Bande décorative en trois rangées ─────────
   Trois lignes de photos qui coulissent à l'horizontale au fil du défilement
   vertical, une sur deux en sens inverse. Le glissement est piloté en JS
   (translateX selon la progression de la section dans la fenêtre) ; ici on ne
   pose que le décor et les garde-fous. */
.showcase {
  /* Pleine largeur, hors de la gouttière `.wrap` : la bande respire d'un bord
     à l'autre comme sur la maquette. */
  margin: clamp(40px, 7vh, 90px) 0;
  display: flex; flex-direction: column;
  gap: clamp(14px, 1.6vw, 22px);
  overflow: hidden;
}
.showcase-row { overflow: hidden; }
.showcase-track {
  display: flex;
  gap: clamp(14px, 1.6vw, 22px);
  width: max-content;
  will-change: transform;
  /* Position de repos avant que le JS ne prenne la main (et état final sous
     `prefers-reduced-motion`) : légèrement décalé pour ne pas coller au bord. */
  transform: translate3d(-8%, 0, 0);
}
.showcase-card {
  margin: 0;
  flex: 0 0 auto;
  width: clamp(220px, 26vw, 380px);
  aspect-ratio: 16 / 10;
  border-radius: 20px;
  overflow: hidden;
  background: var(--color-surface-2, #1a1a1a);
  box-shadow: var(--shadow-md, 0 8px 30px rgba(0, 0, 0, .35));
}
.showcase-card a { display: block; width: 100%; height: 100%; }
.showcase-card img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .5s cubic-bezier(.22, 1, .36, 1);
}
.showcase-card:hover img { transform: scale(1.05); }

@media (max-width: 640px) {
  .showcase-card { width: clamp(160px, 62vw, 240px); border-radius: 16px; }
}

.hero-kicker {
  display: inline-block; font-size: 12px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--color-accent-700); font-weight: 700; margin-bottom: 22px;
}
.hero-title {
  font-size: clamp(30px, 6.4vw, 88px); line-height: 1.04; margin: 0 auto; letter-spacing: -.02em;
}
/* Le brief impose le titre sur deux lignes (§4.1). nowrap garantit que chaque
   ligne reste entière ; à l'étroit, la taille fluide fait le travail. */
.hero-title span { display: block; white-space: nowrap; }
.hero-sub { font-size: clamp(16px, 1.6vw, 19px); max-width: 52ch; margin: 26px auto 0; line-height: 1.55; }

.section-kicker {
  font-size: 12px; letter-spacing: .2em; text-transform: uppercase;
  color: var(--color-accent-700); font-weight: 700;
}

/* — grilles — */
.grid-steps  { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
.grid-latest { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 14px; }

/* — Carrousel « dernier événement » — ~4 photos qui défilent doucement, dans la
     largeur de la page (le strip est dans .wrap, comme le reste). La piste rend
     la série DEUX fois et anime translateX 0 → -50 % → boucle sans raccord. */
.latest-strip { overflow: hidden; }
.latest-track {
  display: flex; gap: 14px; width: max-content;
  will-change: transform;
  /* Défilé continu = repli SANS JavaScript. Avec JS (ux.js), on passe en pas à
     pas toutes les 2,5 s avec inertie (l'animation CSS est alors coupée). */
  animation: latest-marquee 46s linear infinite;
}
.latest-strip:hover .latest-track,
.latest-track:focus-within { animation-play-state: paused; } /* pause pour cliquer */
.latest-card {
  margin: 0; flex: 0 0 clamp(190px, 18vw, 264px);
  aspect-ratio: 4 / 5; border-radius: 18px; overflow: hidden;
  background: var(--color-surface);
  border: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.latest-card a { display: block; width: 100%; height: 100%; }
.latest-card img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .5s cubic-bezier(.22, 1, .36, 1);
}
.latest-card:hover img { transform: scale(1.05); }
@keyframes latest-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

@media (prefers-reduced-motion: reduce) {
  /* Pas de défilé auto : on rend le strip parcourable à la main et on retire le
     doublon décoratif pour ne pas afficher deux fois chaque photo. */
  .latest-track { animation: none; width: auto; }
  .latest-strip { overflow-x: auto; }
  .latest-track > [aria-hidden] { display: none; }
}
.grid-events { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
/* Galerie de photos — VRAIE grille dense, façon planche-contact. Un événement
   contient couramment 100+ photos : on doit en voir beaucoup d'un coup et
   défiler naturellement, jamais une par une. 2 colonnes sur téléphone → 5 sur
   grand écran, petit interstice. Les actions (prix / panier) passent en
   surimpression sur l'image (voir .photo-card-foot) pour ne pas rallonger les
   cases. */
.grid-photos { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
@media (min-width: 560px)  { .grid-photos { grid-template-columns: repeat(3, 1fr); gap: 10px; } }
@media (min-width: 900px)  { .grid-photos { grid-template-columns: repeat(4, 1fr); gap: 12px; } }
@media (min-width: 1280px) { .grid-photos { grid-template-columns: repeat(5, 1fr); } }

.section-head {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: 20px; flex-wrap: wrap; margin-bottom: 26px;
}

/* — carte d'événement — */
.event-card {
  text-align: left; background: var(--color-surface); border-radius: 24px;
  overflow: hidden; box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column; text-decoration: none; color: inherit;
}
.event-card figure { margin: 0; aspect-ratio: 3/2; overflow: hidden; position: relative; }
.event-card figure img { width: 100%; height: 100%; object-fit: cover; }
.event-card-cat {
  position: absolute; top: 12px; left: 12px;
  background: color-mix(in srgb, #000 62%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 14%, transparent);
  color: var(--color-accent-800); font-size: 11px; font-weight: 700;
  letter-spacing: .04em; padding: 4px 11px; border-radius: 999px;
}
.event-card-body { padding: 16px 18px 18px; }
.event-card-body h3 { font-family: var(--font-heading); font-size: 20px; margin: 0 0 6px; line-height: 1.15; }
.event-meta { display: flex; align-items: center; gap: 12px; font-size: 13px; flex-wrap: wrap; }
.event-meta span { display: flex; align-items: center; gap: 5px; }
.event-card-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 14px; }
.event-card-cta { color: var(--color-accent); font-size: 14px; font-weight: 700; display: flex; align-items: center; gap: 4px; }

/* — chips de filtre —
   Dans le prototype, l'état actif était une chaîne de style construite en JS.
   Ici c'est une vraie classe, ce qui rend l'état stylable et testable. */
.chip {
  display: inline-flex; align-items: center; font-size: 12px; font-weight: 600;
  padding: 7px 15px; border-radius: 999px; cursor: pointer; text-decoration: none;
  border: 1px solid var(--color-divider); background: transparent; color: var(--color-text);
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.chip:hover { border-color: var(--color-accent); color: var(--color-accent); }
.chip.is-active { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-bg); }
.chip.is-active:hover { background: var(--color-accent-600); color: var(--color-bg); }
.chip.is-soon { opacity: .55; }
.chip-soon-label { opacity: .6; margin-left: 5px; }

.chip-sm {
  font-size: 12px; padding: 5px 12px; border-radius: 999px; text-decoration: none;
  color: color-mix(in srgb, var(--color-text) 65%, transparent); background: transparent; border: 0; cursor: pointer;
}
.chip-sm:hover { color: var(--color-accent); }
.chip-sm.is-active { background: var(--color-surface); color: var(--color-text); font-weight: 700; }

.filter-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* — détail d'un événement — */
.pricing-bar {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  background: var(--color-surface); border-radius: 20px;
  padding: 16px 22px; margin: 26px 0 24px;
}
.pricing-bar .note { font-size: 12px; margin-left: auto; display: flex; align-items: center; gap: 6px; }

.photo-card {
  position: relative; border-radius: 16px; overflow: hidden;
  box-shadow: var(--shadow-sm); background: var(--color-surface);
}
.photo-card figure { margin: 0; aspect-ratio: 4/5; overflow: hidden; position: relative; }
.photo-card figure img { width: 100%; height: 100%; object-fit: cover; }

.photo-card-foot { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; }

/* ── Galerie « planche-contact » : actions en surimpression ───────────────────
   Réservé aux grilles portant `.gallery` (galerie d'un événement, « Mes
   photos ») — PAS la liste des sections privées, dont le pied porte le nom de
   l'événement et doit rester lisible sous l'image.

   Les actions (prix / panier / téléchargement) passent en bas de l'image sur un
   dégradé sombre : les cases restent purement visuelles, la planche respire. */
.gallery .photo-card-foot {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 30px 9px 9px; gap: 6px;
  background: linear-gradient(to top, rgba(0,0,0,.85), rgba(0,0,0,.4) 55%, transparent);
  color: #fff;
  /* Le dégradé laisse passer le clic vers l'image (ouvre la visionneuse) ;
     seuls les vrais contrôles captent le clic. */
  pointer-events: none;
}
.gallery .photo-card-foot button,
.gallery .photo-card-foot a,
.gallery .photo-card-foot form { pointer-events: auto; }
.gallery .photo-card-foot > span:first-child { color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.7); font-size: 13px; }
.gallery .photo-card-foot .text-muted { color: rgba(255,255,255,.82); }
.gallery .photo-card-foot .btn { padding: 7px 10px; font-size: 12px; }
/* Bouton compact dans la grille : icône seule (libellé conservé pour les
   lecteurs d'écran), sinon « Ajouter au panier » déborde une case. */
.gallery .photo-card-foot .lbl {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* Desktop : actions révélées au survol (planche épurée). Mobile : toujours là. */
@media (hover: hover) {
  .gallery .photo-card-foot { opacity: 0; transform: translateY(8px); transition: opacity .2s ease, transform .2s ease; }
  .gallery .photo-card:hover .photo-card-foot,
  .gallery .photo-card:focus-within .photo-card-foot { opacity: 1; transform: none; }
}
.photo-num {
  position: absolute; top: 10px; left: 10px;
  background: color-mix(in srgb, #000 62%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 12%, transparent);
  font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 999px;
  color: var(--color-neutral-200);
}

/* Calque anti-glisser (§2.2).
   ⚠️ COSMÉTIQUE UNIQUEMENT. Ce n'est PAS ce qui protège les images : la vraie
   protection est que le fichier servi est déjà filigrané et dégradé (§2.1).
   Ce calque ne fait qu'ajouter un frein au clic droit / glisser-déposer. */
.photo-guard { position: absolute; inset: 0; pointer-events: auto; }

.protected-img {
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
}

/* — pied de page — */
.site-footer { background: var(--color-neutral-900); color: var(--color-neutral-200); margin-top: 20px; }
.footer-grid { padding: 48px 0 30px; display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 32px; }
/* Titres de colonnes du pied de page : ce sont des <h2> (pour ne pas sauter de
   niveau de titre h1→h6, cf. audit Lighthouse), stylés en petites capitales. */
.footer-heading {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 13px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--color-neutral-300); margin: 0 0 12px;
}
.footer-brand { font-family: var(--font-heading); font-size: 22px; color: var(--color-text); }
.footer-about { font-size: 14px; line-height: 1.6; margin: 10px 0 0; max-width: 38ch; color: var(--color-neutral-300); }
.footer-links { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.footer-links a { font-size: 14px; color: var(--color-neutral-200); text-decoration: none; }
.footer-links a:hover { color: var(--color-accent); }
.footer-links a.muted { color: var(--color-neutral-400); display: flex; align-items: center; gap: 6px; }
.footer-contact { display: flex; flex-direction: column; gap: 8px; font-size: 14px; color: var(--color-neutral-300); }
.footer-contact span { display: flex; align-items: center; gap: 8px; }
.footer-legal {
  padding: 16px 0 26px; border-top: 1px solid var(--color-neutral-800);
  /* neutral-400 et non 500 : contraste suffisant (AA) sur le fond du pied de
     page — le 500 était en dessous du seuil (audit Lighthouse). */
  font-size: 12px; color: var(--color-neutral-400);
}

/* — utilitaires — */
.lift { transition: transform .35s cubic-bezier(.2,.7,.2,1), box-shadow .35s ease; }
.lift:hover { transform: translateY(-6px); box-shadow: var(--shadow-lg); }
.u-link { background: none; border: 0; cursor: pointer; font: inherit; color: inherit; padding: 0; }
.icon { width: 1em; height: 1em; display: inline-block; vertical-align: -.125em; flex: none; }
.stack { display: flex; flex-direction: column; }
.empty-state {
  text-align: center; padding: 60px 20px;
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--color-neutral-300); border-radius: 999px; }

/* — apparition au défilement —
   Le prototype utilisait `animation-timeline: view()`, non supporté par
   Safari ni Firefox : l'effet y disparaissait silencieusement. Le §7 autorise
   explicitement IntersectionObserver, qui marche partout — c'est lui qui pose
   la classe .is-revealed (voir app.js). */
@media (prefers-reduced-motion: no-preference) {
  .reveal { opacity: 0; transform: translateY(32px); transition: opacity .6s ease, transform .6s cubic-bezier(.2,.7,.2,1); }
  .reveal.is-revealed { opacity: 1; transform: none; }
}
/* Sans JS, rien ne doit rester invisible. */
.no-js .reveal { opacity: 1; transform: none; }

/* Quand Motion pilote (motion-fx.js pose `.motion-on` sur <html>), on lui laisse
   le `transform` : on coupe le survol CSS concurrent et la transition CSS sur
   transform, sinon les deux animations se battent sur la même propriété.
   Le box-shadow, lui, reste animé en CSS au survol. */
.motion-on .lift { transition-property: box-shadow; }
.motion-on .lift:hover { transform: none; }
/* Bouton magnétique : promu sur sa couche GPU au survol, pour un déplacement
   lisse (le mouvement lui-même est piloté en JS par interpolation rAF). */
.btn-primary:hover { will-change: transform; }

/* ── Motion tactile (mobile) ──────────────────────────────────────────────────
   Sur un écran tactile il n'y a ni curseur ni survol : le relief 3D, le halo et
   l'aimant n'existent pas. On rend donc le toucher vivant par un enfoncement au
   tap (retour immédiat, universel). Les révélations au ressort et le défilement
   horizontal de la vitrine, eux, marchent déjà au doigt. */
@media (hover: none) {
  .event-card:active, .photo-card:active, .grid-latest > figure:active,
  .showcase-card:active, .btn:active, .chip:active, .stat-tile:active,
  .lang-toggle:active {
    transform: scale(.97);
    transition: transform .12s ease;
  }
}
/* Plus de rectangle gris au tap sur iOS/Android. */
a, button, .btn, .chip { -webkit-tap-highlight-color: transparent; }
/* Le relief 3D (ux.js) pilote lui-même le transform des cartes : on coupe les
   :hover CSS concurrents pour ces cartes. */
.tilt-on .lift:hover { transform: none; }

/* — Séparateur « ou » + bouton Google (auth) — */
.or-sep { display: flex; align-items: center; gap: 12px; margin: 18px 0; }
.or-sep > span:not(.text-muted) { flex: 1; height: 1px; background: var(--color-divider); }
.or-sep .text-muted { font-size: 12px; }
.google-btn { gap: 10px; padding: 12px; font-size: 14.5px; font-weight: 600; }
.google-btn .google-g { flex: none; }

/* — Bandeau d'appel à collaborer (accueil) — */
.cta-band {
  text-align: center;
  padding: clamp(34px, 5vw, 60px);
  border-radius: 32px;
  background:
    radial-gradient(120% 130% at 50% -20%, color-mix(in srgb, var(--color-accent) 20%, transparent), transparent 70%),
    var(--color-surface);
  border: 1px solid color-mix(in srgb, var(--color-accent) 28%, transparent);
}
.cta-band h2 { font-size: clamp(28px, 4vw, 44px); margin: 8px 0 14px; letter-spacing: -.02em; }
.cta-band p {
  color: color-mix(in srgb, var(--color-text) 70%, transparent);
  font-size: 17px; line-height: 1.6; max-width: 54ch; margin: 0 auto 24px;
}
.cta-band .cta-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }

/* ═══════════ Nouveaux éléments d'UX/UI ═══════════ */

/* — Chargement interne de la galerie (filtrage AJAX) — même diaphragme que
     l'écran de transition, en petit, posé sur la zone de résultats. */
.gallery-loader {
  position: absolute; inset: 0; z-index: 5; display: grid; place-items: center;
  background: color-mix(in srgb, var(--color-bg) 64%, transparent);
  opacity: 0; transition: opacity .18s ease; pointer-events: none;
}
.gallery-loader.is-on { opacity: 1; }
.aperture-spin {
  width: 42px; height: 42px; color: var(--color-accent); display: inline-block;
  animation: aperture-rot 2.4s cubic-bezier(.5, .1, .5, .9) infinite;
}
.aperture-spin svg { width: 100%; height: 100%; display: block; }
@keyframes aperture-rot { to { transform: rotate(360deg); } }
/* Apparition des résultats fraîchement filtrés. */
.gallery-in { animation: gallery-in .45s cubic-bezier(.22, 1, .36, 1) both; }
@keyframes gallery-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .aperture-spin { animation: none; }
  .gallery-in { animation: none; }
}

/* — Barre de progression de lecture — un trait fin en haut, qui se remplit au
     défilement. Piloté par scaleX (composité GPU). */
.read-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 60;
  transform: scaleX(0); transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--color-accent), var(--color-accent-2));
  pointer-events: none;
}

/* — Halo lumineux qui suit le curseur — nappe de lumière terracotta posée sur
     le fond sombre, DERRIÈRE le contenu (les cartes flottent au-dessus). */
.cursor-glow {
  position: fixed; top: 0; left: 0; width: 520px; height: 520px; z-index: -1;
  margin: -260px 0 0 -260px; border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, color-mix(in srgb, var(--color-accent) 22%, transparent), transparent 62%);
  mix-blend-mode: screen; opacity: 0;
  transition: opacity .4s ease;
  will-change: transform;
}

/* — Bandeau « en chiffres » — tuiles de surface avec liseré, chiffre en
     display, incrémenté au défilement. */
.stats-band {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
}
.stat-tile {
  display: flex; flex-direction: column; gap: 6px;
  padding: 22px 22px 20px;
  background: color-mix(in srgb, var(--color-surface) 70%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
  border-radius: 20px;
  position: relative; overflow: hidden;
}
/* Filet lumineux en haut de tuile, comme les cartes. */
.stat-tile::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--color-text) 10%, transparent);
}
.stat-ic { color: var(--color-accent); }
.stat-ic .icon { width: 22px; height: 22px; }
.stat-val {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: clamp(30px, 4vw, 44px); line-height: 1; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.stat-val--text { font-size: clamp(22px, 2.6vw, 30px); color: var(--color-accent-2-800); }
.stat-lbl { font-size: 13px; color: color-mix(in srgb, var(--color-text) 58%, transparent); }
.stat-tile--place { background: color-mix(in srgb, var(--color-accent-2-200) 55%, transparent); }

/* Tablette / petit écran : 2×2 (plus lisible que 4 colonnes serrées ou 4 tuiles
   empilées). On ne passe en pleine colonne que sur les très petits téléphones. */
@media (max-width: 860px) { .stats-band { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 360px) { .stats-band { grid-template-columns: 1fr; } }
@media (max-width: 600px) {
  .stat-tile { padding: 16px 16px 15px; border-radius: 16px; }
  .stat-val { font-size: clamp(26px, 9vw, 34px); }
}

@media (prefers-reduced-motion: reduce) {
  .cursor-glow { display: none; }
  .read-progress { display: none; }
}

/* ── Mobile-first : allègement du fond technique ──────────────────────────────
   Sur petits écrans (souvent GPU modeste + fort DPR), on retire les courbes de
   niveau (repeating-radial-gradient coûteux à rastériser) et on élargit la
   grille : le décor reste, la charge de peinture chute. */
@media (max-width: 700px) {
  body::before {
    background-image:
      radial-gradient(120% 45% at 50% -6%, color-mix(in srgb, var(--color-accent) 12%, transparent), transparent 66%),
      radial-gradient(130% 120% at 50% 40%, transparent 54%, rgba(0, 0, 0, .5) 100%),
      linear-gradient(color-mix(in srgb, var(--color-text) 3.5%, transparent) 1px, transparent 1px),
      linear-gradient(90deg, color-mix(in srgb, var(--color-text) 3.5%, transparent) 1px, transparent 1px);
    background-size: 100% 100%, 100% 100%, 44px 44px, 44px 44px;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Le pas automatique est déjà coupé côté JS ; on remet la bande à plat pour
     qu'elle se lise d'un bloc, et on coupe tout le reste — élan, flottement,
     survol — plutôt que de laisser vivre des mouvements sans le défilement
     qu'ils accompagnaient. */
  .hero-track { flex-wrap: wrap; width: auto; justify-content: center; transform: none; }
  .hero-track.is-stepping,
  .hero-slot,
  .hero-frame { transition: none; }
  .hero-track.is-lagging .hero-slot { transform: none; transition: none; }
  .hero-drift { animation: none; }
  .hero-track:hover .hero-slot:hover .hero-frame { transform: var(--fan, none); }
  .reveal { opacity: 1; transform: none; }
  html { scroll-behavior: auto; }
}

/* — points de rupture (identiques au prototype) — */
@media (max-width: 900px) {
  .nav-links { display: none; }
  .hamburger { display: inline-flex; }
  .nav-actions .lbl { display: none; }
  .stack-mobile { grid-template-columns: 1fr !important; }
  .checkout-grid { grid-template-columns: 1fr !important; }
  .footer-grid { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
  .two-mobile { grid-template-columns: 1fr !important; }

  /* À 375 px, la marque se coupait en deux lignes une fois la barre remplie
     (langue + compte + panier + burger). On la garde d'un bloc en réduisant
     légèrement le corps plutôt qu'en sacrifiant une action. */
  .brand-name { font-size: 19px; white-space: nowrap; }
  .brand-role { font-size: 9px; letter-spacing: .22em; }
  .nav-inner  { gap: 8px; }
}

/* Téléphones : le titre du hero est verrouillé en `nowrap` (2 lignes voulues),
   mais « Capturer l'instant, » déborde sous ~380 px → scroll horizontal, le pire
   défaut mobile. On l'autorise à revenir à la ligne et on réduit le corps : plus
   jamais de débordement, quel que soit le ratio. */
@media (max-width: 430px) {
  .hero-title { font-size: clamp(27px, 8.4vw, 38px); }
  .hero-title span { white-space: normal; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Visionneuse plein écran
   L'image affichée reste l'aperçu filigrané : agrandir ne donne accès à
   rien de plus que la vignette (§2.1).
   ══════════════════════════════════════════════════════════════════════ */
/* Un `display` explicite l'emporte sur la règle par défaut de [hidden] : sans
   ces deux lignes, la visionneuse resterait affichée en permanence et
   recouvrirait la page dès l'arrivée sur l'événement. Même piège que pour
   .menu-overlay et .cart-portal plus haut. */
.viewer[hidden]    { display: none; }
.viewer-dl[hidden] { display: none; }   /* .btn est en inline-flex */

.viewer {
  position: fixed; inset: 0; z-index: 200;
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: 1fr auto;
  align-items: center; gap: 8px;
  padding: 18px;
  background: color-mix(in srgb, var(--color-neutral-900) 92%, transparent);
  backdrop-filter: blur(6px);
  animation: viewer-in .18s ease-out;
}
@keyframes viewer-in { from { opacity: 0; } to { opacity: 1; } }

.viewer-stage {
  grid-column: 2; grid-row: 1;
  position: relative; margin: 0;
  display: flex; align-items: center; justify-content: center;
  /* min-height:0 : sans lui, l'image déborde de la grille au lieu de se
     contraindre à la hauteur disponible. */
  height: 100%; min-height: 0;
}
.viewer-stage img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
  border-radius: 12px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, .5);
  transition: opacity .18s ease;
}

/* Pendant que la grande image se charge, on l'estompe et on montre un
   halo qui tourne — la visionneuse ne paraît plus figée. Le repère est
   posé derrière l'image (::before sur la scène), donc visible tant que
   l'image n'est pas peinte par-dessus. */
.viewer.is-loading .viewer-stage img { opacity: .35; }
.viewer-stage::before {
  content: ""; position: absolute; width: 34px; height: 34px;
  border-radius: 50%; opacity: 0; pointer-events: none;
  border: 3px solid rgba(255, 255, 255, .25);
  border-top-color: rgba(255, 255, 255, .9);
}
.viewer.is-loading .viewer-stage::before {
  opacity: 1; animation: viewer-spin .7s linear infinite;
}
@keyframes viewer-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .viewer.is-loading .viewer-stage::before { animation: none; }
}

.viewer-nav, .viewer-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border: 0; cursor: pointer;
  border-radius: 999px; color: var(--color-neutral-100);
  background: color-mix(in srgb, var(--color-neutral-100) 14%, transparent);
  transition: background .15s, transform .15s;
}
.viewer-nav:hover, .viewer-close:hover {
  background: color-mix(in srgb, var(--color-neutral-100) 26%, transparent);
  transform: scale(1.06);
}
.viewer-prev  { grid-column: 1; grid-row: 1; }
.viewer-next  { grid-column: 3; grid-row: 1; }
.viewer-close { position: absolute; top: 18px; right: 18px; }

.viewer-bar {
  grid-column: 1 / -1; grid-row: 2;
  display: flex; align-items: center; justify-content: center;
  gap: 16px; flex-wrap: wrap;
  color: var(--color-neutral-100); font-size: 14px;
  padding-top: 8px;
}
.viewer-count   { font-weight: 700; letter-spacing: .04em; }
.viewer-caption { opacity: .75; max-width: 46ch; text-align: center; }

@media (max-width: 700px) {
  .viewer { padding: 10px; gap: 4px; }
  .viewer-nav, .viewer-close { width: 40px; height: 40px; }
  .viewer-caption { display: none; }   /* la place manque, le compteur suffit */
}

@media (prefers-reduced-motion: reduce) {
  .viewer { animation: none; }
  .viewer-nav, .viewer-close { transition: none; }
  .viewer-nav:hover, .viewer-close:hover { transform: none; }
}

/* ── Information cookies (pas une fenêtre de consentement — voir layout) ── */
.cookie-note {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 150;
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  max-width: 620px; margin: 0 auto; padding: 14px 18px;
  border-radius: 18px; background: var(--color-surface);
  box-shadow: 0 12px 40px rgba(0, 0, 0, .18); font-size: 13px;
}
/* Un display explicite l'emporte sur [hidden] — même piège que la visionneuse. */
.cookie-note[hidden] { display: none; }
.cookie-note p { margin: 0; flex: 1 1 240px; line-height: 1.5; }

/* ── Panier : lots dépliables ──────────────────────────────────────────── */
.cart-lot { border: 1px solid var(--color-divider); border-radius: 14px; overflow: hidden; }
.cart-lot > summary {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px; cursor: pointer; list-style: none;
  background: var(--color-surface); font-size: 13px;
}
.cart-lot > summary::-webkit-details-marker { display: none; }
/* Le chevron indique qu'il y a quelque chose à ouvrir : sans lui, le lot
   ressemble à une ligne inerte. */
.cart-lot > summary::before {
  content: '+'; font-weight: 700; font-size: 15px; line-height: 1;
  color: var(--color-accent); width: 14px; flex: none;
}
.cart-lot[open] > summary::before { content: '−'; }
.cart-lot-label { flex: 1; min-width: 0; }
.cart-lot-price { font-weight: 700; white-space: nowrap; color: var(--color-accent); }
.cart-lot-body { padding: 10px 14px 14px; display: flex; flex-direction: column; gap: 12px; }
/* Un pack se distingue d'une photo isolée : liseré accentué + pastille d'icône. */
.cart-lot > summary { background: color-mix(in srgb, var(--color-accent) 7%, var(--color-surface)); }
.cart-lot { border-color: color-mix(in srgb, var(--color-accent) 26%, var(--color-divider)); }
.cart-lot-ic { color: var(--color-accent); display: grid; place-items: center; flex: none; }
.cart-lot-ic .icon { width: 17px; height: 17px; }

/* Retrait fluide : la ligne s'efface et se replie avant recomposition. */
.cart-item.is-removing {
  opacity: 0; overflow: hidden; pointer-events: none; transform: translateX(14px);
  transition: max-height .24s ease, opacity .2s ease, transform .2s ease, margin .24s ease;
}
/* Un pack qui vient de se former « éclot ». */
.pack-pop { animation: pack-pop .5s cubic-bezier(.22, 1.1, .36, 1) both; }
@keyframes pack-pop {
  0% { transform: scale(.9); opacity: 0; }
  60% { transform: scale(1.02); }
  100% { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .cart-item.is-removing { transition: opacity .1s ease; transform: none; }
  .pack-pop { animation: none; }
}

/* Le « + » d'ajout qui devient un « ✓ » : éclosion. */
.icon-pop { animation: icon-pop .42s cubic-bezier(.22, 1.4, .36, 1) both; }
@keyframes icon-pop {
  0% { transform: scale(0) rotate(-30deg); }
  60% { transform: scale(1.25) rotate(6deg); }
  100% { transform: scale(1) rotate(0); }
}
/* Le bouton panier tressaute quand une photo « y rentre ». */
.cart-bump { animation: cart-bump .45s cubic-bezier(.22, 1.3, .36, 1); }
@keyframes cart-bump {
  0%, 100% { transform: scale(1); }
  35% { transform: scale(1.18); }
  65% { transform: scale(.94); }
}
@media (prefers-reduced-motion: reduce) { .icon-pop, .cart-bump { animation: none; } }

/* — La pastille du compteur et le bouton qui l'accueille —
   TOUT se joue à l'arrivée de la photo (jamais avant) : la pastille NAÎT
   (count-in : de 0 à sa taille) et le bouton s'agrandit pour l'ACCUEILLIR
   (cart-receive). Incrément suivant → petit rebond (count-pop). Quand le panier
   se VIDE, la pastille se referme (count-out) et le bouton la raccompagne
   (cart-farewell) avant de disparaître. transform-origin centré pour les scales. */
.cart-count { transform-origin: center; }
.cart-count.count-in   { animation: count-in .5s cubic-bezier(.34, 1.55, .5, 1) both; }
.cart-count.count-pop  { animation: count-pop .42s cubic-bezier(.34, 1.5, .5, 1) both; }
.cart-count.count-down { animation: count-down .4s ease both; }
.cart-count.count-out  { animation: count-out .34s cubic-bezier(.5, 0, .75, 0) both; }
@keyframes count-in  { 0% { transform: scale(0); opacity: 0; } 60% { transform: scale(1.28); opacity: 1; } 100% { transform: scale(1); } }
@keyframes count-pop { 0% { transform: scale(1); } 45% { transform: scale(1.32); } 100% { transform: scale(1); } }
@keyframes count-down{ 0% { transform: scale(1); } 45% { transform: scale(.72); opacity: .6; } 100% { transform: scale(1); opacity: 1; } }
@keyframes count-out { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(0); opacity: 0; } }
[data-cart-open].cart-receive  { animation: cart-receive .55s cubic-bezier(.34, 1.56, .5, 1); }
[data-cart-open].cart-farewell { animation: cart-farewell .5s cubic-bezier(.34, 1.56, .5, 1); }
/* Le bouton « s'ouvre » franchement pour accueillir la pastille (grossit puis se
   pose avec un petit rebond), et se « referme » nettement quand le panier se vide. */
@keyframes cart-receive  { 0% { transform: scale(1); } 30% { transform: scale(1.26); } 60% { transform: scale(.94); } 82% { transform: scale(1.05); } 100% { transform: scale(1); } }
@keyframes cart-farewell { 0% { transform: scale(1); } 35% { transform: scale(.74); } 68% { transform: scale(1.05); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .cart-count.count-in, .cart-count.count-pop, .cart-count.count-down, .cart-count.count-out { animation: none; }
  [data-cart-open].cart-receive, [data-cart-open].cart-farewell { animation: none; }
}

/* Ouverture / fermeture animée du pack (hauteur pilotée en JS). */
.cart-lot-body { transition: height .3s ease, opacity .28s ease; }
/* Formation d'un pack : les photos « se rassemblent » dedans, en cascade. */
.pack-gather .cart-item { animation: pack-gather-in .42s cubic-bezier(.22, 1, .36, 1) both; }
@keyframes pack-gather-in {
  from { opacity: 0; transform: translateY(-14px) scale(.9); }
  to { opacity: 1; transform: none; }
}
.pack-gather .cart-item:nth-child(1) { animation-delay: .03s; }
.pack-gather .cart-item:nth-child(2) { animation-delay: .08s; }
.pack-gather .cart-item:nth-child(3) { animation-delay: .13s; }
.pack-gather .cart-item:nth-child(4) { animation-delay: .18s; }
.pack-gather .cart-item:nth-child(5) { animation-delay: .23s; }
.pack-gather .cart-item:nth-child(6) { animation-delay: .28s; }
.pack-gather .cart-item:nth-child(7) { animation-delay: .33s; }
.pack-gather .cart-item:nth-child(8) { animation-delay: .38s; }
.pack-gather .cart-item:nth-child(9) { animation-delay: .43s; }
.pack-gather .cart-item:nth-child(10) { animation-delay: .48s; }
@media (prefers-reduced-motion: reduce) {
  .cart-lot-body { transition: none; }
  .pack-gather .cart-item { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Neutralisation du filtre « washed » sur les photos du client
   ══════════════════════════════════════════════════════════════════════════

   Le design system applique `.washed { filter: saturate(.6) contrast(.85)
   brightness(1.1) opacity(.94) }` : une patine voulue pour des images
   d'ambiance, mais appliquée ici aux photographies vendues.

   Résultat : les vignettes étaient désaturées de 40 %, décontrastées de 15 %
   et éclaircies de 10 % — le client ne voyait pas l'étalonnage qu'il achète,
   et la grande visionneuse (sans .washed) ne correspondait pas aux cartes.

   Le filtre est porté par le <figure>, et un `filter` CSS s'applique à TOUT
   son sous-arbre : le neutraliser sur l'<img> ne sert à rien, il faut le
   retirer de l'élément qui le pose. Dans ce projet, `.washed` n'habille que
   des photographies — jamais un visuel décoratif — donc on l'annule partout. */
.washed {
  filter: none;
  opacity: 1;
}

/* ═══════════ Finitions du thème sombre ═══════════
   Sur fond sombre, une surface plate paraît « creuse ». On lui donne du relief
   par une arête claire en haut (liseré interne) plutôt que par une ombre seule.
   Regroupé ici pour que la bascule clair/sombre reste lisible et réversible. */
.card,
.event-card,
.photo-card,
.pricing-bar,
.showcase-card {
  border: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.event-card, .photo-card, .showcase-card { position: relative; }
/* Arête lumineuse : un trait clair juste sous le bord haut, comme une lumière
   rasante. Discret mais c'est lui qui « décolle » la carte du fond. */
.card::before,
.event-card::before,
.photo-card::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--color-text) 10%, transparent);
}
.event-card, .photo-card { overflow: hidden; }

/* Navbar : liseré bas discret. Fond quasi opaque (voir plus haut : pas de
   backdrop-filter, trop coûteux au scroll). */
.navbar { border-bottom: 1px solid color-mix(in srgb, var(--color-text) 9%, transparent); }

/* Puce « neutre » : sur sombre, une pastille claire jurerait avec les puces
   d'accent devenues sombres. On l'aligne en surface translucide + texte clair. */
.tag-neutral {
  background: color-mix(in srgb, var(--color-text) 9%, transparent);
  color: var(--color-neutral-200);
}

/* Champs de saisie : la surface seule manque de bord sur sombre. */
.input { background: color-mix(in srgb, var(--color-text) 5%, transparent); }
.input::placeholder { color: color-mix(in srgb, var(--color-text) 40%, transparent); }

/* Barre de défilement discrète, accordée au sombre. */
::-webkit-scrollbar-thumb { background: var(--color-neutral-700); }
::-webkit-scrollbar-thumb:hover { background: var(--color-neutral-600); }
