/*
 * Copyright (c) 2025-2026.
 * kwargs SRL – Ingénierie logicielle, Bruxelles
 * Tous droits réservés.
 */

/* ── FUTILITY : LA FEUILLE ────────────────────────────────────────── */
/*                                                                      */
/*  Écrite ici, sans cadre. Le produit ne charge pas Bootstrap et        */
/*  n'étend pas la base du socle (décision du gérant du 13/09/2026,      */
/*  `SPEC.md` §5). Le motif est mesuré : la feuille d'avant passait plus */
/*  de lignes à DÉFAIRE un cadre de 2019 qu'à poser quoi que ce soit,    */
/*  ce qui est le signe qu'on se bat contre son outil.                   */
/*                                                                      */
/*  Ce que le produit garde du socle est tout le reste : les canaris,    */
/*  le calcul de contraste, l'audit i18n, la CSP à nonce, les vues et    */
/*  les formulaires de `poesie-identity`. On garde les paquets, on ne    */
/*  prend pas leur habillage.                                            */
/*                                                                      */
/*  L'ÉCRAN EST UN TERMINAL. Un seul corps de texte, une seule chasse,   */
/*  un fond noir, pas de relief, pas d'angle rond, pas d'icône. Ce qui   */
/*  se clique s'écrit comme une commande, derrière son invite.           */

/* ── 1. La police ─────────────────────────────────────────────────── */
/*                                                                      */
/*  Glass TTY VT220, vendorée : la fonte d'un terminal DEC de 1983,      */
/*  redessinée. Zéro CDN, comme partout dans la maison. La pile de       */
/*  repli est réelle et non décorative : si la fonte ne charge pas, il   */
/*  faut une chasse fixe, pas la police de traitement de texte du        */
/*  système.                                                             */

@font-face {
    font-family: "Glass TTY VT220";
    src: url("../fonts/glasstty/Glass_TTY_VT220.ttf") format("truetype");
    font-display: swap;
    font-weight: normal;
    font-style: normal;
}

/* ── 2. La remise à zéro, et elle tient en peu ────────────────────── */
/*                                                                      */
/*  Pas de « reset » emprunté : le produit n'emploie qu'une poignée      */
/*  d'éléments, et une feuille de remise à zéro générique poserait des   */
/*  règles pour des balises qu'il n'écrit nulle part.                    */

*, *::before, *::after {
    box-sizing: border-box;
}

html {
    background: var(--ks-fond);
}

body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--ks-fond);
    color: var(--ks-texte);
    font-family: var(--ks-font-main);
    font-size: var(--ks-font-size-base);
    line-height: 1.4;
}

img, svg {
    max-width: 100%;
}

h1, h2, h3 {
    margin: 0 0 var(--ks-espace-3);
    font-weight: 400;
    line-height: 1.1;
    color: var(--ks-intense);
}

h1 { font-size: var(--ks-titre-2); }
h2 { font-size: var(--ks-titre-3); }
h3 { font-size: var(--ks-font-size-lg); }

p, ul, ol {
    margin: 0 0 var(--ks-espace-3);
}

/* ── 3. La page ───────────────────────────────────────────────────── */

.page {
    width: 100%;
    max-width: var(--ks-largeur-page);
    margin-inline: auto;
    padding-inline: var(--ks-espace-3);
}

.texte {
    max-width: var(--ks-largeur-texte);
}

main {
    flex: 1;
    padding-block: var(--ks-espace-5);
}

.centre {
    text-align: center;
}

.centre .texte {
    margin-inline: auto;
}

/*  Le lien d'évitement : invisible tant qu'il n'a pas le focus, et bien */
/*  visible dès qu'il l'a. WCAG 2.4.1, et le canari de structure du      */
/*  socle exige sa présence et sa cible sur chaque écran rendu.          */

.evitement {
    position: absolute;
    inset-inline-start: -100vw;
    top: var(--ks-espace-1);
    z-index: var(--ks-plan-evitement);
    padding: var(--ks-espace-1) var(--ks-espace-2);
    background: var(--ks-texte);
    color: var(--ks-fond);
    text-decoration: none;
}

.evitement:focus {
    inset-inline-start: var(--ks-espace-1);
}

/* ── 4. Le lien et le bouton : une commande qu'on tape ────────────── */
/*                                                                      */
/*  Sur un VT, un choix ne se souligne pas : il s'écrit comme une        */
/*  commande qu'on taperait, derrière l'invite, et le curseur le         */
/*  surligne en inversant la vidéo. C'est ce que rendent le survol et le */
/*  focus : le fond prend la couleur du texte, le texte celle du fond.   */
/*                                                                      */
/*  Ce produit n'a pas de boutons, il n'a que des commandes : `<a>` et   */
/*  `<button>` partagent donc une seule règle. `appearance: none` est    */
/*  nécessaire et non décoratif, WebKit redessinant sinon le bouton      */
/*  par-dessus la feuille (même raison que `.navbar-icon` au socle).      */
/*                                                                      */
/*  Les deux couleurs sont nommées, jamais `currentColor` : celui-ci se  */
/*  résout avec la valeur FINALE de `color`, si bien qu'un fond posé à   */
/*  `currentColor` dans une règle qui change aussi `color` donnait du    */
/*  noir sur noir, donc un lien invisible au survol.                     */
/*                                                                      */
/*  L'invite est décorative : posée en `::before`, elle n'entre pas dans */
/*  le nom accessible du contrôle, qu'un lecteur d'écran annoncerait     */
/*  sinon « supérieur se connecter ».                                    */

.commande {
    -webkit-appearance: none;
    appearance: none;
    display: inline-block;
    margin: 0;
    padding: 0 .2em;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-align: inherit;
    text-decoration: none;
    cursor: pointer;
}

.commande::before {
    content: ">";
    color: var(--ks-invite);
    padding-inline-end: .5em;
}

.commande:hover,
.commande:focus-visible,
.commande:hover::before,
.commande:focus-visible::before {
    background: var(--ks-texte);
    color: var(--ks-fond);
    outline: none;
}

/*  L'état courant d'un choix : la langue affichée. Elle reste une       */
/*  commande, mais on ne la propose pas, on la constate.                 */

