/* ==========================================================================
   Nebula — feuille de style du site public
   --------------------------------------------------------------------------
   Direction : « panneau d'instruments dans le noir ». Charte 60 / 30 / 10,
   reprise du site d'origine : le NOIR CHAUD est le vide, l'AMBRE est la lumiere
   fonctionnelle — un element interactif EMET, il ne reflete pas —, le BLANC
   CASSE est ce qui se lit.

   Ce fichier etait bleu jusqu'au 2026-09-22 (« vide froid et lumiere
   d'alerte »). Le changement n'est pas un ravalement : c'est le retour a la
   charte du site d'origine, que le proprietaire a designee comme reference.

   Contraintes qui ont faconne ce fichier :
   - La CSP interdit tout style en ligne et toute ressource externe. La police
     est donc auto-hebergee, et la texture est une data-URI SVG (autorisee par
     img-src 'self' data:).
   - Contraste AA verifie sur le fond compose le PIRE CAS (#221209 : le point
     le plus clair du degrade de fond, sous le halo ambre a .10), pas sur le
     noir nu : texte 16,11:1, secondaire 7,48:1, tertiaire 4,82:1,
     accent 6,33:1, danger 5,39:1. Toute augmentation de l'opacite du halo
     degrade ces chiffres. Le seuil a ete cherche et non suppose : a .14 tout
     tient encore, a .15 le texte tertiaire tombe a 4,47:1.
     Ces chiffres sont MEILLEURS que ceux de la charte bleue (12,38 / 5,31 /
     4,64 / 4,62 / 4,56) : le fond a descendu en meme temps que la teinte a
     change.
     ⚠️ Le site d'origine dont vient cette charte n'a JAMAIS mesure ses
     contrastes : 5 420 lignes de SCSS, zero ratio calcule, et son texte est
     peint par-dessus un trou noir WebGL et une galaxie, donc son pire pixel
     reel n'a jamais ete connu. On reprend la teinte, pas l'absence de mesure.
   - Les classes de ce fichier ne sont PLUS assertees par les tests. Elles l'ont
     ete : site-header et site-footer etaient cherchees telles quelles, si bien
     qu'un renommage de classe faisait echouer des tests de page d'erreur qui ne
     parlent pas de style. Les tests visent desormais des attributs data-region,
     data-lang-switch, data-page et data-download poses dans le balisage. On peut
     donc renommer librement ici — mais pas retirer ces attributs-la.
   - La charte complete — palette par role, echelles, rayons, et le tableau de
     contraste de chaque couple — vit dans docs/architecture/17-charte-visuelle.md.
     Ce fichier en est l'implementation, pas la source.
   ========================================================================== */

/* --- Police -------------------------------------------------------------- */
/* Space Grotesk variable : un seul fichier de 49 Ko couvre 300 a 700.
   Grotesque geometrique derive de Space Mono — caractere technique sans tomber
   dans l'Orbitron vu partout. SIL OFL, licence dans /fonts/OFL.txt. */
@font-face {
    font-family: "Space Grotesk";
    src: url("../fonts/space-grotesk-var.woff2") format("woff2-variations");
    font-weight: 300 700;
    font-style: normal;
    font-display: swap;
}

/* --- Jetons -------------------------------------------------------------- */
:root {
    /* --- Profondeurs -----------------------------------------------------
       Noir CHAUD, et non plus bleute. Le fond ne se contente pas de changer de
       teinte : il descend. L'ancien #06080f portait une nebuleuse bleue et deux
       halos ; le nouveau descend a #060504 sous un degrade radial et un unique
       halo ambre a .10. Le pire pixel du fond compose passe donc de #1f2839 a
       #221209, et TOUS les contrastes y gagnent — le detail est dans le
       chapitre 17, et infra/scripts/contraste-wcag.py les remesure. */
    --void: #060504;
    /* Des blancs translucides aplatis en OPAQUES sur --void, a l'arrondi au
       plus proche. Deux viennent du site d'origine : .025 (--hud-surface) donne
       --surface, .08 (--hud-line) donne --line.
       Les deux autres sont INTERPOLES ICI, et il faut le dire : le site
       d'origine n'a ni panneau survole ni filet eclaire — ses survols virent a
       l'ambre au lieu de monter vers un blanc plus dense. .055 et .18 sont donc
       une decision de ce depot, pas un emprunt.
       Pourquoi opaques : un panneau translucide sur un fond variable donne un
       contraste variable, et ce site veut pouvoir mesurer une fois. */
    --surface: #0c0b0a;
    --surface-2: #141312;
    --line: #1a1918;
    --line-lit: #333231;

    /* --- Encres -----------------------------------------------------------
       Blanc casse et non blanc pur : #fff sur du noir chauffe l'oeil et fait
       vibrer les petits corps. Les deux gris sont l'aplatissement de 66 % et
       50 % de --text sur le fond compose. */
    --text: #f4f1ee;
    --text-dim: #ada5a0;
    --text-faint: #8b827c;

    /* --- Accent ------------------------------------------------------------
       Ambre. Charte 60 / 30 / 10 reprise du site d'origine : noir au fond,
       ambre pour ce qui repond au doigt, blanc pour ce qui se lit.
       ⚠️ --accent-glow n'est PAS --accent avec un alpha : c'est un ton plus
       chaud (#ff7a28), choix delibere du site d'origine pour que le halo ne
       soit pas une simple dilution du trait. Ne pas « simplifier ». */
    --accent: #ff6a1a;
    --accent-lit: #ff8c3d;
    --accent-dim: rgba(255, 106, 26, .34);
    --accent-glow: rgba(255, 122, 40, .55);
    --accent-veil: rgba(255, 106, 26, .12);
    /* Depart du degrade des jauges. Jamais de texte dessus : 2,17:1 sur --void.
       L'ancien --blue-deep valait #1c4fb8 et servait au meme role. */
    --accent-deep: #7a2f08;
    /* Encre posee SUR un aplat ambre. Noir chaud, pas noir pur : 7,07:1. */
    --on-accent: #0a0503;

    /* --- Factions ----------------------------------------------------------
       Les deux couleurs viennent du site d'origine, ou elles sont « calquees
       sur le logo de chaque faction » — c'est sa formule. Il en a une
       troisieme, --cyborg #5cb4ff, qui n'est PAS posee ici : la faction
       n'existe pas encore cote Razor, et un jeton sans appelant est le piege
       n°1 du projet.
       ⚠️ --fed etait #7fb2ff, un BLEU CLAIR, alors que l'embleme de la
       Federation affiche sur cette page est argent et OR massif, sans un pixel
       de bleu. Le jeton contredisait l'image qu'il titrait, et la palette
       n'avait aucun or sur ses 52 entrees. Corrige ici : « L'ordre, grave dans
       l'or » est la devise de la faction dans le contenu d'origine.
       ⚠️ --pirate #ff4651 et --accent #ff6a1a sont a 1,17:1 l'un de l'autre :
       un protanope ou un deuteranope ne les distingue pas. Aucune information
       ne doit donc reposer sur cette seule difference de teinte — toujours un
       libelle ou une forme en plus. */
    --fed: #f0c463;
    --pirate: #ff4651;
    /* Pose avec la troisieme faction, pas avant : un jeton sans appelant est le
       piege n°1 du projet. Son appelant est .fband--cyborg, plus bas. */
    --cyborg: #5cb4ff;

    /* Signaux. --danger est le rouge pirate : le site n'a pas besoin de deux
       rouges, et en avoir deux voisins les rendrait indiscernables. */
    --ok: #3ddc97;
    --warn: #ffb454;
    --danger: #ff4651;

    /* Rythme */
    --gutter: clamp(1.25rem, 4vw, 3rem);
    /* Le min(..., 100%) n'est pas decoratif : une largeur en ch seule deborde
       des l'ecran etroit (62ch valent ~620 px, un telephone en fait 390). */
    --measure: min(62ch, 100%);
    --radius: 3px;
    --radius-lg: 6px;

    /* --- Espacement : huit pas sur une base de 4 px ----------------------
       Charte complete : docs/architecture/17-charte-visuelle.md.

       Ces huit pas couvrent 41 des 86 occurrences d'espacement en rem du
       fichier, soit 48 %. Le reste est laisse LITTERAL a dessein, et il faut
       dire pourquoi : de .15rem a .9rem la feuille occupe tous les echelons
       d'une rampe continue de .05rem (0,8 px) sans un seul trou. Quinze paires
       adjacentes, chacune a 0,8 px de sa voisine. Aligner .7rem sur .75rem
       deplace un pixel — ce qui est peut-etre souhaitable, mais c'est une
       decision de rendu, pas de refactorisation. Elle se prend a la refonte
       visuelle, ou le changement se voit et s'assume.

       La preuve que cette passe n'a rien deplace est algebrique et non
       visuelle : re-expanser chaque var(--sp-n) en sa valeur litterale redonne
       le fichier d'avant, octet pour octet. Le depot n'a ni navigateur sans
       tete ni capture de reference — c'etait la seule verification possible,
       et elle est plus forte qu'une comparaison d'images. */
    --sp-1: .25rem;
    --sp-2: .5rem;
    --sp-3: .75rem;
    --sp-4: 1rem;
    --sp-5: 1.25rem;
    --sp-6: 1.5rem;
    --sp-7: 1.75rem;
    --sp-8: 2rem;

    /* --- Typographie : deux registres, parce que le fichier en a deux -----
       Six pas FIXES pour l'interface. Il faut l'avouer : ce n'est pas une
       echelle, c'est un amas. Les six tiennent dans 11,84 -> 16,8 px, avec des
       rapports successifs de 1,054 / 1,051 / 1,037 / 1,118 / 1,105, la ou une
       echelle digne du nom demande au moins 1,125. --fs-3 et --fs-4 sont
       separes de 0,48 px, que personne ne voit.

       La raison est structurelle : TOUTES les grandes tailles du site vivent
       dans un clamp(), donc hors de portee d'une echelle statique. Le vrai
       rythme typographique du site est FLUIDE. Une refonte qui poserait six
       pas fixes en croyant tenir la typographie ne tiendrait que les petites
       tailles, c'est-a-dire rien de ce qui se voit. D'ou la seconde famille
       ci-dessous, nommee par role et non par rang — ce ne sont pas des pas
       d'une echelle, ce sont sept decisions distinctes. */
    --fs-1: .74rem;
    --fs-2: .78rem;
    --fs-3: .82rem;
    --fs-4: .85rem;
    --fs-5: .95rem;
    --fs-6: 1.05rem;

    --fs-body: clamp(1rem, .96rem + .2vw, 1.075rem);
    --fs-lede: clamp(1.075rem, 1rem + .5vw, 1.3rem);
    --fs-h3-fluid: clamp(1.05rem, .95rem + .4vw, 1.28rem);
    --fs-faction: clamp(1.45rem, 1.1rem + 1vw, 2rem);
    --fs-h2: clamp(1.5rem, 1.25rem + 1vw, 2.05rem);
    --fs-h1: clamp(2.05rem, 1.35rem + 3.4vw, 4.75rem);
    --fs-hero: clamp(2.4rem, 1.2rem + 4.4vw, 4.4rem);

    /* Mouvement */
    --ease: cubic-bezier(.2, .7, .3, 1);
    --fast: 140ms;
    --slow: 260ms;

    /* Ombres teintees, jamais du noir pur : une ombre neutre sur un fond chaud
       se lit comme une tache grise. Le jeton --lift, mort depuis sa creation
       (zero usage), a ete supprime plutot que repeint. */
    --lift-accent: 0 8px 28px -10px rgba(255, 106, 26, .45);
}

