/* layout.css — primitives visuelles partagées.
   Chaque primitive est pilotée par des custom properties, pour être recolorée
   sur place par la section qui l'utilise. */

/* ---------- Bords festonnés ----------
   Bandeau de bosses jointives qui déborde de la section porteuse et se peint sur
   sa voisine, dans la couleur de la section porteuse (--scallop-color).

   ⚠ CORRIGÉ le 2026-08-11. La version d'origine rendait un demi-disque SEUL, d'une
   hauteur de --scallop-size/2. C'était faux : la source empile un demi-disque de
   rayon --scallop-size/2 SUR une bande pleine, pour une hauteur totale
   --scallop-h ≈ 0,7874 × --scallop-size. Relevé aux trois paliers sur #statistics
   et #benefits, qui portent la MÊME forme (96 × 75,59 / 68,27 × 53,76 / 26 × 20,47,
   remontées 61 / 40 / 20). La tâche 9 avait dû reconstruire la forme localement
   faute d'une primitive juste ; cette version la remplace, et #benefits l'utilise.

   Le bandeau ne remonte pas de toute sa hauteur : --scallop-up < --scallop-h, donc
   il mord sur sa propre section. Une section qui porte cette classe ne doit donc
   PAS passer en overflow: hidden/clip, sous peine de rogner son propre feston.

   Deux couches de background plutôt qu'un masque : la bande pleine et les bosses
   sont deux formes distinctes, et un masque unique les rendrait solidaires. */
.scallop-top,
.scallop-bottom { position: relative; }

.scallop-top::before,
.scallop-bottom::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: var(--scallop-h);
  pointer-events: none;
  background-repeat: repeat-x, no-repeat;
  /* ⚠ CORRIGÉ le 2026-08-11 par la tâche 13. Les deux couches se rejoignaient
     EXACTEMENT à --scallop-size/2 (48px à 1440). Le bandeau étant posé à un
     `top` fractionnaire (les sections commencent à 4115,63 / 8649,13 / 9950,13),
     la rangée de pixels à cheval sur cette jointure mélangeait la couche haute
     (transparente à cet endroit, entre les bosses) et la couche basse : d'où un
     filet d'UN pixel sur toute la largeur, mesuré aux canaux 107·53·23 sur la
     totalité de la ligne, soit 38 % d'orange sur l'olive — et la source n'a
     rien de tel (comparaison de pixels sur les deux rendus au même offset).
     Les deux couches se CHEVAUCHENT donc maintenant d'un pixel : la bande
     monte 1px plus haut, sous les bosses, dans sa propre couleur. À cette
     hauteur l'écart géométrique entre deux bosses voisines vaut 0,02px, le
     chevauchement est donc invisible partout ailleurs.
     Défaut PRÉEXISTANT de la primitive, pas introduit par la tâche 13 : il
     touchait aussi #benefits (tâche 9) et #comparison (tâche 12), qui l'ont
     l'une et l'autre laissé passer. Les trois jointures ont été re-vérifiées
     après correction. */
  background-size:
    var(--scallop-size) calc(var(--scallop-size) / 2),
    100% calc(var(--scallop-h) - var(--scallop-size) / 2 + 1px);
}

.scallop-top::before {
  top: calc(var(--scallop-up) * -1);
  /* bosses en haut, bande pleine sous elles */
  background-image:
    radial-gradient(circle farthest-side at 50% 100%,
      var(--scallop-color, var(--c-cream)) 99.5%, transparent 100%),
    linear-gradient(var(--scallop-color, var(--c-cream)) 0 0);
  background-position: 0 0, 0 100%;
}

/* ⚠ NON CALIBRÉ. Aucune section ne l'utilise à ce jour et aucun feston de bas de
   section n'a été relevé sur la source : c'est le miroir vertical de .scallop-top,
   pas une mesure. La première section qui en aura besoin doit remesurer la forme
   réelle avant de s'y fier (règle A₁.0 — une primitive n'est pas un passe-partout). */
.scallop-bottom::after {
  bottom: calc(var(--scallop-up) * -1);
  /* bande pleine en haut, bosses sous elle */
  background-image:
    radial-gradient(circle farthest-side at 50% 0%,
      var(--scallop-color, var(--c-cream)) 99.5%, transparent 100%),
    linear-gradient(var(--scallop-color, var(--c-cream)) 0 0);
  background-position: 0 100%, 0 0;
}