.commande[aria-current] {
    color: var(--ks-intense);
}

/* ── 5. La barre et le pied ───────────────────────────────────────── */

.barre {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ks-espace-2);
    padding-block: var(--ks-espace-2);
}

.barre .marque {
    display: inline-flex;
    align-items: center;
    gap: var(--ks-espace-1);
    color: var(--ks-intense);
    text-decoration: none;
}

.barre .commandes {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ks-espace-2);
    margin-inline-start: auto;
    font-size: var(--ks-font-size-sm);
}

footer {
    padding-block: var(--ks-espace-3);
    color: var(--ks-attenue);
    font-size: var(--ks-font-size-sm);
}

/*  Le lien du pied ne se souligne pas : rien ne se souligne dans ce      */
/*  produit, où un choix se marque par l'invite et par l'inversion vidéo. */
/*  Il reste distinguable au survol et au focus, par cette inversion, qui */
/*  est le seul signal de lien de tout l'écran.                           */

footer a {
    color: inherit;
    text-decoration: none;
}

footer a:hover,
footer a:focus-visible {
    background: var(--ks-texte);
    color: var(--ks-fond);
    outline: none;
}

/* ── 6. Les formulaires ───────────────────────────────────────────── */
/*                                                                      */
/*  Un champ de terminal est une zone de saisie soulignée, sur le fond   */
/*  du terminal : pas de boîte, pas de coin, pas de halo. Le focus se    */
/*  marque par le trait qui s'éclaircit et s'épaissit, ce qui reste un   */
/*  indicateur non textuel au sens de WCAG 1.4.11 et se mesure comme     */
/*  tel (paire `saisie au focus`).                                        */
/*                                                                      */
/*  Le libellé est au-dessus et jamais à l'intérieur : un libellé qui    */
/*  disparaît à la saisie prive de son étiquette qui relit sa réponse.   */

.champ {
    margin-bottom: var(--ks-espace-3);
    max-width: var(--ks-largeur-texte);
}

/*  LA VOIX DE LA MACHINE, déclarée une fois pour ses trois porteurs      */
/*  (15/09/2026) : l'étiquette d'un champ, la question posée au-dessus    */
/*  d'un groupe de boutons, et le visage qui suit le nom en tête de       */
/*  profil. Trois balises différentes, une seule couleur, parce que c'est */
/*  une seule voix. Trois règles jumelles auraient divergé au premier     */
/*  réglage, et la voix se serait fêlée en trois.                         */
/*                                                                        */
/*  La COULEUR seule : le corps appartient à l'endroit où la voix parle,  */
/*  et le visage prend celui du nom qu'il suit.                           */

.champ label,
.question,
.machine {
    color: var(--ks-intense);
}

/*  Ce qui précède un contrôle prend sa ligne et son écart ; le visage    */
/*  du titre, lui, s'assied sur la ligne du nom.                          */

.champ label,
.question {
    display: block;
    margin-bottom: var(--ks-espace-1);
}

.champ input,
.champ select,
.champ textarea {
    -webkit-appearance: none;
    appearance: none;
    display: block;
    width: 100%;
    padding: var(--ks-espace-1) 0;
    border: 0;
    border-bottom: 1px solid var(--ks-trait);
    border-radius: 0;
    background: var(--ks-fond);
    color: var(--ks-texte);
    font: inherit;
}

.champ input:focus,
.champ select:focus,
.champ textarea:focus {
    outline: none;
    border-bottom: 2px solid var(--ks-texte);
    padding-bottom: calc(var(--ks-espace-1) - 1px);
}

/*  Une zone de saisie multiligne porte ses quatre côtés : un trait sous  */
/*  douze lignes de texte ne se lit pas comme un champ, il se lit comme   */
/*  un souligné égaré. La règle du champ à une ligne reste au-dessus.     */

.champ textarea {
    border-width: var(--ks-filet);
    border-style: solid;
    border-color: var(--ks-trait);
    padding: var(--ks-espace-1);
    resize: vertical;
}

.champ textarea:focus {
    border-width: var(--ks-filet);
    border-style: solid;
    border-color: var(--ks-texte);
    padding: var(--ks-espace-1);
}

.champ .aide {
    margin: var(--ks-espace-1) 0 0;
    color: var(--ks-attenue);
    font-size: var(--ks-font-size-sm);
}

.champ .faute {
    margin: var(--ks-espace-1) 0 0;
    color: var(--ks-alerte);
    font-size: var(--ks-font-size-sm);
}

/*  Le pied d'un champ : la commande à gauche, l'état à droite, sur une   */
/*  seule ligne qui ferme le cadre.                                       */
/*                                                                        */
/*  Sa marge haute est NÉGATIVE parce que le champ porte déjà la sienne    */
/*  (`espace-3`) : les deux se fondent, et il reste `espace-1` entre le    */
/*  cadre et la ligne. Sans cela elle flotterait à mi-chemin, détachée de  */
/*  la zone qu'elle mesure.                                                */
/*                                                                        */
/*  Sa largeur est celle du champ, sans quoi l'état se poserait au bord    */
/*  de la colonne et non au bord du cadre : c'est un alignement, pas une   */
/*  marge.                                                                 */

.pied-de-champ {
    margin: calc(var(--ks-espace-1) - var(--ks-espace-3)) 0 0;
    max-width: var(--ks-largeur-texte);
}

/*  `margin-left: auto` plutôt qu'un `order` ou un `row-reverse` : la      */
/*  place se gagne dans la ligne, et l'ordre du source reste celui de      */
/*  l'œil. Sur une colonne étroite la ligne se replie, et l'état garde     */
/*  son bord droit.                                                        */

.reste {
    margin: 0 0 0 auto;
    color: var(--ks-attenue);
    font-size: var(--ks-font-size-sm);
}