/* --- Decor de l'accueil : trou noir et galaxie ---------------------------- */
/* Repris du site d'origine. Le conteneur est rendu VIDE par Razor ; le script
   wwwroot/js/decor-accueil.js y insere deux <canvas> s'il peut s'executer.

   La regle qui gouverne ce bloc : « le contenu est rendu par le serveur, le
   decor s'ajoute par-dessus s'il peut ». Si le JavaScript ne tourne pas, si
   WebGL est indisponible, ou si le fichier n'arrive jamais, le conteneur reste
   vide et le fond etoile de body::before assure seul l'ambiance. Aucun texte,
   aucun lien, aucune information ne dependent de ce script.

   z-index -1 et non 0 : le decor doit passer DERRIERE tout le contenu sans
   dependre de l'ordre du DOM. L'empilement complet de la page, du fond vers
   l'avant : body::before (-2, etoiles) -> .home-space (-1, decor) ->
   body::after (-1, grain, peint apres car pseudo-element tardif) -> main. */
.home-space {
    position: fixed;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    pointer-events: none;
}

/* ⚠️ SPECIFICITE VOLONTAIRE : `body .home-space` (0,1,1) et non `.home-space`
   (0,1,0). origine.css est chargee APRES celle-ci et redefinit
   `.home-space { z-index: 0 }`. A specificite egale, c'est la derniere chargee
   qui gagne — et un element `position: fixed` en z-index 0 se peint APRES le
   contenu en flux.

   Le canvas est cree avec `{alpha: false}` : il est OPAQUE. Le resultat mesure
   etait que le trou noir recouvrait TOUT le contenu des huit pages internes —
   sur la page de telechargement, le titre et le bouton disparaissaient, tout en
   restant cliquables puisque le conteneur porte `pointer-events: none`. Le mode
   de panne le plus couteux : tout a l'air de marcher.

   Ne PAS corriger dans origine.css : compiler-origine.py l'ecrase a chaque
   regeneration. La correction doit vivre ici, avec une specificite qui survit. */
body .home-space {
    z-index: -1;
}

.home-space .bh-gl,
.home-space .galaxy {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    pointer-events: none;
}

/* --- L'arrivée sur une page : AUCUNE animation, et c'est voulu ------------- */
/* ⚠️ Ne pas remettre d'animation d'opacité sur `.site` (le <body>).
   Jusqu'au 2026-10-13, `.site { animation: page-rise .55s … both }` faisait
   partir tout le document de `opacity: 0`, pour imiter l'`.is-entering` du
   site d'origine, qui navigue sans recharger.

   Le coût, mesuré : la première peinture se fait à opacité nulle, puis
   l'opacité monte sur le compositeur sans nouvelle peinture. Chrome n'enregistre
   alors JAMAIS de First Contentful Paint ni de Largest Contentful Paint sur les
   pages internes — relevé le 2026-10-13 sur spacefate.com, Chrome 155 :
   téléchargement, mentions légales et confidentialité sans FCP ni LCP, décor
   chargé ou bloqué. Lighthouse et PageSpeed y échouent, et les Core Web Vitals
   de ces pages n'existent pas. Mesure de la branche perf/site-plus-rapide
   (2026-09-29) : retirer cette seule animation rend un FCP de 0,4 à 1,0 s.

   Écartée aussi : une montée par `transform` au lieu de l'opacité. Un
   `transform` sur le <body>, même `translateY(0)` gardé par `both`, en fait le
   bloc conteneur de tout descendant `position: fixed` — le décor et les écrans
   de l'accueil — et les décrocherait de la fenêtre.

   La règle `.site.is-entering` d'origine.css reste inerte : aucun script ne
   pose cette classe. CssInvariantsTests garde ce retrait. */

