:root{color-scheme:dark;--ink:#e8e2d4;--ink-dim:#bdb7a9;--panel:#171915;--raise:#20221c;--edge:#68583b;--gold:#dfae5e;--gold-ink:#dfae5e;--ink-on-gold:#221306;--gold-hi:#e3b871;--link:#dfae5e;--link-rule:#68583b;--edge-soft:rgba(104,88,59,.4);--font-body:system-ui, 'Segoe UI', Roboto, sans-serif;--font-display:Cinzel, Georgia, "Times New Roman", serif;--hp:#ff4c39;--pa:#4d96d7;--pm:#35b37e;--xp:#aa8b46;--el-t:#b2894e;--el-f:#e0663a;--el-e:#4aa6d6;--el-a:#8fc25a;--rar-common:#cfd6de;--rar-rare:#8fd0f0;--rar-legendary:#ffd873;--shadow:0 12px 36px rgba(23,25,21,.62), inset 0 0 0 1px rgba(23,25,21,.42);--gold-wash:rgba(223,174,94,.14);--gold-glow:rgba(223,174,94,.42)}
@font-face{font-family:Cinzel;src:url(fonts/cinzel-latin.woff2) format('woff2');
  font-weight:400 700;font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+2000-206F,U+2122,U+2191,U+2193,U+2212}
@font-face{font-family:Cinzel;src:url(fonts/cinzel-latin-ext.woff2) format('woff2');
  font-weight:400 700;font-display:swap;unicode-range:U+0100-024F,U+1E00-1EFF,U+2020,U+20A0-20AB}
*{box-sizing:border-box}
body{margin:0;background:var(--panel);color:var(--ink);
  font:17px/1.6 var(--font-body)}
a{color:var(--link);text-decoration:none}
h1,h2,.cta,.brand{font-family:var(--font-display);letter-spacing:.03em}
.wrap{max-width:68rem;margin:0 auto;padding:0 1.25rem}

/* ---- THE POSTER REGISTER (brief §1bis.1, refs WoW fr + Dofus « découvrir ») ----
   A section title is two to three times the size one dares by default, in Cinzel, broken over
   two lines ON PURPOSE, with a small eyebrow above carrying the fine print. That single choice
   is most of the difference between a landing and a documentation page. */
/*  ⚠ THE EYEBROW COLOUR CHANGED BECAUSE IT FAILED ON THE BARE PAGE, not because of the bands.
    #8a6a24 measures 4.43:1 against the page colour — under the 4.5 floor with NO art behind it
    at all, at 13 px in small caps. It had been that way since v1 and no gate reads colour pairs
    on this page. --ink-dim is 9.18:1 and is already a token, so the palette gains nothing new. */
.eyebrow{font-family:var(--font-display);font-size:.78rem;letter-spacing:.22em;
  text-transform:uppercase;color:var(--ink-dim);margin:0 0 .55rem}
.poster{font-family:var(--font-display);font-weight:700;line-height:.98;
  font-size:clamp(2.3rem,5.4vw,4.2rem);margin:0 0 .9rem}
.poster .l2{display:block}
.lede{font-size:1.05rem;max-width:34rem;margin:0 0 1.4rem}
.facts{list-style:none;padding:0;margin:1rem 0 1.3rem}
.facts li{padding:.26rem 0 .26rem 1.15rem;position:relative}
.facts li::before{content:"";position:absolute;left:0;top:.86em;width:.42rem;height:.42rem;
  background:var(--gold);transform:rotate(45deg)}

/*  ⛔ THE ORNATE PANEL RULE IS GONE, AND IT WAS ALREADY DEAD. Nothing on this landing has
    carried class orn since the v1 frame around the hero was VETOED (owner: un contour comme
    l UI ingame mais ca a aucun sens, ca rend moche) — measured before removing it: zero
    occurrences in the emitted index.html, both languages. What kept it visible was its url(),
    which published a 40 Ko ivory frame art nobody drew.
    ⚠ REMOVED BY READING ITS CONSUMERS, not by deleting a span: the same atom is used by the
    planner, where it has just been replaced by the obsidian plate for the reason written in
    scripts/theorycraft/skin.css. The rule and its picture leave together, which is why the new
    media assertion goes green rather than gaining an exception. */

/* ---- the jewel divider: the game's divider atom between sections ---- */
.div{display:flex;align-items:center;gap:.9rem;padding:2.6rem 0 .2rem}
.div::before,.div::after{content:"";flex:1;height:1px;
  background:linear-gradient(90deg,transparent,var(--edge),transparent)}
.div img{width:87px;height:10px;display:block;opacity:.92}

/* ---- v1.1 - A BAND IS A FULL-WIDTH BACKDROP WITH ART ON IT (brief §4bis) -------------
   The v1 band was a two-column grid: a thumbnail beside a panel of prose with bullet points.
   Owner on the combat band: « ça aussi c moche et inutile, privilégie le visuel au texte ».
   So the law is the SURFACE now: the art owns the band, the words are a cartouche on it.
   ⚠ THE BACKDROP IS A DECOR, NEVER A COMPETITOR. Each band names its own plate; the scrim is
   applied HERE, in CSS, never baked into the file - a baked darkness is one darkness forever,
   and the same plate is reused at another strength somewhere else.
   ⚠⚠ THE SCRIM IS NOT DECORATION, IT IS THE CONTRAST FLOOR. The hero already cost this pass a
   measured failure (eyebrow at 3.49:1 on a sky that LOOKED safe). Every band that puts type
   straight onto art carries a scrim strong enough to have been measured, not chosen by eye. */
/*  ⚠⚠⚠ overflow:clip, PAS overflow:hidden — ET LA PARALLAXE DES BANDES N'AVAIT JAMAIS BOUGÉ.
    Mesuré 2026-09-03 sur la page servie, à cinq positions de défilement couvrant 1 600 px:
    la matrice de .band::before rendait matrix(1.14,0,0,1.14,0,0) aux CINQ, c'est-à-dire une
    translation nulle partout, pour une keyframe qui demande +/- 3,5 % de la hauteur de la bande.
    La cause est celle que la bande de combat a déjà payée deux cents lignes plus bas, jamais
    appliquée aux autres: un ancêtre en overflow:hidden EST un conteneur de défilement, et
    view() se résout contre le conteneur de défilement le plus proche. Ce conteneur-là ne défile
    jamais, donc la timeline est INACTIVE et l'animation tient son image de remplissage — ce qui
    ressemble exactement à une parallaxe très douce, et n'en est pas une.
    ⚠ Rien n'était rouge et rien ne pouvait l'être: le lot v2 a livré cette parallaxe, l'a
    décrite dans son registre, et une capture d'écran ne distingue pas une plaque qui dérive de
    3 % d'une plaque immobile. Il a fallu lire la MATRICE à plusieurs hauteurs.
    ⚠⚠ LES DEUX DÉCLARATIONS, DANS CET ORDRE. overflow:clip découpe à l'identique et ne crée
    aucun conteneur de défilement; un navigateur qui ne le connaît pas ignore la seconde ligne et
    garde hidden, donc le fond sur-dimensionné reste enfermé partout. */
.band{position:relative;isolation:isolate;padding:4.2rem 0 4.6rem;overflow:hidden;overflow:clip}
.band::before{content:"";position:absolute;inset:0;z-index:0;background-size:cover;
  background-position:center}
.band::after{content:"";position:absolute;inset:0;z-index:1}
.band>.inner{position:relative;z-index:2;max-width:82rem;margin:0 auto;padding:0 1.25rem}
/*  ⛔ LE VOILE COMMUN DES BANDES CLAIRES EST RETIRÉ, PARCE QU'IL NE POUVAIT PLUS S'APPLIQUER À
    PERSONNE. Il disait « les bandes claires se dissolvent dans la page en haut et en bas, le
    milieu reste dégagé pour qu'on voie la peinture » — une règle d'ALTERNANCE, dans une page qui
    alternait clair et sombre. Cette alternance n'existe plus depuis V0 (le §3/O3 le dit en toutes
    lettres : chaque bande a besoin d'une décision de SOL, parce que pale/dark ne veut plus rien
    dire), et les trois bandes qui portent encore la classe l'annulent ou la remplacent une par
    une : le monde a sa rampe de mur de carte, les classes n'ont plus de peinture à voiler, la
    clôture monte depuis l'obsidienne. Une règle que ses trois seuls sujets écrasent est une règle
    morte, et une règle morte est ce que le prochain lecteur essaiera de comprendre.
    ⚠ LE NOM DE LA CLASSE SURVIT, et ce qu'elle veut dire aujourd'hui est « cette bande porte
    un cartouche en PLAQUE » (voir plus bas). Renommer une classe CSS est un changement d'API — la
    règle est écrite dans ce dépôt — donc le nom survit à son sens et c'est ce commentaire qui
    porte la différence. */
/*  The dark bands do the opposite: they are a room the page walks into. The ink is the chrome's
    own brown, never pure black - a landing that goes to 000 stops being the same object as the
    game it is selling. */
/*  ⚠⚠ THE FADE IS A FIXED STRIP IN rem, NOT A PERCENTAGE — and that is a measured correction.
    With percentage stops the dark band opened at the PAGE colour and only reached .72 at 16% of
    its own height; the title, which starts at the top, sat in near-white ON CREAM. A percentage
    ties the fade to the band's height, so the taller the band the further the text falls inside
    the pale part — the two things grow from different quantities, exactly the coupling the hero
    veil already taught this pass. A rem strip is the same 3.2rem everywhere, and the padding
    below is what guarantees no text ever lands in it. */
.band.dark{color:var(--ink);padding:6rem 0 6.4rem}
.band.dark::after{background:linear-gradient(to bottom,var(--panel) 0,rgba(23,25,21,.80) 3.2rem,
  rgba(23,25,21,.80) calc(100% - 3.2rem),var(--panel) 100%)}
.band.dark .eyebrow{color:var(--gold-ink)}
.band.dark .lede{color:var(--ink-dim)}
/*  ⚡⚡⚡ O-7 TRANCHÉE 2026-09-03 : « ok garde le lot, intègre A3 et A10 ». La bande du monde
    devient le MUR DE CARTE que le plan §3/O3 demande depuis le début — la carte peinte de Théran
    en pleine largeur, les onze tuiles de région flottant dessus comme des plaques.
    ⚠⚠ ET LE VOILE DESCEND DE .34 À .22 AU MILIEU, CE QUI EST LE SEUL CHANGEMENT DE SOL DE CES
    DEUX VAGUES QUI VA DANS LE MAUVAIS SENS. Il est légitime ici pour une raison qui se vérifie et
    pas pour une raison de goût: **plus aucun glyphe de cette bande ne se pose sur l'art.** Le
    cartouche est une plaque en verre depuis V2 (bornée par construction, indépendante de l'image
    qui passe derrière) et chaque légende de tuile vit dans SA tuile, sur le dégradé de la tuile.
    La question « ce voile porte-t-il du texte » a une réponse mesurable, et c'est non.
    ⚠ La plaque B1 du monde n'est ni supprimée ni dé-générée: elle cesse simplement d'être NOMMÉE,
    donc l'étape de copie ne la publie plus. C'est le mécanisme, pas un effet de bord. */
/*  ⚠⚠⚠ ET LE CADRAGE cover EST LE MAUVAIS POUR UNE CARTE, CE QUE SEULE LA PLANCHE A DIT. Toutes
    les autres bandes portent un DÉCOR, dont n'importe quel morceau vaut n'importe quel autre;
    celle-ci porte un OBJET, dont le sujet est la position de douze lieux les uns par rapport aux
    autres. Mesuré: la bande fait 1 057 px de haut à 1440 comme à 1920 (sa hauteur vient des
    tuiles, pas de la fenêtre), et cover sur une carte 16:10 y agrandit 1,56× — la planche
    montrait un fragment brun sans un seul repère reconnaissable. Une image dont on ne peut pas
    lire le sujet est une texture, et on ne commande pas une carte pour obtenir une texture.
    ⇒ 100% auto: la carte prend la LARGEUR et garde son rapport. Mesuré aux quatre largeurs, en
    % de la hauteur de bande: **114 % à 1920 · 85 % à 1440 · 76 % à 1100**. Elle déborde donc en
    bas sur un grand écran (aucune couture) et se termine ailleurs DERRIÈRE LA GRILLE DE TUILES,
    dont le bord haut est à 432 px et le bas à 983 — la couture tombe à 900 à 1440 et à 688 à
    1100, dans les deux cas entre deux rangées de plaques. Ce n'est pas de la chance à laisser
    tacite: si la grille change de hauteur, cette couture sort au grand jour.
    ⚠ SOUS 50 rem LA CARTE REDEVIENT UNE TEXTURE, ET C'EST ASSUMÉ. La bande y fait 1 770 px pour
    393 de large: la carte à 100 % de largeur y serait un ruban de 246 px, soit 14 % de la bande —
    un timbre-poste en haut d'un mur vide. Aucun cadrage ne rend une carte 16:10 lisible dans une
    colonne deux fois plus haute que large, donc le téléphone garde cover et reçoit un sol
    peint. La carte est une information de BUREAU; les onze tuiles, elles, sont partout. */
/*  ⛔ LE SOL PEINT DE CETTE BANDE EST RETIRÉ, ET AVEC LUI SON VOILE ET SA BRUME. La carte est
    passée en ÉLÉMENT (voir .mapwall, et le commentaire dans le markup): la garder AUSSI en fond
    ferait payer 189 Ko deux fois pour montrer la même image deux fois, une nette et une en
    texture. Un voile sans art à voiler ne peut plus qu'éteindre — c'est l'argument que la bande
    des classes a déjà payé, mot pour mot, une vague plus tôt.
    ⚠ La brume part pour la même raison: .bhaze est une vignette + un halo doré posés SUR une
    peinture, et sur un sol qui EST --panel elle n'ajoute rien qu'un assombrissement des bords
    d'une bande qui n'a plus de bords à assombrir. */
.band-world::before{background-image:none}
.band-world::after{background:none}
.band-world .bhaze{background:none}
/*  ⚡⚡⚡ V6-2 — LE MUR DE CARTE. Enfant direct de la bande (donc à SA largeur, pas à celle du
    wrap de 82 rem), l'image à 100 % et son rapport à elle.
    ⚠⚠ LE FONDU EST UN CALQUE PAR-DESSUS, PAS UN MASQUE. Un mask-image découperait l'image et
    laisserait passer ce qu'il y a derrière — ici la page, ce qui donnerait le même résultat —
    mais il n'est supporté qu'avec préfixe sur une partie du parc, et une carte dont les bords se
    coupent net sur un navigateur et se fondent sur l'autre est une image qui n'a pas de forme.
    Un dégradé de --panel vers transparent est la même image partout.
    ⚠ LE MILIEU EST À ZÉRO: c'est là tout le lot. Le voile de V2 valait .22 au centre et .52 en
    haut, et il était légitime pour un FOND; sur l'objet lui-même il n'y a rien à borner, parce
    qu'aucun glyphe de cette bande ne se pose dessus — le cartouche est au-dessus, chaque légende
    de tuile est dans sa tuile. La question « ce voile porte-t-il du texte » a une réponse
    mesurable, et c'est toujours non.
    ⚠ 6 % EN HAUT ET 4 % EN BAS SEULEMENT: le brief d'A3 fait peindre la carte bord à bord, donc
    tout ce qu'un fondu mange est de la carte. Il n'est là que pour qu'elle ne finisse pas sur un
    rectangle. */
/*  ⚡⚡⚡ V9-3 — LE PLAFOND DE HAUTEUR (ruling owner C du 2026-09-04: plafonnée ~60 vh, centrée,
    à son rapport exact). À 1440 la carte sortait à 900 px de haut pour 1 440 de large et faisait
    à elle seule 45 % d'une bande de 2 002: c'est le seul endroit d'où les px manquants pouvaient
    venir, la grille ne rendant que 551.
    ⚠⚠⚠ ET LA BOÎTE DES ÉPINGLES EST L'IMAGE, PAS LA FIGURE — C'EST TOUTE LA CONSTRUCTION.
    Une épingle est un POURCENTAGE (§15.5): si son bloc conteneur est plus large que l'image,
    chaque pourcentage tombe à côté, sur une carte qui a toujours l'air d'une carte — rien
    n'erre, rien ne rougit, et le phare est dans la mer. La figure fait donc toute la largeur de
    la bande et CENTRE un .mapwall__board en inline-block, qui se rétracte EXACTEMENT sur
    l'image qu'il contient (les épingles sont absolues, donc hors flux, donc elles ne le
    gonflent pas). Le plafond peut alors bouger sans que personne re-vérifie les épingles: une
    réduction UNIFORME ne déplace aucun pourcentage, c'est cover qui est interdit, pas la
    réduction.
    ⚠⚠ width:auto et height:auto SONT PORTANTS. Les attributs width/height sur l'image posent la boîte
    (loi déjà payée sur les tuiles: 333x450 pour un fichier 16/9), donc un max-height seul
    aurait été traversé par la largeur imposée. Et c'est l'inverse du piège du §15.6: ici il n'y
    a aucun aspect-ratio, l'image porte son rapport elle-même, donc le plafond reste un
    plafond de HAUTEUR au lieu de devenir un plafond de largeur.
    ⚠ LE FONDU DESCEND SUR LE BOARD, PAS SUR LA FIGURE: il borde l'IMAGE, et sur la figure il
    aurait dessiné un cadre de 1 440 px autour d'une carte de 864. */
.mapwall{position:relative;z-index:2;margin:0 0 1.5rem;width:100%;line-height:0;text-align:center}
.mapwall__board{position:relative;display:inline-block;max-width:100%;line-height:0}
.mapwall img{display:block;width:auto;height:auto;max-width:100%;max-height:60vh}
.mapwall__board::after{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  background:linear-gradient(to bottom,var(--panel) 0,rgba(23,25,21,0) 6%,
    rgba(23,25,21,0) 96%,var(--panel) 100%),
  linear-gradient(to right,var(--panel) 0,rgba(23,25,21,0) 4%,rgba(23,25,21,0) 96%,var(--panel) 100%)}
.band-fight::before{background-image:url(media/combat.webp);filter:blur(9px) saturate(.85);
  transform:scale(1.08)}
/*  ⚡⚡ O3 — LA BANDE DES CLASSES N'A PLUS DE PEINTURE DU TOUT, et c'est le seul sol de ce lot
    qui change vraiment de nature. Le plan §3/O3 l'écrit: « obsidian, the arc on a radial gold
    glow + Runefield. K1 retires. » Ce n'est pas une décision d'ART en attente d'une image —
    c'est la décision que cette bande-là n'en veut pas: neuf blasons transparents posés sur une
    rue peinte étaient neuf silhouettes qui se battaient avec un décor, et la mesure du lot L5
    le disait déjà en creux (le nom d'un blason avait besoin d'un SOL pour être lisible, et ce
    sol était une plaque opaque par-dessus la peinture — donc la peinture ne servait plus).
    ⚠ ET LA PLAQUE CESSE D'ÊTRE PUBLIÉE TOUTE SEULE. L'étape de copie des médias lit les
    RÉFÉRENCES depuis V0: plus une seule règle ne nomme la peinture des classes, donc elle ne
    part plus. La SOURCE reste dans assets/site_media/ — rien n'est supprimé, ça arrête de shipper.
    ⚠⚠⚠ ET CE COMMENTAIRE A GARDÉ LE FICHIER EN VIE PENDANT UN BUILD. Sa première rédaction
    ÉPELAIT le chemin de la plaque pour expliquer qu'il ne servait plus — et cette feuille est
    servie AVEC ses commentaires (à la différence de celle du codex, que CSS_PUBLIC nettoie).
    Le scan de références ne sait pas lire une intention: il a vu le chemin, l'a compté comme une
    demande, et la peinture a continué de shipper sous une assertion VERTE. Une note qui nomme un
    fichier EST une référence à ce fichier.
    ⚠ Le voile de bande est retiré AVEC la peinture: .band.pale::after est une rampe de --panel
    sur de l'art, et sur un sol qui EST --panel elle ne fait qu'éteindre la seule chose qui
    reste, la nappe. Un voile sans art à voiler est une couche qui ne peut que retirer. */
.band-class::before{background-image:radial-gradient(70% 58% at 50% 46%,rgba(223,174,94,.16) 0,
  rgba(223,174,94,.05) 44%,rgba(223,174,94,0) 72%)}
.band-class::after{background:none}
/*  ⚡⚡⚡ V6-3 — A4 REMPLACE LA TAVERNE (defaut D3). Le §3/O3 l'avait ecrit dès le premier jour
    (« sol = A4, la bannière du bestiaire, assombrie ») et la vague V3 l'a peinte pour le hub du
    codex; elle est restée ici en plaque B1 une vague de trop.
    ⚠⚠ ET LE VOILE NE BOUGE PAS, CE QUI EST UNE MESURE ET PAS UNE OMISSION. A4 est deux fois et
    demie plus sombre que la taverne (luminance moyenne 39,8 contre 99,2 sur 255), donc le
    réflexe est de lever le voile pour la voir. Il n'y a pas la place: la lède de cette bande
    (« 61 créatures peuplent les régions ») est en --ink-dim et se pose DIRECTEMENT sur l'art,
    et sur le pire sol qu'un voile puisse recevoir — un art BLANC PUR — --ink-dim ne franchit AA
    qu'à partir de 86 % de voile. Le .80 en vigueur est déjà le plafond de ce que cette bande
    peut donner à son image; le baisser à .70 mettrait sa seule phrase à 3,3:1.
    ⚠ L'échange va donc dans le sens QUI NE DEMANDE PAS DE NOUVELLE LECTURE: l'image nouvelle
    est plus sombre que l'ancienne, donc tout point de cette bande reçoit au moins autant de
    contraste qu'avant. Une modification monotone dans le bon sens se prouve par son sens. */
.band-beast::before{background-image:url(media/band-bestiaire.webp)}
/*  ⛔ « LA MÊME IMAGE, LUE UNE SECONDE FOIS » N'EST PLUS VRAI, ET LA JUSTIFICATION PART AVEC LA
    RÈGLE. Cette ligne portait le fichier du héros et cet argument-là ; A10 est une image faite
    POUR cette place — quatre personnages de dos devant une porte runique ouverte, vus au moment
    où l'on décide d'entrer. La page ne se referme plus sur son propre début, elle se referme sur
    ce qu'elle a promis. Un raisonnement écrit dans un commentaire est une décision qui ne se
    relit plus: il se remplace quand la règle change, il ne se laisse pas dormir à côté. */
/*  ⚡ O3 — ET ELLE MONTE DEPUIS L'OBSIDIENNE (plan §3/O3: « bottom-to-top obsidian gradient »).
    La rampe des bandes claires était SYMÉTRIQUE — sombre en haut, sombre en bas, l'art au milieu —
    ce qui est juste pour une bande qu'on TRAVERSE et faux pour celle où la page s'arrête: le
    lecteur y descend, et la peinture doit s'ouvrir devant lui puis se refermer sur le pied de
    page. La rampe part donc de --panel en haut, laisse l'art respirer au tiers supérieur et
    redevient opaque au bas, où le pied de page prend le relais.
    ⚠ ELLE NE PEUT QUE FONCER. Le milieu de l'ancienne rampe valait .34 et celui-ci vaut .42:
    tout point de cette bande reçoit AU MOINS autant de voile qu'avant, donc le halo mesuré du
    cartouche (§ plus bas, .82 sur le centre de la plaque) garde toute sa marge. Une nouvelle
    lecture de contraste n'est pas nécessaire pour une modification monotone dans le bon sens —
    et l'aurait été dans l'autre. */
/*  ⚠⚠⚠ ET LA RAMPE ÉCRITE POUR L'ANCIENNE PLAQUE ÉCRASAIT CELLE-CI. Elle avait été réglée sur
    une peinture d'HEURE DORÉE — un ciel clair qu'il fallait éteindre — et A10 est une scène de
    NUIT dont les deux tiers hauts sont déjà presque noirs. À .42 au milieu et .74 aux trois
    quarts, la planche ne montrait plus ni les quatre personnages ni l'arche: un voile ne sait pas
    qu'il voile du noir. Un réglage de voile appartient à SON image, et il périme avec elle.
    ⚠⚠ LA BANDE FAISAIT 556 px POUR 1440 DE LARGE, SOIT UN RAPPORT DE 2,59 CONTRE 1,78 À L'IMAGE:
    cover y coupait le HAUT ET LE BAS et ne gardait qu'une tranche médiane — le ciel et la moitié
    haute de l'arche, c'est-à-dire exactement la partie que le brief réserve à la plaque, et rien
    du groupe qui est le sujet. 56 rem de hauteur minimale mettent l'image entière dans le cadre
    (l'échelle passe de la largeur à la HAUTEUR: 1 594 px de large pour 1 440 visibles, donc 77 px
    rognés de chaque côté, et le groupe occupe 28 % à 72 % de la largeur — il ne peut pas sortir).
    La plaque tient alors le ciel, de 122 à 447 px, et les têtes commencent à 448.
    ⚠ SOUS 50 rem LA HAUTEUR MINIMALE NE S'APPLIQUE PAS: sur un téléphone elle ferait une bande
    de 900 px pour un titre et deux boutons. */
.band-play::before{background-image:url(media/play-runegate.webp)}
@media (min-width:50rem){.band-play{min-height:56rem}}
.band-play::after{background:linear-gradient(to bottom,var(--panel) 0,rgba(23,25,21,.62) 9%,
  rgba(23,25,21,.20) 34%,rgba(23,25,21,.10) 62%,rgba(23,25,21,.42) 100%)}

/* ---- v2/L7 - LA BANDE DES CHIFFRES -------------------------------------------------
   ⚠⚠ LES NOMBRES SONT DE L'ENCRE, PAS DE L'OR, ET C'EST UNE MESURE QUI TRANCHE, pas un goût.
   --gold-ink vaut 2,76:1 sur le panneau (laws.json le consigne, le ruling D6 le declare
   display-only a 18 px / 700). C'est SOUS le plancher 3:1 des grands caracteres, et cette bande
   n'existe que pour faire lire six nombres: doree, elle aurait mis sa seule raison d'etre sous
   le plancher. L'or y fait ce que l'or est declare faire dans laws.json - un FILET au-dessus de
   chaque cellule, un accent, jamais l'encre.
   ⚠ min-width EN ch SUR LE CHIFFRE, PARCE QU'IL COMPTE. Un compteur qui passe de 9 a 61 puis a
   364 change trois fois de largeur et recentre la cellule a chaque fois. --d porte le nombre de
   chiffres de la valeur FINALE, ecrit par le generateur qui la connait, donc la boite est
   reservee des le premier rendu et rien ne bouge pendant la montee. */
.band.tally{padding:2.9rem 0 3.1rem}
.band.tally .eyebrow{text-align:center;margin:0 0 1.5rem}
.tallies{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(8rem,1fr));gap:1.1rem}
.tallies li{position:relative;padding-top:.95rem}
.tallies li::before{content:"";position:absolute;top:0;left:14%;right:14%;height:2px;
  background:linear-gradient(90deg,transparent,var(--edge),transparent)}
