/* ==========================================================================
   L'enveloppe : poche basse, rabat pentagonal, dentelle, sceau.

   Découpage en profondeur (z-index) :
     1  la carte d'invitation      (card.css)
     2  la poche basse             — le bas de l'enveloppe, qui coulisse
     3  la couche de grattage      (reveal.css)
     4  le rabat                   — bascule autour de son bord haut
     6  le sceau et l'accroche

   Le rabat et la poche se partagent la même ligne de pli en V, décrite par
   --v-cote / --v-pointe dans tokens.css.
   ========================================================================== */

/* ---- Poche basse : tout ce qui est SOUS la ligne du V ------------------ */
.pocket {
  position: absolute;
  inset: 0;
  z-index: 2;

  background:
    var(--tex-enveloppe) 0 0 / 300px 300px,
    linear-gradient(180deg, var(--enveloppe), var(--enveloppe-ombre));

  clip-path: polygon(
    0    var(--v-cote-h),
    50%  var(--v-pointe-h),
    100% var(--v-cote-h),
    100% 100%,
    0    100%
  );
}

/* ---- Rabat : couvre tout le haut, charnière sur le bord haut de l'écran -- */
.flap {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--v-pointe-h);
  z-index: 4;

  transform-origin: top center;   /* la charnière : hors champ, donc invisible */
  backface-visibility: hidden;
  will-change: transform, filter; /* GSAP anime les deux à chaque image */
}

/* Le pentagone : un rectangle plein en haut, prolongé par le triangle qui
   descend jusqu'à la pointe. Répété tel quel par .lit, .sheen et .face::after,
   qui doivent épouser exactement la même silhouette. */
.flap .face,
.flap .lit,
.flap .sheen,
.flap .face::after {
  clip-path: polygon(
    0    0,
    100% 0,
    100% var(--pli),
    50%  100%,
    0    var(--pli)
  );
}

.flap .face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  background:
    var(--tex-enveloppe) 0 0 / 300px 300px,
    linear-gradient(180deg, #e9ecef, var(--enveloppe-ombre));
}

/* Légère occlusion permanente le long des arêtes du V : le creux du pli. */
.flap .face::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 60% at 50% 100%, rgba(0, 0, 0, .22), transparent 60%);
}

/* ---- Dentelle : un ruban par bras du V ---------------------------------
   L'asset est un ruban horizontal, pas un V. On en pose un exemplaire sur
   chaque bras, tourné de l'angle du bras et répété sur sa longueur.

   C'est ce qui rend le motif indépendant de la taille de l'écran : la
   hauteur est fixée, la largeur laissée en `auto`, donc la dentelle garde
   toujours ses proportions et seule la longueur répétée varie. Une image en
   V étirée dans sa boîte, elle, était écrasée d'un facteur 3,8 en 1920×1080.

   L'angle et la longueur ne peuvent pas se calculer en CSS — `calc()` ne
   sait ni faire d'arc tangente, ni composer une hypoténuse entre vw et vh.
   Ils sont publiés par assets/invitation/js/lace.js en --bras-angle / --bras-longueur /
   --pli-y.

   L'asset doit être recadré au plus juste sur son motif : c'est son bord BAS
   (l'arête droite du ruban) qui se pose sur la ligne de pli. Une marge
   transparente en bas décollerait la dentelle de l'arête.
   `tools/prepare-lace.py` s'en charge, et met la bande en miroir pour
   qu'elle se répète sans couture. */
/* Pas de découpe pentagonale ici, contrairement aux autres calques du rabat :
   une dentelle collée sur une arête la déborde, elle ne s'arrête pas net
   dessus. C'est ce qui donnait une coupure franche aux extrémités. */
.lace-band {
  position: absolute;
  inset: 0;
}

.lace-arm {
  position: absolute;
  /* L'ancrage part du coin du V, puis se décale de --dentelle-ancre-*.
     Ce décalage-ci agit AVANT la rotation, donc dans le repère de l'écran :
     x pousse le ruban vers l'extérieur, y le fait descendre. C'est ce qui
     permet de poser la dentelle légèrement en dessous de l'arête plutôt que
     dessus. */
  top: calc(var(--pli-y) - var(--dentelle-hauteur) + var(--dentelle-ancre-y));
  height: var(--dentelle-hauteur);
  background: var(--tex-dentelle) left bottom / auto 100% repeat-x;

  /* Recul du ruban vers l'EXTÉRIEUR seulement, jamais au-delà de la pointe :
     rallonger les deux bouts ferait dépasser chaque bras de l'autre côté du
     V, et les deux se croiseraient en X au lieu de se rejoindre.

     La coupe du ruban est perpendiculaire à l'arête : pour qu'elle sorte de
     l'écran il faut reculer d'au moins hauteur × tan(angle), sinon le coin
     haut de la coupe reste visible. --dentelle-rallonge est le débord voulu
     en plus de ce strict nécessaire. */
  --bras-recul: calc(
    var(--dentelle-hauteur) * var(--bras-tangente) + var(--dentelle-rallonge));

  /* On recule d'un côté, donc on rallonge d'autant : l'autre bout retombe
     exactement sur la pointe du V. Le dernier terme rattrape l'écartement de
     l'ancrage, qui allonge le trajet jusqu'à l'axe de ancre-x / cos(angle). */
  width: calc(
    var(--bras-longueur)
    + var(--bras-recul)
    + var(--dentelle-ancre-x) * var(--bras-secante));
}