/* --- L'accueil, le temps que le script du décor décide -------------------- */
/* Sans JavaScript, l'accueil se lit en colonne : les six écrans l'un sous
   l'autre (`.home:not(.js-step)`). Avec JavaScript, le script du décor pose
   `.js-step` et les range en diaporama — mais il est chargé en `defer`, et la
   première peinture peut le devancer : la colonne s'affiche, puis saute en
   diaporama. L'animation d'opacité du corps, retirée ci-dessus, le masquait.
   Mesuré le 2026-10-13 en local, une fois l'animation retirée et sans cette
   règle : décalage de mise en page (CLS) de 0,957 à l'arrivée sur l'accueil,
   contre 0 avant.

   Le contenu de l'accueil reste donc caché tant que le script n'a pas décidé :
   il pose `.js-step` (diaporama) ou `.js-lineaire` (mouvement réduit, navigateur
   piloté, colonne gardée). Le pied de page aussi : en colonne il suit les six
   écrans, sous le bord de la fenêtre ; en diaporama il se fixe en bas — caché
   seul le contenu, son saut faisait encore un CLS de 0,108 (mesuré le même
   jour). `visibility` et non `opacity` : un élément ainsi caché n'est ni peint
   ni compté dans les décalages, et ce n'est pas un mouvement — le bloc
   « Mouvement réduit » n'a rien à en neutraliser.

   Deux filets : sans JavaScript (`scripting: none`), rien n'est caché ; et si le
   script n'arrive jamais (réseau, blocage), l'animation à retardement rend la
   colonne au bout de 2 s. Un navigateur qui ignore `scripting` (avant Chrome
   120, Firefox 113, Safari 17) ne cache rien : il retrouve le saut, rien de pire. */
@media (scripting: enabled) {
    .home:not(.js-step):not(.js-lineaire) :is(.home-content, .site-footer) {
        animation: accueil-en-attente 0s 2s both;
    }
}

@keyframes accueil-en-attente {
    from { visibility: hidden; }
    to { visibility: visible; }
}

/* --- La lisibilite des ecrans, sans plaque ------------------------------- */
/* ⚠️ Le pire pixel du decor n'est PAS un ambre sombre : c'est du BLANC PUR. Le
   tone mapping ACES du shader sature des que le canal lineaire depasse 7,24, et
   le disque y monte a 14 — sur les trois canaux a la fois du cote qui
   s'approche. Toute la charte de ce site a ete mesuree sur un fond de luminance
   0,0079 ; le pire pixel reel de l'accueil vaut 1,0000. Facteur 127.

   Une premiere version posait sous chaque ecran une plaque rgba(5,4,3,.88), qui
   ramenait tous les couples au-dessus de AA. Elle a ete RETIREE : elle se voyait
   — un ovale noir au milieu de la scene — et elle defigurait le decor, ce que ce
   portage cherche precisement a servir.

   Ce qui la remplace : une ombre portee en trois couches, qui epouse la forme
   des lettres au lieu de poser une boite.

   ⚠️ C'EST MOINS SUR, ET IL FAUT LE SAVOIR. Une ombre regagne environ 0,4 point
   de contraste la ou la plaque en regagnait treize. Tant qu'un texte repose sur
   l'ombre du trou noir — la zone noire du centre, ou il se trouve aux six
   ecrans — il reste largement lisible. S'il venait a deborder sur le disque, il
   redeviendrait difficile a lire, et aucune couleur de la palette ne
   rattraperait cela : #f4f1ee est deja l'encre la plus claire du site. C'est un
   arbitrage assume en faveur du decor. */
.home.js-step .warp__in :is(h2, h3, p, span, a, dt, dd, time) {
    text-shadow:
        0 0 .25rem rgba(5, 4, 3, .95),
        0 0 .6rem rgba(5, 4, 3, .9),
        0 0 1.5rem rgba(5, 4, 3, .85);
}

/* ⚠️ Le decalage vers la gauche vient du letter-spacing, pas du centrage.
   `letter-spacing: .14em` pose un espace APRES chaque lettre, y compris la
   derniere : chaque entree est donc plus large que son texte visible, d'un
   quart de cadratin a droite. Sur cinq entrees centrees, ces cinq espaces
   fantomes s'additionnent et poussent l'ensemble vers la gauche.
   La marge negative annule exactement l'espace de la derniere lettre. C'est la
   contrepartie connue du letter-spacing : le centrage optique n'est pas le
   centrage geometrique. */
.home.js-step .home-logo__menu-item {
    margin-inline-end: -.14em;
    text-shadow:
        0 0 .25rem rgba(5, 4, 3, .95),
        0 0 .5rem rgba(5, 4, 3, .9),
        0 0 1.25rem rgba(5, 4, 3, .8);
}

/* --- L'accueil n'a pas de barre de navigation ----------------------------- */
/* Sur l'accueil, la navigation EST le menu central du logo : une barre en haut
   ferait double emploi avec lui, et elle mangeait le cadre de la scene.

   L'element reste dans le document, il est seulement masque a l'oeil. Il porte
   `data-region="header"`, que les tests visent pour verifier qu'une page porte
   bien la chrome du site — le retirer du balisage les ferait echouer, et pour
   une bonne raison : les huit autres pages en ont besoin. */
.home .site-header {
    display: none;
}

/* Le menu est borne par `width: 96%` du logo, soit 96 % de min(80vmin, 800px).
   En capitales espacees, « TELECHARGER » et « ACTUALITES » y tiennent mal des
   que la fenetre est basse — vmin suit la plus PETITE dimension, donc un ecran
   large et court retrecit le logo alors qu'il reste de la place a l'horizontale.
   On delie donc le menu du logo, mais on le RESSERRE : 46rem, c'est la largeur
   ou les cinq entrees tiennent sur une ligne sans s'etaler. Un menu qui occupe
   toute la scene se lit comme une barre de navigation ; groupe au centre, il se
   lit comme la signature du logo — ce qu'il est. */
.home.js-step .home-logo__menu {
    width: min(92vw, 46rem);
    gap: .4rem clamp(.7rem, 1.6vmin, 1.3rem);
}

/* --- Ce que le site d'origine posait en attribut de style ----------------- */
/* Trois endroits de son accueil portaient un `style="..."` en ligne. La politique
   de securite de contenu de ce site l'interdit : un seul attribut de style et la
   page sort NUE, en 200 OK, sans erreur serveur ni test rouge. C'est le mode de
   panne le plus couteux du projet — tout a l'air de marcher.
   Les trois sont repris ici, sous forme de selecteurs. */

/* 1. Les couleurs de faction. Le site d'origine ecrivait
      style="--fc:#f0c463;--fc-glow:rgba(240,196,99,.5)" sur chaque carte.
      Les valeurs viennent de son src/content/factions.ts, ou elles sont
      « calquees sur le logo de chaque faction » — c'est sa formule.
      Bonne surprise : --fed et --pirate de notre charte SONT deja exactement
      ces couleurs, puisque les deux viennent de la meme source. */
/* ⚠️ SPECIFICITE VOLONTAIRE : `body .fband--x` (0,1,1) et non `.fband--x`
   (0,1,0). origine.css est chargee APRES celle-ci et pose
   `.fband { --fc: var(--hud-orange); --fc-glow: var(--hud-orange-glow) }`.
   A specificite egale, c'est la derniere chargee qui gagne — et les trois
   factions se peignaient donc en AMBRE, halo compris, au lieu de porter chacune
   sa couleur.

   C'est la deuxieme fois que ce piege se presente, apres le z-index du decor :
   toute regle de ce fichier qui doit survivre a origine.css a besoin d'un cran
   de specificite en plus. Corriger dans origine.css ne servirait a rien,
   compiler-origine.py l'ecrase a chaque regeneration.

   Les valeurs viennent de src/content/factions.ts du site d'origine, ou elles
   sont « calquees sur le logo de chaque faction » — c'est sa formule. --fed et
   --pirate de notre charte SONT deja exactement ces couleurs, les deux venant
   de la meme source. */
body .fband--cyborg {
    --fc: var(--cyborg);
    --fc-glow: rgba(79, 176, 255, .55);
}

body .fband--federation {
    --fc: var(--fed);
    --fc-glow: rgba(240, 196, 99, .50);
}

body .fband--pirate {
    --fc: var(--pirate);
    --fc-glow: rgba(255, 59, 72, .50);
}