/*  ⚡⚡ O3 — LA CELLULE EST UNE PLAQUE ET LE NOMBRE EST DORÉ, ET LES DEUX SONT LA MÊME MESURE
    RETOURNÉE. Le commentaire au-dessus de cette section est écrit POUR L'IVOIRE: --gold-ink y
    valait 2,76:1 sur le panneau, donc « dorée, cette bande aurait mis sa seule raison d'être
    sous le plancher ». Sur l'obsidienne le même jeton mesure 8,73:1 avec un delta rec601 de
    155,6 — l'assertion V3-3 le lit dans la feuille émise à chaque build. La règle n'a pas
    changé (un nombre ne descend jamais sous le plancher); c'est le SOL qui a changé, et la
    conclusion suit. Le ruling D6 tient toujours: --gold-ink est display-only, et ces six
    nombres sont du display — 32 à 48 px en 700.
    ⚠ LA PLAQUE EST PLUS SOMBRE QUE LA PAGE, PAS PLUS CLAIRE. --raise à 62 % sur --panel donne
    un sol entre les deux jetons, donc les deux paires que V3-3 garde (ink/panel et ink/raise)
    bornent ce fond des deux côtés sans qu'aucune troisième couleur n'entre dans la feuille. */
.tallies a{display:block;text-align:center;color:inherit;padding:.7rem .5rem .8rem;
  border-radius:.3rem;border:1px solid var(--edge);background:rgba(32,34,28,.62);
  box-shadow:var(--shadow);
  transition:transform .2s ease,background .2s ease,border-color .2s ease}
.tallies .n{display:inline-block;min-width:calc(var(--d,3) * 1ch);
  font-family:var(--font-display);font-weight:700;font-variant-numeric:tabular-nums;
  font-size:clamp(2rem,4.2vw,3rem);line-height:1;color:var(--gold-ink)}
.tallies .k{display:block;margin-top:.35rem;font-family:var(--font-display);
  font-size:.78rem;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-dim)}
.tallies a:hover,.tallies a:focus-visible{transform:translateY(-3px);
  background:rgba(223,174,94,.14);border-color:var(--gold);outline:none}
.tallies a:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.tallies a:hover .k,.tallies a:focus-visible .k{color:var(--ink)}
/* ---- PRE-REGISTRATION: the landing band, and the page it opens (lots P-L / P-L2) ----
   The backdrop is the Runegate arch, already shipped for the world card - a band that opens a
   door reuses the picture of a door rather than commissioning a thirty-eighth webp. */
/*  ⚡⚡ V6-3 — ET LA PREINSCRIPTION PASSE SUR A7. Le commentaire au-dessus se lit encore juste
    (« une bande qui ouvre une porte reutilise la photo d'une porte ») et il reste vrai: A7 est le
    monde vu d'en haut, avec ses arches. Ce qui n'est plus vrai, c'est « deja shippe pour la carte
    du monde » — cette carte est morte en V2, et waystone.webp ne servait plus qu'ici, dans un
    fichier de 800 px etale sur 1440 puis 1920.
    ⚠ Le voile reste à .80 pour la meme raison mesuree qu'au bestiaire: la lède de cette bande
    est en --ink-dim, sur l'art, sans plaque. */
.band-prereg::before{background-image:url(media/band-runegates.webp)}
.prereg{display:flex;gap:2.4rem;align-items:flex-start;justify-content:center;
  max-width:58rem;margin:0 auto;flex-wrap:wrap}
.prereg.teaser{align-items:center;max-width:52rem}
.pr-pet{width:15rem;height:auto;flex:0 0 auto;
  filter:drop-shadow(0 14px 26px rgba(23,25,21,.6))}
/*  ⚠ LE COLLANT SE DÉCOLLE SOUS LE DOCK, PAS SOUS LE HAUT DE LA FENÊTRE. Un top de 2rem posait
    le Fouineur derrière une barre fixe de 4rem dès le premier défilement. */
.pr-pet.big{width:19rem;position:sticky;top:calc(var(--dock-h) + 1rem)}
.pr-form{flex:1 1 24rem;min-width:min(100%,20rem)}
.pr-say{color:var(--ink-dim);margin:0 0 1.2rem}
/* A stat line is one token to a reader: +10 and Sagesse must not fall on two lines. */
.band-prereg .lede b{white-space:nowrap}
.pr-act{margin:1.1rem 0 0;display:flex;gap:.6rem;align-items:center;flex-wrap:wrap}
/* THE [hidden] ATTRIBUTE IS A UA RULE AND A CLASS BEATS IT. .pr-act display:flex outranks
   the UA rule on specificity, so the script hid the connect row and the browser kept drawing
   it - the signed-in reader saw a Connect button sitting above the form it had already opened.
   Measured on the verification plate, 2026-08-30. Anything in this band that the script hides
   needs the attribute restated at class strength. */
.prereg [hidden],.prereg [hidden].pr-act,.prpage [hidden]{display:none}
/* A DISABLED BUTTON STILL HAS TO BE READ - it is carrying the refusal. Dimming it to the
   usual .45 put the one sentence that explains the state under the contrast floor. */
.prereg .cta[disabled]{opacity:.72;cursor:default;box-shadow:none;transform:none}
.pr-fields{margin:0}
.pr-handle{display:block;margin:0 0 1rem}
.pr-handle span{display:block;font-size:.86rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--gold-ink);margin:0 0 .35rem}
.prereg input[type=text]{width:100%;max-width:22rem;padding:.55rem .8rem;border-radius:.3rem;
  border:1px solid var(--edge);background:rgba(23,25,21,.55);color:var(--ink);font:inherit;font-size:1rem}
.prereg input[type=text]:focus-visible{outline:2px solid var(--gold);outline-offset:1px}
.prereg input[readonly]{color:var(--ink-dim)}
.pr-in{color:var(--ink-dim)}
.pr-in b{color:var(--gold-ink)}
.pr-count{color:var(--gold-ink);font-size:.92rem;margin:.7rem 0 0}

/* ---- the three numbered steps of the dedicated page ---- */
.pr-step{border-top:1px solid var(--edge);padding:1.6rem 0 0;margin:1.9rem 0 0}
.pr-step:first-child{border-top:0;padding-top:0;margin-top:0}
.pr-step h3{font-family:var(--font-display);font-size:1.18rem;font-weight:700;
  letter-spacing:.03em;color:var(--ink);margin:0 0 .7rem;display:flex;align-items:center;gap:.7rem}
.pr-step h3 .num{flex:0 0 auto;width:1.75rem;height:1.75rem;border-radius:50%;
  display:grid;place-items:center;font-size:.95rem;
  background:linear-gradient(var(--gold-hi),var(--gold));color:var(--ink-on-gold)}
/* ---- the tasks: a link that ticks its own box ---- */
.pr-tasks{list-style:none;padding:0;margin:0}
.pr-task{display:flex;align-items:center;gap:.7rem;padding:.34rem 0}
.pr-task input{flex:0 0 auto;width:1.05rem;height:1.05rem;accent-color:var(--gold)}
.pr-task a{color:var(--link);text-decoration:underline;text-underline-offset:3px}
.pr-task a:hover{color:var(--gold-hi)}
.pr-task input:checked+a{color:var(--ink-dim)}
/* The check the reader watches. It verifies nothing - see the script's own note; it exists
   because a wait somebody watches run is what makes them go and do the thing. */