/* ---------- Diviseur en feuilles ----------
   Bord gauche découpé en une pile de vingt formes en "feuille" (pointe effilée
   vers la gauche, côté droit plat aux deux coins arrondis), qui débordent du
   bloc vers son voisin de gauche — même technique de débord que .scallop-top
   (pseudo-élément décalé de 100% de sa propre taille hors du bloc porteur).

   Géométrie relevée directement sur le SVG inline de la source (comparatif,
   bord gauche de la colonne "Croque Studio", `viewBox 0 0 28 641`, 20 motifs
   identiques espacés de 32 unités viewBox). Le motif ci-dessous est le premier
   de la source, tel quel : il tient déjà entièrement dans `y ∈ [2,08 ; 30,29]`,
   donc dans une tuile `viewBox="0 0 28 32"` sans décalage ni découpe — vérifié
   par calcul avant écriture (un décalage de -16 essayé d'abord, comme suggéré
   par une première piste, faisait sortir la courbe de la tuile à ses deux
   extrémités ; abandonné). Non pas un zigzag (dents triangulaires) : un
   `conic-gradient` ne peut pas produire cette courbe, d'où le passage à un
   masque SVG.

   Le débord visible n'est PAS la largeur totale du SVG source (41px, le
   `viewBox` fait 28 unités de large). Comparaison DOM directe le 2026-08-10 :
   le `<svg>` source (x=692, largeur 40,8px) déborde de 12,8px sur la colonne
   jaune elle-même (bord réel entre colonnes mesuré à x=720 via les deux
   `<div>` de couleur, donc à 19,2 unités viewBox depuis la gauche du SVG, pas
   28) — cette portion se superpose au jaune et y est invisible. Rejouer un
   masque "à fond perdu" (bord droit du motif = 28 = bord du bloc) fait donc
   déborder la pointe ~60% plus loin que la source (confirmé par comparaison
   de pixels, voir rapport). Le motif est donc rogné à 60% de sa largeur avant
   d'être décalé, pour que seule la partie réellement visible dans la source
   dépasse. */
.leaf-divider {
  position: relative;
}

.leaf-divider::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  /* ⚠ CORRIGÉ le 2026-08-11, par la tâche 12 comme la tâche 3 l'avait prescrit.
     La largeur visible était `--leaf-size * .6`, un rapport dérivé de mesures.
     La source n'emploie AUCUN rapport : elle pose un débord en pixels ronds,
     choisi à la main, qui ne suit pas l'échelle du motif —
         1440 : 28px   ·   1024 : 25px   ·   809 : 28px   ·   390 : 28px
     Le rapport `.6` était juste à 1440 (erreur -0,05 px) mais trop grand de
     3,5 % à 1024 (débord +0,91 px). Mesuré par l'implémenteur puis re-mesuré
     indépendamment par le relecteur, aux quatre paliers, comptage de pixels à
     l'appui. Le modèle en pixels est donc à la fois plus juste et plus fidèle
     à la façon dont la source est construite.
     Le token n'est employé que par cette primitive, elle-même employée par la
     seule section `#comparison`. */
  width: var(--leaf-overhang);
  transform: translateX(-100%);
  background-color: var(--leaf-color, var(--c-yellow-pale));
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 32'%3E%3Cpath d='M6.0777 19.6546C3.39044 18.119 3.39044 14.2442 6.0777 12.7087L22.0154 3.60139C24.6821 2.0776 28 4.00307 28 7.07436L28 25.2889C28 28.3602 24.6821 30.2857 22.0154 28.7619L6.0777 19.6546Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 32'%3E%3Cpath d='M6.0777 19.6546C3.39044 18.119 3.39044 14.2442 6.0777 12.7087L22.0154 3.60139C24.6821 2.0776 28 4.00307 28 7.07436L28 25.2889C28 28.3602 24.6821 30.2857 22.0154 28.7619L6.0777 19.6546Z'/%3E%3C/svg%3E");
  -webkit-mask-repeat: repeat-y;
  mask-repeat: repeat-y;
  /* La tuile de masque garde sa largeur pleine (28 unités viewBox, l'échelle
     qui donne le bon pas vertical) même si le bloc ne fait que 60% de cette
     largeur : le débordement du masque au-delà du bloc est simplement rogné,
     ce qui masque la portion du motif qui, dans la source, se superpose à la
     colonne jaune elle-même sans y être visible. */
  -webkit-mask-size: calc(var(--leaf-size) * 28 / 32) var(--leaf-size);
  mask-size: calc(var(--leaf-size) * 28 / 32) var(--leaf-size);
  -webkit-mask-position: left top;
  mask-position: left top;
  pointer-events: none;
}