/* 2. L'apparition en cascade des entrees du menu central. Le site d'origine
      ecrivait style="--i:0", style="--i:1"… et calculait
      `transition-delay: calc(var(--i) * 120ms + 400ms)`.
      Le rang d'un element est une information que le CSS sait lire tout seul :
      cinq :nth-child rendent l'attribut inutile. Si une sixieme entree est
      ajoutee au menu, elle apparaitra sans delai plutot que de casser — ajouter
      alors sa ligne ici. */
.home .home-logo__menu-item:nth-child(1) { --i: 0; }
.home .home-logo__menu-item:nth-child(2) { --i: 1; }
.home .home-logo__menu-item:nth-child(3) { --i: 2; }
.home .home-logo__menu-item:nth-child(4) { --i: 3; }
.home .home-logo__menu-item:nth-child(5) { --i: 4; }

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

* { margin: 0; padding: 0; }

html {
    scroll-behavior: smooth;
    -webkit-text-size-adjust: 100%;
    /* Garde-fou : aucune cause de debordement horizontal ne doit produire un
       defilement lateral sur telephone. Les causes sont traitees en dessous
       (min-width: 0 sur les elements flex, largeurs en ch plafonnees) ; ceci
       n'est que la ceinture apres les bretelles. */
    overflow-x: hidden;
}

body {
    position: relative;
    min-height: 100dvh;
    background: var(--void);
    color: var(--text);
    font-family: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
    font-size: var(--fs-body);
    font-weight: 400;
    line-height: 1.65;
    letter-spacing: .005em;
    font-synthesis-weight: none;
    text-rendering: optimizeLegibility;
    display: flex;
    flex-direction: column;
}

/* ⚠️ TROISIEME CRAN DE SPECIFICITE, ET LE PLUS COUTEUX DES TROIS.

   origine.css:19-32 porte le reset plein ecran de la SPA d'origine :

       body { display:flex; justify-content:center; align-items:center;
              height:100vh; width:100vw; overflow-x:hidden }

   Elle est chargee APRES celle-ci (_Layout.cshtml:41 puis :47) et son selecteur
   est `body`, exactement comme le notre : a specificite egale, la derniere
   chargee gagne. Cinq declarations passent donc, dont quatre sont ruineuses.

   CE QUE CA FAISAIT, MESURE. Le body devenait une boite de 100vh en colonne,
   centree verticalement. Tout contenu plus haut que l'ecran debordait DES DEUX
   COTES a parts egales — et le debordement par le bord de DEBUT d'un conteneur
   centre n'est joignable par AUCUN defilement : scrollTop se borne a 0.

   Sur /fr/jeu en 1280x800, le <h1> « Le jeu » etait hors cadre, le chapo coupe
   en son milieu et peint SOUS la barre de navigation. Sur les huit pages
   internes, au bureau comme au telephone, pour tout visiteur, en permanence.
   `html { overflow-x: hidden }` (l.403-411 ci-dessus) empeche la propagation du
   debordement du body vers la zone d'affichage : le body restait donc bien un
   conteneur de defilement a hauteur fixe, et non un simple element qui pousse
   la page.

   POURQUOI PAS DANS compiler-origine.py. Son tuple NEUTRALISER vaut
   (position, inset, overflow, z-index, display, flex-direction) et ne vise que
   `.site` : ni `height`, ni `width`, ni `justify-content`, ni `align-items`
   n'y figurent, et sa substitution `overflow\s*:` ne reconnait meme pas
   `overflow-x:`. Le script imprime « body : display neutralises » et ne change
   rien. La correction doit vivre ici, comme les deux precedentes.

   POURQUOI `:not(.js-step)`. L'accueil en mode diaporama repose justement sur un
   body clippe de 100vh et des `.warp__pin` en `position: fixed` : lui rendre
   `height: auto` casserait le mode. `.home.js-step` (0,2,0) bat `body` (0,0,1)
   pour son propre `overflow: hidden`, mais PAS pour les quatre autres — d'ou la
   garde explicite plutot qu'une confiance dans la cascade.
   Effet de bord VOULU : l'accueil SANS JavaScript n'a pas la classe `js-step`,
   il est donc repare lui aussi (le <main> passait de y=-1108 a y=0).

   `align-items: stretch` rend l'en-tete et le pied de page pleine largeur au
   lieu de les laisser en boite centree — c'est l'intention de `.site-header`
   (sticky + filet horizontal), mais c'est un changement visible au bureau sur
   les neuf pages : il est assume, pas subi.

   On ne redeclare PAS `overflow-x` : `hidden` reste juste une fois la hauteur
   rendue automatique, et `clip` ferait calculer `overflow-y` en `clip` au lieu
   de `auto`, ce qui reintroduirait un piege de defilement d'une autre nature. */
body.site:not(.js-step) {
    height: auto;
    width: auto;
    justify-content: flex-start;
    align-items: stretch;
}

/* Fond etoile, repris tel quel du site d'origine (.site__bg).
   Six etoiles en points radiaux, un halo ambre au sommet, un degrade radial qui
   descend vers le noir. AUCUNE IMAGE : les deux fichiers nebula-900/1600.webp
   qu'il remplace ne servaient qu'a cela.

   Le gain n'est pas le poids — ils pesaient 10 814 octets a eux deux, et ce
   bloc de gradients en rend 4 557 au CSS, donc environ 6 Ko nets. Le gain est
   ailleurs, et il compte plus :
   - DEUX REQUETES EN MOINS, et un fond qui existe des la premiere peinture au
     lieu d'attendre un aller-retour reseau ;
   - le fond n'est plus COMMUTE PAR MEDIA QUERY sur un jeton (--ambient), un
     montage qu'aucun audit d'images ne pouvait voir puisqu'il vivait derriere
     une variable, et qui doublait silencieusement le cout sur telephone si l'on
     se trompait de texture ;
   - le pire pixel du fond compose descend de #1f2839 a #221209, et TOUS les
     contrastes y gagnent : le texte passe de 12,38 a 16,11:1, le secondaire de
     5,31 a 7,48:1, l'accent de 4,62 a 6,33:1.

   Le halo ambre est a .10, exactement comme le site d'origine. Le seuil de
   rupture a ete cherche, pas suppose : a .14 tous les couples tiennent encore,
   a .15 le texte tertiaire tombe a 4,47:1. La marge est donc de QUATRE points
   d'opacite, contre trois sur l'ancienne charte bleue.
   Se verifie a tout moment :
     python infra/scripts/contraste-wcag.py --halo .15 */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -2;
    pointer-events: none;
    background:
        radial-gradient(1.5px 1.5px at 14% 22%, rgba(255, 255, 255, .8), transparent 60%),
        radial-gradient(1.5px 1.5px at 82% 18%, rgba(255, 255, 255, .7), transparent 60%),
        radial-gradient(1px 1px at 64% 70%, rgba(255, 255, 255, .55), transparent 60%),
        radial-gradient(1px 1px at 30% 78%, rgba(255, 255, 255, .5), transparent 60%),
        radial-gradient(1px 1px at 48% 36%, rgba(255, 255, 255, .45), transparent 60%),
        radial-gradient(1.5px 1.5px at 90% 60%, rgba(255, 255, 255, .55), transparent 60%),
        radial-gradient(130% 70% at 50% 0%, rgba(255, 106, 26, .10), transparent 55%),
        radial-gradient(140% 120% at 50% 0%, #0a0807 0%, #050404 55%, #000 100%);
}

/* Grain : casse la platitude du numerique. Data-URI, donc conforme a la CSP. */
body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: .028;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --- Typographie --------------------------------------------------------- */
h1, h2, h3 {
    font-weight: 600;
    line-height: 1.08;
    letter-spacing: -.03em;
    text-wrap: balance;
    /* Filet de securite : un mot long ne doit jamais pousser la page hors cadre. */
    overflow-wrap: break-word;
}

