/* ============================================================================
 * Loutra — feuille de style du site.
 *
 * Le système de l'app (src/theme.ts), retranscrit pour un écran d'ordinateur :
 * mêmes rayons, même police, même menthe — et surtout la même signature, un
 * TRAIT PLEIN sous chaque carte et chaque bouton, jamais une ombre floue.
 *
 * ⚠️ Refonte du 2026-08-28 : le fond crème devient BLANC, le trait devient
 * NOIR, et le thème sombre disparaît — le site est en clair pour tout le
 * monde, comme les captures de l'App Store. C'est le même geste qu'avant, poussé d'un cran — l'aplat menthe reste
 * la seule couleur d'accent, et le noir ne sert qu'à détourer. Une ombre CSS
 * floue rendrait instantanément le site générique ; quand il en faut une, elle
 * est DÉCALÉE ET NETTE (`6px 7px 0`), c'est-à-dire le même trait plein, mais
 * projeté.
 *
 * Deux jeux de tokens à ne pas confondre :
 *   --line    filet discret — tableaux, séparateurs, documents longs
 *   --outline le trait noir — cartes, boutons, champs, tout ce qui se détoure
 * Les pages légales n'utilisent que `--line` : elles restent des documents,
 * pas des affiches.
 * ==========================================================================*/

/* --- Police, auto-hébergée --------------------------------------------------
   Pas de Google Fonts : la politique de confidentialité promet zéro pistage
   tiers, et un <link> vers fonts.gstatic.com enverrait l'IP de chaque visiteur
   à Google. Auto-hébergée, la police part du même serveur que la page — plus
   rapide (une connexion en moins) et cohérent avec ce qu'on écrit. Fredoka est
   sous licence OFL : assets/fonts/OFL.txt. */
@font-face {
  font-family: 'Fredoka';
  src: url('fonts/fredoka-500.ttf') format('truetype');
  font-weight: 500;
  font-display: swap;
}
@font-face {
  font-family: 'Fredoka';
  src: url('fonts/fredoka-700.ttf') format('truetype');
  font-weight: 700;
  font-display: swap;
}