/*  Le ressenti : une ligne, chaque visage à la droite de son chiffre.     */
/*  La chasse fixe aligne, l'échelle se lit d'un coup.                     */
/*                                                                         */
/*  Le chevron de la commande est retiré ici, et seulement ici : devant un */
/*  bloc de deux lignes il s'assiérait seul au-dessus du chiffre. Ce qui   */
/*  dit que la chose se clique est l'inversion, au survol et au focus.     */
/*                                                                         */
/*  La réponse en cours porte la même inversion, et `aria-current` la      */
/*  double pour qui ne voit pas la couleur.                                */

.ressenti {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ks-espace-1) var(--ks-espace-2);
}

.ressenti .commande {
    padding: var(--ks-espace-1);
}

.ressenti .commande::before {
    content: none;
}

/*  L'écart entre le chiffre et son visage vient de la feuille et non    */
/*  d'un blanc dans le gabarit : un espace entre deux balises se perd au  */
/*  premier reformatage, et il ne se voit pas en diff.                    */

.ressenti .visage {
    margin-inline-start: .5em;
}

.ressenti .choisi {
    background: var(--ks-texte);
    color: var(--ks-fond);
}

.avis {
    margin-bottom: var(--ks-espace-3);
    padding: var(--ks-espace-2);
    border-width: 1px;
    border-style: solid;
    border-color: var(--ks-alerte);
    color: var(--ks-alerte);
    max-width: var(--ks-largeur-texte);
}

/* ── 7. Le relevé ─────────────────────────────────────────────────── */
/*                                                                      */
/*  Un `<pre>` porte le sens : un texte préformaté dont l'alignement     */
/*  fait partie de l'information (la lignée d'un joueur, le code de      */
/*  récupération). Tout le produit étant déjà à chasse fixe, il n'a      */
/*  besoin d'aucun habillage.                                            */

.releve {
    margin: 0;
    font: inherit;
}

.intense  { color: var(--ks-intense); }
.attenue  { color: var(--ks-attenue); }

.hors-ecran {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ── 8. Le panneau du hero : les diodes battent ───────────────────── */
/*                                                                      */
/*  Deux animations et non une : chacune revient à l'état d'où elle est  */
/*  partie. C'est ce qui rend le repos correct quand le visiteur demande */
/*  moins de mouvement (§10), l'animation s'arrêtant alors sur sa        */
/*  dernière image, qui doit donc être celle du fichier statique.        */

/*  `steps(1, end)` plutôt qu'un fondu : une diode s'allume, elle ne se  */
/*  dégrade pas. Et elle est ROUGE, comme les voyants d'un IMSAI 8080 :  */
/*  le vert est la couleur du texte, pas celle d'un témoin. La réécriture */
/*  de la feuille du 13/09/2026 les avait passées au vert en reposant ce  */
/*  bloc de mémoire au lieu de le reprendre ; la couleur ET le rythme     */
/*  sont ceux d'origine.                                                  */

@keyframes ks-diode-allumee {
    0%, 40%   { fill: var(--ks-diode); }
    45%, 85%  { fill: var(--ks-c64-grey); }
    90%, 100% { fill: var(--ks-diode); }
}

@keyframes ks-diode-eteinte {
    0%, 30%   { fill: var(--ks-c64-grey); }
    35%, 60%  { fill: var(--ks-diode); }
    65%, 100% { fill: var(--ks-c64-grey); }
}

.panneau .diode {
    animation-duration: var(--ks-diode-periode);
    animation-iteration-count: infinite;
    animation-timing-function: steps(1, end);
}

.panneau .d0, .panneau .d2, .panneau .d5, .panneau .d6 {
    animation-name: ks-diode-allumee;
}

.panneau .d1, .panneau .d3, .panneau .d4, .panneau .d7 {
    animation-name: ks-diode-eteinte;
}

.panneau .d1 { animation-delay: calc(var(--ks-diode-pas) * 1); }
.panneau .d2 { animation-delay: calc(var(--ks-diode-pas) * 3); }
.panneau .d3 { animation-delay: calc(var(--ks-diode-pas) * 6); }
.panneau .d4 { animation-delay: calc(var(--ks-diode-pas) * 2); }
.panneau .d5 { animation-delay: calc(var(--ks-diode-pas) * 5); }
.panneau .d6 { animation-delay: calc(var(--ks-diode-pas) * 7); }
.panneau .d7 { animation-delay: calc(var(--ks-diode-pas) * 4); }

/* ── 9. Le morpion qui se joue seul ───────────────────────────────── */
/*                                                                      */
/*  La page d'enregistrement ne dit pas qu'on ne s'enregistre pas : elle */
/*  le montre. La machine joue contre elle-même une partie qui finit     */
/*  sans vainqueur, comme toute partie de morpion bien jouée, et         */
/*  recommence.                                                          */
/*                                                                      */
/*  Les classes portent le RANG du coup et non la case : la partie est   */
/*  tirée par la vue, une feuille de style n'ayant pas de hasard. Le CSS */
/*  ne sait que « le troisième coup paraît à tel moment » ; où il se     */
/*  pose ne le regarde pas.                                              */
/*                                                                      */
/*  C'est la COULEUR DU TEXTE qui s'anime, et non l'opacité. Les traits  */
/*  de la grille sont portés par les mêmes cases que les signes : animée */
/*  en opacité, chaque case emportait son bout de grille, et la grille   */
/*  se dessinait coup par coup au lieu d'attendre les joueurs.           */

.morpion {
    display: inline-grid;
    grid-template-columns: repeat(3, 2.4em);
    grid-template-rows: repeat(3, 2.4em);
    font-size: 1.6em;
    line-height: 2.4em;
    text-align: center;
}

.morpion span {
    color: transparent;
    animation-duration: var(--ks-morpion-cycle);
    animation-timing-function: steps(1, end);
    animation-iteration-count: infinite;
}

/*  La grille est tracée par les bordures intérieures des cases : deux   */
/*  traits verticaux, deux horizontaux, et rien autour. Leur couleur est */
/*  nommée et non héritée, sans quoi elles disparaîtraient avec le texte.*/

.morpion span:nth-child(3n+1),
.morpion span:nth-child(3n+2) {
    border-right: 1px solid var(--ks-trait);
}

.morpion span:nth-child(-n+6) {
    border-bottom: 1px solid var(--ks-trait);
}

@keyframes ks-coup-1 { 0%,  7% { color: transparent; }  8%, 88% { color: var(--ks-texte); } 92%, 100% { color: transparent; } }
@keyframes ks-coup-2 { 0%, 14% { color: transparent; } 15%, 88% { color: var(--ks-texte); } 92%, 100% { color: transparent; } }
@keyframes ks-coup-3 { 0%, 21% { color: transparent; } 22%, 88% { color: var(--ks-texte); } 92%, 100% { color: transparent; } }
@keyframes ks-coup-4 { 0%, 28% { color: transparent; } 29%, 88% { color: var(--ks-texte); } 92%, 100% { color: transparent; } }
@keyframes ks-coup-5 { 0%, 35% { color: transparent; } 36%, 88% { color: var(--ks-texte); } 92%, 100% { color: transparent; } }
@keyframes ks-coup-6 { 0%, 42% { color: transparent; } 43%, 88% { color: var(--ks-texte); } 92%, 100% { color: transparent; } }
@keyframes ks-coup-7 { 0%, 49% { color: transparent; } 50%, 88% { color: var(--ks-texte); } 92%, 100% { color: transparent; } }
@keyframes ks-coup-8 { 0%, 56% { color: transparent; } 57%, 88% { color: var(--ks-texte); } 92%, 100% { color: transparent; } }
@keyframes ks-coup-9 { 0%, 63% { color: transparent; } 64%, 88% { color: var(--ks-texte); } 92%, 100% { color: transparent; } }

.morpion .r1 { animation-name: ks-coup-1; }
.morpion .r2 { animation-name: ks-coup-2; }
.morpion .r3 { animation-name: ks-coup-3; }
.morpion .r4 { animation-name: ks-coup-4; }
.morpion .r5 { animation-name: ks-coup-5; }
.morpion .r6 { animation-name: ks-coup-6; }
.morpion .r7 { animation-name: ks-coup-7; }
.morpion .r8 { animation-name: ks-coup-8; }
.morpion .r9 { animation-name: ks-coup-9; }

/* ── 10. Moins de mouvement, quand on le demande ──────────────────── */
/*                                                                      */
/*  C'EST LE PRODUIT QUI LE POSE DÉSORMAIS. Le socle réduisait toute     */
/*  animation à une itération de 0,01 ms depuis sa propre feuille, que   */
/*  ce produit ne charge plus : sans cette règle, les diodes et le       */
/*  morpion continueraient de battre chez qui demande le contraire, et   */
/*  rien ne l'aurait dit. C'est exactement ce qu'une sortie de cadre     */
/*  fait perdre en silence, et le canari du produit le garde.            */
/*                                                                      */
/*  Les animations du produit sont écrites pour s'arrêter proprement :   */
/*  chacune revient à son état de départ, si bien que la dernière image  */
/*  est celle du dessin statique.                                        */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    /*  La règle ci-dessus écrase les DURÉES, jamais les retards : sans   */
    /*  cette seconde, la frappe se poserait toujours en cinq secondes,   */
    /*  par petits sauts, pour qui a demandé le calme. Ce que la          */
    /*  préférence retire est le mouvement, pas le contenu : la carte se  */
    /*  pose donc d'un coup, entière.                                     */

    .telemetrie .pas {
        animation-delay: 0s !important;
    }
}