.pr-chk{display:none;align-items:center;gap:.45rem;font-size:.86rem;color:var(--gold-ink)}
.pr-chk.on,.pr-chk.done{display:inline-flex}
.pr-chk.done{color:var(--pm)}
.pr-chk .spin{display:none;width:.8rem;height:.8rem;border-radius:50%;
  border:2px solid rgba(223,174,94,.32);border-top-color:var(--gold-ink);animation:prspin .7s linear infinite}
.pr-chk.on .spin{display:block}
@keyframes prspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.pr-chk .spin{animation-duration:2.4s}}

/* ---- the page itself: the landing's bands on a page that is only this ---- */
/*  ⚡ O4 — LA BARRE DE RETOUR (.prhead) EST PARTIE AVEC LE DOCK QUI LA REMPLACE. Ses trois
    règles sont retirées ici plutôt que laissées à dormir: une règle qui ne peut plus matcher
    est une règle que le prochain lecteur essaiera de comprendre.
    ⚠ LE PADDING DU HAUT EST CELUI DU DOCK, QUI EST FIXE. Le dock ne prend pas de place dans le
    flux, donc sans ce padding la première bande commence SOUS lui. Sur l'accueil la question ne
    se pose pas: le héros est un art plein cadre et le dock flotte dessus, ce qui est le lot. */
.prpage{background:var(--panel);padding-top:var(--dock-h)}
.prpage footer{text-align:center;padding:2.2rem 1.25rem 3rem;color:var(--ink-dim);font-size:.92rem}
.prpage footer a{color:var(--gold-ink)}
@media (max-width:640px){.pr-pet,.pr-pet.big{width:12rem;position:static}.prereg{gap:1.4rem}}

/*  The cartouche is the band's whole text: centred, short, one sentence. Not a panel of prose.
    ⚠⚠ ON A PALE BAND IT CARRIES ITS OWN HALO, AND THE NUMBER IS MEASURED, NOT PICKED.
    The band scrim sits at .34 in the middle so the paint is actually seen — and against the
    darkest 1% of the CENTRE of the plates that is 2.85:1 for the title and 1.26:1 for the
    eyebrow, both far under floor. Raising the whole band scrim would have fixed the text by
    erasing the picture. The halo raises only the ground the words stand on: .82 there, the
    band's own .34 everywhere else.
    ⚠ THE PERIMETER OF THAT MEASUREMENT IS THE PLATE'S CENTRE, AND THAT IS AN ARGUMENT, NOT A
    CONVENIENCE: background-size:cover with position:center maps the image centre to the band
    centre at EVERY width — the crop only ever eats edges — and the cartouche is centred too.
    Measured on the central 60%: worst ground 20.9 luma (K3), so at .82 the title reads 11.0:1
    and the eyebrow 6.3:1 on all three plates.
    ⚠ The halo is an ELLIPSE whose plateau reaches 96% of the box width, not a rectangle: a hard
    edge would put a visible ivory box on the paint, which is the thing the band exists to avoid. */
.cartouche{text-align:center;max-width:44rem;margin:0 auto 2.2rem}
/*  ⚡⚡⚡ O3 — LE HALO ELLIPTIQUE DEVIENT UNE PLAQUE, ET C'EST UN DÉFAUT VU SUR UNE PLANCHE, PAS
    UN GOÛT. Le halo est un radial-gradient dont les RAYONS valent 86 % et 104 % de la boîte:
    au-delà de 50 % le dégradé est COUPÉ par le bord au lieu de s'y éteindre, donc il atteint
    l'arête à 0,82 d'opacité. Sur l'ivoire, une arête crème sur une peinture claire ne se voyait
    pas; sur l'obsidienne c'est un RECTANGLE SOMBRE aux quatre côtés nets, posé sur la bande du
    monde. Mesuré sur la page servie à 1440, il court de x=368 à x=1071.
    ⚠ ET LE RÉPARER EN DÉPLAÇANT LES ARRÊTS NE MARCHE PAS. Pour qu'un dégradé s'éteigne avant le
    bord il faut un rayon de 50 %, et le titre remplit alors les coins de l'ellipse, où l'opacité
    tombe sous le plancher: les glyphes extrêmes lisent 3,8:1. Un halo assez large pour porter du
    texte est un halo qui touche son propre bord. La forme est le problème, pas ses nombres.
    ⇒ LA PLAQUE EST LA GRAMMAIRE DU JEU (§2.3), et elle est BORNÉE PAR CONSTRUCTION: --raise à
    72 %, donc sur un art blanc pur — le pire sol possible — le fond vaut rgb(94,96,92), --ink y
    mesure 4,92:1 et le delta rec601 131. Le halo ne garantissait ce plancher qu'au CENTRE de la
    plaque, et seulement parce que quelqu'un avait échantillonné les trois peintures d'alors.
    ⚠ LES BANDES SOMBRES N'EN PRENNENT PAS. Leur voile vaut .80 sur toute leur hauteur: elles ont
    déjà un sol, et leur en poser un second serait une boîte dans une boîte.
    ⚠ UNE SEULE DÉFINITION DU VERRE POUR LES CARTOUCHES. La bande de clôture ne porte plus de
    classe à elle: elle est claire, donc elle est servie par cette règle-ci. */
/*  ⚡ V6-6 — LES QUATRE COINS D'A9 SUR LE CARTOUCHE, qui est la plaque majeure de l'accueil.
    Sur un pseudo-element et pas sur la boite: un border-image remplace la bordure et annule
    le rayon, donc posé ici il aurait échangé le filet et les coins arrondis contre l'ornement.
    ⚠ PAS DE GRAIN ICI. Le cartouche est du VERRE à 72 % sur une peinture, pas une plaque
    opaque: une texture cuite a 8 % sur panelRaise y serait posee sur un fond qui n est pas
    panelRaise, donc la seule chose que le ruling exige - que la moyenne ne bouge pas - n'y
    serait plus vraie par construction. Le grain va où le jeton est la couleur. */
.band.pale .cartouche{position:relative;padding:1.7rem 2.2rem 1.9rem;border-radius:.5rem;
  border:1px solid var(--edge);box-shadow:var(--shadow);
  background:rgba(32,34,28,.72);
  -webkit-backdrop-filter:blur(14px) saturate(1.2);backdrop-filter:blur(14px) saturate(1.2)}
@supports not ((backdrop-filter:blur(2px)) or (-webkit-backdrop-filter:blur(2px))){
  .band.pale .cartouche{background:rgba(32,34,28,.94)}
}
/*  ⚠ A ROW OF BUTTONS IS NOT A CARTOUCHE. It first inherited the halo and the band then showed
    TWO pale rectangles, the second one framing a single button. The halo exists to carry glyphs
    over paint; a button already carries its own ground. */
.band.pale .cartouche::before{content:"";position:absolute;inset:0;pointer-events:none;
  z-index:1;opacity:.65;border:22px solid transparent;
  border-image:url(media/orn-corner.webp) 128 / 22px stretch}
@media (max-width:40rem){.band.pale .cartouche::before{border-width:14px;border-image-width:14px}}
.ctarow{text-align:center;margin:2.2rem 0 0}
.cartouche .lede{max-width:38rem;margin:0 auto 1.3rem;font-size:1.06rem}
.cartouche .poster{font-size:clamp(2rem,4.6vw,3.4rem)}

/*  ⚠ THE COMBAT PLATE BLEEDS PAST THE CONTENT COLUMN ON PURPOSE (brief §4bis.2): it is the slot
    the gameplay video will take, and a video that sits politely inside a 68rem column announces
    itself as an illustration. Capped so it never out-grows a wide desktop.
    ⚠⚠ IT IS A DIRECT CHILD OF THE BAND, NOT OF THE CONTENT COLUMN, and that is the whole trick.
    The first version lived inside the column and used margin-left:50% + translateX(-50%) — the
    classic full-bleed escape — which resolves 50% against the PARENT, not the viewport: measured
    at 1552 px it landed the plate starting at x=780 and running off the right edge. Hanging it
    on the full-width band makes 100% mean the viewport and the centring is plain auto margins,
    with no negative arithmetic to be wrong about. */
/*  ⚠ z-index 2 like the content column: the band's scrim is a pseudo-element at z-index 1, so a
    plate that only escaped the column would have been PAINTED OVER by the veil meant for it. */
.bleed{display:block;position:relative;z-index:2;width:min(100%,96rem);margin:0 auto;height:auto;
  border-top:1px solid var(--edge);border-bottom:1px solid var(--edge);
  box-shadow:0 10px 40px rgba(23,25,21,.45)}
/* HERO — the plate fills the frame and an ivory veil rises at its foot so the type stays legible
   over whatever the capture happens to show there. */
/* ---- HERO: full-bleed plate, the game's ornate frame over it, poster type on a veil.
   ⚠ The frame is an OVERLAY, not a border on the image: a border would shrink the plate and the
   whole point is that the art reaches the edges and the gold sits ON it. */
/* ---- HERO ----------------------------------------------------------------------
   ⚠⚠ THE TYPE IS NOT ABSOLUTELY POSITIONED BY DEFAULT, and that is a correction. Overlaying it
   is right on a wide screen and WRONG on a phone: measured at 375 px, the poster title made the
   text block 228% of the hero's own height, so an absolutely-positioned block overflowed 256 px
   ABOVE the image, onto nothing. The art is a fixed aspect and the type is not — they cannot be
   locked together at every width. So: stacked by default (art, then type on ivory), overlaid
   only once the hero is tall enough to hold it. */
.hero{position:relative;background:var(--panel)}
.hero .plate{position:relative;overflow:hidden}
/*  ⚠⚠⚠ :not(.hposter) ON EVERY ONE OF THESE, AND IT IS NOT DEFENSIVE — IT IS A REPAIR.
    These five rules were written when the plate held exactly ONE image, so they say "the
    image in the hero plate" and mean "the painted key-art". O1 put a second img in that
    same box, and .hero .plate img (0,2,1) outranks .hposter (0,1,0): the poster was
    served with object-fit COVER at 702 px tall, i.e. stretched across the whole band, and
    it also inherited the drift animation. Measured, not guessed: the computed style came
    back objectFit: cover and height: 702px against a rule that asks for contain at
    68,7 %. A selector that describes a POSITION in the DOM stops being true when the DOM
    gains a sibling; naming what it is about is the fix.  */
.hero img:not(.hposter),.hero video{width:100%;height:auto;display:block;opacity:.94}
/*  ⛔ THE ORNATE FRAME OVER THE HERO IS REMOVED — owner veto: « un contour comme l'UI ingame
    mais ça a aucun sens, ça rend moche ». The rule it breaks is worth keeping written down,
    because the A5 pass will be tempted by exactly this again: the game's vocabulary belongs to
    PANELS and CARDS — things that sit inside a page — and the viewport is not a game window.
    A frame around the whole page frames the BROWSER, which nobody asked to look at. */
.hero .veil{display:none}
.hero .say{position:relative;z-index:4;background:var(--panel);padding:1.6rem 1.25rem 2rem;text-align:center}
.hero .poster{font-size:clamp(2.4rem,8vw,6rem);margin-bottom:.45rem}
.hero .say p{margin:0 auto 1.4rem;max-width:36rem;font-size:clamp(1rem,2.2vw,1.16rem);color:var(--ink-dim)}
@media (min-width:58rem){
  .hero{overflow:hidden}
  /* ⚠ THE PLATE GETS HEIGHT, THE TYPE DOES NOT SHRINK. Measured at 1265 px: a 1800x874 plate is
     614 px tall there, and the poster block needs ~594 — the scrim then covered 97% of the art,
     which is a hero in name only. Giving the plate a viewport-relative min-height and letting it
     COVER means the art crops (it is a wide city plate, it survives a crop) instead of the type
     apologising for it. The width/height attributes still describe the FILE, which is what they
     are for; CSS describes the display. */
  .hero .plate{min-height:78vh}
  /*  ⚠⚠ 58 % ET PLUS 42 %, PARCE QUE LE SUJET DU BAS A CHANGÉ. Sur la plaque B1 le sujet utile
      était l'arche CENTRÉE, en haut du cadre. A1 pose une estrade de pierre gravée dans le bas
      du cadre — c'est la scène sur laquelle le corps se tient — et un recadrage qui garde le
      ciel jette précisément le sol. Le rapport de la bande (2,05 à 1440) est plus large que
      celui de l'image (1,78), donc cover ajuste la LARGEUR et ne laisse de liberté que
      verticalement: ces 16 points sont tout ce qu'il y a à donner, et ils vont au sol. */
  .hero .plate img:not(.hposter){height:78vh;min-height:78vh;object-fit:cover;object-position:50% 58%}
  /* ⚠⚠ THE SCRIM IS ON THE TEXT BLOCK, NOT ON THE HERO. A veil sized as a percentage of the
     IMAGE and a text block sized by its own content cannot stay in step: measured at 1265 px the
     veil covered the lower 72% while the eyebrow sat at 39 px from the top, on bare art, and no
     percentage fixes that at every width because the two grow from different things. Painting
     the gradient on the block that needs it makes the coupling structural instead of hoped-for. */
  /* ⚠⚠ THE RAMP IS 7%/34%, NOT 14%/42% — AND THE 26 PIXELS IT MOVED WERE A MEASURED FAILURE.
     Composited the painted hero (B1/K3) against the gradient and read the worst contrast INSIDE
     each glyph box taken from the live page: eyebrow 3.49:1 against a 4.5 floor, while the 96 px
     title was already at 8.90:1. The eyebrow sits at 9% down the block, where the old ramp was
     barely 19% opaque, over a sky pale enough to look safe and not pale enough to be. Steepening
     the ramp — same start, same end — takes it to 6.04:1 and costs the art nothing above it.
     ⚠ MY FIRST READING SAID THE TITLE FAILED TOO, AT 2.90:1, AND THAT WAS THE INSTRUMENT: I had
     swept the whole 1265 px width, including the dark mossy pillars the CENTRED type never covers.
     The perimeter is part of the measurement. Re-run on the glyph boxes, one real defect remained. */
  .hero .veil{display:none}
  /*  ⚡⚡⚡ V6-1 — LE BLOC DE TEXTE PREND UNE COLONNE À GAUCHE, ET C'EST UNE MESURE QUI L'Y MET,
      pas une préférence de composition. Le défaut D1 demande que le corps se tienne À GAUCHE de
      la porte runique d'A1 au lieu d'être dedans. Mesuré à 1440 sur la peinture nue (corps et
      poster cachés): l'arche occupe 0,749 à 0,838 de la bande, le h1 centré 0,296 à 0,704, et le
      corps 0,159. Il reste 0,045 entre les deux — le corps ne tient pas dans ce qui reste tant
      que le texte occupe toute la largeur, et il n'y a pas de troisième endroit où le mettre.
      Le padding-right rend au sujet le tiers droit; le titre reste centré DANS ce qui lui
      reste, donc rien n'est réécrit — la boîte rétrécit, le centrage est le même mécanisme.
      ⚠⚠ C'EST UN PADDING ET PAS UN right, ET LA DIFFÉRENCE EST LE VOILE. La rampe mesurée
      trois lignes plus bas est peinte sur CE bloc: le raccourcir par inset arreterait le voile
      à 68 % de la plaque et poserait une couture verticale nette au milieu d'une peinture. Le
      fond continue de couvrir la bande entière; seule la boîte de contenu recule.
      ⚠ SOUS 58 rem RIEN DE TOUT CELA NE S'APPLIQUE: le bloc y est EMPILÉ sous l'art (décision
      v1, mesurée), il n'y a pas de corps à sa droite, et un tiers de colonne vide y serait un
      titre serré pour rien. */
  .hero .say{position:absolute;inset:auto 0 0 0;padding:2.6rem 1.25rem 2.8rem;
    padding-right:31%;
    /*  ⚠⚠⚠ LA RAMPE A ÉTÉ REMESURÉE POUR A1, ET C'EST LA MÊME LOI QUE LA BANDE DE CLÔTURE A
        PAYÉE LE MÊME JOUR: un voile est réglé pour SON image et périme avec elle. Celle-ci
        montait à .93 puis à l'opaque parce qu'elle éteignait une peinture d'HEURE DORÉE dont le
        ciel passait à 3,49:1 sous l'eyebrow. A1 est un CRÉPUSCULE, déjà sombre: sous la même
        rampe, l'estrade, les pierres de l'avant-plan et les jambes du personnage disparaissaient
        toutes dans un rectangle noir — la bande devenait une bande de ciel posée sur du vide.
        ⚠⚠ ET LE PLANCHER RESTE BORNÉ PAR CONSTRUCTION, PAS ÉCHANTILLONNÉ. Le pire sol qu'un
        voile puisse recevoir est un art BLANC PUR; à l'alpha où les glyphes se posent, le sol
        vaut alors 0,84·panel + 0,16·255 = rgb(60,62,58), où --ink mesure 7,3:1 et --ink-dim —
        la plus faible des deux, celle de la lede — 5,4:1. Les deux franchissent AA sans regarder
        l'image, ce qui est la seule mesure qui survit à un changement de peinture.
        ⚠ ELLE NE FERME PLUS À L'OPAQUE. .90 en bas laisse l'estrade et les pierres d'A2
        traverser au lieu de finir sur un rectangle plein, et la bande se dissout quand même
        dans celle qui suit — .90 de --panel sur du --panel EST du --panel partout où l'art est
        déjà sombre, c'est-à-dire partout où cette image finit. */
    background:linear-gradient(to bottom,rgba(23,25,21,0),rgba(23,25,21,.45) 8%,
      rgba(23,25,21,.84) 30%,rgba(23,25,21,.90) 100%)}
}
/* ---- O1: THE HERO IS A BODY, AND THE POSTER IS THE SAME BODY -------------------------
   The poster and the canvas share ONE box, so what a reader sees before the 3D arrives and
   what they see after are the same silhouette in the same place. That is the whole reason
   the poster is a render of this rig rather than a painting: a swap between two different
   heroes would be a flash of a different page.
   ⚠⚠ THE BOX IS ON THE RIGHT, AND THE TYPE KEEPS THE CENTRE. The band's title is centred
   and the arch behind it is centred too; a body in the middle would stand in front of its own
   headline. The right third is the quiet part of this plate at every width it is shown at.
   ⚠ THE CANVAS HAS NO INTRINSIC SIZE AND THE IMG DOES. Both are stretched to the box by
   object-fit / 100%, and the img DECLARES the file (800x1200) because the landing gate
   compares a declared pair against the real bytes - the law that caught nine crests declared
   64 for a 160 file. */
