@charset "utf-8";
/* ==================================================================
   Baptême de Luna
   Direction : la colombe, la lumière, la pureté.
   Palette claire — blanc nacré, ciel d'aube, or doux, rameau d'olivier.
   ================================================================== */

/* ---------- Réglages ---------------------------------------------- */
:root {
  /* Lumière */
  --white:      #ffffff;
  --paper:      #fbfcfe;
  --paper-warm: #fdfaf4;
  --paper-sky:  #f4f9fc;
  --card:       #ffffff;

  /* Ciel */
  --sky-1:      #f2f8fc;
  --sky-2:      #e4eff7;
  --sky-3:      #bfd9ed;
  --sky-4:      #9fc5e3;

  /* Encre : un bleu d'ardoise, jamais du noir */
  --ink:        #24353f;
  --ink-soft:   #55707f;
  --ink-faint:  #8aa2b1;
  --hair:       #dbe7ef;

  /* Accents */
  --gold:       #a8863f;
  --gold-soft:  #c9ae76;
  --gold-pale:  #eee2c6;
  --olive:      #90a37f;
  --olive-pale: #dfe6d8;

  /* Polices : volontairement à l'écart des combinaisons habituelles */
  --display: "Cinzel", "Trajan Pro", "Palatino Linotype", serif;
  --serif:   "Spectral", "Iowan Old Style", Georgia, serif;
  --script:  "Petit Formal Script", "Snell Roundhand", cursive;

  --ease-drama: cubic-bezier(.62, .02, .16, 1);
  --ease-soft:  cubic-bezier(.22, .8, .28, 1);
  --ease-wing:  cubic-bezier(.45, 0, .55, 1);

  --gutter:   clamp(1.25rem, 5vw, 4rem);
  --gutter-l: max(clamp(1.25rem, 5vw, 4rem), env(safe-area-inset-left));
  --gutter-r: max(clamp(1.25rem, 5vw, 4rem), env(safe-area-inset-right));
  --measure:  64rem;
}

/* ---------- Base --------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body, button, input, textarea, a { -webkit-tap-highlight-color: rgba(194, 160, 94, .16); }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: clamp(1.02rem, .5vw + .92rem, 1.14rem);
  font-weight: 400;
  line-height: 1.72;
  font-variant-numeric: lining-nums;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Verrouiller le défilement pendant l'ouverture.
   overflow: hidden sur body seul ne suffit pas sur mobile : le
   défilement s'y fait sur html. La page pouvait donc bouger derrière
   l'enveloppe — et surtout la barre d'adresse se repliait, changeant la
   hauteur visible sous une scène censée être centrée dedans. C'est
   pour ça que mesure.php, page courte et non défilable, était parfait
   alors que le site ne l'était pas. */
html.est-verrouille,
body.is-locked {
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
  overscroll-behavior: none;
}

img, svg { max-width: 100%; }
button { font: inherit; color: inherit; }

::selection { background: var(--gold-pale); color: var(--ink); }

/* ---------- Grain de papier, très léger ---------------------------- */
.paper-grain {
  position: fixed; inset: 0; z-index: 9000; pointer-events: none;
  opacity: .2;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='.16'/%3E%3C/svg%3E");
}

/* Halo lumineux plutôt qu'une vignette sombre */
.vignette {
  position: fixed; inset: 0; z-index: 8999; pointer-events: none;
  background: radial-gradient(125% 95% at 50% 32%, transparent 46%, rgba(120, 160, 190, .10) 100%);
}

/* ==================================================================
   LA COLOMBE
   Une seule silhouette, deux ailes animées séparément.
   Le SVG doit être écrit en clair dans la page : à travers <use>,
   le CSS n'atteindrait pas les ailes.
   ================================================================== */

.dove { display: block; overflow: visible; }
.dove .body, .dove .wf, .dove .wn { fill: currentColor; }
.dove .wf { opacity: .7; }

.dove .wf, .dove .wn {
  transform-box: view-box;
  transform-origin: 106px 64px;
}
.dove .wn { animation: flap-near var(--flap, 1.1s) var(--ease-wing) infinite; }
.dove .wf { animation: flap-far  var(--flap, 1.1s) var(--ease-wing) infinite; }

@keyframes flap-near {
  0%, 100% { transform: rotate(-32deg) scaleY(1);   }
  50%      { transform: rotate(36deg)  scaleY(.84); }
}
@keyframes flap-far {
  0%, 100% { transform: rotate(-24deg) scaleY(.93); }
  50%      { transform: rotate(28deg)  scaleY(.78); }
}

/* Le corps tangue légèrement, comme un vrai vol. */
.dove-pitch { animation: pitch var(--flap, 1.1s) var(--ease-wing) infinite; }
@keyframes pitch {
  0%, 100% { translate: 0 4%; rotate: 1.6deg;  }
  50%      { translate: 0 -4%; rotate: -1.6deg; }
}

/* ==================================================================
   ACTE I — L'envolée
   ================================================================== */

.opening {
  position: fixed; inset: 0; z-index: 8000;
  height: 100vh;    /* repli pour les navigateurs anciens */
  height: 100dvh;   /* la hauteur réellement visible, barre d'adresse comprise */
  display: grid; place-items: center;
  background:
    radial-gradient(140% 100% at 50% 20%,
      #ffffff 0%, #f4fbfe 14%, #e2f0fa 34%,
      var(--sky-3) 64%, var(--sky-4) 88%, #8fbadd 100%);
  perspective: 1600px;
  perspective-origin: 50% 42%;
  touch-action: none;
  overflow: hidden;
  transition: opacity .8s var(--ease-soft), visibility .8s;
}
body.is-open .opening { opacity: 0; visibility: hidden; pointer-events: none; }

/* Rayons de lumière obliques */
.rays {
  position: absolute; inset: -20%;
  pointer-events: none;
  background:
    linear-gradient(104deg, transparent 26%, rgba(255,255,255,.62) 36%, transparent 46%),
    linear-gradient(99deg,  transparent 48%, rgba(255,255,255,.45) 58%, transparent 66%),
    linear-gradient(110deg, transparent 66%, rgba(255,255,255,.35) 74%, transparent 82%);
  animation: rays-drift 14s ease-in-out infinite alternate;
}
@keyframes rays-drift {
  from { transform: translateX(-2%) scaleY(1);    opacity: .75; }
  to   { transform: translateX(2%)  scaleY(1.05); opacity: 1;   }
}

/* Le ciel où passent les colombes */
.doves { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }

.doves .dove {
  position: absolute;
  color: var(--white);
  filter: drop-shadow(0 7px 16px rgba(28, 68, 108, .42));
  animation: glide var(--dur, 16s) linear infinite;
}
.doves .dove.far {
  color: #d7e6f2;
  filter: none;
}
/* Un oiseau ne vole pas en ligne droite : il ondule et s'incline dans
   ses virages. Cinq points de passage suffisent à le faire croire. */
@keyframes glide {
  0%   { transform: translate3d(-18vw,  0,      0) rotate(-2deg);   }
  22%  { transform: translate3d(13vw,  -3.2vh,  0) rotate(1.4deg);  }
  45%  { transform: translate3d(45vw,   1.6vh,  0) rotate(-1.8deg); }
  68%  { transform: translate3d(78vw,  -3.6vh,  0) rotate(1.2deg);  }
  100% { transform: translate3d(118vw, var(--rise, -8vh), 0) rotate(-1deg); }
}

/* Plumes qui descendent en tournoyant */
.feathers { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.feather {
  position: absolute; top: -6%;
  color: var(--white);
  opacity: 0;
  animation: fall var(--dur, 16s) linear infinite;
}
.feather svg { display: block; width: 100%; height: auto; fill: currentColor; }
@keyframes fall {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg);                  opacity: 0; }
  10%  { opacity: .85; }
  85%  { opacity: .5; }
  100% { transform: translate3d(var(--drift, 40px), 112vh, 0) rotate(var(--spin, 220deg)); opacity: 0; }
}

/* --- L'envol : quand le cachet cède, les colombes s'échappent --- */
.dove-burst {
  position: fixed; z-index: 8600;
  pointer-events: none;
  color: var(--white);
  filter: drop-shadow(0 8px 18px rgba(70, 115, 150, .40));
  animation: burst 3s var(--ease-soft) forwards;
}
.dove-ghost { filter: none; }
@keyframes burst {
  0%   { transform: translate(-50%, -50%) scale(.18) rotate(0deg); opacity: 0; }
  12%  { opacity: 1; }
  70%  { opacity: 1; }
  100% {
    transform: translate(calc(-50% + var(--bx, 0px)), calc(-50% + var(--by, -60vh)))
               scale(var(--bs, 1.4)) rotate(var(--br, 0deg));
    opacity: 0;
  }
}

/* --- Scène --- */
.envelope-stage {
  position: relative;
  display: grid; justify-items: center;
  transform-style: preserve-3d;
  transition: transform 1.4s var(--ease-soft);
}
body.is-drawing   .envelope-stage { transform: translateY(4%); }
body.is-unfolding .envelope-stage { transform: translateY(0); }

