
 :root{
  /* L'ambiance : encre profonde. La lignee violette de l'origine survit dans
     les surfaces et les bordures — c'est l'accent, et lui seul, qui change de
     famille. */
  --fond:#0a0a14; --voile:#171426; --voile2:#1f1a33; --bord:#2c2545;
  --texte:#f2eefc; --doux:#a79dc6;
  /* L'ACCENT EST L'OR, et ce n'est pas une teinte choisie au gout : dans un jeu
     dont le sujet est une devise et une collection de cartes, l'or est le
     vocabulaire. Le bouton d'enregistrement et l'interrupteur actif se lisent
     parce que la couleur dit ce qu'ils sont.
     ⚠ « devise » et pas l'autre mot : test/tcg-domaine.test.js interdit une
     courte liste de mots sur l'accueil du TCG, et un commentaire de CETTE
     feuille part dans CHAQUE page. Troisieme fois de cette famille — voir
     AGENTS.md §12, un includes sur une page qui embarque son CSS teste la
     feuille de style et pas le contenu. */
  --accent:#c9a227; --accent-clair:#f0d97d;
  /* LE TEXTE POSE SUR L'ACCENT. Il valait #fff en dur a trois endroits, ce qui
     n'etait pas une couleur mais une HYPOTHESE sur l'accent : du blanc sur cet
     or donne un contraste d'environ 2 pour 1, donc un bouton illisible. */
  --sur-accent:#1a1526;
  /* LES ETATS DE L'ACCENT. Ils vivaient en dur a SIX endroits — les survols de
     bouton, de bouton fantome et d'appel a l'action, le voile des pastilles VIP,
     celui de l'etat en attente du coffre, et le halo du corps — donc un
     changement d'ambiance les laissait derriere lui. C'est ce qui a failli
     livrer un survol violet sur un bouton or.
     ⚠ Les anciennes valeurs ne sont PAS citees ici : un test balaie les sources
     a leur recherche, et un commentaire qui les nomme le ferait rougir. */
  --accent-vif:#e0bd4a; --accent-pale:#f8ecc4;
  --accent-voile:rgba(201,162,39,.16); --accent-voile-bord:rgba(201,162,39,.42);
  /* LE HALO DU CORPS. Il reste VIOLET, deliberement : c'est la lumiere
     d'ambiance, pas un accent, et un halo dore sur toute la largeur virerait au
     voile jaune. Il tient la lignee que les surfaces gardent. */
  --halo:rgba(120,78,200,.20);
  --ok:#3ecf8e; --ko:#ff6f61;
  /* LA COQUILLE D'ATELIER. Trois jetons de plus, et ils n'existent que parce
     qu'elle existe — ajouter des variables inutilisees serait du decor.
     Le jeton --rail est un cran plus violet que le fond : le rail doit se distinguer
     sans devenir un bloc, sinon il se lit comme du contenu. */
  --rail:#100e1c; --bord-fort:#3b3159; --tres-doux:#6f6790;
 }
 *{box-sizing:border-box}
 body{font:15px/1.65 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  max-width:48rem;margin:0 auto;padding:2.5rem 1.2rem 4rem;color:var(--texte);
  background:var(--fond);
  background-image:radial-gradient(60rem 30rem at 50% -12rem,var(--halo),transparent 70%);
  background-repeat:no-repeat}
 a{color:var(--accent-clair)}
 h1{font-size:1.4rem;margin:0;letter-spacing:-.01em}
 h2{font-size:1rem;margin:2rem 0 .6rem;color:var(--accent-clair)}
 header{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:1.6rem}
 code{background:var(--voile2);border:1px solid var(--bord);padding:.1em .38em;border-radius:4px;font-size:.92em}
 fieldset{border:1px solid var(--bord);border-radius:12px;padding:1.1rem 1.3rem 1.3rem;margin:0 0 1.1rem;background:var(--voile)}
 legend{padding:0 .5rem;font-weight:600;font-size:.78rem;text-transform:uppercase;letter-spacing:.08em;color:var(--accent-clair)}
 label{display:block;margin:.8rem 0}
 .row{display:flex;gap:1.1rem;flex-wrap:wrap}
 .row label{flex:1;min-width:8rem}
 input[type=text],input[type=number],textarea{width:100%;padding:.5rem .65rem;border:1px solid var(--bord);
  border-radius:7px;font-size:1rem;background:var(--voile2);color:var(--texte)}
 input:focus,textarea:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:transparent}
 textarea{font-family:inherit;line-height:1.5;resize:vertical}
 input[type=checkbox]{accent-color:var(--accent);width:1.05rem;height:1.05rem;vertical-align:-.15rem;margin-right:.35rem}
 input.bad,textarea.bad{border-color:var(--ko);background:rgba(255,111,97,.09)}
 .hint{color:var(--doux);font-size:.86rem;margin:.25rem 0 0}
 /* La classe « aide » est le nom qu'emploient les pages objets, peche et vitrine pour la meme chose.
    Il ne designait AUCUNE regle : leurs paragraphes d'explication sortaient en
    texte plein, donc au meme poids visuel que le contenu, sur les trois seules
    pages ou l'explication est longue. */
 .aide{color:var(--doux);font-size:.86rem;margin:.25rem 0 .9rem}
 .err{color:var(--ko);font-size:.88rem;margin:.3rem 0 0;font-weight:500}
 .banner{padding:.8rem 1rem;border-radius:8px;margin:1.2rem 0;border:1px solid transparent}
 .ok{background:rgba(62,207,142,.12);border-color:rgba(62,207,142,.4)}
 .ko{background:rgba(255,111,97,.12);border-color:rgba(255,111,97,.4)}
 button{padding:.62rem 1.35rem;border:0;border-radius:8px;background:var(--accent);color:var(--sur-accent);
  font-size:1rem;font-weight:600;cursor:pointer}
 button:hover{background:var(--accent-vif)}
 button.ghost{background:transparent;color:var(--accent-clair);text-decoration:underline;padding:.2rem;font-weight:400;font-size:.92rem}
 button.ghost:hover{background:transparent;color:var(--accent-pale)}
 footer{margin-top:2.5rem;color:var(--doux);font-size:.88rem;border-top:1px solid var(--bord);padding-top:1.2rem}
 footer form{display:inline}
 .actions{display:flex;gap:1.4rem;flex-wrap:wrap;align-items:center;margin:.9rem 0}
 table{border-collapse:collapse;width:100%;margin:.4rem 0 0}
 th,td{text-align:left;padding:.5rem .55rem;border-bottom:1px solid var(--bord);vertical-align:top}
 tr:last-child td{border-bottom:0}
 th{font-size:.72rem;text-transform:uppercase;letter-spacing:.06em;color:var(--doux);font-weight:600}
 td.cmd{white-space:nowrap;width:1%}
 .qui{display:inline-block;font-size:.72rem;padding:.15em .6em;border-radius:99px;
  background:var(--voile2);border:1px solid var(--bord);color:var(--doux);white-space:nowrap}
 .qui.vip{background:var(--accent-voile);border-color:var(--accent-voile-bord);color:var(--accent-clair)}

 /* ===== LA COQUILLE D'ATELIER ==========================================
    Rail fixe a gauche, barre de titre collante, contenu large, sommaire ancre.

    ⚠ POURQUOI ELLE EXISTE. Mesure du 2026-09-06 sur une fenetre de 1440 : la
    page de reglages faisait 6 450 px de haut — 7,2 ecrans — pour 13 cadres, et
    le contenu tenait dans 768 px, soit 53 pour cent de l'ecran vide. Le repli
    avait deja ete tente : SIX des treize cadres etaient deja repliables, et la
    page faisait toujours 6 450 px. Replier plus repoussait le meme mur.

    ⚠⚠ ET LA PAGE NE RACCOURCIT PAS. C'est la decision D6, prise avec Gaetan :
    la lecture reste gratuite, les treize cadres restent deplies, et c'est le
    SOMMAIRE qui les met a un clic. Le defaut nomme etait qu'on ne trouve rien,
    pas qu'on defile. Ne pas « corriger » en repliant tout par defaut : ce
    serait payer un clic pour LIRE un reglage, et melanger « replie » avec
    « desactive », deux etats que le selecteur de repli confond deja.

    ⚠ Le marqueur est sur le CORPS. C'est body qui porte max-width:48rem, ses
    marges automatiques et son remplissage : un div interne ne pourrait pas les
    defaire. */
 body.atelier{max-width:none;margin:0;padding:0}
 .coquille{display:grid;grid-template-columns:15rem minmax(0,1fr);min-height:100vh}

 /* Le rail. position:sticky avec height:100vh : il reste sous les yeux quel
    que soit le defilement, et son propre debordement defile a l'interieur — une
    chaine qui gagnerait vingt onglets ne pousserait pas la page. */
 .coquille > aside{background:var(--rail);border-right:1px solid var(--bord);
  padding:1.1rem .7rem;position:sticky;top:0;height:100vh;overflow:auto}
 .marque{display:flex;align-items:center;gap:.55rem;padding:.2rem .55rem 1.2rem;
  text-decoration:none;color:var(--texte)}
 .marque .pastille{width:1.75rem;height:1.75rem;border-radius:8px;flex:0 0 auto;
  background:linear-gradient(140deg,var(--accent),var(--accent-clair));
  display:grid;place-items:center;font-size:.85rem;font-weight:700;color:var(--sur-accent)}
 .marque b{font-size:.9rem;letter-spacing:-.01em;font-weight:650}
 .marque span{display:block;font-size:.7rem;color:var(--tres-doux);font-weight:400}

 /* Le contenu. minmax(0,1fr) sur la colonne et non 1fr : sans le minimum a
    zero, un tableau large POUSSE la grille au lieu de defiler dans sa case, et
    le rail sort de l'ecran. */
 .coquille > main{min-width:0;padding:0 0 4rem}

 /* La barre de titre. Collante, et translucide pour que le contenu qui passe
    dessous se devine — sinon elle se lit comme un bandeau pose sur un trou. */
 .barre{display:flex;align-items:center;justify-content:space-between;gap:1rem;
  padding:1rem 2rem;border-bottom:1px solid var(--bord);position:sticky;top:0;z-index:5;
  background:color-mix(in srgb,var(--fond) 88%,transparent);backdrop-filter:blur(10px)}
 /* Repli pour un navigateur sans color-mix : une couleur pleine, donc opaque.
    Ecrit AVANT la regle ci-dessus dans l'ordre de lecture ? Non — @supports ne
    depend pas de l'ordre, il depend du support. */
 @supports not (background:color-mix(in srgb,red 50%,transparent)){
  .barre{background:var(--fond)}
 }
 .barre h1{font-size:1.08rem;font-weight:650}
 .barre .chaine{display:block;font-size:.78rem;color:var(--doux);margin-top:.1rem}
 .barre .droite{display:flex;align-items:center;gap:1rem}
 .barre form{margin:0}

 /* Le corps, en deux colonnes. Le sommaire a droite et non a gauche : a gauche
    il ferait un second rail, donc deux colonnes de navigation cote a cote. */
 /* ⚠⚠ LE CONTENU RESPIRE ET SE CENTRE, corrige le 2026-09-07 sur signalement de
    Gaetan : « le cadre central est colle au menu de gauche ». Mesure d'alors, en
    1440 : 32 px entre le rail et le premier bloc, 32 px entre le sommaire et le
    bord droit. Symetrique, donc, mais bien trop serre — un contenu qui touche sa
    navigation se lit comme une seule masse.

    DEUX CHOSES MANQUAIENT, pas une :
    - le remplissage etait de 2rem. Il passe a 4rem, donc 64 px de part et
      d'autre. Le contenu perd 64 px de large et les gagne en lisibilite : il
      reste a 808 px contre 768 avant tout ce lot.
    - il n'y avait AUCUN margin auto. Le plafond de largeur ne mordait pas en
      1440, donc rien ne se voyait ; au-dela il aurait recolle le bloc au rail au
      lieu de le centrer dans la place restante. C'est le defaut qui ne se montre
      que sur l'ecran de quelqu'un d'autre.

    Le plafond descend a 80rem pour que le centrage prenne le relais plus tot,
    et la colonne de contenu se stabilise autour de 890 px — une longueur de
    ligne tenable pour les paragraphes d'aide, qui sont longs sur ces pages. */
 .corps{display:grid;grid-template-columns:minmax(0,1fr) 14rem;gap:2.5rem;
  max-width:80rem;margin:0 auto;padding:1.8rem 4rem 0;align-items:start}
 /* Sans sommaire, le contenu prend toute la largeur : une colonne vide de 14rem
    serait du vide meuble. C'est la meme regle que la barre d'onglets d'un
    viewer qui n'a qu'une entree. */
 .corps.seul{grid-template-columns:minmax(0,1fr)}

 /* ⚠⚠ SANS CETTE REGLE, LE SOMMAIRE MENE SOUS LA BARRE. Une barre collante et
    un saut d'ancre se combinent mal : le navigateur amene la cible en haut de
    la fenetre, c'est-a-dire DERRIERE la barre, qui mesure 84 px. Les treize
    sections arrivaient donc titre masque — le sommaire fonctionnait, et donnait
    l'impression de rater sa cible.
    Mesure du 2026-09-07 : 84 px de barre, donc 6 rem laissent une marge. La
    regle porte sur TOUT porteur d'id d'une page d'atelier, pas seulement sur
    les cadres : scroll-margin ne sert qu'a une cible de defilement, donc large
    ne coute rien et couvrira les sections de l'etape 6 sans y penser. */
 body.atelier [id]{scroll-margin-top:6rem}

 /* ===== LE BLOC, QUI REMPLACE LE CADRE BORDE ============================
    C'est LA piece que Gaetan avait nommee : « ca fait trop statique, genre site
    des annees 2000 ». Le coupable n'etait pas la couleur, c'etait le composant —
    un fieldset avec sa legende encochee DANS la bordure, c'est-a-dire le
    vocabulaire visuel de 1997.

    ⚠ LE FIELDSET NE DISPARAIT PAS DU DEPOT, et c'est important : blocNatures
    groupe de vrais boutons radio, ou l'element est semantiquement JUSTE et ou
    l'accessibilite perdrait a le remplacer. Les regles fieldset{} et legend{} du
    socle restent donc vivantes, et ne servent plus qu'a lui.

    ⚠ LA CLASSE EST bloc TOUT COURT, pas section.bloc : les pages objets et
    peche employaient deja un div de classe bloc pour leurs sections, avec une
    simple marge dans LEUR feuille.
    Le depot portait donc DEUX langages de section en parallele. Une classe
    partagee les unifie ; deux selecteurs distincts auraient fige la divergence. */
 .bloc{background:var(--voile);border:1px solid var(--bord);border-radius:12px;
  padding:1.15rem 1.35rem 1.35rem;margin:0 0 1.1rem}
 /* Le titre VIT DANS le bloc, il ne l'encoche pas. Casse normale et couleur de
    texte : la legende etait en petites capitales dorees, ce qui la faisait lire
    comme une etiquette plutot que comme un titre. */
 .bloc > h2{margin:0 0 .9rem;font-size:1rem;font-weight:650;color:var(--texte);
  letter-spacing:-.005em}
 /* Un bloc qui n'a qu'un titre et une phrase n'a pas a payer la marge basse
    complete du dernier enfant. */
 .bloc > :last-child{margin-bottom:0}

 /* LE REPLI, TRANSPOSE. ⚠⚠ LES DEUX CLAUSES, et la seconde est une PROTECTION,
    pas un raffinement : les champs d'un bloc partent au validateur MEME quand sa
    case est decochee, donc un message d'erreur cache dans un pli donnerait un
    formulaire refuse sans une seule explication a l'ecran. */
 .bloc.repliable:has(> label.bascule > input:not(:checked)):not(:has(.err)) > .repli{display:none}
 .bloc.repliable:has(> label.bascule > input:not(:checked)):not(:has(.err)){padding-bottom:1.15rem}

 /* ⚠⚠ LE DEBORDEMENT HORIZONTAL DE 19 PX S'EST REGLE TOUT SEUL, et il faut
    savoir pourquoi avant d'y toucher un jour.

    Mesure du 2026-09-07, ecran de 500 px : la page de reglages defilait de cote
    de 19 px, AVANT cette refonte comme apres la coquille. Le coupable etait le
    cadre des commandes du chat : un fieldset porte un min-inline-size de
    min-content par defaut, donc son tableau l'empechait de retrecir et poussait
    la page entiere.

    Une section n'herite PAS de ce quirk. Convertir les cadres a donc supprime le
    defaut sans une ligne de CSS pour lui — re-mesure : 500 px de contenu dans
    500 px de fenetre, aucun coupable, et le tableau tient a 457 px dans 462.

    ⚠ La regle min-width a zero reste, et elle n est pas decorative : sans elle,
    un tableau plus
    large que sa case de grille la pousserait au lieu d'y tenir. Aucune page n'en
    est la aujourd'hui ; c'est la borne qui evite d'y revenir. */
 .bloc{min-width:0}
 .sommaire{position:sticky;top:5.2rem;font-size:.82rem}
 .sommaire p{margin:0 0 .55rem;font-size:.66rem;text-transform:uppercase;
  letter-spacing:.1em;color:var(--tres-doux);font-weight:700}
 .sommaire a{display:block;padding:.28rem .6rem;color:var(--doux);text-decoration:none;
  border-left:2px solid var(--bord);line-height:1.35}
 .sommaire a:hover{color:var(--texte)}
 .sommaire a.actif{color:var(--accent-clair);border-left-color:var(--accent);font-weight:600}

 /* ⚠ SOUS 60 REM, TOUT REDEVIENT UNE COLONNE — question ouverte 2 du document
    de conception, tranchee sur mesure reelle a 390 px.
    Le rail redevient une barre horizontale (la forme historique, deja testee),
    et le sommaire redevient une liste de liens en ligne au-dessus du contenu.
    RIEN N'EST CACHE : sur un ecran etroit, sauter a une section est ce qui sert
    LE PLUS, et le replier derriere un bloc repliable natif aurait rejoue la
    panne du 2026-09-03 — un bloc replie cache aussi ce qu'il porte. */
 @media (max-width:60rem){
  .coquille{grid-template-columns:minmax(0,1fr)}
  .coquille > aside{position:static;height:auto;border-right:0;
   border-bottom:1px solid var(--bord);padding:.9rem 1.2rem}
  .marque{padding:0 0 .8rem}
  .barre{padding:.9rem 1.2rem}
  .corps,.corps.seul{grid-template-columns:minmax(0,1fr);gap:1.2rem;padding:1.2rem 1.2rem 0}
  .sommaire{position:static;order:-1}
  .sommaire nav{display:flex;flex-wrap:wrap;gap:.15rem .5rem}
  .sommaire a{border-left:0;padding:.2rem .5rem;border:1px solid var(--bord);border-radius:99px}
  .sommaire a.actif{border-color:var(--accent)}
 }

 /* Un cadre qui se replie sur sa case a cocher. Decochee, elle est la SEULE
    chose qui reste : des baremes qui ne servent a rien n'ont pas a occuper un
    tiers de la page, et le cadre replie dit d'un coup d'oeil ce qui est coupe.

    En CSS et rien d'autre. Ces pages n'embarquent pas une ligne de JavaScript,
    et ce n'est pas l'endroit pour en introduire : un navigateur qui ne connait
    pas la pseudo-classe :has() ignore la regle entiere et affiche tout, ce qui
    est exactement le comportement d'avant. Un repli en JavaScript, lui,
    laisserait le cadre grand ouvert au premier script bloque — ou pire,
    replierait un cadre que le streamer ne pourrait plus rouvrir.

    (Pas d'accent grave dans ce commentaire : il vit dans un litteral de
    gabarit, et un seul le refermerait.) */
 label.bascule{margin-top:0;font-weight:600}

 /* ===== L'INTERRUPTEUR A PISTE ==========================================
    ⚠⚠ IL EST DESSINE SUR L'INPUT LUI-MEME, et ce n'est pas un detail de gout.

    Le selecteur de repli descend du bloc vers son label PUIS vers son input,
    avec DEUX combinateurs enfant. Le reflexe habituel pour dessiner un
    interrupteur — masquer la case et poser un span a cote — intercalerait un
    element entre le label et l'input : le repli casserait SANS casser un seul
    test de comportement. La case cocherait toujours, le formulaire posterait
    toujours, et le cadre resterait simplement grand ouvert, decoche.

    On habille donc la case elle-meme : appearance:none pour la piste, ::after
    pour le bouton. Et ca reste une VRAIE case a cocher — le repli vit en CSS, un
    interrupteur en JavaScript laisserait le cadre grand ouvert au premier script
    bloque, ou pire, replierait un cadre qu'on ne pourrait plus rouvrir.

    ⚠ DEGRADATION : si ::after ne rendait pas, il resterait une pastille qui
    change de couleur en se cochant. Lisible, donc acceptable — c'est pourquoi la
    piste est un fond sur l'input et non un second pseudo-element. */
 label.bascule > input[type=checkbox]{appearance:none;-webkit-appearance:none;
  width:2.1rem;height:1.2rem;border-radius:99px;background:var(--bord-fort);
  position:relative;cursor:pointer;flex:0 0 auto;margin-right:.55rem;
  vertical-align:-.25rem;transition:background .15s}
 label.bascule > input[type=checkbox]:checked{background:var(--accent)}
 label.bascule > input[type=checkbox]::after{content:"";position:absolute;
  top:.15rem;left:.15rem;width:.9rem;height:.9rem;border-radius:99px;
  background:var(--texte);transition:left .15s}
 label.bascule > input[type=checkbox]:checked::after{left:calc(100% - 1.05rem);
  background:var(--sur-accent)}
 /* ⚠ appearance:none EMPORTE LE CONTOUR NATIF avec l'apparence. Sans cette
    regle, l'interrupteur devient invisible au clavier — et c'est le seul geste
    de ces pages qui allume ou eteint un jeu entier. */
 label.bascule > input[type=checkbox]:focus-visible{outline:2px solid var(--accent);
  outline-offset:2px}

 /* LE REPLI CEDE DEVANT UNE ERREUR, et cette moitie-la n'est pas un detail.
    Les champs d'un cadre partent au validateur MEME quand sa case est
    decochee : decocher « activer » et enregistrer un champ fautif rend un 400
    dont le message serait cache dans le repli. Le streamer verrait un
    formulaire refuse sans une seule explication a l'ecran.

    C'est le meme selecteur, pas une seconde regle qui viendrait par-dessus :
    deux regles de meme specificite se departageraient a l'ordre d'ecriture, ce
    qui est exactement le genre de dependance qu'on ne voit pas venir. */
 ⚠ ET CES DEUX REGLES ONT DEMENAGE le 2026-09-07, elles n'ont pas disparu : le
    repli vit desormais sur .bloc.repliable, ecrit avec la coquille. Elles ont
    survecu quelques heures ici, sans aucun balisage pour les employer, parce que
    la liste INDISPENSABLES de test/ui.test.js les epinglait — un test gardait
    vivante une regle morte. */

 fieldset.natures{padding:.7rem 1.1rem 1rem}
 fieldset.natures label.choix{margin:.35rem 0;font-weight:400}
 fieldset.natures input[type=radio]{accent-color:var(--accent);margin-right:.4rem;vertical-align:-.15rem}

 /* UNE CONFIRMATION AVANT UN GESTE QUI NE SE DEFAIT PAS.

    C'est un <dialog> NATIF, et le script de enveloppe l'ouvre avec
    showModal() : Echap referme, le focus est piege dans la boite, et l'URL ne
    se remplit pas de dieses. Rien de tout cela ne s'ecrit en CSS.

    MAIS LE SOCLE RESTE LE LIEN ET :target, ET CE N'EST PAS DE LA COQUETTERIE.
    Un <dialog> sans attribut open est display:none par la feuille du
    navigateur : si le script ne tourne pas — bloque, en erreur, pas encore
    charge au moment du clic — la boite serait INTROUVABLE, donc la suppression
    IMPOSSIBLE. Le lien vers son id la rend visible sans une ligne de
    JavaScript, et le script ne fait que promouvoir ce qui marche deja.

    D'ou les deux selecteurs d'ouverture : [open] quand showModal() a pose
    l'attribut, :target quand c'est le lien qui a joue.

    Les styles du navigateur pour <dialog> sont neutralises a la main (bordure,
    fond blanc, largeur limitee, marge auto) : la boite occupe l'ecran et centre
    son contenu, dans les deux modes.

    (Pas d'accent grave dans ce commentaire : il vit DANS un litteral de gabarit,
    ou un seul le refermerait.) */
 .modale{display:none;position:fixed;inset:0;z-index:50;align-items:center;justify-content:center;
  padding:1.2rem;margin:0;max-width:none;max-height:none;width:auto;height:auto;
  border:0;background:transparent;color:var(--texte);overflow:visible}
 .modale:target,.modale[open]{display:flex}
 /* Le fond sombre est le voile, jamais ::backdrop : superposes, les deux
    assombriraient deux fois, et seul le voile existe dans les deux modes. */
 .modale::backdrop{background:transparent}
 .modale .voile{position:absolute;inset:0;background:rgba(6,4,12,.72)}
 .modale .boite{position:relative;width:100%;max-width:26rem;background:var(--voile);
  border:1px solid var(--bord);border-radius:12px;padding:1.3rem 1.4rem;
  box-shadow:0 1.4rem 3rem rgba(0,0,0,.55)}
 .modale h3{margin:0 0 .55rem;font-size:1.02rem;color:var(--texte)}
 .modale p{margin:0 0 1.3rem;color:var(--doux);font-size:.92rem;line-height:1.6}
 .modale .gestes{display:flex;gap:1.2rem;align-items:center;justify-content:flex-end}
 .modale .gestes form{margin:0}
 .modale a.annuler{color:var(--doux);font-size:.92rem}
 /* Le bouton qui efface porte la couleur du refus, la seule de la palette qui
    dise « attention » — et il est le SECOND des deux gestes, jamais le premier. */
 button.danger{background:var(--ko)}
 button.danger:hover{background:#ff8a7e}

 /* Page d'accueil et pages d'un seul message. */
 .hero{text-align:center;padding:2.5rem 0 2rem}
 .hero .marque{font-size:.75rem;text-transform:uppercase;letter-spacing:.18em;color:var(--doux)}
 .hero h1{font-size:2.4rem;line-height:1.12;margin:.7rem 0 0;
  background:linear-gradient(100deg,#fff 20%,var(--accent-clair));
  -webkit-background-clip:text;background-clip:text;color:transparent}
 .hero p{font-size:1.06rem;color:var(--doux);max-width:32rem;margin:.9rem auto 0}
 .cta{display:inline-block;margin-top:1.7rem;padding:.8rem 1.7rem;border-radius:10px;
  background:var(--accent);color:var(--sur-accent);font-weight:600;text-decoration:none;font-size:1.02rem}
 .cta:hover{background:var(--accent-vif);color:var(--sur-accent)}
 .sous-cta{margin-top:.8rem;font-size:.86rem;color:var(--doux)}
 .cartes{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));gap:.9rem;margin:1rem 0}
 .carte{background:var(--voile);border:1px solid var(--bord);border-radius:12px;padding:1.1rem}
 .carte h3{margin:0 0 .35rem;font-size:.95rem;color:var(--accent-clair)}
 .carte p{margin:0;font-size:.9rem;color:var(--doux)}
 ol.etapes{margin:0;padding-left:1.3rem}
 ol.etapes li{margin:.45rem 0}
 /* LA SOUS-NAVIGATION DES DEUX ECRANS DU TCG.
    Meme motif que le selecteur de saison de la galerie : une rangee de liens,
    l'actif en <strong>. Rien de neuf a apprendre, et un seul jeu de regles.

    ⚠ UN REPLI details.reglages A VECU ICI UNE HEURE, le 2026-09-03, et le
    decoupage en deux pages l'a rendu inutile — avec les trois pannes qu'il
    avait introduites (l'attribut open pose cote serveur, les ancres tombant
    dans le repli, le dialog que :target n'atteignait plus). Deux pages qui font
    chacune un metier n'ont rien a cacher.

    (Pas d'accent grave dans ce commentaire : il vit dans un litteral de
    gabarit, et un seul le refermerait.) */
 p.sous-nav{margin:.2rem 0 1.4rem;font-size:.92rem}
 p.sous-nav strong{color:var(--texte)}

 /* La galerie de cartes. album-carte et PAS carte : .carte est deja la grille
    de presentation de la page d'accueil, juste au-dessus. Les confondre aurait
    lie deux ecrans qui n'ont rien a voir. */
 ul.album-grille{display:grid;grid-template-columns:repeat(auto-fill,minmax(9rem,1fr));
  gap:.7rem;margin:1rem 0;padding:0;list-style:none}
 .album-carte{background:var(--voile);border:1px solid var(--bord);border-radius:10px;
  padding:.6rem;text-align:center}
 /* CARREE ET « contain », et les deux moities comptent.
    Une photo de profil Twitch est carree, mais une illustration de carte ne
    l'est pas : celle que Gaetan veut poser fait 1054x1492. En hauteur libre,
    une grille melangeant les deux devient un escalier. En « cover », l'image
    serait recadree — inacceptable pour une carte, dont le cadre fait partie du
    dessin. « contain » montre le dessin ENTIER dans une case de taille fixe.

    ⚠ height:auto N'EST PAS DECORATIF, et son absence a coute une vignette
    ecrasee jusqu'au 2026-09-04. Le gabarit ecrit width="120" height="120" sur
    la balise ; ces attributs valent une declaration de presentation, que rien
    ici ne contredisait. Largeur ET hauteur etant alors toutes deux definies,
    aspect-ratio est ignore : la case faisait 153x120 au lieu de 153x153, la
    carte etait plus petite qu'elle ne devait, et sa legende remontait de 33 px
    au-dessus de celles des cases vides voisines. */
 .album-carte img{width:100%;height:auto;aspect-ratio:1;object-fit:contain;border-radius:8px;
  display:block;background:rgba(255,255,255,.04)}
 .album-carte p{margin:.4rem 0 0;font-size:.82rem;line-height:1.35}
 .album-carte small{display:block;color:var(--doux);font-size:.75rem}
 .album-carte button{margin-top:.45rem;padding:.3rem .7rem;font-size:.78rem}
 /* La silhouette occupe la MEME hauteur qu'une image, sinon la grille sautille
    a chaque carte sans visage et la page devient illisible. */
 .album-carte .silhouette{display:flex;align-items:center;justify-content:center;
  aspect-ratio:1;border-radius:8px;background:rgba(255,255,255,.04);
  border:1px dashed var(--bord);color:var(--doux);font-size:.78rem;margin:0}

 /* LA CASE « + », derniere cellule de la grille. Pointilles pour dire qu'elle
    n'est pas une carte, et centree parce qu'elle n'a qu'un geste. */
 .album-carte.ajout{border-style:dashed;display:flex;align-items:center;justify-content:center}
 .album-carte.ajout a.visuel{text-align:center;width:100%}
 .album-carte.ajout .plus{font-size:1.8rem;line-height:1.1;color:var(--accent-clair)}
 .album-carte.ajout p{color:var(--doux);font-size:.78rem}
 .album-carte.ajout:hover{border-color:var(--accent)}
 .album-carte.retiree{opacity:.62}
 /* LE COMPTE D'EXEMPLAIRES — 2026-09-04. Un badge dans le coin, convention de
    tous les jeux de cartes, et il n'apparait QUE au-dela du premier : « x1 »
    sur chaque case serait du bruit sur toute la grille, et le badge cesserait
    de vouloir dire « tu en as en trop ».
    La cellule devient le repere de position du badge ; elle ne l'etait pas, et
    sans ca il se placerait par rapport a la page. */
 .album-carte{position:relative}
 .album-carte .doubles{position:absolute;top:.35rem;right:.35rem;z-index:1;
  background:var(--accent);color:var(--sur-accent);border-radius:999px;padding:.05rem .42rem;
  font-size:.72rem;font-weight:600;line-height:1.5;
  /* Le badge se pose SUR l'image : sans ce liset, un chiffre clair sur une
     illustration claire devient illisible, et l'information disparait
     exactement sur les cartes qu'on regarde le plus. */
  box-shadow:0 0 0 2px var(--fond)}
 /* LA PILE, l'autre moitie du signal — et elle est la parce qu'un badge se lit,
    tandis qu'une pile se VOIT en balayant la grille. Deux ombres decalees
    suffisent : aucun element de plus, donc rien a maintenir dans le gabarit. */
 .album-carte.empilee{box-shadow:3px 3px 0 -1px var(--voile),3px 3px 0 0 var(--bord),
  6px 6px 0 -1px var(--voile),6px 6px 0 0 var(--bord)}

 /* LE PANNEAU DES DOUBLONS. UNE ligne par carte, un champ de quantite, et deux
    boutons de soumission pour tout le panneau — jamais un bouton par ligne.
    Le dépot a deja tranche cette question sur la galerie du streamer :
    « vingt-quatre boites de confirmation sur une page seraient absurdes ». */
 table.doublons{width:100%;border-collapse:collapse;margin:.6rem 0 1rem}
 table.doublons th,table.doublons td{padding:.42rem .5rem;text-align:left;
  border-bottom:1px solid var(--bord);font-size:.88rem}
 table.doublons th{color:var(--doux);font-weight:600;font-size:.78rem}
 table.doublons td.q{text-align:right;white-space:nowrap}
 /* Un champ de quantite n'a pas besoin de la largeur d'un champ de texte, et
    une colonne large ferait croire qu'on y saisit autre chose qu'un nombre. */
 table.doublons input[type=number]{width:4.2rem;text-align:right}
 .paillettes{display:flex;align-items:center;gap:.8rem;flex-wrap:wrap;
  background:var(--voile);border:1px solid var(--bord);border-radius:10px;
  padding:.7rem .9rem;margin:1rem 0}
 .paillettes strong{font-size:1.05rem}
 .paillettes form{display:flex;gap:.5rem;flex-wrap:wrap}
 /* CE QUI MANQUE se lit, il ne se devine pas : le service a paye la lecon sur
    « ton album est vide », ou l'etat etait annonce sans le geste suivant. */
 .paillettes .manque{color:var(--doux);font-size:.85rem}
 /* L'IMAGE PAR DEFAUT DE LA CHAINE, mise a la place du mot « silhouette ».
    Elle est ATTENUEE, et c'est le point : sur la galerie du streamer, la
    mention « en silhouette » reste sous la vignette, et il faut que l'oeil
    distingue d'un coup ce qui porte un vrai visage de ce qui porte l'image
    d'attente. Sans ca, la galerie cesse d'etre l'ecran de diagnostic qui
    justifie son existence, et le bouton « retenter les photos » perd son
    unique lecteur. */
 .album-carte img.par-defaut{opacity:.72;filter:saturate(.55)}
 /* La vignette est un LIEN vers la page de la carte, ou le streamer pose son
    image. Le lien enveloppe l'image ET la silhouette : une carte sans visage
    est justement celle sur laquelle on veut cliquer. */
 .album-carte a.visuel{display:block;text-decoration:none}
 .album-carte a.visuel:hover img,.album-carte a.visuel:hover .silhouette{
  outline:2px solid var(--accent);outline-offset:2px}
 /* La page d'UNE carte. Le visuel y est grand : c'est tout l'objet de l'ecran,
    et une vignette de 120 px ne dit pas si l'image posee est la bonne. */
 .carte-seule{max-width:18rem;margin:0 0 1.2rem}
 .carte-seule img{width:100%;height:auto;border-radius:12px;display:block;
  background:rgba(255,255,255,.04)}
 input[type=file]{display:block;width:100%;margin:.5rem 0;color:var(--doux);font-size:.9rem}
 input[type=file]::file-selector-button{margin-right:.7rem;padding:.4rem .9rem;border:0;
  border-radius:7px;background:var(--voile2);color:var(--texte);border:1px solid var(--bord);
  font:inherit;font-size:.88rem;cursor:pointer}
 input[type=file]::file-selector-button:hover{background:var(--bord)}
 /* Un emplacement non possede (D24) : le « ? », et RIEN d'autre — plus meme la
    rarete depuis le 2026-09-04 (voir album-page.js). Le « ? » est plus gros que
    le reste parce que c'est le seul contenu de la case, et une grille de cases
    vides doit rester lisible comme une grille.

    LA CASE REMPLIT SA RANGEE, et c'est la consequence d'avoir retire le texte :
    une rangee melangeant une carte possedee — qui porte son nom sous elle — et
    des cases vides laisserait sous celles-ci le vide de la legende absente.
    flex-basis:auto et non 0 : sans hauteur intrinseque, une rangee entierement
    vide s'ecraserait a zero, et c'est l'aspect-ratio de la silhouette qui la
    donne. */
 .album-carte.manquante{display:flex;flex-direction:column}
 .album-carte.manquante .silhouette{flex:1 0 auto;font-size:1.5rem;font-weight:600}

 /* LA LOUPE : une carte possedee vue en taille lisible.
    Meme socle que la confirmation — un lien vers l'id d'un <dialog>, ouvert par
    :target sans JavaScript, promu en modale native par SCRIPT_MODALE quand il
    tourne. Une vignette de 153 px ne se lit pas ; le texte d'une carte, si.

    ⚠ LES DEUX REGLES CI-DESSOUS SONT PLUS SPECIFIQUES QUE .album-carte img,
    et elles doivent l'etre : le dialog vit DANS le <li> de la grille, donc
    l'image de la loupe est aussi une .album-carte img. Sans ca elle heriterait
    du carre en « contain » de la vignette, c'est-a-dire de ce qu'on cherche
    justement a defaire. */
 .modale.loupe .boite{width:auto;max-width:none;padding:.5rem;line-height:0}
 .album-carte .modale.loupe img{width:auto;height:auto;aspect-ratio:auto;
  max-width:min(24rem,86vw);max-height:82vh;border-radius:8px;background:transparent}
 .modale.loupe p{margin:.55rem .2rem .15rem;font-size:.85rem;line-height:1.35;
  color:var(--texte);text-align:center}
 /* LA CROIX EST LE GESTE VISIBLE. Le voile referme deja en cliquant a cote, et
    showModal() apporte Echap — mais ni l'un ni l'autre ne se VOIT, et sur un
    ecran tactile ouvert par :target il ne reste que le voile a deviner. */
 .modale.loupe a.fermer{position:absolute;top:.45rem;right:.45rem;z-index:1;
  width:1.9rem;height:1.9rem;display:flex;align-items:center;justify-content:center;
  border-radius:99px;background:rgba(6,4,12,.72);border:1px solid var(--bord);
  color:var(--texte);font-size:.95rem;line-height:1;text-decoration:none}
 .modale.loupe a.fermer:hover{background:var(--voile2)}
 ul.liste-chaines{margin:1rem 0;padding-left:1.2rem}
 ul.liste-chaines li{margin:.45rem 0}
 .pagination{margin:.6rem 0 0;font-size:.86rem;color:var(--doux)}
 .verrou-saison{margin:.5rem 0 .9rem;font-size:.86rem;color:var(--doux)}
 .verrou-saison p{margin:0;display:flex;gap:.7rem;align-items:baseline;flex-wrap:wrap}
 .verrou-saison button{flex:none}

 /* flex-wrap : la barre est passee a sept entrees, et sur un ecran etroit la
    derniere sortait du cadre sans que rien ne le montre. Pas d'accent grave ici,
    ce commentaire vit DANS un litteral de gabarit. */
 nav.onglets{display:flex;flex-wrap:wrap;gap:1.2rem;margin:-.6rem 0 1.4rem;border-bottom:1px solid var(--bord)}
 nav.onglets a{padding:.5rem 0;text-decoration:none;color:var(--doux);font-size:.92rem;border-bottom:2px solid transparent}
 nav.onglets a.actif{color:var(--accent-clair);border-bottom-color:var(--accent)}

 /* ===== LE RAIL, c'est-a-dire la MEME navigation debout ==================
    Il vit ici et non dans COMPOSANTS parce qu'il EST la barre d'onglets sous
    une autre forme : les separer garantirait qu'un onglet ajoute un jour
    n'apparaisse que d'un cote.

    ⚠ Le selecteur est nav.onglets.rail, en DEUX classes. La chaine nav.onglets{ reste
    donc introuvable dans COMPOSANTS, ce que test/onglets.test.js verifie sous
    le titre « deux feuilles distinctes ».

    Pourquoi debout : neuf entrees au 2026-09-06, dont SIX ajoutees entre le
    25/08 et le 02/09. Une barre horizontale sature ; une colonne en accepte
    vingt sans broncher. */
 nav.onglets.rail{display:flex;flex-direction:column;flex-wrap:nowrap;gap:1px;
  margin:0;border-bottom:0}
 nav.onglets.rail a{display:flex;align-items:center;gap:.6rem;padding:.5rem .55rem;
  border-radius:8px;border-bottom:0;font-size:.885rem;line-height:1.3}
 nav.onglets.rail a:hover{background:var(--voile2);color:var(--texte)}
 nav.onglets.rail a.actif{background:var(--accent-voile);color:var(--accent-clair);
  font-weight:600;box-shadow:inset 2px 0 0 var(--accent)}
 /* La pastille du coffre. Elle etait un « (3) » dans le libelle ; debout, elle
    a la place d'etre un vrai compteur pousse a droite. */
 nav.onglets.rail a .compte{margin-left:auto;font-size:.7rem;padding:.05rem .4rem;
  border-radius:99px;background:var(--accent);color:var(--sur-accent);font-weight:700}
 /* Le titre de groupe qui separe le streamer du viewer. Un viewer ne le voit
    jamais : il n'a qu'une entree, donc aucun groupe a nommer. */
 .rail-titre{font-size:.66rem;text-transform:uppercase;letter-spacing:.11em;
  color:var(--tres-doux);padding:1.2rem .6rem .4rem;font-weight:700;margin:0}

 /* Sous 60 rem, le rail se recouche : c'est exactement la barre d'avant. */
 @media (max-width:60rem){
  nav.onglets.rail{flex-direction:row;flex-wrap:wrap;gap:1rem}
  nav.onglets.rail a{padding:.35rem .1rem;border-radius:0;border-bottom:2px solid transparent}
  nav.onglets.rail a:hover{background:transparent}
  nav.onglets.rail a.actif{background:transparent;box-shadow:none;border-bottom-color:var(--accent)}
  nav.onglets.rail a .compte{margin-left:.35rem}
  .rail-titre{display:none}
 }