/*  ⚡⚡ THE POSTER'S BOX IS PINNED TO WHERE THE LIVE CAMERA PUTS THE BODY, and the four
    numbers are MEASURED, not chosen: with the rig's fov of 32, a distance of 4,3 m and an aim
    at 0,78 m, the 1,58 m body projects to y 14,3 % .. 83,0 % of the band and its centre lands
    at x 73,4 % — read off __HERO3D__.stats().screenPct at 1440x702.
    ⚠⚠⚠ THE FIRST VERSION SIZED THIS BOX BY EYE (a 46 % column on the right) AND THE POSTER
    CAME OUT HALF AGAIN AS TALL AS THE CANVAS THAT REPLACES IT. Nothing errored and every
    number was green: the body loaded, 154 088 triangles drew, the projected box was correct.
    What was wrong was that the picture UNDER the canvas was a different size from the render
    ON it, so the one moment this whole lot exists for — the swap — was a jump. A poster that
    is a still of the scene has to be laid out by the scene's own arithmetic.
    ⚠ contain + a vertical span: the image's own aspect then decides its width, so a re-rendered
    poster of a different build stays aligned without touching this rule. */
/*  ⚠⚠ AN EXPLICIT HEIGHT, BECAUSE height:auto ON A REPLACED ELEMENT IGNORES bottom.
    Absolutely positioned with top AND bottom set, a normal box takes its height from the two
    offsets; a replaced element with height:auto takes it from its INTRINSIC RATIO instead
    (CSS 2.1 10.6.5) and the bottom offset is dropped. Measured: the poster's box came out at
    117 % of the band and the picture hung out of the hero. */
.hposter{position:absolute;left:0;width:100%;top:14.3%;height:68.7%;
  display:block;z-index:1;pointer-events:none;
  object-fit:contain;object-position:68.7% 50%;
  filter:drop-shadow(0 18px 30px rgba(23,25,21,.55))}
/*  ⚡ V10-5 — ET SANS CETTE LIGNE LE hidden DE hero3d.js NE FAIT RIEN. L attribut [hidden] vaut
    display:none dans la feuille du NAVIGATEUR ; le .hposter{display:block} ci-dessus est une
    regle d AUTEUR, et une regle d auteur bat une regle de navigateur a specificite egale. Le
    poster serait reste visible, l attribut pose, et le heros reste dedouble — un defaut qui se
    lit comme « le correctif n a pas marche » alors que c est la cascade qui a parle.
    Meme famille que le :not(.hposter) du bloc ci-dessus, qui est deja une reparation de cascade.
    ⚠ ET CE COMMENTAIRE N A PAS DE BACKTICK, parce qu il vit DANS un template literal: le §8 du
    plan compte six generateurs tues par un backtick en commentaire, celui-ci a ete le septieme
    avant cette ligne. */
.hposter[hidden]{display:none}
/*  ⚠⚠ THE CANVAS IS BAND-WIDE AND THE POSTER IS NOT, AND THAT IS THE ONE ASYMMETRY OF THIS
    lot. The Runefield is a VEIL over the whole plate - mist, a horizon and a vignette that has
    to reach the edges to be a vignette at all - while the poster is a body-shaped picture. So
    the canvas covers the plate, the body is composed inside it to the right by the camera, and
    the poster sits under it in exactly that place. What the poster cannot carry is the field:
    a still of moving mist is a smudge, and the field is the half of this lot that arrives in
    milliseconds anyway. Written down because "the poster IS the scene" is true of the BODY and
    approximate about the air around it. */
.h3d{position:absolute;inset:0;width:100%;height:100%;display:block;z-index:2}

/*  ⚡⚡ SOUS 58 rem LE POSTER REPOND A LA MISE EN PAGE, PAS A LA CAMERA, et c est un defaut
    trouve par l owner sur la version en ligne: « en version mobile le perso est tout en haut ».
    Il avait raison, et la cause est que les quatre pourcentages ci-dessus sont DERIVES DU CADRAGE
    DE BUREAU - ils placent le corps la ou la camera le projette dans une plaque de 78 vh. Sur un
    telephone la plaque n a pas de min-height du tout: elle fait la hauteur naturelle de l art,
    221 px a 393 de large, et le texte est EMPILE DESSOUS au lieu d etre superpose (une decision
    mesuree de v1: a 375 px le bloc de titre faisait 228 % de la hauteur du hero). Les memes
    pourcentages y donnaient donc un corps de 152 px suspendu, avec 17 % de plaque vide sous ses
    pieds - sur le bureau ce creux est couvert par le voile du bloc de texte, sur le telephone il
    ne l est pas et le personnage flotte.
    ⚠ ET C EST LEGITIME DE LE CADRER AUTREMENT ICI: sous 58 rem la porte refuse toujours le 3D
    (pointeur grossier), donc il n y a pas de canvas avec lequel s aligner. Le poster est alors
    seul, et sa seule obligation est de se tenir SUR le sol de l art. Ancre en bas.  */
/*  ⚡⚡⚡ V6-1 — ET LE TÉLÉPHONE COUPAIT LE CRÂNE, PAR UNE BARRE ET PAS PAR UN CADRAGE.
    D1 le lit comme un recadrage; mesuré à 393 px (Pixel 5, vrai descripteur d'appareil), c'en
    est un autre: la plaque fait 222 px de haut, le poster y occupe 32 → 221, et le DOCK fait
    64 px depuis le haut de la page. Les trente-deux premiers pixels du personnage — le haut du
    crâne — passent donc sous le bouton Jouer. object-fit:contain n'a jamais rien coupé: c'est
    une barre fixe qui posait dessus.
    ⚠⚠ DEUX DÉFAUTS, UN SEUL CORRECTIF, ET LE SECOND EST CELUI QUI COMPTE: dans une plaque de
    222 px le héros mesurait **68 × 189** — une vignette. Le « premier paint » d'un téléphone
    est le seul héros qu'il verra jamais (la porte refuse le 3D sur pointeur grossier), donc
    c'est la surface où la taille du sujet compte le PLUS et c'était celle où il était le plus
    petit. La plaque prend une hauteur minimale, l'art la couvre au lieu de la remplir par son
    rapport, et le poster part SOUS le dock.
    ⚠ 46 vh ET PAS PLUS: le bloc de titre est EMPILÉ dessous (décision v1, mesurée — à 375 px
    il faisait 228 % de la hauteur du héros), donc chaque vh donné à la plaque est un vh que le
    lecteur doit faire défiler avant de lire la première phrase. */
@media (max-width:57.99rem){
  .hero .plate{min-height:46vh}
  .hero .plate img:not(.hposter){height:46vh;min-height:46vh;object-fit:cover;
    object-position:50% 58%}
  .hposter{top:calc(var(--dock-h) + .5rem);bottom:0;height:calc(100% - var(--dock-h) - .5rem)}
}

/*  ---- O1b: THE FIELD'S STILL, FOR EVERYONE WHO NEVER GETS THE CANVAS -----------------
    The Runefield runs behind five conditions; a phone, a reduced-motion reader and anyone
    without WebGL2 get this instead. It is not a decoration added to fill the gap: it is the
    field's OWN AVERAGE FRAME, measured on the canvas and written back here, so the two are
    the same picture at two levels of detail.
    MEASURED 2026-09-02 on 138 996 samples of the band's left 55 % (the body never stands
    there), ANGLE d3d11, at 1440x702: 97,3 % of the sampled pixels carry paint, mean alpha
    30,6/255 = 12 %, mean colour rgb(27,17,4) - a warm umber, which is the mist running from
    the plate wash towards the game's own button brown, plus the gold horizon at 62 % height.
    THE NUMBERS ARE ALPHAS, NOT COLOURS. Those three stops are var()-derived: the horizon is
    the gold token, the ground is the panel token, and only the OPACITIES come from the
    measurement. A mean rgb copied in literally would have been a fourth answer to what
    colour the mist is, right for one palette and silently wrong at the next re-sampling.
    IT IS ALWAYS PAINTED, and the canvas simply covers it when it boots - one rule instead of
    a pair that has to be turned off, which is the shape this sheet's register asks for. */
.hero .plate::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    radial-gradient(120% 42% at 50% 62%,rgba(223,174,94,.12) 0,rgba(223,174,94,0) 70%),
    radial-gradient(88% 88% at 50% 50%,rgba(23,25,21,0) 0,rgba(23,25,21,.55) 100%),
    linear-gradient(to bottom,rgba(23,25,21,.10),rgba(32,34,28,.16) 55%,rgba(23,25,21,.10))}
/*  [hidden] IS A UA RULE AND A CLASS BEATS IT - the pre-registration band paid this bill on
    2026-08-30 and the codex sheet restates it too. A canvas the script has not revealed must
    not be drawn over the poster it is waiting to replace. */
.h3d[hidden]{display:none}
/*  ⚠ THE CANVAS FADES IN, THE POSTER DOES NOT FADE OUT. They hold the same silhouette, so
    cross-fading them would show the body at DOUBLE opacity through the middle of the
    transition - a ghost of itself. The canvas simply arrives on top. */
.h3d{opacity:0;transition:opacity .5s ease}
.h3d:not([hidden]){opacity:1}
@media (prefers-reduced-motion:reduce){.h3d{transition:none}}

/*  ⚡ O1 §6 - THE POINTER PARALLAX LIVES ON THE BODY, NOT ON THE PAINTED PLANES, and that is
    a correction made while writing it rather than after shipping it. The plan asks for the
    backdrop to shift 12 px and the foreground 28 px the other way; both of those elements
    ALREADY have their transform driven by an animation - drift and foreDrift on the clock,
    baseScrub and foreScrub on the scroll timeline. A running animation wins the transform
    property outright, so a static rule there is either dead or, worse, alive for the one frame
    the animation is not. Rebuilding those keyframes to carry a pointer term would be a rewrite
    of a v2 mechanism, which §10 of this plan forbids in as many words.
    ⚠ THE DEPTH CUE IS THE SAME EITHER WAY: what a reader perceives is the body moving
    AGAINST the plate, and moving the body is the half of that pair nothing else animates.
    12 px, the plan's own number, in the direction opposite the camera's yaw.
    ⚠ --px and --py are written by hero3d.js from the pointer inside the band; with no script,
    no pointer, or a reader who asked for less motion they are absent and the calc resolves to
    0. The fallback is in the var(), never in a second rule that has to be undone. */
.hbody{transform:translate3d(calc(var(--px,0) * -12px),calc(var(--py,0) * -5px),0)}
@media (prefers-reduced-motion:no-preference){
  .hbody{transition:transform .35s cubic-bezier(.2,.7,.3,1)}
}

/* ---- BUTTONS WITH REAL STATES (brief §4bis.3) --------------------------------------
   ⚠ NOT a hover that fades the button out. The game's buttons brighten their edge on hover and
   go INTO the surface when pressed; a faded button is a web habit, not this game's vocabulary.
   The pressed state moves the label 1px AND flips the inset highlight to an inset shadow - that
   pair is what reads as depth; either one alone reads as a glitch. */
/*  ⚠⚠⚠ vertical-align:middle, ET C EST LA REPARATION D UN DECALAGE DE 23,2 px MESURE, PAS
    une precaution. Jouer et Explorer le codex ont exactement le meme padding, la meme bordure,
    la meme fonte et la meme hauteur de ligne - et le premier montait de 23 px. La cause est une
    regle CSS que rien dans ces deux boutons ne laisse voir: un inline-block dont l overflow
    n est PAS visible prend son bord INFERIEUR comme ligne de base, au lieu de la ligne de base
    de sa derniere ligne de texte. Le bouton primaire porte overflow:hidden depuis le lot L8,
    pour retenir la brillance qui le traverse; le secondaire ne l a pas. Les deux s alignaient
    donc sur deux choses differentes.
    ⚠ LE CORRECTIF NE TOUCHE PAS overflow, parce que la brillance en depend. Aligner les boites
    au lieu des lignes de base rend la paire independante de cette propriete - et de la suivante
    que quelqu un ajoutera a l un des deux sans penser a l autre. */
.cta{display:inline-block;vertical-align:middle;
  padding:.6rem 1.5rem;border-radius:.3rem;font-size:1rem;
  border:1px solid var(--edge);margin:.25rem .3rem;
  transition:border-color .16s ease,box-shadow .16s ease,transform .08s ease,background .16s ease}
.cta.p{background:linear-gradient(var(--gold-hi),var(--gold));color:var(--ink-on-gold);border-color:var(--edge);
  box-shadow:0 1px 0 rgba(243,224,194,.4) inset}
.cta.p:hover{background:linear-gradient(#f3e0c2,var(--gold-hi));border-color:var(--gold);
  box-shadow:0 1px 0 rgba(243,224,194,.55) inset,0 0 14px rgba(223,174,94,.45)}
.cta.p:active{transform:translateY(1px);box-shadow:0 2px 5px rgba(34,19,6,.35) inset}
.cta.s{background:var(--raise);color:var(--ink)}
.cta.s:hover{border-color:var(--gold);box-shadow:0 0 12px rgba(223,174,94,.30)}
.cta.s:active{transform:translateY(1px);box-shadow:0 2px 5px rgba(34,19,6,.22) inset}
.band.dark .cta.s{background:rgba(232,226,212,.10);color:var(--ink);border-color:var(--edge)}
.cta:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* ---- v2/L8 - THE SOUND TOGGLE, THE CTA SHEEN, AND THE RUNE THAT FOLLOWS ------------
   ⚡⚡ O-4 (owner, 2026-09-02): L'INTERRUPTEUR N'EST PLUS ÉPINGLÉ, IL EST DANS LE DOCK. La règle
   qu'il portait — « en bas à GAUCHE parce que le sélecteur de langue est en bas à DROITE, et
   deux commandes fixes qui se recouvrent sont une commande » — est SATISFAITE, pas abandonnée:
   les deux sont maintenant côte à côte dans la barre, où elles ne peuvent plus se recouvrir.
   ⚠ ET LE RULING D3 NE BOUGE PAS D'UN CARACTÈRE. Le bouton est toujours servi hidden avec
   aria-pressed="false", et l'assertion V3-8 le lit sur chaque page qui le porte — une DÉFAUT
   se retape exactement quand on déménage la commande, ce qui est pourquoi le gate a été élargi
   à la préinscription à V0, avant le déménagement plutôt qu'après.
   ⚠ CONTRAST IS BOUNDED, NOT SAMPLED - the same method the class wheel's plate uses, and for
   the same reason: this button sits in a bar that is transparent over the hero art, so there is
   no one background to measure. It carries no plate of its own now; the DOCK's scrim (measured
   at 6,9:1 for --ink over pure white art) is the ground of everything in the bar. */
.snd{isolation:isolate;display:flex;align-items:center;
  gap:.45rem;padding:.42rem .72rem;border-radius:.3rem;border:1px solid var(--edge);
  background:rgba(23,25,21,.4);color:var(--ink);cursor:pointer;
  font:700 .74rem/1 var(--font-display);letter-spacing:.16em;text-transform:uppercase;
  transition:border-color .18s ease,box-shadow .18s ease}
/*  [hidden] IS A UA RULE AND display:flex OUTRANKS IT - the pre-registration band paid this
    exact bill on 2026-08-30 and the note is three hundred lines up. Restated at class strength. */
.snd[hidden]{display:none}
.snd:hover{border-color:var(--gold)}
.snd:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.snd[aria-pressed="true"]{border-color:var(--gold);box-shadow:0 0 12px var(--gold-glow)}
/*  The speaker keeps its cone in both states and swaps only what comes out of it: waves when the
    sound is on, a cross when it is off. An icon that DISAPPEARS reads as a disabled control. */
.snd .sw{opacity:0}
.snd .sx{opacity:1}
.snd[aria-pressed="true"] .sw{opacity:1}
.snd[aria-pressed="true"] .sx{opacity:0}