h1 {
    font-size: var(--fs-h1);
    font-weight: 700;
    letter-spacing: -.042em;
    margin-bottom: var(--sp-5);
}

h2 {
    font-size: var(--fs-h2);
    margin-bottom: var(--sp-3);
}

h3 {
    font-size: 1.15rem;
    letter-spacing: -.018em;
}

p { text-wrap: pretty; }

a { color: var(--accent); }

code, .mono, time {
    font-family: "Cascadia Mono", ui-monospace, "SF Mono", Consolas, monospace;
    font-variant-numeric: tabular-nums;
    letter-spacing: -.01em;
}

/* --- Accessibilite ------------------------------------------------------- */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    background: var(--accent);
    color: var(--on-accent);
    padding: .7rem 1.2rem;
    font-weight: 600;
    text-decoration: none;
    border-radius: 0 0 var(--radius) 0;
}

.skip-link:focus { left: 0; }

/* « Focalise = sous tension ». L'anneau est ambre et double d'un halo, repris
   du site d'origine qui le centralise pour toutes ses vues avec le meme
   rationnel. L'outline-offset le detache du bord de l'element : c'est ce que
   WCAG 2.2 demande au critere de focus non obscurci (SC 2.4.11), un anneau
   colle au bord disparaissant sous le premier element qui le recouvre d'un
   pixel. Le halo, lui, le rend visible sur un fond clair comme sur le noir. */
:focus-visible {
    outline: 2px solid var(--accent-lit);
    outline-offset: 3px;
    border-radius: 2px;
    box-shadow: 0 0 .75rem var(--accent-glow);
}

/* --- En-tete ------------------------------------------------------------- */
.site-header {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-4) var(--gutter);
    padding: .9rem var(--gutter);
    background: rgba(6, 5, 4, .72);
    backdrop-filter: blur(14px) saturate(1.4);
    border-bottom: 1px solid var(--line);
}

.brand {
    display: inline-flex;
    align-items: baseline;
    gap: var(--sp-2);
    font-size: var(--fs-6);
    font-weight: 700;
    letter-spacing: .16em;
    color: var(--text);
    text-decoration: none;
    white-space: nowrap;
}

.brand .glyph {
    color: var(--accent);
    font-size: .85em;
    transition: color var(--fast) var(--ease);
}

.brand:hover .glyph { color: var(--danger); }

.site-header nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-7);
    margin-right: auto;
}

.site-header nav a {
    position: relative;
    padding: .35rem 0;
    color: var(--text-dim);
    font-size: var(--fs-5);
    text-decoration: none;
    transition: color var(--fast) var(--ease);
}

/* Le soulignement se deploie depuis la gauche — transform seulement, pas de
   width, pour rester sur le compositeur. */
.site-header nav a::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 1px;
    background: var(--accent);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--slow) var(--ease);
}

.site-header nav a:hover { color: var(--text); }
.site-header nav a:hover::after { transform: scaleX(1); }

.site-header nav a[aria-current="page"] { color: var(--text); }
.site-header nav a[aria-current="page"]::after { transform: scaleX(1); }

/* Metaphore du site d'origine, reprise ici : un element interactif est ETEINT au
   repos et s'allume quand on le touche. La bordure au repos est donc l'accent
   attenue plutot qu'un gris qui ne dit rien de sa nature cliquable : 1,69:1 sur
   le fond, contre 1,16:1 pour un filet neutre.
   ⚠️ Ces deux valeurs sont sous le seuil de 3:1 de SC 1.4.11, et c'est assume :
   la bordure est DECORATIVE, l'element reste identifie par son libelle « EN » /
   « FR » et par son aria-label. Le jour ou une bordure portera seule une
   information — un champ en erreur, un etat actif — il lui faudra --accent nu,
   a 7,11:1. */
.lang-switch {
    padding: .3rem .7rem;
    border: 1px solid var(--accent-dim);
    border-radius: var(--radius);
    color: var(--text-dim);
    font-size: var(--fs-2);
    font-weight: 600;
    letter-spacing: .1em;
    text-decoration: none;
    transition: border-color var(--fast) var(--ease), color var(--fast) var(--ease);
}

.lang-switch:hover {
    border-color: var(--accent);
    color: var(--text);
}

/* ⚠️ Dès 1080 px, origine.css:803-804 change l'en-tête en grille
   `1fr auto 1fr` : la marque et le sélecteur de langue deviennent des
   éléments de grille, dont l'alignement par défaut est `stretch`. Mesuré le
   2026-10-13 sur main, page de téléchargement : le cadre « EN » s'étirait sur
   511 px en 1366×768 et 788 px en 1920×1080, avec le libellé collé à gauche ;
   la zone cliquable du logo couvrait toute la première colonne (511 et 788 px)
   sans que rien ne le montre. origine.css prévoit bien `justify-self: start`,
   mais sur `.site-header__brand`, une classe que notre balisage n'emploie pas.
   Sous 1080 px l'en-tête repasse en flex (origine.css:1001) : ces deux
   déclarations y sont sans effet, et le `margin-left: auto` du téléphone
   continue de pousser la langue à droite. Premier relevé : branche
   perf/site-plus-rapide, 2026-09-29. */
.site-header > .brand { justify-self: start; }
.site-header > .lang-switch { justify-self: end; }

/* --- Grille -------------------------------------------------------------- */
/* Grille a colonnes nommees plutot qu une simple boite centree : elle donne une
   colonne "content" bornee ET une colonne "full" pleine largeur, ce qui permet
   au hero cinematique de deborder sans recourir a 100vw (qui compte la barre de
   defilement et cree un debordement horizontal reel). */
main {
    flex: 1;
    /* min-width: 0 est indispensable : un element flex vaut min-width: auto par
       defaut, donc il refuse de retrecir sous la taille de son contenu et pousse
       toute la page hors cadre sur petit ecran. */
    min-width: 0;
    width: 100%;
    display: grid;
    align-content: start;
    grid-template-columns:
        [full-start] minmax(var(--gutter), 1fr)
        [content-start] min(72rem, 100% - var(--gutter) * 2) [content-end]
        minmax(var(--gutter), 1fr) [full-end];
    /* Bas plus genereux que le haut : compensation optique. */
    padding-block: clamp(3rem, 7vw, 6rem) clamp(5rem, 10vw, 8rem);
}

main > * { grid-column: content; }
main > .bleed { grid-column: full; }

/* Un element de grille est etire sur toute sa colonne par defaut. Pour un bloc
   de texte c'est ce qu'on veut ; pour un bouton enfant direct de main, cela
   produit une barre pleine largeur. On les laisse a leur taille naturelle —
   la regle petit ecran qui les repasse a 100 % continue de s'appliquer. */
main > .cta,
main > .download-btn { justify-self: start; }

/* Le hero cinematique se colle a l en-tete : la marge haute de main le
   decollerait du cadre et casserait l effet plein ecran. */
main:has(> .hero-cine) { padding-top: 0; }

main > p,
main > ol,
main > ul { max-width: var(--measure); }

.lede {
    max-width: min(46ch, 100%);
    margin-bottom: 2.5rem;
    color: var(--text-dim);
    font-size: var(--fs-lede);
    line-height: 1.55;
}

/* --- Accueil ------------------------------------------------------------- */
/* La mise en forme du hero vit plus bas, avec le bloc cinematique. */
.hero-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--sp-4) var(--sp-6);
}

/* Sans min-width: 0, le bouton et la pastille refusent de retrecir et sortent
   du cadre au lieu de passer a la ligne. */
.hero-actions > * { min-width: 0; }

/* --- Actions ------------------------------------------------------------- */
.cta,
.download-btn {
    display: inline-flex;
    align-items: center;
    gap: .6rem;
    padding: .85rem 1.7rem;
    background: var(--accent);
    color: var(--on-accent);
    font-weight: 600;
    font-size: 1rem;
    letter-spacing: -.005em;
    text-decoration: none;
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    box-shadow: var(--lift-accent);
    transition: transform var(--fast) var(--ease),
                background-color var(--fast) var(--ease),
                box-shadow var(--fast) var(--ease);
}