/* ── 11. L'accueil ────────────────────────────────────────────────── */
/*                                                                      */
/*  Le titre reste en minuscules, et il ressort par l'intensité, non par */
/*  la graisse. Glass TTY VT220 n'a qu'une graisse : un gras serait      */
/*  synthétisé par le navigateur, qui doublerait les traits d'une police */
/*  de terminal au lieu de les élargir. Et c'est ainsi que le VT220      */
/*  rendait son attribut « bold », par la lumière et non par l'épaisseur.*/

.hero {
    padding-block: var(--ks-espace-6) var(--ks-espace-5);
}

.hero .panneau-hero {
    margin-bottom: var(--ks-espace-4);
}

.hero .titre {
    font-size: var(--ks-titre-1);
    margin-bottom: var(--ks-espace-2);
}

.hero .slogan {
    font-size: var(--ks-font-size-lg);
    margin-bottom: var(--ks-espace-6);
}

/*  Les deux chemins : côte à côte dès qu'il y a la place, l'un sous     */
/*  l'autre sinon.                                                       */

.chemins {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--ks-espace-2) var(--ks-espace-5);
    margin-bottom: 0;
}

/*  L'écran de la frappe : un listing, et rien d'autre. Chaque ligne porte  */
/*  son RANG, le bloc porte le NOMBRE DE RELEVÉS, et la feuille en tire le */
/*  retard de chacune. Deux règles suffisent alors, là où une classe par   */
/*  rang en aurait demandé quinze, et le rang reste une donnée du serveur  */
/*  plutôt qu'un nom de classe.                                            */
/*                                                                         */
/*  `forwards` et non une boucle : un tir ne se rejoue pas.                */

.telemetrie {
    margin: 0 0 var(--ks-espace-4);
    padding: 0;
    list-style: none;
    font-variant-numeric: tabular-nums;
}

/*  EN PROPRIÉTÉS LONGUES, JAMAIS EN RACCOURCI. Un `var()` dans un         */
/*  raccourci rend la déclaration ENTIÈRE invalide si le jeton manque :    */
/*  `animation-name` retombe alors à `none`, l'animation ne tourne jamais, */
/*  et les lignes restent transparentes. C'est ce qu'un `palette.css`      */
/*  gardé en cache produisait, et rien à l'écran ne le disait.             */
/*                                                                         */
/*  En longues, un jeton manquant ne coûte que sa propriété : la durée     */
/*  retombe à zéro, le remplissage tient, et le listing paraît d'un coup   */
/*  au lieu de disparaître.                                                */

.telemetrie .pas {
    color: transparent;
    animation-name: ks-frappe;
    animation-duration: var(--ks-lancement-pose);
    animation-fill-mode: forwards;
    animation-delay: calc(var(--rang) / var(--pas) * var(--ks-lancement-cycle));
}