/*  L8 - THE GOLD SHEEN ON THE PRIMARY CTA. It is a highlight that travels INSIDE the plaque,
    never a change to the plaque's ink: the pseudo-element sits at z-index -1 inside the button's
    own stacking context, so it paints over the gold gradient and UNDER the label. The label is
    #2b1c05 on gold; a white sheen only lightens what is behind it, so the contrast can only go
    up as it passes - which is the one direction a moving highlight is allowed to go.
    ⚠ The long pause is the whole effect. 5.5 s with the travel packed into the last third reads
    as a glint on metal; a continuous sweep reads as a loading bar. */
/*  ⚠⚠ isolation SUR LA CLASSE cta ENTIÈRE, PAS SUR SA VARIANTE PRIMAIRE, ET LA LUEUR PASSE EN
    z-index NÉGATIF AVEC LA BRILLANCE. Mesuré sur un rendu: sans ça, le pseudo-élément before est
    un descendant POSITIONNÉ à z-index auto, donc il se peint AU-DESSUS du texte en flux.
    ⚑ MESURÉ SUR LA PAGE SERVIE, EN RETIRANT LA SEULE DÉCLARATION EN CAUSE: l'encre la plus
    sombre du bouton survolé passe de rgb(43,28,5) — la couleur du libellé — à rgb(68,55,33)
    quand la lueur repasse au-dessus. Le halo ne décore plus le mot, il le délave. Dans un
    contexte d'empilement, un enfant à z-index négatif se peint APRÈS le fond de l'élément et
    AVANT son contenu: c'est exactement la couche voulue, et le libellé reste à 14,95:1.
    ⚠ Et l'œil s'est trompé DEUX FOIS dans les deux sens sur cette seule règle: le premier
    verdict venait d'une planche de 439 px où le bouton semblait effacé alors qu'il ne l'était
    pas encore, et le second d'une planche où il semblait l'être alors que c'était réparé. Les
    deux nombres ci-dessus viennent d'un rendu à 3× lu au pixel, pas d'une vignette.
    ⚠ (Et ce commentaire ne cite aucun sélecteur entre accents graves: il vit dans le littéral de
    gabarit qui porte toute la feuille, et un accent grave le termine. Sixième fois.) */
.cta{isolation:isolate}
.cta.p{position:relative;overflow:hidden}
.cta.p::after{content:"";position:absolute;inset:-1px;z-index:-1;pointer-events:none;
  background:linear-gradient(105deg,transparent 40%,rgba(243,224,194,.62) 50%,transparent 60%);
  transform:translateX(-150%);animation:ctaSheen 5.5s ease-in-out infinite}
@keyframes ctaSheen{0%,64%{transform:translateX(-150%)}92%,100%{transform:translateX(150%)}}

/*  L8 - THE RUNE GLOW FOLLOWS THE POINTER, and only where the page has BUTTONS.
    ⚠ --gx/--gy are written by the script from the pointer's position inside the element, on a
    requestAnimationFrame, so the glow tracks rather than jumps. Without the script the rule still
    works: the custom properties fall back to the element's centre, which is a plain hover glow.
    ⚠ (hover:hover) ONLY. A touch screen has no pointer to follow and would light the glow on
    tap, then leave it lit until something else was tapped. */
@media (hover:hover){
  .cta::before,.snd::before{content:"";position:absolute;inset:0;border-radius:inherit;
    z-index:-1;pointer-events:none;opacity:0;transition:opacity .22s ease;
    background:radial-gradient(6.5rem 6.5rem at var(--gx,50%) var(--gy,50%),
      rgba(223,174,94,.42),rgba(223,174,94,0) 70%)}
  .cta{position:relative}
  .cta:hover::before,.cta:focus-visible::before,
  .snd:hover::before,.snd:focus-visible::before{opacity:1}
  /*  On the gold plaque the same glow would be gold on gold. There it is a soft WHITE bloom,
      which is the same gesture in the one place the accent colour cannot read. */
  .cta.p::before{background:radial-gradient(6.5rem 6.5rem at var(--gx,50%) var(--gy,50%),
    rgba(243,224,194,.42),rgba(243,224,194,0) 70%)}
}

/*  ⚡⚡⚡ V9-2 — LES ONZE TUILES SONT REMPLACÉES PAR ONZE ÉPINGLES SUR LA PEINTURE, et tout le
    bloc de grille et de tuiles part avec elles: la bande ne LISTE plus le monde, elle le MONTRE.
    ⚑ ZÉRO OCTET DE MÉDIA NE BOUGE — les onze fichiers zone-slug.webp restent publiés, parce que
    l'index des zones les affiche depuis le lot C de V8, et c'est précisément ce qui a rendu la
    grille de l'accueil redondante. Ce lot DÉPLACE une liste, il n'en supprime aucune: la garde
    V9-c le tient dans les deux sens (zéro tuile ici, douze cartes toujours à l'index).
    ⚠ L'ACCUEIL PERD ONZE IMAGES PARESSEUSES et gagne onze ancres de 26 px. */

/*  ⚡⚡⚡ LA TUILE DE REGION, TELLE QU ELLE ETAIT, MAIS POSEE SUR LA CARTE PEINTE. Ruling owner du
    2026-09-04, au troisieme essai et en toutes lettres: tu reprends les vignettes et tu les fous
    dessus comme c etait avant. C est donc la tuile de la grille - l image, son cadre, son ombre,
    son cartouche degrade et son nom - et ce qui change est UNIQUEMENT ou elle se pose: plus une
    grille sous la carte, mais la coordonnee mesuree de sa region SUR la carte.
    ⚠ CE QUI A ETE ESSAYE ET REFUSE AVANT, pour que personne ne le re-propose: des pastilles
    numerotees avec une legende de onze lignes dessous (la forme que le paragraphe 20.5
    prescrivait), puis les memes vignettes SANS leur nom. Les deux sont mortes a l oeil.
    ⚠⚠ LES EPINGLES SONT ABSOLUES, DONC HORS FLUX, donc elles ne gonflent pas la boite qui se
    retracte sur l image de la carte - c est ce qui garde chaque pourcentage exact. Une tuile en
    flux aurait elargi la figure et fait tomber les onze positions a cote. */
.ztile{position:absolute;transform:translate(-50%,-50%);z-index:3;
  display:block;width:14%;overflow:hidden;border-radius:.3rem;
  border:1px solid var(--edge);box-shadow:0 6px 22px rgba(23,25,21,.55);background:var(--raise)}
.ztile img{width:100%;height:auto;aspect-ratio:16/9;object-fit:cover;display:block;
  transform:scale(1.02);transition:transform 6s cubic-bezier(.16,.6,.28,1)}
.ztile:hover img,.ztile:focus-visible img{transform:scale(1.14) translate3d(-1.4%,1.4%,0)}
.ztile:hover,.ztile:focus-within{z-index:4;border-color:var(--gold);
  box-shadow:0 12px 30px rgba(23,25,21,.66),0 0 0 1px rgba(223,174,94,.45)}
.ztile:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/*  ⚠⚠ 14 %, ET C EST UNE MESURE AVEC UN PLANCHER, PAS UN REGLAGE. Deux boites centrees sur leurs
    points se touchent des que leur largeur depasse l ecart de la paire la plus serree: marche sur
    les onze couples, la limite est 14,80 % - Les Grottes contre Le Pic, 14,8 points d ecart pour
    0,2 de latitude. 14 % laisse 0,8 point d air.
    ⚠ UN PREMIER ESSAI A 16 % A ETE MESURE ET REFUSE: le gate a rendu Les Grottes contre Le Pic
    dans les deux langues et aux deux largeurs, et l oeil le voyait sur la planche - deux
    cartouches qui se mordent en haut a droite de la carte. Ce n est pas un pixel de tolerance a
    accorder, c est le nombre qui etait faux.
    ⚠⚠ ET LE NOM NE PAIE PAS LA REDUCTION: le clamp plus bas tient son plancher a 12 px, donc un
    nom trop long PASSE A LA LIGNE au lieu de retrecir - le paragraphe 8 interdit de descendre
    Cinzel sous 12 px pour faire tenir un mot.
    ⚠ EN POURCENT DE LA CARTE, JAMAIS EN px: le plafond en vh retrecit la carte sur un ecran bas
    (672 px a 1440x700) sans que la largeur du viewport bouge, et une taille en px changerait le
    rapport tuile sur carte d un ecran a l autre - la geometrie ci-dessus cesserait d etre vraie. */
.zt{position:absolute;inset:auto 0 0 0;padding:1.6rem .5rem .35rem;
  background:linear-gradient(to top,rgba(23,25,21,.96),rgba(23,25,21,.78) 55%,rgba(23,25,21,0))}
.zn{display:block;font-family:var(--font-display);color:var(--ink);letter-spacing:.03em;
  line-height:1.15;font-size:.75rem}
/*  ⚑ LE NOM SUIT LA TAILLE DE SA TUILE, ET LA TUILE SUIT LA CARTE. Une taille en rem serait juste
    a une largeur et fausse a toutes les autres, puisque la carte va de 390 a 1037 px de large.
    cqw est un pourcentage de la LARGEUR DE LA TUILE, donc le cartouche garde ses proportions a
    toutes les tailles. Le clamp tient le plancher de 12 px du paragraphe 8: un nom qui ne tient
    pas passe a la ligne, il ne retrecit pas.
    ⚠ La regle en rem au dessus reste le repli pour un navigateur sans requetes de conteneur; elle
    vaut exactement le plancher, donc le repli est lisible et jamais casse. */
@supports (container-type: inline-size) {
  .ztile{container-type:inline-size}
  .zn{font-size:clamp(.75rem,8.5cqw,1rem)}
  .zt{padding:11cqw 4cqw 3cqw}
}
/*  ⛔ SOUS 35 rem LE CARTOUCHE PART, ET C EST UNE MESURE, PAS UN ABANDON. A 390 px la carte fait
    390 de large, donc une tuile de 16 % en fait 62 - un nom de Cinzel a son plancher de 12 px n y
    entre pas, et le baisser est precisement ce que le paragraphe 8 interdit. La tuile redevient
    donc l image seule; le nom reste le nom ACCESSIBLE du lien par le alt, et la grille NOMMEE du
    monde vit a l index des zones, qui est une page a images depuis V8. */
@media (max-width:34.99rem){ .zt{display:none} }
.zm{display:flex;flex-wrap:wrap;gap:0 .5rem;margin-top:.2rem;
  font-size:.7rem;color:var(--ink-dim);opacity:0;transform:translateY(7px);
  transition:opacity .22s ease,transform .22s ease}
/*  ⚠ :focus-within FOR THE REVEAL, :focus-visible FOR THE RING, and the two are different
    questions. The ring must not appear under a mouse click, which is exactly what focus-visible
    is for; the facts must appear whenever the tile HAS focus, however it got it. */
.ztile:hover .zm,.ztile:focus-within .zm{opacity:1;transform:none}
.zm span+span::before{content:"·";color:var(--gold);margin-right:.5rem}
/*  The Runegate is the one fact on this line that is about the NETWORK rather than the place,
    so it wears the page's own marker - the same rotated gold square the fact lists use. */
.zr{color:var(--ink)}
.zr::after{content:"";display:inline-block;width:.3rem;height:.3rem;margin-left:.35rem;
  background:var(--gold);transform:rotate(45deg);vertical-align:.06em}
/*  ⚠ LA LIGNE DE FAITS NE S OUVRE PAS TOUTE SEULE SUR UN ECRAN TACTILE, ici, contrairement a la
    grille d avant. Elle ferait deborder un cartouche de 62 px qui n a deja plus de nom. */

/* ---- v2/L5 - THE CLASS WHEEL: nine crests on an arc, and one card under them --------
   ⚠ drop-shadow, not box-shadow: the crest is a transparent PNG, and a box shadow would light
   up its bounding RECTANGLE instead of the shape a reader actually sees.
   ⚠ THE CREST NAMES NEEDED A GROUND, and v1 had none: nine small-caps labels at 62% opacity over
   a painted street. The subdued state survives as a dimmer COLOUR at full opacity rather than a
   transparency, because a transparent glyph borrows whatever is behind it and there is a
   painting behind it.
   ⚠⚠ AND THE PIXEL IS THE WRONG INSTRUMENT FOR THIS BAND — twice measured, twice wrong. Sampling
   the rendered page under a label caught the glyph's own anti-aliasing; sampling an axis-aligned
   box under a TILTED tile caught the bare painting beside the plate, and reported 2.12:1 for
   Paladin while the one untilted tile (Prêtre) reported 7.13:1 for identical CSS. The plate is a
   COMPOSITION whose two terms are both known, so the worst case is BOUNDED instead of hunted:
   over art that is pure black, plate .78 resolves to rgb(196,186,172) and plate .92 to
   rgb(231,220,202). Against those — the darkest ground this band can ever produce, whatever art
   it is given — the resting name measures 5.45:1, the hovered name 9.42:1, the card's class and
   specs 13.31:1 and its kit line 7.69:1. Every one clears AA's 4.5, by construction.
   ⚠⚠ THE ARC IS --lift / --tilt, WRITTEN BY THE GENERATOR. A parabola cannot be expressed once
   in CSS for nine children without nine rules or a squared custom property; the generator knows
   each crest's rank, so it writes the two numbers and this sheet only spends them.
   ⚠⚠⚠ THE CARD IS ABSOLUTE AGAINST THE ARC, WHICH MEANS NO ANCESTOR BETWEEN THE TWO MAY CARRY
   A TRANSFORM. The li is deliberately left untransformed (the tilt lives on the a inside it) and
   the step class sits on the ul, which IS the containing block already. */
.warc{position:relative;list-style:none;margin:0;padding:0 0 7.4rem;
  display:grid;grid-template-columns:repeat(9,1fr);align-items:start;justify-items:center}
.warc>li{margin-top:var(--lift,0)}
.warc>li>a{display:block;text-align:center;padding:.75rem .3rem .6rem;border-radius:.3rem;
  border:1px solid transparent;color:inherit;background:rgba(23,25,21,.78);
  transform:rotate(var(--tilt,0deg));
  transition:transform .22s ease,border-color .22s ease,background .22s ease}
.band.dark .warc>li>a{background:rgba(23,25,21,.62)}
.warc img{width:72px;height:72px;display:block;margin:0 auto .4rem;transition:filter .22s ease}
.wn{display:block;font-family:var(--font-display);font-size:.86rem;color:var(--ink-dim);
  transition:color .22s ease}
.warc>li>a:hover,.warc>li>a:focus-visible{transform:rotate(0deg) translateY(-7px) scale(1.06);
  border-color:var(--edge);background:rgba(223,174,94,.14);outline:none}
.warc>li>a:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.warc>li>a:hover img,.warc>li>a:focus-visible img{filter:drop-shadow(0 0 12px rgba(223,174,94,.9))}
.warc>li>a:hover .wn,.warc>li>a:focus-visible .wn{color:var(--ink)}

/*  The card: one box, nine stacked copies, only one painted at a time. Its room is reserved by
    the arc's own bottom padding, so opening a card never moves the crests above it. */
/*  ⚠⚠ THE CARD NAMES ITS OWN CLASS, and the first build did not. It read
    Physique · Poison · Finesse over 39 sorts · Terre · Feu · Air with nothing saying WHOSE
    they were: at rest, under nine crests, that is a card about no one. The crest above carries
    the name too, but proximity is not attribution when the card is a metre wide and the crest
    it belongs to is at the far left. */
.wdet{position:absolute;left:0;right:0;bottom:0;margin:0 auto;max-width:46rem;
  opacity:0;pointer-events:none;
  display:flex;flex-direction:column;align-items:center;gap:.45rem;
  padding:.95rem .9rem 1rem;border-radius:.3rem;border:1px solid var(--edge);
  background:rgba(23,25,21,.92);transition:opacity .18s ease}
.warc>li:hover .wdet,.warc>li:focus-within .wdet{opacity:1}
.wh{display:flex;flex-wrap:wrap;justify-content:center;align-items:baseline;gap:.15rem .55rem}
.wc{font-family:var(--font-display);font-size:1.2rem;font-weight:700;color:var(--ink);
  letter-spacing:.03em}
.wsp{display:flex;flex-wrap:wrap;justify-content:center;gap:.4rem .55rem}
.wsp::before{content:"—";color:var(--edge)}
.wsp b{font-family:var(--font-display);font-size:.95rem;font-weight:700;
  color:var(--ink);letter-spacing:.02em}
.wsp b+b::before{content:"·";color:var(--edge);margin-right:.55rem;font-weight:400}
.wk{display:flex;flex-wrap:wrap;justify-content:center;gap:0 .5rem;
  font-size:.82rem;color:var(--ink-dim)}
.wk span+span::before{content:"·";color:var(--edge);margin-right:.5rem}
/*  ⚠ THE RESTING CARD IS THE FIRST CLASS, NEVER AN EMPTY BOX - a reserved 8 rem of nothing under
    nine crests is the dead ivory L9 exists to remove. It needs :has to know that no crest is
    hovered, so it is declared where :has is answered; a browser without it opens the box on
    hover only, which is a smaller page, not a broken one. */
@supports selector(:has(*)){
  .warc:not(:has(>li:hover)):not(:has(>li:focus-within))>li:first-child .wdet{opacity:1}
}