.cta:hover,
.download-btn:hover {
    background: var(--accent-lit);
    transform: translateY(-2px);
    box-shadow: 0 14px 34px -10px var(--accent-glow);
}

/* Retour physique au clic. */
.cta:active,
.download-btn:active {
    transform: translateY(0);
    box-shadow: 0 4px 14px -8px var(--accent-glow);
}

.download-btn {
    flex-direction: column;
    align-items: flex-start;
    gap: .15rem;
    padding: var(--sp-4) var(--sp-8);
    margin-bottom: var(--sp-7);
}

.download-btn .size {
    font-family: "Cascadia Mono", ui-monospace, Consolas, monospace;
    font-size: var(--fs-2);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
    opacity: .8;
}

/* --- Statut serveur ------------------------------------------------------ */
.server-status {
    display: inline-flex;
    align-items: center;
    gap: .55rem;
    padding: .4rem .9rem;
    border: 1px solid var(--line);
    border-radius: 100px;
    background: rgba(12, 11, 10, .6);
    color: var(--text-dim);
    font-size: var(--fs-3);
    letter-spacing: .01em;
}

.server-status::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 22%, transparent);
}

.server-status.status-online { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 32%, var(--line)); }
.server-status.status-degraded { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 32%, var(--line)); }
.server-status.status-unknown { color: var(--text-faint); }

/* --- Panneaux ------------------------------------------------------------ */
/* Pas de « bordure + ombre + fond » sur tout. Un filet vertical ambre marque
   le bord d'attaque, le fond reste presque invisible. */
.pillars {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
    gap: 1px;
    /* La derniere rangee est complete meme quand le nombre de panneaux ne tombe
       pas juste : voir la regle nth-child(5):last-child plus bas. */
    margin-top: var(--sp-4);
    background: var(--line);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.pillars article {
    position: relative;
    padding: 1.9rem var(--sp-7);
    background: var(--surface);
    transition: background-color var(--slow) var(--ease);
}

.pillars article::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 2px;
    background: linear-gradient(to bottom, var(--accent), transparent);
    opacity: 0;
    transition: opacity var(--slow) var(--ease);
}

.pillars article:hover { background: var(--surface-2); }
.pillars article:hover::before { opacity: 1; }

/* Cinq panneaux sur trois colonnes laissaient une cellule vide, qui se lisait
   comme un bug de mise en page. Le dernier occupe la place restante. La
   condition nth-child(5):last-child limite la regle au cas a cinq : si un
   sixieme panneau est ajoute, la grille redevient pleine d'elle-meme. */
@media (min-width: 60rem) {
    .pillars { grid-template-columns: repeat(3, 1fr); }
    .pillars article:nth-child(5):last-child { grid-column: span 2; }
}

.pillars h2 {
    font-size: var(--fs-6);
    font-weight: 600;
    letter-spacing: -.01em;
    margin-bottom: .55rem;
}

.pillars p {
    color: var(--text-dim);
    font-size: var(--fs-5);
    line-height: 1.6;
}

/* Troisieme niveau d'action, pour eviter le duo « bouton plein + bouton
   fantome » sur chaque bloc. La fleche avance au survol. */
.quiet-link {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    margin-top: 1.1rem;
    color: var(--accent);
    font-size: .9rem;
    font-weight: 500;
    text-decoration: none;
}

.quiet-link::after {
    content: "→";
    transition: transform var(--slow) var(--ease);
}

.quiet-link:hover { color: var(--text); }
.quiet-link:hover::after { transform: translateX(4px); }

/* --- FAQ ----------------------------------------------------------------- */
.faq {
    display: flex;
    flex-direction: column;
    max-width: var(--measure);
    border-top: 1px solid var(--line);
}

.faq details {
    border-bottom: 1px solid var(--line);
    transition: background-color var(--fast) var(--ease);
}

.faq details[open] { background: rgba(12, 11, 10, .5); }

.faq summary {
    display: flex;
    align-items: baseline;
    gap: .9rem;
    padding: 1.15rem var(--sp-2);
    font-weight: 500;
    cursor: pointer;
    list-style: none;
    transition: color var(--fast) var(--ease);
}

.faq summary::-webkit-details-marker { display: none; }

/* Chevron maison : pas de puce native, et il pivote. */
.faq summary::before {
    content: "";
    flex: none;
    width: .45rem;
    height: .45rem;
    margin-top: .45rem;
    border-right: 1.5px solid var(--accent);
    border-bottom: 1.5px solid var(--accent);
    transform: rotate(-45deg);
    transition: transform var(--slow) var(--ease);
}

.faq details[open] summary::before { transform: rotate(45deg); }

.faq summary:hover { color: var(--accent); }

.faq p {
    padding: 0 var(--sp-2) 1.3rem 1.85rem;
    color: var(--text-dim);
    font-size: var(--fs-5);
}

/* --- Devlog -------------------------------------------------------------- */
.news-item {
    max-width: var(--measure);
    padding: 1.85rem 0;
    border-bottom: 1px solid var(--line);
}

.news-item h2 {
    font-size: 1.2rem;
    margin-bottom: .35rem;
}

.news-item time {
    display: block;
    margin-bottom: .8rem;
    color: var(--text-faint);
    font-size: var(--fs-2);
    letter-spacing: .06em;
    text-transform: uppercase;
}

.news-item p { color: var(--text-dim); }

/* --- Pages legales ------------------------------------------------------- */
.legal {
    display: flex;
    flex-direction: column;
    gap: 1.4rem;
    max-width: var(--measure);
}

.legal p {
    color: var(--text-dim);
    font-size: var(--fs-5);
}

/* --- Pied de page -------------------------------------------------------- */
.site-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-3) var(--sp-8);
    padding: 2.25rem var(--gutter);
    border-top: 1px solid var(--line);
}

/* Gris secondaire et non tertiaire : le pied de page n'a pas de fond opaque, ses
   liens reposent donc sur l'ambiance. Le tertiaire y tombait a 3,26:1 — sous le
   seuil AA. Le secondaire tient 5,6:1 et se lit mieux comme un lien. */
.site-footer a {
    color: var(--text-dim);
    font-size: var(--fs-4);
    text-decoration: none;
    transition: color var(--fast) var(--ease);
}

.site-footer a:hover { color: var(--text); }

/* --- Pages d'erreur ------------------------------------------------------ */
.error-page { padding-block: clamp(3rem, 10vw, 7rem); }

.error-page .code {
    display: block;
    margin-bottom: var(--sp-2);
    font-family: "Cascadia Mono", ui-monospace, Consolas, monospace;
    font-size: var(--fs-4);
    letter-spacing: .22em;
    color: var(--danger);
    text-transform: uppercase;
}

/* --- Petits ecrans ------------------------------------------------------- */
/* Le fichier precedent n'avait aucune media query : l'en-tete se comprimait
   sous ~640 px. */
@media (max-width: 46rem) {
    .site-header {
        gap: .65rem var(--sp-4);
        padding: var(--sp-3) var(--gutter);
    }

    .site-header nav {
        order: 3;
        width: 100%;
        gap: var(--sp-1) var(--sp-5);
        margin-right: 0;
    }

    .site-header nav a { font-size: .9rem; }

    .lang-switch { margin-left: auto; }

    .download-btn { width: 100%; }
}

/* --- Mouvement reduit ---------------------------------------------------- */
/* Toute animation ajoutee a ce fichier doit etre neutralisee ici. Le bloc
   couvre les transitions et les animations DECLAREES ; il ne couvre pas ce qui
   bouge sans etre anime, d'ou la regle qui suit. Le site d'origine traite ce
   reglage a quatorze endroits contre un seul ici avant cette PR : c'est le
   point sur lequel il fait franchement mieux, et on le reprend. */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }

    *,
    *::before,
    *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }

    /* Les deux fonds sont en position: fixed. Ils ne sont pas animes, mais ils
       GLISSENT sous le contenu a chaque defilement — c'est exactement la
       parallaxe que ce reglage existe pour supprimer, et aucune declaration
       d'animation ne les couvrait. On les ancre au flux. */
    body::before,
    body::after {
        position: absolute;
    }
}