/* --- L'enveloppe, blanc nacré --- */
.envelope {
  --env-w: min(80vw, 440px, 94vh);
  --env-h: calc(var(--env-w) * .66);
  --flap-h: 62%;

  position: relative;
  width: var(--env-w); height: var(--env-h);
  padding: 0; border: 0; background: none;
  cursor: pointer;
  transform-style: preserve-3d;
  transform: rotateX(9deg);
  transition: transform 1s var(--ease-soft), opacity .9s ease;
  animation: hover-env 6s ease-in-out infinite;
}
/* « translate » est la seule façon d'animer ce flottement sans écraser
   le rotateX. Sur un navigateur trop ancien, l'enveloppe reste immobile. */
@keyframes hover-env {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -10px; }
}

.envelope:hover { transform: rotateX(6deg) scale(1.015); }
.envelope:focus-visible { outline: 1px solid var(--gold); outline-offset: 18px; }
body.is-opening .envelope { animation: none; cursor: default; }

.env-back {
  position: absolute; inset: 0;
  transform: translateZ(0);
  background: linear-gradient(166deg, #ffffff 0%, #f7fafd 55%, #eaf1f7 100%);
  border-radius: 3px;
  box-shadow:
    0 38px 64px -22px rgba(30, 70, 110, .44),
    0 12px 24px -10px rgba(30, 70, 110, .30),
    0 1px 0 rgba(255, 255, 255, .95) inset;
}

/* Pans avant */
.env-front {
  position: absolute; inset: 0; z-index: 4;
  transform: translateZ(4px);
  border-radius: 3px; overflow: hidden;
}
.env-side {
  position: absolute; top: 0; bottom: 0; width: 52%;
  background: linear-gradient(100deg, #fdfefe, #eef4f9);
}
.env-side.left  { left: 0;  clip-path: polygon(0 0, 100% 50%, 0 100%); }
.env-side.right { right: 0; clip-path: polygon(100% 0, 0 50%, 100% 100%);
                  background: linear-gradient(260deg, #fdfefe, #ebf2f8); }

.env-pocket {
  position: absolute; left: 0; right: 0; bottom: 0; height: 62%;
  background: linear-gradient(184deg, #ffffff 0%, #f6fafd 55%, #e9f1f7 100%);
  clip-path: polygon(0 100%, 0 12%, 50% 0, 100% 12%, 100% 100%);
  box-shadow: 0 -1px 0 rgba(255, 255, 255, .8) inset;
}
.env-pocket::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(184deg, rgba(120, 160, 190, .16), transparent 24%);
}

/* Rabat — doublé de ciel à l'intérieur, comme un vrai faire-part */
.env-flap {
  position: absolute; top: 0; left: 0; right: 0; height: var(--flap-h);
  z-index: 6;
  transform: translateZ(6px);
  transform-origin: top center;
  transform-style: preserve-3d;
  transition: transform 1.15s var(--ease-drama) .45s, z-index 0s linear .95s;
}
.env-flap-face {
  position: absolute; inset: 0;
  background: linear-gradient(176deg, #ffffff 0%, #f5f9fc 60%, #e6eff6 100%);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.env-flap-face::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(176deg, transparent 52%, rgba(110, 155, 190, .16) 100%);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}
body.is-opening .env-flap { transform: translateZ(6px) rotateX(-178deg); z-index: 1; }

/* --- Le sceau : cire ivoire, colombe et croissant dorés --- */
.seal {
  position: absolute; z-index: 7;
  left: 50%; top: calc(var(--flap-h) - 7%);
  width: 27%;
  transform: translate(-50%, -50%) translateZ(9px);
  filter: drop-shadow(0 4px 9px rgba(90, 130, 160, .38));
}
.seal svg {
  display: block; width: 100%; height: auto; overflow: visible;
  transform-origin: 50% 50%;
  transition: transform .3s ease;
}
.seal-wax { fill: url(#waxGrad); }
.seal-rim { fill: none; stroke: rgba(194, 160, 94, .55); stroke-width: 1.1; }
.seal-moon, .seal-dove { fill: var(--gold); }
.seal-dove { opacity: .95; }

.envelope:hover .seal svg { transform: scale(1.05) rotate(-2deg); }

/* Le cachet cède selon la courbe « lent — rafale — longue traîne » :
   il frémit d'abord, part d'un coup, puis dérive en ralentissant.
   Le flou suit exactement la vitesse : maximal dans la rafale, nul à
   l'arrêt. C'est ce couplage qui donne l'impression de vitesse réelle. */
body.is-opening .seal svg { animation: seal-lift 1.25s linear forwards; }
@keyframes seal-lift {
  /* frémissement : la tension monte, presque rien ne bouge */
  0%   { transform: none; opacity: 1; filter: blur(0);
         animation-timing-function: cubic-bezier(.55, .06, .68, .19); }
  18%  { transform: translateY(-3%) scale(1.12) rotate(-4deg); opacity: 1; filter: blur(0);
         animation-timing-function: cubic-bezier(.55, .06, .68, .19); }
  26%  { transform: translateY(-6%) scale(1.06) rotate(4deg); opacity: 1; filter: blur(.4px);
         animation-timing-function: linear; }
  /* rafale : l'essentiel du trajet, à vitesse constante */
  44%  { transform: translate(-6%, -78%) scale(.9) rotate(-26deg); opacity: .85; filter: blur(2.6px);
         animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  /* traîne : décélération longue, le flou se dissipe */
  100% { transform: translate(-13%, -168%) scale(.58) rotate(-52deg); opacity: 0; filter: blur(4.5px); }
}

/* --- La lettre --- */
.letter {
  position: absolute; z-index: 2;
  left: 6%; right: 6%; top: 0; height: 100%;
  transform: translateZ(2px);
  background: linear-gradient(178deg, #ffffff 0%, #fdfdfb 58%, #f8f6f1 100%);
  border-radius: 2px;
  box-shadow: 0 2px 4px rgba(80, 120, 150, .12);
  display: grid; place-items: center;
  transform-origin: 50% 50%;
  transition: transform 1.5s var(--ease-drama), box-shadow 1.5s ease;
  overflow: hidden;
}
.letter::before {
  content: ""; position: absolute; inset: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='110' height='110'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.95' numOctaves='3'/%3E%3C/filter%3E%3Crect width='110' height='110' filter='url(%23p)' opacity='.22'/%3E%3C/svg%3E");
  mix-blend-mode: multiply; opacity: .4; pointer-events: none;
}
.letter::after {
  content: ""; position: absolute; inset: 5%;
  border: 1px solid rgba(194, 160, 94, .3);
  pointer-events: none;
}

/* La lettre quitte l'enveloppe sur la même courbe que le cachet :
   elle se décolle à peine, glisse d'un trait, puis flotte jusqu'à l'arrêt.
   Une courbe unique donnerait un coup sec à l'arrivée. */
body.is-drawing .letter {
  z-index: 5;
  box-shadow: 0 26px 44px -14px rgba(70, 115, 150, .42);
  animation: letter-draw 1.5s linear forwards;
}
@keyframes letter-draw {
  0%   { transform: translateZ(12px) translateY(0);
         animation-timing-function: cubic-bezier(.55, .06, .68, .19); }
  21%  { transform: translateZ(12px) translateY(-5.5%);
         animation-timing-function: linear; }
  39%  { transform: translateZ(12px) translateY(-39%);
         animation-timing-function: cubic-bezier(.16, 1, .3, 1); }
  100% { transform: translateZ(12px) translateY(-52%); }
}
/* Les étapes suivantes reprennent la main : on rend la transition au CSS. */
body.is-unfolding .letter,
body.is-finishing .letter { animation: none; }
body.is-unfolding .letter { transform: translateZ(12px) translateY(-2%) scale(var(--letter-zoom, 1.22)); }
body.is-finishing .letter {
  transform: translateZ(12px) translateY(-6%) scale(2.5); opacity: 0;
  transition: transform 1s var(--ease-drama), opacity .8s ease .1s;
}

body.is-unfolding .env-back,
body.is-unfolding .env-front,
body.is-unfolding .env-flap { opacity: 0; transition: opacity .8s ease; }

.letter-inner {
  position: relative; z-index: 1;
  display: grid; justify-items: center; gap: .2em;
  padding: 4% 8%;
  text-align: center;
}
.letter-dove { width: clamp(40px, 9vw, 62px); color: var(--gold); margin-bottom: .3em; }
.letter-dove .wf, .letter-dove .wn { animation: none; }
.letter-dove .wf { transform: rotate(-26deg); }
.letter-dove .wn { transform: rotate(-34deg); }

.letter-kicker {
  font-family: var(--display);
  font-size: clamp(.58rem, 1.5vw, .74rem);
  letter-spacing: .3em; text-transform: uppercase; font-weight: 500;
  color: var(--ink-soft); padding-left: .3em;
}
.letter-title {
  font-family: var(--script);
  font-size: clamp(1.7rem, 5.6vw, 2.7rem);
  line-height: 1; color: var(--gold);
}
.letter-name {
  font-family: var(--display);
  font-size: clamp(1.7rem, 6.4vw, 2.8rem);
  letter-spacing: .2em; text-transform: uppercase;
  line-height: 1.1; padding-left: .2em;
  color: var(--ink);
}
.letter-rule { width: 46px; height: 1px; background: var(--gold); opacity: .5; margin: .5em 0 .35em; }
.letter-date {
  font-style: italic;
  font-size: clamp(.7rem, 2.2vw, .92rem);
  color: var(--ink-soft); letter-spacing: .05em;
}

/* --- Invite --- */
.prompt {
  position: absolute;
  top: 100%; left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: min(92vw, 30rem);
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 1rem;
  text-align: center;
  margin: clamp(2.2rem, 6vw, 3.4rem) 0 0;
  color: var(--ink-soft);
  font-family: var(--display);
  font-size: clamp(.6rem, 1.6vw, .74rem);
  letter-spacing: .26em; text-transform: uppercase; font-weight: 500;
  animation: pulse-soft 3.6s ease-in-out infinite;
  transition: opacity .4s ease, transform .5s ease;
}
.prompt-text { padding-left: .4em; }
.prompt-line { width: clamp(22px, 8vw, 46px); height: 1px; background: var(--gold-soft); }
@keyframes pulse-soft { 0%, 100% { opacity: .6; } 50% { opacity: 1; } }
body.is-opening .prompt {
  opacity: 0;
  transform: translateX(-50%) translateY(10px);   /* garder le centrage */
  animation: none;
}

.skip {
  position: absolute; bottom: max(1.4rem, env(safe-area-inset-bottom)); left: 50%;
  transform: translateX(-50%);
  padding: .55rem 1rem;
  border: 0; background: none; cursor: pointer;
  color: var(--ink-faint);
  font-style: italic; font-size: .86rem; letter-spacing: .06em;
  transition: color .3s ease, opacity .4s ease;
}
.skip:hover { color: var(--ink); }
body.is-opening .skip { opacity: 0; pointer-events: none; }

/* --- Rideau de transition, en blanc --- */
.curtain {
  position: fixed; inset: 0; z-index: 8500;
  background: var(--paper);
  opacity: 0; pointer-events: none;
  transition: opacity .55s ease;
}
body.is-finishing .curtain { opacity: 1; }
body.is-open .curtain { opacity: 0; transition: opacity .9s ease .15s; }

/* ==================================================================
   LE CIEL — décor vivant, derrière toute la page
   Trois plans de nuages, une lumière qui change avec le défilement.
   Piloté par --sy (défilement en pixels) et --sp (progression 0 → 1).
   ================================================================== */

.skyscape {
  position: fixed; inset: 0; z-index: -1;
  pointer-events: none; overflow: hidden;
}

/* Deux ciels superposés : le second se dévoile à mesure qu'on descend. */
.sky-layer { position: absolute; inset: -10% 0; }
.sky-dawn {
  background: linear-gradient(180deg,
    #ffffff 0%, #f6fbfe 16%, #e8f3fb 38%, #d6e9f7 66%, #c8e0f2 100%);
  opacity: calc(1 - var(--sp, 0) * .9);
}
.sky-noon {
  background: linear-gradient(180deg,
    #eef7fc 0%, #f6f6f2 30%, #fdf6e9 58%, #fbeedc 82%, #f7e6d2 100%);
  opacity: calc(var(--sp, 0) * .95);
}

/* Le soleil : un halo qui descend lentement pendant la lecture. */
.sun-halo {
  position: absolute; left: 50%; top: 8%;
  width: min(120vw, 900px); aspect-ratio: 1;
  transform: translate3d(-50%, calc(var(--sy, 0px) * .16), 0);
  background: radial-gradient(circle,
    rgba(255, 252, 240, .95) 0%, rgba(255, 246, 222, .5) 26%, transparent 62%);
  will-change: transform;
}

/* --- Les nuages ------------------------------------------------- */
.cloud-layer { position: absolute; inset: 0; will-change: transform; }
.cloud-layer.far  { transform: translate3d(0, calc(var(--sy, 0px) * -.06), 0); opacity: .55; }
.cloud-layer.mid  { transform: translate3d(0, calc(var(--sy, 0px) * -.14), 0); opacity: .75; }
.cloud-layer.near { transform: translate3d(0, calc(var(--sy, 0px) * -.26), 0); opacity: .92; }

.cloud {
  position: absolute;
  animation: cloud-drift var(--dur, 90s) linear infinite;
}
.cloud i {
  position: absolute; display: block;
  border-radius: 50%;
  /* Le dégradé fait tout le travail. Un filter: blur() par-dessus
     obligeait le navigateur à re-rastériser 38 éléments à chaque
     image, alors qu'ils dérivent en permanence. */
  background: radial-gradient(ellipse at 50% 56%,
    rgba(255,255,255,.92) 0%, rgba(255,255,255,.78) 30%,
    rgba(255,255,255,.42) 56%, rgba(255,255,255,.12) 76%,
    rgba(255,255,255,0) 100%);
}
@keyframes cloud-drift {
  from { translate: -30vw 0; }
  to   { translate: 130vw 0; }
}

/* Colombes qui traversent le site pendant la lecture */
.sky-doves { position: absolute; inset: 0; }
.sky-doves .dove {
  position: absolute;
  color: var(--white);
  filter: drop-shadow(0 6px 14px rgba(40, 90, 130, .28));
  animation: glide var(--dur, 30s) linear infinite;
}
.sky-doves .dove.far { color: #dcebf6; filter: none; }

/* Une colombe lancée par l'arrivée d'une section */
.dove-cue {
  position: fixed; z-index: 4;
  pointer-events: none;
  color: var(--white);
  filter: drop-shadow(0 8px 18px rgba(40, 90, 130, .34));
  animation: cue-cross var(--dur, 9s) linear forwards;
}
@keyframes cue-cross {
  from { transform: translate3d(-22vw, 0, 0)  rotate(-3deg); opacity: 0; }
  8%   { opacity: 1; }
  88%  { opacity: 1; }
  to   { transform: translate3d(122vw, var(--rise, -14vh), 0) rotate(2deg); opacity: 0; }
}

/* Les sections laissent voir le ciel ; un voile blanc donne le rythme
   et garde le texte parfaitement lisible. */
.section, .hero { background: transparent; }
.section-alt {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, .42) 0%,
    rgba(255, 255, 255, .86) 22%,
    rgba(255, 255, 255, .86) 78%,
    rgba(255, 255, 255, .42) 100%);
  border-top: 0; border-bottom: 0;
}
.footer {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, .55) 30%,
    rgba(255, 255, 255, .78) 100%);
}

/* ==================================================================
   ACTE II — L'invitation
   ================================================================== */

.invitation {
  opacity: 0; visibility: hidden;
  transition: opacity .9s ease .2s, visibility .9s;
}
body.is-open .invitation { opacity: 1; visibility: visible; }

/* --- Révélations : plus lentes, avec une mise au point --- */
/* Opacité et transform uniquement : ce sont les deux seules propriétés
   que le navigateur sait animer sans redessiner. Il y avait ici un
   filter: blur(7px) sur 33 blocs simultanés — une passe de flou plein
   écran par image, à elle seule responsable des saccades sur mobile. */
.reveal {
  opacity: 0;
  transform: translateY(34px) scale(.98);
  transition: opacity 1.05s var(--ease-soft),
              transform 1.35s var(--ease-soft);
}
.reveal.in { opacity: 1; transform: none; }
html.no-js .reveal { opacity: 1; transform: none; filter: none; }

/* Variantes, pour que tout n'arrive pas de la même façon */
.reveal-left  { transform: translate3d(-46px, 18px, 0) rotate(-1.2deg); }
.reveal-right { transform: translate3d(46px, 18px, 0) rotate(1.2deg); }
.reveal-rise  { transform: translateY(64px) scale(.94); }
.reveal-left.in, .reveal-right.in, .reveal-rise.in { transform: none; rotate: 0deg; }

/* --- Hero --- */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: grid; align-content: center; justify-items: center;
  gap: clamp(.7rem, 1.6vw, 1.2rem);
  padding: clamp(4.5rem, 12vh, 8rem) var(--gutter-r) clamp(5rem, 12vh, 7rem) var(--gutter-l);
  text-align: center;
  background:
    radial-gradient(120% 80% at 50% 4%, #ffffff 0%, var(--paper-sky) 44%, var(--sky-2) 100%);
  overflow: hidden;
}

/* Colombe en filigrane, derrière le prénom */
.dove-mark {
  position: absolute; top: 46%; left: 50%;
  width: min(88vw, 760px);
  transform: translate(-50%, -50%);
  color: var(--sky-4);
  opacity: .34;
  pointer-events: none;
}
.dove-mark .wf, .dove-mark .wn { animation: none; }
.dove-mark .wf { transform: rotate(-20deg); opacity: .5; }
.dove-mark .wn { transform: rotate(-30deg); }

/* Colombes qui traversent le hero, discrètes */
.hero-doves { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.hero-doves .dove {
  position: absolute;
  color: var(--white);
  filter: drop-shadow(0 5px 12px rgba(86, 130, 165, .22));
  animation: glide var(--dur, 26s) linear infinite;
}

.hero-kicker {
  margin: 0; max-width: 30ch;
  font-style: italic;
  font-size: clamp(.95rem, 2vw, 1.1rem);
  color: var(--ink-soft);
  position: relative;
}

.hero-name { margin: 0; display: grid; justify-items: center; gap: .06em; position: relative; }

/* Une lueur s'épanouit derrière le prénom, puis respire sans jamais
   monter assez haut pour se faire remarquer. */
.hero::after {
  content: ""; position: absolute;
  left: 50%; top: 46%;
  width: min(130vw, 1000px); height: min(130vw, 1000px);
  transform: translate(-50%, -50%) scale(.62);
  background: radial-gradient(circle,
    rgba(255, 252, 242, .95) 0%, rgba(255, 246, 224, .5) 26%, transparent 62%);
  opacity: 0; pointer-events: none; z-index: 0;
}
body.is-open .hero::after { animation: bloom 2.6s var(--ease-soft) .35s forwards; }
@keyframes bloom {
  0%   { opacity: 0;   transform: translate(-50%, -50%) scale(.62); }
  70%  { opacity: .44; transform: translate(-50%, -50%) scale(1.02); }
  100% { opacity: .4;  transform: translate(-50%, -50%) scale(1); }
}

/* Le contenu du hero passe devant la lueur. */
.hero > *:not(.dove-mark):not(.hero-doves) { position: relative; z-index: 1; }

.hero-script {
  display: block;
  font-family: var(--script);
  font-size: clamp(2.6rem, 9.5vw, 5rem);
  line-height: .95;
  color: var(--gold);
  transition-delay: .1s;
}
.hero-main {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(3.2rem, 14vw, 8rem);
  line-height: 1;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding-left: .12em;
  color: var(--ink);
  transition-delay: .2s;
}
.hero-surname {
  display: block;
  font-family: var(--display);
  font-size: clamp(.72rem, 2.4vw, 1rem);
  letter-spacing: .36em; text-transform: uppercase; font-weight: 500;
  color: var(--ink-soft);
  padding-left: .36em; margin-top: .6em;
  transition-delay: .3s;
}

/* --- Ornement : le rameau d'olivier ------------------------------
   La tige se trace d'abord, puis chaque brindille, puis la feuille
   qu'elle porte s'ouvre depuis sa base. C'est cet ordre-là qui donne
   l'impression d'une pousse plutôt que d'une apparition.
   ----------------------------------------------------------------- */
.ornament { width: min(84vw, 300px); transition-delay: .35s; }
.ornament svg, .footer-ornament { width: 100%; height: auto; overflow: visible; }

.branch-stem {
  fill: none; stroke: var(--gold); stroke-width: 1.5; opacity: .78;
  stroke-linecap: round;
  stroke-dasharray: 140; stroke-dashoffset: 140;
}
.branch-twig {
  fill: none; stroke: var(--gold); stroke-width: .9; opacity: .55;
  stroke-linecap: round;
  stroke-dasharray: 26; stroke-dashoffset: 26;
}

/* Le pivot est la BASE de la feuille, pas son centre : une feuille qui
   grandit depuis son milieu se décolle de sa brindille. */
.branch-leaf {
  opacity: 0;
  transform-box: fill-box; transform-origin: 0% 50%;
  transform: scale(.15) rotate(-14deg);
}
.branch-olive {
  opacity: 0;
  transform-box: fill-box; transform-origin: 50% 0%;
  transform: scale(.2);
}

.ornament.in .branch-stem,
.footer-ornament.in .branch-stem {
  animation: branch-draw 1.3s var(--ease-soft) .1s forwards;
}
.ornament.in .branch-twig,
.footer-ornament.in .branch-twig {
  animation: branch-draw .5s var(--ease-soft) .45s forwards;
}
.ornament.in .branch-leaf,
.footer-ornament.in .branch-leaf {
  animation: leaf-grow .6s var(--ease-soft) var(--d, .5s) forwards;
}
.ornament.in .branch-olive,
.footer-ornament.in .branch-olive {
  animation: olive-drop .55s var(--ease-soft) var(--d, 1.1s) forwards;
}

@keyframes branch-draw { to { stroke-dashoffset: 0; } }

@keyframes leaf-grow {
  0%   { opacity: 0;  transform: scale(.15) rotate(-14deg); }
  62%  { opacity: 1;  transform: scale(1.07) rotate(3deg); }
  100% { opacity: .92; transform: none; }
}

@keyframes olive-drop {
  0%   { opacity: 0; transform: scale(.2); }
  68%  { opacity: 1; transform: scale(1.12); }
  100% { opacity: 1; transform: none; }
}

/* --- Date --- */
.hero-date {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(1.2rem, 5vw, 2.8rem);
  margin-top: .4rem;
  transition-delay: .4s;
  position: relative;
}
.date-block { display: grid; justify-items: center; gap: .12em; }
.date-sep { width: 1px; align-self: stretch; background: linear-gradient(transparent, var(--gold-soft), transparent); }

.dow { font-style: italic; font-size: clamp(.8rem, 2vw, .95rem); color: var(--ink-soft); }
.dnum {
  font-family: var(--display);
  font-size: clamp(2.2rem, 7vw, 3.4rem);
  line-height: 1.05; letter-spacing: .04em;
}
.dnum.small { font-size: clamp(1.5rem, 5vw, 2.4rem); }
.dmonth {
  font-family: var(--display);
  font-size: clamp(.56rem, 1.7vw, .7rem);
  letter-spacing: .22em; text-transform: uppercase; font-weight: 500;
  color: var(--ink-soft); padding-left: .22em; max-width: 18ch;
}

/* --- Compte à rebours --- */
.countdown {
  display: flex; gap: clamp(.9rem, 4vw, 2.2rem);
  margin-top: clamp(1rem, 3vw, 1.8rem);
  padding: clamp(.8rem, 2vw, 1.1rem) clamp(1.1rem, 4vw, 2rem);
  border-top: 1px solid var(--hair);
  border-bottom: 1px solid var(--hair);
  transition-delay: .5s;
  position: relative;
}
.cd-cell { display: grid; justify-items: center; min-width: 3.4em; }
.cd-cell b {
  font-family: var(--display); font-weight: 400;
  font-size: clamp(1.2rem, 3.6vw, 1.6rem);
  line-height: 1.2; font-variant-numeric: tabular-nums;
}
.cd-cell i {
  font-style: normal; font-family: var(--display);
  font-size: clamp(.52rem, 1.4vw, .64rem);
  letter-spacing: .2em; text-transform: uppercase; font-weight: 500;
  color: var(--ink-soft); padding-left: .2em;
}

/* --- Boutons --- */
.hero-actions {
  margin-top: clamp(1.2rem, 3vw, 2rem);
  transition-delay: .6s;
  position: relative;
}

.btn {
  position: relative; overflow: hidden; isolation: isolate;
  display: inline-flex; align-items: center; justify-content: center;
  padding: .95em 2.1em .95em calc(2.1em + .26em);
  border: 1px solid var(--gold-soft);
  border-radius: 1px;
  background: var(--white);
  color: var(--ink);
  text-decoration: none; cursor: pointer;
  font-family: var(--display);
  font-size: clamp(.66rem, 1.6vw, .78rem);
  letter-spacing: .18em; text-transform: uppercase; font-weight: 500;
  transition: color .45s ease, border-color .45s ease, box-shadow .45s ease;
  box-shadow: 0 1px 2px rgba(90, 130, 160, .06);
}
.btn::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--ink);
  transform: scaleX(0); transform-origin: right;
  transition: transform .5s var(--ease-soft);
}
.btn:hover::before { transform: scaleX(1); transform-origin: left; }
.btn:hover { color: var(--white); border-color: var(--ink); box-shadow: 0 8px 20px -10px rgba(50, 68, 79, .5); }

.btn-solid { background: var(--ink); border-color: var(--ink); color: var(--white); }
.btn-solid::before { background: var(--gold); }
.btn-solid:hover { color: var(--white); border-color: var(--gold); }

.btn-wide { width: 100%; }
.btn[disabled] { opacity: .5; pointer-events: none; }

/* --- Indicateur de défilement --- */
.scroll-cue {
  position: absolute; bottom: clamp(1.4rem, 4vh, 2.4rem); left: 50%;
  transform: translateX(-50%);
  width: 1px; height: clamp(38px, 7vh, 58px);
  background: linear-gradient(var(--gold-soft), transparent);
  opacity: .7;
}
.scroll-cue span {
  position: absolute; top: 0; left: -2px;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--gold);
  animation: cue 2.8s var(--ease-soft) infinite;
}
@keyframes cue {
  0%   { transform: translateY(0); opacity: 0; }
  25%  { opacity: 1; }
  75%  { opacity: 1; }
  100% { transform: translateY(clamp(38px, 7vh, 58px)); opacity: 0; }
}

/* --- Sections --- */
.section {
  padding: clamp(4.5rem, 13vh, 8.5rem) var(--gutter-r) clamp(4.5rem, 13vh, 8.5rem) var(--gutter-l);
  position: relative;
}
.section-alt {
  background: linear-gradient(180deg, var(--paper-sky) 0%, var(--white) 100%);
  border-top: 1px solid var(--hair);
  border-bottom: 1px solid var(--hair);
}

.wrap { max-width: var(--measure); margin-inline: auto; text-align: center; }
.wrap-narrow { max-width: 40rem; }

.eyebrow {
  margin: 0 0 .9rem;
  font-family: var(--display);
  font-size: clamp(.54rem, 1.6vw, .68rem);
  letter-spacing: .34em; text-transform: uppercase;
  color: var(--gold); padding-left: .34em; font-weight: 500;
}
.section-title {
  margin: 0;
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.65rem, 4.6vw, 2.6rem);
  line-height: 1.22; letter-spacing: .04em;
  transition-delay: .08s;
}
.lede {
  margin: 1rem auto 0; max-width: 46ch;
  font-style: italic; color: var(--ink-soft);
  transition-delay: .14s;
}

/* --- Cartes --- */
.card-row {
  display: grid; gap: clamp(1rem, 2.4vw, 1.6rem);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  margin-top: clamp(2.4rem, 6vw, 3.6rem);
  text-align: left;
}
.card {
  position: relative;
  padding: clamp(1.9rem, 3.4vw, 2.5rem) clamp(1.4rem, 3vw, 1.9rem) clamp(1.5rem, 3vw, 2rem);
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 2px;
  box-shadow: 0 1px 2px rgba(90, 130, 160, .05), 0 18px 34px -26px rgba(70, 110, 145, .5);
  transition: transform .6s var(--ease-soft), box-shadow .6s var(--ease-soft),
              opacity 1s var(--ease-soft), border-color .6s ease;
}
.card:nth-child(2) { transition-delay: .1s; }
.card:nth-child(3) { transition-delay: .2s; }
.card:hover {
  transform: translateY(-5px);
  border-color: var(--gold-soft);
  box-shadow: 0 1px 2px rgba(90, 130, 160, .05), 0 30px 44px -28px rgba(70, 110, 145, .55);
}

/* Une petite colombe en guise de numéro */
.card-num {
  position: absolute; top: -.9em; left: clamp(1.4rem, 3vw, 1.9rem);
  display: inline-flex; align-items: center; gap: .5em;
  padding: 0 .6em;
  background: var(--card);
  font-family: var(--display);
  font-size: .7rem; letter-spacing: .24em;
  color: var(--gold);
}
.card-num .dove { width: 22px; color: var(--gold-soft); }
.card-num .dove .wf, .card-num .dove .wn { animation: none; }
.card-num .dove .wf { transform: rotate(-22deg); }
.card-num .dove .wn { transform: rotate(-32deg); }
.section-alt .card, .section-alt .card-num { background: var(--white); }

.card h3 {
  margin: 0 0 .6rem;
  font-family: var(--display); font-weight: 400;
  font-size: clamp(.68rem, 1.7vw, .8rem);
  letter-spacing: .2em; text-transform: uppercase; font-weight: 500;
  color: var(--ink-soft); padding-left: .2em;
}
.card p { margin: 0 0 1rem; line-height: 1.6; }
.card p:last-of-type { margin-bottom: .8rem; }

.link-arrow {
  display: inline-flex; align-items: center; gap: .5em;
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--gold);
  font-family: var(--display);
  font-size: clamp(.62rem, 1.5vw, .72rem);
  letter-spacing: .16em; text-transform: uppercase; font-weight: 500;
  text-decoration: none;
  transition: gap .35s var(--ease-soft), color .3s ease;
}
.link-arrow::after { content: "→"; letter-spacing: 0; }
.link-arrow:hover { color: var(--ink); gap: .85em; }

/* --- Programme --- */
.timeline {
  list-style: none;
  margin: clamp(2.4rem, 6vw, 3.6rem) auto 0;
  padding: 0; max-width: 34rem; text-align: left;
}
.timeline li {
  position: relative;
  display: grid; grid-template-columns: 5.2em 1.6em 1fr;
  align-items: baseline;
  padding-bottom: clamp(1.6rem, 4vw, 2.2rem);
}
.timeline li:last-child { padding-bottom: 0; }
.timeline li:nth-child(2) { transition-delay: .1s; }
.timeline li:nth-child(3) { transition-delay: .2s; }
.timeline li:nth-child(4) { transition-delay: .3s; }
.timeline li:nth-child(5) { transition-delay: .4s; }

.timeline li:not(:last-child)::before {
  content: ""; position: absolute;
  left: 6em; top: 1.1em; bottom: -.3em;
  width: 1px; transform: translateX(-.5px);
  background: linear-gradient(var(--gold-soft), rgba(217, 195, 147, .2));
}
.tl-time {
  font-family: var(--display);
  font-size: clamp(.78rem, 2vw, .9rem);
  letter-spacing: .1em; color: var(--gold); white-space: nowrap;
}
.tl-dot {
  position: relative; justify-self: center;
  width: 7px; height: 7px; border-radius: 50%;
  border: 1px solid var(--gold); background: var(--white);
  transform: translateY(-.25em);
}
.tl-body { display: grid; gap: .15em; }
.tl-body strong {
  font-family: var(--display); font-weight: 400;
  font-size: clamp(1.08rem, 2.8vw, 1.3rem); letter-spacing: .03em;
}
.tl-body em { font-style: italic; color: var(--ink-soft); font-size: .95em; }

/* ==================================================================
   Formulaire
   ================================================================== */
.rsvp-form {
  margin-top: clamp(2.4rem, 6vw, 3.4rem);
  display: grid; gap: 1.4rem; text-align: left;
  transition-delay: .2s;
}
.rsvp-form[hidden] { display: none; }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.field { display: grid; gap: .45rem; }
.field-row { display: grid; gap: 1.4rem; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }

.field label, .field-legend {
  font-family: var(--display);
  font-size: clamp(.62rem, 1.5vw, .72rem);
  letter-spacing: .18em; text-transform: uppercase; font-weight: 500;
  color: var(--ink-soft); padding-left: .18em;
}
.req { color: var(--gold); margin-left: .4em; letter-spacing: 0; }
.opt { color: var(--ink-faint); text-transform: none; letter-spacing: .05em; font-style: italic; }

.field input, .field textarea {
  width: 100%;
  padding: .8em .2em;
  border: 0; border-bottom: 1px solid var(--hair);
  border-radius: 0; background: transparent;
  color: var(--ink);
  font-family: var(--serif); font-weight: 400; font-size: 1.06rem;
  transition: border-color .35s ease, background-color .35s ease;
  -webkit-appearance: none;
}
.field textarea { resize: vertical; line-height: 1.6; padding-top: .6em; }
.field input:focus, .field textarea:focus {
  outline: none; border-bottom-color: var(--gold);
  background: rgba(244, 249, 252, .7);
}
.field input:-webkit-autofill { -webkit-text-fill-color: var(--ink); box-shadow: 0 0 0 60px var(--paper) inset; }

.err {
  min-height: 1.1em; font-size: .82rem; font-style: italic;
  color: #a8615f;
  opacity: 0; transform: translateY(-3px);
  transition: opacity .3s ease, transform .3s ease;
}
.err.show { opacity: 1; transform: none; }
.field.invalid input { border-bottom-color: #c98684; }

/* Choix de présence */
.choice { border: 0; padding: 0; margin: 0; display: grid; gap: .45rem;
          grid-template-areas: "l l" "a b"; }
.field-legend { grid-area: l; float: none; }
.choice-opt { display: block; cursor: pointer; }
.choice-opt input { position: absolute; opacity: 0; width: 0; height: 0; }

.choice-box {
  display: grid; gap: .1em;
  padding: 1.05em 1.2em;
  border: 1px solid var(--hair);
  border-radius: 2px; background: var(--white);
  transition: border-color .35s ease, background .35s ease,
              transform .35s var(--ease-soft), box-shadow .35s ease;
}
.choice-opt:hover .choice-box {
  border-color: var(--gold-soft); transform: translateY(-2px);
  box-shadow: 0 10px 22px -16px rgba(70, 110, 145, .6);
}
.choice-opt input:focus-visible + .choice-box { outline: 1px solid var(--gold); outline-offset: 3px; }
.choice-opt input:checked + .choice-box {
  border-color: var(--ink); background: var(--ink); color: var(--white);
}
.choice-opt input:checked + .choice-box .choice-sub { color: rgba(255, 255, 255, .7); }

.choice-title { font-family: var(--display); font-size: clamp(1rem, 2.4vw, 1.14rem); letter-spacing: .05em; }
.choice-sub { font-size: .88rem; font-style: italic; color: var(--ink-soft); }

.only-yes { display: grid; gap: 1.4rem; overflow: hidden; }
.only-yes[hidden] { display: none; }
.only-yes.animate { animation: unfold .55s var(--ease-soft); }
@keyframes unfold {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}

/* Interrupteur */
.switch { display: flex; align-items: center; gap: .9rem; cursor: pointer; padding: .3rem 0; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  position: relative; flex: none;
  width: 46px; height: 24px;
  border: 1px solid var(--hair); border-radius: 12px;
  background: var(--white);
  transition: background .35s ease, border-color .35s ease;
}
.switch-knob {
  position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--gold-soft);
  transition: transform .35s var(--ease-soft), background .35s ease;
}
.switch input:checked + .switch-track { background: var(--ink); border-color: var(--ink); }
.switch input:checked + .switch-track .switch-knob { transform: translateX(22px); background: var(--gold); }
.switch input:focus-visible + .switch-track { outline: 1px solid var(--gold); outline-offset: 3px; }
.switch-label { font-size: .98rem; color: var(--ink-soft); }

.form-status {
  margin: 0; min-height: 1.4em;
  font-style: italic; font-size: .92rem; text-align: center;
  color: #a8615f; opacity: 0; transition: opacity .3s ease;
}
.form-status.show { opacity: 1; }
#rsvp-submit.busy .btn-label { opacity: .55; }

/* Remerciement */
.thanks {
  margin-top: clamp(2.4rem, 6vw, 3.4rem);
  display: grid; justify-items: center; gap: .5rem;
  padding: clamp(2.4rem, 6vw, 3.4rem) clamp(1.4rem, 4vw, 2.4rem);
  border: 1px solid var(--hair);
  background: var(--white);
  box-shadow: 0 20px 40px -30px rgba(70, 110, 145, .5);
  animation: unfold .7s var(--ease-soft);
}
.thanks[hidden] { display: none; }

/* La colombe s'envole en guise de confirmation */
.thanks-dove { width: 84px; color: var(--gold); margin-bottom: .4rem; }
.thanks-dove .wf, .thanks-dove .wn { animation-duration: .9s; }
.thanks[hidden="false"] .thanks-dove, .thanks:not([hidden]) .thanks-dove {
  animation: take-off 1.4s var(--ease-soft);
}
@keyframes take-off {
  0%   { opacity: 0; transform: translate(-26px, 22px) scale(.8); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: none; }
}

.thanks h3 {
  margin: 0; font-family: var(--display); font-weight: 400;
  font-size: clamp(1.6rem, 4.4vw, 2.1rem);
}
.thanks p {
  margin: 0 0 .6rem; max-width: 34ch; text-align: center;
  font-style: italic; color: var(--ink-soft);
}

/* ==================================================================
   Pied de page
   ================================================================== */
.footer {
  display: grid; justify-items: center; gap: .3rem;
  padding: clamp(3.5rem, 9vh, 5.5rem) var(--gutter-r)
           max(clamp(2.5rem, 6vh, 3.5rem), env(safe-area-inset-bottom)) var(--gutter-l);
  text-align: center;
  background: linear-gradient(180deg, var(--white) 0%, var(--paper-sky) 60%, var(--sky-2) 100%);
  border-top: 1px solid var(--hair);
  position: relative; overflow: hidden;
}
.footer-doves { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.footer-doves .dove {
  position: absolute; color: var(--white);
  filter: drop-shadow(0 4px 10px rgba(86, 130, 165, .25));
  animation: glide var(--dur, 30s) linear infinite;
}

.footer-ornament { width: min(62vw, 220px); margin-bottom: 1rem; position: relative; }
.footer-script {
  margin: 0; font-family: var(--script);
  font-size: clamp(1.7rem, 5.4vw, 2.4rem);
  color: var(--gold); line-height: 1.2; position: relative;
}
.footer-parents {
  margin: .2rem 0 0; font-family: var(--display);
  font-size: clamp(.74rem, 2vw, .88rem);
  letter-spacing: .24em; text-transform: uppercase; font-weight: 500; padding-left: .24em;
  position: relative;
}
.footer-contact { margin: .8rem 0 0; font-style: italic; color: var(--ink-soft); font-size: .95rem; position: relative; }
.footer-contact a { color: var(--gold); text-decoration: none; border-bottom: 1px solid var(--gold-pale); }
.footer-name {
  margin: 1.6rem 0 0; font-family: var(--display);
  font-size: .64rem; letter-spacing: .22em; text-transform: uppercase; font-weight: 500;
  color: var(--ink-faint); padding-left: .36em; position: relative;
}

/* --- Téléphone en portrait : la carte occupe vraiment le cadre ------ */
@media (max-width: 30rem) and (orientation: portrait) {
  /* ATTENTION au produit. La lettre fait 88 % de l'enveloppe, puis elle
     est encore multipliée par --letter-zoom en se dépliant. À 92vw et
     1,14, le résultat atteignait 92 % de l'écran : aucune marge, et ça
     débordait au moindre écart d'un navigateur.
     Largeur finale = env-w x 0,88 x zoom. Ici : 90 x 0,88 x 1,05 = 83 %. */
  .envelope {
    --env-w: min(90vw, 430px, 58vh);
    --env-h: calc(var(--env-w) * .76);
    --letter-zoom: 1.05;
  }
  /* Le contenu suit l'agrandissement, sinon la carte grandit autour d'un
     texte resté petit et paraît vide à son tour. */
  .letter-inner  { padding: 6% 9%; gap: .28em; }
  .letter-title  { font-size: clamp(1.7rem, 9vw, 2.3rem); }
  .letter-name   { font-size: clamp(1.8rem, 10vw, 2.5rem); }
  .letter-kicker { font-size: .66rem; }
  .letter-date   { font-size: .82rem; }
  .letter-dove   { width: clamp(44px, 12vw, 60px); }
  .envelope      { --med: calc(var(--env-w) * .168); }
}

/* ==================================================================
   TÉLÉPHONE — alléger la composition
   ------------------------------------------------------------------
   Un calque fixe plein écran posé AU-DESSUS de tout oblige le
   compositeur à refondre la totalité de la fenêtre à chaque image. Il y
   en avait deux (le grain de papier et le halo), plus trois couches de
   nuages promises en mémoire vidéo par will-change. Sur un téléphone ça
   donne les deux symptômes observés : des zones qui restent blanches
   après un défilement rapide, et une chute à 20 images par seconde.
   Ces trois éléments sont purement décoratifs et invisibles sur un
   petit écran : on les retire là où ils coûtent.
   ================================================================== */
@media (max-width: 47.99rem) {
  .paper-grain,
  .vignette { display: none; }

  /* Sans promotion permanente : les nuages n'ont plus de flou à
     recalculer, un simple repaint suffit et la mémoire vidéo reste
     libre pour la 3D. */
  .cloud-layer { will-change: auto; }
  .skyscape    { will-change: auto; }
}

/* ==================================================================
   QUALITÉ BASSE
   Posée sur <html> par app.js quand l'affichage tient moins de 40
   images par seconde pendant deux secondes. On retire tout ce qui
   s'anime en permanence sans rien dire : le décor. On garde la
   typographie, les photos, la mise en page et le formulaire — c'est-à-
   dire le site. Mieux vaut un carton d'invitation net et fluide qu'un
   ciel animé qui saccade.
   ================================================================== */
html.perf-bas .skyscape,
html.perf-bas .paper-grain,
html.perf-bas .vignette,
html.perf-bas .dove-cue,
html.perf-bas .hero-doves,
html.perf-bas .footer-doves,
html.perf-bas .feather { display: none; }

/* Le fond du ciel reste, mais figé : c'est le mouvement qui coûte. */
html.perf-bas body { background: linear-gradient(180deg, var(--sky-1) 0%, var(--sky-2) 55%, var(--sky-3) 100%); }

/* Les apparitions au défilement deviennent instantanées. */
html.perf-bas .reveal { opacity: 1; transform: none; transition: none; }

/* ==================================================================
   Adaptations
   ================================================================== */
@media (max-height: 560px) {
  .envelope { --env-w: min(70vw, 320px, 88vh); }
  .prompt   { margin-top: clamp(1rem, 3vh, 1.6rem); }
  .skip     { bottom: max(.6rem, env(safe-area-inset-bottom)); }

  .letter-dove   { width: 32px; margin-bottom: .1em; }
  .letter-kicker { font-size: .54rem; letter-spacing: .26em; }
  .letter-title  { font-size: 1.4rem; }
  .letter-name   { font-size: 1.5rem; }
  .letter-rule   { margin: .3em 0 .2em; width: 34px; }
  .letter-date   { font-size: .7rem; }

  /* Écran bas — téléphone couché, petite fenêtre. Mêmes règles de
     centrage que partout ailleurs : la lettre revient au milieu au lieu
     de rester collée en haut. Seul le grossissement est réduit, sinon
     elle déborderait d'un écran de 375 px de haut. */
  /* Le grossissement passe par la variable, comme partout ailleurs :
     codé en dur ici, il échappait au garde-fou qui mesure et corrige. */
  .envelope { --letter-zoom: 1.1; }
  body.is-drawing   .envelope-stage { transform: translateY(3%); }
  body.is-unfolding .envelope-stage { transform: translateY(0); }
  body.is-drawing   .letter { transform: translateZ(12px) translateY(-38%); }
  body.is-unfolding .letter { transform: translateZ(12px) translateY(-2%) scale(var(--letter-zoom, 1.1)); }
  body.is-finishing .letter { transform: translateZ(12px) translateY(-6%) scale(2.2); }
}

@media (max-width: 34rem) {
  .choice { grid-template-areas: "l" "a" "b"; grid-template-columns: 1fr; }
  .hero-date { gap: 1.1rem; }
  .countdown { gap: .7rem; padding-inline: .6rem; }
  .cd-cell { min-width: 3.1em; }
  .timeline li { grid-template-columns: 4.4em 1.3em 1fr; }
  .timeline li:not(:last-child)::before { left: 5.05em; }
  .btn { padding-inline: 1.5em; padding-left: calc(1.5em + .26em); }
  .dove-mark { width: 130vw; opacity: .28; }
}

@media (min-width: 34.01rem) {
  .choice-opt:first-of-type { grid-area: a; }
  .choice-opt:last-of-type  { grid-area: b; }
  .choice { gap: .9rem; grid-template-columns: 1fr 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .doves, .feathers, .hero-doves, .footer-doves, .rays { display: none; }
}

@media print {
  .opening, .curtain, .paper-grain, .vignette, .scroll-cue,
  .countdown, .rsvp-form, .hero-actions,
  .hero-doves, .footer-doves, .dove-mark { display: none !important; }
  body { overflow: visible; background: #fff; }
  .invitation { opacity: 1 !important; visibility: visible !important; }
  .hero { min-height: auto; background: #fff; }
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* ==================================================================
   LA TRAVERSÉE DE L'ÉGLISE
   La section est haute, la scène y reste collée : c'est le défilement
   qui devient la caméra. Trois hauteurs d'écran donnent une avancée
   lente, sans qu'on ait l'impression de se battre avec la molette.
   ================================================================== */
.eglise3d {
  position: relative;
  height: 320vh;
  margin: 0;
  background: #1b2630;   /* la même que le ciel de la nef */
}

.e3-scene {
  position: sticky;
  top: 0;
  /* lvh, pas svh. svh est la hauteur AVEC la barre d'adresse visible :
     dès qu'elle se replie, la fenêtre grandit, la scène reste courte et
     le fond de la section apparaît en dessous — le bloc sombre. lvh
     prend toujours la plus grande des deux ; être un peu trop haut ne
     coûte rien puisque le débordement est masqué. */
  height: 100vh;
  height: 100lvh;
  overflow: hidden;
}

.e3-canvas {
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 1.1s var(--ease-soft);
}
.e3-prete .e3-canvas { opacity: 1; }

/* Le voile : la pierre est claire, le texte est clair. Sans ce fondu
   par le bas, la légende devient illisible dès qu'on croise un mur. */
.e3-voile {
  position: absolute;
  inset: auto 0 0 0;
  height: 58%;
  background: linear-gradient(180deg,
              rgba(14, 26, 34, 0) 0%,
              rgba(14, 26, 34, .34) 44%,
              rgba(14, 26, 34, .82) 100%);
  pointer-events: none;
}

.e3-texte {
  position: absolute;
  left: var(--gutter-l);
  right: var(--gutter-r);
  bottom: clamp(2.5rem, 7vh, 5rem);
  text-align: center;
  color: var(--paper-warm);
  pointer-events: none;
  /* Le texte s'installe pendant le premier tiers de la traversée. */
  opacity: clamp(0, calc((var(--e3-p, 0) - .06) * 7), 1);
  transform: translateY(calc((1 - clamp(0, calc((var(--e3-p, 0) - .06) * 7), 1)) * 1.4rem));
  transition: opacity .5s linear, transform .5s var(--ease-soft);
}

.e3-eyebrow {
  font-family: var(--display);
  font-size: clamp(.68rem, 1.5vw, .78rem);
  font-weight: 500;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--gold-soft);
  margin: 0 0 .85rem;
}

.e3-titre {
  font-family: var(--display);
  font-size: clamp(1.5rem, 4.4vw, 2.6rem);
  font-weight: 600;
  letter-spacing: .1em;
  line-height: 1.24;
  margin: 0;
  text-shadow: 0 2px 26px rgba(6, 14, 20, .55);
}

.e3-legende {
  font-family: var(--script);
  font-size: clamp(1.05rem, 2.6vw, 1.5rem);
  color: var(--gold-pale);
  margin: .9rem 0 0;
  text-shadow: 0 2px 20px rgba(6, 14, 20, .6);
}

/* L'attente : trois points, pas un sablier. Le fichier arrive pendant
   qu'on lit la section précédente — on ne devrait presque jamais les voir. */
.e3-attente {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  gap: .5rem;
  opacity: 0;
  transition: opacity .4s linear;
}
.e3-chargement .e3-attente { opacity: 1; }
.e3-attente span {
  width: .4rem;
  height: .4rem;
  border-radius: 50%;
  background: var(--gold-soft);
  animation: e3-pouls 1.25s var(--ease-wing) infinite;
}
.e3-attente span:nth-child(2) { animation-delay: .16s; }
.e3-attente span:nth-child(3) { animation-delay: .32s; }

@keyframes e3-pouls {
  0%, 100% { opacity: .22; transform: translateY(0); }
  45%      { opacity: 1;   transform: translateY(-.28rem); }
}

/* Si la 3D ne peut pas s'afficher, la section ne doit pas laisser un
   trou noir de trois écrans de haut. */
.e3-echec { height: auto; }
.e3-echec .e3-scene { height: auto; padding: clamp(4rem, 14vh, 8rem) 0; }
.e3-echec .e3-canvas,
.e3-echec .e3-attente { display: none; }
.e3-echec .e3-texte { position: static; opacity: 1; transform: none; }

/* Mouvement réduit : une seule image, et la section se raccourcit
   puisqu'il n'y a plus de course à parcourir. */
@media (prefers-reduced-motion: reduce) {
  .eglise3d { height: 100vh; height: 100lvh; }
  .e3-canvas { transition: none; }
  .e3-texte { opacity: 1; transform: none; transition: none; }
  .e3-attente span { animation: none; }
}

@media print {
  .eglise3d { display: none; }
}

/* L'aperçu forcé : il faut rendre à la section sa course, sinon il n'y
   a plus rien à faire défiler. */
@media (prefers-reduced-motion: reduce) {
  .eglise3d.e3-mouvement { height: 320vh; }
  .eglise3d.e3-mouvement .e3-canvas { transition: opacity 1.1s var(--ease-soft); }
  .eglise3d.e3-mouvement .e3-texte {
    opacity: clamp(0, calc((var(--e3-p, 0) - .06) * 7), 1);
    transform: translateY(calc((1 - clamp(0, calc((var(--e3-p, 0) - .06) * 7), 1)) * 1.4rem));
  }
}

/* ==================================================================
   LES COLOMBES EN VOLUME
   Une seule nappe, fixe, transparente, qui ne capte aucun clic. Elle
   passe devant l'enveloppe pendant l'ouverture — c'est là que la
   colombe s'échappe du cachet — puis repasse derrière le texte pour
   ne plus jamais gêner la lecture.
   ================================================================== */
.colombes3d {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  transition: opacity .8s var(--ease-soft);
}
.colombes3d.prete { opacity: 1; }

/* Tant que le cachet n'a pas cédé, la nappe est au-dessus de tout le
   théâtre de l'enveloppe (8000) mais sous le grain du papier (9000). */
body.is-locked .colombes3d,
body.is-opening .colombes3d,
body.is-drawing .colombes3d,
body.is-unfolding .colombes3d,
body.is-finishing .colombes3d { z-index: 8700; }

@media (prefers-reduced-motion: reduce) {
  .colombes3d { display: none; }
  .colombes3d.forcee { display: block; }
}

@media print { .colombes3d { display: none; } }

/* ==================================================================
   LA CONSIGNE DU HAUT
   Un bouton « Confirmer ma présence » qui saute en bas de page laisse
   croire qu'on a répondu. Une phrase qui explique où aller est plus
   honnête, et ne se rate pas.
   ================================================================== */
.hero-consigne {
  flex: 1 1 100%;
  max-width: 34rem;
  margin: 0 auto .35rem;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1rem, 2.4vw, 1.12rem);
  line-height: 1.62;
  color: var(--ink);
  text-align: center;
}
.hero-consigne strong {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(1.02rem, 2.6vw, 1.18rem);
  letter-spacing: .04em;
  margin-bottom: .34em;
}
.hero-consigne em {
  font-style: normal;
  font-weight: 600;
  color: var(--gold);
  border-bottom: 1px solid var(--gold-pale);
}

/* ==================================================================
   LE FORMULAIRE — pensé pour être rempli par quelqu'un de 80 ans
   Étiquettes lisibles plutôt que petites capitales espacées, champs
   qui ressemblent à des champs (un cadre, pas un simple trait), et
   des cibles assez grandes pour un doigt qui tremble un peu.
   ================================================================== */
.rsvp-form { gap: 1.85rem; }

.field { gap: .5rem; }

.field label, .field-legend {
  font-family: var(--serif);
  font-size: clamp(1rem, 2.4vw, 1.08rem);
  font-weight: 600;
  letter-spacing: .01em;
  text-transform: none;
  color: var(--ink);
  padding-left: 0;
}
.opt {
  color: var(--ink-soft);
  font-weight: 400;
  font-size: .94em;
  font-style: italic;
  text-transform: none;
  letter-spacing: 0;
}
.req { color: #a8615f; font-weight: 700; }

/* Un trait seul ne se lit pas comme « ici, on écrit ». Un cadre, si. */
.field input, .field textarea {
  padding: .78em .85em;
  min-height: 3.1rem;
  border: 1.5px solid #c3d3de;
  border-bottom: 1.5px solid #c3d3de;
  border-radius: 4px;
  background: var(--white);
  font-size: clamp(1.06rem, 2.6vw, 1.16rem);
  line-height: 1.4;
}
.field textarea { min-height: 6.5rem; }

.field input:focus, .field textarea:focus {
  border-color: var(--gold);
  background: #fffdf7;
  box-shadow: 0 0 0 3px rgba(168, 134, 63, .18);
}
.field.invalid input, .field.invalid textarea { border-color: #c07a78; }

.err { font-size: .95rem; font-style: normal; color: #96504e; }

/* ==================================================================
   LE CHOIX DE PRÉSENCE
   C'est LA question du formulaire. Elle doit se voir de loin, et on
   doit comprendre d'un coup d'œil laquelle des deux est cochée : une
   simple inversion de couleurs ne suffit pas si on voit mal.
   ================================================================== */
.choice { gap: .7rem; grid-template-areas: "l" "h" "a" "b"; }
.choice-aide {
  grid-area: h;
  margin: -.15rem 0 .1rem;
  font-family: var(--serif);
  font-size: .98rem;
  font-style: italic;
  color: var(--ink-soft);
}
.choice-opt:first-of-type { grid-area: a; }
.choice-opt:last-of-type  { grid-area: b; }

.choice-box {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.15em 1.25em;
  min-height: 4.6rem;
  border: 2px solid #c3d3de;
  border-radius: 6px;
  background: var(--white);
}

/* La pastille : vide, elle appelle le doigt ; pleine, elle ne laisse
   aucun doute sur ce qui a été choisi. */
.choice-mark {
  flex: 0 0 auto;
  width: 2.1rem; height: 2.1rem;
  border: 2px solid #b6c8d5;
  border-radius: 50%;
  background: var(--paper-sky);
  display: grid; place-items: center;
  transition: background .25s ease, border-color .25s ease, transform .25s var(--ease-soft);
}
.choice-mark svg { width: 1.35rem; height: 1.35rem; }
.choice-mark path {
  fill: none; stroke: var(--white); stroke-width: 2.8;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 24; stroke-dashoffset: 24;
  transition: stroke-dashoffset .3s var(--ease-soft);
}

.choice-texte { display: grid; gap: .18em; }
.choice-title {
  font-family: var(--display);
  font-size: clamp(1.12rem, 2.9vw, 1.3rem);
  font-weight: 600; letter-spacing: .03em;
  color: var(--ink);
}
.choice-sub {
  font-family: var(--serif);
  font-size: clamp(.95rem, 2.3vw, 1.02rem);
  font-style: normal; color: var(--ink-soft);
}

.choice-opt:hover .choice-box { border-color: var(--gold-soft); transform: none; box-shadow: none; }
.choice-opt input:focus-visible + .choice-box {
  outline: 3px solid var(--gold); outline-offset: 3px;
}

/* Coché : cadre épais, fond teinté, pastille pleine, coche tracée. */
.choice-opt input:checked + .choice-box {
  border-color: var(--olive);
  background: #f2f6ee;
  box-shadow: 0 0 0 3px rgba(144, 163, 127, .22);
}
.choice-opt input:checked + .choice-box .choice-title { color: #33452c; }
.choice-opt input:checked + .choice-box .choice-sub   { color: #5d7150; }
.choice-opt input:checked + .choice-box .choice-mark {
  background: var(--olive); border-color: var(--olive); transform: scale(1.06);
}
.choice-opt input:checked + .choice-box .choice-mark path { stroke-dashoffset: 0; }

/* « À regret » coché : la même clarté, mais sans le vert de la fête. */
.choice-opt:last-of-type input:checked + .choice-box {
  border-color: var(--ink-soft); background: #eef3f7;
  box-shadow: 0 0 0 3px rgba(85, 112, 127, .2);
}
.choice-opt:last-of-type input:checked + .choice-box .choice-title { color: var(--ink); }
.choice-opt:last-of-type input:checked + .choice-box .choice-sub   { color: var(--ink-soft); }
.choice-opt:last-of-type input:checked + .choice-box .choice-mark {
  background: var(--ink-soft); border-color: var(--ink-soft);
}

/* L'interrupteur du vin d'honneur suit la même règle de lisibilité. */
.switch-label {
  font-family: var(--serif);
  font-size: clamp(1rem, 2.4vw, 1.08rem);
  color: var(--ink);
}

#rsvp-submit { min-height: 3.6rem; font-size: 1.02rem; }

@media (min-width: 40rem) {
  .choice { grid-template-areas: "l l" "h h" "a b"; }
}


/* ==================================================================
   LES MÉDAILLONS DE LA LETTRE
   Quatre visages glissés dans les angles, comme des photos qu'on aurait
   posées sur le papier. Ils passent DERRIÈRE le texte : devant, ils
   rendraient le titre illisible dès qu'on est sur un petit écran.
   ================================================================== */
.letter-photos {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
}

.lp {
  position: absolute;
  width: var(--med);
  height: var(--med);
  border-radius: 50%;
  overflow: hidden;
  /* Un liseré ivoire puis un cheveu d'or : c'est ce qui les détache du
     papier sans les cerner d'un trait dur. */
  box-shadow: 0 0 0 3px #fffdf8,
              0 0 0 4px rgba(194, 160, 94, .38),
              0 6px 14px -8px rgba(70, 110, 145, .55);
  opacity: 0;
  transform: scale(.4) rotate(-14deg);
}
.lp img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Le diamètre suit la taille de l'enveloppe : en dur, il serait énorme
   sur un téléphone et minuscule sur un écran large. */
.envelope { --med: calc(var(--env-w) * .152); }

.lp-1 { left: 2.5%;  top: 5%;    }
.lp-2 { right: 2%;   top: 13%;   }
.lp-3 { left: 5%;    bottom: 6%; }
.lp-4 { right: 4.5%; bottom: 3%; }

/* Elles arrivent pendant que la lettre se déplie, l'une après l'autre. */
body.is-unfolding .lp { animation: lp-pose .72s var(--ease-soft) forwards; }
body.is-unfolding .lp-1 { animation-delay: .10s; }
body.is-unfolding .lp-2 { animation-delay: .24s; }
body.is-unfolding .lp-3 { animation-delay: .38s; }
body.is-unfolding .lp-4 { animation-delay: .52s; }
body.is-finishing .lp, body.is-open .lp { opacity: 1; transform: none; }

@keyframes lp-pose {
  0%   { opacity: 0; transform: scale(.4) rotate(-14deg); }
  64%  { opacity: 1; transform: scale(1.07) rotate(3deg); }
  100% { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .lp { opacity: 1; transform: none; animation: none; }
}

/* ==================================================================
   L'ALBUM
   Trois colonnes décalées plutôt qu'une grille alignée : le décalage
   donne le rythme d'une page de magazine. Chaque photo est montée comme
   un tirage — une marge blanche, un cheveu, une ombre portée basse.
   ================================================================== */
.section-album { background: var(--paper-warm); }

.album {
  margin-top: clamp(2.4rem, 6vw, 3.6rem);
  display: grid;
  gap: clamp(1.1rem, 3vw, 2rem);
  grid-template-columns: 1fr;
  justify-items: center;
}

.album-item {
  margin: 0;
  width: min(100%, 26rem);
  padding: .6rem .6rem 1.35rem;      /* la marge basse du tirage */
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: 2px;
  box-shadow: 0 18px 34px -26px rgba(60, 100, 130, .7);
}
.album-item img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 1px;
}

/* Sur téléphone, un décalage alterné évite l'empilement de catalogue. */
@media (max-width: 47.99rem) {
  .album-item:nth-child(odd)  { justify-self: start; }
  .album-item:nth-child(even) { justify-self: end; }
  .album-item { width: min(88%, 24rem); }
}

@media (min-width: 48rem) {
  .album {
    grid-template-columns: repeat(3, 1fr);
    align-items: start;
    gap: clamp(1.4rem, 2.6vw, 2.2rem);
  }
  .album-item { width: 100%; }
  /* La colonne du milieu descend, la troisième remonte : c'est ce
     déséquilibre-là qui fait respirer la page. */
  .album-item:nth-child(3n + 2) { margin-top: clamp(2rem, 5vw, 4.5rem); }
  .album-item:nth-child(3n)     { margin-top: clamp(-1rem, -1.5vw, 0rem); }
}

@media print {
  .album-item { break-inside: avoid; box-shadow: none; }
}

/* Les colombes s'effacent au-dessus des photos : blanches sur un
   portrait, elles passeraient pour un défaut d'affichage. */
.colombes3d.en-retrait { opacity: .1; }

/* Une dernière photo seule sur sa ligne : on la centre sous la grille
   plutôt que de la laisser calée à gauche, où elle aurait l'air d'un
   reste. La règle vaut pour tout album dont le nombre n'est pas
   multiple de trois. */
@media (min-width: 48rem) {
  .album-item:last-child:nth-child(3n + 1) {
    grid-column: 2;
    margin-top: clamp(1.4rem, 3.5vw, 3rem);
  }
}

/* Le reste du formulaire, revele une fois la question repondue. Meme
   animation de depliage que le bloc « combien serez-vous ». */
.suite-form { display: grid; gap: 1.85rem; }
.suite-form[hidden] { display: none; }
.suite-form.animate { animation: unfold .6s var(--ease-soft); }