/*  ⚠⚠ UNDER 60 rem THE ARC IS NOT AN ARC - it is a scroll-snap rail, and every card is OPEN.
    A touch screen has no hover, so a reveal-on-hover wheel would hide two thirds of this band's
    content from every phone. The card returns to normal flow, which is what turns each cell into
    a real card rather than a crest with something hidden under it. */
@media (max-width:59.99rem){
  .warc{display:flex;grid-template-columns:none;overflow-x:auto;overscroll-behavior-x:contain;
    scroll-snap-type:x mandatory;gap:.75rem;padding:0 0 .6rem}
  .warc>li{flex:0 0 min(66%,15rem);scroll-snap-align:center;margin-top:0;display:flex;
    flex-direction:column;gap:.55rem}
  .warc>li>a{transform:none}
  .warc>li>a:hover,.warc>li>a:focus-visible{transform:translateY(-4px)}
  .wdet{position:static;max-width:none;opacity:1;pointer-events:auto;flex:1 1 auto;
    justify-content:center}
  /*  ⚠ THE NAME GOES ON THE PHONE, and only there: the rail shows ONE card at a time under its
      own crest, so the card and the crest that names it are two lines apart. Repeating it would
      print the class twice in a 15 rem cell. On the desktop arc the card is far from its crest,
      which is exactly why it carries the name there. */
  .wc{display:none}
  .wsp::before{content:none}
}

/* ---- BESTIARY - a light scale, and the name is a tooltip that appears ---------------
   ⚠ The name is ALWAYS in the DOM, only its opacity moves. Building it on hover would hide it
   from a screen reader and from everyone who never hovers, which on a phone is everyone. */
 /*  ⚠⚠ THESE PORTRAITS ARE CUT OFF AT THE BOTTOM AT THE SOURCE, and the tile is how this band
    stops showing it. Owner, on the live band: the mushroom and the harpy are missing their
    bottoms. He is right — enlarged, the sporet's stem and the harpy's talons both end in a
    straight horizontal slice. The cut is in the HARVEST frame, not in this CSS, so it is on
    every page that shows a portrait, and fixing it belongs to the portrait pass, not here.
    ⚠⚠⚠ AND I BUILT THREE INSTRUMENTS FOR IT AND ALL THREE DISAGREED WITH THE EYE:
      · bottom MARGIN inside the canvas said sporet and harpie were fine — a trim RE-CENTRES an
        already-cut sprite, so the margin comes back and stops meaning anything;
      · last-row width over TOTAL width said harpie was the 2nd cleanest of 55 — a thin-legged
        creature cut at the ankles only spans 12% of its own width;
      · last-row width over the width 6 rows up ranked harpie the CLEANEST of all 55.
    The render decided, three times against the numbers. No fourth metric is being written.
    What a free-standing silhouette on a dark band cannot hide, a TILE can: the sprite is
    cropped square from the top, so its bottom edge is the tile's edge and reads as framing —
    a bust — instead of an amputation. Same pixels, different reading; it is why the wiki's
    bordered portrait box never looked wrong while this band did. */
.beasts{display:grid;grid-template-columns:repeat(auto-fit,minmax(7.8rem,1fr));gap:.8rem}
.beasts a{position:relative;display:block;overflow:hidden;border-radius:.3rem;
  aspect-ratio:1/1;border:1px solid var(--edge);background:rgba(23,25,21,.55);
  transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease}
 /*  ⚠ 0%, NOT 8%. The tile crops 50 of the file's 250 rows; putting that window at the very TOP
    is what pushes each sprite's sliced bottom edge off screen. Sliding it down by even 8% (4 px)
    brings 4 px of the cut back — the whole point of the window is WHERE it ends. */
.beasts img{width:100%;height:100%;object-fit:cover;object-position:50% 0;display:block}
.beasts .n{position:absolute;inset:auto 0 0 0;padding:1.4rem .5rem .45rem;
  font-family:var(--font-display);font-size:.82rem;letter-spacing:.03em;
  color:var(--ink);opacity:0;transition:opacity .2s ease;
  background:linear-gradient(to top,rgba(23,25,21,.88),rgba(23,25,21,0))}
.beasts a:hover,.beasts a:focus-visible{transform:scale(1.05);outline:none;
  border-color:var(--gold);box-shadow:0 0 14px rgba(223,174,94,.35)}
.beasts a:hover .n,.beasts a:focus-visible .n{opacity:1}

/* ---- v2/L6: THE BESTIARY DRIFTS ----------------------------------------------------
   Two strips, opposite directions, slow enough to read. The tile is the same square window over
   the same 200-row frame the grid used; only the layout and the clock are new.
   ⚠ THE EDGES FADE. A strip that stops at a hard edge announces that it is a widget; a strip
   that dissolves into the band announces that the bestiary continues past the screen, which is
   the true statement and the one the band exists to make. */
.strip{position:relative;overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,var(--ink) 5rem,var(--ink) calc(100% - 5rem),transparent);
  mask-image:linear-gradient(90deg,transparent,var(--ink) 5rem,var(--ink) calc(100% - 5rem),transparent)}
.strip + .strip{margin-top:.8rem}
.btrack{display:flex;width:max-content;animation:slideL 110s linear infinite}
.strip.rev .btrack{animation-name:slideR}
.half{display:flex;gap:.8rem;padding-right:.8rem}
@keyframes slideL{from{transform:translate3d(0,0,0)}to{transform:translate3d(-50%,0,0)}}
@keyframes slideR{from{transform:translate3d(-50%,0,0)}to{transform:translate3d(0,0,0)}}
/*  ⚠ THE PAUSE IS ON THE STRIP, NOT ON THE TILE. Pausing when a TILE is hovered means the row
    keeps sliding the moment the pointer lands between two tiles, so the thing the reader reached
    for walks away from the cursor. focus-within is the same courtesy for the keyboard. */
.strip:hover .btrack,.strip:focus-within .btrack{animation-play-state:paused}
.bt{position:relative;display:block;flex:0 0 auto;width:8.2rem;aspect-ratio:1/1;overflow:hidden;
  border-radius:.3rem;border:1px solid var(--edge);background:rgba(23,25,21,.55);
  transition:transform .2s ease,border-color .2s ease,box-shadow .2s ease}
.bt img{width:100%;height:100%;object-fit:cover;object-position:50% 0;display:block}
.bt .n{position:absolute;inset:auto 0 0 0;padding:1.4rem .5rem .45rem;
  font-family:var(--font-display);font-size:.8rem;letter-spacing:.03em;
  color:var(--ink);opacity:0;transition:opacity .2s ease;
  background:linear-gradient(to top,rgba(23,25,21,.88),rgba(23,25,21,0))}
.bt:hover,.bt:focus-visible{transform:scale(1.05);outline:none;
  border-color:var(--gold);box-shadow:0 0 14px rgba(223,174,94,.35)}
.bt:hover .n,.bt:focus-visible .n{opacity:1}
/*  ⚠⚠ ON A TOUCH SCREEN THERE IS NO HOVER, SO THERE IS NO PAUSE — a drifting row a reader cannot
    stop is a row they cannot use. There, the strip stops being a marquee and becomes what a
    phone already knows how to drive: a real horizontal scroller with snap points. The duplicate
    copy is removed with it, because its only job was to make a loop seamless. */
@media (hover:none){
  .strip{overflow-x:auto;scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch;
    scrollbar-width:none}
  .strip::-webkit-scrollbar{display:none}
  .btrack{animation:none}
  .strip.rev .btrack{animation-name:none}
  .half.dup{display:none}
  .bt{scroll-snap-align:center}
}

.play{text-align:center;padding:2.6rem 1.25rem}
footer{border-top:1px solid var(--edge);padding:1.4rem 1.25rem 2.6rem;
  color:var(--ink-dim);font-size:.9rem;text-align:center}

/* ---- ENTRY MOTION, IN CASCADE (brief §1, briefed from the start, never implemented) --
   ⚠ The stagger is a CSS variable set per child in the markup, not a JS timer: the observer only
   ever adds one class, so a section revealed while the tab was in the background does not replay
   a queue of timeouts when it comes back. */
.rise{opacity:0;transform:translateY(16px);transition:opacity .55s ease,transform .55s ease}
.rise.in{opacity:1;transform:none}
.rise.in .step{animation:stepin .55s ease both;animation-delay:calc(var(--i,0) * .07s)}
@keyframes stepin{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}

/* ---- THE HERO BREATHES, WITH NO JAVASCRIPT AT ALL (brief §4bis.3) -------------------
   40 s, alternating, on a plate already scaled past its frame so the drift never uncovers an
   edge. It is the video pass in one CSS rule, at zero credits and zero bytes. */
@keyframes drift{from{transform:scale(1.06) translate3d(-1.1%,0,0)}
  to{transform:scale(1.06) translate3d(1.1%,0,0)}}
.hero .plate img:not(.hposter){animation:drift 40s ease-in-out infinite alternate;will-change:transform}

/* ============================================================================
   LE HÉROS EST DEUX IMAGES — A1 derrière, A2 détourée devant (plan §3/O1)
   ⚡ v2/L1 EN POSAIT TROIS, TAILLÉES DANS UNE SEULE, et la loi qui rendait ce
   découpage juste mérite d'être relue avant d'être rangée: masque et image
   DEVAIENT partager une seule cartographie (cover à 50% 42% des deux côtés),
   parce qu'un masque dimensionné à 100% 100% s'étire avec la BOÎTE pendant que
   l'image dedans se RECADRE — les deux ne coïncident qu'à une seule largeur, et
   l'ouverture de l'arche marchait sur la pierre à toutes les autres.
   ⚠ CE N'EST PLUS CE QUI PRODUIT LA PROFONDEUR, et le remplacement coûte des
   octets là où L1 n'en coûtait aucun: A2 est un vrai fichier, pas une seconde
   lecture de celui déjà en cache. C'est le prix d'un avant-plan qui a sa propre
   forme au lieu d'être une découpe du fond, et le budget de premier paint le
   compte (il pèse les DEUX images maintenant, voir plus bas).
   ⚠⚠ LA TRANSFORMATION VA SUR L'ENFANT, JAMAIS SUR LA BOÎTE. Elle est
   sur-dimensionnée pour qu'aucune dérive ne découvre un bord. */
/*  ⚠⚠⚠ isolation:isolate IS LOAD-BEARING, AND IT COST THE TITLE. position:relative with z-index
    auto does NOT open a stacking context, so the two planes at z-index 1 and 2 painted into the
    ROOT context — above .say, which has no z-index at all. Measured in the browser on the first
    build: the eyebrow, the title, the lede and both buttons were all behind the picture, with two
    stray glyphs surviving where the mask happened to be transparent. Isolating the plate keeps
    its own planes inside it; .say then gets a z-index of its own so the order is DECLARED rather
    than inherited from document order, which is what broke. */
/*  ⛔ LES DEUX MASQUES SVG SONT PARTIS AVEC LA PEINTURE QU'ILS DÉCOUPAIENT, ET C'EST UNE
    SUPPRESSION OBLIGATOIRE, PAS UN NETTOYAGE. Ils étaient tracés dans un viewBox de 1672x941 —
    les dimensions du FICHIER B1 — et le premier d'entre eux décrivait l'ouverture de SON arche,
    centrée. A1 porte son arche à DROITE: le même chemin y aurait découpé un morceau de ciel et
    fait dériver un trou sur de la brume. Un masque taillé sur une image n'est pas une couche
    réutilisable, c'est une partie de cette image.
    ⚑ ET LE MOTIF DE LA LOI SURVIT: si un jour une plaque redevient une source à trois plans, la
    règle à relire est celle du dessous — masque et image DOIVENT partager une seule cartographie
    (cover à 50% 42% des deux côtés), sans quoi les plans se séparent à toutes les largeurs sauf
    une. C'est ce qui rendait la version B1 juste; ce n'est plus ce qui produit la profondeur. */
.hero .plate{position:relative;overflow:hidden;isolation:isolate}
/*  ⚡ L'AVANT-PLAN EST MAINTENANT UNE IMAGE À CANAL ALPHA (A2), posée sur le bas du cadre. Elle
    est peinte pour ce rôle: deux amas de pierre moussue dans les coins BAS, noir pur ailleurs,
    détourée au build (seuils mesurés, voir ALPHA_PLATES dans build_site_media.py).
    ⚠ contain ET ancrée EN BAS, pas cover. Le sujet de ce plan est la LIGNE DU SOL: en cover, une
    bande large recadre par le haut et le bas et emporte précisément les deux amas, qui sont le
    seul contenu du fichier. En contain ancré en bas, les pierres restent posées sur le bord
    inférieur à toutes les largeurs, et ce qui manque sur les côtés d'un cadre très large est du
    noir transparent — c'est-à-dire rien.
    ⚠ LE SUR-DIMENSIONNEMENT DE 1,06 EST CE QUI PERMET À LA DÉRIVE DE NE JAMAIS DÉCOUVRIR UN BORD,
    et il est plus faible que le 1,12 des anciens plans parce qu'un plan détouré n'a pas de bord à
    découvrir sur trois côtés — seulement en bas, où il est ancré. */
.hero .lay{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden}
.hero .lay>i{position:absolute;inset:0;display:block;
  background:url(media/hero-fore.webp) 50% 100%/contain no-repeat;
  transform:scale(1.06);will-change:transform}
/*  Sans timeline de défilement, le plan respire sur son horloge: une dérive lente et déphasée du
    fond lit comme de la profondeur même immobile. */
@keyframes foreDrift{from{transform:scale(1.06) translate3d(-1%,-.5%,0)}
  to{transform:scale(1.06) translate3d(1%,.5%,0)}}
.hero .fore>i{animation:foreDrift 26s ease-in-out infinite alternate}

/*  The atmosphere plane: motes, a light sweep and a vignette. All three are position-independent
    on purpose — anything pinned to a FEATURE of the painting (the keystone rune, the crystal)
    would need the cover mapping solved a second time and would drift the day the crop changes. */
.hero .fx{position:absolute;inset:0;z-index:2;pointer-events:none;overflow:hidden}
.hero .fx::before{content:"";position:absolute;inset:-25%;opacity:.55;
  background-image:
    radial-gradient(2px 2px at 18% 24%,rgba(243,224,194,.95),transparent 62%),
    radial-gradient(3px 3px at 72% 61%,rgba(243,224,194,.75),transparent 62%),
    radial-gradient(2px 2px at 41% 78%,rgba(243,224,194,.85),transparent 62%),
    radial-gradient(2px 2px at 88% 18%,rgba(243,224,194,.70),transparent 62%);
  background-size:330px 250px,540px 410px,420px 320px,270px 210px;
  animation:motes 90s linear infinite}
@keyframes motes{from{transform:translate3d(0,0,0)}to{transform:translate3d(-330px,-250px,0)}}
.hero .fx::after{content:"";position:absolute;inset:-40%;
  background:linear-gradient(104deg,transparent 40%,rgba(243,224,194,.17) 49%,transparent 58%);
  animation:sweep 17s ease-in-out infinite}
@keyframes sweep{0%,72%{transform:translate3d(-46%,0,0)}100%{transform:translate3d(46%,0,0)}}

/*  ⚠ THE TITLE IS NEVER SPLIT INTO PER-LETTER SPANS. The localiser walks TEXT NODES: a title cut
    into one node per glyph is 12 lookups that all miss, and the English page would ship the
    French title. The shimmer is a background-clip on the unsplit node, and the cascade below is
    per CHILD of the block, which is markup that already exists. */
.hero .say>*{animation:stepin .7s cubic-bezier(.2,.7,.3,1) both}
.hero .say>*:nth-child(2){animation-delay:.10s}
.hero .say>*:nth-child(3){animation-delay:.20s}
.hero .say>*:nth-child(4),.hero .say>*:nth-child(5){animation-delay:.30s}
.hero .poster{background:linear-gradient(100deg,currentColor 42%,rgba(223,174,94,.95) 50%,
  currentColor 58%) 0 0/300% 100% no-repeat;-webkit-background-clip:text;background-clip:text;
  animation:shimmer 9s ease-in-out 1.2s infinite}
@keyframes shimmer{0%,62%{background-position:100% 0}100%{background-position:0 0}}

/*  The scroll cue: a chevron, no words — a label here would be one more string to translate for
    a gesture every reader already owns. It fades out for good on the first scroll. */
/*  ⚡ V6-6 — LE REPÈRE DE SCROLL EST LE MOTIF PEINT D'A9 (un chevron sur un point runique) et
    plus deux bords CSS tournés de 45°.
    ⚠⚠ LA ROTATION SORT DES KEYFRAMES AVEC LA BOÎTE QU'ELLE FABRIQUAIT. rotate(45deg) etait
    la moitie de la FORME - c est ce qui faisait d un coin de boite une pointe - donc le laisser
    aurait fait tourner l'image de 45°. Le nom cue ne bouge pas: site_build_check V3-7 compare
    les NOMS déclarés à ceux que le bloc mouvement-réduit nomme, et renommer une animation aurait
    été un changement d'API pour un changement de dessin.
    ⚠ LE MOUVEMENT DEVIENT VERTICAL, ce qu'il aurait toujours dû être: la diagonale de 4 px sur 4
    était « vers le bas » dans le repère TOURNÉ de la boîte, pas dans celui du lecteur. */