/*  Le listing est la seule chose rouge de tout le produit : c'est la      */
/*  couleur des diodes du panneau, donc celle d'une machine qui signale.   */

@keyframes ks-frappe { to { color: var(--ks-diode); } }

/* ── 12. Les écrans qui restent ───────────────────────────────────── */

/*  La séquence : la recherche du code de lancement. Les positions qui     */
/*  défilent sont en retrait, celles qui se figent prennent la lumière ;  */
/*  c'est le contraste qui fait voir le remplissage.                      */
/*                                                                        */
/*  Au corps du titre et centrée (15/09/2026) : c'est le seul endroit de  */
/*  l'écran qui doit se regarder plutôt que se lire, et une ligne alignée */
/*  à gauche se serait rangée parmi les champs.                           */
/*                                                                        */
/*  Les caractères tombés prennent la couleur de la DIODE (15/09/2026),   */
/*  la seule du produit qui ne soit pas du vert de terminal : c'est celle */
/*  des voyants du panneau, donc celle d'une machine qui signale.         */
/*                                                                        */
/*  Chaque position réserve sa place (`1ch`) avant d'être remplie : sans  */
/*  cela la ligne pousserait d'un caractère à chaque verrou, et le bloc    */
/*  sauterait sous les yeux.                                              */

.sequence {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--ks-espace-2);
    margin-bottom: var(--ks-espace-3);
    font-size: var(--ks-titre-1);
    color: var(--ks-attenue);
}

.sequence .position {
    display: inline-block;
    width: 1ch;
}

.sequence .position.figee {
    color: var(--ks-diode);
}

/*  Le décompte tient le début de la barre de menu, seul de son côté :    */
/*  la marge automatique gagne la place dans la ligne, et les entrées du  */
/*  menu gardent la fin sans qu'on touche à leur alignement.              */
/*                                                                        */
/*  Marge LOGIQUE, donc elle suit la direction d'écriture : en RTL le     */
/*  début est à droite, et le RTL de la maison est une source unique.     */
/*                                                                        */
/*  Il prend le corps de la barre : c'en est une entrée, pas un titre. La */
/*  couleur reste celle du corps, le rouge de la diode disant ce qui est  */
/*  TOMBÉ, et un décompte ne tombe pas.                                   */

.chrono {
    margin: 0;
    margin-inline-end: auto;
}

.code-recuperation {
    font-size: var(--ks-titre-3);
    margin-block: var(--ks-espace-4);
}

.non {
    font-size: var(--ks-titre-1);
    margin-bottom: var(--ks-espace-4);
}

.apres-formulaire,
.sortie {
    margin-top: var(--ks-espace-5);
}

.code-erreur {
    font-size: var(--ks-titre-1);
    color: var(--ks-intense);
    margin-bottom: var(--ks-espace-2);
}

/*  Deux choix de même poids, côte à côte : aucun n'est mis en avant par  */
/*  la couleur ni par la taille, ce qui serait un dark pattern là où le   */
/*  choix engage (le consentement, CJUE Planet49).                        */

.choix {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ks-espace-2) var(--ks-espace-4);
    margin-bottom: var(--ks-espace-3);
}

/*  Les portes ouvertes : un lien par ligne, avec le geste de le copier.  */
/*  L'adresse peut casser n'importe où, un jeton n'ayant pas de mots.     */

.portes {
    list-style: none;
    margin: 0 0 var(--ks-espace-4);
    padding: 0;
}

/*  Le formulaire de suppression ne doit pas casser la ligne : il vit    */
/*  dans une liste où chaque élément est déjà une rangée, et un `<form>` */
/*  est un bloc. Sans cela, « Supprimer » tomberait sous « Copier ».     */

.portes form.ligne-invitation {
    display: inline;
}

.portes li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--ks-espace-2);
    padding-block: var(--ks-espace-2);
    border-top: 1px solid var(--ks-trait);
}

.portes .porte {
    flex: 1 1 20rem;
    overflow-wrap: anywhere;
    font-size: var(--ks-font-size-sm);
}

/*  La bannière de consentement, en bas de l'écran et non par-dessus.     */

#consentement {
    position: sticky;
    bottom: 0;
    padding-block: var(--ks-espace-2);
    border-top: 1px solid var(--ks-trait);
    background: var(--ks-fond);
    font-size: var(--ks-font-size-sm);
}

#consentement p {
    margin: 0;
}

[hidden] {
    display: none !important;
}

/* ── 13. La barre de menu ─────────────────────────────────────────── */
/*                                                                      */
/*  Sous la barre de navigation, au-dessus de la page. Celle du dessus   */
/*  porte l'identité du site et vaut pour tout le monde ; celle-ci porte */
/*  ce qu'un joueur peut faire, et n'existe que pour lui.                */
/*                                                                      */
/*  Un filet la sépare de la page, et rien d'autre : un terminal ne      */
/*  dessine pas de boîte autour de ses commandes.                        */

.menu {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--ks-espace-2) var(--ks-espace-4);
    padding-block: var(--ks-espace-1);
    border-block: 1px solid var(--ks-trait);
    font-size: var(--ks-font-size-sm);
}

/* ── 14. Le cadre d'un relevé ─────────────────────────────────────── */
/*                                                                      */
/*  L'état d'une nation est encadré (13/09/2026). Un terminal DEC savait */
/*  le faire avec son jeu semi-graphique dès 1978, et c'est ainsi qu'un  */
/*  écran d'état se distingue d'une sortie de commande : ce qui est dans */
/*  le cadre est un état constaté, pas un texte qui défile.              */
/*                                                                      */
/*  Le filet est dessiné en `border` et non en caractères. Un cadre en   */
/*  `┌─┐` se brise au premier écran plus étroit que lui, et il entre     */
/*  dans le texte que lit un lecteur d'écran, qui épellerait les traits. */
/*                                                                      */
/*  La boîte épouse son contenu (`inline-block`) plutôt que la largeur   */
/*  de la page : un cadre vide sur sa droite dirait qu'il manque une     */
/*  colonne, alors qu'il ne manque rien.                                 */