/* Chaque bras pivote autour du coin du V qui l'ancre : coin bas-gauche pour
   le bras gauche, bas-droit pour le droit.

   Le `translate` s'applique APRÈS la rotation, donc dans le repère du bras :
   son x glisse le long de l'arête (la rallonge), son y perpendiculairement
   (le décalage, qui fait descendre la bande sous la ligne de pli). */
.lace-arm--left {
  left: calc(-1 * var(--dentelle-ancre-x));
  transform-origin: 0 100%;
  transform:
    rotate(var(--bras-angle))
    translate(calc(-1 * var(--bras-recul)), var(--dentelle-decalage));
}

.lace-arm--right {
  right: calc(-1 * var(--dentelle-ancre-x));
  transform-origin: 100% 100%;
  transform:
    rotate(calc(-1 * var(--bras-angle)))
    translate(var(--bras-recul), var(--dentelle-decalage));
  /* Motif lu depuis l'autre extrémité, pour que les deux bras se répondent
     au lieu de répéter la même amorce. */
  background-position: right bottom;
}

/* ---- Éclairage simulé, piloté image par image depuis assets/invitation/js/lighting.js ---
   Deux calques superposés au rabat, invisibles au repos (opacity 0). */
.flap .lit,
.flap .sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
}

/* Ombrage diffus : assombrit la face à mesure qu'elle s'incline hors de la
   lumière. `multiply` pour éteindre sans laver la texture. */
.flap .lit {
  background: #0b0e14;
  mix-blend-mode: multiply;
}

/* Reflet spéculaire : la bande de lumière que le papier accroche au passage.
   `screen` pour éclaircir sans délaver. */
.flap .sheen {
  background: linear-gradient(112deg,
    transparent 34%,
    rgba(255, 255, 255, .85) 50%,
    transparent 66%);
  mix-blend-mode: screen;
}

/* ---- Sceau : une galette de cire posée sur la pointe du rabat ------------
   C'est le déclencheur, et c'est aussi le seul objet de la scène qui ait du
   VOLUME. Il ne se contente pas de tourner avec le rabat : il s'en décolle.

   Empilement, du papier vers le lecteur :
     .cast   l'ombre portée sur le papier
     .rim    la tranche de cire, restée sur le papier
     .wax    le corps, qui s'élève de --sceau-eleve
       .disc   la face, et ses calques de relief

   Aucune de ces couches n'est peinte à l'avance : `--sceau-*` est réécrit à
   chaque image par assets/invitation/js/lighting.js, à partir de la direction
   de la lumière dans le repère du rabat. C'est ce qui fait tourner la cire
   comme un objet et non comme une image.

   Les valeurs posées ici ne sont qu'un REPLI, le temps que le module
   s'exécute — l'écran de chargement couvre largement cet instant. Le modèle
   fait autorité, elles ne s'accordent pas à la main. */
.seal {
  position: absolute;
  left: 50%;
  top: 98%;
  z-index: 6;

  /* Pas de `transform` ici : c'est GSAP qui possède celui du sceau, centrage
     compris (xPercent/yPercent), pour pouvoir y composer la respiration sans
     que les deux s'écrasent mutuellement. Le relief passe donc par des jetons
     et jamais par le `transform` de .seal. */

  width: var(--sceau-taille);
  height: var(--sceau-taille);
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;

  --sceau-eleve: 0px;
  --sceau-relief-x: -1.44px;
  --sceau-relief-y: -1.44px;
  --sceau-lustre-x: 42%;
  --sceau-lustre-y: 42%;
  --sceau-lustre-force: .54;
  --sceau-nuit: .07;
  --sceau-tranche-jour: .4;
  --sceau-ombre-x: 5.4px;
  --sceau-ombre-y: 5.4px;
  --sceau-ombre-flou: 5.3px;
  --sceau-ombre-force: .43;
}

.seal .cast,
.seal .rim,
.seal .wax {
  position: absolute;
  inset: 0;
}

/* Ombre portée de la galette sur le papier — celle du sceau, pas celle du
   rabat, qui est un `drop-shadow` posé sur .flap.

   La texture sert de pochoir : `brightness(0)` la noircit en gardant sa
   silhouette, `blur` l'étale. Un `mask` ne conviendrait pas — il s'applique
   APRÈS le filtre, et recouperait net le flou sur le contour. */