.hero .cue{position:absolute;left:50%;bottom:.7rem;z-index:5;width:26px;height:31px;
  margin-left:-13px;opacity:.85;pointer-events:none;
  background:url(media/orn-cue.webp) center/contain no-repeat;
  animation:cue 2.4s ease-in-out infinite}
@keyframes cue{0%,100%{transform:translate3d(0,0,0);opacity:.28}
  50%{transform:translate3d(0,5px,0);opacity:.85}}

/* ============================================================================
   L3 — THE COMBAT BAND PLAYS ITSELF (SITE_V2_GAMIFIED_PLAN §3)
   A rail four screens tall, one pinned stage inside it, and every beat of the
   scene keyed to how far the rail has crossed the viewport.
   ⚠ THE RANGE IS contain, NOT cover, AND THE DIFFERENCE IS THE WHOLE PIN.
   For a subject taller than the scrollport, contain 0% → 100% is EXACTLY the
   window where the subject fully covers the viewport — which is exactly when
   the sticky child is pinned. cover would start the scene while the rail was
   still entering, so the first two beats would play off-screen above the fold
   and the last two would never be reached. Measured off the geometry: on a
   420vh rail in a 100vh viewport, cover puts the pin between 19% and 81%.
   ⚠ WITHOUT A SCROLL TIMELINE THERE IS NO RAIL AND NO PIN. The @supports block
   is what GIVES the rail its height; the default below is one screen, one
   still, and the five beats as a plain list — which is a legible section, not
   a broken one. Firefox stable still ships this feature behind a flag. */
/*  ⚠⚠⚠ overflow:clip, NOT overflow:hidden, AND IT IS THE WHOLE PIN. Every band sets
    overflow:hidden so the scaled backdrop cannot escape it — and an ancestor with
    overflow:hidden is a SCROLL CONTAINER, which is what a sticky child resolves against.
    Measured on the first build: the stage scrolled straight past the top of the window and the
    five beats arrived stacked at the top of an empty green field, with every CSS property
    reading exactly as authored (position:sticky, rail 3780 px, timeline --fight resolved). The
    bug was two hundred lines away in a rule written for a different reason.
    overflow:clip clips identically and creates NO scroll container, so the pin resolves against
    the viewport again. */
.band.band-fight{overflow:clip}
.frail{position:relative}
.fpin{position:relative;z-index:2;max-width:82rem;margin:0 auto;padding:0 1.25rem}
.fstage{position:relative}
.fstage img{display:block;width:100%;height:auto;border-top:1px solid var(--edge);
  border-bottom:1px solid var(--edge);box-shadow:0 10px 40px rgba(23,25,21,.45)}
/*  ⚠ A DROP SHADOW, NOT A DARKER GOLD. The board is drawn over bright sunlit grass and the first
    build read as a watermark — measured by eye against the plate, which is the only instrument
    that applies to a line over a painting. Darkening the stroke would have taken it out of the
    chrome's palette; a 1 px ink shadow keeps the gold and gives every line its own ground. */
.fsvg{position:absolute;inset:0;width:100%;height:100%;overflow:visible;
  filter:drop-shadow(0 1px 1px rgba(23,25,21,.9))}
/*  ⚠ THE LATTICE IS THE QUIETEST THING ON THE BOARD, AND THE FIRST BUILD HAD IT THE LOUDEST.
    Forty cells of 2.5 px gold read as a fishnet over the paint and the four annotations that
    carry the actual meaning — the walk, the ray, the cut, the blast — could not be picked out
    of it. The grid states the RULE (there are cells); everything that MOVES states the beat. */
.fsvg .cell{fill:none;stroke:rgba(223,174,94,.55);stroke-width:1.8;vector-effect:non-scaling-stroke}
/*  Blue for movement and red for the enemy are the game's own two colours; a board that spoke
    gold for everything would be a diagram of nothing in particular. */
.fsvg .lit{fill:rgba(120,190,235,.26);stroke:rgba(160,220,252,.95);stroke-width:3;
  vector-effect:non-scaling-stroke}
.fsvg .foe{fill:rgba(206,74,52,.28);stroke:rgba(240,128,98,.95);stroke-width:3;
  vector-effect:non-scaling-stroke}
.fsvg .aoe{fill:rgba(255,152,58,.38);stroke:rgba(255,206,132,.98);stroke-width:4;
  vector-effect:non-scaling-stroke}
/*  ⚠⚠ THE PATH DOES NOT DRAW ITSELF, AND THAT IS A CORRECTION. Animating stroke-dasharray from
    "0 400" to "9 9" to make a dashed line grow spends the whole middle of its range as almost
    pure gap: measured at 46% of the rail the walk was invisible, on the one beat that exists to
    show it. A dash pattern and a draw-on are the same property and cannot both have it. So the
    dashes are constant and they MARCH, on a clock — the reveal is the group's own fade. */
.fsvg .walk{fill:none;stroke:rgba(150,215,252,.98);stroke-width:7;stroke-dasharray:10 10;
  stroke-linecap:round;vector-effect:non-scaling-stroke;animation:ants 1.3s linear infinite}
@keyframes ants{to{stroke-dashoffset:-20}}
.fsvg .ray{fill:none;stroke:rgba(245,215,150,.98);stroke-width:7;stroke-linecap:round;
  vector-effect:non-scaling-stroke}
.fsvg .cut{fill:none;stroke:rgba(226,86,62,.98);stroke-width:7;stroke-dasharray:8 9;
  stroke-linecap:round;vector-effect:non-scaling-stroke}
.fsvg text{font-family:var(--font-display);font-size:26px;fill:var(--ink);
  paint-order:stroke;stroke:rgba(23,25,21,.85);stroke-width:5;stroke-linejoin:round}
.fsvg .chip rect{fill:rgba(23,25,21,.82);stroke:rgba(223,174,94,.75);stroke-width:2;rx:6}
.fsvg .hit{font-size:52px;fill:var(--gold-hi)}
.fbeats{list-style:none;margin:1.6rem auto 0;padding:0;max-width:46rem;display:grid;gap:.35rem}
.fbeats li{display:grid;grid-template-columns:auto 1fr;gap:.7rem;align-items:baseline;
  padding:.3rem .7rem;border-left:2px solid rgba(223,174,94,.35)}
.fbeats b{font-family:var(--font-display);letter-spacing:.04em;color:var(--gold-ink);
  white-space:nowrap}
.fbeats span{color:var(--ink-dim)}

/* ============================================================================
   O2 — LE DOCK (plan §3/O2)
   ⚠⚠⚠ LE CONTRASTE DE CETTE BARRE EST BORNÉ PAR CONSTRUCTION, JAMAIS ÉCHANTILLONNÉ. Elle flotte
   au-dessus de ce qui défile dessous — le héros peint, puis six bandes — donc il n'y a AUCUN
   fond à mesurer. La méthode est celle de la roue des classes et de la bande de combat: on prend
   le pire sol qu'une couche puisse jamais recevoir, l'art BLANC PUR.
     · barre transparente: la rampe vaut .78 de --panel là où les glyphes se posent (ils
       occupent 31 % à 69 % d'une barre de 64 px, la rampe finit à 70 %), donc le sol y vaut au
       pire rgb(74,76,72) et --ink mesure 6,9:1 avec un delta rec601 de 152.
     · barre en verre: --raise à 72 %, sol au pire rgb(94,96,92), --ink à 4,92:1 et delta 131.
   Les deux franchissent les DEUX planchers du dépôt (4,5:1 WCAG et 90 de delta) sans regarder
   une seule image, ce qui est la seule mesure qui reste vraie quand la peinture change.
   ⚠⚠ ALIGNER LES BOÎTES, JAMAIS LES LIGNES DE BASE. Cette barre pose côte à côte un wordmark,
   cinq liens, un sélecteur de langue injecté par un AUTRE fichier, un bouton avec un SVG dedans
   et une CTA dorée qui porte overflow:hidden pour retenir sa brillance — et un inline-block dont
   l overflow n est pas visible prend son bord INFÉRIEUR comme ligne de base. C'est le décalage
   de 23,2 px que le lot L8 a payé sur deux boutons seulement. align-items:center sur un flex ne
   consulte pas les lignes de base du tout.
   ⚠ LE DOCK N'EST PAS DANS LE FLUX. Sur l'accueil c'est le lot (il flotte sur l'art du héros);
   sur la préinscription c'est --dock-h de padding en haut du corps, une ligne plus haut. */
html{--dock-h:4rem;scroll-padding-top:calc(var(--dock-h) + 1rem)}
.dock{position:fixed;inset:0 0 auto 0;z-index:1000;height:var(--dock-h);
  display:flex;align-items:center;gap:1rem;padding:0 1.25rem;
  border-bottom:1px solid transparent;
  transition:background .25s ease,border-color .25s ease}
.dock:not(.glass){background:linear-gradient(to bottom,rgba(23,25,21,.90) 0,rgba(23,25,21,.78) 70%,rgba(23,25,21,0) 100%)}
.dock.glass{border-bottom-color:var(--edge)}
.dk-brand{flex:0 0 auto;font-family:var(--font-display);font-weight:700;font-size:1.02rem;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink)}
.dk-brand:hover{color:var(--gold-ink)}
.dk-fold{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:1.2rem}
.dk-nav{flex:1 1 auto;display:flex;justify-content:center;align-items:center;gap:.2rem;min-width:0}
.dk-nav a{position:relative;padding:.5rem .7rem;border-radius:.25rem;color:var(--ink);
  font-family:var(--font-display);font-size:.82rem;letter-spacing:.14em;text-transform:uppercase;
  transition:color .18s ease}
.dk-nav a:hover{color:var(--gold-ink)}
.dk-nav a:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/*  L'ancre courante est SOULIGNÉE, pas colorée: la couleur est déjà le survol, et une barre qui
    dit la même chose de deux façons ne dit plus laquelle est laquelle. */
.dk-nav a::after{content:"";position:absolute;left:.7rem;right:.7rem;bottom:.24rem;height:2px;
  background:var(--gold);transform:scaleX(0);transform-origin:50% 50%;
  transition:transform .22s ease}
.dk-nav a[aria-current="true"]{color:var(--gold-ink)}
.dk-nav a[aria-current="true"]::after{transform:none}
.dk-tools{flex:0 0 auto;display:flex;align-items:center;gap:.5rem}
.dk-play{flex:0 0 auto;margin:0;padding:.44rem 1.1rem;font-size:.86rem;letter-spacing:.1em;
  text-transform:uppercase}
/*  ⚠ LE BURGER EST SERVI hidden — la loi de l'interrupteur de son: un bouton qui ne peut rien
    ouvrir ment, donc il n'apparaît que quand un script peut l'écouter. Le [hidden] est restaté
    à force de classe parce que display:grid bat la règle du navigateur. */
.dk-burger{flex:0 0 auto;display:none;place-items:center;width:2.4rem;height:2.4rem;
  padding:0;border-radius:.3rem;border:1px solid var(--edge);background:rgba(23,25,21,.4);
  color:var(--ink);cursor:pointer}
.dk-burger[hidden]{display:none}
.dk-burger:hover{border-color:var(--gold)}
.dk-burger:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.dk-burger>span{position:relative;display:block;width:1.05rem;height:2px;background:currentColor}
.dk-burger>span::before,.dk-burger>span::after{content:"";position:absolute;left:0;width:100%;
  height:2px;background:currentColor}
.dk-burger>span::before{top:-.34rem}
.dk-burger>span::after{top:.34rem}
/*  Le sélecteur de langue est injecté par scripts/i18n/site.mjs avec son propre bloc <style>
    épinglé en bas à droite. Ce bloc arrive APRÈS landing.css dans le head, donc l'ordre ne
    suffit pas: c'est la SPÉCIFICITÉ qui reprend la main (.dock .site-language, 0-2-0, contre
    .site-language, 0-1-0). Le fichier i18n n'a qu'une seule modification — poser la barre dans
    la fente du dock quand la page en offre une — et les 675 fiches du codex, qui n'en offrent
    pas, gardent exactement le coin qu'elles avaient. */
.dock .site-language{position:static;z-index:auto;gap:.15rem;padding:0;background:none;
  border:0;border-radius:0;font:inherit}
.dock .site-language a{padding:.5rem .5rem;border-radius:.25rem;color:var(--ink-dim);
  font-family:var(--font-display);font-size:.74rem;letter-spacing:.12em;text-transform:uppercase}
.dock .site-language a:hover{color:var(--gold-ink)}
.dock .site-language a:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.dock .site-language [aria-current]{color:var(--ink);font-weight:700;text-decoration:none}
/*  ⚠ SOUS 50 rem LA BARRE GARDE LE WORDMARK ET JOUER, LE RESTE DEVIENT UNE FEUILLE. Elle part
    SOUS la barre (inset commence à --dock-h) pour que le bouton qui l'a ouverte reste celui qui
    la referme — une feuille qui recouvre son propre burger est une porte sans poignée. */
@media (max-width:49.99rem){
  .dock{gap:.6rem;padding:0 .9rem}
  .dk-burger{display:grid;order:3}
  .dk-fold{position:fixed;inset:var(--dock-h) 0 0 0;z-index:-1;
    flex-direction:column;justify-content:flex-start;align-items:stretch;gap:1.4rem;
    padding:1.8rem 1.5rem 2.4rem;background:var(--panel);overflow-y:auto}
  .dock[data-open="0"] .dk-fold{display:none}
  .dk-nav{flex:0 0 auto;flex-direction:column;align-items:stretch;gap:.2rem}
  .dk-nav a{padding:.85rem .6rem;font-size:1rem;border-bottom:1px solid var(--edge-soft)}
  .dk-nav a::after{left:.6rem;right:auto;width:2rem;bottom:.5rem}
  .dk-tools{justify-content:space-between}
  .dk-play{margin-left:auto}
}

/* ============================================================================
   O3 — LE SOL DES BANDES : LA SECONDE COUCHE, LE VERRE, LA PLAQUE
   ⚠⚠ parallax-2 DEMANDE DEUX PLANS PAR BANDE (plan §2.4) ET UNE BANDE N'EN AVAIT QU'UN.
   Le fond peint est ::before, le voile est ::after — et un voile qui bouge découvre l'art qu'il
   est là pour couvrir. Le second plan est donc un ÉLÉMENT réel, .bhaze, et il porte deux rôles
   qui sont la même image à deux forces:
     · sur une bande peinte (monde, jeu, préinscription) c'est une brume chaude et une vignette
       qui dérivent 1,15x plus vite que la peinture, laquelle dérive 0,6x;
     · sur une bande SANS peinture (les classes, les chiffres) c'est la nappe du Runefield —
       littéralement la même recette que .hero .plate::after, mesurée sur 138 996 échantillons de
       la vraie nappe à V1: 97,3 % de pixels peints, alpha moyen 30,6/255, teinte rgb(27,17,4).
   ⚠⚠⚠ ET LA NAPPE DES CLASSES ET DES CHIFFRES EST UNE IMAGE FIXE, PAS LE CANVAS. Le plan veut
   le Runefield VIVANT sur ces bandes; le registre §13 dit pourquoi il ne l'est pas, et ce n'est
   pas un oubli: un seul contexte WebGL est un CONTRAT (Chromium jette le plus ancien au-delà de
   ~16), donc une seconde bande vivante veut un canvas PAGE — fixe, plein cadre, découpé au
   ciseau par bande — et le canvas du héros est aujourd'hui posé DANS sa plaque, avec un poster
   aligné dessus à 1,4 % près par l'arithmétique de sa propre caméra. Déménager ce canvas est de
   la chirurgie sur V1, pas de la mise en page V2.
   ⚠ inset:-8% EN HAUT ET EN BAS: c'est la marge que la dérive consomme, sinon le plan découvre
   un bord au milieu du parcours. */
.bhaze{position:absolute;inset:-8% 0;z-index:1;pointer-events:none;
  background:
    radial-gradient(120% 42% at 50% 62%,rgba(223,174,94,.12) 0,rgba(223,174,94,0) 70%),
    radial-gradient(88% 88% at 50% 50%,rgba(23,25,21,0) 0,rgba(23,25,21,.55) 100%),
    linear-gradient(to bottom,rgba(23,25,21,.10),rgba(32,34,28,.16) 55%,rgba(23,25,21,.10))}

/*  ---- LE VERRE ET LA PLAQUE, les deux matières du §2.3, écrites UNE fois ------------------
    ⚠ backdrop-filter EST UN LUXE ET LE REPLI EST UNE MESURE, pas un « au cas où »: sans lui le
    verre à 72 % laisse passer l'art derrière le texte sans le flouter, ce qui est exactement le
    fond illisible que le verre existe pour éviter. Le repli ferme à 94 %, donc presque opaque. */
.glass{background:rgba(32,34,28,.72);
  -webkit-backdrop-filter:blur(14px) saturate(1.2);backdrop-filter:blur(14px) saturate(1.2)}
@supports not ((backdrop-filter:blur(2px)) or (-webkit-backdrop-filter:blur(2px))){
  .glass{background:rgba(32,34,28,.94)}
}
.plate{background:var(--raise);border:1px solid var(--edge);border-radius:.5rem;
  box-shadow:var(--shadow)}