.cadre {
    display: block;
    width: fit-content;
    max-width: 100%;
    margin-top: var(--ks-espace-3);
    padding: 0 var(--ks-espace-3) var(--ks-espace-3);
    /*  En LONGHANDS et non en raccourci `border`. Un `var()` dans un    */
    /*  raccourci rend la déclaration ENTIÈRE invalide au calcul si le   */
    /*  jeton ne se résout pas, et `border` retombe alors sur sa valeur  */
    /*  initiale, `border-style: none` : le cadre disparaît tout entier  */
    /*  au lieu de perdre une épaisseur. En longhands, un jeton manquant */
    /*  ne coûte que sa propre propriété, et le trait reste dessiné.     */
    border-width: var(--ks-filet);
    border-style: solid;
    border-color: var(--ks-trait);
}

/*  La légende ouvre le filet : elle chevauche le trait du haut et porte le  */
/*  fond de la page, ce qui le coupe derrière elle. Un `<fieldset>` ferait   */
/*  cela nativement, mais il appartient aux formulaires, et ce cadre n'en    */
/*  est pas un.                                                              */
/*                                                                          */
/*  DEUX CHOSES SE SONT APPRISES EN LA POSANT, et toutes deux par l'écran.   */
/*                                                                          */
/*  Elle reste DANS LE FLUX, décalée par `transform` et non par `position:   */
/*  absolute`. En absolu elle ne compte pas dans la largeur du cadre : un    */
/*  nom plus long que le relevé dépasse le filet par la droite. Dans le      */
/*  flux, c'est le cadre qui s'élargit pour la contenir.                     */
/*                                                                          */
/*  Et le cadre n'a PAS d'`overflow` : dès qu'un axe passe en `auto`,        */
/*  l'autre cesse d'être `visible`, et la boîte rogne ce qui déborde         */
/*  au-dessus d'elle, c'est-à-dire la moitié haute de sa propre légende. Le  */
/*  débordement horizontal appartient au relevé, qui est ce qui peut être    */
/*  trop large, et c'est là qu'il est déclaré.                               */
/*                                                                          */
/*  Elle reste au corps du texte : c'est le titre de l'écran, pas une        */
/*  enseigne. Un terminal ne grossit pas ses titres, il les intensifie.      */
/*                                                                          */
/*  LA CORRECTION OPTIQUE EST MESURÉE, PAS TÂTONNÉE. `translateY(-50%)`      */
/*  centre la BOÎTE sur le filet, et la boîte n'est pas l'encre : Glass TTY  */
/*  VT220 déclare un ascendant de 800/1000 alors que son plus haut glyphe    */
/*  ne monte qu'à 400 (relevé dans la fonte, table `head`). Avec             */
/*  `line-height: 1`, la boîte va de -175 à +825 et son centre tombe à       */
/*  +325, quand la bande des capitales est centrée à +200 : l'encre passait  */
/*  donc 0,125 em sous le trait, ce qui se voit.                             */

.cadre .legende {
    display: inline-block;
    transform: translateY(calc(-50% - 0.125em));
    margin: 0 0 var(--ks-espace-1);
    padding-inline: var(--ks-espace-1);
    background: var(--ks-fond);
    color: var(--ks-intense);
    font-size: inherit;
    line-height: 1;
}

.cadre .releve {
    overflow-x: auto;
}

/*  Le trait dit la distance : plein pour chez soi, discontinu pour le   */
/*  dehors. C'est la seule différence entre les deux cadres, et elle n'a */
/*  besoin d'aucun mot. Les cadres du dehors s'empilent, chacun sur sa   */
/*  ligne : ils épousent leur contenu, et alignés ils feraient une       */
/*  colonne de largeurs inégales.                                         */
/*                                                                        */
/*  DES TIRETS, ET NON DES POINTS (14/09/2026). Un tiret se lit de loin   */
/*  comme une frontière ; un point se lit comme une hésitation.           */
/*                                                                        */
/*  LE FILET EST PEINT, PAS BORDÉ, et c'est la seule façon d'obtenir      */
/*  « tiret, espace, tiret ». `border-style: dashed` dérive la longueur   */
/*  du tiret et l'écart de l'épaisseur du trait, trois fois pour l'un,    */
/*  autant pour l'autre : on ne peut ni allonger le tiret sans épaissir   */
/*  le trait, ni écarter deux tirets du tout. Quatre dégradés répétés,    */
/*  un par côté, rendent les trois mesures indépendantes.                 */
/*                                                                        */
/*  La bordure RESTE, en transparent : elle réserve la place que les      */
/*  dégradés viennent peindre. Sans elle, le contenu se décalerait de la  */
/*  largeur du filet en passant d'un cadre à l'autre. Le fond se peint    */
/*  bien jusque-là, `background-clip` valant `border-box` par défaut.     */

.cadre.tirets {
    border-color: transparent;
    border-width: var(--ks-filet-tirets);
    background-image:
        repeating-linear-gradient(to right, var(--ks-trait) 0 var(--ks-tiret),
            transparent var(--ks-tiret) calc(var(--ks-tiret) + var(--ks-tiret-ecart))),
        repeating-linear-gradient(to right, var(--ks-trait) 0 var(--ks-tiret),
            transparent var(--ks-tiret) calc(var(--ks-tiret) + var(--ks-tiret-ecart))),
        repeating-linear-gradient(to bottom, var(--ks-trait) 0 var(--ks-tiret),
            transparent var(--ks-tiret) calc(var(--ks-tiret) + var(--ks-tiret-ecart))),
        repeating-linear-gradient(to bottom, var(--ks-trait) 0 var(--ks-tiret),
            transparent var(--ks-tiret) calc(var(--ks-tiret) + var(--ks-tiret-ecart)));
    background-size:
        100% var(--ks-filet-tirets),
        100% var(--ks-filet-tirets),
        var(--ks-filet-tirets) 100%,
        var(--ks-filet-tirets) 100%;
    background-position: 0 0, 0 100%, 0 0, 100% 0;
    background-repeat: no-repeat;
}