/* ==========================================================================
   Sections de la page d'accueil
   Ajoutees lors du passage a une vraie page de jeu : hero, factions, gameplay,
   devlog, appel final. Le decoupage suit les codes du genre (BSGO, Star
   Conflict) sans en copier les tics visuels.
   ========================================================================== */

/* --- Rythme de section ---------------------------------------------------- */
.band {
    padding-block: clamp(3.5rem, 9vw, 7rem);
    border-top: 1px solid var(--line);
}

.band:first-of-type { border-top: 0; padding-top: 0; }

/* Etiquette de section : mono, capitales espacees, precedee d un filet.
   Registre "console de bord" plutot que sur-titre marketing. */
.eyebrow {
    display: flex;
    align-items: center;
    gap: .85rem;
    margin-bottom: .9rem;
    font-family: "Cascadia Mono", ui-monospace, Consolas, monospace;
    font-size: .72rem;
    font-weight: 500;
    letter-spacing: .22em;
    text-transform: uppercase;
    color: var(--accent);
}

.eyebrow::before {
    content: "";
    flex: none;
    width: 1.75rem;
    height: 1px;
    background: currentColor;
    opacity: .55;
}

.band > h2 {
    max-width: min(22ch, 100%);
    margin-bottom: var(--sp-4);
}

.band > .lede { margin-bottom: clamp(2rem, 4vw, 3rem); }

/* --- Hero cinematique ----------------------------------------------------- */
/* Plein cadre sur une vraie capture du jeu. C est ce qui separe une page de
   presentation d un site de jeu : l illustration porte, le texte accompagne.
   Le texte occupe la moitie droite parce que la capture place le vaisseau a
   gauche — la composition de l image dicte celle de la page, pas l inverse. */
.hero-cine {
    position: relative;
    isolation: isolate;
    display: grid;
    align-items: end;
    min-height: clamp(28rem, 78vh, 44rem);
    padding-block: clamp(4rem, 10vh, 7rem) clamp(3rem, 7vh, 5rem);
    overflow: hidden;
}

.hero-media {
    position: absolute;
    inset: 0;
    z-index: -2;
}

.hero-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Cadre sur le vaisseau capital : c est le sujet, il doit survivre au
       recadrage en portrait ou l image perd les deux tiers de sa largeur. */
    object-position: 32% 50%;
}

/* Voiles. Le fondu vers le bas n est pas decoratif : sans lui l image se
   termine par une arete nette en travers de la page. Celui de droite creuse le
   contraste sous le texte, la ou le vide spatial est deja sombre. */
.hero-cine::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
        linear-gradient(to top, var(--void) 2%, rgba(6, 5, 4, .55) 30%, transparent 68%),
        linear-gradient(to right, transparent 24%, rgba(6, 5, 4, .62) 58%, rgba(6, 5, 4, .88) 88%),
        linear-gradient(to bottom, rgba(6, 5, 4, .70), transparent 22%);
}

/* Balayage facon ecran de bord. A 5 % il se devine ; au-dela il devient un
   effet de style et salit la capture. */
.hero-cine::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    opacity: .05;
    background: repeating-linear-gradient(
        to bottom,
        rgba(255, 238, 214, .9) 0 1px,
        transparent 1px 4px);
}

.hero-inner {
    width: 100%;
    max-width: 72rem;
    margin-inline: auto;
    padding-inline: var(--gutter);
}

.hero-copy { max-width: min(36rem, 100%); }

@media (min-width: 64rem) {
    /* Le texte bascule a droite : la gauche appartient au vaisseau. */
    .hero-copy { margin-left: auto; }
}

.hero-cine h1 {
    max-width: min(16ch, 100%);
    font-size: var(--fs-hero);
    letter-spacing: -.03em;
    text-wrap: balance;
    /* Ombre portee large et douce : garantit la lisibilite sur les zones
       claires de la capture sans poser de plaque opaque derriere le titre. */
    text-shadow: 0 2px 24px rgba(4, 3, 2, .85);
}

.hero-cine .lede {
    max-width: min(38ch, 100%);
    /* Encre pleine et non attenuee : sur une illustration, le gris secondaire
       tombe sous le seuil de lisibilite. */
    color: var(--text);
    text-shadow: 0 1px 12px rgba(4, 3, 2, .9);
}

.hero-cine .eyebrow { color: var(--text-dim); }

.hero-cine .eyebrow strong {
    color: var(--danger);
    font-weight: 600;
}

/* --- Factions ------------------------------------------------------------- */
/* Le coeur du design : deux moities qui s opposent, OR contre ROUGE. Elles se
   font face — la Federation ancree a gauche, les Pirates a droite — pour que la
   confrontation se lise dans la mise en page et pas seulement dans le texte.
   L'opposition etait bleu contre rouge tant que --fed valait #7fb2ff ; elle est
   passee a l'or avec la charte ambre, parce que l'embleme de la Federation
   affiche juste a cote est argent et or, sans un pixel de bleu. */
.faction-split {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
    gap: 1px;
    background: var(--line);
    border: 1px solid var(--line);
    overflow: hidden;
}

.faction {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    padding: clamp(2rem, 4vw, 3.25rem);
    background: var(--surface);
    isolation: isolate;
    overflow: hidden;
}

/* Halo de faction, sous le contenu. Il s intensifie au survol : la moitie
   survolee s allume, l autre reste en retrait. */
.faction::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    opacity: .55;
    transition: opacity var(--slow) var(--ease);
}

.faction:hover::before { opacity: 1; }

.faction--fed::before {
    background: radial-gradient(32rem 24rem at 30% -8%, rgba(240, 196, 99, .22), transparent 68%);
}

.faction--pirate::before {
    background: radial-gradient(32rem 24rem at 70% -8%, rgba(255, 70, 81, .24), transparent 68%);
}

/* Filet superieur aux couleurs de la faction, oriente vers son bord. */
.faction::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: linear-gradient(to right, var(--fed), transparent 72%);
}

.faction--pirate::after {
    background: linear-gradient(to left, var(--pirate), transparent 72%);
}

.faction img {
    width: clamp(10rem, 21vw, 16rem);
    height: auto;
    margin-bottom: var(--sp-7);
    /* Les sources ont un canal alpha : plus besoin du mix-blend-mode: screen qui
       servait a effacer un fond noir, et qui delavait le blanc de la Federation. */
    filter: drop-shadow(0 10px 30px rgba(4, 3, 2, .9));
    transition: transform var(--slow) var(--ease);
}

.faction:hover img { transform: translateY(-5px) scale(1.03); }

.faction h3 {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--sp-3);
    font-size: var(--fs-faction);
    margin-bottom: var(--sp-2);
}

/* Indicatif de faction en mono, comme un code d escadron. */
.faction .tag {
    font-family: "Cascadia Mono", ui-monospace, Consolas, monospace;
    font-size: .68rem;
    font-weight: 500;
    letter-spacing: .16em;
    padding: .2rem var(--sp-2);
    border: 1px solid currentColor;
    border-radius: var(--radius);
}

.faction--fed h3 { color: var(--fed); }
.faction--pirate h3 { color: var(--pirate); }

.faction p {
    max-width: min(42ch, 100%);
    color: var(--text-dim);
    font-size: .97rem;
}

.faction .role {
    width: 100%;
    margin-top: 1.4rem;
    padding-top: 1.1rem;
    border-top: 1px solid var(--line);
    color: var(--text-faint);
    font-size: var(--fs-4);
}

/* Les deux camps se font face. En dessous de cette largeur les cartes
   s empilent et le miroir n a plus de sens. */