/* ---------- Bouton à ombre dure ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-7);
  border-radius: var(--r-pill);
  background: var(--btn-bg, var(--c-yellow));
  color: var(--c-ink);
  /* Poppins 600 22px, interlettrage -0.88px — mesuré sur la source (le CTA
     du hero). `.btn` est une primitive partagée (tâche 3) donc ce correctif
     s'applique à tout appel futur, ce qui est voulu : c'était un défaut de la
     primitive, pas du hero seul. */
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: var(--fs-22);
  letter-spacing: -0.88px;
  line-height: 1;
  box-shadow: var(--shadow-hard-lg);
  transition:
    transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}

.btn--yellow { --btn-bg: var(--c-yellow); }

.btn:hover,
.btn:focus-visible {
  transform: translate(4px, 4px);
  box-shadow: var(--shadow-hard-press);
}

.btn:active {
  transform: translate(6px, 6px);
  box-shadow: 0 0 0 var(--c-ink);
}

/* ---------- Sticker ----------
   Pastille organique inclinée. La forme du blob vient d'un border-radius
   asymétrique ; les stickers dont la source utilise un SVG dédié le passeront
   en --sticker-shape (mask-image). */
.sticker {
  display: inline-grid;
  place-items: center;
  padding: var(--sp-3) var(--sp-5);
  background: var(--sticker-bg, var(--c-mint));
  color: var(--c-ink);
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: var(--fs-17);
  line-height: 1;
  white-space: nowrap;
  border-radius: 46% 54% 58% 42% / 52% 44% 56% 48%;
  transform: rotate(var(--sticker-rotate, 0deg));
}

.sticker--masked {
  background: var(--sticker-bg, var(--c-mint));
  border-radius: 0;
  -webkit-mask: var(--sticker-shape) no-repeat center / contain;
  mask: var(--sticker-shape) no-repeat center / contain;
}

/* ---------- Explosion étoilée ----------
   Halo à pointes derrière les illustrations. Généré en conic-gradient masqué,
   pour éviter un SVG par couleur. */
.starburst {
  /* --starburst-step = 360deg / nombre de pointes. 11.25deg -> 32 pointes.
     Relever le nombre réel sur la source et ajuster par section si besoin. */
  --starburst-step: 11.25deg;
  background: var(--starburst-color, var(--c-purple));
  -webkit-mask-image: repeating-conic-gradient(
    from 0deg,
    black 0deg calc(var(--starburst-step) / 2),
    transparent calc(var(--starburst-step) / 2) var(--starburst-step));
  mask-image: repeating-conic-gradient(
    from 0deg,
    black 0deg calc(var(--starburst-step) / 2),
    transparent calc(var(--starburst-step) / 2) var(--starburst-step));
  border-radius: 50%;
  pointer-events: none;
}

/* ---------- Carte ---------- */
.card {
  background: var(--card-bg, var(--c-cream-light));
  border-radius: var(--r-xl);
  overflow: hidden;
}

/* ---------- Apparition au scroll ----------
   Loi relevée sur la source (2026-08-13) : le contenu d'une section MONTE de
   --reveal-rise à son entrée dans la fenêtre, SANS fondu d'opacité, et tous
   les éléments de la section partent et arrivent ensemble — la cascade de 90ms
   qui existait ici était un ajout, elle est ramenée à 0 dans reveal.js.
   L'opacité n'est plus animée : sur la source, les 92 éléments enregistrés
   dans #benefits gardent opacity:1 du début à la fin. */
[data-reveal] {
  transform: translateY(var(--reveal-rise));
  transition: transform var(--dur-reveal) var(--ease-expo) var(--reveal-delay, 0ms);
}

[data-reveal].is-revealed {
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal] { transform: none; }
}