/* ── 15. Le portrait ──────────────────────────────────────────────── */
/*                                                                      */
/*  Le socle redimensionne à 300x300 avant STOCKAGE ; la page en affiche  */
/*  la moitié (14/09/2026), ce qui laisse le double de pixels pour un     */
/*  écran à forte densité. Le carré est réservé, et le portrait le        */
/*  remplit sans se déformer si la source n'était pas carrée.             */
/*                                                                      */
/*  Encadré du filet plein, comme le relevé de sa nation : c'est chez    */
/*  soi. Pas d'angle rond ni d'ombre, un terminal n'en a pas.            */

.portrait {
    display: block;
    width: 150px;
    height: 150px;
    max-width: 100%;
    /*  Au bord gauche de sa colonne, qui est plus large que lui (elle    */
    /*  garde 300 pixels pour ne pas sauter au téléversement, le portrait */
    /*  en fait 150). Il a été centré du 14 au 15/09/2026 : centré, il ne */
    /*  s'alignait sur rien, ni sur le nom, ni sur ses propres commandes, */
    /*  ni sur l'arbre, qui partent tous du même bord.                    */
    margin: 0 0 var(--ks-espace-3);
    object-fit: cover;
    border-width: var(--ks-filet);
    border-style: solid;
    border-color: var(--ks-trait);
}

/* ── 16. La fiche : deux colonnes, dont une en attente ────────────── */
/*                                                                      */
/*  Tout est à gauche : le nom, le portrait, la lignée, séparés par des  */
/*  doubles filets. La droite attend son contenu, et se replie tant      */
/*  qu'elle n'en a pas : un élément flex prend sa part qu'il porte       */
/*  quelque chose ou non, et l'écran aurait porté vingt rem de blanc.    */
/*                                                                      */
/*  La gauche épouse son contenu : un arbre profond est plus large qu'un */
/*  portrait, et sa largeur dépend du joueur. Elle ne descend pas sous   */
/*  300 pixels, pour que la page ne saute pas d'un joueur sans           */
/*  descendance à un autre. `flex-wrap` les fait passer l'une sous       */
/*  l'autre avant de les comprimer.                                      */

/*  Le nom du joueur porte le plus grand corps de l'échelle, celui du    */
/*  hero : c'est l'écran de quelqu'un, et il s'annonce comme le produit   */
/*  s'annonce sur l'accueil. Un cran redéclaré, pas une valeur écrite.    */

/*  EN CAPITALES PAR LA FEUILLE, jamais par la donnée. Le nom reste tel   */
/*  que le joueur l'a écrit dans la base, dans les lignées des autres et  */
/*  dans l'onglet ; seul cet écran le crie. Le transformer à la source    */
/*  renommerait quelqu'un pour une raison de mise en page.                 */

/*  Le titre du profil : l'ancienneté du joueur, et rien d'autre depuis le */
/*  16/09/2026. Le nom et le visage y ont tenu compagnie une journée ; la  */
/*  barre de menu nomme le joueur partout, et son ressenti se règle plus   */
/*  bas dans la page.                                                      */

.nom {
    margin: 0 0 var(--ks-espace-4);
    font-size: var(--ks-titre-1);
}

/*  Seul le palier prend les capitales : le préambule est de la phrase, le */
/*  palier est un grade. Les capitales étaient sur le titre entier quand   */
/*  il ne portait qu'un nom.                                               */

.nom .palier {
    text-transform: uppercase;
}

.fiche {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--ks-espace-4) var(--ks-espace-5);
}

.colonne-gauche {
    flex: 0 1 auto;
    min-width: 300px;
    max-width: 100%;
}

/*  `min-width: 0` : un élément flex refuse par défaut de rétrécir sous  */
/*  son contenu, et un long texte pousserait la colonne hors de la page. */

.colonne-droite {
    flex: 1 1 20rem;
    min-width: 0;
}

.colonne-droite:empty {
    display: none;
}

/*  Le blanc autour des séparateurs est le leur, et le leur seul.        */
/*                                                                      */
/*  Il ne l'était pas : s'y ajoutait la marge basse que le navigateur    */
/*  donne d'office à un `<form>`, et une marge que le relevé portait     */
/*  lui-même. Trois sources pour un seul blanc, donc deux écarts         */
/*  différents de part et d'autre du trait.                              */

.fiche form {
    margin: 0;
}

/*  Et le relevé de la lignée défile plutôt que de pousser la colonne    */
/*  hors de la page : un arbre à dix générations n'a pas de largeur      */
/*  raisonnable.                                                          */

.fiche .releve {
    overflow-x: auto;
}

/*  L'invitation suit l'arbre sans filet entre eux : ce sont les deux     */
/*  moments d'une même chose. Elle respire tout de même, sans quoi elle   */
/*  se collerait à la dernière branche et se lirait comme un descendant.  */

.apres-arbre {
    margin: var(--ks-espace-2) 0 0;
}

/*  La silhouette et le portrait font la même taille : deux tailles       */
/*  différentes feraient sauter la mise en page au moment même où l'on    */
/*  regarde le résultat de son envoi. La classe reste pour distinguer     */
/*  les deux, l'un étant un dessin et l'autre une image, et un canari     */
/*  s'en sert.                                                            */

/* ── 17. Le séparateur, en double filet ───────────────────────────── */
/*                                                                      */
/*  Deux traits et non un : c'est le « ═ » des terminaux, et surtout ce  */
/*  n'est PAS un pointillé. Le tiret a déjà un sens dans ce produit, il  */
/*  dit « le dehors » sur le tableau de bord ; s'en servir ici pour      */
/*  séparer deux parties d'un même écran brouillerait les deux.          */
/*                                                                      */
/*  L'élément n'a pas de corps, il n'est fait que de ses deux bordures : */
/*  sa hauteur EST l'écart entre elles. À un pixel, les deux traits se   */
/*  touchaient et l'on lisait un « - » épais et non un « = » ; six les   */
/*  séparent, et c'est dérivé de l'épaisseur du filet plutôt qu'écrit en */
/*  dur, de sorte que les deux restent liés si l'un change.              */
/*                                                                      */
/*  `width: 100%` est posé et non supposé : un `<hr>` remplit son bloc,  */
/*  mais la colonne épouse son contenu et le navigateur a sa propre      */
/*  feuille pour cette balise.                                           */
/*                                                                      */
/*  La marge est la même des deux côtés, et c'est la SEULE du voisinage :*/
/*  les marges des formulaires et du relevé sont à zéro, sans quoi trois */
/*  sources composaient un blanc différent de part et d'autre du trait.  */