.seal .cast {
  background: var(--tex-sceau) center / contain no-repeat;
  transform: translate(var(--sceau-ombre-x), var(--sceau-ombre-y));
  filter: brightness(0) blur(var(--sceau-ombre-flou));
  opacity: var(--sceau-ombre-force);
}

/* La tranche : la MÊME silhouette, restée sur le papier pendant que la face
   s'en décolle. Deux galettes identiques décalées suffisent à faire un
   cylindre — le croissant qui dépasse sous la face EST l'épaisseur vue de
   biais, et il grandit tout seul à mesure que le rabat se lève.

   Elle s'éclaire en fin d'ouverture : c'est une paroi verticale, et la
   lumière finit par passer dessous. */
.seal .rim {
  background: var(--tex-sceau) center / contain no-repeat;
  filter: brightness(var(--sceau-tranche-jour)) saturate(1.1);
}

/* Le corps de cire, qui se décolle du papier.

   `--sceau-eleve` est mesuré DANS LE PLAN DU RABAT : la rotation le
   raccourcit d'elle-même de cos(angle) à l'écran, ce qui redonne exactement
   l'épaisseur × sin(angle) d'un objet posé dessus. C'est de là que vient la
   parallaxe, sans qu'on ait à sortir le sceau de son plan — impossible ici,
   le `filter` de .flap aplatit toute perspective 3D de ses descendants. */
.seal .wax {
  transform: translateY(calc(-1 * var(--sceau-eleve)));
}

/* La face de cire. Elle porte la texture — c'est elle que relit
   assets/invitation/js/loader.js pour précharger --tex-sceau — et le `scale`
   du galbe, qui la laisse un peu en retrait de la tranche. */
.seal .disc {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--tex-sceau) center / contain no-repeat;

  /* Le survol agit sur .disc et non sur .seal : GSAP écrit dans le
     `transform` de .seal, les deux se marcheraient dessus. */
  transform: scale(var(--sceau-galbe));
  transition: transform .25s var(--ease-doux);
}
.seal:hover .disc { transform: scale(calc(var(--sceau-galbe) * 1.05)); }

.seal .emboss,
.seal .gloss,
.seal .shade {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Estampage du monogramme.

   Deux copies de la texture, décalées en sens INVERSE le long de la lumière :
   sur un aplat elles se compensent — l'une éclaircit autant que l'autre
   assombrit —, mais sur une arête du monogramme elles se décalent l'une par
   rapport à l'autre et laissent un liseré clair du côté de la lumière, sombre
   de l'autre. C'est un estampage, et il TOURNE : le décalage est la
   composante tangente de la source, donc nul de face et maximal quand elle
   rase la cire.

   Le pochoir est indispensable : sans lui, le décalage ferait déborder les
   deux copies sur le papier, l'une l'éclaircissant et l'autre l'assombrissant
   d'un côté et de l'autre du sceau. Le préfixe reste utile — c'est la seule
   propriété du projet dont Safari a longtemps exigé sa version à lui. */
.seal .emboss,
.seal .gloss {
  -webkit-mask: var(--tex-sceau) center / contain no-repeat;
          mask: var(--tex-sceau) center / contain no-repeat;
}

.seal .emboss {
  background: var(--tex-sceau) center / contain no-repeat;
  opacity: var(--sceau-relief-force);
}
.seal .emboss--light {
  transform: translate(var(--sceau-relief-x), var(--sceau-relief-y));
  mix-blend-mode: screen;
}
.seal .emboss--dark {
  transform: translate(calc(-1 * var(--sceau-relief-x)), calc(-1 * var(--sceau-relief-y)));
  mix-blend-mode: multiply;
}

/* Lustre : le reflet large que renvoie un dôme de cire polie. Son centre se
   porte du côté d'où vient la lumière, et il faiblit quand elle rase. */
.seal .gloss {
  background: radial-gradient(ellipse 38% 38% at var(--sceau-lustre-x) var(--sceau-lustre-y),
    rgba(255, 255, 255, .8),
    rgba(255, 255, 255, .22) 46%,
    transparent 78%);
  mix-blend-mode: screen;
  opacity: var(--sceau-lustre-force);
}

/* Ombrage diffus de la face, pendant de `.flap .lit` pour la cire. Pas de
   pochoir ici : la texture noircie en tient lieu, et son alpha borne
   l'assombrissement à la silhouette. */
.seal .shade {
  background: var(--tex-sceau) center / contain no-repeat;
  filter: brightness(0);
  mix-blend-mode: multiply;
  opacity: var(--sceau-nuit);
}

.seal:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 4px;
  border-radius: 50%;
}

/* ---- Accroche ---------------------------------------------------------- */
.hint {
  position: absolute;
  left: 0;
  right: 0;
  top: 62%;
  z-index: 6;
  text-align: center;

  color: #7a5561;
  font-size: 11px;
  letter-spacing: .34em;
  text-transform: uppercase;
}