:root {
  /* surfaces */
  --bg: #FFFFFF;
  --surface: #FFFFFF;
  --surface-alt: #F3FAF8;
  --line: #E7E2DC;         /* filet discret */
  /* le trait noir, et le bord bas plein qui va avec */
  --outline: #171310;
  --edge: #171310;
  --edge-strong: #000000;
  /* panneaux "scène" (derrière la loutre) */
  --sky: #DDF3EE;
  --sky-deep: #C6EBE2;
  --ground: rgba(23, 19, 16, .14);
  /* marque */
  --primary: #15B5A3;
  --primary-dark: #0E9384;
  --primary-soft: #D9F4EF;
  /* accents */
  --accent: #FF8A5B;
  --gold: #F6B93B;
  --flame: #FF6B4A;
  /* texte
     ⚠️ Les valeurs ci-dessous sont mesurées, pas choisies à l'œil. Le gris de
     l'app (#9A8F86) ne donne que 3.0 de contraste, là où la norme
     d'accessibilité en demande 4.5 : sur un téléphone tenu à 30 cm ça passe,
     sur un écran lu de loin non — et Google le note. Le teal de MARQUE
     (#15B5A3) est inchangé partout où il est une surface. */
  --ink: #171310;
  --muted: #6B615B;        /* 6.0 sur blanc */
  --muted-tint: #5A524D;   /* le même, un cran plus foncé, pour les fonds teintés */
  --teal-ink: #076A5F;     /* teal de TEXTE : 6.5 sur blanc, 5.5 sur menthe */
  /* 🚨 Le bouton principal est un APLAT MENTHE À TEXTE NOIR, pas l'inverse.
     Menthe + blanc ne donne que 2.6 de contraste ; menthe + noir en donne 7.2.
     C'est le seul arrangement où la couleur de marque reste la couleur de
     marque ET où le libellé se lit. */
  --btn-bg: #15B5A3;
  --btn-edge: #171310;
  --on-primary: #12100E;
  /* avertissements (utilisés par les pages légales) */
  --warn: #B2560E;
  --warn-line: #F6D9BE;
  --warn-bg: #FFF3E8;
  /* rayons — carrés adoucis, jamais des bulles */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;
  --shell: 68rem;
  /* 🚨 Le site est en clair, un point c'est tout — il n'y a plus de bloc
     `prefers-color-scheme: dark` (retiré le 2026-08-28, décision produit : une
     seule identité, celle de l'App Store). `color-scheme: light` n'est donc pas
     décoratif : sans lui, un navigateur réglé en sombre repeint quand même ce
     qu'il dessine lui-même — champs de saisie, barres de défilement, fond du
     <dialog> — et on retrouve du texte clair sur nos fonds blancs. */
  color-scheme: light;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 500 17px/1.6 'Fredoka', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.shell { max-width: var(--shell); margin: 0 auto; padding: 0 1.25rem; }

h1, h2, h3 { font-weight: 700; line-height: 1.1; margin: 0; letter-spacing: -.02em; }
h1 { font-size: clamp(2.1rem, 6vw, 3.5rem); }
h2 { font-size: clamp(1.7rem, 4.4vw, 2.5rem); }
h3 { font-size: 1.15rem; }
p { margin: 0; }
a { color: var(--teal-ink); }

/* Le surligneur menthe du titre — un aplat derrière les mots, détouré comme
   tout le reste. C'est le seul endroit de la page où la couleur passe DEVANT
   le texte plutôt que dans la marge. */
/* `inline` + `box-decoration-break: clone`, jamais `inline-block` : le français
   est plus long que l'anglais et « un jour à la fois. » doit pouvoir se couper.
   En inline-block, le surligneur devient un pavé insécable qui déborde de sa
   colonne dès que la traduction s'allonge. */
.hl {
  display: inline; padding: 0 .18em; margin: 0 -.06em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  background: var(--primary-soft);
  box-shadow: inset 0 -.14em 0 var(--primary);
  border-radius: var(--r-sm);
}

/* ---------------------------------------------------------------- en-tête */
.top {
  display: flex; align-items: center; gap: .75rem;
  padding: 1.1rem 0; margin-bottom: .5rem;
}
.brand { display: flex; align-items: center; gap: .55rem; font-weight: 700; font-size: 1.15rem;
         color: var(--ink); text-decoration: none; }
.brand img { width: 34px; height: 34px; border-radius: var(--r-md);
             border: 2px solid var(--outline); display: block; }
.top .spacer { flex: 1; }

.top .toplink {
  padding: .35rem .8rem; margin-inline-end: .5rem; font-size: .88rem; font-weight: 700;
  text-decoration: none; color: var(--ink); background: var(--surface);
  border: 2px solid var(--outline); border-bottom-width: 4px; border-bottom-color: var(--edge);
  border-radius: var(--r-md);
}
.top .toplink:hover { background: var(--primary-soft); }

/* sélecteur de langue — deux pastilles, la courante enfoncée */
.langs { display: flex; gap: .3rem; }
/* 🚨 Sans ça, l'attribut [hidden] NE MASQUE RIEN ici : le `display: flex` ci-dessus
   est une règle d'auteur, elle bat le `display: none` que le navigateur attache à
   [hidden] dans sa propre feuille. Le script du sélecteur masque cette rangée après
   l'avoir lue — elle restait à l'écran, au-dessus du menu. Vu en capture le 02-09. */
.langs[hidden] { display: none; }
.langs a {
  display: block; padding: .35rem .65rem; font-size: .82rem; font-weight: 700;
  text-decoration: none; color: var(--muted); background: var(--surface);
  border: 2px solid var(--outline); border-bottom-width: 4px; border-bottom-color: var(--edge);
  border-radius: var(--r-md);
}
.langs a[aria-current="true"] { color: var(--ink); background: var(--primary); }

/* ------------------------------------------- le sélecteur de langue (02-09) */
/* Un menu écrit à la main, pas un <select> natif : celui-ci ne se style pas
   (la liste déroulante est rendue par le système), et avec 13 entrées drapeau +
   nom il sortait hors charte sur tous les OS.
   🚨 Le HTML contient TOUJOURS les vrais liens <a> des langues disponibles ; le
   script ne fait que les habiller. Sans JavaScript, on garde une rangée de liens
   qui marche — jamais un bouton mort.
   ⚠️ Défini ICI et nulle part ailleurs : le blog (Astro) et les pages écrites à
   la main servent le même style, sinon les deux dérivent. */
.langpick { position: relative; }

.langpick .lp-btn {
  display: flex; align-items: center; gap: .4rem;
  font: inherit; font-size: .82rem; font-weight: 700;
  padding: .35rem .6rem;
  color: var(--ink); background: var(--surface);
  border: 2px solid var(--outline);
  border-bottom-width: 4px; border-bottom-color: var(--edge);
  border-radius: var(--r-md);
  cursor: pointer;
}
.langpick .lp-btn:focus-visible { outline: 3px solid var(--primary-dark); outline-offset: 2px; }
.langpick .lp-btn .lp-flag { font-size: 1.05rem; line-height: 1; }
.langpick .lp-btn .lp-caret {
  width: .42rem; height: .42rem; margin-inline-start: .1rem;
  border-right: 2px solid var(--ink); border-bottom: 2px solid var(--ink);
  transform: rotate(45deg) translate(-.1rem, -.1rem);
}
.langpick[data-open] .lp-btn .lp-caret { transform: rotate(-135deg) translate(-.15rem, -.15rem); }

.langpick .lp-menu {
  position: absolute; z-index: 40; top: calc(100% + .45rem); inset-inline-end: 0;
  min-width: 12.5rem; max-height: 17rem; overflow-y: auto;
  margin: 0; padding: .3rem; list-style: none;
  background: var(--surface);
  border: 2px solid var(--outline); border-bottom-width: 5px; border-bottom-color: var(--edge);
  border-radius: var(--r-lg, 14px);
  box-shadow: 5px 6px 0 var(--outline);
}
.langpick .lp-menu li { margin: 0; }
.langpick .lp-menu a,
.langpick .lp-menu span {
  display: flex; align-items: center; gap: .55rem;
  padding: .5rem .6rem;
  font-size: .9rem; font-weight: 600; text-decoration: none;
  color: var(--ink); border-radius: var(--r-md);
}
.langpick .lp-menu a:hover,
.langpick .lp-menu a:focus-visible { background: var(--primary-soft); outline: none; }
.langpick .lp-menu [aria-current="true"] { background: var(--primary); }
/* Une langue déclarée mais pas encore servie : visible, jamais cliquable. */
.langpick .lp-menu span { color: var(--muted); cursor: default; }
.langpick .lp-menu .lp-soon {
  margin-inline-start: auto; padding: .1rem .35rem;
  font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  color: var(--muted); border: 1px solid var(--line); border-radius: 999px;
}

/* Sans JavaScript : le script n'a rien remplacé, la rangée de liens reste.
   🚨 `:not([hidden])` N'EST PAS DÉCORATIF. `display: flex` bat l'attribut [hidden]
   (dont le `display: none` vient de la feuille du navigateur, moins spécifique) : sans
   ce filtre, le script masque la rangée et elle reste À L'ÉCRAN, au-dessus du menu
   qu'il vient de construire. Constaté en capture le 02-09. */
.langpick .langs:not([hidden]) { display: flex; gap: .3rem; }

@media (max-width: 26rem) {
  .langpick .lp-menu { min-width: 11rem; }
  .langpick .lp-btn .lp-name { display: none; }
}

/* ------------------------------------------------------------------ héros */
/* Mobile : titre → LOUTRE → boutons. Sur un téléphone, la loutre placée après
   le formulaire tombait à deux écrans de défilement — or c'est elle l'argument.
   Desktop : deux colonnes, la scène occupe toute la hauteur à droite. */
.hero { display: grid; gap: 2rem; padding: 1.5rem 0 3rem; }
/* `order` réordonne l'AFFICHAGE sans toucher au HTML : la scène est écrite en
   dernier dans la page (donc chargée en dernier) mais montrée au milieu. La
   navigation au clavier suit le HTML — le bouton « Click me » est donc atteint
   après le formulaire, ce qui est le bon ordre d'importance. */
/* Sur téléphone : titre → BOUTONS → loutre → liste d'attente. Les deux boutons
   de magasin remontent au-dessus de la loutre parce que c'est la seule action
   qui compte pour quelqu'un qui est déjà sur iPhone ; la liste d'attente, elle,
   ne concerne que les gens sur Android et peut attendre le bas. */
.hero-copy { order: 1; }
.hero > .stores { order: 2; margin-top: -.75rem; }
.hero .stage { order: 3; }
.hero-cta { order: 4; margin-top: -.5rem; }
@media (min-width: 60rem) {
  .hero {
    grid-template-columns: 1.15fr .85fr;
    grid-template-areas: "copy scene" "stores scene" "cta scene";
    gap: 0 3rem; align-items: center; padding: 3.5rem 0 5rem;
  }
  .hero-copy { grid-area: copy; }
  /* ⚠️ `.hero > .stores`, pas `.stores` : la bande finale en a un second, et il
     n'a rien à faire dans cette grille. */
  .hero > .stores { grid-area: stores; margin-top: 1rem; }
  .hero-cta { grid-area: cta; margin-top: 1.25rem; }
  .hero .stage { grid-area: scene; }
}
/* --- la scène et son bouton ------------------------------------------------
   La loutre, puis « Click me » juste dessous. Les deux forment un bloc : le
   bouton n'est pas un élément de formulaire égaré, c'est le socle de la scène. */
.stage { display: grid; gap: .9rem; justify-items: center; }
.stage .scene { width: 100%; }

.scene {
  background: var(--sky);
  border: 2px solid var(--outline); border-bottom-width: 7px; border-bottom-color: var(--edge);
  border-radius: var(--r-xl); padding: 1.5rem 1.25rem; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1rem; min-height: 24rem;
}
/* Le sol appartient à la LOUTRE, pas au panneau : depuis que la carte d'humeur
   occupe le bas de la scène, un disque calé sur le panneau se retrouvait posé
   sur la carte. */
.scene-otter { position: relative; display: flex; justify-content: center; width: 100%; }
.scene-otter::after {
  content: ''; position: absolute; bottom: 8%; left: 50%; transform: translateX(-50%);
  width: 42%; height: 1.1rem; background: var(--ground); border-radius: 999px;
}
.scene canvas, .scene img {
  position: relative; z-index: 1;
  width: min(22rem, 82%); height: auto; aspect-ratio: 1 / 1; display: block;
}
.scene img { object-fit: contain; }
/* 🚨 L'image fixe et Rive ne CADRENT PAS la loutre pareil : le .webp est une
   découpe serrée, l'artboard Rive garde de la marge autour. À boîte égale, la
   loutre de l'image fixe sortait donc 31 % plus grosse — et au moment où
   l'animation prenait le relais, elle rapetissait d'un coup sous les yeux du
   visiteur. Mesuré au pixel sur les deux rendus : facteur 0.763, et 6 % de
   descente pour reposer les pieds sur la même ligne. Le `transform` ne touche
   pas à la mise en page, donc il ne coûte aucun décalage. */
.scene .fallback { transform: translateY(6%) scale(.763); }
/* L'image fixe est la valeur par défaut : elle s'affiche tout de suite et
   reste si Rive n'arrive jamais. Le canvas ne prend sa place qu'une fois
   l'animation vraiment chargée (classe posée par loutra.js). */
.scene canvas { display: none; }
.scene.rive-on canvas { display: block; }
.scene.rive-on .fallback { display: none; }

/* --- la carte d'humeur ------------------------------------------------------
   La copie de celle de l'écran d'accueil de l'app : « Mood » à gauche, le
   pourcentage à droite dans la couleur du moment, et une piste épaisse dessous.
   Les trois couleurs sont relevées au pixel sur assets/shot-home.webp — la
   piste vide (#F1EDE5) et le remplissage « content » (#72C18D) sont ceux de
   l'app, exactement.

   ⚠️ Le remplissage et le TEXTE n'ont pas la même couleur, et ce n'est pas une
   coquille. Le vert de l'app ne donne que 2.9 de contraste sur blanc : parfait
   pour un aplat, illisible pour des chiffres. Chaque palier a donc son aplat
   (celui de l'app) et son encre (la même teinte, assombrie jusqu'à 5.0 ou
   plus). */
.mood {
  --mood-fill: #E8836B;   /* triste  — aplat */
  --mood-ink:  #B44A2E;   /*         — encre, 5.3 sur blanc */
  display: block; width: 100%; max-width: 22rem;
  background: var(--surface); border: 2px solid var(--outline);
  border-bottom-width: 5px; border-bottom-color: var(--edge);
  border-radius: var(--r-lg); padding: .7rem .85rem .8rem;
}
.mood[data-level="1"] { --mood-fill: #F0B44E; --mood-ink: #8A5A08; }  /* 5.9 */
.mood[data-level="2"] { --mood-fill: #72C18D; --mood-ink: #2E7D57; }  /* 5.0 */

.mood-top {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  margin-bottom: .5rem;
}
.mood-label { font-weight: 700; font-size: 1rem; }
/* Largeur fixe et chiffres tabulaires : le compteur passe par 8 % et 100 %
   soixante fois par seconde, et sans ça la ligne entière tremble. */
.mood-pct {
  min-width: 3rem; text-align: end; font-weight: 700; font-size: 1rem;
  font-variant-numeric: tabular-nums; color: var(--mood-ink);
  transition: color .35s ease;
}
.mood-track {
  height: 1rem; overflow: hidden; background: #F1EDE5;
  border: 2px solid var(--outline); border-radius: 999px;
}
/* Seule la COULEUR est en transition. Une transition sur la largeur se
   battrait avec le rAF qui la réécrit à chaque image, et la barre traînerait. */
/* 72 % en dur, et ce n'est pas une décoration : c'est la valeur que le HTML
   affiche avant que Rive n'arrive, et celle sur laquelle loutra.js fait
   DÉMARRER sa boucle. Les deux doivent rester d'accord, sinon la barre saute au
   moment où l'animation prend la main. */
.mood-fill {
  display: block; height: 100%; width: 72%; border-radius: 999px;
  background: var(--mood-fill); transition: background-color .35s ease;
}

/* 🚨 `visibility`, pas `display`. Le bouton ne se montre que si l'animation
   tourne vraiment — sans Rive il n'y a rien à déclencher, et un bouton vivant
   qui ne fait rien est pire qu'un bouton absent — mais il OCCUPE sa place dès
   le premier rendu. En `display: none`, son arrivée poussait toute la scène
   vers le haut une seconde après le chargement. Aucun JS là-dedans : le
   sélecteur frère suit la classe `.rive-on`. */
.otter-poke { visibility: hidden; }
.scene.rive-on ~ .otter-poke { visibility: visible; }
.otter-poke {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .8rem 1.4rem; font: inherit; font-weight: 700; font-size: 1.02rem;
  cursor: pointer; color: var(--on-primary); background: var(--primary);
  border: 2px solid var(--outline); border-bottom: 5px solid var(--edge);
  border-radius: 999px;
  animation: poke-bob 2.6s ease-in-out infinite;
}
.otter-poke:active { transform: translateY(2px); border-bottom-width: 3px; animation: none; }
@keyframes poke-bob {
  0%, 88%, 100% { transform: translateY(0) rotate(0); }
  92% { transform: translateY(-4px) rotate(-2deg); }
  96% { transform: translateY(-2px) rotate(2deg); }
}
@media (prefers-reduced-motion: reduce) { .otter-poke { animation: none; } }

/* ---------------------------------------------------------------- boutons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  padding: .95rem 1.4rem; font: inherit; font-weight: 700; font-size: 1rem;
  text-decoration: none; cursor: pointer;
  background: var(--btn-bg); color: var(--on-primary);
  border: 2px solid var(--outline); border-bottom: 5px solid var(--btn-edge);
  border-radius: var(--r-md);
}
.btn:active { transform: translateY(2px); border-bottom-width: 3px; }
.btn.ghost { background: var(--surface); color: var(--ink); }

/* --- les deux boutons de magasin --------------------------------------------
   ⚠️ Le badge App Store est FOURNI par Apple et ses règles de marque interdisent
   de le redessiner, de le recolorer, de le déformer ou de l'encadrer. On ne
   touche donc qu'à sa hauteur — pas de tuile, pas de trait noir autour,
   contrairement au reste de la page. La variante NOIRE : le fond du site est
   blanc, la blanche ne s'y verrait pas.

   Le bouton Google Play se range DERRIÈRE lui plutôt que devant : même noir,
   même hauteur, même filet gris clair, même absence de bord bas épais. Les deux
   se lisent comme une paire. Seul le triangle garde ses quatre couleurs — c'est
   le logo du magasin, et c'est tout ce qui distingue les deux boutons. */
/* 🚨 Les deux boutons doivent faire la MÊME hauteur à l'œil, et deux boîtes de
   56 px n'y suffisent pas : le noir du badge d'Apple ne remplit que 95.36 % de
   sa boîte (mesuré à la loupe — le SVG garde un filet gris #a6a6a6 et sa marge
   autour du pavé noir), là où le bouton Play est noir jusqu'à son bord. À
   boîtes égales, Apple paraissait 3 px plus court, et ça se voyait.

   On règle donc la boîte du badge pour que sa SURFACE NOIRE tombe à la hauteur
   du bouton Play, et non l'inverse : `--store-h` est la hauteur commune
   visible, `--badge-h` la boîte qu'il faut au badge pour l'atteindre. Une seule
   valeur à changer, et le téléphone n'a qu'à redéfinir `--store-h`.

   `align-items: center` et non `stretch` : sous `stretch`, le bouton Play se
   serait étiré à la hauteur de la boîte du badge, et l'écart serait revenu à
   l'identique dans l'autre sens. */
.stores { display: flex; flex-wrap: wrap; gap: .7rem; align-items: center; --store-h: 56px; }
.store-badge { display: inline-block; line-height: 0; }
/* ⚠️ Le calcul est ÉCRIT ICI, pas rangé dans une seconde variable. Une variable
   dérivée est figée là où elle est déclarée : `--badge-h` posée sur `.stores`
   n'aurait pas suivi le `--store-h` redéfini plus bas par le bloc téléphone.
   Le repli `56px` sert aux badges isolés — celui des fins d'article n'a pas de
   `.stores` au-dessus de lui, et sans repli il retombait à sa taille naturelle
   de 40 px. */
.store-badge img { height: calc(var(--store-h, 56px) / .9536); width: auto; display: block; }
.store-badge:active { transform: translateY(2px); }

/* --- les deux boutons sur un téléphone étroit -------------------------------
   Côte à côte, toujours, et de la MÊME hauteur.

   🚨 La première version les faisait se partager la largeur (`flex: 1 1 0`).
   Erreur de grandeur pilote : la hauteur du badge d'Apple découle alors de sa
   largeur (ratio oblige), celle du bouton Play de son texte, et les deux
   n'atterrissaient pas au même endroit — 46 px contre 54 px à 390 px, mesuré.

   Ici c'est la HAUTEUR qui est la grandeur partagée : une seule valeur fluide,
   posée sur le badge (dont la largeur suit son ratio, jamais l'inverse — le
   déformer est interdit par les règles de marque d'Apple) et sur le bouton
   Play. Les deux ne peuvent plus diverger. Les largeurs, elles, restent
   naturelles : à 390 px les deux boutons occupent 297 px des 350 disponibles,
   et à 320 px ils tiennent encore. */
@media (max-width: 30rem) {
  /* Une seule valeur change : la hauteur commune. `--badge-h` en découle. */
  .stores { flex-wrap: nowrap; gap: .5rem; --store-h: clamp(40px, 13vw, 56px); }
  .stores > * { min-width: 0; }
  .store { padding: 0 .55rem; gap: .4rem; }
  .store svg { width: 20px; height: 20px; }
  /* `clamp` plutôt qu'une taille fixe : à 320 px, « Google Play » se coupait en
     deux lignes et faisait déborder le bouton. Le `nowrap` est la ceinture, la
     borne basse du clamp la bretelle. */
  .store .l1 { font-size: clamp(.55rem, 2.6vw, .64rem); white-space: nowrap; }
  .store .l2 { font-size: clamp(.72rem, 3.5vw, .86rem); white-space: nowrap; }
}

/* Google Play : le magasin n'est pas ouvert, mais le bouton, lui, fait quelque
   chose — il ouvre la fenêtre qui explique et prend l'adresse. C'est la seule
   version honnête : ni un lien mort, ni un lien vers une fiche inexistante. */
.store {
  display: flex; align-items: center; gap: .65rem;
  min-height: var(--store-h, 56px); padding: 0 1.1rem; text-align: start; cursor: pointer;
  font: inherit; color: #fff; background: #000;
  /* 1px de gris clair, et un rayon de 11 px : les deux valeurs sont relevées
     DANS le SVG d'Apple, pour que les deux boutons aient le même contour. */
  border: 1px solid #A6A6A6; border-radius: 11px;
}
.store:hover { background: #23201E; }
.store:active { transform: translateY(2px); }
/* Pas de `fill: currentColor` ici : chaque facette du triangle porte sa propre
   couleur dans le SVG, et un fill hérité les repeindrait toutes en noir. */
.store svg { width: 26px; height: 26px; flex: none; }
/* Blanches toutes les deux, comme sur le badge d'Apple : c'est la TAILLE qui
   hiérarchise les deux lignes, pas la couleur. Un gris sur ce noir tomberait
   sous le seuil de contraste. */
.store .l1 { display: block; font-size: .72rem; line-height: 1.2; }
.store .l2 { display: block; font-size: 1.02rem; font-weight: 700; line-height: 1.25; }

/* --------------------------------------------------------- liste d'attente */
/* Le champ est volontairement GRAND : c'est la seule conversion possible pour
   les deux tiers du monde qui sont sur Android, et il est en concurrence
   visuelle avec un badge App Store dessiné par Apple. */
.waitlist {
  margin-top: 1.6rem; max-width: 34rem;
  background: var(--surface); border: 2px solid var(--outline);
  border-bottom-width: 6px; border-bottom-color: var(--edge);
  border-radius: var(--r-xl); padding: 1.15rem 1.2rem 1.25rem;
}
.waitlist .wl-label {
  display: block; margin-bottom: .7rem;
  font-size: 1.02rem; font-weight: 700; color: var(--ink);
}
.waitlist .row { display: flex; flex-wrap: wrap; gap: .6rem; }
.waitlist input {
  flex: 1 1 13rem; min-width: 0;
  padding: 1.05rem 1.1rem; font: inherit; font-weight: 500; font-size: 1.15rem;
  color: var(--ink); background: var(--surface);
  border: 2px solid var(--outline); border-bottom-width: 5px; border-bottom-color: var(--edge);
  border-radius: var(--r-md);
}
.waitlist input::placeholder { color: var(--muted); }
.waitlist input:focus { outline: none; border-color: var(--primary-dark); background: var(--surface-alt); }
.waitlist input:disabled { opacity: .6; }
.waitlist .row .btn { flex: 1 1 9rem; padding: 1.05rem 1.4rem; font-size: 1.08rem; }
.waitlist .note { margin-top: .7rem; font-size: .85rem; color: var(--muted); }
.waitlist .said { margin-top: .7rem; font-size: .95rem; font-weight: 700; color: var(--teal-ink); }
.waitlist .said:empty { display: none; }

/* ------------------------------------------------- la fenêtre Google Play */
/* `<dialog>` natif : la pile de plans, la touche Échap et le piège à focus
   viennent du navigateur, pas de nous. Là où `showModal` manque, loutra.js
   renvoie au formulaire de la page — jamais un bouton qui ne fait rien. */
.modal {
  width: min(30rem, calc(100vw - 2rem)); padding: 1.5rem;
  color: var(--ink); background: var(--surface);
  border: 2px solid var(--outline); border-radius: var(--r-xl);
  box-shadow: 8px 9px 0 var(--outline);
}
.modal::backdrop { background: rgba(23, 19, 16, .45); }
.modal h2 { font-size: 1.35rem; padding-inline-end: 2rem; }
.modal > p { margin-top: .7rem; color: var(--muted); font-size: .98rem; }
.modal .waitlist { margin-top: 1.2rem; max-width: none; border: none; padding: 0; }
.modal-x { position: absolute; top: .8rem; right: .8rem; margin: 0; }
.modal-x button {
  width: 2.1rem; height: 2.1rem; display: grid; place-items: center;
  font: inherit; font-weight: 700; cursor: pointer;
  color: var(--ink); background: var(--surface);
  border: 2px solid var(--outline); border-bottom-width: 4px; border-bottom-color: var(--edge);
  border-radius: var(--r-sm);
}

/* --------------------------------------------------------------- sections */
section { padding: 3.5rem 0; }
.head { max-width: 42rem; margin-bottom: 2.25rem; }
.head p { margin-top: .8rem; color: var(--muted); font-size: 1.05rem; }

/* l'étiquette de section — trois mots au-dessus du titre, qui disent de quoi
   on parle avant de le dire */
.eyebrow {
  display: inline-block; margin-bottom: .9rem; padding: .3rem .8rem;
  font-size: .76rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink); background: var(--primary-soft);
  border: 2px solid var(--outline); border-radius: 999px;
}

/* le bloc-réponse : ce que les moteurs IA découpent et citent */
.answer {
  background: var(--surface-alt); border: 2px solid var(--outline);
  border-bottom-width: 6px; border-bottom-color: var(--edge);
  border-radius: var(--r-lg); padding: 1.35rem 1.5rem; margin: 0 0 1rem;
}
.answer h2 { font-size: 1.25rem; }
.answer p { margin-top: .6rem; color: var(--muted-tint); }

/* --- pastilles d'addictions : les mêmes vignettes-lettres que dans l'app --- */
.chips { display: flex; flex-wrap: wrap; gap: .55rem; }
.chip {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .45rem .85rem .45rem .45rem; font-size: .92rem; font-weight: 700;
  background: var(--surface); border: 2px solid var(--outline);
  border-bottom-width: 4px; border-bottom-color: var(--edge); border-radius: 999px;
}
.chip i {
  display: grid; place-items: center; width: 1.7rem; height: 1.7rem; flex: none;
  font-style: normal; font-size: .8rem; font-weight: 700;
  border: 2px solid var(--outline); border-radius: 999px; color: #2C2621;
}
.chip.more { color: var(--muted); padding-inline-start: .85rem; }

/* ------------------------------------------------------ « sans » / « avec »
   Deux colonnes qui se répondent. La colonne de gauche décrit un mécanisme —
   le zéro, la chaîne cassée — pas un concurrent nommé : on ne met le nom de
   personne dans une comparaison qu'on écrit soi-même. */
.versus { display: grid; gap: 1.1rem; }
@media (min-width: 52rem) { .versus { grid-template-columns: 1fr 1fr; gap: 1.25rem; } }
.versus > div {
  background: var(--surface); border: 2px solid var(--outline);
  border-bottom-width: 6px; border-bottom-color: var(--edge);
  border-radius: var(--r-lg); padding: 1.35rem 1.5rem;
}
.versus .good { background: var(--primary-soft); }
.versus h3 { font-size: 1.1rem; margin-bottom: 1rem; }
.versus ul { list-style: none; margin: 0; padding: 0; }
.versus li {
  display: grid; grid-template-columns: 1.5rem 1fr; gap: .7rem; align-items: start;
  padding: .6rem 0; font-size: .97rem; color: var(--muted-tint);
}
.versus li + li { border-top: 2px solid var(--line); }
.versus .good li + li { border-top-color: rgba(23, 19, 16, .12); }
.versus li b {
  display: grid; place-items: center; width: 1.5rem; height: 1.5rem;
  font-size: .8rem; border: 2px solid var(--outline); border-radius: 999px;
}
.versus .bad li b { background: var(--surface-alt); }
.versus .good li b { background: var(--primary); color: var(--on-primary); }

/* --------------------------------------------------- les blocs "capture" */
.steps { display: grid; gap: 3rem; }
@media (min-width: 52rem) { .steps { gap: 4.5rem; } }
.step { display: grid; gap: 1.5rem; align-items: center; }
@media (min-width: 52rem) {
  .step { grid-template-columns: 1fr 1fr; gap: 3rem; }
  .step:nth-child(even) .shot { order: -1; }
}
.step .n {
  display: inline-grid; place-items: center; min-width: 2.8rem; height: 2.6rem;
  padding: 0 .6rem; margin-bottom: .9rem;
  font-size: 1.05rem; font-weight: 700; letter-spacing: .02em;
  color: var(--on-primary); background: var(--primary);
  border: 2px solid var(--outline); border-bottom-width: 4px; border-bottom-color: var(--edge);
  border-radius: var(--r-md);
}
.step h3 { font-size: 1.4rem; }
.step p { margin-top: .8rem; color: var(--muted); }

/* le cadre de téléphone — un rectangle épais, dans l'esprit "chunky" de l'app,
   pas un mockup photoréaliste qui jurerait avec le reste */
.shot { display: flex; justify-content: center; }
.phone {
  width: min(16rem, 74%); padding: .55rem; background: var(--surface);
  border: 2px solid var(--outline); border-bottom-width: 7px; border-bottom-color: var(--edge);
  border-radius: 28px;
}
.phone img { width: 100%; height: auto; display: block; border-radius: 22px; }

/* ------------------------------------------------------------- la galerie */
.gallery { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; }
@media (min-width: 46rem) { .gallery { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 64rem) { .gallery { grid-template-columns: repeat(6, 1fr); } }
.gallery .phone { width: 100%; }

/* ------------------------------------------------------------------- cartes */
.cards { display: grid; gap: 1.1rem; }
@media (min-width: 46rem) { .cards { grid-template-columns: repeat(3, 1fr); } }
.card {
  background: var(--surface); border: 2px solid var(--outline);
  border-bottom-width: 6px; border-bottom-color: var(--edge);
  border-radius: var(--r-lg); padding: 1.35rem;
}
.card .ico {
  display: grid; place-items: center; width: 2.8rem; height: 2.8rem;
  font-size: 1.4rem; line-height: 1; background: var(--primary-soft);
  border: 2px solid var(--outline); border-radius: var(--r-md);
}
.card h3 { margin: .9rem 0 .4rem; }
.card p { color: var(--muted); font-size: .95rem; }

/* --------------------------------------------------------- la bande finale */
.finale {
  background: var(--primary-soft); border: 2px solid var(--outline);
  border-radius: var(--r-xl); box-shadow: 8px 9px 0 var(--outline);
  padding: 2.25rem 1.5rem; margin-bottom: .75rem;
  display: grid; gap: 1.5rem; align-items: center;
}
@media (min-width: 56rem) {
  .finale { grid-template-columns: 1.1fr auto; gap: 2.5rem; padding: 2.75rem 2.5rem; }
}
.finale h2 { font-size: clamp(1.6rem, 3.6vw, 2.2rem); }
.finale p { margin-top: .7rem; color: var(--muted-tint); }

/* ---------------------------------------------------------------- la FAQ */
.faq { display: grid; gap: .7rem; max-width: 48rem; }
.faq details {
  background: var(--surface); border: 2px solid var(--outline);
  border-bottom-width: 5px; border-bottom-color: var(--edge);
  border-radius: var(--r-lg); padding: 1rem 1.2rem;
}
.faq summary {
  cursor: pointer; font-weight: 700; font-size: 1.02rem; list-style: none;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: '+'; flex: none;
  display: grid; place-items: center; width: 1.6rem; height: 1.6rem;
  font-size: 1.1rem; line-height: 1; color: var(--ink); background: var(--primary-soft);
  border: 2px solid var(--outline); border-radius: 999px;
}
.faq details[open] summary::after { content: '–'; }
.faq details p { margin-top: .8rem; color: var(--muted); font-size: .97rem; }

/* --------------------------------------------------------------- le pied */
footer {
  margin-top: 2rem; padding: 2.5rem 0 3rem; border-top: 2px solid var(--outline);
  color: var(--muted); font-size: .92rem;
}
footer nav { display: flex; flex-wrap: wrap; gap: 1.25rem; margin-bottom: 1.25rem; }
footer nav a { font-weight: 700; text-decoration: none; }
footer nav a:hover { text-decoration: underline; }
footer .fine { max-width: 44rem; }
footer .fine + .fine { margin-top: .7rem; }

/* --------------------------------------------------- accessibilité clavier */
:focus-visible { outline: 3px solid var(--primary-dark); outline-offset: 2px; border-radius: var(--r-sm); }
.skip {
  position: absolute; inset-inline-start: -9999px; top: 0; padding: .7rem 1rem; z-index: 10;
  background: var(--surface); border: 2px solid var(--outline); border-radius: var(--r-md);
  font-weight: 700; text-decoration: none;
}
.skip:focus { inset-inline-start: 1rem; top: 1rem; }

/* ============================================================================
 * LE BLOG
 *
 * Un article est un document long : loutra.css met `p { margin: 0 }` pour la
 * landing, il faut donc rendre leur respiration aux paragraphes ici. Le reste
 * réutilise le système de la page d'accueil — mêmes cartes, mêmes bords bas
 * pleins — pour qu'un article ne ressemble pas à un autre site.
 * ==========================================================================*/

/* Le fil d'Ariane suit la mise en page de ce qui vient après lui : centré
   au-dessus d'un article (dont la colonne l'est), calé à gauche au-dessus de
   l'index (dont la liste l'est). Un fil aligné autrement que son contenu donne
   l'impression de deux pages superposées.
   `:has()` n'est pas supporté partout ; là où il manque, le fil reste à gauche
   — un décalage cosmétique, jamais une page cassée. */
.crumb { max-width: 44rem; margin: .5rem 0 1.5rem; font-size: .85rem; color: var(--muted); }
.crumb:has(+ article) { margin-left: auto; margin-right: auto; }
.crumb a { text-decoration: none; }
.crumb a:hover { text-decoration: underline; }

article { max-width: 44rem; margin: 0 auto; }
article h1 { font-size: clamp(1.9rem, 5vw, 2.6rem); margin: 0 0 .6rem; }
article h2 { font-size: 1.4rem; margin: 2.5rem 0 .8rem; }
article h3 { font-size: 1.08rem; margin: 1.75rem 0 .5rem; }
article p, article ul, article ol { margin: 0 0 1.1rem; }
article ul, article ol { padding-inline-start: 1.3rem; }
article li { margin-bottom: .45rem; }
article p, article li { color: var(--ink); }

/* La ligne date + temps de lecture, sous le titre. */
.meta { font-size: .88rem; color: var(--muted); margin: 0 0 2rem; }

/* Le chiffre qu'on retient — un seul par article, sinon il ne veut plus rien
   dire. C'est aussi le morceau qu'un moteur IA découpe et cite. */
.figure {
  background: var(--primary-soft); border: 2px solid var(--outline);
  border-bottom-width: 6px; border-bottom-color: var(--edge);
  border-radius: var(--r-lg); padding: 1.2rem 1.4rem; margin: 1.5rem 0 2rem;
}
.figure strong { display: block; font-size: 1.9rem; line-height: 1.1; color: var(--teal-ink); }
.figure span { display: block; margin-top: .4rem; font-size: .95rem; color: var(--ink); }

/* La chronologie du sevrage : une liste, pas un tableau — un tableau à deux
   colonnes sur un téléphone se lit très mal. */
.tl { list-style: none; padding: 0; margin: 1.25rem 0 1.75rem; }
.tl li {
  display: grid; grid-template-columns: 7.5rem 1fr; gap: .9rem;
  padding: .8rem 0; border-top: 2px solid var(--line); margin: 0;
}
.tl li:last-child { border-bottom: 2px solid var(--line); }
.tl b { font-size: .92rem; color: var(--teal-ink); }
@media (max-width: 32rem) { .tl li { grid-template-columns: 1fr; gap: .2rem; } }

/* Les sources, en bas — vraies références, vrais liens, jamais décoratives. */
.sources {
  background: var(--surface-alt); border: 2px solid var(--outline);
  border-bottom-width: 6px; border-bottom-color: var(--edge);
  border-radius: var(--r-lg); padding: 1.2rem 1.4rem; margin: 2.5rem 0 1.25rem;
}
.sources h2 { font-size: 1.05rem; margin: 0 0 .7rem; }
.sources ol { margin: 0; padding-inline-start: 1.2rem; font-size: .92rem; }
.sources li { margin-bottom: .5rem; color: var(--muted-tint); }
.sources a { word-break: break-word; }

.disclaimer { font-size: .88rem; color: var(--muted); margin: 0 0 2rem; }

/* L'appel à télécharger, en fin d'article. */
.cta {
  background: var(--sky); border: 2px solid var(--outline);
  border-radius: var(--r-xl); box-shadow: 6px 7px 0 var(--outline);
  padding: 1.5rem; margin: 2.5rem 0 1.5rem;
  display: grid; gap: 1rem; align-items: center;
}
@media (min-width: 40rem) { .cta { grid-template-columns: 1fr auto; gap: 1.75rem; } }
.cta h2 { margin: 0 0 .4rem; font-size: 1.2rem; }
.cta p { margin: 0; font-size: .95rem; color: var(--muted-tint); }

/* La liste des articles. */
.posts { display: grid; gap: 1rem; max-width: 48rem; }
.posts a {
  display: block; text-decoration: none; color: inherit;
  background: var(--surface); border: 2px solid var(--outline);
  border-bottom-width: 5px; border-bottom-color: var(--edge);
  border-radius: var(--r-lg); padding: 1.2rem 1.4rem;
}
.posts a:hover { background: var(--surface-alt); }
.posts h2, .posts h3 { font-size: 1.15rem; margin: 0 0 .4rem; }  /* h3 sur la landing :
     les cartes y sont sous le <h2> de la section, pas des titres de premier rang. */
.posts p { font-size: .95rem; color: var(--muted); margin: 0; }
.posts .when { display: block; margin-top: .6rem; font-size: .82rem; color: var(--muted); }

/* le « tous les articles » sous les trois cartes de la landing */
.posts-more { max-width: 48rem; margin: 1.1rem 0 0; font-size: .95rem; font-weight: 700; }
.posts-more a { text-decoration: none; }
.posts-more a:hover { text-decoration: underline; }

/* ============================================================================
 * LES SCRIPTS QUE FREDOKA NE COUVRE PAS, ET L'ARABE
 * (ajouté le 02-09-2026, en allumant les 11 langues déclarées)
 * ==========================================================================*/

/* 🚨 FREDOKA NE COUVRE QUE LE LATIN. Ni CJK, ni arabe, ni devanagari, ni bengali, ni
   gurmukhi — sur ces pages le navigateur tombait déjà sur « sans-serif », c'est-à-dire
   sur ce que l'OS a décidé. Ces règles ne changent pas ce constat, elles le RENDENT
   DÉLIBÉRÉ : on nomme, par script, des fontes système qui existent vraiment sur macOS,
   iOS, Windows et Android.
   🚨 ON NE CHARGE RIEN. Une Noto CJK pèse plus de 10 Mo, et un <link> vers
   fonts.googleapis.com enverrait l'IP de chaque visiteur à Google — ce que la politique
   de confidentialité promet de ne pas faire (cf. le bloc @font-face en haut). Zéro
   téléchargement, zéro tiers : uniquement l'ordre de préférence.
   ⚠️ `:lang()` lit <html lang>, que Shell.astro pose depuis `htmlLang` (src/lib/site.ts).
   Une langue ajoutée sans son entrée ici retombe simplement sur la pile par défaut. */
:lang(zh) body, :lang(zh-Hans) body {
  font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
}
:lang(ja) body {
  font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Yu Gothic', 'Noto Sans JP', sans-serif;
}
:lang(ar) body {
  font-family: 'SF Arabic', 'Geeza Pro', 'Segoe UI', 'Noto Naskh Arabic', 'Noto Sans Arabic', sans-serif;
}
:lang(hi) body {
  font-family: 'Kohinoor Devanagari', 'Nirmala UI', 'Noto Sans Devanagari', sans-serif;
}
:lang(bn) body {
  font-family: 'Kohinoor Bangla', 'Nirmala UI', 'Noto Sans Bengali', 'Vrinda', sans-serif;
}
:lang(pa) body {
  font-family: 'Mukta Mahee', 'Nirmala UI', 'Noto Sans Gurmukhi', 'Raavi', sans-serif;
}

/* ⚠️ Ces scripts n'ont PAS de graisse 700 dessinée partout : le navigateur la synthétise
   en épaississant le trait, ce qui referme les contre-formes de la devanagari et du
   bengali. On redescend d'un cran là où le latin met 700, et seulement là. */
:lang(hi) h1, :lang(bn) h1, :lang(pa) h1, :lang(ar) h1,
:lang(hi) h2, :lang(bn) h2, :lang(pa) h2, :lang(ar) h2,
:lang(hi) h3, :lang(bn) h3, :lang(pa) h3, :lang(ar) h3 { font-weight: 600; }

/* Ces écritures montent et descendent plus que le latin : l'interligne du corps de texte
   est calculé pour Fredoka et serre trop dès qu'on en sort. */
:lang(hi) body, :lang(bn) body, :lang(pa) body, :lang(ar) body { line-height: 1.75; }

/* 🚨 LE RTL — l'arabe. Tout le reste de la feuille est passé en propriétés LOGIQUES
   (inset-inline, margin-inline, padding-inline, text-align: start/end) : elles se
   retournent seules. Il ne reste ici que ce qu'aucune propriété logique ne couvre.
   ⚠️ Les ombres portées « chunky » sont un décalage EN PIXELS : à l'envers, une ombre à
   droite tombe du mauvais côté de la carte et le relief se lit à l'envers. */
[dir="rtl"] .langpick .lp-menu { box-shadow: -5px 6px 0 var(--outline); }
[dir="rtl"] .modal,
[dir="rtl"] .finale { box-shadow: -8px 9px 0 var(--outline); }
[dir="rtl"] .cta { box-shadow: -6px 7px 0 var(--outline); }

/* ⚠️ Les listes du blog (`.tl`) sont une grille : elle suit `dir` toute seule, la colonne
   des libellés passe à droite sans rien faire. Ne pas « corriger » ça avec un ordre
   explicite, ce serait la remettre à l'envers. */

/* September 2026: clearer first viewport and long-form reading. */
.hero-kicker { color: var(--teal-ink); font-size: .95rem; font-weight: 700; margin-bottom: .85rem; }
.hero-intro { max-width: 36rem; margin-top: 1.25rem; font-size: 1.125rem; color: var(--muted-tint); }
.hero-trust { margin-top: 1rem; font-size: .875rem; color: var(--muted); }
.hero-jump { display: inline-block; margin-top: .75rem; padding-block: .35rem; text-underline-offset: .2em; }
.hero-copy h1 { text-wrap: balance; }
.health-note { margin-top: 1.5rem; padding-inline-start: 1rem; border-inline-start: 3px solid var(--primary-dark); font-size: .9375rem; color: var(--muted-tint); }
:where(a, button, summary, input):focus-visible { outline: 3px solid var(--teal-ink); outline-offset: 4px; }
:where(h2, h3, section, main)[id] { scroll-margin-top: 1.5rem; }
/* The language links remain usable when JavaScript is disabled. */
.langpick .langs:not([hidden]) { flex-wrap: wrap; justify-content: flex-end; }
.top:has(.langs:not([hidden])) { flex-wrap: wrap; }
.top .langpick { min-width: 0; }
.hero > *, .guide-grid > *, .posts > * { min-width: 0; }
.guide-start { margin-bottom: 2.5rem; }
.guide-start h2, .all-guides { font-size: 1.5rem; margin-bottom: 1rem; }
.guide-grid { display: grid; gap: .8rem; }
.guide-grid a { display: grid; gap: .5rem; padding: 1.1rem; border: 2px solid var(--outline); border-bottom-width: 5px; border-radius: var(--r-md); color: var(--ink); background: var(--primary-soft); text-decoration: none; }
.guide-grid span { font-size: .9375rem; color: var(--muted-tint); }
@media (min-width: 48rem) { .guide-grid { grid-template-columns: repeat(3, 1fr); } }
article { overflow-wrap: break-word; }
article > p, article > ul, article > ol, article > blockquote { font-family: system-ui, sans-serif; font-weight: 400; font-size: 1.0625rem; line-height: 1.8; }
article > .meta { font-family: inherit; font-size: .875rem; margin-bottom: .45rem; }
article > .article-credit { font-family: inherit; font-size: .875rem; margin: 0 0 1.5rem; color: var(--muted); }
.article-toc { margin: 1.75rem 0 2rem; padding: 1.25rem; background: var(--surface-alt); border-radius: var(--r-md); }
.article-toc h2 { font-size: 1rem; margin: 0 0 .65rem; }
.article-toc ol { margin: 0; }
.article-toc a { display: inline-block; padding-block: .2rem; text-underline-offset: .2em; }
article blockquote { border-inline-start: 3px solid var(--primary-dark); margin-inline: 0; padding-inline-start: 1.2rem; }
.related-reading { margin-block: 2.5rem; }
.related-reading > h2 { font-size: 1.4rem; }
.related-reading .posts h3 { font-size: 1.1rem; }
.posts .when { font-size: .875rem; }
@media (max-width: 32rem) {
  .hero { gap: 1.5rem; }
  .hero .scene { min-height: 19rem; }
  .hero .scene canvas, .hero .scene img { width: min(16rem, 80%); }
  .hero-cta .waitlist { margin-top: 0; }
}