/*  ---- reveal-letters (§2.4) --------------------------------------------------------------
    ⚠⚠⚠ LES LETTRES N'EXISTENT PAS DANS LE HTML SERVI. Le localiseur parcourt les NŒUDS DE
    TEXTE: un titre coupé en un nœud par glyphe est douze recherches qui échouent toutes, et la
    page anglaise partirait avec le titre français. Cette feuille décrit donc une forme que
    DOCK_JS fabrique après coup, et sous mouvement réduit elle ne fabrique rien du tout — il n'y
    a alors ni span ni animation à défaire, seulement le nœud que le générateur a écrit.
    ⚠ LE MOT EST LA BOÎTE QUI SE COUPE, LA LETTRE EST LA BOÎTE QUI MONTE. Des inline-block sans
    mot autour se couperaient n'importe où en fin de ligne. */
.rl{display:inline}
.rw{display:inline-block;white-space:nowrap}
.rl i{display:inline-block;font-style:normal}
.rise.in .rl i{animation:rlIn .5s cubic-bezier(.2,.7,.3,1) both;
  animation-delay:calc(var(--i,0) * 28ms)}
@keyframes rlIn{from{opacity:0;transform:translate3d(0,12px,0)}to{opacity:1;transform:none}}

/*  ---- rule-draw (§2.3 « gold rule ») ------------------------------------------------------
    Un filet de 1 px en --edge avec un segment doré au centre — le diviseur que le jeu dessine,
    sous chaque titre de bande.
    ⚠ IL EST ABSENT DANS L'ÉPINGLE DU COMBAT, ET C'EST DE L'ARITHMÉTIQUE. La scène épinglée est
    plafonnée par calc((100vh - 24rem) * 1.7778): à V1 elle mesurait déjà 939 px dans une épingle
    de 922, et l'image a été coupée en haut. Un filet plus sa marge, ce sont 19 px de plus dans
    le seul endroit de la page où la hauteur est un budget fermé. R4 dit d'ailleurs que cette
    bande est RECOLORÉE, pas redécorée. */
.cartouche .poster::after{content:"";display:block;width:min(20rem,62%);height:1px;
  margin:1.15rem auto 0;transform-origin:50% 50%;
  background:linear-gradient(90deg,rgba(104,88,59,0),var(--edge) 16%,
    var(--gold) 44%,var(--gold) 56%,var(--edge) 84%,rgba(104,88,59,0))}
.fpin .cartouche .poster::after{display:none}

/*  ---- O3/bestiaire : LA JANTE DORÉE AU REPOS -----------------------------------------------
    Le plan demande que les portraits prennent la lueur de jante. Au REPOS c'est une jante, pas
    un halo: une lumière rasante par le bas de la tuile, dans l'or du jeu, à une force où elle
    éclaire le bord du sujet sans allumer le carré. Le survol garde son halo entier. */
.bt,.beasts a{box-shadow:inset 0 -34px 30px -30px rgba(223,174,94,.34)}

/* ============================================================================
   L2 · L3 — SCROLL-DRIVEN, AND ONLY WHERE IT IS SUPPORTED
   ⚠ Everything in here is an ENHANCEMENT over a page that is already complete
   without it. The IntersectionObserver reveal below stays exactly as it was;
   this block replaces its one-shot fade with a scrub and hands the fight band
   its rail. Nothing here is load-bearing for reading the page. */
/*  ⚠⚠ THE WHOLE BLOCK SITS INSIDE not (prefers-reduced-motion) RATHER THAN BEING UNDONE BY THE
    RULE AT THE END OF THE FILE, AND THAT IS NOT A STYLE PREFERENCE. Half of what is in here is
    LAYOUT, not animation: the rail's four screens of height and the pin. Turning only the
    animations off would leave a reader who asked for stillness scrolling four empty screens past
    a frozen picture. What has to be reversed is the whole apparatus, so it is never built. */
@supports (animation-timeline:view()){
 @media not (prefers-reduced-motion:reduce){
  /*  The hero's three planes stop drifting on a clock and start answering the scroll:
      the gate rises with you (the world through the arch pulls away), the ground pushes
      down and out (it is closest), the plate itself barely moves (it is furthest). */
  .hero .plate img:not(.hposter){animation:baseScrub linear both;animation-timeline:scroll(root block);
    animation-range:0 100vh}
  .hero .fore>i{animation:foreScrub linear both;animation-timeline:scroll(root block);
    animation-range:0 100vh}
  .hero .cue{animation:cue 2.4s ease-in-out infinite,cueOut linear both;
    animation-timeline:auto,scroll(root block);animation-range:normal,0 22vh}
  @keyframes baseScrub{from{transform:scale(1.06) translate3d(0,0,0)}
    to{transform:scale(1.13) translate3d(0,2.6%,0)}}
  @keyframes foreScrub{from{transform:scale(1.06) translate3d(0,-1.2%,0)}
    to{transform:scale(1.18) translate3d(0,7%,0)}}
  @keyframes cueOut{to{opacity:0}}

  /*  L2 — the bands scrub instead of snapping. Each .step reads ITS OWN passage through the
      viewport, so the cascade is produced by where the elements SIT rather than by a queue of
      delays: a reader who scrolls back up sees it play backwards, which a one-shot never does. */
  .rise{opacity:1;transform:none;transition:none}
  .rise .step{animation:scrubIn linear both;animation-timeline:view();
    animation-range:entry 4% entry 62%}
  .rise.in .step{animation-name:scrubIn}
  @keyframes scrubIn{from{opacity:0;transform:translate3d(0,26px,0) scale(.985)}
    to{opacity:1;transform:none}}
  /*  And the painted backdrop of every band travels slower than the band itself. This is the
      parallax; the scale is baked into both keyframes because a band that also sets its own
      transform (the blurred fight plate) would otherwise have it overwritten to none. */
  .band::before{animation:plateScrub linear both;animation-timeline:view();
    animation-range:cover 0% cover 100%}
  @keyframes plateScrub{from{transform:scale(1.14) translate3d(0,-3.5%,0)}
    to{transform:scale(1.14) translate3d(0,3.5%,0)}}
  .band-fight::before{animation:fightPlate linear both;animation-timeline:view();
    animation-range:cover 0% cover 100%}
  @keyframes fightPlate{from{transform:scale(1.16) translate3d(0,-3%,0)}
    to{transform:scale(1.16) translate3d(0,3%,0)}}

  /*  ⚡ O3 — parallax-2 : LE SECOND PLAN, SUR LES TROIS BANDES QUE LE PLAN NOMME (monde,
      classes, jeu). La peinture parcourt 7 % de la bande (plateScrub, ci-dessus) et la brume
      13,4 %, soit le rapport 0,6x / 1,15x du §2.4 exprimé en amplitudes plutôt qu'en vitesses —
      c'est la même chose sur une plage de défilement commune, et une amplitude est la seule des
      deux qu'une keyframe sait écrire.
      ⚠ LA BANDE DES CHIFFRES GARDE SA BRUME IMMOBILE, ET C'EST CE QUE LE PLAN DIT. Elle n'est
      ni claire ni sombre, elle est la respiration entre le bestiaire et la clôture; un second
      plan qui dérive y ferait une sixième bande là où il fallait une césure. */
  .band-class .bhaze,.band-play .bhaze{animation:hazeScrub linear both;
    animation-timeline:view();animation-range:cover 0% cover 100%}
  /*  ⚠ 7,2 % ET PAS 6,7 %, ET LE DEMI-POINT EST UNE MESURE. Un pourcentage de translation se
      résout contre la BOÎTE DE L'ÉLÉMENT: la brume déborde de 8 % en haut et en bas, donc sa
      boîte fait 1,16 fois la bande, tandis que le fond peint porte en plus une échelle de 1,14
      qui multiplie sa propre translation. Les deux effets ne s'annulent pas: à 6,7 % le rapport
      lu sur la page servie était 1,79 au lieu du 1,917 que 1,15x / 0,6x demande. */
  @keyframes hazeScrub{from{transform:translate3d(0,-7.2%,0)}to{transform:translate3d(0,7.2%,0)}}

  /*  ⚡ O3 — rule-draw : LE FILET SE TIRE DEPUIS SON CENTRE pendant que le titre entre. Il est
      COMPLET partout ailleurs (un navigateur sans timeline de défilement, un lecteur qui a
      demandé moins de mouvement): la règle statique dessine le filet entier et seule cette
      keyframe le part de presque rien. Un effet qui laisse sa cible incomplète quand il n'est
      pas joué est un effet qui a caché la moitié de la page. */
  .cartouche .poster::after{animation:ruleDraw linear both;animation-timeline:view();
    animation-range:entry 8% entry 64%}
  @keyframes ruleDraw{from{transform:scaleX(.06);opacity:.15}to{transform:none;opacity:1}}

  /*  L3 — the rail exists only here. view-timeline is declared on the rail and consumed by its
      own descendants, so no timeline-scope is needed and no name escapes this section. */
  /*  ⚠ .band.dark.band-fight, NOT .band-fight alone. The dark bands set their own 6rem/6.4rem at
      specificity (0,2,0) and a single class loses to it — measured: the rail read 420vh and the
      band 442vh, so the pin let go 199 px before the section ended and the page showed a strip
      of nothing under a frozen picture. */
  .band.dark.band-fight{padding:0}
  .frail{height:420vh;view-timeline:--fight block}
  .fpin{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;
    align-items:center;justify-content:center;gap:1rem}
  /*  ⚠⚠ THE STAGE IS CAPPED BY THE VIEWPORT'S HEIGHT, NOT ONLY BY ITS WIDTH, and that is what
      keeps a PIN honest. A 16:9 plate at 100% width plus a poster title plus five beats measured
      939 px inside a 922 px pin: the picture was cut at the top and the last beat at the bottom,
      on a section whose whole promise is that it holds still while you read it. 1.7778 is the
      plate's own ratio (1360/765), so the cap converts the height left over after the type into
      the width the picture may take. */
  .fstage{width:min(100%,76rem,calc((100vh - 24rem) * 1.7778))}
  /*  Inside the pin the title is a caption, not a poster: it shares one screen with the board and
      five beats, and every rem it takes is a rem the board loses. */
  .fpin .cartouche{margin-bottom:0}
  .fpin .cartouche .poster{font-size:clamp(1.5rem,3.2vw,2.4rem);margin-bottom:0}
  .fpin .cartouche .eyebrow{margin-bottom:.3rem}
  .fbeats{margin-top:.4rem}
  .fbeats li{opacity:.22;transform:translate3d(0,6px,0);
    animation:beat linear both;animation-timeline:--fight}
  .fbeats li:nth-child(1){animation-range:contain 1% contain 19%}
  .fbeats li:nth-child(2){animation-range:contain 19% contain 38%}
  .fbeats li:nth-child(3){animation-range:contain 38% contain 57%}
  .fbeats li:nth-child(4){animation-range:contain 57% contain 76%}
  .fbeats li:nth-child(5){animation-range:contain 76% contain 99%}
  @keyframes beat{0%{opacity:.22;transform:translate3d(0,6px,0);border-left-color:rgba(223,174,94,.2)}
    18%,82%{opacity:1;transform:none;border-left-color:var(--gold)}
    100%{opacity:.34;transform:none;border-left-color:rgba(223,174,94,.2)}}
  /*  The scene: five groups, five windows, one timeline. A group that has played STAYS on —
      a landing that erases what it just explained makes the reader scroll back to re-read it. */
  .fsvg g[data-beat]{opacity:0;animation:sceneIn linear both;animation-timeline:--fight}
  @keyframes sceneIn{from{opacity:0}to{opacity:1}}
  .fsvg g[data-beat="1"]{animation-range:contain 2% contain 14%}
  .fsvg g[data-beat="2"]{animation-range:contain 20% contain 32%}
  .fsvg g[data-beat="3"]{animation-range:contain 39% contain 51%}
  .fsvg g[data-beat="4"]{animation-range:contain 58% contain 70%}
  .fsvg g[data-beat="5"]{animation-range:contain 77% contain 88%}
  /*  The grid DRAWS rather than fades, and the walk path draws along its own length. */
  .fsvg .cell{stroke-dasharray:200;stroke-dashoffset:200;
    animation:draw linear both;animation-timeline:--fight;animation-range:contain 2% contain 16%}
  @keyframes draw{to{stroke-dashoffset:0}}
  .fsvg .hit{animation:pop linear both;animation-timeline:--fight;
    animation-range:contain 79% contain 93%}
  @keyframes pop{0%{opacity:0;transform:translate3d(0,18px,0)}
    30%{opacity:1;transform:translate3d(0,-6px,0)}100%{opacity:1;transform:translate3d(0,-22px,0)}}
 }
}

/* ⚠⚠ ONE RULE TURNS OFF EVERY MOTION ON THIS PAGE, AND IT HAS TO NAME EACH ONE. A blanket
   animation:none would also freeze the reveal in its INVISIBLE state and hide the whole page.
   Reduced motion means the content ARRIVES INSTANTLY; it never means the content is missing. */
@media (prefers-reduced-motion:reduce){
  .rise{opacity:1;transform:none;transition:none}
  .rise.in .step{animation:none}
  .hero .plate img:not(.hposter){animation:none}
  /*  ⚠ v2/L8 - THE SHEEN IS NEVER BUILT, THE GLOW STILL APPEARS. A glint travelling across a
      button on a five-second loop is exactly what someone asking for less motion is asking to be
      spared; a hover highlight is not motion, it is feedback, so it arrives instantly instead. */
  .cta.p::after{animation:none}
  .cta::before,.snd::before{transition:none}
  .snd{transition:none}
  .warc>li>a,.warc img,.wn,.wdet,.beasts a,.beasts .n,.cta{transition:none}
  .warc>li>a:hover .wn,.warc>li>a:focus-visible .wn{color:var(--ink)}
  /*  ⚠ THE ARC IS NOT UNDONE HERE, ONLY ITS MOTION. --lift and --tilt are LAYOUT: this band
      is a wheel, and flattening it under reduced motion would hand that reader a different
      page rather than the same page held still. What stops is the travel on hover. */
  .warc>li>a:hover,.warc>li>a:focus-visible{transform:rotate(var(--tilt,0deg))}
  .beasts a:hover,.beasts a:focus-visible{transform:none}
  .beasts a{transition:none}
  .tallies a{transition:none}
  .tallies a:hover,.tallies a:focus-visible{transform:none}
  .cta.p:active,.cta.s:active{transform:none}
  /*  v2 — the clock-driven half of the new motion. The scroll-driven half is never built at all
      (see the @media inside @supports above), so nothing here has to undo it.
      ⚠ THE PLANES ARE NOT HIDDEN, THEY ARE STILLED. The depth cut is what the picture IS now;
      removing the layers would show a reader who asked for less motion a DIFFERENT painting
      from everyone else. They keep their over-scale and stop moving. */
  .hero .fore>i{animation:none;transform:scale(1.06)}
  .hero .fx::before,.hero .fx::after,.hero .poster{animation:none}
  .hero .say>*{animation:none}
  .hero .cue{animation:none;opacity:.5}
  .fsvg .walk{animation:none}
  /*  The strip becomes the scroller the touch rule already describes — the bestiary is still all
      there, the reader moves it instead of the clock. */
  .strip{overflow-x:auto;scroll-snap-type:x proximity;scrollbar-width:none}
  .btrack{animation:none}
  /*  ⚠⚠ THE REVERSED HALF NEEDS ITS OWN LINE, AND ONLY THE NEW GATE COULD SEE THAT. The rule
      that drives the second strip is .strip.rev .btrack, three classes, against one class here.
      The more specific selector therefore keeps setting animation-name, and what actually stops
      the strip today is the SHORTHAND above resetting animation-duration to 0s — from a rule that
      LOST the cascade on the property anyone would have checked. It works by longhand luck, and
      it stops working the moment somebody splits that shorthand.
      ⚠ V3-7 asserts that every clock-driven selector is NAMED in this block. This one was not,
      and the assertion found it on its first run, which is the whole argument for the assertion. */
  .strip.rev .btrack{animation-name:none}
  .half.dup{display:none}
  .bt{scroll-snap-align:center;transition:none}
  .bt:hover,.bt:focus-visible{transform:none}
  /*  ⚡ V2 — LES TROIS MOUVEMENTS NEUFS DE CETTE VAGUE, NOMMÉS ICI COMME LES TRENTE AUTRES.
      ⚠ reveal-letters N'EST MÊME PAS CONSTRUIT: DOCK_JS ne découpe aucun titre sous mouvement
      réduit, donc .rl i n'existe pas dans le document. La règle est écrite quand même — une
      garde qui compte sur l'absence d'un élément est une garde qui rougit le jour où quelqu'un
      découpe les titres pour une autre raison, et V3-7 lit la FEUILLE, pas le document.
      ⚠ LE DOCK ET LES TUILES NE PERDENT QUE LEUR TRANSITION, jamais leur état: l'ancre courante
      reste soulignée, le verre reste du verre, la tuile reste inclinable — ils arrivent
      instantanément. Mouvement réduit veut dire que le contenu ARRIVE, jamais qu'il manque. */
  .rise.in .rl i{animation:none}
  .dock,.dk-nav a,.dk-nav a::after,.ztile,.ztile img,.zm,.tallies a{transition:none}
}