@media (min-width: 56rem) {
    .faction--pirate {
        align-items: flex-end;
        text-align: right;
    }

    .faction--pirate h3 { flex-direction: row-reverse; }
}

/* --- Releve technique ----------------------------------------------------- */
/* Remplace la grille de cartes du gameplay. Avec quatre textes de longueurs
   tres inegales, des cellules cote a cote s alignaient sur la plus haute et
   laissaient de grands vides : la page paraissait cassee. Des lignes pleine
   largeur se dimensionnent sur leur propre contenu, le probleme disparait. */
.readout { border-top: 1px solid var(--line); }

.readout-row {
    display: grid;
    gap: .4rem var(--sp-8);
    padding: clamp(1.4rem, 3vw, 2.1rem) clamp(.75rem, 2vw, 1.25rem);
    border-bottom: 1px solid var(--line);
    transition: background-color var(--slow) var(--ease);
}

@media (min-width: 52rem) {
    .readout-row {
        grid-template-columns: 3.5rem minmax(11rem, 16rem) 1fr;
        align-items: baseline;
    }
}

.readout-row:hover { background: rgba(20, 19, 18, .55); }

.readout-row .idx {
    font-family: "Cascadia Mono", ui-monospace, Consolas, monospace;
    font-size: var(--fs-2);
    font-variant-numeric: tabular-nums;
    letter-spacing: .16em;
    color: var(--accent);
}

.readout-row h3 { font-size: var(--fs-h3-fluid); }

.readout-row p {
    max-width: min(58ch, 100%);
    color: var(--text-dim);
    font-size: .97rem;
}

/* --- Vaisseaux ------------------------------------------------------------ */
/* Quatre classes, telles qu elles existent dans le registre serveur. Les roles
   sont decrits, jamais chiffres : l equilibrage bouge et le site mentirait au
   premier reglage. */
.ship-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
    gap: 1px;
    background: var(--line);
    border: 1px solid var(--line);
}

.ship {
    display: flex;
    flex-direction: column;
    padding: var(--sp-7) var(--sp-6);
    background: var(--surface);
    transition: background-color var(--slow) var(--ease);
}

.ship:hover { background: var(--surface-2); }

.ship .hull {
    margin-bottom: var(--sp-6);
    font-family: "Cascadia Mono", ui-monospace, Consolas, monospace;
    font-size: .7rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: .2em;
    color: var(--text-faint);
}

.ship h3 {
    font-size: 1.12rem;
    margin-bottom: var(--sp-2);
}

.ship .duty {
    margin-bottom: var(--sp-3);
    font-family: "Cascadia Mono", ui-monospace, Consolas, monospace;
    font-size: .7rem;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--accent);
}

.ship p {
    color: var(--text-dim);
    font-size: .93rem;
    line-height: 1.6;
}

/* Armement epingle en bas : les cartes forment une ligne propre quelle que soit
   la longueur du texte au-dessus. */
.ship .arms {
    margin-top: auto;
    padding-top: var(--sp-5);
    border-top: 1px solid var(--line);
    font-family: "Cascadia Mono", ui-monospace, Consolas, monospace;
    font-size: var(--fs-1);
    letter-spacing: .1em;
    color: var(--text-faint);
}

/* --- Apercu du devlog ----------------------------------------------------- */
.devlog-teaser {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
    gap: var(--sp-6);
}

.devlog-card {
    display: flex;
    flex-direction: column;
    padding: 1.6rem var(--sp-6);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    transition: border-color var(--slow) var(--ease), transform var(--slow) var(--ease);
}

.devlog-card:hover {
    border-color: var(--line-lit);
    transform: translateY(-3px);
}

.devlog-card time {
    color: var(--text-faint);
    font-size: var(--fs-1);
    letter-spacing: .12em;
    text-transform: uppercase;
}

.devlog-card h3 {
    margin: .55rem 0 .6rem;
    font-size: var(--fs-6);
}

.devlog-card p {
    color: var(--text-dim);
    font-size: .92rem;
    /* Trois lignes puis coupure : les entrees ont des longueurs tres
       differentes et les cartes doivent rester alignees. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Lien epingle en bas : les cartes forment une ligne propre quel que soit le
   volume de texte au-dessus. */
.devlog-card .quiet-link {
    margin-top: auto;
    padding-top: 1.1rem;
}

/* Lien de renvoi en fin de section. */
.band-more { margin-top: var(--sp-6); }

/* --- Masque a l oeil, lu par les lecteurs d ecran ------------------------ */
/* display:none retirerait l element de l arbre d accessibilite : le titre ne
   serait plus annonce du tout, et aria-labelledby pointerait dans le vide. On
   le sort donc du flux visuel sans le retirer du document. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* --- Fiches de vaisseau detaillees --------------------------------------- */
/* La page Vaisseaux reutilise .ship-grid et .ship de l accueil : une coque doit
   se reconnaitre d une page a l autre. Seules les jauges et le tableau sont
   propres a cette page. */

.ship-detail .ship-stats {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: .35rem .9rem;
    align-items: center;
    margin: var(--sp-4) 0 var(--sp-3);
    padding-top: .9rem;
    border-top: 1px solid var(--line);
}

.ship-detail .ship-stats dt {
    font-size: var(--fs-1);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-faint);
}

.ship-detail .ship-stats dd {
    display: flex;
    align-items: center;
    gap: .6rem;
    margin: 0;
}

/* Les chiffres en tabulaire : sans cela les colonnes tremblent d une ligne a
   l autre, un 1 n ayant pas la largeur d un 8. */
.ship-detail .figure,
.ship-table td {
    font-variant-numeric: tabular-nums;
}

.ship-detail .figure {
    font-size: var(--fs-3);
    color: var(--text-dim);
    white-space: nowrap;
}

/* meter porte sa valeur en ATTRIBUT, jamais en style : la CSP interdit le style
   en ligne, et l element annonce en prime sa valeur aux lecteurs d ecran. Son
   rendu natif differe d un navigateur a l autre, d ou les deux pseudo-elements
   qui suivent — l un pour Firefox, l autre pour les moteurs WebKit/Blink. */
.ship-detail meter {
    flex: 1;
    min-width: 5rem;
    height: .4rem;
    border: 1px solid var(--line-lit);
    background: var(--void);
    appearance: none;
}

.ship-detail meter::-webkit-meter-bar {
    background: var(--void);
    border: 0;
}

.ship-detail meter::-webkit-meter-optimum-value,
.ship-detail meter::-webkit-meter-suboptimum-value,
.ship-detail meter::-webkit-meter-even-less-good-value {
    background: linear-gradient(90deg, var(--accent-deep), var(--accent));
}

.ship-detail meter::-moz-meter-bar {
    background: linear-gradient(90deg, var(--accent-deep), var(--accent));
}

/* --- Tableau comparatif -------------------------------------------------- */
/* Le tableau deborde sur telephone plutot que d etre reflue en cartes : une
   comparaison se lit en colonnes, la casser la rend inutilisable. Le conteneur
   defile donc horizontalement, et lui seul. */
.table-scroll {
    overflow-x: auto;
    margin-top: var(--sp-6);
}

.ship-table {
    width: 100%;
    border-collapse: collapse;
    min-width: 34rem;
}

.ship-table caption {
    text-align: left;
}

.ship-table th,
.ship-table td {
    padding: .7rem .9rem;
    text-align: right;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}

.ship-table thead th {
    font-size: var(--fs-1);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-faint);
    border-bottom-color: var(--line-lit);
}

/* La premiere colonne nomme la coque : elle se lit, elle ne se compare pas. */
.ship-table th[scope="row"],
.ship-table thead th:first-child {
    text-align: left;
    color: var(--text);
    font-weight: 500;
}

.ship-table tbody tr:hover {
    background: var(--accent-veil);
}

.ship-table .note,
.note {
    margin-top: var(--sp-4);
    font-size: var(--fs-3);
    color: var(--text-faint);
}