/* ---------- Titres lettre par lettre ----------
   La source découpe chacun de ses grands titres en mots ET en lettres, et fait
   apparaître chaque lettre par un pop d'échelle en cascade (voir les tokens
   --dur-letter / --letter-step / --letter-overshoot). Le découpage est fait à
   l'exécution par js/modules/letters.js ; le texte reste lisible sans JS et
   reste annoncé d'un bloc par les lecteurs d'écran (aria-label posé sur le
   bloc, fragments en aria-hidden).
   `font-kerning: none` est posé dès le repos, avant tout découpage : le
   découpage supprime le crénage entre lettres et changerait la largeur du
   texte — donc, sur un titre de trois lignes, le nombre de lignes. En le
   neutralisant des deux côtés, la géométrie reste celle qui a été validée. */
[data-letters] {
  font-kerning: none;
}

.word {
  display: inline-block;
  white-space: nowrap;
}

.letter {
  display: inline-block;
  will-change: transform;
}

/* Doublon hors-champ du texte entier, posé par letters.js pour les lecteurs
   d'écran : exclu de la sélection, sinon un copier-coller du titre rendrait le
   texte deux fois. */
.letters__label {
  user-select: none;
  -webkit-user-select: none;
}

[data-letters].is-split .letter { scale: 0; }

[data-letters].is-lettered .letter {
  animation: letter-pop var(--dur-letter) linear both;
  animation-delay: calc(var(--letter-i, 0) * var(--letter-step));
}

/* Les neuf points relevés sur une lettre de la source (voir --dur-letter dans
   tokens.css). La forme est portée par les images clés, d'où le `linear`
   ci-dessus : une fonction d'assouplissement en plus déformerait le ressort.
   Le creux à 55 % est le cœur de l'effet — la lettre repasse SOUS sa taille
   finale avant de remonter. C'est lui qui manquait. */
@keyframes letter-pop {
  0%   { scale: 0; }
  5%   { scale: 0.319; }
  10%  { scale: 0.621; }
  20%  { scale: 1.154; }
  28%  { scale: var(--letter-overshoot); }
  40%  { scale: 1.078; }
  55%  { scale: 0.937; }
  70%  { scale: 0.978; }
  85%  { scale: 1.016; }
  100% { scale: 1; }
}

@media (prefers-reduced-motion: reduce) {
  [data-letters].is-split .letter,
  [data-letters].is-lettered .letter { scale: 1; animation: none; }
}

/* ---------- Flottement des stickers ----------
   Amplitudes et périodes relevées sur la source le 2026-08-13, sur le CENTRE
   de la boîte (invariant à la rotation, contrairement au bord haut — piège
   A₁.9) : elles diffèrent fortement d'une pastille à l'autre. Les valeurs par
   défaut ci-dessous sont celles d'avant, conservées pour la pastille
   « Benefitsss » ; les trois pastilles du hero les redéfinissent chacune. */
/* ⚠ Ordre `translateY` PUIS `rotate`, et non l'inverse. Écrit dans l'autre sens,
   le déplacement se fait dans le repère DÉJÀ tourné de la pastille : il ne vaut
   plus que `montée × cos(angle)` en vertical, et part de travers pour le reste.
   Sans conséquence tant que les angles valaient 2° ; à 67,7° sur « Créa », la
   montée mesurée tombait à 29px pour 78,5 demandés. La source, elle, ne bouge
   ses pastilles que verticalement (0,0 px de déplacement horizontal relevé sur
   les trois) : c'est bien une translation dans le repère du parent. */
@keyframes sticker-float {
  0%, 100% { transform: translateY(0) rotate(var(--sticker-rotate, 0deg)); }
  50%      { transform: translateY(calc(var(--float-rise, 8px) * -1))
                       rotate(calc(var(--sticker-rotate, 0deg) + var(--float-rotate, 2deg))); }
}

.sticker.is-floating {
  animation: sticker-float var(--float-duration, 4500ms) var(--float-delay, 0ms) ease-in-out infinite;
}

/* ---------- Accordéon ---------- */
.accordion__panel {
  overflow: hidden;
  transition: max-height var(--dur-accordion) var(--ease-accordion);
}

@media (prefers-reduced-motion: reduce) {
  .accordion__panel { transition: none; }
}