.separateur {
    width: 100%;
    height: calc(var(--ks-filet) * 6);
    margin: var(--ks-espace-4) 0;
    border: 0;
    border-top: var(--ks-filet) solid var(--ks-trait);
    border-bottom: var(--ks-filet) solid var(--ks-trait);
}

/*  La silhouette et le portrait font la même taille depuis le 14/09/2026 */
/*  (l'un a rejoint l'autre). La classe reste, mais elle ne porte plus de */
/*  dimension : elle distingue encore les deux à l'écran, l'un étant un   */
/*  dessin et l'autre une image, et un canari s'en sert. Lui redonner une */
/*  taille propre serait une décision, pas un réglage.                    */

/* ── 17. Le choix d'un fichier ────────────────────────────────────── */
/*                                                                      */
/*  Ce n'est pas Bootstrap qui dessine ce bouton, c'est le NAVIGATEUR.  */
/*  `input[type="file"]` porte son contrôle dans son arbre d'ombre, hors */
/*  d'atteinte des règles ordinaires : `appearance: none` sur le champ  */
/*  ne le touche pas, et c'est pourquoi il restait au dessin du système */
/*  quand tout le reste avait changé.                                    */
/*                                                                      */
/*  Il s'atteint par `::file-selector-button`, et par son ancien nom    */
/*  WebKit, que Safari reconnaît seul dans les versions plus anciennes. */
/*  Les deux règles sont écrites SÉPARÉMENT et non dans une liste : un  */
/*  sélecteur qu'un navigateur ne comprend pas invalide la règle        */
/*  entière, donc la liste ne styleraient plus rien nulle part.          */
/*                                                                      */
/*  Le libellé reste celui du navigateur (« Choisir le fichier »), dans */
/*  sa langue : il n'est pas dans le document et ne se traduit pas. Il  */
/*  n'a donc pas l'invite `>` des autres commandes, qu'un `::before` ne */
/*  peut pas lui ajouter, un pseudo-élément n'en portant pas.            */

.champ input[type="file"] {
    padding-inline: 0;
    cursor: pointer;
}

.champ input[type="file"]::file-selector-button {
    appearance: none;
    margin: 0 var(--ks-espace-2) 0 0;
    padding: 0 .2em;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

.champ input[type="file"]::-webkit-file-upload-button {
    -webkit-appearance: none;
    margin: 0 var(--ks-espace-2) 0 0;
    padding: 0 .2em;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

/*  L'inversion vidéo, comme sur toute commande du produit.              */

.champ input[type="file"]:hover::file-selector-button,
.champ input[type="file"]:focus-visible::file-selector-button {
    background: var(--ks-texte);
    color: var(--ks-fond);
}

.champ input[type="file"]:hover::-webkit-file-upload-button,
.champ input[type="file"]:focus-visible::-webkit-file-upload-button {
    background: var(--ks-texte);
    color: var(--ks-fond);
}

/*  Le champ de fichier, commandé par son étiquette. Le contrôle natif est  */
/*  retiré de la vue mais pas de la page : il garde le focus et sa place    */
/*  dans l'ordre de tabulation, et `<label for>` ouvre le sélecteur au clic */
/*  comme le faisait son bouton.                                            */
/*                                                                          */
/*  `:focus-within` porte le focus sur l'étiquette : sans cela il tomberait */
/*  sur un élément invisible, et la page serait aveugle au clavier.         */

.champ-fichier {
    margin-bottom: var(--ks-espace-3);
}

/*  Deux commandes côte à côte tant qu'il y a la place, l'une sous       */
/*  l'autre sinon : `flex-wrap` décide, sans point de rupture choisi à   */
/*  la main. Le champ de fichier perd sa marge basse ici, la rangée      */
/*  portant son propre écart.                                            */
/*                                                                      */
/*  `baseline` et non `center` : ce sont deux textes, et ils doivent     */
/*  s'asseoir sur la même ligne d'écriture, pas se centrer l'un sur      */
/*  l'autre.                                                             */

.en-ligne {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--ks-espace-1) var(--ks-espace-3);
}

.en-ligne .champ-fichier {
    margin-bottom: 0;
}

/*  Une rangée AUTOUR des deux formulaires du portrait, et non dans l'un */
/*  d'eux : changer envoie un fichier, retirer en efface un, et les      */
/*  mêler ferait d'un geste l'autre.                                     */

.rangee {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--ks-espace-1) var(--ks-espace-3);
}

/*  Une faute de saisie prend sa ligne : dans la rangée elle s'assiérait */
/*  à côté de la commande, où personne ne lit un message d'erreur.       */

.en-ligne .faute {
    flex-basis: 100%;
    margin: var(--ks-espace-1) 0 0;
}

.champ-fichier input[type="file"] {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.champ-fichier:focus-within label,
.champ-fichier:focus-within label::before {
    background: var(--ks-texte);
    color: var(--ks-fond);
}

/* ── 18. L'épigraphe ──────────────────────────────────────────────── */
/*                                                                      */
/*  Ni guillemets ni italiques : la machine ne décore pas ce qu'elle     */
/*  affiche, et une citation imprimée comme le reste se lit comme le     */
/*  reste. C'est la source, sur sa ligne, qui dit que ce n'est pas la    */
/*  machine qui parle.                                                   */
/*                                                                      */
/*  Le navigateur met une marge latérale d'office à `<blockquote>` : on  */
/*  la retire, rien ici n'étant en retrait.                              */

.citation {
    margin: 0 0 var(--ks-espace-4);
    max-width: var(--ks-largeur-texte);
}

/*  Les vers gardent leurs coupes : ce sont celles de Virgile, et les    */
/*  laisser couler au fil de la largeur en ferait de la prose.           */

.citation .vers {
    margin-bottom: var(--ks-espace-3);
}

.citation cite {
    font-style: normal;
}
