/* ═══════════════════════════════════════════════════════════════════
   v0.129 — POLICE DISPLAY SPACE GROTESK (Bloc 10 — 1/4, GDD §22.3)
   ═══════════════════════════════════════════════════════════════════
   Déclarations @font-face self-hosted pour la police display.

   POURQUOI SELF-HOSTED ?
   - Performance : pas de DNS lookup vers fonts.googleapis.com (~150ms)
   - RGPD : Google Fonts CDN log les IP utilisateurs (jugé non-conforme
     par un tribunal allemand en 2022) — héberger soi-même = pas de
     transfert de données vers un tiers
   - Offline : le jeu fonctionne hors-ligne (utile pour le futur PWA)
   - Souveraineté : pas dépendant d'un service externe

   POURQUOI 3 GRAISSES SEULEMENT (400/500/700) ?
   - 400 (Regular) : titres standards (H2-H3, écrans cinématiques)
   - 500 (Medium)  : titres importants (H1, titre de vue)
   - 700 (Bold)    : titres critiques (Display, logo, écrans critiques)
   - Total ~40 Ko (vs ~150 Ko pour 6 graisses + italiques)

   POURQUOI font-display: swap ?
   - Affiche IMMÉDIATEMENT le texte avec la police fallback (Inter)
   - Remplace par Space Grotesk quand chargée
   - Évite le "FOIT" (Flash Of Invisible Text) qui pénalise la perf
   ─────────────────────────────────────────────────────────────────── */

@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/space-grotesk/space-grotesk-400.woff2') format('woff2');
  /* unicode-range : optimisation — on indique au navigateur que cette
     police couvre le sous-ensemble latin (suffisant pour FR + EN).
     Les caractères hors range utilisent le fallback. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/space-grotesk/space-grotesk-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/space-grotesk/space-grotesk-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --bg:       #050810;
  --bg2:      #0a0f1e;
  --bg3:      #0f172a;
  --card:     #0d1526;
  --border:   #162040;
  --border2:  #1e3060;
  --accent1:  #e94560;
  --accent2:  #0ea5e9;
  --accent3:  #7c3aed;
  --accent4:  #10b981;
  --gold:     #f59e0b;
  --text:     #c8d6f0;
  /* v0.0.89 : les deux niveaux montent ENSEMBLE. Porter `--text3` au niveau
     de `--text2` sans toucher à ce dernier donnerait DEUX TOKENS DE LA MÊME
     COULEUR — 480 textes du même gris, et la hiérarchie disparaîtrait en
     silence. Contrastes : 12,4:1 · 7,1:1 · 5,1:1. Le niveau 3 est très
     exactement l'ancien niveau 2, affiché depuis toujours sur 301 textes et
     jamais signalé — c'est ce qui rend le choix sûr. */
  --text2:    #8aa4c8;
  --text3:    #6b8ab0;
  /* v2.9.13 : les micro-libellés HUD (ANCRAGE, ÉPUISEMENT, COQUE, sous-titre du
     logo…) étaient en --text3 (#2d4070), quasi invisibles sur le fond sombre.
     On les passe en bleu turquoise BIEN identifiable (distinct des bleus/cyans
     déjà présents : --accent2 #0ea5e9, --cobaltis #3b82f6). --text3 reste
     inchangé pour ses autres usages (bordures, états désactivés, jauges). */
  --turquoise: #2dd4bf;
  --ferrith:  #f97316;
  --cobaltis: #3b82f6;
  --noctium:  #a78bfa;
  --lumynite: #fbbf24;
  --sun-y:    #fde68a;
  --sun-r:    #fca5a5;
  --sun-b:    #93c5fd;
  --hud:      'Orbitron', monospace;
  --mono:     'Share Tech Mono', monospace;
  --body:     'Exo 2', sans-serif;

  /* ═══════════════════════════════════════════════════════════════
     v0.121 — DESIGN SYSTEM TOKENS (GDD §21.3, Bloc 9 — 1/8)
     ═══════════════════════════════════════════════════════════════
     Tokens ajoutés en COHABITATION avec les variables historiques
     ci-dessus (décision Q1). Les composants v0.121+ utilisent ces
     tokens, le code historique conserve --hud/--body/--mono jusqu'au
     Bloc 10 (Identité visuelle) qui tranchera la migration totale.

     ⚠️ OÙ VA UNE NOUVELLE RÈGLE DE STYLE ?
     Deux vocabulaires cohabitent dans ce fichier, et c'est voulu :
     les variables historiques (--hud / --body / --mono, .btn-*,
     .modal-*) et les tokens du design system (--color-*, --space-*,
     --text-*, .dsh-*). La règle tient en deux lignes :
       • un composant NOUVEAU s'écrit entièrement avec les tokens ;
       • un composant HISTORIQUE ne se convertit PAS « au passage »
         parce qu'on l'a ouvert pour autre chose — la conversion
         partielle a déjà cassé des boutons, l'avertissement est posé
         au-dessus des composants .dsh-* concernés.
     Il n'existe plus de liste des zones restant à migrer : celle qui
     était tenue à la main a fini par mentir. `grep -c` sur une classe
     donne l'état réel, à jour, en une seconde.
     ═══════════════════════════════════════════════════════════════ */

  /* ── §21.3.1 Familles de polices ─────────────────────────────────
     Inter : interface principale (libre, géométrique, 9 graisses)
     JetBrains Mono : code, identifiants techniques, coordonnées G-S-P
     Fallback système robuste si le chargement réseau échoue. */
  /* ⚠️ --font-body N'EST PAS APPLIQUÉE au <body> — c'est délibéré.
     L'identité visuelle du jeu repose sur Orbitron (--hud) ; brancher
     Inter globalement la remplacerait partout d'un coup. Pour utiliser
     Inter, il faut l'écrire explicitement sur le composant :
     font-family: var(--font-body). */
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI",
               Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, Monaco,
               Consolas, "Liberation Mono", "Courier New", monospace;

  /* ── §21.3.2 Échelle typographique (ratio 1.25 Major Third) ─────
     Plancher absolu 11 px (GDD strict). En-dessous = a11y impossible. */
  --text-display:    32px;  /* Titre du jeu, écrans cinématiques */
  --text-h1:         24px;  /* Titre de la vue courante (« Galaxie ») */
  --text-h2:         20px;  /* Titre de section dans une vue */
  --text-h3:         16px;  /* Titre de sous-section, header de carte (gras) */
  --text-body-lg:    16px;  /* Descriptions importantes (Dashboard) */
  --text-body:       14px;  /* TEXTE PAR DÉFAUT dans toute l'interface */
  --text-small:      12px;  /* Helper text, méta secondaire, légendes */
  --text-tiny:       11px;  /* Timestamps, indicateurs très secondaires */

  /* ── §21.3.3 Poids typographiques ────────────────────────────────
     Bold 700 PROSCRIT sauf exception très ponctuelle (titre du jeu). */
  --font-weight-regular:  400;  /* Texte courant par défaut */
  --font-weight-medium:   500;  /* Labels, méta importante, headers de tableau */
  --font-weight-semibold: 600;  /* H1-H3, valeurs clés, boutons primaires */

  /* ── §21.3.3 Hauteurs de ligne ───────────────────────────────────
     Body et H3 : 1.5 (équilibre lisibilité / densité)
     H1 et H2 : 1.3 (titres plus compacts)
     Display : 1.2 */
  --line-height-display: 1.2;
  --line-height-heading: 1.3;
  --line-height-body:    1.5;

  /* ── §21.3.4 Système d'espacement (grille de 8 px) ──────────────
     Toute l'interface utilise des multiples de 8 px (avec un cran à
     4 px pour intra-mot). Crée un rythme visuel cohérent. */
  --space-0:   0;       /* Élimination explicite */
  --space-1:   4px;     /* Intra-mot (icône / label) */
  --space-2:   8px;     /* Éléments très proches */
  --space-3:   12px;    /* Entre cartes (cf. §21.5.1) */
  --space-4:   16px;    /* Padding interne standard de carte */
  --space-6:   24px;    /* Entre sections d'un écran */
  --space-8:   32px;    /* Séparation entre grands blocs */
  --space-12:  48px;    /* Espacement vertical entre vues / écrans */
  --space-16:  64px;    /* Espacement très large (rarement utilisé) */
  /* Padding latéraux d'écran */
  --space-screen-desktop: 24px;
  --space-screen-mobile:  16px;

  /* ── §21.3.5 Hiérarchie de profondeur (z-index) ──────────────────
     8 niveaux strictement ordonnés. Règle : on ne crée JAMAIS de
     z-index libre. Toute couche utilise un de ces niveaux. Évite les
     guerres de z-index (+1, +1, +1) qui finissent par tout casser.

     ⚠️ v0.0.26 — LES QUATRE NIVEAUX DU HAUT SONT PASSÉS DE 40/50/60/70 À
     1300/1310/1320/1330, ET IL NE FAUT PAS LES REDESCENDRE. La table
     d'origine ne tenait pas compte de l'échelle du RESTE du jeu, écrite
     avant elle : celle-là monte jusqu'à 99 999, et surtout la barre de
     ressources est à 190 et la topbar à 200 (cf. « ZONE A SACRÉE »).
     Une modale à 40 naissait donc ENTERRÉE sous les deux barres du haut —
     titre imprimé derrière les tuiles de ressources, croix de fermeture
     non cliquable, page derrière non assombrie.

     Le défaut était devenu structurel : QUATRE panneaux l'avaient chacun
     contourné dans son coin (projection 1200, journal des versions 1000,
     conseil de production 1000, comète 1000) — très exactement la « guerre
     de z-index » que ces quatre lignes de commentaire prétendaient
     empêcher. Ces quatre rustines ont été retirées avec cette correction :
     tout repasse par les jetons ci-dessous, et une fenêtre neuve n'a plus
     rien à savoir.

     POURQUOI 1300 ET PAS 1000 : pour ne se cogner à RIEN, donc n'avoir
     aucune égalité à arbitrer par ordre du DOM. Au-dessus de tout ce
     qu'une modale doit couvrir (topbar 200, panneau de production 320 —
     d'où s'ouvre la projection —, chat 999, modale d'authentification
     1000, sa topbar boostée 1100) et sous les trois utilitaires
     délibérément posés au-dessus de tout (autocomplétion de mention 1500,
     bandeau de mute 9999, lien d'évitement 99 999). */
  --z-base:       0;    /* Contenu courant des écrans */
  --z-elevated:   10;   /* Panneau VMs (desktop), bandeau diagnostic */
  --z-sticky:     20;   /* Topbar et barre d'onglets (sticky en haut) */
  --z-drawer:     30;   /* Panneau VMs mobile (drawer flottant) */
  --z-modal:    1300;   /* Modales et bottom sheets */
  --z-toast:    1310;   /* Notifications éphémères */
  --z-critical: 1320;   /* Modales critiques (attaque, périclitement H-1) */
  --z-tooltip:  1330;   /* Tooltips et infobulles (toujours au-dessus) */

  /* ═══════════════════════════════════════════════════════════════
     v0.122 — CODE COULEUR SÉMANTIQUE (GDD §21.4, Bloc 9 — 2/8)
     ═══════════════════════════════════════════════════════════════
     Principe fondateur §21.4.1 : la couleur ne porte JAMAIS seule le
     sens. Toujours doublée d'icône, texte, forme ou position.
     ~8% des hommes et ~0.5% des femmes ont une forme de daltonisme —
     le rouge vs vert est particulièrement problématique.

     Décisions v0.122 :
     - Q1 cohabitation : tokens GDD ajoutés, --bg/--card/--border
       historiques préservés (futurs composants utilisent les tokens
       sémantiques, le code historique reste intact)
     - Q2 cohabitation accents : --accent1/2/3/4 préservés, tokens
       sémantiques ajoutés en parallèle
     - Q3 migration ciblée ressources rares : seules Tritanium /
       Palladrine / Iridyum sont posées comme tokens (les anciennes
       --ferrith/--cobaltis/--noctium/--lumynite restent à leurs
       valeurs actuelles — réévaluation potentielle Bloc 10)
     - Q4 périclitement : tokens + intégration v0.116-120 récents
     - Q5 migration éléments criants Bloc 8

     ═══════════════════════════════════════════════════════════════ */

  /* ── §21.4.2 États systémiques (4 couleurs universelles) ─────────
     Utilisées partout pour notifications, bandeau diagnostic, états
     de cartes, retours d'action. Triplet bg/fg/border par état.
     Cohérence avec le bandeau diagnostic v0.5 (mêmes hex exacts). */

  /* ℹ️ INFO — état neutre / contextuel / en attente */
  --color-info-fg:     #2563eb;             /* Texte / icône info */
  --color-info-bg:     rgba(37, 99, 235, 0.12);   /* Fond subtil */
  --color-info-border: rgba(37, 99, 235, 0.40);   /* Bordure marquée */

  /* ✅ SUCCÈS — construction terminée, transit complété, butin reçu */
  --color-success-fg:     #22c55e;
  --color-success-bg:     rgba(34, 197, 94, 0.12);
  --color-success-border: rgba(34, 197, 94, 0.40);

  /* ⚠️ ATTENTION — énergie insuffisante, stocks bientôt pleins */
  --color-warning-fg:     #ea580c;
  --color-warning-bg:     rgba(234, 88, 12, 0.12);
  --color-warning-border: rgba(234, 88, 12, 0.40);

  /* ❌ ERREUR — connecteur manquant, attaque, ressources manquantes */
  --color-error-fg:     #dc2626;
  --color-error-bg:     rgba(220, 38, 38, 0.12);
  --color-error-border: rgba(220, 38, 38, 0.40);

  /* ── §21.4.3 Seuils de jauges de santé ──────────────────────────
     3 seuils universels pour toute valeur quantifiable en jauge :
     ≥50% sain / 20-50% à surveiller / <20% critique / <5% clignotant. */
  --gauge-safe:     var(--color-success-fg);    /* ≥ 50% */
  --gauge-medium:   var(--color-warning-fg);    /* 20-50% */
  --gauge-danger:   var(--color-error-fg);      /* < 20% */
  --gauge-critical: var(--color-error-fg);      /* < 5% + animation 0.8s */

  /* ── §21.4.4 Escalade visuelle Périclitement ─────────────────────
     Calendrier d'alertes strict sur 3 paliers. L'escalade va du
     statique vers le clignotant vers le permanent ; teinte s'assombrit
     à mesure que l'échéance se rapproche. */
  --periclitement-j7: #ea580c;   /* Orange statique, bandeau visible */
  --periclitement-j1: #dc2626;   /* Rouge clignotant 1.2s, push si activé */
  /* ⚠️ #7f1d1d sur le fond principal ne donne que 3,2 : 1, sous le
     seuil de 4,5 : 1 exigé pour du texte. C'est une dérogation ASSUMÉE,
     pas un oubli : le palier le plus grave doit être le plus sombre,
     sinon l'escalade s'inverse à l'écran. Elle ne tient QUE parce que
     la couleur n'est jamais seule — clignotement + texte explicite
     portent l'information. Retirer l'un des deux rend ce palier
     illisible pour une partie des joueurs. */
  --periclitement-h1: #7f1d1d;   /* Rouge profond permanent, son si activé */

  /* ── §21.4.7 — LES TROIS COULEURS DE RARES SONT PARTIES (v0.0.158) ─
     ⚠️ NE PAS LES REPOSER ICI. Elles ont existé (`--resource-tritanium`,
     `--resource-palladrine`, `--resource-iridyum`), aucune ligne du jeu ne
     les a jamais lues, et surtout elles ne disaient PAS la même chose que
     la table qui fait foi : là où le jeu peint le Tritanium en cyan, la
     Palladrine en ambre et l'Iridyum en violet, elles annonçaient un
     blanc-argent, un orangé doré et un cuivré sombre.
     ⚠️ LA COULEUR D'UNE RESSOURCE VIT DANS `RES_INFO`
     (`public/shared/constants.js`), et nulle part ailleurs. C'est de là que
     le JS la pose en ligne quand il en a besoin. Une seconde table dans
     cette feuille rouvrirait le piège : elle aurait l'air officielle, elle
     serait rangée avec les autres, et le premier écran qui s'en servirait
     peindrait ces minerais d'une couleur que le joueur n'a vue nulle part. */

  /* ── §21.4.8 Couleurs factions PNJ (§16.7) ───────────────────────
     Triptyque bleu/rouge/vert classique pour lecture instantanée des
     allégeances. Nuances SOMBRES pour ne pas concurrencer le sémantique. */
  --faction-federation:   #1e3a8a;   /* Autorité, ordre, légalité */
  --faction-corsaires:    #991b1b;   /* Rébellion, anarchie, danger */
  --faction-sentinelles:  #166534;   /* Gardiens silencieux, mystère ancien */

  /* ── §21.4.9 Tags spéciaux ───────────────────────────────────────
     Badges discrets pour signaler une caractéristique particulière. */
  --tag-rare:    #8b5cf6;   /* RARE — connecteurs de rares, ordres marché */
  --tag-premium: #eab308;   /* PREMIUM — officiers cristaux stellaires */
  --tag-boost:   #ea580c;   /* BOOST ×N — panneau admin v0.3 */

  /* ── §21.4.10 Palette neutre (cohabitation Q1) ───────────────────
     DSH est un jeu à thème sombre. La palette neutre GDD est construite
     autour de #0a0e1c. Tokens en cohabitation avec --bg historique. */
  --color-bg-main:   #0a0e1c;                   /* Background général GDD */
  --color-bg-card:   #131930;                   /* Cartes, modales, panneaux */
  --color-bg-hover:  #1a2240;                   /* État survolé / sélectionné */
  --color-text-primary:   #f1f5f9;              /* Titres, valeurs principales */
  --color-text-secondary: #94a3b8;              /* Sous-titres, descriptions */
  /* v0.0.89 : aligné sur le nouveau niveau 3 des `--text*` (il était à
     3,8:1). Les deux familles cessent ainsi de dire deux choses
     différentes du même rang de lecture ; les deux autres niveaux
     (16,6:1 et 7,1:1) allaient déjà bien et n'ont pas bougé. */
  --color-text-tertiary:  #7c8ba3;              /* Hints, placeholders, désactivés */
  --color-border-subtle: rgba(255, 255, 255, 0.10);  /* Séparations subtiles */
  --color-border-strong: rgba(255, 255, 255, 0.20);  /* Séparations marquées, focus */

  /* ═══════════════════════════════════════════════════════════════
     v0.123 — PATTERNS D'INTERACTION (GDD §21.5, Bloc 9 — 3/8)
     ═══════════════════════════════════════════════════════════════
     Formalise les briques de l'interface : cartes, boutons, modales,
     tooltips. Objectif : qu'un même composant ait toujours le même
     comportement et la même apparence où qu'il apparaisse.

     Décisions v0.123 :
     - Q1 : .dsh-modal en cohabitation + migration modales Bloc 8
     - Q2 : .dsh-btn-* en cohabitation + migration boutons Bloc 8
     - Q3 : .dsh-card en cohabitation (.card historique préservé)
     - Q4 : tooltips via attribut data-tooltip (CSS pur + JS léger)
     - Q5 : migration Bloc 8 complète (modales onboarding/quêtes/
       mentor + leurs boutons) + composants génériques + audit. */

  /* ── §21.5.1 Anatomie de carte ──────────────────────────────────
     Bordure radius 8 px = "border-radius-md" GDD. Padding 16 px,
     espacement 12 px entre cartes (cf. v0.121 --space-3 / --space-4). */
  --radius-sm:  4px;    /* Boutons, badges, tags */
  --radius-md:  8px;    /* Cartes, modales */
  --radius-lg:  12px;   /* Modales larges, panneaux */
  --radius-pill: 999px; /* Pills, boutons ronds, badges */

  /* ── §21.5.2 Hauteurs de boutons standardisées ──────────────────
     Standard = 40 px desktop / 44 px mobile (taille tactile min Apple).
     Small = 32 px (tableaux denses). Large = 48 px (CTAs majeurs). */
  --btn-height-small:    32px;
  --btn-height-standard: 40px;   /* Desktop par défaut */
  --btn-height-mobile:   44px;   /* Tactile minimum GDD §21.8.6 */
  --btn-height-large:    48px;   /* CTAs majeurs (transit, attaque) */

  /* ── §21.5 Ombres et profondeur ─────────────────────────────────
     Ombres discrètes pour différencier les couches. Pas d'ombres
     hollywoodiennes — DSH est sobre. */
  --shadow-sm:   0 1px 2px rgba(0, 0, 0, 0.3);     /* Hover carte */
  --shadow-md:   0 4px 12px rgba(0, 0, 0, 0.4);    /* Modale standard */
  --shadow-lg:   0 20px 60px rgba(0, 0, 0, 0.6);   /* Modale critique */

  /* ── §21.5 Durées d'animation standardisées ─────────────────────
     Cohérence des transitions à travers le jeu. Animation courte
     (100ms) pour les retours immédiats, longue (250ms) pour les
     mouvements de panneaux/modales. */
  --anim-fast:   100ms;   /* Hover, active */
  --anim-normal: 200ms;   /* Fade, scale modale */
  --anim-slow:   300ms;   /* Drawer mobile, transition vue */

  /* ── §21.5.7 Tooltips (Q4 : data-tooltip) ───────────────────────
     Délais GDD : 500ms desktop, 700ms long press mobile. Largeur max
     pour limiter à ~3 lignes. */
  --tooltip-bg:           rgba(15, 25, 50, 0.97);
  --tooltip-fg:           var(--color-text-primary);
  --tooltip-border:       var(--color-border-strong);
  --tooltip-max-width:    240px;
  --tooltip-delay:        500ms;

  /* ═══════════════════════════════════════════════════════════════
     v0.124 — ARCHITECTURE DE L'INFORMATION (GDD §21.2, Bloc 9 — 4/8)
     ═══════════════════════════════════════════════════════════════
     Barre d'onglets unifiée à 11 vues GDD §21.2.1 (révision v0.26 du
     GDD) : Dashboard / Galaxie / VM / Recherche / Social / Factions IA /
     Combat / Marché / Alliance / Profil / Notifications.
     Pattern retenu : défilement horizontal scrollable (GDD §21.2.2). */

  /* ── §21.2.2 Barre d'onglets ────────────────────────────────────
     Hauteur 48 px desktop / 44 px mobile (tactile minimum Apple).
     Indicateur d'onglet actif : barre cyan en bas, 3 px. */
  --tabs-height-desktop: 48px;
  --tabs-height-mobile:  44px;
  --tabs-bg:             rgba(15, 24, 40, 0.95);
  --tabs-border-bottom:  1px solid var(--color-border-subtle);
  --tab-active-indicator-thickness: 3px;
  --tab-active-color:    var(--accent2);                /* cyan #0ea5e9 (identité DSH) */
  --tab-inactive-color:  var(--color-text-secondary);
  --tab-hover-bg:        rgba(255, 255, 255, 0.04);
  --tab-padding-x:       var(--space-4);
  --tab-gap:             var(--space-1);
  --tab-scroll-shadow:   linear-gradient(to right, rgba(15, 24, 40, 0.9), transparent);

  /* ── §21.5.6 Files d'attente (Q4 : composant générique .dsh-queue) ─
     Reprend la grammaire des files existantes (bâtiments v0.6,
     recherche v0.21). Les futures files (Phase 2 combat, Phase 3
     marché) utiliseront .dsh-queue. */
  --queue-item-height:    44px;        /* = --btn-height-mobile */
  --queue-progress-h:     4px;
  --queue-gap:            var(--space-2);

  /* ── §21.5.4 Champs de saisie (Q5 : sous-set .dsh-input/.dsh-toggle) ─
     .dsh-input : champ texte standard (label + input + helper)
     .dsh-toggle : switch on/off (label + interrupteur visuel) */
  --input-height:         var(--btn-height-standard);   /* 40px */
  --input-padding-x:      var(--space-3);
  --input-bg:             var(--color-bg-card);
  --input-border:         var(--color-border-subtle);
  --input-border-focus:   var(--color-info-fg);
  --input-border-error:   var(--color-error-fg);
  --toggle-width:         44px;
  --toggle-height:        24px;
  --toggle-knob-size:     18px;

  /* ═══════════════════════════════════════════════════════════════
     v0.125 — RESPONSIVE ET ADAPTATION MOBILE (GDD §21.6, Bloc 9 — 5/8)
     ═══════════════════════════════════════════════════════════════
     Pose les 5 breakpoints standardisés GDD §21.6.1 et les tokens des
     composants responsive : drawer, table responsive, grille adaptative.

     Décisions v0.125 :
     - Q1 : Cohabitation. Nouveaux composants Bloc 9 alignés sur les
            5 breakpoints. Les ~116 règles body.mobile-mode historiques
            conservées intactes pour ne rien casser.
     - Q2 : .dsh-drawer en cohabitation (panneau VMs actuel préservé)
     - Q3 : .dsh-table générique en cohabitation
     - Q4 : Swipe down bottom sheets + pull-to-refresh Dashboard
     - Q5 : Sections Dashboard empilées + grilles .dsh-view-grid
            adaptatives 4 → 3 → 2 → 1 colonnes selon GDD §21.6.4 */

  /* ── §21.6.1 Breakpoints standardisés ────────────────────────────
     Pour usage dans @media queries (les variables CSS ne marchent PAS
     dans @media, donc on documente ici les valeurs en référence et on
     les utilise en dur dans les media queries). Le seuil critique est
     700 px (mode mobile vs desktop) — déjà encodé dans le MVP v0.20.

     Mobile portrait        :  < 480 px
     Mobile landscape / petite tablette : 480 - 700 px
     Tablette               : 700 - 1024 px
     Desktop standard       : 1024 - 1440 px
     Desktop large          : > 1440 px

     Tokens de référence (utilisables dans calc() et autres contextes) : */
  --bp-mobile-portrait:   480px;   /* Téléphones tenus verticalement */
  --bp-mobile-landscape:  700px;   /* SEUIL CRITIQUE mode mobile vs desktop */
  --bp-tablet:           1024px;   /* iPad et fenêtres desktop étroites */
  --bp-desktop:          1440px;   /* Écrans desktop standards → larges */

  /* ── §21.6.3 Drawer flottant pour panneau VMs sur mobile ─────────
     Sur mobile (< 700 px), le panneau VMs devient un drawer flottant
     accessible via un bouton flottant 🚀 en bas à droite. Le drawer
     glisse depuis la droite (240 px de large pour la lisibilité
     tactile, plus large que le panneau desktop 130 px). */
  --drawer-width-mobile:  240px;   /* Drawer mobile (lisibilité tactile) */
  --drawer-bg:            rgba(15, 24, 40, 0.97);
  --drawer-shadow:        -4px 0 20px rgba(0, 0, 0, 0.5);
  --drawer-anim-duration: var(--anim-slow);   /* 300ms — animation glissement */
  --drawer-handle-size:   56px;    /* Bouton flottant 🚀 (tactile confortable) */
  --drawer-handle-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);

  /* ── §21.6.5 Tableaux ↔ listes de cartes sous 700 px ─────────────
     Composant .dsh-table qui bascule automatiquement entre :
       - desktop ≥ 700 px : tableau classique avec colonnes, tri, header sticky
       - mobile < 700 px : chaque ligne devient une carte verticale autonome
     Le contenu est identique, seul le contenant change (cf. §21.5.7). */
  --table-header-height:  44px;
  --table-row-height:     44px;
  --table-row-hover-bg:   var(--color-bg-hover);
  --table-border-color:   var(--color-border-subtle);

  /* ── §21.6.4 Grilles de cartes adaptatives ───────────────────────
     4 → 3 → 2 → 1 colonnes selon la largeur. Largeur minimale d'une
     carte : 280 px (en dessous les contenus ne tiennent plus). C'est
     ce minimum qui détermine le nombre de colonnes possibles. */
  --card-min-width:       280px;
  --grid-gap:             var(--space-3);   /* 12 px entre cartes */

  /* ── §21.5.9 Gestes tactiles ─────────────────────────────────────
     Délai pour différencier tap et long press. Le tap est ≤ 200 ms,
     le long press est ≥ 700 ms (recommandation iOS/Android). Entre
     les deux, comportement ambigu — on ignore. */
  --gesture-long-press-ms: 700;       /* Durée seuil pour long press */
  --gesture-swipe-threshold: 60px;    /* Distance min pour détecter un swipe */

  /* ═══════════════════════════════════════════════════════════════
     v0.126 — NOTIFICATIONS ET FEEDBACK (GDD §21.7, Bloc 9 — 6/8)
     ═══════════════════════════════════════════════════════════════
     Pose les 4 patterns visuels GDD §21.7.2 (toast / badge / bandeau /
     modale critique), les 6 catégories GDD officielles, les états du
     feedback immédiat (§21.7.9) et la page Préférences DND.

     Décisions v0.126 :
     - Q1 : Hooks sur events existants (badges live sur .dsh-tab)
     - Q2 : Migration 4 → 6 catégories GDD + bouton "→ Voir"
     - Q3 : Vue Profil enrichie avec Préférences notifications
     - Q4 : Coquille push UI uniquement (vraie implémentation Bloc 11)
     - Q5 : Coquille email UI uniquement (vraie implémentation Bloc 11) */

  /* ── §21.7.2 — 4 patterns visuels de notifications in-game ─────── */
  /* Toast : glisse haut-droite desktop / haut-centré mobile, 5s auto-dismiss */
  --toast-width:           320px;
  --toast-max-width-mobile: 90vw;
  --toast-duration-ms:     5000;       /* Auto-dismiss 5 s */
  --toast-duration-error-ms: 8000;     /* Plus long pour erreurs (temps de lire) */
  --toast-gap:             var(--space-2);
  --toast-padding:         var(--space-3) var(--space-4);
  --toast-anim:            var(--anim-normal);

  /* Badge : compteur numérique rouge sur l'icône cloche */
  --badge-bg:              var(--color-error-fg);
  --badge-fg:              #ffffff;
  --badge-size:            18px;
  --badge-font-size:       11px;     /* Plancher absolu GDD §21.3.2 */
  --badge-border-color:    var(--color-bg-main);

  /* Bandeau diagnostic : sous la topbar, permanent tant que la condition est vraie */
  --banner-height:         40px;
  --banner-padding-x:      var(--space-4);

  /* Modale critique : centrée plein écran, bloque l'interaction */
  /* Pas de tokens dédiés — utilise .dsh-modal v0.123 avec variant is-critical */

  /* ── §21.7.3 — Centre de notifications (côté droit desktop) ──── */
  --notif-center-width-desktop: 40vw;
  --notif-center-max-width:     440px;
  --notif-center-min-width:     320px;

  /* ── §21.7.8 — Préférences DND (Do Not Disturb) ──────────────── */
  --dnd-row-height:        56px;
  --dnd-section-gap:       var(--space-4);

  /* ── §21.7.9 — Feedback immédiat (loading, skeleton) ──────────── */
  --skeleton-bg:           var(--color-bg-card);
  --skeleton-highlight:    rgba(255, 255, 255, 0.06);
  --skeleton-anim-duration: 1.5s;

  /* ═══════════════════════════════════════════════════════════════
     v0.127 — ACCESSIBILITÉ WCAG 2.1 AA (GDD §21.8, Bloc 9 — 7/8)
     ═══════════════════════════════════════════════════════════════
     Pose les tokens nécessaires à la conformité WCAG 2.1 AA :
     - Bordure de focus contrastée (3:1 minimum)
     - Skip link visible au focus
     - Barre de raccourcis clavier cliquable (suggestion utilisateur)
     - Animation ralentie pour Périclitement J-1 (2s au lieu de 1.2s)
       quand prefers-reduced-motion est actif

     Décisions v0.127 :
     - Q1 : Audit ciblé des contrastes sur les tokens Bloc 9. Une seule
       dérogation en est sortie, elle est expliquée sur le token
       --periclitement-h1 lui-même.
     - Q2 : 9 raccourcis clavier complets + barre cliquable
     - Q3 : ARIA ciblé sur composants v0.121-126
     - Q4 : Désactivation totale animations via prefers-reduced-motion
     - Q5 : Long press 700ms ciblé sur cartes VMs */

  /* ── §21.8.4 Bordure de focus (3:1 minimum) ─────────────────────
     Cyan vif #0ea5e9 sur fond sombre = ratio largement au-dessus de 3:1.
     2 px d'épaisseur pour être visible sans envahir, offset 2 px pour
     respirer autour de l'élément. */
  --focus-outline-color:   var(--accent2);   /* cyan #0ea5e9 identité DSH */
  --focus-outline-width:   2px;
  --focus-outline-offset:  2px;

  /* ── §21.8.4 Skip link "Aller au contenu principal" ──────────────
     Visible uniquement au focus (caché par défaut hors écran).
     Pour les utilisateurs au clavier ou lecteur d'écran. */
  --skip-link-bg:          var(--color-info-fg);
  --skip-link-fg:          #ffffff;
  --skip-link-padding:     var(--space-3) var(--space-4);

  /* ── Barre d'actions rapides (raccourcis clavier cliquables) ────
     Suggestion utilisateur v0.127. Pattern type OGame : barre flottante
     avec pastilles cliquables qui dupliquent les 9 raccourcis clavier
     GDD §21.5.8. Utile sur desktop (découvrabilité) et indispensable
     sur mobile (pas de clavier physique). */
  --shortcuts-bar-height:  44px;     /* = btn-height-mobile */
  --shortcuts-bar-bg:      rgba(15, 24, 40, 0.92);
  --shortcuts-bar-border:  1px solid var(--color-border-subtle);
  --shortcuts-btn-size:    36px;
  --shortcuts-btn-bg:      var(--color-bg-card);
  --shortcuts-btn-hover-bg: var(--color-bg-hover);
  --shortcuts-btn-border:  var(--color-border-subtle);
  --shortcuts-btn-fg:      var(--color-text-secondary);
  --shortcuts-btn-fg-active: var(--accent2);

  /* ── v1.01 — Sidebar verticale gauche (refonte navigation) ──────
     Ajout v1.01 : la barre d'onglets principale .dsh-tabs (refonte
     v0.124) est désormais affichée en sidebar verticale gauche fixée
     (icônes seules, label en tooltip). Les 9 raccourcis clavier
     y sont également rapatriés en pied. Tokens dédiés pour ne pas
     polluer .dsh-tabs qui reste valide pour les sous-onglets niveau 3. */
  --sidebar-width:        60px;   /* largeur de la sidebar (icônes seules) */
  --sidebar-bg:           rgba(10, 15, 28, 0.95);
  --sidebar-border:       1px solid var(--color-border-subtle);
  --sidebar-item-size:    44px;   /* hauteur d'un bouton (tactile-safe) */
  --sidebar-icon-size:    1.25rem;
  --sidebar-gap:          4px;
  --sidebar-tooltip-bg:   rgba(5, 8, 16, 0.95);
  --sidebar-tooltip-fg:   var(--color-text-primary);

  /* ── §21.8.7 Animation Périclitement J-1 ralentie ───────────────
     En mode prefers-reduced-motion, le clignotement passe de 1.2s
     à 2s — toujours visible, mais moins agressif pour les joueurs
     sensibles au mouvement (troubles vestibulaires). */
  --periclitement-j1-duration-reduced: 2s;

  /* ═══════════════════════════════════════════════════════════════
     v0.128 — PRINCIPES ERGONOMIQUES FINAUX (GDD §21.1, Bloc 9 — 8/8)
     ═══════════════════════════════════════════════════════════════
     DERNIÈRE BRIQUE du Bloc 9. Pass de polissage général qui consolide
     tout le travail Bloc 9 (v0.121-127). Pose les tokens du menu
     contextuel long press et du feedback immédiat.

     Décisions v0.128 :
     - Q1 : ARIA ciblé sur composants historiques les plus utilisés
     - Q2 : Cohabitation maintenue des DEUX systèmes d'affichage
       mobile — la classe body.mobile-mode (posée par le JS, elle
       obéit au bouton de la barre) et les media queries
       @media (max-width: 700px) (qui ne regardent QUE la largeur
       réelle). ⚠️ Elles ne se déclenchent PAS ensemble : forcer
       la classe sur une fenêtre large ne déclenche aucune media
       query, et on croit alors avoir vérifié le mobile. Pour de
       vrai, il faut RÉTRÉCIR la fenêtre sous 700 px.
     - Q3 : Migration ciblée #prod-hint → .dsh-banner (alias en cohabitation)
     - Q4 : Pièges de focus sur toutes les modales bloquantes
     - Q5 : Long press 700ms cartes VMs → menu contextuel */


  /* ── §21.1.4 Feedback immédiat — Bouton "loading" ────────────────
     Pattern d'état transitoire (100ms à 3s) : bouton désactivé avec
     spinner inline. Token pour la taille du spinner. */
  --btn-spinner-size:      14px;
  --btn-spinner-border-width: 2px;

  /* ═══════════════════════════════════════════════════════════════
     v0.129 — TYPOGRAPHIE DISPLAY (GDD §22.3, Bloc 10 — 1/4)
     ═══════════════════════════════════════════════════════════════
     Première brique du Bloc 10 (Identité visuelle). Pose la police
     display Space Grotesk pour les grandes échelles cinématiques :
     - Titre du jeu / logo
     - Écran de connexion / création de compte
     - Onboarding (modales pleine page Bloc 8)
     - Modales critiques (Périclitement H-1)
     - Heros Dashboard (titre principal)

     Décisions v0.129 :
     - Q1 : Self-hosted via @font-face (perf + RGPD + offline)
     - Q2 : Application CIBLÉE titres + écrans cinématiques (pas le body)
     - Q3 : 3 graisses 400/500/700 (couvre tous les usages)
     - Q4 : Token --font-display + classe utilitaire .dsh-display
     - Q5 : Cohabitation avec Orbitron historique (pas de migration brutale)

     Justification GDD §22.3 :
     - Space Grotesk : caractère affirmé sans tape-à-l'œil
     - Compatible visuellement avec Inter (frères-de-style)
     - À ÉVITER : Eurostile (cliché Star Trek), Orbitron (2010-2015)
       → On laisse Orbitron sur les composants legacy pour ne pas
         tout refaire, mais on n'en ajoute PLUS dans les nouveaux. */

  /* ── §22.3 Police display ─────────────────────────────────────────
     Famille Space Grotesk avec fallbacks géométriques modernes.
     Ordre d'importance :
       1. Space Grotesk (notre choix, self-hosted)
       2. Inter Display (alternative si Space Grotesk pas encore chargée)
       3. Inter (la police body, frère-de-style)
       4. Fallbacks système (Apple, Microsoft, etc.) */
  --font-display: 'Space Grotesk', 'Inter Display', 'Inter',
                  -apple-system, BlinkMacSystemFont,
                  'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* ── Graisses Space Grotesk disponibles ───────────────────────────
     On charge 3 graisses pour couvrir tous les usages sans alourdir
     le bundle (3 × 13 Ko = ~40 Ko, négligeable). */
  --font-display-weight-regular:  400;
  --font-display-weight-medium:   500;
  --font-display-weight-bold:     700;

  /* ── Letter-spacing pour Space Grotesk en grand format ───────────
     Space Grotesk a un letter-spacing par défaut un peu serré aux
     grandes tailles ; on l'aère légèrement pour le rendu cinématique. */
  --display-letter-spacing-tight:  -0.02em;  /* Titres serrés (logo) */
  --display-letter-spacing-normal:  0;        /* Défaut */
  --display-letter-spacing-wide:    0.04em;   /* UPPERCASE éparpillé */
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{font-size:14px}
body{background:var(--bg);color:var(--text);font-family:var(--body);min-height:100vh;overflow:hidden}

/* ═══════════════════════════════════════════════════════════════════
   v0.122 (GDD §21.4.4) — Animations Périclitement (Bloc 9 — 2/8)
   ═══════════════════════════════════════════════════════════════════
   GDD strict §21.4.3 : jauge critique <5% = animation 0.8s.
   GDD strict §21.4.4 : Périclitement J-1 = clignotant 1.2s.
   2 keyframes distinctes pour respecter les cadences.
   IMPORTANT : ces animations sont skippables via prefers-reduced-motion
   (GDD §21.8.7). Le bloc media query est dans la section accessibilité
   en bas du CSS (à compléter en v0.127). */

@keyframes dshGaugeCritical {
  /* <5% : clignotement rapide pour action immédiate */
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}

@keyframes dshPericlitementJ1 {
  /* J-1 : clignotement plus lent pour notification d'avertissement */
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.6; transform: scale(0.98); }
}

/* Classes utilitaires pour appliquer les animations */
.is-critical-gauge {
  animation: dshGaugeCritical 0.8s ease-in-out infinite;
}
.is-periclitement-j1 {
  animation: dshPericlitementJ1 1.2s ease-in-out infinite;
}

/* ═══════════════════════════════════════════════════════════════════
   v0.121 (GDD §21.3.2) — Hiérarchie typographique globale (Bloc 9 — 1/8)
   ═══════════════════════════════════════════════════════════════════
   Échelle 1.25 (Major Third). Applique l'échelle GDD sur les balises
   sémantiques. Les composants peuvent override mais on a une baseline
   cohérente partout.
   IMPORTANT : on ne touche PAS aux composants .logo, .modal-title, etc.
   qui ont leur propre styling historique — leur migration sera traitée
   par les versions ultérieures du Bloc 9. */
h1, .h1 {
  font-size: var(--text-h1);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-heading);
  color: var(--text);
}
h2, .h2 {
  font-size: var(--text-h2);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-heading);
  color: var(--text);
}
h3, .h3 {
  font-size: var(--text-h3);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-body);
  color: var(--text);
}
/* Classe utilitaire .display réservée aux écrans cinématiques (§21.3.2) */
.display {
  font-size: var(--text-display);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-display);
}
/* Classes utilitaires de texte (cohérence avec l'échelle GDD) */
.text-body-lg { font-size: var(--text-body-lg); }
.text-body    { font-size: var(--text-body); }
.text-small   { font-size: var(--text-small); }
.text-tiny    { font-size: var(--text-tiny); }


/* ── VIEWS ── */
.view{display:none;position:absolute;inset:0}
.view.active{display:flex;flex-direction:column}
/* ⚠️ INTERRUPTEUR vueJusquAuBas — Loryan, au téléphone : « il y a une zone
   noire en bas et l'encart des Voies ne descend pas jusque là ». Une vue est
   en `position: absolute` sur le bloc initial de la page, dont la hauteur
   est celle de l'écran BARRES DU NAVIGATEUR AFFICHÉES. Quand la barre du bas
   se replie (Samsung Internet, Chrome), l'écran grandit, les éléments fixes
   (la fusée, les tiroirs) suivent le vrai bas… et la vue s'arrête où elle
   était : une bande de fond noir, la hauteur de la barre repliée.
   `100dvh` est la hauteur de l'écran À CET INSTANT : l'écart avec le bloc
   initial (100 %) est nul au bureau et barres affichées, et vaut la barre
   repliée sinon — la vue descend d'autant. Un navigateur qui ignore `dvh`
   rejette la déclaration : il garde le `inset: 0` d'origine.
   ⚠️ Invisible au banc : aucun navigateur de test ne replie de barre. */
body.f-vueJusquAuBas .view { bottom: calc(100% - 100dvh); }

/* ── TOPBAR ── */
#topbar{
  display:flex;align-items:center;gap:1rem;padding:.5rem 1.2rem;
  background:rgba(5,8,16,.97);border-bottom:1px solid var(--border2);
  position:relative;z-index:50;flex-shrink:0;
  overflow-x:auto;overflow-y:hidden;flex-wrap:nowrap;
  scrollbar-width:none;
}
#topbar::-webkit-scrollbar{display:none;}
.logo{font-family:var(--hud);font-size:1rem;font-weight:900;letter-spacing:.15em;
  background:linear-gradient(90deg,var(--accent1),var(--accent2));
  -webkit-background-clip:text;-webkit-text-fill-color:transparent}
/* v2.15 : nouveau logo — lockup horizontal 1 ligne (symbole + lettrage) */
.brand-block{display:flex;align-items:center}
.logo-img{height:34px;width:auto;display:block;flex-shrink:0}
.logo-sub{font-family:var(--hud);font-size:.55rem;color:var(--turquoise);letter-spacing:.3em;margin-top:-2px;white-space:nowrap}
.topbar-sep{flex:1}
.hud-pill{font-family:var(--mono);font-size:.72rem;color:var(--text2);
  padding:.25rem .7rem;border:1px solid var(--border);border-radius:3px;
  background:rgba(14,165,233,.04)}
/* Pastille de version (à côté de l'heure).
   `white-space:nowrap` parce que la topbar défile horizontalement quand
   elle est serrée : sans ça « v0.0.1 » se casserait en deux lignes et
   ferait grandir la barre. */
#version-badge{display:block;white-space:nowrap;cursor:pointer;
  font-family:var(--mono);transition:color .15s,border-color .15s}
/* La pastille est un vrai <button> : elle ouvre le journal des versions.
   Il faut donc qu'elle SE VOIE cliquable — sans quoi elle a exactement
   l'allure de l'horloge posée à côté, qui, elle, ne fait rien. */
#version-badge:hover{color:var(--accent2);border-color:var(--accent2)}
#version-badge:focus-visible{outline:2px solid var(--accent2);outline-offset:2px}
.nav-btn{font-family:var(--hud);font-size:.62rem;letter-spacing:.1em;
  padding:.3rem .8rem;border:1px solid var(--border2);border-radius:3px;
  background:rgba(255,255,255,.03);color:var(--text2);cursor:pointer;transition:all .15s}
.nav-btn:hover,.nav-btn.active{background:rgba(14,165,233,.1);color:var(--accent2);border-color:var(--accent2)}

/* ── RES BAR ── */
#resbar{
  display:flex;gap:.4rem;padding:.4rem 1.2rem;flex-wrap:wrap;
  background:rgba(5,8,16,.95);border-bottom:1px solid var(--border);flex-shrink:0;
}
/* v2.9.4 : largeur UNIFORME pour tous les encarts (avant : min-width sans max
   → l'encart Lumynite/Distarium s'élargissait quand le taux affichait un texte
   long comme « ⚠ pas de Convertisseur »). Largeur fixe + troncature du bloc
   central : tous les encarts font la même taille, le texte long est coupé en
   « … » (le détail complet reste au survol via l'attribut title). */
.res{display:flex;align-items:center;gap:.4rem;padding:.28rem .6rem;
  background:rgba(255,255,255,.02);border:1px solid var(--border);border-radius:3px;
  width:132px;box-sizing:border-box;position:relative;overflow:hidden;transition:border-color .2s}
.res>div{min-width:0}            /* autorise la troncature du bloc central (flex) */
.res::before{content:'';position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--rc)}
.res:hover{border-color:var(--rc)}
.res-ico{font-size:.9rem;flex-shrink:0}
/* v2.9.6 : logos des ressources = vignettes tirées des illustrations de mine,
   pour coller au visuel des encarts d'extracteurs.
   v2.27 : plus d'émoji dans le bandeau — les NEUF ressources ont leur vignette.
   Les six nouvelles sont découpées dans l'illustration du bâtiment qui les
   produit ou les stocke (extracteurs pour les rares, panneaux solaires pour
   l'Electryte, convertisseur photonique pour la Lumynite, soute pour le
   Distarium) et éclaircies : à 18 px, un cadrage sombre ne se voit pas —
   c'était le défaut du Cobaltis, recadré ici sur sa veine luminescente. */
.res-ico-img{width:18px;height:18px;border-radius:4px;object-fit:cover;display:block;
  box-shadow:0 0 0 1px rgba(255,255,255,.14), 0 1px 2px rgba(0,0,0,.4)}
/* v0.0.5 : la MÊME vignette, en miniature, partout où l'émoji tenait lieu
   d'icône de ressource (pastilles de coût, bilans, infobulles). Posée par
   resIco() — cf. shared/helpers.js. `vertical-align` la cale sur la ligne
   de base du chiffre qu'elle accompagne : sans ça elle pend sous le texte
   et décale la hauteur de chaque pastille. */
.res-mini{width:14px;height:14px;border-radius:3px;object-fit:cover;
  display:inline-block;vertical-align:-2px;flex-shrink:0;
  box-shadow:0 0 0 1px rgba(255,255,255,.14)}
/* Dans une pastille de coût, la vignette respire un peu plus. */
.cost .res-mini{width:15px;height:15px;margin-right:.1rem}
/* Interrupteur ficheSoleilIcones : dans la fiche d'un soleil, la vignette du
   bandeau remplace l'émoji ; un peu plus grande que dans une pastille de coût,
   pour tenir la place que l'émoji occupait à côté du nom. */
body.f-ficheSoleilIcones .sp-res-ico{width:17px;height:17px;vertical-align:-3px;margin-right:.15rem}
.res-name{font-family:var(--hud);font-size:.55rem;color:var(--text2);letter-spacing:.1em;text-transform:uppercase;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.res-val{font-family:var(--mono);font-size:.85rem}
.res-rate{font-family:var(--mono);font-size:.6rem;color:var(--text3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* v2.49 : la production /h passe de .6rem à .64rem — c'est le chiffre qui
   dit si le compte avance, il ne doit pas être le plus discret. */
.res-rate{font-size:.64rem}
/* Electryte affiche « prod / conso » (deux nombres) → un peu plus large. */
#res-electryte{width:150px}
.res-rate.pos{color:var(--accent4)}
.res-rate.neg{color:var(--accent1)}
/* Interrupteur bandeauProductionPotentielle : ce que l'Extracteur produirait
   branché sur un gisement — en gris, entre parenthèses, à côté du « +0/h ».
   ⚠️ LA PARENTHÈSE NE DOIT PAS ÉLARGIR LA COLONNE. Une ligne plus longue
   (`nowrap`) élargissait le bloc de texte, qui ne tenait plus à côté de
   l'icône : l'encart passait à la ligne (`flex-wrap`), le nom tombait sous
   la vignette et tout le bandeau grandissait d'une vingtaine de pixels (vu
   au navigateur à 1500 px). Le bloc prend donc la place qui reste, et la
   ligne se coupe en « … » si elle ne tient pas. */
/* ⚠️ UN CRAN PLUS PETIT (.9em, 10,5 px) : sous le plancher de 11 px, et
   c'est voulu — dans cette boîte fixe, « +0/h (12.5k) » ne tient qu'à cette
   taille, et la règle préfère un chiffre un peu plus petit à un chiffre
   coupé. */
.res-rate .res-rate-potentiel{color:var(--text3);font-size:.9em}
.res>div:has(.res-rate-potentiel){flex:1 1 0}
/* v2.49 — COLONNE DROITE des encarts : capacité + délai de remplissage.
   La capacité était un texte gris .6rem noyé dans le décor ; elle devient
   une vraie ligne (« / 327.6M »), et gagne dessous le TEMPS AVANT SOUTE
   PLEINE au débit courant (« ⏳ 4j 12h »). Pleine → « ■ pleine » ambré. */
/* ── Colonne de droite : base, puis la mise en page détaillée ── */
.res-right{flex:1;min-width:0;display:flex;align-items:center;
  justify-content:flex-end;overflow:hidden}
.res-cap{font-family:var(--mono);font-size:.6rem;color:var(--text3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.res-eta{display:none}

/* ── LA MISE EN PAGE DÉTAILLÉE ──
   La colonne de droite d'origine n'a pas la place : sur un encart de
   ~150 px, « 200.0k » en gros PLUS « / 1.28M » PLUS un délai, côte à côte,
   finissent en « / 1.2… ». La capacité et le délai prennent donc une
   DEUXIÈME LIGNE, pleine largeur, sous le bloc principal : délai à gauche,
   capacité à droite — l'encart grandit un peu, rien n'est tronqué. */
.res{flex-wrap:wrap;row-gap:1px}
.res-right{flex:1 1 100%;flex-direction:row;
  align-items:baseline;justify-content:space-between;gap:.4rem}
.res-cap{font-size:.66rem;color:var(--text2);margin-left:auto}
.res-eta{display:block;font-family:var(--mono);font-size:.58rem;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.res-eta:empty{display:none}
.res-eta.plein{color:#fbbf24}
/* v0.0.28 — ÉCHELLE DE COULEUR DU DÉLAI AVANT SOUTE PLEINE (flag
   `etaSouteCouleurs`). Le gris de `--text3` ne se voyait pas et ne disait
   rien : il fallait lire le chiffre pour savoir s'il y avait urgence.
   Rouge sous 6 h · orange de 6 à 24 h · bleu de 24 à 72 h · vert au-delà.
   Teintes prises claires (palier 400 de la palette) : sur le fond sombre
   des encarts, les teintes saturées passent mal en 0.58rem. */
.res-eta.eta-rouge {color:#f87171}
.res-eta.eta-orange{color:#fb923c}
.res-eta.eta-bleu  {color:#60a5fa}
.res-eta.eta-vert  {color:#4ade80}
.res-bar-bg{position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--border)}
.res-bar-fill{height:100%;background:var(--rc);opacity:.5;transition:width .8s}

/* ════════════════════════════════════════
   GALAXY VIEW
════════════════════════════════════════ */
#view-galaxy{background:var(--bg)}
#galaxy-canvas{flex:1;position:relative;overflow:hidden;cursor:default}
canvas#gc{position:absolute;inset:0;width:100%;height:100%}

/* Planet tooltip */
#planet-tooltip{
  position:absolute;pointer-events:none;z-index:100;
  background:rgba(5,8,16,.95);border:1px solid var(--border2);border-radius:6px;
  padding:.7rem 1rem;min-width:200px;display:none;
}
.pt-name{font-family:var(--hud);font-size:.8rem;color:var(--text);margin-bottom:.4rem}
.pt-row{display:flex;justify-content:space-between;font-size:.72rem;margin:.15rem 0}
.pt-label{color:var(--text2)}
.pt-val{font-family:var(--mono);color:var(--text)}

/* Planet panel (click) */
#planet-panel{
  position:absolute;right:1rem;top:50%;transform:translateY(-50%);
  width:260px;background:rgba(5,8,16,.97);border:1px solid var(--border2);
  border-radius:8px;padding:1rem;z-index:100;display:none;
}
.pp-title{font-family:var(--hud);font-size:.85rem;color:var(--text);margin-bottom:.3rem}
.pp-sub{font-size:.7rem;color:var(--text2);margin-bottom:.8rem}
.pp-section{font-family:var(--hud);font-size:.58rem;color:var(--text3);letter-spacing:.15em;
  text-transform:uppercase;margin:.7rem 0 .3rem}
.pp-res-bar{display:flex;align-items:center;gap:.5rem;margin:.25rem 0;font-size:.72rem}
.pp-res-name{width:80px;color:var(--text2)}
.pp-res-track{flex:1;height:5px;background:var(--border);border-radius:3px;overflow:hidden}
.pp-res-fill{height:100%;border-radius:3px}
.pp-res-pct{font-family:var(--mono);font-size:.65rem;color:var(--text);width:35px;text-align:right}
/* « disponible / de base » à droite de la part — le disponible doré, la
   base en retrait. Deux colonnes fixes : les chiffres tombent les uns sous
   les autres d'une ligne à l'autre. */
.pp-res-val{font-family:var(--mono);font-size:.65rem;color:var(--gold);width:44px;text-align:right;white-space:nowrap}
.pp-res-base{font-family:var(--mono);font-size:.65rem;color:var(--text2);width:50px;white-space:nowrap}
/* Le temps avant que le gisement soit vide, trois caractères au plus
   (« 23h », « 4m », « >1a ») ; le premier à tomber en doré. */
.pp-res-eta{font-family:var(--mono);font-size:.65rem;color:var(--text2);width:24px;text-align:right;white-space:nowrap}
.pp-res-eta.is-premier{color:var(--gold)}
/* v0.0.8 : l'amas orbital, sous la composition — en chiffres ; ses
   notes réutilisent
   .pps-note / .is-mort, déjà stylées pour ce panneau. */
.pp-amas{font-family:var(--mono);font-size:.72rem;color:var(--gold);margin:.3rem 0}
.pp-amas .pps-note{font-family:var(--body)}
/* Interrupteur raidDixPhases : les Décombres d'une bataille, sous l'amas.
   Les ressources passent à la ligne au lieu de déborder (jamais de
   space-between : la dernière ligne prendrait des écarts absurdes). */
.pp-decombres{margin:.3rem 0}
.pp-decombres[hidden]{display:none}
.pp-dec-total{font-family:var(--mono);font-size:.95rem;color:var(--gold)}
.pp-dec-liste{display:flex;flex-wrap:wrap;gap:.25rem .7rem;margin:.25rem 0;font-family:var(--mono);font-size:.86rem;color:var(--text)}
.pp-dec-res{white-space:nowrap}
.pp-dec-btn{margin:.3rem 0 .2rem;padding:.35rem .8rem;font-size:.86rem;cursor:pointer;color:var(--gold);
  background:rgba(214,163,92,.10);border:1px solid rgba(214,163,92,.45);border-radius:6px}
.pp-dec-btn:hover{background:rgba(214,163,92,.2)}
/* Plancher de lisibilité (11 px) pour la note de ce bloc : la classe
   partagée .pps-note est plus petite. */
#planet-panel .pp-decombres .pps-note{font-size:.79rem;line-height:1.45}
/* L'onglet « Récolter » de la Rampe : ses textes au plancher de 11 px au
   moins (les classes partagées des cartes de destination sont plus petites). */
.rp-rec .rpb-dest-nom{font-size:.9rem}
.rp-rec .rpb-dest-sous{font-size:.8rem}
.rp-rec .rp-note,.rp-rec .rp-aide{font-size:.8rem}
.rp-rec .rpb-dests{margin-bottom:.7rem}
.rp-rec .rp-delai,.rp-rec .rp-flotte-nom{font-size:.85rem}
/* Le bouton d'envoi, dans l'ambre des Décombres (sans style propre, un
   .btn-primary hors de ses écrans retombe sur le bouton du navigateur). */
.rp-rec .btn-primary.rp-envoyer{padding:8px 18px;background:linear-gradient(90deg,#8a5a1c,#d6a35c);
  border:none;border-radius:6px;color:#1a1204;font-weight:700;cursor:pointer;font-family:inherit;font-size:.9rem}
.rp-rec .btn-primary.rp-envoyer:hover{filter:brightness(1.1)}
/* Interrupteur raidDixPhases (étape 5) : la note de la salve orbitale du Viseur,
   au plancher de 11 px (la classe partagée y est plus petite). */
.rpv-salve .rp-aide{font-size:.8rem;line-height:1.45}
/* Interrupteur raidDixPhases (étape 4) : l'onglet Renfort, dans le rouge du raid. */
.rp-rf .btn-primary.rp-envoyer{background:linear-gradient(90deg,#9f1239,#f87171);color:#fff}
/* ── v0.0.5 : le détail sous la composition ──
   La ligne du premier gisement vide, et les notes (sol épuisé, rien ne
   creuse, transmutation active). */
.pp-stock-detail:empty{display:none}
.pps-eta{font-size:.72rem;color:var(--text);margin:.15rem 0 .5rem}
.pps-eta strong{color:var(--gold);font-family:var(--mono)}
.pps-eta-sub{color:var(--text3);font-size:.62rem;display:block;margin-top:.1rem}
/* Le conteneur reste une grille, que ses lignes traversent de part en part :
   c'est elle qui tient leurs marges (une grille ne les fusionne pas). */
.pp-stock-detail{
  display:grid;grid-template-columns:minmax(5.6rem,1fr) auto auto auto auto;
  column-gap:.5rem;align-items:center}
.pp-stock-detail:empty{display:none}
.pps-eta,
.pps-note{grid-column:1 / -1}
.pps-note{font-size:.6rem;color:var(--text3);line-height:1.45;margin-top:.4rem}
.pps-note.is-mort{color:var(--accent1)}
.pp-status{font-size:.7rem;margin:.4rem 0}
.pp-status.anchored{color:var(--accent4)}
.pp-status.free{color:var(--text2)}
.pp-status.occupied{color:var(--accent1)}
/* v0.0.128 : QUI est ancré ici (fiche de planète).
   Deux lignes par vaisseau — le nom + la classe, puis le joueur + sa
   relation. Les teintes de relation sont CELLES DES MARQUEURS DE LA CARTE
   (presAlly/presFollowed/presNeutral de map.js) : le triangle vert de la
   carte et l'étiquette « allié » de la fiche doivent parler d'une seule
   voix, sinon le joueur doit apprendre deux codes couleur. */
.pp-occupants{margin:.2rem 0 .4rem}
.pp-occ-row{padding:.28rem 0;border-bottom:1px solid var(--border)}
.pp-occ-row:last-of-type{border-bottom:none}
.pp-occ-l1{display:flex;align-items:baseline;justify-content:space-between;gap:.5rem;font-size:.72rem}
.pp-occ-vm{color:var(--text);font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pp-occ-classe{font-size:.62rem;white-space:nowrap;color:var(--text2)}
.pp-occ-l2{font-size:.64rem;color:var(--text2);margin-top:.1rem;display:flex;align-items:center;gap:.35rem}
.pp-occ-l2 b{color:var(--text)}
.pp-occ-rel{font-family:var(--hud);font-size:.5rem;letter-spacing:.08em;text-transform:uppercase;
  padding:.06rem .3rem;border-radius:3px;border:1px solid currentColor}
.pp-occ-rel.rel-mine{color:var(--accent2)}
.pp-occ-rel.rel-ally{color:#34d399}
.pp-occ-rel.rel-followed{color:#fbbf24}
.pp-occ-rel.rel-neutral{color:#94a3b8}
.pp-occ-note{font-size:.6rem;color:var(--text3);line-height:1.45;margin-top:.35rem}
/* interrupteur `lunes` — la section « Lunes » de la fiche planète, la fenêtre
   de choix de lune et la ligne d'empreinte. Trois lignes par lune : le nom et
   la taille, le penchant, puis ce qui y est bâti (et le geste possible).
   ⚠️ PLANCHER DE LISIBILITÉ : rien sous 0,79 rem (11 px) — ce sont des
   éléments neufs, ils naissent au plancher même si leurs voisins de la
   fiche sont encore en dessous.
   ⚠️ RANGÉES REPLIABLES SANS `space-between` : le geste passe à la ligne au
   téléphone, et `space-between` y creuserait des écarts absurdes. */
.pp-lunes{margin:.2rem 0 .4rem}
.lune-ligne{padding:.4rem 0;border-bottom:1px solid var(--border)}
.lune-ligne:last-of-type{border-bottom:none}
.lune-l1{display:flex;flex-wrap:wrap;align-items:baseline;gap:.2rem .6rem;font-size:.86rem}
.lune-nom{color:var(--text);font-weight:600;white-space:nowrap}
.lune-taille{color:var(--text2);font-size:.79rem}
.lune-l2{font-size:.79rem;color:var(--text2);margin-top:.12rem;line-height:1.4}
.lune-oppose{color:var(--text3)}
.lune-l3{display:flex;flex-wrap:wrap;align-items:center;gap:.3rem .6rem;margin-top:.22rem;font-size:.79rem;line-height:1.4}
.lune-etat{color:var(--text2);min-width:0}
.lune-etat.est-libre{color:#34d399}
.lune-etat.est-service{color:var(--text)}
.lune-etat.est-abandon{color:var(--gold)}
.lune-etat.est-chantier{color:var(--accent2)}
.lune-btn{font-size:.79rem;padding:.3rem .65rem;border-radius:5px;border:1px solid var(--accent2);
  background:rgba(14,165,233,.12);color:var(--text);cursor:pointer;line-height:1.3;text-align:left}
.lune-btn:hover{background:rgba(14,165,233,.22)}
.lune-btn.est-renover{border-color:var(--gold);background:rgba(245,158,11,.12)}
.lune-btn.est-renover:hover{background:rgba(245,158,11,.22)}
/* Interrupteur renovationDuree : le second choix devant un bâtiment à
   l'abandon — le remplacer par le sien —, et les deux boutons côte à côte
   (ils passent à la ligne au téléphone). */
body.f-renovationDuree .lune-actions{display:inline-flex;flex-wrap:wrap;gap:.3rem}
body.f-renovationDuree .lune-btn.est-remplacer{border-color:var(--accent2);background:rgba(14,165,233,.10)}
.lune-refus{color:var(--text3)}
.lune-note{font-size:.79rem;color:var(--text3);line-height:1.45;margin:.35rem 0}
.lune-vide{font-size:.79rem;color:var(--text3);padding:.3rem 0;line-height:1.45}
.lune-empreinte{font-size:.79rem;color:var(--text2);margin-top:.55rem;line-height:1.5}
.lune-empreinte b{color:var(--text)}
.lune-note-inline{color:var(--text3)}
.lunes-modale{max-width:600px}
/* interrupteur `lunesVue` — la fiche d'une lune, dans la fiche de planète
   passée en « mode lune » : tout le reste de la fiche se cache, la croix de
   fermeture reste. */
#planet-panel.pp-mode-lune > :not(#pp-lune-vue):not(.btn-close){display:none !important}
.pp-lune-vue[hidden]{display:none}
.lune-fiche-tete{display:flex;align-items:center;gap:.8rem;margin-bottom:.4rem}
.lune-skin{border-radius:50%;flex:0 0 auto;display:block}
.lune-nom .lune-skin{display:inline-block;vertical-align:-5px;margin-right:.2rem}
.lune-fiche-sous{font-size:.86rem;color:var(--text2)}
.lune-fiche-ligne{font-size:.86rem;color:var(--text);line-height:1.5}
.lune-fiche-actions{display:flex;flex-wrap:wrap;gap:.45rem;margin:.9rem 0 .4rem}
.lune-retour{margin-top:1rem;font-size:.86rem;padding:.4rem .7rem;border-radius:5px;cursor:pointer;
  border:1px solid var(--border);background:transparent;color:var(--text2)}
.lune-retour:hover{color:var(--text)}
.lune-fiche{font-size:.79rem;color:var(--text2);line-height:1.45;margin:.35rem 0 .5rem}
.lune-fiche b{color:var(--text)}
/* v2.0.6 : bloc détail du transit (couple Réacteur+Techno, bonus Explorer) */
.pp-transit-detail{font-size:.62rem;color:var(--text3,#8aa);margin:.2rem 0 .4rem;line-height:1.4}
.pp-transit-detail .pp-badge-intra,.pp-transit-detail .pp-badge-inter{display:inline-block;padding:.05rem .35rem;border-radius:3px;font-weight:700;font-size:.55rem;margin-right:.3rem;letter-spacing:.5px}
.pp-transit-detail .pp-badge-intra{background:rgba(96,165,250,0.25);color:#93c5fd}
.pp-transit-detail .pp-badge-inter{background:rgba(168,85,247,0.25);color:#d8b4fe}
.pp-transit-detail .pp-badge-bonus{display:inline-block;padding:.05rem .35rem;border-radius:3px;background:rgba(34,197,94,0.2);color:#86efac;font-size:.55rem;margin-left:.3rem}
.pp-transit-detail .pp-warn{color:#fca5a5}
/* v0.0.146 — LE TROP-PLEIN DES SOUTES SE LIT.
   ⚠️ SA PROPRE CLASSE, ET PAS UN RÉGLAGE DE `.pp-warn` : ce bloc de détail
   tourne à 10,92 px mesurés, sous le plancher maison (`--badge-font-size:
   11px`). Remonter `.pp-warn` remonterait AUSSI les quatre avertissements
   qui vivent déjà là — Distarium manquant, réacteur requis, flottes dehors,
   Berceau détruit — c'est-à-dire repeindre un écran que ce lot ne touche
   pas. La règle vaut pour CE QU'ON AJOUTE : la ligne nouvelle naît au-dessus
   du plancher, les anciennes attendent leur propre lot.
   ⚠️ ET LA BOÎTE NE GROSSIT PAS : `#pp-transit-detail` défile avec la fiche,
   il n'a ni hauteur fixe ni `overflow:hidden` — mesuré aux deux formats,
   rien n'est tronqué. */
.pp-transit-detail .pp-surplus{font-size:.82rem;line-height:1.45}
/* v0.0.152 — LE VOL OFFERT PAR LA VOIE SE LIT, LUI AUSSI.
   ⚠️ MÊME RAISON, MÊME REMÈDE QUE `.pp-surplus` JUSTE AU-DESSUS : la
   pastille verte de ce bloc (`.pp-badge-bonus`) tourne à 7,7 px mesurés au
   téléphone — bien sous le plancher maison (`--badge-font-size: 11px`, soit
   0,79 rem sur une racine à 14 px). Remonter `.pp-badge-bonus` remonterait
   AUSSI le badge de bonus classe/coalition qui vit là, c'est-à-dire
   repeindre un écran que ce lot ne touche pas. La règle vaut pour CE QU'ON
   AJOUTE.
   ⚠️ ET C'EST LA PHRASE LA PLUS IMPORTANTE DE LA FICHE au moment où elle
   s'affiche : elle annonce que ce départ-là ne coûtera rien. Illisible, la
   récompense de tout un chapitre passe inaperçue.
   ⚠️ `display:block` ET NON `inline-block` : à 0,82 rem la phrase fait
   soixante-et-onze caractères et se serrerait contre le badge INTER. Sur sa
   propre ligne elle se replie proprement — mesuré aux deux formats, rien
   n'est tronqué et la fiche ne déborde pas (elle défile, elle n'a pas de
   hauteur fixe). */
.pp-transit-detail .pp-vol-offert{
  display:block;margin:.35rem 0 0;padding:.2rem .45rem;
  font-size:.82rem;line-height:1.4;border-radius:3px;
  background:rgba(34,197,94,.16);color:#86efac}
.btn-anchor{width:100%;padding:.5rem;margin-top:.6rem;
  font-family:var(--hud);font-size:.65rem;letter-spacing:.1em;
  border:1px solid var(--accent2);border-radius:4px;
  background:rgba(14,165,233,.08);color:var(--accent2);
  cursor:pointer;transition:all .15s}
.btn-anchor:hover:not(:disabled){background:rgba(14,165,233,.2)}
.btn-anchor:disabled{opacity:.4;cursor:not-allowed;border-color:var(--border);color:var(--text3)}
/* v0.0.127 : le bouton de repère personnel des fiches (planète
   ET soleil). Même gabarit que .btn-anchor, mais la teinte fuchsia du
   marqueur de la carte — le bouton et l'anneau parlent de la même chose.
   ⚠️ La règle [hidden] est posée exprès (leçon v2.69/v0.0.71/v0.0.72) :
   le bouton naît caché et ne s'affiche que si le flag est déployé. */
.btn-marque{width:100%;padding:.5rem;margin-top:.6rem;
  font-family:var(--hud);font-size:.65rem;letter-spacing:.1em;
  border:1px solid rgba(232,121,249,.55);border-radius:4px;
  background:rgba(232,121,249,.07);color:#e879f9;
  cursor:pointer;transition:all .15s}
.btn-marque:hover:not(:disabled){background:rgba(232,121,249,.18)}
.btn-marque:disabled{opacity:.4;cursor:not-allowed}
.btn-marque.est-marque{background:rgba(232,121,249,.22);border-color:#e879f9}
.btn-marque[hidden]{display:none}
.btn-close{position:absolute;top:.6rem;right:.8rem;background:none;border:none;
  color:var(--text3);cursor:pointer;font-size:1rem;line-height:1}
.btn-close:hover{color:var(--accent1)}

/* Galaxy legend */
#galaxy-legend{
  position:absolute;left:1rem;bottom:1rem;
  background:rgba(5,8,16,.85);border:1px solid var(--border);border-radius:6px;
  padding:.6rem .9rem;font-size:.68rem;z-index:50;
}
.leg-title{font-family:var(--hud);font-size:.55rem;color:var(--text3);letter-spacing:.15em;margin-bottom:.4rem}
.leg-row{display:flex;align-items:center;gap:.5rem;margin:.2rem 0;color:var(--text2)}
.leg-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}

/* Galaxy info bar */
#galaxy-info{
  position:absolute;left:1rem;top:1rem;
  font-family:var(--mono);font-size:.68rem;color:var(--text2);
  background:rgba(5,8,16,.8);border:1px solid var(--border);border-radius:4px;
  padding:.4rem .7rem;z-index:50;
}

/* ───────────────────────────────────────────
   NAVIGATION DES SYSTÈMES (un à la fois + flèches)
─────────────────────────────────────────── */
/* Bandeau du système courant.
   v1.01 : déplacé du centre-haut vers HAUT-GAUCHE (sous la topbar +
   à droite de la sidebar). Il était auparavant centré horizontalement,
   ce qui chevauchait la vue galaxie sans utilité.
   v1.01 : pointer-events:none SUPPRIMÉ — c'était ce qui empêchait le
   clic sur le <select id="si-jump"> dropdown ("Aller à un système…").
   Désormais le bandeau capte les clics, mais reste en arrière-plan
   visuel (z-index modéré). */
#system-indicator{
  position:absolute;
  left:calc(var(--sidebar-width, 60px) + 1rem);
  /* v1.03 : top était à 0.8rem (~13px), ce qui passait SOUS la resbar
     (en position:fixed, top:44px, hauteur ~55px). Le bandeau était donc
     masqué par les ressources. Désormais top:105px = juste sous la resbar. */
  top: 105px;
  background:rgba(5,8,16,.92);border:1px solid var(--border2);border-radius:6px;
  padding:.75rem 1.6rem;z-index:55;text-align:left;min-width:320px;max-width:440px;
}
.si-label{font-family:var(--hud);font-size:.65rem;color:var(--text3);letter-spacing:.2em;display:block}
.si-name{font-family:var(--hud);font-size:1.2rem;color:var(--text);letter-spacing:.05em;margin:.2rem 0}
.si-sub{font-family:var(--mono);font-size:.8rem;color:var(--text2)}
.si-count{font-family:var(--mono);font-size:.75rem;color:var(--gold);margin-top:.2rem}
/* v0.22 : dropdown de saut rapide entre les 30 systèmes */
#si-jump{
  margin-top:.4rem;
  font-family:var(--mono);font-size:.62rem;
  background:rgba(255,255,255,.04);
  border:1px solid var(--border2);
  color:var(--text2);
  padding:.25rem .4rem;
  border-radius:3px;
  cursor:pointer;
  max-width:240px;
  width:100%;
}
#si-jump:hover{background:rgba(255,255,255,.08);color:var(--text)}
#si-jump optgroup{font-style:normal;color:var(--gold);background:#0a0e1c}
#si-jump option{color:var(--text);background:#0a0e1c}

/* Boutons flèches gauche / droite */
.sys-arrow{
  position:absolute;top:50%;transform:translateY(-50%);
  width:54px;height:54px;border-radius:50%;
  background:rgba(5,8,16,.85);border:1px solid var(--border2);
  color:var(--text2);font-size:1.4rem;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  z-index:55;transition:all .15s;
  font-family:var(--hud);
}
.sys-arrow:hover{
  background:rgba(14,165,233,.15);border-color:var(--accent2);color:var(--accent2);
  transform:translateY(-50%) scale(1.05);
}
.sys-arrow:active{transform:translateY(-50%) scale(.95)}
.sys-arrow.left{left:1rem}
.sys-arrow.right{right:1rem}

/* ════════════════════════════════════════
   MOTHERSHIP VIEW
════════════════════════════════════════ */
#view-ship{flex-direction:column}
#ship-content{flex:1;display:flex;overflow:hidden}
#ship-nav{
  width:190px;background:rgba(5,8,16,.97);border-right:1px solid var(--border);
  padding:.8rem 0;display:flex;flex-direction:column;gap:.1rem;flex-shrink:0;overflow-y:auto;
}
.sn-sect{font-family:var(--hud);font-size:.52rem;color:var(--text3);
  letter-spacing:.2em;padding:.6rem 1rem .2rem;text-transform:uppercase}
.sn-item{display:flex;align-items:center;gap:.5rem;padding:.45rem .9rem;
  cursor:pointer;transition:all .12s;border-left:2px solid transparent;
  color:var(--text2);font-size:.8rem}
.sn-item:hover{background:rgba(255,255,255,.03);color:var(--text);border-left-color:var(--border2)}
.sn-item.active{background:rgba(14,165,233,.07);color:var(--accent2);border-left-color:var(--accent2)}
.sn-ico{font-size:.95rem;width:18px;text-align:center}

/* v2.39 — les sections de MODULES en sous-liste de « Modules ».
   Elles ne sont pas onze destinations de même rang : ce sont les modules
   pris un par un. Un filet vertical part de « Modules » et court le long
   du groupe, chaque entrée s'y raccrochant par un petit trait — la lecture
   d'une liste à puces, sans changer le comportement des items. */
.sn-sub{position:relative;margin-left:1.15rem}
.sn-sub::before{
  content:'';position:absolute;left:0;top:.1rem;bottom:.6rem;
  border-left:1px solid var(--border2,rgba(110,130,170,.28));
}
.sn-sub .sn-item{padding-left:1.15rem;position:relative}
.sn-sub .sn-item::before{
  content:'';position:absolute;left:0;top:50%;width:.7rem;
  border-top:1px solid var(--border2,rgba(110,130,170,.28));
}
/* Le filet de l'entrée active s'allume avec elle, pour qu'on voie d'où
   elle se raccroche. */
.sn-sub .sn-item.active::before{border-top-color:var(--accent2)}

#ship-main{flex:1;padding:1.2rem;overflow-y:auto;position:relative}
/* ════════════════════════════════════════════════════════════════════
   v0.0.9 — CHANTIER DÉTAILLÉ
   La barre du bas est faite pour SURVEILLER ; cette fenêtre est faite
   pour DÉCIDER. D'où deux colonnes côte à côte : la file à gauche avec
   ses deux durées par chantier, la réserve de Nanites à droite avec ce
   qu'elle produit, ce qu'elle coûte et combien de temps elle tient.
   ════════════════════════════════════════════════════════════════════ */
.cd-box{max-width:1080px;width:94%}
.cd-body{padding:14px 16px 18px}
.cd-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px}
.cd-tab{background:rgba(255,255,255,.03);border:1px solid var(--border2,#2d4a6e);
  border-radius:6px;color:var(--text2,#8da4c4);font-family:var(--hud,sans-serif);
  font-size:.72rem;padding:.4rem .7rem;cursor:pointer;display:inline-flex;align-items:center;gap:6px}
.cd-tab:hover{border-color:var(--accent2,#0ea5e9);color:var(--text,#cbd6ea)}
.cd-tab.is-active{background:rgba(14,165,233,.16);border-color:var(--accent2,#0ea5e9);color:#7dd3fc}
.cd-tab-n{font-family:var(--mono,monospace);font-size:.66rem;opacity:.75}

/* Deux colonnes : la file prend les deux tiers, la réserve le tiers.
   Sous 860 px elles s'empilent — deux colonnes de 300 px ne se lisent
   plus, et cette fenêtre est faite pour être lue. */
.cd-cols{display:grid;grid-template-columns:1.9fr 1fr;gap:14px;align-items:start}
@media (max-width:860px){.cd-cols{grid-template-columns:1fr}}

.cd-bloc{background:rgba(8,14,28,.6);border:1px solid var(--border,#1f2937);
  border-radius:8px;padding:12px 14px}
.cd-bloc-titre{font-family:var(--hud,sans-serif);font-size:.78rem;font-weight:700;
  color:var(--accent2,#0ea5e9);letter-spacing:.06em;margin-bottom:10px;
  display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
.cd-niv{font-size:.68rem;color:var(--text3,#6e7691);font-weight:500;letter-spacing:0}
.cd-vide .cd-note{margin:0}
.cd-note{font-size:.68rem;color:var(--text3,#6e7691);line-height:1.5;margin:10px 0 0}

.cd-jauge{height:6px;border-radius:99px;background:rgba(255,255,255,.07);overflow:hidden;margin:6px 0 4px}
.cd-jauge-fill{height:100%;background:linear-gradient(90deg,#0ea5e9,#2dd4bf);border-radius:99px}
.cd-jauge-txt{font-family:var(--mono,monospace);font-size:.7rem;color:var(--text2,#8da4c4);margin-bottom:8px}

.cd-lignes{display:flex;flex-direction:column;gap:2px}
.cd-ligne{display:flex;justify-content:space-between;align-items:baseline;gap:10px;
  font-size:.72rem;padding:3px 0}
.cd-l{color:var(--text2,#8da4c4)}
.cd-v{font-family:var(--mono,monospace);font-weight:600;white-space:nowrap}
.cd-v.g{color:#4ade80} .cd-v.r{color:#fb7185} .cd-v.y{color:var(--gold,#ffd54f)}
.cd-sous{font-size:.66rem;color:var(--text3,#6e7691);line-height:1.5;padding:2px 0 6px}

.cd-item{border:1px solid var(--border,#1f2937);border-radius:7px;padding:9px 11px;margin-bottom:8px;
  background:rgba(255,255,255,.02)}
.cd-item-tete{border-color:rgba(14,165,233,.5);background:rgba(14,165,233,.05)}
.cd-item-accel{border-color:rgba(45,212,191,.6);box-shadow:0 0 14px rgba(45,212,191,.12)}
.cd-item-hdr{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.cd-item-ico{font-size:1rem}
.cd-item-nom{font-size:.8rem;color:var(--text,#cbd6ea);font-weight:600;flex:1;min-width:120px}
.cd-item-lv{font-family:var(--mono,monospace);font-size:.68rem;color:var(--gold,#ffd54f);font-weight:500}
.cd-tag{font-family:var(--hud,sans-serif);font-size:.58rem;letter-spacing:.08em;text-transform:uppercase;
  color:#7dd3fc;border:1px solid rgba(14,165,233,.45);border-radius:99px;padding:1px 7px}
.cd-tag-attente{color:var(--text3,#6e7691);border-color:var(--border,#1f2937)}
/* v0.0.123 — la pastille d'un développement de
   module dans les files : ni « en cours » ni « n-ième », il avance en
   parallèle des chantiers. Violet, la teinte de rien d'autre dans ce bloc. */
.cd-tag-module{color:#c4b5fd;border-color:rgba(139,92,246,.5);cursor:help}
.cd-item-temps{font-size:.72rem;color:var(--text2,#8da4c4);line-height:1.6;margin-top:2px}
.cd-item-temps b{color:var(--text,#cbd6ea);font-family:var(--mono,monospace)}
.cd-gain b{color:#2dd4bf}
.cd-eco{color:#4ade80;font-size:.68rem;margin-left:6px}
/* v0.0.113 : le PRIX de l'accélération, à côté du temps gagné. Turquoise,
   la couleur des Nanites partout ailleurs — et pas le vert du gain : un gain
   et une dépense ne se lisent pas de la même façon. */
.cd-cout{color:#2dd4bf;font-size:.68rem;margin-left:8px;font-family:var(--mono);cursor:help}
.cd-warn{display:block;color:#fbbf24;font-size:.66rem;margin-top:2px}

.cd-accel{background:rgba(255,255,255,.03);border:1px solid var(--border2,#2d4a6e);border-radius:6px;
  color:var(--text2,#8da4c4);font-size:.68rem;padding:.28rem .6rem;cursor:pointer;white-space:nowrap}
.cd-accel:hover{border-color:#2dd4bf;color:#2dd4bf}
.cd-accel.on{background:rgba(45,212,191,.18);border-color:#2dd4bf;color:#2dd4bf}
/* Interrupteur `nanitesEnAttente` : chantier en tête mais EN ATTENTE de ses
   ressources — l'accélération ne tourne pas, le bouton le montre en gris. */
.cd-accel.est-attente,.qbar-slot-accel.est-attente{filter:grayscale(1);opacity:.5;border-color:var(--border2,#2d4a6e);color:var(--text2,#7f93ad);background:rgba(255,255,255,.03)}

.cd-total{display:flex;justify-content:space-between;align-items:baseline;gap:10px;
  border-top:1px solid var(--border,#1f2937);margin-top:10px;padding-top:9px;
  font-size:.74rem;color:var(--text2,#8da4c4)}
.cd-total b{font-family:var(--mono,monospace);color:var(--text,#cbd6ea)}
.cd-total b.g{color:#4ade80}
/* v0.0.9 — la ligne « File terminée » suit celle du total : pas de second
   filet au-dessus, les deux forment un seul pied de bloc. */
.cd-total-fin{border-top:none;margin-top:2px;padding-top:0}
/* v0.0.123 — la ligne « Avec Nanites » du
   récapitulatif : elle suit la ligne du total (pas de second filet) et ses
   trois chiffres gardent les codes couleur des cartes — durée en vert,
   gain en .cd-eco, prix en .cd-cout. Le bloc de droite a le droit de
   passer à la ligne : la fenêtre « Chantier détaillé » est étroite. */
.cd-total-nanites{border-top:none;margin-top:2px;padding-top:0}
.cd-total-nanites>span:last-child{display:flex;flex-wrap:wrap;
  justify-content:flex-end;align-items:baseline;column-gap:2px}
/* L'heure de fin d'un chantier, discrète sous sa durée : c'est un repère
   qu'on cherche, pas l'information principale de la ligne. */
.cd-fin{font-size:.7rem;color:#93c5fd;margin-top:2px}

/* Anchor status bar */
#anchor-bar{
  display:flex;align-items:center;gap:1rem;padding:.5rem 1.2rem;
  background:rgba(10,15,30,.95);border-bottom:1px solid var(--border);
  font-size:.72rem;flex-shrink:0;flex-wrap:wrap;row-gap:.4rem;
}
.anch-label{font-family:var(--hud);font-size:.58rem;color:var(--turquoise);letter-spacing:.1em}
.anch-val{font-family:var(--mono);color:var(--text)}
.anch-val.green{color:var(--accent4)}
.anch-val.red{color:var(--accent1)}
.anch-val.gold{color:var(--gold)}
.anch-sep{color:rgba(45,212,191,.4);margin:0 .2rem} /* v2.9.13 : séparateur en turquoise atténué */

/* Page header */
.pg-hdr{display:flex;align-items:flex-end;justify-content:space-between;
  margin-bottom:1.2rem;padding-bottom:.7rem;border-bottom:1px solid var(--border)}
.pg-title{font-family:var(--hud);font-size:1.1rem;font-weight:700;color:var(--text);letter-spacing:.08em}
.pg-sub{font-size:.72rem;color:var(--text2);margin-top:.2rem}

/* Section */
.sec{display:none}
.sec.active{display:block}

/* Le corps du Fil posé en SECTION : la fenêtre lui donnait ses marges et
   sa hauteur, une section doit se les donner elle-même. */
.cd-body-section{padding:0;max-height:none;overflow:visible}
.cd-body-section .cd-cols{margin-top:10px}

/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — LE FIL EN CHAÎNES
   ════════════════════════════════════════════════════════════════════
   Une ligne par file : poteau · piste · crochet. La largeur d'un maillon
   vaut sa durée, sur une règle horaire commune aux six lignes.
   ⚠️ LA PISTE NE DÉFILE PAS (`overflow:hidden`) : une chaîne qu'il faut
   faire défiler perd tout son intérêt, puisqu'on ne peut plus comparer
   deux files d'un coup d'œil. Ce qui dépasse la fenêtre porte un chevron,
   et le crochet donne la vraie heure. */
.fc-chaines{display:flex;flex-direction:column;gap:0}
.fc-file{
  display:grid;grid-template-columns:172px minmax(0,1fr) 158px;
  gap:14px;align-items:center;padding:10px 0;border-top:1px solid var(--border,#162040);
}
.fc-file:first-child{border-top:none}
.fc-file-vide{opacity:.62}
/* ⚠️ LE CARRÉ GLOBAL EST DE TAILLE FIXE, ET C'EST LA DEMANDE (« il faudrait
   remettre un carré global de taille fixe »). Sa colonne ne bouge pas d'une
   file à l'autre : les totaux des six lignes tombent donc sur la MÊME
   verticale et se comparent d'un coup d'œil. Un carré qui respirerait au gré
   de la longueur des noms ne le permettrait pas. */
.fc-poteau{
  min-width:0;padding:7px 9px;border-radius:8px;
  border:1px solid var(--border,#162040);background:rgba(255,255,255,.022);
  display:flex;flex-direction:column;gap:2px;
}
.fc-file-vide .fc-poteau{background:none;border-color:transparent}
.fc-glob{
  display:grid;grid-template-columns:auto 1fr;gap:1px 7px;align-items:baseline;
  margin-top:5px;padding-top:5px;border-top:1px solid var(--border,#162040);
}
.fc-glob-l{font-size:.55rem;color:var(--text3,#6b8ab0);white-space:nowrap}
.fc-glob-v{
  font-family:var(--mono,monospace);font-size:.7rem;color:var(--text,#c8d6f0);
  text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;
}
.fc-glob-v.g{color:#4ade80}
.fc-glob-sous{font-size:.55rem;color:#4ade80;text-align:right;white-space:nowrap}
.fc-nom-file{
  display:block;font-family:var(--hud,sans-serif);font-size:.62rem;font-weight:700;
  letter-spacing:.05em;color:var(--accent2,#0ea5e9);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;
}
.fc-compte{display:flex;align-items:center;gap:6px;font-size:.58rem;color:var(--text3,#6b8ab0)}
.fc-tout{
  background:rgba(255,255,255,.03);border:1px solid var(--border2,#2d4a6e);border-radius:5px;
  color:var(--text2,#8aa4c8);font-size:.6rem;padding:1px 5px;cursor:pointer;line-height:1.3;
}
.fc-tout:hover{border-color:#2dd4bf;color:#2dd4bf}
.fc-tout.on{background:rgba(45,212,191,.18);border-color:#2dd4bf;color:#2dd4bf}

/* ══════════════════════════════════════════════════════════════════════
   v0.0.151 — LA COCHE DE RESSOURCES D'UNE FILE
   ══════════════════════════════════════════════════════════════════════
   Verte : la soute couvre ce chantier ET tous ceux qui le précèdent sans
   être encore payés. Rouge : elle ne les couvre pas. La bulle dit ce qui
   manque, ressource par ressource.

   ⚠️ LA COULEUR NE PORTE PAS LE MESSAGE À ELLE SEULE. Le glyphe diffère
   (✓ contre ✕) et l'`aria-label` le dit en toutes lettres : un joueur qui
   ne distingue pas le vert du rouge lit quand même le verdict. C'est la
   règle d'accessibilité déjà tenue par les pastilles de la barre du bas.

   ⚠️ `flex:none` EST OBLIGATOIRE, et c'est le défaut que ce projet attrape
   le plus souvent : dans un maillon (`overflow:hidden`, `max-width:260px`),
   une coche qui se laisse comprimer disparaît sur les noms longs — le seul
   signal de l'écran, effacé sans qu'aucune erreur ne le dise. C'est le nom
   qui se tronque, jamais elle. */
.fc-coche{
  flex:none;display:inline-flex;align-items:center;justify-content:center;
  min-width:15px;height:15px;padding:0 3px;margin-left:5px;
  border:1px solid currentColor;border-radius:99px;
  font-size:.79rem;line-height:1;font-weight:700;cursor:help;
  position:relative;z-index:3;
}
.fc-coche.est-ok{color:#4ade80;background:rgba(74,222,128,.14)}
.fc-coche.est-ko{color:#fb7185;background:rgba(251,113,133,.16)}
/* Dans une vignette de la barre du bas, la coche garde la MÊME taille de
   texte qu'ailleurs — le plancher de 11 px vaut aussi ici. ⚠️ MESURÉ, pas
   supposé : la rangée du haut d'une vignette fait 137 px de contenu pour
   137 px de large au téléphone et 273 px dans le tiroir, à 11 px comme à
   9,5 px — rien n'y est poussé dehors. Et la hauteur ne bouge pas non plus :
   la coche fait 15 px dans une rangée qui en réserve 18 (`min-height`).
   Seule la marge tombe : la rangée a déjà son propre écartement. */
.fc-coche-mini{margin-left:0}

/* ⚠️ LA PISTE S'ENROULE, ELLE NE COUPE PLUS. Tant qu'un maillon valait une
   durée, la couper était le seul choix : décaler d'un pixel aurait menti sur
   l'heure. Sans échelle, plus rien à protéger — une chaîne trop longue
   passe simplement à la ligne, et aucun chantier ne disparaît. */
.fc-piste{position:relative;display:flex;align-items:center;flex-wrap:wrap;
  gap:5px 0;min-width:0}
/* Les remises en état sont PARALLÈLES : sans échelle elles n'ont plus besoin
   d'une ligne chacune et s'alignent côte à côte, en s'enroulant. Sur le
   vaisseau de Loryan, cinq remises en état passent de cinq rangées à deux. */
.fc-pistes{display:flex;flex-wrap:wrap;gap:6px;min-width:0}
.fc-piste-para{width:auto;flex:0 1 auto}
/* ⚠️ DEUX FOIS PLUS GROS, ET C'EST LA DEMANDE (« le détail à droite il est
   minuscule on voit rien, grossis-le au moins x2 »). C'est le chiffre qu'on
   vient chercher sur cet écran — à quelle heure la file sera vide — et il
   était écrit plus petit que le nom d'un chantier qui attend son tour.
   ⚠️ SA COLONNE PASSE À 158 px ET LE CHIFFRE PEUT S'ENROULER : « demain à
   01:44 » et « le 12/08 à 03:12 » sont des réponses possibles de
   `quandFini`, et un chiffre doublé qui se coupe au milieu ne vaut pas mieux
   qu'un chiffre minuscule. Il passe donc à la ligne plutôt que d'être rogné.
   Mesuré : 9,2 px avant, 18,5 px après — le facteur deux demandé. */
.fc-crochet{
  display:flex;flex-direction:column;align-items:flex-end;gap:1px;
  text-align:right;min-width:0;
}
.fc-crochet-l{
  font-family:var(--hud,sans-serif);font-size:.52rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text3,#6b8ab0);white-space:nowrap;
}
.fc-crochet b{
  font-family:var(--mono,monospace);font-size:1.32rem;line-height:1.12;
  font-weight:400;color:#93c5fd;font-variant-numeric:tabular-nums;
  white-space:normal;overflow-wrap:anywhere;
}
.fc-file-vide .fc-crochet b{color:var(--text3,#6b8ab0)}
.fc-vide{font-size:.64rem;color:var(--text3,#6b8ab0);font-style:italic}
.fc-vide{font-size:.64rem;color:var(--text3,#6b8ab0);font-style:italic}
/* LE MAILLON d'un chantier qui attend son tour : une pastille nue, à la
   taille de ce qu'elle dit. `max-width` pour qu'un nom à rallonge ne
   pousse pas la chaîne entière à la ligne. */
.fc-maillon{
  position:relative;flex:0 1 auto;max-width:260px;height:26px;
  display:flex;align-items:center;padding:0 9px;
  border:1.5px solid var(--border2,#2d4a6e);border-radius:99px;
  background:rgba(255,255,255,.025);overflow:hidden;z-index:1;
}
.fc-nom{
  position:relative;z-index:2;font-size:.64rem;font-weight:600;color:var(--text,#c8d6f0);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.fc-lv{font-family:var(--mono,monospace);font-size:.6rem;color:var(--gold,#f59e0b);font-weight:500}
/* v0.0.155 : le ✕ d'une pastille en attente —
   même gabarit que celui des vignettes de la barre (`.qbar-slot-cancel`).
   `flex:none` : c'est le NOM qui cède la place (ellipse), jamais le bouton —
   un bouton tronqué est un bouton disparu. Et il reste au-dessus de la
   jauge d'avancement (`.fc-avance`, z-index:1), comme le nom. */
.fc-annul{
  position:relative;z-index:2;flex:none;
  width:18px;height:18px;margin:0 -4px 0 6px;padding:0;
  display:inline-flex;align-items:center;justify-content:center;
  border-radius:50%;cursor:pointer;line-height:1;
  font-size:var(--badge-font-size,11px);
  color:var(--accent1,#e94560);background:rgba(233,69,96,.08);
  border:1px solid rgba(233,69,96,.3);
}
.fc-annul:hover{background:rgba(233,69,96,.22);border-color:var(--accent1,#e94560)}
/* Au doigt, la cible grossit — mêmes DEUX seuils que le reste du bloc fc-
   (cf. « AU TÉLÉPHONE, LE POTEAU PASSE AU-DESSUS DE SA CHAÎNE ») : la
   pastille fait 26px de haut, un ✕ de 22px y tient sans la grossir. */
@media (max-width:820px){ .fc-annul{width:22px;height:22px} }
body.mobile-mode .fc-annul{width:22px;height:22px}
/* v0.0.x — LES FLÈCHES ↑/↓ DE RÉORDONNANCEMENT, à côté du ✕. Neutres (pas
   le rouge de l'annulation), même gabarit que .fc-annul pour rester
   atteignables au doigt (cf. règle mobile en dessous). */
.fc-ordre-grp{position:relative;z-index:2;flex:none;display:inline-flex;gap:2px;margin:0 0 0 4px}
.fc-ordre{
  width:18px;height:18px;padding:0;display:inline-flex;align-items:center;justify-content:center;
  border-radius:5px;cursor:pointer;line-height:1;font-size:var(--badge-font-size,11px);
  color:var(--accent2,#4aa3ff);background:rgba(120,170,255,.10);
  border:1px solid rgba(120,170,255,.30);
}
.fc-ordre:hover{background:rgba(120,170,255,.24);border-color:var(--accent2,#4aa3ff)}
@media (max-width:820px){ .fc-ordre{width:22px;height:22px} }
body.mobile-mode .fc-ordre{width:22px;height:22px}
.fc-maillon.fc-pale{opacity:.66}
.fc-tete{border-color:rgba(14,165,233,.6);background:rgba(14,165,233,.08)}
.fc-accel{border-color:#2dd4bf;background:rgba(45,212,191,.12);
  box-shadow:0 0 12px rgba(45,212,191,.16)}
/* Une tête qui n'a pas encore pu payer : le maillon est OUVERT. Rien n'est
   débité, la chaîne se ressoude dès que la soute est remplie. */
.fc-rompu{border-color:#fb7185;border-style:dashed;background:rgba(251,113,133,.08)}

/* L'avancement de l'unité EN COURS, derrière le nom. */
.fc-avance{
  position:absolute;left:0;top:0;bottom:0;z-index:1;display:block;
  background:linear-gradient(90deg,rgba(14,165,233,.45),rgba(45,212,191,.45));
}

/* LES CRANS D'UN LOT — un dégradé répété, pas N balises : quatre cents
   Cargos donneraient quatre cents éléments à réécrire chaque seconde. Et
   quand la place manque, les crans se resserrent tout seuls jusqu'à ne
   plus former qu'une barre pleine : la dégradation est douce. */
/* ⚠️ ON DESSINE LES TRAITS DE SÉPARATION, PAS LES CASES. Remplir chaque
   cran donnait, à trois unités, trois grands aplats très pâles qui se
   lisaient comme une BOÎTE VIDE — attrapé au navigateur. Un trait par
   frontière, lui, se lit juste à toutes les tailles : deux unités donnent
   un trait, quarante donnent un peigne. */
.fc-crans::before{
  content:"";position:absolute;inset:5px 0;z-index:1;pointer-events:none;
  background:repeating-linear-gradient(90deg,
    transparent 0 calc(var(--fc-pas) - 1.5px),
    rgba(255,255,255,.30) calc(var(--fc-pas) - 1.5px) var(--fc-pas));
}
.fc-crans.fc-accel::before{
  background:repeating-linear-gradient(90deg,
    transparent 0 calc(var(--fc-pas) - 1.5px),
    rgba(45,212,191,.55) calc(var(--fc-pas) - 1.5px) var(--fc-pas));
}

/* LE RACCORD entre deux maillons. Allumé = les Nanites y coulent. */
.fc-raccord{
  flex:none;width:13px;height:8px;margin:0 -5px;z-index:2;border-radius:99px;
  border:1.5px solid var(--border2,#2d4a6e);background:var(--bg,#050810);
}
.fc-raccord.on{border-color:#2dd4bf;background:rgba(45,212,191,.35);
  box-shadow:0 0 7px rgba(45,212,191,.5)}

/* ════════════════════════════════════════════════════════════════════
   LE MAILLON DE TÊTE, EN GRAND (affichage C4)
   ════════════════════════════════════════════════════════════════════
   Le détail vit DANS le maillon de tête, qui devient une boîte : nom,
   jauge, durées avec et sans Nanites, prix, heure de fin, boutons.

   ⚠️ ELLE EST BORNÉE, ET C'EST TOUT LE SUJET DU RETRAIT DE L'ÉCHELLE.
   Tant que sa largeur valait sa durée, un chantier de treize heures dans
   une fenêtre de trois donnait une boîte PLEINE LARGEUR — et comme tous
   les chantiers d'un vrai vaisseau dépassent la fenêtre, les six lignes
   devenaient six barres d'un bout à l'autre de l'écran. Elle prend
   maintenant la largeur de son contenu, entre un plancher (sous lequel sa
   ligne de chiffres deviendrait illisible) et un plafond. */
.fc-boite{
  flex:0 1 auto;min-width:260px;max-width:420px;height:auto;
  flex-direction:column;align-items:stretch;gap:3px;
  padding:6px 9px;border-radius:9px;overflow:visible;
}
.fc-boite-hdr{display:flex;align-items:center;gap:7px;flex-wrap:wrap;min-width:0}
.fc-boite-ico{font-size:.9rem;flex:none}
.fc-boite-ico-img{width:20px;height:20px;border-radius:4px;flex:none;object-fit:cover}
.fc-boite-nom{
  flex:1;min-width:70px;font-size:.72rem;font-weight:600;color:var(--text,#c8d6f0);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.fc-boite-jauge{
  position:relative;display:block;height:5px;border-radius:99px;
  background:rgba(255,255,255,.08);overflow:hidden;
}
/* La jauge d'un LOT porte ses crans : un trait par unité restante, mêmes
   traits de séparation que les maillons de la chaîne. */
.fc-jauge-crans::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:repeating-linear-gradient(90deg,
    transparent 0 calc(var(--fc-pas) - 1.5px),
    rgba(7,11,20,.75) calc(var(--fc-pas) - 1.5px) var(--fc-pas));
}
.fc-boite-jauge>i{display:block;height:100%;
  background:linear-gradient(90deg,#0ea5e9,#2dd4bf);border-radius:99px}
/* ⚠️ LA LIGNE DE CHIFFRES S'ENROULE, elle ne se tronque pas : dans une
   boîte au plancher, « avec 3m 39s − 18m 17s 🧬 4.0k » passerait à la ligne
   ou disparaîtrait. Elle s'enroule, et la boîte grandit en hauteur — c'est
   la seule dimension qui ne coûte rien à l'échelle. */
.fc-boite-txt{
  display:block;font-size:.66rem;line-height:1.5;color:var(--text2,#8aa4c8);
}
.fc-boite-txt b{font-family:var(--mono,monospace);color:var(--text,#c8d6f0)}
.fc-boite .cd-accel,.fc-boite .cd-annul{font-size:.6rem;padding:.18rem .45rem;flex:none}
.fc-boite .cd-tag{flex:none}
.fc-tq{color:#2dd4bf}

/* ⚠️ AU TÉLÉPHONE, LE POTEAU PASSE AU-DESSUS DE SA CHAÎNE. C'est le format
   qui souffre le plus : à 390 px, garder le poteau et le crochet sur les
   côtés ne laisserait que deux cents pixels à la piste, et l'échelle n'y
   dirait plus rien. En pleine largeur, elle reste lisible.
   ⚠️ DEUX SEUILS, PAS UN : sous 820 px de largeur RÉELLE, et aussi sous
   `body.mobile-mode` — un joueur qui force le mode téléphone sur un grand
   écran doit avoir la même vue que celui dont la fenêtre est étroite. */
@media (max-width:820px){
  .fc-file{grid-template-columns:1fr;gap:5px}
  .fc-file{padding:11px 0}
  .fc-poteau{flex-direction:row;flex-wrap:wrap;align-items:baseline;gap:4px 10px}
  .fc-glob{flex:1 1 100%;margin-top:3px;padding-top:3px}
  .fc-crochet{flex-direction:row;align-items:baseline;justify-content:flex-start;gap:8px}
  .fc-crochet b{font-size:1.2rem}
  /* Sous 820 px, la boîte prend la ligne : son plancher avale à lui seul
     les deux tiers de la piste. Le reste de la chaîne s'enroule dessous. */
  .fc-boite{max-width:100%;min-width:0;flex-basis:100%;order:-1}
}
body.mobile-mode .fc-file{grid-template-columns:1fr;gap:5px}
body.mobile-mode .fc-poteau{flex-direction:row;flex-wrap:wrap;align-items:baseline;gap:4px 10px}
body.mobile-mode .fc-glob{flex:1 1 100%;margin-top:3px;padding-top:3px}
body.mobile-mode .fc-crochet{flex-direction:row;align-items:baseline;justify-content:flex-start;gap:8px}
body.mobile-mode .fc-boite{max-width:100%;min-width:0;flex-basis:100%;order:-1}

/* Build queue */
#queue-box{background:var(--card);border:1px solid var(--border);border-radius:6px;margin-bottom:1.2rem}
.qb-hdr{padding:.5rem .9rem;background:rgba(255,255,255,.02);border-bottom:1px solid var(--border);
  font-family:var(--hud);font-size:.62rem;color:var(--text2);letter-spacing:.1em;
  display:flex;align-items:center;justify-content:space-between}
.qb-count{font-family:var(--mono);font-size:.65rem;color:var(--gold);letter-spacing:0}
.qb-count.full{color:var(--accent1)}
.q-item{display:flex;align-items:center;gap:.6rem;padding:.55rem .9rem;border-bottom:1px solid var(--border);transition:opacity .15s}
.q-item:last-child{border-bottom:none}
.q-item.queued{opacity:.6}                  /* items en attente : un peu effacés */
.q-item.active{background:rgba(14,165,233,.04)}  /* item en construction : highlight bleu léger */
.q-pos{font-family:var(--hud);font-size:.65rem;font-weight:700;
  min-width:28px;text-align:center;
  padding:.2rem .35rem;border-radius:3px;
  background:rgba(255,255,255,.04);color:var(--text2);}
.q-pos.active{background:rgba(14,165,233,.15);color:var(--accent2)}
.q-name{font-size:.78rem;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.q-bar{flex:1.5;height:4px;background:var(--border);border-radius:2px;overflow:hidden;min-width:50px}
.q-fill{height:100%;background:linear-gradient(90deg,var(--accent2),var(--accent3));transition:width .5s}
.q-fill.queued{background:var(--text3);opacity:.4}
.q-time{font-family:var(--mono);font-size:.68rem;color:var(--gold);min-width:54px;text-align:right;flex-shrink:0}
.q-time.queued{color:var(--text3)}
.q-cancel{
  width:28px;height:28px;border-radius:50%;
  border:1px solid var(--border2);
  background:rgba(255,255,255,.02);color:var(--text2);
  font-size:.85rem;font-weight:700;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  transition:all .12s;flex-shrink:0;
}
.q-cancel:hover{background:rgba(233,69,96,.18);border-color:var(--accent1);color:var(--accent1)}
.q-empty{padding:.7rem .9rem;font-size:.72rem;color:var(--text3)}

/* Grid */
/* v2.9.8 : cartes bâtiment COMPACTES, calées sur le gabarit des connecteurs
   (colonnes plus étroites 248px comme .conn-grid, paddings et polices réduits)
   → moins encombrant, plus d'items visibles à l'écran. */
.bld-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(248px,1fr));gap:.8rem}

/* v2.22 : l'ancienne CARTE bâtiment (.bld, .bld-hdr, .bld-body, .bld-desc…)
   a été retirée : les modules affichent désormais des VIGNETTES compactes
   (.bld-tile, plus bas) et tout le détail part dans le panneau de droite.
   Ce qui suit (.bld-stats, .st, .costs, .btn-up) sert aux DEUX : la stat
   clé portée par la vignette, et le panneau de détail. */
.bld-stats{display:flex;flex-direction:column;gap:.2rem;margin-bottom:.5rem}
.st{display:flex;justify-content:space-between;font-size:.66rem}
.st-l{color:var(--text2)}
.st-v{font-family:var(--mono);color:var(--text)}
.st-v.g{color:var(--accent4)}
.st-v.y{color:var(--gold)}
.st-v.r{color:var(--accent1)}
.costs{display:flex;flex-wrap:wrap;gap:.3rem;margin-bottom:.5rem}
.cost{display:flex;align-items:center;gap:.25rem;padding:.18rem .45rem;border-radius:3px;
  font-family:var(--mono);font-size:.65rem;border:1px solid}
.cost.ok{background:rgba(16,185,129,.07);border-color:rgba(16,185,129,.3);color:var(--accent4)}
.cost.ko{background:rgba(233,69,96,.07);border-color:rgba(233,69,96,.3);color:var(--accent1)}
.btn-up{width:100%;padding:.42rem;font-family:var(--hud);font-size:.64rem;letter-spacing:.08em;
  border:1px solid var(--border2);background:rgba(14,165,233,.07);color:var(--accent2);
  border-radius:4px;cursor:pointer;transition:all .12s}
.btn-up:hover:not(:disabled){background:rgba(14,165,233,.17);border-color:var(--accent2)}
.btn-up:disabled{opacity:.35;cursor:not-allowed;color:var(--text3);background:rgba(255,255,255,.02);border-color:var(--border)}
.btn-up.wip{background:rgba(245,158,11,.08);border-color:var(--gold);color:var(--gold);cursor:default}

/* Connectors panel */
.conn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:.8rem}
.conn-card{background:var(--card);border:1px solid var(--border);border-radius:6px;padding:.8rem}
.conn-name{font-family:var(--hud);font-size:.7rem;color:var(--text);margin-bottom:.4rem}
.conn-rare-tag{display:inline-block;font-family:var(--mono);font-size:.55rem;padding:.1rem .3rem;margin-left:.3rem;background:rgba(139,92,246,.15);border:1px solid #8b5cf6;color:#8b5cf6;border-radius:2px;letter-spacing:.05em;vertical-align:middle}
.conn-status{font-size:.68rem;margin-bottom:.5rem}
.conn-status.connected{color:var(--accent4)}
.conn-status.disconnected{color:var(--text3)}
.conn-stock{font-family:var(--mono);font-size:.7rem;color:var(--gold);margin-bottom:.5rem}
.btn-conn{width:100%;padding:.4rem;font-family:var(--hud);font-size:.62rem;letter-spacing:.08em;
  border:1px solid;border-radius:4px;cursor:pointer;transition:all .12s}
.btn-conn.build{border-color:var(--accent4);background:rgba(16,185,129,.08);color:var(--accent4)}
.btn-conn.build:hover{background:rgba(16,185,129,.18)}
.btn-conn.built{border-color:var(--border);background:rgba(255,255,255,.02);color:var(--text3);cursor:default}
.btn-conn.ko{border-color:var(--border);background:rgba(255,255,255,.02);color:var(--text3);cursor:not-allowed;opacity:.4}

/* Notifs */
#notifs{position:fixed;top:60px;right:1rem;z-index:999;display:flex;flex-direction:column;gap:.4rem;pointer-events:none}
.notif{padding:.5rem .9rem;background:var(--card);border:1px solid var(--accent4);border-radius:4px;
  font-size:.75rem;color:var(--accent4);font-family:var(--mono);
  animation:sli .2s ease,fdo .3s ease 2.7s forwards}
.notif.err{border-color:var(--accent1);color:var(--accent1)}
.notif.warn{border-color:var(--gold);color:var(--gold)}
@keyframes sli{from{transform:translateX(16px);opacity:0}to{transform:translateX(0);opacity:1}}
@keyframes fdo{from{opacity:1}to{opacity:0}}

::-webkit-scrollbar{width:3px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}

/* ════════════════════════════════════════
   BANDEAU DIAGNOSTIC DE PRODUCTION
   — Apparaît juste sous la barre de ressources
   — Visible depuis la vue Galaxie ET la vue VM
   — Clic = navigation vers la section utile
════════════════════════════════════════ */
#prod-hint{
  /* v1.18 : bandeau d'alerte COMPACT À DROITE (au lieu de pleine largeur).
     Auto-ajusté à son contenu avec max 500px, marge de 8px du bord droit.
     Avant v1.18 : left:0 + right:0 → pleine largeur, écrasait visuellement
     le titre de la vue (ex: "Module d'Extraction"). Le porteur a demandé
     une version plus discrète qui ne mange pas l'espace central. */
  position:fixed;
  right:8px;     /* marge légère du bord droit */
  /* v2.9.11 : sur bureau, `top`/`right` sont surchargés en bas de fichier pour
     fixer le bandeau dans le coin haut-droit (bloc « COIN HAUT-DROIT »). Ces
     valeurs de base ne servent que de repli (mobile, ou avant surcharge). */
  top:96px;
  max-width:460px;
  width:auto;    /* auto-ajusté au contenu */
  display:none;
  align-items:center;gap:.45rem;
  padding:.3rem .6rem;
  font-size:.58rem;
  line-height:1.3;
  z-index:180;
  cursor:pointer;
  border-radius:6px;  /* coins arrondis car bandeau flottant maintenant */
  border:1px solid;   /* bordure complète, pas juste border-bottom */
  transition:background .15s;
}
/* v1.18 : le bandeau d'alerte est désormais FLOTTANT À DROITE (max 500px)
   au lieu de pleine largeur. Il ne pousse plus le contenu vers le bas,
   il flotte par-dessus le coin haut-droit. On retire donc le padding-top
   additionnel. La classe body.with-hint reste posée par notif-center
   pour d'éventuels styles conditionnels futurs, mais ne décale plus. */
body.with-hint .view{padding-top:var(--total-header-h, 105px) !important}
#prod-hint.visible{display:flex}
#prod-hint.warn{
  background:rgba(245,158,11,.12);
  border-color:rgba(245,158,11,.4);
  color:#fcd34d;
}
#prod-hint.warn:hover{background:rgba(245,158,11,.2)}
#prod-hint.err{
  background:rgba(233,69,96,.15);
  border-color:rgba(233,69,96,.4);
  color:#fca5a5;
}
#prod-hint.err:hover{background:rgba(233,69,96,.25)}
#prod-hint.info{
  background:rgba(14,165,233,.1);
  border-color:rgba(14,165,233,.35);
  color:var(--accent2);
}
#prod-hint.info:hover{background:rgba(14,165,233,.18)}
.ph-ico{font-size:1.05rem;flex-shrink:0}
.ph-msg{flex:1;line-height:1.3;min-width:0;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.ph-msg b{color:#fff}
.ph-arrow{font-family:var(--hud);font-size:.65rem;letter-spacing:.1em;flex-shrink:0;opacity:.7}
/* v0.0.x — LA CROIX « VU » du bandeau d'alerte. Masquée par défaut ;
   diagnostic.js pose la classe `ph-masquable` sur #prod-hint, ce qui la
   révèle. flex-shrink:0 pour qu'elle ne soit jamais rognée par .ph-msg. */
.ph-close{display:none;flex-shrink:0;background:none;border:none;color:inherit;
  cursor:pointer;font-size:.82rem;line-height:1;padding:2px 5px;margin:-2px -3px -2px 0;
  opacity:.55;border-radius:4px;transition:opacity .12s,background .12s}
.ph-close:hover{opacity:1;background:rgba(255,255,255,.14)}
#prod-hint.ph-masquable .ph-close{display:inline-flex;align-items:center}
/* v0.0.53 : le bouton d'ancrage de secours, posé DANS le message du bandeau
   (il voyage ainsi tel quel dans la pop-up « message en grand »).
   ⚠️ IL FAUT LEVER LA TRONCATURE, ET C'EST LE POINT QUI M'A EU. `.ph-msg` est
   coupé à DEUX lignes (`-webkit-line-clamp`) avec `overflow:hidden` : le
   bouton tombait sous la coupe et devenait INVISIBLE — mesuré au navigateur,
   pas en relisant le code. Raccourcir la phrase ne suffisait pas, le bandeau
   ne fait que ~240 px de large. La classe `.ph-secours` (posée par
   renderProdHint et effacée au message suivant, puisque `show()` réécrit
   className en entier) desserre la coupe pour ce seul cas. */
/* ⚠️ v0.0.53 : SIX LIGNES NE SUFFISAIENT PAS. Le message de sortie de trou
   de ver NOMME trois systèmes ; à ~240 px de large, la phrase et les trois
   noms mangent déjà cinq lignes et le bouton retombait sous la coupe —
   invisible, une deuxième fois. Mesuré au navigateur : le texte s'arrêtait
   sur « Delphinus Aestea, Delphinus… ». Ce bandeau-là est le seul du jeu à
   porter une ACTION : il a le droit de s'allonger. */
/* ⚠️ ET IL FAUT BATTRE LA RÈGLE DE BUREAU, qui vit 10 000 lignes plus bas
   (`body:not(.mobile-mode) #prod-hint .ph-msg { -webkit-line-clamp: 4 }`).
   Elle compte un ÉLÉMENT de plus (`body`) : à nombre égal de classes et
   d'identifiants, c'est elle qui gagne, où qu'on écrive la nôtre. Ma
   première version desserrait donc la coupe sans le moindre effet — le
   navigateur affichait toujours 4 lignes, mesuré. On reprend le même
   préfixe pour passer devant. */
#prod-hint.ph-secours .ph-msg,
body:not(.mobile-mode) #prod-hint.ph-secours .ph-msg{-webkit-line-clamp:14}
/* ⚠️ ET IL FAUT LE LAISSER REVENIR À LA LIGNE. En `nowrap` il dépassait la
   largeur du bandeau (~240 px) et `overflow:hidden` mangeait la fin du mot :
   « Ancrer au hasa ». Un bouton tronqué au milieu d'un mot ressemble à un
   bug d'affichage, pas à une action. */
.ph-btn-secours{
  display:block;width:100%;box-sizing:border-box;
  margin:.35rem 0 0;padding:.22rem .4rem;white-space:normal;text-align:center;
  font-family:var(--hud);font-size:.6rem;letter-spacing:.04em;cursor:pointer;
  color:#fff;background:rgba(245,158,11,.35);
  border:1px solid rgba(245,158,11,.75);border-radius:4px;
}
.ph-btn-secours:hover{background:rgba(245,158,11,.6)}

/* Petit badge rouge sur l'item de menu Connecteurs si action requise */
.sn-item .sn-badge{
  margin-left:auto;font-family:var(--mono);font-size:.6rem;
  background:var(--accent1);color:#fff;
  padding:.05rem .35rem;border-radius:8px;
  display:none;
}
.sn-item.has-badge .sn-badge{display:inline-block}
/* Interrupteur pastillesMenu — les pastilles du menu du vaisseau
   (features/vm/menu-pastilles.js), dans leur enveloppe `.sn-pms` : poussées à
   droite, ou juste après le badge natif quand l'item en montre un (deux
   marges automatiques se partageraient la place). Plancher de lecture :
   0,8 rem — le badge natif, lui, est resté à 0,6 rem. */
body.f-pastillesMenu .sn-item .sn-pms{margin-left:auto;display:inline-flex;align-items:center;gap:4px;flex:none}
body.f-pastillesMenu .sn-item.has-badge .sn-pms{margin-left:6px}
body.f-pastillesMenu .sn-pm{font-family:var(--mono);font-size:.8rem;line-height:1.25;border-radius:8px;padding:0 .4rem;white-space:nowrap}
body.f-pastillesMenu .sn-pm.est-nombre,body.f-pastillesMenu .sn-pm.est-rouge{background:var(--accent1);color:#fff}
body.f-pastillesMenu .sn-pm.est-alerte{background:var(--gold);color:#1a1204}
body.f-pastillesMenu .sn-pm.est-chantier{background:transparent;padding:0;font-size:.85rem}

/* ════════════════════════════════════════
   SÉLECTEUR DE VAISSEAUX MÈRES (multi-VM)
   — Apparaît dans la barre du haut
   — Une pastille par VM, l'actif est mis en avant
   — Bouton + pour créer un nouveau VM (gating à venir)
════════════════════════════════════════ */
/* ════════════════════════════════════════
   v0.20 : Panneau VERTICAL des Vaisseaux Mères (à droite)
   ────────────────────────────────────────
   Remplace l'ancien sélecteur horizontal qui était dans la topbar.
   Visible sur toutes les vues (Galaxie, Vaisseau Mère, future Recherche).
   Largeur fixe : --vm-panel-w. Sur mobile, transformé en drawer.
════════════════════════════════════════ */
:root {
  /* v2.23 : élargi (130 → 180 px) pour laisser respirer les noms de VM
     et la miniature de vaisseau qui remplace l'ancienne pastille ronde. */
  --vm-panel-w: 180px;  /* largeur du panneau latéral droit (desktop) */
}

#vm-panel-right {
  position: fixed;
  top: 88px;  /* sous la topbar + resbar */
  right: 0;
  bottom: 0;
  width: var(--vm-panel-w);
  background: rgba(5, 8, 16, 0.92);
  border-left: 1px solid var(--border2);
  z-index: 150;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  box-shadow: -3px 0 12px rgba(0, 0, 0, 0.4);
}

.vmp-header {
  font-family: var(--hud);
  font-size: 0.58rem;
  letter-spacing: 0.12em;
  color: var(--text3);
  padding: 0.5rem 0.6rem 0.4rem;
  border-bottom: 1px solid var(--border);
  text-align: center;
  flex-shrink: 0;
}

.vmp-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.5rem;
  flex: 1;
}

/* Pastille VM individuelle (verticale, ~110px de large) */
/* v2.23 : plus de cadre autour de la pastille. Le rectangle servait à
   marquer le VM ACTIF — cette information passe désormais sur la fusée
   elle-même : le vaisseau actif est à pleine intensité et rayonne, les
   autres sont estompés et se ravivent au survol. Chaque VM ayant sa
   propre couleur, l'écart d'intensité est ce qui distingue l'actif — pas
   la teinte. */
.vmp-pill {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 0.5rem 0.4rem 0.4rem;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--text2);
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.15s, background 0.15s;
  user-select: none;
  position: relative;
}
.vmp-pill:hover { opacity: 0.85; background: rgba(255, 255, 255, 0.03); }
.vmp-pill.active {
  opacity: 1;
  color: var(--vmc, var(--gold));
  /* halo diffus derrière la fusée, sans arête : ce n'est plus une boîte */
  background: radial-gradient(60% 55% at 50% 42%,
              rgba(var(--vmc-rgb, 245, 158, 11), 0.16), transparent 70%);
}

/* En-tête de la pastille : point coloré + nom */
/* v2.23 : la pastille d'un VM ne montre plus que la silhouette du
   vaisseau et son nom. Les règles de l'ancien en-tête, de l'état
   d'ancrage, de la portée (🔭) et de la jauge d'épuisement ont été
   retirées avec eux — ces infos vivent dans l'infobulle. */
/* VM actif : le nom s'allume avec la silhouette */
.vmp-pill.active .vmp-name { text-shadow: 0 0 8px rgba(var(--vmc-rgb, 245, 158, 11), 0.5); }

/* ══════════════════════════════════════════════════════════════════
   v0.0.146 — LA MINIATURE DE LA CITÉ-NEF
   ══════════════════════════════════════════════════════════════════
   La même silhouette que la Vue globale, teintée à la couleur du
   vaisseau. Le grand plan est en gris métal ; ici la teinte remplace
   les cinq nuances de tôle — à cent pixels de large, cinq gris ne se
   distinguent plus, et c'est la COULEUR qui dit de quel vaisseau il
   s'agit. On garde donc l'étagement par l'OPACITÉ : quille sombre,
   coque moyenne, ville claire. */
.vmp-nef {
  width: 100%;
  max-width: 148px;
  height: auto;
  flex-shrink: 0;
  overflow: visible;
}
.vmp-nef-quille {
  fill: rgba(var(--vmc-rgb, 245, 158, 11), 0.10);
  stroke: var(--vmc, var(--text3));
  stroke-width: 3;
  stroke-linejoin: round;
  opacity: 0.6;
}
.vmp-nef-coque {
  fill: rgba(var(--vmc-rgb, 245, 158, 11), 0.16);
  stroke: var(--vmc, var(--text3));
  stroke-width: 3.4;
  stroke-linejoin: round;
}
.vmp-nef-pont {
  fill: var(--vmc, var(--text3));
  opacity: 0.55;
}
/* Les étages de la ville : c'est la partie qui fait reconnaître le
   navire, donc la plus lisible des trois. Le liseré les sépare les uns
   des autres — sans lui, la ville devient un pâté. */
.vmp-nef-ville rect,
rect.vmp-nef-ville {
  fill: rgba(var(--vmc-rgb, 245, 158, 11), 0.30);
  stroke: var(--vmc, var(--text3));
  stroke-width: 2.6;
  stroke-linejoin: round;
}
/* VM actif : le navire s'allume, exactement comme la fusée le faisait */
.vmp-pill.active .vmp-nef { filter: drop-shadow(0 0 6px var(--vmc, transparent)); }
.vmp-pill.active .vmp-nef-coque { fill: rgba(var(--vmc-rgb, 245, 158, 11), 0.30); }
.vmp-pill.active .vmp-nef-ville rect { fill: rgba(var(--vmc-rgb, 245, 158, 11), 0.5); }
/* En transit : la coque se pointille, même convention que la fusée */
.vmp-pill.state-transit .vmp-nef-coque { stroke-dasharray: 12 8; }

.vmp-name {
  font-family: var(--hud);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* État (planète / transit / sans ancrage) en dessous du nom */

/* Mini-jauge d'épuisement de la planète (vert / orange / rouge selon stock) */

/* Bouton "+" pour créer un nouveau VM (tout en bas de la liste) */
.vmp-add {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.2rem;
  padding: 0.5rem 0.4rem;
  border: 1px dashed var(--border2);
  background: rgba(14, 165, 233, 0.04);
  color: var(--accent2);
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.12s;
  text-align: left;
}
.vmp-add:hover {
  background: rgba(14, 165, 233, 0.15);
  border-style: solid;
  border-color: var(--accent2);
}
.vmp-add-header {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  justify-content: center;
}
.vmp-add-icon {
  font-size: 1rem;
  line-height: 1;
  font-weight: 700;
}
.vmp-add-label {
  font-family: var(--hud);
  font-size: 0.55rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
/* v0.23 : affichage du coût + statut des prérequis sur le bouton */
.vmp-add-cost {
  font-family: var(--mono);
  font-size: 0.5rem;
  color: var(--text2);
  text-align: center;
  padding: 0.1rem 0;
}
.vmp-add-reqs {
  display: flex;
  gap: 0.25rem;
  justify-content: center;
  font-family: var(--mono);
  font-size: 0.5rem;
}
.vmp-add-reqs .req-ok {
  color: var(--accent4);
  opacity: 0.85;
}
.vmp-add-reqs .req-ko {
  color: var(--accent1);
  opacity: 0.85;
}

/* v0.22 : état "limite VMs atteinte" — affiché à la place de .vmp-add */
.vmp-limit {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.6rem 0.5rem;
  border: 1px solid var(--border2);
  background: rgba(255, 255, 255, 0.02);
  color: var(--text3);
  border-radius: 6px;
  opacity: 0.7;
}
.vmp-limit-icon { font-size: 1rem; opacity: 0.7; }
.vmp-limit-label {
  font-family: var(--hud);
  font-size: 0.52rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
}

/* Décalage du contenu principal pour éviter de passer sous le panneau */
.view { right: var(--vm-panel-w); }
#topbar, #resbar, #prod-hint { right: var(--vm-panel-w) !important; }

/* Bouton flottant qui ouvre le drawer sur mobile (caché par défaut sur desktop) */
#vmp-mobile-toggle {
  display: none;
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--accent2);
  color: #fff;
  border: none;
  font-size: 1.4rem;
  cursor: pointer;
  z-index: 160;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
}
#vmp-mobile-toggle:hover { background: var(--accent2); transform: scale(1.05); }

/* ── Mobile : transformer le panneau en drawer ───────────────────── */
@media (max-width: 700px) {
  /* Sur mobile, le contenu reprend toute la largeur (pas de décalage permanent) */
  .view { right: 0; }
  #topbar, #resbar, #prod-hint { right: 0 !important; }

  /* Le panneau devient un drawer caché par défaut (slide depuis la droite) */
  #vm-panel-right {
    width: 240px;
    transform: translateX(100%);
    transition: transform 0.3s ease-out;
  }
  #vm-panel-right.mobile-open {
    transform: translateX(0);
  }

  /* Le bouton flottant apparaît */
  #vmp-mobile-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
  }
}

/* Bouton ADMIN dans la barre du haut */
.adm-trigger{
  font-family:var(--hud);font-size:.62rem;letter-spacing:.1em;
  padding:.3rem .8rem;border:1px solid var(--gold);border-radius:3px;
  background:rgba(245,158,11,.08);color:var(--gold);
  cursor:pointer;transition:all .15s;
}
.adm-trigger:hover{background:rgba(245,158,11,.2)}

/* Pastille de vitesse — visible quand vitesse ≠ ×1 */
.speed-badge{
  font-family:var(--mono);font-size:.72rem;
  padding:.25rem .7rem;border-radius:3px;
  background:rgba(245,158,11,.15);border:1px solid var(--gold);color:var(--gold);
  display:none;
}
.speed-badge.visible{display:inline-block;animation:pulseSpeed 2s infinite}
@keyframes pulseSpeed{
  0%,100%{box-shadow:0 0 0 0 rgba(245,158,11,.4)}
  50%{box-shadow:0 0 0 4px rgba(245,158,11,0)}
}

/* Modale Progression */
.progression-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1200;
}
.progression-modal-box {
  background: var(--bg);
  border: 1px solid rgba(34, 211, 238, 0.4);
  border-radius: 10px;
  width: min(640px, 94vw);
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 40px rgba(0,0,0,0.6), 0 0 30px rgba(34,211,238,0.15);
  overflow: hidden;
}
.progression-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid rgba(255,255,255,0.07);
  background: rgba(34,211,238,0.05);
}
.progression-modal-title {
  font-family: var(--hud);
  font-size: 0.95rem;
  color: #67e8f9;
  letter-spacing: 0.08em;
}
.progression-modal-close {
  background: none;
  border: none;
  color: var(--text2);
  font-size: 1.1rem;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
}
.progression-modal-close:hover { color: var(--text); background: rgba(255,255,255,0.06); }
.progression-modal-body {
  padding: 16px 18px;
  overflow-y: auto;
  flex: 1;
}
.prog-section-title {
  font-family: var(--hud);
  font-size: 0.72rem;
  color: var(--text3);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  margin: 14px 0 8px;
  padding-bottom: 4px;
  border-bottom: 1px solid rgba(255,255,255,0.06);
}
.prog-section-title:first-child { margin-top: 0; }
.prog-buff-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  background: rgba(34,197,94,0.06);
  border: 1px solid rgba(34,197,94,0.2);
  border-radius: 6px;
  margin-bottom: 6px;
  font-size: 0.8rem;
}
.prog-buff-icon { font-size: 1.1rem; flex-shrink: 0; }
.prog-buff-info { flex: 1; }
.prog-buff-label { color: var(--text); font-size: 0.8rem; }
.prog-buff-timer { color: var(--text3); font-size: 0.7rem; font-family: var(--mono); margin-top: 1px; }
.prog-buff-timer.permanent { color: #34d399; }
.prog-link-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text2);
  font-size: 0.8rem;
  cursor: pointer;
  width: 100%;
  margin-bottom: 6px;
  transition: background 0.15s, border-color 0.15s;
}
.prog-link-btn:hover { background: rgba(34,211,238,0.08); border-color: rgba(34,211,238,0.3); color: #67e8f9; }
.prog-empty { color: var(--text3); font-size: 0.8rem; font-style: italic; padding: 4px 0; }

/* (v0.0.41 : tout l'habillage des RELIQUES — la pastille de la topbar et
   la modale du Cabinet — est retiré avec la mécanique.) */
/* (v0.0.39 : tout l'habillage de la modale HERITAGE — l'ossature de la
   v0.67 comme les ajouts de la refonte v2.66 : les trois voies, leurs
   barres de progression, les paliers, le résumé des axes et le dépliant
   des sources — est retiré avec la mécanique.) */

/* Voile d'arrière-plan derrière le panneau admin */
#admin-overlay{
  position:fixed;inset:0;background:rgba(0,0,0,.7);
  z-index:300;display:none;
}
#admin-overlay.visible{display:block}

/* La modale du panneau admin */
/* v1.23 — Panneau admin refondu : largeur 1100px + sidebar verticale gauche
   avec onglets thématiques. Les anciennes règles .adm-* sont conservées
   pour rétrocompatibilité (rien d'autre que le panneau admin ne les utilise,
   mais on évite tout risque de régression visuelle). */
#admin-modal{
  position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
  width:95%;max-width:1100px;max-height:88vh;
  background:var(--bg2);border:2px solid var(--gold);border-radius:8px;
  z-index:301;display:none;flex-direction:column;
  box-shadow:0 10px 40px rgba(0,0,0,.5);
}
#admin-modal.visible{display:flex}

.adm-hdr{
  display:flex;align-items:center;justify-content:space-between;
  padding:.9rem 1.2rem;border-bottom:1px solid var(--border2);
  background:rgba(245,158,11,.05);
  flex-shrink:0;
}
.adm-title{font-family:var(--hud);font-size:.95rem;color:var(--gold);letter-spacing:.1em}

/* v1.23 — Layout : sidebar verticale gauche + zone contenu droite */
.adm-body{
  display:flex;
  flex:1;
  min-height:0;            /* nécessaire pour que les enfants overflow:auto fonctionnent */
  overflow:hidden;
}
/* v1.23 — Zone de contenu droite : un seul panel visible à la fois */
.adm-panels{
  flex:1;
  padding:1.2rem 1.4rem;
  overflow-y:auto;
}
/* v0.0.38 (purge `consoleTestUnifiee`) : les panneaux ne s'excluent
   plus — la console tient sur une seule page. */
.adm-panel{display:block}
.adm-panel-title{
  font-family:var(--hud);font-size:.95rem;color:var(--gold);
  letter-spacing:.1em;margin-bottom:.4rem;
}
.adm-panel-subtitle{
  font-size:.78rem;color:var(--text2);margin-bottom:1.2rem;line-height:1.5;
}

.adm-sect{margin-bottom:1.4rem}
.adm-sect-title{
  font-family:var(--hud);font-size:.72rem;color:var(--accent2);
  letter-spacing:.15em;margin-bottom:.4rem;
  padding-bottom:.3rem;border-bottom:1px solid var(--border);
}
.adm-sect-desc{font-size:.75rem;color:var(--text2);margin-bottom:.8rem;line-height:1.5}

/* Boutons de presets vitesse */
.adm-speed-grid{
  display:grid;grid-template-columns:repeat(4,1fr);gap:.4rem;margin-bottom:.6rem;
}
.adm-speed-btn{
  padding:.55rem;font-family:var(--hud);font-size:.78rem;
  border:1px solid var(--border2);background:rgba(255,255,255,.03);
  color:var(--text2);border-radius:4px;cursor:pointer;transition:all .12s;
}
.adm-speed-btn:hover{background:rgba(14,165,233,.1);color:var(--accent2)}
.adm-speed-btn.active{
  background:rgba(245,158,11,.15);border-color:var(--gold);color:var(--gold);
}

/* Champ vitesse personnalisée */
.adm-custom{display:flex;align-items:center;gap:.5rem;font-size:.75rem;color:var(--text2)}
.adm-custom input{
  flex:1;padding:.45rem;font-family:var(--mono);font-size:.8rem;
  background:rgba(0,0,0,.3);border:1px solid var(--border2);color:var(--text);
  border-radius:3px;
}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.27 — LA CONSOLE DE TEST SUR UNE SEULE PAGE (flag `consoleTestUnifiee`)
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : « que tu mettes tous les boutons sur une page et que
   ce soit plus ergonomique ».

   CE QUI N'ALLAIT PAS. La console avait trois onglets verticaux — Vitesse,
   Ressources, VMs & Transit — pour ONZE outils au total. Régler une vitesse
   puis ajouter des ressources puis terminer une file, c'est-à-dire la
   séquence de test la plus banale qui soit, demandait deux allers-retours
   dans la barre de gauche. Et rien ne disait ce que les autres onglets
   contenaient : il fallait les ouvrir pour se souvenir.

   ⚠️ « TOUT SUR UNE PAGE » NE VEUT PAS DIRE « TOUT À LA SUITE ». Empilées
   verticalement, les onze sections font plus de deux mille pixels de haut :
   on aurait remplacé deux clics par un long défilement, ce qui n'est pas un
   progrès. Trois choses les font tenir :
     · DEUX COLONNES, qui coupent la hauteur de moitié. Le CSS `columns`
       plutôt qu'une grille : il RÉPARTIT tout seul selon la hauteur réelle
       de chaque section, alors qu'une grille imposerait de décider à la
       main ce qui va à gauche et ce qui va à droite — décision qui serait
       fausse au premier outil ajouté.
     · LES HUIT MULTIPLICATEURS SUR UNE LIGNE (au lieu de deux rangées de
       quatre) : quatre sections de vitesse y gagnent une rangée chacune, et
       la suite ×1 → ×1000 se lit d'un seul coup d'œil au lieu de se replier.
     · LES TITRES DE PANNEAU DISPARAISSENT. « ⚡ Vitesse du jeu » suivi de
       « VITESSE UNIVERS » disait deux fois la même chose : c'était utile
       pour annoncer un onglet, ça ne l'est plus quand tout est là.

   La modale s'élargit (1100 → 1400 px) pour que les deux colonnes respirent,
   et retombe sur UNE colonne en dessous de 1100 px de fenêtre — sinon les
   deux colonnes deviennent deux gouttières illisibles.

   v0.0.38 — PURGÉ : c'est désormais LA console, sans condition. La barre
   d'onglets a quitté le HTML avec ses règles CSS, et les panneaux ne
   s'excluent plus. */

/* ⚠️ Les COLONNES vivent sur `.adm-panels` et non sur chaque panneau —
   sinon chacun des trois se couperait en deux dans son coin et la page
   ferait six colonnes en escalier au lieu de deux qui courent d'un bout à
   l'autre. `break-inside: avoid` empêche qu'une section soit coupée en
   plein milieu par le saut de colonne. */
.adm-panels {
  columns: 2;
  column-gap: 1.6rem;
  padding: 1rem 1.2rem;
}
.adm-panel { break-inside: auto; }
.adm-sect {
  break-inside: avoid;
  margin-bottom: 1.1rem;
  padding-bottom: .2rem;
}
.adm-panel-title,
.adm-panel-subtitle { display: none; }

/* Les huit multiplicateurs sur UNE ligne. */
.adm-speed-grid {
  grid-template-columns: repeat(8, 1fr);
  gap: .3rem;
  margin-bottom: .45rem;
}
.adm-speed-btn { padding: .4rem .2rem; font-size: .7rem; }
.adm-sect-desc { font-size: .71rem; margin-bottom: .55rem; }
.adm-sect-title { margin-bottom: .3rem; }

/* La fenêtre s'élargit pour que deux colonnes tiennent sans se serrer. */
#admin-modal { max-width: 1400px; }

/* Sous 1100 px de fenêtre, deux colonnes deviennent deux gouttières : on
   revient à une seule, la page reste d'un seul tenant. */
@media (max-width: 1100px) {
  .adm-panels { columns: 1; }
  .adm-speed-grid { grid-template-columns: repeat(4, 1fr); }
}

/* Boutons d'actions admin (ressources, reset, etc.) */
.adm-actions{display:flex;flex-wrap:wrap;gap:.4rem}
/* Interrupteur testeurPericlitement : la liste des soleils prend la ligne
   entière, les trois boutons passent dessous — au téléphone, un nom de
   soleil coupé enverrait tuer le mauvais. */
.adm-peri-test-actions input[type=search],
.adm-peri-test-actions select{
  flex:1 1 100%;min-width:0;max-width:100%;
  background:#0a1120;color:var(--text,#eaf2ff);border:1px solid #23324f;border-radius:7px;
  padding:6px 8px;font-size:.86rem}
/* Le compte à rebours et le résultat : visibles même « sans explications ». */
.adm-peri-test-etat{margin-top:.5rem;font-size:.8rem;color:var(--gold,#fbbf24)}
.adm-peri-test-etat:empty{display:none}
/* v0.0.30 : une case à cocher alignée avec les boutons de la console. */
.adm-check{display:inline-flex;align-items:center;gap:.35rem;font-size:.72rem;
  color:var(--text2,#8da4c4);white-space:nowrap}
.adm-check input{margin:0}
.adm-btn{
  padding:.55rem .85rem;font-family:var(--hud);font-size:.7rem;letter-spacing:.08em;
  border:1px solid var(--accent2);background:rgba(14,165,233,.08);color:var(--accent2);
  border-radius:4px;cursor:pointer;transition:all .12s;
}
.adm-btn:hover{background:rgba(14,165,233,.18)}
.adm-btn.warn{border-color:var(--gold);background:rgba(245,158,11,.08);color:var(--gold)}
.adm-btn.warn:hover{background:rgba(245,158,11,.2)}
.adm-btn.danger{border-color:var(--accent1);background:rgba(233,69,96,.08);color:var(--accent1)}
.adm-btn.danger:hover{background:rgba(233,69,96,.2)}

/* Interrupteur testeurAjoutPersonnalise — le bloc « Personnalisé » de l'Ajout
   rapide : une case par ressource, le nom au-dessus de sa case. Trois par
   rangée en large, deux sous 700 px (à trois, une case de téléphone ne
   laisse plus la place de lire un milliard). */
.adm-perso{margin:.7rem 0 .8rem}
.adm-perso-titre{font-size:.8rem;color:var(--text2);margin-bottom:.45rem;line-height:1.45}
.adm-perso-champs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.45rem .7rem;margin-bottom:.55rem}
.adm-perso-champ{display:flex;flex-direction:column;gap:.2rem;min-width:0}
.adm-perso-nom{display:flex;align-items:center;gap:.3rem;font-size:.8rem;color:var(--text)}
.adm-perso-nom .res-mini{width:16px;height:16px}
.adm-perso-champ input{
  width:100%;box-sizing:border-box;padding:.4rem .5rem;font-family:var(--mono);font-size:.85rem;
  background:rgba(0,0,0,.3);border:1px solid var(--border2);color:var(--text);
  border-radius:3px;text-align:right;
}
.adm-perso-champ input:focus{outline:none;border-color:var(--accent2)}
.adm-perso-champ input.invalide{border-color:var(--accent1)}
.adm-perso-etat{font-size:.8rem;color:var(--accent2);margin-top:.45rem;line-height:1.45}
.adm-perso-etat:empty{display:none}
.adm-perso-etat.erreur{color:var(--accent1)}
/* Interrupteur testeurHeuresProduction — le compte rendu des heures de
   production : une ligne par vaisseau, sous les quatre boutons. */
.adm-heures-etat > div + div{margin-top:.15rem}
/* Le bloc neuf atteint le plancher de lecture (0,79 rem = 11 px) ; le reste du
   panneau est encore en dessous. Le padding vertical des boutons baisse
   d'autant : leur hauteur ne bouge pas. */
#adm-heures-production .adm-sect-title,
#adm-heures-production .adm-sect-desc,
#adm-heures-production .adm-btn{font-size:.79rem}
#adm-heures-production .adm-btn{padding-top:.48rem;padding-bottom:.48rem}
/* Interrupteur testeurHeuresExpedition — même gabarit que le bloc du dessus ;
   le compte rendu reprend `.adm-heures-etat`. La ligne des repères (« un
   Récolteur rapporte … à l'heure ») se détache un peu du reste. */
#adm-heures-expedition .adm-sect-title,
#adm-heures-expedition .adm-sect-desc,
#adm-heures-expedition .adm-btn{font-size:.79rem}
#adm-heures-expedition .adm-btn{padding-top:.48rem;padding-bottom:.48rem}
#adm-heures-expedition .adm-heures-reperes{color:var(--text);margin-bottom:.25rem}
#adm-heures-expedition .adm-heures-nonverse{opacity:.8}
#adm-heures-expedition .adm-insecable{white-space:nowrap}
/* Interrupteur testeurSansExplications — Loryan, capture barrée à l'appui :
   « je veux supprimer les textes ici ». Les paragraphes d'explication de
   chaque bloc du panneau partent ; panel.js pose la classe. */
#admin-modal.adm-sans-explications .adm-sect-desc{display:none}
@media (max-width:700px){
  .adm-perso-champs{grid-template-columns:repeat(2,minmax(0,1fr))}
}
/* 16 px dans la case : en dessous, Safari sur iPhone zoome toute la page au
   premier appui dans le champ. */
body.mobile-mode .adm-perso-champ input{font-size:16px;min-height:40px}

/* v0.0.38 (purge `consoleTestUnifiee`) : la barre d'onglets a disparu du
   HTML — sa version horizontale pour petit écran n'a plus rien à coiffer.
   Le passage en colonne du corps reste : c'est lui qui empile la console. */
@media (max-width:800px){
  .adm-body{flex-direction:column}
}

/* ════════════════════════════════════════
   Modale "Liste des joueurs" (admin)
══════════════════════════════════════════ */
#players-modal{
  position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
  width:92%;max-width:900px;max-height:85vh;
  background:var(--bg2);border:2px solid var(--accent2);border-radius:8px;
  z-index:302;display:none;flex-direction:column;
  box-shadow:0 10px 40px rgba(0,0,0,.6);
  /* v1.25 : overflow:hidden essentiel pour que les enfants flex puissent
     contraindre leur taille. Sans ça, le contenu déborde et casse le scroll. */
  overflow:hidden;
}
#players-modal.visible{display:flex}

/* v1.25 fix critique : .players-box était dans le HTML depuis longtemps mais
   N'AVAIT AUCUNE règle CSS. Résultat : le flex-direction:column de
   #players-modal s'appliquait sur cet unique enfant wrapper, et ses propres
   enfants (header / help / list) débordaient sans scroll possible.
   Fix : .players-box prend la structure flex-column elle-même + flex:1 pour
   occuper toute la hauteur dispo de la modale. */
.players-box{
  display:flex;
  flex-direction:column;
  flex:1;
  min-height:0;        /* permet à #players-list (flex:1) de scroller */
  overflow:hidden;
}

.players-header{
  display:flex;align-items:center;justify-content:space-between;
  padding:.9rem 1.2rem;border-bottom:1px solid var(--border2);
  background:rgba(14,165,233,.06);
  flex-shrink:0;        /* v1.25 : header garde sa hauteur, ne se compresse pas */
}
.players-title{font-family:var(--hud);font-size:.95rem;color:var(--accent2);letter-spacing:.1em}
.players-close{
  background:none;border:1px solid var(--border2);color:var(--text2);
  padding:.25rem .6rem;cursor:pointer;border-radius:4px;font-size:.9rem;
}
.players-close:hover{background:var(--bg3);color:var(--text)}

.players-help{
  padding:.6rem 1.2rem;font-size:.7rem;color:var(--text2);line-height:1.5;
  border-bottom:1px solid var(--border);
  background:rgba(0,0,0,.15);
  flex-shrink:0;        /* v1.25 : help garde sa hauteur aussi */
}
.players-help em{color:var(--text3);font-style:italic}

#players-list{
  overflow-y:auto;flex:1;padding:.6rem;
  min-height:0;         /* v1.25 : indispensable au scroll dans flex */
}

.player-row{
  display:flex;align-items:center;gap:1rem;
  padding:.8rem 1rem;margin-bottom:.4rem;
  background:var(--bg3);border:1px solid var(--border);border-radius:6px;
  transition:background .12s;
}
.player-row:hover{background:rgba(14,165,233,.04)}

.player-info{flex:1;min-width:0}
.player-name{
  font-family:var(--hud);font-size:.85rem;color:var(--text);
  margin-bottom:.3rem;display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;
}
.player-meta{font-size:.7rem;color:var(--text3);margin-bottom:.3rem}
.player-stats{
  display:flex;gap:.8rem;flex-wrap:wrap;
  font-size:.72rem;color:var(--text2);
}
.player-stats span{white-space:nowrap}

.player-badge{
  display:inline-block;padding:.1rem .4rem;
  font-family:var(--hud);font-size:.55rem;letter-spacing:.08em;
  border-radius:3px;text-transform:uppercase;
}
.player-badge.admin{background:rgba(245,158,11,.15);color:var(--gold);border:1px solid var(--gold)}
.player-badge.super{background:rgba(233,69,96,.15);color:var(--accent1);border:1px solid var(--accent1)}
.player-badge.self{background:rgba(14,165,233,.15);color:var(--accent2);border:1px solid var(--accent2)}

/* v1.25 : boutons d'actions désormais en LIGNE HORIZONTALE (avant : colonne).
   Petit carré compact côte à côte pour gagner en densité verticale.
   La ligne joueur est plus compacte → 5-6 joueurs visibles d'un coup au lieu de 2-3. */
.player-actions{
  display:flex;
  flex-direction:row;     /* v1.25 : horizontal */
  gap:.3rem;
  flex-shrink:0;
  align-items:center;
}
.player-action{
  /* v1.25 : compact côte à côte. Padding réduit, taille fixée pour l'alignement. */
  padding:.4rem .55rem;
  font-family:var(--hud);font-size:.6rem;letter-spacing:.05em;
  border:1px solid;border-radius:4px;cursor:pointer;transition:all .12s;
  white-space:nowrap;
  min-width:auto;
}
.player-action.reset{
  border-color:var(--accent2);background:rgba(14,165,233,.08);color:var(--accent2);
}
.player-action.reset:hover{background:rgba(14,165,233,.2)}
.player-action.delete{
  border-color:var(--accent1);background:rgba(233,69,96,.08);color:var(--accent1);
}
.player-action.delete:hover{background:rgba(233,69,96,.2)}

/* Bouton "→ Voie Lactée" — débloque un joueur coincé dans une galaxie verrouillée */
.player-action.move-mw{
  border-color:#8b6dff;background:rgba(139,109,255,.08);color:#b9a6ff;
}
.player-action.move-mw:hover{background:rgba(139,109,255,.22)}

@media (max-width:700px){
  /* Sous 700px : la ligne joueur passe en colonne (info en haut, boutons dessous
     mais TOUJOURS horizontaux entre eux). */
  .player-row{flex-direction:column;align-items:stretch}
  .player-actions{
    flex-direction:row;
    justify-content:flex-start;
    flex-wrap:wrap;
    margin-top:.4rem;
  }
  .player-action{flex:0 0 auto}
}

/* Bouton "Bannir" (super-admin) — rouge plus aggressif que "Supprimer" */
.player-action.ban{
  border-color:#7c1f3b;background:rgba(124,31,59,.18);color:#ff4d6d;
  font-weight:bold;
}
.player-action.ban:hover{background:rgba(124,31,59,.35)}

/* v0.18 — Bouton "Reset mdp" (super-admin) */
.player-action.reset-pwd{
  border-color:var(--gold);background:rgba(245,158,11,.1);color:var(--gold);
}
.player-action.reset-pwd:hover{background:rgba(245,158,11,.22)}

/* La modale "bans" réutilise les mêmes styles que la modale "players" */
#bans-modal{
  position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
  width:92%;max-width:600px;max-height:80vh;
  background:var(--bg2);border:2px solid #7c1f3b;border-radius:8px;
  z-index:303;display:none;flex-direction:column;
  box-shadow:0 10px 40px rgba(0,0,0,.6);
  overflow:hidden;     /* v1.25 : même fix scroll que players-modal */
}
#bans-modal.visible{display:flex}
#bans-list{overflow-y:auto;flex:1;padding:.6rem;min-height:0}

/* ════════════════════════════════════════════════════════
   AUTH MODAL (v0.16) — Modale de login / register / set-password
   ════════════════════════════════════════════════════════
   Affichée plein écran au démarrage du jeu tant que le joueur n'est
   pas authentifié. Bloque toute interaction avec le reste du jeu.
   Pilotée par AuthUI dans app.js. */
#auth-modal{
  position:fixed;inset:0;
  background:radial-gradient(ellipse at center, rgba(14,165,233,.06) 0%, var(--bg) 70%);
  z-index:1000;display:none;align-items:center;justify-content:center;
  padding:1rem;
}
#auth-modal.visible{display:flex}

.auth-box{
  width:100%;max-width:440px;
  background:var(--bg2);border:2px solid var(--accent2);border-radius:10px;
  box-shadow:0 10px 60px rgba(14,165,233,.15), 0 4px 30px rgba(0,0,0,.7);
  overflow:hidden;
}

.auth-header{
  text-align:center;padding:1.4rem 1rem 1rem;
  background:linear-gradient(180deg, rgba(14,165,233,.08), transparent);
  border-bottom:1px solid var(--border);
}
.auth-brand{
  font-family:var(--hud);font-size:2rem;font-weight:900;letter-spacing:.2em;
  background:linear-gradient(90deg,var(--accent1),var(--accent2));
  -webkit-background-clip:text;-webkit-text-fill-color:transparent;
}
/* v2.15 : logo vertical mixte (symbole + lettrage encadré) sur la connexion */
.auth-brand-img{height:150px;width:auto;max-width:100%;display:block;margin:0 auto}
.auth-brand-sub{
  font-family:var(--hud);font-size:.6rem;color:var(--text3);
  letter-spacing:.4em;margin-top:-2px;
}

.auth-body{padding:1.5rem 1.6rem 1.4rem}

.auth-title{
  font-family:var(--hud);font-size:1.1rem;color:var(--accent2);
  letter-spacing:.1em;margin-bottom:.4rem;text-align:center;
}
.auth-subtitle{
  font-size:.78rem;color:var(--text2);line-height:1.5;
  margin-bottom:1.2rem;text-align:center;
}
.auth-subtitle strong{color:var(--text);font-weight:600}

.auth-row{margin-bottom:.85rem;display:flex;flex-direction:column;gap:.3rem}
.auth-row label{
  font-family:var(--hud);font-size:.6rem;letter-spacing:.12em;
  color:var(--text2);text-transform:uppercase;
}
.auth-row input{
  padding:.6rem .8rem;font-family:var(--body);font-size:.9rem;
  background:rgba(0,0,0,.35);border:1px solid var(--border2);
  color:var(--text);border-radius:4px;
  transition:border-color .15s;
}
.auth-row input:focus{
  outline:none;border-color:var(--accent2);
  background:rgba(14,165,233,.05);
}

.auth-error{
  min-height:1.2rem;
  font-size:.78rem;color:var(--accent1);margin:.3rem 0 .6rem;text-align:center;
}

.auth-submit{
  width:100%;padding:.75rem;font-family:var(--hud);font-size:.78rem;
  letter-spacing:.15em;
  border:1px solid var(--accent2);background:rgba(14,165,233,.12);
  color:var(--accent2);border-radius:5px;cursor:pointer;
  transition:all .15s;
  margin-top:.3rem;
}
.auth-submit:hover:not(:disabled){background:rgba(14,165,233,.25)}
.auth-submit:disabled{opacity:.5;cursor:wait}

.auth-switch{
  margin-top:1.1rem;text-align:center;font-size:.8rem;color:var(--text2);
  padding-top:1rem;border-top:1px solid var(--border);
}
.auth-switch a{
  color:var(--accent2);text-decoration:none;font-weight:600;cursor:pointer;
}
.auth-switch a:hover{text-decoration:underline}

/* Mobile : petite taille de police pour que tout rentre sur l'écran */
@media (max-width:480px){
  .auth-box{max-width:100%}
  .auth-body{padding:1.2rem 1rem}
  .auth-header{padding:1rem .8rem .8rem}
  .auth-brand{font-size:1.6rem}
}

/* ════════════════════════════════════════════════════════════════════
   v0.21 — VUE RECHERCHE (Module Technologie)
   ────────────────────────────────────────────────────────────────────
   Structure :
     #view-research
       .res-header      → titre + info contextuelle (réductions)
       .res-queue-box   → file de recherche en cours (max 5)
       .res-categories  → 6 sections, une par catégorie
         .res-section
           .res-section-hdr
           .res-section-grid
   (v0.0.146 : le contenu est rendu en table des éléments — voir le bloc
   « VUE RECHERCHE : LA TABLE DES ÉLÉMENTS » ; `.res-section*` sert encore
   à la vue Commandement.)
   ════════════════════════════════════════════════════════════════════ */

#view-research {
  overflow-y: auto;
  padding-left: 1rem;
  padding-right: 1rem;
}

/* ─── En-tête de la vue ──────────────────────────────────────────── */
.res-header {
  background: rgba(14, 165, 233, 0.06);
  border: 1px solid var(--accent2);
  border-radius: 6px;
  padding: 0.9rem 1.1rem;
  margin-bottom: 0.8rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.res-header-title {
  font-family: var(--hud);
  font-size: 0.9rem;
  letter-spacing: 0.12em;
  color: var(--accent2);
}
.res-header-info {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.7rem;
}
.res-info-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.15rem 0;
}
.res-info-label {
  color: var(--text2);
  font-family: var(--mono);
}
.res-info-value {
  font-family: var(--mono);
  font-weight: 600;
  color: var(--text);
}
.res-info-total {
  margin-top: 0.3rem;
  padding-top: 0.4rem;
  border-top: 1px solid rgba(14, 165, 233, 0.25);
}
.res-info-total .res-info-value {
  color: var(--gold);
  font-size: 0.78rem;
}

/* ─── File de recherche ──────────────────────────────────────────── */
.res-queue-box {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.7rem 0.9rem;
  margin-bottom: 1.2rem;
}
.res-queue-title {
  font-family: var(--hud);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  color: var(--text3);
  margin-bottom: 0.5rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.res-queue-title span {
  font-family: var(--mono);
  font-size: 0.65rem;
}
.res-queue-empty {
  font-size: 0.7rem;
  color: var(--text3);
  font-style: italic;
  text-align: center;
  padding: 0.4rem 0;
}
.res-queue-item {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  gap: 0.3rem 0.6rem;
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.4rem;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.02);
  border-left: 2px solid var(--text3);
}
.res-queue-item.active {
  border-left-color: var(--accent2);
  background: rgba(14, 165, 233, 0.08);
}
.res-queue-item.pending {
  opacity: 0.65;
}
.res-queue-item-info {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  grid-column: 1;
  flex-wrap: wrap;
}
.res-queue-badge {
  font-family: var(--mono);
  font-size: 0.6rem;
  padding: 0.15rem 0.35rem;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 2px;
  color: var(--text2);
}
.res-queue-item.active .res-queue-badge {
  background: var(--accent2);
  color: #fff;
}
.res-queue-name {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--text);
}
.res-queue-time {
  font-family: var(--mono);
  font-size: 0.62rem;
  color: var(--text3);
}
/* v0.0.49 : le Vaisseau Mère qui a lancé la recherche — c'est lui qui la
   paie au démarrage de l'item, donc lui qu'il faut ravitailler. Discret :
   c'est un repère, pas le sujet de la ligne. */
.res-queue-vm {
  font-family: var(--mono);
  font-size: 0.62rem;
  color: var(--text3);
  opacity: .85;
  margin-left: 6px;
  white-space: nowrap;
}
.res-queue-bar {
  grid-column: 1 / span 2;
  height: 3px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 2px;
  overflow: hidden;
}
.res-queue-bar-fill {
  height: 100%;
  background: var(--accent2);
  transition: width 0.4s ease-out;
}
.res-queue-cancel {
  grid-column: 2;
  grid-row: 1;
  width: 22px;
  height: 22px;
  border: 1px solid var(--accent1);
  border-radius: 50%;
  background: rgba(233, 69, 96, 0.1);
  color: var(--accent1);
  cursor: pointer;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8rem;
  line-height: 1;
}
.res-queue-cancel:hover {
  background: rgba(233, 69, 96, 0.3);
}

/* ─── Sections par catégorie ─────────────────────────────────────── */
.res-categories {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding-bottom: 2rem;
}
.res-loading {
  text-align: center;
  padding: 2rem;
  color: var(--text3);
  font-family: var(--hud);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
}
.res-section {
  background: rgba(255, 255, 255, 0.015);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.9rem;
}
.res-section-hdr {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: 0.7rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
}
.res-section-icon {
  font-size: 1.5rem;
  flex-shrink: 0;
}
.res-section-titles {
  flex: 1;
}
.res-section-title {
  font-family: var(--hud);
  font-size: 0.85rem;
  letter-spacing: 0.1em;
  color: var(--text);
}
.res-section-desc {
  font-size: 0.65rem;
  color: var(--text3);
  margin-top: 0.15rem;
}
.res-section-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 0.7rem;
}

/* ─── Habillage des recherches ───────────────────────────────────────
   Badges d'effet et bouton d'action, posés par la fiche de la table des
   éléments (tech.js) — c'est aujourd'hui le seul rendu des recherches.
   Le badge de NIVEAU ne se trouve pas ici : la fiche l'écrit avec sa
   propre classe (`.res-fiche-niv`, plus bas). */
/* v0.56 : badge "✓ ACTIF" — indique que la techno a un effet réel en jeu */
.tech-actif-badge {
  font-size: 0.58rem;
  font-weight: 700;
  color: #22c55e;          /* vert clair lisible sur fond sombre */
  background: rgba(34, 197, 94, 0.12);
  border: 1px solid rgba(34, 197, 94, 0.35);
  border-radius: 3px;
  padding: 0.05rem 0.3rem;
  margin-left: auto;       /* pousse le badge tout à droite dans le header flex */
  letter-spacing: 0.05em;
  white-space: nowrap;
}
/* v0.56 : badge "À VENIR" — indique que l'effet sera implémenté plus tard */
.tech-avenir-badge {
  font-size: 0.58rem;
  font-weight: 600;
  color: var(--text3);
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: 3px;
  padding: 0.05rem 0.3rem;
  margin-left: auto;
  letter-spacing: 0.05em;
  white-space: nowrap;
  opacity: 0.6;
}
.tech-btn {
  font-family: var(--hud);
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  padding: 0.45rem 0.7rem;
  border-radius: 3px;
  border: 1px solid var(--accent2);
  background: rgba(14, 165, 233, 0.1);
  color: var(--accent2);
  cursor: pointer;
  transition: all 0.12s;
}
.tech-btn.ready:hover {
  background: rgba(14, 165, 233, 0.25);
}
.tech-btn.locked, .tech-btn.nores {
  border-color: var(--border2);
  background: rgba(255, 255, 255, 0.02);
  color: var(--text3);
  cursor: not-allowed;
}
.tech-btn.nores {
  border-color: var(--accent1);
  color: var(--accent1);
  background: rgba(233, 69, 96, 0.05);
}

/* ════════════════════════════════════════════════════════════════ */
/* v0.25 — Périclitement : compteurs de durée de vie des systèmes    */
/* ════════════════════════════════════════════════════════════════ */
/* Niveaux d'urgence (alignés sur §21.4.3 du GDD) :                  */
/*   - safe : système avec > 7 jours restants — vert sémantique      */
/*   - warn : entre 1 et 7 jours — orange sémantique                  */
/*   - urgent : entre 1 heure et 1 jour — rouge clignotant            */
/*   - critical : moins d'1h — rouge profond, clignotement rapide     */
/*   - dead : système mort, en période morte avant régénération       */
/* La couleur n'est jamais seule (icône emoji + texte explicite       */
/* fournissent les canaux redondants pour l'accessibilité)            */

.peri {
  display: inline-block;
  font-weight: 500;
  margin-left: 4px;
}
.peri.peri-safe {
  color: #22c55e; /* vert sémantique GDD §21.4.2 */
}
.peri.peri-warn {
  color: #ea580c; /* orange sémantique */
}
.peri.peri-urgent {
  color: #dc2626; /* rouge sémantique */
  animation: peri-blink 1.2s ease-in-out infinite;
}
.peri.peri-critical {
  color: #fff;
  background: #7f1d1d; /* rouge profond GDD §21.4.4 (H-1) */
  padding: 2px 6px;
  border-radius: 4px;
  animation: peri-blink 0.8s ease-in-out infinite;
}
.peri.peri-dead {
  color: #94a3b8; /* gris pour les morts (neutre) */
  font-style: italic;
}

@keyframes peri-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/* Mode mouvement réduit (§21.8.7) : on garde le rouge mais sans clignotement */
@media (prefers-reduced-motion: reduce) {
  .peri.peri-urgent,
  .peri.peri-critical {
    animation: none;
  }
}

/* ════════════════════════════════════════════════════════════════════
   v0.27 — Centre de notifications (bouton cloche + panneau)
   ════════════════════════════════════════════════════════════════════
   Cf. features/notifications/center.js pour la logique.
   - Bouton 🔔 dans la topbar avec badge rouge pour les non-lus
   - Panneau flottant qui s'ouvre au clic, fermeture par clic extérieur
   - Filtres par catégorie (TOUTES / PÉRICLITEMENT / VMs / SYSTÈMES) */

/* ─── Bouton cloche dans la topbar ─── */
.topbar-icon-btn {
  position: relative;
  background: transparent;
  border: 1px solid #1f2937;
  color: var(--text);
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 1rem;
  height: 28px;
  margin: 0 4px;
  transition: background 0.15s, border-color 0.15s;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.topbar-icon-btn:hover {
  background: #131930;
  border-color: #334155;
}
.notif-bell-icon {
  font-size: 0.95rem;
  line-height: 1;
}
.notif-bell-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  background: #dc2626;
  color: #fff;
  font-size: 0.72rem;
  font-weight: bold;
  font-family: var(--mono);
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #0a0e1c;
  line-height: 1;
  box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.4);
}
/* Le badge partage sa classe entre la cloche de notifications et le bouton
   Tutoriel ; les deux le posent avec l'attribut `hidden` à 0. Sans cette
   règle, `.notif-bell-badge { display: inline-flex }` a la MÊME spécificité
   que la règle native `[hidden] { display: none }` — et comme les styles
   d'auteur passent après la feuille de l'agent utilisateur, la nôtre
   gagnait : le badge restait visible (avec son dernier texte affiché,
   parfois périmé) même une fois masqué. */
.notif-bell-badge[hidden] {
  display: none;
}

/* ─── Panneau du Centre de notifications ─── */
.notif-panel {
  position: fixed;
  top: 52px;
  right: calc(var(--vm-panel-w, 130px) + 8px);
  width: 360px;
  max-height: calc(100vh - 70px);
  background: #0e1424;
  border: 1px solid #1f2937;
  border-radius: 8px;
  z-index: 500;
  display: flex;
  flex-direction: column;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
  font-family: inherit;
}
.notif-panel[hidden] { display: none; }

.notif-panel-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid #1f2937;
  flex-shrink: 0;
}
.notif-panel-title {
  flex: 1;
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--text);
}
.notif-panel-action {
  background: transparent;
  border: 1px solid #334155;
  color: var(--text2);
  font-size: 0.7rem;
  padding: 3px 8px;
  border-radius: 4px;
  cursor: pointer;
  font-family: inherit;
}
.notif-panel-action:hover {
  background: #131930;
  border-color: #475569;
  color: var(--text);
}
.notif-panel-close {
  background: transparent;
  border: none;
  color: var(--text2);
  font-size: 1.2rem;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}
.notif-panel-close:hover { color: var(--text); }

/* ─── Filtres ─── */
.notif-panel-filters {
  display: flex;
  gap: 4px;
  padding: 8px 12px;
  border-bottom: 1px solid #1f2937;
  flex-wrap: wrap;
  flex-shrink: 0;
}
.notif-filter {
  background: transparent;
  border: 1px solid #1f2937;
  color: var(--text2);
  font-size: 0.7rem;
  padding: 3px 8px;
  border-radius: 12px;
  cursor: pointer;
  font-family: inherit;
  transition: all 0.15s;
}
.notif-filter:hover {
  border-color: #475569;
  color: var(--text);
}
.notif-filter.active {
  background: #0ea5e9;
  border-color: #0ea5e9;
  color: #0a0e1c;
  font-weight: 600;
}

/* ─── Liste ─── */
.notif-panel-list {
  flex: 1;
  overflow-y: auto;
  padding: 4px 0;
  min-height: 100px;
}
.notif-empty,
.notif-loading,
.notif-error {
  padding: 24px 16px;
  text-align: center;
  color: var(--text3);
  font-size: 0.8rem;
}
.notif-error { color: #f87171; }

.notif-item {
  display: flex;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid #131930;
  cursor: pointer;
  transition: background 0.1s;
  position: relative;
}
.notif-item:hover { background: #131930; }
.notif-item.unread { background: rgba(14, 165, 233, 0.04); }
.notif-item.unread:hover { background: rgba(14, 165, 233, 0.08); }
.notif-item:last-child { border-bottom: none; }

/* v1.36 — Bouton poubelle / restaurer sur chaque notification.
   Discret par défaut, plus visible au survol de la notif. */
.notif-item-trash {
  align-self: center;
  flex-shrink: 0;
  background: transparent;
  border: none;
  color: var(--text3, #8b94b8);
  font-size: 0.95rem;
  line-height: 1;
  padding: 4px 6px;
  border-radius: 4px;
  cursor: pointer;
  opacity: 0.35;
  transition: opacity 0.12s, background 0.12s, color 0.12s;
}
.notif-item:hover .notif-item-trash { opacity: 0.7; }
.notif-item-trash:hover {
  opacity: 1;
  background: rgba(233, 69, 96, 0.18);
  color: #ff6b81;
}

/* v1.36 — Bandeau d'action en tête de la corbeille. */
.notif-trash-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  font-size: 0.8rem;
  color: var(--text2, #aab2d5);
  background: rgba(255, 255, 255, 0.02);
  border-bottom: 1px solid #131930;
}
.notif-trash-empty-btn {
  background: rgba(233, 69, 96, 0.15);
  border: 1px solid rgba(233, 69, 96, 0.4);
  color: #ff8095;
  padding: 5px 10px;
  border-radius: 4px;
  font-size: 0.75rem;
  cursor: pointer;
  transition: background 0.12s;
}
.notif-trash-empty-btn:hover { background: rgba(233, 69, 96, 0.28); }

/* ── v0.122 (GDD §21.4) — Variantes sémantiques des notifs ─────────
   Classes appliquées dynamiquement par center.js selon le type d'event.
   Les notifs critiques ont une bordure gauche colorée + fond léger.
   L'animation Périclitement J-1 (1.2s) est appliquée via la classe
   utilitaire .is-periclitement-j1 (déclarée dans le bloc :root v0.122).
   Principe GDD §21.4.1 : la couleur ne porte JAMAIS seule le sens —
   ces variantes ajoutent une bordure colorée + un fond ≠ la couleur
   seule, et chaque event a déjà icône + titre + sous-titre explicites. */

.notif-item.notif-semantic-error {
  border-left: 4px solid var(--color-error-fg);
  background: var(--color-error-bg);
}
.notif-item.notif-semantic-error:hover {
  background: rgba(220, 38, 38, 0.20);
}
.notif-item.notif-semantic-error-h1 {
  /* Périclitement H-1 : rouge profond permanent (escalade GDD §21.4.4) */
  border-left: 4px solid var(--periclitement-h1);
  background: rgba(127, 29, 29, 0.20);
}
.notif-item.notif-semantic-error-h1:hover {
  background: rgba(127, 29, 29, 0.30);
}
.notif-item.notif-semantic-warning {
  /* Périclitement J-7 / J-14 : orange statique (escalade GDD §21.4.4) */
  border-left: 4px solid var(--color-warning-fg);
  background: var(--color-warning-bg);
}
.notif-item.notif-semantic-warning:hover {
  background: rgba(234, 88, 12, 0.20);
}
.notif-item.notif-semantic-success {
  border-left: 4px solid var(--color-success-fg);
  background: var(--color-success-bg);
}
.notif-item.notif-semantic-success:hover {
  background: rgba(34, 197, 94, 0.20);
}
.notif-item.notif-semantic-info {
  border-left: 4px solid var(--color-info-fg);
  background: var(--color-info-bg);
}
.notif-item.notif-semantic-info:hover {
  background: rgba(37, 99, 235, 0.20);
}

.notif-item-icon {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
}
.notif-item-body {
  flex: 1;
  min-width: 0; /* permet le text-overflow ellipsis */
}
.notif-item-title {
  font-size: 0.8rem;
  color: var(--text);
  font-weight: 500;
  line-height: 1.3;
  margin-bottom: 2px;
}
.notif-item-subtitle {
  font-size: 0.7rem;
  color: var(--text2);
  line-height: 1.3;
}
.notif-item-subtitle:empty { display: none; }
.notif-item-time {
  font-size: 0.65rem;
  color: var(--text3);
  font-family: var(--mono);
  margin-top: 4px;
}
.notif-item-dot {
  position: absolute;
  top: 14px;
  right: 12px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #0ea5e9;
}

/* ─── Mobile : panneau pleine largeur ─── */
@media (max-width: 700px) {
  .notif-panel {
    right: 8px;
    left: 8px;
    width: auto;
  }
}

/* (purge cette semaine : la section v0.28 « Rôles principaux » — badge
   topbar + modale de sélection de rôle, ~160 lignes — est retirée. Elle
   documentait elle-même sa dépendance à `features/role/ui.js`, un fichier
   qui n'existe plus : les rôles sont supprimés depuis la v2.32, et aucun
   JS ne créait plus le moindre `.role-badge` ni `.role-modal` nulle part
   dans le repo.) */

/* ════════════════════════════════════════════════════════════════════
   v0.33 — Affichage portée de visibilité par VM (panneau VM)
   ════════════════════════════════════════════════════════════════════ */

/* ════════════════════════════════════════════════════════════════════
   v0.34 — Mini-map de la galaxie
   v1.01 — Déplacée en bas-DROITE (avant : bas-gauche).
            Légende sortie du cadre : flottante à GAUCHE de la mini-map
            (au lieu de DESSOUS), masquée par défaut, affichée au survol
            (PC) ou au clic (mobile via classe .is-legend-open).
   ════════════════════════════════════════════════════════════════════ */
#minimap-container {
  position: absolute;
  bottom: 16px;
  right: 16px;            /* v1.01 : était left:16px, maintenant à droite */
  background: rgba(14, 20, 36, 0.92);
  border: 1px solid #1f2937;
  border-radius: 8px;
  padding: 8px;
  z-index: 50;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  user-select: none;
}
#minimap-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 6px;
  font-size: 0.65rem;
  font-family: var(--mono);
  letter-spacing: 0.5px;
}
.mm-title {
  color: var(--text);
  font-weight: 700;
}
.mm-stats {
  color: var(--text2);
}
#minimap-canvas {
  display: block;
  border-radius: 4px;
  background: #050810;
}

/* ───────────────────────────────────────────────────────────────────
   v1.01 — Légende EXTERNE : sortie du cadre de la mini-map
   ───────────────────────────────────────────────────────────────────
   Avant v1.01 : la légende était à l'intérieur du conteneur, sous le
   canvas, ce qui "cannibalisait" l'espace de la mini-map (la légende
   prenait autant de hauteur que les vignettes).
   Désormais : la légende est en position absolute, à GAUCHE de la
   mini-map (puisque la mini-map est en bas-droite, il y a la place).
   Cachée par défaut. Affichée :
     - PC : au survol du #minimap-container (:hover sur le parent)
     - Mobile : quand le conteneur a la classe .is-legend-open
       (toggled par le JS au tap).
   ─────────────────────────────────────────────────────────────────── */
#minimap-legend {
  /* Position : à gauche de la mini-map, alignée en bas */
  position: absolute;
  bottom: 0;
  right: 100%;                /* colle à gauche du conteneur parent */
  margin-right: 8px;
  /* Look : même style sombre que le conteneur */
  background: rgba(14, 20, 36, 0.95);
  border: 1px solid #1f2937;
  border-radius: 8px;
  padding: 8px 10px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  /* Contenu : pile verticale (plus lisible qu'horizontal) */
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.6rem;
  font-family: var(--mono);
  color: var(--text2);
  white-space: nowrap;
  /* Visibilité : masquée par défaut, transition douce */
  opacity: 0;
  pointer-events: none;
  transform: translateX(6px);
  transition: opacity .15s ease-out, transform .15s ease-out;
  z-index: 51;                /* au-dessus du canvas, sous la sidebar */
}

/* Affichage de la légende :
   - PC : au survol du conteneur parent
   - Mobile : quand la classe .is-legend-open est posée par le JS */
#minimap-container:hover #minimap-legend,
#minimap-container.is-legend-open #minimap-legend,
#minimap-container:focus-within #minimap-legend {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(0);
}

.mm-leg {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.mm-leg-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
}
.mm-sun-jaune { background: #fde047; box-shadow: 0 0 3px #fde047; }
.mm-sun-rouge { background: #ef4444; box-shadow: 0 0 3px #ef4444; }
.mm-sun-bleu  { background: #60a5fa; box-shadow: 0 0 3px #60a5fa; }
.mm-leg-vm {
  color: #fde047;
  font-size: 0.7rem;
  line-height: 1;
}
.mm-leg-memo {
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.75rem;
  line-height: 1;
}

/* Mobile : réduire la mini-map.
   La légende externe est conservée (et accessible au tap) — au lieu
   d'être display:none comme avant v1.01. C'était trop frustrant
   pour les nouveaux joueurs de ne pas avoir la légende sur mobile. */
@media (max-width: 700px) {
  #minimap-container {
    bottom: 8px;
    right: 8px;             /* v1.01 : était left:8px */
  }
  #minimap-canvas {
    width: 200px;
    height: 200px;
  }
  /* Légende : on garde visible (display flex) mais cachée par défaut
     via opacity. Le tap (gestion JS) ajoute .is-legend-open. */
}

/* ════════════════════════════════════════════════════════════════════
   v0.37 — Panneau Flotte (vaisseaux constructibles, début Phase 2)
   ════════════════════════════════════════════════════════════════════ */
.fleet-section {
  margin-top: 20px;
  padding: 14px;
  background: rgba(31, 41, 55, 0.3);
  border: 1px solid #1f2937;
  border-radius: 8px;
}
.fleet-title {
  font-size: 1rem;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 4px;
}
.fleet-sub {
  font-size: 0.75rem;
  color: var(--text2);
  margin-bottom: 12px;
}
.fleet-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 10px;
  margin-bottom: 12px;
}
.ship-card {
  padding: 10px;
  background: rgba(14, 20, 36, 0.6);
  border: 1px solid #1f2937;
  border-radius: 6px;
}
.ship-card.locked {
  opacity: 0.55;
}
.ship-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
}
.ship-ico {
  font-size: 1.2rem;
}
.ship-name {
  font-weight: 600;
  color: var(--text);
  flex: 1;
}
.ship-owned {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--accent, #0ea5e9);
  font-weight: 700;
}
.ship-desc {
  font-size: 0.7rem;
  color: var(--text2);
  margin-bottom: 8px;
  min-height: 28px;
}
.ship-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  font-size: 0.7rem;
  color: var(--text2);
  margin-bottom: 10px;
  font-family: var(--mono);
}
.ship-queue-tag {
  color: #f59e0b;
}
.ship-build-btn {
  width: 100%;
  padding: 8px;
  background: var(--accent, #0ea5e9);
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 0.8rem;
  cursor: pointer;
  font-weight: 600;
}
.ship-build-btn:hover:not(:disabled) {
  filter: brightness(1.15);
}
.ship-build-btn:disabled {
  background: #374151;
  color: var(--text2);
  cursor: not-allowed;
}
/* v0.60 : bouton "Envoyer en expédition" — style violet pour différencier
   de "Produire 1" (qui est en accent1). Apparaît juste sous le bouton de
   production sur la fiche d'un Pionnier. */
.ship-exped-btn {
  margin-top: 4px;
  width: 100%;
  padding: 6px;
  background: linear-gradient(180deg, #7c3aed, #6d28d9);
  border: 1px solid #8b5cf6;
  color: white;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  transition: filter 0.15s;
}
.ship-exped-btn:hover:not(:disabled) { filter: brightness(1.2); }
.ship-exped-btn:disabled {
  background: #374151;
  border-color: #4b5563;
  color: var(--text3);
  cursor: not-allowed;
}
/* v0.60 : panneau "Expéditions en cours" — encart en haut de la vue Flotte */
.expeditions-panel {
  margin: 8px 0 12px 0;
  padding: 10px 12px;
  background: rgba(124, 58, 237, 0.08);
  border: 1px solid rgba(139, 92, 246, 0.35);
  border-radius: 6px;
}
.expeditions-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.expeditions-title {
  font-weight: 700;
  font-size: 0.85rem;
  color: #c4b5fd;     /* violet clair, lisible sur fond sombre */
  letter-spacing: 0.03em;
}
.expeditions-count {
  font-size: 0.7rem;
  color: var(--text2);
  font-family: var(--mono);
}
.expeditions-list { display: flex; flex-direction: column; gap: 6px; }
.expedition-item {
  background: rgba(14, 20, 36, 0.4);
  border-radius: 4px;
  padding: 6px 8px;
}
.expedition-row {
  display: flex;
  justify-content: space-between;
  font-size: 0.75rem;
  margin-bottom: 4px;
}
.expedition-ship { color: var(--text); }
.expedition-remaining {
  color: #c4b5fd;
  font-family: var(--mono);
}
.expedition-bar {
  height: 4px;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 2px;
  overflow: hidden;
}
.expedition-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, #7c3aed, #a78bfa);
  transition: width 0.3s ease;
}
/* v0.61 : ligne du système cible sous le nom du vaisseau dans le panneau */
.expedition-target {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.7rem;
  color: var(--text2);
  margin-bottom: 4px;
}
.expedition-target-sun {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
/* v0.61 : modale de sélection du système cible (full screen overlay) */
.expedition-modal-overlay {
  position: fixed;
  top: 0; left: 0;
  width: 100vw; height: 100vh;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}
.expedition-modal-box {
  background: var(--bg);
  border: 1px solid rgba(139, 92, 246, 0.5);
  border-radius: 8px;
  max-width: 600px;
  width: 90%;
  max-height: 80vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5), 0 0 30px rgba(139, 92, 246, 0.2);
}
.expedition-modal-box .modal-header {
  padding: 12px 16px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}
.expedition-modal-box .modal-title {
  font-size: 1rem;
  font-weight: 700;
  color: #c4b5fd;
}
.expedition-modal-box .modal-subtitle {
  font-size: 0.75rem;
  color: var(--text2);
  margin-top: 4px;
}
.expedition-modal-box .modal-close {
  background: transparent;
  border: none;
  color: var(--text2);
  font-size: 1.5rem;
  cursor: pointer;
  padding: 0 8px;
  line-height: 1;
}
.expedition-modal-box .modal-close:hover { color: var(--text); }
.expedition-modal-box .modal-body {
  padding: 12px 16px;
  overflow-y: auto;
}
.expedition-modal-hint {
  font-size: 0.78rem;
  color: var(--text2);
  margin-bottom: 10px;
  padding: 8px 10px;
  background: rgba(124, 58, 237, 0.08);
  border-left: 3px solid #7c3aed;
  border-radius: 3px;
}
.expedition-target-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 50vh;
  overflow-y: auto;
}
.expedition-target-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: rgba(14, 20, 36, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  cursor: pointer;
  color: var(--text);
  text-align: left;
  font-size: 0.85rem;
  transition: background 0.15s, border-color 0.15s;
}
.expedition-target-item:hover {
  background: rgba(124, 58, 237, 0.15);
  border-color: rgba(139, 92, 246, 0.5);
}
.expedition-target-name { flex: 1; }
.expedition-target-id {
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--text3);
}
.fleet-queue {
  font-size: 0.8rem;
  color: var(--text2);
  font-family: var(--mono);
  padding: 8px 10px;
  background: rgba(14, 20, 36, 0.4);
  border-radius: 4px;
}
.fleet-queue-head {
  color: var(--text);
  margin-bottom: 4px;
}
.fleet-queue-progress {
  height: 4px;
  background: #1f2937;
  border-radius: 2px;
  overflow: hidden;
  margin: 4px 0 6px 0;
}
.fleet-queue-fill {
  height: 100%;
  background: var(--accent, #0ea5e9);
  transition: width 0.3s linear;
}
.fleet-queue-rest {
  font-size: 0.7rem;
  color: var(--text2);
}

/* ════════════════════════════════════════════════════════════════════
   v0.38 — Groupement par catégorie dans le panneau Flotte
   ════════════════════════════════════════════════════════════════════ */
.ship-cat-block {
  margin-bottom: 16px;
}
.ship-cat-title {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text);
  padding: 6px 0;
  border-bottom: 1px solid #1f2937;
  margin-bottom: 8px;
}
.ship-cat-count {
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--text2);
  font-family: var(--mono);
}
.ship-cat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 10px;
}

/* Le grid global devient juste un container vertical de blocs */
.fleet-grid {
  display: block;
}

/* v0.42 — bandeau de capacité au-dessus des grilles Flotte / Défenses */
.fleet-cap {
  font-size: 0.79rem;
  font-family: var(--mono);
  padding: 6px 10px;
  margin-bottom: 10px;
  background: rgba(14, 20, 36, 0.4);
  border-radius: 4px;
  border-left: 2px solid currentColor;
}

/* v0.0.162 — le plafond NOMINAL, barré à côté du plafond réel du Hangar.
   ⚠️ CETTE CLASSE N'EST ÉCRITE QUE PAR LA BRANCHE NEUVE de `renderFleet`
   (features/vm/fleet.js) : tant que l'écran n'affiche pas le plafond du
   moteur, aucune balise ne la porte et la feuille de style ne change rien. */
.fleet-cap-nominal {
  opacity: 0.55;
  margin-left: 4px;
  text-decoration-thickness: 1px;
}

/* v2.0.7 — état vide d'une grille Flotte / Défenses. Le catalogue militaire
   et celui des défenses ont été vidés en attendant de nouvelles unités :
   sans ce message, l'onglet afficherait une zone blanche qui passe pour un
   bug. La grille étant en `grid`, on prend toute la largeur. */
.fleet-empty {
  grid-column: 1 / -1;
  padding: 28px 16px;
  text-align: center;
  font-size: 0.85rem;
  color: var(--text2);
  background: rgba(14, 20, 36, 0.35);
  border: 1px dashed rgba(120, 140, 180, 0.28);
  border-radius: 6px;
  line-height: 1.7;
}
.fleet-empty-sub {
  font-size: 0.75rem;
  opacity: 0.65;
}

/* ════════════════════════════════════════════════════════════════════
   v0.43 — Combat (bandeaux attaques)
   ════════════════════════════════════════════════════════════════════ */
.combat-alert-band {
  background: linear-gradient(90deg, #7f1d1d 0%, #991b1b 100%);
  color: #fee2e2;
  padding: 10px 14px;
  font-size: 0.9rem;
  border-left: 4px solid #ef4444;
  display: flex;
  gap: 10px;
  align-items: center;
  position: sticky;
  top: 0;
  z-index: 100;
  animation: combatPulse 1.6s infinite ease-in-out;
}
.combat-alert-band strong { color: #fff; }
.combat-alert-ico { font-size: 1.2rem; }
@keyframes combatPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.6); }
  50%      { box-shadow: 0 0 12px 2px rgba(239, 68, 68, 0.4); }
}

.attack-outgoing-row {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 0.8rem;
  padding: 4px 0;
  border-bottom: 1px dotted #1f2937;
}
.attack-outgoing-row:last-child { border-bottom: none; }
.attack-outgoing-compo { color: var(--text); }
.attack-outgoing-time { color: var(--text2); }

.admin-test-attack-btn {
  margin-top: 8px;
  padding: 6px 10px;
  background: #7f1d1d;
  color: #fee2e2;
  border: 1px solid #ef4444;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.75rem;
}
.admin-test-attack-btn:hover { filter: brightness(1.15); }

/* ════════════════════════════════════════════════════════════════════
   v0.44 — Modale de rapport de combat
   ════════════════════════════════════════════════════════════════════ */
.combat-report-modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
  padding: 20px;
}
.combat-report-content {
  background: #0e1424;
  border: 1px solid #1f2937;
  border-radius: 8px;
  max-width: 800px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  padding: 20px;
  color: var(--text);
}
.combat-report-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #1f2937;
  padding-bottom: 10px;
  margin-bottom: 14px;
}
.combat-report-header h3 { margin: 0; font-size: 1rem; }
.combat-report-close {
  background: transparent;
  border: none;
  color: var(--text2);
  font-size: 1.4rem;
  cursor: pointer;
}
.combat-report-close:hover { color: var(--text); }
.combat-report-winner {
  text-align: center;
  font-size: 1.4rem;
  font-weight: 700;
  margin-bottom: 18px;
  padding: 10px;
  border: 1px solid currentColor;
  border-radius: 6px;
}
.combat-report-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 14px;
}
@media (max-width: 600px) {
  .combat-report-grid { grid-template-columns: 1fr; }
}
.combat-report-camp {
  background: rgba(31, 41, 55, 0.4);
  border: 1px solid #1f2937;
  border-radius: 6px;
  padding: 12px;
}
.combat-report-camp-title {
  font-weight: 700;
  font-size: 0.95rem;
  margin-bottom: 8px;
  text-align: center;
  border-bottom: 1px solid #374151;
  padding-bottom: 6px;
}
.combat-report-section { margin-bottom: 10px; }
.combat-report-section-title {
  font-size: 0.7rem;
  color: var(--text2);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 4px;
}
.combat-report-compo {
  font-size: 0.8rem;
  line-height: 1.5;
  font-family: var(--mono);
}
.combat-report-loss {
  text-align: center;
  font-weight: 600;
  padding: 6px;
  background: rgba(239, 68, 68, 0.15);
  color: #fecaca;
  border-radius: 4px;
  margin-top: 8px;
}
.combat-report-summary {
  text-align: center;
  font-size: 0.75rem;
  color: var(--text2);
  font-family: var(--mono);
  padding: 8px;
  background: rgba(14, 20, 36, 0.6);
  border-radius: 4px;
}
.notif-item-clickable { cursor: pointer; }
.notif-item-clickable:hover { background: rgba(31, 41, 55, 0.4); }

/* ════════════════════════════════════════════════════════════════════
   v0.45 — Panneau "Joueurs présents" + modale d'attaque
   ════════════════════════════════════════════════════════════════════ */
/* v0.45 — Panneau "Joueurs présents" + modale d'attaque
   v1.03 — Repositionnement : était en bas-droite (collé à la mini-map),
   ce qui le rendait illisible. Désormais en HAUT-DROITE, juste à GAUCHE
   du panneau Vaisseaux Mères, sans superposition.
   ════════════════════════════════════════════════════════════════════ */
.known-vms-panel {
  position: absolute;
  top: 100px;                                       /* sous topbar + resbar */
  right: calc(var(--vm-panel-w, 130px) + 16px);     /* à gauche du panneau VMs */
  width: 320px;
  background: rgba(14, 20, 36, 0.92);
  border: 1px solid #1f2937;
  border-radius: 8px;
  padding: 14px;
  z-index: 50;
  max-height: 60vh;
  overflow-y: auto;
  font-size: 0.85rem;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* v1.03 : sur mobile, le panneau VMs est en drawer (caché par défaut),
   donc le panneau Joueurs présents peut occuper directement le coin
   haut-droite sans décalage. */
@media (max-width: 700px) {
  .known-vms-panel {
    right: 8px;
    width: calc(100vw - 76px);  /* respecte la sidebar 60px + marges */
    max-width: 380px;
    top: 110px;
  }
}
.known-vms-title {
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--text);
  margin-bottom: 4px;
}
.known-vms-sub {
  font-size: 0.75rem;
  color: var(--text2);
  margin-bottom: 10px;
}
.known-vms-system {
  margin-bottom: 8px;
  padding-bottom: 8px;
  border-bottom: 1px dotted #1f2937;
}
.known-vms-system:last-child { border-bottom: none; }
.known-vms-system-name {
  font-size: 0.7rem;
  color: var(--text2);
  margin-bottom: 4px;
  font-style: italic;
}
.known-vms-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 0;
}
.known-vms-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  font-size: 0.7rem;
  line-height: 1.3;
}
.known-vms-owner { font-weight: 700; color: var(--text); }
.known-vms-vmname { color: var(--text2); }
.known-vms-planet {
  color: var(--text3);
  font-family: var(--mono);
  font-size: 0.65rem;
}
.known-vms-attack-btn {
  background: #7f1d1d;
  color: #fee2e2;
  border: 1px solid #ef4444;
  padding: 4px 8px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.7rem;
  font-weight: 600;
}
.known-vms-attack-btn:hover { filter: brightness(1.15); }

/* Modale d'attaque (réutilise .combat-report-modal pour le fond) */
.attack-target-info {
  background: rgba(31, 41, 55, 0.4);
  padding: 10px;
  border-radius: 4px;
  margin-bottom: 14px;
  text-align: center;
  font-size: 0.85rem;
}
.attack-section-title {
  font-size: 0.75rem;
  text-transform: uppercase;
  color: var(--text2);
  letter-spacing: 0.05em;
  margin-bottom: 8px;
}
.attack-compo-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 16px;
}
.attack-compo-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  background: rgba(14, 20, 36, 0.6);
  border-radius: 4px;
}
.attack-compo-name { flex: 1; font-size: 0.85rem; }
.attack-compo-stock {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--text2);
}
.attack-compo-input {
  width: 70px;
  padding: 4px 6px;
  background: rgba(0,0,0,0.3);
  border: 1px solid #374151;
  border-radius: 4px;
  color: var(--text);
  font-family: var(--mono);
  text-align: right;
}
.attack-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.attack-cancel-btn, .attack-confirm-btn {
  padding: 8px 14px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 600;
  border: 1px solid #374151;
}
.attack-cancel-btn {
  background: #1f2937;
  color: var(--text);
}
.attack-confirm-btn {
  background: #7f1d1d;
  color: #fee2e2;
  border-color: #ef4444;
}
.attack-confirm-btn:hover { filter: brightness(1.15); }

/* v0.46 — distinction visuelle des flottes aller / retour */
.attack-outgoing-row.return {
  background: rgba(16, 185, 129, 0.08);
  border-left: 2px solid #10b981;
  padding-left: 6px;
}
.attack-outgoing-label {
  font-size: 0.7rem;
  color: var(--text2);
  font-family: var(--mono);
  margin-right: 4px;
}

/* v0.47 — section "Déroulement par round" dans la modale rapport */
.combat-report-rounds {
  background: rgba(31, 41, 55, 0.3);
  border: 1px solid #1f2937;
  border-radius: 6px;
  padding: 10px 12px;
  margin: 10px 0 14px 0;
}
.combat-report-round-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  font-family: var(--mono);
  font-size: 0.8rem;
  border-bottom: 1px dotted #1f2937;
}
.combat-report-round-row:last-child { border-bottom: none; }
.rr-label {
  font-weight: 700;
  color: #f59e0b;
  min-width: 28px;
}
.rr-camp {
  flex: 1;
  text-align: center;
}
.rr-sep {
  color: var(--text3);
  font-size: 0.7rem;
}
.rr-delta {
  color: #ef4444;
  margin-left: 2px;
}

/* v0.49 — Section "Butin pillé" dans la modale rapport */
.combat-report-loot {
  background: rgba(245, 158, 11, 0.1);
  border: 1px solid #f59e0b;
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 12px;
}
.combat-report-loot .combat-report-section-title {
  color: #f59e0b;
  margin-bottom: 8px;
}
.loot-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
}
.loot-item {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: #fcd34d;
}
.loot-item strong { color: #fff; }

/* Bandeau butin dans la ligne de retour */
.attack-outgoing-loot {
  font-family: var(--mono);
  font-size: 0.7rem;
  color: #fcd34d;
  background: rgba(245, 158, 11, 0.15);
  padding: 2px 6px;
  border-radius: 3px;
  margin: 0 4px;
}

/* v0.50 — Panneau "Débris sur cette planète" dans le Module Militaire */
.debris-panel {
  background: rgba(139, 92, 246, 0.08);
  border: 1px solid #8b5cf6;
  border-radius: 6px;
  padding: 12px 14px;
  margin-top: 12px;
}
.debris-title {
  font-weight: 700;
  color: #a78bfa;
  margin-bottom: 8px;
  font-size: 0.9rem;
}
.debris-content {
  display: flex;
  gap: 16px;
  margin-bottom: 10px;
}
.debris-res {
  font-family: var(--mono);
  font-size: 0.95rem;
  color: #fcd34d;
}
.debris-action {
  text-align: center;
}
.debris-collect-btn {
  background: #6d28d9;
  color: #ede9fe;
  border: 1px solid #8b5cf6;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
}
.debris-collect-btn:hover { filter: brightness(1.15); }
.debris-note {
  font-size: 0.75rem;
  color: var(--text2);
  font-style: italic;
}

/* Section "Débris générés" dans la modale rapport */
.combat-report-debris {
  background: rgba(139, 92, 246, 0.08);
  border: 1px solid #8b5cf6;
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 12px;
}
.combat-report-debris .combat-report-section-title {
  color: #a78bfa;
  margin-bottom: 8px;
}
.debris-note-small {
  font-size: 0.7rem;
  color: var(--text3);
  font-style: italic;
  margin-top: 6px;
}

/* v0.51 — Barre de coque du VM dans la barre d'ancrage */
.hull-bar {
  display: inline-block;
  width: 80px;
  height: 8px;
  background: rgba(31, 41, 55, 0.6);
  border: 1px solid #1f2937;
  border-radius: 4px;
  overflow: hidden;
  margin-left: 4px;
  vertical-align: middle;
}
.hull-bar-fill {
  display: block;
  height: 100%;
  background: #10b981;
  transition: width 0.4s ease, background 0.4s ease;
}

/* v0.51 — Section "Coque du VM" dans la modale rapport */
.combat-report-vmhull {
  background: rgba(239, 68, 68, 0.1);
  border: 1px solid #ef4444;
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 12px;
}
.combat-report-vmhull .combat-report-section-title {
  color: #ef4444;
  margin-bottom: 8px;
}
.vm-hull-stat {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 0.85rem;
  margin-bottom: 4px;
}
.vm-hull-damage {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: #fca5a5;
}
.vm-hull-critical {
  margin-top: 8px;
  padding: 6px 10px;
  background: rgba(239, 68, 68, 0.25);
  border-radius: 4px;
  font-weight: 700;
  color: #fff;
  text-align: center;
}

/* v0.52 — Badges PvP dans le panneau known-vms */
.known-vms-shield, .known-vms-cd, .known-vms-range {
  font-size: 0.75rem;
  margin-left: 4px;
  cursor: help;
}
.known-vms-shield { color: #3b82f6; }
.known-vms-cd     { color: #f59e0b; }
.known-vms-range  { color: #ef4444; }

.known-vms-power {
  font-family: var(--mono);
  font-size: 0.65rem;
  color: var(--text2);
  margin-left: 6px;
  background: rgba(59, 130, 246, 0.15);
  padding: 1px 4px;
  border-radius: 3px;
}

.known-vms-attack-btn:disabled {
  background: #374151;
  color: var(--text3);
  border-color: #4b5563;
  cursor: not-allowed;
  opacity: 0.5;
}
.known-vms-attack-btn:disabled:hover {
  filter: none;
}

/* v0.53 — Bouton spy + modale d'espionnage */
.known-vms-actions {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.known-vms-spy-btn {
  background: #1e3a8a;
  color: #dbeafe;
  border: 1px solid #3b82f6;
  padding: 3px 8px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.7rem;
  font-weight: 600;
}
.known-vms-spy-btn:hover { filter: brightness(1.15); }

.spy-explainer {
  font-size: 0.75rem;
  color: var(--text2);
  background: rgba(31, 41, 55, 0.4);
  padding: 8px 12px;
  border-radius: 4px;
  margin-bottom: 12px;
  line-height: 1.4;
}
.spy-probability {
  text-align: center;
  font-family: var(--mono);
  font-size: 0.85rem;
  padding: 6px;
  margin-bottom: 12px;
  border: 1px dashed #374151;
  border-radius: 4px;
}
.spy-detected-banner {
  background: rgba(239, 68, 68, 0.15);
  border: 1px solid #ef4444;
  color: #fca5a5;
  padding: 12px;
  border-radius: 4px;
  text-align: center;
  font-weight: 600;
  margin-bottom: 12px;
}
.spy-success-banner {
  background: rgba(16, 185, 129, 0.15);
  border: 1px solid #10b981;
  color: #6ee7b7;
  padding: 12px;
  border-radius: 4px;
  text-align: center;
  font-weight: 600;
  margin-bottom: 12px;
}
.spy-section {
  background: rgba(31, 41, 55, 0.3);
  border: 1px solid #1f2937;
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 10px;
}
.spy-section-title {
  font-size: 0.75rem;
  text-transform: uppercase;
  color: var(--text2);
  letter-spacing: 0.05em;
  margin-bottom: 6px;
}
.spy-line {
  font-family: var(--mono);
  font-size: 0.8rem;
  padding: 2px 0;
}

/* v0.54 — Bandeau de bonus Pirate sur le panneau Joueurs présents */
.pirate-bonus-banner {
  background: linear-gradient(135deg, rgba(251, 191, 36, 0.15) 0%, rgba(239, 68, 68, 0.15) 100%);
  border: 1px solid #fbbf24;
  color: #fde68a;
  padding: 8px 12px;
  border-radius: 4px;
  margin-bottom: 10px;
  font-size: 0.75rem;
  line-height: 1.4;
}
.pirate-bonus-banner strong { color: #fcd34d; }


/* ════════════════════════════════════════════════════════════════════
   v0.75 — BASCULE DESKTOP / MOBILE
   ════════════════════════════════════════════════════════════════════
   Bouton dans la topbar (en haut à gauche, après le logo) qui cycle
   entre 3 modes :
     - Auto    : détection automatique de la largeur d'écran
     - Desktop : force l'affichage PC
     - Mobile  : force l'affichage mobile (UI tactile, polices grossies)

   Le mode mobile est activé en posant la classe .mobile-mode sur <body>.
   Toutes les règles ci-dessous sont préfixées par body.mobile-mode pour
   ne s'appliquer QUE dans ce mode, sans toucher au CSS desktop existant.

   Voir features/mobile/mobile-mode.js pour la logique JS associée.
   ════════════════════════════════════════════════════════════════════ */

/* ─── Bouton de bascule dans la topbar ──────────────────────────────── */
.display-mode-btn {
  /* Visible partout (desktop ET mobile) — c'est le point d'entrée du switch */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  font-size: 1rem;
  line-height: 1;
  margin: 0 0.4rem 0 0.2rem;
  padding: 0;
  border: 1px solid var(--border2);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.1s;
  flex-shrink: 0;   /* ne se compresse pas si la topbar est serrée */
  position: relative;
  /* Important : le bouton doit RESTER cliquable même sur les écrans
     très étroits, donc on lui donne une taille minimum tactile (>=32px). */
}

/* CAS CRITIQUE : quand la modale d'auth est ouverte (écran de login), la
   topbar entière est cachée par-dessous (la modale a z-index 1000 et la
   topbar est à z-index 200). Or on veut absolument que le bouton de mode
   d'affichage reste cliquable AVANT la connexion (un utilisateur arrive
   sur le site depuis son téléphone et doit pouvoir basculer en mode mobile
   immédiatement). La solution : quand <body> contient une auth-modal en
   classe "visible", on booste le z-index de la topbar pour qu'elle passe
   AU-DESSUS de la modale.
   Le selecteur :has() est supporté par tous les navigateurs modernes
   (Chrome 105+, Safari 15.4+, Firefox 121+) — largement assez pour DSH. */
body:has(#auth-modal.visible) #topbar {
  z-index: 1100 !important;
  /* La topbar passe au-dessus, mais on rend transparent le reste de la
     topbar (sauf le bouton de mode) pour que la modale reste lisible :
     on cache les autres éléments visuellement. */
  background: transparent !important;
  border-bottom: none !important;
  pointer-events: none;   /* on désactive les clics partout dans la topbar... */
}
/* ...sauf sur le bouton de mode, qu'on réactive explicitement. */
body:has(#auth-modal.visible) #display-mode-btn {
  pointer-events: auto;
  background: rgba(10, 15, 30, 0.85);   /* opacité pour rester lisible sur le fond bleu de la modale */
  backdrop-filter: blur(4px);
}
/* On masque aussi les autres éléments de la topbar pendant le login pour
   ne pas créer de confusion visuelle (le joueur ne doit voir que le logo
   et le bouton de mode). */
body:has(#auth-modal.visible) #topbar > *:not(#display-mode-btn):not(:first-child) {
  visibility: hidden;
}
.display-mode-btn:hover {
  background: rgba(14, 165, 233, 0.12);
  border-color: var(--accent2);
}
.display-mode-btn:active { transform: scale(0.93); }

/* Pastille discrète pour signaler le mode auto. Affichée en bas-droite
   du bouton, c'est un petit point cyan qui dit "le mode est dynamique". */
.display-mode-btn.is-auto::after {
  content: '';
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent2);
  /* Le bouton lui-même n'est pas position:relative par défaut, on le pose
     ici pour que ::after soit ancré dessus. */
}
.display-mode-btn { position: relative; }
.display-mode-btn.is-auto::after {
  bottom: 3px;
  right: 3px;
  box-shadow: 0 0 4px rgba(14, 165, 233, 0.7);
}


/* ════════════════════════════════════════════════════════════════════
   MODE MOBILE — déclenché par body.mobile-mode
   ════════════════════════════════════════════════════════════════════
   Toutes les règles ci-dessous transforment l'UI desktop en UI mobile.
   Elles s'ajoutent (et parfois remplacent) les media queries pré-existantes
   (max-width: 700px). Le résultat est que body.mobile-mode produit le même
   rendu que si l'écran faisait <= 700 px, mais quelle que soit la largeur
   réelle de la fenêtre — c'est ce qui permet de FORCER le mode mobile.
   ════════════════════════════════════════════════════════════════════ */


/* ─── Topbar : compactée, scroll horizontal si elle déborde ─────────── */
/* La topbar contient logo + bouton mode + 3 nav buttons + pastilles + clock.
   Sur mobile elle déborde forcément en largeur : on autorise le scroll
   horizontal plutôt que de tout casser. */
body.mobile-mode #topbar {
  padding: 0.4rem 0.6rem;
  gap: 0.4rem;
  overflow-x: auto;
  overflow-y: hidden;
  flex-wrap: nowrap;
  /* Masquer la scrollbar horizontale sur mobile (geste tactile = ok sans) */
  scrollbar-width: none;
}
body.mobile-mode #topbar::-webkit-scrollbar { display: none; }
/* Le séparateur flex:1 entre nav-buttons (à gauche) et hud-pills (à droite)
   gaspille l'espace sur mobile où tout est déjà serré. On le réduit à gap
   minimal pour coller les éléments à gauche. */
body.mobile-mode .topbar-sep { flex: 0 0 0.4rem; }
/* v2.15 : le lockup horizontal (symbole + lettrage) est réduit sur mobile
   pour ne pas manger la largeur de la topbar déjà serrée. */
body.mobile-mode .logo-img { height: 26px; }
body.mobile-mode .nav-btn {
  font-size: 0.62rem;
  padding: 0.4rem 0.6rem;
  min-height: 32px;
  white-space: nowrap;
  flex-shrink: 0;
}
/* En mobile, on garde uniquement l'emoji des boutons de nav vue. Le texte
   ("Galaxie", "Vaisseau Mère", "Recherche") est cachée via une astuce CSS :
   on rend la couleur de texte transparente et on règle font-size à 0 sur
   tout sauf le premier "caractère" (emoji). Comme c'est fragile, on utilise
   à la place une approche plus robuste : couper la chaîne après le 1er char.
   Solution simple et fiable : on rend la couleur de texte transparente et
   on positionne un pseudo-élément ::before avec uniquement l'emoji.

   Encore plus simple en pratique : on met font-size petit, on ajoute du
   padding raisonnable, on tronque visuellement avec max-width + overflow:hidden.
   Mais le plus pragmatique et lisible : on retire le texte des labels en
   utilisant ::first-letter et en masquant le reste. Vu la fragilité de ces
   tricks et la complexité du HTML, le mieux est de demander aux 3 nav-btn
   d'avoir une taille réduite. */
body.mobile-mode #btn-galaxy,
body.mobile-mode #btn-ship,
body.mobile-mode #btn-research {
  /* On utilise font-size 0 sur le bouton (cache le texte) puis on remet
     la taille pour ::first-letter qui contient l'emoji. */
  font-size: 0;
  padding: 0.4rem 0.45rem;
  min-width: 36px;
}
body.mobile-mode #btn-galaxy::first-letter,
body.mobile-mode #btn-ship::first-letter,
body.mobile-mode #btn-research::first-letter {
  font-size: 1rem;
}
body.mobile-mode .hud-pill { font-size: 0.65rem; padding: 0.2rem 0.5rem; flex-shrink: 0; }
body.mobile-mode #clock { display: none; }  /* L'horloge est en double avec celle du téléphone */
/* La pastille "COMMANDANT" : utile sur desktop pour identifier le rôle joueur,
   mais en mobile c'est de la place perdue, on la cache. */
body.mobile-mode #player-pill { display: none; }
body.mobile-mode .adm-trigger {
  /* Comme les autres nav-btn : on cache le texte "ADMIN" et on ne garde
     que l'emoji ⚙. Astuce CSS : font-size: 0 sur le bouton + ::first-letter
     remet la taille pour l'emoji uniquement. */
  font-size: 0;
  padding: 0.4rem 0.45rem;
  min-width: 36px;
  flex-shrink: 0;
}
body.mobile-mode .adm-trigger::first-letter {
  font-size: 1rem;
}

/* Petits indicateurs (notif bell, buffs) : sur mobile la topbar
   est très contrainte. Les 3 boutons de navigation (🌌 Galaxie /
   🚀 Vaisseau Mère / 🧪 Recherche) sont CRITIQUES et doivent tenir SANS
   scroll horizontal. On masque donc les pastilles accessoires (buffs,
   qui de toute façon ouvrent des panneaux accessibles
   autrement. La cloche de notifications reste visible, utile au
   gameplay quotidien.
   (purge cette semaine : `#heritage-pill-host` et les trois règles
   `.role-badge*` retirés — la pastille Heritage n'a plus de fonction qui
   la peigne, et aucun JS ne crée plus le moindre `.role-badge` depuis la
   suppression des rôles en v2.32.) */
body.mobile-mode #speed-badge { display: none !important; }

/* ─── Resbar : 7 ressources sur une grille à 2 colonnes ─────────────── */
/* En desktop, les 7 ressources tiennent sur une ligne. Sur mobile, ça
   déborde violemment. On passe en grille fluide pour rester lisible. */
body.mobile-mode #resbar {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
body.mobile-mode .res {
  padding: 0.3rem 0.5rem;
  min-width: 0;       /* sinon le contenu force la largeur */
  font-size: 0.7rem;
  gap: 0.3rem;
}
body.mobile-mode .res-ico { font-size: 0.9rem; }
body.mobile-mode .res-name { font-size: 0.6rem; }
body.mobile-mode .res-val { font-size: 0.75rem; font-weight: 600; }
body.mobile-mode .res-rate { font-size: 0.55rem; }
/* La barre de capacité et le compteur "/ 40k" : on les cache sur mobile.
   Le joueur peut taper sur la ressource pour voir le détail dans une future
   itération ; pour l'instant on garde uniquement la valeur courante. */
body.mobile-mode .res > div[id^="rc-"],
body.mobile-mode .res .res-bar-bg { display: none; }

/* Les vues ont un padding-top en dur (88px) sur desktop pour passer sous la
   topbar+resbar. Sur mobile la resbar prend plus de place (2 lignes), donc
   on augmente la marge en haut. */
body.mobile-mode #view-galaxy,
body.mobile-mode #view-ship,
body.mobile-mode #view-research { padding-top: 110px !important; }

/* ─── Vue Galaxie : flèches plus grandes, tooltip et panneau planète
       repensés pour le tactile ─────────────────────────────────────── */

/* Flèches gauche/droite de navigation entre systèmes : zone tactile élargie. */
body.mobile-mode .sys-arrow {
  width: 48px;
  height: 48px;
  font-size: 1.3rem;
}
body.mobile-mode .sys-arrow.left { left: 0.5rem; }
body.mobile-mode .sys-arrow.right { right: 0.5rem; }

/* Indicateur du système courant : compacté.
   v1.01 : reste à gauche (collé sur sidebar) sur mobile aussi, avec
   max-width réduit pour ne pas écraser l'écran. */
body.mobile-mode #system-indicator {
  font-size: 0.7rem;
  padding: 0.4rem 0.6rem;
  max-width: 60%;
  left: calc(var(--sidebar-width, 48px) + 0.5rem);
}
body.mobile-mode .si-label { font-size: 0.55rem; }
body.mobile-mode .si-name { font-size: 0.85rem; }
body.mobile-mode #si-jump { font-size: 0.7rem; padding: 4px 6px; }

/* Légende galaxie : masquée sur mobile (trop d'espace pour peu d'info) */
body.mobile-mode #galaxy-legend { display: none; }

/* Mini-map : v1.01 — bas-DROITE (avant : bas-gauche), légende externe
   au survol/clic. */
body.mobile-mode #minimap-container {
  bottom: 8px;
  right: 8px;            /* v1.01 : était left:8px */
  padding: 6px;
}
body.mobile-mode #minimap-canvas { width: 200px; height: 200px; }
/* v1.01 : on NE masque PLUS la légende sur mobile-mode — elle est
   en flotting externe, accessible au tap (gestion JS minimap.js). */

/* Panneau planète : passe en bottom-sheet (collé en bas, pleine largeur) */
body.mobile-mode #planet-panel {
  position: fixed !important;
  left: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  top: auto !important;
  width: 100% !important;
  max-width: 100% !important;
  max-height: 75vh;
}
body.mobile-mode .pp-title { font-size: 1.05rem; }
body.mobile-mode .btn-anchor {
  /* Bouton d'ancrage : élargi pour cible tactile confortable */
  min-height: 48px;
  font-size: 0.9rem;
  width: 100%;
  margin-top: 0.6rem;
}
body.mobile-mode .btn-marque {
  /* v0.0.127 : même élargissement tactile que le bouton
     d'ancrage juste au-dessus — les deux vivent côte à côte dans la fiche. */
  min-height: 48px;
  font-size: 0.9rem;
  width: 100%;
  margin-top: 0.6rem;
}
body.mobile-mode .btn-close {
  /* Croix de fermeture : élargie aussi (sinon dur à taper précisément) */
  width: 36px; height: 36px;
  font-size: 1.1rem;
}

/* Panneau "Joueurs présents" (PvP) : repositionné pour ne pas chevaucher
   le bottom-sheet planète + mini-map. En mode mobile, la resbar fait 2
   lignes (au lieu d'1 en desktop), donc on le pousse plus bas pour passer
   sous topbar (~44px) + resbar (~70px sur 2 lignes) = 115px de hauteur cumulée.
   On laisse une marge confortable à 170px. */
body.mobile-mode #known-vms-panel {
  top: 170px;
  bottom: auto;
  right: 8px;
  left: 8px;
  width: auto;
  max-width: 100%;
  font-size: 0.75rem;
  /* En cas d'absence d'ennemis (cas le plus courant), le panneau est
     petit et discret. S'il y a beaucoup d'ennemis, il scrolle (overflow-y
     déjà défini sur .known-vms-panel). */
  max-height: 35vh;
}

/* ─── Vue Vaisseau Mère : la nav latérale devient horizontale ──────── */
/* C'est le plus gros changement : sur desktop, #ship-nav est une colonne
   à gauche de 190px. Sur mobile, on ne peut pas se permettre de bouffer
   tant de largeur. On la transforme en barre horizontale scrollable en
   haut de la vue. */
body.mobile-mode #ship-content { flex-direction: column; }
body.mobile-mode #ship-nav {
  width: 100%;
  flex-direction: row;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 0.4rem 0.4rem;
  gap: 0.3rem;
  border-right: none;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  scrollbar-width: none;
}
body.mobile-mode #ship-nav::-webkit-scrollbar { display: none; }
body.mobile-mode .sn-sect { display: none; }  /* le titre "Modules" est inutile sur la barre horizontale */
body.mobile-mode .sn-item {
  /* Chaque item devient un onglet : taille tactile confortable, flex-shrink: 0
     pour qu'il garde sa largeur lors du scroll horizontal. */
  flex-shrink: 0;
  padding: 0.6rem 0.7rem;
  font-size: 0.72rem;
  border-left: none;
  border-bottom: 2px solid transparent;
  min-height: 40px;
}
body.mobile-mode .sn-item.active {
  border-left: none;
  border-bottom-color: var(--accent2);
}
body.mobile-mode .sn-ico { font-size: 1rem; }

/* Anchor bar : on simplifie (le séparateur "|" disparaît, retour à la ligne) */
body.mobile-mode #anchor-bar {
  padding: 0.4rem 0.6rem;
  gap: 0.5rem;
  font-size: 0.65rem;
}
body.mobile-mode .anch-sep { display: none; }  /* les | sont moches avec retour à la ligne */
body.mobile-mode .anch-label { font-size: 0.55rem; }
body.mobile-mode #anchor-bar .nav-btn { font-size: 0.6rem; padding: 0.35rem 0.5rem; }

/* Main area : padding réduit pour gagner de la place */
body.mobile-mode #ship-main { padding: 0.7rem 0.6rem; }

/* Grille des bâtiments : 1 colonne pleine largeur sur mobile */
body.mobile-mode .bld-grid { grid-template-columns: 1fr; gap: 0.6rem; }

/* Carte bâtiment : on garde la structure mais réduit les espaces */
body.mobile-mode .bld-hdr { padding: 0.5rem 0.7rem; }
body.mobile-mode .bld-name { font-size: 0.75rem; }

/* File de construction : items un peu plus aérés (cibles tactiles) */
body.mobile-mode .q-item { padding: 0.7rem 0.7rem; }
body.mobile-mode .q-cancel { width: 36px; height: 36px; }
body.mobile-mode .q-name { font-size: 0.78rem; }

/* Grilles de flotte et de défenses : 1 colonne aussi */
body.mobile-mode .fleet-grid { grid-template-columns: 1fr; }

/* ─── Vue Recherche : grille à 1 colonne sur mobile ─────────────────── */
/* L'arbre tech a sa propre grille de cartes. Sur mobile on les empile. */
body.mobile-mode .tech-grid,
body.mobile-mode #grid-research { grid-template-columns: 1fr !important; }

/* ─── Panneau VM (drawer à droite) : pleine largeur sur mobile ──────── */
/* Le drawer existe déjà (cf. règles v0.20 plus haut, ligne ~625).
   Sur mobile forcé, on s'assure qu'il fonctionne comme prévu, même si
   l'écran est en réalité large. */
body.mobile-mode .view { right: 0; }
body.mobile-mode #topbar,
body.mobile-mode #resbar,
body.mobile-mode #prod-hint { right: 0 !important; }
body.mobile-mode #vm-panel-right {
  width: 80%;
  max-width: 320px;
  transform: translateX(100%);
  transition: transform 0.3s ease-out;
}
body.mobile-mode #vm-panel-right.mobile-open { transform: translateX(0); }
body.mobile-mode #vmp-mobile-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ─── Modales (admin, auth, joueurs, combat, etc.) : pleine largeur ──── */
/* Toutes les modales ont position fixed avec un width ~92% / max-width.
   Sur mobile, on les colle aux bords (presque pleine largeur) pour profiter
   de chaque pixel disponible. */
body.mobile-mode #admin-modal,
body.mobile-mode #players-modal,
body.mobile-mode #bans-modal,
body.mobile-mode .role-modal,
body.mobile-mode .combat-modal,
body.mobile-mode .fleet-compose-modal {
  width: 96vw !important;
  max-width: 96vw !important;
  max-height: 90vh !important;
}
body.mobile-mode #auth-modal .auth-box {
  width: 92vw;
  max-width: 92vw;
  padding: 1.2rem 1rem;
}
body.mobile-mode .auth-row input {
  /* Cibles tactiles : un input doit faire au moins 44px pour être confortable */
  min-height: 44px;
  font-size: 1rem;     /* >= 16px évite le zoom automatique sur iOS Safari */
}
body.mobile-mode .auth-submit {
  min-height: 48px;
  font-size: 0.95rem;
}

/* ─── Panneau Admin : sections empilées, boutons tactiles ───────────── */
body.mobile-mode .admin-section { padding: 0.6rem; }
body.mobile-mode .admin-btn,
body.mobile-mode .player-action {
  min-height: 40px;
  font-size: 0.75rem;
}

/* ─── Notifications (toasts en bas de l'écran) : pleine largeur ─────── */
body.mobile-mode #notifs {
  /* Sur desktop top: 60px = sous topbar (44) + resbar 1 ligne. Sur mobile
     la resbar fait 2 lignes (~70px), donc on pousse les toasts plus bas
     pour ne pas qu'ils se cachent derrière. */
  top: 130px;
  left: 8px;
  right: 8px;
  bottom: auto;
  width: auto;
}
body.mobile-mode #notifs > div {
  /* Chaque toast s'étend sur toute la largeur disponible */
  width: 100%;
  font-size: 0.78rem;
}

/* Centre de notifications (panneau ouvert via 🔔) : pleine largeur */
body.mobile-mode .notif-panel {
  right: 8px;
  left: 8px;
  width: auto;
  max-height: 70vh;
}

/* ─── Indicateur de production bloquée (prod-hint) : compact ────────── */
body.mobile-mode #prod-hint {
  font-size: 0.7rem;
  padding: 0.4rem 0.6rem;
  top: 105px !important;
}

/* ════════════════════════════════════════════════════════════════════
   SUR MOBILE, LE BANDEAU POUSSE LE CONTENU
   ════════════════════════════════════════════════════════════════════
   Signalement de Loryan, capture à l'appui : sur téléphone, le bandeau
   d'alerte flottait par-dessus le bandeau d'ancrage. Il se pose désormais en
   BANDE pleine largeur juste sous la barre des
   ressources (--resbar-bottom), et la vue RÉSERVE sa hauteur mesurée
   (--hint-h, posée par core/layout.js) pour décaler le contenu vers le bas.
   ⚠️ Mobile SEULEMENT (« body.mobile-mode », posée aussi sous 700 px) : sur
   ordinateur, le bandeau garde sa place flottante dans le coin haut-droit
   vide — cette règle ne le touche pas.
   ⚠️ LA RÉSERVE DE PLACE N'EST PAS ICI : elle est repliée dans
   --total-header-h par core/layout.js (sur mobile, la hauteur du
   bandeau y entre), parce que chaque vue réserve sa place par une règle en
   #id — et un #id gagne en spécificité sur n'importe quelle classe. Poser la
   réserve en CSS de classe ici ne servirait à rien : le #id la couvrirait. */
body.mobile-mode #prod-hint.visible {
  top: var(--resbar-bottom, 88px) !important;
  left: 0 !important;
  right: 0 !important;
  width: auto !important;
  max-width: none !important;
  border-radius: 0 !important;
}

/* ─── Astuce pratique : éviter le zoom automatique iOS sur les inputs ── */
/* iOS Safari zoome automatiquement quand on focus un input dont la taille
   de police est < 16px. On force tous les inputs à >= 16px en mode mobile. */
body.mobile-mode input,
body.mobile-mode select,
body.mobile-mode textarea {
  font-size: max(1rem, 16px) !important;
}

/* Modale de sélection de rôle (Foreur/Explorateur/Pirate) : 1 colonne */
body.mobile-mode .role-modal { padding: 16px; }
body.mobile-mode .role-modal-title { font-size: 1.1rem; }
body.mobile-mode .role-cards { grid-template-columns: 1fr; }
body.mobile-mode .role-card-icon { font-size: 1.5rem; }

/* Rapport de combat : 2 camps empilés sur mobile */
body.mobile-mode .combat-report-grid { grid-template-columns: 1fr; }

/* Liste des joueurs dans le panneau admin : empilé vertical */
body.mobile-mode .player-row { flex-direction: column; align-items: stretch; }
body.mobile-mode .player-actions { flex-direction: row; }
body.mobile-mode .player-action { flex: 1; }

/* ════════════════════════════════════════════════════════════════════
   v0.76 — Mode vacances
   ════════════════════════════════════════════════════════════════════
   Deux états visuels pour la pastille :
   - inactif : discret, juste l'icône 🏖️ comme point d'entrée
   - actif : orange clignotant pour signaler le mode pause */

.vacation-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  background: #1a2540;
  border: 1px solid #324569;
  border-radius: 999px;
  color: #cbd6ea;
  cursor: pointer;
  font-family: var(--mono);
  font-size: .75rem;
  transition: all .15s;
}

.vacation-pill:hover {
  background: #243049;
  border-color: #4a6294;
}

.vacation-pill-icon {
  font-size: .9rem;
  line-height: 1;
}

.vacation-pill-text {
  letter-spacing: .04em;
}

/* État ACTIF : orange + animation discrète pour rappeler en permanence
   que le joueur est "en pause". Pas trop agressif pour ne pas distraire,
   mais suffisamment visible pour qu'on ne l'oublie pas. */
.vacation-pill-active {
  background: linear-gradient(135deg, #7c2d12 0%, #9a3412 100%);
  border-color: #ea580c;
  color: #fed7aa;
  animation: vacation-pulse 2.4s ease-in-out infinite;
}

.vacation-pill-active:hover {
  background: linear-gradient(135deg, #9a3412 0%, #c2410c 100%);
  border-color: #fb923c;
}

@keyframes vacation-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(234, 88, 12, 0.4); }
  50%      { box-shadow: 0 0 0 6px rgba(234, 88, 12, 0); }
}

/* Sur mobile, on cache le texte et on garde juste l'icône pour
   économiser la place dans la topbar. */
body.mobile-mode .vacation-pill-text { display: none; }

/* v0.0.8 (remarque de noue, « c'est voulu ? »).
   Les pastilles Mode Vacances (au repos) et Inventaire portaient un fond
   PLEIN (#1a2540) et une bordure claire là où toutes leurs voisines de la
   topbar (cloche, 🎓, 👤) sont transparentes : elles semblaient
   SÉLECTIONNÉES en permanence — surbrillance que Loryan n'avait pas
   voulue. Alignées ici sur `.topbar-icon-btn`, le style commun des
   boutons d'icône de la barre. ⚠️ La pastille Vacances ACTIVE garde son
   orange clignotant : là, se faire remarquer est le but.
   La classe `pastille-discrete` est posée par les deux modules qui rendent
   ces pastilles (vacation.js, inventaire.js). */
.vacation-pill.pastille-discrete,
.btq-pastille.pastille-discrete,
.inv-pastille.pastille-discrete {
  background: transparent;
  border: 1px solid #1f2937;
  border-radius: 6px;
}
.vacation-pill.pastille-discrete:hover,
.btq-pastille.pastille-discrete:hover,
.inv-pastille.pastille-discrete:hover {
  background: #131930;
  border-color: #334155;
}

/* ════════════════════════════════════════════════════════════════════
   v0.77 — Pirate Marqué
   ════════════════════════════════════════════════════════════════════
   Deux contextes d'affichage :
   1. Pastille topbar : pour le JOUEUR LUI-MÊME quand il est marqué.
      Rouge / orange clignotant, sert d'avertissement permanent.
   2. Badge inline : à côté du nom des AUTRES joueurs marqués dans
      le panneau PvP. Plus discret (juste l'icône) pour ne pas
      surcharger la liste. */

/* ── Pastille topbar (joueur courant) ────────────────────────────── */
.marked-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  cursor: pointer;
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .04em;
  transition: all .15s;
  border: 1px solid;
  animation: marked-pulse 2s ease-in-out infinite;
}

.marked-pill-icon { font-size: .9rem; line-height: 1; }

/* Niveau 1 — Bronze : marron rougeoyant */
.marked-pill-1 {
  background: linear-gradient(135deg, #78350f 0%, #92400e 100%);
  border-color: #b45309;
  color: #fed7aa;
}
.marked-pill-1:hover { background: linear-gradient(135deg, #92400e 0%, #b45309 100%); }

/* Niveau 2 — Argent : gris froid agressif */
.marked-pill-2 {
  background: linear-gradient(135deg, #4b5563 0%, #6b7280 100%);
  border-color: #9ca3af;
  color: #f3f4f6;
}
.marked-pill-2:hover { background: linear-gradient(135deg, #6b7280 0%, #9ca3af 100%); }

/* Niveau 3 — Or : rouge sang vif (paradoxal mais c'est le NIVEAU LE
   PLUS DANGEREUX, on veut que ça crie « danger ») */
.marked-pill-3 {
  background: linear-gradient(135deg, #991b1b 0%, #b91c1c 100%);
  border-color: #ef4444;
  color: #fef2f2;
  animation: marked-pulse-strong 1.2s ease-in-out infinite;
}
.marked-pill-3:hover { background: linear-gradient(135deg, #b91c1c 0%, #dc2626 100%); }

@keyframes marked-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
  50%      { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
}

@keyframes marked-pulse-strong {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
  50%      { box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
}

/* Mobile : juste l'icône, le texte « WANTED » prendrait trop de place */
body.mobile-mode .marked-pill-text { display: none; }

/* ── Badge inline (autres joueurs dans le panneau PvP) ───────────── */
.known-vms-marked {
  display: inline-block;
  margin: 0 4px;
  font-size: .9rem;
  cursor: help;
}

/* Niveau 3 (Or) : un léger glow rouge pour signaler la cible juteuse */
.known-vms-marked-3 {
  text-shadow: 0 0 4px rgba(239, 68, 68, 0.6);
}

/* ════════════════════════════════════════════════════════════════════
   v0.82 — Chat global serveur
   ════════════════════════════════════════════════════════════════════
   Panneau flottant en bas à droite (PC) ou plein écran (mobile).
   Couleurs cohérentes avec le reste de l'app (sombre, accents cyan). */

/* ─── Bouton de la topbar ──────────────────────────────────────── */
.chat-trigger {
  position: relative;
}

#chat-unread-badge {
  display: inline-block;
  background: #ef4444;
  color: #fef2f2;
  border-radius: 999px;
  padding: 1px 5px;
  font-size: .65rem;
  font-weight: 600;
  margin-left: 4px;
  line-height: 1.2;
  vertical-align: middle;
  min-width: 16px;
  text-align: center;
}

/* ─── Panneau flottant ────────────────────────────────────────── */
.chat-panel {
  position: fixed;
  bottom: 0;
  right: 16px;
  width: 360px;
  height: 480px;
  background: #0a0e1c;
  border: 1px solid #324569;
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  display: flex;
  flex-direction: column;
  z-index: 999;
  font-family: inherit;
  box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.5);
}

/* Sur mobile : plein écran */
body.mobile-mode .chat-panel {
  right: 0;
  left: 0;
  width: 100%;
  height: calc(100vh - 60px);
  border-radius: 0;
  border-left: none;
  border-right: none;
}

.chat-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: linear-gradient(135deg, #1e3a5f 0%, #1a2540 100%);
  border-bottom: 1px solid #324569;
  border-radius: 8px 8px 0 0;
}

.chat-title {
  font-weight: 600;
  font-size: .95rem;
  color: #cbd6ea;
  letter-spacing: .02em;
}

.chat-close {
  background: transparent;
  border: none;
  color: #8da4c4;
  font-size: 1.1rem;
  cursor: pointer;
  padding: 0 6px;
  line-height: 1;
}
.chat-close:hover { color: #ef4444; }

/* v0.0.x — LES DEUX ONGLETS Public / Coalition.
   Un segment cloisonné, l'onglet actif en surbrillance, pour que « les deux
   soient bien séparés ». Gabarit assez grand pour le doigt en mobile. */
.chat-canaux {
  display: inline-flex;
  gap: 2px;
  background: rgba(10,18,34,.55);
  border: 1px solid #324569;
  border-radius: 7px;
  padding: 2px;
}
.chat-onglet {
  border: none;
  background: transparent;
  color: #8da4c4;
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .02em;
  padding: 4px 12px;
  border-radius: 5px;
  cursor: pointer;
  line-height: 1.2;
}
.chat-onglet:hover { color: #cbd6ea; }
.chat-onglet.actif {
  background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
  color: #fff;
}
@media (max-width:820px){ .chat-onglet{ padding:6px 14px; font-size:.9rem; } }
body.mobile-mode .chat-onglet{ padding:6px 14px; font-size:.9rem; }

/* ─── Zone des messages ───────────────────────────────────────── */
.chat-messages-container {
  flex: 1;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.chat-messages-list {
  flex: 1;
  overflow-y: auto;
  padding: 10px 12px;
  scroll-behavior: smooth;
}

.chat-empty {
  text-align: center;
  color: #8da4c4;
  padding: 40px 20px;
  font-size: .9rem;
}

/* Indicateur "X nouveaux messages" */
.chat-new-indicator {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  background: #1e3a5f;
  border: 1px solid #4a6294;
  color: #fef2f2;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: .75rem;
  cursor: pointer;
  z-index: 2;
  animation: chat-bounce-in .3s ease-out;
}
.chat-new-indicator:hover { background: #324569; }

@keyframes chat-bounce-in {
  from { opacity: 0; transform: translateX(-50%) translateY(20px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* ─── Bulle de message ────────────────────────────────────────── */
.chat-message {
  margin-bottom: 10px;
  padding: 6px 10px;
  background: #1a2540;
  border: 1px solid #243049;
  border-radius: 6px;
  font-size: .85rem;
  color: #cbd6ea;
  word-wrap: break-word;
  word-break: break-word;
}

.chat-message-me {
  background: #1e3a5f;
  border-color: #324569;
}

.chat-message-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 3px;
  font-size: .75rem;
}

.chat-message-author {
  color: #60a5fa;
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px dotted #4a6294;
}
.chat-message-author:hover { color: #93c5fd; }

.chat-message-me .chat-message-author {
  color: #fbbf24;
}

.chat-message-time {
  color: #8da4c4;
  font-size: .7rem;
  margin-left: auto;
  font-family: var(--mono);
}

.chat-message-delete {
  background: transparent;
  border: none;
  color: #6b7280;
  cursor: pointer;
  font-size: .75rem;
  padding: 0 4px;
  line-height: 1;
}
.chat-message-delete:hover { color: #ef4444; }

.chat-message-content {
  white-space: pre-wrap;
  line-height: 1.4;
}

/* ─── Zone d'envoi ────────────────────────────────────────────── */
.chat-input-area {
  border-top: 1px solid #324569;
  padding: 8px 10px;
  background: #0a0e1c;
}

.chat-input {
  width: 100%;
  background: #1a2540;
  border: 1px solid #324569;
  border-radius: 4px;
  color: #cbd6ea;
  padding: 6px 8px;
  font-family: inherit;
  font-size: .85rem;
  resize: none;
  min-height: 36px;
  max-height: 80px;
  outline: none;
}
.chat-input:focus { border-color: #4a6294; }

.chat-input-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 6px;
}

.chat-char-count {
  font-size: .7rem;
  color: #8da4c4;
  font-family: var(--mono);
}

/* v0.0.7 — le bouton 🔔/🔕 et son menu de durées.
   `margin-right:auto` colle le groupe au compteur et laisse « Envoyer »
   seul à droite malgré le `space-between` de la barre. Le menu s'ouvre
   AU-DESSUS de la barre : en dessous, il sortirait du panneau. */
.chat-outils { display: flex; align-items: center; margin-right: auto;
  margin-left: 8px; position: relative; }
.chat-sourdine-btn { background: none; border: 1px solid #33456b;
  border-radius: 6px; color: #8da4c4; cursor: pointer; font-size: .8rem;
  padding: 2px 8px; line-height: 1.3; }
.chat-sourdine-btn:hover { border-color: #4a6294; color: #cfe0f5; }
.chat-sourdine-btn.en-sourdine { color: #f59e0b; border-color: rgba(245, 158, 11, .45); }
.chat-sourdine-menu { position: absolute; bottom: calc(100% + 8px); left: 0;
  display: flex; flex-direction: column; gap: 4px; min-width: 230px;
  background: #0d1626; border: 1px solid #33456b; border-radius: 8px;
  padding: 8px; z-index: 60; box-shadow: 0 6px 18px rgba(0, 0, 0, .5); }
.chat-sourdine-titre { font-size: .68rem; color: #8da4c4; padding: 2px 4px 4px; }
.chat-sourdine-choix { background: rgba(255, 255, 255, .04);
  border: 1px solid #33456b; border-radius: 6px; color: #cfe0f5;
  cursor: pointer; font-size: .75rem; padding: 5px 8px; text-align: left; }
.chat-sourdine-choix:hover { background: rgba(255, 255, 255, .09); }
.chat-sourdine-lever { margin-top: 4px; color: #7dd3a0; }

.chat-send-btn {
  background: linear-gradient(135deg, #1e3a5f 0%, #324569 100%);
  border: 1px solid #4a6294;
  color: #fef2f2;
  padding: 4px 14px;
  border-radius: 4px;
  cursor: pointer;
  font-size: .8rem;
  font-weight: 500;
}
.chat-send-btn:hover:not(:disabled) {
  background: linear-gradient(135deg, #324569 0%, #4a6294 100%);
}
.chat-send-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ════════════════════════════════════════════════════════════════════
   v0.83 — Messagerie privée (PM 1-to-1)
   ════════════════════════════════════════════════════════════════════ */

/* ─── Badge non-lus topbar ──────────────────────────────────── */
#pm-unread-badge {
  display: inline-block;
  background: #60a5fa;
  color: #0a0e1c;
  border-radius: 999px;
  padding: 1px 5px;
  font-size: .65rem;
  font-weight: 700;
  margin-left: 4px;
  line-height: 1.2;
  vertical-align: middle;
  min-width: 16px;
  text-align: center;
}

/* ─── Liste des conversations (modale inbox) ────────────────── */
.pm-conv-row {
  display: flex;
  align-items: center;
  padding: 10px 12px;
  background: #0a0e1c;
  border: 1px solid #324569;
  border-radius: 4px;
  cursor: pointer;
  transition: background .15s;
}
.pm-conv-row:hover { background: #1a2540; }

.pm-conv-badge {
  background: #60a5fa;
  color: #0a0e1c;
  border-radius: 999px;
  padding: 1px 6px;
  font-size: .7rem;
  font-weight: 700;
  min-width: 18px;
  text-align: center;
}

/* ─── Modale de conversation ────────────────────────────────── */
.pm-conv-box {
  max-width: 540px;
  width: 100%;
}

.pm-messages-list {
  flex: 1;
  overflow-y: auto;
  padding: 12px;
  scroll-behavior: smooth;
}

/* ─── Bulles de message ─────────────────────────────────────── */
.pm-message {
  margin-bottom: 10px;
  padding: 8px 12px;
  border-radius: 8px;
  max-width: 80%;
  word-wrap: break-word;
  word-break: break-word;
}

.pm-message-other {
  background: #1a2540;
  border: 1px solid #243049;
  margin-right: auto;
}

.pm-message-me {
  background: #1e3a5f;
  border: 1px solid #324569;
  margin-left: auto;
}

.pm-message-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
  font-size: .72rem;
}

.pm-message-author {
  color: #60a5fa;
  font-weight: 600;
}

.pm-message-me .pm-message-author { color: #fbbf24; }

.pm-message-time {
  color: #8da4c4;
  font-family: var(--mono);
  font-size: .7rem;
  margin-left: auto;
}

.pm-message-content {
  white-space: pre-wrap;
  line-height: 1.4;
  color: #cbd6ea;
  font-size: .85rem;
}

/* ─── Zone d'input ──────────────────────────────────────────── */
.pm-input-area {
  border-top: 1px solid #324569;
  padding: 10px 12px;
  background: #0a0e1c;
}

.pm-input {
  width: 100%;
  background: #1a2540;
  border: 1px solid #324569;
  border-radius: 4px;
  color: #cbd6ea;
  padding: 6px 8px;
  font-family: inherit;
  font-size: .85rem;
  resize: none;
  min-height: 44px;
  max-height: 100px;
  outline: none;
}
.pm-input:focus { border-color: #4a6294; }

.pm-input-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 6px;
}

.pm-char-count {
  font-size: .7rem;
  color: #8da4c4;
  font-family: var(--mono);
}

.pm-send-btn {
  background: linear-gradient(135deg, #1e3a5f 0%, #324569 100%);
  border: 1px solid #4a6294;
  color: #fef2f2;
  padding: 4px 14px;
  border-radius: 4px;
  cursor: pointer;
  font-size: .8rem;
  font-weight: 500;
}
.pm-send-btn:hover:not(:disabled) {
  background: linear-gradient(135deg, #324569 0%, #4a6294 100%);
}
.pm-send-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ════════════════════════════════════════════════════════════════════
   v0.84-client — Modération : boutons signaler, bandeau mute
   ════════════════════════════════════════════════════════════════════ */

/* ─── Bouton signaler dans les messages chat ──────────────────── */
.chat-message-report,
.pm-message-report {
  background: transparent;
  border: none;
  color: #ef4444;
  cursor: pointer;
  font-size: .8rem;
  padding: 0 4px;
  line-height: 1;
  opacity: 0.5;
  transition: opacity .15s;
}
.chat-message-report:hover,
.pm-message-report:hover {
  opacity: 1;
}

/* ─── Bandeau "Tu es muet pendant X" ──────────────────────────── */
.mute-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  background: linear-gradient(90deg, #7c1d1d 0%, #991b1b 100%);
  color: #fecaca;
  padding: 8px 12px;
  text-align: center;
  font-size: .85rem;
  z-index: 9999;
  border-bottom: 1px solid #7f1d1d;
  box-shadow: 0 2px 8px rgba(127, 29, 29, .3);
}
body.mobile-mode .mute-banner {
  font-size: .75rem;
  padding: 6px 8px;
}

/* ════════════════════════════════════════════════════════════════════
   v0.85 — Mentions @pseudo + Présence + Annotations enrichies
   ════════════════════════════════════════════════════════════════════ */

/* ─── @mentions stylisées dans le rendu chat ──────────────── */
.chat-mention {
  color: #60a5fa;
  background: rgba(96, 165, 250, .12);
  padding: 0 4px;
  border-radius: 3px;
  cursor: pointer;
  font-weight: 500;
}
.chat-mention:hover {
  background: rgba(96, 165, 250, .24);
}

/* Si la mention me concerne (G.username) — plus visible */
.chat-mention-me {
  color: #fbbf24;
  background: rgba(251, 191, 36, .18);
  padding: 0 4px;
  border-radius: 3px;
  cursor: pointer;
  font-weight: 600;
  border: 1px solid rgba(251, 191, 36, .35);
}
.chat-mention-me:hover {
  background: rgba(251, 191, 36, .3);
}

/* ─── Dropdown d'autocomplete @mentions ─────────────────────── */
.mention-autocomplete {
  background: #0a0e1c;
  border: 1px solid #4a6294;
  border-radius: 6px;
  box-shadow: 0 -4px 12px rgba(0, 0, 0, .5);
  max-height: 200px;
  overflow-y: auto;
  z-index: 1500;
}

.mention-suggestion {
  padding: 6px 10px;
  color: #cbd6ea;
  font-size: .85rem;
  cursor: pointer;
  border-bottom: 1px solid #1a2540;
}
.mention-suggestion:last-child { border-bottom: none; }
.mention-suggestion:hover { background: #1a2540; }
.mention-suggestion.selected {
  background: #1e3a5f;
  color: #fef2f2;
}

/* ════════════════════════════════════════════════════════════════════
   v0.86-client — Guildes Pirates
   ════════════════════════════════════════════════════════════════════ */

/* ─── Cartes de guildes (page liste) ───────────────────────────── */
.guild-card {
  transition: transform .15s ease, box-shadow .15s ease;
}
.guild-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, .4);
}

/* ─── Options de VM dans la modale tribut ──────────────────────── */
.tribute-vm-option {
  transition: background .15s ease, border-color .15s ease;
}
.tribute-vm-option:hover:not(.tribute-vm-disabled) {
  background: #1a2540 !important;
  border-color: #4a6294 !important;
}
.tribute-vm-disabled {
  opacity: 0.6;
}

/* ════════════════════════════════════════════════════════════════════
   v0.87-client — Comptoirs commerciaux
   ════════════════════════════════════════════════════════════════════ */

.post-card {
  transition: transform .15s ease, box-shadow .15s ease;
}
.post-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, .4);
}

/* Onglets dans la modale détail */
.post-tab-btn {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  color: #8da4c4;
  padding: 6px 12px;
  cursor: pointer;
  font-size: .85rem;
  transition: color .15s, border-color .15s;
}
.post-tab-btn:hover {
  color: #cbd6ea;
}
.post-tab-active {
  color: #fed7aa !important;
  border-bottom-color: #f59e0b !important;
  font-weight: 500;
}

/* ════════════════════════════════════════════════════════════════════
   v0.88-client — Explorateurs IA
   ════════════════════════════════════════════════════════════════════ */

.explorer-card {
  transition: transform .15s ease, box-shadow .15s ease;
}
.explorer-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, .4);
}

.explorer-vm-option {
  transition: background .15s ease, border-color .15s ease;
}
.explorer-vm-option:hover:not(.explorer-vm-disabled) {
  background: #1a2540 !important;
  border-color: #4a6294 !important;
}
.explorer-vm-disabled {
  opacity: 0.6;
}

/* ════════════════════════════════════════════════════════════════════
   v0.89 — Contrats Compagnie Libre
   ════════════════════════════════════════════════════════════════════ */

.contract-target-btn {
  transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.contract-target-btn:hover {
  background: #1a2540 !important;
  border-color: #4a6294 !important;
  transform: translateY(-1px);
}

/* ─── v0.90 : Marché de cohabitation ─────────────────────────── */
/* Pastille "🤝 N" sur le bouton ÉCHANGES quand j'ai des offres en attente */
#cohab-pending-badge {
  display: inline-block;
  background: #fbbf24;
  color: #0a0e1c;
  border-radius: 999px;
  padding: 1px 5px;
  font-size: .65rem;
  font-weight: 700;
  margin-left: 4px;
  line-height: 1.2;
  vertical-align: middle;
  min-width: 16px;
  text-align: center;
}

/* Carte d'un cohabitant ou d'une offre dans la page Échanges */
.cohab-card:hover {
  background: #243348 !important;
  transform: translateY(-1px);
  transition: all 0.15s ease;
}

.cohab-offer-card {
  transition: border-color 0.15s ease;
}
.cohab-offer-card:hover {
  border-color: #475569 !important;
}

/* Boutons de choix de planète (quand le joueur a plusieurs planètes) */
.cohab-planet-choice:hover {
  background: #1e3a5f !important;
  border-color: #475569 !important;
}

/* ═══════════════════════════════════════════════════════════════════
   v0.123 — COMPOSANTS GÉNÉRIQUES GDD §21.5 (Bloc 9 — 3/8)
   ═══════════════════════════════════════════════════════════════════
   4 systèmes posés en COHABITATION avec le code historique :
     1. .dsh-modal — anatomie de modale unifiée (header / body / footer)
     2. .dsh-card — anatomie de carte unifiée (header / body / footer)
     3. .dsh-btn-* — 5 niveaux de boutons GDD §21.5.2
     4. [data-tooltip] — tooltips via attribut data

   Les composants pré-v0.116 conservent leurs classes propres (.modal-*,
   .card, .btn-anchor, .btn-up, etc.).

   ⚠️ NE JAMAIS POSER UNE CLASSE .dsh-* À CÔTÉ D'UNE CLASSE HISTORIQUE
   SUR LE MÊME ÉLÉMENT. Ça a été essayé (.dsh-btn .dsh-btn-primary
   ajoutées sur les boutons .onboarding-btn) et ça casse : les deux
   classes fixent les MÊMES propriétés dimensionnelles avec des valeurs
   différentes (.onboarding-btn impose son padding, .dsh-btn impose sa
   hauteur), donc c'est l'ordre de déclaration qui tranche, pas
   l'intention. Résultat observé : boutons trop hauts et mal alignés.
   Une migration est donc COMPLÈTE (on retire l'ancienne classe, on
   pose la nouvelle, et on regarde l'écran) ou elle n'a pas lieu.
   ─────────────────────────────────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════
   1. ANATOMIE DE MODALE — GDD §21.5.3
   ═══════════════════════════════════════════════════════════════════
   Header (sticky) + Body (scrollable) + Footer optionnel (sticky).
   Backdrop + scale-in fade. Empilement INTERDIT (GDD strict). */

.dsh-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: rgba(8, 12, 20, 0.85);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 50px var(--space-4) var(--space-4) var(--space-4);
  overflow-y: auto;
  animation: dshModalFadeIn var(--anim-normal) ease-out;
}
.dsh-modal-backdrop.is-critical {
  z-index: var(--z-critical);
}

@keyframes dshModalFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.dsh-modal {
  max-width: 640px;
  width: 100%;
  background: linear-gradient(135deg, #1a2538 0%, #0f1828 100%);
  border: 1px solid #2d4a6e;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 100px);
  animation: dshModalScaleIn var(--anim-normal) ease-out;
}

@keyframes dshModalScaleIn {
  from { transform: scale(0.96); opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}

.dsh-modal-header {
  position: sticky;
  top: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--color-border-subtle);
  background: rgba(15, 24, 40, 0.95);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  flex-shrink: 0;
}

.dsh-modal-title {
  font-size: var(--text-h2);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-heading);
  color: var(--color-text-primary);
  margin: 0;
}

.dsh-modal-close {
  background: transparent;
  border: 1px solid var(--color-border-strong);
  color: var(--color-text-secondary);
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-size: var(--text-body-lg);
  line-height: 1;
  transition: color var(--anim-fast), border-color var(--anim-fast);
}
.dsh-modal-close:hover {
  color: var(--color-text-primary);
  border-color: var(--color-text-secondary);
}

.dsh-modal-body {
  padding: var(--space-6);
  overflow-y: auto;
  flex: 1;
  color: var(--color-text-primary);
}

.dsh-modal-footer {
  position: sticky;
  bottom: 0;
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-6);
  border-top: 1px solid var(--color-border-subtle);
  background: rgba(15, 24, 40, 0.95);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  flex-shrink: 0;
}

/* ═══════════════════════════════════════════════════════════════════
   2. ANATOMIE DE CARTE — GDD §21.5.1
   ═══════════════════════════════════════════════════════════════════
   3 zones : Header (toujours) / Body (toujours) / Footer (souvent).
   Hauteur min 120 px, padding 16 px, radius 8 px, fond #131930. */

.dsh-card {
  display: flex;
  flex-direction: column;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  min-height: 120px;
  transition: border-color var(--anim-fast), transform var(--anim-fast),
              box-shadow var(--anim-fast);
}
/* États visuels GDD §21.5.1 */
.dsh-card:hover {
  border-color: var(--color-border-strong);
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}
.dsh-card.is-selected {
  border-color: var(--color-info-fg);
}
.dsh-card.is-error {
  border-color: var(--color-error-fg);
}
.dsh-card.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}
.dsh-card.is-building {
  position: relative;
}
.dsh-card.is-building::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(15, 24, 40, 0.5);
  border-radius: var(--radius-md);
  pointer-events: none;
}

.dsh-card-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.dsh-card-icon {
  font-size: var(--text-body-lg);
  flex-shrink: 0;
}

.dsh-card-title {
  font-size: var(--text-h3);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
  margin: 0;
  flex: 1;
}

.dsh-card-badge {
  font-size: var(--text-tiny);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-border-subtle);
  color: var(--color-text-secondary);
}

.dsh-card-body {
  flex: 1;
  color: var(--color-text-secondary);
  font-size: var(--text-body);
  line-height: var(--line-height-body);
}

.dsh-card-footer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
}

/* ═══════════════════════════════════════════════════════════════════
   3. BOUTONS — 5 NIVEAUX GDD §21.5.2
   ═══════════════════════════════════════════════════════════════════
   .dsh-btn-primary : action principale (1 par zone, fond plein coloré)
   .dsh-btn-secondary : bordure colorée, fond transparent
   .dsh-btn-tertiary : texte coloré, soulignement hover (« Annuler »)
   .dsh-btn-icon : icône seule (bouton carré ou rond)
   .dsh-btn-destructive : irréversible (supprimer, bannir) — fond rouge */

.dsh-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--btn-height-standard);
  padding: 0 var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-family: var(--body);
  font-size: var(--text-body);
  font-weight: var(--font-weight-semibold);
  line-height: 1;
  cursor: pointer;
  transition: background var(--anim-fast), border-color var(--anim-fast),
              color var(--anim-fast), opacity var(--anim-fast),
              transform var(--anim-fast);
  white-space: nowrap;
  user-select: none;
}
.dsh-btn:active {
  /* Effet d'enfoncement subtil GDD §21.5.2 */
  transform: translateY(1px);
}
.dsh-btn:disabled {
  /* État disabled GDD §21.5.2 : opacité 40%, curseur non-interactif */
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}
.dsh-btn-small { height: var(--btn-height-small); padding: 0 var(--space-3); font-size: var(--text-small); }
.dsh-btn-large { height: var(--btn-height-large); padding: 0 var(--space-6); font-size: var(--text-body-lg); }

/* ─── Primaire : fond plein info (par défaut), succès ou erreur ───── */
.dsh-btn-primary {
  background: var(--color-info-fg);
  color: #fff;
  border-color: var(--color-info-fg);
}
.dsh-btn-primary:hover:not(:disabled) {
  background: #1d4ed8;  /* info-fg légèrement plus sombre */
  border-color: #1d4ed8;
}
.dsh-btn-primary.is-success {
  background: var(--color-success-fg);
  border-color: var(--color-success-fg);
}
.dsh-btn-primary.is-success:hover:not(:disabled) {
  background: #16a34a;
  border-color: #16a34a;
}

/* ─── Secondaire : bordure + texte coloré, fond transparent ─────── */
.dsh-btn-secondary {
  background: transparent;
  color: var(--color-info-fg);
  border-color: var(--color-info-fg);
}
.dsh-btn-secondary:hover:not(:disabled) {
  background: var(--color-info-bg);
}

/* ─── Tertiaire : texte coloré seul + soulignement hover ─────────── */
.dsh-btn-tertiary {
  background: transparent;
  color: var(--color-text-secondary);
  border-color: transparent;
}
.dsh-btn-tertiary:hover:not(:disabled) {
  color: var(--color-text-primary);
  text-decoration: underline;
}

/* ─── Icône seule : bouton carré ou rond (×, ♥, ⚙) ──────────────── */
.dsh-btn-icon {
  width: var(--btn-height-standard);
  height: var(--btn-height-standard);
  padding: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  border-color: var(--color-border-subtle);
}
.dsh-btn-icon:hover:not(:disabled) {
  background: var(--color-bg-hover);
  color: var(--color-text-primary);
}

/* ─── Destructif : fond rouge, toujours avec confirmation ──────── */
.dsh-btn-destructive {
  background: var(--color-error-fg);
  color: #fff;
  border-color: var(--color-error-fg);
}
.dsh-btn-destructive:hover:not(:disabled) {
  background: #b91c1c;  /* rouge plus sombre */
  border-color: #b91c1c;
}

/* ═══════════════════════════════════════════════════════════════════
   4. TOOLTIPS — GDD §21.5.7 (data-tooltip CSS pur + JS positionnement)
   ═══════════════════════════════════════════════════════════════════
   Utilisation : <button data-tooltip="Texte">Bouton</button>
   Affichage après 500ms desktop / 700ms long press mobile.
   Contenu limité à ~3 lignes via max-width 240 px. */

[data-tooltip] {
  position: relative;
}
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + var(--space-2));
  left: 50%;
  transform: translateX(-50%);
  background: var(--tooltip-bg);
  color: var(--tooltip-fg);
  border: 1px solid var(--tooltip-border);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-small);
  font-family: var(--font-body);
  line-height: var(--line-height-body);
  max-width: var(--tooltip-max-width);
  width: max-content;
  white-space: normal;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--anim-fast), visibility var(--anim-fast);
  transition-delay: var(--tooltip-delay);
  z-index: var(--z-tooltip);
  box-shadow: var(--shadow-md);
}
[data-tooltip]:hover::after,
[data-tooltip]:focus::after {
  opacity: 1;
  visibility: visible;
}
/* Tooltip ne s'affiche pas si vide */
[data-tooltip=""]::after { display: none; }
/* Variante positionnement bas */
[data-tooltip][data-tooltip-position="bottom"]::after {
  bottom: auto;
  top: calc(100% + var(--space-2));
}
/* Variante positionnement gauche / droite */
[data-tooltip][data-tooltip-position="right"]::after {
  bottom: 50%;
  left: calc(100% + var(--space-2));
  transform: translateY(50%);
}
[data-tooltip][data-tooltip-position="left"]::after {
  bottom: 50%;
  left: auto;
  right: calc(100% + var(--space-2));
  transform: translateY(50%);
}

/* ═══════════════════════════════════════════════════════════════════
   v0.124 — COMPOSANTS GÉNÉRIQUES GDD §21.2 + §21.5.4 (Bloc 9 — 4/8)
   ═══════════════════════════════════════════════════════════════════
   3 nouveaux systèmes posés en cohabitation :
     1. .dsh-tabs — barre d'onglets horizontale scrollable (§21.2.2)
     2. .dsh-queue — files d'attente UI génériques (§21.5)
     3. .dsh-input + .dsh-toggle — champs de saisie sous-set GDD §21.5.4

   La barre .dsh-tabs est utilisée pour la navigation principale du
   jeu (refonte topbar v0.124 — 11 onglets) ET pour les sous-onglets
   internes des vues (Marché, Factions IA, Social, etc. = niveau 3
   GDD §21.2.5).
   ─────────────────────────────────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════
   1. BARRE D'ONGLETS — GDD §21.2.2 (révision v0.26)
   ═══════════════════════════════════════════════════════════════════
   Pattern : queue leu leu + scroll horizontal natif quand débordement.
   Pas de bouton "⋯ Plus" (abandonné en v0.26 du GDD).
   Indicateurs de scroll : ombres dégradées sur les bords gauche/droite.
   ─────────────────────────────────────────────────────────────────── */

.dsh-tabs {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: stretch;
  gap: var(--tab-gap);
  height: var(--tabs-height-desktop);
  background: var(--tabs-bg);
  border-bottom: var(--tabs-border-bottom);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;          /* Firefox : barre fine */
  scrollbar-color: var(--color-border-subtle) transparent;
  scroll-behavior: smooth;
  /* Indicateurs de scroll en bordure (ombres dégradées) */
  background-image:
    /* Ombre gauche (visible si scrolled-from-left) */
    linear-gradient(to right, var(--tabs-bg), rgba(15, 24, 40, 0)) left center / 32px 100% no-repeat,
    /* Ombre droite (visible si scrolled-to-right pas atteint) */
    linear-gradient(to left,  var(--tabs-bg), rgba(15, 24, 40, 0)) right center / 32px 100% no-repeat,
    /* Indicateur de bord gauche (apparait quand on scroll) */
    linear-gradient(to right, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0)) left center / 16px 100% no-repeat,
    linear-gradient(to left,  rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0)) right center / 16px 100% no-repeat;
  background-attachment: local, local, scroll, scroll;
  padding: 0 var(--space-1);
}
.dsh-tabs::-webkit-scrollbar { height: 4px; }
.dsh-tabs::-webkit-scrollbar-track { background: transparent; }
.dsh-tabs::-webkit-scrollbar-thumb {
  background: var(--color-border-subtle);
  border-radius: var(--radius-pill);
}

.dsh-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--tab-padding-x);
  height: 100%;
  flex-shrink: 0;
  white-space: nowrap;
  background: transparent;
  border: none;
  border-bottom: var(--tab-active-indicator-thickness) solid transparent;
  color: var(--tab-inactive-color);
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  transition: color var(--anim-fast), border-color var(--anim-fast),
              background var(--anim-fast);
  user-select: none;
}
.dsh-tab:hover {
  color: var(--color-text-primary);
  background: var(--tab-hover-bg);
}
.dsh-tab.is-active {
  color: var(--tab-active-color);
  border-bottom-color: var(--tab-active-color);
}
.dsh-tab .dsh-tab-icon {
  font-size: var(--text-body-lg);
  line-height: 1;
}

/* ═══ v0.0.7 : LA PASTILLE DE CHANTIER SUR L'ONGLET 🚀 ═══════════════
   (demande de Loryan : « ajouter quelque chose ici pour signifier
   qu'une construction est en cours »)
   Un point ambre qui respire, posé sur le coin de l'icône — le même
   langage que les badges numériques des autres onglets, sans le chiffre :
   la question est « quelque chose se construit-il ? », pas « combien ? ».
   La pastille s'ancre au BOUTON, pas à l'icône, pour rester posée au même
   endroit dans la barre horizontale ET la barre latérale — d'où le
   `position:relative` posé ici sur l'onglet marqué (les badges numériques,
   eux, vivent DANS le flux, ils n'en avaient pas besoin). */
.dsh-tab.a-chantier { position: relative; }
.dsh-tab.a-chantier::after {
  content: ''; position: absolute; top: 4px; right: 6px;
  width: 7px; height: 7px; border-radius: 50%;
  background: #fbbf24; box-shadow: 0 0 6px rgba(251,191,36,.8);
  animation: chantier-pulse 2s ease-in-out infinite;
  pointer-events: none;
}
@keyframes chantier-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: .45; transform: scale(.8); }
}

.dsh-tab .dsh-tab-label {
  font-size: var(--text-small);
  line-height: 1;
}
/* Badge optionnel sur un onglet (ex: nb messages non-lus) */
.dsh-tab .dsh-tab-badge {
  display: inline-block;
  min-width: 18px;
  padding: 2px 6px;
  border-radius: var(--radius-pill);
  background: var(--color-error-fg);
  color: #fff;
  font-size: var(--text-tiny);
  font-weight: var(--font-weight-semibold);
  line-height: 1.2;
  text-align: center;
}

/* Mobile : hauteur tactile + icônes seules sous 360 px */
@media (max-width: 700px) {
  .dsh-tabs { height: var(--tabs-height-mobile); }
}
@media (max-width: 360px) {
  .dsh-tab .dsh-tab-label { display: none; }
  .dsh-tab { padding: 0 var(--space-3); }
}

/* Indicateur de niveau de navigation GDD §21.2.5 (Q3 v0.124 mixte) :
   data-attributes pour faciliter le debug et la doc */
[data-nav-level="1"] { /* Persistant : topbar, panneau VMs */ }
[data-nav-level="2"] { /* Vues principales : .dsh-tabs */ }
[data-nav-level="3"] { /* Sous-vues : onglets internes */ }
[data-nav-level="4"] { /* Détails : modales, bottom sheets */ }

/* ═══════════════════════════════════════════════════════════════════
   v1.01 — VARIANTE SIDEBAR VERTICALE GAUCHE
   ═══════════════════════════════════════════════════════════════════
   Décision produit v1.01 : la navigation principale .dsh-tabs (qui était
   horizontale en haut sous la topbar depuis v0.124) bascule en sidebar
   verticale FIXE à gauche. Raisons :
     - Sur écran large, la barre horizontale faisait défiler une partie
       des onglets en queue (les derniers — Notifs, Profil — devenaient
       invisibles).
     - L'écran est plus large que haut (16:9, 16:10) — donc une nav
       verticale "mange" proportionnellement moins de surface utile.
     - Pattern type "rail" connu (Discord, VSCode, etc.) : icônes seules,
       label en tooltip au survol.

   La classe générique .dsh-tabs (barre horizontale) RESTE valide pour
   les sous-onglets internes (niveau 3 GDD §21.2.5 : Marché, Factions IA,
   Social, etc.). Seule la barre principale (#dsh-tabs-main) reçoit le
   modifier .is-sidebar.

   Structure HTML attendue (inchangée) :
     <nav class="dsh-tabs is-sidebar" id="dsh-tabs-main">
       <button class="dsh-tab" data-view="dashboard">
         <span class="dsh-tab-icon">🏠</span>
         <span class="dsh-tab-label">Dashboard</span>
       </button>
       ...
     </nav>

   En sidebar : .dsh-tab-label est masqué visuellement mais utilisé
   comme tooltip via title (assigné en JS au boot).
   ─────────────────────────────────────────────────────────────────── */

/* Interrupteur railSansFlash : sur un univers jamais visité, les onglets
   restent invisibles jusqu'à ce qu'applyUniverseUI() ait résolu la barre
   (classe posée par le script qui suit le <nav> dans index.html). La barre,
   elle, garde sa place et son fond : rien ne saute à l'arrivée. */
#dsh-tabs-main.rail-en-attente > .dsh-tab { visibility: hidden; }

/* Conteneur sidebar : fixé à gauche, sous la topbar, plein-hauteur */
.dsh-tabs.is-sidebar {
  /* Position : fixée à gauche, sous le bandeau de ressources.
     v1.33 : avant, top valait 44px (topbar seule), ce qui plaçait la
     sidebar SOUS la topbar mais DERRIÈRE le bandeau de ressources
     (#resbar) — son premier onglet (Univers 🪐) se retrouvait masqué.
     On utilise désormais --resbar-bottom (calculé au runtime par
     core/layout.js = hauteur topbar + resbar), qui suit la hauteur
     réelle et variable du bandeau (flex-wrap selon la largeur d'écran).
     Fallback à 88px si la variable n'est pas encore définie. */
  position: fixed;
  left: 0;
  top: var(--resbar-bottom, 88px);   /* sous topbar + bandeau de ressources */
  bottom: 0;
  width: var(--sidebar-width);
  height: auto;                      /* override .dsh-tabs height */

  /* Orientation : verticale (vs horizontale par défaut .dsh-tabs) */
  flex-direction: column;
  align-items: stretch;
  gap: var(--sidebar-gap);

  /* Look : fond sombre opaque, bordure à droite uniquement */
  background: var(--sidebar-bg);
  background-image: none;            /* enlève les ombres horizontales de .dsh-tabs */
  border-bottom: none;
  border-right: var(--sidebar-border);
  padding: var(--space-2) 0;

  /* Scroll vertical natif si la liste déborde (rare mais possible) */
  overflow-x: hidden;
  overflow-y: auto;

  /* Au-dessus du contenu, sous la topbar */
  z-index: 150;
}

/* Cache la scrollbar webkit (Chromium/Edge/Safari) sur la sidebar.
   Pour Firefox, on hérite du scrollbar-width: thin du parent. */
.dsh-tabs.is-sidebar::-webkit-scrollbar {
  width: 4px;
  height: 0;
}

/* Boutons d'onglet en mode sidebar : icône seule, taille tactile fixe */
.dsh-tabs.is-sidebar .dsh-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: var(--sidebar-item-size);
  padding: 0;
  /* On retire l'indicateur d'onglet actif côté bas (.dsh-tab classique)
     pour le mettre côté gauche (barre verticale). */
  border-bottom: none;
  border-left: 3px solid transparent;
  position: relative;                /* pour positionner le tooltip */
}

/* Indicateur d'onglet actif en sidebar : barre verticale à gauche */
.dsh-tabs.is-sidebar .dsh-tab.is-active {
  border-left-color: var(--tab-active-color);
  background: var(--tab-hover-bg);
}

/* Icône agrandie en sidebar (label masqué, donc on a la place) */
.dsh-tabs.is-sidebar .dsh-tab .dsh-tab-icon {
  font-size: var(--sidebar-icon-size);
  line-height: 1;
}
/* ─── Interrupteur barreIconesGrandes — les icônes de la barre de gauche ×1,5 ───
   Loryan : « je garde les logos actuels, je veux juste que tu les grossisses
   de 1,5, et je parle de tous les logos de la barre latérale gauche ».
   Trois sortes d'icônes, chacune à 1,5 fois SA taille : les images
   (`.dsh-tab .dsh-tab-icon img`, dans le bloc des icônes dessinées), les
   deux émojis (🔔, 📖 : la règle juste au-dessus) et l'emblème de la
   Diplomatie (dessiné à 24 px par assemblee.js).
   ⚠️ PAS DANS LE BLOC DES IMAGES : test/icones-rail.test.js y interdit toute
   règle qui toucherait une case sans image — et celle des émojis le fait exprès.
   ⚠️ Les trois facteurs sont relus par test/barre-icones-grandes.test.js :
   changer une taille de base sans l'autre romprait le ×1,5 sans un bruit. */
body.f-barreIconesGrandes .dsh-tabs.is-sidebar .dsh-tab .dsh-tab-icon img { width: calc(1.85rem * 1.5); height: calc(1.85rem * 1.5); }
body.f-barreIconesGrandes .dsh-tabs.is-sidebar .dsh-tab .dsh-tab-icon { font-size: calc(var(--sidebar-icon-size) * 1.5); }
body.f-barreIconesGrandes .dsh-tabs.is-sidebar #btn-assemblee-embleme svg { width: calc(24px * 1.5); height: auto; }
/* Au téléphone, le bouton fait 40 px de haut au lieu de 44 : à 39 px d'icône,
   deux icônes voisines n'étaient plus qu'à 5 px l'une de l'autre (mesuré à
   390 px). Il reprend la hauteur du bureau, soit 9 px d'écart comme là-bas. */
@media (max-width: 700px) { body.f-barreIconesGrandes .dsh-tabs.is-sidebar .dsh-tab { height: 44px; } }
/* ─── Interrupteur barreIconesEspacees — plus d'air entre les logos de la barre ───
   Loryan, capture de la barre à l'appui : « Mets un peu plus d'espacements
   entre les logos ». Le bouton passe à 50 px de haut, au bureau comme au
   téléphone : avec les icônes ×1,5 (39 px), l'écart entre deux logos passe de
   9 à 15 px, et la zone de clic de chaque bouton grandit d'autant.
   ⚠️ APRÈS le bloc barreIconesGrandes : sa règle du téléphone (44 px) a le
   même poids de sélecteur, c'est l'ordre du fichier qui départage. */
body.f-barreIconesEspacees .dsh-tabs.is-sidebar .dsh-tab { height: 50px; }

/* Le label est masqué visuellement en sidebar (icônes seules)
   On le laisse dans le DOM pour qu'il serve de title/tooltip et reste
   lu par les lecteurs d'écran. */
.dsh-tabs.is-sidebar .dsh-tab .dsh-tab-label {
  /* Masquage accessible (visually-hidden pattern) — l'élément reste
     dans le flux d'accessibilité mais n'occupe aucune place visuelle. */
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Badge sur un onglet en mode sidebar : pastille en haut-droite de l'icône */
.dsh-tabs.is-sidebar .dsh-tab .dsh-tab-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 16px;
  padding: 0 4px;
  font-size: var(--text-tiny);
  line-height: 1.4;
}

/* ── Séparateur entre les onglets de navigation et les raccourcis ─── */
.dsh-sidebar-sep {
  height: 1px;
  background: var(--color-border-subtle);
  margin: var(--space-2) var(--space-2);
  flex-shrink: 0;
}
/* v1.20 : les blocs CSS qui stylaient les 9 raccourcis dans la sidebar
   (Esc / 1-5 / V / N / /) ont été retirés en même temps que le module
   keyboard.js et le point d'ancrage HTML #sidebar-shortcuts-mount.
   Anciennes règles supprimées :
     - .dsh-sidebar-sep.is-pushed-down  (séparateur "margin-top: auto")
     - .dsh-tabs.is-sidebar .dsh-tab[data-shortcut] ...
     - .dsh-tabs.is-sidebar .dsh-tab[data-shortcut]::after
   Si on réintroduit une vraie UI mobile plus tard, ces règles seront
   réécrites depuis zéro (probablement sous un autre nom plus parlant). */

/* ── Décalage du contenu principal pour libérer la zone de la sidebar ─
   Toutes les vues (#view-galaxy, #view-ship, etc. + .dsh-view) sont
   décalées de var(--sidebar-width) vers la droite.

   v1.01 : on RÉDUIT AUSSI le padding-top des vues. Avant v1.01 elles
   avaient padding-top:88px (inline-style) = 44px topbar + 44px barre
   d'onglets horizontale. La barre d'onglets a disparu (= sidebar), donc
   44px de padding-top suffisent. On override via !important parce que
   les vues utilisent du style inline ("style=padding-top:88px") qui est
   plus spécifique qu'une règle CSS normale. */
body.has-sidebar #view-galaxy,
body.has-sidebar #view-ship,
body.has-sidebar #view-research,
body.has-sidebar .dsh-view {
  padding-left: var(--sidebar-width);
  /* v1.39 : avant, padding-top valait 52px (topbar seule) — le bandeau de
     ressources (#resbar) n'était PAS pris en compte, donc le titre des vues
     passait DERRIÈRE le bandeau sur écran étroit (chevauchement signalé sur
     la Rampe de Lancement). On utilise --total-header-h, calculé au runtime
     par core/layout.js = hauteur réelle topbar + bandeau de ressources + marge.
     Cette règle a une spécificité plus forte que celle plus bas dans le fichier,
     d'où la nécessité de la corriger ici aussi. +16px de marge de confort. */
  padding-top: calc(var(--total-header-h, 96px) + 16px) !important;
}

/* Sur mobile (< 700px) : sidebar réduite, ou pliée selon le besoin.
   Pour cette v1.01 on garde la sidebar même sur mobile mais on la
   compacte un peu (44px au lieu de 60px). */
@media (max-width: 700px) {
  :root {
    --sidebar-width: 48px;
    --sidebar-item-size: 40px;
  }
}

/* v1.20 : règle "body.has-sidebar .dsh-shortcuts-bar { display: none }"
   supprimée. Avant : la barre flottante existait dans le DOM mais était
   cachée. Maintenant : keyboard.js n'est plus chargé donc la barre
   n'existe plus du tout, plus besoin de la masquer. */

/* ═══════════════════════════════════════════════════════════════════
   2. FILES D'ATTENTE — GDD §21.5 (composant générique)
   ═══════════════════════════════════════════════════════════════════
   Pattern utilisable pour les files de bâtiments, recherche, militaire,
   contrats, etc. Item = ligne avec icône, label, jauge progression,
   temps restant, action. Disponible pour les futures vues (Phase 2-4).
   ─────────────────────────────────────────────────────────────────── */

.dsh-queue {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.dsh-queue-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg-hover);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  font-size: var(--text-small);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary);
}
.dsh-queue-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--color-bg-card);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  transition: border-color var(--anim-fast);
}
.dsh-queue-item:hover { border-color: var(--color-border-strong); }
.dsh-queue-item.is-active {
  border-color: var(--color-info-fg);
  background: var(--color-info-bg);
}
.dsh-queue-item.is-done {
  border-color: var(--color-success-border);
  background: var(--color-success-bg);
}
.dsh-queue-icon {
  font-size: var(--text-h3);
  flex-shrink: 0;
}
.dsh-queue-body { flex: 1; min-width: 0; }
.dsh-queue-title {
  font-size: var(--text-body);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-primary);
}
.dsh-queue-meta {
  font-size: var(--text-small);
  color: var(--color-text-secondary);
  margin-top: var(--space-1);
}
.dsh-queue-progress {
  margin-top: var(--space-2);
  height: 4px;
  background: var(--color-border-subtle);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.dsh-queue-progress-bar {
  height: 100%;
  background: var(--color-info-fg);
  transition: width var(--anim-normal);
}
.dsh-queue-item.is-active .dsh-queue-progress-bar { background: var(--color-success-fg); }
.dsh-queue-empty {
  text-align: center;
  padding: var(--space-6);
  color: var(--color-text-tertiary);
  font-size: var(--text-small);
  font-style: italic;
}

/* ═══════════════════════════════════════════════════════════════════
   3. CHAMPS DE SAISIE — GDD §21.5.4 (sous-set v0.124 Q5=b)
   ═══════════════════════════════════════════════════════════════════
   Sous-set retenu : .dsh-input (texte simple) + .dsh-toggle (switch).
   Stepper / dropdown / slider reportés v0.128.
   ─────────────────────────────────────────────────────────────────── */

.dsh-input-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.dsh-input-label {
  font-size: var(--text-small);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary);
}
.dsh-input {
  height: var(--btn-height-standard);
  padding: 0 var(--space-3);
  background: var(--color-bg-main);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  color: var(--color-text-primary);
  font-family: var(--font-body);
  font-size: var(--text-body);
  transition: border-color var(--anim-fast), background var(--anim-fast);
}
.dsh-input:hover { border-color: var(--color-border-strong); }
.dsh-input:focus {
  outline: none;
  border-color: var(--color-info-fg);
  background: var(--color-bg-card);
}
.dsh-input::placeholder { color: var(--color-text-tertiary); }
.dsh-input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background: var(--color-bg-hover);
}
.dsh-input.is-error {
  border-color: var(--color-error-fg);
}
.dsh-input-hint {
  font-size: var(--text-tiny);
  color: var(--color-text-tertiary);
}
.dsh-input-hint.is-error { color: var(--color-error-fg); }
.dsh-input-counter {
  font-size: var(--text-tiny);
  color: var(--color-text-tertiary);
  text-align: right;
}

/* Toggle switch on/off */
.dsh-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  user-select: none;
}
.dsh-toggle-input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.dsh-toggle-track {
  position: relative;
  width: 36px;
  height: 20px;
  background: var(--color-border-subtle);
  border-radius: var(--radius-pill);
  transition: background var(--anim-fast);
}
.dsh-toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  background: var(--color-text-primary);
  border-radius: 50%;
  transition: transform var(--anim-fast);
}
.dsh-toggle-input:checked + .dsh-toggle-track {
  background: var(--color-info-fg);
}
.dsh-toggle-input:checked + .dsh-toggle-track .dsh-toggle-thumb {
  transform: translateX(16px);
}
.dsh-toggle-label {
  font-size: var(--text-body);
  color: var(--color-text-primary);
}
.dsh-toggle-input:disabled + .dsh-toggle-track {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ═══════════════════════════════════════════════════════════════════
   v0.124 — VUES PRINCIPALES (DASHBOARD + COQUILLES)
   ═══════════════════════════════════════════════════════════════════
   Conteneur de vue : prend toute la zone sous la topbar + tabs.
   Les vues existantes (.view) conservent leur layout actuel. Les
   nouvelles vues v0.124 utilisent .dsh-view qui ajoute du padding
   et un max-width. Cohabitation préservée.
   ─────────────────────────────────────────────────────────────────── */

.dsh-view {
  display: none;          /* Masqué par défaut, switchView() le rend visible */
  padding: var(--space-6) var(--space-screen-desktop);
  overflow-y: auto;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
}
.dsh-view.is-active { display: block; }
/* v2.18 : hauteur BORNÉE à l'écran. Sans elle, `overflow-y: auto` ci-dessus ne
   sert à rien : la vue grandit à la hauteur de son contenu, et comme
   `body { overflow: hidden }` (l.636) la page ne scrolle pas non plus, tout ce
   qui dépasse est simplement COUPÉ et inatteignable. Ça ne se voyait pas tant
   que les vues tenaient dans l'écran ; ça bloquait l'Arbre de progression
   (~3 200 px) et la vue Profil (~1 500 px). Avec une hauteur bornée, les vues
   courtes sont inchangées (pas d'ascenseur si le contenu tient) et les vues
   longues défilent normalement. box-sizing: border-box est global (l.634), donc
   le padding-top réservé à l'en-tête est bien compris dans les 100vh. */
.dsh-view.is-active { height: 100vh; }
/* Interrupteur vueJusquAuBas : au téléphone, `100vh` est l'écran barres
   REPLIÉES — plus haut que ce qu'on voit quand elles sont affichées : le bas
   de la vue passait sous la barre du navigateur. `100dvh` suit l'écran réel
   (même règle que les `.view`, plus haut). */
body.f-vueJusquAuBas .dsh-view.is-active { height: 100dvh; }

.dsh-view-header {
  margin-bottom: var(--space-6);
}
.dsh-view-title {
  font-size: var(--text-h1);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-heading);
  color: var(--color-text-primary);
  margin-bottom: var(--space-2);
}
.dsh-view-subtitle {
  font-size: var(--text-body);
  color: var(--color-text-secondary);
}

.dsh-view-section {
  margin-bottom: var(--space-8);
}
.dsh-view-section-title {
  font-size: var(--text-h2);
  font-weight: var(--font-weight-semibold);
  margin-bottom: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Grille de cartes interne aux vues (Marché, Factions IA, Combat) */
.dsh-view-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--space-3);
}

@media (max-width: 700px) {
  .dsh-view {
    padding: var(--space-4) var(--space-screen-mobile);
  }
}

/* Mobile : pas de margin-top sur .dsh-view car la barre tabs est sticky.
   On laisse le defaut. */

/* ═══════════════════════════════════════════════════════════════════
   v0.125 — COMPOSANTS RESPONSIVE GDD §21.6 (Bloc 9 — 5/8)
   ═══════════════════════════════════════════════════════════════════
   3 nouveaux systèmes posés en cohabitation :
     1. .dsh-drawer — drawer flottant pour mobile (§21.6.3)
     2. .dsh-table — tableau qui bascule en cartes sous 700 px (§21.6.5)
     3. .dsh-view-grid responsive — grille adaptative 4→3→2→1 col (§21.6.4)
     4. Sections Dashboard empilées sur mobile (§21.6.7)
     5. Helpers gestes tactiles (.dsh-modal swipe down, pull-to-refresh)

   Le seuil critique est 700 px (déjà encodé dans le MVP v0.20). Les 5
   breakpoints GDD §21.6.1 (480 / 700 / 1024 / 1440) sont utilisés pour
   les transitions précises.
   ─────────────────────────────────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════
   1. .dsh-drawer — Drawer flottant GDD §21.6.3
   ═══════════════════════════════════════════════════════════════════
   Composant générique pour les panneaux qui glissent depuis la droite
   sur mobile. Utilisable pour le panneau VMs en remplacement de la
   logique mobile-mode actuelle (en cohabitation pour v0.125, migration
   plus tard si besoin).

   Structure HTML attendue :
     <button class="dsh-drawer-trigger">🚀</button>
     <div class="dsh-drawer" id="my-drawer">
       <header class="dsh-drawer-header">...</header>
       <div class="dsh-drawer-body">...</div>
     </div>
   ─────────────────────────────────────────────────────────────────── */

/* Bouton flottant pour ouvrir le drawer (visible sur mobile seulement) */
.dsh-drawer-trigger {
  position: fixed;
  bottom: var(--space-4);
  right: var(--space-4);
  width: var(--drawer-handle-size);
  height: var(--drawer-handle-size);
  border-radius: var(--radius-pill);
  background: var(--color-info-fg);
  color: #fff;
  border: none;
  font-size: 24px;
  cursor: pointer;
  box-shadow: var(--drawer-handle-shadow);
  z-index: var(--z-sticky);
  /* Caché sur desktop par défaut. Affiché par media query. */
  display: none;
  transition: transform var(--anim-fast), background var(--anim-fast);
}
.dsh-drawer-trigger:hover {
  background: var(--color-info-fg);
  transform: scale(1.05);
}
.dsh-drawer-trigger:active {
  transform: scale(0.95);
}

/* Panneau drawer (caché par défaut, slide-in à droite) */
.dsh-drawer {
  position: fixed;
  top: 0;
  right: 0;
  width: var(--drawer-width-mobile);
  height: 100vh;
  background: var(--drawer-bg);
  box-shadow: var(--drawer-shadow);
  z-index: var(--z-modal);
  /* Caché par défaut : translaté hors écran à droite */
  transform: translateX(100%);
  transition: transform var(--drawer-anim-duration) ease-out;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}
.dsh-drawer.is-open {
  transform: translateX(0);
}

.dsh-drawer-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-border-subtle);
  background: rgba(15, 24, 40, 0.95);
  flex-shrink: 0;
}

.dsh-drawer-title {
  font-size: var(--text-h3);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}

.dsh-drawer-close {
  background: transparent;
  border: 1px solid var(--color-border-strong);
  color: var(--color-text-secondary);
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-size: var(--text-body-lg);
  line-height: 1;
}
.dsh-drawer-close:hover {
  color: var(--color-text-primary);
  border-color: var(--color-text-secondary);
}

.dsh-drawer-body {
  padding: var(--space-4);
  flex: 1;
  overflow-y: auto;
}

/* Backdrop derrière le drawer (clic = fermeture) */
.dsh-drawer-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(8, 12, 20, 0.6);
  z-index: calc(var(--z-modal) - 1);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--drawer-anim-duration), visibility var(--drawer-anim-duration);
}
.dsh-drawer-backdrop.is-open {
  opacity: 1;
  visibility: visible;
}

/* Sur desktop : le drawer trigger est masqué (le panneau VMs est visible) */
@media (min-width: 700px) {
  .dsh-drawer-trigger { display: none !important; }
  .dsh-drawer        { display: none; }
  .dsh-drawer-backdrop { display: none; }
}

/* Sur mobile : le drawer trigger devient visible */
@media (max-width: 699px) {
  .dsh-drawer-trigger { display: flex; align-items: center; justify-content: center; }
}

/* ═══════════════════════════════════════════════════════════════════
   2. .dsh-table — Tableau responsive GDD §21.6.5
   ═══════════════════════════════════════════════════════════════════
   Bascule automatique entre :
     - desktop ≥ 700 px : tableau classique avec colonnes, header sticky
     - mobile < 700 px : chaque ligne devient une carte verticale

   Structure HTML utilisée :
     <table class="dsh-table">
       <thead><tr><th>Col1</th><th>Col2</th>...</tr></thead>
       <tbody>
         <tr><td data-label="Col1">val1</td><td data-label="Col2">val2</td></tr>
       </tbody>
     </table>

   Le `data-label` sur chaque <td> est utilisé en mobile pour afficher
   le libellé de la colonne devant la valeur (pattern bien connu pour
   "tableau → cartes responsives").
   ─────────────────────────────────────────────────────────────────── */

.dsh-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-body);
  color: var(--color-text-primary);
}

.dsh-table thead {
  background: var(--color-bg-card);
  position: sticky;
  top: 0;
  z-index: 1;
}

.dsh-table th {
  text-align: left;
  padding: 0 var(--space-3);
  height: var(--table-header-height);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
  font-size: var(--text-small);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-bottom: 1px solid var(--table-border-color);
}

.dsh-table td {
  padding: var(--space-3);
  height: var(--table-row-height);
  border-bottom: 1px solid var(--table-border-color);
  vertical-align: middle;
}

.dsh-table tbody tr {
  transition: background var(--anim-fast);
}
.dsh-table tbody tr:hover {
  background: var(--table-row-hover-bg);
}

/* Mode mobile : chaque ligne devient une carte verticale */
@media (max-width: 699px) {
  .dsh-table thead {
    /* Masquer le header (les data-label des cellules le remplacent) */
    display: none;
  }
  .dsh-table tbody tr {
    /* Chaque ligne devient une carte */
    display: block;
    background: var(--color-bg-card);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-3);
    padding: var(--space-3);
  }
  .dsh-table td {
    /* Les cellules s'empilent verticalement */
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--color-border-subtle);
    height: auto;
  }
  .dsh-table td:last-child {
    border-bottom: none;
  }
  .dsh-table td::before {
    /* Le libellé de la colonne apparaît à gauche en mobile */
    content: attr(data-label);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text-secondary);
    font-size: var(--text-small);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-right: var(--space-3);
    flex-shrink: 0;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   3. .dsh-view-grid — Grille adaptative GDD §21.6.4
   ═══════════════════════════════════════════════════════════════════
   Cette classe existe déjà depuis v0.124 (cf. composants génériques).
   v0.125 ajoute le comportement responsive précis 4 → 3 → 2 → 1 cols.

   Largeur minimale d'une carte : 280 px (en dessous, les contenus
   ne tiennent plus). C'est ce minimum qui détermine le nombre de
   colonnes possibles. */

/* Override v0.125 : grille fluide stricte GDD §21.6.4 */
.dsh-view-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--card-min-width), 1fr));
  gap: var(--grid-gap);
}

/* ≥ 1440 px : 4 colonnes max (déjà géré par auto-fill avec min 280 px) */
/* 1024 - 1440 px : 3 colonnes */
@media (max-width: 1440px) {
  .dsh-view-grid { grid-template-columns: repeat(auto-fill, minmax(var(--card-min-width), 1fr)); }
}
/* 700 - 1024 px : 2 colonnes (cartes un peu plus étroites) */
@media (max-width: 1024px) {
  .dsh-view-grid { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
}
/* 480 - 700 px : 2 colonnes très étroites OU 1 selon contenu */
@media (max-width: 699px) {
  .dsh-view-grid { grid-template-columns: 1fr; }
}

/* ═══════════════════════════════════════════════════════════════════
   4. Sections Dashboard empilées sur mobile (GDD §21.6.7)
   ═══════════════════════════════════════════════════════════════════
   Sur desktop large (≥ 1024 px), le Dashboard pourra afficher Alertes
   pleine largeur en haut puis une grille 2 colonnes (VMs / Activité).
   Sur mobile, tout est empilé verticalement (Alertes → VMs → Activité).

   En v0.125, on pose la grille mais la version desktop sophistiquée
   sera enrichie en Phase 4 (le contenu des sections VMs et Activité
   est aujourd'hui placeholder). */

@media (min-width: 1024px) {
  /* Sur desktop large : Alertes pleine largeur, VMs et Activité en grille 2 cols */
  #dashboard-vms-section,
  #dashboard-activity-section {
    display: inline-block;
    width: calc(50% - var(--space-2));
    vertical-align: top;
  }
  #dashboard-vms-section { margin-right: var(--space-2); }
}

/* ═══════════════════════════════════════════════════════════════════
   5. Gestes tactiles GDD §21.5.9 (helpers CSS)
   ═══════════════════════════════════════════════════════════════════
   - Swipe down sur bottom sheets : géré côté JS (cf. features/responsive/
     gestures.js) avec animation CSS de transition.
   - Pull-to-refresh sur Dashboard : indicateur visuel CSS pour le
     pattern "tire vers le bas pour rafraîchir".
   ─────────────────────────────────────────────────────────────────── */

/* Indicateur pull-to-refresh (apparait quand l'utilisateur tire vers le bas) */
.dsh-pull-to-refresh {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 0;
  overflow: hidden;
  transition: height var(--anim-fast);
  color: var(--color-text-secondary);
  font-size: var(--text-small);
}
.dsh-pull-to-refresh.is-pulling {
  height: 48px;
}
.dsh-pull-to-refresh.is-refreshing {
  height: 48px;
  color: var(--color-info-fg);
}
.dsh-pull-to-refresh-icon {
  display: inline-block;
  margin-right: var(--space-2);
  transition: transform var(--anim-normal);
}
.dsh-pull-to-refresh.is-pulling .dsh-pull-to-refresh-icon {
  transform: rotate(180deg);
}
.dsh-pull-to-refresh.is-refreshing .dsh-pull-to-refresh-icon {
  animation: dshSpinRefresh 1s linear infinite;
}

@keyframes dshSpinRefresh {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Bottom sheets : transition de glissement vertical pour mobile */
@media (max-width: 699px) {
  .dsh-modal-backdrop {
    /* Sur mobile, la modale glisse depuis le bas plutôt que de fade */
    align-items: flex-end;
    padding: 0;
  }
  .dsh-modal {
    /* Bottom sheet : prend toute la largeur, 70-90% de la hauteur */
    width: 100%;
    max-width: 100%;
    max-height: 90vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    /* Animation glissement vertical depuis le bas */
    animation: dshBottomSheetSlideIn var(--anim-normal) ease-out;
  }
  /* Indicateur visuel du swipe down (petite barre en haut) */
  .dsh-modal::before {
    content: '';
    display: block;
    width: 40px;
    height: 4px;
    background: var(--color-border-strong);
    border-radius: var(--radius-pill);
    margin: var(--space-2) auto 0;
    cursor: grab;
  }
}

@keyframes dshBottomSheetSlideIn {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* ═══════════════════════════════════════════════════════════════════
   v0.126 — COMPOSANTS NOTIFICATIONS ET FEEDBACK GDD §21.7 (Bloc 9 — 6/8)
   ═══════════════════════════════════════════════════════════════════
   4 nouveaux systèmes posés en cohabitation :
     1. .dsh-toast — toasts GDD §21.7.2 (4 variantes sémantiques)
     2. .dsh-banner — bandeau diagnostic GDD §21.7.2
     3. .dsh-skeleton — squelette de chargement GDD §21.7.9
     4. .dsh-prefs — page Préférences DND GDD §21.7.8

   Le centre de notifications existant (.notif-* dans center.js) est
   conservé intact. v0.126 le complète avec les 6 catégories GDD et le
   bouton "→ Voir" pour ouvrir la vue associée.
   ─────────────────────────────────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════
   1. .dsh-toast — Toast GDD §21.7.2
   ═══════════════════════════════════════════════════════════════════
   Glisse en haut à droite (desktop) ou en haut centré (mobile),
   5s auto-dismiss, persiste si hover. 4 variantes sémantiques
   alignées sur §21.4.2 (info/success/warning/error). */

.dsh-toast-host {
  /* Conteneur unique des toasts (créé dynamiquement par notifications/toasts.js) */
  position: fixed;
  top: var(--space-4);
  right: var(--space-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--toast-gap);
  pointer-events: none;   /* Laisse passer les clics ailleurs */
}
@media (max-width: 699px) {
  .dsh-toast-host {
    /* Mobile : haut centré, pleine largeur (avec marges) */
    top: var(--space-3);
    left: var(--space-3);
    right: var(--space-3);
    align-items: center;
  }
}

.dsh-toast {
  width: var(--toast-width);
  max-width: var(--toast-max-width-mobile);
  padding: var(--toast-padding);
  background: var(--color-bg-card);
  border: 1px solid var(--color-border-strong);
  border-left: 4px solid var(--color-info-fg);   /* Variante info par défaut */
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  color: var(--color-text-primary);
  pointer-events: auto;
  animation: dshToastSlideIn var(--toast-anim) ease-out;
}
.dsh-toast.is-leaving {
  animation: dshToastSlideOut var(--toast-anim) ease-in forwards;
}
.dsh-toast-icon {
  font-size: var(--text-h3);
  flex-shrink: 0;
  line-height: 1;
}
.dsh-toast-body {
  flex: 1;
  font-size: var(--text-body);
  line-height: var(--line-height-body);
}
.dsh-toast-title {
  font-weight: var(--font-weight-semibold);
  margin-bottom: var(--space-1);
}
.dsh-toast-message {
  color: var(--color-text-secondary);
}
.dsh-toast-close {
  background: transparent;
  border: none;
  color: var(--color-text-tertiary);
  cursor: pointer;
  font-size: var(--text-body-lg);
  line-height: 1;
  padding: 0;
  margin-top: -2px;
}
.dsh-toast-close:hover { color: var(--color-text-primary); }

/* 4 variantes sémantiques GDD §21.4.2 */
.dsh-toast.is-success { border-left-color: var(--color-success-fg); }
.dsh-toast.is-warning { border-left-color: var(--color-warning-fg); }
.dsh-toast.is-error   { border-left-color: var(--color-error-fg); }
.dsh-toast.is-info    { border-left-color: var(--color-info-fg); }

@keyframes dshToastSlideIn {
  from { transform: translateX(120%); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}
@keyframes dshToastSlideOut {
  from { transform: translateX(0);    opacity: 1; }
  to   { transform: translateX(120%); opacity: 0; }
}
@media (max-width: 699px) {
  /* Mobile : glisse depuis le haut au lieu de la droite */
  @keyframes dshToastSlideIn {
    from { transform: translateY(-100%); opacity: 0; }
    to   { transform: translateY(0);     opacity: 1; }
  }
  @keyframes dshToastSlideOut {
    from { transform: translateY(0);     opacity: 1; }
    to   { transform: translateY(-100%); opacity: 0; }
  }
}

/* ═══════════════════════════════════════════════════════════════════
   2. .dsh-banner — Bandeau diagnostic GDD §21.7.2
   ═══════════════════════════════════════════════════════════════════
   Sous la topbar, permanent tant que la condition est vraie.
   Usage : "Pas d'ancrage", "Connecteur manquant", "Énergie insuffisante".
   Reprend l'existant #prod-hint (v0.5) mais formalise le pattern. */

.dsh-banner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--banner-height);
  padding: var(--space-2) var(--banner-padding-x);
  background: var(--color-warning-bg);
  border-bottom: 1px solid var(--color-warning-border);
  color: var(--color-text-primary);
  font-size: var(--text-body);
  cursor: pointer;
  transition: background var(--anim-fast);
}
.dsh-banner:hover { background: var(--color-warning-bg); filter: brightness(1.1); }
.dsh-banner-icon { font-size: var(--text-body-lg); flex-shrink: 0; }
.dsh-banner-text { flex: 1; }

/* Variantes sémantiques */
.dsh-banner.is-error {
  background: var(--color-error-bg);
  border-bottom-color: var(--color-error-border);
}
.dsh-banner.is-info {
  background: var(--color-info-bg);
  border-bottom-color: var(--color-info-border);
}
.dsh-banner.is-success {
  background: var(--color-success-bg);
  border-bottom-color: var(--color-success-border);
}

/* ═══════════════════════════════════════════════════════════════════
   3. .dsh-skeleton — Squelette de chargement GDD §21.7.9
   ═══════════════════════════════════════════════════════════════════
   Pattern "skeleton screen" pour les listes en cours de chargement.
   Évite le saut visuel "Loading…" → contenu. */

.dsh-skeleton {
  background: linear-gradient(
    90deg,
    var(--skeleton-bg) 0%,
    var(--skeleton-highlight) 50%,
    var(--skeleton-bg) 100%
  );
  background-size: 200% 100%;
  border-radius: var(--radius-sm);
  animation: dshSkeletonShimmer var(--skeleton-anim-duration) ease-in-out infinite;
}
.dsh-skeleton-line {
  height: 14px;
  margin-bottom: var(--space-2);
}
.dsh-skeleton-line.is-short  { width: 40%; }
.dsh-skeleton-line.is-medium { width: 70%; }
.dsh-skeleton-line.is-full   { width: 100%; }
.dsh-skeleton-card {
  height: 120px;
  margin-bottom: var(--space-3);
}

@keyframes dshSkeletonShimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

/* ═══════════════════════════════════════════════════════════════════
   4. .dsh-prefs — Page Préférences DND GDD §21.7.8
   ═══════════════════════════════════════════════════════════════════
   Structure utilisée par la vue Profil enrichie (renderProfileView) :
     - .dsh-prefs-section : un bloc par catégorie (Construction, Combat...)
     - .dsh-prefs-row : une ligne (label + 3 toggles in-game/push/email)
     - .dsh-prefs-dnd : la plage horaire Do Not Disturb */

.dsh-prefs {
  max-width: 800px;
  margin: 0 auto;
}
.dsh-prefs-section {
  background: var(--color-bg-card);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--dnd-section-gap);
}
.dsh-prefs-section-title {
  font-size: var(--text-h3);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
  margin-bottom: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.dsh-prefs-row {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  gap: var(--space-3);
  align-items: center;
  min-height: var(--dnd-row-height);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border-subtle);
}
.dsh-prefs-row:last-child { border-bottom: none; }

.dsh-prefs-row-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.dsh-prefs-row-name {
  font-weight: var(--font-weight-medium);
  color: var(--color-text-primary);
}
.dsh-prefs-row-desc {
  font-size: var(--text-small);
  color: var(--color-text-secondary);
}
.dsh-prefs-row-toggle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-size: var(--text-tiny);
  color: var(--color-text-tertiary);
}

/* Toggle visuel (compatible .dsh-toggle v0.124, override compact) */
.dsh-prefs-toggle {
  position: relative;
  display: inline-block;
  width: var(--toggle-width);
  height: var(--toggle-height);
  cursor: pointer;
}
.dsh-prefs-toggle input { display: none; }
.dsh-prefs-toggle-slider {
  position: absolute;
  inset: 0;
  background: var(--color-border-subtle);
  border-radius: var(--radius-pill);
  transition: background var(--anim-fast);
}
.dsh-prefs-toggle-slider::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: var(--toggle-knob-size);
  height: var(--toggle-knob-size);
  background: var(--color-text-primary);
  border-radius: 50%;
  transition: transform var(--anim-fast);
}
.dsh-prefs-toggle input:checked + .dsh-prefs-toggle-slider {
  background: var(--color-info-fg);
}
.dsh-prefs-toggle input:checked + .dsh-prefs-toggle-slider::after {
  transform: translateX(calc(var(--toggle-width) - var(--toggle-knob-size) - 6px));
}
.dsh-prefs-toggle.is-disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* DND : plage horaire */
.dsh-prefs-dnd-range {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.dsh-prefs-dnd-range label {
  font-size: var(--text-small);
  color: var(--color-text-secondary);
}
.dsh-prefs-dnd-range input[type="time"] {
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  color: var(--color-text-primary);
  font-family: var(--font-mono);
}

/* Badge "Bientôt disponible" pour Push / Email v0.126 coquille */
.dsh-prefs-coming-soon {
  display: inline-block;
  padding: 2px var(--space-2);
  background: var(--color-border-subtle);
  color: var(--color-text-tertiary);
  font-size: var(--text-tiny);
  border-radius: var(--radius-pill);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-left: var(--space-2);
}

/* Bouton "→ Voir" sur les notifications du centre (lien vers la vue) */
.notif-item-view-btn {
  background: transparent;
  border: 1px solid var(--color-info-fg);
  color: var(--color-info-fg);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-small);
  cursor: pointer;
  transition: background var(--anim-fast);
  white-space: nowrap;
}
.notif-item-view-btn:hover {
  background: var(--color-info-bg);
}

/* ═══════════════════════════════════════════════════════════════════
   v0.127 — COMPOSANTS ACCESSIBILITÉ GDD §21.8 (Bloc 9 — 7/8)
   ═══════════════════════════════════════════════════════════════════
   Pose les patterns d'accessibilité WCAG 2.1 AA :
     1. Bordure de focus visible (3:1 minimum)
     2. Skip link "Aller au contenu principal"
     3. Barre de raccourcis clavier cliquable (suggestion utilisateur)
     4. Gestion prefers-reduced-motion (désactivation animations sensibles)
     5. Helpers .visually-hidden et .sr-only

   Référence : GDD §21.8 (WCAG 2.1 AA, ratios contraste, navigation clavier,
   lecteurs d'écran, prefers-reduced-motion).
   ─────────────────────────────────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════
   1. BORDURE DE FOCUS VISIBLE — GDD §21.8.4
   ═══════════════════════════════════════════════════════════════════
   Tout élément focusable au clavier doit avoir une bordure visible
   et contrastée (3:1 minimum). On utilise :focus-visible qui ne
   s'affiche QUE pour la navigation au clavier — pas au clic souris
   (évite le bruit visuel des focus involontaires).

   :focus-visible est supporté par tous les navigateurs modernes
   (Chrome 86+, Firefox 85+, Safari 15.4+). Fallback :focus pour
   les anciens navigateurs.
   ─────────────────────────────────────────────────────────────────── */

/* Fallback large : tout élément focusé reçoit l'outline cyan */
*:focus-visible {
  outline: var(--focus-outline-width) solid var(--focus-outline-color);
  outline-offset: var(--focus-outline-offset);
  border-radius: var(--radius-sm);
}

/* Ne PAS appliquer pour les éléments qui définissent déjà leur focus
   visuel (boutons primaires avec ring custom, par exemple). On exclut
   les composants qui ont leur propre :focus défini. */

/* Pour les boutons système .dsh-btn-*, on conserve le focus-visible
   par-dessus le hover existant — c'est additif, pas conflictuel. */

/* ═══════════════════════════════════════════════════════════════════
   2. SKIP LINK "Aller au contenu principal" — GDD §21.8.4
   ═══════════════════════════════════════════════════════════════════
   Lien caché par défaut hors écran, visible UNIQUEMENT au focus.
   Permet aux utilisateurs au clavier ou au lecteur d'écran de
   bypasser la topbar et la navigation pour aller directement au
   contenu.

   Usage : <a class="dsh-skip-link" href="#main-content">Aller au contenu principal</a>
   en tout début de <body>.
   ─────────────────────────────────────────────────────────────────── */

.dsh-skip-link {
  position: absolute;
  top: -9999px;        /* Caché hors écran par défaut */
  left: var(--space-3);
  z-index: 99999;      /* Au-dessus de TOUT — modales y compris */
  background: var(--skip-link-bg);
  color: var(--skip-link-fg);
  padding: var(--skip-link-padding);
  border-radius: var(--radius-md);
  font-weight: var(--font-weight-semibold);
  text-decoration: none;
  transition: top 0s;  /* Pas de transition (apparition immédiate) */
}
.dsh-skip-link:focus,
.dsh-skip-link:focus-visible {
  top: var(--space-3); /* Visible en haut à gauche au focus */
}

/* ═══════════════════════════════════════════════════════════════════
   3. BARRE DE RACCOURCIS CLAVIER CLIQUABLE — RETIRÉE en v1.20
   ═══════════════════════════════════════════════════════════════════
   Toutes les règles CSS .dsh-shortcuts-bar / .dsh-shortcuts-btn /
   .dsh-shortcuts-sep et leur media query mobile ont été retirées.
   Le HTML d'ancrage et le module JS keyboard.js ont aussi été
   supprimés. La feature reviendra le jour où un vrai chantier d'UI
   mobile sera lancé (sous une nouvelle architecture, plus simple).
   ─────────────────────────────────────────────────────────────────── */


/* ═══════════════════════════════════════════════════════════════════
   4. PREFERS-REDUCED-MOTION — GDD §21.8.7
   ═══════════════════════════════════════════════════════════════════
   Désactivation totale des animations sensibles pour les joueurs
   souffrant de troubles vestibulaires.

   ANIMATIONS DÉSACTIVÉES :
     - dshGaugeCritical (v0.122, 0.8s clignotement jauge < 5%)
     - dshSpinRefresh (v0.125, rotation pull-to-refresh)
     - dshBottomSheetSlideIn (v0.125, glissement vertical)
     - dshToastSlideIn / dshToastSlideOut (v0.126)
     - dshSkeletonShimmer (v0.126, gradient de chargement)

   ANIMATIONS CONSERVÉES MAIS RALENTIES :
     - dshPericlitementJ1 (v0.122) : 1.2s → 2s
       (essentielle à l'information : indique J-1 imminent)

   TRANSITIONS COURTES CONSERVÉES :
     - Hover, focus, click : indispensables au feedback immédiat
       (§21.7.9). Réduites à 0.01ms (quasi-instantané) plutôt que
       désactivées totalement (évite les sauts visuels).
   ─────────────────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  /* Désactivation totale des animations en boucle */
  .is-critical-gauge,
  .dsh-toast,
  .dsh-toast.is-leaving,
  .dsh-skeleton,
  .dsh-pull-to-refresh.is-refreshing .dsh-pull-to-refresh-icon {
    animation: none !important;
  }

  /* Périclitement J-1 ralenti (essentiel à l'info, mais moins agressif) */
  .is-periclitement-j1 {
    animation-duration: var(--periclitement-j1-duration-reduced) !important;
  }

  /* Transitions ramenées à quasi-instantané */
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  /* Override pour conserver les transitions essentielles (focus, hover) */
  *:focus-visible {
    transition: outline-color 0.01ms !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   5. HELPERS .visually-hidden / .sr-only — Accessibilité ARIA
   ═══════════════════════════════════════════════════════════════════
   Classe utilitaire pour cacher visuellement du contenu tout en le
   gardant accessible aux lecteurs d'écran.

   Usage typique : libellés d'icônes décoratives, titres de sections
   qui n'ont pas besoin d'être visuellement présents.
   ─────────────────────────────────────────────────────────────────── */

.visually-hidden,
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ═══════════════════════════════════════════════════════════════════
   v0.128 — COMPOSANTS PRINCIPES ERGONOMIQUES FINAUX (Bloc 9 — 8/8 ✅)
   ═══════════════════════════════════════════════════════════════════
   DERNIÈRE BRIQUE DU BLOC 9. Pass de polissage général qui consolide
   tout le travail Bloc 9 (v0.121-127).

   3 nouveaux systèmes posés :
     1. .dsh-context-menu — menu contextuel long press 700ms (§21.5.9)
     2. .dsh-btn-spinner — feedback immédiat sur boutons en chargement (§21.7.9)
     3. Alias #prod-hint → .dsh-banner (migration ciblée Q3=b)

   Migration Q3 : le bandeau diagnostic historique #prod-hint reçoit
   les styles .dsh-banner sans toucher au HTML existant — cohabitation
   transparente, gain visuel immédiat.
   ─────────────────────────────────────────────────────────────────── */

/* ═══════════════════════════════════════════════════════════════════
   2. .dsh-btn-spinner — Feedback immédiat sur boutons (§21.7.9)
   ═══════════════════════════════════════════════════════════════════
   Pattern d'état transitoire : un bouton qui déclenche une action
   serveur (transit, attaque, achat marché) doit montrer un retour
   visuel pendant l'attente. Spinner inline + bouton désactivé.

   Usage HTML :
     <button class="dsh-btn dsh-btn-primary is-loading">
       <span class="dsh-btn-spinner" aria-hidden="true"></span>
       <span>Chargement…</span>
     </button>
   ─────────────────────────────────────────────────────────────────── */

.dsh-btn-spinner {
  display: inline-block;
  width: var(--btn-spinner-size);
  height: var(--btn-spinner-size);
  border: var(--btn-spinner-border-width) solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: dshBtnSpinnerRotate 0.8s linear infinite;
  vertical-align: text-bottom;
  margin-right: var(--space-2);
}

@keyframes dshBtnSpinnerRotate {
  to { transform: rotate(360deg); }
}

/* Quand un bouton est en is-loading, on le désactive visuellement */
.dsh-btn.is-loading,
button.is-loading {
  opacity: 0.7;
  cursor: progress;
  pointer-events: none;   /* Empêche les double-clics */
}

/* prefers-reduced-motion : le spinner reste mais sans rotation visible */
@media (prefers-reduced-motion: reduce) {
  .dsh-btn-spinner {
    animation-duration: 0.01ms !important;
    /* Garde la bordure rotative en position statique pour rester visible */
    border-top-color: var(--color-text-secondary);
  }
}

/* ═══════════════════════════════════════════════════════════════════
   3. Alias #prod-hint → .dsh-banner (migration ciblée Q3=b)
   ═══════════════════════════════════════════════════════════════════
   Le bandeau diagnostic historique #prod-hint (v0.5) reçoit les styles
   du composant .dsh-banner GDD §21.7.2 SANS modifier le HTML existant.
   Cohabitation transparente.

   Le HTML existant reste :
     <div id="prod-hint" onclick="onProdHintClick()">
       <span class="ph-ico">⚠</span>
       <span class="ph-msg">—</span>
       <span class="ph-arrow">→</span>
     </div>

   Les styles .dsh-banner s'appliquent ici quand on ajoute la classe
   `.dsh-banner` au #prod-hint. Pour l'instant on hérite via un selector
   cumulé qui aligne #prod-hint sur les tokens .dsh-banner sans casser
   l'existant.
   ─────────────────────────────────────────────────────────────────── */

/* Override v0.128 : aligne #prod-hint sur les tokens design system .dsh-banner */
#prod-hint {
  /* Hérite des tokens du composant .dsh-banner GDD §21.7.2 */
  min-height: var(--banner-height);
  padding: var(--space-2) var(--banner-padding-x);
  font-size: var(--text-body);
  /* Conserve les variantes .warn et .err existantes (cohabitation) */
}

/* ═══════════════════════════════════════════════════════════════════
   v0.129 — COMPOSANTS POLICE DISPLAY (Bloc 10 — 1/4, GDD §22.3)
   ═══════════════════════════════════════════════════════════════════
   Application CIBLÉE de Space Grotesk sur les grandes échelles
   cinématiques. Stratégie Q2=b : on N'APPLIQUE PAS Space Grotesk
   globalement sur le body (Inter reste la police d'interface).

   ZONES D'APPLICATION (cibles GDD §22.3) :
     1. .dsh-display          — utilitaire : applique Space Grotesk
     2. .dsh-display-tight    — version letter-spacing serré (logo)
     3. .dsh-display-wide     — version letter-spacing large (UPPERCASE)
     4. .dsh-display-title    — H1 cinématique (32 px+, bold)
     5. .role-modal-title     — création de compte (écran cinématique)
     6. .onboarding-step h1   — onboarding (modale plein écran)
     7. .dsh-modal.is-critical .dsh-modal-title — modales critiques

   Stratégie Q5=cohabitation : on ne touche PAS aux titres Orbitron
   historiques (header de jeu, etc.) — ils restent en place. La
   migration éventuelle sera décidée plus tard.
   ─────────────────────────────────────────────────────────────────── */

/* ── 1. Classe utilitaire .dsh-display ──────────────────────────────
   Usage : <h1 class="dsh-display">Deep Star Horizon</h1>
   Applique Space Grotesk + line-height titre + letter-spacing par défaut */
.dsh-display {
  font-family: var(--font-display);
  font-weight: var(--font-display-weight-medium);
  letter-spacing: var(--display-letter-spacing-normal);
  line-height: 1.2;
}

/* ── 2. Variantes letter-spacing ────────────────────────────────────
   .dsh-display-tight = pour logo et titres très impactants
   .dsh-display-wide  = pour UPPERCASE éparpillés (UI critiques, badges) */
.dsh-display-tight {
  font-family: var(--font-display);
  letter-spacing: var(--display-letter-spacing-tight);
}
.dsh-display-wide {
  font-family: var(--font-display);
  letter-spacing: var(--display-letter-spacing-wide);
}

/* ── 3. Variante titre cinématique ──────────────────────────────────
   Combine Space Grotesk + taille Display + bold + letter-spacing serré.
   À utiliser pour le titre principal du Dashboard, le logo, et les
   écrans héro. */
.dsh-display-title {
  font-family: var(--font-display);
  font-weight: var(--font-display-weight-bold);
  font-size: var(--text-display);  /* 32 px depuis v0.121 */
  letter-spacing: var(--display-letter-spacing-tight);
  line-height: 1.2;
  /* Pas d'italic, pas d'uppercase forcé (cf. §21.3.4 : casse sentence) */
}

/* ── 4. Modale création de compte / sélection de rôle ──────────────
   La page de création de compte est un écran cinématique GDD §22.3
   (« écran de connexion / création de compte »). On y applique
   Space Grotesk sur le titre principal. */
.role-modal-title {
  font-family: var(--font-display);
  font-weight: var(--font-display-weight-medium);
  letter-spacing: var(--display-letter-spacing-normal);
}

/* ── 5. Onboarding : titres des étapes (Bloc 8) ─────────────────────
   L'onboarding utilise des modales plein écran — c'est par définition
   un moment cinématique. On y applique Space Grotesk sur les titres. */
.onboarding-step h1,
.onboarding-step h2 {
  font-family: var(--font-display);
  font-weight: var(--font-display-weight-medium);
  letter-spacing: var(--display-letter-spacing-normal);
}

/* ── 6. Modales critiques (Périclitement H-1, etc.) ─────────────────
   Modales identifiées is-critical (v0.123). Titre en Space Grotesk
   bold pour l'impact dramatique. */
.dsh-modal.is-critical .dsh-modal-title,
.dsh-modal-backdrop.is-critical .dsh-modal-title {
  font-family: var(--font-display);
  font-weight: var(--font-display-weight-bold);
  letter-spacing: var(--display-letter-spacing-tight);
}

/* ── 7. Dashboard : titre de bienvenue (v0.124) ─────────────────────
   La carte "écran d'arrivée" du Dashboard mérite Space Grotesk pour
   l'effet "console d'opérateur" GDD §22.1. */
.dsh-dashboard-hero h1,
.dsh-dashboard-hero .hero-title {
  font-family: var(--font-display);
  font-weight: var(--font-display-weight-medium);
  letter-spacing: var(--display-letter-spacing-tight);
}

/* ── prefers-reduced-motion : pas d'impact ──────────────────────────
   Space Grotesk ne contient pas d'animation, rien à désactiver. */

/* ═══════════════════════════════════════════════════════════════════
   v0.116 — Tutoriel onboarding (Bloc 8)
   ═══════════════════════════════════════════════════════════════════
   Modale plein écran sombre + animations fade-in. Variante "critical"
   pour l'avertissement Périclitement (couleur orange/rouge avertissement).
*/

.onboarding-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(8, 12, 20, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-critical);  /* v0.121 (GDD §21.3.5) : critique = 60 */
  animation: onboardingFadeIn 0.25s ease-out;
  padding: 20px;
  overflow-y: auto;
}

@keyframes onboardingFadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.onboarding-modal {
  background: linear-gradient(135deg, #1a2538 0%, #0f1828 100%);
  border: 1px solid #2d4a6e;
  border-radius: 12px;
  padding: var(--space-8) var(--space-8);  /* v0.121 (GDD §21.3.4) : space-8 = 32px ≈ 40px-36px */
  max-width: 540px;
  width: 100%;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  text-align: center;
  animation: onboardingScaleIn 0.3s ease-out;
}

@keyframes onboardingScaleIn {
  from { transform: scale(0.92); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

.onboarding-modal-critical {
  border-color: #d97706;
  background: linear-gradient(135deg, #2a1a1a 0%, #1a0f0a 100%);
  box-shadow: 0 20px 60px rgba(217, 119, 6, 0.25);
}

.onboarding-icon {
  font-size: 56px;
  margin-bottom: var(--space-4);  /* v0.121 (GDD §21.3.4) : space-4 = 16px */
  line-height: 1;
}

.onboarding-icon-critical {
  filter: drop-shadow(0 0 8px rgba(245, 158, 11, 0.5));
}

.onboarding-title {
  font-size: 24px;
  font-weight: 700;
  color: #e2e8f0;
  margin: 0 0 16px 0;
  letter-spacing: 0.3px;
}

.onboarding-body {
  font-size: var(--text-body-lg);  /* v0.121 (GDD §21.3.2) : body-lg = 16px ≈ 15px */
  line-height: 1.6;
  color: #cbd5e1;
  margin: 0 0 14px 0;
  text-align: left;
}

.onboarding-body-warning {
  color: #fbbf24;
  background: rgba(217, 119, 6, 0.12);
  padding: 12px 14px;
  border-left: 3px solid #d97706;
  border-radius: 4px;
}

.onboarding-body-info {
  color: #93c5fd;
  background: rgba(59, 130, 246, 0.10);
  padding: 12px 14px;
  border-left: 3px solid #3b82f6;
  border-radius: 4px;
}

.onboarding-body-muted {
  color: #94a3b8;
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px ≈ 13px */
}

.onboarding-list {
  text-align: left;
  margin: 14px 0;
  padding-left: 0;
  list-style: none;
}

.onboarding-list li {
  padding: 6px 10px;
  font-size: 14px;
  color: #cbd5e1;
  border-bottom: 1px solid rgba(255,255,255,0.05);
}

.onboarding-list li:last-child { border-bottom: none; }

.onboarding-actions {
  display: flex;
  gap: var(--space-3);  /* v0.121 (GDD §21.3.4) : space-3 = 12px */
  justify-content: center;
  margin-top: 24px;
  flex-wrap: wrap;
}

.onboarding-btn {
  padding: 12px 24px;
  font-size: var(--text-body-lg);  /* v0.121 (GDD §21.3.2) : body-lg = 16px ≈ 15px */
  font-weight: var(--font-weight-semibold);  /* v0.121 (GDD §21.3.3) : 600 */
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: all 0.15s ease;
  min-width: 160px;
}

.onboarding-btn-primary {
  background: #3b82f6;
  color: #fff;
  border-color: var(--color-info-fg);  /* v0.122 (GDD §21.4.2) : info */
}

.onboarding-btn-primary:hover:not(:disabled) {
  background: var(--color-info-fg);  /* v0.122 (GDD §21.4.2) : info */
  transform: translateY(-1px);
}

.onboarding-btn-critical {
  background: #d97706;
  border-color: #b45309;
}

.onboarding-btn-critical:hover:not(:disabled) {
  background: #b45309;
}

.onboarding-btn-ghost {
  background: transparent;
  color: #94a3b8;
  border-color: #334155;
}

.onboarding-btn-ghost:hover:not(:disabled) {
  background: rgba(148, 163, 184, 0.1);
  color: #cbd5e1;
}

.onboarding-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ═══════════════════════════════════════════════════════════════════
   v0.117 — Panneau Quêtes onboarding (Bloc 8)
   ═══════════════════════════════════════════════════════════════════
   Modale panneau quêtes : icône topbar avec badge rouge si claimable +
   modale liste avec 3 états visuels (in_progress/claimable/claimed).
*/

/* Badge sur le bouton 📜 QUÊTES dans la topbar */
#quests-pill {
  cursor: pointer;
  user-select: none;
}

#quests-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  font-size: 0.7rem;
  font-weight: 700;
  border-radius: 10px;
  background: #475569;
  color: #cbd5e1;
  min-width: 18px;
  text-align: center;
}

/* Badge ROUGE quand au moins 1 quête est claimable (attire l'attention) */
#quests-badge.quests-badge-active {
  background: var(--color-error-fg);  /* v0.122 (GDD §21.4.2) : erreur */
  color: #fff;
  box-shadow: 0 0 8px rgba(220, 38, 38, 0.6);
  animation: questsBadgePulse 2s ease-in-out infinite;
}

@keyframes questsBadgePulse {
  0%, 100% { box-shadow: 0 0 8px rgba(220, 38, 38, 0.6); }
  50%      { box-shadow: 0 0 14px rgba(220, 38, 38, 0.9); }
}

/* Modale panneau quêtes */
.quests-panel-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(8, 12, 20, 0.85);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  z-index: var(--z-modal);  /* v0.121 (GDD §21.3.5) : modale = 40 */
  animation: questsPanelFadeIn 0.2s ease-out;
  padding: 50px 20px 20px 20px;
  overflow-y: auto;
}

@keyframes questsPanelFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.quests-panel {
  background: linear-gradient(135deg, #1a2538 0%, #0f1828 100%);
  border: 1px solid #2d4a6e;
  border-radius: 12px;
  padding: var(--space-6) var(--space-6);  /* v0.121 (GDD §21.3.4) : space-6 = 24px */
  max-width: 720px;
  width: 100%;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  animation: questsPanelScaleIn 0.25s ease-out;
}

@keyframes questsPanelScaleIn {
  from { transform: scale(0.96); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}

.quests-panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-2);  /* v0.121 (GDD §21.3.4) : space-2 = 8px */
}

.quests-panel-title {
  font-size: var(--text-h1);  /* v0.121 (GDD §21.3.2) : H1 = 24px ≈ 22px */
  font-weight: 700;
  color: #e2e8f0;
  margin: 0;
  letter-spacing: 0.3px;
}

.quests-panel-close {
  background: transparent;
  border: 1px solid #334155;
  color: #94a3b8;
  width: 30px;
  height: 30px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.quests-panel-close:hover {
  background: rgba(148, 163, 184, 0.1);
  color: #cbd5e1;
}

.quests-panel-subtitle {
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px ≈ 13px */
  color: #94a3b8;
  margin: 0 0 18px 0;
}

.quests-panel-subtitle strong {
  color: #cbd5e1;
}

.quests-panel-claimable-hint {
  color: #fbbf24;
  font-weight: var(--font-weight-semibold);  /* v0.121 (GDD §21.3.3) : 600 */
}

.quests-panel-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);  /* v0.121 (GDD §21.3.4) ≈ space-3 = 12px */
}

/* Une row de quête */
.quest-row {
  display: flex;
  gap: 16px;
  padding: var(--space-3) var(--space-4);  /* v0.121 (GDD §21.3.4) */
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 8px;
  transition: all 0.15s ease;
}

.quest-row-in_progress {
  border-color: rgba(148, 163, 184, 0.15);
}

.quest-row-claimable {
  border-color: #d97706;
  background: linear-gradient(135deg, rgba(217, 119, 6, 0.08) 0%, rgba(217, 119, 6, 0.02) 100%);
  box-shadow: 0 0 12px rgba(217, 119, 6, 0.15);
}

.quest-row-claimed {
  opacity: 0.5;
  border-color: rgba(148, 163, 184, 0.08);
}

.quest-icon {
  font-size: 32px;
  line-height: 1;
  flex-shrink: 0;
  width: 40px;
  text-align: center;
}

.quest-body {
  flex: 1;
  min-width: 0;
}

.quest-title {
  font-size: var(--text-body-lg);  /* v0.121 (GDD §21.3.2) : body-lg = 16px ≈ 15px */
  font-weight: var(--font-weight-semibold);  /* v0.121 (GDD §21.3.3) : 600 */
  color: #e2e8f0;
  margin-bottom: 4px;
}

.quest-check {
  color: #10b981;
  font-weight: 700;
  margin-right: 4px;
}

.quest-desc {
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px ≈ 13px */
  color: #94a3b8;
  margin-bottom: var(--space-2);  /* v0.121 (GDD §21.3.4) : space-2 = 8px */
  line-height: 1.4;
}

.quest-progress-wrap {
  display: flex;
  align-items: center;
  gap: var(--space-3);  /* v0.121 (GDD §21.3.4) ≈ space-3 = 12px */
  margin-bottom: var(--space-2);  /* v0.121 (GDD §21.3.4) : space-2 = 8px */
}

.quest-progress-bar {
  flex: 1;
  height: 6px;
  background: rgba(148, 163, 184, 0.15);
  border-radius: 3px;
  overflow: hidden;
}

.quest-progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%);
  transition: width 0.3s ease;
}

.quest-progress-text {
  font-size: var(--text-tiny);  /* v0.121 (GDD §21.3.2) : tiny = 11px */
  color: #94a3b8;
  font-family: var(--mono, monospace);
  min-width: 80px;
  text-align: right;
}

.quest-reward-line {
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */
  color: #cbd5e1;
  margin-bottom: var(--space-2);  /* v0.121 (GDD §21.3.4) : space-2 = 8px */
}

.quest-reward-label {
  color: #64748b;
  margin-right: 6px;
}

.quest-reward-value strong {
  color: #fbbf24;
}

.quest-status {
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */
  padding: 4px 10px;
  border-radius: 4px;
  display: inline-block;
  font-weight: var(--font-weight-medium);  /* v0.121 (GDD §21.3.3) : 500 */
}

.quest-status-pending {
  color: #64748b;
  background: rgba(100, 116, 139, 0.1);
}

.quest-status-claimed {
  color: #10b981;
  background: rgba(16, 185, 129, 0.1);
}

.quest-claim-btn {
  background: #d97706;
  color: #fff;
  border: 1px solid #b45309;
  border-radius: 6px;
  padding: 8px 16px;
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px ≈ 13px */
  font-weight: var(--font-weight-semibold);  /* v0.121 (GDD §21.3.3) : 600 */
  cursor: pointer;
  transition: all 0.15s ease;
}

.quest-claim-btn:hover:not(:disabled) {
  background: #b45309;
  transform: translateY(-1px);
}

.quest-claim-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ═══════════════════════════════════════════════════════════════════
   v0.119 — Section "Sortie d'onboarding" dans le panneau quêtes
   ═══════════════════════════════════════════════════════════════════
   Affiche jusqu'à 3 boutons selon l'état (canExitGrace, completionPack,
   pionnierPack). Encart visuel distinct des quêtes (cadre plus prononcé).
*/

.onboarding-exit-section {
  background: linear-gradient(135deg, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.02) 100%);
  border: 1px solid rgba(59, 130, 246, 0.3);
  border-radius: 8px;
  padding: var(--space-3) var(--space-4);  /* v0.121 (GDD §21.3.4) */
  margin-bottom: var(--space-4);  /* v0.121 (GDD §21.3.4) : space-4 = 16px */
}

.onboarding-exit-section-done {
  background: linear-gradient(135deg, rgba(16, 185, 129, 0.08) 0%, rgba(16, 185, 129, 0.02) 100%);
  border-color: rgba(16, 185, 129, 0.3);
}

.onboarding-exit-title {
  font-size: var(--text-body-lg);  /* v0.121 (GDD §21.3.2) : body-lg = 16px ≈ 15px */
  font-weight: var(--font-weight-semibold);  /* v0.121 (GDD §21.3.3) : 600 */
  color: #e2e8f0;
  margin-bottom: 6px;
}

.onboarding-exit-subtitle {
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */
  color: #94a3b8;
  margin-bottom: 10px;
}

.onboarding-exit-counters strong {
  color: #cbd5e1;
}

.onboarding-exit-desc {
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px ≈ 13px */
  color: #94a3b8;
  margin-top: 4px;
}

.onboarding-exit-buttons {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);  /* v0.121 (GDD §21.3.4) : space-2 = 8px */
}

.onboarding-exit-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  padding: var(--space-3) var(--space-4);  /* v0.121 (GDD §21.3.4) */
  background: rgba(59, 130, 246, 0.15);
  color: #cbd5e1;
  border: 1px solid rgba(59, 130, 246, 0.4);
  border-radius: 6px;
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px ≈ 13px */
  font-weight: var(--font-weight-semibold);  /* v0.121 (GDD §21.3.3) : 600 */
  cursor: pointer;
  transition: all 0.15s ease;
}

.onboarding-exit-btn:hover:not(:disabled) {
  background: rgba(59, 130, 246, 0.25);
  transform: translateY(-1px);
}

.onboarding-exit-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.onboarding-exit-btn-hint {
  display: block;
  font-size: var(--text-tiny);  /* v0.121 (GDD §21.3.2) : tiny = 11px */
  font-weight: 400;
  color: #94a3b8;
  margin-top: 2px;
  letter-spacing: 0;
}

/* Variant rouge/orange pour le bouton "Sortir maintenant" (IRRÉVOCABLE) */
.onboarding-exit-btn-warning {
  background: rgba(217, 119, 6, 0.15);
  border-color: rgba(217, 119, 6, 0.5);
  color: #fef3c7;
}

.onboarding-exit-btn-warning:hover:not(:disabled) {
  background: rgba(217, 119, 6, 0.25);
}

/* Variant vert pour le pack 100% générales (récompense) */
.onboarding-exit-btn-success {
  background: rgba(16, 185, 129, 0.15);
  border-color: rgba(16, 185, 129, 0.5);
  color: #d1fae5;
}

.onboarding-exit-btn-success:hover:not(:disabled) {
  background: rgba(16, 185, 129, 0.25);
}

/* Variant doré pour le pack Pionnier (récompense festive) */
.onboarding-exit-btn-pionnier {
  background: linear-gradient(135deg, rgba(251, 191, 36, 0.18) 0%, rgba(217, 119, 6, 0.10) 100%);
  border-color: rgba(251, 191, 36, 0.5);
  color: #fef3c7;
  box-shadow: 0 0 8px rgba(251, 191, 36, 0.15);
}

.onboarding-exit-btn-pionnier:hover:not(:disabled) {
  background: linear-gradient(135deg, rgba(251, 191, 36, 0.28) 0%, rgba(217, 119, 6, 0.15) 100%);
  box-shadow: 0 0 14px rgba(251, 191, 36, 0.3);
}

/* Badge Pionnier sur la fiche joueur publique */
.player-pionnier-badge {
  display: inline-block;
  padding: 3px 8px;
  font-size: var(--text-tiny);  /* v0.121 (GDD §21.3.2) : tiny = 11px */
  font-weight: var(--font-weight-semibold);  /* v0.121 (GDD §21.3.3) : 600 */
  color: #fef3c7;
  background: linear-gradient(135deg, rgba(251, 191, 36, 0.25) 0%, rgba(217, 119, 6, 0.15) 100%);
  border: 1px solid rgba(251, 191, 36, 0.5);
  border-radius: 10px;
  margin-left: 6px;
  letter-spacing: 0.3px;
}

/* ═══════════════════════════════════════════════════════════════════
   v0.120 — Mentor↔protégé (Bloc 8 — 5/5, clôture)
   ═══════════════════════════════════════════════════════════════════
*/

/* Badge topbar */
#mentor-pill { cursor: pointer; }
.mentor-badge {
  display: inline-block; padding: 0 6px; font-size: var(--text-tiny);  /* v0.121 (GDD §21.3.2) : tiny = 11px */
  color: #cbd5e1; background: #475569; border-radius: 10px; margin-left: 4px;
}
.mentor-badge-active {
  display: inline-block; padding: 0 6px; font-size: var(--text-tiny);  /* v0.121 (GDD §21.3.2) : tiny = 11px */
  color: #fff; background: var(--color-error-fg);  /* v0.122 (GDD §21.4.2) : erreur */ border-radius: 10px; margin-left: 4px;
  animation: mentorBadgePulse 2s ease-in-out infinite;
}
@keyframes mentorBadgePulse {
  0%, 100% { opacity: 0.85; }
  50% { opacity: 1; }
}

/* Modale Mentor */
.mentor-panel-backdrop {
  position: fixed; inset: 0; z-index: var(--z-modal);  /* v0.121 (GDD §21.3.5) : modale = 40 */
  background: rgba(8, 12, 20, 0.85); padding-top: 50px;
  overflow-y: auto; animation: mentorPanelFadeIn 0.2s ease-out;
}
@keyframes mentorPanelFadeIn {
  from { opacity: 0; } to { opacity: 1; }
}
.mentor-panel {
  max-width: 640px; margin: 0 auto;
  padding: var(--space-6) var(--space-6);  /* v0.121 (GDD §21.3.4) : space-6 = 24px */ border: 1px solid #2d4a6e; border-radius: 12px;
  background: linear-gradient(135deg, #0f1825 0%, #0a1220 100%);
  animation: mentorPanelScaleIn 0.25s ease-out;
}
@keyframes mentorPanelScaleIn {
  from { transform: scale(0.95); opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}
.mentor-panel-header {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: var(--space-4);  /* v0.121 (GDD §21.3.4) : space-4 = 16px */
}
.mentor-panel-title {
  font-size: var(--text-h1);  /* v0.121 (GDD §21.3.2) : H1 = 24px ≈ 1.5rem */ color: #e2e8f0; margin: 0;
}
.mentor-panel-close {
  background: transparent; color: #94a3b8; border: 1px solid #324569;
  border-radius: 50%; width: 32px; height: 32px; cursor: pointer;
  font-size: 18px; line-height: 1;
}
.mentor-panel-close:hover { color: #e2e8f0; border-color: #475569; }

.mentor-section { margin-bottom: 22px; }
.mentor-section h3 { font-size: 1.05rem; color: #cbd5e1; margin: 0 0 10px; }
.mentor-empty { font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px ≈ 13px */ color: #94a3b8; font-style: italic; }
.mentor-emerite-badge {
  display: inline-block; padding: 4px 10px;
  background: linear-gradient(135deg, rgba(251,191,36,0.25), rgba(217,119,6,0.15));
  border: 1px solid rgba(251,191,36,0.5); border-radius: 12px;
  color: #fef3c7; font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */ font-weight: 600;
  margin-bottom: 10px;
}

.mentor-relation-row, .mentor-suggestion-row {
  display: flex; align-items: center; gap: var(--space-3);  /* v0.121 (GDD §21.3.4) ≈ space-3 = 12px */
  padding: var(--space-3) var(--space-3);  /* v0.121 (GDD §21.3.4) */ margin-bottom: 8px;
  background: #0a0e1c; border: 1px solid #324569; border-radius: 6px;
}
.mentor-row-username {
  flex: 1; font-weight: var(--font-weight-semibold);  /* v0.121 (GDD §21.3.3) : 600 */ color: #e2e8f0; font-size: 14px;
}
.mentor-row-info {
  font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */ color: #94a3b8;
}
.mentor-btn-accept, .mentor-btn-request {
  padding: 6px 12px; font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */ font-weight: 600;
  background: #10b981; color: #fff; border: none; border-radius: 4px;
  cursor: pointer;
}
.mentor-btn-accept:hover, .mentor-btn-request:hover { background: #059669; }
.mentor-btn-reject {
  padding: 6px 12px; font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */
  background: transparent; color: #94a3b8; border: 1px solid #475569;
  border-radius: 4px; cursor: pointer;
}
.mentor-btn-reject:hover { color: #e2e8f0; }
.mentor-btn-end {
  padding: 6px 12px; font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */
  background: transparent; color: #fbbf24; border: 1px solid #d97706;
  border-radius: 4px; cursor: pointer;
}
.mentor-btn-end:hover { background: rgba(217,119,6,0.15); }

/* Listing dans le tutoriel mentor_match */
.mentor-match-list {
  margin: 12px 0;
}
.mentor-match-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-3) var(--space-3);  /* v0.121 (GDD §21.3.4) */ margin-bottom: 6px;
  background: rgba(15,30,55,0.5); border-radius: 4px;
}
.mentor-match-meta { color: #94a3b8; font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */ }
.onboarding-btn-small { padding: 4px 10px; font-size: var(--text-small);  /* v0.121 (GDD §21.3.2) : small = 12px */ }

/* Badge "Mentor émérite" sur la fiche publique (à côté de Pionnier) */
.player-mentor-emerite-badge {
  display: inline-block;
  padding: 3px 8px;
  font-size: var(--text-tiny);  /* v0.121 (GDD §21.3.2) : tiny = 11px */
  font-weight: var(--font-weight-semibold);  /* v0.121 (GDD §21.3.3) : 600 */
  color: #fef3c7;
  background: linear-gradient(135deg, rgba(190,18,60,0.20) 0%, rgba(127,29,29,0.10) 100%);
  border: 1px solid rgba(251,113,133,0.4);
  border-radius: 10px;
  margin-left: 6px;
  letter-spacing: 0.3px;
}

/* ════════════════════════════════════════════════════════════════════
   v1.02 — Barre unifiée des 4 files (sticky en bas) + Vue globale du VM
   ════════════════════════════════════════════════════════════════════ */

/* ─── Barre sticky en bas ───────────────────────────────────────── */
#queues-bar {
  position: fixed;
  left: var(--sidebar-width, 60px);   /* respecte la sidebar gauche v1.01 */
  /* v1.04 : la barre était à right:0, donc la 10e vignette passait SOUS
     le panneau Vaisseaux Mères (var(--vm-panel-w) à droite, ~130px).
     Désormais on recule de la largeur du panneau VM pour que les 10
     vignettes tiennent dans la zone visible. */
  right: var(--vm-panel-w, 130px);
  bottom: 0;
  z-index: 140;                        /* sous la topbar (200), au-dessus du contenu */
  background: rgba(13, 16, 32, 0.92);
  border-top: 1px solid var(--border);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 0.5rem 0.75rem 0.55rem;
}

/* v1.04 : en mode mobile, le panneau VMs est en drawer (caché par défaut),
   donc la barre peut reprendre toute la largeur sans risque de chevauchement. */
@media (max-width: 700px) {
  #queues-bar { right: 0; }
}

/* Mini-onglets en haut de la barre */
/* ════════════════════════════════════════════════════════════════════
   v0.0.81 — LA FILE EN TIROIR (interrupteur purgé en v0.0.82)
   Dockée, la barre occupait le bas de la colonne de droite en permanence,
   sous la fiche du bâtiment — donc elle repoussait l'information qu'on est
   venu lire. Elle devient un tiroir : fermée, il ne reste que sa poignée.
   ⚠️ LA TRANSLATION LAISSE LA LARGEUR DE LA POIGNÉE VISIBLE
   (`100% - 34px`) : un `translateX(100%)` nu emporterait la poignée avec
   le panneau, et le tiroir deviendrait impossible à rouvrir.
   ════════════════════════════════════════════════════════════════════ */
body.qbar-tiroir #queues-bar {
  /* Les deux valeurs sont MESURÉES par queues.js à chaque rendu (largeur du
     rail des Vaisseaux Mères, bas de la barre de ressources) : le tiroir se
     cale à côté d'eux au lieu de sortir dessous. Les replis servent le temps
     du tout premier rendu, avant la première mesure. */
  /* ⚠️ IL DESCEND JUSQU'EN BAS DE LA PAGE (demande de Loryan : « fais en
     sorte que la file prenne toute la page jusqu'en bas, sinon pas
     d'intérêt »). `bottom: auto` le faisait s'arrêter à la hauteur de son
     contenu — quatre vignettes, donc un panneau court flottant au milieu du
     bord droit, qui ne libérait pas plus de place qu'avant. Avec `top` ET
     `bottom` posés, la hauteur n'est plus celle du contenu mais celle de la
     colonne : la poignée fermée devient aussi plus facile à attraper, elle
     court sur toute la hauteur. */
  position: fixed; top: var(--qbar-tiroir-haut, 225px);
  right: var(--qbar-tiroir-droite, 0px); bottom: 8px;
  /* ⚠️ C'EST LA LARGEUR QUI S'OUVRE, PAS UNE TRANSLATION. Un
     `translateX(100% - 34px)` faisait sortir le panneau PAR-DESSUS le rail
     des Vaisseaux Mères, à droite : la languette était bien là, mais cent
     seize pixels de panneau vide traversaient le rail en permanence. En
     largeur, le tiroir reste dans sa case et ne déborde jamais. */
  /* ⚠️ `box-sizing: border-box` OBLIGATOIRE : sans lui le padding de la
     languette S'AJOUTE à la largeur, le tiroir fermé fait 68 px au lieu de
     34, et une bande du panneau vide reste visible à côté de sa poignée. */
  box-sizing: border-box;
  /* ⚠️ PLUS DE `max-height` : `top` et `bottom` décident seuls de la
     hauteur. En laisser une valeur ici la reprendrait au premier écran plus
     haut que 100vh − 130 px, et le tiroir cesserait d'aller au bas de la
     page sans qu'on comprenne pourquoi. */
  width: var(--qbar-tiroir-poignee);
  overflow: hidden; z-index: 160;
  /* ⚠️ LE PADDING DROIT TOMBE À ZÉRO QUAND LE TIROIR EST FERMÉ, et ce n'est
     pas de la coquetterie : en `border-box`, une largeur INFÉRIEURE à la
     somme des paddings et des bordures est simplement ignorée. Avec ses
     34 px à gauche plus 8,4 px à droite, le tiroir fermé mesurait 43 px au
     lieu de 34 — neuf pixels de panneau vide collés à la poignée, mesurés
     au navigateur. Une déclaration `width` peut donc être vraie et sans
     effet, et rien ne le signale. */
  padding-left: 34px; padding-right: 0;
  transition: width .24s ease;
  border-radius: 10px 0 0 10px;
  box-shadow: -8px 0 24px -12px rgba(0, 0, 0, .8);
  /* ⚠️ UN FOND OPAQUE EST OBLIGATOIRE ICI. Dockée, la barre héritait du
     fond de la colonne ; en `fixed`, elle flotte au-dessus de la page et
     laissait tout voir au travers — vu au navigateur, illisible. */
  background: var(--panel, #0f1420);
  border: 1px solid var(--line, #24314b); border-right: 0;
}
body.qbar-tiroir.qbar-tiroir-ouvert #queues-bar {
  width: var(--qbar-tiroir-largeur); overflow-y: auto;
  padding-right: 0.75rem;
}
/* ⚠️ LE TIROIR ÉCARTE LE CONTENU, IL NE LE RECOUVRE PAS (signalement de
   Loryan, capture à l'appui : « il devrait écarter les autres vers la
   gauche pour s'insérer, parce que là ça cannibalise »). Ouvert, il
   passait PAR-DESSUS la fiche du bâtiment et la file de chantier — c'est-à-
   dire très exactement l'information qu'on venait de lui faire libérer.
   La vue du Vaisseau Mère recule donc sa bordure droite d'autant, et sur la
   MÊME durée que l'ouverture : les deux mouvements n'en font qu'un.
   ⚠️ LA LARGEUR EST UNE VARIABLE, ET C'EST OBLIGATOIRE : écrite deux fois
   en dur, un ajustement futur n'en corrigerait qu'une, et le décalage du
   contenu cesserait de correspondre au tiroir — sans qu'aucune erreur ne le
   signale, juste un panneau qui remord sur la page. */
body.qbar-tiroir {
  --qbar-tiroir-largeur: min(330px, 86vw);
  /* 34 px de languette + le 1 px de bordure gauche du tiroir. */
  --qbar-tiroir-poignee: 35px;
}
/* ⚠️ LA POIGNÉE AUSSI PREND SA PLACE, MÊME FERMÉE (signalement de Loryan :
   « et replié ça mange un peu »). Fermé, le tiroir reste posé en `fixed`
   par-dessus la page : ses trente-cinq pixels rognaient le bord droit de la
   fiche du bâtiment — « temps ÷ 1… » coupé, le sélecteur de niveau amputé.
   La vue réserve donc la languette en permanence, et ne recule d'avantage
   qu'à l'ouverture. Vu au navigateur, pas à la relecture : trente-cinq
   pixels sur un panneau large ne se remarquent qu'au caractère tronqué. */
body.qbar-tiroir #view-ship {
  right: calc(var(--vm-panel-w, 130px) + var(--qbar-tiroir-poignee));
  transition: right .24s ease;
}
body.qbar-tiroir.qbar-tiroir-ouvert #view-ship {
  right: calc(var(--vm-panel-w, 130px) + var(--qbar-tiroir-largeur));
}
/* ─── Les dix vignettes remplissent la hauteur du tiroir ─────────────
   Le mode compact est retiré par `queues.js` en tiroir (cf. son
   commentaire) : il ne reste qu'à faire GRANDIR la grille au lieu de la
   laisser à sa hauteur naturelle, sinon les cinq rangées se tasseraient en
   haut d'un panneau de huit cents pixels.
   ⚠️ `minmax(58px, 1fr)` ET PAS `1fr` SEUL : sur un écran court, `1fr` peut
   descendre sous la hauteur du contenu d'une vignette, et le nom du
   bâtiment passerait sous le bord. Le plancher est celui de la vignette
   dockée. */
body.qbar-tiroir #queues-bar { display: flex; flex-direction: column; }
body.qbar-tiroir #queues-bar.is-docked .qbar-slots {
  flex: 1 1 auto;
  grid-auto-rows: minmax(58px, 1fr);
  align-content: stretch;
}
/* ─── v0.0.86 : UNE SEULE COLONNE DANS LE TIROIR ──────────────────────
   Demande de Loryan, capture à l'appui : « Retirer les 2 colonnes de la
   file — mettre une seule colonne ».
   Les deux colonnes viennent de `#queues-bar.is-docked .qbar-slots`, écrite
   pour le DOCK de la colonne de droite (« 316 px de large : les 10
   vignettes passent sur 2 colonnes × 5 lignes »). Le tiroir a hérité de
   cette règle sans que sa raison la suive : il fait la même largeur, mais
   il occupe TOUTE LA HAUTEUR de la page pour lui seul — la place manque en
   largeur et abonde en hauteur, exactement l'inverse de ce que deux
   colonnes supposent. D'où des vignettes de ~150 px où « Institut
   Quantique Niv.12 » se coupe, dans une case haute et vide.
   ⚠️ IL FAUT QUATRE CLASSES POUR PASSER DEVANT : la règle d'origine porte
   un identifiant et deux classes (`#queues-bar.is-docked .qbar-slots`), donc
   une règle plus courte PERDRAIT où qu'on l'écrive — le piège du bandeau de
   production (v0.0.53) et du mode compact (v0.0.82).
   ⚠️ LE DOCK HORS TIROIR N'EST PAS TOUCHÉ : il garde ses deux colonnes, et
   c'est voulu — là, la hauteur est comptée. */
body.qbar-tiroir #queues-bar.is-docked .qbar-slots {
  grid-template-columns: minmax(0, 1fr);
}
/* ⚠️ `#prod-hint` NE BOUGE PAS, ET C'EST MESURÉ. Le bandeau de conseil flotte
   dans le coin haut-droit, AU-DESSUS du tiroir (qui démarre sous la barre de
   ressources) : il n'y a donc aucun recouvrement à éviter. Le décaler comme
   la vue le faisait au contraire entrer dans la barre du haut, par-dessus le
   bouton Inventaire — vu au navigateur. */
/* La poignée occupe la bande laissée visible, sur toute la hauteur. */
.qbar-poignee {
  position: absolute; left: 0; top: 0; bottom: 0; width: 34px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; cursor: pointer;
  background: var(--panel2, #121a2a);
  border: 1px solid var(--line, #24314b); border-right: 0;
  border-radius: 10px 0 0 10px;
  color: var(--text2, #9fb0d0); font-size: 11px; letter-spacing: .08em;
}
.qbar-poignee:hover { color: var(--text1, #e5e8f5); border-color: var(--accent2, #0ea5e9); }
.qbar-poignee-txt { writing-mode: vertical-rl; text-transform: uppercase; }
.qbar-poignee-fleche { font-size: 12px; }
.qbar-poignee-badge {
  background: var(--accent2, #0ea5e9); color: #06121f;
  border-radius: 8px; padding: 1px 5px; font-weight: 700; font-size: 10px;
}
/* ⚠️ Le tiroir sort du flux : la colonne de droite ne doit plus lui
   réserver de place, sinon on aurait retiré la barre pour laisser son
   trou. */
body.qbar-tiroir #queues-dock { display: none; }
@media (max-width: 720px) {
  /* Sur écran étroit la barre de ressources s'empile sur deux lignes : on
     laisse un peu plus d'air en bas, la hauteur reste tenue par top/bottom. */
  body.qbar-tiroir #queues-bar { bottom: 14px; }
}

.qbar-tabs {
  display: flex;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.4rem;
}
.qbar-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: transparent;
  color: var(--text2, #b8c2da);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.32rem 0.6rem;
  font-family: var(--hud, inherit);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.3px;
  cursor: pointer;
  transition: all 0.15s;
}
.qbar-tab:hover { color: var(--text1, #e5e8f5); border-color: var(--accent2, #0ea5e9); }
.qbar-tab.is-active {
  background: rgba(14, 165, 233, 0.12);
  color: var(--accent2, #0ea5e9);
  border-color: var(--accent2, #0ea5e9);
}
.qbar-tab-ico { font-size: 1rem; line-height: 1; }
.qbar-tab-count {
  font-family: var(--mono, monospace);
  font-size: 0.65rem;
  color: var(--text3, #6e7691);
  background: rgba(255, 255, 255, 0.03);
  padding: 1px 5px;
  border-radius: 3px;
  margin-left: 2px;
}
.qbar-tab-count.is-full { color: var(--accent1, #e94560); }

/* Grille des 10 slots */
.qbar-slots {
  display: grid;
  grid-template-columns: repeat(10, minmax(0, 1fr));
  gap: 0.4rem;
}

/* Une vignette (slot) */
.qbar-slot {
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.4rem 0.45rem;
  min-height: 88px;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.65rem;
  position: relative;
  transition: all 0.15s;
}
.qbar-slot-active {
  background: rgba(14, 165, 233, 0.06);
  border-color: var(--accent2, #0ea5e9);
}
.qbar-slot-queued {
  opacity: 0.78;
}
.qbar-slot-empty {
  background: rgba(255, 255, 255, 0.01);
  border-style: dashed;
  opacity: 0.4;
  justify-content: center;
  align-items: center;
}
.qbar-slot-empty-num {
  font-family: var(--mono, monospace);
  color: var(--text3, #6e7691);
  font-size: 0.85rem;
  font-weight: 700;
}
/* v0.0.126 : le message de l'onglet Entretien vide, dans la barre des
   files. */
.qbar-entretien-vide {
  grid-column: 1 / -1;
  padding: 10px 12px;
  color: var(--text3, #6e7691);
  font-size: 0.82rem;
  line-height: 1.4;
}
/* L'onglet Entretien porte rarement plus de deux ou trois cartes : dans le
   TIROIR, la grille les ÉTIRAIT sur toute la hauteur disponible — des
   vignettes géantes, vu au navigateur. On les cale en haut, à hauteur de
   carte ordinaire.
   ⚠️ MÊME PRÉFIXE QUE LA RÈGLE DU TIROIR (`body.qbar-tiroir
   #queues-bar.is-docked .qbar-slots`) : à moins de classes, la sienne
   gagnerait où qu'on écrive celle-ci (piège du bandeau, v0.0.53). Et on ne
   touche QUE le tiroir — le mode compact du téléphone rendait déjà bien,
   une règle générale l'a cassé au premier essai (chevauchement mesuré). */
body.qbar-tiroir #queues-bar.is-docked .qbar-slots.qbar-slots-entretien {
  grid-auto-rows: min-content;
  align-content: start;
}
body.qbar-tiroir #queues-bar.is-docked .qbar-slots.qbar-slots-entretien .qbar-slot {
  min-height: 84px;
  max-height: 128px;
}

.qbar-slot-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.2rem;
  min-height: 18px;
}
.qbar-slot-ico { font-size: 1.05rem; line-height: 1; }
/* v2.11 : bouton d'accélération Nanites (chantier de tête) */
.qbar-slot-accel {
  background: rgba(45, 212, 191, 0.10);
  color: #2dd4bf;
  border: 1px solid rgba(45, 212, 191, 0.35);
  border-radius: 50%;
  width: 18px; height: 18px;
  font-size: 0.7rem; line-height: 1;
  cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
}
.qbar-slot-accel:hover { background: rgba(45, 212, 191, 0.22); }
.qbar-slot-accel.on {
  background: rgba(45, 212, 191, 0.30);
  border-color: #2dd4bf;
  animation: naniteAccelPulse 1.1s ease-in-out infinite;
}
@keyframes naniteAccelPulse { 0%,100%{box-shadow:0 0 5px rgba(45,212,191,.4)} 50%{box-shadow:0 0 12px rgba(45,212,191,.85)} }
.qbar-slot-pending {
  font-size: 0.7rem;
  color: var(--gold, #ffd54f);
  cursor: help;
}
/* v0.0.135 — LA TÊTE QUI ATTEND SES
   RESSOURCES. Un item du fond de file attend son tour : c'est normal, et le
   ⏳ discret suffit. La TÊTE, elle, est la seule chose qui explique une file
   qui n'avance plus : elle clignote, pour qu'on la trouve sans la chercher.
   ⚠️ Le clignotement s'éteint sous `prefers-reduced-motion`, comme partout
   ailleurs dans cette feuille. */
.qbar-slot-pending.is-bloque {
  color: #fbbf24;
  animation: qbarAttentePulse 1.6s ease-in-out infinite;
}
@keyframes qbarAttentePulse { 0%,100%{opacity:1} 50%{opacity:.35} }
@media (prefers-reduced-motion: reduce) {
  .qbar-slot-pending.is-bloque { animation: none; }
}
.qbar-slot-cancel {
  background: rgba(233, 69, 96, 0.08);
  color: var(--accent1, #e94560);
  border: 1px solid rgba(233, 69, 96, 0.3);
  border-radius: 50%;
  width: 18px;
  height: 18px;
  font-size: 0.75rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex-shrink: 0;
}
.qbar-slot-cancel:hover {
  background: rgba(233, 69, 96, 0.22);
  border-color: var(--accent1, #e94560);
}
/* ════════════════════════════════════════════════════════════════════
   v0.0.149 — LE × RESTE À DROITE
   ════════════════════════════════════════════════════════════════════
   Signalement de Loryan, deux captures de la MÊME carte à quelques
   secondes d'écart : « au début la croix est à droite et après qq
   secondes elle passe à gauche ».

   ⚠️ C'EST `justify-content: space-between` SUR UNE RANGÉE DONT LE
   NOMBRE D'ÉLÉMENTS CHANGE — le piège que ce projet a déjà rencontré sur
   la barre des onglets. La rangée du haut porte, dans l'ordre : la
   vignette (masquée quand la carte a déjà sa planche en fond,
   `.a-vignette`), le badge ×N, le ⏳ « en attente », le 🧬 et le ×. Sur
   une recherche à vignette et sans 🧬, il ne reste donc que DEUX
   éléments — ⏳ et × — que `space-between` envoie aux deux bouts. Dès
   que le chantier démarre, le ⏳ disparaît, le × se retrouve SEUL, et
   `space-between` le repousse au début de la ligne : il saute à gauche
   sous les yeux du joueur.

   La correction ne touche pas la rangée : elle épingle le × au bord,
   quel que soit le nombre de ses voisins. */
.qbar-slot-cancel { margin-left: auto; }
/* Les flèches ▲▼ du tiroir vont AVEC le ×, au bord droit —
   le groupe prend la marge automatique, et le × se colle à lui (deux marges
   automatiques se partageraient la place et ouvriraient un trou). */
.qbar-slot-ordre-grp { margin-left: auto; display: flex; gap: 2px; flex-shrink: 0; }
.qbar-slot-ordre-grp + .qbar-slot-cancel { margin-left: 2px; }
.qbar-slot-ordre {
  background: rgba(96, 165, 250, 0.10);
  color: #93c5fd;
  border: 1px solid rgba(96, 165, 250, 0.35);
  border-radius: 50%;
  width: 18px; height: 18px;
  font-size: 0.7rem; line-height: 1;
  cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
}
.qbar-slot-ordre:hover { background: rgba(96, 165, 250, 0.25); border-color: #60a5fa; }

.qbar-slot-name {
  font-size: 0.65rem;
  color: var(--text1, #e5e8f5);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.2;
}
.qbar-slot-lv {
  color: var(--gold, #ffd54f);
  font-family: var(--mono, monospace);
  font-weight: 600;
  font-size: 0.6rem;
}

/* v1.41 : badge quantité « ×N » pour les lots groupés (vaisseaux/défenses). */
.qbar-slot-qty {
  margin-left: auto;
  color: var(--accent2, #0ea5e9);
  font-family: var(--mono, monospace);
  font-weight: 700;
  font-size: 0.68rem;
  background: rgba(14, 165, 233, 0.14);
  border: 1px solid rgba(14, 165, 233, 0.35);
  border-radius: 6px;
  padding: 0 5px;
  line-height: 1.3;
}

.qbar-slot-bar {
  height: 3px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 2px;
  overflow: hidden;
}
.qbar-slot-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--accent2, #0ea5e9), var(--accent3, #c084fc));
  transition: width 0.5s ease;
}

.qbar-slot-time {
  font-family: var(--mono, monospace);
  font-size: 0.62rem;
  color: var(--gold, #ffd54f);
  text-align: center;
  letter-spacing: 0.2px;
}
/* ⚠️ UNE TÊTE BLOQUÉE NE MENT PLUS SUR SON TEMPS (constat d'audit 3ᵉ passage).
   Un chantier de tête non démarré (en attente de ressources) affichait un
   compte à rebours plein qui ne descendait jamais — la file semblait avancer
   alors qu'elle était gelée. On dit « en attente », en ambre, comme le ⏳. */
.qbar-slot-time.est-attente { color: #fbbf24; }

/* Décaler le contenu principal du jeu pour ne pas être caché par la barre */
/* v1.05 : étendu à la vue Recherche aussi (qui affiche désormais la barre) */
body.has-queues-bar #ship-content,
body.has-queues-bar #view-research { padding-bottom: 130px; }

/* v2.9.7 — Barre des files REPLIABLE.
   Repliée : le conteneur ne prend plus toute la largeur, juste un petit bouton
   pilule en bas à gauche ; on rend au contenu la place perdue. */
body.has-queues-bar.queues-collapsed #ship-content,
body.has-queues-bar.queues-collapsed #view-research { padding-bottom: 52px; }

/* v2.29 — DOCKÉE dans la colonne de droite (vue VM) : la barre ne traverse
   plus le bas de l'écran, il n'y a donc plus rien à réserver sous le contenu.
   Placé APRÈS les deux règles ci-dessus, qui ont la même spécificité. */
body.has-queues-bar.queues-docked #ship-content { padding-bottom: 0; }
#queues-bar.is-docked {
  position: static;
  left: auto; right: auto; bottom: auto; width: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.55rem 0.6rem;
}
/* 316 px de large : les 10 vignettes passent sur 2 colonnes × 5 lignes, et
   les 4 onglets sur deux rangées de deux. */
#queues-bar.is-docked { --qbar-slot-gap: 0.35rem; }
#queues-bar.is-docked .qbar-slots { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--qbar-slot-gap); }
/* Vignettes plus compactes qu'en pleine largeur : 5 rangées doivent tenir
   sous le panneau de focus sans l'écraser. */
#queues-bar.is-docked .qbar-slot { min-height: 58px; padding: 0.3rem 0.35rem; gap: 0.18rem; }

/* ─── v2.33 : FILE COMPACTE ──────────────────────────────────────────
   Les 10 vignettes empilées sur 5 rangées mangeaient ~315 px de la
   colonne de droite : le panneau du bâtiment / de la recherche
   sélectionné, juste au-dessus, se retrouvait tronqué et il fallait
   défiler pour le lire en entier.

   Ici on borne la GRILLE à 2 rangées — soit 4 vignettes visibles en
   permanence — et les suivantes défilent à l'intérieur. La colonne
   récupère ~190 px, assez pour que le panneau du dessus tienne d'un bloc.

   La hauteur de rangée est FIXÉE (--qbar-compact-row) pour que la fenêtre
   de défilement tombe pile sur une rangée, jamais au milieu d'une vignette.
   60 px couvre le contenu réel (59.8 px mesuré) : le nom est sur une seule
   ligne (white-space:nowrap + ellipsis), donc la hauteur ne varie pas.
   Si une vignette gagnait une ligne un jour, c'est cette variable qu'il
   faudrait relever. */
#queues-bar.is-docked.is-compact .qbar-slots {
  --qbar-compact-row: 60px;
  grid-auto-rows: var(--qbar-compact-row);
  max-height: calc(var(--qbar-compact-row) * 2 + var(--qbar-slot-gap));
  overflow-y: auto;
  /* Le défilement s'arrête à la file : il ne se propage pas à la page. */
  overscroll-behavior: contain;
  padding-right: 3px;   /* respiration à côté de l'ascenseur */
}
/* Ascenseur discret, accordé au thème sombre. */
#queues-bar.is-docked.is-compact .qbar-slots {
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 150, 220, 0.35) transparent;
}
#queues-bar.is-docked.is-compact .qbar-slots::-webkit-scrollbar { width: 6px; }
#queues-bar.is-docked.is-compact .qbar-slots::-webkit-scrollbar-track { background: transparent; }
#queues-bar.is-docked.is-compact .qbar-slots::-webkit-scrollbar-thumb {
  background: rgba(120, 150, 220, 0.35);
  border-radius: 3px;
}
#queues-bar.is-docked.is-compact .qbar-slots::-webkit-scrollbar-thumb:hover {
  background: rgba(120, 150, 220, 0.55);
}
/* ─── v2.34 : BANDEAU DE RESSOURCES AGRANDI ─────────────────────────
   Le bandeau d'origine tient sur ~55 px, encart de 42 px : le stock, en
   0.85rem, est le chiffre qu'on lit le plus souvent du jeu et c'était le
   plus petit texte de l'écran. Ici on l'agrandit d'UNE FOIS ET DEMIE —
   hauteur d'encart 63 px, stock en 1.28rem. Un ×2 avait été essayé : trop
   massif, et surtout trop large (les neuf encarts passaient sur deux
   lignes dès qu'un écran descendait sous ~1900 px).

   La largeur FIXE de l'encart (132 px, posée en v2.9.4 pour que tous
   fassent la même taille) devient ÉLASTIQUE : les neuf se partagent la
   largeur disponible à parts égales — l'uniformité voulue est conservée —
   et le bandeau s'adapte tout seul au panneau « Vaisseaux Mères » qui le
   rétrécit à droite. Le plancher reste la largeur d'origine, pour que le
   passage sur deux lignes arrive le plus tard possible.

   Rien d'autre à recaler : core/layout.js MESURE la hauteur réelle de
   #resbar (ResizeObserver) et republie --resbar-h / --resbar-bottom, dont
   dépend tout ce qui se positionne dessous.

   Les capacités (« / 40.0k ») portent un style INLINE dans index.html :
   seul !important peut le contredire depuis une feuille de style. */
#resbar { gap: .4rem; padding: .42rem 1.2rem; }
#resbar .res {
  width: auto;
  /* Base 0 (et non auto) : avec flex-wrap, la répartition en lignes se fait
     sur la taille « hypothétique » des encarts — base auto = taille du
     CONTENU, que les gros chiffres gonflent, d'où des retours à la ligne
     précoces. Base 0 clampée par min-width : la ligne se calcule sur
     132 px par encart, comme la largeur fixe d'origine. */
  flex: 1 1 0;
  min-width: 132px;
  max-width: 178px;
  min-height: 63px;          /* 1,5 × la hauteur naturelle (42 px) */
  padding: .34rem .5rem;
  gap: .45rem;
  border-radius: 4px;
}
#resbar #res-electryte { width: auto; min-width: 150px; }  /* « prod / conso » */
#resbar .res-ico-img { width: 26px; height: 26px; border-radius: 5px; }
/* v2.50 : encart plus large, icône plus petite, pour loger la deuxième
   ligne du bandeau détaillé. */
#resbar .res { max-width: 200px; padding: .3rem .42rem; gap: .38rem; }
#resbar .res-ico-img { width: 22px; height: 22px; }
/* `vignettesVives` — remontée de Loryan : « ils sont ternes et ne se voient
   pas bien ». Les planches vives ont un fond TRANSPARENT : plus de cadre
   arrondi ni de liseré (ils dessineraient un carré autour de rien), mais un
   halo de la couleur de la ressource (`--rc`, posé sur chaque encart), et
   30 px au lieu de 22 — l'encart a la place (hauteur mesurée inchangée).
   ⚠️ La taille vaut pour les NEUF vignettes, pour que les chiffres restent
   alignés d'un encart à l'autre ; le style « sans cadre » ne vise que les
   images vives (`/vif/`) — le Distarium, qui n'en a pas, garde son cadre.
   ⚠️ Au téléphone, la taille ne bouge pas (règle suivante, plus précise). */
body.f-vignettesVives #resbar .res-ico-img { width: 30px; height: 30px; }
body.f-vignettesVives.mobile-mode #resbar .res-ico-img { width: 18px; height: 18px; }
body.f-vignettesVives #resbar .res-ico-img[src*="/vif/"] {
  border-radius: 0; box-shadow: none; object-fit: contain;
  filter: drop-shadow(0 0 3px color-mix(in srgb, var(--rc) 65%, transparent));
}
body.f-vignettesVives .res-mini[src*="/vif/"] { border-radius: 0; box-shadow: none; object-fit: contain; }
#resbar .res-name { font-size: .58rem; }
#resbar .res-val  { font-size: 1.28rem; line-height: 1.12; font-weight: 600; }
#resbar .res-rate { font-size: .66rem; }
#resbar .res > div[id^="rc-"] { font-size: .68rem !important; }
/* v2.49 : la prod /h et la capacité montent d'un cran — demande de Loryan,
   « on ne voit pas assez le stock max, on ne voit pas assez la production ».
   La capacité abandonne son gris de légende pour la couleur du texte. */
#resbar .res-rate { font-size: .74rem; font-weight: 600; }
#resbar .res-cap  { font-size: .74rem; font-weight: 600; color: var(--text); }
#resbar .res-eta  { font-size: .66rem; }
#resbar .res { min-height: 74px; align-content: center; }
/* ─── Interrupteur ressourcesCompactes : LES CASES À LA TAILLE DE LEUR CONTENU ───
   Loryan, sur une capture du bandeau : « réduire les bandeaux de ressources,
   j'ai l'impression qu'il y a de l'espace vide pour rien ». Les cases
   élastiques du bloc v2.34 s'étiraient jusqu'à leur plafond : le chiffre et
   la capacité n'en occupaient guère plus de la moitié, et sur un écran
   moyen la neuvième case passait seule sur une deuxième rangée.

   Ici, largeur FIXE et sans étirement, taillée sur le contenu le plus large
   qu'une case puisse afficher (six caractères de stock, un débit à cinq
   chiffres, la capacité). Fixe et pas « à la taille du texte » : les stocks
   bougent à chaque seconde, une case au contenu ferait danser tout le
   bandeau. L'Electryte a sa propre largeur (« prod / conso »).

   ⚠️ LA DEUXIÈME LIGNE SE REPLIE AU LIEU DE COUPER : quand le délai est long
   (« ⚠ à sec dans 12h30 » de la Lumynite en vol), il passe sous la
   capacité — auparavant il finissait en « … » et perdait justement l'heure.
   La marge du haut et du bas est réduite, et l'interligne de cette ligne
   resserré, pour que ces trois lignes tiennent dans la hauteur minimale de
   la case : le bandeau ne change JAMAIS de hauteur quand un délai s'allonge
   (tout ce qui se cale dessous suivrait). Mesuré au navigateur, contenu le
   plus large injecté dans les neuf cases.

   ⚠️ Au téléphone, rien ne change : le mode mobile a sa propre grille, et
   une fenêtre étroite forcée en mode bureau garde les cases étirées (la
   règle ne vit qu'au-delà de 700 px). */
@media (min-width: 701px) {
  body.f-ressourcesCompactes:not(.mobile-mode) #resbar .res {
    flex: 0 0 auto;
    width: 120px;
    min-width: 0;
    max-width: none;
    padding-top: .15rem;
    padding-bottom: .15rem;
  }
  body.f-ressourcesCompactes:not(.mobile-mode) #resbar #res-electryte { width: 136px; min-width: 0; }
  body.f-ressourcesCompactes:not(.mobile-mode) #resbar .res-right { flex-wrap: wrap; row-gap: 0; line-height: 1.05; }
}
/* ─── Interrupteur ressourcesGrandEcran : SUR GRAND ÉCRAN, TOUT GROSSIT ENSEMBLE ───
   Loryan, à la question « sur un très grand écran le bandeau s'arrête avant
   le bord droit, préfères-tu que les chiffres grossissent pour remplir cette
   place ? » : « OUI ». Il ne vit qu'avec ressourcesCompactes.

   LE PRINCIPE : une seule taille pilote toute la case. La case porte une
   taille de police calculée sur la largeur du bandeau, et TOUT le reste s'y
   exprime en `em` — largeur, hauteur, marges, vignette, et les cinq textes.
   À l'échelle 1, un `em` vaut exactement la racine du jeu : les valeurs en
   `em` ci-dessous sont celles, en `rem` et en pixels, du bloc du dessus.
   Case et chiffres grossissent donc dans la même proportion : pas d'espace
   vide qui revient à l'intérieur.

   LA FORMULE : ce qui reste de la largeur du bandeau (`100cqw`, qui exige
   `container-type` sur le bandeau) une fois retirés les huit écarts, divisé
   par la largeur de dessin des neuf cases (huit fois 120 px, plus l'Electryte
   à 136 px), fois la racine. Deux pixels de marge contre les arrondis : sans
   eux, la neuvième case retombe sur une deuxième rangée au pixel près.
   ⚠️ Changer une largeur du bloc du dessus sans changer le diviseur ici (ou
   l'inverse), c'est faire déborder le bandeau : le banc refait le calcul.

   LE PLANCHER est la taille d'aujourd'hui (rien ne rapetisse : sous la
   largeur où les neuf cases tiennent, le bandeau passe sur deux rangées comme
   avant). LE PLAFOND évite un bandeau démesuré sur les écrans géants.
   ⚠️ Au téléphone, rien ne change (mêmes gardes que le bloc du dessus). */
@media (min-width: 701px) {
  body.f-ressourcesCompactes.f-ressourcesGrandEcran:not(.mobile-mode) #resbar { container-type: inline-size; }
  body.f-ressourcesCompactes.f-ressourcesGrandEcran:not(.mobile-mode) #resbar .res {
    font-size: clamp(14px, (100cqw - 8 * .4rem - 2px) * 14 / 1096, 24px);
    width: 8.5714em;                         /* 120 px à l'échelle 1 */
    min-height: 5.2857em;                    /*  74 px */
    padding: .15em .42em;
    gap: .38em;
  }
  body.f-ressourcesCompactes.f-ressourcesGrandEcran:not(.mobile-mode) #resbar #res-electryte { width: 9.7143em; }   /* 136 px */
  /* ⚠️ `font-size: inherit` D'ABORD : la vignette porte aussi la classe
     `.res-ico` (police à .9rem), et un `em` de largeur se mesure sur la police
     de l'élément lui-même — sans cette ligne, elle grossit moins que la case. */
  body.f-ressourcesCompactes.f-ressourcesGrandEcran:not(.mobile-mode) #resbar .res-ico-img { font-size: inherit; width: 1.5714em; height: 1.5714em; }   /* 22 px */
  body.f-ressourcesCompactes.f-ressourcesGrandEcran.f-vignettesVives:not(.mobile-mode) #resbar .res-ico-img { width: 2.1429em; height: 2.1429em; }   /* 30 px */
  body.f-ressourcesCompactes.f-ressourcesGrandEcran:not(.mobile-mode) #resbar .res-name { font-size: .58em; }
  body.f-ressourcesCompactes.f-ressourcesGrandEcran:not(.mobile-mode) #resbar .res-val  { font-size: 1.28em; }
  body.f-ressourcesCompactes.f-ressourcesGrandEcran:not(.mobile-mode) #resbar .res-rate { font-size: .74em; }
  body.f-ressourcesCompactes.f-ressourcesGrandEcran:not(.mobile-mode) #resbar .res-cap  { font-size: .74em; }
  body.f-ressourcesCompactes.f-ressourcesGrandEcran:not(.mobile-mode) #resbar .res-eta  { font-size: .66em; }
}
/* ─── Interrupteur ressourcesAlignees : TOUT LE TEXTE, ET LES NOMS ALIGNÉS ───
   Loryan, sur une capture : « on voit pas la suite de à sec, peux-tu élargir
   vers le bas ? Également l'Electryte n'est pas au même niveau au niveau du
   texte ». Ne vit qu'avec ressourcesCompactes.
   ⚠️ LE DÉLAI LE PLUS LONG N'AVAIT PAS ÉTÉ PRÉVU : « ■ pleine · ⚠ à sec dans
   17h22 » (soute pleine ET débit négatif, render.js) dépasse la largeur d'une
   case même seul sur sa ligne — il finissait en « … ». Il passe désormais à la
   ligne dans la case, qui grandit vers le bas : le bandeau aussi, le temps que
   ce délai existe (tout ce qui se cale dessous suit, c'est mesuré).
   ⚠️ L'ELECTRYTE N'A PAS DE DEUXIÈME LIGNE (ni capacité ni délai) : centré
   verticalement, son contenu tombait plus bas que celui des autres. Les deux
   lignes de chaque case s'écartent désormais vers le haut et le bas — le nom
   en haut, la capacité en bas, partout ; l'Electryte, avec une seule ligne,
   s'aligne en haut. */
@media (min-width: 701px) {
  body.f-ressourcesCompactes.f-ressourcesAlignees:not(.mobile-mode) #resbar .res {
    align-content: space-between;
    padding-bottom: .3em;
  }
  body.f-ressourcesCompactes.f-ressourcesAlignees:not(.mobile-mode) #resbar .res-eta {
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
  }
}

#queues-bar.is-docked .qbar-tabs { flex-wrap: wrap; gap: 0.3rem; }
#queues-bar.is-docked .qbar-tab { flex: 1 1 calc(50% - 0.3rem); justify-content: center; }
/* Interrupteur filesDeuxParLigne — Loryan, capture du tiroir à l'appui :
   « peux-tu en mettre 2 par ligne pour que ça ne prenne que 3 lignes au
   total ? ».
   ⚠️ LE `flex: 50 %` DU DESSUS NE DONNAIT PAS DEUX PAR LIGNE : une base
   de 50 % cède devant la largeur du contenu, et « Bâtiments 10/10 » est plus
   large que la moitié du tiroir — il passait seul sur sa ligne, l'Entretien
   aussi, et la rangée tenait sur quatre ou cinq lignes selon la police.
   Une GRILLE impose les deux colonnes quoi que dise le contenu.
   ⚠️ LA MARGE ÉTAIT NULLE, ET C'EST POUR ÇA QUE L'ÉCRAN VARIE D'UN POSTE À
   L'AUTRE. Mesuré à 1500 px, police Orbitron chargée : « Bâtiments 10/10 »
   occupe 144 px pour une demi-largeur de 140. Quelques pixels de rendu en
   plus (police, système) et Recherche, Vaisseaux passent seuls à leur tour —
   les cinq lignes de la capture. Le retrait, l'écart et le compteur se
   resserrent, l'espacement des lettres tombe : 126 px à 11 px, quatorze
   pixels de marge (11 %).
   ⚠️ ET SI LE CONTENU NE TIENT TOUJOURS PAS, IL PASSE À LA LIGNE DANS
   L'ONGLET (le compteur sous le nom), il ne se coupe pas : l'onglet devient
   plus haut, jamais tronqué.
   Le nom monte au plancher de lecture (0,79 rem = 11 px) ; la hauteur de
   l'onglet ne bouge pas (25 px avant comme après, mesuré). Le compteur reste
   à sa taille : à 11 px, « 10/10 » ne tiendrait plus sur la ligne du nom. */
body.f-filesDeuxParLigne #queues-bar.is-docked .qbar-tabs {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
}
body.f-filesDeuxParLigne #queues-bar.is-docked .qbar-tab {
  min-width: 0; flex-wrap: wrap; row-gap: 0.15rem; gap: 0.2rem; letter-spacing: 0;
  padding-left: 0.3rem; padding-right: 0.3rem;
}
body.f-filesDeuxParLigne #queues-bar.is-docked .qbar-tab-label { font-size: 0.79rem; }
body.f-filesDeuxParLigne #queues-bar.is-docked .qbar-tab-count { margin-left: 0; padding: 1px 3px; }
/* Repliée dans la colonne : le bouton pilule prend la largeur, il ne va plus
   se coller en bas à gauche de l'écran. */
#queues-bar.is-docked.is-collapsed {
  position: static; padding: 0; border: none; background: none; pointer-events: auto;
}
#queues-bar.is-docked.is-collapsed .qbar-toggle { width: 100%; justify-content: center; }
#queues-bar.is-collapsed {
  right: auto; background: none; border-top: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
  padding: 0 0 10px 8px; pointer-events: none;   /* les clics passent à côté du bouton */
}
#queues-bar.is-collapsed .qbar-toggle { pointer-events: auto; }
.qbar-toggle {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.4rem 0.85rem; border-radius: 999px;
  background: rgba(13, 16, 32, 0.92); border: 1px solid var(--border);
  color: var(--text2, #b7c0da); font-family: var(--hud); font-size: 0.68rem;
  letter-spacing: 0.05em; cursor: pointer;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); transition: color .15s, border-color .15s, transform .12s;
}
.qbar-toggle:hover { color: var(--text1, #e5e8f5); border-color: var(--accent2, #0ea5e9); transform: translateY(-1px); }
.qbar-toggle.is-active { border-color: var(--accent2, #0ea5e9); color: var(--text1, #e5e8f5); }
.qbar-toggle-ico { font-size: 0.95rem; line-height: 1; }
.qbar-toggle-badge {
  min-width: 1.15rem; height: 1.15rem; padding: 0 0.3rem; border-radius: 999px;
  background: var(--accent2, #0ea5e9); color: #fff; font-family: var(--mono);
  font-size: 0.62rem; display: inline-flex; align-items: center; justify-content: center;
}
.qbar-toggle-chevron { font-size: 0.58rem; opacity: 0.65; }
/* En-tête de la barre déployée : titre + bouton Réduire */
.qbar-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.45rem; }
.qbar-header-title {
  font-family: var(--hud); font-size: 0.62rem; letter-spacing: 0.12em;
  color: var(--text2, #b7c0da); text-transform: uppercase;
}
.qbar-collapse-btn {
  background: none; border: 1px solid var(--border); color: var(--text2, #b7c0da);
  font-family: var(--hud); font-size: 0.6rem; letter-spacing: 0.05em;
  padding: 0.2rem 0.6rem; border-radius: 5px; cursor: pointer; transition: color .15s, border-color .15s;
}
.qbar-collapse-btn:hover { color: var(--text1, #e5e8f5); border-color: var(--accent2, #0ea5e9); }

/* Responsive : sur écran étroit, la grille se réduit à 5 colonnes ×2 lignes */
@media (max-width: 980px) {
  .qbar-slots { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
@media (max-width: 540px) {
  .qbar-slots { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .qbar-tab-label { display: none; }   /* on ne garde que l'icône sur mobile */
}

/* ─── Vue globale du VM (#sec-overview) ────────────────────────── */
#grid-overview { padding: 0.6rem 0 0; }
.ov-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.85rem;
}
@media (max-width: 920px) {
  .ov-grid { grid-template-columns: 1fr; }
}
.ov-section {
  background: var(--card, rgba(255,255,255,0.02));
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.85rem 0.95rem;
}
.ov-section-title {
  font-family: var(--hud, inherit);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text2, #b8c2da);
  margin-bottom: 0.7rem;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.4rem;
}

/* Position */
.ov-position { padding: 0.6rem 0.7rem; border-radius: 6px; margin-bottom: 0.7rem; }
.ov-position.is-anchored { background: rgba(34, 197, 94, 0.08); border: 1px solid rgba(34, 197, 94, 0.3); }
.ov-position.is-transit  { background: rgba(168, 85, 247, 0.08); border: 1px solid rgba(168, 85, 247, 0.3); }
.ov-position.is-drift    { background: rgba(251, 191, 36, 0.08); border: 1px solid rgba(251, 191, 36, 0.3); }
.ov-position-title {
  font-family: var(--hud, inherit);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.4px;
  margin-bottom: 0.25rem;
}
.ov-position-body {
  font-size: 0.78rem;
  color: var(--text1, #e5e8f5);
  line-height: 1.4;
}

/* Jauges */
.ov-gauges { display: flex; flex-direction: column; gap: 0.55rem; }
.ov-gauge-card {
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.5rem 0.65rem;
}
.ov-gauge-label {
  font-size: 0.7rem;
  color: var(--text2, #b8c2da);
  margin-bottom: 0.3rem;
  display: flex;
  justify-content: space-between;
}
.ov-gauge-bar {
  height: 5px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 3px;
  overflow: hidden;
  margin-bottom: 0.25rem;
}
.ov-gauge-fill { height: 100%; transition: width 0.5s; }
.ov-gauge-fill.is-hull   { background: linear-gradient(90deg, #22c55e, #84cc16); }
.ov-gauge-fill.is-energy { background: linear-gradient(90deg, #fbbf24, #f59e0b); }
.ov-gauge-val {
  font-family: var(--mono, monospace);
  font-size: 0.68rem;
  color: var(--text3, #6e7691);
}

/* Grille ressources */
.ov-res-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 0.5rem;
}
.ov-res-card {
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.5rem 0.6rem;
}
.ov-res-head {
  display: flex;
  gap: 0.35rem;
  align-items: center;
  font-size: 0.7rem;
  color: var(--text2, #b8c2da);
  margin-bottom: 0.25rem;
}
.ov-res-ico { font-size: 0.95rem; line-height: 1; }
.ov-res-stock {
  font-family: var(--mono, monospace);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text1, #e5e8f5);
}
/* Files résumées */
.ov-q-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.74rem;
}
.ov-q-row:last-child { border-bottom: none; }
.ov-q-label {
  font-weight: 700;
  color: var(--text2, #b8c2da);
  min-width: 100px;
}
.ov-q-head { color: var(--text1, #e5e8f5); }
.ov-q-time {
  font-family: var(--mono, monospace);
  color: var(--gold, #ffd54f);
  font-size: 0.7rem;
  margin-left: auto;
}
.ov-q-wait {
  font-family: var(--mono, monospace);
  color: var(--text3, #6e7691);
  font-size: 0.66rem;
}
.ov-q-empty {
  color: var(--text3, #6e7691);
  font-style: italic;
  font-size: 0.72rem;
}

/* Alertes */
.ov-alert-line {
  padding: 0.4rem 0;
  font-size: 0.74rem;
  color: var(--text1, #e5e8f5);
  border-bottom: 1px dashed var(--border);
}
.ov-alert-line:last-child { border-bottom: none; }
.ov-alert-empty {
  color: var(--text3, #6e7691);
  font-style: italic;
  font-size: 0.72rem;
}

/* Tag "(en attente)" dans la file legacy */
.q-waiting {
  color: var(--gold, #ffd54f);
  font-family: var(--mono, monospace);
  font-size: 0.62rem;
  font-style: italic;
}

/* ════════════════════════════════════════════════════════════════════
   v1.05 — Refonte vue Recherche : header titre+bonus, nav catégorie
   ════════════════════════════════════════════════════════════════════ */

/* ─── Nouvel en-tête avec titre à gauche + bonus à droite ─────── */
.res-header-v2 {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 1rem;
  align-items: center;
  background: rgba(14, 165, 233, 0.06);
  border: 1px solid var(--accent2);
  border-radius: 6px;
  padding: 0.7rem 1rem;
  margin-bottom: 0.8rem;
}
.res-header-title-v2 {
  font-family: var(--hud);
  font-size: 0.95rem;
  letter-spacing: 0.12em;
  color: var(--accent2);
}

/* Bandeau des 5 mini-cartes de bonus, à droite */

/* Une mini-carte de bonus */

@media (max-width: 1100px) {
  .res-header-v2 { grid-template-columns: 1fr; gap: 0.5rem; }
}

/* ════════════════════════════════════════════════════════════════════
   v1.06 — Variable dynamique --total-header-h
   ════════════════════════════════════════════════════════════════════
   Plus d'écrasement par la resbar, quelle que soit sa hauteur (1, 2 ou
   3 lignes de ressources selon largeur d'écran).

   Le JS /core/layout.js mesure topbar + resbar au runtime et expose
   --total-header-h sur :root. Toutes les vues qui doivent commencer
   sous le header utilisent cette variable au lieu d'un nombre fixe.

   Valeur de secours : 100px (utile si le JS n'a pas encore tourné).
   ════════════════════════════════════════════════════════════════════ */

:root {
  --total-header-h: 100px;   /* fallback ; sera écrasé par layout.js */
}

/* Toutes les vues principales du jeu : on FORCE le padding-top à la
   variable dynamique. Le !important est nécessaire pour battre les
   styles inline "padding-top:88px" présents dans index.html. Quand
   le JS a tourné, --total-header-h est plus grand (typiquement 110px
   ou plus selon le wrap de la resbar), donc le contenu commence
   toujours SOUS le header, jamais derrière. */
#view-galaxy,
#view-ship,
#view-research,
#view-universe,
.dsh-view {
  padding-top: var(--total-header-h) !important;
}

/* Panneau Vaisseaux Mères latéral droit : il commence sous le header */
#vm-panel-right {
  top: var(--total-header-h) !important;
}

/* Bandeau "Aller à un système" en vue Galaxie : juste sous le header */
#system-indicator {
  top: calc(var(--total-header-h) + 4px) !important;
}

/* Panneau "Joueurs présents" en vue Galaxie : juste sous le header,
   à GAUCHE du panneau VMs */
.known-vms-panel {
  top: calc(var(--total-header-h) + 4px) !important;
}

/* (Note v1.06 : #ship-nav et #ship-main n'ont pas besoin de règle top
   dédiée — ils sont enfants de #view-ship qui reçoit le padding-top
   variable plus haut, donc ils commencent automatiquement sous le
   header.) */

/* ─── v1.06 : Bonus de recherche en COLONNE VERTICALE à droite ──── */
/* L'utilisateur a demandé que les 5 mini-cartes de bonus passent en
   colonne verticale collée à droite, plutôt qu'en strip horizontal
   sous la resbar. Les cartes restent compactes mais empilées. */
.res-header-v2 {
  /* On garde grid 2 colonnes (titre à gauche, bonus à droite), mais
     on aligne le tout en haut pour que la colonne verticale puisse
     s'étaler proprement à droite. */
  align-items: start !important;
}







/* En responsive (écran moyen, header en colonne), la strip se replie
   en grille 2 colonnes pour rester compacte. */
@media (max-width: 1100px) {
}

/* ════════════════════════════════════════════════════════════════════
   v1.07 — Refonte vue Recherche : header simple + panneau bonus flottant
   ════════════════════════════════════════════════════════════════════
   Règle d'or : la "Zone A" (topbar + resbar) est SACRÉE. RIEN ne peut
   y entrer, sur AUCUNE vue. Garantie technique : tous les éléments
   visibles du jeu commencent à top >= var(--total-header-h), qui est
   mesuré dynamiquement par /core/layout.js et garantit qu'on ne touche
   jamais la resbar quelle que soit sa hauteur.
   ════════════════════════════════════════════════════════════════════ */

/* Largeur du panneau bonus flottant : variable centralisée pour pouvoir
   l'ajuster en un seul endroit (et pour que la zone des techs sache
   combien de place lui laisser à droite). */
:root {
  --research-bonus-w: 130px;
}

/* ─── Header simple de la vue Recherche (v1.07) ──────────────── */
.res-header-v3 {
  background: rgba(14, 165, 233, 0.06);
  border: 1px solid var(--accent2);
  border-radius: 6px;
  padding: 0.7rem 1rem;
  margin-bottom: 0.8rem;
  /* IMPORTANT : on réserve l'espace à droite, sinon le header passerait
     sous le panneau bonus flottant (qui est en position:fixed, donc
     hors flux). */
  margin-right: calc(var(--research-bonus-w) + 1rem + var(--vm-panel-w, 130px));
}
.res-header-title-v3 {
  font-family: var(--hud);
  font-size: 0.95rem;
  letter-spacing: 0.12em;
  color: var(--accent2);
}

/* ─── Zone des recherches : la place réservée à droite ──────────── */
/* ⚠️ IL N'Y A PLUS DE PANNEAU FLOTTANT À DROITE (retiré en v0.0.149) : ce
   retrait ne réserve donc plus que la colonne des Vaisseaux Mères. La règle
   qui remet ce retrait à zéro, tout en bas du fichier, est CELLE QUI GAGNE
   sur le tableau périodique — elle est écrite plus loin, à spécificité
   égale. */
.res-categories-shrunk {
  padding-right: calc(var(--research-bonus-w) + 1rem + var(--vm-panel-w, 130px));
}

/* ─── Responsive : sur écran moyen, panneau se replie horizontalement ─ */
@media (max-width: 1100px) {
  .res-categories-shrunk {
    padding-right: 0;
  }
  .res-header-v3 {
    margin-right: 0;
  }
}
@media (max-width: 700px) {
  /* Sur mobile, le panneau VMs est en drawer (caché par défaut), donc
     on n'a pas besoin de réserver d'espace à droite. */
  .res-header-v3 {
    margin-right: 0;
  }
}

/* ─── ZONE A SACRÉE — Garde-fou final ──────────────────────────── */
/* Pour garantir absolument que RIEN ne passe sur la zone A (topbar +
   resbar), on impose un z-index élevé sur le header lui-même.
   Tout élément en dessous (z-index < 200) qui viendrait par hasard
   à top:0 sera masqué.
   La topbar est à z-index:200, la resbar à z-index:190 (déjà en place). */
#topbar { z-index: 200 !important; }
#resbar { z-index: 190 !important; }

/* ════════════════════════════════════════════════════════════════════
   v1.08 — Vue Univers zoomable (POC)
   ════════════════════════════════════════════════════════════════════ */

/* Vue principale : full-screen sous le header, à gauche du panneau VMs */
#view-universe {
  position: fixed;
  top: 0;
  left: var(--sidebar-width, 60px);
  right: var(--vm-panel-w, 130px);
  bottom: 0;
  /* padding-top géré par règle globale --total-header-h v1.06 */
  overflow: hidden;
}

/* Wrapper du canvas : remplit la zone disponible */
#universe-canvas-wrapper {
  position: absolute;
  /* Le padding-top de #view-universe pousse déjà sous le header,
     mais on doit s'assurer que ce wrapper reste sous lui aussi. */
  top: var(--total-header-h, 100px);
  left: 0;
  right: 0;
  bottom: 0;
}

#universe-canvas {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
  background: radial-gradient(ellipse at center, #0a0e1f 0%, #03060f 100%);
}

/* Overlay d'infos (en haut à gauche de la vue, semi-transparent) */
.universe-overlay {
  position: absolute;
  top: 12px;
  left: 12px;
  background: rgba(5, 8, 16, 0.85);
  border: 1px solid var(--border, #1f2937);
  border-radius: 6px;
  padding: 0.6rem 0.9rem;
  font-size: 0.72rem;
  color: var(--text2, #b8c2da);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
  max-width: 360px;
  z-index: 10;
}
.uo-hint {
  font-family: var(--hud, sans-serif);
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--accent2, #0ea5e9);
  letter-spacing: 0.06em;
  margin-bottom: 0.25rem;
}
.uo-zoom {
  font-family: var(--mono, monospace);
  font-size: 0.7rem;
  color: var(--gold, #ffd54f);
  margin-bottom: 0.3rem;
}
.uo-help {
  font-size: 0.62rem;
  color: var(--text3, #6e7691);
  line-height: 1.4;
}

/* ─── v0.0.50 : LE BANDEAU « ALLER À » ────────────────────────────────
   Demande de Loryan : chercher un système, une planète ou des coordonnées
   et s'y rendre. Il se pose SOUS l'encart d'aide, en haut à gauche : c'est
   le coin des repères, la légende tient le bas et la colonne des Vaisseaux
   Mères la droite.
   ⚠️ Il CAPTE la souris (contrairement à l'encart d'aide au-dessus) : on
   y tape du texte. Il est donc volontairement étroit, pour laisser la
   carte glissable tout autour. */
#map-recherche {
  display: block;
  position: absolute;
  top: 92px;                        /* juste sous .universe-overlay */
  left: 12px;
  width: 320px;
  max-width: calc(100vw - 90px);
  z-index: 11;
  /* ⚠️ FOND QUASI OPAQUE, contrairement aux autres encarts de la carte.
     Déplié avec ses huit propositions, ce panneau descend jusque sur la
     légende (qui, elle, part du bas et monte haut sur un grand écran).
     Il passe devant — mais un fond translucide laissait le texte de la
     légende transparaître à travers le sien, et les deux devenaient
     illisibles ensemble. L'ombre portée finit de dire lequel est devant. */
  background: rgba(5, 8, 16, 0.97);
  border: 1px solid var(--border, #1f2937);
  border-radius: 6px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--text2, #b8c2da);
  font-size: 0.72rem;
}
.rch-poignee {
  display: flex; align-items: center; gap: 0.4rem;
  width: 100%; background: none; border: 0; cursor: pointer;
  padding: 0.42rem 0.7rem;
  color: var(--accent2, #0ea5e9);
  font-family: var(--hud, sans-serif);
  font-size: 0.73rem; font-weight: 700; letter-spacing: 0.05em;
}
.rch-poignee:hover { color: #7dd3fc; }
.rch-titre { flex: 1; text-align: left; }
.rch-chevron { font-size: 0.62rem; opacity: 0.75; }
.rch-corps { padding: 0 0.7rem 0.6rem; }
.rch-ligne { display: flex; gap: 0.4rem; }
.rch-champ {
  flex: 1; min-width: 0;
  background: rgba(12, 18, 30, 0.9);
  border: 1px solid var(--border, #1f2937);
  border-radius: 4px; padding: 0.32rem 0.5rem;
  color: var(--text, #eaf2ff); font-size: 0.74rem;
}
.rch-champ:focus { outline: none; border-color: var(--accent2, #0ea5e9); }
/* Le bouton reprend le gabarit commun mais pas sa robe claire : à côté
   d'un champ sombre, un bloc blanc tirait tout l'œil sur lui alors que
   l'action se lit aussi bien avec la touche Entrée. */
.rch-valider {
  height: auto; padding: 0.3rem 0.6rem; font-size: 0.72rem; white-space: nowrap;
  background: rgba(14, 165, 233, 0.16);
  border: 1px solid rgba(14, 165, 233, 0.45);
  color: #7dd3fc;
}
.rch-valider:hover { background: rgba(14, 165, 233, 0.28); color: #e0f2fe; }
/* Les propositions : au plus huit, donc jamais de barre de défilement en
   pratique — la borne est là pour le jour où elle changerait. */
.rch-resultats { display: flex; flex-direction: column; max-height: 210px; overflow-y: auto; }
.rch-resultats:not(:empty) { margin-top: 0.4rem; }
.rch-res {
  display: flex; align-items: baseline; gap: 0.4rem;
  width: 100%; text-align: left; cursor: pointer;
  background: none; border: 0; border-top: 1px solid rgba(31, 41, 55, 0.7);
  padding: 0.3rem 0.15rem; color: var(--text2, #b8c2da); font-size: 0.72rem;
}
.rch-res:first-child { border-top: 0; }
.rch-res:hover { background: rgba(14, 165, 233, 0.12); color: var(--text, #eaf2ff); }
.rch-res-nom { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rch-res-type { font-size: 0.62rem; opacity: 0.55; }
.rch-res-coord { font-family: var(--mono, monospace); font-size: 0.66rem; color: var(--gold, #ffd54f); }
.rch-message { font-size: 0.68rem; line-height: 1.35; }
.rch-message:not(:empty) { margin-top: 0.4rem; }
.rch-ok { color: #6ee7b7; }
.rch-ko { color: #fca5a5; }
.rch-aide { margin-top: 0.45rem; font-size: 0.62rem; color: var(--text3, #6e7691); line-height: 1.4; }

/* ─── Légende de la carte ────────────────────────────────────────────
   En BAS À GAUCHE (demande de Loryan) : le bandeau d'aide occupe déjà le
   haut à gauche, et la colonne des Vaisseaux Mères la droite.
   Le panneau NE CAPTE PAS la souris — on doit pouvoir glisser la carte au
   travers — sauf son bouton de repli, qui la capte, lui. */
.map-legende {
  position: absolute;
  bottom: 12px;
  left: 12px;
  background: rgba(5, 8, 16, 0.88);
  border: 1px solid var(--border, #1f2937);
  border-radius: 6px;
  padding: 0.45rem 0.7rem 0.55rem;
  color: var(--text2, #b8c2da);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
  width: 320px;
  max-width: calc(100vw - 90px);
  z-index: 10;
}
.mlg-bascule {
  pointer-events: auto;            /* la seule zone cliquable du panneau */
  display: flex;
  align-items: center;
  gap: 0.35rem;
  width: 100%;
  background: none;
  border: 0;
  padding: 0 0 0.15rem;
  font-family: var(--hud, sans-serif);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--accent2, #0ea5e9);
  text-align: left;
  cursor: pointer;
}
.mlg-bascule:hover { filter: brightness(1.2); }
.mlg-bascule:hover .mlg-chevron { background: rgba(14, 165, 233, 0.28); }
/* La flèche est un VRAI bouton visible (remarque de Loryan : le petit
   chevron fondu dans le titre passait inaperçu) : une pastille bordée,
   calée à droite de l'en-tête, assez grande pour le doigt sur mobile. */
.mlg-chevron {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: 1px solid var(--accent2, #0ea5e9);
  border-radius: 4px;
  background: rgba(14, 165, 233, 0.12);
  font-size: 0.55rem;
  line-height: 1;
  transition: background 0.15s;
}

.mlg-bloc { margin-top: 0.45rem; }
/* v0.0.32 : la case à cocher d'un filtre de couche (flag `filtresCarte`).
   Elle vit DANS le titre d'un bloc ou dans une ligne de la légende — assez
   grosse pour le doigt, alignée sur le texte, aux couleurs du panneau. */
.mlg-case {
  /* ⚠️ `pointer-events: auto` EST INDISPENSABLE ICI, et son absence a rendu
     les cases INERTES à la livraison (défaut signalé par Loryan, vidéo à
     l'appui : elles s'affichaient, aucun clic ne les cochait). Le panneau
     de légende entier est en `pointer-events: none` — c'est volontaire et
     ancien : il recouvre le coin bas-gauche de la carte, et sans ça on ne
     pourrait plus ni cliquer une planète ni faire glisser la vue à travers
     lui. Une seule zone reprenait la main jusqu'ici, le bouton de repli
     (.mlg-bascule) ; les cases sont la seconde.
     ⚠️ ET C'EST POURQUOI LA VÉRIFICATION AUTOMATISÉE N'AVAIT RIEN VU : elle
     cliquait via `element.click()` en JavaScript, qui appelle le
     gestionnaire DIRECTEMENT et IGNORE `pointer-events`. Un test comme
     celui-là ne pouvait pas échouer sur ce défaut. Toute vérification de
     zone cliquable doit poser un vrai clic AUX COORDONNÉES (mouse.click)
     et contrôler avec `elementFromPoint` qui le reçoit. */
  pointer-events: auto;
  width: 15px; height: 15px; margin: 0 6px 0 0; vertical-align: -3px;
  accent-color: #6ee7f9; cursor: pointer;
  position: relative;
}
/* Une cible confortable au doigt sans grossir le dessin : la zone
   réellement cliquable déborde de 6 px tout autour (Loryan joue aussi au
   téléphone, où une case de 15 px se rate une fois sur deux). */
.mlg-case::after { content: ''; position: absolute; inset: -6px; }

/* v0.0.49 : les cinq tranches du filtre de périclitement. Deux colonnes —
   cinq lignes pleine largeur allongeraient le panneau d'autant, et ces
   libellés sont courts. ⚠️ `pointer-events: auto` sur le label ET sa case,
   pour la même raison que ci-dessus : le panneau entier est inerte au clic,
   chaque zone active doit reprendre la main explicitement. */
.mlg-peri-liste {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 8px;
  margin: 2px 0 4px;
}
.mlg-peri-case {
  pointer-events: auto;
  display: flex; align-items: center;
  font-size: 0.66rem; color: var(--text2, #b6c6da);
  cursor: pointer; white-space: nowrap;
}
.mlg-peri-case:hover { color: var(--text, #e8f0fa); }
.mlg-peri-case .mlg-case { margin-right: 4px; }
.mlg-bloc-titre {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text3, #6e7691);
  margin-bottom: 0.22rem;
}
.mlg-cond { text-transform: none; font-weight: 400; letter-spacing: 0; }

.mlg-ligne {
  display: flex;
  align-items: flex-start;
  gap: 0.45rem;
  margin-bottom: 0.22rem;
}
/* Les témoins reprennent la FORME du dessin de la carte, pas seulement sa
   couleur : c'est un triangle ou un anneau qu'on cherche des yeux à
   l'écran, pas « du vert ». Largeur fixe pour que les textes s'alignent. */
.mlg-glyphe {
  flex: 0 0 auto;
  min-width: 0.85rem;
  font-size: 0.72rem;
  line-height: 1.3;
  text-align: center;
}
/* Le trait pointillé des deux zones du Scanner, à l'identique du canvas. */
.mlg-trait {
  flex: 0 0 24px;
  height: 0;
  margin-top: 0.4rem;
  border-top: 1px dashed var(--mlg-c, #22c55e);
}
/* La pastille chiffrée des VM ancrés, reprise telle qu'elle est peinte. */
.mlg-pastille {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-top: 0.05rem;
  border-radius: 50%;
  background: rgba(18, 26, 48, 0.92);
  border: 1px solid rgba(130, 165, 235, 0.75);
  color: #e6edff;
  font-size: 0.55rem;
  font-weight: 600;
}
.mlg-txt { font-size: 0.66rem; line-height: 1.35; }
.mlg-txt b { color: var(--text, #dbe4f5); font-weight: 600; }
.mlg-sous { display: block; color: var(--text3, #6e7691); font-size: 0.61rem; }

/* Les familles de mission : cinq teintes sur UNE ligne. Sept lignes
   doubleraient la hauteur du panneau pour ce qui se lit d'un coup d'œil. */
.mlg-familles {
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.5rem;
  font-size: 0.61rem;
  color: var(--text3, #6e7691);
}
.mlg-fam { margin-left: 0.15rem; }

@media (max-width: 700px) {
  .map-legende { width: 200px; padding: 0.35rem 0.5rem 0.45rem; }
  .mlg-sous { display: none; }
}

/* Bouton "Retour" en haut à droite quand on est zoomé */
.uo-back-btn {
  position: absolute;
  top: 12px;
  right: 12px;
  background: rgba(14, 165, 233, 0.15);
  color: var(--accent2, #0ea5e9);
  border: 1px solid var(--accent2, #0ea5e9);
  border-radius: 5px;
  padding: 0.45rem 0.85rem;
  font-family: var(--hud, sans-serif);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  cursor: pointer;
  z-index: 11;
  transition: all 0.15s;
}
.uo-back-btn:hover {
  background: rgba(14, 165, 233, 0.28);
  filter: brightness(1.15);
}

/* Mobile : sidebar plus étroite et panneau VMs caché */
@media (max-width: 700px) {
  #view-universe {
    left: var(--sidebar-width, 60px);
    right: 0;
  }
  .universe-overlay {
    font-size: 0.62rem;
    padding: 0.4rem 0.6rem;
    max-width: 200px;
  }
  .uo-help { display: none; }  /* on cache l'aide texte sur mobile */
  /* v0.0.50 : sans la ligne d'aide, l'encart du dessus est bien plus court
     — le bandeau « Aller à » remonte d'autant, sinon il flotte tout seul
     au milieu du vide. */
  #map-recherche { top: 58px; width: 260px; }
}

/* ════════════════════════════════════════════════════════════════════
   v1.17 — Refonte topbar en 3 bandeaux empilés
   ════════════════════════════════════════════════════════════════════
   Décisions :
   - BANDEAU 1 (#topbar)    : méta (logo, role, vacances, modération, admin, heure, commandant)
   - BANDEAU 2 (#resbar)    : TOUTES les ressources (rares incluses, même à 0)
   - BANDEAU 3 (#prod-hint) : alertes contextuelles (extracteur manquant, périclitement...)
   - BANDEAU 4              : contenu de la vue active (déjà géré par --total-header-h)
   ════════════════════════════════════════════════════════════════════ */

/* ── v1.17 : retrait des pills méta-progression de la topbar ──
   (v0.0.41 : les trois mécaniques qui les portaient — Reliques,
   Intelligence Collective, Heritage — ont toutes été retirées du jeu
   depuis. Il ne reste que la règle de mise en page.)
   (purge cette semaine : `#heritage-pill-host` retiré de cette liste — le
   div lui-même a disparu de index.html et sa fonction de rendu,
   `renderHeritagePanel`, n'a plus d'appelant nulle part.) */
#ic-pill-host {
  display: none !important;
}

/* ── v1.17 : positionnement dynamique du bandeau d'alertes #prod-hint ──
   Avant v1.17 : top:88px hardcodé → chevauchement quand la resbar est
   plus haute (ressources rares maintenant toujours visibles).
   Désormais : top calculé en s'appuyant sur la variable --resbar-bottom
   (calculée dynamiquement par layout.js). */
#prod-hint {
  /* v1.18 : décalage vers le bas de 8px pour respiration visuelle
     entre le bandeau et la resbar (bandeau flottant compact). */
  top: calc(var(--resbar-bottom, 88px) + 8px);
}

/* v1.17 : ressources rares désormais TOUJOURS visibles (override le
   display:none inline du HTML). Le joueur voit toutes ses ressources
   en permanence, qu'elles soient à 0 ou non. */
.res.res-rare {
  display: flex !important;
}

/* ════════════════════════════════════════════════════════════════════
   v1.21 — Rampe de Lancement (UI joueur, étape 2/3)
   ════════════════════════════════════════════════════════════════════
   Préfixe .fl- pour tous les sélecteurs (fleet-launcher), aucun risque
   de collision avec les .fleet-* existants (production de vaisseaux
   dans la vue VM). La plomberie serveur a été livrée en v1.20.

   Structure :
   - .fl-view-body         → corps de la vue dsh-view-fleet (sidebar)
   - .fl-intro             → bandeau d'intro + bouton "Composer"
   - .fl-section           → "Mes flottes en l'air" / "Historique"
   - .fl-mission-row       → ligne d'une mission avec CDR
   - .fl-modal-overlay     → grande modale de composition
   - .fl-cols              → 2 colonnes (compo gauche / briefing droite)
   - .fl-ships-grid        → liste vaisseaux avec −/+/MAX
   - .fl-cargo-gauge       → jauge horizontale capacité/utilisé
   - .fl-speed-slider      → slider vitesse 10-100 par paliers de 10
   - .fl-briefing          → panneau récap (durée, conso, retour…)
   ════════════════════════════════════════════════════════════════════ */

/* ── Vue Rampe (dsh-view-fleet) ── */
.fl-view-body {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 24px 40px;
  color: var(--color-text-primary, #cbd6ea);
}
.fl-intro {
  background: rgba(10, 15, 28, 0.6);
  border: 1px solid var(--color-border-subtle, #324569);
  border-radius: 8px;
  padding: 16px 18px;
  margin-bottom: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.fl-intro p {
  margin: 0;
  color: #8da4c4;
  font-size: 0.9rem;
  max-width: 640px;
  line-height: 1.5;
}
.fl-section {
  margin-bottom: 24px;
}
.fl-section-title {
  font-size: 0.95rem;
  font-weight: 600;
  color: #cbd6ea;
  margin-bottom: 10px;
  letter-spacing: 0.02em;
}
.fl-empty {
  padding: 16px;
  color: #8da4c4;
  font-style: italic;
  text-align: center;
  background: rgba(10, 15, 28, 0.4);
  border: 1px dashed #324569;
  border-radius: 6px;
}

/* ── Liste de missions ── */
.fl-missions-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.fl-mission-row {
  background: rgba(10, 15, 28, 0.7);
  border: 1px solid #324569;
  border-left: 3px solid #324569;
  border-radius: 6px;
  padding: 10px 14px;
}
.fl-mission-row.is-outbound {
  border-left-color: #f97316; /* orange — en route */
}
.fl-mission-row.is-returning {
  border-left-color: #3b82f6; /* bleu — retour */
}
.fl-mission-row.is-done {
  opacity: 0.6;
}
.fl-mission-row.is-cancelled {
  opacity: 0.4;
}
.fl-mission-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 6px;
  font-size: 0.85rem;
}
.fl-mission-badge {
  font-family: var(--mono, monospace);
  font-size: 0.7rem;
  padding: 2px 8px;
  border-radius: 3px;
  letter-spacing: 0.05em;
  background: #1a2540;
  color: #cbd6ea;
}
.fl-mission-badge.is-outbound { background: #7c2d12; color: #fed7aa; }
.fl-mission-badge.is-returning { background: #1e3a8a; color: #bfdbfe; }
.fl-mission-badge.is-done { background: #1f2937; color: #9ca3af; }
.fl-mission-badge.is-cancelled { background: #4c1d24; color: #fca5a5; }
.fl-mission-type {
  font-size: 0.8rem;
  color: #8da4c4;
  text-transform: capitalize;
}
.fl-mission-coords {
  font-family: var(--mono, monospace);
  color: #fed7aa;
}
.fl-mission-speed {
  margin-left: auto;
  font-size: 0.75rem;
  color: #8da4c4;
  font-family: var(--mono, monospace);
}
.fl-mission-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  font-size: 0.8rem;
  color: #cbd6ea;
  margin-bottom: 6px;
}
.fl-mission-ships { flex: 1; }
.fl-mission-cargo {
  font-family: var(--mono, monospace);
  color: #fed7aa;
}
.fl-mission-cdr {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 4px;
}
.fl-cdr-bar {
  flex: 1;
  height: 4px;
  background: #1a2540;
  border-radius: 2px;
  overflow: hidden;
}
.fl-cdr-fill {
  height: 100%;
  background: linear-gradient(90deg, #f97316, #fbbf24);
  transition: width 1s linear;
}
.fl-mission-row.is-returning .fl-cdr-fill {
  background: linear-gradient(90deg, #3b82f6, #60a5fa);
}
.fl-cdr-text {
  font-family: var(--mono, monospace);
  font-size: 0.78rem;
  color: #cbd6ea;
  min-width: 160px;
  text-align: right;
}

/* ── Modale Rampe de Lancement ── */
/* v1.22 fix systémique : la classe générique .modal-overlay (utilisée par
   pm.js, guilds.js, moderation.js, et la Rampe) n'avait AUCUNE règle CSS.
   Résultat : toutes ces modales s'affichaient dans le flux du document
   (tout en bas de la page) au lieu d'en overlay plein écran. Pire : elles
   restaient visibles quand on changeait de vue (Social, Recherche, etc.).
   Fix : règle générique .modal-overlay qui couvre toutes les modales
   utilisant cette classe seule. Les modales préfixées (.expedition-modal-overlay,
   gardent leurs règles spécifiques inchangées —
   elles déclarent déjà position:fixed. */
.modal-overlay {
  position: fixed;
  top: 0; left: 0;
  width: 100vw; height: 100vh;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}
.modal-box {
  background: var(--bg, #0a0e1c);
  border: 1px solid #324569;
  border-radius: 8px;
  max-width: 600px;
  width: 90%;
  max-height: 85vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}
.modal-header {
  padding: 12px 16px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.modal-title {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--color-text-primary, #cbd6ea);
}
.modal-subtitle {
  font-size: 0.8rem;
  color: var(--color-text-secondary, #8da4c4);
  margin-top: 2px;
}
/* v0.0.162 — UN PSEUDO DE 30 CARACTÈRES SANS ESPACE TIENT DANS LE TITRE.
   ⚠️ `.modal-box` est en `overflow: hidden` : un mot plus large que la
   boîte ne déborde pas, il est COUPÉ, et rien ne le signale. Un pseudo
   sans espace est un mot insécable — à 390 px de large la fenêtre fait
   environ 351 px, et trente caractères de titre y touchent le bord.
   La règle vaut pour toutes les fenêtres du jeu. */
.modal-title { overflow-wrap: anywhere; }
.modal-close {
  background: transparent;
  border: none;
  color: var(--color-text-secondary, #8da4c4);
  font-size: 1.4rem;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}
.modal-close:hover { color: var(--color-text-primary, #cbd6ea); }
.modal-body {
  padding: 16px;
  overflow-y: auto;
  flex: 1;
}

/* ── Spécifique Rampe : box plus large que la modale par défaut ── */
.fl-modal-overlay {
  /* hérite tout de .modal-overlay ci-dessus */
}
.fl-modal-box {
  max-width: 980px;
  width: 95vw;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
}
.fl-modal-body {
  overflow-y: auto;
  padding: 16px 18px 18px;
}

/* ── Bandeau du haut : VM source + coords + type ── */
.fl-top-bar {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 12px;
  margin-bottom: 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid #324569;
}
.fl-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.fl-label {
  font-size: 0.72rem;
  color: #8da4c4;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.fl-select {
  width: 100%;
  padding: 6px 10px;
  background: #0a0e1c;
  border: 1px solid #324569;
  color: #cbd6ea;
  border-radius: 4px;
  font-size: 0.85rem;
}
.fl-coords-input {
  display: flex;
  align-items: center;
  gap: 4px;
  background: #0a0e1c;
  border: 1px solid #324569;
  border-radius: 4px;
  padding: 4px 8px;
}
.fl-coord-cell {
  width: 60px;
  padding: 4px 6px;
  background: transparent;
  border: none;
  color: #fed7aa;
  font-family: var(--mono, monospace);
  font-size: 0.95rem;
  text-align: center;
  outline: none;
}
.fl-coord-cell::-webkit-outer-spin-button,
.fl-coord-cell::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.fl-coord-sep {
  color: #8da4c4;
  font-weight: bold;
}

/* ── Corps 2 colonnes ── */
.fl-cols {
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: 16px;
  align-items: start;
}
@media (max-width: 800px) {
  .fl-top-bar {
    grid-template-columns: 1fr;
  }
  .fl-cols {
    grid-template-columns: 1fr;
  }
}
.fl-col-left {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}
.fl-col-right {
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: sticky;
  top: 0;
}

/* ── Blocs (vaisseaux, cargo, vitesse) ── */
.fl-block {
  background: rgba(10, 15, 28, 0.5);
  border: 1px solid #324569;
  border-radius: 6px;
  padding: 12px 14px;
}
.fl-block-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: #cbd6ea;
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.fl-block-hint {
  font-size: 0.7rem;
  color: #8da4c4;
  font-weight: 400;
  font-family: var(--mono, monospace);
}

/* ── Grille de vaisseaux ── */
.fl-ships-grid {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 280px;
  overflow-y: auto;
}
.fl-ship-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  background: #0a0e1c;
  border: 1px solid #1f2a44;
  border-radius: 4px;
}
.fl-ship-info {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.fl-ship-ico { font-size: 1.1rem; }
.fl-ship-name {
  color: #cbd6ea;
  font-size: 0.85rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fl-ship-stock {
  color: #8da4c4;
  font-size: 0.75rem;
  font-family: var(--mono, monospace);
}
.fl-ship-controls {
  display: flex;
  align-items: center;
  gap: 4px;
}
.fl-btn-step {
  width: 26px;
  height: 26px;
  background: #1a2540;
  border: 1px solid #324569;
  color: #cbd6ea;
  border-radius: 3px;
  cursor: pointer;
  font-weight: bold;
}
.fl-btn-step:hover { background: #243150; }
.fl-ship-input,
.fl-cargo-input {
  width: 70px;
  padding: 4px 6px;
  background: #1a2540;
  border: 1px solid #324569;
  color: #cbd6ea;
  border-radius: 3px;
  text-align: center;
  font-family: var(--mono, monospace);
  font-size: 0.85rem;
}
.fl-btn-max {
  padding: 4px 8px;
  background: #1a2540;
  border: 1px solid #324569;
  color: #fed7aa;
  border-radius: 3px;
  cursor: pointer;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.05em;
}
.fl-btn-max:hover { background: #243150; }

/* ── Jauge cargo ── */
.fl-cargo-gauge {
  position: relative;
  height: 22px;
  background: #0a0e1c;
  border: 1px solid #324569;
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 12px;
}
.fl-cargo-fill {
  height: 100%;
  background: linear-gradient(90deg, #10b981, #34d399);
  transition: width 0.2s ease;
}
.fl-cargo-fill.is-overflow {
  background: linear-gradient(90deg, #ef4444, #f87171);
}
.fl-cargo-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono, monospace);
  font-size: 0.78rem;
  color: #fff;
  text-shadow: 0 0 4px rgba(0,0,0,0.7);
}
.fl-cargo-label.is-overflow {
  color: #fee2e2;
}
/* v0.0.39 — LA SECONDE JAUGE : la soute à rares du Convoyeur Raréfié.
   Même dessin que la jauge cargo (c'est la même idée), mais bleutée et
   titrée : deux barres vertes identiques l'une sous l'autre feraient
   croire à une seule mesure coupée en deux. */
.fl-rare-gauge-wrap { margin-top: -4px; }
.fl-rare-titre {
  font-family: var(--hud, sans-serif); font-size: .62rem; letter-spacing: .08em;
  text-transform: uppercase; color: #7ec4ff; margin-bottom: 4px;
}
.fl-rare-gauge-wrap .fl-cargo-fill {
  background: linear-gradient(90deg, #2f74b5, #7ec4ff);
}
.fl-rare-gauge-wrap .fl-cargo-fill.is-overflow {
  background: linear-gradient(90deg, #ef4444, #f87171);
}
.fl-cargo-row.fl-cargo-row-rare {
  border-color: rgba(126,196,255,.38);
  background: rgba(126,196,255,.06);
}

.fl-cargo-grid {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.fl-cargo-row {
  display: grid;
  grid-template-columns: 1fr auto 80px auto;
  align-items: center;
  gap: 8px;
  padding: 4px 6px;
  background: #0a0e1c;
  border: 1px solid #1f2a44;
  border-radius: 4px;
}
.fl-cargo-res {
  color: #cbd6ea;
  font-size: 0.85rem;
}
.fl-cargo-stock {
  color: #8da4c4;
  font-size: 0.75rem;
  font-family: var(--mono, monospace);
}

/* ── Slider vitesse ── */
.fl-speed-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.fl-speed-slider {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 6px;
  background: linear-gradient(90deg, #1f2a44 0%, #f97316 100%);
  border-radius: 3px;
  outline: none;
}
.fl-speed-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  background: #fed7aa;
  border: 2px solid #f97316;
  border-radius: 50%;
  cursor: pointer;
}
.fl-speed-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  background: #fed7aa;
  border: 2px solid #f97316;
  border-radius: 50%;
  cursor: pointer;
}
.fl-speed-value {
  min-width: 56px;
  text-align: center;
  font-family: var(--mono, monospace);
  font-size: 1rem;
  font-weight: 600;
  color: #fed7aa;
  background: #0a0e1c;
  border: 1px solid #324569;
  border-radius: 4px;
  padding: 4px 8px;
}

/* ── Briefing live (colonne droite) ── */
.fl-briefing {
  background: rgba(10, 15, 28, 0.7);
  border: 1px solid #324569;
  border-radius: 6px;
  padding: 14px;
}
.fl-briefing-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: #cbd6ea;
  margin-bottom: 12px;
}
.fl-briefing-grid {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.fl-brief-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.8rem;
  padding: 3px 0;
}
.fl-brief-k { color: #8da4c4; }
.fl-brief-v {
  color: #cbd6ea;
  font-family: var(--mono, monospace);
  font-weight: 500;
}
.fl-brief-v.is-err { color: #ef4444; }
.fl-brief-sep {
  height: 1px;
  background: #1f2a44;
  margin: 6px 0;
}
.fl-briefing-wait,
.fl-briefing-err {
  text-align: center;
  font-size: 0.8rem;
  color: #8da4c4;
  font-style: italic;
  padding: 12px 4px;
}
.fl-briefing-err {
  color: #ef4444;
  font-style: normal;
}

/* ── Bouton lancement ── */
.fl-launch-btn {
  width: 100%;
  padding: 12px;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}
.fl-launch-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.fl-launch-error {
  padding: 8px 12px;
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid #ef4444;
  border-radius: 4px;
  color: #fca5a5;
  font-size: 0.8rem;
  text-align: center;
}

/* ════════════════════════════════════════════════════════════════════
   v1.22 — Rampe de Lancement, ajouts (Étape 3/3)
   ════════════════════════════════════════════════════════════════════
   - .fl-cancel-btn       → bouton "Annuler" sur les missions outbound
   - .fl-mission-outcome  → message de résultat sur les missions completed
   ════════════════════════════════════════════════════════════════════ */

.fl-cancel-btn {
  margin-left: 8px;
  padding: 4px 10px;
  background: #4c1d24;
  border: 1px solid #ef4444;
  color: #fca5a5;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.75rem;
  font-weight: 500;
}
.fl-cancel-btn:hover {
  background: #7c2d12;
  color: #fed7aa;
  border-color: #f97316;
}

.fl-mission-outcome {
  margin-top: 8px;
  padding: 6px 10px;
  background: rgba(10, 15, 28, 0.5);
  border-left: 2px solid #3b82f6;
  border-radius: 0 4px 4px 0;
  font-size: 0.8rem;
  color: #cbd6ea;
  font-style: italic;
}

/* ════════════════════════════════════════════════════════════════════
   v1.24 — Refonte vue Vaisseau Mère : éclatement du module Militaire
   ════════════════════════════════════════════════════════════════════
   Composants ajoutés :
   - .ship-tabs / .ship-tab     → onglets horizontaux internes (sous-cat)
   - .ship-cat-header           → bandeau capacité + expéditions (au-dessus)
   - .ship-card-compact         → carte vaisseau format OGame-like
   - .ship-card-ico-col         → colonne icône à gauche
   - .ship-card-info-col        → colonne stats à droite
   - .ship-qty-input            → input numérique pour la quantité
   - .ship-build-btn-compact    → bouton Produire compact
   ════════════════════════════════════════════════════════════════════ */

/* Onglets horizontaux dans Vaisseaux / Défenses (sous-catégories) */
.ship-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 12px;
  padding: 4px;
  background: rgba(0, 0, 0, 0.2);
  border-radius: 6px;
}
.ship-tab {
  flex: 1;
  min-width: 140px;
  padding: 8px 14px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text2, #8da4c4);
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 500;
  transition: all 0.15s;
  text-align: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.ship-tab:hover {
  background: rgba(14, 165, 233, 0.08);
  color: var(--text, #cbd6ea);
}
.ship-tab.active {
  background: rgba(14, 165, 233, 0.15);
  border-color: var(--accent2, #0ea5e9);
  color: var(--accent2, #0ea5e9);
  font-weight: 600;
}
.ship-tab-count {
  display: inline-block;
  min-width: 24px;
  padding: 1px 7px;
  background: rgba(245, 158, 11, 0.15);
  color: var(--gold, #f59e0b);
  border-radius: 10px;
  font-size: 0.72rem;
  font-weight: 600;
}

/* Bandeau au-dessus de la grille (capacité Hangar/Parc + expéditions) */
.ship-cat-header {
  margin-bottom: 12px;
}

/* Carte compacte OGame-like : icône à gauche, stats à droite */
.fleet-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
  gap: 12px;
}
.ship-card-compact {
  display: flex;
  gap: 14px;
  padding: 12px 14px;
  background: rgba(10, 14, 28, 0.6);
  border: 1px solid var(--border2, #324569);
  border-radius: 6px;
  transition: border-color 0.15s;
}
.ship-card-compact:hover {
  border-color: var(--accent2, #0ea5e9);
}
.ship-card-compact.locked {
  opacity: 0.55;
}
.ship-card-compact.locked:hover {
  border-color: var(--border2, #324569);
}

/* Colonne icône à gauche : grosse icône + ×stock dessous */
.ship-card-ico-col {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 64px;
}
.ship-card-ico-big {
  font-size: 2.4rem;
  line-height: 1;
}
.ship-card-owned-tag {
  display: inline-block;
  padding: 2px 8px;
  background: rgba(245, 158, 11, 0.15);
  color: var(--gold, #f59e0b);
  border-radius: 10px;
  font-size: 0.75rem;
  font-weight: 600;
  font-family: var(--mono, monospace);
}

/* Colonne infos à droite : nom + desc + stats + bouton */
.ship-card-info-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;  /* permet le text-overflow sur les enfants */
}
.ship-card-name {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text, #cbd6ea);
}
.ship-card-desc {
  font-size: 0.75rem;
  color: var(--text2, #8da4c4);
  line-height: 1.3;
}
.ship-card-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  font-size: 0.75rem;
  color: var(--text2, #8da4c4);
  font-family: var(--mono, monospace);
}
.ship-card-stats > span {
  white-space: nowrap;
}
.ship-card-stats .ship-queue-tag {
  color: var(--accent2, #0ea5e9);
  font-weight: 500;
}
.ship-card-req {
  font-size: 0.72rem;
  color: #f59e0b;
  margin-top: 2px;
}

/* ─── v0.0.69 : la pastille Mk et son bouton ─────────────────────────
   ⚠️ NOMS PRÉFIXÉS `mk-`, et c'est délibéré. `.pastille` et `.badge`
   existent déjà ailleurs dans cette feuille : réutiliser un nom court,
   c'est hériter de marges écrites pour un autre écran. La collision
   `.part` du Codex avait déjà coûté un aller-retour au navigateur
   (v0.0.65) — même famille de piège, même parade. */
.mk-ligne {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 5px;
  flex-wrap: wrap;
}
.mk-pastille {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: .04em;
  padding: 1px 7px;
  border-radius: 9px;
  color: #7dd3fc;
  background: rgba(56, 189, 248, 0.10);
  border: 1px solid rgba(56, 189, 248, 0.35);
}
.mk-max {
  font-size: 0.68rem;
  color: #64748b;
}
.mk-btn {
  font-size: 0.68rem;
  padding: 2px 9px;
  border-radius: 9px;
  cursor: pointer;
  color: #7dd3fc;
  background: rgba(56, 189, 248, 0.06);
  border: 1px solid rgba(56, 189, 248, 0.30);
  transition: background .15s, border-color .15s;
}
.mk-btn:hover { background: rgba(56, 189, 248, 0.16); border-color: rgba(56, 189, 248, 0.55); }
/* Bloqué par les recherches : il reste CLIQUABLE, et c'est voulu — le clic
   ouvre la fenêtre qui dit ce qu'il manque. Un bouton `disabled` ne dirait
   rien du tout, et le joueur ne saurait pas quoi chercher. */
.mk-btn.is-bloque {
  color: #94a3b8;
  background: rgba(148, 163, 184, 0.06);
  border-color: rgba(148, 163, 184, 0.28);
}
.mk-btn.is-bloque:hover { background: rgba(148, 163, 184, 0.14); }

/* Ligne du bas : input quantité + bouton Produire + (optionnel) expédition */
.ship-card-build-row {
  display: flex;
  gap: 6px;
  margin-top: 6px;
  align-items: stretch;
}
.ship-qty-input {
  width: 70px;
  padding: 6px 8px;
  background: rgba(0, 0, 0, 0.4);
  border: 1px solid var(--border2, #324569);
  color: var(--text, #cbd6ea);
  border-radius: 4px;
  font-family: var(--mono, monospace);
  font-size: 0.9rem;
  text-align: center;
}
.ship-qty-input:focus {
  outline: none;
  border-color: var(--accent2, #0ea5e9);
}
.ship-qty-input:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.ship-build-btn-compact {
  flex: 1;
  padding: 6px 12px;
  background: rgba(14, 165, 233, 0.12);
  border: 1px solid var(--accent2, #0ea5e9);
  color: var(--accent2, #0ea5e9);
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 500;
  transition: background 0.12s;
}
.ship-build-btn-compact:hover:not(:disabled) {
  background: rgba(14, 165, 233, 0.25);
}
.ship-build-btn-compact:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Sur petit écran : input + bouton sur ligne séparée si trop serré */
@media (max-width: 600px) {
  .fleet-grid {
    grid-template-columns: 1fr;
  }
  .ship-tab {
    min-width: 100px;
    padding: 6px 10px;
    font-size: 0.78rem;
  }
}

/* ════════════════════════════════════════════════════════════════════
   v1.25 — Vue Classements (#dsh-view-leaderboard)
   ════════════════════════════════════════════════════════════════════
   - .lb-tabs / .lb-tab    → onglets de catégorie (Général / Éco / ...)
   - .lb-table             → tableau stylisé du classement
   - .lb-row.is-me         → ligne du joueur courant mise en évidence
   - .lb-rank-1/2/3        → médailles or/argent/bronze
   ════════════════════════════════════════════════════════════════════ */

/* Onglets horizontaux des 6 catégories */
.lb-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 16px;
  padding: 4px;
  background: rgba(0, 0, 0, 0.2);
  border-radius: 6px;
}
.lb-tab {
  flex: 1;
  min-width: 140px;
  padding: 10px 14px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text2, #8da4c4);
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 500;
  transition: all 0.15s;
  text-align: center;
}
.lb-tab:hover {
  background: rgba(14, 165, 233, 0.08);
  color: var(--text, #cbd6ea);
}
.lb-tab.active {
  background: rgba(245, 158, 11, 0.12);
  border-color: var(--gold, #f59e0b);
  color: var(--gold, #f59e0b);
  font-weight: 600;
}

/* En-tête de catégorie (titre + description) */
.lb-cat-info {
  margin-bottom: 16px;
  padding: 12px 16px;
  background: rgba(10, 14, 28, 0.5);
  border-left: 3px solid var(--gold, #f59e0b);
  border-radius: 0 4px 4px 0;
}
.lb-cat-title {
  margin: 0 0 6px 0;
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--gold, #f59e0b);
}
.lb-cat-desc {
  margin: 0;
  font-size: 0.82rem;
  color: var(--text2, #8da4c4);
  line-height: 1.4;
}

/* États transitoires */
.lb-loading, .lb-empty, .lb-error {
  padding: 32px 16px;
  text-align: center;
  font-style: italic;
  color: var(--text2, #8da4c4);
}
.lb-error {
  color: #f87171;
}

/* Conteneur du tableau (scrollable horizontalement sur mobile) */
.lb-table-container {
  overflow-x: auto;
  background: rgba(10, 14, 28, 0.5);
  border: 1px solid var(--border2, #324569);
  border-radius: 6px;
}

/* Tableau de classement */
.lb-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}
.lb-table thead {
  background: rgba(14, 165, 233, 0.1);
  border-bottom: 2px solid var(--accent2, #0ea5e9);
}
.lb-table th {
  padding: 10px 14px;
  text-align: left;
  font-weight: 600;
  color: var(--accent2, #0ea5e9);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.lb-table td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border, #1f2a44);
  color: var(--text, #cbd6ea);
}
.lb-table tbody tr:last-child td {
  border-bottom: none;
}
.lb-row {
  transition: background 0.1s;
}
.lb-row:hover {
  background: rgba(14, 165, 233, 0.04);
}

/* Ligne du joueur courant — fond doré subtil + bord gauche */
.lb-row.is-me {
  background: rgba(245, 158, 11, 0.07);
}
.lb-row.is-me:hover {
  background: rgba(245, 158, 11, 0.12);
}
.lb-row.is-me td:first-child {
  border-left: 3px solid var(--gold, #f59e0b);
}

/* Colonnes */
.lb-col-rank {
  width: 80px;
  white-space: nowrap;
}
.lb-col-user {
  min-width: 160px;
}
.lb-col-role {
  width: 140px;
  color: var(--text2, #8da4c4);
}
.lb-col-points {
  width: 110px;
  text-align: right;
  font-family: var(--mono, monospace);
  font-weight: 600;
  color: var(--accent2, #0ea5e9);
}
.lb-table thead .lb-col-points {
  text-align: right;
}

/* Rang : style par défaut (rang ≥ 4) */
.lb-rank {
  display: inline-block;
  padding: 2px 8px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 10px;
  font-family: var(--mono, monospace);
  font-size: 0.8rem;
  color: var(--text2, #8da4c4);
}

/* Médailles top 3 */
.lb-rank-1 {
  background: rgba(245, 158, 11, 0.18);
  color: #fbbf24;
  font-weight: 700;
  border: 1px solid #fbbf24;
}
.lb-rank-2 {
  background: rgba(203, 213, 225, 0.15);
  color: #e2e8f0;
  font-weight: 600;
  border: 1px solid #cbd5e1;
}
.lb-rank-3 {
  background: rgba(217, 119, 6, 0.15);
  color: #fb923c;
  font-weight: 600;
  border: 1px solid #fb923c;
}

/* Pseudo + badge "TOI" sur la ligne du joueur courant */
.lb-username {
  font-weight: 500;
}
.lb-self-badge {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 6px;
  background: rgba(245, 158, 11, 0.2);
  color: var(--gold, #f59e0b);
  border-radius: 3px;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  vertical-align: middle;
}

/* Footer du tableau (compteur + note) */
.lb-footer {
  margin-top: 12px;
  padding: 8px 12px;
  font-size: 0.78rem;
  color: var(--text2, #8da4c4);
  text-align: center;
  font-style: italic;
}

/* Interrupteur classementSansCommandement : l'onglet Commandement disparaît. */
body.f-classementSansCommandement #lb-tab-command { display: none; }

/* Responsive : sur petit écran, on cache la colonne classe secondaire */
@media (max-width: 700px) {
  .lb-tab {
    min-width: 100px;
    padding: 6px 8px;
    font-size: 0.75rem;
  }
  .lb-col-role:nth-child(4) {
    display: none;
  }
  .lb-table th, .lb-table td {
    padding: 8px 10px;
  }
}

/* ════════════════════════════════════════════════════════════════════
   v1.26 — Bandeau Electryte en déficit (rouge clignotant)
   ════════════════════════════════════════════════════════════════════
   Quand la consommation d'Electryte dépasse la production, on alerte le joueur
   par un fond rouge qui pulse. Activé via la classe .is-deficit sur #res-electryte
   (toggle dans queues.js / render.js selon le calcul). */
@keyframes electryte-deficit-pulse {
  0%, 100% { background-color: rgba(239, 68, 68, 0.10); border-color: rgba(239, 68, 68, 0.40); }
  50%      { background-color: rgba(239, 68, 68, 0.30); border-color: rgba(239, 68, 68, 0.80); }
}
#res-electryte.is-deficit {
  animation: electryte-deficit-pulse 1.4s ease-in-out infinite;
  border: 1px solid rgba(239, 68, 68, 0.40);
}
#res-electryte.is-deficit .res-val,
#res-electryte.is-deficit .res-rate {
  color: #ef4444;
  font-weight: 600;
}

/* ════════════════════════════════════════════════════════════════════
   v1.27 — Paramétrage de production (modale OGame + résumé Vue globale)
   ════════════════════════════════════════════════════════════════════
   Composants :
   - .sn-prod-btn : bouton "🎛️ Paramétrage" dans la sidebar VM (toujours visible)
   - .sn-sect-sep : séparateur fin entre le bouton et la liste des modules
   - #prod-settings-modal : modale plein écran style admin
   - .ps-bilan-table : tableau bilan en haut de la modale
   - .ps-row : ligne par bâtiment avec slider
   - .ps-mini-* : mini-résumé sur la Vue globale
   ════════════════════════════════════════════════════════════════════ */

/* Bouton "Paramétrage prod." dans la sidebar VM */
.sn-prod-btn {
  background: linear-gradient(135deg, rgba(168, 85, 247, 0.15), rgba(168, 85, 247, 0.05));
  border-left: 3px solid #a855f7 !important;
  margin: 4px 6px;
  border-radius: 4px;
  font-weight: 600;
  color: #c4b5fd !important;
}
.sn-prod-btn:hover {
  background: linear-gradient(135deg, rgba(168, 85, 247, 0.30), rgba(168, 85, 247, 0.10));
  color: #ddd6fe !important;
}
.sn-sect-sep {
  height: 1px;
  background: rgba(255, 255, 255, 0.06);
  margin: 8px 12px;
}

/* Modale */
#prod-settings-modal {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0, 0, 0, 0.65);
  z-index: 320;
  display: none;
  justify-content: center;
  align-items: flex-start;
  padding: 4vh 2vw;
  overflow-y: auto;
}
#prod-settings-modal.visible {
  display: flex;
}
.prod-settings-box {
  width: 100%;
  max-width: 920px;
  background: var(--bg2, #0f1729);
  border: 2px solid #a855f7;
  border-radius: 8px;
  box-shadow: 0 12px 50px rgba(168, 85, 247, 0.30);
  display: flex;
  flex-direction: column;
  max-height: 92vh;
  overflow: hidden;
}
.prod-settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid #2a3855;
  background: rgba(168, 85, 247, 0.08);
  flex-shrink: 0;
}
.prod-settings-title {
  font-family: var(--hud, monospace);
  font-size: 1rem;
  color: #c4b5fd;
  letter-spacing: 0.08em;
}
.prod-settings-close {
  background: none;
  border: 1px solid #4a5878;
  color: var(--text2, #b0c0e0);
  padding: 4px 10px;
  cursor: pointer;
  border-radius: 4px;
  font-size: 0.95rem;
}
.prod-settings-close:hover {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
}
.prod-settings-help {
  padding: 12px 20px;
  font-size: 0.78rem;
  color: var(--text2, #b0c0e0);
  background: rgba(0, 0, 0, 0.20);
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  flex-shrink: 0;
  line-height: 1.45;
}
#prod-settings-content {
  flex: 1;
  overflow-y: auto;
  padding: 16px 20px;
  min-height: 0;
}
.prod-settings-footer {
  padding: 12px 20px;
  border-top: 1px solid #2a3855;
  background: rgba(0, 0, 0, 0.15);
  text-align: right;
  flex-shrink: 0;
}
.prod-settings-apply-all {
  background: rgba(168, 85, 247, 0.15);
  border: 1px solid #a855f7;
  color: #c4b5fd;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  font-family: var(--hud, monospace);
  font-size: 0.82rem;
  letter-spacing: 0.05em;
}
.prod-settings-apply-all:hover {
  background: rgba(168, 85, 247, 0.30);
  color: #fff;
}

/* Bilan global (tableau en haut de la modale) */
.ps-bilan {
  margin-bottom: 20px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 6px;
  padding: 12px 14px;
}
.ps-bilan-title {
  font-family: var(--hud, monospace);
  font-size: 0.85rem;
  color: #fbbf24;
  letter-spacing: 0.06em;
  margin-bottom: 10px;
}
.ps-bilan-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.82rem;
}
.ps-bilan-table thead {
  border-bottom: 1px solid #2a3855;
}
.ps-bilan-table th,
.ps-bilan-table td {
  padding: 6px 10px;
  text-align: left;
  vertical-align: middle;
}
.ps-bilan-table th {
  color: #94a3b8;
  font-weight: 600;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.ps-bilan-table td.ps-cell-res {
  color: var(--text, #cbd6ea);
  width: 30%;
}
.ps-bilan-table td.ps-cell-prod {
  color: #22c55e;
  font-family: var(--mono, monospace);
  text-align: right;
  width: 22%;
}
/* v0.0.38 (flag detailProduction) : la pastille ⓘ qui ouvre le détail du
   débit. Discrète au repos — elle ne doit pas concurrencer le chiffre
   qu'elle explique — et franche au survol, avec le curseur d'aide qui dit
   qu'il y a quelque chose à lire. */
.ps-info {
  color: var(--text3, #64748b);
  cursor: help;
  font-size: .9em;
  margin-left: .35em;
  transition: color .12s ease;
}
.ps-info:hover { color: #67e8f9; }

/* ═══════════════════════════════════════════════════════════════════
   v0.0.39 (flag infobulleProductionStylee) — LE DÉTAIL DU DÉBIT EN
   PANNEAU DESSINÉ
   ═══════════════════════════════════════════════════════════════════
   Remarque de Loryan : « l'infobulle est moche ». Elle l'était parce
   qu'elle n'était pas de nous : la v0.0.38 posait le détail dans un
   attribut `title`, donc c'est le SYSTÈME D'EXPLOITATION qui la
   dessinait — sa police, son fond jaunâtre, son alignement, et aucune
   règle de cette feuille ne pouvait l'atteindre. D'où le remplacement
   par un panneau à nous, que voici.

   TOUT LE BLOC EST TEINTÉ PAR SA RESSOURCE. `--psi-res` est posée en
   ligne par le JS depuis RES_INFO (la table qui décide déjà de la
   couleur d'une ressource PARTOUT dans le jeu) : le Ferrith arrive
   orange, l'Iridyum violet, sans qu'aucune couleur ne soit recopiée
   ici — une teinte changée dans RES_INFO suit toute seule.

   ⚠️ LA COULEUR DES LIGNES PORTE UNE INFORMATION, elle n'est pas une
   décoration : vert = ce facteur AUGMENTE le débit, ambre = il le
   RÉDUIT, estompé = il ne fait rien (×1). Sur les dix facteurs d'un
   minerai, cinq valent typiquement ×1 ; les estomper est ce qui fait
   ressortir les trois qui décident vraiment du chiffre.
   ─────────────────────────────────────────────────────────────────── */

/* La pastille, version panneau. Le glyphe posé par le JS est un « i »
   NU (et non le « ⓘ » de la variante native) : le cercle est dessiné
   ici, sinon on lirait deux cercles l'un dans l'autre. */
.ps-info-riche {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid rgba(107, 138, 176, .38);
  background: rgba(107, 138, 176, .08);
  font-family: var(--font-body, var(--body));
  font-size: 10px;
  font-style: italic;
  font-weight: 600;
  line-height: 1;
  vertical-align: middle;
  user-select: none;
  transition: color .12s ease, border-color .12s ease,
              background-color .12s ease, box-shadow .12s ease;
}
.ps-info-riche:hover,
.ps-info-riche:focus-visible,
.ps-info-riche.is-open {
  color: var(--psi-res, #67e8f9);
  border-color: var(--psi-res, #67e8f9);
  background: rgba(255, 255, 255, .06);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .05);
  outline: none;
}

.psi-pop {
  position: fixed;
  top: 0; left: 0;
  /* L'échelle de profondeur du projet, jamais un nombre libre (§21.3.5). */
  z-index: var(--z-tooltip, 1330);
  display: flex;
  flex-direction: column;
  width: min(430px, calc(100vw - 16px));
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, .10);
  background: linear-gradient(168deg, #0c1424 0%, #070b14 100%);
  box-shadow: 0 20px 52px rgba(0, 0, 0, .68),
              0 2px 10px rgba(0, 0, 0, .5);
  font-family: var(--font-body, var(--body));
  color: var(--text, #c8d6f0);
  /* Caché par VISIBILITÉ et non par `display` : le JS a besoin de mesurer
     le panneau (largeur et hauteur) pour décider de quel côté l'ouvrir,
     et un `display:none` n'a aucune dimension à mesurer. */
  visibility: hidden;
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity .12s ease, transform .12s ease;
}
.psi-pop.is-open {
  visibility: visible;
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
/* Le filet de couleur en haut du panneau — la seule chose qu'on voit
   avant même de lire, et elle dit déjà de quelle ressource on parle. */
.psi-pop::before {
  content: '';
  position: absolute;
  left: 10px; right: 10px; top: 0;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--psi-res, #67e8f9), transparent);
}
/* La flèche qui rattache le panneau à sa pastille. Sa position
   horizontale est calculée par le JS (`--psi-fleche-x`) : le panneau
   glisse sur le côté quand il touche le bord de l'écran, la flèche non. */
.psi-pop::after {
  content: '';
  position: absolute;
  left: var(--psi-fleche-x, 50%);
  top: -5px;
  width: 10px; height: 10px;
  margin-left: -5px;
  transform: rotate(45deg);
  background: #111a2c;
  background: color-mix(in srgb, var(--psi-res, #67e8f9) 12%, #0c1424);
  border-left: 1px solid rgba(255, 255, 255, .10);
  border-top: 1px solid rgba(255, 255, 255, .10);
}
.psi-pop--dessus::after {
  top: auto;
  bottom: -5px;
  transform: rotate(225deg);
  background: rgba(255, 255, 255, .02);
  background: color-mix(in srgb, var(--psi-res, #67e8f9) 3%, #080d18);
}

/* ─── En-tête : quelle ressource, et quel chiffre on explique ─────── */
.psi-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px 10px;
  border-bottom: 1px solid rgba(255, 255, 255, .07);
  border-radius: 10px 10px 0 0;
  overflow: hidden;
}
/* Le fond teinté passe par un calque à part plutôt que par une couleur
   composée : `--psi-res` arrive en opaque, on ne peut pas la diluer
   sans la recalculer. */
.psi-head::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--psi-res, #67e8f9);
  opacity: .13;
}
.psi-head > * { position: relative; }
.psi-head-ico { display: inline-flex; flex-shrink: 0; }
.psi-head-ico .res-mini {
  width: 28px; height: 28px;
  border-radius: 6px;
  vertical-align: 0;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .18),
              0 0 14px rgba(0, 0, 0, .45);
}
.psi-head-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.psi-head-titre {
  font-size: 13px;
  font-weight: 600;
  color: #eaf2ff;
  letter-spacing: .01em;
}
.psi-head-sous {
  font-family: var(--hud, monospace);
  font-size: 9.5px;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--psi-res, #67e8f9);
  opacity: .85;
}

/* ─── Les facteurs, un par ligne, dans l'ordre du calcul ──────────── */
.psi-lignes {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  /* Assez haut pour que TOUS les facteurs d'un minerai tiennent d'un bloc
     sur un écran ordinaire. L'intérêt du panneau est justement de voir le
     calcul entier d'un coup d'œil : un plafond trop bas oblige à faire
     défiler pour lire les derniers, c'est-à-dire ceux qu'on vient d'ajouter.
     ⚠️ v0.0.83 : ils sont passés de DIX à TREIZE (l'usure de l'extracteur et
     les deux Voies), soit ~600 px mesurés au navigateur au lieu de ~460 —
     le treizième tombait sous la coupe. Le plafond suit.
     ⚠️ Le relever ne change RIEN là où la liste est plus courte : `max-height`
     ne fait que couper, il n'étire pas. */
  /* ⚠️ v0.0.118 — LA VALEUR CI-DESSOUS N'EST PLUS QU'UN REPLI, le temps que le
     JS passe. Le code qui pose le panneau (`psInfoPlacer`) connaît sa position
     exacte à l'écran : il mesure la place disponible et écrit le vrai plafond
     dans `--psi-liste-max` — ou `none` quand tout tient, et le panneau ne
     défile alors pas du tout. C'est LUI qui fait foi, à toute largeur. */
  max-height: var(--psi-liste-max, min(76vh, 640px));
  overscroll-behavior: contain;
}
.psi-l {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto;
  grid-template-areas:
    "op  lib  val"
    "op  form form";
  column-gap: 9px;
  row-gap: 1px;
  padding: 7px 14px;
  transition: background-color .1s ease, opacity .1s ease;
}
.psi-l + .psi-l { border-top: 1px solid rgba(255, 255, 255, .045); }
.psi-l:hover { background: rgba(255, 255, 255, .035); opacity: 1; }
/* Un facteur sans effet (×1) est REPOUSSÉ AU SECOND PLAN — il compte
   dans le calcul, il ne compte pas dans la décision. Il redevient
   parfaitement lisible au survol de sa ligne. */
.psi-neutre { opacity: .5; }

.psi-op {
  grid-area: op;
  align-self: start;
  font-family: var(--mono, monospace);
  font-size: 12px;
  text-align: center;
  color: var(--text3, #2d4070);
  padding-top: 1px;
}
.psi-l-base .psi-op {
  font-family: var(--hud, monospace);
  font-size: 8px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--psi-res, #67e8f9);
  opacity: .9;
  padding-top: 4px;
}
.psi-lib {
  grid-area: lib;
  font-size: 12.5px;
  color: #d7e3f7;
  min-width: 0;
}
.psi-val {
  grid-area: val;
  display: flex;
  align-items: baseline;
  gap: 6px;
  justify-self: end;
  font-family: var(--mono, monospace);
  font-size: 13px;
  white-space: nowrap;
}
/* La phrase qui dit D'OÙ SORT le facteur. C'est elle que Loryan a
   demandée en v0.0.38 (« avec explication de la formule à chaque
   fois ») : elle reste sur sa propre ligne, sous le libellé, pour ne
   jamais bousculer la colonne des valeurs. */
.psi-form {
  grid-area: form;
  font-size: 10.5px;
  line-height: 1.4;
  color: var(--text2, #6b8ab0);
}

/* Le gain ou la perte en clair : « ×0,31 » ne dit rien, « −69 % » se
   comprend sans calcul. */
.psi-delta {
  font-family: var(--font-body, var(--body));
  font-size: 9.5px;
  font-weight: 600;
  padding: 1.5px 5px;
  border-radius: 999px;
  letter-spacing: .01em;
}
.psi-l-base .psi-val { color: #f1f6ff; font-weight: 600; }
.psi-bonus  .psi-val { color: #4ade80; }
.psi-bonus  .psi-delta { background: rgba(74, 222, 128, .14); color: #4ade80; }
.psi-malus  .psi-val { color: #fbbf24; }
.psi-malus  .psi-delta { background: rgba(251, 191, 36, .14); color: #fbbf24; }
.psi-neutre .psi-val { color: var(--text2, #6b8ab0); }

/* v0.0.84 — LA LIGNE QUI RETRANCHE. Elle porte un montant en /h et non un
   facteur, donc elle ne se lit pas comme les autres : un filet à gauche et
   un fond très léger la détachent de la colonne des multiplicateurs, sans
   changer la hauteur de ligne (une bordure pleine décalerait tout le reste
   d'un pixel, leçon du panneau de vol en v0.0.55). */
.psi-l-moins {
  box-shadow: inset 2px 0 0 rgba(251, 191, 36, .55);
  background: rgba(251, 191, 36, .05);
}
.psi-l-moins .psi-op { color: #fbbf24; font-weight: 700; }

/* ─── v0.0.119 : la ligne qui S'AJOUTE ────────────────────────────────
   L'Electryte n'est pas un débit modulé, c'est la SOMME de cinq producteurs.
   Sa ligne porte donc un montant en /h, comme la base et comme celles qui
   retranchent — et le même filet vertical, en vert cette fois, pour qu'on
   voie d'un coup d'œil ce qui monte et ce qui descend.
   ⚠️ Elle reprend `psi-bonus` (le vert des multiplicateurs favorables) : une
   troisième couleur pour dire « ça monte » aurait fait deux vocabulaires
   dans le même panneau. */
.psi-l-plus {
  box-shadow: inset 2px 0 0 rgba(34, 197, 94, .55);
  background: rgba(34, 197, 94, .05);
}
.psi-l-plus .psi-op { color: #22c55e; font-weight: 700; }

/* ═══════════════════════════════════════════════════════════════════
   v0.0.135 — LES CINQ FAMILLES
   ═══════════════════════════════════════════════════════════════════
   Signalement de Loryan : « le détail prend toute la page, et je ne
   veux pas ça ». Quatorze lignes à plat, sept d'entre elles à ×1 : la
   moitié du panneau ne disait rien d'autre que « rien ici ».

   La colonne de gauche cesse donc de répéter l'opération — le « × »
   était déjà écrit dans la valeur, à droite — et porte à sa place la
   FAMILLE du facteur, doublée d'un filet de couleur. Deux lectures du
   même classement : l'abrégé pour qui lit, le filet pour qui balaie.

   ⚠️ LE FILET DE FAMILLE REMPLACE CELUI DES LIGNES `+` ET `−`, et pas
   leur FOND : le fond dit ce qui monte et ce qui descend, la couleur du
   filet dit de quoi ça parle. Deux informations, deux supports — les
   fondre en un seul en perdrait une.
   ─────────────────────────────────────────────────────────────────── */
.psi-l {
  /* La colonne de gauche accueille « BONUS », le plus large des cinq
     abrégés. Mesuré au navigateur : 27 px de glyphes, 40 px de colonne. */
  grid-template-columns: 40px minmax(0, 1fr) auto;
}
/* ⚠️ SANS CETTE RÈGLE, LE TIROIR NE SE FERME PAS. `.psi-l` pose
   `display: grid` ; c'est une règle de CLASSE, donc elle l'emporte sur le
   `[hidden] { display: none }` du navigateur, qui n'est qu'une règle
   d'attribut. Les huit lignes « repliées » restaient affichées, et le
   panneau prenait toujours toute la page — le défaut, intact. Vu au
   navigateur, jamais à la relecture. */
.psi-l[hidden] { display: none; }
.psi-op {
  font-family: var(--hud, monospace);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: .06em;
  white-space: nowrap;
  padding-top: 4px;
}
/* La BASE garde son étiquette et la couleur de la ressource : c'est la
   seule ligne qui ne se multiplie pas, l'annoncer « PROD » comme les
   facteurs qui la suivent la ferait lire comme l'un d'eux. */
.psi-l-base .psi-op { font-size: 8px; }

.psi-cat-prod  { box-shadow: inset 3px 0 0 rgba(74, 222, 128, .7); }
.psi-cat-sol   { box-shadow: inset 3px 0 0 rgba(96, 165, 250, .7); }
.psi-cat-bat   { box-shadow: inset 3px 0 0 rgba(251, 146, 60, .7); }
.psi-cat-raid  { box-shadow: inset 3px 0 0 rgba(168, 85, 247, .8); }
.psi-cat-bonus { box-shadow: inset 3px 0 0 rgba(250, 204, 21, .7); }

.psi-cat-prod  .psi-op { color: #4ade80; }
.psi-cat-sol   .psi-op { color: #60a5fa; }
.psi-cat-bat   .psi-op { color: #fb923c; }
.psi-cat-raid  .psi-op { color: #c084fc; }
.psi-cat-bonus .psi-op { color: #facc15; }
/* Un facteur sans effet est déjà à demi effacé (`.psi-neutre`) : son
   abrégé n'a pas besoin de crier en plus. */
.psi-neutre .psi-op { opacity: .8; }

/* ─── Le tiroir des facteurs sans effet ──────────────────────────────
   Il est une LIGNE de la même liste, et non un bloc en dessous : c'est
   la hauteur de `.psi-lignes` que le JS mesure pour décider si le
   panneau doit défiler (v0.0.118), et un second conteneur lui
   échapperait — le tiroir ouvert sortirait de l'écran sans un mot. */
.psi-tiroir { display: block; padding: 0; }
.psi-tiroir-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 14px;
  border: 0;
  border-top: 1px solid rgba(255, 255, 255, .045);
  background: rgba(255, 255, 255, .022);
  color: var(--text2, #6b8ab0);
  font-family: var(--hud, monospace);
  font-size: 9.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  text-align: left;
  cursor: pointer;
  transition: background-color .1s ease, color .1s ease;
}
.psi-tiroir-btn:hover,
.psi-tiroir-btn:focus-visible {
  background: rgba(255, 255, 255, .06);
  color: #d7e3f7;
  outline: none;
}
.psi-tiroir-fleche {
  display: inline-block;
  width: 26px;
  text-align: center;
  font-size: 11px;
  transition: transform .14s ease;
}
.psi-tiroir-btn[aria-expanded="true"] .psi-tiroir-fleche { transform: rotate(90deg); }
.psi-tiroir-txt { flex: 1; min-width: 0; }
.psi-tiroir-ind {
  font-family: var(--mono, monospace);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: none;
  opacity: .75;
}

/* ─── Le total, en pied, dans la couleur de la ressource ──────────── */
.psi-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px 11px;
  border-top: 1px solid rgba(255, 255, 255, .10);
  background: rgba(255, 255, 255, .022);
  border-radius: 0 0 10px 10px;
}
.psi-total-lib {
  font-family: var(--hud, monospace);
  font-size: 9.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text2, #6b8ab0);
}
.psi-total-val {
  font-family: var(--mono, monospace);
  font-size: 17px;
  font-weight: 700;
  color: var(--psi-res, #67e8f9);
  text-shadow: 0 0 14px currentColor;
}
/* Un solde NÉGATIF ne se peint pas dans la couleur de la ressource : la
   teinte dit « voilà ce que tu gagnes », et elle mentirait exactement au
   moment où la réserve descend. */
.psi-total-neg .psi-total-val { color: #f87171; }

/* L'aveu, quand les lignes ne redonnent pas le total affiché : le
   panneau se contredit VISIBLEMENT plutôt que de mentir en silence
   (règle posée en v0.0.38, conservée telle quelle). */
.psi-alerte {
  padding: 8px 14px 10px;
  border-top: 1px solid rgba(248, 113, 113, .3);
  background: rgba(248, 113, 113, .08);
  border-radius: 0 0 10px 10px;
  font-size: 11px;
  line-height: 1.45;
  color: #fca5a5;
}
/* v0.0.135 — L'EXCÉDENT PERDU.
   ⚠️ MÊME GABARIT QUE `.psi-alerte`, MAIS PAS LA MÊME COULEUR ni le même
   nom : le rouge de l'alerte dit « la liste est incomplète, signale-le »,
   c'est-à-dire un DÉFAUT du panneau. Celle-ci dit une vérité du jeu — de
   l'Électryte se perd, et voici le levier — donc l'ambre des avertissements,
   jamais le rouge des pannes. */
.psi-perte {
  padding: 8px 14px 10px;
  border-top: 1px solid rgba(245, 158, 11, .3);
  background: rgba(245, 158, 11, .08);
  border-radius: 0 0 10px 10px;
  font-size: 11px;
  line-height: 1.45;
  color: #fbbf24;
}
/* L'alerte se pose SOUS le total : c'est elle qui ferme alors le panneau,
   donc c'est elle qui porte les coins arrondis du bas. */
.psi-pop:has(.psi-alerte) .psi-total,
.psi-pop:has(.psi-perte) .psi-total { border-radius: 0; }

/* Sur un écran étroit, le panneau prend la largeur utile et rend un peu
   de hauteur au tableau qu'il recouvre. */
@media (max-width: 520px) {
  .psi-l { padding: 6px 11px; }
  .psi-head { padding: 9px 11px 8px; }
}
.ps-bilan-table td.ps-cell-conso {
  color: #f87171;
  font-family: var(--mono, monospace);
  text-align: right;
  width: 22%;
}
.ps-bilan-table td.ps-cell-solde {
  font-family: var(--mono, monospace);
  text-align: right;
  font-weight: 600;
  width: 26%;
}
.ps-solde-pos { color: #22c55e; }
.ps-solde-neg { color: #ef4444; font-weight: 700; }
.ps-solde-zero { color: #94a3b8; }
.ps-bilan-empty {
  text-align: center !important;
  color: #94a3b8;
  font-style: italic;
  padding: 14px !important;
}

/* Sliders par bâtiment */
.ps-sliders {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ps-group {
  background: rgba(0, 0, 0, 0.20);
  border: 1px solid rgba(255, 255, 255, 0.05);
  border-radius: 6px;
  padding: 10px 12px;
}
.ps-group-title {
  font-family: var(--hud, monospace);
  font-size: 0.78rem;
  color: #93c5fd;
  letter-spacing: 0.06em;
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.ps-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 0;
}
.ps-row + .ps-row {
  border-top: 1px solid rgba(255, 255, 255, 0.03);
}
.ps-row-disabled {
  opacity: 0.4;
}
.ps-row-info {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 240px;
}
.ps-bld-icon { font-size: 1.1rem; }
.ps-bld-label {
  color: var(--text, #cbd6ea);
  font-size: 0.85rem;
}
.ps-bld-level {
  color: #94a3b8;
  font-size: 0.72rem;
  margin-left: auto;
  padding-right: 6px;
}
.ps-row-slider {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  max-width: 300px;
}
.ps-row-slider input[type="range"] {
  flex: 1;
  accent-color: #a855f7;
  cursor: pointer;
}
.ps-row-slider input[type="range"]:disabled {
  cursor: not-allowed;
}
.ps-pct {
  width: 48px;
  text-align: right;
  font-family: var(--mono, monospace);
  color: #c4b5fd;
  font-weight: 600;
  font-size: 0.85rem;
}

/* Mini-résumé sur la Vue globale (sous l'encart File réduit) */
.ov-section-q-compact .ov-q-row {
  /* Réduit la hauteur des lignes de la file pour gagner de la place */
  padding: 3px 0 !important;
  font-size: 0.78rem !important;
}
.ov-section-q-compact {
  /* Limite la hauteur globale pour forcer le compactage */
  max-height: 140px;
  overflow-y: auto;
}
.ov-section-prod-summary {
  /* Carte qui contient le mini-bilan sous la file */
}
.ps-mini-card {
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(168, 85, 247, 0.20);
  border-radius: 6px;
  padding: 10px 12px;
}
.ps-mini-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: var(--hud, monospace);
  font-size: 0.82rem;
  color: #c4b5fd;
  letter-spacing: 0.05em;
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.ps-mini-btn {
  background: rgba(168, 85, 247, 0.15);
  border: 1px solid #a855f7;
  color: #c4b5fd;
  padding: 3px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.72rem;
  font-family: var(--hud, monospace);
}
.ps-mini-btn:hover {
  background: rgba(168, 85, 247, 0.30);
  color: #fff;
}
/* v0.0.9 — bascule Production / Solde net de la fenêtre « Sur la durée ».
   Le titre du bilan est un bloc : le bouton qu'on y ajoute doit se poser à
   sa droite sans casser la ligne. */
.ps-bilan-title { display: flex; align-items: center; gap: 10px; }
.ps-bilan-title .ps-mini-btn { margin-left: auto; }
.ps-projection-bascule {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}
/* ─── La fenêtre « Sur la durée » ────────────────────────────────────
   ⚠️ ELLE NE DOIT JAMAIS ÊTRE PLUS LARGE QUE L'ÉCRAN. Six colonnes de
   chiffres réclament de la place, mais un `max-width` fixe de 860 px
   débordait des fenêtres plus étroites : la modale, centrée, dépassait
   alors des DEUX côtés et le tableau se retrouvait coupé à droite,
   décalé, à moitié sous le bandeau de ressources. C'est le `min()` qui
   règle ça — on prend 860 px quand il y en a, la largeur de l'écran
   sinon, et c'est le tableau qui défile à l'intérieur (.ps-table-wrap). */
.projection-backdrop .dsh-modal {
  max-width: min(860px, calc(100vw - 24px));
}
/* Centrée VERTICALEMENT, et non collée à 50 px du haut comme les modales
   standard : à cette hauteur-là elle passait derrière le bandeau des
   ressources, qui mange déjà près de 180 px. */
.projection-backdrop {
  align-items: center;
  padding: 12px;
}
.projection-backdrop .dsh-modal { max-height: calc(100vh - 24px); }
/* ⚠️ AU-DESSUS DU BANDEAU DE RESSOURCES. Sur une fenêtre étroite, le
   bandeau des ressources passe en position FIXE avec un étage de 190 —
   très au-dessus du `--z-modal` (40) des modales. La fenêtre s'ouvrait
   donc DERRIÈRE lui. C'est le défaut du SOCLE, et il est corrigé À LA
   RACINE depuis la v0.0.26 : les jetons du §21.3.5 valent désormais
   1300/1310/1320/1330, donc `.dsh-modal-backdrop` s'ouvre au-dessus des
   deux bandeaux — et au-dessus du panneau de production (320) d'où
   celle-ci s'ouvre. Le contournement local (z-index 1200) qui vivait ici
   a donc été retiré : il n'a plus rien à corriger. */
.ps-mini-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.78rem;
}
.ps-mini-table th {
  color: #94a3b8;
  font-size: 0.68rem;
  text-transform: uppercase;
  font-weight: 600;
  padding: 4px 6px;
  text-align: left;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.ps-mini-table td {
  padding: 4px 6px;
  vertical-align: middle;
}
.ps-mini-table td.ps-mini-res {
  color: var(--text, #cbd6ea);
}
.ps-mini-table td.ps-mini-prod {
  color: #22c55e;
  font-family: var(--mono, monospace);
  text-align: right;
}
.ps-mini-table td.ps-mini-solde {
  font-family: var(--mono, monospace);
  text-align: right;
  font-weight: 600;
}

/* Responsive : sliders en colonne sur petit écran */
@media (max-width: 700px) {
  .ps-row {
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
  }
  .ps-row-info {
    min-width: 0;
  }
  .ps-row-slider {
    max-width: 100%;
  }
}

/* ════════════════════════════════════════════════════════════════════
   v1.29 — Aides à la sélection des coordonnées (Rampe de Lancement)
   ════════════════════════════════════════════════════════════════════
   Composants :
   - .pp-coords-clickable : coords (G:S:P) cliquables dans le panneau planète
   - .pt-coords : coords dans le tooltip de la vue Galaxie
   - .fl-coords-helpers : boutons "📋 Choisir" + "📍 Sur la carte"
   - .fl-coords-picker-dropdown : liste déroulante des planètes connues
   - .fl-minimap-overlay : overlay de la mini-carte
   ════════════════════════════════════════════════════════════════════ */

/* Coords cliquables dans le panneau planète de la vue Galaxie */
.pp-coords-clickable {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  background: rgba(168, 85, 247, 0.12);
  border: 1px solid rgba(168, 85, 247, 0.40);
  border-radius: 4px;
  color: #c4b5fd;
  font-family: var(--mono, monospace);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.12s;
  vertical-align: middle;
}
.pp-coords-clickable:hover {
  background: rgba(168, 85, 247, 0.30);
  color: #fff;
  border-color: #a855f7;
  transform: translateY(-1px);
}

/* Coords dans le tooltip survol planète sur la vue Galaxie */
.pt-coords {
  color: #c4b5fd;
  font-family: var(--mono, monospace);
  font-size: 0.78rem;
  margin-left: 4px;
}

/* ─── Bouton "Choisir / Sur la carte" sous le champ coords (Rampe) ─── */
.fl-coords-helpers {
  display: flex;
  gap: 6px;
  margin-top: 6px;
  flex-wrap: wrap;
}
.fl-coords-helper-btn {
  flex: 1;
  min-width: 130px;
  padding: 5px 10px;
  background: rgba(168, 85, 247, 0.10);
  border: 1px solid rgba(168, 85, 247, 0.35);
  color: #c4b5fd;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.78rem;
  font-family: var(--hud, monospace);
  transition: all 0.12s;
}
.fl-coords-helper-btn:hover {
  background: rgba(168, 85, 247, 0.25);
  color: #fff;
  border-color: #a855f7;
}

/* ─── Liste déroulante "Choisir une planète" ─────────────────────── */
.fl-coords-picker-dropdown {
  position: absolute;
  margin-top: 4px;
  width: 380px;
  max-width: calc(100vw - 40px);
  background: rgba(15, 23, 41, 0.98);
  border: 1px solid #a855f7;
  border-radius: 6px;
  box-shadow: 0 8px 32px rgba(168, 85, 247, 0.40);
  z-index: 350;
  max-height: 380px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.fl-picker-search-row {
  display: flex;
  gap: 6px;
  padding: 8px 10px;
  background: rgba(168, 85, 247, 0.08);
  border-bottom: 1px solid rgba(168, 85, 247, 0.20);
}
.fl-picker-search {
  flex: 1;
  padding: 6px 10px;
  background: rgba(0, 0, 0, 0.30);
  border: 1px solid rgba(255, 255, 255, 0.10);
  color: #fff;
  border-radius: 4px;
  font-size: 0.82rem;
}
.fl-picker-search:focus {
  outline: none;
  border-color: #a855f7;
}
.fl-picker-close {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #ccc;
  width: 28px;
  height: 28px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
}
.fl-picker-close:hover {
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
}
.fl-picker-list {
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}
.fl-picker-item {
  padding: 8px 14px;
  font-size: 0.84rem;
  color: #cbd6ea;
  cursor: pointer;
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
  transition: background 0.08s;
}
.fl-picker-item:hover {
  background: rgba(168, 85, 247, 0.18);
  color: #fff;
}
.fl-picker-item:last-child {
  border-bottom: none;
}
.fl-picker-empty {
  padding: 20px;
  text-align: center;
  color: #94a3b8;
  font-style: italic;
  font-size: 0.85rem;
}

/* ─── Mini-carte simplifiée ───────────────────────────────────────── */
/* v1.30 fix : la mini-carte doit passer au-dessus de la modale Rampe qui
   l'a ouverte. La modale Rampe utilise la classe générique .modal-overlay
   qui a z-index: 1000. On met donc 1100 ici, et on utilise !important
   pour battre la spécificité de .modal-overlay (qui est lu après dans le CSS).
   Idem pour la liste déroulante "Choisir une planète" : elle doit aussi
   passer au-dessus de la modale Rampe (sinon elle est cachée derrière). */
.fl-minimap-overlay {
  /* hérite de .modal-overlay (sombre fond + flex center) */
  z-index: 1100 !important;
}
.fl-minimap-box {
  max-width: 900px;
  width: 92vw;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
}
.fl-minimap-body {
  flex: 1;
  overflow-y: auto;
  padding: 14px 18px;
  min-height: 0;
}
.fl-minimap-search {
  width: 100%;
  padding: 8px 12px;
  margin-bottom: 12px;
  background: rgba(0, 0, 0, 0.30);
  border: 1px solid rgba(168, 85, 247, 0.35);
  border-radius: 4px;
  color: #fff;
  font-size: 0.85rem;
}
.fl-minimap-search:focus {
  outline: none;
  border-color: #a855f7;
}
.fl-minimap-systems {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.fl-minimap-system {
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 6px;
  padding: 10px 12px;
}
.fl-minimap-system-name {
  font-family: var(--hud, monospace);
  font-size: 0.85rem;
  color: #fbbf24;
  letter-spacing: 0.04em;
  margin-bottom: 8px;
}
.fl-minimap-planets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.fl-minimap-planet {
  padding: 6px 12px;
  background: rgba(168, 85, 247, 0.10);
  border: 1px solid rgba(168, 85, 247, 0.30);
  color: #cbd6ea;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.82rem;
  transition: all 0.12s;
}
.fl-minimap-planet:hover {
  background: rgba(168, 85, 247, 0.30);
  color: #fff;
  border-color: #a855f7;
  transform: translateY(-1px);
}
.fl-minimap-coords {
  font-family: var(--mono, monospace);
  color: #c4b5fd;
  font-size: 0.74rem;
  margin-left: 4px;
}
.fl-minimap-empty {
  padding: 30px;
  text-align: center;
  color: #94a3b8;
  font-style: italic;
}

/* Responsive : tooltip plus compact sur mobile */
@media (max-width: 700px) {
  .fl-coords-picker-dropdown {
    width: 100%;
    max-width: none;
  }
  .fl-minimap-planet {
    flex: 1 1 100%;
  }
}

/* ════════════════════════════════════════════════════════════════════
   v1.37 — CENTRE DE RENSEIGNEMENT
   Timeline "tour de contrôle" : barre de filtres + frise de cartes.
   Réutilise les tokens du jeu (sci-fi sombre, fonts HUD/mono).
   ════════════════════════════════════════════════════════════════════ */
.intel-body {
  max-width: 920px;
  margin: 0 auto;
  padding: 0 1.2rem 3rem;
}
.intel-loading, .intel-error {
  text-align: center;
  color: var(--text2);
  padding: 3rem 1rem;
  font-family: var(--mono);
  font-size: 0.85rem;
}
.intel-error { color: var(--accent1); }

/* ── Barre de filtres (onglets stations) ── */
.intel-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1.4rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--border);
}
.intel-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.42rem 0.85rem;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text2);
  font-family: var(--hud);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.intel-filter:hover { border-color: var(--border2); color: var(--text); }
.intel-filter.active {
  background: rgba(14, 165, 233, 0.08);
  border-color: var(--accent2);
  color: var(--text);
}
.intel-filter-dot {
  width: 8px; height: 8px; border-radius: 50%;
  display: inline-block;
  box-shadow: 0 0 6px currentColor;
}
.intel-filter-count {
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--text3);
  background: rgba(255,255,255,0.04);
  padding: 1px 6px;
  border-radius: 8px;
}
.intel-filter.active .intel-filter-count { color: var(--text2); }

/* ── Frise verticale ── */
.intel-timeline {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  position: relative;
}

/* ── Carte de renseignement ── */
.intel-card {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  padding: 0.85rem 1rem 0.85rem 1.1rem;
  background: linear-gradient(180deg, var(--card) 0%, var(--bg2) 100%);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  transition: border-color 0.15s, transform 0.1s;
}
.intel-card.clickable { cursor: pointer; }
.intel-card.clickable:hover {
  border-color: var(--intel-accent, var(--border2));
  transform: translateX(2px);
}
/* Liseré coloré à gauche (couleur de catégorie) */
.intel-card-rail {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--intel-accent, var(--accent2));
  box-shadow: 0 0 10px var(--intel-accent, var(--accent2));
  opacity: 0.85;
}
.intel-card-icon {
  flex-shrink: 0;
  width: 38px; height: 38px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9px;
  font-size: 1.1rem;
  margin-top: 1px;
}
.intel-card-main { flex: 1; min-width: 0; }
.intel-card-top {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.intel-card-title {
  font-family: var(--hud);
  font-size: 0.86rem;
  color: var(--text);
  letter-spacing: 0.02em;
}
.intel-card-subtitle {
  color: var(--text2);
  font-size: 0.8rem;
  margin-top: 0.2rem;
}
.intel-card-detail {
  color: var(--text2);
  font-family: var(--mono);
  font-size: 0.74rem;
  margin-top: 0.25rem;
  opacity: 0.85;
}
.intel-card-time {
  color: var(--text3);
  font-family: var(--mono);
  font-size: 0.68rem;
  margin-top: 0.4rem;
}
.intel-card-arrow {
  align-self: center;
  color: var(--text3);
  font-size: 1.4rem;
  flex-shrink: 0;
  transition: color 0.15s, transform 0.15s;
}
.intel-card.clickable:hover .intel-card-arrow {
  color: var(--intel-accent);
  transform: translateX(3px);
}

/* Pastilles de direction (sortant / entrant) */
.intel-dir {
  font-family: var(--mono);
  font-size: 0.62rem;
  padding: 1px 7px;
  border-radius: 6px;
  letter-spacing: 0.03em;
}
.intel-dir-out { background: rgba(16,185,129,0.12); color: #34d399; }
.intel-dir-in  { background: rgba(233,69,96,0.14);  color: #ff7a8c; }

/* État vide */
.intel-empty {
  text-align: center;
  color: var(--text2);
  padding: 3.5rem 1rem;
}
.intel-empty-icon { font-size: 2.4rem; margin-bottom: 0.6rem; opacity: 0.7; }
.intel-empty-sub {
  color: var(--text3);
  font-size: 0.78rem;
  margin-top: 0.5rem;
  max-width: 360px;
  margin-left: auto; margin-right: auto;
}

/* Mobile : cartes plus compactes */
body.mobile-mode .intel-body { padding: 0 0.6rem 3rem; }
body.mobile-mode .intel-card { padding: 0.7rem 0.8rem; gap: 0.6rem; }
body.mobile-mode .intel-card-icon { width: 32px; height: 32px; font-size: 0.95rem; }

/* ════════════════════════════════════════════════════════════════════
   v2.2 — CARTE UNIFIÉE (#view-map) — fusion Univers + Galaxie.
   Même gabarit que #view-universe : plein écran entre la sidebar et le
   panneau VM, canvas sous le header. Le panneau planète (#planet-panel)
   et le tooltip y sont reparentés au runtime quand la carte est active.
   ════════════════════════════════════════════════════════════════════ */
#view-map {
  position: fixed;
  top: 0;
  left: var(--sidebar-width, 60px);
  right: var(--vm-panel-w, 130px);
  bottom: 0;
  padding-top: var(--total-header-h) !important;
  overflow: hidden;
}
#map-canvas-wrapper {
  position: absolute;
  top: var(--total-header-h, 100px);
  left: 0;
  right: 0;
  bottom: 0;
}
#map-canvas {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
  background: radial-gradient(ellipse at center, #0a0e1f 0%, #03060f 100%);
}

/* ════════════════════════════════════════════════════════════════════
   v2.9.11 — Bandeau d'alerte (#prod-hint) DÉPLACÉ dans le COIN HAUT-DROIT
   ════════════════════════════════════════════════════════════════════
   Avant : le bandeau se calait sur la ligne du titre de la section active
   (et se masquait au scroll). Choix produit : le fixer dans le COIN
   HAUT-DROIT — la zone vide au-dessus du panneau « Vaisseaux Mères » et à
   droite des bandeaux topbar/resbar (ceux-ci s'arrêtent à --vm-panel-w du
   bord droit). Là, il est AU-DESSUS du contenu : il ne peut plus se
   superposer aux cartes ni bouger quand on scrolle.

   Le coin utile est étroit (~130px) → format CARTE COMPACTE verticale :
   icône, puis message (tronqué à 4 lignes), puis flèche en bas à droite.
   Réservé au bureau (body:not(.mobile-mode)) : sur mobile le panneau VM
   est un tiroir, ce coin n'existe pas → on garde le comportement mobile.
   Les !important neutralisent `right:var(--vm-panel-w)!important` et
   `top:calc(--resbar-bottom…)` posés ailleurs sur #prod-hint. */
body:not(.mobile-mode) #prod-hint {
  top: 8px !important;
  right: 8px !important;
  left: auto !important;
  width: 122px;
  max-width: 122px;
  flex-direction: column;
  align-items: stretch;
  gap: .2rem;
  padding: .4rem .5rem;
  font-size: .55rem;
  line-height: 1.25;
  border-radius: 8px;
  z-index: 210;               /* au-dessus de la topbar (z-index 200) */
}
body:not(.mobile-mode) #prod-hint .ph-ico { font-size: 1rem; line-height: 1; }
body:not(.mobile-mode) #prod-hint .ph-msg { -webkit-line-clamp: 4; flex: none; }
body:not(.mobile-mode) #prod-hint .ph-arrow { align-self: flex-end; font-size: .5rem; }

/* ════════════════════════════════════════════════════════════════════
   v2.9.12 — Bandeau d'alerte CLIGNOTANT + pop-up « message en grand »
   ════════════════════════════════════════════════════════════════════
   1) Le bandeau clignote (lueur pulsée à la couleur du niveau via
      currentColor) pour attirer l'œil. Désactivé si l'utilisateur
      préfère moins d'animations (prefers-reduced-motion).
   2) Au clic, une pop-up réaffiche le message EN GRAND (le bandeau du
      coin est compact, message tronqué à 4 lignes → illisible). */
@keyframes prodHintBlink {
  0%, 100% { box-shadow: 0 0 0 0 transparent; opacity: .72; }
  50%      { box-shadow: 0 0 0 2px currentColor, 0 0 10px 2px currentColor, 0 0 26px 8px currentColor; opacity: 1; }
}
#prod-hint.visible {
  animation: prodHintBlink 1s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  #prod-hint.visible { animation: none; }
}

/* Pop-up d'alerte : message lisible en grand. Réutilise .dsh-modal. */
/* La topbar/resbar sont à z-index 200/190 (échelle héritée, cf. « ZONE A
   SACRÉE ») — au-dessus de l'échelle des modales (--z-modal:40). On monte donc
   le fond de CETTE pop-up au-dessus d'elles pour que l'en-tête (titre + bouton ×)
   ne soit pas masqué. On centre aussi verticalement (pop-up courte). */
/* v0.0.26 : le `z-index: 1000` qui ouvrait cette règle a été retiré — le
   défaut est corrigé à la racine (§21.3.5). Le centrage vertical, lui,
   RESTE : il n'a rien à voir avec les couches, il sert à poser cette
   pop-up courte au milieu de l'écran plutôt qu'en haut. */
.prod-hint-backdrop {
  align-items: center;
  padding-top: var(--space-4);
}
.prod-hint-backdrop .ph-pop { max-width: 520px; }
.ph-pop .ph-pop-ico { font-size: 1.35rem; margin-right: .35rem; }
.ph-pop-msg {
  font-size: 1.15rem;
  line-height: 1.6;
  color: var(--color-text-primary, #cbd6ea);
}
.ph-pop-msg b { color: #fff; }
/* Accent de bordure selon le niveau de l'alerte */
.ph-pop--warn .dsh-modal { border-color: rgba(245, 158, 11, .55); }
.ph-pop--err  .dsh-modal { border-color: rgba(233, 69, 96, .55); }
.ph-pop--info .dsh-modal { border-color: rgba(14, 165, 233, .5); }

/* ════════════════════════════════════════════════════════════════════
   v2.18 — VUE « ARBRE DE PROGRESSION » (features/arbre/arbre.js)
   La toile porte le dessin d'arbre (bloc « L'ARBRE DESSINÉ », .arbre-d-*).
   Convention : vert = prérequis atteint, bleu = manquant, violet = ce que
   ça débloque.
   ════════════════════════════════════════════════════════════════════ */
.arbre-loading { color: var(--color-text-secondary); padding: var(--space-6); text-align: center; }

/* Onglets de filtre par famille */
.arbre-cats { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }
.arbre-cat {
  background: var(--color-bg-card); color: var(--color-text-secondary);
  border: 1px solid var(--color-border-subtle); border-radius: var(--radius-pill);
  padding: 6px 14px; font-family: var(--font-body); font-size: var(--text-small, 13px);
  font-weight: var(--font-weight-medium); cursor: pointer;
  transition: border-color var(--anim-fast), color var(--anim-fast);
}
.arbre-cat:hover { border-color: var(--color-border-strong); color: var(--color-text-primary); }
.arbre-cat.active { color: var(--accent2); border-color: var(--accent2); background: rgba(14,165,233,.08); }
.arbre-cat-n { opacity: .55; font-family: var(--font-mono); font-size: 11px; }

/* Disposition : arbre + panneau de détail */
.arbre-layout { display: grid; grid-template-columns: minmax(0,1fr) 300px; gap: var(--space-4); align-items: start; }
@media (max-width: 980px) { .arbre-layout { grid-template-columns: 1fr; } }

/* Toile de l'arbre : elle porte le SVG du dessin */
.arbre-canvas {
  position: relative; overflow-x: auto;
  background: linear-gradient(180deg, rgba(15,24,40,.55), rgba(8,12,24,.55));
  border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md);
  padding: var(--space-5) var(--space-3);
}
/* Icône ronde d'un élément — posée par le panneau de détail
   (.arbre-detail-ico) quand l'élément a une illustration. */
.arbre-node-ico {
  width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center;
  justify-content: center; font-size: 24px; background-color: rgba(255,255,255,.04);
  background-size: cover; background-position: center;
  border: 1px solid rgba(255,255,255,.10);
}

/* Bande « sans prérequis » */
.arbre-isolated { margin-top: var(--space-4); }
.arbre-isolated-title {
  font-family: var(--hud); font-size: .62rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text2); margin-bottom: var(--space-2);
}
.arbre-isolated-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.arbre-chip {
  background: var(--color-bg-card); border: 1px solid var(--color-border-subtle);
  border-left: 3px solid var(--fam, var(--color-border-strong));
  color: var(--color-text-secondary); border-radius: var(--radius-sm);
  padding: 3px 9px; font-size: 11px; font-family: var(--font-body); cursor: pointer;
  transition: border-color var(--anim-fast), color var(--anim-fast);
}
.arbre-chip:hover { border-color: var(--color-border-strong); color: var(--color-text-primary); }
/* v2.46 : une puce peut être la cible d'un lien venu d'ailleurs (panneau
   de focus d'un vaisseau verrouillé) — elle doit se voir comme un nœud. */
.arbre-chip.is-selected {
  color: var(--color-text-primary);
  border-color: var(--fam, var(--color-border-strong));
  box-shadow: 0 0 0 1px var(--fam, transparent), 0 0 14px rgba(255, 255, 255, 0.08);
}

/* Légende */
.arbre-legende {
  display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-3);
  color: var(--color-text-secondary); font-size: 11px; font-family: var(--font-body);
}
.arbre-leg-item { display: inline-flex; align-items: center; gap: 5px; }
.arbre-leg-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.arbre-leg-edge { width: 18px; height: 0; border-top: 2px solid rgba(110,130,170,.45); display: inline-block; }
.arbre-leg-edge-ok { border-top-color: rgba(34,197,94,.6); }
.arbre-leg-edge-up { border-top-color: #0ea5e9; }
.arbre-leg-edge-down { border-top-color: #a855f7; }

/* ═══════════════════════════════════════════════════════════════════
   L'ARBRE DESSINÉ (features/arbre/arbre.js)
   ═══════════════════════════════════════════════════════════════════
   La toile contient UN SEUL SVG, déjà complet et déjà recadré sur son
   contenu. Il n'y a donc rien à mesurer ni
   à repositionner ici : la feuille de style ne fait que décider de sa
   TAILLE, et c'est tout ce qui sépare le confort du dessin sur un grand
   écran de son confort sur un téléphone.

   ⚠️ TROIS TAILLES, PAS UNE. « Ajusté » fait tenir l'arbre entier dans la
   largeur — parfait pour la vue d'ensemble, illisible sur 390 px. « Grand »
   et « Très grand » lui donnent une largeur fixe et laissent la toile
   défiler : c'est ainsi qu'on lit les noms au téléphone.
   ─────────────────────────────────────────────────────────────────── */
/* ⚠️ SANS CE `min-width: 0`, LE DESSIN NE DÉFILE PAS, IL POUSSE. Mesuré sur
   une fenêtre de 390 px, taille « Grand » : la toile faisait 1 800 px de LARGE
   au lieu d'en faire 390 et de laisser défiler son contenu. C'est le piège
   classique de la grille CSS — une colonne `1fr` porte un `min-width: auto`
   implicite, donc elle s'élargit jusqu'à la taille de ce qu'elle contient au
   lieu de le contraindre. Le `overflow-x: auto` de la toile n'avait alors plus
   rien à faire défiler.
   La règle est portée par `.is-dessin`, que le rendu de l'arbre pose
   systématiquement sur sa colonne et sur sa toile (arbre.js) : c'est
   aujourd'hui le seul rendu de l'arbre, la classe est donc toujours là. */
.arbre-main.is-dessin { min-width: 0; }
.arbre-canvas.is-dessin { padding: 0; }
.arbre-canvas.is-dessin .arbre-d-svg { display: block; height: auto; }
.arbre-canvas.is-dessin.arbre-zoom-1 .arbre-d-svg { width: 100%; }
.arbre-canvas.is-dessin.arbre-zoom-2 .arbre-d-svg { width: 1800px; max-width: none; }
.arbre-canvas.is-dessin.arbre-zoom-3 .arbre-d-svg { width: 2700px; max-width: none; }

/* ⚠️ UNE TOILE AGRANDIE DOIT ÊTRE UNE FENÊTRE, PAS UNE AFFICHE. Mesuré sur
   390 px en « Très grand » : le dessin faisait 2 700 px de large et donc
   ~2 500 px de HAUT, poussés dans la page — on se retrouvait au milieu d'un
   vide, à faire défiler la page entière pour retrouver l'arbre. On borne
   donc la hauteur et on laisse la toile défiler dans les DEUX sens : le
   dessin se parcourt à l'intérieur de son cadre, comme une carte. */
.arbre-canvas.is-dessin.arbre-zoom-2,
.arbre-canvas.is-dessin.arbre-zoom-3 { max-height: 78vh; overflow: auto; }

/* Un élément se clique — à la souris comme au clavier. */
.arbre-d-item { cursor: pointer; }
.arbre-d-nom  { cursor: pointer; font-family: var(--font-body); }
.arbre-d-item:focus-visible circle:nth-of-type(4),
.arbre-d-nom:focus-visible { outline: 2px solid var(--accent2); outline-offset: 2px; }

/* Le sélecteur de taille, au-dessus de la toile. */
.arbre-d-zoom { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-2); }

/* Le pointillé de la légende : un prérequis qui ne porte pas la branche. */
.arbre-leg-edge-fil { border-top-style: dashed; border-top-color: rgba(110,130,170,.7); }

@media (max-width: 700px) {
  /* Sur un téléphone, « Ajusté » ne sert qu'à situer l'arbre : les trois
     boutons prennent toute la largeur pour qu'on passe vite à une taille
     lisible. */
  .arbre-d-zoom { gap: 6px; }
  .arbre-d-zoom .arbre-cat { flex: 1; padding: 6px 4px; text-align: center; }
  .arbre-canvas.is-dessin.arbre-zoom-2,
  .arbre-canvas.is-dessin.arbre-zoom-3 { max-height: 68vh; }
}

/* Panneau de détail (chaîne de déblocage) */
.arbre-detail {
  background: var(--color-bg-card); border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md); padding: var(--space-4);
  position: sticky; top: calc(var(--resbar-bottom, 88px) + 16px);
  max-height: calc(100vh - var(--resbar-bottom, 88px) - 32px); overflow-y: auto;
}
.arbre-detail-empty { color: var(--color-text-secondary); font-size: 13px; line-height: 1.5; }
.arbre-detail-hdr {
  display: flex; align-items: center; gap: 10px; padding-bottom: var(--space-3);
  border-bottom: 2px solid var(--fam, var(--color-border-subtle)); margin-bottom: var(--space-3);
}
.arbre-detail-ico { font-size: 30px; line-height: 1; }
.arbre-detail-ico .arbre-node-ico { width: 46px; height: 46px; }
.arbre-detail-name { font-family: var(--font-body); font-weight: var(--font-weight-semibold); color: var(--color-text-primary); }
.arbre-detail-fam { font-size: 11px; color: var(--fam, var(--color-text-secondary)); }
.arbre-state { display: inline-block; font-size: 11px; border-radius: var(--radius-pill); padding: 2px 10px; margin-bottom: var(--space-2); }
.arbre-state.owned { color: #86efac; background: rgba(34,197,94,.12); border: 1px solid rgba(34,197,94,.4); }
.arbre-state.ok    { color: #7dd3fc; background: rgba(14,165,233,.12); border: 1px solid rgba(14,165,233,.4); }
.arbre-state.ko    { color: #fca5a5; background: rgba(233,69,96,.12); border: 1px solid rgba(233,69,96,.4); }
/* v2.47 : l'état et l'accès au module partagent la même ligne — c'est le
   vide à droite de la pastille d'état qui accueille le bouton. Il passe à
   la ligne tout seul quand le nom du module est long. */
.arbre-detail-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-bottom: var(--space-2);
}
.arbre-detail-actions .arbre-state { margin-bottom: 0; }
.arbre-goto {
  /* Il tient sur la ligne de l'état quand la place le permet, et occupe
     toute la sienne dès qu'il passe à la ligne — la fiche fait ~300 px,
     un bouton orphelin collé à droite y aurait l'air d'un accident. */
  flex: 1 1 190px;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  font-size: 11px; font-family: var(--font-body); cursor: pointer;
  padding: 2px 10px; border-radius: var(--radius-pill);
  color: var(--turquoise, #2dd4bf);
  background: rgba(45, 212, 191, 0.1);
  border: 1px solid rgba(45, 212, 191, 0.4);
  transition: background var(--anim-fast), border-color var(--anim-fast);
}
.arbre-goto:hover { background: rgba(45, 212, 191, 0.2); border-color: #2dd4bf; }
.arbre-detail-desc { font-size: 12px; color: var(--color-text-secondary); line-height: 1.5; margin-bottom: var(--space-2); }
.arbre-detail-sec {
  font-family: var(--hud); font-size: .6rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--turquoise); margin: var(--space-3) 0 var(--space-2);
  border-bottom: 1px solid var(--color-border-subtle); padding-bottom: 4px;
}
.arbre-req { font-size: 12px; line-height: 1.7; color: var(--color-text-secondary); }
.arbre-req.ok { color: #86efac; }
.arbre-req.ko { color: #fca5a5; }
.arbre-req-cur { opacity: .6; font-family: var(--font-mono); font-size: 11px; }
/* v0.0.136 : la fiche dit sur QUEL vaisseau les bâtiments d'une recherche
   ont été mesurés. */
.arbre-req-note { font-size: 11.5px; line-height: 1.45; color: var(--text2, #6b8ab0); margin: 2px 0 8px; }
.arbre-req-note b { color: var(--text, #c8d6f0); }
.arbre-unlock {
  display: flex; align-items: center; gap: 6px; width: 100%; text-align: left;
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--color-text-secondary); font-size: 12px; padding: 3px 6px; cursor: pointer;
}
.arbre-unlock:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
.arbre-unlock-lv { font-family: var(--font-mono); font-size: 10px; color: var(--gold); min-width: 38px; }

/* ═══════════════════════════════════════════════════════════════════
   v2.21 — VUE GLOBALE : bascule Plan/Données + conteneur du plan
   ═══════════════════════════════════════════════════════════════════
   Le dessin lui-même — la Cité-Nef — vit plus bas, sous `.cn-`
   (features/vm/cite-nef.js). Ici : les deux boutons de mode et la
   boîte qui accueille le plan.
   ═══════════════════════════════════════════════════════════════════ */

/* Bascule Plan / Données dans l'en-tête de la vue globale */
.ov-mode-switch { display: flex; gap: 4px; align-items: center; margin-left: 12px; }
.ov-mode-btn {
  font-family: var(--hud); font-size: .62rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text2);
  background: rgba(255,255,255,.02); border: 1px solid var(--border);
  border-radius: 6px; padding: 6px 10px; cursor: pointer; transition: .15s;
}
.ov-mode-btn:hover { color: var(--text); border-color: var(--border2); }
.ov-mode-btn.active {
  color: #7dd3fc; border-color: rgba(14,165,233,.55); background: rgba(14,165,233,.10);
}

/* Conteneur du plan */
#ship-blueprint {
  margin-top: .6rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background:
    radial-gradient(120% 80% at 50% 30%, rgba(14,165,233,.06), transparent 70%),
    var(--card);
  overflow-x: auto;     /* écrans étroits : on fait défiler plutôt que d'écraser */
}

/* Écrans étroits : la bascule Plan/Données passe sous le titre */
@media (max-width: 640px) {
  .ov-mode-switch { margin-top: .4rem; }
}

/* ═══════════════════════════════════════════════════════════════════
   v2.21 → v2.27 — BÂTIMENTS ET RECHERCHES CÔTE À CÔTE (vue VM)
   ═══════════════════════════════════════════════════════════════════
   v2.21 : chaque section de module avait deux facettes commutables
   (onglets « Bâtiments » / « Recherches »).
   v2.27 (demande Loryan : « je veux que les deux s'affichent en même
   temps, sans avoir besoin de cliquer sur un bouton ») : les onglets
   disparaissent, la section devient deux COLONNES toujours visibles :
     #split-<sec>   → conteneur en grille (2 colonnes)
     #main-<sec>    → colonne gauche : bâtiments (ou production)
     #techs-<sec>   → colonne droite : technos du module
   La colonne des bâtiments est un peu plus large (elle porte plus de
   vignettes) ; un module sans techno replie la colonne droite
   (.is-single) et les bâtiments reprennent toute la largeur.
   Logique : features/vm/module-techs.js.
   ═══════════════════════════════════════════════════════════════════ */

.module-split {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 1.1rem;
  align-items: start;
}
.module-split.is-single { grid-template-columns: minmax(0, 1fr); }
.module-split.is-single .module-col--tech { display: none; }
/* Séparation discrète entre les deux colonnes (elles portent des
   vignettes de même gabarit : sans repère l'œil les confond). */
.module-col--tech {
  border-left: 1px solid var(--border);
  padding-left: 1.1rem;
}
/* Titre de colonne : reprend le ton des onglets qu'il remplace. */
.module-col-hdr {
  display: flex; align-items: center; gap: 6px;
  margin: 0 0 0.7rem;
  font-family: var(--mono, monospace);
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text2);
}
@media (max-width: 1180px) {
  /* Écran étroit : les deux colonnes s'empilent (bâtiments puis technos). */
  .module-split { grid-template-columns: minmax(0, 1fr); gap: 1.4rem; }
  .module-col--tech { border-left: 0; padding-left: 0; }
}

/* Badge « ⏳ N » sur le titre Recherches quand une techno du module est
   dans la file — même esprit que .ship-tab-count, mais doré. */
.facet-queue-badge {
  font-family: var(--mono, monospace);
  font-size: 0.66rem;
  color: var(--gold);
  background: rgba(245, 158, 11, 0.12);
  border: 1px solid rgba(245, 158, 11, 0.4);
  border-radius: 8px;
  padding: 1px 6px;
  margin-left: 4px;
}

/* La grille de technos réutilise .res-section-grid (même densité que la
   vue Recherche). On borne juste la largeur des cartes dans ce contexte. */
.module-techs { padding-bottom: 1rem; }
.module-techs-empty {
  color: var(--text3);
  font-size: 0.8rem;
  padding: 1rem 0.2rem;
}

/* Sous-onglets de catégories (Vaisseaux/Défenses), sous le titre de colonne. */
.module-main > .ship-tabs { margin-bottom: 0.8rem; }

/* ═══════════════════════════════════════════════════════════════════
   v2.22 — VIGNETTES COMPACTES + PANNEAU DE DÉTAIL DES MODULES
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : « il faudrait que deux lignes soient visibles en
   une page sans défilement ». Les anciennes cartes (.bld) faisaient
   ~250 px de haut — deux rangées ne tenaient jamais dans un écran.

   Nouveau modèle, calqué sur l'Arbre de progression :
     .bld-tile / .conn-tile → vignette ~112 px (illustration, nom,
       niveau, UNE stat clé). Cliquable = sélectionne.
     #module-detail         → colonne de droite, tout le détail de
       l'élément sélectionné + les boutons d'action.

   Deux rangées de vignettes + en-tête + onglets tiennent dans ~430 px,
   soit largement moins que la hauteur utile de #ship-main sur un écran
   de 800 px. À partir de la 3e rangée, le défilement reprend son cours.
   Logique : features/vm/module-detail.js.
   ═══════════════════════════════════════════════════════════════════ */

/* Colonne de détail : n'existe que sur les sections qui listent des
   éléments sélectionnables (classe posée par renderModuleDetail). */
#ship-main.has-detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 316px;
  gap: 1rem;
  align-items: start;
}
/* v2.29 — Colonne de droite de la vue VM : elle empile le panneau de focus
   et la barre des files d'attente (dockée depuis le bas de l'écran). C'est
   ELLE qui colle maintenant en haut pendant le défilement de la grille —
   avant, le sticky était porté par .module-detail seul. */
.ship-side {
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  /* Le contenu ne dépasse jamais la zone visible. La marge basse a fondu
     (100 px au lieu de 150) : plus rien ne traverse le bas de l'écran, mais
     il faut encore couvrir la barre d'ancrage et le padding de #ship-main,
     que --total-header-h ne compte pas. */
  max-height: calc(100vh - var(--total-header-h, 112px) - 100px);
}
.queues-dock { flex-shrink: 0; }

@media (max-width: 1180px) {
  /* Écran étroit : la colonne repasse SOUS la grille (une seule colonne). */
  #ship-main.has-detail { grid-template-columns: minmax(0, 1fr); }
  .ship-side { position: static; max-height: none; }
  .module-detail { position: static !important; max-height: none !important; }
}

.module-detail {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.9rem;
  /* v2.29 : la hauteur est bornée par .ship-side. Le panneau se comprime et
     défile tout seul pour laisser à la barre des files la place qu'il lui
     faut (elle, ne se comprime pas). */
  min-height: 0;
  overflow-y: auto;
}
.md-empty { color: var(--text3); font-size: 0.76rem; line-height: 1.5; }

/* En-tête du panneau : vignette d'illustration + nom + niveau */
.md-hdr {
  display: flex; align-items: center; gap: 0.6rem;
  padding-bottom: 0.6rem; margin-bottom: 0.6rem;
  border-bottom: 1px solid var(--border);
}
.md-art {
  width: 52px; height: 52px; flex-shrink: 0; border-radius: 6px;
  background-size: cover; background-position: center;
  border: 1px solid var(--border2);
}
.md-ico { font-size: 1.8rem; line-height: 1; width: 52px; text-align: center; }
.md-titles { min-width: 0; }
.md-name {
  font-family: var(--hud); font-size: 0.82rem; font-weight: 600;
  color: var(--text); letter-spacing: 0.04em;
}
.md-lv { font-family: var(--mono); font-size: 0.68rem; color: var(--accent2); margin-top: 0.15rem; }
.md-queued { color: var(--gold); }
/* ── v0.0.11 : le bouton « Arbre » de l'en-tête de fiche ──────────────
   Posé à droite du nom, identique sur les quatre familles (bâtiment,
   recherche, vaisseau, défense) — c'est ce qui en fait un réflexe.
   `margin-left:auto` le colle au bord droit de l'en-tête flex ; le mot
   « Arbre » s'efface sous 340 px pour ne garder que l'icône, le panneau
   de détail étant étroit. */
.md-arbre-btn {
  margin-left: auto; flex-shrink: 0; align-self: flex-start;
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.25rem 0.5rem; cursor: pointer;
  font-family: var(--hud); font-size: 0.62rem; letter-spacing: 0.04em;
  background: rgba(74, 222, 128, 0.08); color: #4ade80;
  border: 1px solid rgba(74, 222, 128, 0.45); border-radius: 5px;
  transition: background 0.15s, border-color 0.15s;
}
.md-arbre-btn:hover { background: rgba(74, 222, 128, 0.18); border-color: #4ade80; }
@media (max-width: 340px) { .md-arbre-btn-txt { display: none; } }
/* ── v0.0.53 : le bouton « Ouvrir ↗ » de l'en-tête du panneau MODULE ──
   Même place et même gabarit que le « 🌳 Arbre » des fiches de bâtiment
   (demande de Loryan, capture à l'appui) — seule la couleur change, pour
   dire qu'on va vers une section et non vers l'arbre. Le mot s'efface sur
   panneau étroit, la flèche suffit alors. */
.md-goto-btn {
  margin-left: auto; flex-shrink: 0; align-self: flex-start;
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.25rem 0.5rem; cursor: pointer;
  font-family: var(--hud); font-size: 0.62rem; letter-spacing: 0.04em;
  background: rgba(56, 189, 248, 0.08); color: var(--accent2);
  border: 1px solid rgba(56, 189, 248, 0.45); border-radius: 5px;
  transition: background 0.15s, border-color 0.15s;
}
.md-goto-btn:hover { background: rgba(56, 189, 248, 0.2); border-color: var(--accent2); }
@media (max-width: 340px) { .md-goto-txt { display: none; } }
.md-desc { font-size: 0.7rem; color: var(--text2); line-height: 1.5; margin-bottom: 0.7rem; }
/* ── v0.0.8 : connecteurs — note de tarif progressif et chantier en cours ── */
.md-note {
  font-size: 0.64rem; color: var(--text2); line-height: 1.45;
  margin: 0.35rem 0 0.2rem; opacity: 0.85;
}
.conn-dev {
  margin: 0.5rem 0 0.2rem; padding: 0.5rem 0.6rem;
  border: 1px solid var(--border); border-radius: 6px;
  background: rgba(255, 255, 255, 0.03);
}
.conn-dev-hdr {
  font-family: var(--hud); font-size: 0.66rem; color: var(--gold);
  margin-bottom: 0.4rem;
}
/* La barre de la file fait 3 px : ici elle est seule dans son encadré et
   c'est le seul repère visuel d'avancement, donc on l'épaissit. */
.conn-dev .qbar-slot-bar { height: 6px; border-radius: 3px; }
.conn-dev-time {
  font-family: var(--mono); font-size: 0.66rem; color: var(--text2);
  margin-top: 0.3rem; text-align: right;
}
/* Le compte à rebours d'un chantier de connecteur, sur la vignette. */
.conn-tile-dev {
  font-family: var(--mono); font-size: 0.6rem; color: var(--gold);
  display: block;
}
.md-sec {
  font-family: var(--hud); font-size: 0.55rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--turquoise);
  margin: 0.7rem 0 0.4rem; padding-bottom: 0.25rem;
  border-bottom: 1px solid var(--border);
}
/* ── v0.0.5 : les deux flèches de projection de coût ──
   Elles vivent DANS le titre de section, à droite : c'est le seul endroit
   où elles se rattachent sans ambiguïté au bloc de coûts qui suit. Le
   titre passe donc en flex, sans changer de taille ni de bordure. */
.md-sec-proj {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem;
}
.md-proj { display: inline-flex; align-items: center; gap: 0.3rem; flex: none; }
.md-proj-btn {
  font-family: var(--mono); font-size: 0.6rem; line-height: 1;
  color: var(--turquoise); background: rgba(45, 212, 191, 0.08);
  border: 1px solid var(--border); border-radius: 3px;
  padding: 0.15rem 0.35rem; cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.md-proj-btn:hover:not(:disabled) {
  background: rgba(45, 212, 191, 0.2); border-color: var(--turquoise);
}
.md-proj-btn:disabled { opacity: 0.3; cursor: default; }
.md-proj-lv {
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.04em;
  color: var(--gold); min-width: 3.4em; text-align: center;
}
/* ── v0.0.6 : la DURÉE cumulée, sous les ressources cumulées
   Posée comme une ligne à part et non comme une
   pastille de coût : ce n'est pas une ressource qu'on dépense, c'est du temps
   qu'on attend, et la mélanger aux six minerais l'aurait fait lire comme un
   septième coût. */
.md-cumul-duree {
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.03em;
  color: var(--text2); margin-top: 0.35rem;
}
.md-cumul-duree strong { color: var(--gold); font-weight: 600; }

/* Les boutons d'action réutilisent .btn-up / .btn-conn */
.module-detail .btn-up,
.module-detail .btn-conn { width: 100%; margin-top: 0.6rem; }

/* ── Vignette d'un bâtiment ─────────────────────────────────────── */
.bld-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(184px, 1fr));
  gap: 0.6rem;
}
.bld-tile {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: space-between;
  min-height: 112px; padding: 0.5rem 0.6rem; text-align: left;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 6px; cursor: pointer; color: inherit; font: inherit;
  transition: border-color 0.15s, transform 0.12s;
}
.bld-tile:hover { border-color: var(--border2); transform: translateY(-1px); }
.bld-tile.is-selected { border-color: var(--accent2); box-shadow: 0 0 0 1px var(--accent2) inset; }
.bld-tile.is-building { border-color: var(--accent4); }
/* v2.35 : un bâtiment verrouillé se lit d'un coup d'œil — grisé, terni,
   et l'illustration désaturée. Avec l'arbre de progression, 35 bâtiments
   sur 38 le sont au démarrage : la distinction doit être franche, sinon
   le joueur clique dans le vide. Il reste cliquable, pour consulter le
   détail et voir ce qui manque. */
.bld-tile.is-locked   { opacity: 0.55; }
.bld-tile.is-locked .bld-tile-art  { filter: grayscale(0.85) brightness(0.7); }
.bld-tile.is-locked .bld-tile-name { color: var(--text2); }
.bld-tile.is-locked:hover { opacity: 0.8; }
.bld-tile-hdr { position: relative; z-index: 1; display: flex; align-items: center; gap: 0.4rem; }
.bld-tile-ico { font-size: 0.95rem; line-height: 1; }
.bld-tile-name {
  font-family: var(--hud); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.03em; color: var(--text); line-height: 1.25;
}
.bld-tile-lv {
  margin-left: auto; flex-shrink: 0; font-family: var(--mono); font-size: 0.66rem;
  padding: 0.1rem 0.35rem; border: 1px solid var(--border); border-radius: 3px;
  background: rgba(255, 255, 255, 0.04); color: var(--accent2); white-space: nowrap;
}
.bld-tile-queued { color: var(--gold); font-size: 0.6rem; }
/* La stat clé reprend le style .st des anciennes cartes */
.bld-tile-stat { position: relative; z-index: 1; display: block; margin-top: 0.4rem; }
.bld-tile-stat .st { font-size: 0.62rem; gap: 0.4rem; }
.bld-tile-stat .st-v { text-align: right; }

/* ── LA VIGNETTE SE LIT DE LOIN ──────────────────────────────────────
   Remarque de Loryan : « les indicateurs de production un peu plus gros,
   on voit pas grand-chose là ». Le chiffre était à 0,62 rem — la même
   taille que son propre libellé, dans un coin d'une vignette dont les
   trois quarts sont une illustration. C'est pourtant LE nombre qu'on vient
   lire. Il passe à 0,86 rem et prend du poids ; le libellé « Production »,
   lui, RÉTRÉCIT (0,55 rem) et s'estompe — il ne sert qu'à nommer la ligne,
   la répéter aussi fort que la valeur volait la moitié de l'attention.
   La ligne du sol, elle, reste discrète : c'est un complément, pas le
   sujet. */
/* ⚠️ LE LIBELLÉ PASSE AU-DESSUS, PAS À CÔTÉ. Côte à côte, « Production »
   et le nombre se partagent les ~145 px de la vignette : un débit un peu
   long (« +328.34M/h (×1000) ») s'y faisait couper en plein milieu. Empilé,
   le nombre dispose de toute la largeur — c'est précisément ce qu'on lui
   demande ici, et le libellé, minuscule, ne coûte qu'une ligne de 8 px. */
.bld-tile-stat .st { display: block; }
.bld-tile-stat .st-l {
  display: block; font-size: 0.56rem; color: var(--text3);
  text-transform: uppercase; letter-spacing: 0.08em; line-height: 1.1;
}
.bld-tile-stat .st-v {
  display: block; text-align: left; font-size: 0.98rem; font-weight: 600;
  line-height: 1.2; letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Les refus (« Ressource absente », « Connecteur non déployé ») sont des
   phrases, pas des nombres : en gros ils rempliraient la vignette. On les
   ramène à une taille de texte courant et on les laisse passer à la ligne ;
   la couleur rouge suffit à les signaler. */
.bld-tile-stat .st-v.r {
  font-size: 0.68rem; font-weight: 500; white-space: normal; line-height: 1.25;
}
/* Le sol restant sous un extracteur : ton mineur, et sur UNE ligne — c'est
   un complément, pas le sujet de la vignette. */
.bld-tile-stat .bld-tile-sol {
  display: flex; justify-content: space-between; gap: 0.3rem; margin-top: 0.25rem;
}
.bld-tile-stat .bld-tile-sol .st-l { display: inline; flex-shrink: 0; }
.bld-tile-stat .bld-tile-sol .st-v {
  display: inline; font-size: 0.66rem; font-weight: 500;
  color: var(--gold); text-align: right;
}
.bld-tile-stat .bld-tile-sol.is-vide .st-v { color: var(--accent1); }
.bld-tile-sol-eta { color: var(--text3); font-weight: 400; }

/* ─── v0.0.51 : LA SECONDE LIGNE DES SOURCES D'ÉNERGIE ────────────────
   (v0.0.51)
   Le facteur de la planète — ou le débit d'eau de la Centrale — passe
   SOUS le débit produit, et dans le même ton mineur que le sol restant
   d'un extracteur : deux gros chiffres empilés se disputeraient le regard
   alors qu'un seul des deux est le sujet de la vignette.
   ⚠️ La règle est scopée à `.bld-tile-stat` : la même ligne, dans le
   panneau de détail à droite, doit rester une ligne de stat ordinaire. */
.bld-tile-stat .vgn-sec {
  display: flex; justify-content: space-between; gap: 0.3rem; margin-top: 0.25rem;
}
.bld-tile-stat .vgn-sec .st-l { display: inline; flex-shrink: 0; }
.bld-tile-stat .vgn-sec .st-v {
  display: inline; font-size: 0.98rem; font-weight: 500;
  color: var(--text2); text-align: right; white-space: nowrap;
}
/* La raison du verrou a le droit de passer à la LIGNE : coupé à
   l'ellipse, un prérequis à trois membres (« Forge Spatiale niv 2 +
   Institut Quantique niv 1 ») perdait précisément le morceau qu'on
   venait lire. */
.bld-tile-lock {
  font-size: 0.7rem; color: var(--gold);
  display: block; white-space: normal; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis;
}

/* Illustration de fond (mêmes images que les anciennes cartes) */
.bld-tile-art {
  position: absolute; inset: 0; z-index: 0;
  background-size: cover; background-position: center;
  transition: transform 0.4s ease;
}
.bld-tile--illus:hover .bld-tile-art { transform: scale(1.05); }
.bld-tile-art::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(6,10,20,.62) 0%, rgba(6,10,20,.5) 45%, rgba(6,10,20,.86) 100%);
}
.bld-tile--illus .bld-tile-name,
.bld-tile--illus .bld-tile-stat .st-l,
.bld-tile--illus .bld-tile-stat .st-v { text-shadow: 0 1px 3px rgba(0,0,0,.8); }
.bld-tile--illus .bld-tile-name { color: #f2f6ff; }
.bld-tile--illus .bld-tile-lv { background: rgba(6,10,20,.55); }

/* ── Vignette d'un connecteur ───────────────────────────────────── */
.conn-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(184px, 1fr));
  gap: 0.6rem;
}
.conn-tile {
  display: flex; flex-direction: column; gap: 0.25rem;
  min-height: 92px; padding: 0.6rem 0.7rem; text-align: left;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 6px; cursor: pointer; color: inherit; font: inherit;
  transition: border-color 0.15s, transform 0.12s;
}
.conn-tile:hover { border-color: var(--border2); transform: translateY(-1px); }
.conn-tile.is-selected { border-color: var(--accent2); box-shadow: 0 0 0 1px var(--accent2) inset; }
.conn-tile-hdr { display: flex; align-items: center; gap: 0.4rem; }
.conn-tile-ico { font-size: 1rem; line-height: 1; }
.conn-tile-name { font-family: var(--hud); font-size: 0.66rem; color: var(--text); letter-spacing: 0.03em; }
.conn-tile-state { font-family: var(--mono); font-size: 0.66rem; }
.conn-tile.is-on  .conn-tile-state { color: var(--accent4); }
.conn-tile.is-off .conn-tile-state { color: var(--text3); }
.conn-tile-pct { font-size: 0.62rem; color: var(--text2); }
/* v2.25 : niveau du connecteur + bonus d'extraction qu'il apporte */
.conn-tile-lv {
  font-family: var(--mono); font-size: 0.62rem; color: var(--accent4);
  margin-top: 2px;
}
.conn-tile.is-off .conn-tile-lv { color: var(--text2); }

/* v2.24 : connecteur dont la ressource est ABSENTE de la planète ancrée —
   affiché quand même (les 6 sont toujours visibles), mais estompé. */
.conn-tile.is-absent { opacity: 0.55; }
.conn-tile.is-absent .conn-tile-state { color: var(--text3); }

/* v2.24 : illustration plein cadre des connecteurs (même pattern que
   .bld-tile--illus : image + voile dégradé, contenu au-dessus). */
.conn-tile { position: relative; overflow: hidden; }
.conn-tile-art {
  position: absolute; inset: 0; z-index: 0;
  background-size: cover; background-position: center;
  transition: transform 0.4s ease;
}
.conn-tile--illus:hover .conn-tile-art { transform: scale(1.05); }
.conn-tile-art::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(6,10,20,.62) 0%, rgba(6,10,20,.48) 45%, rgba(6,10,20,.86) 100%);
}
.conn-tile--illus .conn-tile-hdr,
.conn-tile--illus .conn-tile-state,
.conn-tile--illus .conn-tile-pct { position: relative; z-index: 1; }
.conn-tile--illus .conn-tile-name,
.conn-tile--illus .conn-tile-pct { text-shadow: 0 1px 3px rgba(0,0,0,.8); }
.conn-tile--illus .conn-tile-name { color: #f2f6ff; }
/* v0.0.8 : le niveau du connecteur et le compte à rebours de son chantier
   passent AU-DESSUS de l'illustration. Sans
   ces deux lignes, ils restent sous le voile — donc invisibles. */
.conn-tile--lisible .conn-tile-lv,
.conn-tile--lisible .conn-tile-dev { position: relative; z-index: 1; text-shadow: 0 1px 3px rgba(0,0,0,.8); }
/* Une ligne de plus a besoin d'une vignette un peu plus haute, sinon le
   compte à rebours est rogné par le `overflow: hidden` de la vignette. */
.conn-tile--chantier { min-height: 112px; }

.module-empty { color: var(--text3); font-size: 0.8rem; padding: 1rem 0.2rem; }

/* v2.22 — Bouton d'action posé à côté d'un titre de page (renommage du VM) */
.pg-title-btn {
  margin-left: 8px; padding: 2px 6px; font-size: 0.8rem; line-height: 1;
  background: rgba(255, 255, 255, 0.03); border: 1px solid var(--border);
  border-radius: 5px; color: var(--text2); cursor: pointer;
  vertical-align: middle; transition: 0.15s;
}
.pg-title-btn:hover { border-color: var(--accent2); background: rgba(14, 165, 233, 0.12); }

/* v2.22 — Vignette d'une technologie (facette Recherches d'un module).
   Même gabarit que .bld-tile, sans illustration de fond. */
.tech-tile {
  display: flex; flex-direction: column; justify-content: space-between;
  min-height: 92px; padding: 0.5rem 0.6rem; text-align: left;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 6px; cursor: pointer; color: inherit; font: inherit;
  transition: border-color 0.15s, transform 0.12s;
}
.tech-tile:hover { border-color: var(--border2); transform: translateY(-1px); }
.tech-tile.is-selected  { border-color: var(--accent2); box-shadow: 0 0 0 1px var(--accent2) inset; }
.tech-tile.is-searching { border-color: var(--gold); }
/* v2.35 : même traitement que les bâtiments — avec l'arbre, toutes les
   technos sont verrouillées tant qu'il n'y a pas d'Institut Quantique. */
.tech-tile.is-locked    { opacity: 0.55; }
.tech-tile.is-locked .tech-tile-art  { filter: grayscale(0.85) brightness(0.7); }
.tech-tile.is-locked:hover { opacity: 0.8; }
.tech-tile-effect {
  display: block; margin-top: 0.4rem;
  font-size: 0.63rem; color: var(--text2); line-height: 1.35;
}
.tech-tile.is-locked .tech-tile-effect { color: var(--gold); }

/* ════════════════════════════════════════════════════════════════════
   v2.30 — Vue COMMANDEMENT
   ════════════════════════════════════════════════════════════════════ */
/* ════════════════════════════════════════════════════════════════════
   LE POSTE DE PILOTAGE (features/command/pilotage.js) : toutes les
   classes `cmdp-`.
   ⚠️ PLANCHER DE LECTURE : rien sous 0,8 rem (11,2 px) — la racine du jeu
   est à 14 px. Les étiquettes en capitales comprises.
   ════════════════════════════════════════════════════════════════════ */
.cmdp { padding: 0 1.2rem 1.4rem; }
.cmdp-onglets { display: flex; flex-wrap: wrap; gap: .4rem; margin: .2rem 0 1rem;
  border-bottom: 1px solid var(--border); padding-bottom: .6rem; }
.cmdp-onglet { font: 600 .9rem var(--body); color: var(--text2); background: var(--bg2);
  border: 1px solid var(--border2); border-radius: 999px; padding: .45rem 1rem; cursor: pointer; }
.cmdp-onglet:hover { color: var(--text); border-color: var(--turquoise); }
.cmdp-onglet.is-active { color: #e6eeff; background: rgba(45,212,191,.12); border-color: var(--turquoise); }
.cmdp-onglet:focus-visible, .cmdp-btn:focus-visible { outline: 2px solid var(--turquoise); outline-offset: 2px; }
/* Interrupteur vueJournal — le journal de bord de la Vue globale
   (features/vm/journal-bord.js) : deux colonnes, le passé puis l'avenir,
   l'une sous l'autre au téléphone. Plancher de lecture : 0,8 rem. */
body.f-vueJournal .jb { grid-column: 1 / -1; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
body.f-vueJournal .jb-col { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; min-width: 0; }
body.f-vueJournal .jb-titre { font: 600 .8rem var(--hud); letter-spacing: .1em; text-transform: uppercase; color: var(--turquoise); margin: 0 0 6px; }
body.f-vueJournal .jb-passe .jb-titre { color: var(--text3); }
body.f-vueJournal .jb-ev { display: grid; grid-template-columns: 92px 24px minmax(0, 1fr); gap: 8px; align-items: start;
  padding: 7px 0; border-bottom: 1px solid var(--border); font-size: .88rem; color: var(--text); }
body.f-vueJournal .jb-ev:last-child { border-bottom: 0; }
body.f-vueJournal .jb-quand { font-family: var(--mono); font-size: .82rem; color: var(--text2); }
body.f-vueJournal .jb-quand small, body.f-vueJournal .jb-texte small { display: block; font-size: .8rem; color: var(--text3); }
body.f-vueJournal .jb-avenir .jb-quand { color: var(--turquoise); }
body.f-vueJournal .jb-ev.est-alerte .jb-texte { color: #fcd34d; }
body.f-vueJournal .jb-vide { font-size: .86rem; color: var(--text3); padding: 6px 0; }
@media (max-width: 700px) { body.f-vueJournal .jb { grid-template-columns: minmax(0, 1fr); } }
/* Interrupteur empireSansDoctrine : le dépliant des quatre classes au bout de
   la rangée d'onglets, poussé à droite. Sa languette passe au plancher de
   lecture du poste (0,8 rem) même sans classesRefondues. Au téléphone, la
   règle `.clse-encart { width: 100% }` le renvoie sur sa propre ligne, et il
   se déplie dans le flux. */
body.f-empireSansDoctrine .cmdp-onglets { align-items: center; }
body.f-empireSansDoctrine .cmdp-onglets .clse-encart { margin-left: auto; }
body.f-empireSansDoctrine .cmdp-onglets .clse-tete { font-size: .8rem; white-space: normal; }
.cmdp-etiq { display: block; font: 600 .8rem var(--hud); letter-spacing: .08em; text-transform: uppercase;
  color: var(--text3); margin-bottom: .15rem; }
.cmdp-sous { display: block; font-size: .82rem; color: var(--text3); line-height: 1.4; }
.cmdp-mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.cmdp-mono.est-rouge, .est-rouge.cmdp-mono { color: #fda4af; }
.cmdp-note { font-size: .82rem; color: var(--text3); margin: .8rem .2rem 0; line-height: 1.5; }
.cmdp-vide { font-size: .88rem; color: var(--text3); padding: .4rem 0; }
.cmdp-btn { font: 600 .85rem var(--body); color: #e6eeff; background: var(--bg2); border: 1px solid var(--border2);
  border-radius: 6px; padding: .4rem .75rem; cursor: pointer; white-space: nowrap; }
.cmdp-btn:hover:not([disabled]) { border-color: var(--turquoise); }
.cmdp-btn.est-fort { background: rgba(45,212,191,.14); border-color: rgba(45,212,191,.6); color: #99f6e4; }
.cmdp-btn[disabled] { opacity: .45; cursor: not-allowed; }

/* ─── Passerelle ─── */
.cmdp-synthese { display: grid; grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); gap: .6rem; margin-bottom: 1rem; }
.cmdp-chiffre { border: 1px solid var(--border); background: var(--bg2); border-radius: 8px; padding: .6rem .8rem; }
.cmdp-chiffre b { display: block; font: 1.5rem var(--mono); color: #e6eeff; line-height: 1.2; }
.cmdp-chiffre.est-alerte { border-color: rgba(245,158,11,.5); }
.cmdp-chiffre.est-alerte b { color: var(--gold); }
.cmdp-entete, .cmdp-ligne {
  display: grid; align-items: center; gap: .4rem .9rem;
  grid-template-columns: minmax(150px, 1fr) minmax(170px, 1.3fr) minmax(110px, .8fr) 90px minmax(160px, 1.2fr) minmax(170px, 1.4fr) auto;
}
.cmdp-entete { padding: 0 .9rem .4rem; }
.cmdp-entete span { font: 600 .8rem var(--hud); letter-spacing: .08em; text-transform: uppercase; color: var(--text3); }
.cmdp-lignes { display: flex; flex-direction: column; gap: .4rem; }
.cmdp-ligne { border: 1px solid var(--border); background: var(--card); border-radius: 8px; padding: .65rem .9rem;
  font-size: .88rem; color: var(--text); }
.cmdp-ligne.est-actif { border-color: var(--border2); box-shadow: inset 3px 0 0 var(--accent2); }
.cmdp-ligne.est-rouge { border-color: rgba(233,69,96,.55); background: linear-gradient(90deg, rgba(233,69,96,.08), var(--card) 40%); }
.cmdp-ligne .cmdp-etiq { display: none; }   /* l'en-tête les porte, en large */
.cmdp-c-nom b { display: block; color: #e6eeff; font-size: .95rem; margin-bottom: .2rem; }
.cmdp-classe { display: inline-block; font-size: .8rem; font-weight: 600; color: var(--cc); border: 1px solid var(--cc);
  border-radius: 999px; padding: .05rem .5rem; white-space: nowrap; }
.cmdp-classe.est-sans { border-style: dashed; color: var(--text3); border-color: var(--border2); }
.cmdp-vol { color: var(--accent2); }
.cmdp-jauge { display: inline-block; width: 56px; height: 6px; border-radius: 3px; background: var(--border);
  overflow: hidden; vertical-align: middle; margin-right: .35rem; }
.cmdp-jauge i { display: block; height: 100%; background: var(--accent4); }
.cmdp-jauge.est-moyen i { background: var(--gold); }
.cmdp-jauge.est-bas i { background: var(--accent1); }
/* Interrupteur `echeancesParGisement` : la colonne « Planète » porte une
   ligne par gisement, le prochain à tomber en tête (doré, comme dans la
   fiche planète). Classes absentes quand il est éteint. */
/* ⚠️ UNE SEULE GRILLE POUR TOUTE LA LISTE (les `li` en `display: contents`) :
   une grille par ligne alignait chaque temps sur SA ligne, et « 3j 11h »
   ne tombait pas sous « 15j 11h ». Du coup l'opacité se pose sur les
   enfants — un élément `contents` n'a pas de boîte à rendre transparente. */
.cmdp-gisements { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: max-content max-content; gap: .15rem .8rem; align-items: center;
  font-size: .85rem; line-height: 1.35; }
.cmdp-gis { display: contents; }
.cmdp-gis-nom { color: var(--text2); white-space: nowrap; }
.cmdp-gis-nom .res-mini { vertical-align: -2px; }
.cmdp-gis > .cmdp-mono { text-align: right; white-space: nowrap; }
.cmdp-gis.est-premier .cmdp-gis-nom { color: var(--text); }
.cmdp-gis.est-premier > .cmdp-mono { color: var(--gold); font-weight: 600; }
.cmdp-gis.est-orange > .cmdp-mono { color: #fcd34d; }
.cmdp-gis.est-vide > * { opacity: .55; }
/* Sa colonne s'élargit (123 px mesurés à 1500 px : nom et temps s'y
   touchaient), prise sur le reste. ⚠️ SEULEMENT AU-DESSUS DE 1180 PX : ce
   sélecteur est plus précis que la pile de cartes du dessous, il l'écraserait. */
@media (min-width: 1181px) {
  .cmdp-entete.a-gisements, .cmdp-ligne.a-gisements {
    grid-template-columns: minmax(150px, 1fr) minmax(170px, 1.3fr) minmax(165px, 1.05fr) 90px minmax(150px, 1.1fr) minmax(170px, 1.3fr) auto;
  }
}
/* Au téléphone, une demi-carte ne tient plus « Tritanium 15j 11h » (mesuré
   à 320 px : 101 px de large, les temps coupés au bord). La liste prend
   toute la largeur, et « Système » remonte à côté de « Position » pour ne
   pas laisser de trou ; « Chantier » suit sur toute la largeur. */
@media (max-width: 480px) {
  .cmdp-ligne.a-gisements > .cmdp-c-pla, .cmdp-ligne.a-gisements > .cmdp-c-chan,
  .cmdp-ligne.a-gisements > .cmdp-c-alerte, .cmdp-ligne.a-gisements > .cmdp-c-act { order: 1; }
  .cmdp-ligne.a-gisements > .cmdp-c-pla, .cmdp-ligne.a-gisements > .cmdp-c-chan { grid-column: 1 / -1; }
}
.cmdp-puce { display: inline-block; font-size: .8rem; border-radius: 4px; padding: .1rem .4rem; margin: .1rem .25rem .1rem 0;
  line-height: 1.35; }
.cmdp-puce.est-orange { background: rgba(245,158,11,.13); color: #fcd34d; }
.cmdp-puce.est-rouge { background: rgba(233,69,96,.15); color: #fda4af; }
.cmdp-puce.est-ok { background: rgba(16,185,129,.12); color: #6ee7b7; }
.cmdp-c-act { display: flex; gap: .35rem; justify-content: flex-end; flex-wrap: wrap; }

/* ⚠️ EN DESSOUS DE 1180 PX, LE TABLEAU DEVIENT UNE PILE DE CARTES. Sept
   colonnes ne tiennent plus : plutôt que de faire défiler un tableau de
   côté (et de cacher les boutons au bout), chaque vaisseau devient une
   carte où chaque case porte son étiquette. */
@media (max-width: 1180px) {
  .cmdp-entete { display: none; }
  .cmdp-ligne { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  .cmdp-ligne .cmdp-etiq { display: block; }
  .cmdp-c-nom, .cmdp-c-alerte, .cmdp-c-act { grid-column: 1 / -1; }
  .cmdp-c-act { justify-content: flex-start; }
}

/* ─── Interrupteur passerelleDetaillee ───
   ⚠️ LES TITRES GLISSAIENT VERS LA DROITE (capture de Loryan) : la ligne des
   titres et chaque ligne de vaisseau sont deux grilles distinctes, et la
   dernière colonne (`auto`) valait 0 dans les titres, la largeur des boutons
   dans les lignes. Les colonnes `fr` se partageaient donc une place
   différente, et chaque titre se décalait un peu plus que le précédent. La
   colonne des boutons a désormais une largeur FIXE, la même des deux côtés, et
   les titres reçoivent le même cadre d'un pixel que les lignes. */
body.f-passerelleDetaillee .cmdp-entete { border: 1px solid transparent; border-top: 0; border-bottom: 0; }
body.f-passerelleDetaillee .cmdp-entete, body.f-passerelleDetaillee .cmdp-ligne {
  grid-template-columns: minmax(150px, 1fr) minmax(170px, 1.3fr) minmax(110px, .8fr) 118px minmax(160px, 1.2fr) minmax(170px, 1.4fr) 92px;
}
@media (min-width: 1181px) {
  body.f-passerelleDetaillee .cmdp-entete.a-gisements, body.f-passerelleDetaillee .cmdp-ligne.a-gisements {
    grid-template-columns: minmax(150px, 1fr) minmax(170px, 1.3fr) minmax(140px, .9fr) 118px minmax(160px, 1.2fr) minmax(170px, 1.3fr) 92px;
  }
}
@media (max-width: 1180px) {
  body.f-passerelleDetaillee .cmdp-ligne { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
body.f-passerelleDetaillee .cmdp-sys-date { white-space: nowrap; }
/* L'extraction, ressource par ressource : la tuile prend deux places. */
body.f-passerelleDetaillee .cmdp-extraction { grid-column: span 2; }
body.f-passerelleDetaillee .cmdp-extr { list-style: none; margin: .4rem 0 0; padding: 0; display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: .2rem .9rem; font-size: .85rem; }
body.f-passerelleDetaillee .cmdp-extr li { display: flex; align-items: center; gap: .35rem; min-width: 0; }
body.f-passerelleDetaillee .cmdp-extr-nom { color: var(--text2); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.f-passerelleDetaillee .cmdp-extr .cmdp-mono { color: #e6eeff; white-space: nowrap; }
body.f-passerelleDetaillee .cmdp-extr li.est-zero { opacity: .5; }
/* La ligne « Chantier » se déplie sur le reste de la file. */
body.f-passerelleDetaillee .cmdp-chan-pli > summary { list-style: none; cursor: pointer; }
body.f-passerelleDetaillee .cmdp-chan-pli > summary::-webkit-details-marker { display: none; }
body.f-passerelleDetaillee .cmdp-chan-plus { display: inline-block; font-size: .82rem; color: var(--turquoise); white-space: nowrap; }
body.f-passerelleDetaillee .cmdp-chan-plus::after { content: ' ▾'; }
body.f-passerelleDetaillee .cmdp-chan-pli[open] .cmdp-chan-plus::after { content: ' ▴'; }
body.f-passerelleDetaillee .cmdp-chan-pli > summary:hover .cmdp-chan-plus { text-decoration: underline; }
body.f-passerelleDetaillee .cmdp-chan-liste { list-style: none; margin: .35rem 0 0; padding: .3rem 0 0 .6rem;
  border-left: 2px solid var(--border2); font-size: .85rem; color: var(--text2); display: flex; flex-direction: column; gap: .2rem; }

/* ─── Navigation ─── */
.cmdp-nav { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 1rem; align-items: start; }
@media (max-width: 1180px) { .cmdp-nav { grid-template-columns: minmax(0, 1fr); } }
.cmdp-carte { display: block; width: 100%; height: auto; border: 1px solid var(--border); border-radius: 8px; }
.cmdp-carte-portee { fill: rgba(45,212,191,.05); stroke: #1e3060; stroke-dasharray: 4 5; }
.cmdp-carte-cand { fill: #3b5580; cursor: pointer; }
.cmdp-carte-cand:hover { fill: #7dd3fc; }
.cmdp-carte-cand.est-choisi { fill: none; stroke: var(--turquoise); stroke-width: 2.5; }
.cmdp-carte-vol { stroke: var(--accent2); stroke-width: 2; stroke-dasharray: 6 5; }
.cmdp-carte-nom { fill: #c8d6f0; font: 13px var(--body); paint-order: stroke; stroke: #03060d; stroke-width: 3px; }
.cmdp-carte-legende { margin-top: .4rem; }
.cmdp-bloc { border: 1px solid var(--border); background: var(--bg2); border-radius: 8px; padding: .8rem .9rem; }
.cmdp-bloc + .cmdp-bloc { margin-top: .8rem; }
.cmdp-bloc-titre { font: 600 .8rem var(--hud); letter-spacing: .1em; text-transform: uppercase; color: var(--turquoise); margin-bottom: .6rem; }
.cmdp-vol-ligne + .cmdp-vol-ligne { border-top: 1px solid var(--border); margin-top: .6rem; padding-top: .6rem; }
.cmdp-vol-tete { display: flex; justify-content: space-between; gap: .6rem; color: #e6eeff; }
.cmdp-barre { display: block; height: 5px; border-radius: 3px; background: var(--border); overflow: hidden; margin: .4rem 0 .5rem; }
.cmdp-barre i { display: block; height: 100%; background: var(--accent2); }
.cmdp-champ { display: block; margin-bottom: .6rem; }
.cmdp-champ select { width: 100%; max-width: 100%; font: .88rem var(--body); color: var(--text); background: var(--card);
  border: 1px solid var(--border2); border-radius: 6px; padding: .4rem .5rem; }
.cmdp-devis { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .4rem; margin: .4rem 0 .6rem; }
.cmdp-devis > div { background: var(--card); border: 1px solid var(--border); border-radius: 6px; padding: .4rem .55rem; }
.cmdp-devis b { display: block; font-size: 1.05rem; color: #e6eeff; }
.cmdp-refus, .cmdp-avert { font-size: .84rem; line-height: 1.45; border-radius: 6px; padding: .35rem .55rem; margin-bottom: .35rem; }
.cmdp-refus { background: rgba(233,69,96,.12); color: #fda4af; }
.cmdp-avert { background: rgba(245,158,11,.1); color: #fcd34d; }
.cmdp-actions { display: flex; gap: .4rem; flex-wrap: wrap; margin-top: .6rem; }

/* ─── Doctrine ─── */
.cmdp-doctrine { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: .6rem; margin-bottom: 1rem; align-items: start; }
.cmdp-famille { border: 1px solid var(--border2); border-top: 3px solid var(--cc, var(--border2)); background: var(--bg2);
  border-radius: 8px; padding: .65rem .8rem; }
.cmdp-famille.est-sans { border-style: dashed; border-top-style: dashed; --cc: var(--gold); }
.cmdp-famille-tete { font-weight: 600; color: var(--cc); font-size: .9rem; }
.cmdp-famille-n { display: block; font: 1.7rem var(--mono); color: #e6eeff; margin: .1rem 0 .2rem; }
/* L'encart des quatre classes est le composant du Hangar (classes.js), repris
   tel quel. Son tableau ne se replie pas : au téléphone, il dépassait de
   l'écran — il défile désormais dans sa propre boîte. */
.cmdp-encart-classes { margin-bottom: 1rem; max-width: 100%; overflow-x: auto; }
.cmdp-empire-ligne { display: grid; grid-template-columns: 110px minmax(0, 1fr) 80px; gap: .7rem; align-items: center;
  font-size: .88rem; padding: .2rem 0; }
.cmdp-empire-ligne .cmdp-barre { margin: 0; }
.cmdp-empire-ligne .cmdp-barre i { background: var(--turquoise); }
.cmdp-empire .cmdp-sous { margin: .6rem 0; }
@media (max-width: 480px) {
  .cmdp { padding: 0 .2rem 1rem; }
  .cmdp-devis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cmdp-empire-ligne { grid-template-columns: 90px minmax(0, 1fr) 64px; }
}

.cmd-empty { color: var(--text3); font-size: 0.8rem; padding: 1.5rem; }
.cmd-section { margin: 0 auto 1.4rem; padding: 0 1.2rem; }

/* v2.31 — Vue LARGE : le Commandement avait trop d'espace mort sur les
   côtés (demande Loryan). Les .dsh-view sont bornées à 1200 px et centrées
   SANS tenir compte du panneau Vaisseaux Mères ; celle-ci prend désormais
   toute la largeur utile, en réservant juste la place du panneau de
   droite. Au-dessus de 700 px seulement : en dessous, le panneau est un
   drawer et le contenu reprend toute la largeur. */
@media (min-width: 701px) {
  #dsh-view-command {
    max-width: none;
    /* .dsh-view porte width:100% — sans width:auto, la marge droite
       déborderait au lieu de rétrécir la boîte. */
    width: auto;
    /* ⚠️ v0.0.49 — LA PLACE DU PANNEAU ÉTAIT RÉSERVÉE DEUX FOIS, et c'est
       tout le vide que Loryan avait entouré en rouge sur sa capture. Cette
       règle posait `margin-right: 180px` pour laisser passer le panneau des
       Vaisseaux Mères… alors que `.dsh-view` le fait DÉJÀ, plus bas dans ce
       fichier, avec `padding-right: calc(180px + 24px)`. Les deux
       s'additionnaient : 384 px réservés pour un panneau qui en occupe 204.
       Mesuré au navigateur sur un écran de 1920 : la vue s'arrêtait à
       1536 px au lieu de 1740. La marge vaut donc zéro — le padding suffit,
       et il est le seul des deux à suivre la largeur réelle du panneau. */
    margin-right: 0;
  }
}
.cmd-section-title {
  font-family: var(--hud); font-size: 0.78rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--turquoise);
  border-bottom: 1px solid var(--border); padding-bottom: 0.4rem; margin-bottom: 0.7rem;
}
/* ════════════════════════════════════════════════════════════════════
   v0.0.86 — L'ENCART COMPARATIF DES QUATRE CLASSES
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : « un encadré dépliable en haut à droite de la vue
   commandement avec les bonus malus des 4 types de VM ».
   ⚠️ LE TITRE GARDE SON SOULIGNÉ SUR TOUTE LA LARGEUR. `.cmd-section-title`
   porte un `border-bottom` qui fait la barre de section : mis dans un flex
   à côté de l'encart, il ne soulignerait plus que ses propres mots, et la
   section perdrait son trait. On rend donc la barre au CONTENEUR et on la
   retire au titre — vu au navigateur, pas à la relecture. */
.cmd-section-tete {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}
/* ⚠️ LE TITRE GARDE SON PROPRE SOULIGNÉ, ET IL S'ÉTIRE. Le donner au
   conteneur paraissait plus simple — mais le conteneur grandit avec l'encart
   déplié, et le trait de section se retrouvait alors 375 px sous le titre.
   Mesuré au navigateur, pas à la relecture. */
.cmd-section-tete .cmd-section-title { flex: 1 1 12rem; margin-bottom: 0; }
/* ⚠️ L'ENCART DÉPLIÉ SE POSE PAR-DESSUS, IL NE POUSSE PAS LA FLOTTE.
   Dans le flux, ses 375 px repoussaient les cartes de vaisseaux vers le bas
   et laissaient un grand vide à leur gauche — c'est-à-dire qu'ouvrir un
   comparatif chassait de l'écran ce que la vue est venue montrer. Il se
   déplie donc en surimpression, ancré sous sa propre languette. */
.clse-encart {
  flex: 0 0 auto; max-width: 100%; position: relative;
  border: 1px solid var(--border); border-radius: 8px; background: var(--card);
}
.clse-tete {
  cursor: pointer; list-style: none; user-select: none;
  font-family: var(--hud); font-size: 0.62rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text2);
  padding: 0.35rem 0.7rem; white-space: nowrap;
}
.clse-tete::-webkit-details-marker { display: none; }
.clse-tete::after { content: ' ▾'; color: var(--text3); }
.clse-encart[open] .clse-tete::after { content: ' ▴'; }
.clse-tete:hover { color: var(--text1, #e5e8f5); }
/* ⚠️ LE TABLEAU DÉFILE DANS SA BOÎTE, il ne pousse jamais la page. Cinq
   colonnes ne tiennent pas sur un écran étroit, et la vue Commandement, elle,
   ne défile pas horizontalement : sans ce conteneur, les deux dernières
   classes sortiraient de l'écran sans que rien ne le signale (leçon du
   tableau des trente rangs, v0.0.82). */
.clse-corps {
  position: absolute; right: -1px; top: calc(100% + 2px); z-index: 60;
  padding: 0.5rem 0.7rem 0.6rem; width: max-content; max-width: min(560px, 92vw);
  overflow-x: auto;
  background: var(--card); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .55);
}
.clse-table { border-collapse: collapse; font-size: 0.62rem; width: 100%; }
.clse-th {
  color: var(--clsc, var(--text2)); font-family: var(--hud); font-weight: 600;
  padding: 0.25rem 0.4rem; text-align: center; white-space: nowrap; font-size: 0.58rem;
}
.clse-th span { display: block; }
.clse-nom {
  text-align: left; font-weight: 400; color: var(--text2);
  padding: 0.18rem 0.5rem 0.18rem 0; white-space: nowrap;
}
.clse-td {
  text-align: center; padding: 0.18rem 0.4rem; font-family: var(--mono);
  white-space: nowrap;
}
.clse-dom-titre {
  text-align: left; font-family: var(--hud); font-size: 0.58rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text3);
  padding: 0.5rem 0 0.15rem; border-bottom: 1px solid var(--border);
}
.clse-note {
  margin-top: 0.5rem; font-size: 0.6rem; line-height: 1.5; color: var(--text2);
  max-width: 46ch;
}
/* Au téléphone, l'encart prend toute la largeur sous le titre : le poser à
   droite d'un titre déjà large le réduirait à une colonne illisible. */
/* Au téléphone, l'encart prend toute la largeur sous le titre et se déplie
   DANS LE FLUX : une surimpression de 560 px sur un écran de 390 sortirait
   par la droite, et la page ne défile pas horizontalement — le contenu
   serait inatteignable sans que rien ne le signale (leçon du tableau des
   trente rangs, v0.0.82). */
@media (max-width: 700px) {
  .cmd-section-tete { flex-direction: column; align-items: stretch; gap: 0.5rem; }
  /* ⚠️ LA BASE DU TITRE DOIT ÊTRE REMISE À PLAT. En colonne, `flex-basis`
     porte sur la HAUTEUR : les 12 rem qui donnent au titre sa largeur
     minimale sur grand écran lui donnaient ici 192 px de HAUT, soit un
     trou de la taille d'un écran et demi entre le titre et l'encart.
     Mesuré au premier passage mobile, invisible à la relecture. */
  .cmd-section-tete .cmd-section-title { flex: 0 0 auto; }
  .clse-encart { width: 100%; }
  .clse-corps {
    position: static; width: auto; max-width: 100%;
    border: none; box-shadow: none; border-top: 1px solid var(--border);
    border-radius: 0;
  }
}

/* v2.33 — Sélecteur de CLASSE du prochain vaisseau. */
.cmd-class-title {
  font-family: var(--hud); font-size: 0.58rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--turquoise); margin: 0.9rem 0 0.4rem;
}
.cmd-classes { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem; }
.cmd-class {
  display: flex; flex-direction: column; gap: 0.25rem; text-align: left;
  padding: 0.6rem 0.7rem; cursor: pointer; font: inherit;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border); border-radius: 6px;
  transition: border-color 0.15s, background 0.15s, transform 0.12s;
}
.cmd-class:hover { border-color: var(--clsc); transform: translateY(-1px); }
.cmd-class.is-selected {
  border-color: var(--clsc);
  background: color-mix(in srgb, var(--clsc) 14%, transparent);
  box-shadow: 0 0 0 1px var(--clsc) inset;
}
.cmd-class-ico { font-size: 1.25rem; line-height: 1; }
.cmd-class-name { font-family: var(--hud); font-size: 0.72rem; color: var(--text); }
.cmd-class-effect { font-size: 0.64rem; color: var(--text2); line-height: 1.35; }
.cmd-class.is-selected .cmd-class-name { color: var(--clsc); }
/* Rappel de la classe commandée, en tête du bloc d'assemblage. */
.cmd-prod-class {
  font-family: var(--hud); font-size: 0.7rem; color: var(--clsc, var(--text));
  margin-bottom: 0.5rem;
}
.cmd-prod-class-eff {
  font-family: var(--font-body); font-size: 0.64rem; color: var(--text2); margin-left: 0.5rem;
}
/* Projet : checklist + coût + lancement */
.cmd-project {
  background: var(--card); border: 1px solid var(--border); border-radius: 8px;
  padding: 0.9rem;
}
.cmd-checklist { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 0.8rem; }
.cmd-check { display: flex; align-items: baseline; gap: 0.5rem; font-size: 0.74rem; }
.cmd-check-mark { font-family: var(--mono); font-weight: 700; width: 1em; }
.cmd-check.is-ok .cmd-check-mark { color: var(--accent4, #22c55e); }
.cmd-check.is-ko .cmd-check-mark { color: var(--accent1, #e94560); }
.cmd-check-label { color: var(--text); font-weight: 600; }
.cmd-check-detail { color: var(--text2); font-size: 0.68rem; }
.cmd-check.is-ko .cmd-check-detail { color: var(--gold); }
.cmd-cost-title {
  font-family: var(--hud); font-size: 0.58rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--turquoise); margin-bottom: 0.3rem;
}
.cmd-cost-line { font-family: var(--mono); font-size: 0.78rem; color: var(--text); }
.cmd-cost-note { font-size: 0.66rem; color: var(--text2); line-height: 1.45; margin-top: 0.35rem; }
/* ════════════════════════════════════════════════════════════════════
   v0.0.163 — LE BOUTON D'ENTRETIEN DU BERCEAU, DANS LE PANNEAU D'ASSEMBLAGE
   ════════════════════════════════════════════════════════════════════
   Il vit à côté du chiffre qu'il répare : la durée d'assemblage. D'où la
   rangée qui se replie — le prix et le gain tiennent sur une ligne au
   large, sur deux au téléphone.
   ⚠️ PAS DE `space-between` SUR CETTE RANGÉE : repliée, la dernière ligne
   prendrait des écarts absurdes (règle de maison). On remplit par le
   contenu, avec un simple `gap`.
   ⚠️ LES DEUX TAILLES SONT MESURÉES, PAS DEVINÉES. Premier jet : bouton à
   .78rem et note à .72rem — soit 10,92 px et 10,08 px au navigateur, sous
   le plancher de lisibilité du projet (11 px, cf. --badge-font-size). La
   racine du jeu est à 14 px, pas 16 : .78rem ne fait PAS 11 px. Relevées à
   .8rem (11,2 px) et .79rem (11,06 px), vérifiées aux deux largeurs. */
.cmd-ent {
  display: flex; flex-wrap: wrap; align-items: center; gap: .45rem;
  margin-top: .5rem;
}
.cmd-ent-btn {
  border: 1px solid rgba(94, 234, 212, .5); border-radius: 6px;
  background: rgba(94, 234, 212, .12); color: #99f6e4;
  font-size: .8rem; padding: 5px 11px; cursor: pointer;
}
.cmd-ent-btn:hover:not(:disabled) { background: rgba(94, 234, 212, .24); }
.cmd-ent-btn:disabled { opacity: .5; cursor: not-allowed; }
.cmd-ent-gain { font-size: .79rem; color: var(--text2); }
.cmd-ent-encours { color: #99f6e4; }
/* L'usure, dite à côté du compte à rebours qu'elle allonge. */
.cmd-prod-usure { display: block; font-size: .7rem; color: #fbbf24; margin-top: .15rem; }
.cmd-launch {
  display: block; width: 100%; margin-top: 0.8rem; padding: 0.6rem;
  font-family: var(--hud); font-size: 0.72rem; letter-spacing: 0.06em;
  background: rgba(255,255,255,0.03); border: 1px solid var(--border);
  border-radius: 6px; color: var(--text3); cursor: not-allowed;
}
.cmd-launch.is-ready {
  background: rgba(14,165,233,0.12); border-color: var(--accent2);
  color: var(--text); cursor: pointer; transition: background 0.15s;
}
.cmd-launch.is-ready:hover { background: rgba(14,165,233,0.22); }
/* Interrupteur assemblageManque : ce qui manque pour assembler, sous le
   bouton grisé. La classe n'existe qu'allumé. */
.cmd-manque { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; margin-top: 0.5rem;
  font-size: 0.86rem; color: #fda4af; }
.cmd-manque-titre { color: var(--text2); }
.cmd-manque-res { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.cmd-manque-res b { color: #fecdd3; }
.cmd-goto {
  display: block; width: 100%; margin-top: 0.5rem; padding: 0.5rem;
  font-family: var(--hud); font-size: 0.66rem; background: none;
  border: 1px dashed var(--border2); border-radius: 6px; color: var(--text2); cursor: pointer;
}
.cmd-goto:hover { border-color: var(--accent2); color: var(--text); }
/* Production en cours */
.cmd-prod-host { font-size: 0.74rem; color: var(--text); margin-bottom: 0.5rem; }
.cmd-prod-bar { height: 10px; background: rgba(255,255,255,0.06); border-radius: 5px; overflow: hidden; }
.cmd-prod-fill { height: 100%; background: linear-gradient(90deg, var(--accent2), var(--accent3, #c084fc)); transition: width 0.5s ease; }
.cmd-prod-time { font-family: var(--mono); font-size: 0.7rem; color: var(--gold); margin-top: 0.4rem; }
.cmd-prod-warn { font-size: 0.68rem; color: var(--accent1, #e94560); line-height: 1.45; margin-top: 0.6rem; }
/* ══════════════════════════════════════════════════════════════════
   v0.0.146 — LE HANGAR (vue Commandement, features/command/hangar.js)
   ══════════════════════════════════════════════════════════════════
   Une case par Vaisseau Mère, plus une case en pointillés pour celui
   qui n'existe pas encore. Tout replié ; un seul tiroir ouvert.

   ⚠️ LA CASE FAIT 172 px MINIMUM, ET C'EST MESURÉ. À 210 px — la valeur
   des anciennes cartes — un écran de 390 px n'en met qu'UNE par rangée :
   la grille redevient la pile qu'elle remplace. À 172, il en tient deux
   avec la gouttière, et la silhouette reste lisible. */
.hgr-grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(172px, 1fr));
  gap: 0.6rem;
}

.hgr-vignette {
  display: flex; flex-direction: column; gap: 0.35rem;
  padding: 0.55rem; text-align: left; width: 100%;
  background: var(--card); color: inherit; font: inherit; cursor: pointer;
  border: 1px solid var(--border2);
  border-top: 2px solid var(--vmc, var(--border2));
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.hgr-vignette:hover { background: var(--bg3); transform: translateY(-1px); }
.hgr-vignette:focus-visible { outline: 2px solid var(--accent2); outline-offset: 2px; }

/* La case OUVERTE : elle se relie visuellement au tiroir qui suit. La
   bordure basse disparaît, le fond s'éclaire à la couleur du vaisseau. */
.hgr-vignette.est-ouverte {
  background: rgba(var(--vmc-rgb, 14, 165, 233), 0.10);
  border-color: var(--vmc, var(--accent2));
  box-shadow: 0 0 24px -10px var(--vmc, var(--accent2));
}
/* Le vaisseau ACTIF du reste du jeu — celui que la colonne de droite et
   l'onglet Vaisseau Mère montrent. Un liseré, pas une couleur de plus :
   « ouvert » et « actif » sont deux états différents et souvent simultanés. */
.hgr-vignette.est-active .hgr-nom::before {
  content: '●'; color: var(--vmc, var(--accent2));
  font-size: 0.6em; margin-right: 0.35em; vertical-align: 0.25em;
}
.hgr-vignette.est-alerte { border-color: rgba(233, 69, 96, 0.55); }
.hgr-vignette.est-vide { border-style: dashed; background: transparent; }
.hgr-vignette.est-vide:hover { background: rgba(255, 255, 255, 0.02); }
.hgr-vignette.est-vide.est-chantier { border-style: solid; }

/* La « porte » du hangar : un renfoncement éclairé, où se tient la
   silhouette. C'est ce qui fait lire la case comme une CASE, et pas comme
   une carte de plus. */
.hgr-porte {
  display: block; padding: 0.35rem 0.25rem;
  background: radial-gradient(120% 90% at 30% 20%, rgba(255, 255, 255, 0.05), transparent 70%);
  border-bottom: 1px solid var(--border);
}
.hgr-nom {
  font-family: var(--hud); font-size: 0.72rem; color: var(--text);
  line-height: 1.3; overflow-wrap: anywhere;
}
/* ⚠️ `overflow-wrap: anywhere` N'EST PAS DÉCORATIF : un joueur renomme son
   vaisseau, et un nom d'un seul tenant plus large que la case la ferait
   déborder — la page ne défile pas horizontalement, le nom serait coupé
   sans que rien ne le signale (leçon du tiroir des files, v0.0.82). */
.hgr-classe {
  display: inline-flex; align-items: center; align-self: flex-start;
  font-family: var(--mono); font-size: 0.64rem; line-height: 1.35;
  padding: 0.08rem 0.4rem; max-width: 100%;
  border: 1px solid var(--clsc, var(--text3)); color: var(--clsc, var(--text3));
  background: rgba(255, 255, 255, 0.02);
}
.hgr-etat { font-family: var(--mono); font-size: 0.64rem; color: var(--text2); }
.hgr-badges { display: flex; flex-wrap: wrap; gap: 0.25rem; }
.hgr-badge {
  font-family: var(--mono); font-size: 0.58rem; padding: 0.05rem 0.3rem;
  border: 1px solid var(--border2); color: var(--text3); white-space: nowrap;
}
.hgr-badge.est-or { color: var(--gold); border-color: rgba(245, 158, 11, 0.35); }
.hgr-badge.est-violet { color: #c4a8ff; border-color: rgba(167, 139, 250, 0.40); }

/* ── Le tiroir ─────────────────────────────────────────────────────
   Pleine largeur, sous la rangée. À gauche ce que le vaisseau EST, à
   droite ce qu'on peut LUI FAIRE. */
.hgr-tiroir {
  margin-top: 0.6rem; padding: 0.85rem;
  border: 1px solid var(--border2);
  border-top: 2px solid var(--vmc, var(--accent2));
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent 45%), var(--bg2);
}
.hgr-tiroir-corps {
  display: grid; gap: 1rem;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  align-items: start;
}
.hgr-tiroir-corps > * { min-width: 0; }
.hgr-sous-titre {
  font-family: var(--hud); font-size: 0.68rem; letter-spacing: 0.05em;
  color: var(--text); margin-bottom: 0.2rem;
}
.hgr-vide { font-size: 0.7rem; color: var(--text3); padding: 0.4rem 0; }
.hgr-actions .cmd-goto { margin-top: 0.5rem; }

/* ⚠️ LA MATRICE PASSE EN COLONNES, ET C'EST TOUT L'INTÉRÊT DU TIROIR.
   `.cls-matrice` est une pile verticale — parfaite dans une carte de
   210 px, absurde sur 800 : onze lignes d'affilée pendant que la moitié
   droite reste vide. C'est précisément le défaut que le Hangar répare. */
.hgr-tiroir .cls-matrice {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
  gap: 0.4rem 1rem; margin-top: 0.4rem;
}
/* La section « Prochain Vaisseau Mère » est rendue TELLE QUELLE dans le
   tiroir de la case vide : on lui retire seulement les marges de section,
   sinon elle se décalerait de 1,2 rem à l'intérieur d'un cadre qui a déjà
   sa propre gouttière. */
.hgr-tiroir .cmd-section { margin: 0; padding: 0; }
.hgr-tiroir .cmd-section-title { margin-top: 0; }

.hgr-invite {
  margin-top: 0.6rem; font-size: 0.68rem; color: var(--text3);
  font-family: var(--mono); line-height: 1.5;
}

/* ── Téléphone ─────────────────────────────────────────────────────
   ⚠️ LE SEUIL EST CELUI DU JEU (700 px), et il regarde la LARGEUR RÉELLE :
   la classe `body.mobile-mode` peut être posée sur une fenêtre large (mode
   forcé), et le tiroir a alors toute la place d'afficher ses deux colonnes.
   C'est bien la largeur qui décide ici, pas le mode. */
@media (max-width: 700px) {
  /* ⚠️ 118 px, ET C'EST MESURÉ AU NAVIGATEUR — pas déduit de la largeur de
     l'écran. Sur un téléphone de 390 px, la grille n'en reçoit que ~265 :
     le rail de gauche, la gouttière de `.cmd-section` et la marge du corps
     en prennent 125 au passage. À 148 px il ne tenait donc qu'UNE case par
     rangée — la grille redevenait exactement la pile verticale qu'elle
     remplace, avec quatre cases de 185 px de haut à faire défiler avant
     d'atteindre le tiroir. À 118, il en tient deux. */
  .hgr-grille { grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 0.45rem; }
  .hgr-tiroir { padding: 0.7rem 0.6rem; }
  .hgr-tiroir-corps { grid-template-columns: minmax(0, 1fr); gap: 0.85rem; }
  .hgr-tiroir .cls-matrice { grid-template-columns: minmax(0, 1fr); }
}

/* ── Interrupteur hangarSansClasse — LE TIROIR D'UN VAISSEAU SANS CLASSE ──
   Loryan : « peut être optimiser cet affichage ? ». Mesuré à 1500 px : la
   colonne de gauche ne portait qu'un titre et une phrase (la même que la
   métamorphose), les quatre cartes tenaient dans la colonne de droite, et le
   plus petit texte faisait 7,7 px. Une colonne pleine largeur, les cartes
   côte à côte, rien sous le plancher de 11 px. Ces classes n'existent que
   sous l'interrupteur (features/command/hangar.js, features/vm/classes.js). */
.hgr-sans-classe { display: flex; flex-direction: column; gap: 0.7rem; }
.hgr-sans-classe > .md-sec { margin: 0; font-size: var(--badge-font-size, 11px); }
.hgr-sans-classe .md-desc { font-size: max(var(--badge-font-size, 11px), 0.82rem); line-height: 1.5; max-width: 95ch; }
.hgr-sans-classe .cmd-classes { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 0.6rem; }
.hgr-sans-classe .cmd-class-name { font-size: max(var(--badge-font-size, 11px), 0.82rem); }
.hgr-sans-classe .cmd-class-reserve { margin: 0; }
.hgr-sans-classe-pied { display: flex; justify-content: flex-end; }
.hgr-sans-classe-pied .cmd-goto { margin: 0; width: auto; flex: 0 1 auto; padding-left: 1.4rem; padding-right: 1.4rem;
  font-size: var(--badge-font-size, 11px); }
/* Au téléphone, le bouton passe à gauche : à droite, l'icône flottante du
   guide (🎓) le recouvrait en partie (mesuré à 390 px). */
@media (max-width: 700px) { .hgr-sans-classe-pied { justify-content: flex-start; } }
body.mobile-mode .hgr-sans-classe-pied { justify-content: flex-start; }
.cmd-class-lignes { display: flex; flex-direction: column; gap: 3px; }
.cmd-class-ligne { font-size: max(var(--badge-font-size, 11px), 0.78rem); line-height: 1.35; color: var(--text); }
.cmd-class-cle { color: var(--text2); }
/* En mode téléphone, la case devient une CIBLE DE DOIGT : le rembourrage
   monte, et la porte gagne de la hauteur pour que la silhouette reste
   reconnaissable même à 148 px de large. */
body.mobile-mode .hgr-vignette { padding: 0.65rem 0.55rem; gap: 0.4rem; }
body.mobile-mode .hgr-vignette:hover { transform: none; }


/* v2.29 — Vignette ILLUSTRÉE d'une techno : même traitement que
   .bld-tile--illus (image de fond + voile dégradé, contenu au-dessus), pour
   que les deux colonnes d'un module se ressemblent. On aligne aussi la
   hauteur sur celle des bâtiments, sinon les grilles se décalent. */
.tech-tile--illus { position: relative; overflow: hidden; min-height: 112px; }
.tech-tile-art {
  position: absolute; inset: 0; z-index: 0;
  background-size: cover; background-position: center;
  transition: transform 0.4s ease;
}
.tech-tile--illus:hover .tech-tile-art { transform: scale(1.05); }
.tech-tile-art::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(6,10,20,.62) 0%, rgba(6,10,20,.5) 45%, rgba(6,10,20,.86) 100%);
}
.tech-tile--illus .tech-tile-effect { position: relative; z-index: 1; }
.tech-tile--illus .bld-tile-name,
.tech-tile--illus .tech-tile-effect { text-shadow: 0 1px 3px rgba(0,0,0,.8); }
.tech-tile--illus .bld-tile-name { color: #f2f6ff; }
.tech-tile--illus .bld-tile-lv { background: rgba(6,10,20,.55); }
/* La grille des technos suit le même gabarit que celle des bâtiments */
.module-techs { grid-template-columns: repeat(auto-fill, minmax(184px, 1fr)); gap: 0.6rem; }

/* Liste « Débloque » dans le panneau de focus d'une recherche */
.md-unlocks { display: flex; flex-wrap: wrap; gap: 4px; }
.md-unlock {
  font-size: 0.63rem; color: var(--turquoise);
  background: rgba(45, 212, 191, 0.1); border: 1px solid rgba(45, 212, 191, 0.35);
  border-radius: 4px; padding: 2px 6px;
}

/* ═══════════════════════════════════════════════════════════════════
   v2.23 — SECTION MODULES : expérience + niveaux du vaisseau
   ═══════════════════════════════════════════════════════════════════
   L'expérience n'est PAS une ressource : pas de plafond, pas de soute,
   pas de place dans la barre du haut. Le bandeau porte le récap de ce
   qu'on peut monter (`.xp-recap-*`, plus bas). Logique :
   features/vm/modules.js.
   ═══════════════════════════════════════════════════════════════════ */

.xp-banner {
  display: flex; flex-wrap: wrap; gap: 1.2rem; align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem; padding: 0.9rem 1.1rem;
  border: 1px solid rgba(45, 212, 191, 0.35); border-radius: 10px;
  background:
    radial-gradient(120% 140% at 0% 0%, rgba(45, 212, 191, 0.10), transparent 60%),
    var(--card);
}

/* Vignette d'un module — gabarit des bâtiments, sans illustration */
.mod-tile {
  display: flex; flex-direction: column; justify-content: space-between;
  min-height: 92px; padding: 0.5rem 0.6rem; text-align: left;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 6px; cursor: pointer; color: inherit; font: inherit;
  transition: border-color 0.15s, transform 0.12s;
}
.mod-tile:hover { border-color: var(--border2); transform: translateY(-1px); }
.mod-tile.is-selected { border-color: var(--accent2); box-shadow: 0 0 0 1px var(--accent2) inset; }
/* Développement payable tout de suite : liseré turquoise, comme le stock d'XP */
.mod-tile.is-ready { border-color: rgba(45, 212, 191, 0.5); }
/* v0.0.40, lot 2 — MODULE ÉTEINT (flag reworkModulesZero). Un vaisseau neuf
   naît avec ses onze modules hors service : la vignette doit se distinguer
   d'un module simplement bas niveau, sinon le joueur croit à un bug. Le
   liseré « payable » (is-ready) reste prioritaire — c'est justement celui
   qu'on peut allumer tout de suite. */
/* ════════════════════════════════════════════════════════════════════
   v0.0.135 — LE MODULE QU'ON PEUT MONTER MAINTENANT
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : « orange pour rester dans le thème des voies ». Ce
   sont EXACTEMENT les trois valeurs de `.voie-node.is-prete` — le palier
   de Voie qu'on peut réclamer — et c'est tout l'intérêt : le joueur
   apprend une couleur une fois, elle veut dire la même chose partout.
   ⚠️ ELLE PASSE DEVANT `is-ready` (déclarée après lui, même spécificité) :
   le turquoise ne dit que « payable », l'orange dit « payable ET le
   serveur l'accepte ». Quand les deux sont vraies, c'est la seconde qui
   informe. */
.mod-tile.is-a-developper {
  border-color: rgba(245, 158, 11, 0.75);
  box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.25) inset, 0 0 16px rgba(245, 158, 11, 0.12);
}
.mod-tile.is-a-developper .mod-tile-cost { color: #fbbf24; }
.mod-tile.is-eteint { opacity: 0.72; border-style: dashed; }
.mod-tile.is-eteint.is-a-developper { opacity: 1; }
.mod-tile.is-eteint .bld-tile-lv { color: var(--text3); letter-spacing: 0.04em; }
.mod-tile.is-eteint.is-ready { opacity: 1; }

/* ══════════════════════════════════════════════════════════════════
   v0.0.146 — LA SÉLECTION SE VOIT, MÊME SUR UNE VIGNETTE QUI A UN ÉTAT
   ══════════════════════════════════════════════════════════════════
   Signalements en série — andraudOP : « je ne peux rien sélectionner sur
   le VM3 QUI A DÉJÀ UN NIVEAU DE UP. Sur le VM1 uniquement le générateur
   tellurique » ; Rends les nanites : « je n'arrive pas à sélectionner
   l'usine d'assemblage, il faut que je la sélectionne sur un autre VM » ;
   Life : « pas que énergie, c'est général ».

   ⚠️ LE CLIC PARTAIT, ET C'EST POUR ÇA QUE PERSONNE NE TROUVAIT. La
   sélection était bien prise ; c'est le LISERÉ qui ne se voyait pas. Le
   liseré de sélection est déclaré AVANT les liserés d'état, à spécificité
   ÉGALE : en CSS, c'est le dernier écrit qui gagne. Un bâtiment déjà en
   file gardait donc son liseré vert, une recherche en cours son doré, un
   module payable son turquoise. Mesuré au navigateur : rgb(16,185,129)
   au lieu de rgb(14,165,233).

   Restait bien l'ombre intérieure d'un pixel — mais sur une vignette à
   illustration pleine, un trait d'un pixel à l'intérieur d'une bordure de
   couleur ne se voit pas. Et sur `is-a-developper`, qui pose SA propre
   ombre, il ne restait RIEN du tout ; sur `is-en-chantier`, l'animation
   du contour l'effaçait aussi.

   D'où le « ce ne sont pas toujours les mêmes bâtiments » : ce sont ceux
   qui ont un état — donc ceux qu'on est justement en train de monter.

   ⚠️ ON NE RÉORDONNE PAS LES RÈGLES, ON CHANGE DE CANAL. Passer la
   sélection après les états la ferait gagner… et effacerait l'état, qui
   dit une chose différente et tout aussi utile (« celui-là est en file »).
   Les deux doivent tenir ensemble : l'ÉTAT garde la bordure, la SÉLECTION
   prend un CERNE (`outline`), une propriété qu'aucune règle d'état
   n'emploie. Rien ne peut plus l'écraser, et les deux se lisent d'un coup
   d'œil.

   ⚠️ ET LE CERNE NE DÉPLACE RIEN. Contrairement à une bordure, il ne
   prend aucune place dans le flux : la grille ne bouge pas d'un pixel
   quand on change de sélection. C'est la même raison qui a fait choisir
   l'ombre plutôt que la bordure pour le battement du chantier
   (v0.0.131). */
.bld-tile.is-selected,
.conn-tile.is-selected,
.tech-tile.is-selected,
.mod-tile.is-selected,
.ship-card-compact.is-selected {
  outline: 2px solid var(--accent2);
  outline-offset: 2px;
}
/* Une vignette verrouillée est estompée à 55 % — son cerne le serait
   aussi, et la sélection redeviendrait invisible là où le joueur en a le
   plus besoin (35 bâtiments sur 38 sont verrouillés au démarrage). */
.bld-tile.is-locked.is-selected,
.tech-tile.is-locked.is-selected,
.mod-tile.is-eteint.is-selected { opacity: 0.85; }
.mod-tile-cost {
  display: block; margin-top: 0.4rem;
  font-family: var(--mono); font-size: 0.64rem; color: var(--text3);
}
.mod-tile-cost.is-ready { color: var(--turquoise); }

/* v0.0.132 — LA RÉSERVE D'EXPÉRIENCE DU MODULE, SUR SA VIGNETTE.
   ⚠️ Elle vit SOUS la ligne de prix, jamais à côté : la vignette fait
   ~11 rem de large en mode téléphone, et deux chiffres à sept caractères
   sur la même ligne s'y tronquent au milieu (le défaut des trente-cinq
   pixels du tiroir des files, v0.0.82). En colonne, la largeur ne dépend
   plus du montant. */
.mod-tile-xp {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .4rem; width: 100%;
  margin-top: .25rem; padding-top: .25rem;
  border-top: 1px solid rgba(255,255,255,.07);
  font-family: var(--mono); font-size: .62rem; color: var(--text3);
  white-space: nowrap; overflow: hidden;
}

/* v0.0.133 — LE COMPTE À REBOURS ET SA DATE DE FIN.
   ⚠️ `md-rebours` A UNE LARGEUR MINIMALE EN CHIFFRES TABULAIRES, et ce n'est
   pas de la coquetterie : le texte passe de « 2h 19m 04s » à « 2h 19m 03s »
   chaque seconde, et sans chasse fixe la ligne entière tressaute à chaque
   battement. Sur un compteur qu'on regarde justement défiler, c'est le
   défaut le plus visible de tous.
   ⚠️ La date de fin passe À LA LIGNE sous 700 px : les deux bouts mis
   côte à côte font ~26 caractères, et la colonne de droite d'une fiche
   n'en tient que ~18 sur un téléphone — le « à 17:06 » s'y tronquait. */
.md-rebours {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}
.md-quand {
  font-style: normal; opacity: .72; margin-left: .35rem; white-space: nowrap;
}
@media (max-width: 700px) {
  .md-quand { display: block; margin-left: 0; }
}

/* Lien vers la section détaillée, dans le panneau de focus d'un module */
.md-link {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--accent2); font: inherit; text-decoration: underline;
}
.md-link:hover { color: #7dd3fc; }

/* ═══════════════════════════════════════════════════════════════════
   v2.24 — CAPS DE MODULES
   ═══════════════════════════════════════════════════════════════════ */

/* Le bonus actuel du module, sur la vignette. Au plancher de
   lisibilité (0,8 rem ≈ 11,2 px) et sur UNE ligne — l'étiquette est courte,
   la phrase complète est dans la fiche et dans l'infobulle. */
.mod-tile-bonus {
  display: block; margin-top: 0.35rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-family: var(--mono); font-size: 0.8rem; color: #4ade80;
}

/* L'avancement d'un module vers son prochain niveau.
   La barre de la vignette remplace la ligne « +X XP/s » : il n'y a plus de
   débit à la seconde, seulement ce qui est réuni. Mince (5 px) pour ne pas
   grandir la vignette ; la grande (8 px) vit dans la fiche. */
.xpa-barre {
  display: block; width: 100%; height: 5px; border-radius: 3px;
  background: rgba(255,255,255,.08); overflow: hidden;
}
.xpa-barre > span { display: block; height: 100%; background: var(--turquoise); border-radius: 3px; }
.mod-tile-xpa { align-items: center; }
.xpa-barre-grande { height: 8px; margin: .35rem 0 .2rem; }
.xpa-jauges { display: flex; flex-direction: column; gap: .45rem; margin: .3rem 0 .4rem; }
.xpa-jauge-tete {
  display: flex; justify-content: space-between; gap: .5rem; flex-wrap: wrap;
  font-size: var(--badge-font-size, 11px); margin-bottom: .2rem;
}
.xpa-jauge-nom { color: var(--text2); }
.xpa-jauge-val { font-family: var(--mono); color: var(--text3); white-space: nowrap; }
.xpa-jauge.est-pleine .xpa-jauge-val { color: #4ade80; }
.xpa-jauge.est-pleine .xpa-barre > span { background: #4ade80; }

/* ════════════════════════════════════════════════════════════════════
   v2.46 — PRÉREQUIS DANS LE PANNEAU DE FOCUS (vaisseaux et défenses)
   ════════════════════════════════════════════════════════════════════
   Chaque prérequis manquant est une LIGNE CLIQUABLE qui ouvre l'arbre de
   progression sur le nœud correspondant. D'où l'aspect « bouton discret »
   plutôt que du texte : il faut qu'on voie qu'il y a quelque chose à
   cliquer, sans que quatre lignes empilées ne crient plus fort que le
   bouton d'action en dessous. */
.md-reqs { display: flex; flex-direction: column; gap: 4px; margin-bottom: 0.6rem; }
.md-mystere-note {
  /* v0.0.7 : ce que dit le tiroir de détail à la place d'un coût encore
     masqué. Même teinte que les notes secondaires du panneau — il ne s'agit
     pas d'une alerte, seulement d'une chose qu'on ne sait pas encore. */
  padding: 8px 10px;
  font-size: 12px;
  font-style: italic;
  color: var(--text-dim, #94a3b8);
  border-left: 2px solid rgba(166, 120, 232, 0.45);
  background: rgba(166, 120, 232, 0.06);
  border-radius: 0 6px 6px 0;
}

.md-req-line {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: 5px 7px; text-align: left; cursor: pointer;
  background: rgba(248, 113, 113, 0.07);
  border: 1px solid rgba(248, 113, 113, 0.3); border-radius: 5px;
  color: var(--text1); font-family: inherit; font-size: 0.66rem;
  transition: 0.15s;
}
.md-req-line:hover {
  background: rgba(248, 113, 113, 0.16); border-color: rgba(248, 113, 113, 0.65);
}
.md-req-ico { font-size: 0.8rem; flex-shrink: 0; }
.md-req-txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.md-req-have {
  flex-shrink: 0; font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.6rem; color: var(--text3);
}
.md-req-go { flex-shrink: 0; color: var(--text3); font-size: 0.7rem; }
.md-req-line:hover .md-req-go { color: #f87171; }
.md-req-ok {
  font-size: 0.66rem; color: #4ade80;
  padding: 4px 2px;
}
/* Accès à l'arbre sur le nœud de l'unité elle-même — même famille visuelle
   que « développer le module », en vert d'arbre. */
/* Quantité + bouton Produire, dans le panneau */
.md-build-row { display: flex; gap: 6px; align-items: stretch; margin-top: 0.5rem; }
.md-build-row .ship-qty-input { width: 58px; flex-shrink: 0; }
.md-build-row .btn-up { flex: 1; margin-top: 0; }

/* Une carte de vaisseau / défense est une vignette sélectionnable (v2.46) :
   elle doit le dire, et montrer laquelle le panneau de droite décrit. */
.ship-card-compact { cursor: pointer; }
.ship-card-compact.is-selected {
  border-color: var(--turquoise, #2dd4bf);
  box-shadow: 0 0 0 1px rgba(45, 212, 191, 0.35), 0 0 16px rgba(45, 212, 191, 0.12);
}

/* ════════════════════════════════════════════════════════════════════
   v2.35 — PANNEAU DE PLANÈTE PLEINE HAUTEUR
   ════════════════════════════════════════════════════════════════════
   L'encart d'origine fait 260 px de large, centré verticalement, sans
   défilement : la fiche d'une planète (composition, stock, ressources
   énergétiques, statut, détail du transit, bouton de lancement) n'y tient
   pas. Le texte débordait dès que le système avait de quoi raconter.

   Ici la fiche prend TOUTE la hauteur sous les bandeaux, gagne en largeur,
   défile si besoin, et son texte est agrandi d'environ un tiers.

   Le contenu est À PLAT dans #planet-panel (pas de conteneur de corps) :
   plutôt que d'ajouter des div au HTML, on rend le panneau défilant et on
   COLLE le titre en haut et le bouton d'action en bas avec position:sticky.
   On ne perd donc jamais de vue ni le nom de la planète, ni le bouton.

   Le panneau est REPARENTÉ au runtime dans #view-map quand la carte est
   active : la classe étant posée sur l'élément lui-même, elle le suit. */
#planet-panel {
  /* top:0 et non --total-header-h : le conteneur de référence
     (#map-canvas-wrapper sur la carte, #galaxy-canvas sur la galaxie)
     commence DÉJÀ sous les bandeaux. Réappliquer la hauteur d'en-tête
     décalait le panneau une seconde fois vers le bas. */
  top: 0;
  bottom: 0;
  right: 0;
  transform: none;
  width: 340px;
  max-width: 40vw;
  border-radius: 0;
  border-top: none;
  border-right: none;
  border-bottom: none;
  padding: 0 1.1rem 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 150, 220, .35) transparent;
}
#planet-panel::-webkit-scrollbar { width: 7px; }
#planet-panel::-webkit-scrollbar-track { background: transparent; }
#planet-panel::-webkit-scrollbar-thumb {
  background: rgba(120, 150, 220, .35); border-radius: 4px;
}

/* Titre collé en haut. Le fond opaque évite que le contenu défilant
   transparaisse dessous. */
#planet-panel .pp-title {
  position: sticky; top: 0; z-index: 2;
  margin: 0 -1.1rem .35rem;
  padding: 1.1rem 1.1rem .45rem;
  background: rgba(5, 8, 16, .98);
  font-size: 1.15rem;
}
#planet-panel .pp-sub { font-size: .88rem; margin-bottom: .9rem; line-height: 1.5; }

/* Texte agrandi d'environ un tiers — c'est la demande de fond. */
#planet-panel .pp-section  { font-size: .72rem; margin: 1.1rem 0 .45rem; }
#planet-panel .pp-res-bar  { font-size: .9rem; margin: .4rem 0; gap: .6rem; }
#planet-panel .pp-res-name { width: 105px; }
/* v0.0.51 : la vraie vignette du minerai remplace le glyphe — elle a une
   HAUTEUR, donc il faut la caler sur la ligne de texte, sinon les trois
   lignes ne s'alignent plus entre elles. */
#planet-panel .pp-res-name { display: inline-flex; align-items: center; gap: 5px; }
#planet-panel .pp-res-name .res-mini { flex: 0 0 auto; }
#planet-panel .pp-res-track{ height: 7px; }
#planet-panel .pp-res-pct  { font-size: .82rem; width: 46px; }
#planet-panel .pp-res-val  { font-size: .82rem; width: 46px; }
#planet-panel .pp-res-base { font-size: .82rem; width: 58px; }
/* Avec « disponible / de base », la ligne porte deux colonnes de plus :
   on resserre le nom (calé sur « Palladrine », le plus long), la part et
   les écarts, sinon la barre tombe sous les 20 px. Ces règles ne visent
   que `.a-stock` : la fiche sans l'interrupteur ne bouge pas. */
#planet-panel .pp-res-bar.a-stock { gap: .45rem; }
#planet-panel .pp-res-bar.a-stock .pp-res-name { width: 90px; }
#planet-panel .pp-res-bar.a-stock .pp-res-pct  { width: 34px; }
/* Une colonne de plus (l'échéance) : on reprend la place sur la part et
   les deux stocks, calés sur leur contenu le plus long (« 100% »,
   « 988.8M », « / 988.8M ») — sinon la barre tombe sous les 30 px. */
#planet-panel .pp-res-bar.a-eta { gap: .4rem; }
#planet-panel .pp-res-bar.a-eta .pp-res-pct  { width: 30px; }
#planet-panel .pp-res-bar.a-eta .pp-res-val  { width: 43px; }
#planet-panel .pp-res-bar.a-eta .pp-res-base { width: 56px; }
#planet-panel .pp-res-eta { font-size: .82rem; width: 26px; }
/* v0.0.8 : la ligne d'amas. */
#planet-panel .pp-amas     { font-size: .95rem; margin: .45rem 0; }
#planet-panel .pp-status   { font-size: .88rem; margin: .55rem 0; line-height: 1.5; }
#planet-panel .pp-energy   { font-size: .88rem; }
#planet-panel .pp-transit-detail { font-size: .78rem; line-height: 1.55; margin: .35rem 0 .6rem; }
#planet-panel .pp-transit-detail .pp-badge-intra,
#planet-panel .pp-transit-detail .pp-badge-inter { font-size: .68rem; padding: .1rem .45rem; }

/* Bouton d'action collé en bas : toujours atteignable, quelle que soit la
   longueur de la fiche. */
#planet-panel .btn-anchor {
  position: sticky; bottom: 0; z-index: 2;
  margin: .9rem -1.1rem 0;
  padding: .75rem 1.1rem;
  width: auto;
  border-radius: 0;
  border-left: none; border-right: none; border-bottom: none;
  font-size: .88rem;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.82 — LE TABLEAU DES TRAJETS DE LA FICHE DE PLANÈTE
   (interrupteur purgé en v0.0.82)
   ════════════════════════════════════════════════════════════════════
   ⚠️ CES RÈGLES SONT PRÉFIXÉES `#planet-panel`, ET CE N'EST PAS DÉCORATIF.
   Le bloc vit DANS `.pp-transit-detail`, dont la taille de police est
   réécrite dix mille lignes plus bas par `#planet-panel .pp-transit-detail`
   (.78rem contre .62rem). Une règle écrite sans le préfixe perdrait à
   spécificité égale contre celle-là, sans que rien ne le signale — le
   piège du bandeau de production, v0.0.53.
   Le bloc est SOUS le flag, donc rien ici ne s'affiche sur un univers
   qui ne l'a pas déployé : aucun sélecteur ne peut donc repeindre un
   écran existant. */
#planet-panel .pp-traj { margin: .5rem 0 .2rem; border-top: 1px solid rgba(148,163,184,.18); padding-top: .45rem; }
#planet-panel .pp-traj-tete {
  display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap;
  cursor: pointer; user-select: none;
  color: var(--text2, #cbd5e1); font-weight: 600;
}
#planet-panel .pp-traj-tete:hover { color: #e2e8f0; }
#planet-panel .pp-traj-fleche { display: inline-block; width: .8em; color: #60a5fa; }
#planet-panel .pp-traj-sub { font-weight: 400; font-size: .9em; color: var(--text2, #6b8ab0); }
#planet-panel .pp-traj-corps { margin-top: .45rem; }
#planet-panel .pp-traj-vide { color: var(--text2, #6b8ab0); padding: .3rem 0; }
#planet-panel .pp-traj-note { color: var(--text2, #6b8ab0); line-height: 1.45; margin-bottom: .35rem; }
#planet-panel .pp-traj-pied { margin: .4rem 0 0; font-size: .92em; }
/* ⚠️ `table-layout: auto` est VOULU : les noms de vaisseaux vont de
   « Chasseur » à « Transporteur de Flotte », et une largeur fixe couperait
   les longs ou gâcherait la place des courts. Le panneau est étroit, donc
   la colonne des durées est calée à droite pour rester lisible en diagonale. */
/* ⚠️ LE TABLEAU REPREND LA COULEUR DU TEXTE, ET C'EST OBLIGATOIRE.
   Il vit dans `.pp-transit-detail`, peint en `var(--text3)` — qui vaut
   #2d4070 dans la palette du jeu, c'est-à-dire presque la couleur du FOND.
   C'est une teinte de note de bas de page, parfaite pour deux lignes de
   commentaire et illisible sur quinze lignes de tableau (signalement de
   Loryan, capture à l'appui). On repose donc une couleur pleine ici, et on
   ne descend d'un cran que pour ce qui est vraiment secondaire. */
#planet-panel .pp-traj-table { width: 100%; border-collapse: collapse; color: var(--text, #c8d6f0); }
#planet-panel .pp-traj-table th,
#planet-panel .pp-traj-table td { padding: .16rem .28rem; text-align: left; white-space: nowrap; }
/* ⚠️ SEULE LA COLONNE DES NOMS S'ENROULE. Le panneau fait environ 330 px et
   « Transporteur de Flotte » n'y tient pas sur une ligne : sans ce
   `normal`, la colonne poussait les trois autres HORS du panneau — la
   durée et la cale sortaient de l'écran, et rien ne le signalait puisque
   la fiche, elle, ne défile pas horizontalement. Vu au navigateur. */
#planet-panel .pp-traj-table td.pp-traj-nom { white-space: normal; line-height: 1.25; }
#planet-panel .pp-traj-table th { font-size: .86em; color: var(--text2, #6b8ab0); font-weight: 600; border-bottom: 1px solid rgba(148,163,184,.28); }
#planet-panel .pp-traj-table td.num,
#planet-panel .pp-traj-table th.num { text-align: right; }
/* La durée est la réponse à la question posée : c'est elle qu'on cherche en
   parcourant le tableau, donc la seule à mériter d'être mise en avant. */
#planet-panel .pp-traj-table td.pp-traj-duree { color: #93c5fd; font-weight: 600; }
#planet-panel .pp-traj-table tbody tr + tr td { border-top: 1px solid rgba(148,163,184,.08); }
#planet-panel .pp-traj-table .pp-traj-fam { color: var(--text2, #6b8ab0); font-size: .9em; }
/* Le Vaisseau Mère est la LIGNE DE RÉFÉRENCE (les vitesses du jeu se
   comptent en « fois un Vaisseau Mère ») : il se repère d'un coup d'œil. */
#planet-panel .pp-traj-table tr.pp-traj-vm td { color: #93c5fd; font-weight: 600; background: rgba(96,165,250,.08); }
/* Un vaisseau qu'on ne possède pas reste AFFICHÉ — Loryan a demandé « tous
   les vaisseaux » — mais estompé : le tableau sert d'abord à décider avec
   ce qu'on a en cale. */
/* ⚠️ CE QUI RENDAIT LE TABLEAU ILLISIBLE : cette règle estompait à 55 %
   TOUTE ligne d'un vaisseau qu'on ne possède pas — c'est-à-dire douze
   lignes sur quinze, puisqu'un joueur n'a jamais qu'une poignée de types en
   cale. L'intention était bonne (« décider avec ce qu'on a »), le résultat
   délavait la page entière. On inverse : les lignes restent toutes
   lisibles, et c'est ce qu'on POSSÈDE qui se marque — seul le « 0 » de la
   colonne Cale reste discret. */
#planet-panel .pp-traj-table tr.pp-traj-a-bord td { background: rgba(96,165,250,.06); }
#planet-panel .pp-traj-table td.pp-traj-cale { color: #86efac; font-weight: 600; }
#planet-panel .pp-traj-zero { color: var(--text2, #6b8ab0); }
#planet-panel .pp-traj-mk { display: inline-block; padding: 0 .25rem; border-radius: 3px; background: rgba(251,191,36,.18); color: #fcd34d; font-size: .8em; }
#planet-panel .pp-traj-esc { display: inline-block; padding: 0 .25rem; border-radius: 3px; background: rgba(148,163,184,.16); color: #cbd5e1; font-size: .78em; }
/* Le ⚙ marque les vaisseaux qui profitent de la remise des réacteurs. Il
   remplace une colonne entière : le facteur ne dépend pas du vaisseau mais
   du RÉGIME, donc la colonne recopiait quinze fois le même nombre. */
#planet-panel .pp-traj-roue { color: #86efac; font-size: .9em; }

/* ⚠️ LE BOUTON COLLANT ÉTAIT TRANSPARENT, ET CE LOT LE REND VISIBLE.
   `.btn-anchor` est peint à `rgba(14,165,233,.08)` — 8 % — alors qu'il est
   `position: sticky; bottom: 0` : tout ce qui défile dessous se lit À
   TRAVERS. Le défaut existe depuis que la fiche défile, mais il fallait un
   contenu assez long pour le rencontrer ; le tableau des trajets ajoute
   quinze lignes et le rend systématique — « Lancer le transit » s'écrit
   par-dessus les noms de vaisseaux.
   On pose un fond OPAQUE derrière le bouton — la couleur du panneau, la
   même que celle du titre collant juste au-dessus.
   ⚠️ CES DEUX RÈGLES ONT FAILLI MOURIR À LA PURGE, et sans un mot : leur
   consigne annonçait « public/style.css : rien ». Elles étaient gardées par
   `body.f-trajetsParVaisseau` — une classe posée d'après le CATALOGUE, donc
   qui disparaît avec l'entrée. Retirer l'entrée sans les décrocher aurait
   rendu le bouton transparent à nouveau, c'est-à-dire réintroduit le défaut
   que ce lot venait de corriger. La règle de BASE, elle, ne bouge toujours
   pas d'un caractère : on n'écrase que le fond, ici, sur la seule fiche de
   planète. */
#planet-panel .btn-anchor {
  background: linear-gradient(rgba(5, 8, 16, .98), rgba(5, 8, 16, .98)),
              rgba(14, 165, 233, .08);
}
#planet-panel .btn-anchor:hover:not(:disabled) {
  background: linear-gradient(rgba(5, 8, 16, .98), rgba(5, 8, 16, .98)),
              rgba(14, 165, 233, .2);
}

/* La croix de fermeture suit le titre collant. */
#planet-panel .btn-close { position: sticky; top: .5rem; z-index: 3; }

/* Sur un écran étroit, la fiche ne doit pas dévorer la carte. */
@media (max-width: 1280px) {
  #planet-panel { width: 300px; }
}

/* ════════════════════════════════════════════════════════════════════
   v2.36 — EXPÉDITIONS
   ════════════════════════════════════════════════════════════════════
   Deux zones : le POSTE DE COMMANDEMENT dans le module Exploration
   (emplacements, Spécialistes, consommables, missions) et les LANCEURS,
   une modale par vaisseau. Tout réutilise la charte existante — mêmes
   variables de couleur, même modale, même barre de progression que les
   expéditions historiques. */

.exped2-block {
  border: 1px solid #324569;
  border-radius: 8px;
  background: rgba(12, 18, 36, 0.6);
  padding: 10px 12px;
  margin-bottom: 12px;
}
.exped2-block-hdr {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--color-text-primary, #cbd6ea);
  margin-bottom: 8px;
}

/* Les emplacements de mission : un par vaisseau de la gamme. */
.exped2-slots {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.exped2-slot {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid #324569;
  border-radius: 999px;
  background: rgba(124, 58, 237, 0.10);
  font-size: 0.78rem;
}
/* Plus une place de libre : on le dit franchement, sans crier. */
.exped2-slot.full {
  background: rgba(239, 68, 68, 0.12);
  border-color: rgba(239, 68, 68, 0.45);
}
.exped2-slot-ico { font-size: 1rem; }
.exped2-slot-count { font-family: var(--mono); color: #c4b5fd; }

.exped2-sub { margin-top: 10px; }
.exped2-sub-hdr {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--color-text-secondary, #8da4c4);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 5px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.exped2-dim { color: var(--color-text-secondary, #8da4c4); font-size: 0.75rem; }

/* v0.0.33 : le bandeau de brouillage des astéroïdes. En bronze et non en
   rouge : ce n'est pas une attaque, c'est un obstacle qui passe — et il
   repart tout seul. Le rouge est réservé à ce qui détruit. */
.exped2-brouillage {
  margin: 8px 0 4px; padding: 9px 12px; border-radius: 8px; font-size: .78rem;
  line-height: 1.5; border: 1px solid rgba(224,180,99,.45);
  background: rgba(224,180,99,.10); color: var(--text, #eaf2ff);
}
.exped2-brouillage .exped2-dim { display: block; margin-top: 4px; }
.exped2-badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(124, 58, 237, 0.25);
  color: #ddd6fe;
  font-size: 0.72rem;
  font-family: var(--mono);
}

.exped2-spec-list, .exped2-item-list { display: flex; flex-wrap: wrap; gap: 6px; }
.exped2-spec {
  display: flex; flex-direction: column; gap: 1px;
  padding: 5px 9px;
  border: 1px solid #324569;
  border-radius: 6px;
  font-size: 0.78rem;
}
.exped2-item {
  padding: 5px 9px;
  border: 1px solid #324569;
  border-radius: 6px;
  background: rgba(124, 58, 237, 0.10);
  color: var(--color-text-primary, #cbd6ea);
  font-size: 0.78rem;
  cursor: pointer;
}
.exped2-item:hover { background: rgba(124, 58, 237, 0.22); }

/* Une mission en cours. L'Éclaireur n'a pas de barre : il n'a pas de
   date de retour, il vole jusqu'à trouver. */
.exped2-mission {
  padding: 6px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.exped2-mission:last-child { border-bottom: none; }
.exped2-mission-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.8rem;
  color: var(--color-text-primary, #cbd6ea);
}
.exped2-mission-time { font-family: var(--mono); color: #c4b5fd; }
.exped2-mission-sub { margin: 2px 0 4px; }

.exped2-recent {
  font-size: 0.76rem;
  color: var(--color-text-secondary, #8da4c4);
  padding: 3px 0;
}
.exped2-recent.lost { color: #fca5a5; }

/* v0.0.2 : la ligne d'historique n'affiche plus que
   son titre — le détail s'ouvre au clic, d'où le chevron et le survol. */
.exped2-recent-clic {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  border-radius: 4px;
  padding: 3px 6px;
  margin: 0 -6px;
  transition: background 0.12s ease, color 0.12s ease;
}
.exped2-recent-clic:hover {
  background: rgba(124, 58, 237, 0.12);
  color: var(--color-text, #e6eefc);
}
.exped2-recent-chevron {
  margin-left: auto;
  opacity: 0.5;
  font-size: 0.95rem;
  line-height: 1;
}
.exped2-recent-clic:hover .exped2-recent-chevron { opacity: 1; }

.exped2-finds {
  max-height: 240px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.exped2-find {
  font-size: 0.76rem;
  color: var(--color-text-secondary, #8da4c4);
  padding: 3px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.03);
}

/* ── Les lanceurs ────────────────────────────────────────────────── */
.exped2-modal-box { max-width: 620px; }
.exped2-hint {
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--color-text-secondary, #8da4c4);
  background: rgba(124, 58, 237, 0.08);
  border-left: 2px solid rgba(124, 58, 237, 0.55);
  padding: 8px 10px;
  border-radius: 0 6px 6px 0;
  margin-bottom: 12px;
}
.exped2-field { margin-bottom: 12px; }
.exped2-field label {
  display: block;
  font-size: 0.8rem;
  color: var(--color-text-primary, #cbd6ea);
  margin-bottom: 4px;
}
.exped2-field input, .exped2-field select {
  width: 100%;
  padding: 7px 10px;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid #324569;
  border-radius: 6px;
  color: var(--color-text-primary, #cbd6ea);
  font-family: var(--mono);
}
.exped2-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
}
.exped2-actions .btn-primary {
  padding: 8px 16px;
  background: linear-gradient(90deg, #7c3aed, #a78bfa);
  border: none;
  border-radius: 6px;
  color: #fff;
  font-weight: 600;
  cursor: pointer;
}
.exped2-actions .btn-primary:hover { filter: brightness(1.12); }

/* ═══ « ENVOYER 1 » ET « ENVOYER TOUS » ══════════════════════════════
   Demande de Loryan : le champ « Combien ? » et son bouton unique
   disparaissent au profit de deux boutons. Un Limier part SEUL (le serveur
   refuse count > 1) : le champ proposait donc de saisir un nombre que le
   jeu allait refuser, et il fallait revenir dans la fenêtre autant de fois
   qu'on voulait de missions — douze allers-retours pour douze emplacements.

   DEUX PARTIS PRIS.
   1. « Envoyer 1 » est le geste PRINCIPAL (plein, violet), « Envoyer tous »
      le geste appuyé (contour). L'un se fait sans réfléchir, l'autre engage
      toute la flotte disponible — ils ne peuvent pas avoir le même poids.
   2. LE COMPTE EST SUR LE BOUTON (« ×12 »). Un bouton « tous » qui ne dit
      pas combien laisse deviner ; ici on lit ce qui va partir avant de
      cliquer. */
.exped2-actions-duo {
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 10px;
}
.exped2-actions-duo .btn-secondary {
  padding: 8px 16px;
  background: transparent;
  border: 1px solid rgba(167, 139, 250, .55);
  border-radius: 6px;
  color: #c4b5fd;
  font-weight: 600;
  cursor: pointer;
}
.exped2-actions-duo .btn-secondary:hover:not(:disabled) {
  background: rgba(124, 58, 237, .16);
  border-color: #a78bfa;
}
.exped2-actions-duo button:disabled { opacity: .38; cursor: not-allowed; filter: none; }
.exped2-btn-n {
  font-family: var(--mono, monospace);
  background: rgba(255, 255, 255, .12);
  border-radius: 999px;
  padding: 0 6px;
  margin-left: 3px;
  font-size: .82em;
}
/* Le sélecteur du Récolteur, posé À CÔTÉ des boutons et non au-dessus :
   c'est le seul vaisseau dont l'effectif compte (il ramasse en meute), et
   « Envoyer 1 » y désigne UNE MISSION de N Récolteurs — pas un vaisseau. */
.exped2-selecteur {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 10px 5px 12px;
  background: rgba(0, 0, 0, .28);
  border: 1px solid #324569;
  border-radius: 6px;
  font-size: .78rem;
  color: var(--color-text-secondary, #8da4c4);
}
.exped2-selecteur input {
  width: 74px;
  padding: 4px 6px;
  background: rgba(0, 0, 0, .35);
  border: 1px solid #3d5480;
  border-radius: 4px;
  color: var(--color-text-primary, #e8f0ff);
  font-family: var(--mono, monospace);
}
/* v0.0.20 — LE SÉLECTEUR DE DESTINATION DU VOYAGEUR (flag
   Même cadre que le compteur du Récolteur, à la même
   place — à côté de « Envoyer 1 » — parce que c'est la même nature de
   réglage : ce qui va réellement partir quand on clique. Un `<select>`
   plutôt qu'une liste dépliée : il tient sur une ligne dans la tuile, là
   où une liste aurait repoussé les boutons hors de l'encart. */
.exped2-selecteur-cible { padding: 4px 8px; max-width: 100%; }
.exped2-selecteur-cible select {
  max-width: 260px;
  padding: 4px 6px;
  background: rgba(0, 0, 0, .35);
  border: 1px solid #3d5480;
  border-radius: 4px;
  color: var(--color-text-primary, #e8f0ff);
  font-family: inherit;
  font-size: .76rem;
  cursor: pointer;
}
.exped2-selecteur-cible select:hover { border-color: #5b7cb8; }
/* Une destination sans route cartographiée reste VISIBLE mais inerte : la
   masquer laisserait croire que le Vaisseau Mère n'existe pas, alors que
   le problème est qu'aucun Précurseur n'a encore ouvert la voie. */
.exped2-target-item.est-bloquee { opacity: .45; cursor: not-allowed; }
.exped2-target-item.est-bloquee:hover { background: rgba(0, 0, 0, 0.25); }

/* v0.0.49 : un système vers lequel un Précurseur VOLE DÉJÀ. Même parti pris
   que ci-dessus — on le laisse dans la liste, éteint : le cacher ferait
   chercher en vain un système qu'on a sous les yeux sur la carte. */
.exped2-target-item.exped2-target-pris { opacity: .5; cursor: not-allowed; }
.exped2-target-item.exped2-target-pris:hover { background: rgba(0, 0, 0, 0.25); }
.exped2-target-item.exped2-target-pris .exped2-target-meta { color: #f0b849; }
/* v0.0.x — une cible qu'on VIENT d'envoyer, quand la fenêtre reste ouverte.
   Vert « fait », non recliquable. */
.exped2-target-item.exped2-target-envoye { opacity: .6; cursor: default; }
.exped2-target-item.exped2-target-envoye .exped2-target-meta { color: #4ade80; font-weight: 600; }
.exped2-hint.exped2-hint-warn {
  border-left: 3px solid #f0b849;
  background: rgba(240, 184, 73, 0.08);
  padding-left: 10px;
}

.exped2-compte { font-size: .78rem; color: var(--color-text-secondary, #8da4c4); margin-bottom: 4px; }
.exped2-rien { margin-top: 8px; color: #fbbf24; }

/* La liste de cibles (systèmes du Précurseur, VM du Voyageur). Elle peut
   être longue — elle défile dans son propre cadre, jamais la page. */
.exped2-target-list {
  max-height: 46vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.exped2-target-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 7px 10px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid #324569;
  border-radius: 6px;
  color: var(--color-text-primary, #cbd6ea);
  font-size: 0.82rem;
  text-align: left;
  cursor: pointer;
}
.exped2-target-item:hover { background: rgba(124, 58, 237, 0.18); }

/* v0.0.8 : le bouton de lancement de la fenêtre
   ouverte depuis la CARTE. Là-bas il n'y a qu'une cible — celle qu'on
   vient de cliquer —, donc pas une ligne de liste mais une action pleine
   largeur, qui se voit comme la conclusion de la fenêtre. */
.exped2-launch {
  display: block;
  width: 100%;
  margin-top: 12px;
  padding: 11px 14px;
  background: rgba(124, 58, 237, 0.22);
  border: 1px solid #7c3aed;
  border-radius: 8px;
  color: var(--color-text-primary, #cbd6ea);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
}
.exped2-launch:hover { background: rgba(124, 58, 237, 0.35); }
.exped2-target-name { flex: 1; }
.exped2-target-meta {
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--color-text-secondary, #8da4c4);
}

/* ─── La durée estimée d'une mission de Précurseur ───────────────────
   Mesuré sur une vraie carte : 16 h pour le système voisin, 15 j pour un
   système du milieu de la galaxie — un rapport de 22 pour 1 entre deux
   lignes que rien ne distinguait. La couleur fait le tri avant même la
   lecture : dans une liste de cent lignes, « 15j 09h » et « 16h 08 » se
   ressemblent trop pour qu'un chiffre seul suffise.
   Largeur fixe : sans elle les durées s'alignent en escalier et la
   colonne cesse d'être comparable d'une ligne à l'autre. */
.exped2-target-duree {
  flex: 0 0 5.2rem;
  text-align: right;
  font-family: var(--mono);
  font-size: 0.74rem;
  white-space: nowrap;
}
.exped2-target-duree.est-courte  { color: var(--color-success-fg, #22c55e); }
.exped2-target-duree.est-moyenne { color: var(--color-warning-fg, #ea580c); }
.exped2-target-duree.est-longue  { color: var(--color-error-fg, #dc2626); }

/* v2.37 — « Voyage accompagné » : le Convoyeur Défensif et le
   Transporteur de Flotte n'ont pas de navigation autonome. Le badge
   prévient AVANT la production, pas au moment du refus de décollage. */
.ship-escort-tag {
  color: #fbbf24;
  background: rgba(251, 191, 36, 0.12);
  border: 1px solid rgba(251, 191, 36, 0.35);
  border-radius: 4px;
  padding: 0 5px;
  font-size: 0.72rem;
}

/* v2.37 — les vaisseaux retirés du catalogue qui dorment encore au
   hangar. Ni produisibles ni volants, mais toujours encombrants : on
   les nomme pour que le compteur de capacité reste lisible. */
.fleet-retired {
  margin-top: 6px;
  padding: 5px 9px;
  border: 1px solid rgba(251, 191, 36, 0.30);
  border-radius: 6px;
  background: rgba(251, 191, 36, 0.07);
  color: #fbbf24;
  font-size: 0.76rem;
}

/* v2.40 — le CONTRE d'une unité, écrit sur sa fiche. C'est le cœur du
   système de combat : on doit pouvoir composer sa flotte sans simulateur,
   donc l'information vit là où on produit, pas dans un wiki. */
.ship-contre-tag {
  color: #f87171;
  background: rgba(248, 113, 113, 0.12);
  border: 1px solid rgba(248, 113, 113, 0.35);
  border-radius: 4px;
  padding: 0 5px;
  font-size: 0.72rem;
}

/* v2.40 — briefing et rapports d'espionnage (Rampe de Lancement). */
.fl-spy-brief { padding: 4px 0; }
.fl-spy-title { font-weight: 600; margin-bottom: 6px; color: var(--text); }
.fl-spy-tier { display: flex; justify-content: space-between; padding: 2px 0; font-size: .82rem; }
.fl-spy-tier.ok { color: #86efac; }
.fl-spy-tier.ko { color: var(--text3, #64748b); }
.fl-spy-need { font-size: .74rem; opacity: .8; }
.fl-spy-note { margin-top: 8px; font-size: .76rem; color: var(--text2); line-height: 1.4; }

.fl-spy-item { border: 1px solid var(--color-border-subtle, rgba(110,130,170,.25));
  border-radius: 6px; margin-bottom: 6px; overflow: hidden; }
.fl-spy-head { display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 7px 10px; background: none; border: 0; cursor: pointer;
  color: var(--text2); font-size: .8rem; text-align: left; }
.fl-spy-head:hover { background: rgba(255,255,255,.03); }
.fl-spy-when { color: var(--text3); font-size: .74rem; }
.fl-spy-target { font-family: var(--hud, monospace); color: var(--accent2); }
.fl-spy-probes { margin-left: auto; }
.fl-spy-detected { color: #fbbf24; }
.fl-spy-clean { color: #86efac; }
.fl-spy-caret { color: var(--text3); }
.fl-spy-detail { padding: 4px 12px 10px; border-top: 1px solid rgba(110,130,170,.18); }
.fl-spy-row { display: flex; gap: 10px; padding: 3px 0; font-size: .79rem; }
.fl-spy-lbl { min-width: 210px; color: var(--text3); }
.fl-spy-val { color: var(--text); }
.fl-spy-missing { margin-top: 6px; font-size: .76rem; color: #fbbf24; }

/* v2.40 — rapports de COMBAT. Ils réutilisent la carte pliable de
   l'espionnage (.fl-spy-item / .fl-spy-head) : c'est le même objet pour le
   joueur, un compte rendu de ce qu'il a envoyé. Seul l'intérieur change. */
.fl-raid-role { color: var(--text2); }
.fl-raid-verdict { margin-left: auto; font-weight: 600; }
.fl-raid-verdict.ok  { color: #86efac; }
.fl-raid-verdict.ko  { color: #f87171; }
.fl-raid-verdict.nul { color: #fbbf24; }
.fl-raid-detail { padding: 6px 12px 12px; border-top: 1px solid rgba(110,130,170,.18); }
.fl-raid-missiles { font-size: .79rem; color: #fbbf24; margin-bottom: 8px; }

/* Les deux camps côte à côte : c'est la seule mise en page qui laisse
   comparer d'un coup d'œil ce qu'on a perdu et ce qu'on a détruit. */
.fl-raid-camps { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media (max-width: 720px) { .fl-raid-camps { grid-template-columns: 1fr; } }
.fl-raid-camp { border: 1px solid rgba(110,130,170,.2); border-radius: 6px; padding: 7px 9px; }
.fl-raid-camp.moi { border-color: rgba(96,165,250,.45); background: rgba(96,165,250,.06); }
.fl-raid-camp-t { font-weight: 600; font-size: .82rem; margin-bottom: 5px; color: var(--text); }
.fl-raid-moi { font-size: .7rem; font-weight: 400; color: var(--accent2);
  border: 1px solid rgba(96,165,250,.4); border-radius: 4px; padding: 0 4px; }
.fl-raid-row { display: flex; gap: 8px; padding: 2px 0; font-size: .77rem; color: var(--text); }
.fl-raid-lbl { min-width: 80px; color: var(--text3); flex: none; }
.fl-raid-perte { color: #f87171; }
.fl-raid-vide { color: var(--text3); }

.fl-raid-rounds { width: 100%; margin-top: 10px; border-collapse: collapse; font-size: .75rem; }
.fl-raid-rounds th { color: var(--text3); font-weight: 500; text-align: right; padding: 2px 6px; }
.fl-raid-rounds th:first-child, .fl-raid-rounds td:first-child { text-align: left; }
.fl-raid-rounds td { text-align: right; padding: 2px 6px; color: var(--text2);
  border-top: 1px solid rgba(110,130,170,.12); }

.fl-raid-butin { margin-top: 10px; font-size: .82rem; color: #fcd34d; }
.fl-raid-butin.vide { color: var(--text3); }
.fl-raid-cale { margin-top: 4px; font-size: .74rem; color: var(--text3); line-height: 1.4; }
.fl-raid-alerte { color: #fbbf24; }
.fl-raid-vm { margin-top: 6px; font-size: .77rem; color: var(--text2); line-height: 1.4; }

/* La salve de Missiles Perforateurs, dans le briefing du raid. Ce n'est
   pas un vaisseau : elle n'a pas sa ligne dans la liste des vaisseaux et
   se règle donc ici, à côté des contres qu'elle complète. */
.fl-raid-salve { display: flex; align-items: center; gap: 6px; padding: 6px 0; font-size: .8rem; }
.fl-raid-salve-lbl { color: var(--text2); margin-right: auto; }
.fl-raid-salve-n { min-width: 28px; text-align: center; font-weight: 600; color: var(--text); }
.fl-raid-salve-btn { border: 1px solid rgba(110,130,170,.35); background: rgba(255,255,255,.03);
  color: var(--text2); border-radius: 4px; min-width: 24px; padding: 1px 6px; cursor: pointer;
  font-size: .78rem; line-height: 1.5; }
.fl-raid-salve-btn:hover { background: rgba(255,255,255,.08); color: var(--text); }
.fl-raid-salve-btn.max { font-size: .68rem; letter-spacing: .04em; }
.fl-raid-salve-stock { color: var(--text3); font-size: .72rem; }

/* ════════════════════════════════════════════════════════════════════
   v2.41 — MOUVEMENTS DE FLOTTE
   ════════════════════════════════════════════════════════════════════
   Une carte par mouvement, toutes bâties pareil quel que soit ce qui
   bouge : en-tête (quoi · phase · compte à rebours), route, barre
   d'avancement, puis le détail. C'est cette régularité qui rend l'écran
   lisible d'un coup d'œil — un Vaisseau Mère en transit et une expédition
   se lisent avec les mêmes yeux.

   La couleur du liseré gauche dit la NATURE du mouvement, le badge dit la
   PHASE. Deux informations, deux canaux : on ne les confond pas. */
.mv-barre-filtres { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.mv-filtre { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px;
  border: 1px solid rgba(110,130,170,.28); border-radius: 999px;
  background: rgba(255,255,255,.02); color: var(--text2); cursor: pointer; font-size: .82rem; }
.mv-filtre:hover { background: rgba(255,255,255,.06); color: var(--text); }
.mv-filtre.actif { border-color: var(--accent2, #60a5fa); color: var(--text);
  background: rgba(96,165,250,.12); }
.mv-filtre-n { font-size: .72rem; opacity: .75; background: rgba(255,255,255,.07);
  border-radius: 999px; padding: 0 6px; }

.mv-liste { display: flex; flex-direction: column; gap: 10px; }

.mv-item { border: 1px solid rgba(110,130,170,.22); border-left-width: 3px;
  border-radius: 8px; padding: 11px 14px; background: rgba(255,255,255,.015); }
.mv-item.mv-vm    { border-left-color: #c084fc; }
.mv-item.mv-fleet { border-left-color: #60a5fa; }
.mv-item.mv-exped { border-left-color: #34d399; }

.mv-head { display: flex; align-items: center; gap: 11px; }
.mv-ico { font-size: 1.35rem; line-height: 1; flex: none; }
.mv-ident { min-width: 0; }
.mv-titre { font-weight: 600; color: var(--text); }
.mv-soustitre { font-size: .76rem; color: var(--text3); }

.mv-badge { font-size: .7rem; letter-spacing: .04em; text-transform: uppercase;
  border-radius: 999px; padding: 2px 9px; border: 1px solid currentColor; flex: none; }
.mv-badge-aller    { color: #60a5fa; }
.mv-badge-surplace { color: #fbbf24; }
.mv-badge-retour   { color: #86efac; }
/* v0.0.163 — le convoi ARRIVÉ qui attend un ordre. Même famille d'ambre que
   « sur place » : les deux disent « il ne bouge plus », et le joueur n'a pas
   à apprendre une troisième couleur pour une troisième nuance d'immobilité. */
.mv-badge-attente  { color: #fbbf24; }

/* Le compte à rebours est la seule chose qu'on vient vraiment lire :
   il est à droite, en chiffres tabulaires pour ne pas frémir à chaque
   seconde (sans quoi la largeur change et la ligne entière tremble). */
.mv-cdr { margin-left: auto; text-align: right; flex: none; }
.mv-cdr-val { font-family: var(--hud, monospace); font-size: 1.05rem; font-weight: 600;
  color: var(--text); font-variant-numeric: tabular-nums; }
.mv-cdr-val.imminent { color: #fbbf24; }
.mv-cdr-sub { font-size: .7rem; color: var(--text3); }

.mv-route { display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  margin: 9px 0 7px; font-size: .84rem; color: var(--text); }
.mv-fleche { color: var(--text3); }
.mv-coords { font-family: var(--hud, monospace); font-size: .76rem; color: var(--accent2); }
.mv-simple { font-size: .7rem; color: #fbbf24; border: 1px solid rgba(251,191,36,.4);
  border-radius: 4px; padding: 0 5px; }

.mv-barre { height: 4px; border-radius: 999px; background: rgba(110,130,170,.18);
  overflow: hidden; margin-bottom: 9px; }
/* La transition suit le tick (250 ms) : la barre GLISSE au lieu de sauter
   par à-coups. C'est tout ce qui sépare « fluide » de « saccadé ». */
.mv-barre-fill { height: 100%; border-radius: 999px; transition: width .25s linear;
  background: linear-gradient(90deg, rgba(96,165,250,.5), var(--accent2, #60a5fa)); }
.mv-exped .mv-barre-fill { background: linear-gradient(90deg, rgba(52,211,153,.4), #34d399); }
.mv-vm    .mv-barre-fill { background: linear-gradient(90deg, rgba(192,132,252,.4), #c084fc); }

.mv-ligne { display: flex; gap: 10px; padding: 2px 0; font-size: .79rem; align-items: baseline; }
.mv-ligne-lbl { min-width: 148px; flex: none; color: var(--text3); }
.mv-unite { display: inline-block; margin-right: 10px; color: var(--text); }

.mv-actions { margin-top: 8px; }
/* ⚠️ v0.0.7 — LA NOTE PASSE SOUS LE BOUTON AU TÉLÉPHONE. Elle suivait le
   bouton sur la même ligne : une note courte (« Ce qui est déjà récolté
   rentre avec le vaisseau. ») s'en accommodait, mais celle du demi-tour de
   L'Horizon fait trois lignes — elle commençait à droite du bouton puis
   repartait SOUS lui, à gauche, en enjambant sa hauteur. Lisible, mais
   cassé. En colonne, chacun a sa ligne. Le desktop, où la place ne manque
   pas, ne bouge pas d'un pixel. */
@media (max-width: 700px) {
  .mv-actions { display: flex; flex-direction: column; align-items: flex-start; gap: .35rem; }
}
.mv-btn { border: 1px solid rgba(110,130,170,.35); background: rgba(255,255,255,.03);
  color: var(--text2); border-radius: 5px; padding: 3px 10px; cursor: pointer; font-size: .78rem; }
.mv-btn:hover { background: rgba(248,113,113,.12); border-color: rgba(248,113,113,.45); color: #f87171; }

.mv-vide { text-align: center; padding: 34px 16px; color: var(--text3); font-size: .9rem;
  border: 1px dashed rgba(110,130,170,.25); border-radius: 8px; }
.mv-vide-sub { display: inline-block; margin-top: 6px; font-size: .78rem; opacity: .8; }

@media (max-width: 720px) {
  .mv-head { flex-wrap: wrap; }
  .mv-cdr { margin-left: 0; width: 100%; text-align: left; }
  .mv-ligne { flex-direction: column; gap: 1px; }
  .mv-ligne-lbl { min-width: 0; }
}

/* v2.41 — bloc FRET de la Rampe. Deux soutes distinctes (Convoyeur pour
   les défenses, Transporteur pour les vaisseaux) : elles se lisent l'une
   sous l'autre, chacune avec sa jauge, parce qu'elles ne se remplacent
   pas — remplir l'une ne libère pas l'autre. */
.fl-fret-nature { margin-top: 10px; }
.fl-fret-nature:first-child { margin-top: 0; }
.fl-fret-titre { display: flex; justify-content: space-between; align-items: baseline;
  font-size: .82rem; font-weight: 600; color: var(--text2); margin-bottom: 5px; }
.fl-fret-vide { font-size: .76rem; color: var(--text3); padding: 3px 0 6px; line-height: 1.4; }
.fl-cargo-fill.over { background: #f87171; }

/* ════════════════════════════════════════════════════════════════════
   v2.42 — COALITIONS
   ════════════════════════════════════════════════════════════════════
   Le panneau d'alliance refondu. Un seul principe de mise en page : la
   vue est LONGUE (six onglets, vingt-cinq voûtes, cent soixante-quinze
   paliers), donc tout ce qui déborde défile DANS son propre conteneur —
   jamais la page entière en largeur. */

.co-vide { padding: 28px 16px; text-align: center; color: var(--text2);
  border: 1px dashed rgba(110,130,170,.25); border-radius: 10px; }
.co-erreur { color: #f87171; border-color: rgba(248,113,113,.35); }
.co-sourdine { color: var(--text2); font-weight: 400; }
.co-note { margin-top: 8px; font-size: .78rem; color: var(--text2); line-height: 1.5;
  border-left: 2px solid var(--border2); padding-left: 9px; }
.co-intro { color: var(--text2); font-size: .84rem; line-height: 1.55; margin: 0 0 12px; }
.co-ico { font-size: 1.15rem; }

/* ── En-tête de la coalition ───────────────────────────────────── */
.co-entete { background: linear-gradient(160deg, rgba(14,165,233,.07), rgba(124,58,237,.05));
  border: 1px solid var(--border2); border-radius: 12px; padding: 16px 18px; margin-bottom: 14px; }
.co-entete-principal { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.co-nom { margin: 0; font-size: 1.35rem; color: var(--text); }
.co-tag { font-family: ui-monospace, monospace; color: var(--accent2); font-weight: 700; }
.co-tag-gros { font-size: 1.1rem; }
.co-badge { font-size: .76rem; padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--border2); background: rgba(255,255,255,.03); color: var(--text2); }
.co-description { margin: 8px 0 0; color: var(--text2); font-size: .85rem; line-height: 1.5; }
.co-stats { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 14px; }
.co-stat { display: flex; flex-direction: column; }
.co-stat-v { font-size: 1.05rem; font-weight: 700; color: var(--text); }
.co-stat-l { font-size: .72rem; color: var(--text2); }

/* ── Onglets internes ──────────────────────────────────────────── */
.co-onglets { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
/* ─── Interrupteur clanRefonte — le clan refondu (features/clan/clan-refonte.js) ───
   Plancher de lecture : 0,8 rem. Tout ce qui ne tient pas à 390 px s'empile
   (hall, forces, lingots, échelles) ou défile dans son cadre (frise de
   Loyauté, calendrier des ordres) — la page, jamais. */
body.f-clanRefonte .clr-hall { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(240px, 1fr); gap: 14px; margin-bottom: 12px;
  border: 1px solid rgba(245,185,66,.35); border-radius: 12px; padding: 16px;
  background: radial-gradient(circle at 92% 130%, rgba(192,106,42,.35) 0 22%, transparent 23%),
              radial-gradient(600px 200px at 15% 0, rgba(245,185,66,.14), transparent), var(--bg2); }
body.f-clanRefonte .clr-identite { display: flex; align-items: center; gap: 12px; }
body.f-clanRefonte .clr-ecu { display: grid; place-items: center; flex: none; width: 58px; height: 66px; font: 900 .82rem var(--hud);
  color: #1a1204; background: linear-gradient(160deg, #f5b942, #7a4a10); clip-path: polygon(50% 100%, 0 72%, 0 0, 100% 0, 100% 72%); }
body.f-clanRefonte .clr-identite-t { min-width: 0; }
body.f-clanRefonte .clr-nom { font: 700 1.35rem var(--hud); color: #f3f6ff; margin: 0; overflow-wrap: anywhere; }
body.f-clanRefonte .clr-lieu { font-size: .86rem; color: var(--text2); }
body.f-clanRefonte .clr-devise { font-style: italic; color: var(--text2); margin: 8px 0; }
body.f-clanRefonte .clr-etendards { display: flex; flex-wrap: wrap; gap: 8px; }
body.f-clanRefonte .clr-etendard { min-width: 160px; flex: 0 1 220px; padding: 8px 10px; border-radius: 6px 6px 14px 14px;
  background: linear-gradient(180deg, rgba(14,165,233,.25), rgba(14,165,233,.05)); border: 1px solid rgba(14,165,233,.5); font-size: .86rem; }
body.f-clanRefonte .clr-etendard.est-vide { color: var(--text3); border-style: dashed; background: none; }
body.f-clanRefonte .clr-etendard-s { font-size: .8rem; color: var(--text2); }
body.f-clanRefonte .clr-hall-droite { display: flex; flex-direction: column; gap: 10px; border-left: 1px solid rgba(245,185,66,.25); padding-left: 14px; min-width: 0; }
body.f-clanRefonte .clr-indic { display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: 2px 10px; }
body.f-clanRefonte .clr-indic b { font: 700 1.05rem var(--hud); color: #f3f6ff; text-align: right; }
body.f-clanRefonte .clr-indic-l { font: 600 .8rem var(--hud); letter-spacing: .08em; text-transform: uppercase; color: var(--text3); }
body.f-clanRefonte .clr-jauge { grid-column: 1 / -1; display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: #0f1a33; }
body.f-clanRefonte .clr-jauge i { display: block; height: 100%; background: var(--gold); }
body.f-clanRefonte .clr-indic-ligne { display: flex; flex-wrap: wrap; gap: 6px; }
body.f-clanRefonte .clr-gestes { display: flex; flex-direction: column; gap: 6px; }
body.f-clanRefonte .clr-geste { width: 100%; text-align: left; }
body.f-clanRefonte .clr-geste.est-alerte { border-color: rgba(233,69,96,.6); color: #fda4af; }
@media (max-width: 760px) {
  body.f-clanRefonte .clr-hall { grid-template-columns: minmax(0, 1fr); }
  body.f-clanRefonte .clr-hall-droite { border-left: 0; padding-left: 0; border-top: 1px solid rgba(245,185,66,.25); padding-top: 10px; }
}
body.f-clanRefonte .clr-forces-grille { display: grid; grid-template-columns: minmax(0, 340px) minmax(0, 1fr); gap: 14px; align-items: center; }
body.f-clanRefonte .clr-radar { width: 100%; max-width: 340px; height: auto; display: block; }
body.f-clanRefonte .clr-radar-grille { fill: none; stroke: #1e3060; }
body.f-clanRefonte .clr-radar-axe { stroke: #162040; }
body.f-clanRefonte .clr-radar-t { font: 11px var(--body); fill: var(--text2); }
body.f-clanRefonte .clr-radar-total { fill: rgba(245,185,66,.28); stroke: #f5b942; stroke-width: 2; }
body.f-clanRefonte .clr-radar-loy { fill: rgba(16,185,129,.35); stroke: #10b981; stroke-width: 1.5; }
body.f-clanRefonte .clr-leg { display: inline-flex; align-items: center; gap: 6px; margin-left: 6px; }
body.f-clanRefonte .clr-leg i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
body.f-clanRefonte .clr-leg i.est-total { background: rgba(245,185,66,.7); }
body.f-clanRefonte .clr-leg i.est-loy { background: rgba(16,185,129,.7); margin-left: 6px; }
body.f-clanRefonte .clr-forces-liste { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 4px 12px; }
body.f-clanRefonte .clr-force { display: grid; grid-template-columns: 1fr auto; gap: 0 8px; font-size: .86rem; padding: 3px 0; border-bottom: 1px solid var(--border); }
body.f-clanRefonte .clr-force b { font-family: var(--mono); color: #6ee7b7; }
body.f-clanRefonte .clr-force small { grid-column: 1 / -1; font-size: .8rem; color: #34d399; min-height: 0; }
body.f-clanRefonte .clr-force.est-nul { opacity: .55; }
body.f-clanRefonte .clr-force.est-nul b { color: var(--text3); }
body.f-clanRefonte .clr-loy { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 10px; }
body.f-clanRefonte .clr-loy-t { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; }
body.f-clanRefonte .clr-frise-defile { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 8px 0; }
body.f-clanRefonte .clr-frise { position: relative; display: grid; grid-template-columns: repeat(10, minmax(72px, 1fr)); min-width: 720px; padding-top: 4px; }
body.f-clanRefonte .clr-frise::before, body.f-clanRefonte .clr-frise::after { content: ""; position: absolute; top: 11px; height: 4px; border-radius: 2px; left: 5%; }
body.f-clanRefonte .clr-frise::before { right: 5%; background: #1e3060; }
body.f-clanRefonte .clr-frise::after { width: calc(var(--fait) * .9); background: var(--accent4); }
body.f-clanRefonte .clr-palier { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; text-align: center; }
body.f-clanRefonte .clr-palier-pt { width: 14px; height: 14px; border-radius: 50%; background: #1e3060; margin: 2px 0 4px; }
body.f-clanRefonte .clr-palier.est-fait .clr-palier-pt { background: var(--accent4); }
body.f-clanRefonte .clr-palier.est-ici .clr-palier-pt { width: 20px; height: 20px; margin: -1px 0 1px; background: var(--accent4); box-shadow: 0 0 0 3px #fff; }
body.f-clanRefonte .clr-palier-nom { font-size: .8rem; color: var(--text3); }
body.f-clanRefonte .clr-palier.est-fait .clr-palier-nom { color: var(--text); }
body.f-clanRefonte .clr-palier.est-ici .clr-palier-nom { color: #fff; font-weight: 700; }
body.f-clanRefonte .clr-palier-j { font: .8rem var(--mono); color: var(--text3); }
@media (max-width: 760px) { body.f-clanRefonte .clr-forces-grille { grid-template-columns: minmax(0, 1fr); } body.f-clanRefonte .clr-radar { margin: 0 auto; } }
body.f-clanRefonte .clr-salle-corps { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px; }
body.f-clanRefonte .clr-salle-total { display: flex; flex-direction: column; gap: 2px; min-width: 160px; }
body.f-clanRefonte .clr-salle-total b { font: 700 1.7rem var(--hud); color: var(--gold); }
body.f-clanRefonte .clr-piles { flex: 1 1 360px; display: flex; gap: 10px; align-items: flex-end; min-height: 170px; min-width: 0; }
body.f-clanRefonte .clr-pile { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 3px; }
body.f-clanRefonte .clr-pile-q { font: .8rem var(--mono); color: var(--text2); }
body.f-clanRefonte .clr-pile-lingot { width: 100%; height: var(--h); border-radius: 4px 4px 0 0; background: linear-gradient(180deg, var(--rc), color-mix(in srgb, var(--rc) 40%, transparent)); }
body.f-clanRefonte .clr-pile-nom { font-size: .8rem; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
/* Au téléphone, six piles debout dans 330 px coupent les noms (« Palladrine ») :
   elles se couchent, une ressource par ligne, la longueur gardant la valeur. */
@media (max-width: 760px) {
  body.f-clanRefonte .clr-piles { flex: 1 1 100%; flex-direction: column; align-items: stretch; gap: 6px; min-height: 0; }
  body.f-clanRefonte .clr-pile { display: grid; grid-template-columns: 96px minmax(0, 1fr) auto; align-items: center; gap: 8px; }
  body.f-clanRefonte .clr-pile-nom { order: -1; }
  body.f-clanRefonte .clr-pile-lingot { order: 0; height: 14px; width: max(4px, calc(var(--f) * 100%)); border-radius: 0 4px 4px 0;
    background: linear-gradient(90deg, color-mix(in srgb, var(--rc) 40%, transparent), var(--rc)); }
  body.f-clanRefonte .clr-pile-q { order: 1; }
}
/* La coalition garde ses tailles ; dans le clan, plancher de 11 px. */
body.f-clanRefonte .clr-vue .co-badge { font-size: .8rem; }
body.f-clanRefonte .clr-vue .co-mini { font-size: .8rem !important; }
body.f-clanRefonte .clr-vue .co-journal-date { font-size: .8rem; }
body.f-clanRefonte .clr-repartition { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }
body.f-clanRefonte .clr-anneau { width: 220px; max-width: 100%; height: auto; flex: none; }
body.f-clanRefonte .clr-anneau-fond { fill: none; stroke: #162040; stroke-width: 26; }
body.f-clanRefonte .clr-anneau-v { font: 700 17px var(--hud); fill: #f3f6ff; }
body.f-clanRefonte .clr-anneau-l { font: 12px var(--body); fill: var(--text3); }
body.f-clanRefonte .clr-anneau-leg { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: auto 1fr auto; gap: 4px 10px; font-size: .86rem; min-width: 220px; }
body.f-clanRefonte .clr-anneau-leg li { display: contents; }
body.f-clanRefonte .clr-anneau-leg i { width: 12px; height: 12px; border-radius: 3px; align-self: center; }
body.f-clanRefonte .clr-anneau-leg b { font-family: var(--mono); text-align: right; }
body.f-clanRefonte .clr-anneau-leg span { grid-column: 2 / 4; color: var(--text3); font: .8rem var(--mono); margin-top: -4px; }
body.f-clanRefonte .clr-echelles { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px; }
body.f-clanRefonte .clr-echelle, body.f-clanRefonte .clr-service { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 10px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
body.f-clanRefonte .clr-echelle header, body.f-clanRefonte .clr-service header { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; color: #f3f6ff; }
body.f-clanRefonte .clr-echelle-ico { font-size: 1.15rem; }
body.f-clanRefonte .clr-marches { display: flex; flex-direction: column; gap: 3px; }
body.f-clanRefonte .clr-marche { font: .8rem var(--mono); text-align: center; border-radius: 3px; padding: 3px 4px; }
body.f-clanRefonte .clr-marche.est-avenir { background: #101a33; color: var(--text3); }
body.f-clanRefonte .clr-marche.est-suivant { border: 1px dashed var(--gold); color: var(--gold); }
body.f-clanRefonte .clr-marche.est-payable { box-shadow: inset 3px 0 0 var(--gold); color: #ffd88a; }
body.f-clanRefonte .clr-marche.est-atteint { background: rgba(245,185,66,.5); color: #1a1204; }
body.f-clanRefonte .clr-marche.est-actuel { background: var(--gold); font-weight: 700; }
body.f-clanRefonte .clr-marche.est-reste { background: rgba(245,185,66,.25); color: #ffd88a; }
body.f-clanRefonte .clr-marche.est-zero { background: #101a33; color: var(--text3); }
body.f-clanRefonte .clr-echelle-s { font-size: .82rem; color: var(--text2); margin: 0; }
body.f-clanRefonte .clr-calendrier-defile { overflow-x: auto; -webkit-overflow-scrolling: touch; }
body.f-clanRefonte .clr-calendrier { min-width: 760px; }
body.f-clanRefonte .clr-cal-jours, body.f-clanRefonte .clr-ordre { display: grid; grid-template-columns: 200px minmax(0, 1fr) 230px; gap: 10px; align-items: center; }
body.f-clanRefonte .clr-cal-jours > div { display: grid; grid-template-columns: repeat(7, 1fr); font: .8rem var(--mono); color: var(--text3); }
body.f-clanRefonte .clr-ordre { border-bottom: 1px solid var(--border); padding: 6px 0; }
body.f-clanRefonte .clr-ordre-t { display: flex; flex-direction: column; font-size: .86rem; min-width: 0; }
body.f-clanRefonte .clr-ordre-t small { font-size: .8rem; color: var(--text2); }
body.f-clanRefonte .clr-ordre-piste { position: relative; height: 26px; background: repeating-linear-gradient(90deg, transparent 0 calc(100% / 7 - 1px), rgba(30,48,96,.6) calc(100% / 7 - 1px) calc(100% / 7)); border-radius: 4px; }
body.f-clanRefonte .clr-ordre-barre { position: absolute; left: 0; top: 2px; bottom: 2px; min-width: 120px; border-radius: 4px; background: rgba(14,165,233,.45);
  border: 1px solid var(--accent2); font-size: .8rem; color: #e6eeff; padding: 2px 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.f-clanRefonte .clr-ordre-libre { font-size: .8rem; color: var(--text3); line-height: 26px; padding-left: 6px; }
body.f-clanRefonte .clr-ordre-act { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
body.f-clanRefonte .clr-ordre-act small { font-size: .8rem; color: #ffd88a; }
body.f-clanRefonte .clr-services-grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 10px; margin-bottom: 10px; }
body.f-clanRefonte .clr-distribution > .co-section { margin: 0; padding: 0; border: 0; background: none; }
body.f-clanRefonte .clr-conseil { display: inline-block; font-size: .8rem; border-radius: 10px; padding: 1px 8px; border: 1px solid; }
body.f-clanRefonte .clr-conseil.est-ok { color: #9ff0cf; border-color: rgba(16,185,129,.5); background: rgba(16,185,129,.12); }
body.f-clanRefonte .clr-conseil.est-avis { color: #ffd88a; border-color: rgba(245,158,11,.55); background: rgba(245,158,11,.13); }
body.f-clanRefonte tr.clr-ici td { background: rgba(14,165,233,.06); }
body.f-clanRefonte .clr-orga { display: flex; flex-direction: column; align-items: stretch; gap: 0; }
body.f-clanRefonte .clr-orga-niv { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
body.f-clanRefonte .clr-orga-carte { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 150px; padding: 10px 12px; border-radius: 8px; background: var(--card); border: 1px solid var(--border2); }
body.f-clanRefonte .clr-orga-carte.est-chef { border-color: var(--gold); background: rgba(245,185,66,.08); }
body.f-clanRefonte .clr-orga-carte.est-libre { border-style: dashed; opacity: .7; }
body.f-clanRefonte .clr-orga-rang { font: 600 .8rem var(--hud); letter-spacing: .05em; color: var(--text3); }
body.f-clanRefonte .clr-orga-lien { width: 2px; height: 16px; background: var(--border2); margin: 0 auto; }
body.f-clanRefonte .clr-orga-groupe { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin-top: 8px; }
body.f-clanRefonte .clr-puces { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
body.f-clanRefonte .clr-puce { font-size: .86rem; border: 1px solid var(--border2); border-radius: 999px; padding: 2px 10px; }
body.f-clanRefonte .clr-puce.r-recruit { border-color: rgba(16,185,129,.5); }
/* ─── Interrupteur clanRecrutementVisuel — Recrutement et Sortie en cartes (features/clan/clan-refonte.js) ─── */
body.f-clanRecrutementVisuel .clr-recrut-haut, body.f-clanRecrutementVisuel .clr-sortie-grille { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
body.f-clanRecrutementVisuel .clr-porte, body.f-clanRecrutementVisuel .clr-inviter { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; min-width: 0; }
body.f-clanRecrutementVisuel .clr-porte.est-ouverte { border-color: rgba(16,185,129,.55); background: linear-gradient(135deg, rgba(16,185,129,.13), var(--card) 65%); }
body.f-clanRecrutementVisuel .clr-porte.est-fermee { border-color: rgba(148,163,184,.35); background: linear-gradient(135deg, rgba(148,163,184,.08), var(--card) 65%); }
body.f-clanRecrutementVisuel .clr-porte-haut { display: flex; align-items: center; gap: 12px; }
body.f-clanRecrutementVisuel .clr-porte-ico { flex: none; width: 52px; height: 52px; display: grid; place-items: center; border-radius: 12px; font-size: 1.9rem; background: rgba(16,185,129,.15); }
body.f-clanRecrutementVisuel .clr-porte.est-fermee .clr-porte-ico { background: rgba(148,163,184,.12); }
body.f-clanRecrutementVisuel .clr-porte-t { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
body.f-clanRecrutementVisuel .clr-porte-t b { font: 700 1rem var(--hud); color: #f3f6ff; }
body.f-clanRecrutementVisuel .clr-porte.est-ouverte .clr-porte-t b { color: #6ee7b7; }
body.f-clanRecrutementVisuel .clr-porte-t span, body.f-clanRecrutementVisuel .clr-porte-s { font-size: .82rem; color: var(--text2); }
/* L'interrupteur : la bille à droite et le fond vert = candidatures ouvertes. */
body.f-clanRecrutementVisuel .clr-bascule { position: relative; flex: none; width: 54px; height: 30px; padding: 0; border-radius: 15px; border: 1px solid var(--border2); background: #1b2540; cursor: pointer; }
body.f-clanRecrutementVisuel .clr-bascule[aria-checked="true"] { background: #10b981; border-color: #34d399; }
body.f-clanRecrutementVisuel .clr-bascule-bille { position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%; background: #cbd5e1; transition: left .2s; }
body.f-clanRecrutementVisuel .clr-bascule[aria-checked="true"] .clr-bascule-bille { left: 27px; background: #fff; }
body.f-clanRecrutementVisuel .clr-bascule:focus-visible { outline: 2px solid var(--accent2); outline-offset: 2px; }
body.f-clanRecrutementVisuel .clr-jauge.est-libre i { background: #10b981; }
body.f-clanRecrutementVisuel .clr-jauge.est-plein i { background: #e94560; }
body.f-clanRecrutementVisuel .clr-inviter header { display: flex; align-items: center; gap: 8px; color: #f3f6ff; }
body.f-clanRecrutementVisuel .clr-inviter-champ { display: flex; gap: 8px; }
body.f-clanRecrutementVisuel .clr-inviter-champ input { flex: 1; min-width: 0; padding: 9px 12px; border-radius: 8px; border: 1px solid var(--border2); background: #0b1328; color: var(--text); font-size: .9rem; }
body.f-clanRecrutementVisuel .clr-inviter-champ input:focus { outline: none; border-color: var(--accent2); }
body.f-clanRecrutementVisuel .clr-sous-titre { display: flex; align-items: center; gap: 8px; margin: 16px 0 8px; font-size: .95rem; font-weight: 600; color: var(--text); }
body.f-clanRecrutementVisuel .clr-compte { display: inline-grid; place-items: center; min-width: 22px; height: 22px; padding: 0 7px; border-radius: 11px; font: 700 .8rem var(--mono); background: #1b2540; color: var(--text2); }
body.f-clanRecrutementVisuel .clr-compte.est-actif { background: #e94560; color: #fff; }
body.f-clanRecrutementVisuel .clr-cands { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 10px; }
body.f-clanRecrutementVisuel .clr-cand { display: flex; flex-direction: column; gap: 8px; min-width: 0; padding: 12px; border-radius: 10px; border: 1px solid var(--border2); background: var(--card); }
body.f-clanRecrutementVisuel .clr-cand-tete { display: flex; align-items: center; gap: 10px; }
body.f-clanRecrutementVisuel .clr-avatar { flex: none; width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; font: 700 1rem var(--hud); color: #fff;
  background: hsl(var(--av) 55% 36%); box-shadow: 0 0 0 2px hsl(var(--av) 70% 60% / .55); }
body.f-clanRecrutementVisuel .clr-avatar.est-petit { width: 26px; height: 26px; font-size: .8rem; }
body.f-clanRecrutementVisuel .clr-cand-t, body.f-clanRecrutementVisuel .clr-invit-t { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
body.f-clanRecrutementVisuel .clr-cand-t small, body.f-clanRecrutementVisuel .clr-invit-t small { font-size: .8rem; color: var(--text3); }
body.f-clanRecrutementVisuel .clr-cand-msg { margin: 0; padding: 8px 10px; border-left: 3px solid var(--accent2); border-radius: 0 6px 6px 0; background: rgba(14,165,233,.07); font-size: .86rem; font-style: italic; color: var(--text); overflow-wrap: anywhere; }
body.f-clanRecrutementVisuel .clr-cand-msg.est-vide { border-left-color: var(--border2); background: none; color: var(--text3); }
body.f-clanRecrutementVisuel .clr-cand-temps { height: 4px; border-radius: 2px; overflow: hidden; background: #162040; }
body.f-clanRecrutementVisuel .clr-cand-temps i { display: block; height: 100%; background: var(--gold); }
body.f-clanRecrutementVisuel .clr-cand-act { display: flex; gap: 8px; margin-top: auto; }
body.f-clanRecrutementVisuel .clr-cand-act .dsh-btn { flex: 1; max-width: 220px; }
body.f-clanRecrutementVisuel .clr-vide { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 18px 12px; text-align: center; border: 1px dashed var(--border2); border-radius: 10px; font-size: .86rem; color: var(--text2); }
body.f-clanRecrutementVisuel .clr-vide > span:first-child { font-size: 2rem; }
body.f-clanRecrutementVisuel .clr-vide b { color: var(--text); }
body.f-clanRecrutementVisuel .clr-invits { display: flex; flex-wrap: wrap; gap: 8px; }
body.f-clanRecrutementVisuel .clr-invit { display: inline-flex; align-items: center; gap: 8px; max-width: 100%; padding: 4px 6px 4px 4px; border-radius: 999px; border: 1px solid var(--border2); background: var(--card); font-size: .86rem; }
body.f-clanRecrutementVisuel .clr-invit-x { flex: none; width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--border2); background: none; color: var(--text2); cursor: pointer; font-size: .8rem; }
body.f-clanRecrutementVisuel .clr-invit-x:hover { border-color: #e94560; color: #fda4af; }
body.f-clanRecrutementVisuel .clr-sortie-carte { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; padding: 14px 14px 14px 66px; border-radius: 10px; border: 1px solid var(--border2); background: var(--card); font-size: .86rem; color: var(--text2); }
body.f-clanRecrutementVisuel .clr-sortie-carte > b { font: 700 1rem var(--hud); color: #f3f6ff; }
body.f-clanRecrutementVisuel .clr-sortie-carte p { margin: 0; }
body.f-clanRecrutementVisuel .clr-sortie-ico { position: absolute; left: 14px; top: 14px; width: 40px; height: 40px; display: grid; place-items: center; border-radius: 10px; font-size: 1.35rem; background: rgba(245,185,66,.12); }
body.f-clanRecrutementVisuel .clr-sortie-carte.est-danger { border-color: rgba(233,69,96,.45); background: repeating-linear-gradient(135deg, rgba(233,69,96,.06) 0 10px, transparent 10px 20px), var(--card); }
body.f-clanRecrutementVisuel .clr-sortie-carte.est-danger .clr-sortie-ico { background: rgba(233,69,96,.16); }
body.f-clanRecrutementVisuel .clr-pertes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
body.f-clanRecrutementVisuel .clr-pertes b { color: var(--text); }
.co-onglet { padding: 7px 13px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--border); background: rgba(255,255,255,.02);
  color: var(--text2); font-size: .82rem; }
.co-onglet:hover { background: rgba(255,255,255,.06); color: var(--text); }
.co-onglet.actif { border-color: var(--accent2); color: var(--text);
  background: rgba(14,165,233,.12); }

/* ── Sections et blocs ─────────────────────────────────────────── */
.co-section { margin-bottom: 22px; }
.co-titre { font-size: .98rem; margin: 0 0 8px; color: var(--text); font-weight: 600; }
.co-duo { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 14px; }
.co-bloc { border: 1px solid var(--border); border-radius: 10px; padding: 13px 15px;
  background: rgba(255,255,255,.015); }
.co-gros { font-size: .95rem; font-weight: 600; margin: 0 0 8px; color: var(--text); }
.co-bandeau { padding: 10px 13px; border-radius: 9px; font-size: .82rem; line-height: 1.5;
  border: 1px solid var(--border2); background: rgba(14,165,233,.07); margin-bottom: 12px; }
.co-bandeau-alerte { border-color: rgba(233,69,96,.4); background: rgba(233,69,96,.08); }

/* ── Jauges ────────────────────────────────────────────────────── */
.co-jauge { height: 7px; border-radius: 4px; background: rgba(255,255,255,.06);
  overflow: hidden; margin: 6px 0; }
.co-jauge-fine { height: 4px; margin: 4px 0 0; }
.co-jauge-grosse { height: 11px; }
.co-jauge-barre { height: 100%; border-radius: 4px;
  background: linear-gradient(90deg, var(--accent2), var(--accent4)); transition: width .4s ease; }

/* ── Cartes ────────────────────────────────────────────────────── */
.co-grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.co-grille-3 { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.co-carte { border: 1px solid var(--border); border-radius: 10px; overflow: hidden;
  background: rgba(255,255,255,.015); display: flex; flex-direction: column; }
.co-carte-eteinte { opacity: .5; }
.co-carte-tete { display: flex; align-items: center; gap: 8px; padding: 10px 13px;
  border-bottom: 1px solid var(--border); background: rgba(255,255,255,.02); }
.co-carte-corps { padding: 11px 13px; font-size: .82rem; color: var(--text2); line-height: 1.5; flex: 1; }
.co-carte-corps p { margin: 0 0 8px; }
.co-carte-pied { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 10px 13px; border-top: 1px solid var(--border); }
.co-devise { font-style: italic; color: var(--accent2); }
.co-ligne { margin-bottom: 3px; }
.co-cout { font-size: .8rem; color: var(--text2); }
.co-cout-manque { color: #f87171; }

.co-puce { display: inline-block; font-size: .7rem; padding: 2px 7px; border-radius: 999px;
  border: 1px solid var(--border2); color: var(--text2); margin: 2px 3px 0 0; }
.co-puce-or { border-color: rgba(250,204,21,.45); color: #facc15; }
.co-axes { margin-top: 6px; }

/* ── Grille des axes de bonus ──────────────────────────────────── */
.co-axes-grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 8px; }
.co-axe { display: flex; align-items: center; gap: 8px; padding: 8px 11px;
  border: 1px solid var(--border); border-radius: 8px; background: rgba(255,255,255,.02); }
.co-axe-nul { opacity: .4; }
.co-axe-label { flex: 1; font-size: .8rem; color: var(--text2); }
.co-axe-val { font-weight: 700; font-size: .85rem; color: var(--accent4); }
.co-axe-nul .co-axe-val { color: var(--text3); font-weight: 400; }

/* ── Conditions (âges, reconversion) ───────────────────────────── */
.co-conditions { display: grid; gap: 9px; }
.co-condition { display: grid; grid-template-columns: 24px 1fr auto; gap: 8px; align-items: center;
  padding: 8px 11px; border: 1px solid var(--border); border-radius: 8px; }
.co-condition.ok { border-color: rgba(16,185,129,.35); background: rgba(16,185,129,.05); }
.co-condition-label { font-size: .82rem; color: var(--text2); }
.co-condition-val { font-size: .8rem; color: var(--text); font-variant-numeric: tabular-nums; }
.co-condition .co-jauge { grid-column: 1 / -1; }

/* ── Pot commun ────────────────────────────────────────────────── */
.co-pot-total { font-size: 2rem; font-weight: 800; color: var(--text);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.co-pot-total .co-sourdine { font-size: 1rem; font-weight: 500; }
.co-versement { border: 1px solid var(--border); border-radius: 10px; padding: 13px 15px; }
.co-versement-res { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 9px; margin: 11px 0; }
.co-res { display: flex; flex-direction: column; gap: 3px; }
.co-res-nom { font-size: .74rem; color: var(--text2); text-transform: capitalize; }
.co-res input, .co-formulaire input, .co-formulaire select, .co-versement select {
  background: var(--bg3); border: 1px solid var(--border2); border-radius: 6px;
  color: var(--text); padding: 6px 9px; font-size: .84rem; width: 100%; }

/* ── Formulaires ───────────────────────────────────────────────── */
.co-formulaire { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 11px; align-items: end; border: 1px solid var(--border); border-radius: 10px; padding: 13px 15px; }
.co-formulaire-ligne { grid-template-columns: 1fr auto; }
.co-formulaire label { display: flex; flex-direction: column; gap: 4px;
  font-size: .76rem; color: var(--text2); }
.co-formulaire .co-large { grid-column: 1 / -1; }

/* ── Tableaux ──────────────────────────────────────────────────── */
.co-table-enveloppe { overflow-x: auto; border: 1px solid var(--border); border-radius: 10px; }
.co-table { width: 100%; border-collapse: collapse; font-size: .81rem; }
.co-table th { text-align: left; padding: 8px 11px; color: var(--text2); font-weight: 600;
  border-bottom: 1px solid var(--border); background: rgba(255,255,255,.02); white-space: nowrap; }
.co-table td { padding: 7px 11px; border-bottom: 1px solid rgba(255,255,255,.04); color: var(--text); }
.co-table tr:last-child td { border-bottom: none; }
.co-table tr.co-acquis td { color: var(--accent4); }
.co-table tr.co-majeur td { background: rgba(250,204,21,.05); }
.co-table tr.co-complete td { opacity: .55; }
.co-table tr.co-moi td { background: rgba(14,165,233,.1); font-weight: 600; }
.co-actions { display: flex; gap: 4px; white-space: nowrap; }
.co-mini { padding: 2px 8px !important; font-size: .75rem !important; }
.co-danger { border-color: rgba(233,69,96,.45) !important; color: #f87171 !important; }

/* ── v0.0.100 : la ligne de classement qui ouvre une fiche ─────────
   ⚠️ LA GARDE EST LA CLASSE `f-…` DU CORPS et non le seul nom de classe :
   sans elle, une ligne deviendrait cliquable à l'œil sur les univers où le
   clic ne fait rien — le curseur promettrait un geste qui n'existe pas.
   ⚠️ ET LE SURVOL NE TOUCHE NI LA TAILLE NI LA BORDURE, seulement le fond :
   une bordure au survol décalerait la ligne d'un pixel à chaque passage de
   souris (leçon du panneau de vol, v0.0.55). */
body .co-table tr.co-cliquable { cursor: pointer; }
body .co-table tr.co-cliquable:hover td { background: rgba(14,165,233,.07); }
body .co-table tr.co-cliquable.co-moi:hover td { background: rgba(14,165,233,.16); }

/* La fiche extérieure d'une coalition, dans une fenêtre du jeu. */
/* ⚠️ `margin-bottom: 3px` VIENT DE `.co-ligne` DE BASE et s'ajoute à ce
   padding sans que rien ne le dise : la fiche respirait 3 px de plus par
   ligne que ce que ce bloc annonce. On le remet à zéro explicitement —
   la fiche gouverne son propre rythme. */
.co-fiche-ext .co-ligne { display: flex; justify-content: space-between; gap: 16px;
  margin-bottom: 0; padding: 5px 0; border-bottom: 1px solid rgba(255,255,255,.05); }
.co-fiche-ext .co-ligne:last-of-type { border-bottom: none; }
.co-fiche-ext .co-ligne span { color: var(--text2); }
.co-fiche-ext .co-devise { margin-top: 10px; }

/* ── Hauts faits ───────────────────────────────────────────────── */
.co-faits { display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); gap: 12px; }
.co-fait { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
  background: rgba(255,255,255,.015); }
.co-fait-fini { border-color: rgba(250,204,21,.4); background: rgba(250,204,21,.04); }
.co-fait-tete { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.co-fait-rang { margin-left: auto; font-family: ui-monospace, monospace;
  font-weight: 700; color: var(--accent2); }
.co-fait-desc { font-size: .76rem; color: var(--text2); line-height: 1.5; margin: 7px 0; }
.co-fait-mesure { display: flex; justify-content: space-between; font-size: .8rem;
  font-variant-numeric: tabular-nums; }
.co-fait-paliers { display: flex; gap: 4px; margin-top: 8px; flex-wrap: wrap; }
.co-palier { flex: 1; min-width: 26px; text-align: center; font-size: .66rem;
  font-family: ui-monospace, monospace; padding: 3px 0; border-radius: 4px; cursor: help;
  border: 1px solid var(--border); color: var(--text3); }
.co-palier.acquis { border-color: var(--accent4); color: var(--accent4);
  background: rgba(16,185,129,.12); }

/* ── Décret en cours ───────────────────────────────────────────── */
.co-decret-actif { display: flex; gap: 11px; align-items: flex-start;
  border: 1px solid rgba(250,204,21,.35); background: rgba(250,204,21,.05);
  border-radius: 10px; padding: 12px 14px; }
.co-decret-actif p { margin: 4px 0 0; font-size: .78rem; line-height: 1.45; }

/* ── Journal ───────────────────────────────────────────────────── */
.co-journal { list-style: none; margin: 0; padding: 0; }
.co-journal li { padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,.04);
  font-size: .8rem; color: var(--text); }
.co-journal li:last-child { border-bottom: none; }
.co-journal-date { display: inline-block; min-width: 78px; color: var(--text2);
  font-size: .74rem; font-variant-numeric: tabular-nums; }

@media (max-width: 720px) {
  .co-stats { gap: 12px; }
  .co-formulaire, .co-formulaire-ligne { grid-template-columns: 1fr; }
  .co-onglet { flex: 1 1 auto; text-align: center; }
}

/* v2.42.1 — Retour d'action des Coalitions.
   ⚠️ `toast()` est neutralisée depuis la v1.38 (plus aucune pop-up
   éphémère, décision produit) : les refus doivent donc s'afficher DANS la
   vue, et y RESTER jusqu'à la prochaine action. Un bouton dont le refus
   part dans le vide a exactement l'air de ne pas marcher. */
.co-msg { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 14px;
  padding: 11px 13px; border-radius: 10px; border: 1px solid var(--border2);
  background: rgba(255,255,255,.03); font-size: .84rem; line-height: 1.5; }
.co-msg-ok    { border-color: rgba(16,185,129,.45); background: rgba(16,185,129,.07); }
.co-msg-erreur{ border-color: rgba(233,69,96,.45);  background: rgba(233,69,96,.08); }
.co-msg-ico { font-size: 1.05rem; line-height: 1.3; }
.co-msg-corps { flex: 1; }
.co-msg-detail { margin: 6px 0 0; padding-left: 18px; color: var(--text2); }
.co-msg-detail li { margin: 2px 0; }
.co-msg-fermer { background: none; border: none; color: var(--text2); cursor: pointer;
  font-size: .9rem; padding: 0 2px; line-height: 1; }
.co-msg-fermer:hover { color: var(--text); }

/* Coût de fondation : ce que la flotte a, en face de ce qu'il faut. */
.co-cout-table { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 7px; margin-bottom: 10px; }
.co-cout-ligne { display: grid; grid-template-columns: 20px 1fr auto; gap: 7px; align-items: center;
  padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px; font-size: .8rem; }
.co-cout-ligne.ok { border-color: rgba(16,185,129,.32); }
.co-cout-ligne.manque { border-color: rgba(233,69,96,.32); }
.co-cout-res { text-transform: capitalize; color: var(--text2); }
.co-cout-val { font-variant-numeric: tabular-nums; color: var(--text); }

/* Plafond réel sous chaque champ de versement. */
.co-res-max { display: flex; align-items: center; gap: 6px; font-size: .7rem; margin-top: 2px; }
.co-lien { background: none; border: none; padding: 0; cursor: pointer;
  color: var(--accent2); font-size: .7rem; text-decoration: underline; }
.co-lien:disabled { color: var(--text3); cursor: default; text-decoration: none; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — LE QG, LES AVANT-POSTES ET LE COFFRE
   ════════════════════════════════════════════════════════════════════
   ⚠️ TOUT EST EN LARGEUR SOUPLE (`auto-fit`, `flex-wrap`, `minmax`) et
   rien n'est posé en pixels fixes : ces panneaux s'ouvrent aussi sur un
   téléphone de 390 px, et c'est exactement là que ce projet a déjà cassé
   des écrans — un tableau qui déborde ne lève aucune erreur. */

/* Le contenu du Coffre et la garnison : des pastilles qui se replient. */
.co-coffre-contenu { display: flex; flex-wrap: wrap; gap: 7px; margin: 10px 0 4px; }
.co-coffre-res { display: inline-flex; align-items: baseline; gap: 5px;
  padding: 4px 10px; border: 1px solid var(--border); border-radius: 999px;
  font-size: .78rem; color: var(--text2); font-variant-numeric: tabular-nums; }
.co-coffre-res b { color: var(--text); text-transform: capitalize; font-weight: 600; }

/* Une structure posée : le lieu, et l'état de son quota de déplacement. */
.co-struct { border: 1px solid var(--border); border-radius: 10px;
  padding: 11px 13px; margin: 10px 0; }
.co-struct-tete { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.co-struct-ico { font-size: 1.1rem; }
/* ⚠️ `min-width: 0` EST NÉCESSAIRE, pas décoratif : sans lui, un nom de
   planète long refuse de se couper dans un conteneur flex et pousse la
   carte hors de l'écran — le débordement horizontal ne se voit qu'au
   téléphone, et aucune erreur ne le signale. */
.co-struct-lieu { flex: 1 1 180px; min-width: 0; overflow-wrap: anywhere; }
.co-puce-froide { border-color: rgba(148,163,184,.4); color: var(--text3); }

/* Un avertissement dans un panneau — plus fort qu'une note, moins qu'une
   alerte de page. */
.co-alerte { margin: 10px 0; padding: 9px 12px; border-radius: 8px;
  border: 1px solid rgba(233,69,96,.35); background: rgba(233,69,96,.08);
  color: var(--text); font-size: .82rem; line-height: 1.45; }

/* Une voûte déjà franchie mais refermée par un pillage : ni acquise, ni
   inconnue. Elle se distingue par une teinte tiède — le vert est réservé à
   ce qui est réellement ouvert. */
.co-perdue { opacity: .78; }
.co-perdue td:first-child { color: #facc15; }

/* ─── La fiche d'une structure, ouverte au clic sur la carte ───────
   Même gabarit que les fiches du Colporteur et du Ferrailleur : un
   panneau flottant en bas à gauche, au-dessus du canvas. */
.cqg-fiche { position: fixed; left: 16px; bottom: 16px; z-index: 60;
  width: min(320px, calc(100vw - 32px));
  background: var(--panel, #12161f); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.45);
  font-size: .82rem; overflow: hidden; }
.cqg-tete { display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; border-bottom: 1px solid var(--border); }
.cqg-ico { font-size: 1.05rem; }
.cqg-titre { flex: 1 1 auto; min-width: 0; font-weight: 700; color: var(--text);
  overflow-wrap: anywhere; }
.cqg-fermer { background: none; border: none; color: var(--text3);
  cursor: pointer; font-size: .95rem; line-height: 1; padding: 2px 4px; }
.cqg-fermer:hover { color: var(--text); }
.cqg-corps { padding: 10px 12px; display: flex; flex-direction: column; gap: 7px; }
.cqg-lieu { font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.cqg-sys { font-weight: 400; color: var(--text3); }
.cqg-ligne { display: flex; justify-content: space-between; gap: 10px; color: var(--text2); }
.cqg-ligne b { color: var(--text); font-variant-numeric: tabular-nums; }
.cqg-note { color: var(--text2); line-height: 1.45; }
.cqg-discret { color: var(--text3); font-size: .76rem; }
.cqg-alerte { color: var(--text); border-left: 2px solid rgba(224,92,92,.7);
  padding-left: 8px; }

/* ⚠️ EN MODE TÉLÉPHONE, LA FICHE PREND TOUTE LA LARGEUR ET REMONTE :
   posée en bas à gauche sur un écran de 390 px, elle recouvrait la barre
   d'action du bas — un bouton caché sous un panneau collant est le
   troisième défaut que ce projet rencontre le plus souvent. */
@media (max-width: 700px) {
  .cqg-fiche { left: 8px; right: 8px; bottom: 68px; width: auto; }
}
body.mobile-mode .cqg-fiche { left: 8px; right: 8px; bottom: 68px; width: auto; }

/* La section « Quartier Général » d'un rapport d'espionnage. Elle reprend
   le gabarit du plan de raid, avec une teinte propre : ce n'est pas le
   même renseignement, et le lecteur doit les distinguer d'un coup d'œil. */
.ci-plan-qg { border-color: rgba(79,209,165,.38); }

/* ════════════════════════════════════════════════════════════════════
   v2.43 — LES VUES OCCUPENT LA PAGE (demande de Loryan)
   ════════════════════════════════════════════════════════════════════
   `.dsh-view` était bridée à 1200 px centrés. Sur un écran large, ça
   donnait une colonne au milieu et deux bandes vides sur les côtés —
   pénible partout, absurde sur les vues à tableaux (Classements,
   Coalition, Notifications, Social) dont les colonnes se serraient
   pendant que l'écran restait à moitié inutilisé.

   ⚠️ Le passage en pleine largeur OBLIGE à réserver la place du panneau
   des Vaisseaux Mères : il est en `position: fixed; right: 0`, donc sans
   marge le contenu passait DESSOUS. C'était déjà le cas avant ce
   changement — la colonne de 1200 px dépassait sous le panneau sur les
   écrans entre 1260 et 1440 px, et la colonne « Points » du classement
   s'en trouvait coupée. Les deux problèmes se règlent d'une ligne. */
.dsh-view {
  max-width: none;
  margin: 0;
  padding-right: calc(var(--vm-panel-w, 180px) + var(--space-screen-desktop, 24px));
}

/* Le panneau des VM devient un drawer sous 900 px : plus rien à réserver. */
@media (max-width: 900px) {
  .dsh-view { padding-right: var(--space-screen-desktop, 24px); }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.157 — LA VUE RÉSERVE LA PLACE DU RAIL JUSQU'AU VRAI SEUIL
   ════════════════════════════════════════════════════════════════════
   ⚠️ DEUX SEUILS QUI NE SE PARLAIENT PAS, ET DEUX CENTS PIXELS DE DÉGÂTS.
   La règle juste au-dessus arrête de réserver la place du rail dès 900 px
   (« plus rien à réserver »), mais le rail, lui, ne se replie en tiroir
   qu'à partir de 700 px — deux blocs plus loin dans ce fichier, et sur
   l'autre axe (`body.mobile-mode`) plus loin encore. Il reste donc une
   bande de 701 à 900 px où le rail est POSÉ sur l'écran pendant que les
   vues font comme s'il n'existait plus.

   MESURÉ (Classements, une seule fenêtre redimensionnée, v0.0.157) :
     1000 · 950 · 902 · 901 px → la vue réserve, rien ne passe dessous
     900 → 701 px             → 156 px de contenu DERRIÈRE le rail
     700 px et moins          → le rail est en tiroir, rien ne passe dessous
   À 820 px, six boutons de la vue sont enterrés sous le rail (Vaisseaux,
   Défenses, Exploration et trois pastilles de tri) : le joueur clique et
   le geste part dans le vide. Aucun ascenseur, aucune erreur — le défaut
   n° 3 de la maison.

   ⚠️ ON DESCEND LE SEUIL DE LA VUE, ON NE REMONTE PAS CELUI DU RAIL.
   L'autre sens (replier le rail dès 900 px) a été écarté : le mode mobile
   porte SON PROPRE seuil à 700 px (`mobile-mode.js`), et déplacer l'un
   sans l'autre met les deux systèmes en désaccord sur toute la bande —
   rail en tiroir par la requête de largeur, mais classe `mobile-mode`
   absente, donc les règles qui en dépendent muettes. Les bouger tous les
   deux ferait basculer en mode mobile tous les téléphones en paysage :
   bien plus large que ce défaut.

   Arbitrage de Loryan : « la vue garde la place du rail ». */
.dsh-view {
  padding-right: calc(var(--vm-panel-w, 180px) + var(--space-screen-desktop, 24px));
}
@media (max-width: 700px) {
  .dsh-view { padding-right: var(--space-screen-desktop, 24px); }
}
/* Le rail se replie AUSSI sur le mode d'affichage, indépendamment de la
   largeur réelle (mobile forcé sur un grand écran) : la réserve doit donc
   tomber sur ce second axe aussi, sinon la vue garderait 180 px de vide
   à droite d'un rail devenu tiroir. */
body.mobile-mode .dsh-view {
  padding-right: var(--space-screen-desktop, 24px);
}

/* Les sections internes suivent : elles étaient elles aussi centrées sur
   une largeur fixe, ce qui aurait annulé le gain juste au-dessus. */
.dsh-prefs { max-width: none; margin: 0; }
.fl-view-body { max-width: none; }

/* Préférences de notification : la colonne Email a disparu (v2.43), la
   grille passe donc de quatre colonnes à trois. Sans ça, une colonne
   fantôme restait réservée à droite des toggles. */
.dsh-prefs-row { grid-template-columns: 1fr auto auto; }

/* ── Centre de notifications ANCRÉ dans son onglet ──────────────────
   Le même panneau sert flottant (sous la cloche) et ancré (dans
   l'onglet). Ancré, il perd tout ce qui fait une fenêtre — position
   fixe, ombre portée, largeur de 360 px — et devient un bloc de page. */
.notif-panel.is-docked {
  position: static;
  width: auto;
  max-height: min(60vh, 640px);
  box-shadow: none;
  border-radius: 10px;
}
/* Ancré, la croix de fermeture n'a plus de sens : il n'y a rien à
   refermer, l'onglet EST le centre. */
.notif-panel.is-docked .notif-panel-close { display: none; }
.notif-panel.is-docked .notif-panel-list { max-height: none; }

/* ════════════════════════════════════════════════════════════════════
   v2.43 — PARAMÉTRAGE DE PRODUCTION, version section
   ════════════════════════════════════════════════════════════════════
   L'écran passe de la modale à une section du Vaisseau Mère, et gagne
   trois colonnes (stock, échéance) plus un bandeau d'énergie et le débit
   effectif de chaque bâtiment. La mise en page suit : plus de largeur
   contrainte, des lignes qui respirent, et des jauges pour lire un
   remplissage sans lire un nombre. */

.ps-entete { display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 14px; font-size: .85rem; }
.ps-ancrage { color: var(--text2); }
.ps-ancrage strong { color: var(--text); }
.ps-ancrage-non { color: #fbbf24; }
.ps-entete-vm { font-family: ui-monospace, monospace; color: var(--accent2); }

/* ── Bandeau énergie ─────────────────────────────────────────────── */
.ps-energie { border: 1px solid var(--border2); border-radius: 10px; padding: 12px 14px;
  margin-bottom: 16px; background: rgba(16,185,129,.05); }
.ps-energie-deficit { border-color: rgba(233,69,96,.45); background: rgba(233,69,96,.07); }
.ps-energie-tete { display: flex; align-items: center; gap: 8px; font-size: .9rem; }
.ps-energie-ico { font-size: 1.05rem; }
.ps-energie-ratio { margin-left: auto; font-variant-numeric: tabular-nums;
  font-weight: 700; color: var(--accent4); }
.ps-energie-deficit .ps-energie-ratio { color: #f87171; }
.ps-energie-jauge { height: 6px; border-radius: 3px; background: rgba(255,255,255,.07);
  overflow: hidden; margin: 8px 0 6px; }
.ps-energie-jauge > i { display: block; height: 100%; background: var(--accent4); }
.ps-energie-deficit .ps-energie-jauge > i { background: #f87171; }
.ps-energie-detail { font-size: .78rem; color: var(--text2); line-height: 1.5; }

/* ── Tableau du bilan ────────────────────────────────────────────── */
.ps-table-wrap { overflow-x: auto; }
.ps-cell-stock, .ps-cell-echeance { white-space: nowrap; }
/* ⚠️ v0.0.126 — LA COLONNE « RESSOURCE » MANQUAIT À CET APPEL. Ce tableau
   interdit déjà le retour à la ligne sur deux de ses colonnes, pour
   exactement cette raison ; la première n'y était pas. « Palladrine » —
   le plus long des neuf libellés — demande 68,21 px là où la case en offre
   67,47 : il manque TROIS QUARTS DE PIXEL, et le mot bascule sous sa
   pastille, calé à gauche, sur la seule ligne du tableau à faire ça.
   ⚠️ LA GÉOMÉTRIE FAUTIVE EST PLUS ANCIENNE QUE LE DÉFAUT (mesurée
   identique sur le code d'avant) : c'est la ligne d'un minerai équipé mais à
   débit nul, arrivée en v0.0.119, qui a amené le mot à l'écran et fait
   rencontrer le manque.
   ⚠️ ET LA COLONNE NE S'ÉLARGIT QUE D'UN DEMI-PIXEL : 87,47 → 88 px au
   téléphone, 216 → 217 px en grand écran, aucune autre colonne ne bouge. La
   variante `min-width: 120px`, elle, élargissait le tableau de 33 px et
   déplaçait les colonnes du bureau : écartée. */
.ps-cell-res { white-space: nowrap; }
.ps-stock-val { font-variant-numeric: tabular-nums; }
.ps-stock-cap { color: var(--text3); font-size: .9em; }
.ps-stock-jauge { height: 4px; border-radius: 2px; background: rgba(255,255,255,.07);
  overflow: hidden; margin-top: 3px; min-width: 90px; }
.ps-stock-jauge > i { display: block; height: 100%; background: var(--accent2); }
.ps-stock-jauge.pleine > i { background: #fbbf24; }
.ps-echeance-plein { color: #fbbf24; }
.ps-echeance-vide  { color: #f87171; }
.ps-na { color: var(--text3); }

/* ── Lignes de bâtiment : niveau · effet · curseur ───────────────── */
.ps-row { grid-template-columns: minmax(200px, 1.2fr) minmax(160px, 1.4fr) minmax(180px, 1fr); }
.ps-row-effet { font-size: .76rem; color: var(--text2); line-height: 1.4; }
.ps-eff-plus  { color: var(--accent4); }
.ps-eff-moins { color: #f87171; }
.ps-eff-unite { color: var(--text3); }
.ps-aide { font-size: .78rem; color: var(--text2); line-height: 1.55; margin: 0 0 12px; }
.ps-actions { margin-top: 16px; }
/* ─── Interrupteur prodConsoleFlux — la Console et le Flux (ps-vues.js) ───
   ⚠️ LE CURSEUR VERTICAL EST UN VRAI <input type="range"> couché par
   `writing-mode` : mêmes gestes, même clavier, même sauvegarde que la liste.
   Les groupes de la console passent à la ligne (mesuré à 1500 px : en une
   seule rangée, Voyage, Nanites et le vumètre sortaient à droite) ; le
   schéma, lui, défile de côté au téléphone, avec sa largeur minimale. Plancher de lecture : 0,8 rem. */
body.f-prodConsoleFlux .ps-vues-bascule { display: inline-flex; border: 1px solid var(--border2); border-radius: 999px; overflow: hidden; margin: 4px 0 12px; }
body.f-prodConsoleFlux .ps-vue-btn { font: 600 .84rem var(--body); color: var(--text2); background: transparent; border: 0; padding: 7px 14px; cursor: pointer; }
body.f-prodConsoleFlux .ps-vue-btn + .ps-vue-btn { border-left: 1px solid var(--border2); }
body.f-prodConsoleFlux .ps-vue-btn.is-on { color: #e6eeff; background: rgba(124,58,237,.25); }
body.f-prodConsoleFlux .ps-vue-btn:focus-visible { outline: 2px solid var(--accent3); outline-offset: -2px; }
body.f-prodConsoleFlux .ps-console-defile { overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 6px; }
body.f-prodConsoleFlux .ps-console { display: flex; flex-wrap: wrap; gap: 10px; align-items: stretch; }
body.f-prodConsoleFlux .ps-ch-grp { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 8px 8px 10px; }
body.f-prodConsoleFlux .ps-ch-grp-t { font: 600 .8rem var(--hud); letter-spacing: .06em; color: var(--turquoise); text-align: center; margin-bottom: 6px; }
body.f-prodConsoleFlux .ps-ch-rangee { display: flex; flex-wrap: wrap; gap: 4px; }
body.f-prodConsoleFlux .ps-ch { width: 96px; display: flex; flex-direction: column; align-items: center; gap: 3px; text-align: center; }
body.f-prodConsoleFlux .ps-ch.is-off { opacity: .45; }
body.f-prodConsoleFlux .ps-ch-ico { font-size: 1.05rem; }
body.f-prodConsoleFlux .ps-ch-piste { height: 150px; display: flex; justify-content: center; }
body.f-prodConsoleFlux .ps-ch-fader { writing-mode: vertical-lr; direction: rtl; width: 28px; height: 150px; margin: 0; accent-color: var(--accent3); cursor: pointer; }
body.f-prodConsoleFlux .ps-ch-pct { font: 600 .9rem var(--mono); color: #e6eeff; }
body.f-prodConsoleFlux .ps-ch-nom { font-size: .82rem; color: var(--text); line-height: 1.2; }
body.f-prodConsoleFlux .ps-ch-niv { font: .8rem var(--mono); color: var(--text3); }
body.f-prodConsoleFlux .ps-ch-effet { font-size: .8rem; line-height: 1.3; color: var(--text2); min-height: 2.6em; }
body.f-prodConsoleFlux .ps-maitre { width: 150px; display: flex; flex-direction: column; align-items: center; gap: 6px; background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 10px; }
body.f-prodConsoleFlux .ps-maitre-t { font: 600 .8rem var(--hud); letter-spacing: .06em; color: #fde68a; }
body.f-prodConsoleFlux .ps-vu { display: flex; gap: 26px; align-items: flex-end; height: 150px; }
body.f-prodConsoleFlux .ps-vu-b { width: 22px; height: 100%; background: #0f1a33; border: 1px solid var(--border2); border-radius: 4px; position: relative; }
body.f-prodConsoleFlux .ps-vu-b i { position: absolute; left: 0; right: 0; bottom: 0; border-radius: 3px; background: linear-gradient(0deg, #facc15, #fde68a); }
body.f-prodConsoleFlux .ps-vu-b.est-demande i { background: linear-gradient(0deg, #e94560, #fda4af); }
body.f-prodConsoleFlux .ps-vu-leg { display: flex; gap: 10px; font-size: .8rem; color: var(--text3); }
body.f-prodConsoleFlux .ps-maitre-ratio { font: 700 1.3rem var(--hud); color: #6ee7b7; }
body.f-prodConsoleFlux .ps-maitre-ratio.est-deficit { color: #fda4af; }
body.f-prodConsoleFlux .ps-maitre-d { font: .8rem var(--mono); color: var(--text2); text-align: center; }
body.f-prodConsoleFlux .ps-flux-defile { overflow-x: auto; -webkit-overflow-scrolling: touch; }
body.f-prodConsoleFlux .ps-flux-svg { display: block; width: 100%; min-width: 640px; height: auto; }
body.f-prodConsoleFlux .ps-fl-r { fill-opacity: .32; }
body.f-prodConsoleFlux .ps-fl-r.est-source { fill: #facc15; }
body.f-prodConsoleFlux .ps-fl-r.est-conso { fill: #8b5cf6; }
body.f-prodConsoleFlux .ps-fl-r.est-manque { fill: #e94560; fill-opacity: .6; }
body.f-prodConsoleFlux .ps-fl-r.est-perdu { fill: #64748b; }
body.f-prodConsoleFlux .ps-fl-barre { fill: #facc15; }
body.f-prodConsoleFlux .ps-fl-n.est-droite .ps-fl-barre { fill: #8b5cf6; }
body.f-prodConsoleFlux .ps-fl-n.est-manque .ps-fl-barre { fill: #e94560; }
body.f-prodConsoleFlux .ps-fl-n.est-perdu .ps-fl-barre { fill: #64748b; }
body.f-prodConsoleFlux .ps-fl-milieu { fill: #facc15; }
body.f-prodConsoleFlux g.ps-fl-n[role="button"] { cursor: pointer; }
body.f-prodConsoleFlux g.ps-fl-n[role="button"]:hover .ps-fl-barre, body.f-prodConsoleFlux g.ps-fl-n.est-choisi .ps-fl-barre { stroke: #fff; stroke-width: 2; }
body.f-prodConsoleFlux g.ps-fl-n:focus { outline: none; }
body.f-prodConsoleFlux .ps-fl-nom { font: 600 13px var(--body); fill: var(--text); }
body.f-prodConsoleFlux .ps-fl-n.est-choisi .ps-fl-nom { fill: #fff; }
body.f-prodConsoleFlux .ps-fl-n.est-manque .ps-fl-nom { fill: #fda4af; }
body.f-prodConsoleFlux .ps-fl-val { font: 12px var(--mono); fill: var(--text3); }
body.f-prodConsoleFlux .ps-fl-titre { font: 600 12px var(--hud); letter-spacing: .12em; fill: #fde68a; }
body.f-prodConsoleFlux .ps-flux-vide { font: 14px var(--body); fill: var(--text3); }
body.f-prodConsoleFlux .ps-flux-bas { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 12px; margin-top: 10px; }
body.f-prodConsoleFlux .ps-fl-detail, body.f-prodConsoleFlux .ps-fl-conseils { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 12px; min-width: 0; }
body.f-prodConsoleFlux .ps-fl-detail-t { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; color: #e6eeff; }
body.f-prodConsoleFlux .ps-fl-niv { font: .82rem var(--mono); color: var(--text3); }
body.f-prodConsoleFlux .ps-fl-pct { margin-left: auto; font: 600 1rem var(--mono); }
body.f-prodConsoleFlux .ps-fl-curseur { width: 100%; margin: 10px 0 6px; accent-color: var(--accent3); }
body.f-prodConsoleFlux .ps-fl-effet { font-size: .86rem; }
body.f-prodConsoleFlux .ps-fl-autres { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
body.f-prodConsoleFlux .ps-fl-autre { font-size: .82rem; color: var(--text2); background: var(--bg2); border: 1px solid var(--border2); border-radius: 999px; padding: 3px 10px; cursor: pointer; }
body.f-prodConsoleFlux .ps-fl-autre span { font-family: var(--mono); color: var(--text3); }
body.f-prodConsoleFlux .ps-fl-conseils { display: flex; flex-direction: column; gap: 6px; font-size: .86rem; }
body.f-prodConsoleFlux .ps-fl-manque { color: #fda4af; font-weight: 600; }
body.f-prodConsoleFlux .ps-fl-ok { color: #6ee7b7; }
body.f-prodConsoleFlux .ps-fl-perdu, body.f-prodConsoleFlux .ps-fl-sous { color: var(--text2); }
body.f-prodConsoleFlux .ps-fl-chips { display: flex; flex-wrap: wrap; gap: 6px; }
body.f-prodConsoleFlux .ps-fl-conseil { font: .82rem var(--mono); color: #fcd34d; background: rgba(245,158,11,.13); border: 1px solid rgba(245,158,11,.55); border-radius: 999px; padding: 3px 10px; cursor: pointer; }
@media (max-width: 700px) { body.f-prodConsoleFlux .ps-flux-bas { grid-template-columns: minmax(0, 1fr); } }

/* ── v0.0.8 : les six interrupteurs de TRANSMUTATION ──────────────────
   Même grille que les régimes (ps-row) — c'est le même écran et la même
   nature de réglage — mais un interrupteur à la place du curseur : la
   question est binaire, un curseur à deux positions serait un mensonge. */
.ps-transmut { margin-top: 22px; }
.ps-aide-verrou { color: var(--text3); }
.ps-row-transmut .ps-row-slider { gap: 10px; }
.ps-switch { position: relative; display: inline-flex; align-items: center; cursor: pointer; }
.ps-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.ps-switch-piste {
  width: 38px; height: 20px; border-radius: 10px;
  background: rgba(255, 255, 255, 0.10); border: 1px solid var(--border);
  transition: background 0.15s, border-color 0.15s; position: relative;
}
.ps-switch-piste::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--text2); transition: transform 0.15s, background 0.15s;
}
.ps-switch input:checked + .ps-switch-piste {
  background: rgba(56, 189, 248, 0.30); border-color: var(--accent2);
}
.ps-switch input:checked + .ps-switch-piste::after {
  transform: translateX(18px); background: var(--accent2);
}
.ps-switch input:disabled + .ps-switch-piste { opacity: 0.4; cursor: not-allowed; }
.ps-transmut-etat { font-family: var(--mono); font-size: .68rem; }
.ps-transmut-etat.is-on { color: var(--accent2); }
.ps-transmut-etat.is-off { color: var(--text3); }
.ps-transmut-reste { font-family: var(--mono); font-size: .72rem; color: var(--text2); }
.ps-transmut-vide { font-size: .72rem; color: var(--text3); }
.ps-transmut-encours { font-size: .72rem; color: var(--gold); }
/* L'alerte de la fiche de planète quand une conversion peut partir. */
.pps-note.is-transmut {
  color: var(--gold); border-left: 2px solid var(--gold); padding-left: 8px;
}

@media (max-width: 900px) {
  .ps-row { grid-template-columns: 1fr; gap: 4px; }
}

/* v2.43 — Bandeau de statut (cf. `Toasts` dans toasts.js).
   UN SEUL à la fois, en bas au centre, effacé au clic ou après 6 s. Ce
   n'est pas le retour de la pile de bulles supprimée en v1.38 : celles-ci
   s'empilaient à chaque événement du jeu, celui-ci ne parle QUE quand une
   action du joueur échoue — sans quoi un bouton refusé n'a l'air que
   cassé. */
#dsh-statut {
  position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 12px);
  max-width: min(560px, 90vw); padding: 11px 16px; border-radius: 10px;
  font-size: .85rem; line-height: 1.45; z-index: 4000; cursor: pointer;
  opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease;
  background: #131a2c; border: 1px solid var(--border2); color: var(--text);
  box-shadow: 0 10px 34px rgba(0,0,0,.55);
}
#dsh-statut.visible { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
#dsh-statut.dsh-statut-erreur { border-color: rgba(233,69,96,.55); background: #2a1220; }
#dsh-statut.dsh-statut-info   { border-color: rgba(14,165,233,.5);  background: #0f1c2c; }

/* Interrupteur `annulationTrenteSecondes` — le bandeau « Annuler le départ »
   d'une expédition lancée par erreur. Posé AU-DESSUS du bandeau de statut
   (qui annonce le départ au même instant) : les deux doivent se lire en
   même temps, et le bouton ne doit jamais passer dessous. */
#exped2-annulation {
  position: fixed; left: 50%; bottom: 84px; transform: translate(-50%, 12px);
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: center;
  max-width: min(560px, calc(100vw - 32px)); padding: 10px 14px; border-radius: 10px;
  z-index: 4001; opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
  background: #1f1a0e; border: 1px solid rgba(240,186,90,.6); color: var(--text);
  box-shadow: 0 10px 34px rgba(0,0,0,.55); font-size: .9rem;
}
#exped2-annulation.visible { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
#exped2-annulation .exped2-annul-btn {
  font: inherit; font-weight: 600; padding: 7px 12px; border-radius: 8px; cursor: pointer;
  background: rgba(240,186,90,.16); border: 1px solid rgba(240,186,90,.7); color: #f5d49a;
}
#exped2-annulation .exped2-annul-sec { font-variant-numeric: tabular-nums; opacity: .8; margin-left: 4px; }
/* ⚠️ AU TÉLÉPHONE, PLEINE LARGEUR ET PLUS HAUT. Mesuré à 390 px : centré
   par `left: 50%`, le bandeau ne disposait que de la moitié droite de
   l'écran (195 px), montait sur trois lignes et recouvrait le bandeau de
   statut — celui qui annonce le départ au même instant. Pleine largeur, il
   tient sur une ligne, et 132 px le posent au-dessus du statut même quand
   celui-ci prend quatre lignes. */
@media (max-width: 700px) {
  #exped2-annulation { left: 16px; right: 16px; bottom: 132px; max-width: none;
                       transform: translateY(12px); }
  #exped2-annulation.visible { transform: none; }
}
body.mobile-mode #exped2-annulation { left: 16px; right: 16px; bottom: 132px; max-width: none;
                                      transform: translateY(12px); }
body.mobile-mode #exped2-annulation.visible { transform: none; }

/* ════════════════════════════════════════════════════════════════════
   v2.44 — CLASSEMENTS : deux portées, deux mesures
   ════════════════════════════════════════════════════════════════════ */
.lb-barre { display: flex; flex-wrap: wrap; gap: 18px; margin-bottom: 14px; }
.lb-groupe { display: flex; align-items: center; gap: 6px; }
.lb-groupe-label { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text3, #2d4070); margin-right: 2px; }
.lb-choix { padding: 5px 12px; border-radius: 8px; cursor: pointer; font-size: .8rem;
  border: 1px solid var(--border, #162040); background: rgba(255,255,255,.02);
  color: var(--text2, #6b8ab0); }
.lb-choix:hover { background: rgba(255,255,255,.06); color: var(--text, #c8d6f0); }
.lb-choix.actif { border-color: var(--accent2, #0ea5e9); color: var(--text, #c8d6f0);
  background: rgba(14,165,233,.12); }

/* La colonne qui décide du tri est mise en avant : sans ça, deux colonnes
   de nombres se ressemblent et on ne sait plus laquelle ordonne la liste. */
.lb-col-points, .lb-col-num { text-align: right; font-variant-numeric: tabular-nums;
  white-space: nowrap; }
.lb-col-points.is-tri { color: var(--text, #c8d6f0); font-weight: 700; }
.lb-table th.lb-col-points.is-tri { color: var(--accent2, #0ea5e9); }
.lb-col-points:not(.is-tri) { color: var(--text2, #6b8ab0); }
.lb-col-best { white-space: nowrap; }
.lb-tag { font-family: ui-monospace, monospace; color: var(--accent2, #0ea5e9);
  font-weight: 700; margin-right: 6px; }
.lb-sourdine { color: var(--text2, #6b8ab0); font-size: .9em; }

/* ════════════════════════════════════════════════════════════════════
   v2.54 — LA RAMPE DE LANCEMENT REFONDUE
   ════════════════════════════════════════════════════════════════════
   Dans l'ordre où on s'en sert : ce que j'ai, ce que j'envoie — le
   vaisseau est celui du menu du Vaisseau Mère. Tout est préfixé `rp-`.
   Logique : features/fleet-launcher/rampe.js */

/* ── Le lanceur ── */
.rp-lanceur {
  background: rgba(255,255,255,.02); border: 1px solid var(--border);
  border-radius: 8px; padding: 1rem; margin-bottom: 1.2rem;
}
.rp-onglets { display: flex; flex-wrap: wrap; gap: .4rem; }
.rp-onglet {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .45rem .9rem; cursor: pointer; font-family: inherit; font-size: .78rem;
  background: transparent; border: 1px solid var(--border); border-radius: 999px;
  color: var(--text2); transition: .15s;
}
/* ⚠️ LE SURVOL NE DOIT PAS RESSEMBLER À LA SÉLECTION.
   Remonté deux fois par Loryan : « quand je clique sur Icarius ça me met
   bien sur le vaisseau mais ça laisse le surlignage sur l'autre », puis
   « le bug est toujours là pour la sélection ». Le clic fonctionne — la
   classe `is-active` suit bien, vérifié image par image sur sa vidéo.
   C'est l'ŒIL qui est trompé.
   Deux causes successives, et la seconde était le remède mal dosé de la
   première. `:hover` posait d'abord la MÊME bordure turquoise que
   `is-active` ; en le neutralisant, on l'a rendu PLUS VOYANT qu'elle —
   bordure claire et nom en blanc vif contre un simple liseré vert sur fond
   sombre. Des deux, la pastille survolée criait le plus fort.
   TROIS RÈGLES, ET ELLES TIENNENT ENSEMBLE :
   1. Le survol ne change NI la bordure NI la couleur du texte. Il soulève
      très légèrement le fond, assez pour répondre à la souris, pas assez
      pour qu'on croie la pastille choisie.
   2. La sélection prend un fond plein, une bordure épaisse et un texte
      lumineux — c'est la seule chose vive de la rangée.
   3. Elle porte en plus un REPÈRE QUI N'EST PAS UNE COULEUR : un point
      devant son nom. Une différence de teinte se discute (écran, lumière,
      daltonisme) ; une puce présente ou absente, non.
   Né sur les pastilles de vaisseau de l'ancienne Rampe, le même triptyque
   s'applique aux onglets de type de mission et aux tuiles de vaisseau
   d'expédition — c'est le même malentendu, à deux endroits de la page. */
/* Les onglets de type de mission (« je clique sur Expédition et ça ne me
   met pas sur l'onglet ») : survol discret, actif franc, et une puce qui
   ne dépend d'aucune couleur. */
.rp-onglet:hover:not(.is-active) {
  background: rgba(255,255,255,.04);
  border-color: var(--border);
  color: var(--text2);
}
.rp-onglet.is-active {
  border: 2px solid var(--turquoise, #2dd4bf);
  padding: calc(.45rem - 1px) calc(.9rem - 1px);
  background: rgba(45,212,191,.3);
  color: #f2fffd; font-weight: 600;
  box-shadow: 0 0 16px rgba(45,212,191,.26);
}
.rp-onglet.is-active .rp-onglet-label::after {
  content: ' ●'; color: #eafffb; font-size: .7em; vertical-align: middle;
}
.rp-onglet-sous {
  font-size: .72rem; color: var(--text3); margin: .6rem 0 .8rem;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.146 — LA BARRE D'ONGLETS OCCUPE LA LARGEUR
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : « grossis aussi les textes pour occuper la largeur de
   la page ». La barre tenait sur un tiers de l'écran en 0,78 rem, le reste
   étant du vide — et neuf onglets y logent sans se serrer.

   ⚠️ LE SIMULATEUR PART AU BOUT, PAS À LA SUITE. C'est le seul onglet qui
   n'envoie RIEN : il calcule. `margin-left: auto` le décolle du peloton, ce
   qui le dit mieux qu'une étiquette — et c'est la demande, au dessin près.

   ⚠️ LA BARRE NE S'ÉTIRE PAS EN JUSTIFIANT : `space-between` sur une ligne
   qui peut se replier laisse une DERNIÈRE rangée aux écarts absurdes (deux
   pastilles aux deux bouts de l'écran). On grossit le texte et le retrait —
   la largeur se remplit par le contenu, pas par du vide réparti. */
.rp-onglets { width: 100%; gap: .5rem; }
.rp-onglet.est-fin { margin-left: auto; }
.rp-onglets .rp-onglet {
  font-size: .95rem; padding: .55rem 1.15rem; gap: .5rem;
}
.rp-onglets .rp-onglet.is-active {
  padding: calc(.55rem - 1px) calc(1.15rem - 1px);
}
.rp-onglets ~ .rp-onglet-sous,
.rp-onglets + .rp-onglet-sous { font-size: .84rem; }
/* Téléphone : neuf pastilles à 0,95 rem ne tiennent pas sur 390 px — elles
   se replient déjà, mais autant qu'elles se replient SERRÉES plutôt que sur
   quatre rangées. Et le Simulateur revient à la suite : « tout à droite »
   n'a plus de sens quand la barre fait cinq lignes. */
body.mobile-mode .rp-onglets .rp-onglet { font-size: .82rem; padding: .4rem .7rem; }
body.mobile-mode .rp-onglets .rp-onglet.is-active {
  padding: calc(.4rem - 1px) calc(.7rem - 1px);
}
body.mobile-mode .rp-onglet.est-fin { margin-left: 0; }
@media (max-width: 700px) {
  .rp-onglets .rp-onglet { font-size: .82rem; padding: .4rem .7rem; }
  .rp-onglets .rp-onglet.is-active {
    padding: calc(.4rem - 1px) calc(.7rem - 1px);
  }
  .rp-onglet.est-fin { margin-left: 0; }
}

.rp-form { margin-top: .4rem; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.114 — LES LIGNES DE DÉLAIS ET LE PAVÉ DU DEVIS
   ════════════════════════════════════════════════════════════════════
   Le gabarit « libellé + chiffre » : un libellé qui s'étire à gauche, la
   valeur de base (barrée quand elle est remplacée) et la valeur effective
   colorée (gain en vert, perte en ambre). C'est lui que `rampeDevisBloc`
   pose dans `#rp-devis`, sous la Balance comme sous l'Horizon. */
.rp-delais {
  margin-top: auto; width: 100%; padding-top: .4rem;
  border-top: 1px solid rgba(110,130,170,.18);
  display: grid; gap: 2px; cursor: help;
}
.rp-delais-tete {
  font-family: var(--hud); font-size: .55rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text3); margin-bottom: 1px;
}
.rp-delai {
  display: flex; align-items: baseline; gap: .35rem;
  font-size: .68rem; color: var(--text2);
}
.rp-delai-lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* v0.0.161 (flag porteeScannerDansLeDevis) — LE REFUS DU DEVIS, EN PHRASE.
   Une ligne de devis est une colonne de chiffres : `rp-delai-lbl` la garde
   sur une seule ligne et la tronque à l'ellipse. Un REFUS est une phrase,
   et mesuré en 390 px de large il sortait de 173 px hors de l'écran — sans
   défilement horizontal pour aller le lire. Ici il revient à la ligne.
   ⚠️ LA TAILLE EST VOLONTAIRE : .79rem = 11,06 px, soit le plancher de
   lisibilité du projet, contre .68rem (9,52 px) hérité de la rangée de
   chiffres. Elle ne grossit AUCUNE autre boîte — ce bloc REMPLACE les
   lignes de chiffres du devis au lieu de s'y ajouter. */
.rp-delai-refus {
  flex: 1 1 100%; min-width: 0;
  white-space: normal; overflow-wrap: anywhere;
  color: #fbbf24; font-size: .79rem; line-height: 1.35;
}
/* v0.0.163 (flag rampeDevisTientAuTelephone) — LA PHRASE D'ACCUEIL DU DEVIS.
   Tant qu'aucun vaisseau n'est coché, le pavé du devis ne porte pas une
   colonne de chiffres mais une PHRASE. `rp-delai-lbl` lui interdisait le
   retour à la ligne : bloc insécable de 465 px dans une grille, qui refuse
   donc de rétrécir et pousse tout le panneau. Mesuré au navigateur en 390 px
   de large : 539 px de panneau pour 387 de place, sur les quatre onglets de
   logistique (Transport, Stationner, Commercer, Ferrailler), à chaque
   ouverture et avant le moindre clic ; les quatre autres onglets tenaient
   exactement, et le grand écran n'avait rien.
   ⚠️ LE `nowrap` GÉNÉRAL DE `.rp-delai-lbl` RESTE : les lignes « libellé +
   chiffre » qui suivent en ont besoin. Seule la phrase d'accueil change de
   classe, comme `rp-delai-refus` juste au-dessus.
   ⚠️ LA TAILLE EST VOLONTAIRE : .79rem = 11,06 px, le plancher de lisibilité
   du projet, contre .68rem (9,52 px) hérité de la rangée de chiffres. Elle ne
   grossit aucune autre boîte : ce bloc REMPLACE les lignes du devis. */
.rp-delai-invite {
  flex: 1 1 100%; min-width: 0;
  white-space: normal; overflow-wrap: anywhere;
  font-size: .79rem; line-height: 1.35;
}
.rp-delai-base { font-family: var(--mono); flex: none; }
/* Barré : le délai du catalogue, remplacé par celui d'à côté. */
.rp-delai-base.est-barre { text-decoration: line-through; color: var(--text3); }
.rp-delai-eff { font-family: var(--mono); flex: none; font-weight: 600; }
.rp-delai-eff.est-gain  { color: #4ade80; }
.rp-delai-eff.est-perte { color: #fbbf24; }
/* Le sélecteur d'effectif et celui de durée, sur leur propre ligne. */
.rp-selecteur {
  width: 100%; justify-content: space-between;
  padding: 2px 7px; font-size: .66rem; gap: 4px;
}
.rp-selecteur input { width: 62px; padding: 2px 4px; font-size: .72rem; }
/* v0.0.37 (flag dureeRecolteurTuile) : le sélecteur de DURÉE, à côté de
   celui de l'effectif. Assez large pour « 5 h 00 · 60 % de débit ». */
.rp-duree select { max-width: 168px; padding: 2px 4px; font-size: .72rem; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — LE RACCOURCI HORIZON, SOUS LES TUILES D'EXPÉDITION
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan, zone entourée en rouge : un bouton pour partir sur la
   Ligne sans avoir à deviner qu'un sixième onglet existe. La bande est POSÉE
   SOUS la grille, séparée d'elle par sa propre bordure et sa teinte violette
   — celle du vaisseau des confins partout ailleurs dans le jeu (fiche de la
   Ligne, marqueur de la carte). Sans cette différence de couleur, elle se
   lirait comme une cinquième tuile, et elle n'en est pas une : elle ne
   consomme aucun emplacement d'expédition.

   ⚠️ `flex-wrap` ET `min-width: 0` NE SONT PAS DÉCORATIFS : au téléphone, le
   bouton passe SOUS le texte au lieu de le comprimer, et le sous-titre —
   deux compteurs plus une phrase de raison — peut alors se couper en
   plusieurs lignes au lieu de pousser la bande hors de l'écran. C'est le
   défaut n° 1 de la liste du CLAUDE.md (« est-ce que quelque chose
   DÉPASSE ? »), et il ne se signale par aucune erreur. */
.rp-hz-raccourci {
  display: flex; align-items: center; flex-wrap: wrap; gap: .5rem .8rem;
  margin: -.4rem 0 1rem; padding: .6rem .8rem;
  background: rgba(167,139,250,.07);
  border: 1px solid rgba(167,139,250,.28); border-radius: 7px;
}
.rp-hz-raccourci.is-bloque { opacity: .68; }
.rp-hz-raccourci-ico { font-size: 1.25rem; line-height: 1; }
.rp-hz-raccourci-corps {
  display: flex; flex-direction: column; gap: 1px; flex: 1 1 12rem; min-width: 0;
}
.rp-hz-raccourci-nom { color: #e9d9ff; font-weight: 600; font-size: .86rem; }
.rp-hz-raccourci-sous { color: var(--text2); font-size: .72rem; }
.rp-hz-raccourci-btn { flex: 0 0 auto; white-space: nowrap; }
@media (max-width: 700px) {
  /* Le bouton prend toute la largeur plutôt que de se serrer contre le
     texte : au doigt, une cible pleine largeur ne se rate pas. */
  .rp-hz-raccourci-btn { flex: 1 1 100%; }
}

/* Le demi-tour, sous le récapitulatif d'une mission en vol (« Ajoute le
   bouton retour », Loryan). La note explique ce qu'on perd — elle est donc
   AUSSI IMPORTANTE que le bouton, et posée juste dessous plutôt qu'en
   infobulle : au téléphone, une infobulle n'existe pas. */
.rp-actions-hz { display: grid; gap: 2px; margin: .35rem 0 .6rem; }
.rp-actions-hz .rp-lien { justify-self: start; }
/* Le stock restant, écrit sur le bouton d'envoi quand on en a plusieurs. */
.rp-envoyer-stock { opacity: .7; font-size: .78em; margin-left: .3rem; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — CE QUE TON CAMP A DÉJÀ RATISSÉ (onglet Horizon de la Rampe)
   ════════════════════════════════════════════════════════════════════
   Le bloc reprend au pixel près celui de la fiche de la Ligne, sur la
   carte (styles injectés par features/map/horizon.js) : c'est la MÊME
   information, lue au même moment de la même décision. Deux apparences
   pour une seule chose, ce serait deux choses à apprendre.

   ⚠️ « min-width: 2px » SUR LES MARQUES, ET C'EST CE QUI REND LA BANDE
   LISIBLE. Une tranche sur 3 279 vaut 0,03 % de la largeur, soit un
   cinquième de pixel : peinte à sa taille réelle elle serait INVISIBLE, et
   un arc travaillé depuis des mois aurait l'air vierge. La bande est donc
   un repère, pas une mesure — c'est le compte écrit à côté qui donne le
   chiffre juste, et les deux se lisent ensemble. */
.rp-hz-ratissage { display: grid; gap: .4rem; }
.rp-hz-compte { font-weight: 400; opacity: .7; margin-left: .5rem;
                text-transform: none; letter-spacing: 0; }
.rp-hz-bande {
  position: relative; display: block; width: 100%; height: 12px;
  border-radius: 3px; overflow: hidden; cursor: crosshair;
  background: rgba(166, 120, 232, .13);
  border: 1px solid rgba(166, 120, 232, .25);
}
.rp-hz-bande > span {
  position: absolute; top: 0; bottom: 0; min-width: 2px;
  background: #a678e8; border-radius: 1px;
}
.rp-hz-curseur {
  position: absolute; top: -2px; bottom: -2px; width: 2px; margin-left: -1px;
  background: #fff5c2; box-shadow: 0 0 5px rgba(255, 245, 194, .9);
}
.rp-hz-bande-bornes {
  display: flex; justify-content: space-between; font-size: .68rem;
  opacity: .55; font-variant-numeric: tabular-nums;
}
.rp-hz-verdict { font-size: .78rem; padding: .35rem .55rem; border-radius: 6px; }
.rp-hz-verdict.est-vierge { color: #a7f3d0; background: rgba(45, 212, 191, .10);
                            border: 1px solid rgba(45, 212, 191, .28); }
.rp-hz-verdict.est-deja { color: #fcd34d; background: rgba(251, 191, 36, .10);
                          border: 1px solid rgba(251, 191, 36, .32); }
.rp-hz-rangs { display: flex; flex-wrap: wrap; gap: 4px; }
.rp-hz-rang-puce {
  font: inherit; font-size: .72rem; font-variant-numeric: tabular-nums;
  padding: 1px 7px; border-radius: 999px; cursor: pointer;
  color: #e9d9ff; background: rgba(166, 120, 232, .20);
  border: 1px solid rgba(166, 120, 232, .38);
}
.rp-hz-rang-puce:hover { background: rgba(200, 170, 255, .34); }
.rp-hz-rangs-suite { font-size: .72rem; opacity: .6; align-self: center; }
.rp-hz-actions { display: flex; flex-wrap: wrap; gap: .5rem; }

/* v0.0.7 — L'AVERTISSEMENT DU DÉCOLLAGE, entre le devis et le bouton.
   Trois tons parce que les trois cas n'appellent pas la même décision : la
   règle générale, l'entre-deux, et « ton système meurt avant son retour » —
   celui-là seul est rouge, parce que là seulement la perte est CERTAINE.
   Mêmes teintes que dans la fiche de la Ligne sur la carte : c'est la même
   phrase, elle doit avoir la même tête. */
.rp-hz-risque { font-size: .75rem; padding: .45rem .6rem; border-radius: 6px;
                line-height: 1.45; }
.rp-hz-risque.est-info { color: #cbb6f0; background: rgba(166, 120, 232, .10);
                         border: 1px solid rgba(166, 120, 232, .28); }
.rp-hz-risque.est-attention { color: #fcd34d; background: rgba(251, 191, 36, .10);
                              border: 1px solid rgba(251, 191, 36, .32); }
.rp-hz-risque.est-danger { color: #fca5a5; background: rgba(248, 113, 113, .12);
                           border: 1px solid rgba(248, 113, 113, .45); font-weight: 600; }
/* v0.0.7 : « (théorique) » sur la durée aller-retour. Le retour se mesure
   vers l'endroit où sera le Vaisseau Mère, pas vers celui d'aujourd'hui —
   c'est le seul chiffre du devis qui repose sur une hypothèse, et il doit se
   distinguer des trois autres, qui sont fermes. */
.rp-hz-theorique { opacity: .6; font-style: italic; cursor: help; }
/* ═══ v0.0.7 : QUI DONNE CES EMPLACEMENTS ═══════════════════════════
   Le VISAGE du métier qui accorde vraiment les emplacements, et le compte
   écrit en entier (« = 3 + 2 »), pour qu'on lise d'un coup d'œil ce qui
   vient du vaisseau et ce qui s'en ira avec le Spécialiste. Le violet est
   la teinte des Spécialistes partout ailleurs dans le jeu. */
.rp-exped-renfort {
  color: #a78bfa; cursor: help; margin-left: .45em;
  display: inline-flex; align-items: center; gap: .3em; white-space: nowrap;
}
/* ⚠️ LA VIGNETTE EST RONDE ET CERCLÉE : une planche sombre posée sur un fond
   sombre disparaît sans le liseré, et la tuile n'a que dix-huit pixels à lui
   accorder. `vertical-align` la cale sur la ligne de texte mono d'à côté. */
.rp-exped-sp-photo {
  width: 18px; height: 18px; border-radius: 50%; object-fit: cover;
  border: 1px solid rgba(167,139,250,.75);
  box-shadow: 0 0 6px rgba(167,139,250,.35);
  vertical-align: -4px; flex: none;
}
.rp-exped-sp-ico { font-size: .92rem; line-height: 1; }
/* ═══ v0.0.7 : LES RACCOURCIS DE L'ÉCRAN SOCIAL ═════════════════════
   Les boutons d'action du classement — discrets au repos, francs au
   survol : la colonne ne doit pas voler la vedette aux points. */
.lb-col-actions { width: 108px; text-align: right; white-space: nowrap; }
.lb-action {
  background: none; border: 1px solid var(--border, #162040); border-radius: 6px;
  padding: 3px 7px; margin-left: 4px; cursor: pointer; font-size: .8rem;
  opacity: .55; transition: opacity .15s, border-color .15s;
}
.lb-action:hover { opacity: 1; border-color: var(--border2, #1e3060); }
/* Le champ « viser un pseudo » des trois modales du Social. */
.social-vise { display: flex; gap: 8px; margin-bottom: 14px; }
.social-vise input {
  flex: 1; min-width: 0; background: #0a0e1c; border: 1px solid #324569;
  border-radius: 6px; padding: 8px 12px; color: #cbd6ea; font: inherit;
}
.social-vise input:focus { outline: none; border-color: #60a5fa; }
.social-vise button {
  flex: none; background: rgba(96,165,250,.12); border: 1px solid #3b5b8f;
  border-radius: 6px; padding: 8px 12px; color: #cbd6ea; font: inherit;
  font-size: .85rem; cursor: pointer; white-space: nowrap;
}
.social-vise button:hover { background: rgba(96,165,250,.22); }

/* v0.0.7 : l'appui prolongé ouvre la fiche — le navigateur tactile ne
   doit pas poser SON menu (loupe iOS, menu d'image Android) sur le même
   geste. */
.bld-tile,
.tech-tile,
.ship-card-compact {
  -webkit-touch-callout: none;
  -webkit-user-select: none; user-select: none;
}
/* v0.0.7 : le bouton « toute la file » du
   Technicien, dans le titre du bloc — même langage que le 🧬 des cartes,
   en plus petit pour ne pas concurrencer le titre. */
.cd-bloc-titre .cd-accel-tout {
  float: right; font-size: .62rem; padding: 2px 8px; margin-top: -2px;
}
/* ═══ v0.0.11 : MOUVEMENTS COMPACTS ════════
   Une mission = une ligne fine, dépliable au clic. La grille de cartes
   reste le rendu sans le flag ; ici la liste redevient une colonne. */
.mv-liste.est-compacte { display: block; }
.mv-row {
  border: 1px solid rgba(110,130,170,.22); border-left-width: 3px;
  border-radius: 7px; margin-bottom: 5px;
  background: rgba(255,255,255,.02);
}
.mv-row.mv-vm    { border-left-color: #c084fc; }
.mv-row.mv-fleet { border-left-color: #60a5fa; }
.mv-row.mv-exped { border-left-color: #34d399; }
/* v0.0.126 : les DEUX raids se reconnaissent à la couleur — l'orbital en
   orange (le combat au ras du sol), l'interplanétaire en rouge. */
.mv-row.mv-mt-raid,
.mv-groupe.mv-mt-raid { border-left-color: #f87171; }
.mv-row.mv-mt-raid_orbital,
.mv-groupe.mv-mt-raid_orbital { border-left-color: #fb923c; }
.mv-row.mv-mt-spy,
.mv-groupe.mv-mt-spy { border-left-color: #facc15; }

/* v0.0.126 : le bloc « cibler d'ici » de la fiche de planète, et le
   bouton de sonde de la fiche de soleil. */
.pp-agression {
  margin: 8px 0; padding: 8px 10px; border-radius: 8px;
  border: 1px solid rgba(248,113,113,.35); background: rgba(248,113,113,.07);
}
.pp-ag-titre { font-size: .78rem; font-weight: 600; color: #fca5a5; margin-bottom: 6px; }
.pp-ag-boutons { display: flex; flex-wrap: wrap; gap: 6px; }
.pp-ag-btn {
  border: 1px solid rgba(248,113,113,.5); border-radius: 6px;
  background: rgba(248,113,113,.12); color: #fecaca;
  font-size: .74rem; padding: 4px 10px; cursor: pointer;
}
.pp-ag-btn:hover { background: rgba(248,113,113,.22); }
.pp-ag-note { font-size: .68rem; color: #8595b0; margin-top: 5px; }
.sp-sonde-btn {
  border: 1px solid rgba(96,165,250,.5); border-radius: 6px;
  background: rgba(96,165,250,.12); color: #bfdbfe;
  font-size: .78rem; padding: 5px 12px; cursor: pointer;
}
.sp-sonde-btn:hover { background: rgba(96,165,250,.22); }
/* Interrupteur testeurPericlitement : les deux boutons de test de la fiche
   d'un soleil, côte à côte (ils passent l'un sous l'autre si la place
   manque), et leur note au plancher de lisibilité (11 px). */
.sp-testeur-btns { display: flex; flex-wrap: wrap; gap: 6px; }
.sp-testeur .sp-note { font-size: .8rem; }
/* ════════════════════════════════════════════════════════════════════
   v0.0.163 — LA FICHE D'UNE ÉTOILE JAMAIS CARTOGRAPHIÉE
   ════════════════════════════════════════════════════════════════════
   Elle ne porte que deux choses : la phrase qui dit pourquoi elle est
   vide, et la porte pour y remédier. Le bouton prend donc TOUTE la
   largeur — c'est le seul geste de l'écran, et dans un tiroir de
   téléphone un bouton au fil du texte se cherche.
   ⚠️ La note est à .8rem (11,2 px), pas au .68rem des notes voisines :
   ici elle n'accompagne pas un chiffre, elle EST le contenu de la fiche.
   (Plancher de lisibilité du projet : 11 px, cf. --badge-font-size.) */
.sp-prec-btn {
  display: block; width: 100%; margin-top: .55rem;
  border-color: rgba(167,139,250,.5); background: rgba(167,139,250,.14);
  color: #ddd6fe; font-size: .82rem; padding: 7px 12px;
}
.sp-prec-btn:hover { background: rgba(167,139,250,.26); }
/* v0.0.126 : la note « il manque un Capitaine » de la Rampe. */
.rp-note-manque { border-left: 3px solid #facc15; }
/* v0.0.126 : le plan de raid du rapport d'espionnage. */
.ci-plan {
  margin: 8px 0; padding: 8px 10px; border-radius: 8px;
  border: 1px solid rgba(250,204,21,.35); background: rgba(250,204,21,.06);
  font-size: .8rem; line-height: 1.45;
}
.ci-plan > div { margin-top: 4px; }
.ci-plan-note { color: #8595b0; font-size: .7rem; }
.fl-spy-plan { color: #fde68a; font-size: .74rem; padding: 2px 0; }
.mv-row-tete {
  position: relative; display: flex; align-items: center; gap: .55rem;
  padding: .42rem .7rem .5rem; cursor: pointer; list-style: none;
  font-size: .76rem; overflow: hidden;
}
.mv-row-tete::-webkit-details-marker { display: none; }
.mv-row-ico { font-size: .95rem; flex: none; }
.mv-row-titre {
  font-weight: 600; color: var(--text); white-space: nowrap; flex: none;
}
.mv-row-sous { font-weight: 400; color: var(--text2); font-size: .68rem; margin-left: .3rem; }
.mv-row-ratio { flex: none; }
.mv-row-route {
  flex: 1; min-width: 0; color: var(--text2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mv-row-tete .mv-badge { flex: none; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.157 — L'EN-TÊTE D'UNE LIGNE RÉCOLTE REDIT SON NOM AU TÉLÉPHONE
   ════════════════════════════════════════════════════════════════════
   ⚠️ MESURÉ, ET C'EST PIRE QUE « ROGNÉ » : à 390 px, le nom d'une ligne
   Récolte repliée tombe à ZÉRO pixel. L'en-tête réclame 457 px pour une
   boîte de 314, et rien ne cède — le titre est en `flex: none`, la boîte
   en `overflow: hidden` sans ellipse. Une ligne Exploration, qui n'a pas
   la pastille de ratio, garde ses 66 px de nom. Le nom complet en
   demande 115.

   Ce que chaque geste rend au nom, mesuré au navigateur :
     pastille complète + deux horloges ....... 0 px   (état d'aujourd'hui)
     icône seule + deux horloges ............ 32 px
     aucune pastille + deux horloges ........ 66 px
     icône seule + compte à rebours court ... 75 px   ← retenu
     aucune pastille + une horloge .......... 108 px

   Arbitrage de Loryan : compte à rebours court + pastille en icône.
   Le compte à rebours court vit dans `movements.js` (interrupteur
   `compteReboursCourt`, partagé avec le radar) ; ici on replie les
   chiffres du ratio et on retire l'heure d'arrivée de l'en-tête.

   ⚠️ RIEN DE TOUT ÇA NE TOUCHE LE GRAND ÉCRAN : les deux règles sont sous
   700 px et sous `body.mobile-mode`, les deux axes du mode mobile. Sur un
   écran large la pastille garde ses chiffres et l'heure reste affichée.
   ⚠️ ET LE DÉTAIL N'EST PAS PERDU : `mvLignesDetail` l'affiche désormais
   dans le dépliant, ce qui est la condition pour pouvoir le replier ici. */
@media (max-width: 700px) {
  .mv-row-tete .mv-ratio-chiffres { display: none; }
  .mv-row-tete .mv-row-eta { display: none; }
}
body.mobile-mode .mv-row-tete .mv-ratio-chiffres { display: none; }
body.mobile-mode .mv-row-tete .mv-row-eta { display: none; }
.mv-row-cdr {
  flex: none; font-family: var(--mono); font-size: .8rem; color: var(--text);
  min-width: 64px; text-align: right;
}
.mv-row-cdr.imminent { color: #fbbf24; }
.mv-row-eta { flex: none; font-family: var(--mono); font-size: .7rem; color: var(--text2); }
/* v0.0.12 : LES DEUX HORLOGES. Une expédition
   annonce désormais ce qui arrive tout de suite ET quand elle sera
   rentrée — un Voyageur disait « 17 min » pour l'étoile suivante, ce qui
   se lisait comme sa destination. Chacune porte son libellé au-dessus de
   son chiffre : sans lui, deux nombres côte à côte ne se distinguent pas. */
.mv-row-horloges { flex: none; display: flex; gap: .9rem; align-items: flex-start; }
.mv-h { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.15; }
.mv-h-lbl {
  font-family: var(--hud); font-size: .55rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text3); white-space: nowrap;
}
.mv-h-val { font-family: var(--mono); font-size: .8rem; color: var(--text); }
.mv-h-val.imminent { color: #fbbf24; }
/* L'échéance de RETOUR est la moins urgente des deux : elle reste lisible
   mais s'efface derrière celle du moment. */
.mv-h-retour .mv-h-val, .mv-h-arrivee .mv-h-val { color: var(--text2); }
/* Le tilde d'une durée ESTIMÉE (le retour du Limier, qui s'allonge tant
   qu'il cherche) — annoncer une estimation comme une certitude serait un
   mensonge, et c'est le seul cas du jeu. */
.mv-h-env { font-family: var(--mono); font-size: .6rem; color: var(--text3); margin-top: -2px; }
/* ─── v0.0.86 : L'HEURE D'HORLOGE SOUS LE COMPTE À REBOURS ────────────
   Signalement de Loryan (« Manque l'heure du retour »). Elle se place SOUS
   le chiffre et non à côté : `.mv-h` est déjà une colonne (libellé /
   valeur), et la mettre à côté élargirait la colonne des horloges — celle
   qui est déjà la plus contrainte de la ligne, au point que ses libellés
   disparaissent sous 900 px.
   ⚠️ Le ton est celui d'un COMPLÉMENT (`--text2`, un cran sous la valeur) :
   c'est le compte à rebours qu'on lit, l'heure sert à se repérer dans sa
   soirée. Deux chiffres du même poids se disputeraient le regard. */
.mv-h-heure {
  font-family: var(--mono); font-size: .62rem; color: var(--text2);
  white-space: nowrap; line-height: 1.2;
}
.mv-h-heure-est { color: var(--text3); }
@media (max-width: 900px) {
  .mv-row-horloges { gap: .5rem; }
  .mv-h-lbl { display: none; }
}
/* ═══ v0.0.13 : LES GRIS ILLISIBLES ═══════
   Remarque de Loryan sur la vue Mouvements : « il faut arrêter avec ce
   gris où on voit rien du tout ». Tout ce qui EXPLIQUE y était peint en
   `--text3` (#2d4070), la couleur la plus sombre de la palette, à deux
   doigts du fond (#0a0f1e) : les libellés au-dessus des deux horloges,
   les intitulés « Retour prévu / Flotte / Soute » du repli, la note du
   bouton de rappel, le compteur d'astres d'un relevé. Autrement dit,
   exactement les mots qui disent au joueur ce qu'il regarde.
   Même correction que la Rampe (v0.0.11) : on monte d'un cran de couleur
   (`--text2`) et, pour les plus petits, d'un cran de taille. */
.mv-h-lbl { color: var(--text2); font-size: .62rem; }
.mv-ligne-lbl { color: var(--text2); }
.mv-actions-note { color: var(--text2); font-size: .74rem; }
.mv-etapes-compte,
.mv-etape-heure { color: var(--text2); }
.mv-h-env { color: var(--text2); }
.mv-row-sous { font-size: .72rem; }

.mv-row-chevron { flex: none; color: var(--text2); font-size: .7rem; transition: transform .15s; }
.mv-row[open] .mv-row-chevron { transform: rotate(180deg); }
/* Le filet de progression, collé au bas de la ligne — c'est lui que le
   tick des 250 ms anime via data-mv-bar, comme la barre des cartes. */
.mv-row-jauge {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: rgba(110,130,170,.15);
}
.mv-row-jauge-fill {
  display: block; height: 100%; background: var(--turquoise, #2dd4bf);
  transition: width .25s linear;
}
.mv-row-jauge-fill.est-retour { background: #60a5fa; }

/* ═══ LES LIGNES JUMELLES, REPLIÉES ══════════════════════════════════
   Constat de Loryan : « quand la file s'allonge et le nombre de VM
   s'allonge, la vue devient difficile à s'y retrouver ». Sur sa capture,
   vingt-deux lignes ne racontent que six situations — sept Limiers partis
   du même vaisseau vers le même système, à cinq secondes d'écart.

   TROIS PARTIS PRIS DE LECTURE.
   1. LE GROUPE RESSEMBLE À UNE LIGNE. Mêmes colonnes, même ordre, même
      hauteur : on ne veut pas apprendre une seconde grammaire pour lire
      un repli. Seuls le « ×7 » et un fond légèrement plus dense le
      distinguent.
   2. LE « ×7 » EST LA DONNÉE, pas une décoration : c'est lui qui dit
      qu'on regarde plusieurs choses. Il est donc coloré et gras, à côté
      du nom.
   3. LES MEMBRES SONT EN RETRAIT, avec un filet vertical à gauche —
      la même écriture que les modules dans la nav (`.sn-sub`) : ce sont
      les mêmes missions vues une par une, pas des lignes de même rang. */
.mv-groupe {
  border: 1px solid rgba(110,130,170,.3); border-left-width: 3px;
  border-radius: 7px; margin-bottom: 5px;
  background: rgba(255,255,255,.045);
}
.mv-groupe.mv-vm    { border-left-color: #c084fc; }
.mv-groupe.mv-fleet { border-left-color: #60a5fa; }
.mv-groupe.mv-exped { border-left-color: #34d399; }
.mv-groupe-tete { font-size: .76rem; }
.mv-groupe-x {
  font-family: var(--mono); font-weight: 700; color: #fbbf24;
  background: rgba(251,191,36,.12); border: 1px solid rgba(251,191,36,.28);
  border-radius: 999px; padding: 0 6px; margin-left: .35rem; font-size: .72rem;
}
/* Le libellé de l'échéance, à gauche du chiffre : sans lui, « 3m58s » sur
   un groupe de sept se lirait comme le temps que met le groupe ENTIER. */
.mv-groupe-cdr-lbl {
  flex: none; display: flex; flex-direction: column; align-items: flex-end;
  line-height: 1.15; font-family: var(--hud); font-size: .55rem;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text2);
  white-space: nowrap;
}
.mv-groupe-premier { color: #fbbf24; font-size: .95em; letter-spacing: .04em; }
/* v0.0.12 : le bout de route qui VARIE d'un membre à l'autre — « 4
   destinations » là où quatre Précurseurs visent quatre systèmes. Il ne
   doit pas se lire comme un nom de lieu : d'où l'italique et le pointillé,
   qui disent « ceci est un décompte, déplie pour voir les noms ». */
.mv-multi {
  font-style: italic; color: var(--text2);
  border-bottom: 1px dotted rgba(110,130,170,.45); cursor: help;
}
/* ⚠️ `> .mv-groupe-tete` ET PAS un descendant libre : les lignes membres
   portent elles aussi un chevron, et une règle large les retournait toutes
   en même temps que celui du groupe — neuf lignes fermées affichaient donc
   un chevron « ouvert ». */
.mv-groupe[open] > .mv-groupe-tete .mv-row-chevron { transform: rotate(180deg); }
.mv-groupe-corps {
  padding: 0 .5rem .5rem 1.4rem; position: relative;
}
/* Le filet vertical qui rattache les membres à leur en-tête. */
.mv-groupe-corps::before {
  content: ''; position: absolute; left: .75rem; top: 0; bottom: .5rem;
  width: 1px; background: rgba(110,130,170,.28);
}
/* Dans un groupe, les membres perdent leur bordure gauche colorée : elle
   ferait doublon avec le filet, et la couleur est déjà portée par
   l'en-tête. */
.mv-groupe-corps .mv-row { border-left-width: 1px; border-left-color: rgba(110,130,170,.18); }
@media (max-width: 900px) {
  .mv-groupe-cdr-lbl { display: none; }
  .mv-groupe-corps { padding-left: .8rem; }
  .mv-groupe-corps::before { left: .35rem; }
}

/* ═══ v0.0.13 : LE VAISSEAU MÈRE, UN ÉTAGE AU-DESSUS DU TYPE ══════════
   Demande de Loryan. Le regroupement par type avait ramené vingt-cinq
   lignes à cinq, mais elles restaient MÉLANGÉES entre deux vaisseaux. Or
   la première question avec plusieurs Vaisseaux Mères n'est pas « qu'est-ce
   qui rentre en premier ? » mais « que fait Icarius ? ».
   L'en-tête ne se dessine PAS comme une ligne : pas de cadre, pas de fond
   plein — juste un nom, un filet et un décompte. Une troisième boîte
   emboîtée aurait donné un escalier de bordures, et c'est précisément
   l'encombrement qu'on combat depuis trois versions. */
.mv-vm-bloc { margin-bottom: 14px; }
.mv-vm-tete {
  display: flex; align-items: baseline; gap: .6rem;
  padding: 2px 2px 5px; margin-bottom: 6px;
  border-bottom: 1px solid rgba(110,130,170,.22);
}
.mv-vm-ico { font-size: .9rem; }
.mv-vm-nom {
  font-family: var(--hud, monospace); font-size: .82rem; font-weight: 700;
  letter-spacing: .04em; color: #c084fc;
}
.mv-vm-compte { font-size: .68rem; color: var(--text2); }
.mv-vm-eta { margin-left: auto; display: flex; align-items: baseline; gap: .4rem; }
.mv-vm-eta-lbl {
  font-family: var(--hud, monospace); font-size: .55rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text2);
}
.mv-vm-eta .mv-row-cdr { min-width: 0; font-size: .76rem; }
@media (max-width: 900px) { .mv-vm-eta-lbl { display: none; } }

/* v0.0.13 : LES ESCALES du Voyageur, posées sur sa
   barre — une par étoile de sa route, à la fraction de voyage où elle
   tombe. Éteinte tant qu'elle est devant, allumée une fois traversée : on
   lit d'un coup d'œil combien de bonds il a faits et combien il en reste.
   Elles débordent volontairement de la barre (2 px de haut) pour rester
   visibles sans l'épaissir. */
.mv-escale {
  position: absolute;
  /* Au-dessus du remplissage, qui la suit dans le DOM et la masquerait
     sinon à moitié une fois dépassée — précisément quand elle compte. */
  z-index: 1;
}

/* ═══ v0.0.19 : LA VIGNETTE DU SOLEIL ET SON NOM ══
   Demande de Loryan : « tu peux mettre la miniature du soleil à la place
   du point ? Et mettre le nom du soleil juste en dessous ? »
   L'image est CELLE DE LA CARTE (même fonction de choix) : la même étoile
   a donc le même visage aux deux endroits.
   La boîte d'une escale occupe SA PART DE BARRE (largeur posée en ligne,
   100 / nombre d'escales) : c'est elle qui borne le nom et l'empêche de
   chevaucher ses voisins quand la route en compte trente.
   Le balisage n'existe que sous le flag — rien à masquer ici. */
.mv-escale-soleil {
  /* La largeur et la marge de recentrage sont posées en ligne : elles
     dépendent du nombre d'escales, que le CSS ne connaît pas. */
  /* ⚠️ SOUS LA BARRE, PAS DESSUS. Un soleil et son nom font trente pixels :
     à cheval sur le rail ils montaient dans le titre de la ligne — mesuré :
     les vignettes se posaient en travers de « Voyageur · 1 vaisseau ».
     Elles descendent dans la réserve ouverte sous la barre (`a-escales`). */
  top: 5px; bottom: auto; height: auto;
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  pointer-events: auto;
}
.mv-escale-astre {
  width: 15px; height: 15px; flex: none;
  background-size: 190%;            /* le cœur du soleil occupe ~55 % de
                                       l'image, le reste est du halo : on
                                       zoome pour que le disque remplisse
                                       la vignette. */
  background-position: center; background-repeat: no-repeat;
  border-radius: 50%;
  /* À VENIR : éteinte. Elle existe, on la voit, mais elle n'est pas encore
     dans l'histoire du voyage — même règle que le point gris d'avant. */
  filter: grayscale(.75) brightness(.55);
  transition: filter .25s, transform .25s;
}
.mv-escale-soleil.est-passee .mv-escale-astre {
  /* TRAVERSÉE : le soleil s'allume, cerclé d'un liseré bleuté (demande de
     Loryan) — la couleur du trajet déjà parcouru, pas celle de la soute. */
  filter: none; transform: scale(1.08);
  box-shadow: 0 0 0 2px rgba(96,165,250,.65), 0 0 9px rgba(96,165,250,.45);
}
.mv-escale-nom {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: .58rem; line-height: 1.1; color: var(--text3);
  letter-spacing: .01em;
}
.mv-escale-soleil.est-passee .mv-escale-nom { color: var(--text2); }
/* ⚠️ LA RÉSERVE DE HAUTEUR NE VAUT QUE POUR LES LIGNES CONCERNÉES.
   Seul le Voyageur a des escales ; ouvrir cet étage sur toutes les lignes
   mettrait deux centimètres de vide sous chaque transport et chaque
   transit. La classe est posée par le rendu, qui sait s'il a produit des
   escales ou non. La barre remonte d'autant : elle reste collée au texte,
   et l'espace libéré passe SOUS elle, là où vivent les soleils. */
/* ⚠️ `.mv-row` EN TÊTE DU SÉLECTEUR, ET C'EST NÉCESSAIRE : le bloc
   `.mv-row-tete` (v0.0.17) repose la marge
   basse de la ligne et vient PLUS BAS dans cette feuille. Sans ce cran de
   priorité, la réserve était écrasée et les soleils remontaient dans le
   titre de la ligne — mesuré : les vignettes se posaient en travers de
   « Voyageur · 1 vaisseau ». */
.mv-row .mv-row-tete.a-escales { padding-bottom: 2.5rem; }
.mv-row .mv-row-tete.a-escales .mv-row-jauge { bottom: 2.3rem; }
@media (max-width: 720px) {
  /* Sur un téléphone, la barre fait quelques centaines de pixels : les
     noms n'y tiennent plus, les soleils si. */
  .mv-escale-nom { display: none; }
  .mv-row .mv-row-tete.a-escales { padding-bottom: 1.4rem; }
  .mv-row .mv-row-tete.a-escales .mv-row-jauge { bottom: 1.2rem; }
}
.mv-row-corps { padding: .5rem .9rem .7rem; border-top: 1px solid rgba(110,130,170,.15); }
@media (max-width: 900px) {
  .mv-row-route { display: none; }   /* la route se lit dans le repli */
  /* ════════════════════════════════════════════════════════════════
     ⚠️ v0.0.113 — ET LE TITRE PREND SA PLACE DE RESSORT
     ════════════════════════════════════════════════════════════════
     Signalement de Loryan : « l'heure se décale à gros zoom ».
     Chrome zoome en RÉDUISANT la largeur CSS : à 200 % sur un écran de
     1500 px, la page fait 750 px et cette media query s'applique. Or la
     route était le SEUL élément élastique de la ligne (`flex: 1`) — celui
     qui poussait le compte à rebours et l'heure contre le bord droit. La
     masquer sans la remplacer laisse tout le bloc de droite retomber vers
     la gauche : mesuré à 200 %, l'heure s'arrêtait à 448 px dans un
     en-tête large de 662 — deux cents pixels de vide derrière elle,
     pendant que le libellé, lui, restait à sa place. D'où l'impression
     que l'heure « se décale » toute seule.
     ⚠️ ON NE REMET PAS LA ROUTE : elle se lit dans le repli, c'est le bon
     choix à cette largeur. C'est le TITRE qui devient élastique — il est
     juste avant, il a une raison légitime d'absorber la place, et son
     `text-overflow` l'empêche de pousser à son tour. */
  body .mv-row-titre {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis;
  }
}

/* ═══ v0.0.17 : LE DÉTAIL EN PLEINE LARGEUR
   Remarque de Loryan, capture à l'appui : « utilise toute la largeur pour
   mettre les infos, grossis la police ». Le repli d'une ligne empilait
   ses cinq intitulés dans une colonne de trois cents pixels et laissait
   les quinze cents suivants entièrement vides — sur un écran large, on
   lisait cinq lignes de petit texte serrées dans le coin gauche.
   Le repli devient donc une GRILLE qui se remplit d'elle-même : autant de
   colonnes que la largeur en autorise, une seule sur un téléphone, sans
   qu'aucun nombre de colonnes ne soit écrit nulle part. Et la police
   monte d'un cran partout — ligne repliée comprise, puisque c'est elle
   qu'on lit le plus souvent. */
.mv-row-corps {
  display: grid;
  /* `min()` évite le débordement horizontal : sur un écran plus étroit que
     420 px, la colonne se rabat sur la largeur disponible au lieu de
     pousser la page. */
  grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
  gap: .1rem 2.4rem;
  padding: .65rem 1.05rem .8rem;
}
/* Ce qui ne se met PAS en colonne : la soute (sa jauge veut de la place),
   le relevé d'astres du Précurseur (c'est une liste, pas une ligne) et la
   rangée de boutons, qui se lit en bas et sur toute la largeur. */
.mv-row-corps > .mv-ligne-soute,
.mv-row-corps > .mv-etapes,
.mv-row-corps > .mv-actions { grid-column: 1 / -1; }

.mv-ligne { font-size: .92rem; padding: .2rem 0; }
.mv-ligne-lbl { min-width: 172px; }
.mv-btn { font-size: .86rem; padding: .28rem .8rem; }
.mv-actions-note { font-size: .82rem; }
/* La ligne REPLIÉE monte aussi : c'est l'état par défaut de la vue, et
   c'est là que se lisent le nom, la route et les deux échéances. */
.mv-row-tete { font-size: .87rem; padding: .5rem .8rem .58rem; }
.mv-row-ico { font-size: 1.1rem; }
.mv-row-sous { font-size: .8rem; }
.mv-row-cdr { font-size: .95rem; min-width: 76px; }
.mv-row-eta { font-size: .82rem; }
.mv-h-val { font-size: .95rem; }
.mv-h-lbl { font-size: .68rem; }
.mv-etapes-titre,
.mv-etape { font-size: .88rem; }

/* ─── LA JAUGE DE SOUTE ──────────────────────────────────────────────
   Demande de Loryan : « ajoute une barre de % de soute rempli qui évolue
   au fil du temps ». Le balisage n'existe que si le serveur a envoyé la
   capacité, donc uniquement sous le flag — ces règles ne peignent rien
   sans lui, et rien à masquer.
   La jauge PREND LA PLACE QUI RESTE (flex: 1) : c'est ce qui la rend
   lisible en pleine largeur, là où le contenu chiffré, lui, garde sa
   taille. */
/* `.mv-ligne` aligne ses enfants sur la LIGNE DE BASE du texte : la barre
   n'en a pas, elle se retrouvait posée trop bas d'un ou deux pixels. Cette
   ligne-là s'aligne donc sur le centre. */
.mv-ligne-soute { align-items: center; }
.mv-soute { display: flex; align-items: center; gap: .75rem; flex: 1; min-width: 0; }
.mv-soute-txt { flex: none; white-space: nowrap; }
.mv-soute-vide { color: var(--text3); font-style: italic; }
.mv-soute-jauge {
  flex: 1; min-width: 60px; height: 8px; border-radius: 999px;
  background: rgba(110,130,170,.18); overflow: hidden;
}
.mv-soute-fill {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, rgba(52,211,153,.45), #34d399);
  /* Une seconde, pas 250 ms comme la barre de progression : la valeur ne
     change qu'au sondage (5 s), donc un glissé long fait un remplissage
     continu à l'œil là où une transition courte ferait un à-coup toutes
     les cinq secondes. */
  transition: width 1s linear;
}
.mv-soute-pct {
  flex: none; font-family: var(--mono); font-variant-numeric: tabular-nums;
  min-width: 46px; text-align: right; color: var(--text2);
}
/* DEUX CAS OÙ LA BARRE N'A PLUS DE PLACE À DROITE DU CHIFFRÉ : le
   téléphone, et la TUILE de la vue en cartes (330 px de large, quelle que
   soit la taille de l'écran — c'est le conteneur qui est étroit, pas la
   fenêtre, donc une media query seule ne l'attrape pas). Dans les deux, le
   contenu passe AU-DESSUS et la barre prend toute la ligne : mesurée à
   60 px, elle ne montrait plus rien. */
.mv-item .mv-soute,
.mv-item .mv-ligne-soute { flex-wrap: wrap; }
.mv-item .mv-soute { gap: .3rem .6rem; }
.mv-item .mv-soute-txt { flex: 1 0 100%; white-space: normal; }
@media (max-width: 720px) {
  .mv-soute { flex-wrap: wrap; gap: .3rem .6rem; }
  .mv-soute-txt { flex: 1 0 100%; white-space: normal; }
}

/* Champs de formulaire */
.rp-champ { margin-bottom: .9rem; }
.rp-label {
  display: block; font-family: var(--hud); font-size: .64rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text3); margin-bottom: .35rem;
}
.rp-select,
.rp-input {
  background: rgba(0,0,0,.35); border: 1px solid var(--border); border-radius: 5px;
  color: var(--text); font-family: var(--mono); font-size: .78rem; padding: .35rem .6rem;
}
.rp-select { width: 100%; max-width: 420px; }
.rp-input-court { width: 84px; text-align: right; }
.rp-cargo,
.rp-flotte { display: flex; flex-direction: column; gap: .3rem; }
.rp-cargo-ligne,
.rp-flotte-ligne {
  display: flex; align-items: center; gap: .6rem; font-size: .76rem;
}
.rp-cargo-nom,
.rp-flotte-nom { flex: 1; min-width: 0; }
.rp-cargo-dispo,
.rp-flotte-dispo { font-family: var(--mono); font-size: .68rem; color: var(--text3); }
.rp-flotte-ico { font-size: .9rem; }
.rp-envoyer { margin-top: .6rem; }

/* v0.0.39 — LES TROIS RARES, MISES À PART DANS LE FORMULAIRE DE TRANSPORT.
   Elles ne partagent pas la soute des trois autres (seul le Convoyeur
   Raréfié les emporte), et la liste doit le DIRE avant le clic — sinon on
   saisit six nombres et on découvre au lancement que trois ne passaient
   pas. Un filet à gauche et un fond très léger suffisent à séparer les
   deux moitiés de la liste sans en faire deux tableaux. */
.rp-cargo-ligne.rp-cargo-rare {
  padding-left: .55rem;
  border-left: 2px solid rgba(126,196,255,.45);
  background: rgba(126,196,255,.05);
  border-radius: 0 4px 4px 0;
}
.rp-note-rares {
  margin-top: .5rem; padding: .45rem .7rem;
  font-size: .7rem; line-height: 1.45; color: var(--text3);
  background: rgba(126,196,255,.06);
  border: 1px solid rgba(126,196,255,.22); border-radius: 6px;
}
.rp-note-rares strong { color: var(--text2); font-family: var(--mono); }

/* Cible d'une agression + notes */
.rp-cible {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .6rem;
  padding: .6rem .8rem; margin-bottom: .8rem;
  background: rgba(233,69,96,.07); border: 1px solid rgba(233,69,96,.3); border-radius: 6px;
}
.rp-cible-label {
  font-family: var(--hud); font-size: .62rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text3);
}
.rp-cible-val { font-size: .84rem; font-weight: 600; }
.rp-cible-coords { font-family: var(--mono); font-size: .7rem; color: var(--text3); margin-left: .4rem; }
.rp-cible-note { flex: 1 1 100%; font-size: .7rem; color: var(--text3); }
.rp-note {
  font-size: .72rem; color: var(--text2); margin-bottom: .8rem;
  padding: .5rem .7rem; background: rgba(255,255,255,.03);
  border-left: 2px solid var(--turquoise, #2dd4bf); border-radius: 0 4px 4px 0;
}
.rp-vide {
  font-size: .78rem; color: var(--text3); font-style: italic;
  padding: .8rem; text-align: center;
}

/* v0.0.71 — le bandeau de CONVALESCENCE de la fiche de planète.
   ⚠️ Il porte l'attribut `hidden` quand il n'y a rien à dire, et la règle
   [hidden] doit donc EXISTER : une classe qui pose `display` sans exception
   pour l'attribut a la même spécificité que la règle native du navigateur, et
   la feuille d'auteur l'emporte — le bandeau resterait affiché, vide. C'est
   exactement le défaut du badge de la cloche corrigé en v2.69. */
.pp-convalescence {
  display: block;
  margin: .5rem 0 .2rem; padding: .55rem .7rem;
  font-size: .76rem; line-height: 1.45;
  color: #fca5a5;
  background: rgba(239,68,68,.10);
  border: 1px solid rgba(239,68,68,.35);
  border-radius: 6px;
}
.pp-convalescence[hidden] { display: none; }

/* v0.0.71 — la note sous le sélecteur de destination : les Vaisseaux Mères
   qu'on ne PEUT PAS viser (autre galaxie). Discrète : c'est une explication,
   pas une alerte — le joueur n'a rien fait de mal. */
.rp-aide {
  font-size: .72rem; color: var(--text3); font-style: italic;
  margin-top: .35rem; line-height: 1.35;
}

/* Le retour du serveur, en clair */
.rp-message {
  padding: .55rem .8rem; border-radius: 6px; font-size: .78rem; margin-bottom: .8rem;
}
.rp-message-ok {
  color: #86efac; background: rgba(34,197,94,.1); border: 1px solid rgba(34,197,94,.35);
}
.rp-message-erreur {
  color: #fca5a5; background: rgba(233,69,96,.1); border: 1px solid rgba(233,69,96,.35);
}

/* Le renvoi vers Mouvements de flotte */
.rp-renvoi {
  font-size: .76rem; color: var(--text3); text-align: center; padding: .8rem 0;
}
.rp-lien {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--turquoise, #2dd4bf); font: inherit; text-decoration: underline;
}

/* Note sous le bouton de rappel, dans Mouvements de flotte */
.mv-actions-note { font-size: .68rem; color: var(--text3); margin-left: .6rem; }

/* ══════════════════════════════════════════════════════════════════
   v2.56 — Illustrations des vaisseaux et des défenses
   ══════════════════════════════════════════════════════════════════
   Les cartes de production affichaient un gros émoji dans leur colonne
   de gauche, faute de planches. Maintenant qu'elles existent, la
   colonne accueille une VIGNETTE au même gabarit (64 px de large), et
   le reste de la carte ne bouge pas d'un pixel.

   Ces règles ne s'appliquent que si le rendu a posé `ship-card--illus`,
   ce qu'il ne fait que sous le flag `visuelsUnites` (cf. illustrationDe
   dans shared/helpers.js) : sans le flag déployé, aucune de ces règles
   ne trouve de cible et l'univers garde exactement l'affichage d'avant. */
.ship-card-art {
  width: 64px; height: 48px; flex-shrink: 0;
  border-radius: 5px;
  border: 1px solid var(--border2, #324569);
  background-size: cover; background-position: center;
  transition: transform 0.3s ease, border-color 0.15s;
}
.ship-card--illus:hover .ship-card-art {
  transform: scale(1.06);
  border-color: var(--accent2, #0ea5e9);
}
/* Une unité verrouillée est déjà atténuée par `.locked` ; on désature en
   plus son illustration, comme le fait la vignette de bâtiment. */
.ship-card-compact.locked .ship-card-art { filter: grayscale(0.85) brightness(0.72); }

/* ══════════════════════════════════════════════════════════════════
   v2.64 — TUTORIEL D'ACCUEIL
   ══════════════════════════════════════════════════════════════════
   Le voile assombrit toute la page ; le « trou » est un rectangle SANS
   fond dont l'ombre portée démesurée peint le voile autour de lui. C'est
   ce qui laisse la zone visée parfaitement NETTE — pas de flou, pas de
   copie du DOM à tenir à jour — et qui suit n'importe quel rectangle.

   Ces classes ne sont posées que par features/tutoriel/tutoriel.js. */
.tuto-voile {
  position: fixed; inset: 0; z-index: 9000;
  background: rgba(4, 8, 18, 0.78);
}
/* Quand une zone est surlignée, le voile lui-même devient TRANSPARENT :
   l'assombrissement vient alors uniquement de l'ombre portée du trou, qui
   s'arrête à ses bords. Sans ça, le voile recouvrait aussi la zone visée
   et on surlignait du gris sur du gris. Pas de backdrop-filter non plus —
   il aurait flouté la cible, c'est-à-dire exactement ce qu'on veut net. */
.tuto-voile.a-trou { background: transparent; }
/* v0.0.8 — tutorielClicsTraversants (bug War_Machine, « le bouton ne
   fonctionne pas »). Transparent ne veut pas dire absent : le voile en mode
   surlignage couvrait toujours l'écran entier et INTERCEPTAIT chaque clic,
   y compris sur la zone qu'il désigne. Le joueur voyait un bouton net,
   entouré d'un halo qui dit « clique ici », et rien ne répondait.
   Dès qu'un trou est percé, le voile laisse donc passer les clics — le jeu
   reste utilisable pendant qu'on est guidé. La fenêtre du tutoriel, elle,
   les reprend explicitement (sinon ses propres boutons deviendraient
   inertes avec lui). SANS trou (bienvenue, panneau des étapes), le voile
   reste bloquant : là, c'est une vraie fenêtre modale.
*/
.tuto-voile.a-trou { pointer-events: none; }
.tuto-voile.a-trou .tuto-carte { pointer-events: auto; }
/* ⚠️ CONSTAT 107 — LES DEUX IDÉES SONT SÉPARÉES, et il fallait le faire.
   `a-trou` disait à la fois « le voile est transparent » et « le voile laisse
   passer les clics ». Dès que le joueur changeait d'écran, la zone visée
   n'existait plus, la classe tombait, et le jeu devenait ENTIÈREMENT inerte —
   y compris le clic qui l'aurait ramené d'où il venait. Rien ne prévenait :
   le voile bloquant se peint de la même teinte que l'ombre du trou.
   `traversant` est posée pendant TOUTE la durée d'une étape guidée, trou ou
   pas. Décision de Loryan : le jeu reste assombri — on doit comprendre qu'une
   étape est en cours — mais les clics passent quand même.
   ⚠️ NI LA BIENVENUE NI LE SOMMAIRE ne la portent : eux restent des fenêtres
   modales, et la retirent explicitement. */
.tuto-voile.traversant { pointer-events: none; }
.tuto-voile.traversant .tuto-carte { pointer-events: auto; }
.tuto-trou {
  position: fixed; z-index: 1;
  border-radius: 10px;
  border: 2px solid var(--accent2, #0ea5e9);
  box-shadow: 0 0 0 9999px rgba(4, 8, 18, 0.78),
              0 0 22px 4px rgba(14, 165, 233, 0.55);
  background: transparent;
  pointer-events: none;                      /* le jeu reste visible dessous */
  transition: left .25s ease, top .25s ease, width .25s ease, height .25s ease;
  animation: tuto-pulse 2.2s ease-in-out infinite;
}
@keyframes tuto-pulse {
  0%, 100% { box-shadow: 0 0 0 9999px rgba(4,8,18,.78), 0 0 22px 4px rgba(14,165,233,.55); }
  50%      { box-shadow: 0 0 0 9999px rgba(4,8,18,.78), 0 0 30px 8px rgba(14,165,233,.28); }
}

/* v0.0.76 — DEUX ZONES À LA FOIS (`cibleAussi`).
   ⚠️ À deux trous, l'ombre portée ne marche plus : chaque trou assombrit
   tout SAUF lui-même, donc l'ombre du premier recouvre le second — les
   deux zones ressortent PLUS SOMBRES, l'inverse d'un surlignage. Le trou
   devient alors un voile plein écran DÉCOUPÉ (clip-path pair-impair, posé
   par le moteur) : ni bordure ni ombre — un div plein écran garderait
   sinon son liseré au bord de l'ÉCRAN, et son ombre géante annulerait le
   découpage. Les liserés sont redessinés par les deux anneaux ci-dessous.
   La transition est coupée : elle animerait le passage plein écran. */
.tuto-trou.multi {
  border: none;
  box-shadow: none;
  animation: none;
  border-radius: 0;
  background: rgba(4, 8, 18, 0.78);
  transition: none;
}
/* Le liseré d'une zone du mode à deux cibles — le même dessin que le trou
   simple, SANS l'ombre géante (c'est le découpage qui assombrit). */
.tuto-anneau {
  position: fixed; z-index: 1;
  border-radius: 10px;
  border: 2px solid var(--accent2, #0ea5e9);
  box-shadow: 0 0 22px 4px rgba(14, 165, 233, 0.55);
  background: transparent;
  pointer-events: none;
  animation: tuto-anneau-pulse 2.2s ease-in-out infinite;
}
.tuto-anneau[hidden] { display: none; }
@keyframes tuto-anneau-pulse {
  0%, 100% { box-shadow: 0 0 22px 4px rgba(14,165,233,.55); }
  50%      { box-shadow: 0 0 30px 8px rgba(14,165,233,.28); }
}

/* La fenêtre. Centrée par défaut ; le moteur la repositionne à côté de la
   zone surlignée pour ne jamais masquer ce qu'elle désigne. */
.tuto-carte {
  position: fixed; z-index: 2;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, #101a30 0%, #0a1120 100%);
  border: 1px solid var(--border2, #324569);
  border-radius: 12px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, .6);
  overflow: hidden;
}
/* Dès que le moteur fixe left/top, on annule le centrage par transform. */
.tuto-carte[style*="left"] { transform: none; }

.tuto-entete {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px 10px;
  border-bottom: 1px solid var(--border, #1e3060);
}
.tuto-etape-ico { font-size: 1.5rem; line-height: 1; flex-shrink: 0; }
.tuto-titre {
  margin: 0; flex: 1; min-width: 0;
  font-family: var(--hud); font-size: .96rem; font-weight: 600;
  letter-spacing: .03em; color: #eaf1ff;
}
.tuto-fermer {
  background: none; border: none; cursor: pointer;
  color: var(--text3, #64748b); font-size: 1rem; padding: 2px 6px; border-radius: 5px;
}
.tuto-fermer:hover { color: #eaf1ff; background: rgba(255,255,255,.06); }

/* Jauge d'avancement : un trait par étape, rempli quand elle est lue. */
.tuto-jauge { display: flex; gap: 4px; padding: 10px 16px 0; }
.tuto-pas {
  flex: 1; height: 3px; border-radius: 2px;
  background: rgba(120, 140, 175, .22);
}
.tuto-pas.is-fait { background: var(--green, #10b981); }
.tuto-pas.is-ici  { background: var(--accent2, #0ea5e9); box-shadow: 0 0 8px rgba(14,165,233,.7); }

.tuto-corps {
  padding: 12px 16px 4px; overflow-y: auto;
  font-size: .82rem; line-height: 1.62; color: #c3d0e6;
}
.tuto-corps p { margin: 0 0 .7em; }
.tuto-corps b { color: #eaf1ff; }
.tuto-liste { margin: 0 0 .8em; padding-left: 1.15rem; }
.tuto-liste li { margin-bottom: .38em; }
/* La ligne « voici le geste à faire » — c'est elle qu'on relit en diagonale. */
.tuto-action {
  margin-top: .2em; padding: 8px 10px;
  background: rgba(14, 165, 233, .09);
  border-left: 3px solid var(--accent2, #0ea5e9);
  border-radius: 0 6px 6px 0;
  color: #dbe7fb;
}

.tuto-pied {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 16px 14px;
  border-top: 1px solid var(--border, #1e3060);
}
.tuto-pied-actions { display: flex; gap: 8px; align-items: center; }
.tuto-compteur {
  font-family: var(--mono); font-size: .7rem; color: var(--text3, #64748b);
}
.tuto-btn-ok {
  padding: 8px 16px; border-radius: 7px; cursor: pointer;
  background: var(--accent2, #0ea5e9); border: none; color: #04101c;
  font-family: var(--hud); font-size: .78rem; font-weight: 700; letter-spacing: .02em;
}
.tuto-btn-ok:hover { filter: brightness(1.12); }
.tuto-btn-sec {
  padding: 8px 12px; border-radius: 7px; cursor: pointer;
  background: transparent; border: 1px solid var(--border2, #324569);
  color: var(--text2, #94a3b8); font-size: .76rem;
}
.tuto-btn-sec:hover { color: #eaf1ff; border-color: var(--accent2, #0ea5e9); }

/* ── La fenêtre de bienvenue ── */
.tuto-carte--accueil { width: min(660px, calc(100vw - 32px)); }
.tuto-accueil-logo {
  font-size: 2.6rem; text-align: center; padding: 22px 0 2px;
  filter: drop-shadow(0 0 18px rgba(14,165,233,.5));
}
.tuto-carte--accueil .tuto-titre {
  text-align: center; font-size: 1.16rem; padding: 0 16px 4px;
}
.tuto-carte--accueil .tuto-corps { padding: 10px 26px 6px; }
.tuto-choix { display: flex; flex-direction: column; gap: 10px; padding: 8px 26px 24px; }
.tuto-btn {
  display: flex; align-items: center; gap: 12px; text-align: left;
  padding: 13px 15px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--border2, #324569);
  background: rgba(10, 16, 32, .6);
  color: #dbe7fb; font: inherit;
  transition: border-color .15s, background .15s, transform .1s;
}
.tuto-btn:hover { transform: translateY(-1px); }
.tuto-btn-ico { font-size: 1.5rem; flex-shrink: 0; }
.tuto-btn-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tuto-btn-txt b { font-size: .86rem; color: #eaf1ff; }
.tuto-btn-txt small { font-size: .72rem; color: var(--text3, #64748b); }
.tuto-btn--oui {
  border-color: rgba(14, 165, 233, .6);
  background: linear-gradient(180deg, rgba(14,165,233,.16), rgba(14,165,233,.05));
}
.tuto-btn--oui:hover { border-color: var(--accent2, #0ea5e9); background: rgba(14,165,233,.22); }
.tuto-btn--non:hover { border-color: var(--text3, #64748b); background: rgba(255,255,255,.04); }

/* ── Le panneau de relecture (bouton 🎓) ── */
.tuto-carte--panneau { width: min(520px, calc(100vw - 32px)); }
.tuto-panneau-intro { color: var(--text2, #94a3b8); margin-bottom: .9em; }
.tuto-sommaire { list-style: none; margin: 0 0 6px; padding: 0; }
.tuto-sommaire li { margin-bottom: 5px; }
.tuto-sommaire-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 11px; border-radius: 8px; cursor: pointer; text-align: left;
  background: rgba(10, 16, 32, .55);
  border: 1px solid var(--border, #1e3060);
  color: #c3d0e6; font: inherit; font-size: .8rem;
}
.tuto-sommaire-item:hover { border-color: var(--accent2, #0ea5e9); color: #eaf1ff; }
.tuto-sommaire-etat { width: 15px; text-align: center; color: var(--text3, #64748b); }
.tuto-sommaire-item.is-fait .tuto-sommaire-etat { color: var(--green, #10b981); }
.tuto-sommaire-ico { font-size: 1rem; }
.tuto-sommaire-titre { flex: 1; min-width: 0; }

/* v0.0.75 : les deux têtes de chapitre du
   sommaire, et le cadre des schémas dessinés dans une étape. Ces classes
   ne sont posées par aucun HTML hors interrupteur : inertes ailleurs. */
.tuto-sommaire-chapitre {
  margin: 10px 0 6px; padding: 0 2px;
  font-size: .68rem; font-weight: 700; letter-spacing: .8px;
  text-transform: uppercase; color: var(--text3, #64748b);
}
.tuto-sommaire li.tuto-sommaire-chapitre:first-child { margin-top: 2px; }
.tuto-schema { margin: 10px 0; padding: 8px 6px; border-radius: 10px;
  background: rgba(10, 16, 32, .55); border: 1px solid var(--border, #1e3060); }
.tuto-schema svg { display: block; width: 100%; max-width: 360px;
  height: auto; margin: 0 auto; }

.tuto-bouton-ico { font-size: 1.05rem; line-height: 1; }

@media (max-width: 720px) {
  .tuto-carte, .tuto-carte--accueil, .tuto-carte--panneau {
    width: calc(100vw - 20px);
    left: 10px !important; right: 10px;
    top: 50% !important; transform: translateY(-50%) !important;
  }
  .tuto-carte--accueil .tuto-corps, .tuto-choix { padding-left: 16px; padding-right: 16px; }
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur voieTutoriel — LA FENÊTRE-GUIDE DES VOIES
   ════════════════════════════════════════════════════════════════════
   Posée par features/voie/voie-guide.js, qui ne la crée que sous
   l'interrupteur : sans lui, aucune de ces classes n'existe dans la page.
   ⚠️ ELLE PASSE AU-DESSUS DU VOILE DU TUTORIEL (z-index 9000) : pendant un
   « Me montrer où », c'est elle qui parle — elle doit rester nette et
   cliquable pendant que le reste s'assombrit. */
.tuto-carte--cachee { display: none; }
.vguide {
  position: fixed; z-index: 9100; right: 14px; top: 96px;
  width: min(370px, calc(100vw - 28px));
  max-height: calc(100vh - 120px);
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, #101a30 0%, #0a1120 100%);
  border: 1px solid var(--border2, #324569);
  border-radius: 12px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .55);
  overflow: hidden;
}
.vguide[hidden] { display: none; }
.vguide.vguide--gauche { right: auto; left: 14px; }
.vguide.vguide--glisse { opacity: .92; transition: none; }
.vguide-entete {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px 9px 12px;
  border-bottom: 1px solid var(--border, #1e3060);
  cursor: grab; user-select: none; touch-action: none;
}
.vguide.vguide--glisse .vguide-entete { cursor: grabbing; }
.vguide-ico { font-size: 1.05rem; line-height: 1; }
.vguide-sur {
  flex: 1; min-width: 0;
  font-family: var(--hud); font-size: .8rem; font-weight: 600; letter-spacing: .03em;
  color: #eaf1ff; overflow-wrap: anywhere;
}
.vguide-btn {
  position: relative; flex-shrink: 0; min-width: 30px; height: 28px;
  background: rgba(255, 255, 255, .04); border: 1px solid var(--border, #1e3060);
  border-radius: 7px; color: #c3d0e6; font-size: .9rem; cursor: pointer; overflow: hidden;
}
.vguide-btn:hover { color: #eaf1ff; border-color: var(--accent2, #0ea5e9); }
/* La jauge du maintien : elle monte de bas en haut pendant les trois
   secondes, et retombe si on lâche avant. */
.vguide-reduire-jauge {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: calc(var(--vg-maintien, 0) * 100%);
  background: rgba(14, 165, 233, .45);
  pointer-events: none;
}
.vguide-reduire > span:last-child { position: relative; }
.vguide-corps {
  padding: 12px 14px 12px; overflow-y: auto;
  font-size: .86rem; line-height: 1.55; color: #c3d0e6;
}
.vguide-titre { font-family: var(--hud); font-size: .95rem; font-weight: 600; color: #eaf1ff; margin-bottom: 6px; }
.vguide-etiquette {
  display: inline-block; margin-right: 4px; padding: 1px 6px; border-radius: 5px;
  font-size: .79rem; font-weight: 700; letter-spacing: .03em;
  background: rgba(14, 165, 233, .12); color: #7dd3fc;
}
.vguide-faire { color: #eaf1ff; }
.vguide-barre { height: 4px; margin: 9px 0 4px; border-radius: 3px; background: rgba(120, 140, 175, .22); overflow: hidden; }
.vguide-barre > span { display: block; height: 100%; background: var(--accent2, #0ea5e9); }
.vguide-avance { font-family: var(--mono); font-size: .79rem; color: var(--text3, #94a3b8); }
.vguide-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 4px; }
.vguide-principal, .vguide-sec {
  flex: 1 1 auto; padding: 8px 12px; border-radius: 8px; cursor: pointer;
  font: inherit; font-size: .86rem; font-weight: 600;
}
.vguide-principal { background: rgba(14, 165, 233, .18); color: #e0f2fe; border: 1px solid var(--accent2, #0ea5e9); }
.vguide-principal:hover { background: rgba(14, 165, 233, .3); }
.vguide-sec { background: rgba(255, 255, 255, .04); color: #c3d0e6; border: 1px solid var(--border2, #324569); }
.vguide-sec:hover { color: #eaf1ff; }
#vguide-encaisser { background: rgba(245, 158, 11, .16); color: #fcd34d; border-color: rgba(245, 158, 11, .7); }
.vguide-lecon { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border, #1e3060); }
.vguide-lecon-titre { font-weight: 700; color: #eaf1ff; margin-bottom: 4px; }
.tuto-corps.vguide-lecon-corps { padding: 0; overflow: visible; font-size: .86rem; }
.vguide-gains { margin-top: 12px; }
.vguide-pied {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border, #1e3060);
}
.vguide-lien { background: none; border: none; padding: 0; cursor: pointer; font: inherit; font-size: .86rem; color: #7dd3fc; }
.vguide-lien:hover { text-decoration: underline; }
.vguide-astuce { font-size: .79rem; color: var(--text3, #94a3b8); }
.vguide-vide { margin: 4px 0; }
/* Le bouton 🎓 s'allume un instant quand le guide s'y range. */
#tuto-bouton.vguide-appel { animation: vguide-appel .65s ease-in-out 4; }
@keyframes vguide-appel {
  0%, 100% { box-shadow: 0 0 0 0 rgba(14, 165, 233, 0); }
  50%      { box-shadow: 0 0 0 5px rgba(14, 165, 233, .55); }
}
/* Au téléphone : un bandeau en bas de l'écran, pleine largeur. Pendant un
   « Me montrer où », il se réduit à son titre pour ne pas cacher ce qu'on
   désigne. */
.vguide.vguide--mobile {
  left: 0; right: 0; top: auto; bottom: 0; width: auto;
  max-height: 46vh; border-radius: 12px 12px 0 0;
}
.vguide.vguide--mobile .vguide-entete { cursor: default; }
.vguide.vguide--mobile #vguide-cote { display: none; }
.vguide.vguide--discret .vguide-corps { display: none; }

/* ── Interrupteur guideCentral ───────────────────────────────────────
   « Le texte doit être plus gros », « une pop-up centrale plus grosse »,
   « rendre la fenêtre plus visible encore ». Tout ce bloc ne vaut que sous
   `.vguide--v2`, posée par voie-guide.js quand l'interrupteur est allumé :
   éteint, la fenêtre d'au-dessus reste telle quelle.
   ⚠️ GROSSIR ICI, PAS AU-DESSUS : les règles d'origine servent encore aux
   univers où l'interrupteur est éteint. */
#vguide-ranger, #vguide-centrer { display: none; }
.vguide.vguide--v2.vguide--centre #vguide-ranger { display: inline-flex; }
.vguide.vguide--v2:not(.vguide--centre) #vguide-centrer { display: inline-block; }
.vguide.vguide--v2.vguide--centre #vguide-cote { display: none; }
/* Plus visible : bordure lumineuse, halo, bandeau de titre teinté. */
.vguide.vguide--v2 {
  width: min(440px, calc(100vw - 28px));
  background: linear-gradient(180deg, #13233f 0%, #0b1527 100%);
  border: 2px solid rgba(56, 189, 248, .85);
  box-shadow: 0 0 0 1px rgba(14, 165, 233, .3), 0 0 34px rgba(14, 165, 233, .42),
              0 24px 60px rgba(0, 0, 0, .6);
}
.vguide.vguide--v2.vguide--appel { animation: vguide-halo 1s ease-in-out 3; }
@keyframes vguide-halo {
  50% { box-shadow: 0 0 0 4px rgba(56, 189, 248, .55), 0 0 64px rgba(56, 189, 248, .75),
                    0 24px 60px rgba(0, 0, 0, .6); }
}
.vguide--v2 .vguide-entete {
  padding: 11px 12px 11px 14px; gap: 10px;
  background: linear-gradient(90deg, rgba(14, 165, 233, .34) 0%, rgba(14, 165, 233, .08) 100%);
  border-bottom-color: rgba(56, 189, 248, .5);
}
.vguide--v2 .vguide-ico { font-size: 1.4rem; }
.vguide--v2 .vguide-sur { font-size: 1rem; color: #fff; }
.vguide--v2 .vguide-btn { height: 34px; min-width: 36px; font-size: 1.05rem; color: #e0f2fe; }
.vguide--v2 #vguide-ranger {
  align-items: center; gap: 6px; padding: 0 14px; height: 36px;
  font: inherit; font-size: 1rem; font-weight: 700;
  background: rgba(14, 165, 233, .3); border-color: #38bdf8; color: #fff;
}
.vguide--v2 #vguide-ranger:hover { background: rgba(14, 165, 233, .45); }
/* Le texte, grossi. */
.vguide--v2 .vguide-corps { padding: 14px 18px; font-size: 1.07rem; line-height: 1.6; color: #d7e2f3; }
.vguide--v2 .tuto-corps.vguide-lecon-corps { font-size: 1.07rem; }
.vguide--v2 .vguide-titre { font-size: 1.25rem; margin-bottom: 8px; }
.vguide--v2 .vguide-lecon { margin-top: 16px; padding-top: 14px; }
/* L'accueil commence par une leçon : pas de filet ni de vide au-dessus. */
.vguide--v2 .vguide-corps > .vguide-lecon:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.vguide--v2 .vguide-lecon-titre { font-size: 1.14rem; margin-bottom: 6px; }
.vguide--v2 .vguide-etiquette, .vguide--v2 .vguide-avance, .vguide--v2 .vguide-astuce { font-size: .93rem; }
.vguide--v2 .vguide-barre { height: 6px; margin: 11px 0 5px; }
.vguide--v2 .vguide-principal, .vguide--v2 .vguide-sec { padding: 11px 14px; font-size: 1.07rem; }
.vguide--v2 .vguide-lien { font-size: 1.07rem; }
/* La grande fenêtre du milieu. */
.vguide.vguide--v2.vguide--centre {
  left: 50%; top: 50%; right: auto; bottom: auto;
  transform: translate(-50%, -50%);
  width: min(820px, calc(100vw - 48px));
  max-height: min(88vh, 940px);
  border-radius: 16px;
}
.vguide.vguide--v2.vguide--centre.vguide--glisse { transform: none; }
.vguide--v2.vguide--centre .vguide-sur { font-size: 1.1rem; }
.vguide--v2.vguide--centre .vguide-corps { padding: 20px 30px 22px; font-size: 1.18rem; }
.vguide--v2.vguide--centre .tuto-corps.vguide-lecon-corps { font-size: 1.18rem; }
.vguide--v2.vguide--centre .vguide-titre { font-size: 1.5rem; }
.vguide--v2.vguide--centre .vguide-lecon-titre { font-size: 1.25rem; }
/* Le fond assombri derrière elle : un clic dessus la range sur le côté. */
.vguide-fond {
  position: fixed; inset: 0; z-index: 9090; cursor: pointer;
  background: rgba(2, 6, 16, .62); backdrop-filter: blur(2px);
}
.vguide-fond[hidden] { display: none; }
/* « Me montrer où » depuis le centre : elle s'efface, puis revient. */
.vguide.vguide--efface { visibility: hidden; opacity: 0; pointer-events: none; }
/* Au téléphone : grande carte au milieu, ou tiroir en bas une fois rangée. */
.vguide.vguide--v2.vguide--mobile { width: auto; max-height: 52vh; border-width: 2px 0 0; }
.vguide.vguide--v2.vguide--mobile.vguide--centre {
  width: calc(100vw - 20px); max-height: 86vh; border-width: 2px; border-radius: 14px;
}
.vguide--v2.vguide--mobile .vguide-corps,
.vguide--v2.vguide--mobile.vguide--centre .vguide-corps { padding: 14px 16px; font-size: 1.07rem; }
.vguide--v2.vguide--mobile .tuto-corps.vguide-lecon-corps { font-size: 1.07rem; }
.vguide--v2.vguide--mobile.vguide--centre .vguide-titre { font-size: 1.3rem; }

/* ── Interrupteur guideMobileIcone ───────────────────────────────────
   Au téléphone, la fenêtre rangée n'est plus un tiroir de la moitié de
   l'écran : une icône 🎓 ronde en bas à droite, posée AU-DESSUS de la fusée
   (#vmp-mobile-toggle : 48 px à 1 rem du bord) pour ne pas la recouvrir. */
.vguide.vguide--icone { display: none !important; }
.vguide-icone {
  position: fixed; z-index: 9100;
  right: 1rem; bottom: calc(1rem + 48px + 12px + env(safe-area-inset-bottom, 0px));
  width: 52px; height: 52px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.5rem; line-height: 1; cursor: pointer; color: #fff;
  background: #0f2340; border: 2px solid #38bdf8;
  box-shadow: 0 0 18px rgba(56, 189, 248, .55), 0 6px 16px rgba(0, 0, 0, .5);
}
.vguide-icone[hidden] { display: none; }
.vguide-icone.vguide-icone--appel { animation: vguide-icone-appel 1s ease-in-out 3; }
@keyframes vguide-icone-appel {
  50% { box-shadow: 0 0 0 5px rgba(56, 189, 248, .5), 0 0 30px rgba(56, 189, 248, .85), 0 6px 16px rgba(0, 0, 0, .5); }
}

/* ── Interrupteur guideIconeBureau ───────────────────────────────────
   « Ajoute le bouton réduire pour que ce soit réduit à l'état de petit icône
   comme sur la version mobile » : sur ordinateur, le bouton « — » de l'en-tête
   (dans les deux états de la grande fenêtre) réduit en l'icône ci-dessus.
   ⚠️ PAS AU TÉLÉPHONE : « ⇲ Ranger » y fait déjà ce geste. */
#vguide-iconiser { display: none; }
body.f-guideIconeBureau .vguide.vguide--v2:not(.vguide--mobile) #vguide-iconiser { display: inline-block; font-weight: 700; }
/* Sur ordinateur, l'icône n'a pas de fusée à éviter : au coin bas-droit. */
.vguide-icone.vguide-icone--bureau { right: 1rem; bottom: 1rem; }

/* ── Interrupteur comptesFactices : les comptes de test du bac à sable ──
   Le bouton rond 🎭 qui change de compte, posé AU-DESSUS de l'icône 🎓 du
   guide (52 px, à 1 rem du bord ; au téléphone, elle-même au-dessus de la
   fusée de 48 px) : les trois s'empilent sans se recouvrir. La pastille à
   sa gauche dit quel compte de test on joue. Le fichier
   features/admin/comptes-factices.js ne le montre que dans un bac à sable. */
.fx-bouton {
  position: fixed; z-index: 9100;
  right: 1rem; bottom: calc(1rem + 52px + 12px + env(safe-area-inset-bottom, 0px));
  width: 52px; height: 52px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.5rem; line-height: 1; cursor: pointer; color: #fff;
  background: #24123d; border: 2px solid #c084fc;
  box-shadow: 0 0 18px rgba(192, 132, 252, .5), 0 6px 16px rgba(0, 0, 0, .5);
}
.fx-bouton[hidden] { display: none; }
/* Le Panneau des Testeurs ouvert porte déjà la section : le bouton s'efface, sans
   quoi il recouvrirait ses boutons au téléphone. */
body:has(#admin-modal.visible) .fx-bouton, body:has(#admin-modal.visible) .fx-liste { display: none; }
.fx-bouton.fx-bouton--factice { border-color: #fb923c; box-shadow: 0 0 18px rgba(251, 146, 60, .6), 0 6px 16px rgba(0, 0, 0, .5); }
.fx-qui {
  position: absolute; right: calc(100% + 8px); top: 50%; transform: translateY(-50%);
  white-space: nowrap; font-size: .86rem; font-weight: 700; padding: 4px 10px; border-radius: 999px;
  background: rgba(15, 23, 42, .94); border: 1px solid #fb923c; color: #fed7aa; pointer-events: none;
}
.fx-qui[hidden] { display: none; }
.fx-liste {
  position: fixed; z-index: 9101;
  right: 1rem; bottom: calc(1rem + 52px + 12px + 52px + 10px + env(safe-area-inset-bottom, 0px));
  width: min(330px, calc(100vw - 2rem)); max-height: 60vh; overflow-y: auto;
  background: #120a22; border: 1px solid #7c3aed; border-radius: 14px; padding: 8px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .6);
}
.fx-liste[hidden] { display: none; }
.fx-liste-titre { font-size: .9rem; font-weight: 700; color: #e9d5ff; padding: 4px 6px 8px; }
.fx-liste-vide, .fx-liste-statut { font-size: .86rem; color: #c4b5fd; padding: 4px 6px; }
.fx-liste-statut:empty { display: none; }
.fx-ligne {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: 10px; padding: 7px 8px; color: #f3e8ff;
}
.fx-ligne:hover { background: rgba(124, 58, 237, .18); }
.fx-ligne.est-actif { border-color: #fb923c; background: rgba(251, 146, 60, .1); cursor: default; }
.fx-av {
  flex: 0 0 34px; height: 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: #2e1065; font-weight: 700; font-size: 1rem;
}
.fx-nom { flex: 1; min-width: 0; font-size: .95rem; font-weight: 600; overflow-wrap: anywhere; }
.fx-nom small { display: block; font-size: .82rem; font-weight: 400; color: #c4b5fd; }
.fx-ici { font-size: .8rem; font-weight: 700; color: #fb923c; text-transform: uppercase; }
.fx-liste-pied { display: block; width: 100%; margin-top: 6px; padding: 8px; font-size: .86rem; color: #c4b5fd; text-align: center;
  background: transparent; border: 0; border-top: 1px solid rgba(124, 58, 237, .4); }
.fx-gerer { cursor: pointer; color: #e9d5ff; font-weight: 600; }
/* Au téléphone, la fusée des Vaisseaux Mères occupe le coin : tout monte d'un cran. */
@media (max-width: 700px) {
  .fx-bouton { bottom: calc(1rem + 48px + 12px + 52px + 12px + env(safe-area-inset-bottom, 0px)); }
  .fx-liste { bottom: calc(1rem + 48px + 12px + 52px + 12px + 52px + 10px + env(safe-area-inset-bottom, 0px)); }
}
body.mobile-mode .fx-bouton { bottom: calc(1rem + 48px + 12px + 52px + 12px + env(safe-area-inset-bottom, 0px)); }
body.mobile-mode .fx-liste { bottom: calc(1rem + 48px + 12px + 52px + 12px + 52px + 10px + env(safe-area-inset-bottom, 0px)); }

/* La section « 🎭 Comptes de test » du Panneau des Testeurs. */
#adm-comptes-factices .adm-sect-desc { font-size: .82rem; }
#adm-comptes-factices .adm-sect-title { font-size: .86rem; }
#adm-comptes-factices .adm-btn { font-size: .82rem; }
.fx-etape { font-size: .86rem; font-weight: 700; color: #e9d5ff; margin: .7rem 0 .35rem; }
.fx-modeles { display: grid; grid-template-columns: repeat(auto-fill, minmax(165px, 1fr)); gap: 8px; }
.fx-modele, .fx-ou-choix {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px; text-align: left; cursor: pointer;
  background: rgba(46, 16, 101, .35); border: 1px solid rgba(124, 58, 237, .45); border-radius: 10px;
  padding: 8px 10px; color: #f3e8ff; font-size: .9rem;
}
.fx-modele.est-choisi, .fx-ou-choix.est-choisi, .fx-puce.est-choisi { border-color: #fb923c; background: rgba(251, 146, 60, .14); }
.fx-modele-ico { font-size: 1.4rem; line-height: 1; }
.fx-modele-cl { font-size: .9rem; }
.fx-modele small, .fx-ou-choix small { font-size: .8rem; color: #c4b5fd; line-height: 1.3; }
.fx-reglages { display: flex; flex-wrap: wrap; gap: 8px 16px; font-size: .86rem; color: #e9d5ff; }
.fx-reglages label { display: flex; align-items: center; gap: 6px; }
.fx-reglages input, .fx-reglages select, .fx-systeme {
  font-size: .9rem; background: #0b0617; color: #f3e8ff; border: 1px solid #7c3aed; border-radius: 6px; padding: 4px 6px;
}
.fx-reglages input { width: 4.2em; }
.fx-tailles { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.fx-puce { cursor: pointer; font-size: .88rem; color: #f3e8ff; background: rgba(46, 16, 101, .35);
  border: 1px solid rgba(124, 58, 237, .45); border-radius: 999px; padding: 4px 12px; }
.fx-puce small { color: #c4b5fd; font-size: .8rem; }
.fx-ou { display: grid; grid-template-columns: repeat(auto-fill, minmax(165px, 1fr)); gap: 8px; }
.fx-ou-choix > span { font-size: 1.2rem; line-height: 1; }
.fx-systeme { margin-top: 8px; width: 8em; }
.fx-systeme[hidden] { display: none; }
.fx-statut { font-size: .86rem; color: #fde68a; margin: .4rem 0; }
.fx-statut:empty { display: none; }
.fx-comptes { display: flex; flex-direction: column; gap: 6px; }
.fx-compte { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 10px;
  background: rgba(46, 16, 101, .3); border: 1px solid rgba(124, 58, 237, .35); }
.fx-compte-id { flex: 1; min-width: 0; font-size: .92rem; color: #f3e8ff; }
.fx-compte-id small { display: block; font-size: .8rem; color: #c4b5fd; overflow-wrap: anywhere; }
/* Interrupteur profilTestUnique : la fiche du profil unique — une ligne par
   rubrique, ses quantités en puces qui passent à la ligne (jamais de
   space-between sur une rangée repliable). */
.fx-profil { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; border-radius: 10px;
  background: rgba(46, 16, 101, .35); border: 1px solid rgba(124, 58, 237, .45); }
.fx-pf-ligne { display: flex; flex-direction: column; gap: 4px; font-size: .86rem; color: #f3e8ff; }
.fx-pf-ligne > b small { font-weight: 400; color: #c4b5fd; font-size: .8rem; margin-left: 4px; }
.fx-pf-puces { display: flex; flex-wrap: wrap; gap: 4px 6px; }
.fx-pf-puce { font-size: .8rem; color: #e9d5ff; background: rgba(11, 6, 23, .55); border: 1px solid rgba(124, 58, 237, .35);
  border-radius: 999px; padding: 2px 8px; white-space: nowrap; }
.fx-pf-puce b { color: #fde68a; }
.fx-pf-texte { font-size: .8rem; color: #c4b5fd; line-height: 1.4; }
.fx-reglages #fx-nom { width: 11em; }

/* ── Interrupteur guidePrerequis ─────────────────────────────────────
   « 🔒 D'abord : Cartographie Stellaire niveau 1 — le Scanner Galactique
   l'exige » : la même teinte que les cadenas des vignettes (ambre). */
.vguide-avant {
  margin: 8px 0 2px; padding: 7px 10px; border-radius: 8px;
  background: rgba(245, 158, 11, .12); border-left: 3px solid #f59e0b;
  color: #fde68a; font-size: .86rem; line-height: 1.45;
}
.vguide-avant b { color: #fff; }
.vguide--v2 .vguide-avant { font-size: 1rem; }
.voie-detail-lecon + .vguide-avant, .voie-detail-sec + .vguide-avant { margin-top: 4px; }

/* ── Interrupteur guideParcours ──────────────────────────────────────
   « Surligner dans la barre latérale la section », et « Me montrer où » en
   plusieurs clics. Rien ici ne sert tant que voie-guide.js ne pose pas
   `.vguide-marque` ni la bulle — ce qu'il ne fait que sous l'interrupteur. */
/* L'entrée de l'étape en cours (et le bouton du Vaisseau Mère) : un liseré,
   un fond, et une pastille 📍 qui respire. ⚠️ ::after, pas ::before — la
   sous-liste des modules (.sn-sub) se sert déjà de ::before pour son filet. */
.sn-item.vguide-marque {
  background: rgba(14, 165, 233, .18); color: #e0f2fe;
  border-left-color: #38bdf8; font-weight: 600;
  animation: vguide-marque 2.4s ease-in-out infinite;
}
.sn-item.vguide-marque::after {
  content: '📍'; margin-left: auto; padding-left: .4rem; font-size: .95rem; line-height: 1;
}
.dsh-tab.vguide-marque { box-shadow: 0 0 0 2px rgba(56, 189, 248, .8), 0 0 14px rgba(56, 189, 248, .55); }
@keyframes vguide-marque {
  0%, 100% { box-shadow: inset 0 0 0 0 rgba(56, 189, 248, 0); }
  50%      { box-shadow: inset 0 0 0 1px rgba(56, 189, 248, .7), 0 0 12px rgba(56, 189, 248, .35); }
}
/* La bulle posée contre la halte du parcours : au-dessus du voile ET de la
   fenêtre-guide, pour que rien ne la cache. */
.vguide-bulle {
  position: fixed; z-index: 9150; max-width: min(320px, calc(100vw - 16px));
  display: flex; align-items: center; gap: 10px;
  padding: 10px 10px 10px 12px; border-radius: 10px;
  background: #0f2340; border: 2px solid #38bdf8; color: #e0f2fe;
  box-shadow: 0 0 24px rgba(56, 189, 248, .5), 0 10px 30px rgba(0, 0, 0, .55);
  font-size: 1.07rem; line-height: 1.35;
  animation: vguide-bulle 1.2s ease-in-out infinite;
  /* Traversable : posée contre la halte, elle peut couvrir une voisine (la
     bande des sections au téléphone) — seul ✕ l'arrête. */
  pointer-events: none;
}
.vguide-bulle[hidden] { display: none; }
.vguide-bulle .vguide-bulle-x { pointer-events: auto; }
/* Interrupteur guideDetour : « Suivant → » quitte le détour par l'écran de
   la leçon sans chercher où cliquer. */
.vguide-bulle .vguide-bulle-suivant {
  pointer-events: auto; flex-shrink: 0; cursor: pointer;
  padding: 6px 10px; border-radius: 7px; font: inherit; font-size: .93rem; font-weight: 700;
  background: rgba(14, 165, 233, .3); border: 1px solid #38bdf8; color: #fff;
}
.vguide-bulle .vguide-bulle-suivant[hidden] { display: none; }
.vguide-bulle .vguide-bulle-suivant:hover { background: rgba(14, 165, 233, .45); }
.vguide-bulle b { color: #fff; }
.vguide-bulle-rang {
  flex-shrink: 0; padding: 2px 7px; border-radius: 6px;
  background: rgba(56, 189, 248, .25); color: #7dd3fc;
  font-family: var(--mono); font-size: .93rem; font-weight: 700;
}
.vguide-bulle-txt { flex: 1; min-width: 0; }
.vguide-bulle-x {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: 7px; cursor: pointer;
  background: rgba(255, 255, 255, .06); border: 1px solid var(--border2, #324569);
  color: #c3d0e6; font-size: 1rem;
}
.vguide-bulle-x:hover { color: #fff; border-color: #38bdf8; }
@keyframes vguide-bulle { 50% { box-shadow: 0 0 36px rgba(56, 189, 248, .8), 0 10px 30px rgba(0, 0, 0, .55); } }

/* Dans la fiche d'un palier (écran des Voies) : sa leçon, à relire. */
.voie-montrer {
  width: 100%; margin: 4px 0 8px; padding: 8px; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 600;
  background: rgba(14, 165, 233, .14); color: #e0f2fe;
  border: 1px solid rgba(14, 165, 233, .6); border-radius: var(--radius-md);
}
.voie-montrer:hover { background: rgba(14, 165, 233, .26); }
.voie-detail-lecon .vguide-lecon:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.voie-detail-lecon .tuto-corps.vguide-lecon-corps { font-size: 12.5px; color: var(--color-text-primary); }

/* ════════════════════════════════════════════════════════════════════
   v2.65 — CENTRE D'INFORMATION (flag centreInformation)
   ════════════════════════════════════════════════════════════════════
   La liste des rapports. Le vocabulaire visuel est celui des rapports de
   la Rampe de Lancement (.fl-spy-*, v2.40) : accordéon, tête cliquable,
   détail dépliable. On ne réutilise pas ces classes telles quelles — la
   Rampe met ses rapports dans une section étroite, le Centre leur donne
   toute la largeur d'un onglet, et les deux n'ont pas les mêmes
   contraintes de repli.

   Tout est préfixé `ci-` et vit sous l'onglet. */

.ci-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: var(--space-2, 8px);
}
.ci-bar-actions { display: flex; gap: 8px; flex-wrap: wrap; }
/* Interrupteur centreInfoSansIntro : Loryan a barré les deux textes
   d'explication (sous le titre, et sous « Rapports »). */
body.f-centreInfoSansIntro .ci-texte-intro { display: none; }

.ci-mini {
  background: transparent; border: 1px solid var(--border2, #1e3060);
  color: var(--text2, #6b8ab0); font: inherit; font-size: .75rem;
  padding: 6px 10px; border-radius: 6px; cursor: pointer;
  transition: border-color .15s, color .15s;
}
.ci-mini:hover { border-color: var(--accent2, #0ea5e9); color: var(--text, #c8d6f0); }
.ci-mini-danger:hover { border-color: var(--accent1, #e94560); color: #ffb3c0; }

/* ─── Les filtres par nature ───────────────────────────────────────
   Dix natures + « Tous » : la barre défile horizontalement plutôt que de
   passer à la ligne sur mobile, où onze puces empilées auraient poussé la
   liste hors de l'écran. */
.ci-filtres {
  display: flex; gap: 6px; overflow-x: auto; padding-bottom: 6px;
  margin-bottom: var(--space-3, 12px);
  scrollbar-width: thin;
}
.ci-filtre {
  flex: 0 0 auto;
  background: rgba(10, 16, 32, .55); border: 1px solid var(--border, #162040);
  color: var(--text2, #6b8ab0); font: inherit; font-size: .74rem;
  padding: 6px 11px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 6px;
  transition: border-color .15s, color .15s, background .15s;
}
.ci-filtre:hover { border-color: var(--border2, #1e3060); color: var(--text, #c8d6f0); }
.ci-filtre-ico { font-size: .9rem; line-height: 1; }
/* v0.0.136 : le compteur de non-lus de la pastille, dans la teinte de sa
   nature ; un raid ou un espionnage SUBI le passe en rouge battant —
   c'est la question « ai-je été attaqué ? ». */
.ci-filtre-nb {
  display: inline-block; min-width: 18px; padding: 1px 6px; border-radius: 999px;
  background: color-mix(in srgb, var(--ci-teinte, #0ea5e9) 80%, #000);
  color: #fff; font-size: .68rem; font-weight: 700; line-height: 1.4; text-align: center;
}
.ci-filtre-nb[hidden] { display: none; }
.ci-filtre-nb.is-subi {
  background: #ef4444;
  animation: alerte-pastille-pulse .9s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) { .ci-filtre-nb.is-subi { animation: none; } }

/* ─── La liste ──────────────────────────────────────────────────── */
.ci-liste {
  border: 1px solid var(--border, #162040); border-radius: 8px;
  background: var(--card, #0d1526); overflow: hidden;
}
.ci-empty {
  padding: 18px 16px; color: var(--text2, #6b8ab0); font-size: .82rem;
  line-height: 1.5; text-align: center;
}
.ci-item { border-bottom: 1px solid var(--border, #162040); }
.ci-item.is-open { background: rgba(14, 165, 233, .04); }

.ci-head {
  width: 100%; display: flex; align-items: center; gap: 10px;
  padding: 11px 13px; background: transparent; border: 0;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
  position: relative;
}
.ci-item.is-unread .ci-titre { color: #eaf1ff; font-weight: 600; }

.ci-ico {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center; font-size: 1rem;
}
.ci-head-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ci-titre {
  color: var(--text, #c8d6f0); font-size: .84rem; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ci-resume {
  color: var(--text2, #6b8ab0); font-size: .74rem; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ci-meta {
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  color: var(--text3, #2d4070); font-size: .7rem;
}
.ci-quand { color: var(--text2, #6b8ab0); white-space: nowrap; }
/* « subi » : ce rapport raconte ce qu'on m'a fait, pas ce que j'ai fait.
   C'est l'information la plus vite cherchée dans une liste mêlant les deux. */
.ci-role {
  border: 1px solid rgba(251, 146, 60, .45); color: #fdba74;
  background: rgba(251, 146, 60, .1);
  padding: 1px 6px; border-radius: 4px; font-size: .64rem;
  text-transform: uppercase; letter-spacing: .06em;
}
.ci-dot {
  flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent2, #0ea5e9);
}
.ci-caret { flex: 0 0 auto; color: var(--text3, #2d4070); font-size: .8rem; }

/* ─── Le détail déplié ──────────────────────────────────────────── */
.ci-detail {
  padding: 4px 13px 14px 53px;   /* aligné sous le texte, pas sous l'icône */
  border-top: 1px dashed var(--border, #162040);
  font-size: .78rem; line-height: 1.5;
}
.ci-lbl {
  flex: 0 0 190px; color: var(--text2, #6b8ab0); font-size: .72rem;
  text-transform: uppercase; letter-spacing: .05em;
}
.ci-val { flex: 1; min-width: 0; color: var(--text, #c8d6f0); }
.ci-val b { color: #eaf1ff; }
.ci-perte b { color: #fca5a5; }
.ci-vide { color: var(--text3, #2d4070); }

.ci-sub {
  margin: 12px 0 6px; color: var(--accent2, #0ea5e9); font-size: .72rem;
  text-transform: uppercase; letter-spacing: .08em; font-weight: 600;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.71 — LE REDESIGN DES RAPPORTS
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : « je voudrais que les rapports soient redesignés car
   les rapports sont moches, je voudrais un style plus sympa ».

   ⚠️ TOUT LE REDESIGN EST ICI, ET C'EST DÉLIBÉRÉ. Le rendu JS n'a bougé que
   d'une ligne — la couleur de la nature, déjà calculée, est publiée en
   variable CSS `--ci-teinte`. Un redesign écrit en CSS ne peut pas casser un
   rapport : au pire il est laid.

   ⚠️ NE JAMAIS SUPPRIMER LES DEUX POSES DE `--ci-teinte` dans
   `features/information/centre.js` : c'est la seule donnée que ce bloc ne
   peut pas déduire seul. Sans elle, toutes les cartes et tous les filtres
   retombent sur la même couleur de repli, et le redesign perd très
   exactement ce qui le rend lisible — sans qu'aucune erreur ne le signale.

   ⚠️ CE BLOC SE POSE PAR-DESSUS LES RÈGLES DE BASE, il ne les remplace pas.
   Il vient donc APRÈS elles dans le fichier, et c'est ce qui le fait gagner
   à spécificité égale. Le déplacer plus haut le rendrait inopérant en
   silence.

   LE PARTI PRIS, en une phrase : un rapport cesse d'être une LIGNE DE
   TABLEAU pour devenir une PIÈCE qu'on ouvre — ce que la v2.65 promettait
   déjà en toutes lettres sans que l'apparence ne suive.
   ──────────────────────────────────────────────────────────────────── */

/* 1. CHAQUE RAPPORT EST UNE CARTE. Des lignes séparées par des filets se
      lisent comme un journal comptable ; on les détache, on les espace, et
      chacune porte le liseré de SA nature. Repérer un raid dans une liste
      de trente ne demande plus de lire — la couleur suffit. */
.ci-liste {
  display: flex; flex-direction: column; gap: 8px;
  padding: 8px; background: transparent;
}
.ci-item {
  border: 1px solid var(--border, #162040);
  border-left: 3px solid var(--ci-teinte, var(--border2, #1e3060));
  border-radius: 9px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ci-teinte, #1e3060) 5%, transparent),
    transparent 60%);
  overflow: hidden;
  transition: border-color .15s, transform .12s, box-shadow .15s;
}
.ci-item:hover {
  border-color: color-mix(in srgb, var(--ci-teinte, #1e3060) 45%, var(--border, #162040));
  transform: translateY(-1px);
  box-shadow: 0 3px 12px rgba(0, 0, 0, .28);
}
/* 2. LE NON-LU SE VOIT DE LOIN. Le liseré d'origine était bleu pour TOUTES
      les natures et écrasait donc leur couleur : on ne pouvait pas lire les
      deux informations à la fois. Il devient un épaississement du liseré de
      nature, plus une pastille — deux signaux qui ne se marchent pas dessus. */
.ci-item.is-unread {
  border-left-width: 5px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ci-teinte, #1e3060) 13%, transparent), transparent 55%);
}
.ci-item.is-unread .ci-titre { color: #f2f7ff; }

/* 3. LE MÉDAILLON. Un carré neutre de 30 px ne dit rien ; un rond teinté de
      sa nature, cerclé, se lit comme un sceau sur un pli. */
.ci-ico {
  width: 34px; height: 34px; border-radius: 50%; font-size: 1.05rem;
  border: 1px solid color-mix(in srgb, var(--ci-teinte, #1e3060) 35%, transparent);
  box-shadow: inset 0 0 10px color-mix(in srgb, var(--ci-teinte, #1e3060) 18%, transparent);
}
.ci-head { padding: 12px 14px; gap: 12px; }
.ci-titre { font-size: .84rem; letter-spacing: .01em; }

/* 4. « SUBI » CESSE D'ÊTRE UNE ÉTIQUETTE GRISE. C'est la seule mention qui
      dit au joueur qu'il a ENCAISSÉ quelque chose — elle mérite la couleur
      de sa nature. */
.ci-role {
  background: color-mix(in srgb, var(--ci-teinte, #1e3060) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--ci-teinte, #1e3060) 40%, transparent);
  color: var(--ci-teinte, #c8d6f0);
}

/* 5. LE DÉTAIL N'EST PLUS UN TABLEAU. Deux colonnes label/valeur avec un
      filet pointillé sous chaque ligne, c'est une facture. On empile : le
      label devient une étiquette discrète au-dessus, la valeur récupère
      toute la largeur et la lisibilité.
      ⚠️ On garde la disposition en deux colonnes sur écran large — au-delà
      de ~520 px, empiler gaspille la moitié de la ligne.
      ⚠️ Cette règle est encore LUE PAR LE PANNEAU DE DROITE (`rendreFichePane`
      émet `class="ci-detail"`) : son fond assombri fait partie de la fiche,
      seuls le retrait et le filet du haut y sont neutralisés par
      `.ci-fiche .ci-detail`. */
.ci-detail {
  padding: 10px 14px 15px;
  border-top: 1px solid color-mix(in srgb, var(--ci-teinte, #1e3060) 22%, transparent);
  background: rgba(0, 0, 0, .13);
}
.ci-row {
  display: grid; grid-template-columns: 165px 1fr;
  gap: 4px 12px; padding: 6px 0; border-bottom: 0; align-items: baseline;
}
.ci-row + .ci-row {
  border-top: 1px solid rgba(255, 255, 255, .045);
}
.ci-lbl {
  flex: none; font-size: .66rem; letter-spacing: .09em;
  color: var(--text3, #2d4070);
}
.ci-val { font-size: .8rem; }
@media (max-width: 560px) {
  .ci-row { grid-template-columns: 1fr; gap: 1px; }
}

/* 6. LES SOUS-TITRES PRENNENT LA COULEUR DE LEUR RAPPORT. Ils étaient tous
      bleus, quelle que soit la nature — le seul endroit de la fiche où la
      couleur ne voulait rien dire. */
.ci-sub {
  color: var(--ci-teinte, var(--accent2, #0ea5e9));
  display: flex; align-items: center; gap: 8px;
}
.ci-sub::after {
  content: ''; flex: 1; height: 1px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--ci-teinte, #1e3060) 35%, transparent), transparent);
}

/* 7. LES FILTRES SUIVENT LA MÊME LOGIQUE : celui qui est actif prend la
      couleur de la nature qu'il filtre, au lieu du même bleu pour tous. */
.ci-filtre.is-active {
  border-color: color-mix(in srgb, var(--ci-teinte, #0ea5e9) 55%, transparent);
  background: color-mix(in srgb, var(--ci-teinte, #0ea5e9) 15%, transparent);
  color: #eaf1ff;
}
/* ⚠️ v0.0.163 — .79rem = 11,06 px, LE PLANCHER DE LISIBILITÉ DU PROJET.
   Ces notes portaient .76rem, soit 10,64 px : sous le plancher. Le constat
   n° 95 ajoute une note dans cette classe — le champ de débris et ce que les
   Fossoyeurs en ont levé — et la règle du projet veut qu'on remonte ce qu'on
   touche. Mesuré au navigateur en 1440 px et en 390 px : aucun débordement,
   aucune troncature, le pavé grandit de trois points de hauteur. */
.ci-note {
  margin: 8px 0; padding: 8px 10px; border-radius: 6px;
  background: rgba(255, 255, 255, .025); border: 1px solid var(--border, #162040);
  color: var(--text2, #6b8ab0); font-size: .79rem; line-height: 1.5;
}
.ci-note b { color: var(--text, #c8d6f0); }
.ci-alerte {
  margin: 8px 0; padding: 8px 10px; border-radius: 6px;
  background: rgba(233, 69, 96, .08); border: 1px solid rgba(233, 69, 96, .35);
  color: #ffc9d2; font-size: .78rem; line-height: 1.5;
}
.ci-alerte-inline { color: #fca5a5; }
.ci-verrou {
  margin: 8px 0; padding: 8px 10px; border-radius: 6px;
  background: rgba(245, 158, 11, .08); border: 1px solid rgba(245, 158, 11, .3);
  color: #fcd34d; font-size: .76rem; line-height: 1.5;
}
.ci-butin {
  margin: 8px 0; padding: 8px 10px; border-radius: 6px;
  background: rgba(16, 185, 129, .08); border: 1px solid rgba(16, 185, 129, .3);
  color: #a7f3d0; font-size: .78rem;
}
.ci-butin.vide {
  background: rgba(255, 255, 255, .02); border-color: var(--border, #162040);
  color: var(--text2, #6b8ab0);
}
.ci-butin b { color: #eaf1ff; }

.ci-finds { margin: 4px 0 0; padding-left: 20px; color: var(--text, #c8d6f0); }
.ci-finds li { padding: 2px 0; }

/* ═══ v0.0.15 : LE CHRONO D'UNE MISSION ══════════════════════════════
   Demande de Loryan : « ajoute l'heure de début, de fin, du temps de
   trajet final ». Le rapport disait ce qui avait été ramené, jamais quand
   ni en combien de temps — or pour un Limier, qui reste en vol tant qu'il
   n'a rien trouvé, deux butins identiques ne se valent pas si l'un a mis
   quatre minutes et l'autre deux heures.
   Une bande de valeurs et non des lignes « intitulé : valeur » : ces
   quatre chiffres se lisent ENSEMBLE, c'est leur rapport qui parle. */
.ci-chrono {
  display: flex; flex-wrap: wrap; gap: 0 1.4rem;
  margin: 8px 0 4px; padding: 7px 10px;
  background: rgba(255,255,255,.03);
  border: 1px solid rgba(110,130,170,.18); border-radius: 7px;
}
.ci-chrono-item {
  display: flex; flex-direction: column; line-height: 1.25;
  font-family: var(--mono, monospace); font-size: .82rem; color: var(--text, #c8d6f0);
}
.ci-chrono-lbl {
  font-family: var(--hud, monospace); font-size: .55rem; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text2, #8da4c4);
}
.ci-chrono-jour { font-size: .8em; color: var(--text2, #8da4c4); }
/* L'écart au devis : en retard on prévient, en avance on félicite. */
.ci-chrono-item.est-tard b { color: #fbbf24; }
.ci-chrono-item.est-tot  b { color: #6ee7b7; }

/* ═══ L'INTENSITÉ DU TIRAGE, sur chaque trouvaille ════════════════════
   Le serveur la calcule depuis toujours (le meilleur de trois dés à cent
   faces) mais elle n'était affichée que sur les rapports d'anomalie. C'est
   pourtant elle qui explique pourquoi deux gisements du même tirage ne
   rapportent pas la même chose.
   Une JAUGE et pas un nombre nu : « 12 » et « 87 » ne se comparent pas
   d'un coup d'œil dans une liste de trente lignes. */
.ci-intensite {
  display: inline-flex; align-items: center; gap: 5px; margin-left: 8px;
  font-family: var(--mono, monospace); font-size: .68rem; color: var(--text2, #8da4c4);
  cursor: help; vertical-align: middle;
}
.ci-intensite-rail {
  display: inline-block; width: 42px; height: 4px; border-radius: 999px;
  background: rgba(110,130,170,.22); overflow: hidden;
}
.ci-intensite-fill { display: block; height: 100%; border-radius: 999px; background: currentColor; }
.ci-intensite.est-faible  { color: #8da4c4; }
.ci-intensite.est-moyenne { color: #7dd3fc; }
.ci-intensite.est-forte   { color: #6ee7b7; }

/* ─── Les deux camps d'un raid ──────────────────────────────────── */
.ci-camps {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 10px 0;
}
.ci-camp {
  border: 1px solid var(--border, #162040); border-radius: 6px;
  padding: 8px 10px; background: rgba(255, 255, 255, .015);
}
/* Le camp du LECTEUR ressort : c'est celui qu'il regarde en premier, et
   sans cette marque il devait relire l'en-tête pour savoir de quel côté
   il était. */
.ci-camp.moi {
  border-color: rgba(14, 165, 233, .45); background: rgba(14, 165, 233, .06);
}
.ci-camp-t {
  color: var(--text, #c8d6f0); font-size: .76rem; font-weight: 600;
  margin-bottom: 6px; display: flex; align-items: center; gap: 6px;
}
.ci-moi {
  border: 1px solid var(--accent2, #0ea5e9); color: var(--accent2, #0ea5e9);
  padding: 0 5px; border-radius: 4px; font-size: .62rem;
  text-transform: uppercase; letter-spacing: .06em; font-weight: 700;
}
.ci-camp .ci-lbl { flex: 0 0 78px; }

/* ─── Le déroulé des rounds ─────────────────────────────────────── */
.ci-table-wrap { overflow-x: auto; }
.ci-table {
  width: 100%; border-collapse: collapse; font-size: .74rem;
  font-variant-numeric: tabular-nums;
}
.ci-table th {
  text-align: right; padding: 5px 8px; color: var(--text2, #6b8ab0);
  border-bottom: 1px solid var(--border2, #1e3060); font-weight: 600;
  white-space: nowrap;
}
.ci-table td {
  text-align: right; padding: 4px 8px; color: var(--text, #c8d6f0);
  border-bottom: 1px dotted rgba(30, 48, 96, .5);
}
.ci-table th:first-child, .ci-table td:first-child { text-align: left; }

.ci-legendaire {
  border: 1px solid var(--gold, #f59e0b); color: var(--gold, #f59e0b);
  padding: 1px 6px; border-radius: 4px; font-size: .62rem;
  letter-spacing: .1em; font-weight: 700;
}
.ci-lore {
  margin: 8px 0; padding-left: 10px; border-left: 2px solid rgba(249, 168, 212, .4);
  color: var(--text2, #6b8ab0); font-style: italic; font-size: .78rem; line-height: 1.6;
}

/* La rangée d'actions sous le détail — encore ÉMISE PAR LE PANNEAU DE
   DROITE (`rendreFichePane`) : le filet et l'alignement à droite du bouton
   « Supprimer ce rapport » viennent d'ici. */
.ci-detail-actions {
  margin-top: 12px; padding-top: 10px;
  border-top: 1px dashed var(--border, #162040);
  display: flex; justify-content: flex-end;
}

/* ─── Le panneau de notifications ancré dans le Centre ──────────────
   Il arrive avec sa classe `is-docked` (v2.43), déjà pleine largeur. Rien
   à redéclarer : on lui retire seulement la marge que le flottant portait
   pour se décoller de la cloche. */
#ci-notif-host .notif-panel.is-docked { margin: 0; }

/* ─── Repli mobile ─────────────────────────────────────────────────
   En dessous de 720 px, le libellé et le résumé ont besoin de toute la
   largeur : les deux camps d'un raid passent l'un sous l'autre, les
   libellés du détail cessent d'être une colonne fixe, et le détail
   récupère la gouttière de 53 px qui l'alignait sous le titre. */
@media (max-width: 720px) {
  .ci-camps { grid-template-columns: 1fr; }
  .ci-detail { padding-left: 13px; }
  .ci-row { flex-direction: column; gap: 2px; }
  .ci-lbl { flex: none; }
  .ci-camp .ci-lbl { flex: none; }
  .ci-meta { flex-direction: column; align-items: flex-end; gap: 2px; }
}


/* ═══════════════════════════════════════════════════════════════════
   v0.0.7 — LA TABLE DE COMMANDEMENT
   ═══════════════════════════════════════════════════════════════════
   Choix de Loryan parmi dix propositions de refonte : « je choisis la
   table de commandement ». La liste se range dans une colonne étroite à
   gauche, le rapport ouvert s'affiche dans un panneau à droite.

   ⚠️ CE BLOC EST LE DERNIER DE LA SECTION, ET C'EST CE QUI LE FAIT GAGNER
   à spécificité égale sur le redesign v0.0.71 qu'il complète. Le déplacer
   plus haut le rendrait inopérant en silence — même règle qu'en v0.0.71.

   ⚠️ TOUT EST PRÉFIXÉ PAR `.ci-tdc`, le conteneur que le navigateur
   fabrique au premier rendu de la liste (`ciPreparerTable`,
   features/information/centre.js).
   ─────────────────────────────────────────────────────────────────── */

.ci-tdc {
  display: grid;
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  gap: 12px;
  align-items: start;
}

/* 1. LA LISTE DEVIENT UNE COLONNE QUI DÉFILE POUR ELLE-MÊME. C'est toute
      la promesse de la table : lire un rapport de six rounds ne fait plus
      descendre la liste, donc on ne perd jamais sa place. */
.ci-tdc > .ci-liste {
  max-height: min(68vh, 640px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  gap: 4px;
}
/* ⚠️ ET LES CARTES NE RÉTRÉCISSENT PAS. `.ci-liste` est une colonne flex :
   dès qu'on lui donne une hauteur maximale, ses enfants se laissent écraser
   pour tenir dedans AU LIEU de la faire défiler — dix rapports passaient de
   58 px à 48 px et l'heure se coupait en deux, sans la moindre erreur.
   C'est le défaut le plus courant du flex en colonne bornée. (Attrapé au
   navigateur, capture à l'appui.)
   ⚠️ ET LE PLI D'UNE VAGUE EN EST UN AUSSI (v0.0.145). Le piège s'est
   refermé une seconde fois, à l'identique : `.ci-vague` n'est pas un
   `.ci-item`, il n'était donc pas épinglé — écrasé de 35 px à 23 px dès que
   la liste dépassait sa hauteur, il se réduisait à un TRAIT entre deux
   cartes, et les rapports qu'il contient devenaient introuvables
   (`overflow: hidden` coupe le reste). Signalé par Loryan : « c'est quoi ça
   c'est moche » puis « je vois même plus les rapports des récolteurs ».
   ⚠️ TOUT NOUVEL ENFANT DIRECT DE `.ci-liste` DOIT ÊTRE AJOUTÉ ICI. */
.ci-tdc > .ci-liste > .ci-item,
.ci-tdc > .ci-liste > .ci-vague { flex: 0 0 auto; }
/* Et les cartes DANS un pli, pour la même raison : `.ci-vague-corps` est une
   colonne flex de plus. Elle n'est pas bornée aujourd'hui — mais la borner
   un jour écraserait ses cartes sans un mot, comme les deux fois d'avant. */
.ci-vague-corps > .ci-item { flex: 0 0 auto; }

/* 2. LA CARTE TIENT SUR DEUX LIGNES SERRÉES, ET RIEN DE PLUS.
      Demande de Loryan sur la première version : « j'aimerais que les
      lignes soient plus fines de moitié ». Elle en empilait TROIS — titre,
      résumé, puis l'heure sur sa propre ligne — soit 67 px de carte et
      huit rapports à l'écran.

      L'heure remonte donc à droite du texte, sur la même ligne que lui, et
      c'est ce qui libère la troisième. Le titre repasse sur UNE ligne : une
      hauteur de carte qui dépend de la longueur du titre donnerait une
      colonne en dents de scie, et une liste qu'on balaye vit de lignes
      toutes pareilles. Ce qui dépasse se coupe — le rapport ouvert à droite
      porte le titre entier, en grand.

      Mesuré : 67 px → 38 px de carte, 76 px → 42 px de pas, et de huit
      rapports visibles à quinze. */
.ci-tdc .ci-head {
  display: flex; align-items: center;
  gap: 9px;
  padding: 4px 10px;
}
.ci-tdc .ci-ico { width: 22px; height: 22px; font-size: .8rem; border-radius: 6px; }
.ci-tdc .ci-head-txt { gap: 0; }
.ci-tdc .ci-titre  { font-size: .78rem; line-height: 1.2; }
.ci-tdc .ci-resume { font-size: .7rem;  line-height: 1.2; }
.ci-tdc .ci-meta   { flex-direction: row; align-items: center; gap: 6px; font-size: .64rem; }
.ci-tdc .ci-role   { padding: 0 4px; font-size: .56rem; letter-spacing: .04em; }
.ci-tdc .ci-dot    { width: 6px; height: 6px; }
/* Plus rien ne se déplie sous la ligne : le chevron promettait un geste
   qui n'existe plus — et sur une ligne aussi fine, chaque pixel horizontal
   rendu au titre se voit. */
.ci-tdc .ci-caret { display: none; }

/* 3. LA LIGNE CHOISIE SE VOIT DE LOIN. En accordéon, le détail dessous
      disait lequel était ouvert ; ici, c'est la seule chose qui le dit. */
.ci-tdc .ci-item.is-open {
  border-left-width: 5px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ci-teinte, #1e3060) 20%, transparent), transparent 70%);
  border-color: color-mix(in srgb, var(--ci-teinte, #1e3060) 55%, var(--border, #162040));
}
.ci-tdc .ci-head:focus-visible {
  outline: 2px solid var(--ci-teinte, var(--accent2, #0ea5e9));
  outline-offset: -2px; border-radius: 7px;
}

/* 4. LE PANNEAU DE DROITE. Il porte la teinte de la nature du rapport
      ouvert, comme la carte de gauche : le même objet garde la même
      couleur d'un bout à l'autre de l'écran. */
.ci-fiche {
  border: 1px solid var(--border, #162040);
  border-top: 2px solid var(--ci-teinte, var(--border2, #1e3060));
  border-radius: 9px;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--ci-teinte, #1e3060) 9%, transparent), transparent 260px),
    var(--card, #0d1526);
  padding: 16px 18px 18px;
  max-height: min(68vh, 640px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  min-width: 0;
}
.ci-fiche-tete {
  margin-bottom: 14px; padding-bottom: 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--ci-teinte, #1e3060) 28%, var(--border, #162040));
}
.ci-fiche-nat {
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--ci-teinte, var(--accent2, #0ea5e9));
  font-size: .64rem; text-transform: uppercase; letter-spacing: .14em; font-weight: 600;
}
.ci-fiche-nat-ico {
  width: 22px; height: 22px; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center; font-size: .82rem;
  background: color-mix(in srgb, var(--ci-teinte, #1e3060) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--ci-teinte, #1e3060) 40%, transparent);
}
.ci-fiche-titre {
  margin: 8px 0 6px; color: #f2f7ff; font-size: 1.02rem; font-weight: 600; line-height: 1.3;
}
.ci-fiche-meta {
  margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  color: var(--text2, #8aa4c8); font-size: .72rem;
}
.ci-fiche-date { color: var(--text3, #6b8ab0); }
/* Interrupteur `rapportVmOrigine` : le vaisseau de départ, dans l'en-tête.
   `currentColor` plutôt qu'une teinte : la peau « dossier » (espionnage)
   peint l'en-tête en sombre sur clair, et l'étiquette doit s'y lire aussi.
   Sans l'interrupteur, aucune balise ne porte cette classe. */
.ci-fiche-vm {
  display: inline-flex; align-items: baseline; gap: 4px; max-width: 100%;
  padding: 1px 8px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
  font-size: var(--badge-font-size, 11px); overflow-wrap: anywhere;
}
.ci-fiche-vm b { font-weight: 600; }

/* Le détail réutilise `.ci-detail` TEL QUEL — c'est la même sortie, écrite
   par les mêmes onze fonctions. Seuls la gouttière qui l'alignait sous le
   titre de la carte et son filet du haut n'ont plus de sens ici. */
.ci-fiche .ci-detail { padding: 0; border-top: 0; }

.ci-fiche-vide {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; min-height: 240px; padding: 24px 20px; text-align: center;
  color: var(--text2, #8aa4c8); font-size: .82rem; line-height: 1.55;
}
.ci-fiche-vide p { margin: 0; max-width: 40ch; }
.ci-fiche-vide-ico { font-size: 1.7rem; opacity: .45; }
.ci-fiche-vide-t { color: var(--text, #c8d6f0); font-weight: 600; font-size: .88rem; }

/* Le retour n'existe qu'en une seule colonne, où la fiche prend la place
   de la liste. Sur deux colonnes, les deux sont là : il n'y a nulle part
   où revenir. */
.ci-fiche-retour {
  display: none;
  margin: 0 0 12px; padding: 7px 12px;
  background: rgba(10, 16, 32, .55); border: 1px solid var(--border2, #1e3060);
  border-radius: 7px; color: var(--text, #c8d6f0); font: inherit; font-size: .78rem;
  cursor: pointer; min-height: 36px;
}
.ci-fiche-retour:hover { border-color: var(--ci-teinte, var(--accent2, #0ea5e9)); }

/* ─── UNE SEULE COLONNE ────────────────────────────────────────────
   ⚠️ DEUX DÉCLENCHEURS, ET IL FAUT LES DEUX. La largeur réelle de la
   fenêtre (la media query) NE VOIT PAS `body.mobile-mode`, qu'un joueur
   peut forcer sur un grand écran ; et la classe ne déclenche aucune media
   query. N'en écrire qu'un donnerait deux colonnes de 190 px à celui qui
   a justement demandé l'affichage téléphone.
   La liste et la fiche ne cohabitent plus : la fiche PREND la place de la
   liste, et le bouton de retour la ramène. */
body.mobile-mode .ci-tdc,
body.mobile-mode .ci-tdc > .ci-liste,
body.mobile-mode .ci-fiche { grid-template-columns: 1fr; max-height: none; overflow-y: visible; }
body.mobile-mode .ci-tdc.is-fiche-ouverte > .ci-liste { display: none; }
body.mobile-mode .ci-tdc:not(.is-fiche-ouverte) > .ci-fiche { display: none; }
body.mobile-mode .ci-fiche-retour { display: inline-flex; align-items: center; }
/* ⚠️ AU DOIGT, LA LIGNE REPREND TROIS PIXELS. Une carte de 35 px passe sous
   les 36 px que ce projet s'est fixés comme plus petite cible tactile
   (cf. `body.mobile-mode .ci-filtre`). Sur grand écran la souris s'en moque,
   et la ligne y reste à 35 px : c'est la seule différence entre les deux. */
body.mobile-mode .ci-tdc .ci-head { min-height: 38px; }

@media (max-width: 780px) {
  .ci-tdc,
  .ci-tdc > .ci-liste,
  .ci-fiche { grid-template-columns: 1fr; max-height: none; overflow-y: visible; }
  .ci-tdc.is-fiche-ouverte > .ci-liste { display: none; }
  .ci-tdc:not(.is-fiche-ouverte) > .ci-fiche { display: none; }
  .ci-fiche-retour { display: inline-flex; align-items: center; }
  .ci-tdc .ci-head { min-height: 38px; }   /* cible tactile, cf. ci-dessus */
  /* Le repli de 720 px range `.ci-meta` en colonne alignée à droite : dans
     la grille de la carte, elle est déjà sur sa propre ligne, à gauche. */
  .ci-tdc .ci-meta { flex-direction: row; align-items: center; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.145 — LES VAGUES DU CENTRE
   Dix Récolteurs rentrés à la même seconde tiennent sur UNE ligne, qui
   se déplie sur les dix rapports. Rendu par `ciVagueHtml`
   (features/information/centre.js) ; les cartes du pli sont les cartes
   ordinaires, sans une règle de plus. Tout vit sous `.ci-vague`.
   ════════════════════════════════════════════════════════════════════ */

/* Le pli EST une carte : même liseré de nature, même fond, même relief.
   Un conteneur neutre autour de cartes aurait fait lire la vague comme un
   accident de mise en page plutôt que comme une pièce de la liste. */
.ci-vague {
  border: 1px solid var(--border, #162040);
  border-left: 3px solid var(--ci-teinte, var(--border2, #1e3060));
  border-radius: 9px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ci-teinte, #1e3060) 5%, transparent),
    transparent 60%);
  overflow: hidden;
  transition: border-color .15s, box-shadow .15s;
}
.ci-vague.is-unread { border-left-width: 5px; }
.ci-vague.is-unread .ci-titre { color: #f2f7ff; }
.ci-vague:hover {
  border-color: color-mix(in srgb, var(--ci-teinte, #1e3060) 45%, var(--border, #162040));
}
.ci-vague.is-ouverte {
  border-color: color-mix(in srgb, var(--ci-teinte, #1e3060) 40%, var(--border, #162040));
}

/* Le compteur de missions, dans la colonne des méta. C'est LE chiffre que
   la ligne pliée doit donner — « combien de rapports y a-t-il là-dessous ? » —
   et il doit se lire sans avoir à finir la phrase du titre. */
.ci-vague-nb {
  font-family: var(--mono); font-size: .68rem; font-weight: 700;
  color: var(--ci-teinte, #c8d6f0);
  background: color-mix(in srgb, var(--ci-teinte, #1e3060) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--ci-teinte, #1e3060) 38%, transparent);
  border-radius: 999px; padding: 1px 7px; white-space: nowrap;
}

/* ⚠️ SA PROPRE CLASSE DE CHEVRON, ET PAS `.ci-caret`. La table de
   commandement masque `.ci-caret` (`.ci-tdc .ci-caret { display: none }`)
   parce qu'une carte n'y déplie plus rien — mais un pli, LUI, déplie
   toujours. Réutiliser la classe aurait laissé la vague sans la moindre
   marque d'ouverture dans la colonne de gauche. */
.ci-vague-caret {
  flex: 0 0 auto; color: var(--ci-teinte, #6b8ab0); font-size: .8rem;
}

/* Le contenu du pli : les cartes rentrent d'un cran et se posent sur un
   fond légèrement creusé, pour qu'on voie d'un coup d'œil où la vague
   commence et où elle finit. */
.ci-vague-corps {
  display: flex; flex-direction: column; gap: 6px;
  padding: 2px 8px 8px 14px;
  background: rgba(0, 0, 0, .18);
  border-top: 1px dashed var(--border, #162040);
}
.ci-vague-corps .ci-item { border-radius: 7px; }
/* Dans le pli, le titre du rapport répète le vaisseau que l'en-tête vient
   de nommer : on le laisse, mais on le rend discret — c'est le RÉSUMÉ
   (ce que cette mission-là a rapporté) qu'on vient lire ligne à ligne. */
.ci-vague-corps .ci-titre { font-size: .78rem; color: var(--text2, #6b8ab0); }
.ci-vague-corps .ci-item.is-unread .ci-titre { color: var(--text, #c8d6f0); }

/* Téléphone : le pli garde exactement l'anatomie d'une carte, il n'a donc
   rien à corriger — sauf la marge de gauche, qui coûterait de la largeur
   utile là où il y en a le moins, et la cible tactile de l'en-tête. */
body.mobile-mode .ci-vague-corps { padding: 2px 5px 6px 7px; }
body.mobile-mode .ci-vague-head { min-height: 38px; }
@media (max-width: 700px) {
  .ci-vague-corps { padding: 2px 5px 6px 7px; }
  .ci-vague-head { min-height: 38px; }
}


/* ═══════════════════════════════════════════════════════════════════
   LES FORMES DE FICHES — une mise en forme par nature (v0.0.154)
   ═══════════════════════════════════════════════════════════════════
   Affectations choisies par Loryan sur la galerie « Dix formes de
   rapport ». Le rendu des onze natures (CI_RENDUS) ne change pas d'un
   caractère : `rendreFichePane` pose `data-forme` sur `.ci-fiche` et
   émet trois décors (verdict, sceau/radar/filigrane) — tout le reste
   est ici. Onze natures, huit peaux :
     billet (expédition) · tampon (transport, anomalie) ·
     manifeste (stationnement) · dossier (espionnage) ·
     radar (espionné) · fil (raid) · holo (missiles) ·
     tuiles (vol, trou de ver) · telex (horizon)

   ⚠️ TOUT est préfixé `.ci-fiche[data-forme…]` : sur une fiche sans
   attribut, pas une règle de ce bloc ne s'applique.
   ⚠️ CE BLOC VIT APRÈS la table de commandement ET les vagues : à
   spécificité proche, c'est l'ordre qui départage — le déplacer plus
   haut casserait des peaux en silence (même règle qu'en v0.0.71).
   ⚠️ LES PSEUDO-ÉLÉMENTS POSÉS SUR DES GRILLES SONT ABSOLUS, exprès :
   un ::before en flux devient un ENFANT DE GRILLE et décale toute la
   rangée (les puces du fil sur `.ci-row`, notamment).
   ─────────────────────────────────────────────────────────────────── */

/* Le mot du verdict — la pièce commune à toutes les peaux. Chacune le met
   en scène à sa façon ; ceci n'est que la base. */
.ci-fiche[data-forme] { position: relative; }
.ci-fiche .ci-verdict {
  display: inline-block; margin: 0 0 10px; padding: 2px 12px;
  border: 1.5px solid var(--ci-v, var(--ci-teinte, #8aa4c8));
  color: var(--ci-v, var(--ci-teinte, #c8d6f0));
  border-radius: 6px; font-size: .8rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
}
.ci-verdict-ok     { --ci-v: #34d399; }
.ci-verdict-warn   { --ci-v: #fbbf24; }
.ci-verdict-bad    { --ci-v: #fb7185; }
.ci-verdict-or     { --ci-v: var(--gold, #f59e0b); }
.ci-verdict-neutre { --ci-v: var(--ci-teinte, #8aa4c8); }

/* ─── BILLET (expéditions) — la carte d'embarquement ────────────────
   L'en-tête devient la bande teintée du billet, et le chrono quitte le
   flux pour devenir le TALON de droite : vertical, séparé par une
   perforation en pointillés, code-barres dessous. */
.ci-fiche[data-forme="billet"] {
  border-radius: 14px;
  border-top-width: 1px;
  border-color: color-mix(in srgb, var(--ci-teinte, #1e3060) 45%, var(--border, #162040));
}
.ci-fiche[data-forme="billet"] .ci-fiche-tete {
  margin: -16px -18px 12px; padding: 12px 18px 10px;
  background: color-mix(in srgb, var(--ci-teinte, #1e3060) 16%, var(--card, #0d1526));
  border-bottom: 1px solid color-mix(in srgb, var(--ci-teinte, #1e3060) 35%, transparent);
  border-radius: 13px 13px 0 0;
}
.ci-fiche[data-forme="billet"] .ci-fiche-nat { letter-spacing: .22em; }
.ci-fiche[data-forme="billet"] .ci-detail {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0 20px;
}
.ci-fiche[data-forme="billet"] .ci-detail > * { grid-column: 1; min-width: 0; }
.ci-fiche[data-forme="billet"] .ci-detail > .ci-chrono {
  grid-column: 2; grid-row: 1 / span 40; align-self: start;
  flex-direction: column; gap: 10px; width: 148px;
  margin: 0; padding: 12px 0 12px 18px;
  background: none; border: 0;
  border-left: 2px dashed color-mix(in srgb, var(--ci-teinte, #1e3060) 55%, #2a3c66);
  border-radius: 0;
}
/* Le code-barres du talon. Décoratif, en dégradé répété : aucun texte,
   rien à lire, juste le billet qui se reconnaît. */
.ci-fiche[data-forme="billet"] .ci-detail > .ci-chrono::after {
  content: ''; display: block; height: 30px; margin-top: 2px;
  background: repeating-linear-gradient(90deg,
    #9db4d8 0 2px, transparent 2px 4px, #9db4d8 4px 7px,
    transparent 7px 9px, #9db4d8 9px 10px, transparent 10px 14px);
  opacity: .55; border-radius: 2px;
}

/* ─── TAMPON (transports, anomalies) — le verdict tamponné ──────────
   La conclusion en énorme, tamponnée légèrement de travers ; le titre
   passe en sourdine au-dessus, le détail se range dessous. */
.ci-fiche[data-forme="tampon"] {
  border-left: 4px solid color-mix(in srgb, var(--ci-teinte, #1e3060) 60%, transparent);
}
.ci-fiche[data-forme="tampon"] .ci-fiche-titre {
  font-size: .9rem; color: var(--text2, #8aa4c8); font-weight: 600;
}
.ci-fiche[data-forme="tampon"] .ci-fiche-tete { border-bottom: 0; padding-bottom: 0; margin-bottom: 4px; }
.ci-fiche[data-forme="tampon"] .ci-verdict {
  padding: 3px 18px 5px; margin: 2px 0 14px;
  font-size: 1.7rem; letter-spacing: .1em; line-height: 1.25;
  border: 3px double var(--ci-v, var(--ci-teinte, #8aa4c8));
  border-radius: 9px; transform: rotate(-2.5deg); opacity: .92;
  mask-image: radial-gradient(circle at 30% 40%, black 60%, rgba(0, 0, 0, .72));
}

/* ─── MANIFESTE (stationnements) — le manifeste de soute ────────────
   Une pièce comptable : en-tête centré et espacé, lignes réglées avec
   pointillés de conduite, valeurs en chiffres de machine à droite, et
   le verdict en cachet penché au coin du document. */
.ci-fiche[data-forme="manifeste"] { border-radius: 4px; }
.ci-fiche[data-forme="manifeste"] .ci-fiche-tete {
  text-align: center;
  border-bottom: 3px double color-mix(in srgb, var(--ci-teinte, #1e3060) 45%, var(--border2, #1e3060));
}
.ci-fiche[data-forme="manifeste"] .ci-fiche-nat { letter-spacing: .3em; }
.ci-fiche[data-forme="manifeste"] .ci-fiche-titre { font-style: italic; font-weight: 500; }
.ci-fiche[data-forme="manifeste"] .ci-fiche-meta { justify-content: center; }
.ci-fiche[data-forme="manifeste"] .ci-verdict {
  float: right; margin: 4px 0 8px 14px; padding: 4px 14px;
  border-width: 2px; border-radius: 7px;
  transform: rotate(-4deg); opacity: .85; font-size: .74rem; letter-spacing: .16em;
}
.ci-fiche[data-forme="manifeste"] .ci-row {
  display: flex; align-items: baseline; gap: 8px;
}
.ci-fiche[data-forme="manifeste"] .ci-row::before {
  content: ''; order: 2; flex: 1 1 auto; min-width: 20px;
  border-bottom: 1px dotted #26375f; transform: translateY(-4px);
}
.ci-fiche[data-forme="manifeste"] .ci-lbl { order: 1; flex: 0 1 auto; }
.ci-fiche[data-forme="manifeste"] .ci-val {
  order: 3; flex: 0 1 auto; text-align: right;
  font-family: var(--mono, monospace); font-variant-numeric: tabular-nums;
}

/* ─── DOSSIER (espionnage) — le dossier scellé ──────────────────────
   Une chemise d'archives : bandeau de classification à la couleur de la
   nature (texte sombre dessus), champs tapés à la machine sur lignes
   pointillées, sceau en filigrane. */
/* ⚠️ PAS d'overflow:hidden ici, malgré le bandeau qui déborde en négatif :
   la fiche DÉFILE (`overflow-y:auto` de base), et l'espionnage écrit les
   rapports les plus longs du jeu — les rogner serait pire qu'un coin vif. */
.ci-fiche[data-forme="dossier"] { border-radius: 4px; }
.ci-fiche[data-forme="dossier"] .ci-fiche-tete {
  margin: -16px -18px 14px; padding: 10px 18px 12px;
  background: color-mix(in srgb, var(--ci-teinte, #8aa4c8) 88%, #0d1526);
  border-bottom: 0;
}
.ci-fiche[data-forme="dossier"] .ci-fiche-nat,
.ci-fiche[data-forme="dossier"] .ci-fiche-titre { color: #0a0f1e; }
.ci-fiche[data-forme="dossier"] .ci-fiche-nat { letter-spacing: .2em; }
.ci-fiche[data-forme="dossier"] .ci-fiche-nat-ico { background: rgba(5, 8, 16, .18); border-color: rgba(5, 8, 16, .3); }
.ci-fiche[data-forme="dossier"] .ci-fiche-meta { color: rgba(10, 15, 30, .75); }
.ci-fiche[data-forme="dossier"] .ci-fiche-meta .ci-fiche-date { color: rgba(10, 15, 30, .6); }
.ci-fiche[data-forme="dossier"] .ci-role { border-color: rgba(10, 15, 30, .5); color: #0a0f1e; background: rgba(5, 8, 16, .12); }
.ci-fiche[data-forme="dossier"] .ci-verdict {
  font-family: var(--mono, monospace); border-radius: 0;
  letter-spacing: .22em; transform: rotate(-1.2deg);
}
.ci-fiche[data-forme="dossier"] .ci-lbl {
  font-family: var(--mono, monospace); text-transform: uppercase;
}
.ci-fiche[data-forme="dossier"] .ci-row { border-top: 0; }
.ci-fiche[data-forme="dossier"] .ci-val {
  border-bottom: 1px dotted #2a3c66; padding-bottom: 2px;
}
/* Le sceau. En filigrane discret (opacité basse, aucun événement de
   souris) : il signe le document sans jamais gêner la lecture du texte
   qui passe dessous. */
.ci-sceau {
  position: absolute; top: 74px; right: 22px; width: 84px; height: 84px;
  border-radius: 50%; border: 2px solid var(--ci-teinte, #8aa4c8);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.6rem; transform: rotate(-8deg); opacity: .3;
  pointer-events: none; z-index: 0;
}
.ci-sceau::before {
  content: ''; position: absolute; inset: 6px; border-radius: 50%;
  border: 1px solid var(--ci-teinte, #8aa4c8);
}

/* ─── RADAR (espionné) — la console radar ───────────────────────────
   Un instrument plutôt qu'une page : l'écran circulaire balaye à
   gauche, le verdict s'affiche dessous, les données restent à droite.
   Le balayage respecte prefers-reduced-motion. */
.ci-fiche[data-forme="radar"] {
  display: grid; grid-template-columns: 180px minmax(0, 1fr);
  gap: 2px 18px; align-items: start; align-content: start;
}
.ci-fiche[data-forme="radar"] .ci-fiche-retour { grid-column: 1 / -1; }
.ci-fiche[data-forme="radar"] .ci-fiche-tete { grid-column: 2; grid-row: 2; margin-bottom: 6px; }
.ci-fiche[data-forme="radar"] .ci-verdict { grid-column: 1; grid-row: 3; justify-self: center; font-family: var(--hud, monospace); font-size: .66rem; letter-spacing: .2em; }
.ci-fiche[data-forme="radar"] .ci-scope { grid-column: 1; grid-row: 2; justify-self: center; }
.ci-fiche[data-forme="radar"] .ci-detail { grid-column: 2; grid-row: 3 / span 2; }
.ci-fiche[data-forme="radar"] .ci-detail-actions { grid-column: 1 / -1; }
.ci-fiche[data-forme="radar"] .ci-lbl { font-family: var(--hud, monospace); font-size: .7rem; letter-spacing: .14em; }
.ci-scope {
  position: relative; width: 160px; aspect-ratio: 1; border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--ci-teinte, #8aa4c8) 55%, transparent);
  background:
    radial-gradient(circle, transparent 0 32%,
      color-mix(in srgb, var(--ci-teinte, #8aa4c8) 22%, transparent) 32% calc(32% + 1px),
      transparent 0 65%,
      color-mix(in srgb, var(--ci-teinte, #8aa4c8) 22%, transparent) 65% calc(65% + 1px),
      transparent 0),
    linear-gradient(90deg, transparent calc(50% - .5px),
      color-mix(in srgb, var(--ci-teinte, #8aa4c8) 20%, transparent) 50%,
      transparent calc(50% + .5px)),
    linear-gradient(0deg, transparent calc(50% - .5px),
      color-mix(in srgb, var(--ci-teinte, #8aa4c8) 20%, transparent) 50%,
      transparent calc(50% + .5px));
  overflow: hidden;
}
.ci-scope-balayage {
  position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(from 0deg, transparent 0 296deg,
    color-mix(in srgb, var(--ci-teinte, #8aa4c8) 34%, transparent) 342deg,
    color-mix(in srgb, var(--ci-teinte, #8aa4c8) 75%, transparent) 358deg,
    transparent 360deg);
}
@media (prefers-reduced-motion: no-preference) {
  .ci-scope-balayage { animation: ci-scope-rot 5.5s linear infinite; }
}
@keyframes ci-scope-rot { to { transform: rotate(360deg); } }
.ci-scope-blip {
  position: absolute; left: 31%; top: 38%; width: 7px; height: 7px;
  border-radius: 50%; background: var(--ci-teinte, #8aa4c8);
  box-shadow: 0 0 9px var(--ci-teinte, #8aa4c8);
}
.ci-scope-blip.b2 { left: 64%; top: 57%; opacity: .6; }
.ci-scope-centre {
  position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-size: 1.4rem;
  filter: drop-shadow(0 0 10px color-mix(in srgb, var(--ci-teinte, #8aa4c8) 70%, transparent));
}

/* ─── FIL (raids) — la ligne de temps ───────────────────────────────
   Le rapport se lit comme le déroulé du combat : un fil vertical, et
   chaque bloc du rendu (la salve, les camps, les rounds, le butin, les
   dommages) accroché dessus par une puce — dans l'ordre où le serveur
   les raconte déjà. */
.ci-fiche[data-forme="fil"] .ci-verdict { margin-bottom: 12px; }
.ci-fiche[data-forme="fil"] .ci-detail {
  position: relative; padding-left: 24px;
}
.ci-fiche[data-forme="fil"] .ci-detail::before {
  content: ''; position: absolute; left: 7px; top: 8px; bottom: 8px; width: 2px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ci-teinte, #1e3060) 60%, transparent),
    color-mix(in srgb, var(--ci-teinte, #1e3060) 20%, transparent));
}
.ci-fiche[data-forme="fil"] .ci-detail > * { position: relative; }
.ci-fiche[data-forme="fil"] .ci-detail > *::before {
  content: ''; position: absolute; left: -21px; top: .55em;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--card, #0d1526);
  border: 2px solid var(--ci-teinte, #1e3060);
}
/* Seuls les ENFANTS DIRECTS du détail portent une puce : la grille des
   deux camps en a UNE (le bloc `.ci-camps`), pas une par camp, et le
   tableau des rounds l'accroche à son conteneur défilant. C'est le
   sélecteur `>` du dessus qui garantit ça — ne pas l'élargir. */

/* ─── HOLO (missiles) — l'hologramme ────────────────────────────────
   Une projection tactique : coins coupés, cadre et lueur à la teinte de
   la nature, insigne en filigrane géant, fines lignes de balayage. */
.ci-fiche[data-forme="holo"] {
  --ci-coin: 16px;
  clip-path: polygon(var(--ci-coin) 0, calc(100% - var(--ci-coin)) 0,
    100% var(--ci-coin), 100% calc(100% - var(--ci-coin)),
    calc(100% - var(--ci-coin)) 100%, var(--ci-coin) 100%,
    0 calc(100% - var(--ci-coin)), 0 var(--ci-coin));
  border-radius: 0;
  border-color: color-mix(in srgb, var(--ci-teinte, #1e3060) 45%, transparent);
  background:
    repeating-linear-gradient(180deg, rgba(255, 255, 255, .02) 0 1px, transparent 1px 3px),
    linear-gradient(165deg, color-mix(in srgb, var(--ci-teinte, #1e3060) 13%, #070c18), #060a14 55%);
  box-shadow: inset 0 0 34px color-mix(in srgb, var(--ci-teinte, #1e3060) 12%, transparent);
}
.ci-fiche[data-forme="holo"] .ci-fiche-nat { font-family: var(--hud, monospace); letter-spacing: .24em; }
.ci-fiche[data-forme="holo"] .ci-fiche-titre {
  text-shadow: 0 0 18px color-mix(in srgb, var(--ci-teinte, #1e3060) 45%, transparent);
}
.ci-fiche[data-forme="holo"] .ci-verdict {
  border-radius: 0; font-family: var(--hud, monospace); font-size: .66rem;
  letter-spacing: .2em;
  background: color-mix(in srgb, var(--ci-v, var(--ci-teinte, #8aa4c8)) 10%, transparent);
  clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
}
.ci-fiche[data-forme="holo"] .ci-lbl { font-family: var(--hud, monospace); font-size: .7rem; letter-spacing: .16em; }
.ci-fiche[data-forme="holo"] .ci-note, .ci-fiche[data-forme="holo"] .ci-alerte,
.ci-fiche[data-forme="holo"] .ci-butin, .ci-fiche[data-forme="holo"] .ci-verrou { border-radius: 0; }
/* ⚠️ Le filigrane reste DANS la boîte (right/bottom positifs) : la fiche
   défile, et un décor posé en négatif lui fabriquait 10 px de défilement
   horizontal fantôme — mesuré au navigateur. */
.ci-filigrane {
  position: absolute; right: 8px; bottom: 4px; font-size: 8rem;
  line-height: 1; opacity: .06; pointer-events: none; filter: saturate(0);
  z-index: 0;
}

/* ─── TUILES (vols, trous de ver) — les tuiles de données ───────────
   Chaque ligne du détail devient une tuile : l'étiquette en petit, la
   valeur en grand — et les textes sont volontairement plus gros (règle
   « occuper la largeur »). Les notes gardent toute la largeur. */
.ci-fiche[data-forme="tuiles"] .ci-verdict { border-radius: 999px; }
.ci-fiche[data-forme="tuiles"] .ci-detail {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px; align-items: stretch;
}
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-row {
  display: flex; flex-direction: column; gap: 3px; justify-content: flex-start;
  border: 1px solid var(--border, #162040); border-radius: 9px;
  padding: 9px 12px; margin: 0;
  background: color-mix(in srgb, var(--ci-teinte, #1e3060) 5%, rgba(255, 255, 255, .02));
}
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-row + .ci-row { border-top: 1px solid var(--border, #162040); }
.ci-fiche[data-forme="tuiles"] .ci-val { font-size: .98rem; }
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-note,
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-alerte,
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-butin,
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-verrou,
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-chrono,
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-sub,
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-finds,
.ci-fiche[data-forme="tuiles"] .ci-detail > .ci-table-wrap { grid-column: 1 / -1; margin-top: 2px; margin-bottom: 2px; }
/* Flag `champDebrisNomme` : sous le nom du bâtiment criblé, sa capacité
   juste après l'impact. Sans l'interrupteur, aucune balise ne porte cette
   classe — la règle ne peint rien. Plancher de lisibilité : 11 px. */
.ci-debris-cap {
  display: block; margin-top: 2px;
  font-size: var(--badge-font-size, 11px); color: #fca5a5;
}

/* ─── TELEX (Ligne d'Horizon) — la dépêche imprimée à bord ──────────
   Caractères de machine, papier à picots (les deux colonnes de trous),
   verdict en ligne inversée, et la mention de fin de transmission. */
.ci-fiche[data-forme="telex"] {
  font-family: var(--mono, monospace);
  border-radius: 0; border-inline: 0;
  border-block: 2px dashed var(--border2, #1e3060);
  padding-inline: 40px;
  background:
    radial-gradient(circle at 11px 13px, var(--bg, #050810) 4.5px, transparent 5.5px) left top / 22px 27px repeat-y,
    radial-gradient(circle at 11px 13px, var(--bg, #050810) 4.5px, transparent 5.5px) right top / 22px 27px repeat-y,
    #0b1120;
}
.ci-fiche[data-forme="telex"] .ci-fiche-tete {
  border-bottom-style: dashed;
}
.ci-fiche[data-forme="telex"] .ci-fiche-nat { letter-spacing: .3em; }
.ci-fiche[data-forme="telex"] .ci-fiche-titre {
  text-transform: uppercase; font-size: .92rem; letter-spacing: .04em;
}
.ci-fiche[data-forme="telex"] .ci-verdict {
  display: block; text-align: center; border: 0; border-radius: 0;
  background: color-mix(in srgb, var(--ci-v, var(--ci-teinte, #8aa4c8)) 85%, #0b1120);
  color: #050810; letter-spacing: .18em; font-size: .88rem; padding: 5px 10px;
}
.ci-fiche[data-forme="telex"] .ci-verdict::before { content: '>> '; }
.ci-fiche[data-forme="telex"] .ci-verdict::after { content: ' <<'; }
.ci-fiche[data-forme="telex"] .ci-lbl { text-transform: uppercase; }
.ci-fiche[data-forme="telex"] .ci-row + .ci-row { border-top-style: dashed; }
.ci-fiche[data-forme="telex"] .ci-note, .ci-fiche[data-forme="telex"] .ci-alerte,
.ci-fiche[data-forme="telex"] .ci-butin, .ci-fiche[data-forme="telex"] .ci-verrou {
  border-radius: 0; border-style: dashed; background: transparent;
}
.ci-fiche[data-forme="telex"] .ci-detail-actions { border-top-style: dashed; }
.ci-fiche[data-forme="telex"] .ci-mini { border-radius: 0; font-family: var(--mono, monospace); }
.ci-telex-fin {
  margin-top: 12px; text-align: center; color: var(--text3, #6b8ab0);
  font-size: .7rem; letter-spacing: .22em;
}

/* ─── Téléphone : les peaux qui changent la GÉOMÉTRIE se replient ───
   ⚠️ DEUX DÉCLENCHEURS, ET IL FAUT LES DEUX (règle de la table de
   commandement, juste au-dessus) : la largeur réelle de la fenêtre ne
   voit pas `body.mobile-mode`, et la classe ne déclenche aucune media
   query. Le billet rend son talon au flux (horizontal, pleine largeur),
   le radar repasse en une colonne, le télex rend ses marges à picots. */
body.mobile-mode .ci-fiche[data-forme="billet"] .ci-detail { display: block; }
body.mobile-mode .ci-fiche[data-forme="billet"] .ci-detail > .ci-chrono {
  flex-direction: row; width: auto; padding: 8px 0 0;
  border-left: 0; border-top: 2px dashed color-mix(in srgb, var(--ci-teinte, #1e3060) 55%, #2a3c66);
  margin-top: 8px;
}
body.mobile-mode .ci-fiche[data-forme="billet"] .ci-detail > .ci-chrono::after { content: none; }
body.mobile-mode .ci-fiche[data-forme="radar"] { display: block; }
body.mobile-mode .ci-fiche[data-forme="radar"] .ci-scope { margin: 0 auto 8px; width: 132px; }
body.mobile-mode .ci-fiche[data-forme="radar"] .ci-verdict {
  display: block; width: max-content; margin: 0 auto 12px;
}
body.mobile-mode .ci-fiche[data-forme="telex"] { padding-inline: 30px; }
body.mobile-mode .ci-fiche[data-forme="tampon"] .ci-verdict { font-size: 1.3rem; }
body.mobile-mode .ci-sceau { width: 64px; height: 64px; right: 12px; }
@media (max-width: 780px) {
  .ci-fiche[data-forme="billet"] .ci-detail { display: block; }
  .ci-fiche[data-forme="billet"] .ci-detail > .ci-chrono {
    flex-direction: row; width: auto; padding: 8px 0 0;
    border-left: 0; border-top: 2px dashed color-mix(in srgb, var(--ci-teinte, #1e3060) 55%, #2a3c66);
    margin-top: 8px;
  }
  .ci-fiche[data-forme="billet"] .ci-detail > .ci-chrono::after { content: none; }
  .ci-fiche[data-forme="radar"] { display: block; }
  .ci-fiche[data-forme="radar"] .ci-scope { margin: 0 auto 8px; width: 132px; }
  .ci-fiche[data-forme="radar"] .ci-verdict { display: block; width: max-content; margin: 0 auto 12px; }
  .ci-fiche[data-forme="telex"] { padding-inline: 30px; }
  .ci-fiche[data-forme="tampon"] .ci-verdict { font-size: 1.3rem; }
  .ci-sceau { width: 64px; height: 64px; right: 12px; }
}


/* ═══════════════════════════════════════════════════════════════════
   JOURNAL DES VERSIONS (changelog) — panneau ouvert par la pastille
   de version du bandeau, à côté de l'heure.
   ═══════════════════════════════════════════════════════════════════
   Le panneau réutilise l'anatomie .dsh-modal (header / body). Ne vivent
   ici que la mise en forme des ENTRÉES : le titre de version, et la
   ligne « [Étiquette] texte ».

   Les étiquettes sont des PASTILLES DE COULEUR à largeur fixe. La
   largeur fixe n'est pas cosmétique : sans elle, « [Correctif] » et
   « [Nouveau] » n'ont pas la même longueur et les textes démarrent en
   escalier, ce qui rend une liste de quinze lignes pénible à parcourir.

   Les couleurs reprennent les états systémiques du design system
   (§21.4.2) SAUF pour Interface, qui prend --accent2 : le bleu info
   (#2563eb) est trop sombre pour un fond de modale sombre, alors que le
   bleu ciel de l'interface se lit sans effort. */

/* v0.0.26 : le contournement de superposition qui vivait ici (z-index
   1000) a été retiré. Le défaut qu'il corrigeait — une `.dsh-modal` qui
   s'ouvre DERRIÈRE les deux bandeaux du haut — était bien « plus large,
   il concerne toute .dsh-modal », comme le disait son propre commentaire :
   il est désormais corrigé à la racine, dans les jetons du §21.3.5. */

.changelog-attente {
  color: var(--text2, #6b8ab0);
  font-size: .85rem;
  text-align: center;
  padding: var(--space-6, 24px) 0;
}

.changelog-version + .changelog-version {
  margin-top: 26px;
  border-top: 1px solid var(--border, #162040);
  padding-top: 18px;
}

.changelog-version-titre {
  font-family: var(--hud);
  font-size: 1rem;
  letter-spacing: .08em;
  color: var(--accent2, #0ea5e9);
  margin: 0 0 12px;
  display: flex;
  align-items: baseline;
  gap: .6rem;
}

.changelog-version-date {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: 0;
  color: var(--text2, #6b8ab0);
}

/* v0.0.7 — le compte à rebours d'une entrée du
   journal encore SOUS TIMER : même habillage que le badge de version de la
   fenêtre des nouveautés, pour que les deux se répondent. */
.changelog-version-timer {
  font-family: var(--mono); font-size: .68rem; letter-spacing: 0;
  color: var(--gold, #eab308);
  border: 1px solid rgba(255, 200, 100, .35); border-radius: 4px;
  padding: 0 .35rem; white-space: nowrap;
}

.changelog-lignes {
  list-style: none;
  margin: 0;
  padding: 0;
}

.changelog-ligne {
  display: flex;
  align-items: baseline;
  gap: .7rem;
  padding: .32rem 0;
  font-size: .85rem;
  line-height: 1.55;
  color: var(--text, #c8d6f0);
}

.changelog-tag {
  /* Calée sur la plus longue étiquette, « [Équilibrage] » : 13 caractères
     de chasse fixe à .72rem, soit ~5,6 rem. 6,2 rem laisse la marge sans
     creuser un fossé entre l'étiquette et son texte. */
  flex: 0 0 6.2rem;
  font-family: var(--mono);
  font-size: .72rem;
  white-space: nowrap;
}

/* Sur mobile, 8,5 rem de pastille + le texte ne tiennent pas côte à côte :
   la ligne passe en deux étages plutôt que d'écraser le texte sur trois
   caractères de large. */
@media (max-width: 620px) {
  .changelog-ligne { flex-direction: column; gap: .1rem; }
  .changelog-tag   { flex: none; }
}

.changelog-tag-correctif   { color: var(--color-warning-fg, #ea580c); }
.changelog-tag-nouveau     { color: var(--color-success-fg, #22c55e); }
.changelog-tag-interface   { color: var(--accent2, #0ea5e9); }
.changelog-tag-equilibrage { color: var(--turquoise, #2dd4bf); }
.changelog-tag-technique   { color: var(--text2, #6b8ab0); }
.changelog-tag-reference   { color: var(--text2, #6b8ab0); }
.changelog-tag-autre       { color: var(--text2, #6b8ab0); }

/* LES RUBRIQUES DU JOURNAL — « L'essentiel » en tête, ouvert ;
   « Autres MAJ » en dessous, replié dans un <details> que le joueur déplie.
   Les deux titres partagent la même typographie : ils nomment deux moitiés
   d'une même version, pas deux versions. Tailles au-dessus du plancher du
   jeu (0,79 rem, soit 11 px sur une racine à 14 px). */
.changelog-rubrique {
  font-family: var(--hud);
  font-size: .8rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  margin: 0 0 4px;
}
.changelog-rubrique-essentiel { color: var(--gold, #eab308); }
.changelog-autres {
  margin-top: 12px;
  border-top: 1px dashed var(--border, #162040);
  padding-top: 6px;
}
/* Le triangle natif est remplacé par le nôtre : celui de Safari et celui de
   Chrome ne s'alignent pas pareil sur un titre en capitales. */
.changelog-autres-titre {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .4rem 0;
  list-style: none;
  cursor: pointer;
  user-select: none;
  color: var(--text2, #6b8ab0);
}
.changelog-autres-titre::-webkit-details-marker { display: none; }
.changelog-autres-titre::before {
  content: '▸';
  display: inline-block;
  transition: transform .15s ease;
}
.changelog-autres[open] > .changelog-autres-titre::before { transform: rotate(90deg); }
.changelog-autres-titre:hover { color: var(--text, #c8d6f0); }
.changelog-autres-n {
  font-family: var(--mono);
  font-size: .8rem;
  letter-spacing: 0;
  padding: 0 .45rem;
  /* Pas `--border` : trop sombre sur le fond de la modale, la pastille y
     perdait son contour au grand écran. */
  border: 1px solid rgba(148, 163, 184, .35);
  border-radius: 999px;
}

/* ═══════════════════════════════════════════════════════════════════
   MOUVEMENTS COMPACTS + FIL TEMPOREL
   ═══════════════════════════════════════════════════════════════════
   Une carte de mission prenait TOUTE la largeur de l'écran pour trois
   lignes de contenu : à deux missions en l'air on défilait déjà, et à
   quatorze (le cas du testeur) l'écran devenait une colonne interminable.

   Les cartes se rangent en GRILLE de tuiles compactes —
   `auto-fill` décide tout seul du nombre de colonnes selon la place, donc
   rien à ajuster par taille d'écran — et la barre pleine laisse la place
   à un FIL : un rail du départ à la cible, avec la vignette du vaisseau
   qui avance dessus.

   garde exactement son rendu d'origine, y compris le fil qui reste
   masqué bien que son balisage soit toujours produit. */

.mv-liste {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: 12px;
  align-items: start;
}

/* La tuile ne s'étire plus : elle tend vers le carré. Le contenu long
   (soutes, fret) reste lisible parce qu'il passe à la ligne au lieu de
   pousser la carte en largeur. */
.mv-item { padding: 12px 13px; }
.mv-item .mv-ligne { flex-wrap: wrap; gap: 4px 8px; }
.mv-ico { font-size: 1.15rem; }
.mv-titre { font-size: .92rem; }

/* Le compte à rebours passe SOUS l'en-tête plutôt qu'à sa droite : à 330 px
   de large, titre + badge + décompte sur une ligne se marchent dessus. */
.mv-head { flex-wrap: wrap; gap: 8px; }
.mv-cdr { margin-left: auto; text-align: right; }

/* La barre pleine s'efface, le fil prend sa place. */
.mv-barre { display: none; }
.mv-fil { display: none; }
.mv-fil {
  display: block;
  position: relative;
  height: 26px;
  margin: 12px 6px 10px;
}

.mv-fil-rail {
  position: absolute; left: 0; right: 0; top: 50%;
  height: 2px; margin-top: -1px; border-radius: 2px;
  background: rgba(110,130,170,.28);
}

/* Le chemin DÉJÀ PARCOURU, en clair : sans lui, la vignette flotte sur un
   rail uniforme et on ne voit plus d'où elle vient. */
.mv-fil-parcouru {
  position: absolute; left: 0; top: 50%;
  height: 2px; margin-top: -1px; border-radius: 2px;
  background: linear-gradient(90deg, rgba(52,211,153,.25), rgba(52,211,153,.85));
}
/* v0.0.9 — AU RETOUR, LA TRAÎNÉE PART DE LA CIBLE. La vignette revient vers
   la gauche : une traînée qui continuerait de pousser depuis la gauche la
   traverserait à contresens. On l'accroche donc à droite, et son dégradé se
   retourne avec elle. */
.mv-fil-parcouru.est-retour {
  left: auto; right: 0;
  background: linear-gradient(270deg, rgba(251,191,36,.25), rgba(251,191,36,.85));
}
/* Même principe pour la barre pleine (l'affichage sans le fil). */
.mv-barre-fill.est-retour {
  margin-left: auto;
  background: linear-gradient(270deg, rgba(251,191,36,.4), #fbbf24) !important;
}
/* L'heure de retour, discrète, à côté de la route. */
.mv-retour-prevu {
  font-size: .7rem; color: #93c5fd;
  border: 1px solid rgba(147,197,253,.35);
  border-radius: 4px; padding: 0 5px; margin-left: auto;
}

/* Les deux bornes : d'où l'on part, où l'on va. */
.mv-fil-borne {
  position: absolute; top: 50%; width: 7px; height: 7px; margin-top: -3.5px;
  border-radius: 50%; background: #0b1220;
  border: 1.5px solid rgba(140,165,205,.75);
}
.mv-fil-depart { left: -1px; }
.mv-fil-cible  { right: -1px; border-color: rgba(52,211,153,.9); }

/* LA VIGNETTE. `translateX(-50%)` la centre sur sa position — sans ça elle
   ne toucherait la cible qu'avec son bord gauche, et paraîtrait en retard
   d'une largeur d'icône sur tout le trajet.
   Aucune transition CSS : le tick l'écrit toutes les 250 ms, une
   interpolation par-dessus la ferait traîner derrière sa vraie position. */
.mv-fil-nef {
  position: absolute; top: 50%; left: 0;
  transform: translate(-50%, -50%);
  font-size: 1.05rem; line-height: 1;
  filter: drop-shadow(0 0 5px rgba(52,211,153,.5));
  pointer-events: none;
}
/* Au retour le vaisseau fait face à l'autre sens : il revient de la cible. */
.mv-fil-nef.est-retour { transform: translate(-50%, -50%) scaleX(-1); }

/* Écrans étroits : une seule colonne, et le fil garde toute sa longueur. */
@media (max-width: 700px) {
  .mv-liste { grid-template-columns: 1fr; }
}

/* ─── Les étapes d'un relevé de Précurseur (mv-etapes) ───────────────
   Liste dépliante dans la tuile de mouvement : le suivi astre par astre
   se consulte quand on le cherche, pas en permanence. Repliée elle tient
   sur une ligne et annonce déjà le compte. */
.mv-etapes { margin: 8px 0 2px; }
.mv-etapes-titre {
  cursor: pointer; list-style: none;
  font-size: .78rem; color: var(--text2, #6b8ab0);
  display: flex; align-items: center; gap: 8px;
  padding: 3px 0;
}
.mv-etapes-titre::-webkit-details-marker { display: none; }
/* Le chevron dit que ça se déplie — sans lui, le titre a l'air d'une
   simple ligne de texte et personne ne clique. */
.mv-etapes-titre::before { content: '▸'; font-size: .7rem; transition: transform .15s; }
.mv-etapes[open] .mv-etapes-titre::before { transform: rotate(90deg); }
.mv-etapes-titre:hover { color: var(--text, #c8d6f0); }
.mv-etapes-compte {
  margin-left: auto; font-family: var(--mono);
  font-size: .72rem; color: var(--text3, #2d4070);
}

.mv-etapes-liste {
  list-style: none; margin: 6px 0 0; padding: 0 0 0 6px;
  border-left: 1px solid rgba(110,130,170,.2);
  max-height: 190px; overflow-y: auto;   /* 15 planètes ne débordent pas la tuile */
}
.mv-etape {
  display: flex; align-items: baseline; gap: 7px;
  font-size: .78rem; line-height: 1.5;
  color: var(--text3, #2d4070);
}
.mv-etape-puce { flex: none; width: 1em; text-align: center; font-size: .7rem; }
.mv-etape-nom { flex: 1; min-width: 0; }

/* L'HEURE de chaque relevé, et le décompte du seul astre en cours.
   Chasse fixe et largeur fixe : sans ça les heures s'alignent en escalier
   d'une ligne à l'autre et la colonne cesse d'être comparable — or c'est
   précisément ce qu'on vient y lire, la cadence. */
.mv-etape-heure {
  /* 4,6 rem : « 15:47:23 » tient sans se couper. À 3,2 rem (avant les
     secondes) la colonne était trop étroite et l'heure débordait sur le
     décompte. */
  flex: 0 0 4.6rem; text-align: right;
  font-family: var(--mono); font-size: .72rem;
  color: var(--text3, #2d4070);
  font-variant-numeric: tabular-nums;
}
.mv-etape.est-fait .mv-etape-heure { color: var(--text2, #6b8ab0); }
.mv-etape-cdr {
  flex: 0 0 4.6rem; text-align: right;
  font-family: var(--mono); font-size: .72rem;
  color: var(--accent2, #0ea5e9);
  font-variant-numeric: tabular-nums;
}
.mv-etape-cdr.imminent { color: var(--color-success-fg, #22c55e); }

/* Sur une tuile étroite, l'heure et le décompte passent sous le nom
   plutôt que de l'écraser sur trois caractères. */
@media (max-width: 620px) {
  .mv-etape { flex-wrap: wrap; }
  .mv-etape-nom { flex: 1 1 100%; }
  .mv-etape-heure, .mv-etape-cdr { flex: none; }
}
.mv-etape.est-fait { color: var(--text, #c8d6f0); }
.mv-etape.est-fait .mv-etape-puce { color: var(--color-success-fg, #22c55e); }
/* L'astre en cours de relevé : c'est là que le vaisseau travaille. */
.mv-etape.est-en-cours { color: var(--accent2, #0ea5e9); }
.mv-etape.est-en-cours .mv-etape-puce { color: var(--accent2, #0ea5e9); }

/* ═══════════════════════════════════════════════════════════════════
   SAUT D'UN VAISSEAU MÈRE À L'AUTRE
   ═══════════════════════════════════════════════════════════════════
   En haut AU CENTRE de la carte, hors du chemin : l'encart d'aide est en
   haut à gauche, le bouton « Retour » en haut à droite. C'est la seule
   bande libre, et c'est là que l'œil revient entre deux déplacements.

   `pointer-events` : le conteneur laisse passer les clics (on doit
   pouvoir glisser la carte derrière), seuls les boutons les captent. */
#map-nav-vm {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 12;
  display: flex;
  align-items: stretch;
  gap: 4px;
  padding: 4px;
  border-radius: 8px;
  background: rgba(5, 8, 16, 0.85);
  border: 1px solid var(--border, #1f2937);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
}
#map-nav-vm > * { pointer-events: auto; }

.nav-vm-fleche {
  min-width: 34px;
  border: 1px solid var(--border2, #2d4a6e);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--text2, #6b8ab0);
  font-size: .8rem;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.nav-vm-fleche:hover {
  background: rgba(14, 165, 233, .14);
  color: var(--text, #c8d6f0);
  border-color: var(--accent2, #0ea5e9);
}

/* Le bloc central est CLIQUABLE lui aussi : il recentre sur le système
   courant. C'est le geste qu'on fait après s'être perdu en glissant la
   carte — sans lui, il faudrait faire un aller-retour avec les flèches. */
.nav-vm-cible {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 170px;
  padding: 3px 12px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
  line-height: 1.15;
}
.nav-vm-cible:hover { background: rgba(255, 255, 255, .04); }
.nav-vm-nom {
  font-size: .78rem;
  color: var(--text, #c8d6f0);
  white-space: nowrap;
}
.nav-vm-rang {
  font-family: var(--mono);
  font-size: .66rem;
  color: var(--text3, #2d4070);
}

/* Écrans étroits : l'encart d'aide passe au-dessus, on décale la barre
   sous lui plutôt que de les superposer. */
@media (max-width: 820px) {
  #map-nav-vm { top: auto; bottom: 14px; }
  .nav-vm-cible { min-width: 130px; }
}

/* ─── v0.0.9 — BOUTONS DE ZOOM DE LA CARTE ──────────────────────────
   EN BAS À DROITE, le seul coin encore libre : le bandeau d'aide tient le
   haut à gauche, le bouton « ← Retour » le haut à droite, les flèches
   entre Vaisseaux Mères le haut au centre et la légende le bas à gauche.
   C'est aussi là que toutes les cartes du monde les mettent.

   ⚠️ REMONTÉS DE 50 px, ET PAS COLLÉS AU BAS DU CADRE : l'ÉCHELLE DE
   DISTANCES (« 5 000 al ») est dessinée SUR LE CANVAS à 26 px du bas et
   22 px de la droite — donc exactement là. À `bottom: 12px`, les boutons
   lui passaient par-dessus et coupaient sa graduation de droite. */
.map-zoom-btns {
  position: absolute;
  right: 12px;
  bottom: 50px;
  z-index: 11;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 4px;
  border-radius: 8px;
  background: rgba(5, 8, 16, 0.85);
  border: 1px solid var(--border, #1f2937);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.map-zoom-btn {
  width: 38px;
  height: 38px;
  border: 1px solid var(--border2, #2d4a6e);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--text2, #6b8ab0);
  font-family: var(--hud, sans-serif);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  /* Maintenir le bouton enchaîne les crans de zoom : sans ceci, l'appui
     prolongé sélectionne le « + » comme un bout de texte, et sur mobile
     il ouvre le menu contextuel du navigateur par-dessus la carte. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
  transition: background .15s, color .15s, border-color .15s;
}
.map-zoom-btn:hover:not(:disabled) {
  background: rgba(14, 165, 233, .14);
  color: var(--text, #c8d6f0);
  border-color: var(--accent2, #0ea5e9);
}
.map-zoom-btn:active:not(:disabled) {
  background: rgba(14, 165, 233, .24);
}
/* Butée atteinte (zoom mini ou maxi) : le bouton se grise et cesse de
   répondre, plutôt que de laisser cliquer dans le vide. */
.map-zoom-btn:disabled {
  opacity: .35;
  cursor: default;
}

/* ⚠️ TÉLÉPHONE : LES BOUTONS DISPARAISSENT, ET C'EST VOULU. Sous 620 px,
   la colonne des Vaisseaux Mères mange la droite de l'écran et la légende
   (200 px) occupe à elle seule presque toute la largeur restante de la
   carte : les boutons se poseraient FORCÉMENT par-dessus son texte, où
   qu'on les mette. Or c'est exactement le format où ils servent le moins —
   un écran tactile zoome au PINCEMENT à deux doigts, geste que ce
   changement ne touche pas. On préfère donc les retirer là plutôt que
   d'abîmer la légende pour un doublon. */
@media (max-width: 620px) {
  .map-zoom-btns { display: none; }
}

/* v0.0.3 — annonce de nouvelle version : lien vers le journal complet.
   La fenêtre d'annonce ne montre QUE la version du jour ; ce bouton reste
   la porte vers l'historique pour qui veut le relire. */
.changelog-tout {
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  text-align: center;
}
.changelog-tout-btn {
  background: none;
  border: 1px solid rgba(124, 58, 237, 0.5);
  color: var(--color-text-secondary, #8da4c4);
  font: inherit;
  font-size: 0.82rem;
  padding: 7px 14px;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.changelog-tout-btn:hover {
  background: rgba(124, 58, 237, 0.14);
  color: var(--color-text, #e6eefc);
}

/* ══════════════════════════════════════════════════════════════════
   v0.0.6 — INVENTAIRE
   ══════════════════════════════════════════════════════════════════
   La pastille reprend TRAIT POUR TRAIT celle du mode vacances, sa
   voisine immédiate : deux boutons côte à côte qui ne se ressemblent
   pas se lisent comme deux natures de choses, alors que ce sont deux
   points d'entrée du même rang. */
.inv-pastille {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  background: #1a2540; border: 1px solid #324569; border-radius: 999px;
  color: #cbd6ea; cursor: pointer;
  font-family: var(--mono); font-size: .75rem;
  transition: all .15s;
}
.inv-pastille:hover { background: #243049; border-color: #4a6294; }
.inv-pastille-ico { font-size: .9rem; line-height: 1; }
.inv-pastille-txt { letter-spacing: .04em; }
/* Le compteur des choses possédées. Discret : c'est un inventaire, pas
   une alerte — rien n'y presse, contrairement à la cloche. */
.inv-pastille-badge {
  min-width: 1.15em; padding: 0 .3em;
  background: var(--turquoise); color: #06121c;
  border-radius: 999px; font-size: .62rem; font-weight: 700;
  line-height: 1.5; text-align: center;
}

.inv-box { max-width: 640px; width: 92vw; }
.inv-body { max-height: 70vh; overflow-y: auto; }
.inv-section { margin-bottom: 1.4rem; }
.inv-section:last-child { margin-bottom: 0; }
.inv-titre {
  font-family: var(--hud); font-size: .62rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--turquoise);
  margin: 0 0 .45rem; padding-bottom: .25rem;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: baseline; justify-content: space-between; gap: .6rem;
}
.inv-total {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .02em;
  color: var(--gold); text-transform: none;
}
.inv-intro { font-size: .72rem; color: var(--text3); margin: 0 0 .6rem; line-height: 1.5; }
.inv-vide, .inv-vide-sec { font-size: .75rem; color: var(--text3); padding: .5rem 0; }
.inv-vide { text-align: center; padding: 1.6rem 0; }
.inv-erreur { color: var(--red, #f87171); }

.inv-liste { display: flex; flex-direction: column; gap: .35rem; }
/* Une ligne = nom, valeur, jauge, note. La jauge occupe toute la largeur
   sur sa propre rangée : coincée entre deux colonnes elle ferait deux
   pixels et n'apprendrait rien. */
.inv-ligne {
  display: grid; grid-template-columns: 1fr auto; gap: .2rem .6rem;
  align-items: baseline;
  padding: .4rem .55rem;
  background: rgba(255,255,255,.02); border: 1px solid var(--border); border-radius: 5px;
}
.inv-ligne-nom { font-size: .78rem; color: var(--text1); }
.inv-ligne-val { font-family: var(--mono); font-size: .74rem; color: var(--gold); text-align: right; }
.inv-ligne-val.est-pleine { color: var(--red, #f87171); }
.inv-ligne-note { grid-column: 1 / -1; font-size: .64rem; }
.inv-sourdine { color: var(--text3); }
.inv-alerte { color: var(--red, #f87171); font-size: .62rem; margin-left: .3rem; }
.inv-jauge {
  grid-column: 1 / -1; height: 3px; border-radius: 2px;
  background: rgba(255,255,255,.07); overflow: hidden;
}
.inv-jauge i { display: block; height: 100%; background: var(--turquoise); }

.inv-objets { display: flex; flex-wrap: wrap; gap: .4rem; }
.inv-objet {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .4rem .6rem;
  background: rgba(45,212,191,.06); border: 1px solid var(--border); border-radius: 5px;
  color: var(--text1); font-size: .74rem; cursor: pointer;
  transition: background .12s, border-color .12s;
}
.inv-objet:hover { background: rgba(45,212,191,.16); border-color: var(--turquoise); }
.inv-objet-qte { font-family: var(--mono); font-size: .66rem; color: var(--gold); }

/* ═══════════════════════════════════════════════════════════════════
   v0.0.8 — OFFICIERS PAR TYPE (flag officiersParType)
   ═══════════════════════════════════════════════════════════════════
   La GRILLE demandée par Loryan : une ligne par vaisseau d'expédition,
   une case par rang (Bronze → Platine). La première colonne porte le
   vaisseau, les quatre suivantes les cases. Une case vide reste posée,
   éteinte — voir ce qui manque fait partie de l'information. */
.inv-off-grille{display:grid;grid-template-columns:minmax(96px,auto) repeat(4,minmax(0,1fr));
  gap:6px;align-items:stretch}
.inv-off-entete{font-size:.64rem;color:var(--text2);text-align:center;
  letter-spacing:.04em;padding:2px 0;align-self:end}
.inv-off-type{display:flex;align-items:center;gap:6px;font-size:.75rem;
  color:var(--text);font-weight:600;padding:4px 2px;white-space:nowrap}
.inv-off-case{border:1px solid var(--border);border-radius:7px;padding:6px 4px;
  text-align:center;background:rgba(255,255,255,.02);min-height:52px;
  display:flex;flex-direction:column;justify-content:center;gap:3px}
.inv-off-case.est-vide{opacity:.32;font-size:.86rem}
.inv-off-case.est-actif{border-color:rgba(45,212,191,.55);background:rgba(45,212,191,.07)}
.inv-off-rang{font-size:.74rem;color:var(--text)}
.inv-off-rang b{color:var(--gold)}
/* Le compte à rebours, GROS et coloré — demande explicite : « un compteur
   de temps comme ce que tu mets déjà mais en plus visible ». Orange sous
   les 24 h : c'est le moment de repartir en chasse. */
.inv-off-duree{font-family:var(--mono,monospace);font-size:.78rem;font-weight:700;
  color:#2dd4bf;white-space:nowrap}
.inv-off-duree.bientot{color:#fbbf24}
@media (max-width:640px){
  .inv-off-grille{grid-template-columns:repeat(4,minmax(0,1fr))}
  .inv-off-type{grid-column:1 / -1;padding-top:8px}
}
/* Le gris des textes d'explication, signalé ILLISIBLE par Loryan : sous le
   flag (classe inv-v2 posée sur la boîte), tout le panneau passe du gris
   sourd (--text3) au gris lisible (--text2), intros et légendes comprises. */
.inv-v2 .inv-intro,.inv-v2 .inv-vide-sec,.inv-v2 .inv-sourdine,
.inv-v2 .inv-ligne-note{color:var(--text2,#8da4c4)}

/* v0.0.10 — le ratio de récolte en cours, dans la carte de mouvement.
   Discret et monospace : c'est une donnée brute qu'on lit, pas un message.
   Aucun texte ne l'accompagne — la mécanique se découvre. */
.mv-ratio{font-family:var(--mono,monospace);font-size:.68rem;color:#7dd3fc;
  background:rgba(125,211,252,.10);border:1px solid rgba(125,211,252,.25);
  border-radius:6px;padding:2px 7px;white-space:nowrap;flex-shrink:0}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.20 — LA FICHE D'UN VAISSEAU AU SURVOL
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : « une fiche type infographie pour le Récolteur…
   intégrée dans le jeu quand je passe la souris au-dessus du vaisseau,
   donc il faut que cette infobulle avec la fiche soit lisible et
   design. »
   TROIS PARTIS PRIS DE LECTURE.
   1. Le CHIFFRE avant le MOT : chaque donnée est un grand nombre avec son
      intitulé en petit dessous. L'œil prend le nombre, le mot suit.
   2. Ce qui se COMPARE devient une barre (les cinq paliers de durée du
      Récolteur) : « 90 % » ne dit rien seul, cinq barres disent tout.
   3. Un fond OPAQUE, pas translucide. Une fiche posée sur une grille de
      tuiles doit se lire d'un coup ; la transparence, c'est joli et c'est
      illisible.
   Le balisage n'existe que sous le flag (aucun écouteur posé sans lui) :
   rien à masquer ici. */
.ship-fiche {
  position: fixed; z-index: 9000; width: 380px; max-width: calc(100vw - 16px);
  max-height: calc(100vh - 16px); overflow: hidden;
  border: 1px solid rgba(110,130,170,.38); border-radius: 12px;
  background: #0c1222;
  box-shadow: 0 18px 48px rgba(0,0,0,.66), 0 0 0 1px rgba(0,0,0,.4);
  opacity: 0; transform: translateY(4px) scale(.985);
  transition: opacity .13s ease, transform .13s ease;
  /* Elle ne reçoit jamais la souris — posé aussi en JS, redit ici pour que
     la règle soit visible du côté où l'on lit la mise en page. */
  pointer-events: none;
}
.ship-fiche.est-visible { opacity: 1; transform: none; }

/* ═══ Fenêtres flottantes FIXÉES après trois secondes de survol ══════
   Demande de Loryan : « si je reste 5 secondes sur l'Extracteur de Ferrith,
   sa fenêtre flottante se fixe », puis « Passe à 3 secondes au lieu de 5 ».
   Le module `core/epingle.js` pose ces classes et ces éléments — lui seul.
   Elles servent à la fiche (`.ship-fiche`) comme au détail des
   ressources (`.psi-pop`), toutes deux en position fixe.

   La jauge : elle se remplit pendant le survol, pour que le geste se voie
   venir. Sa durée est posée par le module (une seule source du délai). */
.epingle-jauge {
  position: absolute; left: 0; bottom: 0; z-index: 3;
  height: 3px; width: 0;
  background: #fbbf24; box-shadow: 0 0 6px rgba(251, 191, 36, .7);
  pointer-events: none;
  animation-name: epingle-jauge; animation-timing-function: linear;
  animation-fill-mode: forwards;
}
@keyframes epingle-jauge { from { width: 0; } to { width: 100%; } }

/* Fixée : un liseré, et elle défile si son contenu dépasse l'écran — elle
   reçoit enfin la souris, autant qu'elle serve à lire jusqu'en bas. */
.ship-fiche.est-epinglee, .psi-pop.est-epinglee {
  outline: 2px solid #fbbf24; outline-offset: -1px;
  overflow-y: auto;
}
/* La barre du haut : ce qui se passe, et comment en sortir. Collée en haut
   quand la fenêtre défile. */
.epingle-barre {
  position: sticky; top: 0; z-index: 4;
  display: flex; align-items: center; gap: .5rem;
  padding: .35rem .5rem .35rem .75rem;
  background: #1f1805; border-bottom: 1px solid rgba(251, 191, 36, .45);
  color: #fde68a; font-size: .86rem; line-height: 1.3;
}
.epingle-titre { font-weight: 600; white-space: nowrap; }
/* ⚠️ LE PLANCHER DE LISIBILITÉ : --badge-font-size (11 px), jamais moins. */
.epingle-aide { flex: 1 1 auto; min-width: 0; color: rgba(253, 230, 138, .78); font-size: var(--badge-font-size, 11px); }
.epingle-fermer {
  flex: 0 0 auto; width: 26px; height: 26px; padding: 0; border-radius: 6px;
  border: 1px solid rgba(251, 191, 36, .5); background: rgba(251, 191, 36, .12);
  color: #fde68a; font-size: .95rem; line-height: 1; cursor: pointer;
}
.epingle-fermer:hover { background: rgba(251, 191, 36, .28); }
/* Une fenêtre détachée (plusieurs fixées à la fois) se tire par sa barre. */
.est-detachee > .epingle-barre { cursor: grab; touch-action: none; user-select: none; }
.est-detachee > .epingle-barre.est-glissee { cursor: grabbing; }
.est-detachee > .epingle-barre .epingle-fermer { cursor: pointer; }

/* ─── Le bandeau : l'illustration du vaisseau, son nom, son stock ─── */
.sf-tete {
  display: flex; align-items: center; gap: .6rem;
  padding: .7rem .85rem; min-height: 74px;
  border-bottom: 1px solid rgba(110,130,170,.22);
  background: linear-gradient(90deg, rgba(52,211,153,.10), transparent);
}
/* L'image passe SOUS un dégradé qui s'ouvre vers la droite : le texte reste
   sur du sombre plein à gauche, l'illustration respire à droite. */
.sf-tete.a-illustration { background-size: cover; background-position: center right; }
.sf-tete-txt { min-width: 0; flex: 1; }
.sf-cat {
  font-family: var(--hud, monospace); font-size: .58rem; letter-spacing: .12em;
  text-transform: uppercase; color: #34d399;
}
.sf-nom { font-size: 1.06rem; font-weight: 700; color: #f2f7ff; line-height: 1.2; margin-top: 2px; }
.sf-stock {
  flex: none; font-family: var(--mono, monospace); font-size: .84rem; font-weight: 700;
  color: #cfe0ff; background: rgba(10,15,30,.72);
  border: 1px solid rgba(110,130,170,.35); border-radius: 999px; padding: 2px 9px;
}

.sf-corps { padding: .7rem .85rem .85rem; max-height: calc(100vh - 110px); overflow-y: auto; }
.sf-desc { font-size: .78rem; line-height: 1.45; color: #a9bcd8; margin-bottom: .7rem; }

.sf-bloc { margin-top: .7rem; }
.sf-bloc:first-child { margin-top: 0; }
.sf-bloc-titre {
  font-family: var(--hud, monospace); font-size: .59rem; letter-spacing: .1em;
  text-transform: uppercase; color: #7f96b8; margin-bottom: .4rem;
  padding-bottom: .25rem; border-bottom: 1px solid rgba(110,130,170,.16);
}

/* ─── Le coût, en vignettes de ressources ─── */
.sf-ligne-cout { display: flex; align-items: center; justify-content: space-between; gap: .6rem; }
.sf-cout { display: flex; gap: .55rem; flex-wrap: wrap; }
.sf-cout-item {
  display: inline-flex; align-items: center; gap: .25rem;
  font-size: .82rem; color: #dbe7fb;
}
.sf-cout-item .res-mini { width: 16px; height: 16px; vertical-align: middle; }
/* Ce qu'on n'a pas en stock se voit tout de suite : c'est la seule chose
   qui empêche de cliquer « Produire ». */
.sf-cout-item.sf-manque { color: #f87171; }
.sf-duree { flex: none; font-family: var(--mono, monospace); font-size: .78rem; color: #a9bcd8; }

/* ─── Les grands chiffres ─── */
.sf-stats { display: flex; gap: .5rem; }
.sf-stat {
  flex: 1; min-width: 0; text-align: center;
  background: rgba(255,255,255,.035); border: 1px solid rgba(110,130,170,.18);
  border-radius: 8px; padding: .42rem .3rem .38rem;
}
/* LA donnée du vaisseau, celle qu'on vient chercher : elle est verte et
   plus grosse que les autres. Une seule par bloc, sinon plus rien ne
   ressort. */
.sf-stat.est-vedette { background: rgba(52,211,153,.10); border-color: rgba(52,211,153,.32); }
.sf-stat-val {
  font-family: var(--mono, monospace); font-size: 1.02rem; font-weight: 700; color: #eaf2ff;
  line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sf-stat.est-vedette .sf-stat-val { color: #6ee7b7; font-size: 1.14rem; }
.sf-stat-un { font-size: .7em; opacity: .8; margin-left: 1px; }
.sf-sur { font-size: .78em; color: #7f96b8; }
.sf-stat-lbl { font-size: .58rem; color: #8ea4c4; line-height: 1.2; margin-top: 3px; }

/* ─── Le débit du Récolteur, ressource par ressource ─── */
.sf-recolte {
  display: flex; gap: .8rem; justify-content: center;
  background: rgba(255,255,255,.03); border: 1px solid rgba(110,130,170,.18);
  border-radius: 8px; padding: .45rem .3rem; margin-bottom: .45rem;
}
.sf-recolte-item { display: inline-flex; align-items: center; gap: .3rem; font-size: .95rem; color: #eaf2ff; }
.sf-recolte-item .res-mini { width: 19px; height: 19px; }
.sf-par-sec { font-size: .62rem; color: #8ea4c4; margin-left: 1px; }

/* ─── Les barres comparatives (paliers de durée) ─── */
.sf-bars { display: flex; flex-direction: column; gap: 3px; }
.sf-bar { display: flex; align-items: center; gap: .5rem; font-size: .72rem; }
.sf-bar-lbl { flex: none; width: 26px; color: #a9bcd8; font-family: var(--mono, monospace); }
.sf-bar-rail { flex: 1; height: 7px; border-radius: 999px; background: rgba(110,130,170,.16); overflow: hidden; }
.sf-bar-fill { display: block; height: 100%; border-radius: 999px; background: rgba(52,211,153,.45); }
/* Le meilleur palier est celui qu'on veut faire remarquer. */
.sf-bar.est-plein .sf-bar-fill { background: #34d399; }
.sf-bar.est-plein .sf-bar-val { color: #6ee7b7; }
.sf-bar-val { flex: none; width: 38px; text-align: right; color: #a9bcd8; font-family: var(--mono, monospace); }

.sf-note { font-size: .68rem; color: #8ea4c4; margin-top: .4rem; line-height: 1.4; font-style: italic; }

/* ─── v0.0.21 : la BARRE D'OBJECTIF du plafond par mission ───────────
   Demande de Loryan : « comme une barre d'objectif ». Elle vit DANS la
   tuile du plafond : le grand chiffre reste le plafond d'aujourd'hui, la
   barre dessous dit le chemin vers le suivant. Or, pas vert : c'est un
   objectif de carrière, pas une jauge de soute — les deux couleurs ne
   doivent pas se confondre dans la même fiche. */
.sf-stat-objectif { text-align: center; }
.sf-obj-rail {
  height: 5px; border-radius: 999px; background: rgba(110,130,170,.16);
  overflow: hidden; margin-top: .35rem;
}
.sf-obj-fill {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, rgba(251,191,36,.45), #fbbf24);
}
.sf-obj-txt {
  font-size: .58rem; color: #8ea4c4; margin-top: 3px;
  font-family: var(--mono, monospace); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sf-obj-gain { color: #fbbf24; font-weight: 700; }

/* ─── Les prérequis ─── */
.sf-reqs { display: flex; flex-direction: column; gap: 2px; }
.sf-req { font-size: .74rem; color: #f87171; display: flex; align-items: baseline; gap: .35rem; }
.sf-req.est-ok { color: #6ee7b7; }
.sf-req-a { margin-left: auto; font-family: var(--mono, monospace); font-size: .68rem; color: #7f96b8; }

/* ─── L'APERÇU DES NIVEAUX SUIVANTS ─────────────────────────────────
   Demande de Loryan, sur le modèle du tableau d'OGame. Trois partis pris.

   1. LA LIGNE DU NIVEAU ACTUEL EST SURLIGNÉE, en vert comme chez OGame.
      C'est le point de repère : sans elle on ne sait pas d'où on part, et
      les colonnes d'écart n'ont plus de sens.
   2. DES CHIFFRES ALIGNÉS À DROITE, en tabular-nums. Une colonne de
      nombres ne se compare que si les unités sont l'une sous l'autre.
   3. LE TABLEAU DÉBORDE PLUTÔT QU'IL NE SE COMPRIME. Six colonnes dans
      380 px, ça finit en « 1.2… » partout ; le cadre défile donc
      horizontalement et chaque nombre reste entier. */
.sfp-cadre { overflow-x: auto; margin: 0 -.2rem; }
.sf-paliers {
  width: 100%; border-collapse: collapse;
  font-family: var(--mono, monospace); font-size: .64rem;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.sf-paliers th {
  font-weight: 600; color: #7f96b8; text-align: right;
  padding: 0 .3rem .25rem; border-bottom: 1px solid rgba(110,130,170,.22);
}
.sf-paliers td {
  text-align: right; padding: .16rem .3rem; color: #cfe0ff;
  border-bottom: 1px solid rgba(110,130,170,.07);
}
.sf-paliers tr:last-child td { border-bottom: 0; }
.sf-paliers .sfp-niv { text-align: left; color: #8ea4c4; }
.sf-paliers .sfp-val { color: #eaf2ff; font-weight: 700; }
/* La contrepartie (énergie consommée, eau, Noctium) se lit comme une
   dépense : elle ne doit jamais se confondre avec la colonne du gain. */
.sf-paliers .sfp-sec  { color: #fbbf24; }
.sf-paliers .sfp-cout { color: #a9bcd8; }
.sf-paliers .sfp-ec   { color: #8ea4c4; }
.sf-paliers .sfp-ec.est-plus  { color: #6ee7b7; }
.sf-paliers .sfp-ec.est-moins { color: #f87171; }

/* Le niveau d'aujourd'hui. */
.sf-paliers tr.est-courant td {
  background: rgba(52,211,153,.13);
  border-top: 1px solid rgba(52,211,153,.35);
  border-bottom: 1px solid rgba(52,211,153,.35);
}
.sf-paliers tr.est-courant .sfp-niv { color: #6ee7b7; font-weight: 700; }
/* Ce qui est déjà payé et attend dans la file : ni tout à fait acquis, ni
   tout à fait à venir. */
.sf-paliers tr.est-en-file td { background: rgba(125,211,252,.07); }
.sf-paliers tr.est-en-file .sfp-niv { color: #7dd3fc; }

/* ⚠️ L'HYPOTHÈSE SOUS LAQUELLE LE TABLEAU CHIFFRE, quand il y en a une :
   sans Connecteur, un extracteur ne touche pas le gisement et toute la
   colonne serait un espoir déçu. On garde le tableau — c'est l'outil pour
   décider jusqu'où monter — mais il dit à quelle condition il vaut. */
.sf-paliers + .sfp-note, .sfp-note { color: #fbbf24; font-style: normal; }

/* Sur un écran étroit, la fiche prend la largeur disponible et ses trois
   grands chiffres passent l'un sous l'autre plutôt que de se comprimer
   jusqu'à l'illisible. */
@media (max-width: 620px) {
  .ship-fiche { width: calc(100vw - 16px); }
  .sf-stats { flex-wrap: wrap; }
  .sf-stat { flex: 1 0 45%; }
}

/* ═══════════════════════════════════════════════════════════════════
   FICHE D'UN BÂTIMENT OU D'UNE RECHERCHE :
   LE PRIX PAR RESSOURCE, ET UN TEXTE PLUS GROS
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan, capture à l'appui : « ajouter le coût en chaque
   ressource sur la droite et grossir un peu le texte ». Le repère
   `.sf-lisible` n'est posé que sur ces deux fiches, les deux qui portent
   « Les niveaux suivants » (`sfHtmlBld`, `sfHtmlTech`, ship-fiche.js) :
   celles des vaisseaux et des défenses ont leur propre mise en page (trois
   tuiles de chiffres dans 380 px), et ne bougent pas.

   1. LA LARGEUR SUIT LE TABLEAU. Deux colonnes de prix pour un extracteur,
      jusqu'à six pour une recherche : aucune largeur fixe ne sert les deux.
      La fiche prend donc celle de son contenu, bornée par l'écran.
   2. ⚠️ ET LES LONGS TEXTES NE LA DICTENT PAS. Sans `contain: inline-size`,
      la description — un paragraphe entier sur une ligne, pour le calcul
      de largeur — pousserait la fiche jusqu'au bord de l'écran. Contenus,
      ils prennent la largeur que le tableau a fixée et reviennent à la
      ligne dedans.
   3. ⚠️ `min()` DANS LE PLANCHER DE LARGEUR : sous 396 px d'écran, un
      `min-width: 380px` nu passerait devant le `max-width` et pousserait la
      fiche hors de l'écran — c'est la règle du CSS quand les deux se
      contredisent. */
.ship-fiche:has(.sf-lisible) {
  width: max-content;
  min-width: min(380px, calc(100vw - 16px));
  max-width: calc(100vw - 16px);
}
.ship-fiche:has(.sf-lisible)
  :is(.sf-desc, .sf-note, .sf-bld-stats, .sf-reqs) { contain: inline-size; }

/* LE TEXTE. Tout ce qui était sous le plancher de lisibilité
   (--badge-font-size, 11 px) y monte ; le reste prend un cran. */
.ship-fiche:has(.sf-lisible) .sf-cat,
.ship-fiche:has(.sf-lisible) .sf-bloc-titre,
.ship-fiche:has(.sf-lisible) .sf-req-a,
.ship-fiche:has(.sf-lisible) .sf-bld-stats .st,
.ship-fiche:has(.sf-lisible) .sf-paliers {
  font-size: var(--badge-font-size, 11px);
}
.ship-fiche:has(.sf-lisible) .sf-nom { font-size: 1.16rem; }
.ship-fiche:has(.sf-lisible) .sf-stock { font-size: .92rem; }
.ship-fiche:has(.sf-lisible) .sf-desc { font-size: .88rem; }
.ship-fiche:has(.sf-lisible) .sf-note { font-size: .82rem; }
.ship-fiche:has(.sf-lisible) .sf-req { font-size: .84rem; }
.ship-fiche:has(.sf-lisible) .sf-cout-item { font-size: .92rem; }
.ship-fiche:has(.sf-lisible) .sf-cout-item .res-mini { width: 18px; height: 18px; }
.ship-fiche:has(.sf-lisible) .sf-duree { font-size: .88rem; }
/* Plus gros, le tableau est aussi plus aéré : à l'écartement d'avant, huit
   colonnes à 11 px se touchaient presque (« Niv Extraction/h » d'un bloc,
   vu au navigateur). */
.ship-fiche:has(.sf-lisible) .sf-paliers th { padding: 0 .45rem .3rem; }
.ship-fiche:has(.sf-lisible) .sf-paliers td { padding: .18rem .45rem; }
/* ⚠️ EMPILÉ, LE TABLEAU DOUBLE SES RANGÉES : au téléphone, à l'interligne
   du tableau large, la fiche montait à 810 px sur un écran de 844 et sa
   dernière phrase passait sous le bord (mesuré à 390 px de large,
   Extracteur de Ferrith niveau 40). L'interligne se resserre donc ici, la
   taille du texte ne bouge pas. Elle défile aussi sous le doigt (plus bas),
   mais moins elle en a besoin, mieux elle se lit. */
.ship-fiche:has(.sf-lisible) .sf-paliers.sfp-empile td {
  padding-top: .08rem; padding-bottom: .08rem;
}
.ship-fiche:has(.sf-lisible) .sf-paliers.sfp-empile tr.sfp-sous td {
  padding-top: 0; padding-bottom: .2rem; line-height: 1.25;
}

/* LES COLONNES DE PRIX. Le total USF ouvre le groupe, un filet le sépare
   des colonnes d'effet : on lit « ce que ça rapporte » à gauche, « ce que
   ça coûte » à droite. Chaque ressource a sa vignette pour en-tête — celle
   du bandeau du haut, pas l'initiale, qui ne se lit pas (v0.0.5). */
.sf-paliers.sfp-par-res .sfp-cout { border-left: 1px solid rgba(110,130,170,.2); }
.sf-paliers .sfp-res { color: #a9bcd8; }
.sf-paliers th.sfp-res .res-mini { width: 16px; height: 16px; vertical-align: middle; }

/* LA DISPOSITION EMPILÉE, quand le tableau large déborde (`sfAjusterPaliers`
   la pose après avoir MESURÉ) : les colonnes de prix s'effacent, et chaque
   niveau porte sa ligne de prix juste dessous. Le niveau et son prix se
   lisent comme un seul bloc : pas de filet entre les deux. */
.sf-paliers tr.sfp-sous { display: none; }
.sf-paliers.sfp-empile tr.sfp-sous { display: table-row; }
.sf-paliers.sfp-empile .sfp-cout,
.sf-paliers.sfp-empile .sfp-res { display: none; }
.sf-paliers.sfp-empile tr.sfp-sous td {
  white-space: normal; text-align: left; padding-top: 0; border-top: 0;
}
.sf-paliers.sfp-empile tr:has(+ tr.sfp-sous) td { border-bottom: 0; }
/* ⚠️ Tableau large : la dernière rangée VISIBLE n'est plus la dernière du
   tableau (une ligne de prix cachée la suit), `tr:last-child` ne la trouve
   donc plus pour lui ôter son filet. */
.sf-paliers.sfp-par-res tr:has(+ tr.sfp-sous:last-child) td { border-bottom: 0; }
/* ⚠️ `vertical-align: middle` : sans lui, le total (du texte seul) et les
   prix à vignette se calent chacun sur sa propre ligne de base, et le total
   tombe plus bas que ses voisins (vu au navigateur). */
.sfp-sous-item {
  display: inline-flex; align-items: center; gap: .2rem; vertical-align: middle;
  margin-right: .7rem; white-space: nowrap; color: #a9bcd8;
}
.sfp-sous-item:last-child { margin-right: 0; }
.sfp-sous-item .res-mini { width: 14px; height: 14px; }
.sfp-sous-usf { color: #7f96b8; }

@media (max-width: 620px) {
  .ship-fiche:has(.sf-lisible) { width: calc(100vw - 16px); }
}

/* ⚠️ AU DOIGT, LE CORPS DE LA FICHE DÉFILE. Plus grosse et plus chiffrée,
   une fiche de recherche dépasse la hauteur d'un téléphone ; or la fiche
   ne reçoit jamais le pointeur (il la ferait clignoter sous la souris). Là
   où il n'y a PAS de souris — les deux mêmes gardes que `sfSansSurvol`,
   ship-fiche.js —, son corps le reçoit donc, pour défiler sous le doigt.
   `overscroll-behavior` : arrivé en bas, le geste ne passe pas à la page,
   dont le défilement refermerait la fiche. */
@media (hover: none) {
  .ship-fiche:has(.sf-lisible) .sf-corps {
    pointer-events: auto; overscroll-behavior: contain;
  }
}
body.mobile-mode .ship-fiche:has(.sf-lisible) .sf-corps {
  pointer-events: auto; overscroll-behavior: contain;
}


/* ═══════════════════════════════════════════════════════════════════
   « MON COMPTE » — menu du bandeau + modale de mot de passe (v0.0.31)
   ═══════════════════════════════════════════════════════════════════
   Le menu est POSÉ SUR LE BODY et non dans le bandeau : le bandeau a un
   `overflow` qui le rognerait, et un z-index plus bas que les modales. */
.compte-menu {
  position: fixed;
  z-index: 9000;
  min-width: 232px;
  padding: 6px;
  background: linear-gradient(135deg, #1a2538 0%, #0f1828 100%);
  border: 1px solid #2d4a6e;
  border-radius: 10px;
  box-shadow: 0 12px 30px rgba(0,0,0,.55);
  animation: compteMenuIn .12s ease-out;
}
@keyframes compteMenuIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
.compte-menu-tete {
  padding: 6px 10px 8px;
  font-family: var(--hud);
  font-size: .62rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--text3);
  border-bottom: 1px solid rgba(255,255,255,.07);
  margin-bottom: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.compte-menu-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 9px 10px;
  background: none;
  border: none;
  border-radius: 7px;
  color: var(--text);
  font: inherit;
  font-size: .82rem;
  text-align: left;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.compte-menu-item:hover { background: rgba(56,189,248,.13); color: #e0f2fe; }
.compte-menu-ico { font-size: .95rem; line-height: 1; }
/* La déconnexion est la seule action irréversible du menu : elle vire au
   rouge au survol, pour qu'on ne la clique pas en visant l'autre. */
.compte-menu-sortie:hover { background: rgba(233,69,96,.15); color: #fecdd3; }

/* ─── La modale de mot de passe ─────────────────────────────────── */
.compte-mdp { max-width: 420px; }
.compte-mdp-note {
  margin: 0 0 14px;
  font-size: .78rem;
  line-height: 1.5;
  color: var(--text3);
}
.compte-mdp-champ { display: block; margin-bottom: 11px; }
.compte-mdp-champ > span {
  display: block;
  margin-bottom: 5px;
  font-size: .76rem;
  color: var(--text2);
}
.compte-mdp-champ input {
  width: 100%;
  padding: 9px 11px;
  background: rgba(0,0,0,.28);
  border: 1px solid var(--border2, #2d4a6e);
  border-radius: 7px;
  color: var(--text);
  font: inherit;
  font-size: .86rem;
}
.compte-mdp-champ input:focus {
  outline: none;
  border-color: var(--accent2, #38bdf8);
  box-shadow: 0 0 0 2px rgba(56,189,248,.18);
}
.compte-mdp-erreur {
  margin: 4px 0 12px;
  padding: 8px 11px;
  background: rgba(233,69,96,.12);
  border: 1px solid rgba(233,69,96,.4);
  border-radius: 7px;
  color: #fecdd3;
  font-size: .79rem;
  line-height: 1.45;
}
.compte-mdp-erreur[hidden] { display: none; }
.compte-mdp-actions {
  display: flex;
  justify-content: flex-end;
  gap: 9px;
  margin-top: 16px;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.8 — VUE MOBILE COMPACTE
   ════════════════════════════════════════════════════════════════════
   « La vue mobile est décédée » (Life) — mesuré sur un écran de 412 × 915 :
   le bandeau de ressources occupait 405 px, soit 44 % de l'écran, et la
   légende de la carte recouvrait la moitié du reste.

   POURQUOI C'ÉTAIT CASSÉ. Le mode mobile v0.75 (body.mobile-mode) avait
   bien un bandeau compact — mais le bandeau AGRANDI de la v2.34 est écrit
   avec des sélecteurs `#resbar .res`, et un identifiant pèse plus lourd
   que toutes les classes : chacune de ses règles ÉCRASE la version mobile,
   silencieusement, depuis des mois.

   ⚠️ TOUS les sélecteurs d'ici embarquent `#resbar` ou un identifiant
   équivalent, précisément pour repasser devant les règles v2.34 — les
   écrire en classes seules les laisserait perdre à nouveau.

   ── Le bandeau : 9 encarts en grille 3 × 3, ~100 px en tout ──────────
   On garde l'essentiel de chaque encart : vignette, stock, débit /h et
   capacité (les deux chiffres que Loryan voulait « plus visibles » — ils
   restent, en petit). Sortent : le NOM (la vignette suffit, elle est
   unique par ressource) et l'échéance « soute pleine » (détail de survol,
   il n'y a pas de survol au doigt). */
body.mobile-mode #resbar {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 3px;
  padding: 3px 4px;
}
body.mobile-mode #resbar .res {
  width: auto;
  min-width: 0;
  max-width: none;
  min-height: 0;
  padding: 3px 6px;
  gap: 5px;
  border-radius: 3px;
}
body.mobile-mode #resbar #res-electryte { min-width: 0; }
body.mobile-mode #resbar .res-ico-img { width: 16px; height: 16px; border-radius: 3px; }
body.mobile-mode #resbar .res-name { display: none; }
body.mobile-mode #resbar .res-val { font-size: .8rem; line-height: 1.15; font-weight: 600; }
body.mobile-mode #resbar .res-rate { font-size: .52rem; font-weight: 500; }
body.mobile-mode #resbar .res-cap { font-size: .52rem; font-weight: 500; }
body.mobile-mode #resbar .res > div[id^="rc-"] { font-size: .52rem !important; }
body.mobile-mode #resbar .res-eta { display: none; }

/* La barre de remplissage reste : à cette taille, c'est elle qui dit
   « soute pleine » d'un coup d'œil. */
body.mobile-mode #resbar .res-bar-bg { display: block; }

/* ── La topbar : recentrée sur ce qui sert au doigt ──────────────────
   Le libellé « Progression » et celui de l'inventaire deviennent des
   icônes seules — les deux boutons restent, ils maigrissent. */
body.mobile-mode #progression-label { display: none; }
body.mobile-mode .inv-pastille-txt { display: none; }
/* ⚠️ v0.0.99 — LA BOUTIQUE MAIGRIT COMME SA VOISINE, et ce n'est pas
   cosmétique : mesuré à 390 px, le bandeau porte DÉJÀ 639 px de contenu
   sans elle — les pastilles Vacances et Inventaire y sont hors champ
   depuis longtemps (défaut antérieur, cf. v0.0.84). Ma pastille y ajoutait
   77 px ; le texte masqué en rend 32. Le SOLDE, lui, RESTE : c'est la
   seule information de la pastille, et une icône seule ne dirait pas
   combien il reste d'Aurores. */
body.mobile-mode .btq-pastille-txt { display: none; }

/* ── Les vues sous le bandeau : plus de marge en dur ─────────────────
   Les 110 px posés en v0.75 supposaient l'ancien bandeau à deux lignes ;
   le bandeau compact est MESURÉ (core/layout.js → --resbar-bottom), on
   suit la mesure. */
body.mobile-mode #view-ship,
body.mobile-mode #view-research { padding-top: calc(var(--resbar-bottom, 100px) + 6px) !important; }

/* ── La carte : toute la largeur ─────────────────────────────────────
   Même maladie que le bandeau : `#view-map { right: var(--vm-panel-w) }`
   (un identifiant) écrase le `right: 0` du mode mobile — la carte cédait
   180 px à un panneau que le mobile transforme pourtant en tiroir. */
body.mobile-mode #view-map { right: 0; }

/* ── … ET LA MÊME MALADIE À GAUCHE (v0.0.118)
   Signalement de Life, capture à l'appui : « le menu se replie bien sur mobile
   mais l'arrière plan n'affiche pas la galaxie ».

   ⚠️ MESURÉ AU NAVIGATEUR (390 × 844) : en vue galaxie, `#dsh-tabs-main` est
   translaté à x = −48 — le rail est bien escamoté, c'est le geste demandé par
   Loryan en v0.0.85. Mais `#view-map` garde `left: var(--sidebar-width)`, soit
   48 px : le canevas commence à 48 et la bande de gauche montre le fond de la
   page. Sur un téléphone de 390 px, c'est 12 % de la largeur perdus — et ce
   n'est pas « la galaxie qui ne s'affiche pas », c'est la carte qui n'occupe
   pas la place qu'on vient de lui faire.

   ⚠️ C'EST EXACTEMENT LE DÉFAUT DE LA LIGNE DU DESSUS, sur l'autre bord : un
   sélecteur à identifiant (`#view-map { left: … }`) écrase la règle du mode
   mobile. On garde donc la même forme, et le même identifiant.

   ⚠️ ET SEULEMENT EN VUE GALAXIE. `body.carte-mobile` n'est posée que là
   (features/mobile/carte-mobile.js) : c'est la seule vue qui escamote son
   rail. Partout ailleurs le rail est à l'écran et ces 48 px lui appartiennent.
   Rail rouvert d'un glissement du doigt, il repasse PAR-DESSUS la carte
   (translation + ombre portée) : rien à défaire ici. */
body.carte-mobile #view-map { left: 0; }

/* ── L'écran Vaisseau Mère : la barre de sections redevient une barre ─
   Le mode mobile v0.75 couche #ship-nav à l'horizontale — mais la
   sous-liste « Modules » (v2.39, .sn-sub) est un BLOC vertical de onze
   entrées : posée telle quelle dans la barre horizontale, elle l'étirait
   à 452 px de haut (mesuré) et poussait tout le contenu hors écran.
   `display: contents` dissout le bloc : ses onze entrées redeviennent
   des onglets de la barre, comme les autres. Le filet vertical qui les
   reliait n'a plus de sens à l'horizontale — il disparaît avec. */
body.mobile-mode #ship-nav .sn-sub { display: contents; }
body.mobile-mode #ship-nav .sn-sub::before { content: none; }
body.mobile-mode #ship-nav .sn-sub .sn-item { padding-left: 0.7rem; }
body.mobile-mode #ship-nav .sn-sub .sn-item::before { content: none; }
body.mobile-mode #ship-nav .sn-item { min-height: 40px; max-height: 48px; }
body.mobile-mode #ship-nav .sn-prod-btn { margin: 0; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.8 — COMPTEUR DE BÂTIMENTS DANS L'EN-TÊTE D'UN MODULE
   (demande Loryan : « le nombre de bâtiments possibles et réalisés juste
   à côté du module »)
   ════════════════════════════════════════════════════════════════════
   Il se pose à droite du .pg-hdr (déjà en flex space-between). */
.pg-modcompteur {
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--text2);
  white-space: nowrap;
  padding: .2rem .55rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: rgba(255, 255, 255, .02);
}
.pg-modcompteur b { color: var(--text); }
.pg-modcompteur-pct { color: var(--text3); }

/* ── Les titres de section ne se cassent plus en colonne ─────────────
   `.pg-hdr` est un flex `space-between` : le titre à gauche, les badges
   (XP, Nanites, compteur de bâtiments) à droite. Sur 364 px utiles, les
   badges volaient la largeur et « Modules du Vaisseau Mère » tombait sur
   TROIS lignes de deux mots. Le titre prend donc toute la largeur, les
   badges passent dessous. */
body.mobile-mode .pg-hdr {
  flex-wrap: wrap;
  align-items: flex-start;
  gap: .45rem;
  margin-bottom: .8rem;
  padding-bottom: .5rem;
}
body.mobile-mode .pg-hdr > div:first-child { flex: 1 0 100%; }
body.mobile-mode .pg-title { font-size: .95rem; }
body.mobile-mode .pg-sub { font-size: .66rem; }
body.mobile-mode .pg-modcompteur { font-size: .66rem; }

/* ── Les cibles tactiles atteignent 40 px ────────────────────────────
   Mesuré à l'audit : le bouton AMÉLIORER — l'action principale du jeu —
   faisait 24 px de haut, les flèches de projection de coût 15 px, le
   bouton Arbre 19 px, les filtres de notification 19 px. En dessous de
   ~40 px, un doigt vise à côté. Les recommandations Apple (44) et
   Android (48) sont trop hautes pour un jeu aussi dense : 40 px est le
   compromis qui rend l'appui fiable sans étirer les panneaux. */
body.mobile-mode .btn-up,
body.mobile-mode .md-arbre-btn,
body.mobile-mode .notif-filter,
body.mobile-mode .mv-btn,
body.mobile-mode .qbar-toggle {
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
body.mobile-mode .btn-up { font-size: .72rem; }
/* Les deux flèches de projection sont côte à côte et minuscules : on les
   élargit AUSSI (une cible de 40 px de haut sur 14 de large ne se vise
   pas mieux). */
body.mobile-mode .md-proj-btn {
  min-height: 34px; min-width: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .75rem;
}
body.mobile-mode .notif-filter { padding: 0 .7rem; font-size: .7rem; }
/* Les filtres du Centre d'information et les actions de son panneau :
   mêmes cibles minuscules (27 px et 19 px) que celles ci-dessus. */
body.mobile-mode .ci-filtre,
body.mobile-mode .notif-panel-action {
  min-height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 .7rem;
}

/* ═══════════════════════════════════════════════════════════════════
   LA SECTION STATISTIQUES
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan, captures d'OGLight à l'appui : « des graphiques, des
   diagrammes, des tableaux, la totale ».

   TROIS PARTIS PRIS.
   1. AUCUNE BIBLIOTHÈQUE. Le jeu n'a pas d'étape de construction et ne
      télécharge rien : anneaux, courbes et barres sont du SVG écrit à la
      main. Ce fichier ne fait donc que les habiller.
   2. UNE GRILLE QUI RESPIRE, PAS UN TABLEAU DE BORD D'AVION. Des cartes
      espacées, un titre par carte : on doit pouvoir en épurer une sans
      que le reste s'effondre — c'est ce que Loryan a annoncé vouloir
      faire ensuite.
   3. LES CHIFFRES EN CHASSE FIXE ET ALIGNÉS À DROITE. Une colonne de
      nombres ne se compare que si les unités sont l'une sous l'autre. */
.st-note {
  padding: 10px 14px; margin-bottom: 14px;
  background: rgba(251,191,36,.08); border: 1px solid rgba(251,191,36,.28);
  border-left-width: 3px; border-radius: 8px;
  font-size: .82rem; line-height: 1.5; color: var(--text, #c8d6f0);
}
.st-vide, .st-graph-vide {
  padding: 18px; text-align: center; color: var(--text2, #8da4c4);
  font-size: .82rem; line-height: 1.6;
}
.st-dim { color: var(--text2, #8da4c4); font-size: .76rem; }

/* La barre de période */
.st-barre-periode { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; }
.st-periode {
  padding: 5px 12px; background: rgba(255,255,255,.03);
  border: 1px solid var(--border, #2d4a6e); border-radius: 999px;
  color: var(--text2, #8da4c4); font-size: .76rem; cursor: pointer;
}
.st-periode:hover { background: rgba(255,255,255,.07); color: var(--text, #c8d6f0); }
.st-periode.actif { border-color: #38bdf8; color: #e8f0ff; background: rgba(56,189,248,.12); }
.st-periode-info { margin-left: auto; }
/* Analyse détaillée — Global puis un bouton par type de Vaisseau Mère
   possédé. Même allure que la barre de période, mais au plancher de
   lisibilité (11,5 px) : c'est une rangée neuve, elle part du bon pied.
   Elle passe à la ligne au téléphone plutôt que de déborder. */
.st-barre-classes { display: flex; flex-wrap: wrap; gap: 8px; margin: -4px 0 14px; }
.st-classe {
  padding: 5px 12px; background: rgba(255,255,255,.03);
  border: 1px solid var(--border, #2d4a6e); border-radius: 999px;
  color: var(--text2, #8da4c4); font-size: .82rem; cursor: pointer; white-space: nowrap;
}
.st-classe:hover { background: rgba(255,255,255,.07); color: var(--text, #c8d6f0); }
.st-classe.actif { border-color: #a78bfa; color: #eef0ff; background: rgba(167,139,250,.14); }
.st-classe-n { opacity: .75; margin-left: 2px; }

/* Les tuiles de synthèse — la première chose qu'on lit */
.st-tuiles {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; margin-bottom: 16px;
}
.st-tuile {
  padding: 10px 14px; background: rgba(255,255,255,.025);
  border: 1px solid rgba(110,130,170,.2); border-radius: 9px;
}
/* Une SEULE tuile en avant — les points, qui résument tout le reste.
   Deux accents et plus rien ne ressort. */
.st-tuile.est-accent { background: rgba(251,191,36,.08); border-color: rgba(251,191,36,.3); }
.st-tuile-lbl {
  font-family: var(--hud, monospace); font-size: .58rem; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text2, #8da4c4);
}
.st-tuile-val {
  font-family: var(--mono, monospace); font-size: 1.28rem; font-weight: 700;
  color: var(--text, #eaf2ff); line-height: 1.25; margin-top: 3px;
}
.st-tuile.est-accent .st-tuile-val { color: #fbbf24; }
.st-tuile-sub { font-size: .66rem; color: var(--text2, #8da4c4); margin-top: 1px; }

/* Les cartes */
.st-carte {
  padding: 14px 16px; margin-bottom: 14px;
  background: rgba(255,255,255,.02);
  border: 1px solid rgba(110,130,170,.2); border-radius: 10px;
}
.st-carte-titre {
  margin: 0 0 12px; font-size: .82rem; font-weight: 700;
  color: var(--text, #dbe7fb); letter-spacing: .02em;
}
.st-duo { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 14px; }
.st-duo .st-carte { margin-bottom: 0; }
.st-duo + .st-duo, .st-duo + .st-carte { margin-top: 14px; }

/* L'anneau et sa légende */
.st-anneau-bloc { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.st-anneau { width: 140px; height: 140px; flex: none; }
.st-arc { transition: stroke-width .15s; }
.st-arc:hover { stroke-width: 23; }
.st-anneau-val {
  text-anchor: middle; font-family: var(--mono, monospace); font-size: 17px;
  font-weight: 700; fill: #eaf2ff;
}
.st-anneau-lbl {
  text-anchor: middle; font-family: var(--hud, monospace); font-size: 8px;
  letter-spacing: .1em; text-transform: uppercase; fill: #8da4c4;
}
.st-legende { flex: 1; min-width: 190px; list-style: none; margin: 0; padding: 0; }
.st-legende li {
  display: flex; align-items: center; gap: 8px; padding: 3px 0;
  font-size: .76rem; border-bottom: 1px solid rgba(110,130,170,.09);
}
.st-legende li:last-child { border-bottom: 0; }
.st-puce { width: 9px; height: 9px; border-radius: 3px; flex: none; display: inline-block; }
.st-legende-nom { flex: 1; color: var(--text, #c8d6f0); }
.st-legende-n {
  font-family: var(--mono, monospace); color: var(--text, #eaf2ff);
  font-variant-numeric: tabular-nums;
}
.st-legende-pct {
  font-family: var(--mono, monospace); color: var(--text2, #8da4c4);
  width: 52px; text-align: right; font-variant-numeric: tabular-nums;
}

/* Les barres classées */
.st-barres { display: flex; flex-direction: column; gap: 4px; max-height: 340px; overflow-y: auto; }
.st-barre { display: flex; align-items: center; gap: 9px; font-size: .74rem; }
.st-barre-lbl {
  flex: none; width: 86px; color: var(--text2, #8da4c4);
  font-family: var(--mono, monospace);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.st-barre-rail { flex: 1; height: 9px; border-radius: 999px; background: rgba(110,130,170,.13); overflow: hidden; }
.st-barre-fill { display: block; height: 100%; border-radius: 999px; }
.st-barre-val {
  flex: none; width: 74px; text-align: right; font-family: var(--mono, monospace);
  color: var(--text, #eaf2ff); font-variant-numeric: tabular-nums;
}

/* Les courbes */
.st-graphs { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 14px; }
.st-graph {
  padding: 10px 12px; background: rgba(0,0,0,.18);
  border: 1px solid rgba(110,130,170,.14); border-radius: 8px;
}
.st-graph-tete {
  display: flex; align-items: center; gap: 7px;
  font-size: .74rem; color: var(--text2, #8da4c4); margin-bottom: 6px;
}
.st-graph-val { margin-left: auto; font-family: var(--mono, monospace); font-size: .88rem; }
/* La courbe s'étire en largeur mais garde sa hauteur : `preserveAspectRatio
   = none` sur le SVG, hauteur fixée ici. Sans ça, une carte large donnerait
   une courbe haute de vingt centimètres. */
.st-svg { width: 100%; height: 120px; display: block; }
.st-graph-axe {
  display: flex; justify-content: space-between;
  font-family: var(--mono, monospace); font-size: .62rem; color: var(--text2, #8da4c4);
}

/* Les tableaux */
.st-table-cadre { overflow-x: auto; }
.st-table {
  width: 100%; border-collapse: collapse;
  font-family: var(--mono, monospace); font-size: .74rem;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.st-table th {
  text-align: right; padding: 0 10px 6px; font-weight: 600;
  color: var(--text2, #8da4c4); border-bottom: 1px solid rgba(110,130,170,.22);
  font-size: .66rem; letter-spacing: .04em; text-transform: uppercase;
}
.st-table th:first-child, .st-table .st-td-nom, .st-table .st-td-evts { text-align: left; }
.st-table td {
  text-align: right; padding: 5px 10px; color: var(--text, #dbe7fb);
  border-bottom: 1px solid rgba(110,130,170,.07);
}
.st-table tbody tr:hover td { background: rgba(255,255,255,.03); }
.st-table .st-td-nom { color: var(--text2, #8da4c4); }
.st-table .st-td-usf { color: #fbbf24; }
.st-table .st-td-evts { color: var(--text2, #8da4c4); font-size: .68rem; white-space: normal; }

@media (max-width: 620px) {
  .st-anneau-bloc { justify-content: center; }
  .st-barre-lbl { width: 62px; }
  .st-barre-val { width: 60px; }
}

/* ═══ v0.0.18 : LES DEUX PORTES DE LA VUE STATISTIQUES ════════════════
   Demande de Loryan : « mets-les dans une section en dessous de la section
   du centre d'information, une section à part ».
   TROISIÈME EMPLACEMENT, et les deux précédents l'expliquent : d'abord
   DANS le Centre d'Information (il fallait traverser les rapports puis les
   notifications), puis en pastilles dans le bandeau du haut (noyées au
   milieu des raccourcis). Elles ont maintenant leur entrée de navigation.
   DES CARTES, PAS DES BOUTONS : chacune annonce ce qu'on y trouvera, ce
   qu'un bouton de trois mots ne peut pas faire — et on choisit son tableau
   de bord sans avoir à ouvrir les deux. Depuis la v0.0.19, la carte cliquée
   DÉPLIE son contenu juste en dessous (`.st-panneau`) au lieu d'ouvrir une
   modale : les statistiques ayant leur propre entrée de navigation, une
   modale par-dessus faisait deux couches pour une seule destination. */
#st-portes { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 14px; }
.st-porte {
  display: flex; align-items: flex-start; gap: 14px; text-align: left;
  padding: 16px 18px; cursor: pointer; font-family: inherit;
  background: rgba(255,255,255,.025);
  border: 1px solid rgba(110,130,170,.24); border-left-width: 3px;
  border-left-color: #38bdf8; border-radius: 10px; transition: .15s;
}
.st-porte:hover {
  background: rgba(56,189,248,.09); border-color: #38bdf8;
  transform: translateY(-1px);
}
.st-porte-ico { font-size: 1.7rem; line-height: 1; flex: none; margin-top: 2px; }
.st-porte-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.st-porte-titre { font-size: .96rem; font-weight: 700; color: var(--text, #eaf2ff); }
.st-porte-sous { font-size: .78rem; color: var(--text2, #8da4c4); line-height: 1.4; }
.st-porte-detail {
  font-size: .7rem; color: var(--text3, #64748b); line-height: 1.45; margin-top: 3px;
}
.st-porte-fleche {
  flex: none; font-size: 1.4rem; color: var(--text2, #8da4c4); align-self: center;
  transition: transform .18s, color .15s;
}
.st-porte:hover .st-porte-fleche { color: #38bdf8; }
/* La carte dépliée : la flèche pivote vers le bas (c'est ce que fait un
   accordéon) et la carte prend la couleur de son panneau, pour qu'on voie
   d'un coup d'œil lequel des deux tableaux de bord est ouvert dessous. */
.st-porte.est-ouverte {
  background: rgba(56,189,248,.12); border-color: #38bdf8;
  box-shadow: 0 0 0 1px rgba(56,189,248,.28) inset;
}
.st-porte.est-ouverte .st-porte-fleche { transform: rotate(90deg); color: #38bdf8; }

/* v0.0.19 — LE PANNEAU EN DESSOUS (demande de Loryan : « quand je clique
   mets en dessous »). Il remplace la modale : le tableau de bord s'écrit
   DANS la page, sous les cartes. Pas de hauteur maximale ni de défilement
   interne, contrairement à la modale — c'est la page qui défile, et un
   tableau de bord qu'on parcourt de haut en bas n'a aucune raison d'avoir
   sa propre barre de défilement dans une vue qui en a déjà une. */
#st-contenu:empty { display: none; }
.st-panneau {
  margin-top: 14px; border: 1px solid rgba(110,130,170,.24); border-radius: 12px;
  background: rgba(255,255,255,.018); overflow: hidden;
  animation: stPanneauEntre .18s ease-out;
}
@keyframes stPanneauEntre { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.st-panneau-tete {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 11px 16px; border-bottom: 1px solid rgba(110,130,170,.18);
  background: linear-gradient(180deg, rgba(56,189,248,.10), rgba(56,189,248,0));
}
.st-panneau-titre { font-size: .95rem; font-weight: 700; color: var(--text, #eaf2ff); }
.st-panneau-fermer {
  flex: none; width: 28px; height: 28px; border-radius: 7px; cursor: pointer;
  font-size: 1.1rem; line-height: 1; font-family: inherit;
  background: rgba(255,255,255,.05); color: var(--text2, #8da4c4);
  border: 1px solid rgba(110,130,170,.24); transition: .15s;
}
.st-panneau-fermer:hover { background: rgba(248,113,113,.16); color: #f87171; border-color: #f87171; }
.st-panneau-corps { padding: 14px 16px 18px; }
@media (max-width: 720px) {
  .st-panneau-corps { padding: 10px; }
  .st-porte { padding: 12px 14px; gap: 10px; }
  .st-porte-detail { display: none; }
}

/* ═══════════════════════════════════════════
   v0.0.23 — L'ANALYSE DÉTAILLÉE (3ᵉ tableau de bord)
   ═══════════════════════════════════════════
   Demande de Loryan : mesurer production contre expéditions contre raids.
   Les deux premiers tableaux de bord DÉCRIVENT ; celui-ci COMPARE et JUGE,
   d'où trois formes qui leur sont propres — le VERDICT (un archétype et sa
   jauge), les ALERTES (des phrases classées par gravité) et la BARRE
   EMPILÉE (des proportions, quatorze fois dans l'écran, là où un camembert
   coûterait trop de hauteur). Tout le reste réemploie les classes des deux
   premiers : mêmes tuiles, mêmes tableaux, mêmes anneaux. */

/* ─── Le verdict : l'archétype du joueur ─── */
.st-verdict { background: rgba(255,255,255,.025); }
.st-verdict-tete { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 14px; }
.st-verdict-ico { font-size: 2.2rem; line-height: 1; flex: none; }
.st-verdict-titre { font-size: 1.15rem; font-weight: 800; letter-spacing: .2px; }
.st-verdict-phrase { font-size: .78rem; color: var(--text2, #8da4c4); line-height: 1.5; margin-top: 3px; }

/* ─── La jauge de dépendance ───
   Un AXE et pas un anneau : « de quel côté je penche » se lit sur une
   ligne, alors qu'un camembert à deux parts oblige à comparer deux arcs. */
.st-jauge { margin: 4px 0 14px; }
.st-jauge-bornes, .st-jauge-chiffres {
  display: flex; justify-content: space-between; font-size: .72rem; font-weight: 700;
}
.st-jauge-chiffres { color: var(--text2, #8da4c4); font-weight: 600; margin-top: 3px; }
.st-jauge-rail {
  position: relative; display: flex; height: 14px; margin-top: 5px;
  border-radius: 999px; overflow: hidden; background: rgba(110,130,170,.13);
}
.st-jauge-fill { display: block; height: 100%; opacity: .85; }
.st-jauge-fill.est-droite { opacity: .7; }
.st-jauge-curseur {
  position: absolute; top: -3px; bottom: -3px; width: 3px; margin-left: -1.5px;
  background: var(--text, #eaf2ff); border-radius: 2px;
  box-shadow: 0 0 0 2px rgba(10,14,24,.55);
}

/* ─── Les quatre familles de recettes ─── */
.st-familles { display: grid; gap: 7px; margin-bottom: 12px; }
.st-famille {
  display: grid; grid-template-columns: 22px 1fr auto auto; gap: 4px 9px;
  align-items: baseline; font-size: .78rem;
}
.st-famille-ico { font-size: .95rem; }
.st-famille-nom { font-weight: 700; color: var(--text, #eaf2ff); }
.st-famille-usf { font-weight: 700; font-variant-numeric: tabular-nums; }
.st-famille-part { color: var(--text2, #8da4c4); font-variant-numeric: tabular-nums; min-width: 48px; text-align: right; }
.st-famille-sous { grid-column: 2 / -1; font-size: .68rem; color: var(--text2, #8da4c4); opacity: .8; }

/* ─── La barre empilée ─── */
.st-empile {
  display: flex; height: 10px; border-radius: 999px; overflow: hidden;
  background: rgba(110,130,170,.13);
}
.st-empile-part { display: block; height: 100%; }
.st-empile-vide { font-size: .72rem; color: var(--text2, #8da4c4); }

/* ─── Les alertes d'équilibrage ───
   Trois niveaux, trois couleurs, et le plus grave en premier : c'est la
   seule chose qu'on vient lire quand on ouvre l'écran pour trancher. */
.st-alertes { display: grid; gap: 9px; }
.st-alerte {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 10px 12px; border-radius: 9px; font-size: .76rem; line-height: 1.5;
  border: 1px solid rgba(110,130,170,.2); background: rgba(255,255,255,.02);
}
.st-alerte.est-danger { border-color: rgba(248,113,113,.42); background: rgba(248,113,113,.08); }
.st-alerte.est-attention { border-color: rgba(251,191,36,.36); background: rgba(251,191,36,.06); }
.st-alerte.est-ok { border-color: rgba(52,211,153,.34); background: rgba(52,211,153,.06); }
.st-alerte-ico { font-size: 1.05rem; line-height: 1.2; flex: none; }
.st-alerte-titre { font-weight: 700; color: var(--text, #eaf2ff); margin-bottom: 2px; }
.st-alerte-txt { color: var(--text2, #8da4c4); }

/* ─── Textes d'accompagnement ───
   L'écran EXPLIQUE ce qu'il montre : sans ces phrases, « 3,4 h de prod »
   dans une colonne de tableau ne veut rien dire pour qui ne l'a pas codé. */
.st-carte-sous {
  font-size: .74rem; color: var(--text2, #8da4c4); line-height: 1.55;
  margin: -4px 0 12px;
}
.st-carte-note {
  font-size: .69rem; color: var(--text2, #8da4c4); opacity: .85;
  line-height: 1.5; margin: 9px 0 0;
}
.st-sous-titre {
  font-size: .72rem; font-weight: 700; color: var(--text2, #8da4c4);
  margin: 12px 0 7px; text-transform: uppercase; letter-spacing: .4px;
}
.st-note-ok { border-color: rgba(52,211,153,.3); background: rgba(52,211,153,.06); }

/* ─── Marqueurs de tableau ─── */
.st-mauvais { color: #f87171; font-weight: 700; }
.st-disparu { opacity: .55; }
.st-td-sous { display: block; font-size: .66rem; font-weight: 400; }
.st-td-empile { min-width: 110px; }
.st-td-jauge { min-width: 130px; }
.st-td-jauge .st-empile { margin-bottom: 3px; }
.st-td-jauge .st-dim { font-size: .66rem; }
.st-table tr.est-production .st-td-nom { color: #f59e0b; }
.st-table tr.est-expedition .st-td-nom { color: #a78bfa; }
.st-table tr.est-predation .st-td-nom { color: #f87171; }

/* ─── Les équivalences en temps ─── */
.st-equivalences { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 8px; }
.st-equiv {
  display: grid; grid-template-columns: 22px 1fr auto; gap: 3px 9px;
  align-items: baseline; padding: 8px 11px; border-radius: 8px;
  border: 1px solid rgba(110,130,170,.18); background: rgba(255,255,255,.02);
  font-size: .76rem;
}
.st-equiv-ico { font-size: .95rem; }
.st-equiv-lbl { color: var(--text, #eaf2ff); }
.st-equiv-usf { color: var(--text2, #8da4c4); font-variant-numeric: tabular-nums; }
.st-equiv-h { grid-column: 2 / -1; font-weight: 700; color: #fbbf24; font-size: .72rem; }

/* ─── La légende d'une courbe à plusieurs séries ─── */
.st-graph-legende { display: inline-flex; flex-wrap: wrap; gap: 12px; margin-left: 10px; font-weight: 400; }
.st-graph-legende > span { display: inline-flex; align-items: center; gap: 4px; font-size: .68rem; color: var(--text2, #8da4c4); }

@media (max-width: 720px) {
  .st-verdict-ico { font-size: 1.6rem; }
  .st-verdict-titre { font-size: 1rem; }
  .st-famille { grid-template-columns: 20px 1fr auto; }
  .st-famille-part { display: none; }
  .st-equivalences { grid-template-columns: 1fr; }
}

/* ══════════════════════════════════════════════════════════════════
   v0.0.24 — LES COMÈTES (flag `cometes`)
   ══════════════════════════════════════════════════════════════════
   La fiche qui s'ouvre au clic sur une comète de la carte. Le dessin de
   la comète elle-même est fait au CANVAS (features/map/map.js) : rien
   ici ne le concerne. */

/* v0.0.26 : ce panneau n'a plus rien à contourner — le défaut du socle
   qu'il signalait (`--z-modal` à 40 sous des bandeaux à 190 et 200) est
   corrigé dans les jetons du §21.3.5. Sa ligne `z-index: 1000` a été
   retirée avec les trois autres rustines du même genre. */
.comete-backdrop .cmt-modal { max-width: 720px; }

/* ─── v0.0.48 : la fiche du TROU DE VER ───────────────────────────── */
/* Elle réutilise la modale des comètes ; #tdv-fiche-hote la centre
   par-dessus la carte, comme le backdrop des comètes le fait pour elles. */
#tdv-fiche-hote:not(:empty) {
  position: fixed; inset: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4, 2, 12, 0.55);
}
#tdv-fiche-hote .cmt-modal { max-width: 640px; width: min(92vw, 640px); }
.tdv-vm-ligne {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-top: 1px solid rgba(166, 120, 232, 0.18);
}
.tdv-vm-ligne strong { min-width: 120px; }
.tdv-vm-jauge { font-size: 0.82rem; opacity: 0.85; white-space: nowrap; }
.tdv-vm-etat { flex: 1; font-size: 0.82rem; opacity: 0.75; }
.tdv-note { font-size: 0.8rem; opacity: 0.65; margin-top: 10px; }
/* Interrupteur `trouDeVerApproche` : la ligne d'un vaisseau porte désormais
   la durée du vol d'approche. Au téléphone elle ne tenait plus (mesuré :
   470 px de contenu pour 309 px de ligne, « Sauter » hors de l'écran) — elle
   passe donc sur deux lignes : nom et jauge, puis l'état et le bouton. La
   classe n'est posée que sous l'interrupteur. */
@media (max-width: 700px) {
  .tdv-vm-ligne--approche { flex-wrap: wrap; }
  .tdv-vm-ligne--approche .tdv-vm-etat { flex: 1 1 60%; min-width: 0; }
}
body.mobile-mode .tdv-vm-ligne--approche { flex-wrap: wrap; }
body.mobile-mode .tdv-vm-ligne--approche .tdv-vm-etat { flex: 1 1 60%; min-width: 0; }
/* interrupteurs `annulationTrenteSecondes` et `connecteurDevConnecte` : les
   deux phrases ajoutées sont au plancher de lisibilité (leurs voisines
   d'origine, plus petites, ne bougent pas). */
.mv-actions-note--annul, .md-note--brancher { font-size: var(--badge-font-size, 11px); }

/* v0.0.50 : le COMPTE À REBOURS dans l'en-tête de la fiche du trou de ver.
   Il se pose entre le titre et la croix de fermeture — `margin-left: auto`
   le pousse à droite sans toucher au gabarit de l'en-tête, partagé par
   toutes les modales du jeu. Chiffres en chasse fixe : sans ça, « 11 h 08 »
   et « 22 h 44 » n'ont pas la même largeur et le badge tressaute à chaque
   seconde — exactement le clignotement qu'on est en train de supprimer. */
.tdv-chrono-badge {
  margin-left: auto; margin-right: 10px;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 999px;
  font-size: 0.82rem; font-variant-numeric: tabular-nums;
  color: #E9D9FF; background: rgba(166, 120, 232, 0.16);
  border: 1px solid rgba(166, 120, 232, 0.38);
  white-space: nowrap;
}
.tdv-chrono-badge .tdv-chrono { font-weight: 600; }

.cmt-entete { display: grid; grid-template-columns: 100px 1fr; gap: 14px; align-items: center; }
.cmt-vignette { width: 100px; height: 80px; }
.cmt-type { margin: 0 0 4px; font-size: .82rem; color: var(--text, #eaf2ff); }
.cmt-desc { margin: 0; font-size: .78rem; color: var(--text2, #8da4c4); font-style: italic; }

/* La jauge de réserve — elle se vide à mesure que les joueurs exploitent
   la comète, y compris pendant qu'on la regarde. */
.cmt-jauge-bloc { margin: 16px 0 6px; }
.cmt-jauge {
  height: 10px; border-radius: 5px; overflow: hidden;
  background: rgba(110,130,170,.16); border: 1px solid rgba(110,130,170,.24);
}
.cmt-jauge > span {
  display: block; height: 100%;
  background: linear-gradient(90deg, #6ee7f9, #a78bfa);
}
.cmt-jauge-txt { margin: 6px 0 0; font-size: .74rem; color: var(--text2, #8da4c4); }

.cmt-chiffres {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px; margin: 14px 0;
}
.cmt-chiffres > div {
  padding: 8px 11px; border-radius: 8px;
  border: 1px solid rgba(110,130,170,.18); background: rgba(255,255,255,.02);
}
.cmt-chiffres span { display: block; font-size: .68rem; color: var(--text2, #8da4c4); }
.cmt-chiffres strong { font-size: .95rem; font-variant-numeric: tabular-nums; }

.cmt-info, .cmt-lancement {
  margin: 14px 0; padding: 12px 14px; border-radius: 10px;
  border: 1px solid rgba(110,130,170,.20); background: rgba(255,255,255,.03);
}
.cmt-lancement { border-color: rgba(124,231,176,.35); }
.cmt-info p, .cmt-lancement-txt { margin: 0 0 8px; font-size: .8rem; line-height: 1.5; }
.cmt-lancement-note { margin: 8px 0 0; font-size: .72rem; color: var(--text2, #8da4c4); line-height: 1.5; }
.cmt-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.cmt-form select { flex: 1 1 220px; min-width: 0; }
.cmt-form input { width: 80px; }

.cmt-escales { width: 100%; border-collapse: collapse; font-size: .76rem; }
.cmt-escales th, .cmt-escales td {
  padding: 5px 8px; text-align: left; border-bottom: 1px solid rgba(110,130,170,.14);
}
.cmt-escales th { font-size: .68rem; color: var(--text2, #8da4c4); font-weight: 600; }
.cmt-escales .cmt-passee { opacity: .45; }
.cmt-escales .cmt-encours { background: rgba(124,231,176,.10); font-weight: 600; }
.cmt-chezmoi { color: #7ce7b0; font-size: .68rem; }

.cmt-plans-note { margin: 12px 0 0; font-size: .74rem; color: var(--text2, #8da4c4); line-height: 1.5; }
.cmt-erreur {
  margin: 10px 0 0; padding: 8px 11px; border-radius: 8px; font-size: .78rem;
  border: 1px solid rgba(248,113,113,.4); background: rgba(248,113,113,.10); color: #fca5a5;
}

@media (max-width: 720px) {
  .cmt-entete { grid-template-columns: 1fr; }
  .cmt-vignette { width: 84px; height: 66px; }
  .cmt-form select, .cmt-form input { flex: 1 1 100%; width: 100%; }
}

/* ══════════════════════════════════════════════════════════════════
   v0.0.30 — LES MÉTÉORITES (flag `meteorites`)
   ══════════════════════════════════════════════════════════════════
   La fiche qui s'ouvre au clic sur une météorite de la carte. Le dessin
   du caillou lui-même est fait au CANVAS (features/map/map.js) : rien ici
   ne le concerne.

   ⚠️ LA PALETTE EST L'INVERSE DE CELLE DES COMÈTES, et c'est délibéré.
   La comète est cyan et violette — elle annonce du butin. La météorite est
   orange sourd sur presque-noir : elle annonce une perte. Les deux fiches
   ne doivent pas pouvoir se confondre d'un coup d'œil, même de loin. */

.meteorite-backdrop .mtr-modal { max-width: 640px; }

.mtr-entete { display: grid; grid-template-columns: 100px 1fr; gap: 14px; align-items: center; }
.mtr-vignette { width: 100px; height: 80px; }
.mtr-type { margin: 0 0 4px; font-size: .82rem; color: var(--text, #eaf2ff); }
.mtr-desc { margin: 0; font-size: .78rem; color: var(--text2, #8da4c4); font-style: italic; }

/* La cible et le compte à rebours — la seule information qui commande une
   décision. Elle est donc la plus grosse chose de la fiche. */
.mtr-cible {
  margin: 16px 0 6px; padding: 12px 14px; border-radius: 10px;
  border: 1px solid rgba(255,120,60,.28); background: rgba(255,120,60,.06);
}
.mtr-cible-chezmoi { border-color: rgba(248,113,113,.55); background: rgba(248,113,113,.10); }
.mtr-cible p { margin: 0 0 4px; font-size: .82rem; }
.mtr-compte { font-size: .95rem !important; }
.mtr-compte strong { font-variant-numeric: tabular-nums; color: #ffb37a; }

.mtr-chiffres {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 8px; margin: 14px 0;
}
.mtr-chiffres > div {
  padding: 8px 11px; border-radius: 8px;
  border: 1px solid rgba(110,130,170,.18); background: rgba(255,255,255,.02);
}
.mtr-chiffres span { display: block; font-size: .68rem; color: var(--text2, #8da4c4); }
.mtr-chiffres strong { font-size: .95rem; font-variant-numeric: tabular-nums; }

.mtr-entamee { margin: 0 0 10px; font-size: .78rem; color: #9ae6b4; }

.mtr-info, .mtr-tir {
  margin: 14px 0; padding: 12px 14px; border-radius: 10px;
  border: 1px solid rgba(110,130,170,.20); background: rgba(255,255,255,.03);
}
.mtr-tir { border-color: rgba(255,140,80,.38); }
.mtr-deja { border-color: rgba(250,204,21,.35); background: rgba(250,204,21,.07); }
.mtr-info p, .mtr-tir-txt { margin: 0 0 8px; font-size: .8rem; line-height: 1.5; }
.mtr-tir-note { margin: 8px 0 0; font-size: .72rem; color: var(--text2, #8da4c4); line-height: 1.5; }
.mtr-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.mtr-form select { flex: 1 1 220px; min-width: 0; }
.mtr-form input { width: 80px; }

.mtr-erreur {
  margin: 10px 0 0; padding: 8px 11px; border-radius: 8px; font-size: .78rem;
  border: 1px solid rgba(248,113,113,.4); background: rgba(248,113,113,.10); color: #fca5a5;
}

@media (max-width: 720px) {
  .mtr-entete { grid-template-columns: 1fr; }
  .mtr-vignette { width: 84px; height: 66px; }
  .mtr-form select, .mtr-form input { flex: 1 1 100%; width: 100%; }
}

/* ══════════════════════════════════════════════════════════════════
   v0.0.33 — LES ASTÉROÏDES (flag `asteroides`)
   ══════════════════════════════════════════════════════════════════
   La fiche qui s'ouvre au clic sur un astéroïde de la carte. Le dessin du
   caillou lui-même est fait au CANVAS (features/map/map.js) : rien ici ne
   le concerne.

   ⚠️ LA PALETTE EST BRONZE, et c'est le troisième code couleur du ciel —
   la comète cyan/violet annonce du butin, la météorite orange annonce une
   perte SUR LA PLANÈTE, l'astéroïde bronze annonce un blocage DES DÉPARTS.
   Les trois fiches doivent se distinguer d'un coup d'œil, même de loin :
   c'est ce qui dit au joueur ce qui lui arrive avant même qu'il lise. */

.asteroide-backdrop .ast-modal { max-width: 720px; }

.ast-entete { display: grid; grid-template-columns: 100px 1fr; gap: 14px; align-items: center; }
.ast-vignette { width: 100px; height: 80px; }
.ast-type { margin: 0 0 4px; font-size: .82rem; color: var(--text, #eaf2ff); }
.ast-desc { margin: 0; font-size: .78rem; color: var(--text2, #8da4c4); font-style: italic; }

/* La jauge de COQUE — elle descend sous les torpilles, à l'inverse exact de
   la jauge de réserve d'une comète, qui se vide quand on en profite. */
.ast-jauge-bloc { margin: 16px 0 6px; }
.ast-jauge {
  height: 10px; border-radius: 5px; overflow: hidden;
  background: rgba(110,130,170,.16); border: 1px solid rgba(110,130,170,.24);
}
.ast-jauge > span {
  display: block; height: 100%;
  background: linear-gradient(90deg, #8f560e, #e0b463);
}
.ast-jauge-txt { margin: 6px 0 0; font-size: .74rem; color: var(--text2, #8da4c4); }

.ast-chiffres {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px; margin: 14px 0;
}
.ast-chiffres > div {
  padding: 8px 11px; border-radius: 8px;
  border: 1px solid rgba(110,130,170,.18); background: rgba(255,255,255,.02);
}
.ast-chiffres span { display: block; font-size: .68rem; color: var(--text2, #8da4c4); }
.ast-chiffres strong { font-size: .95rem; font-variant-numeric: tabular-nums; }

.ast-info, .ast-tir, .ast-brouillage {
  margin: 14px 0; padding: 12px 14px; border-radius: 10px;
  border: 1px solid rgba(110,130,170,.20); background: rgba(255,255,255,.03);
}
/* Ce qu'il COÛTE en ce moment : encadré en rouge, comme la cible d'une
   météorite. C'est la seule information de la fiche qui commande une
   décision — les autres ne font que l'expliquer. */
.ast-brouillage { border-color: rgba(248,113,113,.55); background: rgba(248,113,113,.10); }
.ast-brouillage p { margin: 0 0 6px; font-size: .82rem; line-height: 1.5; }
.ast-brouillage p:last-child { margin-bottom: 0; }
.ast-cumul { color: #fbbf24; }
.ast-tir { border-color: rgba(224,180,99,.42); }
.ast-info p, .ast-tir-txt { margin: 0 0 8px; font-size: .8rem; line-height: 1.5; }
.ast-tir-note { margin: 8px 0 0; font-size: .72rem; color: var(--text2, #8da4c4); line-height: 1.5; }
.ast-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.ast-form select { flex: 1 1 220px; min-width: 0; }
/* Plus large que celui des comètes et des météorites : une salve se compte
   en MILLIERS de torpilles (2 000 pour un Titan), et un champ de 80 px
   coupait le nombre que le formulaire y pose lui-même. */
.ast-form input { width: 110px; }

.ast-escales { width: 100%; border-collapse: collapse; font-size: .76rem; }
.ast-escales th, .ast-escales td {
  padding: 5px 8px; text-align: left; border-bottom: 1px solid rgba(110,130,170,.14);
}
.ast-escales th { font-size: .68rem; color: var(--text2, #8da4c4); font-weight: 600; }
.ast-escales .ast-passee { opacity: .45; }
.ast-escales .ast-encours { background: rgba(224,180,99,.12); font-weight: 600; }
.ast-chezmoi { color: #e0b463; font-size: .68rem; }

.ast-erreur {
  margin: 10px 0 0; padding: 8px 11px; border-radius: 8px; font-size: .78rem;
  border: 1px solid rgba(248,113,113,.4); background: rgba(248,113,113,.10); color: #fca5a5;
}

@media (max-width: 720px) {
  .ast-entete { grid-template-columns: 1fr; }
  .ast-vignette { width: 84px; height: 66px; }
  .ast-form select, .ast-form input { flex: 1 1 100%; width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.40, lot 5 — LES VOIES (flag voiePionnier)
   ═══════════════════════════════════════════════════════════════════
   Même grammaire visuelle que l'arbre de progression : une toile avec un
   SVG d'arêtes derrière, des nœuds au-dessus, un panneau de détail collé
   à droite. On lit les deux écrans de la même façon, volontairement.
   La différence vient de la NATURE d'une Voie : les nœuds sont plus
   larges (ils portent une phrase, pas un nom), groupés par chapitre, et
   celui qui est atteint s'allume au lieu de rester neutre. */
.voie-cats { display: flex; gap: var(--space-2); margin-bottom: var(--space-3); flex-wrap: wrap; }
.voie-cat {
  background: var(--color-bg-card); border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-pill); padding: 5px 14px; cursor: pointer;
  font: inherit; font-size: 12px; color: var(--color-text-secondary);
}
.voie-cat.is-active { border-color: var(--accent2); color: var(--color-text-primary); }

/* En-tête : où j'en suis, en un coup d'œil */
/* ════════════════════════════════════════════════════════════════════
   v0.0.81 — LA VOIE DE CLASSE (carte du vaisseau actif)
   ⚠️ PRÉFIXE `vcl-` ET NON `voie-` : les deux blocs vivent dans le même
   écran, et réutiliser les classes des trois Voies historiques ferait
   hériter cette carte de leur mise en page (leçon de la collision `.part`,
   v0.0.65 — deux règles à 260 lignes d'écart, rien ne signale le conflit).
   ════════════════════════════════════════════════════════════════════ */
.voie-classe {
  border: 1px solid var(--vclc, #64748b);
  border-left-width: 4px;
  border-radius: 10px;
  background: rgba(148, 163, 184, .06);
  padding: 12px 16px 14px;
  margin: 0 0 18px;
  display: flex; flex-direction: column; gap: 8px;
}
.vcl-tete { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.vcl-ico { font-size: 18px; line-height: 1; }
.vcl-nom { font-weight: 700; color: var(--vclc, #cbd5e1); letter-spacing: -.01em; }
.vcl-vm { font-size: 12px; opacity: .7; }
.vcl-rang { margin-left: auto; font-variant-numeric: tabular-nums; font-weight: 700; }
.vcl-rang i { font-style: normal; opacity: .5; font-weight: 400; }
.vcl-jauge {
  height: 7px; border-radius: 4px; overflow: hidden;
  background: rgba(148, 163, 184, .18);
}
.vcl-jauge > span {
  display: block; height: 100%; background: var(--vclc, #64748b);
  transition: width .4s ease;
}
.vcl-pied { display: flex; gap: 12px; flex-wrap: wrap; font-size: 12.5px; opacity: .85; }
.vcl-reste { margin-left: auto; opacity: .8; }
.vcl-effet { font-size: 13px; }
/* ─── v0.0.81 : onglets de classe, verrou, et table d'aperçu ─── */
.voie-cat-classe { border-color: color-mix(in srgb, var(--vclc, #64748b) 55%, transparent); }
.voie-cat-classe.is-active { border-color: var(--vclc, #64748b); }
/* ⚠️ Le verrou s'ESTOMPE, il ne se désactive pas : le bouton doit rester
   cliquable pour montrer les bonus — c'est tout l'objet de l'écran. Un
   `disabled` ne dirait rien et fermerait la seule porte utile. */
.voie-cat-classe.is-verrou { opacity: .62; }
.voie-cat-classe.is-verrou.is-active { opacity: 1; }
.voie-cats-sep {
  width: 1px; align-self: stretch; margin: 2px 6px;
  background: rgba(148, 163, 184, .35);
}
.voie-classe.is-verrou { border-style: dashed; }
.vcl-cadenas { margin-left: auto; font-size: 12px; opacity: .75; }
.vcl-verrou-txt { font-size: 13.5px; }
.vcl-note { font-size: 12.5px; opacity: .7; margin-top: 4px; }
/* ⚠️ LE TABLEAU N'EST PLUS BORNÉ, ET C'EST UNE DEMANDE EXPLICITE DE LORYAN
   (« laisse-le continuer à aller vers le bas, là tu le brides directement,
   je ne vois pas l'intérêt vs ce qui existe déjà pour les autres voies »).
   Ma première version l'enfermait dans un cadre de 340 px qui défilait
   tout seul — au prétexte que trente rangs repousseraient le reste de la
   page. Mais il N'Y A PAS de reste : la Voie est seule sur son écran, et
   les trois Voies historiques laissent déjà la PAGE défiler. Deux
   ascenseurs imbriqués pour le même contenu, c'est une molette qui n'agit
   pas là où on croit.
   ⚠️ L'EN-TÊTE RESTE COLLANT, mais il se colle désormais à la VUE et non
   plus au cadre : sans `top`, une ligne de titre disparaîtrait dès le
   premier tour de molette, et on lirait « +38 % » sans savoir de quoi. */
.vcl-table-box {
  margin-top: 10px;
  border: 1px solid rgba(148, 163, 184, .18); border-radius: 8px;
}
.vcl-table-box .vcl-table { margin-top: 0; }
.vcl-table-box thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--panel, #0f1420);
}
.vcl-cap { font-size: 12.5px; line-height: 1.4; }
/* ─── v0.0.82 : LA COLONNE « REMPLISSAGE » ──────────────────────────
   Demande de Loryan (capture, colonne de droite entourée) : « affiche ici
   le % de remplissage ». La jauge se lit d'un coup d'œil sur trente
   lignes ; le nombre à côté donne le chiffre exact.
   ⚠️ LARGEUR FIXE ET `nowrap` : sans eux, la colonne des capacités —
   la seule à porter du texte long — mange toute la place et la jauge se
   réduit à quelques pixels sur les lignes qui en portent une. */
.vcl-rmp { width: 132px; white-space: nowrap; text-align: right; }
.vcl-rmp-jauge {
  display: inline-block; vertical-align: middle;
  width: 78px; height: 6px; border-radius: 3px;
  background: rgba(148, 163, 184, .18); overflow: hidden;
}
.vcl-rmp-jauge i {
  display: block; height: 100%; border-radius: 3px;
  background: var(--vclc, #60a5fa);
}
.vcl-rmp-txt { display: inline-block; min-width: 42px; margin-left: 6px; font-variant-numeric: tabular-nums; }
/* Un rang déjà franchi affiche 100 % : la jauge y est pleine, donc muette.
   On l'estompe pour que l'œil aille droit à la première ligne qui bouge. */
.vcl-table tr.is-atteint .vcl-rmp { opacity: .45; }

/* ─── v0.0.82 : LE TABLEAU DES RANGS EN MOBILE ──────────────────────
   ⚠️ MESURÉ À 390 px : le tableau fait 552 px dans une boîte de 286, donc
   il était COUPÉ — deux colonnes sur cinq invisibles, et rien pour le
   signaler puisque la page, elle, ne défilait pas horizontalement. C'est
   le défaut nº 1 de la règle de vérification mobile, rencontré sur le
   premier écran vérifié.
   On resserre d'abord (paddings et jauge), puis on laisse la BOÎTE
   défiler à l'horizontale : chaque colonne reste atteignable au doigt.
   ⚠️ CONSÉQUENCE ASSUMÉE, et il faut la connaître : `overflow-x` sur un
   conteneur en fait un CONTEXTE DE DÉFILEMENT, donc l'en-tête collant
   (`position: sticky; top: 0`) se colle désormais à la BOÎTE et non plus
   à la page. Sur mobile il ne suivra donc plus le défilement vertical —
   c'est le prix d'un tableau lisible en entier, et il ne se paie qu'ici :
   sur grand écran, rien ne change. */
body.mobile-mode .vcl-table-box { overflow-x: auto; }
body.mobile-mode .vcl-table th,
body.mobile-mode .vcl-table td { padding-left: 5px; padding-right: 5px; }
body.mobile-mode .vcl-rmp { width: 86px; }
body.mobile-mode .vcl-rmp-jauge { width: 40px; }
body.mobile-mode .vcl-rmp-txt { min-width: 34px; margin-left: 4px; }
/* La description d'une capacité est la seule colonne à texte long : c'est
   elle qui doit céder la place, pas les chiffres. */
body.mobile-mode .vcl-cap-desc { display: block; font-size: 11.5px; }
.vcl-cap-nom { font-weight: 600; color: var(--vclc, #cbd5e1); }
.vcl-cap-avenir {
  margin-left: 6px; font-size: 10px; text-transform: uppercase;
  letter-spacing: .06em; padding: 1px 5px; border-radius: 4px;
  border: 1px dashed rgba(148, 163, 184, .5); opacity: .7;
}
.vcl-cap-desc { display: block; opacity: .65; font-size: 12px; }
.vcl-table tr.is-cap td { border-top: 1px solid color-mix(in srgb, var(--vclc, #64748b) 35%, transparent); }

.vcl-table {
  width: 100%; border-collapse: collapse; margin-top: 10px;
  font-size: 13px; font-variant-numeric: tabular-nums;
}
.vcl-table th {
  text-align: left; font-size: 11px; text-transform: uppercase;
  letter-spacing: .06em; opacity: .55; font-weight: 600;
  padding: 4px 10px 6px; border-bottom: 1px solid rgba(148, 163, 184, .25);
}
.vcl-table td { padding: 5px 10px; border-bottom: 1px solid rgba(148, 163, 184, .12); }
.vcl-table tr:last-child td { border-bottom: 0; }
.vcl-table .vcl-r { width: 1%; opacity: .6; }
.vcl-table .vcl-b { text-align: right; color: var(--vclc, #cbd5e1); font-weight: 600; }
.vcl-table tr.is-atteint td { background: color-mix(in srgb, var(--vclc, #64748b) 12%, transparent); }
.vcl-table tr.is-suivant td { box-shadow: inset 2px 0 0 var(--vclc, #64748b); }
.vcl-effet b { color: var(--vclc, #cbd5e1); }
.vcl-suite { opacity: .6; font-size: 12px; margin-left: 8px; }
@media (max-width: 560px) {
  .vcl-rang, .vcl-reste { margin-left: 0; }
}

.voie-entete {
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  background: linear-gradient(180deg, rgba(34,197,94,.08), rgba(8,12,24,.4));
  border: 1px solid var(--color-border-subtle);
  border-left: 3px solid var(--voiec, #22c55e);
  border-radius: var(--radius-md); padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
}
.voie-entete-txt { flex: 1 1 240px; min-width: 0; }
.voie-entete-nom { font-family: var(--font-display, inherit); font-size: 16px; font-weight: 600; }
.voie-entete-sub { font-size: 12px; color: var(--color-text-secondary); margin-top: 2px; }
.voie-entete-jauge { flex: 1 1 220px; min-width: 180px; }
.voie-jauge {
  height: 8px; border-radius: var(--radius-pill); overflow: hidden;
  background: rgba(255,255,255,.07);
}
.voie-jauge > span { display: block; height: 100%; background: var(--voiec, #22c55e); transition: width .3s; }
.voie-jauge-txt { font-family: var(--font-mono); font-size: 11px; color: var(--color-text-secondary); margin-top: 4px; }
.voie-axes { display: flex; gap: 6px; flex-wrap: wrap; flex: 1 1 100%; }
.voie-axe-acquis {
  font-family: var(--font-mono); font-size: 10.5px; color: #86efac;
  border: 1px solid rgba(34,197,94,.35); border-radius: var(--radius-pill); padding: 1px 9px;
}

.voie-layout { display: grid; grid-template-columns: minmax(0,1fr) 320px; gap: var(--space-4); align-items: start; }
@media (max-width: 1080px) { .voie-layout { grid-template-columns: 1fr; } }

.voie-canvas {
  position: relative; overflow-x: auto;
  background: linear-gradient(180deg, rgba(15,24,40,.55), rgba(8,12,24,.55));
  border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-3);
}
.voie-edges { position: absolute; inset: 0; pointer-events: none; }
.voie-chapitres { position: relative; display: flex; flex-direction: column; gap: 34px; }
.voie-chapitre-hdr {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding-bottom: 6px; margin-bottom: 12px;
  border-bottom: 1px dashed var(--color-border-subtle);
}
.voie-chapitre-n {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em;
  color: var(--color-text-tertiary, var(--color-text-secondary)); text-transform: uppercase;
}
.voie-chapitre-nom { font-size: 14px; font-weight: 600; color: var(--color-text-primary); }
.voie-chapitre-sub { font-size: 11.5px; color: var(--color-text-secondary); flex: 1 1 200px; }
.voie-chapitre-cpt { font-family: var(--font-mono); font-size: 11px; color: var(--color-text-secondary); }
.voie-row { display: flex; flex-wrap: wrap; gap: 34px 16px; }

/* Le nœud : plus large que celui de l'arbre — il porte une phrase */
.voie-node {
  width: 208px; display: flex; flex-direction: column; gap: 5px;
  background: var(--color-bg-card); border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md); padding: 9px 10px; cursor: pointer;
  transition: border-color var(--anim-fast), transform var(--anim-fast);
  position: relative; z-index: 1;   /* au-dessus du SVG d'arêtes */
  font: inherit; color: inherit; text-align: left;
}
.voie-node:hover { border-color: var(--color-border-strong); transform: translateY(-1px); }
.voie-node:focus-visible { outline: 2px solid var(--accent2); outline-offset: 2px; }
.voie-node-hdr { display: flex; align-items: center; gap: 7px; }
.voie-node-rang {
  font-family: var(--font-mono); font-size: 10px; width: 18px; height: 18px; flex: 0 0 18px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; background: rgba(255,255,255,.06); color: var(--color-text-secondary);
}
/* ⚠️ LE TITRE S'AUTORISE DEUX LIGNES (constat d'audit 3ᵉ passage). En
   `white-space: nowrap`, des titres comme « Le premier branchement » ou
   « L'Institut ouvre ses portes » étaient coupés en « … » alors que la carte
   avait la HAUTEUR pour deux lignes — la condition juste en dessous en prend
   déjà deux. On borne à deux lignes (le titre le plus long tient), le reste
   file dans l'infobulle du nœud. */
.voie-node-titre {
  flex: 1 1 auto; min-width: 0; font-size: 12px; font-weight: 600; line-height: 1.25;
  color: var(--color-text-primary);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.voie-node-badge {
  font-family: var(--font-mono); font-size: 10px; color: var(--color-text-secondary);
  border: 1px solid var(--color-border-subtle); border-radius: var(--radius-pill); padding: 0 7px;
}
.voie-node-cond {
  font-size: 11px; line-height: 1.35; color: var(--color-text-secondary);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  min-height: 30px;
}
.voie-node-barre { display: block; height: 4px; border-radius: 2px; background: rgba(255,255,255,.07); overflow: hidden; }
.voie-node-barre > span { display: block; height: 100%; background: rgba(148,163,184,.7); }

/* Les trois états, et ils doivent se distinguer AU PREMIER COUP D'ŒIL :
   c'est le seul écran où le joueur cherche « qu'est-ce que je peux
   toucher maintenant ». */
.voie-node.is-encours { border-color: rgba(148,163,184,.45); }
.voie-node.is-encours .voie-node-barre > span { background: var(--accent2, #0ea5e9); }
.voie-node.is-prete {
  border-color: rgba(245,158,11,.75);
  box-shadow: 0 0 0 1px rgba(245,158,11,.25) inset, 0 0 16px rgba(245,158,11,.12);
}
.voie-node.is-prete .voie-node-badge { color: #fbbf24; border-color: rgba(245,158,11,.5); }
.voie-node.is-prete .voie-node-barre > span { background: #f59e0b; }
.voie-node.is-faite { border-color: rgba(34,197,94,.45); opacity: .78; }
.voie-node.is-faite .voie-node-badge { color: #86efac; border-color: rgba(34,197,94,.45); }
.voie-node.is-faite .voie-node-barre > span { background: rgba(34,197,94,.7); }
.voie-node.is-selected { border-color: var(--accent2); box-shadow: 0 0 0 1px var(--accent2) inset; opacity: 1; }

/* Les récompenses, en pastilles */
.voie-gains { display: flex; flex-wrap: wrap; gap: 4px; }
.voie-gain {
  font-family: var(--font-mono); font-size: 10px; border-radius: var(--radius-pill);
  padding: 1px 7px; border: 1px solid var(--color-border-subtle); color: var(--color-text-secondary);
  white-space: nowrap;
}
.voie-gain-caisson { border-color: rgba(245,158,11,.35); color: #fcd34d; }
.voie-gain-axe     { border-color: rgba(45,212,191,.35); color: #5eead4; }
.voie-gain-equip   { border-color: rgba(14,165,233,.35); color: #7dd3fc; }
.voie-gains.is-compact .voie-gain-res { margin-right: 4px; }

/* ─── v0.0.152 : LA PASTILLE QUI PORTE UNE PHRASE, ET NON UN CHIFFRE ──
   ⚠️ `white-space: nowrap` EST JUSTE POUR UN CHIFFRE, ET FAUX POUR UNE
   PHRASE. Il existe pour que « 22k · 11k · 6k » ne se casse pas au milieu
   d'un nombre — mais il fait SORTIR DE SA CARTE toute étiquette plus
   large qu'elle : mesuré à 208 px de carte, « ⏱ Premier relevé deux fois
   plus court » débordait de vingt-trois pixels, et rien ne le signalait.
   Les faveurs du chapitre « Partir » disent ce qu'elles PERMETTENT, en
   français : elles vont donc à la ligne plutôt que de déborder ou d'être
   coupées. `max-width: 100%` est la ceinture, `white-space: normal` la
   bretelle — sans la première, une phrase sans espace déborderait encore. */
.voie-gain-phrase {
  white-space: normal; max-width: 100%;
  line-height: 1.3; text-align: left;
}

/* ─── v0.0.51 : LES FENÊTRES DU JEU ──────────────────────────────────
   Elles remplacent les quarante-deux boîtes du navigateur : `alert`,
   `confirm`, `prompt`. Un seul gabarit, quatre tons — info, succès,
   danger, question — parce que quarante-deux fenêtres écrites à la main
   auraient divergé dès la troisième.
   ⚠️ Le fond est un élément À PART et non un `::before` : il porte le clic
   de fermeture, et un pseudo-élément ne se clique pas. */
#dsh-fenetre-hote:not(.est-ouvert) { display: none; }
#dsh-fenetre-hote {
  position: fixed; inset: 0; z-index: 500;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.dfen-fond {
  position: absolute; inset: 0; background: rgba(3, 6, 14, .74);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.dfen {
  position: relative; width: min(94vw, 460px); max-height: 86vh; overflow-y: auto;
  background: linear-gradient(180deg, #16233c, #0b1220);
  border: 1px solid rgba(56, 189, 248, .34); border-radius: 16px;
  padding: 32px 26px 24px; text-align: center;
  box-shadow: 0 26px 70px rgba(0, 0, 0, .66), 0 0 0 1px rgba(255, 255, 255, .04);
  animation: dfen-entree .24s cubic-bezier(.16, 1, .3, 1);
}
.dfen-succes { border-color: rgba(245, 158, 11, .40); }
.dfen-danger { border-color: rgba(233, 69, 96, .44); }
@keyframes dfen-entree {
  from { opacity: 0; transform: translateY(14px) scale(.96); }
  to   { opacity: 1; transform: none; }
}
/* Une seule lueur, immobile : elle donne le ton sans transformer la
   fenêtre en feu d'artifice. Sa couleur suit celle du ton. */
.dfen-lueur {
  position: absolute; top: -120px; left: 50%; transform: translateX(-50%);
  width: 320px; height: 250px; pointer-events: none;
  background: radial-gradient(circle, rgba(56, 189, 248, .22), transparent 68%);
}
.dfen-succes .dfen-lueur { background: radial-gradient(circle, rgba(245, 158, 11, .30), transparent 68%); }
.dfen-danger .dfen-lueur { background: radial-gradient(circle, rgba(233, 69, 96, .24), transparent 68%); }
.dfen-ico {
  position: relative; font-size: 44px; line-height: 1; margin-bottom: 10px;
  filter: drop-shadow(0 4px 14px rgba(56, 189, 248, .40));
}
.dfen-succes .dfen-ico { filter: drop-shadow(0 4px 14px rgba(245, 158, 11, .50)); }
.dfen-danger .dfen-ico { filter: drop-shadow(0 4px 14px rgba(233, 69, 96, .45)); }
.dfen-sur {
  position: relative; font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  color: #7dd3fc; font-weight: 700;
}
.dfen-succes .dfen-sur { color: #fcd34d; }
.dfen-danger .dfen-sur { color: #fca5a5; }
.dfen-titre {
  position: relative; margin: 5px 0 16px; font-size: 20px; font-weight: 700;
  line-height: 1.28; color: var(--color-text-primary, #eaf2ff); text-wrap: balance;
}
.dfen-corps { position: relative; }
.dfen-texte {
  margin: 0; font-size: 14px; line-height: 1.6; text-align: left;
  color: var(--color-text-secondary, #b8c2da);
}
/* Un message d'une seule ligne se centre ; c'est le paragraphe qui se
   lit de gauche à droite. Départager sur la longueur donnerait un
   alignement qui saute d'une fenêtre à l'autre — on garde la règle
   simple : le texte à gauche, le titre au centre. */
.dfen-saisie {
  display: block; width: 100%; margin-top: 14px;
  background: rgba(8, 13, 24, .9); color: var(--color-text-primary, #eaf2ff);
  border: 1px solid var(--color-border-subtle, #1f2937); border-radius: 8px;
  padding: 9px 12px; font-family: inherit; font-size: 14px;
}
.dfen-saisie:focus { outline: none; border-color: #38bdf8; }
.dfen-boutons {
  position: relative; margin-top: 20px;
  display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
}
.dfen-btn { min-width: 128px; }
/* Le bouton principal prend la couleur du TON, pas la robe claire du
   gabarit commun : un bloc blanc au milieu d'une fenêtre sombre tire tout
   l'œil sur lui alors que le sujet est au-dessus. */
.dfen-btn.est-principal {
  background: rgba(56, 189, 248, .18); border: 1px solid rgba(56, 189, 248, .55); color: #7dd3fc;
}
.dfen-btn.est-principal:hover { background: rgba(56, 189, 248, .30); color: #e0f2fe; }
.dfen-succes .dfen-btn.est-principal {
  background: rgba(245, 158, 11, .18); border-color: rgba(245, 158, 11, .58); color: #fcd34d;
}
.dfen-succes .dfen-btn.est-principal:hover { background: rgba(245, 158, 11, .30); color: #fef3c7; }
.dfen-btn.est-discret {
  background: transparent; color: var(--color-text-secondary, #b8c2da);
  border: 1px solid var(--color-border-subtle, #1f2937);
}
.dfen-btn.est-discret:hover { border-color: #2f425e; color: var(--color-text-primary, #eaf2ff); }
.dfen-btn.est-danger {
  background: rgba(233, 69, 96, .16); color: #fca5a5;
  border: 1px solid rgba(233, 69, 96, .48);
}
.dfen-btn.est-danger:hover { background: rgba(233, 69, 96, .28); color: #fee2e2; }
/* Les pastilles de gain des Voies, réutilisées telles quelles dans la
   fenêtre de récompense — mais AGRANDIES : sur la fiche d'un palier on
   les survole, ici on vient précisément les lire. */
.dfen-corps .voie-gains { justify-content: center; gap: 7px; }
.dfen-corps .voie-gain { font-size: 13px; padding: 5px 12px; background: rgba(255, 255, 255, .035); }
.dfen-ou { margin-top: 18px; font-size: 12.5px; color: var(--color-text-secondary, #b8c2da); }
.dfen-ou b { color: #7dd3fc; font-weight: 600; }
.dfen-alerte {
  margin-top: 12px; font-size: 12px; line-height: 1.5; text-align: left;
  color: #fcd34d; background: rgba(245, 158, 11, .10);
  border: 1px solid rgba(245, 158, 11, .28); border-radius: 8px; padding: 8px 11px;
}
@media (prefers-reduced-motion: reduce) { .dfen { animation: none; } }
}

.voie-legende { display: flex; gap: 14px; flex-wrap: wrap; margin-top: var(--space-3); font-size: 11px; color: var(--color-text-secondary); }
.voie-leg-item { display: inline-flex; align-items: center; gap: 5px; }
.voie-leg-dot { width: 9px; height: 9px; border-radius: 50%; background: rgba(148,163,184,.4); }
.voie-leg-dot.is-faite { background: rgba(34,197,94,.8); }
.voie-leg-dot.is-prete { background: #f59e0b; }
.voie-leg-dot.is-encours { background: var(--accent2, #0ea5e9); }

/* Panneau de détail — c'est lui qui porte le POURQUOI, et c'est le cœur
   de la mécanique : une Voie qui ne dirait que « quoi faire » serait une
   liste de courses. */
.voie-detail {
  background: var(--color-bg-card); border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md); padding: var(--space-4);
  position: sticky; top: calc(var(--resbar-bottom, 88px) + 16px);
  max-height: calc(100vh - var(--resbar-bottom, 88px) - 32px); overflow-y: auto;
}
.voie-detail-empty { color: var(--color-text-secondary); font-size: 13px; }
.voie-detail-hdr { padding-bottom: var(--space-3); border-bottom: 1px solid var(--color-border-subtle); }
.voie-detail-ch { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .07em; color: var(--color-text-secondary); }
.voie-detail-titre { font-size: 15px; font-weight: 600; margin-top: 3px; }
.voie-detail-cond { font-size: 12.5px; line-height: 1.45; margin-top: var(--space-3); }
.voie-detail-etat { font-family: var(--font-mono); font-size: 11.5px; margin-top: 8px; color: var(--color-text-secondary); }
.voie-detail-etat.is-prete { color: #fbbf24; }
.voie-detail-etat.is-faite { color: #86efac; }
.voie-detail-barre { height: 6px; border-radius: 3px; background: rgba(255,255,255,.07); overflow: hidden; margin-top: 6px; }
.voie-detail-barre > span { display: block; height: 100%; background: var(--accent2, #0ea5e9); }
.voie-detail-sec {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--color-text-secondary); margin-top: var(--space-4); margin-bottom: 5px;
}
.voie-detail-pourquoi { font-size: 12.5px; line-height: 1.5; color: var(--color-text-primary); }
.voie-detail-gains { display: flex; flex-wrap: wrap; gap: 5px; }
.voie-detail-note {
  font-size: 11.5px; line-height: 1.45; color: var(--color-text-secondary);
  border-left: 2px solid rgba(245,158,11,.5); padding-left: 8px; margin-top: var(--space-3);
}
/* v0.0.7 : ce que fait chaque axe versé,
   écrit sous les pastilles de la récompense — la version lisible au doigt
   de l'infobulle du badge d'en-tête. */
.voie-detail-axe {
  font-size: 11.5px; line-height: 1.45; color: var(--color-text-secondary);
  border-left: 2px solid rgba(14,165,233,.5); padding-left: 8px; margin-top: 6px;
}
.voie-detail-axe b { color: var(--color-text-primary); }
/* ─── v0.0.83 : LE DEVIS DU PLEIN, dans la fiche du palier ────────────
   Signalement de Fainian : « on ne connaît pas le montant de la
   récompense ». Deux lignes, une par réservoir, chacune disant ce qu'il
   faut, ce qu'on a, et ce qu'il faut monter quand ça ne rentre pas.
   ⚠️ `grid` et non `flex` : les quatre colonnes doivent s'aligner d'une
   ligne à l'autre, sinon les deux chiffres à comparer ne sont pas l'un
   sous l'autre — c'est très exactement la comparaison qu'on affiche. */
/* ─── v0.0.83 : une tuile du bandeau qui sait expliquer son débit ─────
   Demande de Loryan : le panneau « d'où sort ce débit ? » s'ouvre aussi au
   survol du bandeau du haut. ⚠️ On ne dessine PAS de pastille « i » : le
   bandeau est déjà dense, et la tuile entière est la zone de survol. Un
   curseur d'aide et un liseré au survol suffisent à l'annoncer. */
.res-explicable { cursor: help; }
.res-explicable:hover, .res-explicable.is-open {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--rc, #67e8f9) 55%, transparent);
}

.voie-plein { margin-top: 8px; display: grid; gap: 4px; }
.voie-plein-t { font-size: 11px; color: var(--color-text-secondary); }
.voie-plein-l {
  display: grid; grid-template-columns: 18px 74px 1fr auto; gap: 6px;
  align-items: baseline; font-size: 11.5px;
  padding: 4px 6px; border-radius: var(--radius-sm);
  background: rgba(148,163,184,.07); border-left: 2px solid rgba(148,163,184,.35);
}
.voie-plein-l.is-ok    { border-left-color: rgba(34,197,94,.65); }
.voie-plein-l.is-court { border-left-color: rgba(245,158,11,.75); }
.voie-plein-nom { color: var(--color-text-secondary); }
.voie-plein-val { font-weight: 700; color: #e2e8f0; }
.voie-plein-cap { font-size: 10.5px; color: var(--color-text-secondary); }
.voie-plein-ok  { grid-column: 3 / -1; font-size: 10.5px; color: #4ade80; }
.voie-plein-ko  { grid-column: 3 / -1; font-size: 10.5px; color: #fbbf24; }
.voie-plein-note {
  font-size: 10.5px; line-height: 1.45; color: var(--color-text-secondary);
  padding-left: 8px; border-left: 2px solid rgba(56,189,248,.45);
}
/* ⚠️ AU TÉLÉPHONE, LES QUATRE COLONNES NE TIENNENT PAS — mesuré à 390 px :
   « il manque 12 400 — monte les Cellules d'Accumulation » sort du panneau.
   Elles passent donc en deux rangées plutôt qu'en défilement horizontal :
   une fiche de palier ne défile pas de côté, et le texte tronqué serait le
   seul indice (leçon du tiroir des files, v0.0.82). */
@media (max-width: 700px) {
  .voie-plein-l { grid-template-columns: 18px 1fr auto; }
  .voie-plein-cap { grid-column: 2 / -1; }
}

.voie-encaisser {
  width: 100%; margin-top: var(--space-4); padding: 10px; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 600;
  background: rgba(245,158,11,.14); color: #fcd34d;
  border: 1px solid rgba(245,158,11,.6); border-radius: var(--radius-md);
}
.voie-encaisser:hover:not(:disabled) { background: rgba(245,158,11,.24); border-color: #f59e0b; }
.voie-encaisser:disabled { background: none; color: var(--color-text-secondary); border-color: var(--color-border-subtle); cursor: default; }
.voie-detail-vm { font-size: 11px; color: var(--color-text-secondary); margin-top: 8px; text-align: center; }
.voie-vide { color: var(--color-text-secondary); font-size: 13px; padding: var(--space-5); text-align: center; }
/* v0.0.40, lot 6 — LE VERROU D'OUVERTURE. Une Voie qui attend la
   précédente reste VISIBLE (on doit pouvoir lire ce qui nous attend) mais
   elle dit clairement ce qui la retient, et où. Un mur muet aurait laissé
   le joueur cliquer des boutons grisés sans comprendre. */
.voie-verrou {
  font-size: 12.5px; line-height: 1.45; color: #fcd34d;
  background: rgba(245,158,11,.08); border: 1px solid rgba(245,158,11,.35);
  border-radius: var(--radius-md); padding: 9px 12px; margin-bottom: var(--space-3);
}

/* ─── Interrupteur voieVueEtape : LA VUE « L'ÉTAPE DU MOMENT » ─────────
   Maquette C choisie par Loryan : la frise des chapitres, l'étape en grand,
   les trois suivantes, le « déjà fait » replié. Ces classes n'existent que
   dans la nouvelle vue : l'interrupteur éteint, rien ici ne s'applique.
   Plancher de lecture : 11 px partout (la racine du jeu est à 14 px). */
.vetape { display: flex; flex-direction: column; }
.vetape button { font: inherit; color: inherit; }

/* 1. La frise : un segment par chapitre, un trait par étape. */
.vetape-frise { display: flex; gap: var(--space-2); margin-bottom: var(--space-3); }
.vetape-seg {
  flex: 1 1 0; min-width: 0; text-align: left; cursor: pointer;
  padding: 9px 12px; border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md); background: var(--color-bg-card);
}
.vetape-seg:hover { border-color: var(--color-border-strong); }
.vetape-seg.is-on { border-color: var(--accent2); box-shadow: 0 0 0 1px var(--accent2) inset; }
.vetape-seg-tete { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 12.5px; }
.vetape-seg-nom { min-width: 0; overflow-wrap: break-word; color: var(--color-text-secondary); }
.vetape-seg.is-on .vetape-seg-nom { color: var(--color-text-primary); font-weight: 600; }
.vetape-seg.is-fini .vetape-seg-nom { color: #86efac; }
.vetape-seg-cpt { flex-shrink: 0; font-family: var(--font-mono); font-size: 11.5px; color: var(--color-text-secondary); }
.vetape-points { display: flex; gap: 4px; margin-top: 8px; }
.vetape-pt { flex: 1 1 0; height: 5px; border-radius: 3px; background: rgba(255,255,255,.09); }
.vetape-pt.is-faite { background: rgba(34,197,94,.8); }
.vetape-pt.is-prete { background: #f59e0b; }
.vetape-pt.is-encours { background: rgba(14,165,233,.45); }
.vetape-pt.is-vue { background: var(--accent2); box-shadow: 0 0 0 1px #7dd3fc; }
.vetape-pt.is-faite.is-vue { background: #4ade80; box-shadow: 0 0 0 1px #bbf7d0; }

/* 2. L'étape en grand. */
.vetape-hero {
  display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 16px 22px;
  padding: 20px 22px; border: 1px solid var(--accent2); border-radius: 16px;
  background: linear-gradient(135deg, rgba(14,165,233,.12), rgba(8,12,24,.55));
  box-shadow: 0 12px 34px rgba(0,0,0,.35);
  scroll-margin-top: calc(var(--resbar-bottom, 88px) + 12px);
}
.vetape-hero.is-prete { border-color: #f59e0b; background: linear-gradient(135deg, rgba(245,158,11,.13), rgba(8,12,24,.55)); }
.vetape-hero.is-faite { border-color: rgba(34,197,94,.55); background: linear-gradient(135deg, rgba(34,197,94,.09), rgba(8,12,24,.55)); }
.vetape-hero.is-avenir { border-color: var(--color-border-strong); }
.vetape-gauche, .vetape-cote { min-width: 0; }
.vetape-sur {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase; color: #7dd3fc;
}
.vetape-pastille {
  padding: 1px 9px; border-radius: var(--radius-pill); border: 1px solid currentColor;
  letter-spacing: .03em; text-transform: none; font-size: 11.5px;
}
.vetape-hero.is-prete .vetape-pastille { color: #fcd34d; }
.vetape-hero.is-faite .vetape-pastille { color: #86efac; }
.vetape-hero.is-avenir .vetape-pastille { color: var(--color-text-secondary); }
.vetape-nav { margin-left: auto; display: inline-flex; gap: 6px; align-items: center; }
.vetape-fleche, .vetape-retour {
  cursor: pointer; border: 1px solid var(--color-border-subtle); background: rgba(8,12,24,.45);
  border-radius: var(--radius-md); font-size: 12px; line-height: 1; padding: 6px 9px; text-transform: none; letter-spacing: 0;
}
.vetape-fleche:hover:not(:disabled), .vetape-retour:hover { border-color: var(--accent2); }
.vetape-fleche:disabled { opacity: .35; cursor: default; }
.vetape-retour { color: #7dd3fc; font-family: inherit; }
.vetape-titre { font-family: var(--font-display, inherit); font-size: 25px; font-weight: 700; line-height: 1.2; margin: 8px 0 8px; }
.vetape-cond { font-size: 15px; line-height: 1.45; }
.vetape-barre { height: 8px; border-radius: var(--radius-pill); background: rgba(255,255,255,.08); overflow: hidden; margin-top: 16px; }
.vetape-barre > span { display: block; height: 100%; background: var(--accent2); transition: width .3s; }
.vetape-hero.is-prete .vetape-barre > span { background: #f59e0b; }
.vetape-hero.is-faite .vetape-barre > span { background: rgba(34,197,94,.8); }
.vetape-chiffre { font-family: var(--font-mono); font-size: 12.5px; color: var(--color-text-secondary); margin-top: 6px; }
.vetape-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; }
.vetape-btn {
  cursor: pointer; font-size: 14px; font-weight: 600; padding: 10px 18px;
  border-radius: var(--radius-md); border: 1px solid var(--color-border-strong); background: rgba(8,12,24,.45);
}
.vetape-btn.is-ou { border-color: rgba(14,165,233,.7); background: rgba(14,165,233,.16); color: #e0f2fe; }
.vetape-btn.is-ou:hover { background: rgba(14,165,233,.28); }
.vetape-btn.is-lecon:hover { border-color: var(--accent2); }
.vetape-btn.is-encaisser { border-color: #f59e0b; background: rgba(245,158,11,.18); color: #fcd34d; }
.vetape-btn.is-encaisser:hover { background: rgba(245,158,11,.3); }
.vetape-cote { display: flex; flex-direction: column; gap: 10px; }
.vetape-boite { border: 1px solid var(--color-border-subtle); border-radius: 12px; padding: 12px 14px; background: rgba(8,12,24,.4); }
.vetape-etiq {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--color-text-secondary); margin-bottom: 7px;
}
.vetape-pourquoi { font-size: 13.5px; line-height: 1.5; }
.vetape-boite .voie-gains { gap: 6px; }
.vetape-boite .voie-gain { font-size: 12.5px; padding: 3px 10px; }
.vetape-boite .voie-detail-axe, .vetape-boite .voie-detail-note { font-size: 12px; }
.vetape-vm { font-size: 11.5px; color: var(--color-text-secondary); margin-top: 8px; }
.vetape-lecon { grid-column: 1 / -1; border-top: 1px solid var(--color-border-subtle); padding-top: 14px; }
.vetape-lecon .tuto-corps.vguide-lecon-corps { font-size: 13.5px; }

/* Les récompenses prêtes que l'écran ne montre pas déjà. */
.vetape-prets { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; font-size: 12.5px; color: #fcd34d; }
.vetape-prets-t { font-weight: 600; }
.vetape-pret {
  cursor: pointer; font-size: 12.5px; padding: 4px 11px; border-radius: var(--radius-pill);
  border: 1px solid rgba(245,158,11,.6); background: rgba(245,158,11,.12); color: #fde68a;
}

/* 3. Ensuite : trois cartes. */
.vetape-sec {
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--color-text-secondary); margin: 20px 0 8px;
}
.vetape-suite { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); }
.vetape-carte {
  display: flex; flex-direction: column; gap: 6px; min-width: 0; text-align: left; cursor: pointer;
  padding: 12px 14px; border: 1px solid var(--color-border-subtle); border-radius: 12px; background: var(--color-bg-card);
}
.vetape-carte:hover { border-color: var(--color-border-strong); transform: translateY(-1px); }
.vetape-carte.is-prete { border-color: rgba(245,158,11,.6); }
.vetape-carte.is-faite { border-color: rgba(34,197,94,.45); }
.vetape-carte-n { display: flex; justify-content: space-between; gap: 8px; font-size: 11.5px; color: var(--color-text-secondary); }
.vetape-carte.is-prete .vetape-carte-badge { color: #fcd34d; }
.vetape-carte.is-faite .vetape-carte-badge { color: #86efac; }
.vetape-carte-titre { font-size: 14px; font-weight: 600; }
.vetape-carte-cond { font-size: 12.5px; line-height: 1.4; color: var(--color-text-secondary); }
.vetape-carte .voie-gain { font-size: 11px; }

/* 4. Déjà fait, replié. */
.vetape-deja { margin-top: 14px; border: 1px dashed var(--color-border-subtle); border-radius: 12px; }
.vetape-deja-tete {
  width: 100%; text-align: left; cursor: pointer; background: none; border: 0;
  padding: 11px 14px; font-size: 13px; color: var(--color-text-secondary);
}
.vetape-deja-tete:hover { color: var(--color-text-primary); }
.vetape-deja-fl { margin-left: 6px; color: #7dd3fc; }
.vetape-deja-liste { display: flex; flex-direction: column; padding: 0 8px 8px; }
.vetape-deja-l {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; text-align: left; cursor: pointer;
  background: none; border: 0; border-radius: var(--radius-md); padding: 7px 8px; font-size: 13px;
}
.vetape-deja-l:hover, .vetape-deja-l.is-vue { background: rgba(255,255,255,.05); }
.vetape-deja-n { color: #86efac; font-family: var(--font-mono); font-size: 12px; }
.vetape-deja-t { font-weight: 600; }
.vetape-deja-c { color: var(--color-text-secondary); font-size: 12px; }

/* ⚠️ AU TÉLÉPHONE (700 px, le seuil du mode mobile) : une seule colonne, et
   la frise ne garde le NOM que du chapitre ouvert. Avec cinq chapitres
   (l'Extension), cinq noms dans 390 px se coupaient au milieu d'un mot ; le
   numéro et les traits suffisent pour les autres, le nom complet est écrit
   au-dessus du titre de l'étape. */
@media (max-width: 700px) {
  .vetape-frise { gap: 6px; }
  .vetape-seg { padding: 8px 9px; }
  .vetape-seg.is-on { flex-grow: 3; }
  .vetape-seg:not(.is-on) .vetape-seg-mot, .vetape-seg-cpt { display: none; }
  .vetape-hero { grid-template-columns: 1fr; padding: 16px; }
  .vetape-titre { font-size: 21px; }
  .vetape-nav { margin-left: 0; }
  .vetape-suite { grid-template-columns: 1fr; }
}

/* ─── Interrupteur amiralEmpire : L'ONGLET AMIRAL ──────────────────────
   La réserve de points en tête, les sept compétences en cartes, le barème
   des Vaisseaux Mères. Ces classes n'existent que dans cet onglet, qui ne
   s'ouvre que sous l'interrupteur. Plancher de lecture : 11 px. */
.amiral { display: flex; flex-direction: column; gap: 4px; }
.amiral-vide { color: var(--color-text-secondary); font-size: 13px; padding: var(--space-5); text-align: center; }
.amiral-reserve {
  border: 1px solid var(--accent2); border-radius: 16px; padding: 18px 20px;
  background: linear-gradient(135deg, rgba(14,165,233,.12), rgba(8,12,24,.55));
}
.amiral-reserve.is-depasse { border-color: #f59e0b; }
.amiral-reserve-chiffres { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.amiral-chiffre { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.amiral-chiffre-v { font-family: var(--font-display, inherit); font-size: 30px; font-weight: 700; line-height: 1.1; }
.amiral-chiffre-l { font-size: 12.5px; color: var(--color-text-secondary); }
/* Interrupteur extensionRares : « dont N offerts par la Voie », sous le
   compte des points de compétence (le serveur ne sert rien hors interrupteur). */
.amiral-offerts { display: block; font-size: 11px; color: var(--gold, #f59e0b); }
.amiral-chiffre.is-libres .amiral-chiffre-v { color: #86efac; }
.amiral-reserve.is-depasse .amiral-chiffre.is-libres .amiral-chiffre-v { color: #fbbf24; }
.amiral-jauge { height: 8px; border-radius: var(--radius-pill); background: rgba(255,255,255,.08); overflow: hidden; margin-top: 14px; }
.amiral-jauge > span { display: block; height: 100%; background: var(--accent2); }
.amiral-reserve.is-depasse .amiral-jauge > span { background: #f59e0b; }
.amiral-note { font-size: 12.5px; line-height: 1.45; margin: 10px 0 0; }
.amiral-note.is-alerte { color: #fcd34d; }
.amiral-vms { list-style: none; margin: 14px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.amiral-vm {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px;
  padding: 8px 12px; border-radius: 10px; background: rgba(8,12,24,.45); border: 1px solid var(--color-border-subtle);
}
.amiral-vm-nom { font-weight: 600; font-size: 13.5px; }
.amiral-vm-classe { font-size: 12.5px; color: var(--color-text-secondary); }
.amiral-vm-pts { margin-left: auto; font-family: var(--font-mono); font-size: 12.5px; color: #7dd3fc; }
.amiral-vm.is-amiral .amiral-vm-pts { color: var(--color-text-secondary); }
.amiral-sec {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--color-text-secondary); margin: 22px 0 4px; font-weight: 600;
}
.amiral-intro { font-size: 13px; line-height: 1.5; color: var(--color-text-secondary); margin: 0 0 10px; }
.amiral-comps { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--space-3); }
.amiral-comp {
  display: flex; flex-direction: column; gap: 8px; min-width: 0;
  padding: 14px 16px; border: 1px solid var(--color-border-subtle); border-radius: 12px; background: var(--color-bg-card);
}
.amiral-comp-tete { display: flex; align-items: center; gap: 10px; }
.amiral-comp-ico { font-size: 22px; line-height: 1; }
.amiral-comp-nom { font-size: 15px; font-weight: 700; }
.amiral-comp-niv {
  margin-left: auto; font-family: var(--font-mono); font-size: 12px; padding: 2px 9px;
  border-radius: var(--radius-pill); border: 1px solid var(--color-border-strong); color: var(--color-text-secondary);
}
.amiral-comp-suite { display: flex; flex-direction: column; gap: 4px; margin-top: auto; }
.amiral-etiq { font-family: var(--font-mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--color-text-secondary); }
.amiral-comp-cout { display: flex; flex-wrap: wrap; gap: 4px 10px; }
.amiral-res { font-family: var(--font-mono); font-size: 12px; white-space: nowrap; }
.amiral-former {
  font: inherit; font-size: 13px; font-weight: 600; padding: 8px 12px; border-radius: var(--radius-md);
  border: 1px solid var(--color-border-subtle); background: none; color: var(--color-text-secondary); cursor: default;
}
.amiral-former.is-pret {
  cursor: pointer; color: #0b1120; border-color: #86efac; background: #86efac;
}
.amiral-former.is-pret:hover { filter: brightness(1.08); }
.amiral-former.is-encours { color: #7dd3fc; border-color: rgba(125,211,252,.45); }
.amiral-comp.is-pret { border-color: rgba(134,239,172,.55); }
.amiral-comp.is-encours { border-color: rgba(125,211,252,.55); }
.amiral-formation {
  font-size: 13.5px; padding: 10px 14px; margin: 0 0 10px; border-radius: 10px;
  border: 1px solid rgba(125,211,252,.45); background: rgba(14,165,233,.10);
}
.amiral-vm.is-chantier { border-style: dashed; }
/* « Payer en » : la ressource qui paiera le niveau, avec son prix et le
   stock du vaisseau regardé dans chaque option. */
.amiral-payer { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.amiral-payer select {
  font: inherit; font-size: 13.5px; padding: 7px 10px; min-width: 0; width: 100%;
  border-radius: var(--radius-md); border: 1px solid var(--color-border-strong);
  background: var(--color-bg-card); color: inherit;
}
.amiral-stock { font-family: var(--font-mono); font-size: 12px; color: var(--color-text-secondary); }
.amiral-stock.is-manque { color: #fbbf24; }
.amiral-bareme { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 6px; }
.amiral-bareme li {
  display: flex; justify-content: space-between; gap: 10px; font-size: 13px;
  padding: 8px 12px; border-radius: 10px; border: 1px solid var(--color-border-subtle); background: var(--color-bg-card);
}
.amiral-bareme b { font-family: var(--font-mono); font-size: 12.5px; color: #7dd3fc; white-space: nowrap; }
@media (max-width: 700px) {
  .amiral-reserve { padding: 14px; }
  .amiral-chiffre-v { font-size: 24px; }
  .amiral-comps { grid-template-columns: 1fr; }
}
/* Panneau des Testeurs : la phrase « il termine aussi la formation de
   l'amiral » n'existe que sous l'interrupteur (sans amiral, rien à former). */
.adm-seulement-amiral { display: none; }
body.f-amiralEmpire .adm-seulement-amiral { display: inline; }
/* Sous l'amiral, le module Commandement du vaisseau disparaît de la barre
   du vaisseau : le Berceau et la Chrysalide se bâtissent depuis l'Empire
   (même juge que la Cité-Nef : `moduleVaisseauRetireClient`). */
body.f-amiralEmpire .sn-item[data-sec="command"] { display: none; }
/* Les cartes de classe de la Chrysalide disent ce qu'elles occupent. */
.cmd-class-pts { font-family: var(--font-mono); font-size: 11px; color: #7dd3fc; }
.cmd-class.is-hors-reserve { opacity: .55; cursor: not-allowed; transform: none; }
.cmd-class.is-hors-reserve .cmd-class-pts { color: #fbbf24; }
.cmd-class-reserve { margin-top: 8px; }
.cmd-class-batir { margin-top: 10px; display: flex; }
.cmd-class-batir .cmd-goto { flex: 1 1 auto; }
/* Le bouton qui bâtit le Berceau ou la Chrysalide depuis l'Empire : au
   plancher de lecture, et lisible aussi quand il dit pourquoi il ne peut pas. */
.cmd-goto.empire-batir { font-size: 12px; line-height: 1.4; }
.cmd-goto.empire-batir:disabled { cursor: default; opacity: .75; }

/* ─── v0.0.51 : LES QUATRE COMPTEURS DU VAISSEAU ─────────────────────
   Demande de Loryan : « mettre juste les 4 chiffres qu'on a (nanites, xp,
   sillage, spore) et une flèche à côté à déplier pour avoir le détail du
   remplissage des stocks et du débit ».
   ⚠️ Il a REMPLACÉ les badges XP et Nanites, dont les règles sont parties
   à la purge (v0.0.79) : trois pastilles côte à côte dans un en-tête de
   section prendraient plus de place que le titre.
   Le replié tient sur une ligne ; le déplié se pose EN ABSOLU sous la
   barre, pour ne pas pousser le contenu de la section vers le bas à
   chaque ouverture. */
.cpt-encart {
  margin-left: auto; align-self: center; flex-shrink: 0; position: relative;
}
.cpt-barre {
  display: inline-flex; align-items: center; gap: 10px; cursor: pointer;
  background: linear-gradient(180deg, rgba(45, 212, 191, .12), rgba(45, 212, 191, .03));
  border: 1px solid rgba(45, 212, 191, .40); border-radius: 999px;
  padding: 5px 12px; font-family: var(--mono, ui-monospace, monospace);
  font-size: 12.5px; font-weight: 600; color: #2dd4bf; white-space: nowrap;
  transition: .15s;
}
.cpt-barre:hover { border-color: #2dd4bf; box-shadow: 0 0 14px rgba(45, 212, 191, .22); }
.cpt-encart.est-deplie .cpt-barre { border-color: #2dd4bf; }
.cpt-puce { display: inline-flex; align-items: center; gap: 4px; }
.cpt-ico { font-size: 13px; }
/* `vignettesTransformees` : la vignette remplace l'émoji et prend sa taille
   visuelle (un émoji de 13 px occupe ~16 px). Elle n'existe QUE sous
   l'interrupteur — éteint, il n'y a pas d'image et la règle ne vise rien.
   Mesuré au navigateur : la barre garde sa hauteur de 28 px. */
.cpt-ico .res-mini, .btq-pastille-ico .res-mini { width: 16px; height: 16px; vertical-align: middle; }
.cpt-val { font-variant-numeric: tabular-nums; }
/* Interrupteur galons — au téléphone, la barre PASSE À LA LIGNE au lieu de
   sortir de l'écran. Mesuré à 390 px : sept pastilles faisaient déjà 368 px
   pour 325 px de place (le bout était coupé en silence) ; avec les deux
   Galons, 451 px — justement les Galons auraient été invisibles. */
@media (max-width: 700px) {
  body.f-galons .cpt-encart { flex-shrink: 1; min-width: 0; max-width: 100%; }
  body.f-galons .cpt-barre { flex-wrap: wrap; white-space: normal; row-gap: 4px; border-radius: 14px; max-width: 100%; }
}
/* `vignettesCompteursGrandes` — demande de Loryan, capture de la barre de
   compteurs à l'appui : « idem pour les autres ». Après le bandeau du haut,
   les vignettes vives des compteurs grandissent à leur tour, avec le halo
   de leur couleur (`--rc`, posé ci-dessous d'après `data-c`).
   ⚠️ ELLES NE VISENT QUE LES PLANCHES VIVES (`/vif/`) : un émoji (les
   Nanites) ou une planche d'avant garde sa taille.
   ⚠️ LA BARRE NE GRANDIT PAS : son retrait vertical baisse d'autant que la
   vignette monte, et `:has` ne compense que si une planche vive est là.
   Mesuré au navigateur, en large et au téléphone. */
body.f-vignettesCompteursGrandes .cpt-ico .res-mini[src*="/vif/"] {
  width: 22px; height: 22px;
  filter: drop-shadow(0 0 2px color-mix(in srgb, var(--rc, #ffffff) 70%, transparent));
}
body.f-vignettesCompteursGrandes .cpt-barre:has(img[src*="/vif/"]) { padding-block: 2px; }
body.f-vignettesCompteursGrandes .cpt-l-nom .res-mini[src*="/vif/"] {
  width: 22px; height: 22px; vertical-align: middle; margin-right: 4px;
  filter: drop-shadow(0 0 2px color-mix(in srgb, var(--rc, #ffffff) 70%, transparent));
}
body.f-vignettesCompteursGrandes .btq-pastille-ico .res-mini[src*="/vif/"] {
  width: 20px; height: 20px;
  filter: drop-shadow(0 0 2px color-mix(in srgb, #f472b6 70%, transparent));
}
/* Même compensation pour la pastille de la Boutique : 4 px de retrait
   vertical deviennent 2, la vignette gagne les 4 px (mesuré : 26 px). */
body.f-vignettesCompteursGrandes .btq-pastille:has(img[src*="/vif/"]) { padding-block: 2px; }
body.f-vignettesCompteursGrandes [data-c="modules"] { --rc: #4ade80; }
body.f-vignettesCompteursGrandes [data-c="sillage"] { --rc: #a5b4fc; }
body.f-vignettesCompteursGrandes [data-c="spores"] { --rc: #ec4899; }
body.f-vignettesCompteursGrandes [data-c="heliure-jaune"] { --rc: #facc15; }
body.f-vignettesCompteursGrandes [data-c="heliure-rouge"] { --rc: #ef4444; }
body.f-vignettesCompteursGrandes [data-c="heliure-bleu"] { --rc: #3b82f6; }
/* `vignettesDistariumNanites` : la couleur du halo des Nanites, qui n'ont de
   vignette que sous cet interrupteur (sans elle, l'émoji n'en a pas besoin). */
body.f-vignettesDistariumNanites [data-c="nanites"] { --rc: #2dd4bf; }
/* Interrupteur galons — LES PICTOS DES GALONS, AGRANDIS ET MAGNIFIÉS.
   Remontée de Loryan, capture du détail des compteurs à l'appui : « ça se
   voit très mal, il va falloir l'agrandir et le magnifier ». Les planches
   sont vives (fond transparent) : plus de cadre ni de coin arrondi, mais un
   halo de leur couleur — le sang pour l'attaque, le bleu pour la défense.
   ⚠️ ELLES NE PASSENT PAS PAR `/vif/` : les règles de `vignettesVives` et
   de `vignettesCompteursGrandes` ne les visent pas, seules celles-ci.
   ⚠️ LA BARRE NE GRANDIT PAS : dans la barre, la marge négative ramène la
   place occupée à 16 px (celle d'une vignette ordinaire) ; les 24 px
   débordent dans le retrait. Mesuré au navigateur, en large et au téléphone. */
body.f-galons img.res-mini.galons-vif {
  border-radius: 0; box-shadow: none; object-fit: contain;
  filter: drop-shadow(0 0 3px rgba(239, 68, 68, .75));
}
body.f-galons img.res-mini.galons-vif-galons_defense { filter: drop-shadow(0 0 3px rgba(96, 165, 250, .85)); }
body.f-galons .cpt-ico img.res-mini.galons-vif { width: 24px; height: 24px; margin-block: -4px; vertical-align: middle; }
/* Dans le détail : 30 px, mais le nom reste aligné sur ceux des autres
   compteurs (vignette de 22 px + 4 px) — les 8 px de plus débordent à gauche,
   dans le retrait du panneau. */
body.f-galons .cpt-l-nom img.res-mini.galons-vif { width: 30px; height: 30px; vertical-align: middle; margin: 0 0 0 -4px; }
body.f-galons .mk-plans img.res-mini.galons-vif { width: 20px; height: 20px; margin-block: -3px; vertical-align: middle; }
/* Interrupteur compteursBarreHaute — LA BARRE S'ÉPAISSIT, LES PICTOS GRANDISSENT.
   Remontée de Loryan, capture à l'appui : « la bande verte est coupée par
   les logos » — les pictos remplissaient toute la hauteur utile (24 px dans
   28 px de barre, 2 px de retrait). Il demande l'inverse de la règle
   d'avant : que la barre GRANDISSE vers le haut et le bas, pour agrandir les
   pictos « sans dépasser la taille de ceux du bandeau du haut ».
   ⚠️ LE PLAFOND EST LE BANDEAU DES RESSOURCES, À CHAQUE LARGEUR : 30 px en
   large (sa règle `vignettesVives`), 18 px au téléphone (sa règle
   `mobile-mode`). Toucher l'un sans l'autre est interdit :
   test/compteurs-barre-haute.test.js les relit tous les deux.
   ⚠️ TOUS LES PICTOS À LA MÊME TAILLE, Galons compris : ils n'ont plus
   besoin de déborder pour se voir. La spécificité (cinq classes) passe
   devant les règles des Galons et de `vignettesCompteursGrandes`.
   Mesuré au navigateur, en large et au téléphone. */
/* L'écart entre pastilles descend de 10 à 6 px : les pictos plus grands
   élargissaient la barre de 90 px, et à 1280 px le titre de la section
   passait sur une ligne de plus (mesuré). */
body.f-compteursBarreHaute .cpt-encart button.cpt-barre { padding-block: 6px; gap: 6px; }
body.f-compteursBarreHaute .cpt-barre .cpt-puce .cpt-ico img.res-mini { width: 30px; height: 30px; margin-block: 0; vertical-align: middle; }
body.f-compteursBarreHaute .cpt-barre .cpt-puce .cpt-ico { font-size: 22px; line-height: 1; }
/* Les chiffres suivent les pictos en large (la hauteur de la barre, fixée par
   les pictos, ne bouge pas) ; au téléphone ils restent à 12,5 px, sans quoi
   la barre passerait sur trois lignes. */
body.f-compteursBarreHaute .cpt-barre .cpt-puce .cpt-val { font-size: 14px; }
/* Écran moyen (701 à 1400 px) : 28 px et 4 px d'écart — à 30 px, le crayon
   du titre de la section passait sous le titre à 1280 px (mesuré). */
@media (min-width: 701px) and (max-width: 1400px) {
  body.f-compteursBarreHaute:not(.mobile-mode) .cpt-encart button.cpt-barre { gap: 4px; }
  body.f-compteursBarreHaute:not(.mobile-mode) .cpt-barre .cpt-puce .cpt-ico img.res-mini { width: 28px; height: 28px; }
}
@media (max-width: 700px) {
  body.f-compteursBarreHaute .cpt-encart button.cpt-barre { padding-block: 5px; }
  body.f-compteursBarreHaute .cpt-barre .cpt-puce .cpt-ico img.res-mini { width: 18px; height: 18px; }
  body.f-compteursBarreHaute .cpt-barre .cpt-puce .cpt-ico { font-size: 15px; }
  body.f-compteursBarreHaute .cpt-barre .cpt-puce .cpt-val { font-size: 12.5px; }
}
body.f-compteursBarreHaute.mobile-mode .cpt-encart button.cpt-barre { padding-block: 5px; }
body.f-compteursBarreHaute.mobile-mode .cpt-barre .cpt-puce .cpt-ico img.res-mini { width: 18px; height: 18px; }
body.f-compteursBarreHaute.mobile-mode .cpt-barre .cpt-puce .cpt-ico { font-size: 15px; }
body.f-compteursBarreHaute.mobile-mode .cpt-barre .cpt-puce .cpt-val { font-size: 12.5px; }
/* Un compteur BRIDÉ (la Fabrique tourne au ralenti) s'ambre : c'est la
   seule alerte de la barre repliée, et elle mène au détail qui l'explique. */
.cpt-puce.est-bride .cpt-val { color: var(--gold, #ffd54f); }
/* Des modules sont prêts à monter — même vert que le compte « N module(s)
   prêt(s) » de la section. */
.cpt-puce.est-pret .cpt-val { color: #6ee7b7; }
.cpt-chevron { font-size: 9px; opacity: .7; margin-left: 2px; }

.cpt-detail {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 30;
  width: min(92vw, 340px); text-align: left;
  background: linear-gradient(180deg, #101a2c, #0b1220);
  border: 1px solid rgba(45, 212, 191, .32); border-radius: 10px;
  padding: 10px 12px; box-shadow: 0 14px 36px rgba(0, 0, 0, .6);
}
.cpt-ligne { padding: 7px 0; border-top: 1px solid rgba(31, 41, 55, .8); }
.cpt-ligne:first-child { border-top: 0; padding-top: 2px; }
.cpt-l-tete { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.cpt-l-nom { font-size: 12px; color: var(--text, #eaf2ff); font-weight: 600; }
.cpt-l-chiffre {
  font-family: var(--mono, ui-monospace, monospace); font-size: 12.5px; font-weight: 700;
  color: #2dd4bf; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.cpt-l-chiffre em { font-style: normal; font-weight: 500; opacity: .6; font-size: 11px; }
.cpt-jauge {
  height: 4px; border-radius: 2px; background: rgba(255, 255, 255, .07);
  margin: 5px 0 4px; overflow: hidden;
}
.cpt-jauge i { display: block; height: 100%; background: #2dd4bf; border-radius: 2px; }
.cpt-l-bas { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.cpt-l-aide { flex: 1; font-size: 10.5px; line-height: 1.4; color: var(--text3, #6e7691); }
.cpt-l-flux {
  font-family: var(--mono, ui-monospace, monospace); font-size: 11px;
  white-space: nowrap; flex-shrink: 0;
}
.cpt-l-flux .est-plus { color: #4ade80; }
.cpt-l-flux .est-moins { color: #fb7185; }
.cpt-l-flux i { color: var(--text3, #6e7691); font-style: normal; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.54 — LE PANNEAU DE VOL (flag `consoEnVol`)
   ════════════════════════════════════════════════════════════════════
   Il se glisse juste sous le bandeau d'ancrage et n'apparaît QUE pendant
   un vol. Teinte ambre : c'est la couleur que le jeu emploie déjà pour un
   vaisseau en transit (`.anch-val.gold`), et l'œil fait donc le lien tout
   seul avec le bandeau juste au-dessus.
   ⚠️ AUCUNE HAUTEUR FIXE, AUCUNE TRONCATURE. Le bandeau des conseils de
   production (`.ph-msg`) a coûté deux corrections en v0.0.53 pour cette
   raison exacte : un bouton tombé sous une coupe à deux lignes, puis un
   libellé rogné par un `nowrap`. Ici tout s'enroule. */
#panneau-vol {
  background: linear-gradient(180deg, rgba(30,22,8,.96), rgba(14,12,22,.96));
  border-bottom: 1px solid rgba(245,180,60,.35);
  padding: .7rem 1.2rem .8rem; flex-shrink: 0;
  font-size: .72rem;
}
.pv-tete {
  display: flex; align-items: baseline; gap: .9rem; flex-wrap: wrap;
  cursor: pointer; user-select: none;
}
.pv-tete:hover .pv-titre { color: #ffd88a; }
.pv-chevron { color: var(--gold, #f5b43c); font-size: .7rem; }
.pv-corps { margin-top: .1rem; }

/* ⚠️ LE CLIGNOTEMENT NE PORTE QUE SUR LE CONTOUR, et seulement PLIÉ
   (demande de Loryan : « garde juste la première ligne visible avec un
   clignotement du contour de la fenêtre »). Faire palpiter le fond ou le
   texte rendrait la ligne illisible, et la laisser palpiter une fois
   ouverte transformerait un écran qu'on lit en alarme permanente.
   `box-shadow` plutôt que `border` : une bordure animée décalerait la mise
   en page d'un pixel à chaque battement. */
@keyframes pv-pulse {
  0%, 100% { box-shadow: inset 0 0 0 1px rgba(245,180,60,.25); }
  50%      { box-shadow: inset 0 0 0 2px rgba(245,180,60,.85); }
}
#panneau-vol.pv-plie { animation: pv-pulse 2.2s ease-in-out infinite; }
/* Un joueur qui a demandé moins d'animations n'en reçoit pas : le contour
   reste posé, franc, et dit la même chose sans bouger. */
@media (prefers-reduced-motion: reduce) {
  #panneau-vol.pv-plie { animation: none; box-shadow: inset 0 0 0 2px rgba(245,180,60,.6); }
}
.pv-titre {
  font-family: var(--hud); font-size: .68rem; letter-spacing: .12em;
  color: var(--gold, #f5b43c);
}
.pv-trajet { font-family: var(--mono); color: var(--text); }
.pv-trajet .pv-sys { color: var(--text3, #6e7691); }
.pv-fleche { color: var(--gold, #f5b43c); margin: 0 .3rem; }
.pv-horloge { font-family: var(--mono); color: var(--text2, #9aa3bd); }
.pv-horloge b { color: var(--text); font-weight: 600; }
.pv-h-lab { color: var(--text3, #6e7691); font-size: .66rem; }
.pv-h-sep { color: rgba(245,180,60,.4); margin: 0 .35rem; }
.pv-barre {
  flex-basis: 100%; height: 3px; background: rgba(255,255,255,.08);
  border-radius: 2px; overflow: hidden; margin-top: .15rem;
}
.pv-barre > span { display: block; height: 100%; width: 0; background: var(--gold, #f5b43c); }

.pv-panne {
  margin-top: .55rem; padding: .45rem .6rem; border-radius: 4px;
  background: rgba(220,60,60,.14); border: 1px solid rgba(220,60,60,.4);
  color: #ffb4b4; line-height: 1.45;
}

.pv-reserves { display: flex; gap: .8rem; flex-wrap: wrap; margin-top: .6rem; }
.pv-res {
  flex: 1 1 260px; padding: .45rem .6rem; border-radius: 4px;
  background: rgba(255,255,255,.035); border: 1px solid var(--border);
}
.pv-res-tete { font-family: var(--mono); color: var(--text2, #9aa3bd); }
.pv-res-tete b { color: var(--text); font-size: .82rem; }
.pv-res-flux { font-family: var(--mono); margin-top: .2rem; display: flex; gap: .6rem; flex-wrap: wrap; }
.pv-verdict { margin-top: .25rem; font-size: .66rem; line-height: 1.4; }
.pv-ok { color: #4ade80; }
.pv-alerte { color: #fb7185; }
.pv-unit { color: var(--text3, #6e7691); font-size: .64rem; }
/* Interrupteur zonesDuVol : la zone que le vaisseau traverse en ce moment. */
.pv-zone { color: #93c5fd; font-size: .8rem; margin-top: 3px; }
.pv-zone b { color: #fbbf24; font-weight: 600; }

.pv-note {
  margin-top: .55rem; padding: .4rem .6rem; border-radius: 4px;
  background: rgba(245,180,60,.1); border: 1px solid rgba(245,180,60,.3);
  color: #f0d9a8; line-height: 1.45; font-size: .68rem;
}

/* ─── v0.0.57 : le journal de bord des aléas du vol ─────────────────── */
.pv-journal { margin-top: .7rem; display: flex; flex-direction: column; gap: .25rem; }
.pv-journal-tete {
  font-family: var(--hud); font-size: .6rem;
  letter-spacing: .1em; color: var(--turquoise, #2dd4bf);
}
.pv-alea {
  display: flex; align-items: flex-start; gap: .5rem;
  padding: .35rem .55rem; border-radius: 4px; font-size: .68rem; line-height: 1.4;
  background: rgba(255,255,255,.035); border-left: 2px solid var(--text3, #6e7691);
}
/* La couleur porte l'information : on repère le champ de débris sans lire. */
.pv-alea-bon     { border-left-color: #4ade80; background: rgba(74,222,128,.07); }
.pv-alea-mauvais { border-left-color: #f5b43c; background: rgba(245,180,60,.07); }
.pv-alea-grave   { border-left-color: #fb7185; background: rgba(251,113,133,.09); }
.pv-alea-ico { flex-shrink: 0; }
.pv-alea-txt { flex: 1; min-width: 0; color: var(--text2, #aab2c8); }
.pv-alea-txt b { color: var(--text); }
/* ⚠️ `nowrap` sur le chiffre et NON sur la ligne entière : c'est lui qu'on
   ne veut jamais voir coupé en deux (« +1 h » puis « 12 » à la ligne), et le
   récit à côté doit pouvoir se replier librement. */
.pv-alea-chiffre {
  flex-shrink: 0; white-space: nowrap;
  font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: .66rem;
}

.pv-postes-tete {
  margin-top: .7rem; font-family: var(--hud); font-size: .6rem;
  letter-spacing: .1em; color: var(--turquoise, #2dd4bf);
}
/* Grille auto-ajustée : huit postes tiennent sur deux ou trois colonnes selon
   la largeur, et sur une seule sur mobile — sans media query à maintenir. */
.pv-postes {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
  gap: .35rem .8rem; margin-top: .35rem;
}
.pv-poste {
  display: flex; align-items: center; gap: .5rem;
  padding: .3rem .45rem; border-radius: 4px;
  background: rgba(255,255,255,.03); border: 1px solid transparent;
}
.pv-poste.pv-eteint { opacity: .6; }
/* Un poste verrouillé porte un cadenas à la place des trois crans : la place
   reste la même, donc la grille ne se déforme pas d'une tuile à l'autre. */
.pv-verrou {
  display: inline-flex; align-items: center; justify-content: center;
  width: 70px; height: 20px; font-size: .7rem; opacity: .8;
}
.pv-poste-nom { flex: 1; min-width: 0; font-family: var(--mono); color: var(--text); }
.pv-poste-desc { font-size: .62rem; color: var(--text3, #6e7691); line-height: 1.3; }
.pv-cadenas { margin-left: .3rem; font-size: .62rem; opacity: .7; }
.pv-poste-cout {
  font-family: var(--mono); color: #fb7185; white-space: nowrap; flex-shrink: 0;
}
.pv-crans { display: flex; gap: 2px; flex-shrink: 0; }
.pv-cran {
  width: 22px; height: 20px; border-radius: 3px; cursor: pointer;
  background: rgba(255,255,255,.06); border: 1px solid var(--border);
  color: var(--text3, #6e7691); font-family: var(--mono); font-size: .68rem;
  line-height: 1; padding: 0;
}
.pv-cran:hover:not(:disabled) { background: rgba(245,180,60,.18); color: var(--text); }
.pv-cran.actif {
  background: rgba(245,180,60,.3); border-color: var(--gold, #f5b43c);
  color: var(--gold, #f5b43c);
}
.pv-cran:disabled { cursor: not-allowed; opacity: .35; }
/* v0.0.113 : « ON » et « OFF » ne tiennent pas dans 22 px de large — un
   bouton carré fait pour un seul caractère tronquerait le mot au milieu, le
   défaut que ce projet attrape le plus souvent. On l'élargit, et lui seul :
   les trois crans chiffrés gardent leur gabarit. */
.pv-cran-onoff { width: auto; min-width: 34px; padding: 0 5px; letter-spacing: .04em; }
/* La description d'un poste éteint reste LISIBLE : estomper sert à dire
   « ça ne tourne plus », pas à cacher ce que c'était. */
.pv-poste.pv-eteint .pv-poste-desc { color: var(--text2, #9aa3bd); }


/* ════════════════════════════════════════════════════════════════════
   v0.0.55 — L'ENTRETIEN (flag `entretienModules`)
   ════════════════════════════════════════════════════════════════════ */

/* ─── La pastille de capacité, sur les vignettes ─────────────────────
   Posée en BAS À DROITE de la vignette, à l'endroit que Loryan a entouré
   sur sa capture. ⚠️ `position: absolute` sur un parent déjà positionné :
   les vignettes portent `position: relative` (elles ont une image de fond
   en couche), donc la pastille se cale dans la tuile et jamais dans la
   page. Elle ne prend AUCUNE place dans le flux — la mise en page des
   vignettes ne bouge pas d'un pixel. */
.bld-tile, .mod-tile { position: relative; }
.ent-pastille {
  position: absolute; right: 8px; bottom: 7px; z-index: 2;
  font-family: var(--mono, ui-monospace, monospace); font-size: .62rem;
  padding: 1px 5px; border-radius: 3px; line-height: 1.4;
  background: rgba(6,10,20,.82); border: 1px solid currentColor;
  pointer-events: none;
}
/* ─── v0.0.86 : ELLE CESSE DE RECOUVRIR LA LIGNE DU DESSOUS ───────────
   Signalement de Loryan, gros plan à l'appui : « Superposition de
   l'entretien sur autre info — décaler l'entretien vers le haut ». Sur sa
   capture, le « 88 % » doré chevauche le « 1.7G » du Sol restant.
   ⚠️ CE N'EST PAS UN ACCIDENT DE LARGEUR, C'EST STRUCTUREL. La pastille est
   posée en `position: absolute` à `bottom: 7px`, donc elle ne prend AUCUNE
   place dans le flux — et la dernière ligne de `.bld-tile-stat` est
   précisément au même endroit, alignée à droite elle aussi. Les deux
   occupent la même bande de ~15 px. Le cas mord sur les six EXTRACTEURS,
   c'est-à-dire exactement les bâtiments qui portent un pourcentage
   d'entretien ET une ligne « Sol restant ».
   ⚠️ ON NE LA MONTE PAS DE QUELQUES PIXELS : ça déplacerait le
   chevauchement sur la ligne du dessus, qui est alignée à droite elle
   aussi. On la REMET DANS LE FLUX, entre l'en-tête et les statistiques —
   la vignette est déjà une colonne flex, il suffit de lui donner son rang.
   Plus aucun recouvrement n'est alors possible, quelle que soit la longueur
   des nombres.
   ⚠️ `min-height` DE LA VIGNETTE NE BOUGE PAS et la pastille tient dans le
   jeu que `justify-content: space-between` laissait déjà vide : mesuré au
   navigateur, la grille des bâtiments garde sa hauteur.
   ⚠️ v0.0.132 — LA VIGNETTE DE MODULE A REJOINT LE LOT, et la raison qui
   l'en excluait est tombée. Le commentaire disait « `.mod-tile` n'a pas de
   ligne sous elle » : c'était vrai jusqu'à ce que l'expérience segmentée y
   pose sa réserve (`.mod-tile-xp`), alignée à droite et au ras du bas —
   très exactement la bande que la pastille occupe. Mesuré au navigateur :
   sur la vignette Extracteurs, le « 100 % » recouvrait entièrement le
   « +1,00/s ». Même remède, même rang. */
body .bld-tile .bld-tile-hdr  { order: 0; }
body .bld-tile .ent-pastille  {
  order: 1; position: static; align-self: flex-end;
  margin-top: 0.3rem; margin-bottom: 0.1rem;
}
body .bld-tile .bld-tile-stat { order: 2; }
body .mod-tile .bld-tile-hdr  { order: 0; }
body .mod-tile .ent-pastille  {
  order: 1; position: static; align-self: flex-end;
  margin-top: 0.3rem; margin-bottom: 0.1rem;
}
body .mod-tile .mod-tile-bonus { order: 2; }
body .mod-tile .mod-tile-cost { order: 3; }
body .mod-tile .mod-tile-dev  { order: 3; }
body .mod-tile .mod-tile-xp   { order: 4; }

/* La couleur porte une information, comme l'infobulle du bilan de
   production (v0.0.39) : vert = rien à faire, ambre = ça commence à
   coûter, rouge = à remettre en état. */
.ent-vert  { color: #4ade80; }
.ent-ambre { color: #f5b43c; }
.ent-rouge { color: #fb7185; }

/* ─── L'écran d'entretien ────────────────────────────────────────── */
.ent-vide { padding: 1.2rem; color: var(--text3, #6e7691); line-height: 1.6; font-size: .78rem; }
.ent-tete {
  display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap;
  padding: .6rem .8rem; margin-bottom: .8rem; border-radius: 5px;
  background: rgba(255,255,255,.035); border: 1px solid var(--border);
}
.ent-tete-txt { flex: 1 1 320px; font-size: .72rem; line-height: 1.55; color: var(--text2, #9aa3bd); }
.ent-tete-txt b { color: var(--text); }
.ent-nanites {
  font-family: var(--mono); font-size: .78rem; color: var(--turquoise, #2dd4bf);
  white-space: nowrap; flex-shrink: 0;
}
.ent-mod { margin-bottom: 1rem; }
.ent-mod-tete {
  display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
  padding-bottom: .3rem; border-bottom: 1px solid var(--border); margin-bottom: .35rem;
}
.ent-mod-nom { font-family: var(--hud); font-size: .72rem; letter-spacing: .06em; color: var(--text); }
.ent-mod-moy { font-family: var(--mono); font-size: .82rem; font-weight: 600; }
.ent-mod-sous { font-size: .62rem; color: var(--text3, #6e7691); }
.ent-neuf { flex: 0 0 200px; text-align: right; font-size: .66rem; color: var(--text3, #6e7691); }
.ent-btn {
  flex: 0 0 200px; padding: .25rem .5rem; border-radius: 4px; cursor: pointer;
  background: rgba(45,212,191,.14); border: 1px solid rgba(45,212,191,.45);
  color: var(--turquoise, #2dd4bf);
  font-family: var(--mono); font-size: .66rem; white-space: nowrap;
}
.ent-btn:hover:not(:disabled) { background: rgba(45,212,191,.26); }
.ent-btn-court, .ent-btn:disabled {
  cursor: not-allowed; opacity: .45;
  background: rgba(255,255,255,.05); border-color: var(--border); color: var(--text3, #6e7691);
}
.ent-chantier { flex: 0 0 200px; }
.ent-chantier-txt { font-size: .64rem; color: var(--gold, #f5b43c); font-family: var(--mono); }
.ent-chantier-barre {
  display: block; height: 3px; margin-top: 3px; border-radius: 2px;
  background: rgba(255,255,255,.08); overflow: hidden;
}
.ent-chantier-barre > i { display: block; height: 100%; width: 0; background: var(--gold, #f5b43c); }

/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — LES BÂTIMENTS EN TUILES
   ════════════════════════════════════════════════════════════════════
   Choix de Loryan parmi dix propositions. Les bâtiments se rangent en
   grille de tuiles : cadran, nom, niveau, effet, devis.

   ⚠️ `auto-fill` ET PAS DE MEDIA QUERY. La grille se règle toute seule sur
   la largeur disponible — cinq colonnes en très large, deux sur un
   téléphone de 390 px. C'est précisément ce qui répare le défaut de la
   ligne : sous 720 px elle se repliait en QUATRE rangées par bâtiment,
   soit soixante rangées pour quinze bâtiments. La tuile, elle, garde
   exactement la même forme à toutes les largeurs.
   ⚠️ CE RÉGLAGE-CI EST CELUI DES FAMILLES SANS PHOTO. Dès qu'une tuile
   porte une illustration, la règle `:has(.ent-tuile.est-fond)` plus bas
   reprend la main et pose un autre calibrage — c'est là qu'il est expliqué.
   ⚠️ AUCUNE HAUTEUR FIXE : un nom long (« Générateur Tellurique ») prend
   deux lignes, et c'est la rangée entière qui s'aligne dessus — un
   `height` figé l'aurait tronqué en silence, le défaut que ce projet
   attrape le plus souvent. */
.ent-tuiles {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(155px, 1fr));
  gap: .5rem;
}
.ent-tuile {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: .15rem; padding: .7rem .5rem .55rem;
  border: 1px solid var(--border, #162040); border-radius: 8px;
  background: rgba(255, 255, 255, .022);
}
/* Le liseré reprend la couleur du pourcentage : une tuile rouge se repère
   sans lire son chiffre, ce qui est tout l'intérêt de la grille. */
.ent-tuile.ent-rouge { border-color: rgba(251, 113, 133, .38); }
.ent-tuile.ent-ambre { border-color: rgba(245, 180, 60, .32); }
.ent-tuile.ent-vert  { border-color: rgba(74, 222, 128, .28); }
.ent-tuile-atelier {
  border-color: rgba(245, 180, 60, .55);
  background: rgba(245, 180, 60, .06);
}

/* Le cadran : le pourcentage au centre, l'anneau autour (le pictogramme
   est parti en v0.0.154 — il ne servait à rien et cachait la photo). Les
   deux occupent la place d'UNE seule ligne de la vue précédente — c'est
   là que la grille gagne sa hauteur. */
.ent-tuile-cadran {
  position: relative; width: 58px; height: 58px; flex: none;
  display: grid; place-items: center; margin-bottom: .2rem;
}
/* 58 px pour un `viewBox` de 52 : l'anneau grandit, mais son épaisseur de
   trait suit l'échelle. Ce sont les six pixels qui manquaient au
   pourcentage — à 52 px, le « % » venait toucher l'arc du bas. */
.ent-anneau {
  position: absolute; inset: 0; width: 100%; height: 100%;
  transform: rotate(-90deg); /* l'arc part de midi, pas de trois heures */
}
.ent-anneau-fond { fill: none; stroke: rgba(255, 255, 255, .08); stroke-width: 5; }
.ent-anneau-arc {
  fill: none; stroke: currentColor; stroke-width: 5; stroke-linecap: round;
}
/* v0.0.154 : le pourcentage, seul au centre du cadran, tient le plancher
   de lisibilité du site (11 px). */
.ent-tuile-val {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: var(--badge-font-size, 11px);
  font-variant-numeric: tabular-nums; line-height: 1.2;
}
.ent-tuile-nom {
  font-size: .72rem; line-height: 1.25; color: var(--text, #c8d6f0);
  /* `balance` évite le mot orphelin sur la deuxième ligne — « Extracteur de
     / Noctium » plutôt que « Extracteur de Noctium ». */
  text-wrap: balance;
}
.ent-tuile-niv {
  font-family: var(--mono, ui-monospace, monospace); font-size: .6rem;
  color: var(--text3, #6b8ab0);
}
/* L'effet tient sur UNE ligne et se coupe proprement : la phrase entière
   vit dans l'infobulle, comme sur la vue en lignes. */
.ent-tuile-effet {
  font-size: .58rem; color: var(--text3, #6b8ab0); line-height: 1.3;
  max-width: 100%; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; cursor: help;
}
/* L'action occupe toute la largeur de la tuile et se colle EN BAS : les
   boutons d'une même rangée s'alignent alors, quel que soit le nombre de
   lignes pris par les noms au-dessus. */
.ent-tuile-act { width: 100%; margin-top: auto; padding-top: .4rem; }
.ent-tuile-act .ent-btn,
.ent-tuile-act .ent-neuf,
.ent-tuile-act .ent-chantier { flex: none; width: 100%; text-align: center; }
.ent-tuile-act .ent-btn { padding: .3rem .2rem; font-size: .62rem; }
.ent-tuile-act .ent-neuf { font-size: .62rem; padding: .3rem 0; }
.ent-tuile-act .ent-chantier-txt { font-size: .6rem; }

/* ═════════════════════════════════════════════════════════════════════
   v0.0.149 — LA PHOTO REMPLIT LE CADRE
   ═════════════════════════════════════════════════════════════════════
   « je pensais que l'image serait dans le cadre pas dans le rond, je veux
   plus dans le cadre ». La photo quitte donc le cadran de 58 px et devient
   le FOND de la tuile ; le cadran retrouve son émoji.

   ⚠️ BLOC AUTONOME, ET C'EST CE QUI A PERMIS DE LE PURGER SEUL. Il ne
   touchait aucune règle du bloc « LA MINIATURE DU BÂTIMENT » qui le
   précédait — il réécrivait même la grille pour son propre marqueur
   (`est-fond`). Ce bloc-là est parti avec son interrupteur ; celui-ci
   porte donc seul le cadrage des tuiles à photo.

   ⚠️ LE VOILE N'EST PAS DE LA DÉCORATION. Sur une planche claire (les
   Panneaux Solaires, la Fonderie), « niv. 5 » en gris et le libellé d'effet
   disparaîtraient purement et simplement. Il s'épaissit vers le BAS, là où
   vivent le nom, l'effet et le devis ; le haut reste clair, c'est la partie
   de la photo qu'on voit. */
.ent-tuiles:has(.ent-tuile.est-fond) {
  grid-template-columns: repeat(auto-fit, minmax(170px, 250px));
}
.ent-tuile.est-fond {
  position: relative; overflow: hidden;
  background: rgba(4, 10, 18, .55);
}
.ent-tuile-fond {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover; opacity: .68;
}
.ent-tuile.est-fond::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg,
    rgba(4, 10, 18, .22) 0%, rgba(4, 10, 18, .56) 42%, rgba(4, 10, 18, .88) 100%);
}
/* ⚠️ TOUT LE CONTENU REPASSE DEVANT LE VOILE. Sans cette ligne, le dégradé
   recouvre le bouton — la tuile s'affiche parfaitement et le clic ne part
   jamais, le défaut n° 3 de la liste de vérification. */
.ent-tuile.est-fond > *:not(.ent-tuile-fond) { position: relative; z-index: 2; }
.ent-tuile.est-fond .ent-tuile-nom { text-shadow: 0 1px 4px rgba(0, 0, 0, .95); }
/* v0.0.154 : plus de disque sombre derrière le cadran — la photo se voit
   au travers, et c'est le halo épaissi du pourcentage qui le garde
   lisible sur un ciel étoilé. */
.ent-tuile.est-fond .ent-tuile-val {
  text-shadow: 0 1px 4px rgba(0, 0, 0, .95), 0 0 9px rgba(0, 0, 0, .9);
}
/* Le niveau et l'effet montent d'un ton : le gris à texte secondaire tenait
   sur un fond plat, il se noie sur une photo. */
.ent-tuile.est-fond .ent-tuile-niv,
.ent-tuile.est-fond .ent-tuile-effet {
  color: var(--text2, #9db4d4); text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}
/* Le liseré de couleur se renforce : sur une photo, un trait à .28
   d'opacité ne se voit plus — or c'est lui qui fait repérer une tuile rouge
   sans lire son chiffre, tout l'intérêt de la grille. */
.ent-tuile.est-fond.ent-vert  { border-color: rgba(74, 222, 128, .50); }
.ent-tuile.est-fond.ent-ambre { border-color: rgba(245, 180, 60, .58); }
.ent-tuile.est-fond.ent-rouge { border-color: rgba(251, 113, 133, .68); }
/* ⚠️ L'ATELIER PASSE APRÈS LES TROIS COULEURS, et pas avant : sa règle
   d'origine (`.ent-tuile-atelier`, plus haut) est moins spécifique que
   `.est-fond.ent-vert`. Sans cette ligne, un bâtiment à l'atelier encore
   au-dessus de 90 % reprendrait le liseré VERT en pleine remise en état. */
.ent-tuile.est-fond.ent-tuile-atelier { border-color: rgba(245, 180, 60, .78); }
/* ⚠️ AU TÉLÉPHONE, LE PLAFOND DE 250 px LAISSE UNE BANDE VIDE. Mesuré : la
   section fait 325 px de large à 390 px de fenêtre — une seule colonne y
   tient, plafonnée à 250, donc 75 px perdus à droite, et « une colonne vide
   à droite est une largeur perdue ». Le plafond saute sous 700 px : la
   colonne unique prend toute la largeur, et la photo avec. Deux colonnes
   auraient tronqué la ligne d'effet, dont la phrase entière ne vit que dans
   une infobulle — qu'un doigt n'ouvre jamais. */
@media (max-width: 700px) {
  .ent-tuiles:has(.ent-tuile.est-fond) {
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  }
}

/* ═════════════════════════════════════════════════════════════════════
   v0.0.154 — le bandeau « Tout entretenir »
   ═════════════════════════════════════════════════════════════════════
   Un bouton en tête d'écran lance toutes les remises en état d'un coup ;
   le détail à côté ADDITIONNE les devis déjà affichés sur les tuiles —
   même source, donc jamais de contradiction entre le bandeau et la grille. */
.ent-tout {
  display: flex; align-items: center; gap: .35rem .8rem; flex-wrap: wrap;
  padding: .5rem .8rem; margin-bottom: .8rem; border-radius: 5px;
  background: rgba(45, 212, 191, .06); border: 1px solid rgba(45, 212, 191, .28);
}
/* `flex: none` : la règle de base de `.ent-btn` (flex: 0 0 200px) vient de
   la vue en lignes ; ici le bouton prend la place de son texte. */
.ent-tout .ent-tout-btn { flex: none; padding: .35rem .8rem; font-size: .72rem; }
.ent-tout-detail {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: var(--badge-font-size, 11px); color: var(--text, #c8d6f0);
}
.ent-tout-detail b { color: var(--turquoise, #2dd4bf); }
.ent-tout-manque {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .72rem; color: var(--gold, #f5b43c); white-space: nowrap;
}

/* ═════════════════════════════════════════════════════════════════════
   v0.0.154 — la synthèse repliable
   ═════════════════════════════════════════════════════════════════════
   L'écran s'ouvre sur les seules têtes de modules (la synthèse) ; chaque
   tête se déplie au clic. ⚠️ Les tuiles repliées restent dans la page
   (`display: none`), seulement cachées : les comptes à rebours des
   chantiers continuent de vivre et le dépliage est instantané. */
.ent-mod.est-plie .ent-tuiles { display: none; }
.ent-mod-cliquable { cursor: pointer; user-select: none; }
.ent-mod-cliquable:hover { border-bottom-color: rgba(45, 212, 191, .45); }
.ent-mod-fleche {
  flex: none; width: .95em; font-size: .72rem; color: var(--text3, #6e7691);
}
.ent-mod-cout {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .72rem; color: var(--text3, #6b8ab0); white-space: nowrap;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.62 — LA CLASSE DU VAISSEAU
   ════════════════════════════════════════════════════════════════════
   L'échéance du sans-classe, la matrice des huit effets et la
   reclassification offerte, dans la carte de flotte du Commandement. */

/* ⚠️ TROIS TONS QUI MONTENT, ET C'EST LA DEMANDE : « il doit voir
   l'échéance arriver ». Un avertissement d'intensité constante pendant
   quatre mois se confond avec le décor bien avant d'être utile — au 110ᵉ
   jour, le joueur ne le lit plus. La couleur change donc de camp à trente
   jours, puis à sept. */
.cls-echeance {
  margin-top: 6px; padding: 6px 8px; border-radius: 6px;
  font-size: .76rem; line-height: 1.35;
  border: 1px solid transparent;
}
.cls-ech-calme  { background: rgba(148,163,184,.10); border-color: rgba(148,163,184,.28); }
.cls-ech-ambre  { background: rgba(245,158,11,.12);  border-color: rgba(245,158,11,.40); }
.cls-ech-rouge  { background: rgba(233,69,96,.14);   border-color: rgba(233,69,96,.50); }
.cls-ech-perime { background: rgba(233,69,96,.20);   border-color: rgba(233,69,96,.70); }

/* La matrice, groupée par domaine : c'est le groupement qui rend la règle
   lisible — un domaine fort, un opposé faible, deux tièdes. */
.cls-matrice { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.cls-domaine { border-left: 2px solid rgba(148,163,184,.28); padding-left: 7px; }
.cls-domaine-titre {
  font-size: .7rem; letter-spacing: .04em; text-transform: uppercase;
  opacity: .7; margin-bottom: 2px;
}
.cls-ligne {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-size: .76rem; line-height: 1.4;
}
.cls-nom { opacity: .85; }
/* ⚠️ `tabular-nums` : les valeurs sont lues en colonne et se comparent
   entre elles. Des chiffres de largeurs inégales font danser la colonne et
   obligent à lire ligne à ligne. */
.cls-val { font-variant-numeric: tabular-nums; white-space: nowrap; font-weight: 600; }
.cls-bon     { color: #34d399; }
.cls-mauvais { color: #f87171; }
.cls-neutre  { color: var(--text3, #94a3b8); opacity: .7; font-weight: 400; }

.cls-reclasse {
  margin-top: 8px; padding: 7px 8px; border-radius: 6px;
  background: rgba(167,139,250,.12); border: 1px solid rgba(167,139,250,.40);
  font-size: .76rem; line-height: 1.35;
}
.cls-reclasse-btn { margin-top: 6px; width: 100%; }

/* ══════════════════════════════════════════════════════════════════
   v0.0.66 — LE CODEX DANS LE JEU (super-admin + bac à sable)
   ══════════════════════════════════════════════════════════════════ */
/* ⚠️ LA HAUTEUR EST FIXÉE, ET C'EST NÉCESSAIRE. Un cadre n'a pas de
   hauteur « naturelle » : sans consigne il retombe sur 150 px et on lit
   le Codex par une meurtrière. On lui donne ce qui reste de la fenêtre
   sous l'en-tête, et la page défile À L'INTÉRIEUR — c'est ce qui laisse
   son propre sommaire fonctionner. */
#cdx-hote { margin-top: 10px; }
.cdx-cadre {
  display: block; width: 100%; height: calc(100vh - 260px); min-height: 420px;
  border: 1px solid var(--border, #24304a); border-radius: 10px;
  background: #E9EDF5;   /* le fond clair du Codex, avant qu'il ne se peigne */
}
#cdx-barre { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
#cdx-frais {
  font: inherit; font-size: 12px; padding: 5px 12px; cursor: pointer;
  color: var(--text, #cfe0ff); background: var(--panel2, #16203a);
  border: 1px solid var(--border, #24304a); border-radius: 6px;
}
#cdx-frais:hover:not(:disabled) { border-color: var(--accent, #4aa3ff); }
#cdx-frais:disabled { opacity: .5; cursor: progress; }
.cdx-etat { font-size: 12px; color: var(--muted, #8fa3c8); }
.cdx-etat.travaille { color: var(--accent, #4aa3ff); }
.cdx-etat.rate { color: #ff8b8b; }
.cdx-reserve {
  display: inline-block; margin-left: 6px; padding: 1px 8px; border-radius: 999px;
  font-size: 11px; color: #f59e0b;
  border: 1px solid rgba(245, 158, 11, .45); background: rgba(245, 158, 11, .08);
}
.cdx-attente {
  padding: 40px 20px; text-align: center; color: var(--muted, #8fa3c8);
  border: 1px dashed var(--border, #24304a); border-radius: 10px;
}
.cdx-attente.rate { color: #ff8b8b; border-color: rgba(255, 139, 139, .4); }

/* ════════════════════════════════════════════════════════════════════
   v0.0.72 — L'ALERTE D'AGRESSION
   ════════════════════════════════════════════════════════════════════
   Amendement de Loryan, capture à l'appui : « victime d'un raid ou d'un
   espionnage, ça doit être à cet endroit là en rouge clignotant pétant ».
   Ce lot retire `raid_subi` et `spy_detected` de la cloche ; ce bandeau
   est ce qui les remplace, et il ne doit pas pouvoir se rater.

   ⚠️ LA RÈGLE `[hidden]` EST OBLIGATOIRE, et son absence a déjà coûté
   deux écrans dans ce projet (le badge de la cloche en v2.69, le bandeau
   de convalescence en v0.0.71). Une classe qui pose `display` a la MÊME
   spécificité que la règle native `[hidden] { display: none }`, et à
   égalité c'est la feuille d'AUTEUR qui gagne : sans cette ligne, le
   bandeau resterait affiché en permanence, vide, sur tous les écrans.

   ⚠️ LE CLIGNOTEMENT PORTE SUR LA COULEUR ET L'OMBRE, JAMAIS SUR LA
   TAILLE NI SUR LA BORDURE. Une bordure animée décalerait la barre du
   haut à chaque battement (leçon du panneau de vol, v0.0.55). */
#alerte-agression {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border: 2px solid #ef4444;
  border-radius: 8px;
  background: #2a0b0b;
  color: #fecaca;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
  animation: alerte-agression-pulse 0.9s ease-in-out infinite;
  /* v0.0.136 — JAMAIS ROGNÉ : posé en haut à droite de la FENÊTRE, hors du
     flux de #topbar dont le débordement horizontal masquait le texte
     (capture de Loryan). `position: fixed` échappe au débordement de la
     barre (elle n'a ni transform ni filter, donc ne le contient pas). */
  position: fixed;
  top: 6px;
  right: 10px;
  z-index: 260;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .55);
}
#alerte-agression[hidden] { display: none; }
#alerte-agression:hover { background: #3d0f0f; }
#alerte-agression .aa-nb {
  display: inline-block;
  min-width: 18px;
  padding: 1px 5px;
  border-radius: 9px;
  background: #ef4444;
  color: #fff;
  font-size: 0.72rem;
  text-align: center;
}
@keyframes alerte-agression-pulse {
  0%, 100% { background: #2a0b0b; color: #fecaca;
             box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.0); }
  50%      { background: #7f1d1d; color: #fff;
             box-shadow: 0 0 14px 3px rgba(239, 68, 68, 0.75); }
}
/* Un joueur qui a demandé moins d'animation garde l'alerte, sans le
   battement : c'est la COULEUR qui porte l'information, pas le rythme. */
@media (prefers-reduced-motion: reduce) {
  #alerte-agression { animation: none; background: #7f1d1d; color: #fff; }
}

/* Le bandeau d'aide de production (#prod-hint, « Aucun connecteur
   déployé… ») occupe le même coin sur bureau : tant que l'alerte est
   affichée, il descend d'une hauteur d'alerte au lieu de passer dessous.
   ⚠️ `!important` et la spécificité de `:has()` sont nécessaires : la
   règle « COIN HAUT-DROIT » pose déjà `top: 8px !important`. La classe
   `aa-fixe` est posée par `majAlerteAgression` sur le bandeau. */
body:not(.mobile-mode):has(#alerte-agression.aa-fixe:not([hidden])) #prod-hint {
  top: 44px !important;
}
@media (max-width: 700px) {
  /* ⚠️ SUR TÉLÉPHONE, la barre du haut est un bandeau replié : l'alerte se
     pose en bas, à droite, en plus petit, sur la même ligne que le bouton
     rond du tiroir des Vaisseaux Mères (#vmp-mobile-toggle : 48 px, à
     1 rem du bord) — une seule bande flottante, au lieu de deux étages
     sur le contenu. */
  #alerte-agression {
    top: auto;
    bottom: 26px;
    right: 74px;
    font-size: .7rem;
    padding: 5px 10px;
  }
}

/* La pastille rouge clignotante de l'onglet Mouvements : le même chiffre
   que le bandeau, peint par le même script. Elle se place en BAS à droite
   de l'icône dans la barre latérale, pour ne pas se superposer au compteur
   des mouvements (en haut à droite). */
.dsh-tab .dsh-tab-alerte {
  background: #ef4444;
  color: #fff;
  animation: alerte-pastille-pulse .9s ease-in-out infinite;
}
.dsh-tab .dsh-tab-alerte[hidden] { display: none; }
.dsh-tabs.is-sidebar .dsh-tab .dsh-tab-alerte {
  top: auto;
  bottom: 4px;
  right: 4px;
}
@keyframes alerte-pastille-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); opacity: 1; }
  50%      { box-shadow: 0 0 8px 3px rgba(239, 68, 68, .85); opacity: .7; }
}
@media (prefers-reduced-motion: reduce) {
  .dsh-tab .dsh-tab-alerte { animation: none; }
}

/* La ligne d'alerte en tête de la vue Mouvements : même vocabulaire que le
   bandeau (rouge, pastille du nombre), sur toute la largeur, cliquable.
   ⚠️ AUCUN RENDU NE POSE `.mv-alerte` AUJOURD'HUI, ET ON GARDE LA RÈGLE
   QUAND MÊME. L'alerte d'agression en tête des Mouvements est une
   fonctionnalité VOULUE ; c'est son affichage qui manque, et le défaut est
   signalé au propriétaire du projet. Supprimer l'habillage obligerait à le
   réécrire de zéro le jour où la ligne reparaît. */
.mv-alerte {
  display: flex; align-items: center; gap: 8px; width: 100%;
  margin: 0 0 10px; padding: 8px 14px;
  border: 2px solid #ef4444; border-radius: 8px;
  background: #2a0b0b; color: #fecaca;
  font: inherit; font-size: .8rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; text-align: left; cursor: pointer;
  animation: alerte-agression-pulse 0.9s ease-in-out infinite;
}
.mv-alerte:hover { background: #3d0f0f; }
.mv-alerte .aa-nb {
  display: inline-block; min-width: 18px; padding: 1px 5px; border-radius: 9px;
  background: #ef4444; color: #fff; font-size: .72rem; text-align: center;
}
@media (prefers-reduced-motion: reduce) {
  .mv-alerte { animation: none; background: #7f1d1d; color: #fff; }
}

/* ═══════════════════════════════════════════════════════════════════
   ENCART « AUCUNE ALERTE » — LE COIN HAUT-DROIT N'EST PLUS NU
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan, capture entourée en rouge : « là il n'y a rien, je
   voudrais un encart qui note "Aucune alerte" et que cet encart soit
   remplacé par les alertes quand il y en a ».
   Le coin est le rectangle au-dessus du panneau des Vaisseaux Mères, à
   droite des deux bandeaux du haut (180 × 144 px mesurés à 1 680 px de
   large). Deux alertes y vivent déjà : le bandeau de production
   (`#prod-hint`) et le bandeau rouge des raids et espionnages subis
   (`#alerte-agression`). L'encart n'est que la TROISIÈME occupation du
   même coin, celle qui vaut quand les deux autres sont absentes.

   ⚠️ C'EST L'ÉTAT DES DEUX ALERTES QUI DÉCIDE, PAS UN COMPTEUR À PART. Le
   sélecteur relit directement ce qu'elles affichent (`.visible` pour la
   première, l'absence de `hidden` pour la seconde) : un second calcul de
   « y a-t-il une alerte ? » finirait par dire autre chose que le coin, et
   l'encart se superposerait à une alerte — ou laisserait le coin vide.
   ▸ Une alerte de production marquée « vue » (croix ✕) quitte le coin :
     l'encart revient. C'est voulu — elle a été lue.

   ⚠️ `diagnostic-pret` ÉVITE UN FAUX « AUCUNE ALERTE » AU CHARGEMENT. La
   page s'affiche bien avant le premier diagnostic : sans ce témoin,
   l'encart s'affichait un quart de seconde (mesuré) avant de céder la place
   à l'alerte réelle. `renderProdHint` le pose à son premier passage.

   ⚠️ BUREAU SEULEMENT, PAR LES DEUX SYSTÈMES D'AFFICHAGE MOBILE. Au
   téléphone le coin n'existe pas (les bandeaux vont jusqu'au bord, le
   panneau devient un tiroir) : la classe `mobile-mode` est écartée dans le
   sélecteur, la largeur réelle l'est dans la media query plus bas — un
   joueur qui force « desktop » sur son téléphone n'a pas la classe.

   ⚠️ LES DIMENSIONS SUIVENT LES VARIABLES DU COIN, pas des nombres :
   largeur du panneau des Vaisseaux Mères, hauteur de l'en-tête (qui grandit
   quand la barre de ressources passe à la ligne). 8 px de marge partout,
   la même que le bandeau de production posé au même endroit. */
#aucune-alerte { display: none; }
/* ⚠️ LE SÉLECTEUR DU CORPS TIENT SUR UNE SEULE LIGNE : un retour à la ligne
   avant `:not(:has(…))` serait un ESPACE, donc un combinateur de descendance
   — la condition porterait sur n'importe quel élément de la page, et
   l'encart s'afficherait par-dessus les alertes. */
body.diagnostic-pret:not(.mobile-mode):not(:has(#prod-hint.visible)):not(:has(#alerte-agression:not([hidden]))) #aucune-alerte {
  display: flex;
}
#aucune-alerte {
  position: fixed;
  top: 8px;
  right: 8px;
  width: calc(var(--vm-panel-w, 180px) - 16px);
  height: calc(var(--total-header-h, 144px) - 16px);
  box-sizing: border-box;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 10px;
  border: 1px solid rgba(34, 197, 94, 0.28);
  border-radius: 8px;
  background: rgba(34, 197, 94, 0.05);
  text-align: center;
  /* Au-dessus de la barre du haut (200), comme le bandeau de production
     (210) qu'il remplace : les deux ne sont jamais affichés ensemble. */
  z-index: 205;
  /* La sous-ligne se retire quand l'encart est trop bas pour la porter
     entière (règle @container plus bas) : jamais un mot coupé. */
  container-type: size;
  container-name: aucune-alerte;
}
#aucune-alerte .aa0-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  border: 1.5px solid var(--color-success-fg, #22c55e);
  border-radius: 50%;
  color: var(--color-success-fg, #22c55e);
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
}
#aucune-alerte .aa0-titre {
  font-family: var(--hud);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #bbf7d0;
  line-height: 1.25;
}
#aucune-alerte .aa0-sous {
  font-size: 11px;
  line-height: 1.3;
  color: var(--text3);
}
@container aucune-alerte (max-height: 100px) {
  #aucune-alerte .aa0-sous { display: none; }
}
@media (max-width: 700px) {
  #aucune-alerte { display: none !important; }
}

/* ── Interrupteur alerteCoinTaille ───────────────────────────────────
   Loryan, deux captures côte à côte : « la taille de l'alerte n'est pas la
   même que la fenêtre comme quoi tout va bien ». L'encart « Aucune
   alerte » (au-dessus) remplit le coin ; l'alerte de production, taillée
   avant lui (bloc « COIN HAUT-DROIT », v2.9.11), n'en occupait qu'une
   carte étroite au texte minuscule. Allumé : MÊMES DIMENSIONS que l'encart
   — ses deux expressions, recopiées telles quelles et relues par
   test/alerte-coin-taille.test.js — et le même corps de texte (11 px).
   ⚠️ `min-height`, PAS `height` : le message reste coupé à quatre lignes,
   donc la hauteur ne bouge pas… sauf pour le bandeau d'ancrage de secours
   (`.ph-secours`), le seul à porter un BOUTON, qui a le droit de
   s'allonger — une hauteur fixe le couperait, et le bouton avec.
   ⚠️ Bureau seulement, comme le bloc qu'il retouche (`:not(.mobile-mode)`). */
body.f-alerteCoinTaille:not(.mobile-mode) #prod-hint {
  width: calc(var(--vm-panel-w, 180px) - 16px);
  max-width: calc(var(--vm-panel-w, 180px) - 16px);
  min-height: calc(var(--total-header-h, 144px) - 16px);
  box-sizing: border-box;
  justify-content: center;
  gap: 6px;
  padding: 8px 10px;
  font-size: 11px;
  line-height: 1.3;
}
body.f-alerteCoinTaille:not(.mobile-mode) #prod-hint .ph-ico { font-size: 1.2rem; }
body.f-alerteCoinTaille:not(.mobile-mode) #prod-hint .ph-arrow { font-size: 11px; }
/* La croix « vu » passe en haut à droite : elle ne mange plus une ligne. */
body.f-alerteCoinTaille:not(.mobile-mode) #prod-hint.ph-masquable .ph-close {
  position: absolute; top: 4px; right: 4px; margin: 0; font-size: 11px;
}
/* Sous le bandeau rouge d'agression, l'alerte descend de 36 px (règle
   `aa-fixe` plus haut) : elle raccourcit d'autant pour finir au même bas. */
body.f-alerteCoinTaille:not(.mobile-mode):has(#alerte-agression.aa-fixe:not([hidden])) #prod-hint {
  min-height: calc(var(--total-header-h, 144px) - 52px);
}

/* ── Interrupteur alerteAgressionCoin ────────────────────────────────
   Loryan, capture entourée : « l'espionnage devrait être mis dans le même
   endroit que les autres types d'alerte, juste elles priment sur les autres
   donc on les voit en premier ». L'alerte rouge quitte la pastille du haut
   et devient une CARTE du coin des alertes — la largeur du coin, une ligne
   par alerte — posée en TÊTE. Seule, elle remplit le coin, comme « Aucune
   alerte » ; avec une alerte de production, elle reprend sa hauteur et
   l'alerte de production se range dessous (`--aa-coin-h`, mesurée par
   features/notifications/alerte-agression.js).
   ⚠️ CE BLOC VIENT APRÈS celui d'alerteCoinTaille, exprès : à spécificité
   égale, c'est le dernier qui gagne, et ses hauteurs d'alerte de production
   doivent céder la place à la carte.
   ⚠️ Au téléphone, même idée dans la bande des alertes sous la barre des
   ressources : la carte d'abord, l'alerte de production ensuite, et le
   contenu descend des deux (core/layout.js). */
body.f-alerteAgressionCoin:not(.mobile-mode) #alerte-agression.aa-coin {
  top: 8px;
  right: 8px;
  bottom: auto;
  /* ⚠️ `--vmp-coin` D'ABORD : colonne des Vaisseaux Mères repliable, le coin
     garde ses 180 px quand la colonne rétrécit (bloc colonneVmRepliable). */
  width: calc(var(--vmp-coin, var(--vm-panel-w, 180px)) - 16px);
  box-sizing: border-box;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: 2px;
  padding: 6px 8px;
  white-space: normal;
  text-transform: none;
  letter-spacing: 0;
  font-size: 11px;
  line-height: 1.3;
  text-align: left;
}
body.f-alerteAgressionCoin:not(.mobile-mode):not(:has(#prod-hint.visible)) #alerte-agression.aa-coin {
  min-height: calc(var(--total-header-h, 144px) - 16px);
}
#alerte-agression.aa-coin .aa-tete {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--hud); font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
}
#alerte-agression.aa-coin .aa-ligne { display: block; overflow-wrap: anywhere; }
/* L'alerte de production se range SOUS la carte, et raccourcit d'autant. */
body.f-alerteAgressionCoin:not(.mobile-mode):has(#alerte-agression.aa-coin:not([hidden])) #prod-hint {
  top: calc(14px + var(--aa-coin-h, 36px)) !important;
}
/* Sous la carte, l'alerte de production n'a plus que le bas du coin : une
   rangée compacte — l'icône, puis le message en deux lignes (le clic l'ouvre
   en grand, comme avant). Mesuré à 1 500 px : la carte à deux lignes et
   l'alerte de production tiennent ensemble dans les 146 px du coin. */
body.f-alerteAgressionCoin:not(.mobile-mode):has(#alerte-agression.aa-coin:not([hidden])) #prod-hint {
  flex-direction: row;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 6px;
  padding: 6px 8px;
  min-height: 0;
}
body.f-alerteAgressionCoin:not(.mobile-mode):has(#alerte-agression.aa-coin:not([hidden])) #prod-hint .ph-msg {
  -webkit-line-clamp: 2;
  flex: 1;
  min-width: 0;
}
body.f-alerteAgressionCoin:not(.mobile-mode):has(#alerte-agression.aa-coin:not([hidden])) #prod-hint .ph-arrow {
  display: none;
}
/* Au téléphone : la bande des alertes, sous la barre des ressources. */
body.f-alerteAgressionCoin.mobile-mode #alerte-agression.aa-coin {
  top: var(--resbar-bottom, 88px);
  bottom: auto;
  left: 0;
  right: 0;
  width: auto;
  border-radius: 0;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  padding: 6px 12px;
  white-space: normal;
  text-transform: none;
  letter-spacing: 0;
  font-size: 12px;
  line-height: 1.3;
  text-align: left;
  z-index: 185;
}
body.f-alerteAgressionCoin.mobile-mode:has(#alerte-agression.aa-coin:not([hidden])) #prod-hint.visible {
  top: calc(var(--resbar-bottom, 88px) + var(--aa-coin-h, 0px)) !important;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.73 — L'ÉCRAN « SESSION EXPIRÉE »
   ════════════════════════════════════════════════════════════════════
   Il remplace un écran à moitié dessiné qui plantait. Il couvre tout,
   parce qu'il n'y a rien derrière lui qui vaille la peine d'être vu :
   la partie n'a pas été chargée. */
#session-expiree {
  position: fixed; inset: 0; z-index: 100000;
  display: flex; align-items: center; justify-content: center;
  background: rgba(8, 10, 18, 0.94);
  padding: 24px;
}
#session-expiree .se-boite {
  max-width: 460px; text-align: center;
  background: #141826; border: 1px solid #2a3145; border-radius: 14px;
  padding: 32px 30px; box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.8);
  color: #cbd5e1; font-size: 0.94rem; line-height: 1.6;
}
#session-expiree .se-ico { font-size: 2.4rem; margin-bottom: 10px; }
#session-expiree h2 { margin: 0 0 14px; font-size: 1.24rem; color: #f1f5f9; }
#session-expiree p { margin: 0 0 12px; }
#session-expiree .se-geste { color: #94a3b8; font-size: 0.88rem; }
#session-expiree .se-btn {
  margin-top: 12px; padding: 10px 22px; border-radius: 8px; cursor: pointer;
  border: 1px solid #3b82f6; background: #1d4ed8; color: #fff;
  font-size: 0.92rem; font-weight: 600;
}
#session-expiree .se-btn:hover { background: #2563eb; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.84 — LES PANNEAUX D'INFORMATION EN TIROIR (mobile)
   ════════════════════════════════════════════════════════════════════
   Quatre signalements de Loryan sur la vue téléphone. Ils décrivent le
   même défaut vu de quatre endroits : un panneau d'information prend
   tout l'écran, et rien ne dit comment en sortir ni comment y revenir.
   Le dessin vit ICI, la pose et le retrait des classes dans
   `public/features/mobile/tiroirs.js`.

   ⚠️ TOUT EST GARDÉ PAR `body.mobile-mode` (le format téléphone) OU
   `body.ecran-etroit` (une fenêtre étroite, quel que soit le réglage). Le
   grand écran ne change pas d'un pixel : ces règles ne s'y appliquent jamais.
   ════════════════════════════════════════════════════════════════════ */

/* ⚠️ CHAQUE RÈGLE DE CE BLOC PORTE UN SECOND SÉLECTEUR, tout aussi étroit :
   `body.ecran-etroit`. Il vise le joueur qui a choisi « Desktop forcé » sur
   un téléphone — il refuse la version réduite, pas les panneaux qui montent
   du bas, sans lesquels la fiche d'un bâtiment s'ouvre deux écrans plus bas,
   hors de portée.
   ⚠️ UN SECOND SÉLECTEUR, ET SURTOUT PAS UNE COPIE DU BLOC : deux copies de
   ces règles-ci, lourdes de `!important` et de calculs de spécificité,
   finiraient par diverger — et la divergence ne se verrait qu'à l'écran.
   ⚠️ IL EST AUSSI ÉTROIT QUE L'AUTRE : `body.ecran-etroit` n'est posée que
   sous le seuil de largeur, elle ne peut donc pas atteindre un grand écran,
   et c'est ce que le banc de ce bloc vérifie sélecteur par sélecteur. */

/* ─── 1. LA FICHE D'UN BÂTIMENT — le tiroir du bas ───────────────────
   ⚠️ CETTE RÈGLE DOIT BATTRE `@media (max-width: 1180px) .module-detail
   { position: static !important }`, ~6 000 lignes plus haut. Deux
   `!important` s'arbitrent à la SPÉCIFICITÉ : un identifiant plus deux
   classes contre une seule classe — celle-ci gagne où qu'on l'écrive.
   Retirer le `#` (ou passer `.module-detail`) la rendrait sans effet, en
   silence, et la fiche retomberait deux écrans plus bas. */
body.mobile-mode #module-detail,
body.ecran-etroit #module-detail {
  position: fixed !important;
  /* ⚠️ IL COMMENCE APRÈS LE RAIL DES VUES, jamais à zéro. `#dsh-tabs-main`
     est fixe, large de 48 px et posé à z-index 150 : un tiroir collé au
     bord gauche passerait dessous (ou par-dessus), et dans les deux cas on
     perdrait quelque chose — les premiers caractères de chaque ligne, ou la
     navigation entière. La barre des files respecte déjà ce rail depuis la
     v1.01, on fait comme elle. */
  left: var(--sidebar-width, 48px); right: 0; bottom: 0; top: auto;
  width: auto; z-index: 170;
  max-height: 62vh !important;
  overflow-y: auto;
  margin: 0;
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -14px 34px rgba(0, 0, 0, 0.6);
  /* ⚠️ 102 % ET PAS 100 % : l'ombre portée déborde du panneau et resterait
     visible en bas de l'écran, un liseré gris qu'on ne s'explique pas. */
  transform: translateY(102%);
  /* ⚠️ `visibility` EST INDISPENSABLE, et sa transition est volontaire :
     un panneau seulement déplacé reste dans l'arbre, donc il attrape
     encore les clics et peut allonger la page. En transition, elle bascule
     à la FIN quand on ferme (le glissement reste visible) et tout de suite
     quand on ouvre. */
  visibility: hidden;
  transition: transform .26s ease-out, visibility .26s;
  /* La barre de gestes du téléphone mord sur le bas de l'écran. */
  padding-bottom: calc(0.9rem + env(safe-area-inset-bottom, 0px));
}
body.mobile-mode.tiroir-fiche-ouvert #module-detail,
body.ecran-etroit.tiroir-fiche-ouvert #module-detail {
  transform: translateY(0);
  visibility: visible;
}

/* ⚠️ ET LE TIROIR OUVERT REND LE BAS DE LA GRILLE INATTEIGNABLE SI ON NE
   FAIT RIEN. Il est posé PAR-DESSUS la page : on peut faire défiler, mais
   arrivé en bas, les dernières vignettes restent sous lui — le troisième
   défaut de la liste mobile du projet (« est-ce que tout reste
   atteignable ? »), et il ne se signale par aucune erreur. On rend donc au
   contenu la hauteur exacte du tiroir, mesurée par le JS. */
body.mobile-mode.tiroir-fiche-ouvert #ship-content,
body.ecran-etroit.tiroir-fiche-ouvert #ship-content {
  padding-bottom: calc(var(--tiroir-fiche-h, 62vh) + 20px);
}

/* La poignée. Fermée, elle est la SEULE trace du tiroir — c'est elle qui
   remplace le « clic dans le vide ». Ouverte, elle se pose juste au-dessus
   du tiroir et devient le bouton de fermeture.
   ⚠️ À GAUCHE, ET C'EST MESURÉ : le bouton rond du rail des Vaisseaux
   Mères (`#vmp-mobile-toggle`) occupe déjà le coin bas-droit en mobile. */
#tiroir-fiche-poignee {
  position: fixed;
  /* ⚠️ APRÈS LE RAIL DES VUES, comme le tiroir lui-même : posée à 10 px du
     bord, la pastille se retrouvait PAR-DESSUS deux icônes de navigation
     (elle est à z-index 171, le rail à 150). On ne recouvre jamais la
     navigation pour ouvrir un panneau. */
  left: calc(var(--sidebar-width, 48px) + 10px);
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 171;
  display: inline-flex; align-items: center; gap: 0.45rem;
  min-height: 40px; padding: 0 0.9rem;
  border-radius: 999px;
  background: rgba(13, 16, 32, 0.94);
  border: 1px solid var(--accent2, #0ea5e9);
  color: var(--text1, #e5e8f5);
  font-family: var(--hud); font-size: 0.7rem; letter-spacing: 0.05em;
  cursor: pointer;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  transition: bottom .26s ease-out;
}
body.tiroir-fiche-ouvert #tiroir-fiche-poignee {
  /* ⚠️ LA HAUTEUR VIENT DU JS (`--tiroir-fiche-h`), et il le faut : le
     tiroir s'ajuste à son contenu sous son plafond de 62 vh. Calée sur le
     plafond, la poignée flottait 175 px au-dessus de lui (mesuré sur une
     fiche d'extracteur). Le repli sert le temps du premier rendu. */
  bottom: calc(var(--tiroir-fiche-h, 62vh) + 8px);
  border-color: var(--border2, #2a3145);
}
#tiroir-fiche-poignee .tfp-ico { font-size: 0.9rem; line-height: 1; }
#tiroir-fiche-poignee .tfp-chev { font-size: 0.58rem; opacity: 0.7; }

/* ─── 2. LE PANNEAU DE PLANÈTE — deux hauteurs ───────────────────────
   Mesuré à 390 × 844 : 633 px de haut, soit 75 % de l'écran, la carte
   n'existait plus. Il garde deux positions parce qu'on y lit des listes
   (composition, stock, trajets par vaisseau) : le replier sur son titre
   coûterait un geste de plus sans rien donner en échange. */
/* ⚠️ LE RAIL DES VUES MANGEAIT LES 48 PREMIERS PIXELS DE CHAQUE LIGNE, et
   ce n'est pas nouveau : le panneau part de x = 0 et `#dsh-tabs-main` (fixe,
   48 px, z-index 150) est dessus. Le nom de la planète se lisait
   « andoris IX », les ressources « errith ». Un texte coupé au milieu d'un
   mot est le seul indice visible d'un panneau mal calé, et il ne lève aucune
   erreur. Corrigé par le second sélecteur, sans toucher la règle de base
   (leçon de l'Éolienne, v0.0.65). */
body.mobile-mode #planet-panel,
body.ecran-etroit #planet-panel {
  left: var(--sidebar-width, 48px) !important;
  /* ⚠️ ET IL FAUT REPRENDRE LA LARGEUR AVEC, sinon le panneau sort de
     l'écran par la droite. La règle mobile de base pose `width: 100%
     !important` : déplacer le seul bord GAUCHE le poussait 48 px trop loin,
     et la dernière colonne du tableau de stock se coupait au milieu d'un mot
     — MESURÉ, x = 48 pour une largeur de 390 sur un écran de 390. La page,
     elle, ne défilait pas horizontalement : rien ne le signalait. */
  width: auto !important;
  max-width: none !important;
  max-height: 52vh;
}
body.mobile-mode.tiroir-planete-agrandi #planet-panel,
body.ecran-etroit.tiroir-planete-agrandi #planet-panel { max-height: 92vh; }

/* La poignée vit DANS le panneau, en tête, et reste COLLÉE en haut quand
   le panneau défile : c'est le seul endroit où elle reste sous le pouce. */
#tiroir-planete-poignee {
  position: sticky; top: 0; z-index: 2;
  display: block; width: 100%;
  min-height: 34px; margin: 0 0 0.4rem;
  /* La croix de fermeture est en position absolue en haut à DROITE : on
     laisse sa place plutôt que de passer dessous. */
  padding: 0 44px 0 0.6rem;
  text-align: left;
  background: rgba(13, 16, 32, 0.92);
  border: none; border-bottom: 1px solid var(--border);
  color: var(--text2, #b7c0da);
  font-family: var(--hud); font-size: 0.66rem; letter-spacing: 0.05em;
  cursor: pointer;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
#tiroir-planete-poignee:hover { color: var(--text1, #e5e8f5); }
/* ⚠️ LA CROIX DE FERMETURE N'EST PAS EN POSITION ABSOLUE, contrairement à
   ce que son coin haut-droit laisse croire : `#planet-panel .btn-close` est
   COLLANTE et vit dans le flux, en tête. La poignée insérée devant elle la
   poussait donc d'une ligne entière — 36 px de vide pour rien, sur un
   panneau qu'on vient justement de raccourcir. On la fait flotter à droite
   et remonter sur la barre de la poignée, dont le retrait à droite (44 px)
   lui garde sa place. */
body.mobile-mode #planet-panel .btn-close {
  float: right;
  margin-top: -38px;
  margin-right: 2px;
  z-index: 4;
}

/* ─── 3. LA BARRE DES FILES — elle cesse de manger l'écran ───────────
   Signalement de Yoh : « onglet recherche vide en vue mobile ». La vue
   Recherche est INTACTE dessous — c'est la barre des files, déployée sur
   539 px des 844 de l'écran, qui la recouvrait. On la borne, et ses
   vignettes défilent à l'intérieur.
   ⚠️ NI DOCKÉE NI REPLIÉE : dockée, elle vit dans la colonne de droite de
   la vue Vaisseau Mère et ne recouvre rien ; repliée, il n'en reste que
   sa pastille. Les borner ferait rétrécir deux panneaux qui n'ont jamais
   posé de problème. */
body.mobile-mode #queues-bar:not(.is-docked):not(.is-collapsed) {
  /* 38 vh = 320 px sur un écran de 844, mesuré : l'en-tête, les quatre
     onglets et deux rangées de vignettes tiennent, le reste défile. À
     46 vh, la liste des recherches n'avait encore que six lignes visibles
     — on déplaçait le mur au lieu de l'ouvrir. */
  max-height: 38vh;
  display: flex; flex-direction: column;
}
body.mobile-mode #queues-bar:not(.is-docked):not(.is-collapsed) .qbar-slots {
  overflow-y: auto;
  min-height: 0;
}
/* Même règle qu'au-dessus : la barre est POSÉE sur la vue. Les 130 px
   réservés sous le contenu dataient d'une barre bien plus courte — la
   dernière recherche de la liste restait dessous, et il n'y avait plus
   aucun moyen de l'atteindre. */
body.mobile-mode.has-queues-bar:not(.queues-collapsed):not(.queues-docked) #view-research {
  padding-bottom: calc(38vh + 16px);
}


/* ════════════════════════════════════════════════════════════════════
   v0.0.86 — LE ZOOM DU NAVIGATEUR NE FAIT PLUS SE SUPERPOSER LES PANNEAUX
   ════════════════════════════════════════════════════════════════════
   Signalement de Loryan, deux captures : « à haut zoom, tout se superpose
   sans se redimensionner — encore pire avec la file déployée ». Il parle du
   zoom du NAVIGATEUR (Ctrl + molette), pas de celui de la carte : à 200 %,
   un écran de 1920 px ne vaut plus que 960 px CSS.

   ⚠️ LE DÉFAUT EST UN PLANCHER QUI NE SAIT PAS SE BAISSER, et c'est tout.
   `minmax(380px, 1fr)` veut dire « au moins 380 px » : quand la colonne
   n'en fait plus que 205, la piste reste à 380 et la carte DÉBORDE de
   175 px sur sa voisine. Mesuré au navigateur, section Vaisseaux :

       largeur CSS    colonne des vignettes    débordement d'une carte
          1600 px            434 px                    aucun
          1400 px            319 px                     61 px
          1300 px            262 px                    118 px
          1200 px            205 px                    175 px

   ⚠️ ET RIEN NE LE SIGNALE : mesuré, la PAGE ne défile pas
   horizontalement (`scrollWidth === clientWidth`). Le débordement ne
   pousse donc rien — il se pose PAR-DESSUS la colonne des recherches puis
   par-dessus la fiche de droite. C'est exactement « ça se superpose sans
   se redimensionner ».

   ⚠️ LA BASCULE À 1180 px ARRIVE TROP TARD, et c'est la raison de fond :
   la disposition large cesse d'être tenable vers 1500 px, pas vers 1180.
   On ne DÉPLACE pas ce seuil pour autant — ce serait retirer la colonne de
   droite à tous ceux qui travaillent entre 1200 et 1500 px, alors qu'elle
   leur va très bien. On rend simplement le plancher négociable.

   ⚠️ `min(Npx, 100%)` EST LE GESTE, ET LE PROJET LE CONNAÎT DÉJÀ
   (`.mv-etapes-grid`, v0.0.13). Le plancher devient la largeur du
   conteneur dès que celui-ci est plus étroit : la vignette rétrécit au
   lieu de sortir. À largeur suffisante, la règle rend EXACTEMENT la même
   chose qu'avant — le test le vérifie colonne par colonne.

   ⚠️ QUATRE GRILLES, ET QUATRE SEULEMENT : celles qui vivent dans une
   colonne que la vue Vaisseau Mère peut serrer. Les autres grilles du jeu
   (statistiques, coalitions) ont le même plancher mais pas le même
   voisinage — les toucher changerait des écrans que personne n'a
   signalés. */
body .fleet-grid {
  grid-template-columns: repeat(auto-fill, minmax(min(380px, 100%), 1fr));
}
body .bld-grid,
body .conn-grid,
body .module-techs {
  grid-template-columns: repeat(auto-fill, minmax(min(184px, 100%), 1fr));
}
/* ⚠️ ET IL Y AVAIT UNE SECONDE COUCHE, que seule la première correction a
   fait apparaître : une fois la carte libre de rétrécir, c'est SON contenu
   qui débordait à son tour. `.ship-card-stats` savait déjà passer à la
   ligne, mais chacun de ses éléments porte `white-space: nowrap` — et une
   ligne de statistiques insécable de 272 px ne rentre pas dans une colonne
   de 154, quoi qu'on fasse du conteneur. Mesuré : 118 px de débordement
   DANS la carte, une fois la grille réparée.
   Le `nowrap` garde « 6.0k F » d'un seul tenant, ce qui est le bon choix
   tant qu'il y a la place. On ne le retire donc que là où il n'y en a plus. */
/* ⚠️ SANS MEDIA QUERY, ET C'EST LA LEÇON DU LOT. Ma première version bornait
   ce desserrage à `max-width: 1500px` — or le tiroir ouvert peut serrer la
   colonne à 157 px sur un écran de 1600. Ce n'est pas la FENÊTRE qui manque
   de place, c'est le CONTENEUR, et une media query ne sait rien du second.
   Le désarmer partout ne coûte rien : `white-space: normal` ne passe à la
   ligne que s'il le faut — là où le coût tient sur une ligne, il y reste. */
body .ship-card-stats > span { white-space: normal; }
/* La rangée « quantité + bouton » d'une carte de vaisseau passe à la ligne
   plutôt que de déborder : mesuré, le bouton « 🔒 Verrouillé » sortait de
   53 px d'une rangée de 97 px. */
body .ship-card-build-row { flex-wrap: wrap; }

@media (max-width: 1700px) {
  /* ⚠️ « ENCORE PIRE AVEC LA FILE DÉPLOYÉE », ET C'EST LE PIRE CAS DE LOIN.
     Le tiroir ouvert RÉSERVE ses 330 px sur toute la vue : à 1200 px de
     large, la colonne des vignettes tombe alors à 35 px — mesuré. Aucune
     grille, si souple soit-elle, ne peut rendre cette largeur utilisable.
     Sous 1500 px, le tiroir ouvert se POSE donc par-dessus la page au lieu
     de la pousser : il ne réserve plus que sa poignée, exactement comme
     quand il est fermé. Il est déjà en `position: fixed` — il n'y a rien à
     déplacer, seulement à cesser de lui faire de la place.
     ⚠️ LE SEUIL EST À 1700 px, ET IL SE CALCULE : le tiroir ouvert réserve
     130 px de rail plus 330 px de panneau, soit 460 px. Pour que la vue
     garde les ~1180 px où sa disposition large tient debout, il faut donc
     1640 px de fenêtre. Au-delà, la place existe vraiment et repousser le
     contenu reste préférable à le recouvrir. Mesuré à 1600 px : sans ce
     seuil, la colonne des vignettes tombait à 157 px. */
  body.qbar-tiroir.qbar-tiroir-ouvert #view-ship {
    right: calc(var(--vm-panel-w, 130px) + var(--qbar-tiroir-poignee));
  }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.84 — LA VUE TÉLÉPHONE (outil d'administration)
   ════════════════════════════════════════════════════════════════════
   Un Galaxy S24 Ultra dessiné par-dessus le jeu, avec le vrai jeu qui
   tourne dedans dans un cadre à sa taille exacte. Logique et chiffres :
   `public/features/admin/vue-telephone.js` — AUCUNE dimension d'appareil
   n'est écrite ici, elles y vivent toutes.

   ⚠️ RÉSERVÉ AU SUPER-ADMIN, et ce n'est pas le CSS qui le garantit : le
   bouton qui l'ouvre n'est construit que sous `isSuper` (realm.js). Le
   voile n'existe donc dans le document que si quelqu'un l'a ouvert.
   ════════════════════════════════════════════════════════════════════ */

#vt-voile {
  position: fixed; inset: 0; z-index: 99000;
  display: flex; flex-direction: column;
  background: rgba(6, 8, 16, 0.93);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}

/* ─── La barre d'outils ─────────────────────────────────────────── */
#vt-barre {
  display: flex; align-items: center; gap: 0.4rem;
  padding: 0.5rem 0.7rem;
  background: #0d1020; border-bottom: 1px solid var(--border, #2a3145);
  font-family: var(--hud); font-size: 0.7rem; flex-wrap: wrap;
}
#vt-barre .vt-titre { color: var(--text1, #e5e8f5); letter-spacing: 0.06em; font-weight: 600; }
#vt-barre .vt-sep { width: 1px; height: 20px; background: var(--border, #2a3145); margin: 0 0.25rem; }
#vt-barre .vt-espace { flex: 1 1 auto; }
.vt-btn {
  padding: 0.34rem 0.7rem; border-radius: 6px; cursor: pointer;
  background: #161b2e; border: 1px solid var(--border, #2a3145);
  color: var(--text2, #b7c0da); font-family: var(--hud); font-size: 0.68rem;
  letter-spacing: 0.04em; white-space: nowrap;
  transition: color .12s, border-color .12s, background .12s;
}
.vt-btn:hover { color: var(--text1, #e5e8f5); border-color: var(--accent2, #0ea5e9); }
.vt-btn.is-on { border-color: var(--accent2, #0ea5e9); color: #fff; background: #143349; }
.vt-btn.vt-fermer { font-size: 0.85rem; padding: 0.28rem 0.6rem; }

/* ⚠️ LES DEUX MESURES SONT L'INTÉRÊT DE L'OUTIL, pas sa décoration.
   « 412 × 779 » est ce que le jeu voit ; la ligne du dessous dit d'où ce
   chiffre sort. Sans elle, on ne peut ni le vérifier ni le contester. */
.vt-mesures { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.35; }
.vt-mesures b { color: var(--accent2, #0ea5e9); font-family: var(--mono); font-size: 0.74rem; }
.vt-mesures i { color: var(--text3, #6b7699); font-style: normal; font-size: 0.6rem; font-family: var(--mono); }

/* ─── La scène ──────────────────────────────────────────────────── */
#vt-scene {
  flex: 1 1 auto; min-height: 0; overflow: auto;
  display: flex; justify-content: center; align-items: flex-start;
  padding: 14px;
}

/* ─── Le châssis ────────────────────────────────────────────────── */
/* ⚠️ `transform-origin: top center` : le zoom d'ajustement ne doit
   décaler le téléphone ni vers la gauche ni vers le haut, sinon il sort
   de la scène dès qu'on descend sous 100 %. C'est aussi ce qui permet de
   ne compenser QUE la hauteur dans le JS. */
#vt-appareil {
  position: relative;
  display: flex; flex-direction: column;
  transform-origin: top center;
  background: #000;
  border: 13px solid #17181c;
  border-radius: 34px;
  box-shadow: 0 0 0 2px #3a3d47, 0 26px 70px -18px rgba(0, 0, 0, 0.9);
  overflow: hidden;
  flex: 0 0 auto;
}

/* ─── La barre d'état Android ───────────────────────────────────── */
#vt-statut {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 14px; background: #000; color: #fff;
  font-family: var(--hud); font-size: 11px; letter-spacing: 0.02em;
  flex: 0 0 auto;
}
#vt-statut .vt-poincon {
  width: 11px; height: 11px; border-radius: 50%;
  background: #0b0b0d; box-shadow: inset 0 0 0 1px #24262c;
}
#vt-statut .vt-icones { display: flex; align-items: center; gap: 5px; }
#vt-statut .vt-ico { height: 10px; width: auto; fill: currentColor; color: #fff; }
#vt-statut .vt-ico:last-child { height: 11px; }

/* ─── La barre du navigateur ────────────────────────────────────── */
.vt-nav {
  display: flex; align-items: center; gap: 8px;
  padding: 0 10px; color: #cfd3dc; font-size: 11px;
  font-family: var(--hud); flex: 0 0 auto;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
#vt-nav-haut { border-top: none; border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
.vt-nav .vt-nav-ico { opacity: 0.75; font-size: 12px; }
#vt-nav-bas { justify-content: space-around; }
/* L'adresse est TRONQUÉE comme sur un vrai téléphone : la voir déborder
   apprendrait quelque chose de faux sur la place disponible. */
.vt-nav .vt-url {
  flex: 1 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
  background: #3a3f4a; border-radius: 999px; padding: 4px 10px; color: #e7eaf2;
}

/* ─── L'écran, c'est-à-dire le vrai jeu ─────────────────────────── */
/* ⚠️ AUCUNE TAILLE ICI : elle est posée par le JS depuis le modèle de
   l'appareil, et c'est elle qui décide de tout (media queries comprises).
   Une largeur écrite dans cette feuille prendrait un jour le pas sur le
   modèle, et on mesurerait un téléphone qui n'existe pas. */
#vt-cadre { border: 0; display: block; background: #05070f; flex: 0 0 auto; }

/* ─── La barre de gestes ────────────────────────────────────────── */
#vt-gestes {
  display: flex; align-items: center; justify-content: center;
  background: #000; flex: 0 0 auto;
}
#vt-gestes .vt-trait {
  width: 36%; height: 4px; border-radius: 999px; background: #e6e8ee; opacity: 0.9;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.85 — LA VERSION MOBILE TIENT DANS L'ÉCRAN
   Quatre corrections mesurées (purge v0.0.87), sur
   signalement de Loryan. Hors interrupteur, PAS UN PIXEL NE BOUGE.
   ════════════════════════════════════════════════════════════════════ */

/* ─── 1. LA LÉGENDE EST BORNÉE, ET SA POIGNÉE RESTE ATTEIGNABLE ──────
   « quand je déplie la légende, impossible de la replier, c'est en haut
   sous le bandeau des ressources ».
   ⚠️ MESURÉ AVANT CORRECTION : dépliée, elle fait 847 px de haut sur un
   écran de 779, son sommet est à y = −80 et son bouton de repli à y = −74
   — donc HORS DE L'ÉCRAN. Elle est ancrée par le BAS (`bottom: 12px`) et
   grandit vers le HAUT : en s'ouvrant, elle emporte sa propre poignée
   au-dessus du bord. Rien ne la bornait (`max-height: none`).
   ⚠️ LES 3/4 SONT UN POURCENTAGE DU PARENT, et c'est ce qui rend la
   demande de Loryan littérale : le panneau est `position: absolute` dans
   la vue galaxie, donc `75 %` VEUT DIRE trois quarts de la vue galaxie en
   partant du bas — « pour que même si Aller À est dépliée, la légende ne
   morde pas dessus ». */
body .map-legende {
  display: flex;
  flex-direction: column;
  max-height: 75%;
}
/* ⚠️ L'EN-TÊTE DEVIENT COLLANT : c'est LUI qu'on ne pouvait plus attraper.
   Il lui faut son propre fond — sans lui, les lignes de la légende
   défileraient EN TRANSPARENCE derrière le titre. */
body .mlg-bascule {
  position: sticky;
  top: 0;
  z-index: 2;
  flex: 0 0 auto;
  background: rgba(5, 8, 16, 0.96);
  padding-bottom: 0.25rem;
}
/* ⚠️ `pointer-events: auto` EST INDISPENSABLE ICI, et c'est exactement le
   piège de la v0.0.32 (les cases de filtre inertes) : le panneau entier est
   en `pointer-events: none` pour qu'on puisse faire glisser la carte au
   travers. Sans cette ligne, le corps serait défilable en théorie et le
   doigt ne l'accrocherait JAMAIS.
   ⚠️ CONSÉQUENCE ASSUMÉE : on ne fait plus glisser la carte à travers le
   corps de la légende dépliée. C'est le prix d'un panneau qu'on peut
   parcourir — et il se replie d'un geste. */
body .mlg-corps {
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  pointer-events: auto;
  min-height: 0;               /* sans lui, un enfant flex refuse de rétrécir */
}
@media (max-width: 700px) {
  /* Au téléphone, la moitié de l'écran (demande explicite de Loryan). */
  body .map-legende { max-height: 50vh; }
}

/* ─── 2. LE RAIL DES VUES S'EFFACE EN VUE GALAXIE ────────────────────
   « fais en sorte que la barre verticale de gauche soit masquée mais se
   réaffiche quand on met son doigt sur la bordure gauche ».
   ⚠️ ON LE TRANSLATE, ON NE LE MET PAS EN `display: none`. Un rail retiré
   du flux perdrait sa position de défilement et son onglet actif à chaque
   ouverture, et il n'y aurait plus rien à faire glisser : l'apparition
   serait un surgissement, pas un tiroir.
   ⚠️ ET SEULEMENT EN VUE GALAXIE : `body.carte-mobile` n'est posée que là
   (carte-mobile.js). Les autres vues gardent leur rail — c'est la carte
   qui a besoin de toute sa largeur, pas la fiche d'un bâtiment. */
@media (max-width: 700px) {
  body.carte-mobile #dsh-tabs-main {
    transform: translateX(-100%);
    transition: transform 0.18s ease;
  }
  body.carte-mobile.rail-ouvert #dsh-tabs-main {
    transform: translateX(0);
    box-shadow: 6px 0 18px rgba(0, 0, 0, 0.55);
  }
  /* La lisière que le doigt vient chercher. Elle est DISCRÈTE mais pas
     invisible : un bord tout à fait nu ne s'apprend jamais. */
  body.carte-mobile #rail-lisiere {
    display: block;
    position: fixed;
    left: 0;
    top: var(--topbar-h, 44px);
    bottom: 0;
    width: 18px;
    z-index: 149;              /* sous le rail (150), au-dessus de la carte */
    background: linear-gradient(to right,
      rgba(14, 165, 233, 0.30), rgba(14, 165, 233, 0.04) 60%, transparent);
    border-right: 1px solid rgba(14, 165, 233, 0.22);
  }
  body.carte-mobile.rail-ouvert #rail-lisiere {
    background: none;
    border-right: 0;
  }
  /* ⚠️ v0.0.126 — LES ENCARTS DÉGAGENT LA LISIÈRE, parce que c'est le dépli
     de la carte qui les a amenés dessus. En rendant à `#view-map` les 48 px
     du rail escamoté, on a déplacé les panneaux de x=60 à x=12 — mais leur
     retrait de 12 px avait été calibré quand la carte commençait à 48. Or la
     lisière de réouverture occupe les 18 PREMIERS pixels : les six premiers
     pixels de la légende, du titre d'univers et de la barre « Aller à… »
     passaient donc DESSOUS, et un appui sur ce liseré rouvrait le rail au
     lieu d'agir sur le panneau. Mesuré : `elementFromPoint(14, 807)` rendait
     `#rail-lisiere`, pas le panneau.
     ⚠️ 30 px = les 18 de la lisière + les 12 de retrait maison. Les encarts
     gardent ainsi EXACTEMENT le même retrait qu'avant, simplement mesuré
     depuis ce qui occupe vraiment le bord. 24 px suffirait à supprimer le
     recouvrement, mais collerait les encarts au filet cyan.
     ⚠️ CES RÈGLES SONT SOLIDAIRES DE `body.carte-mobile #view-map { left: 0 }`
     juste au-dessus : c'est ce dégagement de la carte qui a déplacé les
     panneaux, et ce sont ces trois lignes qui les remettent d'aplomb. On ne
     retire jamais l'une sans l'autre.
     ⚠️ ET LA VUE S'ÉCRIT EN TÊTE DE SÉLECTEUR (`body.carte-mobile …`).
     L'ordre des classes ne change rien au CSS, mais il change tout à la
     RELECTURE : `test/mobile-tient-dans-lecran.test.js` vérifie que chaque
     règle de ce bloc est gardée par le format ET par la vue, et il lit la
     garde de vue en TÊTE de sélecteur. Une règle parfaitement sûre mais
     écrite dans l'autre ordre se signale donc comme une fuite — ce qui est
     exactement ce qu'on veut d'un garde-fou : qu'il pose la question. */
  body.carte-mobile .universe-overlay,
  body.carte-mobile #map-recherche,
  body.carte-mobile .map-legende { left: 30px; }
}
/* Hors vue galaxie, la lisière n'existe pas. */
#rail-lisiere { display: none; }

/* ─── 3. LES RESSOURCES SUR UNE SEULE LIGNE ─────────────────────────
   « uniquement en vue galaxie, fais en sorte que les ressources tiennent
   sur une seule ligne, quitte à ce qu'on doive la défiler avec le doigt,
   tu mets simplement l'icône et le stock. »

   ⚠️ CES RÈGLES ONT DÉMÉNAGÉ, ELLES N'ONT PAS DISPARU. La v0.0.90 a étendu
   le bandeau compact à TOUTES les vues (« LE BANDEAU DE RESSOURCES COMPACT
   SUR TOUTES LES VUES », plus bas dans ce fichier) : `body.mobile-mode
   #resbar` couvre donc désormais la vue galaxie comme les quatorze autres,
   et `carte-mobile` n'est jamais posée sans `mobile-mode`. Les règles qui
   vivaient ici pesaient exactement la même spécificité et venaient AVANT :
   elles ne pouvaient plus rien gagner. Elles sont retirées, la vue galaxie
   étant servie par le bloc unique — c'est la fusion que la consigne de
   purge de la v0.0.90 prévoyait.

   ⚠️ LE RESTE DE CE BLOC NE BOUGE PAS : le rail escamoté, la lisière de
   réouverture et la légende bornée sont propres à la vue galaxie. */

/* ─── 4. LE SEUL VRAI DÉBORDEMENT DE LA VUE VAISSEAU MÈRE ────────────
   « j'ai l'impression que le bord droit est coupé […] il faut vraiment que
   toute la version mobile tienne dans le téléphone ».
   ⚠️ MESURÉ, ET LE COUPABLE EST UNIQUE : la PAGE ne déborde pas
   (`scrollWidth === clientWidth`), et le tableau de production défile
   correctement dans sa propre boîte (`.ps-table-wrap`, déjà en
   `overflow-x: auto`). Un SEUL élément sortait vraiment de l'écran — le
   bouton « Appliquer ces réglages à… », 381 px de large, bord droit à
   437 px sur un écran de 412. C'est lui, et lui seul, qui poussait
   `#ship-main` de 25 px. */
@media (max-width: 700px) {
  body #ship-main .dsh-btn {
    max-width: 100%;
    box-sizing: border-box;
    white-space: normal;
  }
}

/* ─── 5. LE TITRE DE LA CARTE NE PASSE PLUS SOUS LE BANDEAU D'ALERTE ──
   ⚠️ CE DÉFAUT EST ANTÉRIEUR À CE LOT, et la mesure le dit : `#prod-hint`
   occupe y = 105→145 pendant que `.universe-overlay` occupe y = 97→142,
   AVEC comme SANS l'interrupteur. Il ne devient visible qu'une fois la
   carte dégagée — on le corrige donc ici, sur l'écran qu'on vient de
   rendre lisible, et jamais ailleurs.
   ⚠️ IL FAUT LA CLASSE `hint-visible`, POSÉE PAR LE JS : les deux éléments
   vivent dans des sous-arbres différents, donc aucun sélecteur CSS ne peut
   relier le titre à l'état du bandeau. Et un décalage POSÉ EN DUR laisserait
   cinquante pixels de vide chaque fois que le bandeau est absent. */
@media (max-width: 700px) {
  body.carte-mobile .universe-overlay,
  body.carte-mobile #map-recherche {
    transition: transform 0.15s ease;
  }
  /* ⚠️ LES DEUX DESCENDENT ENSEMBLE, ET LE BANDEAU « Aller à… » N'EST PAS
     DANS L'OVERLAY. Ne décaler que le titre le faisait tomber sur la barre
     de recherche (mesuré : titre 149→194 contre recherche 143→168) — on
     déplace un chevauchement au lieu de le supprimer. */
  body.carte-mobile.hint-visible .universe-overlay,
  body.carte-mobile.hint-visible #map-recherche {
    transform: translateY(52px);
  }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.85 — UN ÉCRAN ÉTROIT SUFFIT À BORNER LE PANNEAU DE PLANÈTE
   ════════════════════════════════════════════════════════════════════
   Signalement de Loryan, capture à l'appui : « je suis encore coincé par
   l'affichage de la fenêtre de planète qui me bouffe tout l'écran ».

   ⚠️ SON CAS EST REPRODUIT ET MESURÉ, et il tient à UNE condition de trop.
   Le tiroir de la v0.0.84 exige DEUX choses : `body.mobile-mode` ET son
   interrupteur. Or les deux systèmes d'affichage du jeu peuvent se
   désaccorder — un joueur qui force « desktop » sur son téléphone garde un
   écran ÉTROIT sans la classe. Mesuré à 412 px :

     mode mobile ACTIF   → panneau 259 px, ancré en bas, croix à y=527 ✅
     mode mobile INACTIF → panneau 694 px sur 779, `max-height: none`,
                           `position: absolute`, croix à y=92 — c'est-à-dire
                           DERRIÈRE la barre de ressources qui finit à y=126.

   ⚠️ LA CROIX INATTEIGNABLE EST LE VRAI DÉFAUT, pas la hauteur. Un panneau
   trop grand se subit ; un panneau qu'on ne peut plus FERMER bloque le jeu.
   C'est aussi ce qui explique la capture de Loryan, qui commence au
   sous-titre : le nom de la planète ET la croix étaient au-dessus du bord.

   ⚠️ ON NE TOUCHE PAS AUX RÈGLES DE LA v0.0.84, on en ajoute. Les modifier
   changerait l'écran des univers où elles sont DÉJÀ déployées, sans
   déploiement — leçon de l'Éolienne (v0.0.65).
   ⚠️ ET C'EST DU CSS SEUL, DÉLIBÉRÉMENT. Élargir la garde JavaScript
   `tiroirsMobileActifs()` aurait fait apparaître la POIGNÉE du tiroir sans
   les règles qui la portent — une poignée sans tiroir, c'est-à-dire un
   second défaut pour en réparer un premier. La croix suffit à fermer, et
   rouvrir se fait en retapant la planète. */
@media (max-width: 700px) {
  body #planet-panel {
    position: fixed !important;
    top: auto !important;
    bottom: 0 !important;
    left: var(--sidebar-width, 48px) !important;
    right: 0 !important;
    width: auto !important;
    max-width: none !important;
    max-height: 52vh !important;
    overflow-y: auto;
    z-index: 200;
  }
  /* ⚠️ ET SURTOUT : PAS DE `float` NI DE MARGE NÉGATIVE SUR LA CROIX ICI,
     et c'est la mesure qui l'a dit. La v0.0.84 la remonte de 38 px pour
     qu'elle ne prenne pas une ligne entière — mais le panneau est désormais
     `overflow-y: auto`, donc tout ce qui remonte AU-DESSUS de son contenu
     est ROGNÉ. Mesuré sur ma première version : `elementFromPoint` au centre
     de la croix rendait le CORPS DE LA PAGE et non la croix — elle était
     donc invisible et inerte, c'est-à-dire très exactement le défaut qu'on
     répare, déplacé de trente pixels. Elle reste COLLANTE dans le flux (son
     comportement de base) : elle demeure ainsi en tête du panneau pendant
     qu'on le fait défiler. */
  body #planet-panel .btn-close {
    position: sticky;
    top: 0;
    z-index: 4;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.88 — LA FACTURE D'ÉLECTRYTE SOUS LE BLOC DE COÛT
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : « pour les bâtiments qui
   consomment de l'Électryte, dans la fiche là où on clique sur améliorer le
   niveau, mettre la conso du niveau actuel cumulé, du prochain niveau et des
   n niveaux simulés avec les flèches ».

   ⚠️ LA LIGNE EST RENDUE PAR LE JS SOUS SA PROPRE GARDE — la règle CSS n'a
   donc rien à masquer : elle habille un élément qui n'existe pas hors
   interrupteur. Elle n'est pas préfixée pour cette raison, et cela ne peut
   rien changer sur un univers non déployé. */
.md-conso-el {
  margin: 6px 0 2px;
  padding: 6px 9px;
  border-radius: 7px;
  background: rgba(250, 204, 21, .07);
  border: 1px solid rgba(250, 204, 21, .18);
  font-size: .78rem;
  line-height: 1.45;
  color: var(--text2, #b9c6e8);
}
.md-conso-el b { color: var(--text, #e6ecff); }
.md-conso-el b.y { color: #facc15; }
.md-conso-el-ec { color: var(--text3, #2d4070); }
/* ⚠️ La note du Convertisseur passe à la ligne plutôt que d'allonger la
   première : le panneau fait ~330 px, et un `nowrap` la ferait déborder —
   le piège du bouton de secours (v0.0.53) et du tiroir des files (v0.0.82). */
.md-conso-el-note { display: block; color: var(--text3, #2d4070); font-size: .72rem; }
.md-conso-el-vide { background: rgba(148, 163, 184, .06); border-color: rgba(148, 163, 184, .16); }

/* ═══════════════════════════════════════════════════════════════════
   v0.0.88 — LE BANDEAU « MÉTÉORITE EN APPROCHE » DE LA FICHE DE PLANÈTE
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : « ajouter impact après
   météorite dans la fiche de la planète sur la vue galaxie ».

   ⚠️ LA RÈGLE `[hidden]` EST OBLIGATOIRE, et c'est la QUATRIÈME fois dans ce
   projet : une classe qui pose `display` a la MÊME spécificité que la règle
   native du navigateur, et la feuille d'auteur l'emporte — le bandeau
   resterait affiché en permanence, vide. Le badge de la cloche (v2.69), le
   bandeau de convalescence (v0.0.71) et l'alerte d'agression (v0.0.72) ont
   chacun payé cet oubli. */
.pp-meteorite {
  display: block;
  margin: .5rem 0 .2rem; padding: .55rem .7rem;
  font-size: .76rem; line-height: 1.45;
  color: #fdba74;
  background: rgba(249, 115, 22, .10);
  border: 1px solid rgba(249, 115, 22, .38);
  border-radius: 6px;
}
.pp-meteorite[hidden] { display: none; }
.pp-meteorite b { color: #fed7aa; }
/* La file d'attente derrière la première : une précision, pas une alerte. */
.pp-met-plus { display: block; margin-top: .2rem; color: var(--text3, #2d4070); font-size: .72rem; }

/* ═══════════════════════════════════════════════════════════════════
   v0.0.88 — LES BLOCS PAR VAISSEAU DES STATISTIQUES D'EXPÉDITION
   ═══════════════════════════════════════════════════════════════════
   Ces classes n'habillent que des
   éléments rendus SOUS l'interrupteur : hors déploiement, aucun n'existe
   dans le DOM, donc aucune règle ici ne peut rien changer. */
.stv-carte { margin-top: var(--space-3, 12px); }
.stv-intro .st-dim { margin: 0 0 var(--space-3, 12px); line-height: 1.5; }
.stv-tuiles {
  display: flex; flex-wrap: wrap; gap: var(--space-3, 12px);
  margin-bottom: var(--space-3, 12px);
}
.stv-t { display: flex; flex-direction: column; min-width: 7rem; }
.stv-t-v { font-size: 1.05rem; font-weight: 700; color: var(--text, #e6ecff); }
.stv-t-l { font-size: .72rem; color: var(--text3, #2d4070); }
.stv-ligne {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem .8rem;
  padding: .35rem 0; border-top: 1px solid rgba(148, 163, 184, .12);
  font-size: .78rem;
}
.stv-l { color: var(--text2, #b9c6e8); flex: 0 0 auto; }
.stv-v { display: flex; flex-wrap: wrap; gap: .5rem; }
.stv-part { color: var(--text2, #b9c6e8); }
.stv-part b { color: var(--c, #94a3b8); }
/* ⚠️ DEUX COLONNES QUI SAVENT SE RANGER EN UNE. Le bloc vit dans une vue
   qui descend jusqu'à 390 px : `minmax(0, 1fr)` — et non `minmax(280px, …)`
   — est ce qui empêche la piste de garder un plancher plus large que la
   colonne, donc ce qui empêche le débordement silencieux (leçon
   `zoomSansSuperposition`, v0.0.86). */
.stv-duo {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: var(--space-3, 12px); margin-top: var(--space-3, 12px);
}
.stv-col { min-width: 0; }
.stv-sous-titre {
  font-size: .78rem; font-weight: 600; color: var(--text2, #b9c6e8);
  margin-bottom: .4rem;
}
@media (max-width: 700px) {
  .stv-duo { grid-template-columns: 1fr; }
  .stv-ligne { flex-direction: column; align-items: flex-start; gap: .2rem; }
}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.88 — LES MINIATURES ET LE BOUTON D'ANNULATION DES FILES
   ═══════════════════════════════════════════════════════════════════
   ⚠️ CES RÈGLES N'ONT RIEN À MASQUER : le JS ne rend `<img class="…-img">`
   et `.cd-annul` que quand il y a une planche et un chantier. Aucun de ces
   éléments n'existe dans le DOM — les règles sont donc sans effet là-bas,
   et c'est ce qui permet de ne pas les préfixer. */
.qbar-slot-ico-img,
.cd-item-ico-img {
  /* ⚠️ UNE TAILLE FIXE ET `object-fit: cover`. Les planches du jeu n'ont pas
     toutes le même rapport d'image : sans les deux, une vignette large
     pousserait le nom du chantier hors de sa ligne — et le défaut ne se
     verrait qu'au caractère tronqué, jamais à la relecture (leçon des
     trente-cinq pixels du tiroir des files, v0.0.82). */
  width: 22px; height: 22px; flex: 0 0 22px;
  object-fit: cover; border-radius: 5px;
  border: 1px solid rgba(148, 163, 184, .22);
  background: rgba(15, 23, 42, .5);
  vertical-align: middle;
}
.qbar-slot-ico-img { width: 20px; height: 20px; flex-basis: 20px; }

/* Le ✕ d'annulation, en haut à droite de la carte de chantier. */
.cd-annul {
  margin-left: auto;
  flex: 0 0 auto;
  width: 22px; height: 22px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 5px; cursor: pointer;
  font-size: .8rem; line-height: 1;
  color: #fca5a5;
  background: rgba(239, 68, 68, .10);
  border: 1px solid rgba(239, 68, 68, .32);
}
.cd-annul:hover { background: rgba(239, 68, 68, .22); color: #fecaca; }
/* ⚠️ `margin-left:auto` SUR LE ✕ ET NON SUR LE NOM : l'en-tête est un flex
   qui passe à la ligne (`flex-wrap: wrap`). Poser la marge sur le nom
   pousserait les tags « en cours » / « 2ᵉ » à la ligne suivante dès que le
   nom est long — c'est-à-dire sur la moitié des bâtiments. */

/* ═══════════════════════════════════════════════════════════════════
   v0.0.88 — LA MEILLEURE FENÊTRE DANS LA FENÊTRE DU PRÉCURSEUR
   ═══════════════════════════════════════════════════════════════════
   Le bloc n'est rendu que sous
   l'interrupteur : ces règles habillent un élément qui n'existe pas
   ailleurs, elles ne peuvent donc rien changer sur un univers non déployé. */
.exped2-fenetre > div {
  line-height: 1.5;
}
.exped2-gain { color: #4ade80; }
/* ⚠️ LA NOTE PASSE À LA LIGNE. La fenêtre du Précurseur est étroite et
   descend jusqu'au format téléphone : collée en bout de phrase, cette
   précision poussait la ligne hors du cadre — et un texte tronqué est le
   seul indice visible d'une largeur trop juste (leçon des trente-cinq
   pixels du tiroir des files, v0.0.82). */
.exped2-fen-note {
  display: block; margin-top: .25rem;
  font-size: .72rem; color: var(--text3, #2d4070);
}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.89 — LE FOND D'ÉCRAN, CHOISI DEPUIS « MON COMPTE »
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : « je voudrais ajouter un fond […] sélectionnable
   via MON COMPTE ». Quinze images de l'espace profond, posées SOUS
   l'interface — partout où elle laisse voir le vide.

   ⚠️ LA CLASSE `dsh-fond` EST LA CONDITION, et elle dit que l'univers
   connaît la mécanique, `dsh-fond` qu'un fond est effectivement choisi.
   Sans la première, un univers non déployé se repeindrait au premier
   joueur qui aurait choisi un décor ailleurs ; sans la seconde, la règle
   s'appliquerait à un `--fond-ecran` absent — et une déclaration invalide
   à l'exécution ne se plaint pas, elle se contente de ne rien faire.

   ⚠️ ON N'ÉCRIT QUE `background-image`, JAMAIS LA PROPRIÉTÉ RACCOURCIE.
   `body { background: var(--bg) }` (l. 661) pose la couleur de fond du
   jeu ; l'écraser d'un raccourci la remettrait à `transparent`, et le
   temps que l'image se télécharge — ou si elle échoue — l'écran serait
   BLANC. Le voile est donc un dégradé plat EMPILÉ par-dessus l'image,
   au-dessus de la couleur qui, elle, ne bouge pas.

   ⚠️ LE VOILE EST UNE VARIABLE, ET IL VIENT DU SERVEUR. La luminosité des
   quinze images va de 5 à 49 sur 255 : un voile unique rendrait soit les
   sombres invisibles, soit le texte des vues illisible sur les claires.
   La mesure vit dans server/game/fonds-ecran.js, un seul endroit.

   ⚠️ PAS DE `background-attachment: fixed`, ET C'EST DÉLIBÉRÉ. Le corps ne
   défile pas (`overflow:hidden`, l. 661) et les vues sont en
   `position:absolute; inset:0` : la boîte du corps fait donc exactement
   l'écran, et `scroll` — le défaut — rend le même résultat au pixel près.
   `fixed` n'apporterait rien ici, et il est notoirement fantasque sur les
   navigateurs mobiles, où il force en prime un repaint à chaque geste.

   ⚠️ LA CARTE N'EST PAS CONCERNÉE : son canevas peint son propre fond
   opaque à chaque image (`ctx.fillRect` sur toute sa surface, map.js). Ce
   n'est pas un oubli — la rendre translucide poserait la grille, les halos
   et les points de systèmes sur une nébuleuse, sur l'écran le plus lu du
   jeu et le plus fragile en lisibilité. */
body.dsh-fond {
  background-image:
    linear-gradient(rgba(5, 8, 16, var(--fond-voile, .5)),
                    rgba(5, 8, 16, var(--fond-voile, .5))),
    var(--fond-ecran);
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
}

/* ─── Le sélecteur ────────────────────────────────────────────────
   ⚠️ CES RÈGLES NE PORTENT PAS DE PRÉFIXE D'INTERRUPTEUR, et c'est
   volontaire : elles habillent des éléments qui n'existent que dans cette
   fenêtre, et la fenêtre ne s'ouvre que depuis une entrée de menu qui,
   ELLE, est gardée. Les préfixer ferait une barrière de plus à retirer le
   jour de la purge, pour zéro pixel de différence ailleurs (même
   raisonnement que la fenêtre du Précurseur, v0.0.88). */

/* ⚠️ LE VOILE DE LA FENÊTRE EST PLUS CLAIR QUE CELUI DES AUTRES MODALES
   (0,55 contre 0,85) : on choisit un décor, il faut donc le VOIR se poser
   derrière. À 0,85 le clic n'aurait pour seul retour que la vignette
   elle-même — c'est-à-dire l'image qu'on regardait déjà avant de cliquer. */
.fond-backdrop { background: rgba(8, 12, 20, .55); }
.fond-modale { max-width: 820px; }
.fond-note {
  margin: 0 0 14px;
  font-size: .78rem;
  line-height: 1.5;
  color: var(--text3);
}
.fond-erreur {
  margin: 0 0 12px;
  padding: 8px 11px;
  background: rgba(233, 69, 96, .12);
  border: 1px solid rgba(233, 69, 96, .4);
  border-radius: 7px;
  color: #fecdd3;
  font-size: .79rem;
  line-height: 1.45;
}
.fond-erreur[hidden] { display: none; }
.fond-attente { font-size: .8rem; color: var(--text3); }

/* ⚠️ `minmax(0, 1fr)` ET NON `1fr` : une piste de grille a une taille
   minimale automatique, et une vignette large la ferait déborder au lieu
   de la rétrécir — le tableau sortirait de la fenêtre au téléphone sans
   qu'aucune barre de défilement ne le signale. */
.fond-grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 10px;
}
.fond-tuile {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  padding: 6px;
  background: rgba(0, 0, 0, .25);
  border: 1px solid var(--border2, #2d4a6e);
  border-radius: 9px;
  color: var(--text2);
  font: inherit;
  font-size: .74rem;
  text-align: left;
  cursor: pointer;
  position: relative;
  transition: border-color .14s, transform .14s;
}
.fond-tuile:hover { border-color: var(--accent2, #38bdf8); transform: translateY(-2px); }
.fond-tuile:focus-visible { outline: 2px solid var(--accent2, #38bdf8); outline-offset: 2px; }
.fond-vignette {
  display: block;
  width: 100%;
  aspect-ratio: 320 / 234;
  object-fit: cover;
  border-radius: 6px;
  /* Le temps du téléchargement, la case reste à la bonne taille et à la
     bonne couleur : sans ça la grille se réorganiserait sous le doigt. */
  background: #05080f;
}
.fond-nom {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* La case « Aucun » n'a pas d'image : elle prend la même hauteur que ses
   voisines pour ne pas casser la grille, et le trait tireté dit « rien »
   sans avoir à l'écrire deux fois. */
.fond-tuile-aucun .fond-vide {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 320 / 234;
  border: 1px dashed var(--border2, #2d4a6e);
  border-radius: 6px;
  background: #05080f;
  color: var(--text3);
  font-size: 1.5rem;
}
.fond-tuile.est-choisi {
  border-color: var(--accent2, #38bdf8);
  box-shadow: 0 0 0 1px var(--accent2, #38bdf8) inset;
  color: #e0f2fe;
}
.fond-tuile.est-choisi::after {
  content: '✓';
  position: absolute;
  top: 10px; right: 10px;
  width: 20px; height: 20px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--accent2, #38bdf8);
  color: #04121e;
  font-size: .7rem;
  font-weight: 700;
}
/* Le temps de l'aller-retour serveur. Le bouton reste EN PLACE et garde sa
   taille : le remplacer par un texte ferait sauter la grille entière pour
   une fraction de seconde. */
.fond-tuile.est-occupe { opacity: .55; cursor: progress; }

/* ═══════════════════════════════════════════════════════════════════
   v0.0.89 — L'INTERFACE LAISSE VOIR LE FOND
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan, capture à l'appui : « la barre du haut du VM tu peux
   la mettre en transparent — la barre de gauche aussi — le fond du VM
   pareil — idem le panneau de VM à droite ».

   ⚠️ LA CLASSE `dsh-fond` DÉCIDE, ET ELLE SEULE. Cette transparence
   dit que l'univers a l'interrupteur ; `dsh-fond` dit qu'un fond d'écran est
   effectivement posé. Sans la seconde, on rendrait quatre panneaux
   translucides sur… le fond noir du jeu : aucun changement visible, mais
   quatre surfaces qui laissent passer ce qui traîne dessous, et une facture
   de composition pour rien. On ne perce un mur que s'il y a quelque chose
   derrière.

   ⚠️ LE FOND EST DÉJÀ VOILÉ QUAND IL ARRIVE ICI (voir le bloc « FOND
   D'ÉCRAN » plus haut : chaque image porte son propre voile, mesuré, qui
   ramène les quinze entre 4 et 14 sur 255 de luminosité). Ces opacités
   s'ajoutent donc à un fond DÉJÀ sombre — c'est ce qui autorise à descendre
   aussi bas sans que le texte des panneaux souffre. Mesuré au pire : sur la
   zone la plus claire de l'image la plus claire, le texte secondaire reste
   au-dessus de 4:1.

   ⚠️ PAS DE `backdrop-filter: blur()`, ET C'EST UN CHOIX. Le verre dépoli
   est joli et il protège la lecture — mais il coûte une recomposition de
   toute la zone À CHAQUE IMAGE, sur quatre surfaces dont deux couvrent la
   moitié de l'écran, et le jeu se redessine plusieurs fois par seconde. Les
   fonds étant déjà voilés et lissés, il n'apporterait ici que la facture.

   ⚠️ UNE SEULE VALEUR À RÉGLER, dans une variable. Quatre opacités écrites
   à la main dans quatre règles finiraient par diverger au premier
   ajustement, et rien ne le signalerait — on verrait seulement qu'« un
   panneau est plus sombre que les autres » sans savoir lequel a bougé. */
body.dsh-fond {
  /* Les trois surfaces de l'INTERFACE : barre d'ancrage, rail des modules,
     panneau des Vaisseaux Mères. Elles portent du texte serré. */
  --voile-panneau: .35;
  /* La CARTE du plan du vaisseau : presque vide, et son contenu est un
     dessin au trait qui se lit sur n'importe quoi. Elle peut donc s'ouvrir
     davantage — c'est là que le fond se voit le mieux. */
  --voile-carte: .22;
}
/* ⚠️ CHAQUE RÈGLE DOIT REPASSER DEVANT UN SÉLECTEUR À IDENTIFIANT
   (`#ship-nav{background:…}`, l. 1087 et ses voisines). Deux classes plus
   un élément n'y suffiraient pas : on garde donc l'identifiant ET on ajoute
   les deux classes du corps devant. */
body.dsh-fond #anchor-bar {
  background: rgba(10, 15, 30, var(--voile-panneau));
}
body.dsh-fond #ship-nav {
  background: rgba(5, 8, 16, var(--voile-panneau));
}
body.dsh-fond #vm-panel-right {
  background: rgba(5, 8, 16, var(--voile-panneau));
}
/* ⚠️ LE HALO BLEU DU PLAN EST CONSERVÉ (le `radial-gradient`) : c'est lui
   qui donne au vaisseau sa lumière d'ambiance, et le retirer en même temps
   qu'on ouvre la carte l'aplatirait complètement. Seule la couche OPAQUE du
   dessous (`var(--card)`) devient translucide. */
body.dsh-fond #ship-blueprint {
  background:
    radial-gradient(120% 80% at 50% 30%, rgba(14,165,233,.06), transparent 70%),
    rgba(13, 21, 38, var(--voile-carte));
}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.89 — LES TEXTES QUE PERSONNE NE VOYAIT
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : « recolore tous les textes en gris que personne ne
   voit, mets-les en bleu ou en blanc, bref en visible ».

   ⚠️ CE N'EST PAS « DES » GRIS, C'EN EST UN SEUL. Mesuré au navigateur sur
   huit écrans : les 135 textes illisibles portent TOUS la même couleur,
   `--text3` (#2d4070), à **1,8:1 de contraste** sur le fond des panneaux —
   là où la norme d'accessibilité en demande 4,5. C'est donc UN token à
   corriger, pas cent règles à retoucher une par une ; et un token corrigé
   couvre du même coup les écrans où personne n'est encore allé regarder.

   ⚠️ LES DEUX NIVEAUX MONTENT ENSEMBLE, et il le faut. Porter `--text3` au
   niveau de `--text2` sans toucher à ce dernier donnerait DEUX TOKENS DE LA
   MÊME COULEUR : 480 textes du même gris, et la hiérarchie — principal,
   secondaire, tertiaire — disparaîtrait en silence. Chaque niveau monte
   donc d'un cran :
     --text  #c8d6f0  12,4:1  (inchangé — le texte principal allait bien)
     --text2 #8aa4c8   7,1:1  (était 5,1)
     --text3 #6b8ab0   5,1:1  (était 1,8)
   ▸ Le nouveau niveau 3 est TRÈS EXACTEMENT l'ancien niveau 2 : une valeur
     que le jeu affiche depuis toujours, sur 301 textes, et que personne n'a
     jamais signalée. C'est ce qui rend le choix sûr.

   ⚠️ `--text3` NE SERT PAS QU'AU TEXTE — il porte aussi neuf traits,
   bordures et remplissages (une jauge de file, le repli de couleur d'un
   Vaisseau Mère, deux filets, une bordure au survol, l'étiquette d'un
   module vide sur le plan). Ils s'éclaircissent avec lui, et c'est très
   bien : c'était le même défaut, à un autre endroit. Vérifié au navigateur,
   aucun ne devient criard.

   ⚠️ LES TROIS VALEURS DE TOKEN VIVENT DANS LES DEUX DÉCLARATIONS `:root`
   (l. 68-76 pour les `--text*`, l. 282-290 pour les `--color-text-*`),
   chacune dans SA famille : c'est là que le reste du jeu va les lire.

   ⚠️ TROIS ENDROITS AJOUTENT DE LA TRANSPARENCE PAR-DESSUS, et le token
   seul ne les sauve pas : une opacité s'applique APRÈS la couleur. Ils sont
   donc repris nommément ci-dessous, et eux seuls — c'est la mesure qui les
   a désignés, pas une intuition.

   ⚠️ ET LE `body ` DEVANT CHACUN N'EST PAS DÉCORATIF : `.btn-up:disabled`
   porte une classe et une pseudo-classe, la règle d'origine (l. 1302) aussi
   — à égalité, c'est l'ORDRE SOURCE qui tranche, et ce bloc vit après. Le
   `body` rend le point que la classe d'interrupteur portait avant la purge
   et met le classement à l'abri d'un déplacement de bloc. */
/* La flèche d'appel du bandeau de conseil (« CONNECTEURS → ») : c'est le
   GESTE que le bandeau demande, et il était à demi effacé (3,8:1). Sa
   couleur cyan suffit à le distinguer du texte. */
body .ph-arrow { opacity: 1; }
/* « inchangé » dans le tableau comparatif des quatre classes : mesuré à
   1,47:1, le pire de tout le jeu. La couleur suffit à dire « neutre », elle
   n'a pas besoin d'être à demi effacée en plus. */
body .cls-neutre { opacity: 1; }
/* Un bouton indisponible ANNONCE CE QUI MANQUE (« 🧩 Module Extracteurs
   niv. 1 requis ») : c'est la seule chose à lire, et elle était à 2:1. Il
   reste plus terne que les autres — c'est le rôle de sa couleur — mais on
   peut enfin le lire. */
body .btn-up:disabled { opacity: .85; }

/* ═══════════════════════════════════════════════════════════════════
   v0.0.89 — LE COIN HAUT-DROIT N'EST PAS UNE FENÊTRE SUR LE FOND
   ═══════════════════════════════════════════════════════════════════
   Signalement de Loryan, capture entourée en rouge : « ne mets pas de fond
   d'écran sur ce carré rouge ».

   ⚠️ CE COIN N'APPARTIENT À PERSONNE, ET C'EST VOULU DEPUIS LA v2.9.11.
   Mesuré : `document.elementFromPoint()` y rend le CORPS lui-même. Les deux
   bandeaux du haut s'arrêtent à `--vm-panel-w` du bord droit
   (`#topbar, #resbar, #prod-hint { right: var(--vm-panel-w) !important }`,
   l. 1673) et le panneau des Vaisseaux Mères, lui, commence SOUS le header.
   Il reste donc un rectangle de 180 × ~145 px que rien ne couvre — la
   « zone vide au-dessus du panneau Vaisseaux Mères » que le bandeau
   d'alerte occupe quand il y a une alerte, et qui est nue le reste du
   temps.
   ▸ Tant que le fond du jeu était noir, personne ne pouvait le voir : ce
     rectangle avait exactement la couleur de ses voisins. Le fond d'écran
     l'a révélé d'un coup — c'est le seul endroit de la barre du haut où
     l'image passe à 100 %, entre deux bandeaux qui n'en laissent passer
     que 5 %.

   ⚠️ ON BOUCHE LE TROU, ON NE DÉPLACE PAS LES BANDEAUX. Étendre `#topbar`
   et `#resbar` jusqu'au bord serait le geste évident et c'est le mauvais :
   leur contenu défile horizontalement (`overflow-x: auto`) et passerait
   alors SOUS le panneau des Vaisseaux Mères — sauf qu'ils sont au-dessus de
   lui (z-index 200 et 190 contre 150), donc l'heure et la pastille de
   version se poseraient PAR-DESSUS son en-tête. Et la place gagnée
   changerait le retour à la ligne des encarts de ressources, donc la
   hauteur du header, donc la position de tout ce qui commence dessous.

   ⚠️ LA MÊME VARIABLE QUE LE PANNEAU, PAS UN NOMBRE. La hauteur du bouchon
   est `--total-header-h` — très exactement ce qui positionne le haut du
   panneau (l. 9183). Un nombre écrit à la main laisserait un liseré de fond
   d'écran, ou une bande opaque par-dessus le panneau devenu translucide,
   dès que la barre de ressources passe à la ligne — c'est-à-dire à chaque
   changement de largeur de fenêtre.

   ⚠️ IL PREND LA COULEUR DE LA BARRE DE RESSOURCES, PAS UN NOIR PLEIN. À
   95 % comme elle, le coin laisse passer les mêmes 5 % d'image que ses
   voisins : il devient indiscernable du bandeau. Un noir opaque ferait
   apparaître une couture verticale sur les fonds clairs — on aurait
   remplacé une fenêtre par une tache.

   ⚠️ AUCUN INTERRUPTEUR NEUF, ET C'EST LA RÈGLE DU PROJET. La règle ne
   s'applique que sous `dsh-fond` : elle ne peut rien
   changer là où le fond d'écran n'est pas déployé, ni pour un joueur qui
   n'en a pas choisi. C'est le raisonnement de la fenêtre du bouton Mk
   (v0.0.70) : un correctif dont tout le chemin vit déjà derrière une
   barrière n'en demande pas une seconde — et une seconde barrière rendrait
   possible l'état « fond déployé, correctif non déployé », c'est-à-dire le
   défaut lui-même. */
body.dsh-fond:not(.mobile-mode)::before {
  content: '';
  position: fixed;
  top: 0;
  right: 0;
  width: var(--vm-panel-w, 180px);
  height: var(--total-header-h, 88px);
  background: rgba(5, 8, 16, .95);
  /* Au-dessus du fond du corps, sous tout le reste : les deux bandeaux
     (190, 200), le bandeau d'alerte (210) et le panneau (150) portent tous
     un z-index explicite et passent donc devant. */
  z-index: 1;
  /* ⚠️ PUREMENT DÉCORATIF : sans ça, il intercepterait les clics destinés
     au bandeau d'alerte qui vit dans ce coin. */
  pointer-events: none;
}
/* En mobile, le coin n'existe pas : les deux bandeaux vont jusqu'au bord
   (`right: 0 !important`, l. 1697 et 4592) et le panneau devient un tiroir.
   Le bouchon y serait un rectangle noir posé sur la barre de ressources.
   ⚠️ LES DEUX SYSTÈMES D'AFFICHAGE MOBILE COHABITENT : la classe
   `body.mobile-mode` est traitée dans le sélecteur ci-dessus, la largeur
   réelle l'est ici. Il faut les deux — un joueur qui force « desktop » sur
   son téléphone garde un écran étroit sans la classe. */
@media (max-width: 700px) {
  body.dsh-fond::before { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.90 — LE BANDEAU DE RESSOURCES COMPACT SUR TOUTES LES VUES
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : « j'aimerais que le bandeau des ressources de la vue
   galaxie soit mis de la même manière sur les autres vues comme celle du
   vaisseau mère, de la rampe, des recherches, du commandement etc etc pour
   gagner en lisibilité ».

   MESURÉ au téléphone (390 × 844, compte à neuf ressources) : le bandeau
   fait 32 px en vue galaxie et 145 px partout ailleurs — 4 % de l'écran
   contre 17 %. Ce lot rend les 113 px aux quatorze autres vues.

   ⚠️ ON NE RÉUTILISE SURTOUT PAS `body.carte-mobile`. La même classe
   escamote AUSSI le rail de navigation et arme la lisière de réouverture
   (v0.0.85) : la poser hors de la carte retirerait la navigation des autres
   vues. C'est une classe « je suis sur la carte », pas « le bandeau est
   compact » — et elle est posée par un battement de 400 ms qui vérifie
   justement que #view-map est active.

   ⚠️ CE BLOC NE GAGNE PLUS QUE PAR L'ORDRE SOURCE, ET IL FAUT LE SAVOIR.
   `body.mobile-mode #resbar` pèse exactement autant que la grille de
   `style.css:17321` : depuis la purge de sa classe d'interrupteur, il ne
   l'emporte QUE parce qu'il est écrit plus bas dans le fichier. **Déplacer
   ce bloc plus haut casserait le bandeau compact sans la moindre erreur.**
   C'est la situation nommée en v0.0.87 sur le tiroir des files.

   ⚠️ ET C'EST LUI QUI SERT AUSSI LA VUE GALAXIE depuis la purge : les
   règles jumelles de la v0.0.85 (`body.carte-mobile #resbar`) ont été
   retirées, `carte-mobile` n'étant jamais posée sans `mobile-mode`. */
@media (max-width: 700px) {
  body.mobile-mode #resbar {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    gap: 0.25rem;
    padding: 0.2rem 0.3rem;
  }
  body.mobile-mode #resbar .res {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.45rem;
    min-width: 0;
  }
  /* Il ne reste que l'icône et le stock — le détail complet (débit, plafond,
     remplissage) reste à UN TAP : chaque tuile ouvre le panneau de production
     depuis la v0.0.83. */
  body.mobile-mode #resbar .res-name,
  body.mobile-mode #resbar .res-rate,
  body.mobile-mode #resbar .res-right,
  body.mobile-mode #resbar .res-bar-bg {
    display: none;
  }
  body.mobile-mode #resbar .res-val {
    font-size: 0.72rem;
    white-space: nowrap;
  }
  body.mobile-mode #resbar .res-ico,
  body.mobile-mode #resbar .res-ico-img {
    width: 18px;
    height: 18px;
  }
  /* ⚠️ LE CADRE DU CODEX ET DU MANUEL EST LE SEUL À NE PAS SUIVRE : sa
     hauteur est calibrée en dur sur l'en-tête d'avant (100vh − 260px). Le
     bandeau rétrécissant de 113 px, il resterait court d'autant — une bande
     vide en bas de la page. Il se recalcule donc sur la variable que
     layout.js republie déjà à chaque changement de hauteur. */
  body.mobile-mode .cdx-cadre {
    height: calc(100vh - var(--total-header-h, 96px) - 72px);
  }
}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.119 — LE BANDEAU COMPACT SE DÉPLIE AU DOIGT
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : « le bandeau de ressource ne mentionne plus que la
   ressource — je veux que ça reste comme ça, mais si je glisse le doigt vers
   le BAS, ça le déplie pour afficher la soute, la production par heure et le
   délai avant remplissage ; vers le HAUT, ça le replie ».

   ⚠️ CE BLOC DOIT RESTER APRÈS CELUI DE LA v0.0.90, et c'est la même mise en
   garde qu'il porte lui-même : à spécificité égale, c'est l'ORDRE SOURCE qui
   tranche. Ici on a une classe de plus (`resbar-deplie`), donc on gagne de
   toute façon — mais le voisinage garde les deux états lisibles côte à côte,
   et c'est ce qui évite qu'on « répare » l'un en cassant l'autre.

   ⚠️ LE COMPACT RESTE LE DÉFAUT. Le dépli est une VISITE : on l'ouvre pour
   lire un délai, on le referme. La v0.0.90 a rendu 113 px sur 844 aux
   quatorze vues du jeu ; les redonner par défaut annulerait ce gain.

   ⚠️ ET IL PASSE EN DEUX COLONNES, PAS EN UNE LIGNE QUI DÉFILE. Déplié, les
   neuf tuiles portent quatre lignes chacune : les laisser sur une seule
   rangée obligerait à faire glisser le bandeau du doigt pour lire la neuvième,
   c'est-à-dire à chercher une information qu'on vient justement d'ouvrir.
   MESURÉ à 390 px : deux colonnes de 9 tuiles tiennent en 5 rangées. */
@media (max-width: 700px) {
  body.mobile-mode.resbar-deplie #resbar {
    flex-wrap: wrap;
    overflow-x: hidden;
    row-gap: 0.25rem;
    padding-bottom: 0.45rem;
  }
  /* ⚠️ v0.0.126 — LE PLAFOND MANQUAIT, ET C'EST LA NEUVIÈME TUILE QUI LE
     DISAIT. Neuf ressources sur deux colonnes laissent forcément une
     orpheline sur la dernière rangée : `flex-grow: 1` la faisait alors
     gonfler à 381,6 px, soit DEUX FOIS ses voisines, avec son texte toujours
     tassé à gauche et 247 px de vide à droite. La rangée du bas ne
     ressemblait à aucune des quatre au-dessus.
     ⚠️ ON PLAFONNE, ON NE RETIRE PAS LE `grow` : c'est lui qui fait que les
     rangées appariées remplissent la largeur au pixel près (il absorbe les
     2,6 px de reste). C'est la borne haute qui manquait.
     ⚠️ ET 0.125rem N'EST PAS 0.2rem : l'écart entre tuiles vaut 0.25rem, donc
     sa MOITIÉ vaut 0.125rem. Reprendre le 0.2rem du flex-basis donnerait
     188 px là où les voisines font 189,05 — un décalage d'un pixel sur une
     rangée, c'est-à-dire exactement ce qu'on vient de réparer. */
  body.mobile-mode.resbar-deplie #resbar .res {
    flex: 1 1 calc(50% - 0.2rem);
    min-width: 0;
    max-width: calc(50% - 0.125rem);
    align-items: flex-start;
    padding: 0.28rem 0.4rem 0.36rem;
  }
  /* ⚠️ v0.0.126 — ET RIEN NE CONSOMMAIT LA PLACE RESTANTE. Le dépli remplace
     le `flex: 1 1 100%` de `.res-right` (qui lui donnait une DEUXIÈME LIGNE
     pleine largeur) par `flex: 0 0 auto`, pour la ranger en colonne à
     droite. Mais le bloc central n'a, lui, aucun `flex-grow` : les trois
     colonnes se tassaient donc à gauche et le texte s'arrêtait à mi-tuile —
     54 à 66 % de remplissage, contre 96-97 % en compact et 98-103 % en grand
     écran. La moitié droite était un vide noir.
     ⚠️ NE PAS RENDRE À `.res-right` SA LIGNE ENTIÈRE pour autant : ça
     casserait la mise en page à quatre lignes sur deux colonnes que le dépli
     cherche justement à obtenir. C'est au bloc central d'absorber le reste. */
  body.mobile-mode.resbar-deplie #resbar .res > div:not(.res-right):not(.res-bar-bg) {
    flex: 1 1 auto;
    min-width: 0;
  }
  /* Les quatre lignes que le compact avait retirées reviennent. */
  /* ⚠️ `.res-eta` EST DANS LA LISTE, ET IL LE FAUT : c'est le DÉLAI AVANT
     REMPLISSAGE, nommément demandé. Il est masqué depuis la v0.0.84 par une
     règle à part (`body.mobile-mode #resbar .res-eta`, plus haut dans ce
     fichier) et non par le bloc compact de la v0.0.90 : ne remettre que les
     quatre autres lignes aurait rendu le dépli à moitié — la soute et le
     débit reviennent, le délai reste absent, et rien ne le signale.
     ⚠️ `.res-eta:empty` reste masqué par sa propre règle : une ressource sans
     échéance ne laisse pas de ligne vide. */
  body.mobile-mode.resbar-deplie #resbar .res-name,
  body.mobile-mode.resbar-deplie #resbar .res-rate,
  body.mobile-mode.resbar-deplie #resbar .res-eta,
  body.mobile-mode.resbar-deplie #resbar .res-bar-bg {
    display: block;
  }
  body.mobile-mode.resbar-deplie #resbar .res-right {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    align-items: flex-end;
    gap: 0;
  }
  body.mobile-mode.resbar-deplie #resbar .res-name { font-size: 0.5rem; }
  body.mobile-mode.resbar-deplie #resbar .res-rate { font-size: 0.52rem; }
  body.mobile-mode.resbar-deplie #resbar .res-cap,
  body.mobile-mode.resbar-deplie #resbar .res-eta { font-size: 0.5rem; }
}

/* ─── La poignée : ce qui dit qu'il y a quelque chose à tirer ─────────
   ⚠️ UN GESTE NU NE S'APPREND JAMAIS — c'est la leçon de la lisière du rail
   (v0.0.85). Elle est DISCRÈTE mais pas invisible, et elle est cliquable :
   un joueur qui ne devine pas le glissement n'est pas coincé pour autant.

   ⚠️ ELLE SE POSE SUR LE BAS DU BANDEAU, mesuré (`--resbar-bottom`, publié
   par core/layout.js) et jamais écrit en dur : la hauteur du bandeau change
   quand il se déplie, et c'est précisément ce qu'elle commande. Un nombre
   figé la laisserait flotter en plein milieu de l'écran une fois sur deux.

   ⚠️ ET SEULEMENT EN MODE TÉLÉPHONE : sur grand écran le bandeau montre déjà
   tout, il n'y a rien à déplier. Le JS ne la crée d'ailleurs pas ailleurs —
   cette règle est la seconde barrière, pas la première. */
#resbar-poignee { display: none; }
@media (max-width: 700px) {
  body.mobile-mode #resbar-poignee {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: calc(var(--resbar-bottom, 76px) - 1px);
    left: 50%;
    transform: translateX(-50%);
    width: 52px;
    height: 15px;
    padding: 0;
    z-index: 191;             /* juste au-dessus du bandeau (190) */
    border: 1px solid rgba(14, 165, 233, 0.28);
    border-top: 0;
    border-radius: 0 0 8px 8px;
    background: rgba(5, 8, 16, 0.95);
    color: #38bdf8;
    font-size: 0.62rem;
    line-height: 1;
    cursor: pointer;
    /* La zone tactile déborde le dessin : 15 px de haut se visent mal au
       pouce, et le geste part alors sur la vue du dessous. */
    box-shadow: 0 6px 0 -3px transparent;
  }
  /* ⚠️ v0.0.126 — LA ZONE TACTILE NE DESCEND PLUS SOUS L'EN-TÊTE. Elle
     débordait de 12 px vers le BAS, c'est-à-dire par-dessus le haut de la
     vue : mesuré au navigateur, une bande INVISIBLE de 78 × 16 px sous
     l'en-tête interceptait les touchers. Sur la carte, un astre visé là
     n'était jamais sélectionné — le doigt ouvrait le bandeau à la place, et
     rien ne pouvait le faire deviner puisque le dessin, lui, ne descend pas
     si bas. C'est le troisième défaut de la liste de Loryan : « le jeu a
     l'air de fonctionner, et le geste ne part jamais ».
     ⚠️ ET LA HAUTEUR PERDUE SE RATTRAPE SUR LES CÔTÉS, PAS VERS LE HAUT.
     Le haut n'est pas libre : la zone mord déjà 7 px du bandeau et 3 px des
     tuiles, qui ouvrent leur panneau d'explication au toucher (v0.0.83).
     Élargir, en revanche, ne coûte rien — on reste dans la bande de
     l'en-tête. La cible passe de 78 × 33 (dont 16 volés) à 100 × 21, dont
     zéro volé. */
  body.mobile-mode #resbar-poignee::after {
    content: '';
    position: absolute;
    left: -24px; right: -24px; top: -6px; bottom: 0;
  }
  body.mobile-mode #resbar-poignee.est-ouvert {
    border-color: rgba(14, 165, 233, 0.45);
  }
}

/* ─── v0.0.90 : le bandeau d'alerte suit l'en-tête qui a rétréci ──────
   ⚠️ C'EST LE SEUL ÉLÉMENT DE LA CHAÎNE D'EN-TÊTE ÉCRIT EN DUR, et il ne
   pouvait pas suivre : `body.mobile-mode #prod-hint { top: 105px !important }`
   (style.css:4669) était calibré sur un bandeau de ressources de 145 px.
   Le compact le ramenant à 32 px, l'en-tête tombe de 197 px à 85 px — et
   les 105 px du bandeau d'alerte atterrissent alors SUR LE RAIL DE
   NAVIGATION, dont ils avalent deux boutons.

   MESURÉ au téléphone : sans cette règle, `elementFromPoint` au milieu du
   rail rend `prod-hint` à 40 px et 60 px du haut du rail — les entrées
   Vaisseau Mère et voisines deviennent inatteignables. Avec, le rail
   répond sur toute sa hauteur.

   ⚠️ IL S'ÉCARTE AUSSI DU RAIL PAR LA GAUCHE (`left: 56px`) : le rail fait
   48 px et vit en `position: fixed` à x = 0. Ne corriger que le `top`
   laisserait le bandeau lui passer dessus dès qu'il s'allonge sur deux
   lignes. On ne recouvre jamais la navigation. */
@media (max-width: 700px) {
  body.mobile-mode #prod-hint {
    top: calc(var(--total-header-h, 96px) + 6px) !important;
    left: 56px;
    right: 8px;
  }
}

/* ─── v0.0.90 : le contenu descend sous le bandeau d'alerte ───────────
   ⚠️ SANS CETTE RÈGLE, LE COMPACT INTRODUIT UN CHEVAUCHEMENT. `#prod-hint`
   est en position FIXE : il ne pousse rien. Tant que l'en-tête faisait
   197 px, il vivait dans la bande libre au-dessus du contenu. À 85 px, il se
   pose SUR la première ligne de la vue — mesuré sur le Vaisseau Mère, la
   ligne « ANCRAGE · SOLEIL · STATUT » passait dessous.

   ⚠️ LA HAUTEUR VIENT DU JS (`--prodhint-h`), elle n'est pas écrite en dur :
   le bandeau tient sur une ou deux lignes selon le message. Un décalage figé
   laisserait un trou dans un cas et garderait le chevauchement dans l'autre.
   Et le repli à 0 px est ce qui rend la règle inerte quand le bandeau est
   absent — c'est-à-dire la plupart du temps. */
@media (max-width: 700px) {
  body.mobile-mode.hint-visible #view-ship,
  body.mobile-mode.hint-visible #view-research,
  body.mobile-mode.hint-visible .dsh-view {
    padding-top: calc(var(--total-header-h, 96px) + 12px + var(--prodhint-h, 0px)) !important;
  }
}
/* ⚠️ INTERRUPTEUR railSousBandeau — Loryan, capture au téléphone : « le
   message d'énergie chevauche la vignette de galaxie, il faudrait décaler
   vers le bas ». La règle d'au-dessus descend le CONTENU sous le bandeau,
   mais pas le rail des vues (`#dsh-tabs-main`, fixé à `--resbar-bottom`) :
   son premier onglet, la carte, restait dessous (mesuré à 390 × 844 : le
   bandeau occupe y = 77 → 117, l'onglet y = 85 → 125). Même hauteur mesurée
   par le JS, même repli à 0 px quand le bandeau est absent. */
@media (max-width: 700px) {
  body.f-railSousBandeau.mobile-mode.hint-visible #dsh-tabs-main {
    top: calc(var(--resbar-bottom, 88px) + var(--prodhint-h, 0px));
  }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.91 — LA VIGNETTE PREND TOUTE LA CASE
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : « je veux que l'image de l'item monté prenne toute
   la case, et modifie toutes les vues fil de construction en ce sens ».
   Les TROIS écrans du fil sont couverts — la barre du bas (`.qbar-slot`),
   la section « Fil de construction » et la fenêtre « Chantier détaillé »
   (`.cd-item`, rendues par la même fonction).

   ⚠️ L'IMAGE EST UN CALQUE `::before`, PAS UN `background` DE LA CASE.
   Les deux cases portent déjà un fond et une bordure d'état (en cours /
   en attente / accélérée) : un `background-image` posé dessus effacerait
   cette couleur, et on ne saurait plus lequel des dix chantiers tourne.
   Le calque se glisse ENTRE le fond d'état et le texte.

   ⚠️ `z-index` SUR LES ENFANTS, SINON LE TEXTE PASSE DESSOUS. Le calque
   est en position absolue : sans `position: relative` + `z-index: 1` sur
   les lignes de texte, il les recouvrirait — la case deviendrait une
   image muette, ce qui est pire que la pastille d'avant.

   ⚠️ ET LE VOILE N'EST PAS DÉCORATIF : une planche claire sous un texte
   clair ne se lit plus. Le dégradé descend vers le bas de la case, là où
   vivent le nom, la barre et la durée. */
body .qbar-slot.a-vignette,
body .cd-item.a-vignette { position: relative; overflow: hidden; }

body .qbar-slot.a-vignette::before,
body .cd-item.a-vignette::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--file-vignette);
  background-size: cover;
  background-position: center;
  opacity: .5;
  pointer-events: none;
  z-index: 0;
}
body .qbar-slot.a-vignette::after,
body .cd-item.a-vignette::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(6, 12, 24, .30) 0%, rgba(6, 12, 24, .74) 62%, rgba(6, 12, 24, .90) 100%);
  pointer-events: none;
  z-index: 0;
}
body .qbar-slot.a-vignette > *,
body .cd-item.a-vignette > * { position: relative; z-index: 1; }

/* La petite planche du coin n'a plus de raison d'être : elle répète en
   vingt pixels ce que la case entière montre déjà. L'émoji de repli, lui,
   RESTE — c'est le seul témoin des objets sans planche. */
body .qbar-slot.a-vignette .qbar-slot-ico-img,
body .cd-item.a-vignette .cd-item-ico-img { display: none; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.91 — CE QUE LA PLANÈTE A ENCORE À DONNER
   v0.0.91
   ════════════════════════════════════════════════════════════════════
   Deux tons, et ils disent deux choses différentes : VERT pour une
   planète intacte (une bonne nouvelle qu'on cherche avant de s'ancrer),
   AMBRE pour une cicatrice ouverte. Un ton unique aurait fait lire les
   deux comme un avertissement, sur les 99 % de planètes saines comprises.

   ⚠️ LA RÈGLE `[hidden]` EST OBLIGATOIRE : une classe qui pose `display`
   a la MÊME spécificité que la règle native `[hidden] { display: none }`,
   et la feuille d'AUTEUR l'emporte à égalité. Sans elle le bandeau
   resterait affiché, vide, sur tous les univers où l'interrupteur est
   éteint. Quatrième fois — badge de la cloche (v2.69), convalescence
   (v0.0.71), bandeau rouge (v0.0.72). */
.pp-impact {
  display: block;
  margin: .5rem 0 .2rem; padding: .5rem .7rem;
  font-size: .76rem; line-height: 1.45;
  border-radius: 6px;
}
.pp-impact[hidden] { display: none; }
.pp-impact-ok {
  color: #86efac;
  background: rgba(34, 197, 94, .08);
  border: 1px solid rgba(34, 197, 94, .30);
}
.pp-impact-ok b { color: #bbf7d0; }
.pp-impact-ko {
  color: #fcd34d;
  background: rgba(234, 179, 8, .10);
  border: 1px solid rgba(234, 179, 8, .38);
}
.pp-impact-ko b { color: #fef08a; }
.pp-imp-note { display: block; margin-top: .18rem; opacity: .88; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.91 — LA PÉRIODE DES STATISTIQUES SE CHOISIT DE DATE À DATE
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : « ajoute Aujourd'hui aux périodes et un sélecteur
   de date à date ».

   ⚠️ LE `body ` N'EST PAS DÉCORATIF : il rend le point de spécificité que
   la classe d'interrupteur portait avant la purge, et met le classement de
   ces règles à l'abri d'un déplacement de bloc (leçon de la v0.0.87).

   ⚠️ ET LA BARRE PASSE À LA LIGNE. Avec « Aujourd'hui » plus les deux
   champs de date, elle porte quatre boutons et deux sélecteurs : à
   1 180 px, colonne de droite déployée, elle poussait le compte de jours
   HORS de l'écran — mesuré. `flex-wrap` la laisse respirer, et le
   compte garde sa `margin-left: auto` sur sa propre ligne. */
body .st-barre-periode { flex-wrap: wrap; row-gap: 8px; }
body .st-plage {
  display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: .78rem; color: var(--text-dim, #8fa3c8);
}
body .st-plage label {
  display: inline-flex; align-items: center; gap: 4px;
}
body .st-plage input[type="date"] {
  font: inherit; font-size: .78rem; color: var(--text, #c8d6f0);
  background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.14);
  border-radius: 6px; padding: 3px 6px;
}
body .st-plage input[type="date"]:focus {
  border-color: #38bdf8; outline: none;
}
/* Le sélecteur natif ouvre un calendrier sombre là où le jeu l'est. */
body .st-plage input[type="date"] { color-scheme: dark; }
body .st-note-court { margin: 0 0 12px; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.91 — LA PIRE FENÊTRE, ET L'ATTENTE QUI NE VAUT PAS LE COUP
   ════════════════════════════════════════════════════════════════════
   ⚠️ LE `body ` REND LE POINT DE SPÉCIFICITÉ que portait la classe
   d'interrupteur avant la purge (leçon de la v0.0.87). */
body .exped2-perte { color: #f0776b; }
body .exped2-fenetre-vaine {
  border-left: 2px solid rgba(224,169,74,.55);
  padding-left: 8px;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.91 — LA LISTE DU PRÉCURSEUR, TRIÉE ET FENÊTRÉE
   ════════════════════════════════════════════════════════════════════
   ⚠️ LE `body ` REND LE POINT DE SPÉCIFICITÉ que portait la classe
   d'interrupteur avant la purge (leçon de la v0.0.87). */
body .exped2-actions-solo {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
body .exped2-actions-solo .exped2-btn-un { flex: 1 1 auto; }
body .exped2-target-fen {
  font-size: .68rem; display: inline-flex; gap: 6px; white-space: nowrap;
}
body .exped2-fen-ok { color: #4ade80; }
body .exped2-fen-ko { color: #f0776b; }
body .exped2-fen-vaine { color: #e0a94a; }
body .exped2-fen-legende {
  font-size: .7rem; margin-top: 2px; line-height: 1.35;
}

/* ⚠️ AU TÉLÉPHONE, LES DEUX FENÊTRES PASSENT À LA LIGNE — mesuré à 390 px :
   la ligne « Ara Elysiara · 9 astres · 20 h 58 · ↓ 13 h 34 dans 55 min ·
   ↑ 1 j 2 h » porte 330 px de contenu dans une boîte de 317, et le bloc
   des fenêtres finissait 14 px APRÈS le bord droit. Rien ne le signalait :
   la page, elle, ne défile pas horizontalement — c'est le texte qui est
   coupé net dans sa propre boîte, et un « ↑ 1j… » tronqué se lit comme un
   chiffre, pas comme une troncature. */
body.mobile-mode .exped2-target-item { flex-wrap: wrap; }
body.mobile-mode .exped2-target-fen {
  flex: 1 0 100%; justify-content: flex-end; margin-top: 2px;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.92 — LE TABLEAU DE FENÊTRE AU SURVOL
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : « au passage de la souris une petite pop up avec un
   tableau notant Actuel / Meilleur / Pire ».
   ⚠️ LE `body ` DE TÊTE N'EST PAS DÉCORATIF : sans lui, `.exped2-target-row`
   perd un point de spécificité et la règle de base de `.exped2-target-item`
   repasse devant. */
body .exped2-target-row { position: relative; display: block; }
body .exped2-target-row > .exped2-target-item { width: 100%; }

/* ⚠️ `position: fixed` ET NON `absolute` : la liste des cibles défile
   (`overflow-y: auto`), donc un panneau absolu y serait coupé au bord dès
   la première et la dernière ligne. Le JS lui pose son `top`/`left`. */
body .exped2-fen-pop {
  position: fixed; z-index: 400; display: none;
  /* ⚠️ IL NE PREND JAMAIS LE POINTEUR : sinon il se glisserait sous la
     souris, déclencherait un `mouseout` de la rangée, disparaîtrait, et
     l'écran clignoterait tant qu'on ne bouge pas. Il masquerait en prime
     le bouton qu'il documente. */
  pointer-events: none;
  min-width: 380px; max-width: min(560px, 94vw);
  padding: 9px 11px 8px;
  background: #0c1426; border: 1px solid #2b3d63; border-radius: 8px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .55);
  font-size: .72rem; line-height: 1.45; color: var(--text2);
}
body .exped2-target-row:hover .exped2-fen-pop { display: block; }
body .exped2-fen-pop-t {
  display: block; font-weight: 600; color: var(--text); margin-bottom: 5px;
}
body .exped2-fen-tab {
  width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums;
}
body .exped2-fen-tab th,
body .exped2-fen-tab td {
  padding: 2px 6px 2px 0; text-align: right; white-space: nowrap;
}
body .exped2-fen-tab thead th {
  font-weight: 500; font-size: .66rem; text-transform: uppercase;
  letter-spacing: .04em; color: var(--text3); border-bottom: 1px solid #23324f;
}
body .exped2-fen-tab tbody th { text-align: left; color: var(--text); }
body .exped2-fen-tab tbody tr.fp-actuel th { color: #7fd4ff; }
body .exped2-fen-tab tbody tr.fp-mieux th { color: #6ee7a8; }
body .exped2-fen-tab tbody tr.fp-pire th { color: #f0a36b; }
body .exped2-fen-pop-p {
  display: block; margin-top: 6px; padding-top: 5px;
  border-top: 1px solid #23324f; color: var(--text3);
}

/* ⚠️ AU TÉLÉPHONE, JAMAIS. Il n'y a pas de survol : le panneau ne peut donc
   s'ouvrir que par un `:hover` LATCHÉ — l'état que certains navigateurs
   tactiles laissent collé après un appui. Il ferait alors 380 px de large
   sur un écran de 390, par-dessus la liste qu'on vient de toucher, et rien
   ne le fermerait. Mesuré à 390 px : sans cette règle il reste `none` par
   chance, ce qui n'est pas une garantie. La ligne reste cliquable, et la
   fenêtre de confirmation porte déjà tout le détail. */
body.mobile-mode .exped2-target-row:hover .exped2-fen-pop { display: none; }
@media (max-width: 700px) {
  body .exped2-target-row:hover .exped2-fen-pop { display: none; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.97 — LE COLPORTEUR
   ════════════════════════════════════════════════════════════════════
   Deux écrans du même gabarit : la FICHE du marchand (sa réserve, son
   barème de péage, son compte à rebours) et l'ÉCRAN D'ÉCHANGE d'un convoi
   arrivé.

   ⚠️ L'HÔTE EST EN `position: fixed`, PAS `absolute`. Il se pose par-dessus
   la carte, qui est un canevas plein écran avec sa propre origine : un
   panneau absolu se serait calé sur le premier ancêtre positionné — la vue
   Galaxie — et serait sorti de l'écran dès qu'on fait défiler. */
body .clp-hote {
  position: fixed; inset: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4, 2, 12, 0.55);
}
body .clp-panneau {
  width: min(92vw, 620px); max-height: 84vh; overflow-y: auto;
  background: var(--panel, #0e1526);
  border: 1px solid rgba(240, 186, 90, 0.34);
  border-radius: 12px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
  color: var(--text, #eaf2ff);
}
body .clp-tete {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid rgba(240, 186, 90, 0.22);
  /* ⚠️ COLLANT : le panneau défile (jusqu'à sept lignes de réserve plus le
     formulaire d'échange), et une croix de fermeture partie en haut du
     défilement est une fiche dont on ne sort plus. Quatrième fois que ce
     défaut se présente dans ce projet — le panneau de planète (v0.0.85) en
     porte la leçon écrite. */
  position: sticky; top: 0; z-index: 1;
  background: var(--panel, #0e1526);
  border-radius: 12px 12px 0 0;
}
body .clp-tete b { color: #f0ba5a; }
body .clp-dim {
  font-size: .82rem; color: var(--text2, #8da4c4);
  /* La colonne des noms est la seule à pouvoir s'enrouler : à 390 px,
     « Colporteur » + un nom de système + un nom de galaxie sur une seule
     ligne pousse la croix hors du panneau (leçon du tableau des trajets,
     v0.0.82). */
  min-width: 0; overflow-wrap: anywhere;
}
body .clp-x {
  margin-left: auto; flex: none;
  background: none; border: none; cursor: pointer;
  color: var(--text2, #8da4c4); font-size: 1.05rem; line-height: 1;
  padding: 4px 6px; border-radius: 6px;
}
body .clp-x:hover { color: var(--text, #eaf2ff); background: rgba(255,255,255,.06); }
body .clp-corps { padding: 12px 14px 16px; }
body .clp-note {
  font-size: .82rem; color: var(--text2, #8da4c4); margin-bottom: 12px; line-height: 1.5;
}
body .clp-note b { color: var(--text, #eaf2ff); }

/* Le barème. ⚠️ IL DÉFILE DANS SA PROPRE BOÎTE : trois colonnes dont une
   phrase (« 1 Héliure jaune → 10 000 ») ne tiennent pas dans 330 px, et un
   tableau qui déborde ne se signale par AUCUNE erreur — la page, elle, ne
   défile pas horizontalement pour autant. */
body .clp-table-wrap { overflow-x: auto; }
body .clp-table {
  width: 100%; border-collapse: collapse; font-size: .84rem; margin-bottom: 12px;
}
body .clp-table th {
  text-align: left; font-weight: 500; font-size: .68rem;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--text3, #6b8ab0); padding: 6px 8px;
  border-bottom: 1px solid rgba(240, 186, 90, 0.22);
}
body .clp-table td {
  padding: 6px 8px; border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
body .clp-q { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
body .clp-c { white-space: nowrap; font-size: .78rem; }
/* Les trois couleurs d'Héliure. ⚠️ C'EST LA COULEUR DE CE QU'ON REÇOIT
   (arbitrage de Loryan) : la ligne « Iridyum » porte donc du bleu parce que
   c'est une Héliure BLEUE qui l'achète, jamais la teinte du minerai. */
body .clp-c-jaune { color: #f2d06b; }
body .clp-c-rouge { color: #f08a7a; }
body .clp-c-bleu  { color: #7fb8ff; }

body .clp-cale { margin-bottom: 10px; font-size: .84rem; }
body .clp-pill {
  display: inline-flex; align-items: center; gap: 4px;
  margin: 4px 6px 0 0; padding: 3px 9px; border-radius: 999px;
  background: rgba(240, 186, 90, 0.12);
  border: 1px solid rgba(240, 186, 90, 0.26);
  font-size: .8rem; white-space: nowrap;
}
body .clp-form {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end;
  margin: 12px 0; padding: 12px; border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
}
body .clp-form label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: .74rem; color: var(--text3, #6b8ab0); min-width: 0;
}
body .clp-form select,
body .clp-form input {
  background: #0a1120; color: var(--text, #eaf2ff);
  border: 1px solid #23324f; border-radius: 7px;
  padding: 6px 8px; font-size: .86rem; min-width: 0; max-width: 100%;
}
body .clp-btn {
  background: rgba(240, 186, 90, 0.16); color: #f7d79b;
  border: 1px solid rgba(240, 186, 90, 0.42); border-radius: 8px;
  padding: 7px 14px; font-size: .86rem; cursor: pointer; white-space: nowrap;
}
body .clp-btn:hover { background: rgba(240, 186, 90, 0.26); }
body .clp-retour { display: block; width: 100%; margin-top: 12px; }
body .clp-devis {
  font-size: .84rem; line-height: 1.5; padding: 10px 12px; border-radius: 8px;
  background: rgba(127, 184, 255, 0.08); border: 1px solid rgba(127, 184, 255, 0.2);
}
body .clp-manque { margin-top: 6px; color: #f0a36b; font-size: .8rem; }

/* ⚠️ AU TÉLÉPHONE, LE PANNEAU PREND TOUTE LA LARGEUR ET LE FORMULAIRE
   PASSE EN COLONNE. Trois champs côte à côte dans 330 px donnent trois
   sélecteurs de cent pixels où « Palladrine » se coupe au milieu d'un mot —
   et un nom de ressource tronqué, c'est un échange lancé sur la mauvaise
   ligne. LES DEUX FORMES SONT POSÉES : `body.mobile-mode` suit le mode forcé
   sur grand écran, la media query suit la largeur réelle. */
body.mobile-mode .clp-panneau { width: 96vw; max-height: 88vh; }
body.mobile-mode .clp-form { flex-direction: column; align-items: stretch; }
body.mobile-mode .clp-form label { width: 100%; }
body.mobile-mode .clp-btn { width: 100%; }
@media (max-width: 700px) {
  body .clp-panneau { width: 96vw; max-height: 88vh; }
  body .clp-form { flex-direction: column; align-items: stretch; }
  body .clp-form label { width: 100%; }
  body .clp-btn { width: 100%; }
}

/* Interrupteur testeurVitesseVague — l'allure d'une vague, sur sa fiche.
   ⚠️ UNE GRILLE DE SEPT COLONNES ÉGALES, PAS `.clp-btn` : celui-ci passe en
   pleine largeur au téléphone, et sept boutons empilés feraient une fiche
   plus haute que l'écran. Sept colonnes tiennent dans les 320 px utiles à
   390 px de large, « ×50 » compris. */
body .zv-testeur {
  margin-top: 4px; padding: 10px 12px; border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px dashed rgba(240, 186, 90, 0.3);
}
body .zv-testeur-titre {
  font-size: .8rem; letter-spacing: .04em; text-transform: uppercase;
  color: #f0ba5a; margin-bottom: 8px;
}
body .zv-vitesses { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
body .zv-vitesse {
  min-width: 0; padding: 6px 0; border-radius: 8px; cursor: pointer;
  font-size: .86rem; font-variant-numeric: tabular-nums;
  background: rgba(240, 186, 90, 0.10); color: #f7d79b;
  border: 1px solid rgba(240, 186, 90, 0.32);
}
body .zv-vitesse:hover { background: rgba(240, 186, 90, 0.22); }
body .zv-vitesse.zv-vitesse-on {
  background: #f0ba5a; color: #1a1206; border-color: #f0ba5a; font-weight: 700;
}
body .zv-testeur-etat {
  margin-top: 8px; font-size: .82rem; line-height: 1.45; color: var(--text2, #8da4c4);
}
body .zv-testeur-etat b { color: var(--text, #eaf2ff); }
/* Interrupteur testeurEncadre — Loryan : « que ce soit surligné en orange le
   contour de la zone de testeur pour la reconnaître immédiatement ». L'orange
   est celui du bouton 🧪 TEST de la barre du haut (--gold) : le même signe
   partout où un outil de test se glisse dans une fiche que lisent les
   joueurs — la fiche d'un soleil et celle d'une vague. Le reste de la fiche
   garde sa bordure discrète : c'est le contraste qui fait le repère. */
body.f-testeurEncadre .sp-testeur,
body.f-testeurEncadre .zv-testeur {
  border: 2px solid var(--gold);
  background: rgba(245, 158, 11, .06);
  box-shadow: 0 0 0 1px rgba(245, 158, 11, .22), 0 0 14px rgba(245, 158, 11, .2);
}
body.f-testeurEncadre .sp-testeur .sp-lbl { color: var(--gold); }

/* ════════════════════════════════════════════════════════════════════
   v0.0.98 — LE FERRAILLEUR ITINÉRANT
   ════════════════════════════════════════════════════════════════════
   Deux écrans du même gabarit que le Colporteur : la FICHE de l'atelier
   (son barème de rachat, son compte à rebours) et l'ÉCRAN D'ATELIER d'un
   convoi arrivé.

   ⚠️ TEINTE ACIER, ET PAS L'AMBRE DU COLPORTEUR. Les deux marchands se
   ressemblent par leur mécanique ; ce qui les distingue à l'écran doit
   donc être immédiat, sinon on ouvre l'un en croyant lire l'autre. */
body .frl-hote {
  position: fixed; inset: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4, 6, 12, 0.55);
}
body .frl-panneau {
  width: min(92vw, 620px); max-height: 84vh; overflow-y: auto;
  background: var(--panel, #0e1526);
  border: 1px solid rgba(157, 180, 200, 0.34);
  border-radius: 12px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
  color: var(--text, #eaf2ff);
}
/* L'atelier porte trois poches et un formulaire : il lui faut plus de
   large que la simple fiche. */
body .frl-large { width: min(94vw, 760px); }
body .frl-tete {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid rgba(157, 180, 200, 0.22);
  /* ⚠️ COLLANT : le panneau défile (trois poches de vaisseaux plus le
     formulaire), et une croix de fermeture partie en haut du défilement
     est un écran dont on ne sort plus. */
  position: sticky; top: 0; z-index: 1;
  background: var(--panel, #0e1526);
  border-radius: 12px 12px 0 0;
}
body .frl-tete b { color: #b7cade; }
body .frl-dim {
  font-size: .82rem; color: var(--text2, #8da4c4);
  /* La colonne des noms est la seule à pouvoir s'enrouler : à 390 px, un
     nom de système plus un nom de galaxie sur une ligne pousse la croix
     hors du panneau (leçon du tableau des trajets, v0.0.82). */
  min-width: 0; overflow-wrap: anywhere;
}
body .frl-x {
  margin-left: auto; flex: none;
  background: none; border: none; cursor: pointer;
  color: var(--text2, #8da4c4); font-size: 1.05rem; line-height: 1;
  padding: 4px 6px; border-radius: 6px;
}
body .frl-x:hover { color: var(--text, #eaf2ff); background: rgba(255,255,255,.06); }
body .frl-corps { padding: 12px 14px 16px; }
body .frl-note {
  font-size: .82rem; color: var(--text2, #8da4c4); margin-bottom: 12px; line-height: 1.5;
}
body .frl-note b { color: var(--text, #eaf2ff); }

/* Le taux du moment : c'est le chiffre qui décide, il se lit en premier. */
body .frl-taux {
  font-size: .95rem; margin-bottom: 14px; padding: 10px 12px; border-radius: 8px;
  background: rgba(157, 180, 200, 0.10); border: 1px solid rgba(157, 180, 200, 0.24);
}
body .frl-taux b { color: #cfe3f5; font-size: 1.15rem; }

/* ⚠️ LE BARÈME DÉFILE DANS SA PROPRE BOÎTE : deux colonnes tiennent
   partout, mais un tableau qui déborde ne se signale par AUCUNE erreur —
   la page, elle, ne défile pas horizontalement pour autant. */
body .frl-table-wrap { overflow-x: auto; }
body .frl-table {
  width: 100%; border-collapse: collapse; font-size: .84rem; margin-bottom: 12px;
}
body .frl-table th {
  text-align: left; font-weight: 500; font-size: .68rem;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--text3, #6b8ab0); padding: 6px 8px;
  border-bottom: 1px solid rgba(157, 180, 200, 0.22);
}
body .frl-table td {
  padding: 6px 8px; border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
body .frl-q { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Les trois poches du convoi. Elles restent SÉPARÉES à l'écran comme au
   serveur : un vaisseau qui a volé et un vaisseau porté en soute ne se
   retirent pas du même endroit. */
body .frl-poche { margin-bottom: 14px; }
body .frl-poche-titre {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text3, #6b8ab0); margin-bottom: 6px;
}
body .frl-ligne {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px; border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
body .frl-nom { flex: 1; min-width: 0; overflow-wrap: anywhere; }
body .frl-qte {
  width: 92px; flex: none;
  background: #0a1120; color: var(--text, #eaf2ff);
  border: 1px solid #23324f; border-radius: 7px; padding: 5px 7px; font-size: .86rem;
}
body .frl-max {
  flex: none; background: rgba(157, 180, 200, 0.14); color: #cfe3f5;
  border: 1px solid rgba(157, 180, 200, 0.34); border-radius: 6px;
  padding: 4px 9px; font-size: .72rem; cursor: pointer;
}
body .frl-pill {
  display: inline-flex; align-items: center; gap: 4px;
  margin: 4px 6px 0 0; padding: 3px 9px; border-radius: 999px;
  background: rgba(157, 180, 200, 0.12);
  border: 1px solid rgba(157, 180, 200, 0.26);
  font-size: .8rem; white-space: nowrap;
}
body .frl-form {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end;
  margin: 12px 0; padding: 12px; border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
}
body .frl-form label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: .74rem; color: var(--text3, #6b8ab0); min-width: 0;
}
body .frl-form select,
body .frl-form input {
  background: #0a1120; color: var(--text, #eaf2ff);
  border: 1px solid #23324f; border-radius: 7px;
  padding: 6px 8px; font-size: .86rem; min-width: 0; max-width: 100%;
}
body .frl-btn {
  display: block; width: 100%; margin-top: 10px;
  background: rgba(157, 180, 200, 0.16); color: #dbe9f7;
  border: 1px solid rgba(157, 180, 200, 0.42); border-radius: 8px;
  padding: 9px 14px; font-size: .9rem; cursor: pointer;
}
body .frl-btn:hover { background: rgba(157, 180, 200, 0.26); }
body .frl-retour {
  background: rgba(110, 231, 168, 0.12); color: #9ce8c0;
  border-color: rgba(110, 231, 168, 0.34);
}
body .frl-retour:hover { background: rgba(110, 231, 168, 0.22); }
body .frl-devis {
  font-size: .84rem; line-height: 1.5; padding: 10px 12px; border-radius: 8px;
  background: rgba(127, 184, 255, 0.08); border: 1px solid rgba(127, 184, 255, 0.2);
}
/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — LE TAUX LU COMME UNE ADDITION
   ════════════════════════════════════════════════════════════════════
   « 27,3 % » tombe du ciel ; « 20 % + 7,3 = 27,3 % » se comprend. Les trois
   morceaux s'enroulent : à 390 px ils passent l'un sous l'autre plutôt que
   de se serrer jusqu'à l'illisible. */
body .frl-calcul {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  margin-bottom: 10px; padding-bottom: 10px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  font-variant-numeric: tabular-nums;
}
body .frl-calcul b { color: #cfe3f5; font-size: 1.05rem; }
body .frl-calcul-total { color: #9ce8c0 !important; font-size: 1.35rem !important; }
body .frl-ok { color: #9ce8c0; }

/* Le pavé qui explique la règle avec les chiffres DU LOT. Discret, mais il
   porte la seule information qui manquait vraiment : la tranche. */
body .frl-explique {
  font-size: .78rem; line-height: 1.55; color: var(--text2, #8da4c4);
  background: rgba(127, 184, 255, 0.06);
  border: 1px solid rgba(127, 184, 255, 0.18);
  border-radius: 8px; padding: 9px 11px; margin-bottom: 12px;
}
body .frl-explique b { color: var(--text, #eaf2ff); }

body .frl-manque { margin-top: 6px; color: #f0a36b; font-size: .8rem; }

/* ⚠️ AU TÉLÉPHONE, LE FORMULAIRE PASSE EN COLONNE et la ligne d'article
   s'enroule. Trois champs côte à côte dans 330 px donnent des sélecteurs
   de cent pixels où « Transporteur de Flotte » se coupe au milieu d'un
   mot — et un nom tronqué, c'est un lot vendu de travers. Les deux gardes
   sont posées : la classe seule ne déclenche aucune media query, et la
   media query seule ne suit pas un mode mobile forcé sur grand écran. */
body.mobile-mode .frl-panneau,
body.mobile-mode .frl-large { width: 96vw; max-height: 88vh; }
body.mobile-mode .frl-form { flex-direction: column; align-items: stretch; }
body.mobile-mode .frl-form label { width: 100%; }
body.mobile-mode .frl-ligne { flex-wrap: wrap; }
body.mobile-mode .frl-nom { flex: 1 0 100%; }
@media (max-width: 700px) {
  body .frl-panneau,
  body .frl-large { width: 96vw; max-height: 88vh; }
  body .frl-form { flex-direction: column; align-items: stretch; }
  body .frl-form label { width: 100%; }
  body .frl-ligne { flex-wrap: wrap; }
  body .frl-nom { flex: 1 0 100%; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — LES DEUX SIMULATEURS DE MARCHAND
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : un bouton dans chaque fiche de marchand, ouvrant un
   écran d'essai. Ces règles ne peignent que si l'écran existe, et l'écran
   n'est créé que par `SMR.ouvrir…` — que seul un bouton gardé par
   `SMR.actif()` appelle. Aucune classe `f-…` n'est donc nécessaire.

   ⚠️ IL S'OUVRE PAR-DESSUS LA FICHE, d'où le z-index au-dessus des 300 des
   deux marchands : la fiche reste ouverte derrière, et on la retrouve en
   fermant le simulateur. */
/* v0.0.7 : une destination unique s'AFFICHE au
   lieu de se choisir. Même gabarit qu'un `.rp-select` pour que le
   formulaire ne se déforme pas quand on passe de deux marchands à un. */
/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — LA JAUGE DE SOUTE DE FRET
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : la soute qu'ouvrent les porteurs sur LEUR ligne, la
   valeur des unités chargées sur LA LEUR, et un verdict.
   ⚠️ LE VERDICT SE LIT À LA COULEUR ET AU MOT, jamais à la seule couleur :
   « ⚠️ dépasse de 160 k » se comprend sans distinguer l'orange du vert. */
body .rp-flotte-note {
  flex: none; font-size: .74rem; color: var(--text3, #6b8ab0);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  margin-left: auto; padding-right: 8px;
}
body .rp-fret-val { color: var(--text2, #8da4c4); }
body .rp-flotte-soute { color: #8fb9e8; }

body .rp-jauge-hote { margin: 2px 0 8px; }
body .rp-jauge {
  display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
  font-size: .82rem; padding: 5px 10px; border-radius: 7px;
  font-variant-numeric: tabular-nums;
  border: 1px solid transparent;
}
body .rp-jauge b { font-size: .95rem; }
body .rp-jauge-mot { font-size: .78rem; }
body .rp-jauge-ok {
  background: rgba(110, 231, 168, 0.10); border-color: rgba(110, 231, 168, 0.30);
  color: #9ce8c0;
}
body .rp-jauge-ok b { color: #c8f5dd; }
body .rp-jauge-trop {
  background: rgba(240, 163, 107, 0.12); border-color: rgba(240, 163, 107, 0.34);
  color: #f0a36b;
}
body .rp-jauge-trop b { color: #ffc99b; }
body .rp-jauge-attente {
  background: rgba(255, 255, 255, 0.04); border-color: rgba(255, 255, 255, 0.12);
  color: var(--text3, #6b8ab0);
}
body .rp-jauge-attente b { color: var(--text2, #8da4c4); }

/* ⚠️ AU TÉLÉPHONE, LA NOTE RESTE SUR LA LIGNE, ELLE NE PASSE PAS DESSOUS.
   Premier essai : `flex: 1 0 100%` pour lui donner sa propre ligne — et les
   champs de saisie ONT DISPARU de l'écran, parce que `.rp-flotte-ligne`
   n'enroule pas et que les trois éléments restants se sont écrasés à zéro.
   Vu en capture. Cette règle-là est PARTAGÉE par toutes les missions : on
   ne la touche pas pour un seul onglet. La note se contente donc d'être
   courte, et le nom du vaisseau, lui, sait s'enrouler. */
body.mobile-mode .rp-flotte-note,
body .rp-flotte-note { padding-right: 6px; }

body .rp-fixe {
  padding: 8px 10px; border-radius: 8px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.12);
  font-size: .9rem; color: var(--text, #eaf2ff);
}
body .rp-fixe .rp-aide { display: inline; margin-left: 6px; }

body .clp-sim, body .frl-sim {
  margin-left: auto; flex: none;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 7px;
  padding: 4px 10px; font-size: .78rem; cursor: pointer;
  white-space: nowrap;
}
body .clp-sim { color: #e8c07a; border-color: rgba(232, 192, 122, 0.36); }
body .frl-sim { color: #b7cade; border-color: rgba(183, 202, 222, 0.36); }
body .clp-sim:hover { background: rgba(232, 192, 122, 0.18); }
body .frl-sim:hover { background: rgba(183, 202, 222, 0.18); }
/* ⚠️ LA CROIX PERD SON `margin-left: auto` DÈS QU'UN BOUTON LA PRÉCÈDE :
   sans ça les deux se disputent la marge et la croix décolle du bord. */
body .clp-sim ~ .clp-x, body .frl-sim ~ .frl-x { margin-left: 0; }

body .smr-hote {
  position: fixed; inset: 0; z-index: 320;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4, 6, 12, 0.62);
}
body .smr-panneau {
  width: min(94vw, 780px); max-height: 86vh; overflow-y: auto;
  background: var(--panel, #0e1526);
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 12px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.6);
  color: var(--text, #eaf2ff);
}
/* Acier pour l'atelier, ambre pour le marchand — la même distinction que
   leurs deux fiches. On ouvre l'un en croyant lire l'autre, sinon. */
body .smr-ferr { border-color: rgba(157, 180, 200, 0.34); }
body .smr-clp  { border-color: rgba(232, 192, 122, 0.30); }
body .smr-ferr .smr-tete b { color: #b7cade; }
body .smr-clp  .smr-tete b { color: #e8c07a; }

body .smr-tete {
  display: flex; align-items: center; gap: 10px; padding: 12px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  /* ⚠️ COLLANT : la liste du hangar défile, et une croix partie en haut du
     défilement est un écran dont on ne sort plus. */
  position: sticky; top: 0; z-index: 1;
  background: var(--panel, #0e1526); border-radius: 12px 12px 0 0;
}
body .smr-dim {
  font-size: .82rem; color: var(--text2, #8da4c4);
  min-width: 0; overflow-wrap: anywhere;
}
body .smr-x {
  flex: none; background: none; border: none; cursor: pointer;
  color: var(--text2, #8da4c4); font-size: 1.05rem; line-height: 1;
  padding: 4px 6px; border-radius: 6px;
}
body .smr-x:hover { color: var(--text, #eaf2ff); background: rgba(255,255,255,.06); }
body .smr-corps { padding: 12px 14px 16px; }
body .smr-note {
  font-size: .82rem; color: var(--text2, #8da4c4); margin-bottom: 12px; line-height: 1.5;
}
body .smr-note b { color: var(--text, #eaf2ff); }
body .smr-alerte {
  font-size: .84rem; color: #f0a36b; margin-bottom: 12px;
  padding: 8px 10px; border-radius: 8px;
  background: rgba(240, 163, 107, 0.10); border: 1px solid rgba(240, 163, 107, 0.28);
}
body .smr-vide { font-size: .84rem; color: var(--text3, #6b8ab0); padding: 10px 0; }

/* Les réglages. ⚠️ ILS S'ENROULENT : quatre champs côte à côte tiennent en
   780 px et pas un pixel de moins. */
body .smr-form {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end;
  margin-bottom: 14px;
}
body .smr-champ {
  display: flex; flex-direction: column; gap: 4px;
  font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text3, #6b8ab0); min-width: 0; flex: 1 1 160px;
}
body .smr-champ select, body .smr-champ input {
  background: rgba(255, 255, 255, 0.05); color: var(--text, #eaf2ff);
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 7px;
  padding: 6px 8px; font-size: .86rem; text-transform: none; letter-spacing: 0;
  min-width: 0; max-width: 100%;
}
body .smr-mini {
  flex: none; background: rgba(255, 255, 255, 0.07); color: var(--text, #eaf2ff);
  border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 7px;
  padding: 7px 12px; font-size: .8rem; cursor: pointer;
}
body .smr-mini:hover { background: rgba(255, 255, 255, 0.14); }
body .smr-vider { margin: 0 0 14px; }

/* La liste du hangar. */
body .smr-poche { margin-bottom: 14px; }
body .smr-poche-titre {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text3, #6b8ab0); margin-bottom: 6px;
}
body .smr-ligne {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
body .smr-ico { flex: none; font-size: 1rem; }
body .smr-ident { flex: 1 1 auto; min-width: 0; }
body .smr-nom { font-size: .88rem; overflow-wrap: anywhere; }
body .smr-sous { font-size: .74rem; color: var(--text3, #6b8ab0); }
body .smr-trop { color: #f0a36b; }
body .smr-tag {
  margin-left: 6px; font-size: .66rem; padding: 1px 5px; border-radius: 4px;
  background: rgba(240, 163, 107, 0.14); color: #f0a36b;
}
body .smr-qte {
  flex: none; width: 84px;
  background: rgba(255, 255, 255, 0.05); color: var(--text, #eaf2ff);
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 7px;
  padding: 5px 7px; font-size: .84rem;
}
body .smr-max {
  flex: none; background: rgba(255, 255, 255, 0.07); color: var(--text2, #8da4c4);
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 6px;
  padding: 5px 8px; font-size: .7rem; cursor: pointer;
}
body .smr-max:hover { color: var(--text, #eaf2ff); background: rgba(255, 255, 255, 0.14); }

/* Le bilan : c'est LUI qu'on vient lire, il se distingue du reste. */
body .smr-bilan {
  margin-top: 14px; padding: 12px 14px; border-radius: 10px;
  background: rgba(127, 184, 255, 0.07); border: 1px solid rgba(127, 184, 255, 0.2);
}
body .smr-bilan-vide { color: var(--text3, #6b8ab0); font-size: .84rem; }
body .smr-taux {
  font-size: .95rem; margin-bottom: 10px;
  padding-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
body .smr-taux b { font-size: 1.3rem; color: #cfe3f5; }
body .smr-clp .smr-taux b { color: #f0d3a0; }
body .smr-l {
  display: flex; justify-content: space-between; gap: 12px;
  font-size: .84rem; padding: 3px 0; color: var(--text2, #8da4c4);
}
body .smr-l b { color: var(--text, #eaf2ff); font-variant-numeric: tabular-nums; }
/* Le mur qui arrête, souligné : c'est la moitié de l'information. */
body .smr-l-mur { color: #f0a36b; }
body .smr-l-mur b { color: #f0a36b; }
body .smr-gains {
  display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 8px 0 4px; font-size: .84rem;
}
body .smr-gain { color: var(--text2, #8da4c4); }
body .smr-gain b { color: var(--text, #eaf2ff); }
body .smr-manque {
  margin-top: 8px; font-size: .8rem; color: #f0a36b; line-height: 1.5;
}
body .smr-ok { margin-top: 8px; font-size: .8rem; color: #9ce8c0; line-height: 1.5; }

/* ⚠️ AU TÉLÉPHONE, LES RÉGLAGES PASSENT EN COLONNE et la ligne d'article
   s'enroule. Quatre champs côte à côte dans 330 px donnent des sélecteurs
   de soixante pixels où « Transporteur de Flotte » se coupe au milieu d'un
   mot. Les DEUX gardes sont posées : la classe seule ne déclenche aucune
   media query, et la media query seule ne suit pas un mode mobile forcé
   sur grand écran. */
body.mobile-mode .smr-panneau { width: 96vw; max-height: 90vh; }
body.mobile-mode .smr-form { flex-direction: column; align-items: stretch; }
/* ⚠️ `flex: none` EST OBLIGATOIRE ICI, et c'est le défaut vu en capture :
   en colonne, le `flex: 1 1 160px` des champs les fait GRANDIR pour remplir
   la hauteur du panneau — trois sélecteurs séparés par deux cents pixels de
   vide, sur un écran de 390. Le `width: 100%` seul n'y change rien. */
body.mobile-mode .smr-champ { width: 100%; flex: none; }
body.mobile-mode .smr-mini { width: 100%; flex: none; }
body.mobile-mode .smr-ligne { flex-wrap: wrap; }
body.mobile-mode .smr-ident { flex: 1 0 calc(100% - 30px); }
body.mobile-mode .smr-qte { flex: 1 1 auto; width: auto; }
body.mobile-mode .smr-l { flex-wrap: wrap; }
@media (max-width: 700px) {
  body .smr-panneau { width: 96vw; max-height: 90vh; }
  body .smr-form { flex-direction: column; align-items: stretch; }
  body .smr-champ { width: 100%; flex: none; }
  body .smr-mini { width: 100%; flex: none; }
  body .smr-ligne { flex-wrap: wrap; }
  body .smr-ident { flex: 1 0 calc(100% - 30px); }
  body .smr-qte { flex: 1 1 auto; width: auto; }
  body .smr-l { flex-wrap: wrap; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.99 — LA BOUTIQUE ET SON AURORE
   ════════════════════════════════════════════════════════════════════
   ⚠️ AUCUNE RÈGLE N'EST GARDÉE PAR UNE CLASSE `f-…`, ET C'EST VOULU : la
   pastille elle-même ne s'affiche que si le serveur sert un solde
   (`G.aurores`), donc rien de tout ceci n'a d'occasion de peindre sur un
   univers non déployé. Une classe d'interrupteur en plus serait une
   seconde garde à tenir d'accord avec la première.
   ⚠️ Et le panneau reprend `.modal-overlay` / `.modal-box`, comme
   l'Inventaire : deux panneaux voisins qui ne se ressembleraient pas se
   liraient comme deux mécaniques sans rapport. */

.btq-pastille-ico { font-size: .95rem; }
.btq-pastille-txt { letter-spacing: .04em; }
.btq-pastille-solde {
  margin-left: .35rem; padding: .05rem .35rem; border-radius: 999px;
  background: rgba(250, 204, 21, .16); color: #fde68a;
  font-variant-numeric: tabular-nums; font-weight: 700;
}

.btq-box { max-width: 940px; }
.btq-entete-droite { display: flex; align-items: center; gap: .6rem; }
.btq-solde {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .2rem .6rem; border-radius: 999px;
  background: rgba(250, 204, 21, .14); border: 1px solid rgba(250, 204, 21, .3);
  color: #fde68a; font-weight: 700; font-variant-numeric: tabular-nums;
}
.btq-body { padding-top: .4rem; }
.btq-vide { padding: 2rem 1rem; text-align: center; color: #8aa4c8; }
.btq-erreur { color: #fca5a5; }
.btq-intro {
  margin: 0 0 .9rem; padding: .55rem .7rem; border-radius: 8px;
  background: rgba(56, 189, 248, .08); border-left: 3px solid rgba(56, 189, 248, .45);
  color: #c8d6f0; font-size: .86rem; line-height: 1.45;
}
.btq-intro b { color: #e6f0ff; }

/* ⚠️ `auto-fill` ET `minmax`, PAS UN NOMBRE DE COLONNES FIXE : la grille
   doit tomber à une colonne au téléphone toute seule. Et le plancher est
   NÉGOCIABLE (`min()`), sinon une carte de 260 px dans une fenêtre de
   240 px déborderait sans que la page ne défile pour autant — le défaut
   mesuré en v0.0.86 sur les grilles de la vue Vaisseau Mère. */
.btq-grille {
  display: grid; gap: .7rem;
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
}
.btq-carte {
  display: flex; flex-direction: column; gap: .3rem;
  padding: .7rem .8rem; border-radius: 10px;
  background: rgba(15, 23, 42, .5); border: 1px solid rgba(148, 163, 184, .18);
}
.btq-carte.est-actif { border-color: rgba(74, 222, 128, .42); background: rgba(20, 45, 32, .42); }
.btq-carte.est-avenir { opacity: .62; }
/* ⚠️ `nowrap` — MESURÉ AU NAVIGATEUR. Le portrait prend 56 px là où l'émoji
   d'avant en prenait 18, et « Intelligence Artificielle » — le plus long des
   neuf noms — poussait alors sa pastille « À VENIR » sur une deuxième ligne.
   Rien ne débordait, rien n'était tronqué : la carte était simplement de
   travers à côté de ses deux voisines. Le nom s'enroule donc sur lui-même
   (`min-width: 0`, plus bas) et la pastille garde sa place au bout. */
.btq-carte-tete { display: flex; align-items: center; gap: .45rem; flex-wrap: nowrap; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.118 — LE VISAGE DU MÉTIER EN TÊTE DE CARTE
   ════════════════════════════════════════════════════════════════════
   ⚠️ `flex: 0 0 auto` SUR LA VIGNETTE, ET C'EST LA RÈGLE QUI TIENT LA
   CARTE : `.btq-carte-tete` est un flex qui s'enroule, et une image sans
   base fixe s'y écrase ou déborde selon ce que le nom laisse de place.
   « Intelligence Artificielle » est le cas qui le prouve — c'est le plus
   long des neuf, et c'est lui qui pousse la ligne à passer à deux.

   ⚠️ ET LE NOM DOIT POUVOIR RÉTRÉCIR À CÔTÉ (`min-width: 0`) : sans ça, un
   mot long refuse de s'enrouler et pousse la carte hors de sa colonne — le
   débordement horizontal que rien ne signale (v0.0.86). */
.btq-vignette {
  position: relative; flex: 0 0 auto;
  width: 56px; height: 56px;
}
.btq-photo {
  width: 100%; height: 100%; display: block;
  object-fit: cover; border-radius: 9px;
  border: 1px solid rgba(148, 163, 184, .28);
  background: #0b1120;
}
.btq-carte.est-actif .btq-photo { border-color: rgba(74, 222, 128, .5); }
/* « À venir » : la planche existe, le métier non. Le grisé le dit sans
   cacher le visage — même langage que les cases dormantes de l'inventaire. */
.btq-carte.est-avenir .btq-photo { filter: grayscale(1); }
/* ⚠️ LE NOM DOIT POUVOIR RÉTRÉCIR (`min-width: 0`) puisque la tête de carte
   ne s'enroule plus : sans ça, un mot long refuse de céder et pousse la
   carte hors de sa colonne — le débordement horizontal que rien ne signale
   (v0.0.86). */
.btq-carte-tete .btq-nom { min-width: 0; overflow-wrap: anywhere; }
.btq-carte-tete .btq-encours,
.btq-carte-tete .btq-avenir { flex: 0 0 auto; }

/* Au téléphone la carte occupe toute la largeur : le portrait peut rester
   grand, c'est la place qui manque le moins. On le rétrécit tout de même
   d'un cran pour que le nom et le badge « encore 3 j » tiennent sur la
   même ligne que lui. */
@media (max-width: 700px) {
  .btq-vignette { width: 48px; height: 48px; }
}
.btq-nom { font-weight: 700; color: #e6f0ff; }
.btq-encours {
  margin-left: auto; padding: .05rem .4rem; border-radius: 999px;
  background: rgba(74, 222, 128, .18); color: #86efac; font-size: .72rem; font-weight: 700;
}
.btq-avenir {
  margin-left: auto; padding: .05rem .4rem; border-radius: 999px;
  background: rgba(148, 163, 184, .16); color: #cbd5e1; font-size: .72rem;
  text-transform: uppercase; letter-spacing: .05em;
}
.btq-effet { margin: 0; color: #c8d6f0; font-size: .86rem; line-height: 1.35; }
.btq-portee { margin: 0; color: #8aa4c8; font-size: var(--badge-font-size); font-style: italic; }

.btq-prix { display: flex; gap: .4rem; margin-top: .45rem; flex-wrap: wrap; }
.btq-achat {
  flex: 1 1 5.5rem; display: flex; flex-direction: column; align-items: center; gap: .1rem;
  padding: .4rem .3rem; border-radius: 8px; cursor: pointer;
  background: rgba(30, 41, 59, .8); border: 1px solid rgba(148, 163, 184, .25);
  color: #e6f0ff; font-family: inherit;
}
.btq-achat:hover:not(:disabled) {
  background: rgba(250, 204, 21, .12); border-color: rgba(250, 204, 21, .45);
}
.btq-achat:disabled { opacity: .5; cursor: progress; }
.btq-achat-duree { font-size: .74rem; color: #8aa4c8; }
.btq-achat-prix { font-weight: 700; font-variant-numeric: tabular-nums; color: #fde68a; }

/* ⚠️ AU TÉLÉPHONE, LES TROIS DURÉES RESTENT SUR UNE LIGNE mais rétrécissent :
   empilées, une carte ferait trois fois sa hauteur et la grille demanderait
   un défilement interminable pour six articles. Mesuré à 390 px. */
@media (max-width: 700px) {
  .btq-achat { flex: 1 1 4.2rem; padding: .35rem .2rem; }
  .btq-achat-duree { font-size: .68rem; }
  .btq-intro { font-size: .8rem; }
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteurs auroresUnivers et iaChefDeFile — LES DEUX SOLDES, LA PAUSE
   ════════════════════════════════════════════════════════════════════
   ⚠️ CES CLASSES N'EXISTENT QUE LÀ OÙ LES INTERRUPTEURS SONT DÉPLOYÉS :
   l'écran ne les pose que si le serveur sert les champs. Aucune règle ici
   ne touche un élément d'avant — les deux captures (large et 390 px) le
   vérifient. Textes au plancher de lisibilité (`--badge-font-size`) ou
   au-dessus. */
.btq-soldes {
  display: flex; flex-direction: column; gap: .25rem;
  margin: 0 0 .7rem; padding: .5rem .7rem; border-radius: 8px;
  background: rgba(250, 204, 21, .07); border-left: 3px solid rgba(250, 204, 21, .4);
  color: #e6f0ff; font-size: .86rem; line-height: 1.4;
}
.btq-soldes-ligne b { color: #fde68a; font-variant-numeric: tabular-nums; }
.btq-soldes .res-mini { width: 16px; height: 16px; vertical-align: -3px; }
.btq-sourdine { color: #8aa4c8; }
.btq-ia {
  margin: 0 0 .9rem; padding: .5rem .7rem; border-radius: 8px;
  background: rgba(148, 163, 184, .08); border-left: 3px solid rgba(148, 163, 184, .4);
  color: #c8d6f0; font-size: .86rem; line-height: 1.45;
}
.btq-ia.est-active { background: rgba(167, 139, 250, .1); border-left-color: rgba(167, 139, 250, .55); }
.btq-carte.est-pause { border-color: rgba(167, 139, 250, .45); background: rgba(46, 33, 82, .35); }
.btq-carte.est-pause .btq-photo { filter: grayscale(.7); border-color: rgba(167, 139, 250, .55); }
.btq-enpause {
  flex: 0 0 auto; margin-left: auto; padding: .05rem .4rem; border-radius: 999px;
  background: rgba(167, 139, 250, .2); color: #ddd6fe;
  font-size: var(--badge-font-size); font-weight: 700;
}
.btq-pause-zone { display: flex; }
.btq-pause {
  flex: 1 1 auto; padding: .35rem .6rem; border-radius: 8px; cursor: pointer;
  background: rgba(46, 33, 82, .55); border: 1px solid rgba(167, 139, 250, .4);
  color: #ddd6fe; font-family: inherit; font-size: .82rem; font-weight: 600;
}
.btq-pause:hover:not(:disabled) { background: rgba(91, 66, 160, .55); }
.btq-pause:disabled { opacity: .55; cursor: not-allowed; }
.btq-pause.est-relance { background: rgba(20, 45, 32, .6); border-color: rgba(74, 222, 128, .45); color: #bbf7d0; }
.btq-pause.est-relance:hover:not(:disabled) { background: rgba(28, 70, 46, .7); }
@media (max-width: 700px) {
  .btq-soldes, .btq-ia { font-size: .8rem; }
}
/* L'inventaire : un dormeur reste grisé (il ne sert plus), mais son badge
   dit pourquoi — sinon le joueur croirait avoir perdu ce qu'il a payé. */
body .inv-sp-etat.est-pause { color: #ddd6fe; background: rgba(167, 139, 250, .2); }

/* ════════════════════════════════════════════════════════════════════
   v0.0.100 — LE MONTANT GLOBAL D'UNE VAGUE
   ════════════════════════════════════════════════════════════════════
   Il se pose dans la zone vide que Loryan a entourée : entre la route
   (qui prend la place restante) et la pastille de phase.
   ⚠️ `flex: none` ET `white-space: nowrap` : sans eux, la route — qui est
   en `flex: 1` — écraserait le total sur une vague dont le nom de
   destination est long, et le chiffre se couperait au milieu. Un nombre
   tronqué est le seul défaut d'affichage que rien ne signale. */
body .mv-grp-soute {
  flex: none; white-space: nowrap; cursor: help;
  display: inline-flex; align-items: center; gap: .45rem;
  font-family: var(--mono); font-size: .72rem; color: var(--text2);
  padding: .05rem .4rem; border-radius: 999px;
  background: rgba(52, 211, 153, .10);
  border: 1px solid rgba(52, 211, 153, .24);
  /* ⚠️ LA TRANSITION EST SUR LA COULEUR, PAS SUR LE CHIFFRE. Le total est
     réécrit d'un coup à chaque sondage : un bref éclat dit qu'il vient de
     bouger, là où un nombre qui change sans rien signaler passe inaperçu.
     C'est `.vient-de-bouger` que le tick pose puis retire. */
  transition: background .45s, border-color .45s;
}
body .mv-grp-soute.vient-de-bouger {
  background: rgba(52, 211, 153, .26);
  border-color: rgba(52, 211, 153, .6);
}
body .mv-grp-soute b {
  color: #6ee7b7; font-weight: 700; font-variant-numeric: tabular-nums;
}
body .mv-grp-soute-pct { margin-left: .35rem; color: var(--text3); }
/* ─── Le détail par ressource, sur la ligne ───────────────────────────
   Il est séparé du total par un filet plutôt que par un point médian : à
   cette taille, un séparateur en caractère se confond avec les chiffres. */
body .mv-grp-detail {
  display: inline-flex; align-items: center; gap: .4rem;
  padding-left: .45rem; border-left: 1px solid rgba(52, 211, 153, .28);
}
body .mv-grp-res {
  display: inline-flex; align-items: center; gap: .15rem;
}
body .mv-grp-res .res-mini { width: 13px; height: 13px; }
body .mv-grp-res-n { font-variant-numeric: tabular-nums; }
/* Au téléphone la route est déjà masquée (règle de la v0.0.11) et l'en-tête
   est serré : le total et le détail restent, c'est le POURCENTAGE qui part —
   c'est le chiffre le moins utile des trois, et le seul qu'on retrouve d'un
   coup d'œil en dépliant. */
@media (max-width: 700px) {
  body .mv-grp-soute-pct { display: none; }
  body .mv-grp-soute {
    font-size: .66rem; padding: .05rem .3rem; gap: .3rem;
  }
  body .mv-grp-detail { gap: .28rem; padding-left: .3rem; }
  body .mv-grp-res .res-mini { width: 11px; height: 11px; }
  /* ⚠️ AU TÉLÉPHONE, LA PASTILLE PREND SA PROPRE LIGNE — et sans ça le
     détail était COUPÉ AU BORD. Mesuré à 390 px : la pastille fait 201 px
     dans une rangée d'environ 330, donc les deux dernières ressources
     tombaient au-delà du bord droit — et `.mv-row-tete` est en
     `overflow: hidden`, donc elles n'étaient même pas atteignables au
     défilement. Rien ne le signalait : la PAGE, elle, ne débordait pas.
     ⚠️ `order` ET PAS UN DÉPLACEMENT DANS LE HTML : la rangée sert aussi au
     grand écran, où la pastille doit rester entre la route et la pastille de
     phase. Un seul rendu, deux mises en page. */
  body .mv-groupe > .mv-groupe-tete { flex-wrap: wrap; }
  body .mv-groupe > .mv-groupe-tete .mv-grp-soute {
    order: 9; flex: 1 0 100%; margin-top: .3rem; justify-content: flex-start;
  }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.100 — LA PASTILLE « BOUTIQUE » AVAIT UN LIBELLÉ INVISIBLE
   ════════════════════════════════════════════════════════════════════
   Signalement de Loryan, capture à l'appui : « la boutique ne se voit
   pas ». Et il ne s'agissait pas d'un contraste trop faible — le mot
   n'avait AUCUNE couleur à lui.

   ⚠️ `.btq-pastille` N'AVAIT JAMAIS EU DE RÈGLE DE BASE. Trois règles
   existaient pour ses ENFANTS (`-ico`, `-txt`, `-solde`) et une pour son
   fond (`.pastille-discrete`), mais rien pour le bouton lui-même : il
   restait donc sur la couleur de texte par défaut d'un `<button>`, que le
   navigateur met en NOIR. Sur un bandeau sombre, le mot disparaît. L'icône
   (un emoji, qui porte ses propres couleurs) et le solde (qui a sa règle)
   restaient visibles — d'où une pastille qui semble n'avoir jamais eu de
   libellé plutôt qu'un libellé illisible.

   ⚠️ ELLE REPREND TRAIT POUR TRAIT `.inv-pastille`, sa voisine immédiate :
   deux boutons côte à côte qui ne se ressemblent pas se lisent comme deux
   natures de choses, alors que ce sont deux points d'entrée du même rang.
   ⚠️ AUCUNE CLASSE `f-…` ICI, ET C'EST LA MÊME RAISON QUE LE BLOC DE LA
   v0.0.99 : la pastille ne s'affiche que si le serveur sert un solde
   d'Aurores, donc rien de tout ceci n'a d'occasion de peindre sur un
   univers où la boutique n'est pas déployée. */
.btq-pastille {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  background: #1a2540; border: 1px solid #324569; border-radius: 999px;
  color: #cbd6ea; cursor: pointer;
  font-family: var(--mono); font-size: .75rem;
  transition: all .15s;
}
.btq-pastille:hover { background: #243049; border-color: #4a6294; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.100 — LES SPÉCIALISTES EN VIGNETTES
   ════════════════════════════════════════════════════════════════════
   Une grille de carrés, un par métier. La teinte de chacun arrive du JS
   dans `--sp-teinte` : elle vient de la même table que le portrait, donc
   le cadre, l'ombre et le nom ne peuvent pas se retrouver d'une couleur
   quand le casque en porte une autre.

   ⚠️ LE GRISÉ EST UN `filter`, PAS UNE SECONDE PALETTE. Repeindre chaque
   portrait en gris demanderait un second jeu de couleurs à tenir d'accord
   avec le premier — et le jour où l'un des deux bouge, un métier serait
   coloré éteint et gris allumé. `grayscale(1)` retire la couleur de ce qui
   est DÉJÀ dessiné, emoji compris. */
body .inv-sp-grille {
  display: grid; gap: .55rem;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
}
body .inv-sp-case {
  display: flex; flex-direction: column; align-items: center; gap: .25rem;
  padding: .55rem .35rem .5rem; border-radius: 10px;
  background: rgba(255, 255, 255, .03);
  border: 1px solid rgba(110, 130, 170, .22);
  text-align: center; cursor: help;
  transition: border-color .15s, background .15s;
}
body .inv-sp-vignette {
  position: relative; width: 100%; max-width: 68px; aspect-ratio: 1;
  display: flex; align-items: center; justify-content: center;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.118 — LE PORTRAIT PEINT
   ════════════════════════════════════════════════════════════════════
   ⚠️ `object-fit: cover` ET NON `contain`, ET LA DIFFÉRENCE SE VOIT : les
   neuf planches sont carrées aujourd'hui, mais `contain` laisserait deux
   bandes vides le jour où l'une arrive en 4/3 — un cadre de 68 px qui ne
   contient plus que 51 px de visage. `cover` remplit toujours la case et
   rogne les bords, ce qui, sur un portrait cadré au buste, ne coûte rien.

   ⚠️ AUCUN `filter` ICI : le grisé des métiers éteints est posé sur
   `.inv-sp-vignette`, donc sur le PARENT, et il descend sur la photo. Le
   redire ici donnerait deux réglages à tenir d'accord pour un seul effet.

   Le liseré reprend `--sp-teinte`, la même que le cadre et la pastille de
   durée : sur un fond de planche presque noir, sans lui, le portrait se
   fondrait dans la case. */
body .inv-sp-photo {
  width: 100%; height: 100%; display: block;
  object-fit: cover; border-radius: 9px;
  border: 1px solid color-mix(in srgb, var(--sp-teinte, #8aa4c8) 45%, transparent);
  background: #0b1120;
}
body .inv-sp-case.est-actif .inv-sp-photo {
  border-color: color-mix(in srgb, var(--sp-teinte, #8aa4c8) 75%, transparent);
}
body .inv-sp-nom {
  font-size: .68rem; line-height: 1.15; color: var(--text2);
  /* ⚠️ Le nom s'enroule sur deux lignes plutôt que de se couper :
     « Intelligence Artificielle » ne tient pas sur 104 px, et un mot tronqué
     est le seul défaut de largeur que rien ne signale. */
  overflow-wrap: anywhere;
}
body .inv-sp-duree,
body .inv-sp-etat {
  font-family: var(--mono); font-size: .62rem; line-height: 1;
  padding: .12rem .35rem; border-radius: 999px;
}
body .inv-sp-duree {
  color: #0b1120; background: var(--sp-teinte, #8aa4c8); font-weight: 700;
}
body .inv-sp-duree.bientot { background: #fbbf24; }
body .inv-sp-etat { color: var(--text3); background: rgba(255,255,255,.04); }

/* EN SERVICE — le portrait garde ses couleurs, le cadre les reprend. */
body .inv-sp-case.est-actif {
  border-color: color-mix(in srgb, var(--sp-teinte) 55%, transparent);
  background: color-mix(in srgb, var(--sp-teinte) 9%, transparent);
}
body .inv-sp-case.est-actif .inv-sp-nom { color: var(--text); }

/* PAS EN SERVICE — grisé, mais JAMAIS effacé : il faut pouvoir lire ce
   qu'on n'a pas, c'est tout l'objet de la grille. D'où une opacité qui
   reste au-dessus du seuil de lisibilité. */
body .inv-sp-case.est-dormant .inv-sp-vignette,
body .inv-sp-case.est-avenir .inv-sp-vignette {
  filter: grayscale(1); opacity: .5;
}
body .inv-sp-case.est-avenir { border-style: dashed; }
body .inv-sp-case:hover { border-color: rgba(110, 130, 170, .5); }

/* Au téléphone : trois colonnes plutôt qu'une grille qui s'étire, et le
   portrait rétréci pour que les neuf métiers restent visibles d'un coup
   d'œil sans faire défiler tout le panneau. */
@media (max-width: 700px) {
  body .inv-sp-grille {
    grid-template-columns: repeat(3, 1fr); gap: .4rem;
  }
  body .inv-sp-vignette { max-width: 54px; }
  body .inv-sp-nom { font-size: .62rem; }
  body .inv-sp-case { padding: .4rem .2rem .4rem; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.100 — L'HORLOGE DES DÉPLOIEMENTS PROGRAMMÉS
   ════════════════════════════════════════════════════════════════════
   ⚠️ AUCUNE CLASSE `f-…`, ET C'EST DÉLIBÉRÉ : l'hôte reste `hidden` tant
   que le serveur n'annonce rien, et il n'annonce rien hors interrupteur.
   Une seconde garde serait une seconde vérité à tenir d'accord avec la
   première.

   ⚠️ LA RÈGLE `[hidden]` EST OBLIGATOIRE, et c'est la CINQUIÈME fois que
   ce piège se présente dans ce projet (badge de la cloche v2.69,
   convalescence v0.0.71, bandeau rouge v0.0.72, impact v0.0.91) : un
   sélecteur d'IDENTIFIANT qui pose `display` bat la règle native
   `[hidden] { display: none }` — la pastille resterait affichée en
   permanence, vide. */
#deploiement-horloge-host { display: inline-flex; align-items: center; }
#deploiement-horloge-host[hidden] { display: none; }

.dep-horloge {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .18rem .45rem; border-radius: 6px;
  background: transparent; border: 1px solid rgba(251, 191, 36, .35);
  color: #fcd34d; cursor: pointer;
  font-family: var(--mono); font-size: .7rem; line-height: 1.2;
  transition: background .15s, border-color .15s;
}
.dep-horloge:hover { background: rgba(251, 191, 36, .12); border-color: rgba(251, 191, 36, .6); }
.dep-horloge-cdr { font-variant-numeric: tabular-nums; }
.dep-horloge-n {
  padding: 0 .28rem; border-radius: 999px;
  background: rgba(251, 191, 36, .18); font-weight: 700; font-size: .64rem;
}

/* Le panneau de survol. ⚠️ `position: fixed` ET PLACÉ PAR LE JS : le
   bandeau du haut est en `overflow: hidden`, donc un panneau posé dedans
   serait coupé à son bord — et rien ne le signalerait, puisque la page ne
   déborde pas pour autant.
   ⚠️ `pointer-events: none` : sans lui, le panneau se glisse sous la
   souris, déclenche le `mouseout` de la pastille, disparaît — et l'écran
   clignote tant qu'on ne bouge plus (leçon du tableau de fenêtre de vol,
   v0.0.92). */
.dep-panneau {
  position: fixed; z-index: 400; pointer-events: none;
  width: min(340px, 92vw); max-height: 60vh; overflow: hidden;
  padding: .55rem .65rem; border-radius: 9px;
  background: #0b1120; border: 1px solid rgba(251, 191, 36, .3);
  box-shadow: 0 10px 26px rgba(0, 0, 0, .55);
}
.dep-panneau-titre {
  font-family: var(--hud); font-size: .58rem; letter-spacing: .1em;
  text-transform: uppercase; color: #fcd34d; margin-bottom: .4rem;
}
.dep-ligne { padding: .35rem 0; border-top: 1px solid rgba(110, 130, 170, .18); }
.dep-ligne:first-of-type { border-top: 0; }
.dep-ligne-tete { display: flex; align-items: baseline; gap: .35rem; flex-wrap: wrap; }
.dep-ligne-nom { color: var(--text); font-size: .74rem; line-height: 1.3; }
/* v0.0.7 — le numéro du journal quand la ligne
   y est déjà (« v0.0.7-c »). */
.dep-ligne-version { font-family: var(--mono); font-size: .66rem; color: var(--gold);
  border: 1px solid rgba(255, 200, 100, .35); border-radius: 4px; padding: 0 .3rem; }
.dep-ligne-cdr { margin-top: .15rem; font-size: .7rem; color: var(--text2); }
.dep-ligne-cdr b { color: #fcd34d; font-family: var(--mono); font-variant-numeric: tabular-nums; }
/* Dans la fenêtre du jeu (le chemin du téléphone), les lignes se lisent sur
   fond de fenêtre : le cadre du panneau flottant n'a plus lieu d'être. */
.dep-fenetre .dep-ligne { border-top-color: rgba(110, 130, 170, .25); }

@media (max-width: 700px) {
  /* Le bandeau est serré au téléphone : l'horloge garde son icône et son
     compte à rebours, c'est le multiplicateur qui part — le panneau au
     clic le redit, et il y a de la place là-bas. */
  .dep-horloge { font-size: .62rem; padding: .15rem .32rem; }
  .dep-horloge-n { display: none; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.100 — LA FICHE D'UN SOLEIL
   ════════════════════════════════════════════════════════════════════
   ⚠️ ELLE NE PARTAGE AUCUNE RÈGLE AVEC `#planet-panel`, et c'est délibéré.
   Ajouter cet identifiant aux sélecteurs de la fiche de planète ferait
   porter à un écran DÉPLOYÉ le risque de tout réglage fait ici — c'est la
   règle des fonctions parallèles posée en v0.0.74 pour le Manuel. Une
   trentaine de lignes de géométrie recopiées contre un panneau qui n'a rien
   demandé : l'arbitrage est le même.

   ⚠️ LE `body ` DE TÊTE N'EST PAS DÉCORATIF : sans lui, ces règles perdent
   un point de spécificité et celles du panneau générique repassent devant
   (leçon de la sixième purge, v0.0.87). */
body #soleil-panel {
  position: absolute; top: 0; bottom: 0; right: 0;
  width: 320px; max-width: 40vw;
  padding: 1.1rem; z-index: 101; display: none;
  background: rgba(5, 8, 16, .97);
  border-left: 1px solid var(--border2);
  overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-color: rgba(120, 150, 220, .35) transparent;
}
body #soleil-panel .btn-close { position: absolute; top: .6rem; right: .6rem; }
body .sp-titre {
  display: flex; align-items: center; gap: .45rem;
  font-family: var(--hud); font-size: .85rem; color: var(--text);
  padding-right: 2rem;
}
/* Le disque du soleil, à sa vraie couleur : c'est le seul rappel visuel qui
   relie la fiche à ce qu'on vient de cliquer sur la carte. */
body .sp-astre {
  flex: none; width: 18px; height: 18px; border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #fff6, var(--sp-teinte));
  box-shadow: 0 0 10px var(--sp-teinte);
}
body .sp-nom { overflow-wrap: anywhere; }
body .sp-coords {
  font-family: var(--mono); font-size: .7rem; color: var(--text3);
}
body .sp-sous { font-size: .7rem; color: var(--text2); margin: .25rem 0 .9rem; }
body .sp-bloc {
  margin-bottom: .9rem; padding: .55rem .65rem; border-radius: 8px;
  background: rgba(255, 255, 255, .03);
  border: 1px solid rgba(110, 130, 170, .2);
}
body .sp-lbl {
  font-family: var(--hud); font-size: .58rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text3); margin-bottom: .3rem;
}
/* Interrupteur ficheSoleilTitres — demande de Loryan : « grossis les titres
   de la fiche ». Les titres de rubrique sortaient à 8,1 px, sous le plancher
   du projet (11 px, cf. --badge-font-size) ; ils passent à .8rem (11,2 px).
   ⚠️ L'ESPACEMENT DES LETTRES BAISSE AVEC, ET CE N'EST PAS DÉCORATIF : en
   Orbitron majuscule à .14em, « Héliures encore dans ce soleil » mesure
   249 px ; la fiche ne fait que 40vw, soit 228 px de texte à 701 px de
   large — le titre y passait sur deux lignes. À .08em aussi (mesuré) ; à
   .06em, il tient sur une, de 701 px au plus large écran. */
body.f-ficheSoleilTitres .sp-lbl { font-size: .8rem; letter-spacing: .06em; }
body .sp-peri { font-family: var(--mono); font-size: .95rem; }
body .sp-heliures { font-size: .95rem; color: var(--text); }
body .sp-heliures b { font-family: var(--mono); font-size: 1.15rem; }
body .sp-h-jaune b { color: #fbbf24; }
body .sp-h-rouge b { color: #f87171; }
body .sp-h-bleu  b { color: #60a5fa; }
body .sp-h-couleur {
  margin-left: .35rem; font-size: .68rem; color: var(--text3);
  text-transform: uppercase; letter-spacing: .06em;
}
body .sp-note { margin: .35rem 0 0; font-size: .68rem; color: var(--text2); line-height: 1.35; }
/* ⚠️ v0.0.163 — CES TROIS RÈGLES VIVENT ICI, ET PAS AVEC LEUR BOUTON quelque
   mille lignes plus haut : `body .sp-note` a la même spécificité et est
   déclarée APRÈS, donc elle gagnait. Mesuré au navigateur : la note de la
   fiche « non cartographié » sortait à 9,52 px au lieu des 11,2 px voulus,
   sans que rien ne le signale — une taille de police n'a pas d'erreur.
   La note est le CONTENU de cette fiche-là, pas le commentaire d'un chiffre :
   elle se lit à .8rem (11,2 px), au-dessus du plancher du projet (11 px,
   cf. --badge-font-size). */
body .sp-note-inconnu { font-size: .8rem; color: var(--text); margin: 0; }
/* Le disque reste neutre : lui donner la teinte du soleil reviendrait à
   livrer la couleur de l'étoile, que la fiche est justement censée taire. */
body .sp-titre-inconnu .sp-astre {
  background: radial-gradient(circle at 35% 35%, #ffffff22, #4b5a78);
  box-shadow: none;
}
body .sp-titre-inconnu .sp-nom { color: var(--text2); }
body .sp-vide { font-size: .74rem; color: var(--text2); padding: .4rem 0; }
body .sp-erreur { color: #fca5a5; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.112 — LES RESSOURCES DU SYSTÈME ET SES PLANÈTES
   ════════════════════════════════════════════════════════════════════
   Deux listes dans une colonne de 320 px, et c'est TOUTE la difficulté :
   quatorze planètes portant chacune jusqu'à six parts de sol, sans que rien
   ne déborde ni ne se tronque. D'où les trois partis pris ci-dessous.

   ⚠️ LA GRILLE DES RESSOURCES EST À QUATRE COLONNES DE LARGEURS FIXÉES, et
   la barre est la SEULE élastique (`1fr`). Un `flex` aurait laissé le nom
   « Palladrine » pousser la barre hors du panneau — le débordement
   horizontal qu'aucune erreur ne signale.

   ⚠️ LES PARTS D'UNE PLANÈTE PASSENT À LA LIGNE (`flex-wrap`) plutôt que de
   défiler : une ligne qui défile toute seule dans un panneau qui défile déjà
   est impossible à attraper au doigt.

   ⚠️ ET LES DEUX LISTES SONT BORNÉES EN HAUTEUR AU TÉLÉPHONE (voir la media
   query plus bas) : le tiroir mobile fait 52 vh, quatorze planètes le
   rempliraient à elles seules et le Périclitement — le chiffre pour lequel
   on ouvre cette fiche — sortirait de l'écran. */
body .sp-reslist { display: grid; gap: .22rem; }
body .sp-res {
  display: grid; grid-template-columns: 5.6rem 1fr 2.4rem 2.2rem;
  align-items: center; gap: .35rem;
  font-size: .72rem; color: var(--text);
}
body .sp-res-nom {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body .sp-res-rail {
  height: 6px; border-radius: 3px; overflow: hidden;
  background: rgba(255, 255, 255, .07);
}
body .sp-res-rail i { display: block; height: 100%; border-radius: 3px; }
body .sp-res-part {
  font-family: var(--mono); font-size: .7rem; text-align: right; color: var(--text);
}
body .sp-res-ou {
  font-family: var(--mono); font-size: .64rem; text-align: right; color: var(--text3);
}

body .sp-pllist { display: grid; gap: .3rem; }
body .sp-pl {
  display: grid; grid-template-columns: 1fr; gap: .1rem;
  padding: .28rem .35rem; border-radius: 6px;
  background: rgba(255, 255, 255, .025);
}
/* v0.0.x — une planète cliquable ouvre sa fiche (classe posée par
   soleil-panel.js sur chaque ligne de planète). */
body .sp-pl-cliquable { cursor: pointer; transition: background .12s; }
body .sp-pl-cliquable:hover { background: rgba(120, 170, 255, .14); }
body .sp-pl-nom {
  font-size: .72rem; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body .sp-pl-compo { display: flex; flex-wrap: wrap; gap: .2rem .35rem; }
/* Interrupteur nuageRetombe : la note du nuage tombé, et sa marque sur la
   planète. ⚠️ PAS `.sp-note` : elle est à 0,68 rem, sous le plancher de
   lisibilité — c'est un renseignement qu'on vient chercher, il se lit. */
body .sp-note-nuage { margin: .35rem 0 0; font-size: .82rem; color: var(--text2); line-height: 1.45; }
body .sp-note-nuage b { color: var(--text); }
body .sp-pl-nuage { font-size: .8rem; }
/* Interrupteur soleilsTailles : la taille du soleil, sur sa fiche. */
body .sp-taille { font-size: .86rem; color: var(--text); line-height: 1.4; }
body .sp-taille b { font-size: 1rem; }
body .sp-taille-mot { color: var(--text2); }
body .sp-pl-res {
  font-family: var(--mono); font-size: .62rem; color: var(--sp-res, var(--text2));
  white-space: nowrap;
}

/* Au téléphone, la colonne de droite n'existe pas : la fiche devient un
   TIROIR ancré en bas, borné en hauteur, comme celle de la planète depuis
   la v0.0.84. Un panneau de 320 px sur un écran de 390 recouvrirait la
   carte entière — c'est-à-dire ce qu'on vient de cliquer. */
@media (max-width: 700px) {
  body #soleil-panel {
    top: auto; bottom: 0; left: 0; right: 0;
    width: 100%; max-width: 100%; max-height: 52vh;
    border-left: none; border-top: 1px solid var(--border2);
    border-radius: 12px 12px 0 0;
    /* ⚠️ LA MARGE DU BAS N'EST PAS DÉCORATIVE : le bouton flottant des
       Vaisseaux Mères (`#vmp-mobile-toggle`) vit dans le coin bas-droit, à
       un z-index supérieur, et il recouvrait la dernière ligne de la fiche.
       Un texte à demi caché est le genre de défaut que rien ne signale — la
       page ne déborde pas pour autant. Vu au téléphone, pas à la relecture. */
    padding-bottom: 3.4rem;
  }
  /* v0.0.112 : les deux listes se plafonnent et défilent pour elles-mêmes.
     Sans ça, un système à quatorze planètes repousse le Périclitement hors
     du tiroir — et c'est le chiffre pour lequel on ouvre cette fiche. */
  body .sp-reslist,
  body .sp-pllist {
    max-height: 15vh; overflow-y: auto; overscroll-behavior: contain;
  }
  /* La colonne du nom rétrécit : sur 390 px, 5,6 rem ne laissaient plus rien
     à la barre. */
  body .sp-res { grid-template-columns: 4.8rem 1fr 2.2rem 2rem; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.101 — LE CHANTIER D'UN MODULE
   ════════════════════════════════════════════════════════════════════
   Développer un module n'est plus instantané : l'Expérience part tout de
   suite, le niveau tombe à la fin d'un compte à rebours. La vignette
   remplace alors sa ligne de prix par ce qui reste à attendre.

   ⚠️ LA GARDE EST UNE CLASSE DU CORPS, jamais un test JS : cet écran se
   dessine parfois AVANT l'arrivée des interrupteurs (même parade que
   `bandeauSansProgression`, v0.0.49). Le JS ne pose la ligne que sous le
   flag, le CSS ne la peint que sous la classe — les deux moitiés disent la
   même chose, et aucune ne peut peindre seule sur un univers non déployé. */
body .mod-tile-dev {
  display: block; margin-top: 0.4rem;
  font-family: var(--mono); font-size: 0.64rem; color: var(--accent2);
}
/* ⚠️ LE CONTOUR PALPITE, LA BORDURE NE BOUGE PAS. Une bordure animée
   décalerait toute la grille à chaque battement — leçon du panneau de vol
   (v0.0.55) et du bandeau rouge (v0.0.72). On n'anime donc que l'ombre. */
body .mod-tile.is-en-chantier {
  border-color: var(--accent2);
  animation: mod-chantier-pulse 2.4s ease-in-out infinite;
}
@keyframes mod-chantier-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(56, 189, 248, 0); }
  50%      { box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.35); }
}
body .mod-dev-note {
  border-left: 2px solid var(--accent2);
  padding-left: 0.6rem;
}
/* ⚠️ `prefers-reduced-motion` : un contour qui palpite en permanence est
   exactement ce que ce réglage système demande de couper. La bordure reste,
   c'est elle qui porte l'information. */
@media (prefers-reduced-motion: reduce) {
  body .mod-tile.is-en-chantier { animation: none; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.131 — LA FILE QUI REFUSE, DITE AVANT LE CLIC
   ════════════════════════════════════════════════════════════════════
   Un développement de module occupe un emplacement dans la file des
   bâtiments ET dans celle des recherches (v0.0.123). Quand l'une des deux
   est pleine, la vignette remplace sa
   ligne de prix par le refus, et le panneau pose la note qui l'explique.

   ⚠️ MÊME GABARIT QUE `.mod-tile-dev` ET `.mod-dev-note` juste au-dessus,
   au ton près : ce sont les deux autres états « on ne peut pas cliquer
   maintenant » de la même vignette et du même panneau, et trois
   présentations différentes pour un même sens se lisent comme trois
   mécaniques. */
/* ⚠️ LA RÉSERVE À DROITE N'EST PAS DÉCORATIVE. La pastille d'entretien de
   la vignette est posée en `position: absolute` (right: 8px, bottom: 7px) :
   elle ne prend AUCUNE place dans le flux, et cette ligne-ci est justement
   la dernière. Sans la réserve, un libellé un peu long passe DESSOUS et se
   lit tronqué — le défaut réparé en v0.0.86 sur `.bld-tile`, dont la note
   disait que `.mod-tile` n'avait « rien à réparer » parce que sa ligne de
   prix était courte. Elle ne l'est plus dans cet état-ci. */
.mod-tile-cost.mod-tile-file { color: var(--accent1); padding-right: 3.4rem; }
.md-desc.mod-file-note {
  border-left: 2px solid var(--accent1);
  padding-left: 0.6rem;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — L'ICÔNE 💬 DE LA BARRE, EN DEUX
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : « en cliquant sur l'icône du haut du petit icône du
   chat on arrive sur le chat global et pour le bas qu'on arrive sur ses
   messages privés », avec « des pastilles avec les nombres de messages ».

   Deux zones invisibles empilées couvrent le bouton — moitié haute, moitié
   basse — et deux pastilles chiffrées se posent à droite, l'une en haut
   (chat global), l'autre en bas (messages privés).

   ⚠️ LA PASTILLE AGRÉGÉE S'EFFACE quand les deux la remplacent : les
   trois au même endroit se chevaucheraient, et le total ne dirait plus
   rien que les deux détails ne disent mieux. */
.dsh-tab[data-view="social"] { position: relative; }

.social-zone {
  display: block;
  position: absolute;
  left: 0;
  right: 0;
  height: 50%;
  cursor: pointer;
  z-index: 2;
}
.social-zone-haut { top: 0; }
.social-zone-bas  { bottom: 0; }

/* Un liseré au survol dit LAQUELLE des deux moitiés va répondre — sans lui,
   deux cibles invisibles sur une icône de quarante pixels se devinent au
   hasard. */
.social-zone-haut:hover {
  box-shadow: inset 0 2px 0 0 var(--tab-active-color, #60a5fa);
}
.social-zone-bas:hover {
  box-shadow: inset 0 -2px 0 0 var(--tab-active-color, #60a5fa);
}

.social-pastille {
  display: none;                    /* ré-affiché en `block` par le module */
  position: absolute;
  right: 2px;
  z-index: 3;
  min-width: 15px;
  padding: 0 3px;
  border-radius: var(--radius-pill);
  background: var(--color-error-fg);
  color: #fff;
  font-size: 9px;
  font-weight: var(--font-weight-semibold);
  line-height: 15px;
  text-align: center;
  pointer-events: none;             /* c'est la ZONE dessous qui reçoit le clic */
}
.social-pastille-chat { top: 2px; }
.social-pastille-pm   { bottom: 2px; }

/* Sur téléphone, la barre latérale est plus étroite : on rentre les
   pastilles pour qu'elles ne débordent pas du bouton. */
@media (max-width: 700px) {
  .social-pastille {
    right: 1px;
    min-width: 13px;
    font-size: 8px;
    line-height: 13px;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   v0.0.8 — LA SECTION SUCCÈS
   ═══════════════════════════════════════════════════════════════════
   Cent cinquante-cinq cartes en treize familles. Trois contraintes ont
   dessiné ce bloc, et ce sont les trois défauts que ce projet attrape le
   plus souvent :

   1. RIEN NE DOIT DÉPASSER. Un titre long (« La Présence Galactique »)
      dans une carte de 240 px sur téléphone sort de l'écran sans qu'aucune
      erreur ne le signale. D'où `min-width: 0` sur les enfants de flex et
      de grille — sans lui, un enfant de flex refuse de rétrécir sous la
      largeur de son contenu, et c'est LA cause de débordement horizontal
      la plus fréquente du projet.
   2. RIEN NE DOIT ÊTRE TRONQUÉ. Les noms de rang (« Celui qui n'arrive
      jamais ») passent à la ligne plutôt que d'être coupés au milieu d'un
      mot — les trente-cinq pixels du tiroir des files (v0.0.82) ont montré
      qu'un texte coupé est le seul indice visible d'une largeur trop
      étroite.
   3. TOUT DOIT RESTER ATTEIGNABLE. Les puces de filtre sont des vraies
      cibles de 32 px de haut au doigt, et la barre de filtres ne colle
      pas : collante, elle recouvrirait la première rangée de cartes sur
      un écran de téléphone.
   ═══════════════════════════════════════════════════════════════════ */

#sc-contenu { max-width: 1400px; }

/* ── Le bandeau de résumé ─────────────────────────────────────────── */
.sc-resume {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px;
  padding: 18px 20px;
  margin-bottom: 20px;
  border: 1px solid var(--border2);
  border-radius: 14px;
  background: linear-gradient(135deg, rgba(245, 158, 11, .06), rgba(13, 21, 38, .6));
}
.sc-anneau { width: 120px; height: 120px; flex: 0 0 auto; }
.sc-anneau-val {
  fill: var(--text); font-family: var(--mono); font-size: 22px;
  text-anchor: middle; dominant-baseline: middle;
}
.sc-anneau-lbl {
  fill: var(--text3); font-size: 10px; letter-spacing: .08em;
  text-anchor: middle; text-transform: uppercase;
}
/* `min-width: 0` : sans lui, ce bloc refuse de rétrécir sous la largeur de
   sa plus longue phrase et pousse l'anneau hors du cadre. */
.sc-resume-chiffres { flex: 1 1 240px; min-width: 0; }
.sc-gros { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.sc-gros b { font-family: var(--mono); font-size: 26px; color: var(--gold); }
.sc-gros span { color: var(--text2); font-size: 13px; }
.sc-note { margin: 8px 0 0; color: var(--text3); font-size: 12px; line-height: 1.5; }
.sc-note strong { color: var(--text2); }

.sc-raretes { list-style: none; margin: 0; padding: 0; flex: 0 1 200px; min-width: 0; }
.sc-raretes li {
  display: flex; align-items: center; gap: 8px;
  padding: 2px 0; font-size: 12px; color: var(--text2);
}
.sc-puce { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.sc-rar-nom { flex: 1 1 auto; min-width: 0; }
.sc-rar-n { font-family: var(--mono); color: var(--text3); }

/* ── Les derniers décrochés ───────────────────────────────────────── */
.sc-recents { margin-bottom: 20px; }
.sc-recents-liste {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 8px;
}
.sc-recents-liste li {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 12px;
  border: 1px solid var(--sc-rarete, var(--border2));
  border-radius: var(--radius-pill, 999px);
  background: color-mix(in srgb, var(--sc-rarete, #888) 10%, transparent);
  font-size: 12px;
  max-width: 100%; min-width: 0;
}
.sc-recent-nom { color: var(--text); overflow-wrap: anywhere; }
.sc-recent-date { color: var(--text3); font-family: var(--mono); font-size: 11px; }

/* ── Les filtres ──────────────────────────────────────────────────── */
/* ⚠️ PAS DE `position: sticky` ICI. Sur un téléphone, cette barre fait
   trois lignes : collante, elle mangerait le tiers haut de l'écran et
   recouvrirait la première rangée de cartes — le troisième défaut de la
   liste en tête de bloc, « tout doit rester atteignable ». */
.sc-filtres { margin-bottom: 18px; }
.sc-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.sc-chips-bas { margin-top: 8px; align-items: center; }
.sc-chip {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 32px;                 /* cible au doigt */
  padding: 4px 12px;
  border: 1px solid var(--border2);
  border-radius: var(--radius-pill, 999px);
  background: var(--card);
  color: var(--text2);
  font-family: inherit; font-size: 12px;
  cursor: pointer;
  transition: border-color .15s, color .15s, background .15s;
}
.sc-chip:hover { border-color: var(--sc-fam, var(--accent2)); color: var(--text); }
.sc-chip.is-active {
  border-color: var(--sc-fam, var(--gold));
  background: color-mix(in srgb, var(--sc-fam, #f59e0b) 16%, var(--card));
  color: var(--text);
}
.sc-chip i { font-style: normal; font-family: var(--mono); font-size: 11px; color: var(--text3); }
.sc-chip.is-active i { color: var(--text2); }
.sc-recherche {
  flex: 1 1 180px; min-width: 0; max-width: 320px;
  min-height: 32px; padding: 4px 12px;
  border: 1px solid var(--border2); border-radius: var(--radius-pill, 999px);
  background: var(--bg2); color: var(--text);
  font-family: inherit; font-size: 12px;
}
.sc-recherche:focus { outline: none; border-color: var(--accent2); }

/* ── Une famille ──────────────────────────────────────────────────── */
.sc-famille { margin-bottom: 28px; }
.sc-famille-titre {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 0 0 2px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
  color: var(--sc-fam, var(--text));
  font-size: var(--text-h2, 20px); font-weight: 600;
}
.sc-famille-titre i {
  font-style: normal; font-family: var(--mono); font-size: 12px;
  color: var(--text3); margin-left: auto;
}
.sc-famille-desc { margin: 0 0 12px; color: var(--text3); font-size: 12px; }

/* ⚠️ `minmax(0, 1fr)` ET NON `1fr` : `1fr` vaut `minmax(auto, 1fr)`, donc
   une colonne refuse de descendre sous la largeur de son contenu — un seul
   mot long y suffit à faire déborder toute la grille. */
.sc-grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
  gap: 12px;
}

/* ── Une carte ────────────────────────────────────────────────────── */
.sc-carte {
  display: flex; flex-direction: column; gap: 8px;
  min-width: 0;                     /* cf. l'avertissement en tête de bloc */
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--border2);
  border-radius: 10px;
  background: var(--card);
  opacity: .62;                     /* un succès jamais décroché reste en retrait */
  transition: opacity .15s, border-color .15s;
}
.sc-carte.is-acquis { opacity: 1; border-left-color: var(--sc-rarete); }
.sc-carte.is-finie {
  background: linear-gradient(135deg, color-mix(in srgb, var(--sc-rarete) 12%, var(--card)), var(--card));
  border-color: color-mix(in srgb, var(--sc-rarete) 40%, var(--border));
}
.sc-carte-tete { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.sc-ico { font-size: 20px; line-height: 1.2; flex: 0 0 auto; }
.sc-titres { flex: 1 1 auto; min-width: 0; }
.sc-titre {
  margin: 0; font-size: 14px; font-weight: 600; color: var(--text);
  overflow-wrap: anywhere;          /* on passe à la ligne, on ne tronque pas */
}
.sc-rang-nom {
  margin: 1px 0 0; font-size: 11px; color: var(--sc-rarete);
  font-style: italic; overflow-wrap: anywhere;
}
.sc-rang {
  flex: 0 0 auto;
  font-family: var(--mono); font-size: 15px; color: var(--sc-rarete);
  white-space: nowrap;
}
.sc-rang i { font-style: normal; font-size: 10px; color: var(--text3); }
.sc-quoi { margin: 0; font-size: 11.5px; line-height: 1.45; color: var(--text2); }

.sc-barre {
  height: 6px; border-radius: 3px; overflow: hidden;
  background: rgba(110, 130, 170, .16);
}
.sc-barre span { display: block; height: 100%; border-radius: 3px; }
.sc-barre.is-finie span { background: var(--sc-rarete); }
.sc-chiffres {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-size: 11px; min-width: 0;
}
.sc-chiffres b { font-family: var(--mono); color: var(--text); overflow-wrap: anywhere; }
.sc-chiffres span { color: var(--text3); overflow-wrap: anywhere; }

.sc-pied {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  padding-top: 4px; border-top: 1px solid var(--border);
  font-size: 10px; min-width: 0;
}
.sc-rarete { text-transform: uppercase; letter-spacing: .06em; }
.sc-date { color: var(--text3); white-space: nowrap; }
.sc-dim { color: var(--text3); opacity: .7; }

.sc-vide {
  padding: 28px; text-align: center; color: var(--text3);
  border: 1px dashed var(--border2); border-radius: 12px;
}
.sc-erreur { color: var(--accent1); border-color: var(--accent1); }

/* ═══ TÉLÉPHONE ═══════════════════════════════════════════════════════
   Les deux systèmes d'affichage mobile du projet cohabitent : la classe
   `body.mobile-mode` (posée par mobile-mode.js) et la media query de
   700 px, qui ne regarde QUE la largeur réelle. On écrit ici sous la
   MEDIA QUERY, donc sous la largeur réelle — c'est elle qui décide si un
   panneau déborde, pas le mode choisi par le joueur. */
@media (max-width: 700px) {
  .sc-resume { gap: 14px; padding: 14px; }
  /* L'anneau rétrécit au lieu de pousser le texte hors du cadre. */
  .sc-anneau { width: 92px; height: 92px; }
  .sc-gros b { font-size: 22px; }
  /* Les raretés passent sous les chiffres et prennent toute la largeur :
     à 200 px de base elles auraient serré la colonne du milieu à rien. */
  .sc-raretes { flex: 1 1 100%; }
  /* Une seule colonne. `minmax(min(280px, 100%), 1fr)` la donnait déjà,
     mais on l'écrit pour que ça reste vrai si la carte grandit un jour. */
  .sc-grille { grid-template-columns: 1fr; }
  .sc-famille-titre { font-size: 16px; }
  /* Le compteur de la famille repasse à la ligne plutôt que de pousser le
     titre hors de l'écran. */
  .sc-famille-titre i { margin-left: 0; flex: 1 1 100%; }
  .sc-recherche { max-width: none; flex: 1 1 100%; }
  /* La date d'obtention a le droit de passer à la ligne sur un écran
     étroit : `nowrap` y ferait déborder le pied de carte. */
  .sc-date { white-space: normal; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.7 — L'ASSEMBLÉE INTERGALACTIQUE
   ════════════════════════════════════════════════════════════════════
   Quatre morceaux d'écran : l'EMBLÈME dans le rail (SVG injecté par
   assemblee.js à la place du ♻️), la FICHE au clic sur un siège de la
   carte (même gabarit que le Colporteur, mais cerclé de SARCELLE — la
   couleur de l'emblème, qu'aucun autre panneau n'emploie), l'ONGLET
   complet (vote, dépouillement, Ordonnance, historique) et la POP-UP de
   vote obligatoire (des cartes d'Ordonnance DANS une fenêtre du jeu). */

/* — L'emblème du rail : le SVG se cale sur la grille des emojis voisins. */
#btn-assemblee-embleme { display: inline-flex; align-items: center; justify-content: center; }
#btn-assemblee-embleme svg { display: block; }

/* — La fiche au clic sur un siège (gabarit du Colporteur, en sarcelle). */
body .asb-hote {
  position: fixed; inset: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4, 2, 12, 0.55);
}
body .asb-panneau {
  width: min(92vw, 620px); max-height: 84vh; overflow-y: auto;
  background: var(--panel, #0e1526);
  border: 1px solid rgba(43, 163, 154, 0.4);
  border-radius: 12px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
  color: var(--text, #eaf2ff);
}
body .asb-tete {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid rgba(43, 163, 154, 0.26);
  /* Collante, comme toutes les têtes de fiche qui défilent (leçon du
     panneau de planète, v0.0.85). */
  position: sticky; top: 0; z-index: 1;
  background: var(--panel, #0e1526);
}
body .asb-tete b { color: #6fd3ca; }
body .asb-tete-embleme { flex: none; display: inline-flex; }
body .asb-dim {
  font-size: .82rem; color: var(--text2, #8da4c4);
  min-width: 0; overflow-wrap: anywhere;
}
body .asb-x {
  margin-left: auto; flex: none;
  background: none; border: none; cursor: pointer;
  color: var(--text2, #8da4c4); font-size: 1.05rem; line-height: 1;
  padding: 4px 6px; border-radius: 6px;
}
body .asb-x:hover { color: var(--text, #eaf2ff); background: rgba(255,255,255,.06); }
body .asb-corps { padding: 12px 14px 16px; }
body .asb-note {
  font-size: .82rem; color: var(--text2, #8da4c4); margin-bottom: 12px; line-height: 1.5;
}
body .asb-note b { color: var(--text, #eaf2ff); }
body .asb-btn {
  background: rgba(43, 163, 154, 0.16); color: #8fe0d8;
  border: 1px solid rgba(43, 163, 154, 0.45); border-radius: 8px;
  padding: 8px 14px; font-size: .88rem; cursor: pointer;
  display: block; width: 100%;
}
body .asb-btn:hover { background: rgba(43, 163, 154, 0.28); }

/* — L'onglet : l'en-tête à l'emblème, puis des cartes-sections. La mise
   en page du corps (pleine largeur) vit dans le bloc v0.0.145 plus bas. */
.asb-entete {
  display: flex; align-items: center; gap: 16px;
  margin: 4px 0 16px;
}
.asb-entete-embleme { flex: none; display: inline-flex; }
.asb-entete-titre { font-size: 1.3rem; font-weight: 700; letter-spacing: .02em; }
.asb-carte {
  background: var(--panel, #0e1526);
  border: 1px solid rgba(141, 164, 196, 0.18);
  border-radius: 12px;
  padding: 14px 16px;
  margin-bottom: 14px;
  min-width: 0;
}
.asb-carte h2 { margin: 0 0 8px; font-size: 1.02rem; font-weight: 700; }
.asb-carte h2 b { color: #6fd3ca; }
.asb-carte-vote { border-color: rgba(43, 163, 154, 0.45); }
.asb-carte-active { border-color: rgba(240, 186, 90, 0.3); }
.asb-sous { font-size: .82rem; color: var(--text2, #8da4c4); line-height: 1.55; }
.asb-sous b { color: var(--text, #eaf2ff); }

.asb-sieges { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 10px 0; }
.asb-sieges-titre { font-size: .82rem; color: var(--text2, #8da4c4); }
.asb-siege {
  background: rgba(43, 163, 154, 0.12); color: #a9e6df;
  border: 1px solid rgba(43, 163, 154, 0.4); border-radius: 999px;
  padding: 5px 12px; font-size: .84rem; cursor: pointer;
}
.asb-siege:hover { background: rgba(43, 163, 154, 0.24); }
.asb-siege small { color: var(--text2, #8da4c4); }

.asb-mavoix {
  font-size: .84rem; line-height: 1.55; padding: 10px 12px; border-radius: 8px;
  background: rgba(43, 163, 154, 0.08); border: 1px solid rgba(43, 163, 154, 0.24);
  margin: 10px 0 12px;
}
.asb-mavoix b { color: #8fe0d8; }

/* Les options du scrutin : une carte par Ordonnance + le vote blanc,
   chacune avec sa jauge de dépouillement en direct. */
.asb-opt {
  border: 1px solid rgba(141, 164, 196, 0.16); border-radius: 10px;
  padding: 10px 12px; margin-bottom: 8px; min-width: 0;
}
.asb-opt.est-mon-choix {
  border-color: rgba(43, 163, 154, 0.55);
  background: rgba(43, 163, 154, 0.07);
}
.asb-opt-tete { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.asb-opt-ico { font-size: 1.1rem; flex: none; }
.asb-opt-nom { font-weight: 700; min-width: 0; overflow-wrap: anywhere; }
.asb-opt-badge {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .05em;
  color: #0d1626; background: #6fd3ca; border-radius: 999px;
  padding: 2px 8px; margin-left: 6px; vertical-align: middle;
}
.asb-opt-chiffres { margin-left: auto; font-size: .8rem; color: var(--text2, #8da4c4); }
.asb-opt-chiffres b { color: var(--text, #eaf2ff); }
.asb-opt-btn {
  flex: none;
  background: rgba(43, 163, 154, 0.16); color: #8fe0d8;
  border: 1px solid rgba(43, 163, 154, 0.45); border-radius: 8px;
  padding: 5px 12px; font-size: .82rem; cursor: pointer; white-space: nowrap;
}
.asb-opt-btn:hover:not(:disabled) { background: rgba(43, 163, 154, 0.28); }
.asb-opt-btn:disabled { opacity: .75; cursor: default; }
.asb-opt-desc { font-size: .8rem; color: var(--text2, #8da4c4); margin-top: 4px; }
.asb-opt-barre {
  position: relative; height: 16px; border-radius: 999px; margin-top: 8px;
  background: rgba(141, 164, 196, 0.12); overflow: hidden;
}
.asb-opt-jauge {
  position: absolute; inset: 0 auto 0 0;
  background: linear-gradient(90deg, rgba(43,163,154,.75), rgba(111,211,202,.9));
  border-radius: 999px;
  transition: width .4s ease;
}
.asb-opt-part {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  font-size: .72rem; color: var(--text, #eaf2ff);
}

.asb-regles { margin: 4px 0 0; padding-left: 18px; }
.asb-regles li { font-size: .84rem; color: var(--text2, #8da4c4); line-height: 1.6; }
.asb-regles li b { color: var(--text, #eaf2ff); }

.asb-active-ligne { display: flex; align-items: center; gap: 12px; margin: 8px 0; }
.asb-active-ico { font-size: 1.6rem; flex: none; }

.asb-hist {
  border-top: 1px solid rgba(141, 164, 196, 0.14);
  padding: 10px 0; min-width: 0;
}
.asb-hist:first-of-type { border-top: none; }
.asb-hist-tete { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.asb-hist-ico { flex: none; }
.asb-hist-note { font-size: .78rem; color: var(--text2, #8da4c4); }
.asb-hist-detail {
  display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 6px;
}
.asb-hist-ligne { font-size: .8rem; color: var(--text2, #8da4c4); }
.asb-hist-ligne b { color: var(--text, #eaf2ff); }
.asb-vide { padding: 24px; text-align: center; color: var(--text2, #8da4c4); }

/* — La pop-up de vote : des cartes cliquables DANS la fenêtre du jeu.
   Le choix se marque à la coche ; « Voter » et « Vote blanc » sont les
   seuls boutons — la fenêtre est verrouillée, c'est un vote obligatoire. */
.asb-pop-embleme { display: inline-flex; }
.asb-pop-liste {
  display: flex; flex-direction: column; gap: 6px;
  margin-top: 10px; text-align: left;
  max-height: 44vh; overflow-y: auto;
}
.asb-pop-carte {
  display: flex; align-items: center; gap: 10px;
  background: rgba(141, 164, 196, 0.07);
  border: 1px solid rgba(141, 164, 196, 0.2); border-radius: 10px;
  padding: 9px 12px; cursor: pointer; color: var(--text, #eaf2ff);
  text-align: left; font: inherit; width: 100%;
}
.asb-pop-carte:hover { background: rgba(43, 163, 154, 0.1); }
.asb-pop-carte.est-choisi {
  border-color: rgba(43, 163, 154, 0.65);
  background: rgba(43, 163, 154, 0.14);
}
.asb-pop-ico { font-size: 1.2rem; flex: none; }
.asb-pop-txt { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.asb-pop-txt b { font-size: .9rem; }
.asb-pop-txt small { font-size: .76rem; color: var(--text2, #8da4c4); line-height: 1.4; }
.asb-pop-coche {
  margin-left: auto; flex: none; opacity: 0; color: #6fd3ca; font-weight: 700;
}
.asb-pop-carte.est-choisi .asb-pop-coche { opacity: 1; }

/* — Téléphone : les chiffres et le bouton d'une option passent sous le
   nom, les jauges prennent toute la largeur, la fiche s'élargit. Les
   DEUX systèmes d'affichage mobile sont couverts (classe posée par
   mobile-mode.js ET largeur réelle), comme partout ailleurs. */
body.mobile-mode .asb-panneau { width: 96vw; max-height: 88vh; }
body.mobile-mode .asb-opt-chiffres { margin-left: 0; width: 100%; order: 3; }
body.mobile-mode .asb-opt-btn { margin-left: auto; }
/* ⚠️ PAS DE `gap` ICI, ET C'EST VOULU — MESURÉ, PAS DÉDUIT. Ces deux règles
   posaient un `gap: 8px` qui n'a JAMAIS été appliqué : la mise en pleine
   largeur, écrite plus bas, portait `gap: 18px` sur un sélecteur plus lourd
   (et, dans la requête de largeur, simplement plus bas dans le fichier). Le
   remettre RÉTRÉCIRAIT l'en-tête au téléphone — écart constaté au navigateur
   sur les deux systèmes d'affichage mobile. L'empilement et le centrage, eux,
   s'appliquaient bel et bien : ils restent. */
body.mobile-mode .asb-entete { flex-direction: column; text-align: center; }
@media (max-width: 700px) {
  body .asb-panneau { width: 96vw; max-height: 88vh; }
  .asb-opt-chiffres { margin-left: 0; width: 100%; order: 3; }
  .asb-opt-btn { margin-left: auto; }
  .asb-entete { flex-direction: column; text-align: center; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.145 — L'ASSEMBLÉE OCCUPE LA PAGE
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan, capture à l'appui : « fais en sorte que ça prenne
   toute la page, que ça s'ajuste quoi comme pour ma demande sur le reste
   du site, grossis les textes ». C'est la suite directe de la v2.43
   (« LES VUES OCCUPENT LA PAGE ») : cette refonte-là avait libéré
   `.dsh-view`, mais l'Assemblée se re-bridait toute seule juste en
   dessous, à 980 px centrés — d'où la colonne au milieu et les deux
   bandes vides des captures.

   ⚠️ ÉLARGIR NE SUFFIT PAS, ET PEUT MÊME NUIRE. Une carte d'Ordonnance
   étirée sur 1 450 px met son nom tout à gauche et son décompte tout à
   droite, à plus de mille pixels l'un de l'autre, avec une jauge en fil
   de fer entre les deux : on gagne de la place et on perd la lecture.
   La largeur récupérée sert donc à RANGER, pas à étirer — les six
   Ordonnances passent sur deux colonnes, le barème des voix aussi, et
   chaque bloc garde une longueur de ligne lisible.

   ⚠️ LES DEUX SYSTÈMES D'AFFICHAGE MOBILE SONT COUVERTS, comme partout :
   la classe `body.mobile-mode` ET la media query à 700 px. Les grilles,
   elles, se replient d'elles-mêmes (`minmax(min(100%, …), 1fr)`) : sous
   la largeur d'une colonne, il n'y en a plus qu'une. */

/* — 1. Le corps prend toute la largeur. La place du rail des Vaisseaux
   Mères est réservée par `.dsh-view` (v2.43) : rien à refaire ici.
   ⚠️ Cette réserve tombait sous 900 px alors que le rail ne se replie en
   tiroir qu'à 700 px — entre les deux, le contenu passait sous le panneau,
   sur TOUTES les vues. Corrigé en v0.0.157 : voir le bloc « LA VUE RÉSERVE
   LA PLACE DU RAIL JUSQU'AU VRAI SEUIL », plus haut dans ce fichier. */
#dsh-view-assemblee-body {
  max-width: none; margin: 0; padding: 0 0 36px;
}

/* — 2. L'en-tête : le titre de l'écran se met au niveau des autres
   onglets (`--text-h1`, 24 px), au lieu des 18 px d'avant. */
.asb-entete { gap: 18px; margin: 4px 0 20px; }
.asb-entete-titre { font-size: 1.75rem; }

/* — 3. Les cartes-sections respirent, et leurs titres aussi. */
.asb-carte {
  padding: 18px 22px; margin-bottom: 18px; border-radius: 14px;
}
.asb-carte h2 { font-size: 1.26rem; margin-bottom: 10px; }
.asb-sous { font-size: .97rem; line-height: 1.6; }
.asb-sieges { gap: 10px; margin: 14px 0; }
.asb-sieges-titre { font-size: .97rem; }
.asb-siege { font-size: .97rem; padding: 7px 15px; }
.asb-mavoix {
  font-size: 1rem; line-height: 1.6; padding: 13px 16px; margin: 14px 0 16px;
}

/* — 4. LE DÉPOUILLEMENT EN COLONNES. C'est le cœur du changement : six
   Ordonnances empilées sur une pleine largeur donnaient six rubans ; en
   grille, chacune reste une carte compacte et la page se lit d'un coup
   d'œil. 520 px est la largeur en dessous de laquelle la ligne de tête
   (icône · nom · décompte · bouton) commencerait à se replier : c'est
   donc elle qui décide du nombre de colonnes, pas un chiffre choisi. */
.asb-depouillement {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 520px), 1fr));
  gap: 12px;
  align-items: start;
}
.asb-opt {
  margin-bottom: 0; padding: 14px 16px; border-radius: 12px;
}
.asb-opt-ico { font-size: 1.35rem; }
.asb-opt-nom { font-size: 1.08rem; }
.asb-opt-badge { font-size: .74rem; padding: 3px 9px; }
.asb-opt-chiffres { font-size: .93rem; }
.asb-opt-btn { font-size: .94rem; padding: 7px 16px; }
.asb-opt-desc { font-size: .94rem; line-height: 1.5; margin-top: 6px; }
.asb-opt-barre { height: 20px; margin-top: 10px; }
.asb-opt-part { font-size: .82rem; }

/* — 5. Le barème des voix : cinq règles courtes sur deux colonnes, pour
   la même raison qu'au-dessus — une ligne de texte de 1 450 px ne se
   lit pas. `break-inside: avoid` garde chaque règle d'un seul tenant, et
   la puce reste dans la gouttière, jamais coupée.
   ⚠️ `columns: 2 380px` SE REPLIE TOUT SEUL, et c'est voulu : deux
   colonnes AU PLUS, de 380 px AU MOINS. Sous 800 px de carte, il n'y en
   a plus qu'une, sans qu'aucun seuil de fenêtre ait à être maintenu —
   la leçon de l'Escadrille (v0.0.141) : la fenêtre ne dit pas la
   largeur dont un bloc dispose (entre le rail d'icônes et celui des
   Vaisseaux Mères, il s'en perd 260). */
.asb-regles {
  columns: 2 380px; column-gap: 40px; padding-left: 22px; margin-top: 6px;
}
.asb-regles li {
  font-size: .97rem; line-height: 1.65; break-inside: avoid; margin-bottom: 6px;
}

/* — 6. L'Ordonnance en vigueur et l'historique : mêmes textes, un cran
   plus gros. L'icône de l'Ordonnance active passe à la taille d'un
   emblème — c'est la règle qui s'applique à tout l'univers. */
.asb-active-ico { font-size: 2rem; }
.asb-active-ligne b { font-size: 1.1rem; }
.asb-hist { padding: 14px 0; }
.asb-hist-tete { font-size: 1.06rem; gap: 10px; }
.asb-hist-note { font-size: .9rem; }
.asb-hist-ligne { font-size: .92rem; }
.asb-vide { font-size: 1rem; }

/* — 7. TÉLÉPHONE. Les grilles et les colonnes se sont déjà repliées
   toutes seules — c'est tout l'intérêt de les avoir écrites en largeurs
   minimales plutôt qu'en seuils. Il ne reste que deux gestes : rendre au
   titre une taille d'écran de poche (1,75 rem sur 390 px de large
   prendrait trois lignes à lui seul), et resserrer les marges des cartes,
   qui coûtent proportionnellement bien plus cher sur un téléphone.
   Les marges latérales de l'écran, elles, viennent de `.dsh-view` : la
   pleine largeur ne les a jamais touchées.
   ⚠️ LES DEUX SYSTÈMES SONT ÉCRITS, comme partout ailleurs. */
body.mobile-mode .asb-entete-titre { font-size: 1.35rem; }
body.mobile-mode .asb-carte { padding: 14px 15px; }
@media (max-width: 700px) {
  .asb-entete-titre { font-size: 1.35rem; }
  .asb-carte { padding: 14px 15px; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.9 — LE SIMULATEUR DE COMBAT
   ════════════════════════════════════════════════════════════════════
   Le dernier onglet de la Rampe. Un écran DENSE par nature : deux
   formulaires côte à côte, puis quatre pavés de résultat. Toute la mise en
   page tient donc sur deux règles simples — une grille à colonnes
   auto-ajustées pour les deux camps, et des lignes en `grid` à colonnes
   nommées pour que les compteurs restent alignés d'une unité à l'autre.

   ⚠️ LES DEUX SYSTÈMES D'AFFICHAGE MOBILE SONT COUVERTS, comme partout
   ailleurs : la classe `body.mobile-mode` (posée par mobile-mode.js) ET la
   media query à 700 px. Forcer l'une sans l'autre ne mesure que la moitié
   du mode mobile. */

.sim { display: flex; flex-direction: column; gap: 1rem; }

/* — Le bandeau : régime de raid, et chargement d'un espionnage — */
.sim-bandeau {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: .8rem; align-items: start;
  background: rgba(255,255,255,.02); border: 1px solid var(--border);
  border-radius: 8px; padding: .7rem .9rem;
}
.sim-regime, .sim-charger { display: flex; flex-direction: column; gap: .35rem; min-width: 0; }
.sim-regime { flex-direction: row; align-items: center; flex-wrap: wrap; gap: .4rem; }
.sim-regime .rp-label { width: 100%; }
.sim-pastille {
  padding: .35rem .8rem; cursor: pointer; font-family: inherit; font-size: .74rem;
  background: transparent; border: 1px solid var(--border); border-radius: 999px;
  color: var(--text2); transition: .15s;
}
.sim-pastille:hover:not(.is-active) { background: rgba(255,255,255,.04); }
.sim-pastille.is-active {
  border: 2px solid var(--turquoise, #2dd4bf); padding: calc(.35rem - 1px) calc(.8rem - 1px);
  background: rgba(45,212,191,.28); color: #f2fffd; font-weight: 600;
}
.sim-pastille.is-active::after { content: ' ●'; font-size: .7em; vertical-align: middle; }
.sim-select { max-width: 100%; }
.sim-select-court { max-width: 160px; }

/* — Les deux camps — */
.sim-duo {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: .8rem;
}
.sim-camp {
  background: rgba(255,255,255,.02); border: 1px solid var(--border);
  border-radius: 8px; padding: .8rem .9rem; min-width: 0;
}
.sim-camp-moi { border-left: 3px solid var(--turquoise, #2dd4bf); }
.sim-camp-lui { border-left: 3px solid var(--accent1, #e94560); }
.sim-camp-tete { display: flex; flex-direction: column; gap: 2px; margin-bottom: .5rem; }
.sim-camp-titre {
  font-family: var(--hud); font-size: .72rem; letter-spacing: .09em;
  text-transform: uppercase; color: var(--turquoise, #2dd4bf);
}
.sim-camp-lui .sim-camp-titre { color: #f9a8b4; }
.sim-camp-sous { font-size: .7rem; color: var(--text3); }
.sim-actions { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin-bottom: .5rem; }
.sim-btn {
  padding: .28rem .7rem; cursor: pointer; font-family: inherit; font-size: .72rem;
  background: rgba(45,212,191,.14); border: 1px solid var(--border2);
  border-radius: 6px; color: var(--text); transition: .15s;
}
.sim-btn:hover { background: rgba(45,212,191,.24); }
.sim-btn-sobre { background: rgba(255,255,255,.03); }
.sim-compte { margin-left: auto; font-family: var(--mono); font-size: .7rem; color: var(--text3); }

/* — Une ligne d'unité : icône · nom · fiche · compteur · disponible —
   La grille garde les cinq colonnes alignées quel que soit le nom, et
   `minmax(0,1fr)` sur le nom est ce qui autorise l'ellipse : sans lui, un
   nom long POUSSE la ligne hors du panneau au lieu d'être tronqué. */
.sim-unites { display: flex; flex-direction: column; gap: 2px; margin-bottom: .4rem; }
.sim-ligne {
  display: grid; grid-template-columns: 1.2rem minmax(0,1fr) auto 5.2rem 4.2rem;
  align-items: center; gap: .45rem; padding: 2px 5px; border-radius: 4px; font-size: .74rem;
}
.sim-ligne:nth-child(odd) { background: rgba(255,255,255,.02); }
.sim-ico { font-size: .9rem; }
.sim-nom { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sim-fiche { font-family: var(--mono); font-size: .62rem; color: var(--text3); white-space: nowrap; }
.sim-ligne .rp-input { width: 100%; }
.sim-max {
  font-family: var(--mono); font-size: .64rem; color: var(--text3);
  background: transparent; border: 1px solid transparent; border-radius: 4px;
  padding: 1px 4px; cursor: pointer; text-align: right; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.sim-max:hover { border-color: var(--border2); color: var(--turquoise, #2dd4bf); }
.sim-max-vide { cursor: default; }
.sim-max-vide:hover { border-color: transparent; color: var(--text3); }
.sim-ligne-missile { background: rgba(249,115,22,.07) !important; }
.sim-ligne-tech, .sim-ligne-res { grid-template-columns: minmax(0,1fr) 5.2rem 6rem; }
.sim-ligne-res { grid-template-columns: 1.2rem minmax(0,1fr) auto 5.2rem 4.2rem; }

.sim-sous-titre {
  font-family: var(--hud); font-size: .62rem; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text3); margin: .6rem 0 .3rem;
}
.sim-aide-inline { font-family: var(--body); text-transform: none; letter-spacing: 0; color: var(--text3); }
.sim-aide { font-size: .7rem; color: var(--text3); font-style: italic; margin: .3rem 0; }
.sim-tiroir {
  width: 100%; text-align: left; margin-top: .5rem;
  padding: .3rem .5rem; cursor: pointer; font-family: inherit; font-size: .72rem;
  background: rgba(255,255,255,.03); border: 1px solid var(--border);
  border-radius: 6px; color: var(--text2);
}
.sim-tiroir:hover { background: rgba(255,255,255,.06); }
.sim-modifie {
  font-family: var(--mono); font-size: .62rem; color: var(--gold, #f59e0b);
  border: 1px solid rgba(245,158,11,.4); border-radius: 3px; padding: 0 4px;
}
.sim-fins {
  margin-top: .4rem; padding: .5rem .6rem;
  background: rgba(255,255,255,.02); border-left: 2px solid var(--border2); border-radius: 0 6px 6px 0;
}
.sim-note {
  font-size: .71rem; line-height: 1.45; color: var(--text2);
  background: rgba(45,212,191,.07); border-left: 3px solid var(--turquoise, #2dd4bf);
  border-radius: 0 6px 6px 0; padding: .45rem .6rem; margin: .5rem 0;
}
.sim-note-alerte { background: rgba(245,158,11,.09); border-left-color: var(--gold, #f59e0b); }
.sim-note-ok { background: rgba(16,185,129,.09); border-left-color: var(--accent4, #10b981); }

/* — La barre de lancement — */
.sim-lancer { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.sim-go {
  padding: .55rem 1.4rem; cursor: pointer; font-family: var(--hud); font-size: .8rem;
  letter-spacing: .06em; background: rgba(45,212,191,.22);
  border: 2px solid var(--turquoise, #2dd4bf); border-radius: 8px;
  color: #f2fffd; transition: .15s;
}
.sim-go:hover:not(:disabled) { background: rgba(45,212,191,.36); box-shadow: 0 0 18px rgba(45,212,191,.3); }
.sim-go:disabled { opacity: .4; cursor: not-allowed; border-color: var(--border2); background: transparent; }
.sim-iterations { display: flex; align-items: center; gap: .35rem; font-size: .72rem; color: var(--text3); }
.sim-iterations .rp-input { width: 4.5rem; }

/* — Le résultat — */
.sim-resultat { display: flex; flex-direction: column; gap: .9rem; }
.sim-vide {
  font-size: .74rem; color: var(--text3); font-style: italic;
  padding: .9rem; text-align: center;
  border: 1px dashed var(--border); border-radius: 8px;
}
.sim-verdict { border-radius: 8px; padding: .8rem .9rem; border: 1px solid var(--border); }
.sim-verdict-ok     { background: rgba(16,185,129,.09);  border-color: rgba(16,185,129,.5); }
.sim-verdict-mitige { background: rgba(245,158,11,.09);  border-color: rgba(245,158,11,.5); }
.sim-verdict-ko     { background: rgba(233,69,96,.09);   border-color: rgba(233,69,96,.5); }
.sim-verdict-tete { display: flex; align-items: baseline; gap: .55rem; flex-wrap: wrap; }
.sim-verdict-ico { font-size: 1.3rem; }
.sim-verdict-mot { font-family: var(--hud); font-size: 1rem; letter-spacing: .05em; color: var(--text); }
.sim-verdict-taux { font-family: var(--mono); font-size: .74rem; color: var(--text2); }
.sim-verdict-issues { font-family: var(--mono); font-size: .68rem; color: var(--text3); margin-top: .25rem; }
.sim-puissances {
  display: flex; align-items: center; gap: .7rem; flex-wrap: wrap;
  margin-top: .55rem; padding-top: .5rem; border-top: 1px solid var(--border);
  font-size: .74rem;
}
.sim-puiss b { font-family: var(--mono); font-size: .82rem; }
.sim-puiss-moi b { color: var(--turquoise, #2dd4bf); }
.sim-puiss-lui b { color: #f9a8b4; }
.sim-puiss-vs { font-family: var(--hud); font-size: .68rem; color: var(--text3); }
.sim-puiss-verdict { font-size: .74rem; color: var(--text2); }
.sim-puiss-verdict b { font-family: var(--mono); color: var(--text); }
.sim-alertes { display: flex; flex-direction: column; gap: .35rem; margin-top: .6rem; }
.sim-alerte {
  font-size: .71rem; line-height: 1.45; color: var(--text2);
  background: rgba(0,0,0,.22); border-radius: 6px; padding: .4rem .55rem;
}

.sim-bloc {
  background: rgba(255,255,255,.02); border: 1px solid var(--border);
  border-radius: 8px; padding: .8rem .9rem;
}
.sim-bloc-titre {
  font-family: var(--hud); font-size: .68rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--turquoise, #2dd4bf); margin-bottom: .55rem;
}
.sim-bloc-titre-sec { margin-top: .9rem; }

/* — Le bilan — */
.sim-bilan { display: flex; flex-direction: column; gap: 2px; }
.sim-bil-ligne {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  padding: 3px 6px; border-radius: 4px; font-size: .76rem;
}
.sim-bil-ligne:nth-child(odd) { background: rgba(255,255,255,.02); }
.sim-bil-total {
  margin-top: .35rem; padding-top: .45rem; border-top: 1px solid var(--border2);
  font-weight: 600; font-size: .84rem; background: none !important;
}
.sim-bil-val { font-family: var(--mono); }
.sim-bil-val.est-gain  { color: #4ade80; }
.sim-bil-val.est-perte { color: #fbbf24; }
.sim-bilan-detail { font-family: var(--mono); font-size: .68rem; color: var(--text3); margin-top: .4rem; }

/* — Les pertes — */
.sim-pertes { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: .8rem; }
.sim-pertes-camp { min-width: 0; }
.sim-perte {
  display: grid; grid-template-columns: minmax(0,1fr) 4rem auto;
  align-items: center; gap: .4rem; padding: 2px 5px; border-radius: 4px; font-size: .74rem;
}
.sim-perte:nth-child(odd) { background: rgba(255,255,255,.02); }
.sim-perte-nom { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sim-perte-n { font-family: var(--mono); color: #fbbf24; text-align: right; }
.sim-perte-fourchette { font-family: var(--mono); font-size: .64rem; color: var(--text3); white-space: nowrap; }

/* — Les bonus : la demande centrale, « où est quoi » — */
.sim-bonus { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: .8rem; }
.sim-bonus-camp { min-width: 0; padding-left: .5rem; border-left: 2px solid var(--border2); }
.sim-bonus-moi { border-left-color: rgba(45,212,191,.55); }
.sim-bonus-lui { border-left-color: rgba(233,69,96,.55); }
.sim-bonus-ligne {
  display: grid; grid-template-columns: minmax(0,1.1fr) minmax(0,1fr);
  align-items: baseline; gap: .3rem .5rem; padding: 3px 5px; border-radius: 4px; font-size: .73rem;
}
.sim-bonus-ligne:nth-child(odd) { background: rgba(255,255,255,.02); }
.sim-bonus-nom { min-width: 0; }
.sim-bonus-val { font-family: var(--mono); font-size: .72rem; }
.sim-bonus-src { grid-column: 1 / -1; font-size: .64rem; color: var(--text3); font-style: italic; }
/* ⚠️ L'EXPLICATION D'UN BONUS EST VISIBLE, PAS SEULEMENT EN INFOBULLE. Un
   `title` ne s'ouvre pas au doigt : sur téléphone, tout le « où est quoi »
   — la moitié de ce qu'on vient chercher ici — serait resté hors d'atteinte.
   Elle est donc rendue en clair sous chaque ligne, discrète au large et
   pleinement lisible au format étroit. */
.sim-bonus-detail {
  grid-column: 1 / -1; font-size: .64rem; line-height: 1.35;
  color: var(--text3); opacity: .85;
}
.sim-facteur { color: #4ade80; font-weight: 600; }
.sim-axe { color: var(--text3); font-size: .66rem; }
.sim-axe-nul { font-style: italic; }

/* — Les contres — */
.sim-contres { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: .8rem; }
.sim-contres-camp { min-width: 0; }
.sim-contre {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem;
  padding: 3px 5px; border-radius: 4px; font-size: .72rem;
}
.sim-contre:nth-child(odd) { background: rgba(255,255,255,.02); }
.sim-contre-nom { font-family: var(--mono); color: var(--text); }
.sim-contre-fleche { color: #4ade80; font-size: .68rem; }
.sim-contre-cibles { font-family: var(--mono); color: var(--text3); font-size: .68rem; }

/* — Le déroulé, round par round — */
.sim-rounds { display: flex; flex-direction: column; gap: 2px; overflow-x: auto; }
.sim-round {
  display: grid; grid-template-columns: 3.4rem repeat(4, minmax(4.2rem, 1fr));
  gap: .4rem; padding: 3px 5px; border-radius: 4px;
  font-family: var(--mono); font-size: .72rem; min-width: 24rem;
}
.sim-round:nth-child(odd) { background: rgba(255,255,255,.02); }
.sim-round-tete {
  font-family: var(--hud); font-size: .58rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text3); background: none !important;
}
.sim-round span:not(:first-child) { text-align: right; }
.sim-round-tete span:not(:first-child) { text-align: right; }
.sim-round .est-gain  { color: #4ade80; }
.sim-round .est-perte { color: #fbbf24; }

/* ── TÉLÉPHONE ────────────────────────────────────────────────────────
   Les deux camps passent l'un SOUS l'autre (jamais côte à côte à 390 px :
   la ligne d'unité y perdrait ses compteurs), la fiche technique
   attaque/bouclier/structure disparaît (elle reste dans l'infobulle du
   nom), et la ligne d'unité se resserre sur trois colonnes. Le déroulé
   garde son défilement horizontal propre plutôt que d'écraser cinq
   colonnes de chiffres. */
/* ⚠️ SUR TÉLÉPHONE, UN NOM SE PLIE — IL NE SE COUPE PAS. La colonne du
   nom ne fait plus qu'une centaine de pixels une fois les compteurs posés,
   et « Convoyeur Raréfié » suivi de son étiquette « cible » n'y tient pas.
   Une ellipse au milieu d'un mot est le seul indice visible d'une largeur
   trop étroite (la leçon des trente-cinq pixels du tiroir des files) : on
   laisse donc le texte passer à la ligne, quitte à ce que la rangée
   grandisse. */
body.mobile-mode .sim-nom,
body.mobile-mode .sim-perte-nom,
body.mobile-mode .sim-bonus-nom {
  white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.2;
}
body.mobile-mode .sim-ligne, body.mobile-mode .sim-perte { align-items: center; }
body.mobile-mode .sim-duo,
body.mobile-mode .sim-bonus,
body.mobile-mode .sim-pertes,
body.mobile-mode .sim-contres,
body.mobile-mode .sim-bandeau { grid-template-columns: 1fr; }
body.mobile-mode .sim-ligne,
body.mobile-mode .sim-ligne-res { grid-template-columns: 1.2rem minmax(0,1fr) 4.6rem 3.6rem; }
body.mobile-mode .sim-fiche { display: none; }
body.mobile-mode .sim-ligne-tech { grid-template-columns: minmax(0,1fr) 4.6rem; }
body.mobile-mode .sim-ligne-tech .sim-max-vide { display: none; }
body.mobile-mode .sim-camp { padding: .6rem .55rem; }
body.mobile-mode .sim-go { width: 100%; }
body.mobile-mode .sim-bonus-ligne { grid-template-columns: 1fr; }
body.mobile-mode .sim-select-court { max-width: 100%; }

@media (max-width: 700px) {
  .sim-nom, .sim-perte-nom, .sim-bonus-nom {
    white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.2;
  }
  .sim-ligne, .sim-perte { align-items: center; }
  .sim-duo, .sim-bonus, .sim-pertes, .sim-contres, .sim-bandeau { grid-template-columns: 1fr; }
  .sim-ligne, .sim-ligne-res { grid-template-columns: 1.2rem minmax(0,1fr) 4.6rem 3.6rem; }
  .sim-fiche { display: none; }
  .sim-ligne-tech { grid-template-columns: minmax(0,1fr) 4.6rem; }
  .sim-ligne-tech .sim-max-vide { display: none; }
  .sim-camp { padding: .6rem .55rem; }
  .sim-go { width: 100%; }
  .sim-bonus-ligne { grid-template-columns: 1fr; }
  .sim-select-court { max-width: 100%; }
}
/* La marque « cible » d'un civil exposé en raid orbital : elle vit DANS la
   ligne où on le compte, pas dans une note du bas. C'est là que la décision
   se prend. */
.sim-tag {
  font-family: var(--mono); font-size: .58rem; letter-spacing: .04em;
  color: #f9a8b4; background: rgba(233,69,96,.16);
  border-radius: 3px; padding: 0 4px; margin-left: .3rem; vertical-align: middle;
}
.sim-ligne-expose { background: rgba(233,69,96,.06) !important; }
/* La checklist de ce qui NE joue pas : discrète, mais présente. C'est elle
   qui distingue « ce bonus n'existe pas » de « ce bonus a été oublié ». */
.sim-bonus-absents {
  margin-top: .45rem; padding-top: .4rem; border-top: 1px dashed var(--border);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem;
  font-size: .66rem; color: var(--text3);
}
.sim-bonus-absents-tete { font-style: italic; }
.sim-bonus-absent {
  border: 1px solid var(--border2); border-radius: 999px; padding: 0 .45rem;
  cursor: help; white-space: nowrap;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.9 — LES ICÔNES DESSINÉES DU RAIL
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan, deux lots d'images fournies : « j'aimerais que tu
   mettes précisément ces images en icone ». Puis, sur la méthode : « on ne
   peut pas mettre mes images en détourant le fond pour que ce soit pile mon
   image ? » — si, et c'est ce qui est fait. Les quatorze fichiers de
   `public/assets/rail/` SONT ses images, fond rendu transparent, rien de
   redessiné.

   ⚠️ LE NOM DU FICHIER EST L'IDENTIFIANT DE L'ONGLET (`data-view`), pas le
   titre de l'image : `fleet.png` est la Rampe de Lancement, `arbre.png`
   l'Arbre de Progression, `movements.png` les Mouvements de Flotte,
   `leaderboard.png` le Classement, `information.png` le Centre
   d'Information. Les renommer d'après ce qu'ils représentent couperait le
   lien, sans lever la moindre erreur — la case deviendrait simplement vide.

   ⚠️ LES `<img>` SONT DANS index.html, ET LA RÈGLE NE VISE QU'EUX. Les
   trois onglets restés en émoji — 🔔 Notifications, 📖 Codex — et l'emblème
   SVG de l'Assemblée, injecté par assemblee.js, n'ont pas d'image dans leur
   case : le sélecteur `img` les laisse donc intacts. C'est ce qui remplace
   la liste nommée de quatorze sélecteurs qu'il fallait tenir à jour.

   ⚠️ 1,85 rem, ET PAS `--sidebar-icon-size`. Signalement de Loryan sur le
   premier jet : « je trouve que les logos ne sont pas assez gros ». Ils
   étaient posés à 1,25 rem, la taille de POLICE d'un émoji — or un émoji
   remplit sa case, une image dessinée laisse du blanc autour de son trait,
   et paraît donc plus petite à taille égale. Les images ont été regénérées
   pour occuper tout leur carré, et la case est passée à 1,85 rem : c'est
   cette mesure-là que Loryan a validée. Le bouton fait 44 px de haut
   (40 en mobile) pour 60 px de large (48) : 26 px y tiennent avec de la
   marge.
   ════════════════════════════════════════════════════════════════════ */
.dsh-tab .dsh-tab-icon img {
  width: 1.85rem;
  height: 1.85rem;
  display: block;
  /* `contain` et pas `cover` : le vaisseau est bien plus large que haut,
     et `cover` lui couperait la proue et la poupe. */
  object-fit: contain;
}


/* ════════════════════════════════════════════════════════════════════
   v0.0.136 — LA CITÉ-NEF (Vue globale, features/vm/cite-nef.js).
   Le vaisseau en gris métal, les douze modules en cadre au-dessus et
   en dessous, les jauges lues à même le dessin. Tout vit sous `.cn-`.
   ════════════════════════════════════════════════════════════════════ */
.cn-plan { display: flex; flex-direction: column; }
.cn-scroll { overflow-x: auto; overflow-y: hidden; background: #070b11; }
/* ⚠️ LE DESSIN EST PLAFONNÉ (retour de Loryan : « c'est un peu grand ») :
   sans max-width, `width: 100%` l'étire sur toute la largeur d'un grand
   écran, et le dézoom du navigateur ne fait que lui re-donner cette
   largeur. Plafonné, il garde une taille de plan de travail — et les
   textes du dessin sont taillés pour CETTE taille-là (cf. les font-size
   en px du bloc : ce sont des unités du viewBox 1240). */
.cn-svg { display: block; width: 100%; max-width: 980px; min-width: 860px; height: auto; margin: 0 auto; }

/* ─── le métal : cinq valeurs de gris, éclairées par le haut ─── */
.cn-h1 { fill: #b4c0cd; stroke: #12181f; stroke-width: .7; }
.cn-h2 { fill: #8d9aa9; stroke: #12181f; stroke-width: .7; }
.cn-h3 { fill: #697583; stroke: #0e1319; stroke-width: .7; }
.cn-h4 { fill: #464f5c; stroke: #0b1016; stroke-width: .7; }
.cn-h5 { fill: #2c343f; stroke: #0b1016; stroke-width: .7; }
.cn-ln  { stroke: #3b4552; stroke-width: .7; fill: none; }
.cn-ln2 { stroke: #9aa7b5; stroke-width: .7; fill: none; opacity: .55; }
.cn-win  { fill: #ffd79a; opacity: .9; }
.cn-win2 { fill: #8fd8ff; opacity: .8; }
.cn-flanc { fill: url(#cnFlanc); }
.cn-pont  { fill: url(#cnPont); }
.cn-plume { fill: #7fd0f5; opacity: .4; }
.cn-nom  { font-family: var(--hud); font-size: 19px; font-weight: 600;
           fill: #cfdcea; letter-spacing: .16em; }
.cn-sous { font-family: var(--mono); font-size: 13.5px; fill: #63748a; letter-spacing: .16em; }

/* ─── les jauges ─── */
.cn-lettre { font-family: var(--mono); font-size: 16px; font-weight: 700; text-anchor: middle; }
.cn-pct    { font-family: var(--mono); font-size: 13px; text-anchor: middle; }
.cn-tag    { font-family: var(--mono); font-size: 14px; fill: #9fb4cc; text-anchor: middle; }

/* ─── le lien avec la planète ─── */
.cn-sol      { stroke: #2c7a63; stroke-width: 1.3; stroke-dasharray: 10 8; fill: none; }
.cn-sol-fond { fill: rgba(19, 78, 63, .28); }
.cn-sol-txt  { font-family: var(--mono); font-size: 14.5px; fill: #2f9c80; }
.cn-collecteur   { fill: #7c4a06; stroke: #f59e0b; stroke-width: 1.2; }
.cn-ombilic      { stroke: #2dd4bf; stroke-width: 2.2; fill: none; opacity: .75; }
.cn-ombilic-pied { fill: #134e4a; stroke: #2dd4bf; stroke-width: 1; }

/* ─── les pastilles posées sur la coque ─── */
.cn-hs { cursor: pointer; }
.cn-hs:focus-visible { outline: none; }
.cn-hs:focus-visible .cn-hsp { stroke: #9fe4ff; stroke-width: 2; }
.cn-hsl { stroke: #7fd0f5; stroke-width: 1; opacity: .5; }
.cn-hsd { fill: #7fd0f5; }
.cn-hsp { fill: rgba(6,12,20,.90); stroke: #6ba6cc; stroke-width: 1.1; transition: .15s; }
.cn-hs:hover .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-hsi { font-size: 16px; dominant-baseline: middle; text-anchor: middle; }
.cn-hsv { font-family: var(--mono); font-size: 15px; fill: #c9e6f7; dominant-baseline: middle; }
.cn-hs.is-busy .cn-hsp  { stroke: var(--gold); }
.cn-hs.is-busy .cn-hsd  { fill: var(--gold); }
.cn-hs.is-busy .cn-hsl  { stroke: var(--gold); }
.cn-hs.is-alert .cn-hsp { stroke: var(--accent1); }
.cn-hs.is-alert .cn-hsd { fill: var(--accent1); }
.cn-hs.is-alert .cn-hsl { stroke: var(--accent1); }
.cn-hs.is-empty .cn-hsp { stroke: #3a4a60; stroke-dasharray: 4 3; }
.cn-hs.is-empty .cn-hsv { fill: #5b6f8a; }

/* ─── les cadres de cartes ─── */
.cn-cadre { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(256px, 100%), 1fr)); gap: 6px; padding: 10px 12px;
            background: #080d16; }
.cn-cadre + .cn-scroll, .cn-scroll + .cn-cadre { border-top: 1px solid var(--border); }
.cn-carte {
  display: grid; grid-template-columns: 18px 1fr auto; grid-template-rows: auto auto;
  column-gap: 7px; row-gap: 2px; align-items: center; text-align: left;
  border: 1px solid #16233c; border-radius: 7px; background: #0b1424;
  padding: 7px 9px; min-width: 0; cursor: pointer; transition: .15s;
  color: inherit; font: inherit;
}
.cn-carte:hover { border-color: #6ba6cc; background: #0e1c30; }
.cn-carte-ico { font-size: 16px; grid-row: 1; }
.cn-carte-nom { grid-row: 1; grid-column: 2; font-size: 14.5px; color: #dbe8ff; font-weight: 600;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cn-carte-lv  { grid-row: 1; grid-column: 3; font-family: var(--mono); font-size: 13px; color: #7fd0f5; }
.cn-carte-stat { grid-row: 2; grid-column: 1 / -1; font-family: var(--mono); font-size: 12.5px;
                 color: #8fa6c8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cn-carte.is-busy { border-color: rgba(245,158,11,.55); background: rgba(245,158,11,.08); }
.cn-carte.is-busy .cn-carte-lv { color: var(--gold); }
.cn-carte.is-alert { border-color: rgba(233,69,96,.55); background: rgba(233,69,96,.08); }
.cn-carte.is-alert .cn-carte-stat { color: #fca5a5; }
.cn-carte.is-empty { opacity: .6; }
.cn-carte.is-empty .cn-carte-lv::after { content: '—'; color: #5b6f8a; }

/* survoler une carte allume sa pastille sur la coque, et l'inverse */
.cn-plan:has(.cn-m-propulsion:hover) .cn-hs.cn-m-propulsion .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-propulsion:hover) .cn-hs.cn-m-propulsion .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-propulsion:hover) .cn-carte.cn-m-propulsion { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-energy:hover) .cn-hs.cn-m-energy .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-energy:hover) .cn-hs.cn-m-energy .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-energy:hover) .cn-carte.cn-m-energy { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-manufacture:hover) .cn-hs.cn-m-manufacture .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-manufacture:hover) .cn-hs.cn-m-manufacture .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-manufacture:hover) .cn-carte.cn-m-manufacture { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-storage:hover) .cn-hs.cn-m-storage .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-storage:hover) .cn-hs.cn-m-storage .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-storage:hover) .cn-carte.cn-m-storage { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-extractors:hover) .cn-hs.cn-m-extractors .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-extractors:hover) .cn-hs.cn-m-extractors .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-extractors:hover) .cn-carte.cn-m-extractors { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-connectors:hover) .cn-hs.cn-m-connectors .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-connectors:hover) .cn-hs.cn-m-connectors .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-connectors:hover) .cn-carte.cn-m-connectors { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-defenses:hover) .cn-hs.cn-m-defenses .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-defenses:hover) .cn-hs.cn-m-defenses .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-defenses:hover) .cn-carte.cn-m-defenses { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-research:hover) .cn-hs.cn-m-research .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-research:hover) .cn-hs.cn-m-research .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-research:hover) .cn-carte.cn-m-research { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-infrastructures:hover) .cn-hs.cn-m-infrastructures .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-infrastructures:hover) .cn-hs.cn-m-infrastructures .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-infrastructures:hover) .cn-carte.cn-m-infrastructures { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-fleet:hover) .cn-hs.cn-m-fleet .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-fleet:hover) .cn-hs.cn-m-fleet .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-fleet:hover) .cn-carte.cn-m-fleet { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-command:hover) .cn-hs.cn-m-command .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-command:hover) .cn-hs.cn-m-command .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-command:hover) .cn-carte.cn-m-command { border-color: #7fd0f5; background: rgba(14,50,78,.5); }
.cn-plan:has(.cn-m-exploration:hover) .cn-hs.cn-m-exploration .cn-hsp { stroke: #9fe4ff; fill: rgba(12,46,72,.94); }
.cn-plan:has(.cn-m-exploration:hover) .cn-hs.cn-m-exploration .cn-hsl { opacity: 1; stroke-width: 1.6; }
.cn-plan:has(.cn-m-exploration:hover) .cn-carte.cn-m-exploration { border-color: #7fd0f5; background: rgba(14,50,78,.5); }

/* ─── écrans étroits : les cadres passent en une colonne, le dessin se
   fait glisser dans son cadre (il reste lisible à 860 px). ─── */
@media (max-width: 900px) {
  .cn-cadre { grid-template-columns: 1fr; }
}
body.mobile-mode .cn-cadre { grid-template-columns: 1fr; }

/* ════════════════════════════════════════════════════════════════════
   Interrupteur `factureCarteQte` — la facture du lot, sur la carte ET
   dans le panneau de droite (features/vm/fleet.js, `majFactureCarte`).
   Loryan : « à droite j'ai la facture globale (bien que ce soit écrit un
   peu petit) alors qu'à gauche je n'ai pas la facture globale ».
   ⚠️ 0,86 rem et pas 0,79 : la racine descend à 12,8 px en mode mobile,
   0,79 rem y tomberait sous le plancher de 11 px.
   ════════════════════════════════════════════════════════════════════ */
/* La carte : le prix (seul nœud réécrit à la frappe) passe au plancher. */
body.f-factureCarteQte .ship-card-stats > span[id^="facture-"] {
  font-size: 0.86rem;
  color: var(--text, #e5e8f5);
}
body.f-factureCarteQte .ship-card-facture-r { white-space: nowrap; }
body.f-factureCarteQte .ship-card-facture-n {
  color: var(--accent2, #0ea5e9);
  font-weight: 600;
}
/* Le panneau de droite : les vignettes « Coût pour ×N » grossissent. */
body.f-factureCarteQte .md-costs-lot { gap: 0.4rem; }
body.f-factureCarteQte .md-costs-lot .cost { font-size: 0.86rem; padding: 0.28rem 0.55rem; }
body.f-factureCarteQte .md-costs-lot .cost .res-mini { width: 19px; height: 19px; }

/* ── Interrupteur citeNefCartesLigne ─────────────────────────────────
   Loryan, capture des cartes de la Cité-Nef : « Réduis pour tout faire
   passer sur une ligne et grossis un peu les chiffres ». Sur ordinateur, les
   cartes se resserrent jusqu'à 108 px : un cadre de cinq ou six cartes tient
   sur UNE rangée dès qu'il a la place (le cadre n'a que la largeur laissée
   par les panneaux — 690 px pour une fenêtre de 1100), et passe proprement
   à la ligne en dessous sinon.
   Le chiffre de droite grossit partout.
   ⚠️ UNE CARTE ÉTROITE SE RÉORGANISE : le nom seul sur la première ligne
   (« Infrastructures » ne tiendrait pas à côté de l'icône), l'icône et le
   chiffre dessous, la légende en bas. La carte est son propre conteneur :
   c'est SA largeur qui décide, pas celle de la fenêtre.
   ⚠️ PAS AU TÉLÉPHONE : la colonne unique du dessus reste.
   ⚠️ Rien n'est tronqué en silence : la légende passe à la ligne. */
@media (min-width: 901px) {
  body.f-citeNefCartesLigne:not(.mobile-mode) .cn-cadre {
    grid-template-columns: repeat(auto-fit, minmax(108px, 1fr)); gap: 5px; padding: 8px 10px;
  }
  body.f-citeNefCartesLigne:not(.mobile-mode) .cn-carte { padding: 5px 7px; column-gap: 5px; container-type: inline-size; }
  body.f-citeNefCartesLigne:not(.mobile-mode) .cn-carte-nom { font-size: 13px; }
  body.f-citeNefCartesLigne:not(.mobile-mode) .cn-carte-stat { font-size: 11px; white-space: normal; line-height: 1.25; }
}
@container (max-width: 200px) {
  body.f-citeNefCartesLigne:not(.mobile-mode) .cn-carte-nom { grid-row: 1; grid-column: 1 / -1; }
  body.f-citeNefCartesLigne:not(.mobile-mode) .cn-carte-ico { grid-row: 2; grid-column: 1; }
  body.f-citeNefCartesLigne:not(.mobile-mode) .cn-carte-lv { grid-row: 2; grid-column: 2 / -1; }
  body.f-citeNefCartesLigne:not(.mobile-mode) .cn-carte-stat { grid-row: 3; }
}
body.f-citeNefCartesLigne .cn-carte-lv { font-size: 16.5px; font-weight: 700; }
/* ⚠️ L'ACCOLADE CI-DESSOUS EST ORPHELINE : elle fait jeter `.mvr-volets`, la
   première règle qui la suit. Gardée tant que l'interrupteur
   radarMouvementsGrille n'est pas purgé (voir son bloc, plus bas). */
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.136 — LE RADAR ORBITAL (Mouvements de flotte,
   features/movements/movements.js)
   ════════════════════════════════════════════════════════════════════ */
/* ⚠️ `minmax(0, …)` SUR LES DEUX COLONNES, ET C'EST LE CORRECTIF DU ZOOM.
   Une colonne de grille vaut `min-width: auto` par défaut : elle REFUSE de
   descendre sous la largeur de son contenu. À très gros zoom, la fenêtre ne
   fait plus que quelques centaines de pixels CSS ; les lignes de mouvement,
   elles, gardent leur largeur naturelle, et la grille débordait alors vers
   la droite — par-dessus le rail des Vaisseaux Mères (signalé par Loryan,
   capture à l'appui). `minmax(0, …)` autorise le rétrécissement, et rien ne
   sort plus du cadre. */
.mvr-volets {
  display: grid; grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
  gap: 14px; align-items: start; max-width: 100%;
}
.mvr-volet-liste { min-width: 0; }
.mvr-volet-radar {
  position: sticky; top: 8px; min-width: 0; max-width: 100%;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--card); padding: 10px;
}
.mvr-svg { display: block; width: 100%; max-width: 100%; height: auto; margin: 0 auto; }
.mvr-aide { margin: 8px 2px 0; font-size: 11.5px; color: var(--text3); line-height: 1.45; }

.mvr-anneau { fill: none; stroke: #16233c; stroke-width: 1; }
.mvr-axe { stroke: #16233c; stroke-width: 1; }
.mvr-sweep { transform-origin: 224px 224px; animation: mvr-tour 9s linear infinite; }
@media (prefers-reduced-motion: reduce) { .mvr-sweep { animation: none; } }
@keyframes mvr-tour { to { transform: rotate(360deg); } }
.mvr-moyeu { fill: #0b1424; stroke: #2f5f92; stroke-width: 1.5; }
.mvr-moyeu-ico { font-size: 16px; text-anchor: middle; }

/* v0.0.137 : la pastille ouvre sa ligne — elle se donne donc l'air
   cliquable, et le clavier l'atteint comme un bouton. */
.mvr-blip { cursor: pointer; }
.mvr-blip:hover .mvr-cercle { stroke-width: 2.2; }
.mvr-blip:hover .mvr-halo { opacity: .26; }
.mvr-blip:focus-visible { outline: none; }
.mvr-blip:focus-visible .mvr-cercle { stroke: #fff; stroke-width: 2.4; }

/* Le halo bref de la ligne qui vient de répondre au clic. Sans lui, un
   clic sur une pastille du bas fait bouger une ligne hors du regard et
   rien ne dit laquelle. */
.mv-row.est-visee, .mv-groupe.est-visee {
  animation: mvr-visee 1.4s ease-out;
  border-color: var(--accent2) !important;
}
@keyframes mvr-visee {
  0%   { box-shadow: 0 0 0 0 rgba(14, 165, 233, .55); }
  100% { box-shadow: 0 0 0 14px rgba(14, 165, 233, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .mv-row.est-visee, .mv-groupe.est-visee { animation: none; box-shadow: 0 0 0 2px rgba(14, 165, 233, .5); }
}
.mvr-trace { stroke: var(--accent2); stroke-width: 1.4; opacity: .3; }
/* v0.0.142 — le fil qui relie la pastille à l'ancre. ⚠️ POINTILLÉ FIN ET
   ESTOMPÉ, à la demande : il traverse tout le cadran, donc il croise les
   comptes à rebours des autres pastilles. Un fil plein les barrerait. Il
   passe SOUS les marqueurs (`pointer-events: none`) pour ne jamais voler
   un survol. */
.mvr-trace.est-fil {
  stroke-width: 1; opacity: .42; stroke-dasharray: 2 5; stroke-linecap: round;
  pointer-events: none;
}
.mvr-blip:hover .mvr-trace.est-fil,
.mvr-blip.est-allume .mvr-trace.est-fil { opacity: .95; stroke-dasharray: none; }
.mvr-halo { fill: var(--accent2); opacity: .14; }
.mvr-cercle { fill: none; stroke: var(--accent2); stroke-width: 1.3; }
.mvr-ico { font-size: 14px; text-anchor: middle; dominant-baseline: middle; }
.mvr-cdr { font-family: var(--mono); font-size: 10px; fill: #7fd0f5; text-anchor: middle; }
/* la nature du mouvement colore sa pastille — mêmes teintes que les lignes */
.mvr-blip.mv-vm .mvr-cercle    { stroke: var(--accent2); }
.mvr-blip.mv-fleet .mvr-cercle { stroke: var(--accent3); }
.mvr-blip.mv-fleet .mvr-halo   { fill: var(--accent3); }
.mvr-blip.mv-exped .mvr-cercle { stroke: var(--accent4); }
.mvr-blip.mv-exped .mvr-halo   { fill: var(--accent4); }
.mvr-blip.mv-horizon .mvr-cercle { stroke: var(--gold); }
.mvr-blip.mv-horizon .mvr-halo   { fill: var(--gold); }
/* l'allumage croisé : la ligne survolée allume sa pastille, et l'inverse */
.mvr-blip.est-allume .mvr-cercle { stroke: #9fe4ff; stroke-width: 2.2; }
.mvr-blip.est-allume .mvr-halo   { opacity: .3; }
.mv-row.est-allume > summary { background: rgba(14, 165, 233, .10); }

.mvr-prochaine {
  display: flex; gap: 10px; align-items: center;
  border: 1px solid rgba(16, 185, 129, .4); border-radius: 9px;
  background: linear-gradient(90deg, rgba(16, 185, 129, .10), var(--card));
  padding: 9px 12px; margin-bottom: 10px;
}
.mvr-prochaine-ico { font-size: 18px; }
.mvr-prochaine-txt { flex: 1; min-width: 0; }
.mvr-prochaine-txt b { display: block; font-size: 13px; color: var(--text); }
.mvr-prochaine-txt span { display: block; font-size: 11px; color: var(--text3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mvr-prochaine-cdr { text-align: right; }
.mvr-prochaine-cdr .mv-row-cdr { font-size: 15px; }
.mvr-prochaine-heure { display: block; font-family: var(--mono); font-size: 10.5px; color: var(--text3); }

/* écran étroit, ou gros zoom : le radar passe au-dessus de la liste, et il
   se CENTRE. Le seuil est monté de 1100 à 1280 px sur la demande de Loryan
   (« centrer le radar en cas de très gros zoom ») : à 150 % de zoom, un
   écran de 1920 px n'offre plus que 1280 px CSS, et deux colonnes y sont
   déjà trop serrées — le radar y était collé à gauche, décentré. */
@media (max-width: 1280px) {
  .mvr-volets { grid-template-columns: minmax(0, 1fr); }
  .mvr-volet-radar { position: static; max-width: 460px; margin-inline: auto; }
}
body.mobile-mode .mvr-volets { grid-template-columns: minmax(0, 1fr); }
body.mobile-mode .mvr-volet-radar { position: static; max-width: 460px; margin-inline: auto; }
/* LE MÊME CRITÈRE, DANS LE VAISSEAU. Le seuil
   de 1280 px du dessus mesure la FENÊTRE, pas la place laissée à l'écran : dans
   la section Mouvements, le menu du vaisseau et sa marge en prennent ~230 de
   plus. Mesuré au navigateur à 1500 px : deux colonnes y laissaient 602 px à la
   liste, et la route d'un espionnage tombait à 97 px sur les 252 qu'elle
   demande — coupée en « Vaisseau Mère … ».
   Deux gestes, mesurés ensemble : le radar cède 60 px (il est dessiné à
   l'échelle, 360 px se lisent aussi bien), et les deux volets s'empilent
   jusqu'à 1600 px — le point où la liste retrouve ~760 px, mesuré : la même
   route tenait à 1600 et se coupait encore à 1520. */
#sec-mouvements .mvr-volets { grid-template-columns: minmax(0, 360px) minmax(0, 1fr); }
@media (max-width: 1600px) {
  #sec-mouvements .mvr-volets { grid-template-columns: minmax(0, 1fr); }
  #sec-mouvements .mvr-volet-radar { position: static; max-width: 460px; margin-inline: auto; }
}
/* ⚠️ Le mode mobile FORCÉ sur une grande fenêtre doit rester empilé : la règle
   à deux colonnes ci-dessus, par son identifiant, passerait devant
   `body.mobile-mode .mvr-volets`. */
body.mobile-mode #sec-mouvements .mvr-volets { grid-template-columns: minmax(0, 1fr); }
body.mobile-mode #sec-mouvements .mvr-volet-radar { position: static; max-width: 460px; margin-inline: auto; }

/* ── Interrupteur radarMouvementsGrille — la grille des deux volets ──────
   Capture de Loryan : « c'est quoi ce bordel avec le radar qui prend toute la
   place ». Sur un écran de plus de 1 600 px, le radar s'étalait sur toute la
   largeur et la liste passait dessous.
   ⚠️ LA CAUSE N'EST PAS ICI : c'est l'ACCOLADE ORPHELINE qui ferme le bloc
   citeNefCartesLigne, juste au-dessus du titre « LE RADAR ORBITAL ». Un `}`
   en trop fait jeter au navigateur la règle qui le suit, sans un mot — et la
   règle qui suit est `.mvr-volets { display: grid … }`. Sans grille, les deux
   colonnes ci-dessus ne s'appliquaient plus ; sous 1 600 px, la largeur
   bornée à 460 px masquait le défaut.
   Cette règle-ci rend la grille sous l'interrupteur ; la purge retire
   l'accolade et ce bloc ensemble (la règle d'origine reprend alors). */
/* ⚠️ PAS DE `grid-template-columns` ICI : les colonnes restent celles des
   règles du dessus (#sec-mouvements, 1 600 px, téléphone) — celle-ci, plus
   faible qu'un identifiant mais égale à `body.mobile-mode .mvr-volets`, les
   écraserait au mauvais endroit. Elle ne rend que la grille elle-même. */
body.f-radarMouvementsGrille .mvr-volets {
  display: grid; gap: 14px; align-items: start; max-width: 100%;
}

/* Le compteur d'une pastille qui en regroupe plusieurs : « ×N arrivées à
   la même heure ». Il se pose en haut à droite du rond, comme une pastille
   de notification — c'est le seul endroit libre du cercle. */
.mvr-nb-fond { fill: var(--card); stroke: currentColor; stroke-width: 1.2; }
.mvr-nb-txt {
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  fill: currentColor; text-anchor: middle;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.136 — LA BALANCE DE CHARGEMENT (Rampe, devis de la logistique).
   Ce qu'on charge contre ce que la flotte porte :
   le fléau penche, la jauge se remplit, la légende dit ce qui reste.
   ════════════════════════════════════════════════════════════════════ */
.rp-balance {
  display: grid; grid-template-columns: 150px 1fr; gap: .8rem; align-items: center;
  border: 1px solid var(--border); border-radius: 8px;
  background: rgba(255, 255, 255, .02); padding: .5rem .7rem; margin: .3rem 0 .45rem;
}
.rp-balance-svg { display: block; width: 100%; height: auto; }
.rp-balance-pied, .rp-balance-fil { stroke: var(--border2); stroke-width: 2; }
.rp-balance-socle { fill: var(--border2); }
.rp-balance-fleau { stroke: #5b8fb5; stroke-width: 3; stroke-linecap: round; }
.rp-balance-axe { fill: var(--card); stroke: #5b8fb5; stroke-width: 2; }
.rp-balance-plateau { fill: rgba(14, 165, 233, .16); stroke: var(--accent2); stroke-width: 1.3; }
.rp-balance-plateau.est-charge { fill: rgba(124, 58, 237, .22); stroke: var(--accent3); }
.rp-balance-lbl { font-family: var(--mono); font-size: 9px; fill: var(--text3); text-anchor: middle; }
.rp-balance-corps { min-width: 0; }
.rp-balance-chiffre {
  font-family: var(--mono); font-size: 1.05rem; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.rp-balance-sur { font-size: .72rem; color: var(--text3); }
.rp-balance-jauge {
  height: 7px; border-radius: 4px; background: rgba(255, 255, 255, .06);
  overflow: hidden; margin: .3rem 0;
}
.rp-balance-jauge i { display: block; height: 100%; border-radius: 4px;
  background: linear-gradient(90deg, var(--turquoise), var(--accent2)); }
.rp-balance-reste { font-size: .7rem; color: var(--text3); line-height: 1.35; }
/* le ton suit le remplissage : cyan, ambre au-delà de 85 %, rouge en surcharge */
.rp-balance.est-ambre .rp-balance-jauge i { background: linear-gradient(90deg, var(--gold), #fbbf24); }
.rp-balance.est-ambre .rp-balance-chiffre { color: #fde68a; }
.rp-balance.est-rouge { border-color: rgba(233, 69, 96, .55); }
.rp-balance.est-rouge .rp-balance-jauge i { background: linear-gradient(90deg, #b91c1c, var(--accent1)); }
.rp-balance.est-rouge .rp-balance-chiffre,
.rp-balance.est-rouge .rp-balance-reste { color: #fca5a5; }
.rp-balance.est-rouge .rp-balance-fleau { stroke: var(--accent1); }
@media (max-width: 640px) {
  .rp-balance { grid-template-columns: 1fr; }
  .rp-balance-svg { max-width: 190px; margin: 0 auto; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.136 — LES CARTES DE CONTACT (Rampe, écrans Raid et Espionnage).
   Un contact à portée = une carte cliquable, colorée selon la nature de
   la cible ; l'écran du Viseur (rampe-viseur-vue.js) les dessine.
   ════════════════════════════════════════════════════════════════════ */
.rp-contact {
  display: grid; gap: 1px; width: 100%; text-align: left; cursor: pointer;
  font-family: inherit; color: var(--text2);
  background: rgba(255, 255, 255, .02); border: 1px solid var(--border);
  border-radius: 7px; padding: .45rem .6rem; margin-bottom: .35rem; transition: .15s;
}
.rp-contact:hover { background: rgba(255, 255, 255, .05); }
.rp-contact.is-active { border-color: var(--accent1); background: rgba(233, 69, 96, .08); }
.rp-contact-nom { font-size: .82rem; font-weight: 600; color: var(--text); }
.rp-contact-lieu { font-family: var(--mono); font-size: .66rem; color: var(--text3); }
.rp-contact-nature { font-size: .68rem; }
.rp-contact.est-orbital .rp-contact-nature { color: #fca5a5; }
.rp-contact.est-systeme .rp-contact-nature { color: #fde68a; }
.rp-contact.est-scanner .rp-contact-nature { color: #7dd3fc; }
.rp-contact.est-bastion .rp-contact-nature { color: #c4b5fd; }
.rp-contact.est-sonde   .rp-contact-nature { color: #fde68a; }
.rp-contact-spy { font-family: var(--mono); font-size: .64rem; color: var(--text3); }

/* ════════════════════════════════════════════════════════════════════
   v0.0.136 — LE CERCLE DES CONFINS
   La Ligne d'Horizon dessinée telle qu'elle est : un cadran de vingt-
   quatre arcs runiques, le devis au centre, la grille des runes pour
   choisir. Le centre vit dans un <foreignObject> : ses tailles en px
   sont des unités du viewBox 660, elles rétrécissent AVEC le cercle.
   ════════════════════════════════════════════════════════════════════ */
.rp-cercle-volets {
  display: grid; grid-template-columns: minmax(0, 1fr) 340px;
  gap: 1rem; align-items: start; margin-top: .7rem;
}
.rp-cercle-gauche, .rp-cercle-droite { min-width: 0; }

.rp-cercle-scene {
  background: rgba(10, 18, 32, .55); border: 1px solid var(--border);
  border-radius: 12px; padding: .6rem .6rem .45rem;
}
.rp-cercle-svg { display: block; width: 100%; height: auto; max-width: 600px; margin: 0 auto; }
.rp-cercle-secteur { cursor: pointer; transition: filter .15s; }
.rp-cercle-secteur:hover { filter: brightness(1.4); }
.rp-cercle-compte { font-family: var(--mono); font-size: 12px; fill: #93a7c9; }
.rp-cercle-legende {
  text-align: center; font-size: .6rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text3); margin-top: .4rem;
}

/* Le cœur du cadran — un disque HTML au milieu du SVG */
.rp-cercle-centre {
  width: 100%; height: 100%; box-sizing: border-box; border-radius: 50%;
  background: #0a1220; border: 1.5px solid #233761;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 3px; padding: 44px; overflow: hidden;
  font-size: 14px; color: #d8ecff;
}
.rp-cercle-oeil { font-size: 12px; letter-spacing: .18em; text-transform: uppercase; color: #93a7c9; }
.rp-cercle-glyphe { font-size: 42px; line-height: 1.1; color: #ffdf9a; }
.rp-cercle-adresse { font-size: 21px; font-weight: 600; color: #e6efff; }
.rp-cercle-sur { font-size: 12.5px; color: #93a7c9; }
.rp-cercle-fil { width: 56%; height: 1px; background: #1c2c4a; margin: 7px 0 5px; }
.rp-cercle-verdict { font-size: 13.5px; }
.rp-cercle-verdict.est-vierge { color: #6ee7b7; }
.rp-cercle-verdict.est-deja { color: #fbbf24; }
.rp-cercle-devis {
  display: grid; grid-template-columns: auto auto; gap: 7px 26px;
  margin-top: 9px;
}
.rp-cercle-lbl { font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; color: #93a7c9; }
.rp-cercle-val { font-size: 16px; font-family: var(--mono); color: #d8ecff; }
.rp-cercle-val.est-bon { color: #6ee7b7; }
.rp-cercle-val.est-manque { color: #f87171; }
.rp-cercle-attente { font-size: 13px; color: #93a7c9; margin-top: 9px; }

/* La grille des vingt-quatre runes — le vrai sélecteur, taillé au doigt */
.rp-cercle-grille {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: .35rem;
  margin-top: .6rem;
}
.rp-cercle-rune {
  font-family: inherit; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: .35rem .15rem; border-radius: 8px;
  background: rgba(255, 255, 255, .02); border: 1px solid var(--border);
  transition: .15s;
}
.rp-cercle-rune:hover { border-color: var(--border2); }
.rp-cercle-rune-ico { font-size: 1.05rem; line-height: 1.1; color: #5b74a0; }
.rp-cercle-rune-nom { font-size: .58rem; color: var(--text2); }
.rp-cercle-rune-compte { font-size: .58rem; font-family: var(--mono); color: var(--text3); }
.rp-cercle-rune.est-peigne { background: rgba(45, 212, 191, .07); border-color: rgba(45, 212, 191, .3); }
.rp-cercle-rune.est-peigne .rp-cercle-rune-ico { color: #8fd8cd; }
.rp-cercle-rune.est-peigne .rp-cercle-rune-compte { color: #8fd8cd; }
.rp-cercle-rune.is-active { background: rgba(245, 179, 1, .12); border-color: #f5b301; }
.rp-cercle-rune.is-active .rp-cercle-rune-ico { color: #ffdf9a; }

/* En étroit, le cadran passe au-dessus et la grille resserre ses runes */
@media (max-width: 1100px) {
  .rp-cercle-volets { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .rp-cercle-grille { grid-template-columns: repeat(4, 1fr); }
  .rp-cercle-rune-nom { display: none; }
}
body.mobile-mode .rp-cercle-volets { grid-template-columns: 1fr; }
body.mobile-mode .rp-cercle-grille { grid-template-columns: repeat(4, 1fr); }
body.mobile-mode .rp-cercle-rune-nom { display: none; }

/* Le bouton d'envoi du Cercle : or, comme l'arc visé. (Hors du Cercle, le
   bouton garde son style d'origine — on ne touche qu'au nouvel écran.) */
.rp-cercle-droite .btn-primary.rp-envoyer {
  padding: 8px 18px;
  background: linear-gradient(90deg, #a16207, #f5b301);
  border: none; border-radius: 6px;
  color: #1a1204; font-weight: 700; cursor: pointer;
  font-family: inherit; font-size: .85rem;
}
.rp-cercle-droite .btn-primary.rp-envoyer:hover { filter: brightness(1.1); }

/* @@@ DEBUT REFONTE RAMPE v0.0.136 @@@ */
/* ════════════════════════════════════════════════════════════════════
   LE TABLEAU D'ESCADRILLE (maquette B6 — Rampe, onglet Expédition).
   Rendu par features/fleet-launcher/rampe-escadrille.js.
   ════════════════════════════════════════════════════════════════════
   UNE LIGNE PAR VAISSEAU, et tout tient dessus : le voyant d'état,
   l'icône, le nom et le stock, ce qui vole déjà, les pastilles de durée,
   la molette d'effectif, les deux boutons d'envoi. Puis une ligne de
   pied (plafond de flotte à gauche, prochaine arrivée à droite) et trois
   panneaux côte à côte.

   ⚠️ PRÉFIXE `rpe-`, ET IL EST DÉLIBÉRÉ : rien ici ne peut heurter par
   accident une règle `rp-` partagée (devis, badge du renfort
   Spécialiste…) qui sert encore ailleurs dans la Rampe.

   SEULES EXCEPTIONS AU PRÉFIXE : les classes posées par
   `exped2DeuxBoutons` et `exped2SelecteurVoyageur`, qu'on ne réécrit
   jamais (elles sont le contrat des boutons d'envoi). Elles ne sont
   surchargées que DANS le scope `.rpe-cmd` et `.rpe-reglages` — jamais
   au global, sinon la fenêtre du module Vaisseaux changerait d'aspect
   avec elles.

   ⚠️ ET LES DEUX SYSTÈMES D'AFFICHAGE MOBILE SONT TRAITÉS TOUS LES DEUX.
   `body.mobile-mode` est posée par mobile-mode.js SANS regarder la
   largeur ; les `@media` regardent la largeur SANS connaître la classe.
   Chaque repli existe donc en double, aux deux paliers (900 et 700).
   ─────────────────────────────────────────────────────────────────── */

.rpe { display: grid; gap: .55rem; min-width: 0; }

/* ─── La bande d'état ────────────────────────────────────────────── */
/* Elle redit le nom et le lieu que le haut de la Rampe affiche déjà,
   mais elle apporte le Hangar, les flottes en l'air et la coque — qui
   n'existent nulle part ailleurs sur cet onglet. */
.rpe-strip {
  display: flex; flex-wrap: wrap; gap: .4rem 1.6rem; align-items: baseline;
  padding: .45rem .6rem; min-width: 0;
  border: 1px solid var(--border); border-radius: 8px;
  background: rgba(255, 255, 255, .02);
}
.rpe-case { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.rpe-case.est-fin { margin-left: auto; }
.rpe-lbl {
  font-family: var(--mono); font-size: .78rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text3); white-space: nowrap;
}
.rpe-val {
  font-family: var(--mono); font-size: .95rem; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.rpe-mini { font-family: var(--mono); font-size: .78rem; color: var(--text3); }
.rpe-num { font-variant-numeric: tabular-nums; }

/* ─── Le panneau, et son titre ───────────────────────────────────── */
.rpe-pnl {
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--card); padding: .6rem .7rem; min-width: 0;
}
.rpe-h6 {
  display: flex; justify-content: space-between; align-items: center; gap: .5rem;
  margin: 0 0 .5rem; font-family: var(--mono); font-size: .72rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--text3);
}
/* Le panneau du tableau est le plus dense de l'écran : son titre se serre
   un peu plus que celui des trois panneaux du bas. */
.rpe-tableau .rpe-h6 { margin-bottom: .35rem; }
.rpe-chapeau {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .6rem;
  margin-bottom: .55rem;
}
.rpe-hint { flex: 1 1 16rem; min-width: 0; font-size: .82rem; color: var(--text3); line-height: 1.4; }
.rpe-tag {
  font-family: var(--mono); font-size: .72rem; letter-spacing: .1em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 999px;
  border: 1px solid var(--border2); color: var(--text2); white-space: nowrap;
}
.rpe-tag.est-cy {
  color: #bfe8ff; border-color: rgba(14, 165, 233, .5);
  background: rgba(14, 165, 233, .12);
}

/* ─── UNE LIGNE DE L'ESCADRILLE ──────────────────────────────────── */
/* Six cellules, alignées en colonnes d'une ligne à l'autre : c'est
   l'alignement qui fait le tableau. Un cinquième vaisseau n'ajoute qu'une
   ligne de ~34 px — la densité tient. */
.rpe-ligne {
  display: grid; align-items: center; column-gap: .6rem; row-gap: 2px;
  /* ⚠️ DES LARGEURS PLAFONNÉES, ET C'EST CE QUI FAIT LE TABLEAU. Chaque
     ligne est sa PROPRE grille (elle porte son cadre et son fond) : des
     colonnes en `auto` se seraient donc calées sur le contenu de CHAQUE
     ligne prise à part, et « en vol » ne serait pas tombé deux fois au
     même endroit. Quatre lignes mal alignées, ce sont quatre tuiles —
     précisément ce qu'on remplace. Une seule colonne reste élastique,
     celle du NOM, comme sur la maquette.
     ⚠️ ET AUCUNE COLONNE NE PEUT FORCER LA LARGEUR (v0.0.141) : les
     pistes sont en `minmax(0, …)`, jamais nues — une piste `270px` nue
     refuse de descendre et pousse la grille par-dessus le rail des
     Vaisseaux Mères, quoi qu'on fasse du contenu. C'est le filet, il
     tient même chez un navigateur qui ignore `@container`.
     ⚠️ Et les cellules qui pourraient déborder (réglages, commandes)
     REPLIENT leur contenu au lieu de pousser la page : c'est le défaut
     n° 1 de la maison, et il ne se signale par aucune erreur. */
  grid-template-columns: 10px 22px minmax(90px, 1fr)
                         minmax(0, 150px) minmax(0, 270px) minmax(0, 195px);
  grid-template-areas: "led ico nom vol reg cmd";
  padding: .34rem .55rem; margin-bottom: .3rem; min-width: 0;
  border: 1px solid var(--border); border-radius: 7px;
  background: rgba(255, 255, 255, .02);
}
.rpe-ligne > * { min-width: 0; }
.rpe-ligne.est-active {
  border-color: var(--turquoise); background: rgba(45, 212, 191, .09);
  box-shadow: inset 0 0 0 1px var(--turquoise);
}
/* ⚠️ .68 ET NON .45 : il faut pouvoir LIRE pourquoi la ligne est bloquée.
   Une ligne trop pâle cache la raison en même temps que le bouton. */
.rpe-ligne.est-bloque { opacity: .68; }

.rpe-led {
  grid-area: led; width: 8px; height: 8px; border-radius: 50%;
  background: var(--border2); cursor: help;
}
.rpe-led.est-vert { background: #10b981; box-shadow: 0 0 7px rgba(16, 185, 129, .65); }
.rpe-led.est-ambre { background: var(--gold); box-shadow: 0 0 7px rgba(245, 158, 11, .55); }
.rpe-led.est-eteint { opacity: .5; }

.rpe-ico { grid-area: ico; font-size: 1rem; line-height: 1; }

.rpe-nom {
  grid-area: nom; display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 0 .35rem; min-width: 0; overflow: hidden;
  padding: 2px 0; border: 0; background: none; text-align: left;
  color: inherit; font: inherit; line-height: 1.4; cursor: pointer;
}
.rpe-nom-txt { color: #dbe8ff; font-size: .92rem; font-weight: 600; white-space: nowrap; }
.rpe-stock {
  font-family: var(--mono); font-size: .78rem; color: var(--text3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.rpe-trop { color: var(--gold); cursor: help; }

/* Le compteur reste sur une ligne ; le rognage n'est qu'un garde-fou de
   dernier recours (un compte à quatre chiffres avec renfort de
   Spécialiste), et l'infobulle porte de toute façon le détail complet. */
.rpe-vol {
  grid-area: vol; font-family: var(--mono); font-size: .78rem;
  color: var(--text3); cursor: help; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.rpe-vol .rpe-num { color: #7fd0f5; font-weight: 400; }
.rpe-vol.est-plein .rpe-num { color: var(--gold); }

/* ─── La cellule des réglages : pastilles + molette ───────────────── */
.rpe-reglages {
  grid-area: reg; display: flex; flex-wrap: wrap; align-items: center;
  gap: .25rem .4rem; min-width: 0;
}
/* ⚠️ UNE LARGEUR FIXE POUR LES PASTILLES, pour la même raison que les
   colonnes de la ligne : sans elle, la molette du Récolteur (cinq
   pastilles) et celle du Limier (une seule) ne tomberaient pas au même
   endroit, et la colonne des effectifs cesserait d'en être une. */
.rpe-pastilles {
  display: inline-flex; flex-wrap: wrap; gap: 3px; min-width: 0;
  flex: 0 0 186px;
}
/* Le sélecteur de destination du Voyageur est le seul réglage que les
   trois autres vaisseaux n'ont pas : il prend sa propre ligne dans la
   cellule plutôt que de décaler la molette de CETTE ligne-là. */
.rpe-reglages > .exped2-selecteur { flex: 1 1 100%; }
.rpe-molette-zone { display: inline-flex; align-items: center; gap: .3rem; }

/* Les pastilles : cliquables pour le Récolteur (c'est le seul dont la
   durée se règle), INERTES ailleurs — la cadence du Limier, du Précurseur
   et du Voyageur est imposée par le Hangar, elle ne se choisit pas. */
.rpe-pastille {
  border: 1px solid var(--border2); border-radius: 999px; padding: 2px 8px;
  font-family: var(--mono); font-size: .78rem; line-height: 1.35;
  color: var(--text2); background: rgba(255, 255, 255, .03);
  cursor: pointer; white-space: nowrap;
}
.rpe-pastille:hover:not(.est-fixe) { border-color: var(--accent2); color: #d9f4ff; }
.rpe-pastille.est-on {
  border-color: var(--accent2); color: #bfe8ff; background: rgba(14, 165, 233, .14);
}
.rpe-pastille.est-fixe { cursor: help; }
.rpe-pastille.est-gain { color: #4ade80; }
.rpe-pastille.est-perte { color: #fbbf24; }

/* La molette −/+ : elle remplace le champ numérique et ses flèches
   natives, illisibles au doigt. */
.rpe-molette {
  display: inline-flex; align-items: center; gap: 2px;
  border: 1px solid var(--border2); border-radius: 6px;
  background: rgba(0, 0, 0, .28); padding: 1px 3px;
}
/* La molette du Précurseur et du Voyageur ne règle rien — un vaisseau par
   mission — mais elle reste MONTRÉE, comme sur la maquette : la retirer
   ferait croire à une ligne incomplète. Son infobulle dit pourquoi. */
.rpe-molette.est-morte { cursor: help; }
/* ⚠️ `inline-flex` ET NON le défaut : les deux crans du Précurseur et du
   Voyageur sont des <span> (pas des <button>, puisqu'ils ne font rien), et
   une largeur posée sur un élément INLINE est purement et simplement
   ignorée — les deux signes se seraient serrés en exposant à côté du
   chiffre. Même remarque pour le <b> du milieu, d'où `inline-block`. */
.rpe-cran {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; border: 0; background: none;
  color: var(--text2); font-size: .85rem; line-height: 1; cursor: pointer;
  border-radius: 4px;
}
.rpe-cran:hover:not(.est-mort) { background: rgba(255, 255, 255, .08); color: var(--text); }
.rpe-cran.est-mort { opacity: .35; cursor: default; }
.rpe-cran-val {
  display: inline-block;
  width: 3.6ch; padding: 1px 0; text-align: center; font-family: var(--mono);
  font-size: .76rem; background: none; border: 0; color: var(--text);
  -moz-appearance: textfield; appearance: textfield;
}
.rpe-cran-val.est-mort { opacity: .35; }
.rpe-cran-val::-webkit-outer-spin-button,
.rpe-cran-val::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.rpe-sur {
  font-family: var(--mono); font-size: .76rem; color: var(--text3);
  white-space: nowrap; cursor: help;
}

/* Le sélecteur de destination du Voyageur vit dans cette même cellule :
   sans lui, aucun moyen de choisir vers quel Vaisseau Mère partir. On le
   resserre au format de la ligne, sans toucher à son style global. */
.rpe-reglages .exped2-selecteur {
  padding: 2px 6px; font-size: .76rem; border-radius: 5px; max-width: 100%;
}
.rpe-reglages .exped2-selecteur-cible select {
  max-width: 12rem; font-size: .76rem; padding: 2px 4px;
}

/* ─── La cellule des commandes ───────────────────────────────────── */
/* ⚠️ LES BOUTONS RESTENT, ÉTEINTS, ET LA RAISON S'ÉCRIT DESSOUS. L'écran
   d'avant les faisait disparaître au profit d'une phrase : le joueur ne
   voyait plus de bouton du tout et ne savait pas qu'il en existait un. */
.rpe-cmd { grid-area: cmd; min-width: 0; }
.rpe-cmd .exped2-actions {
  margin-top: 0; gap: 6px; flex-wrap: wrap; justify-content: flex-end;
}
.rpe-cmd .exped2-actions button {
  padding: 3px 11px; font-size: .78rem; border-radius: 999px; white-space: nowrap;
}
.rpe-cmd .exped2-rien {
  flex-basis: 100%; text-align: right; font-size: .74rem; color: #fbbf24; margin-top: 2px;
}

/* ─── La ligne de pied du tableau ────────────────────────────────── */
.rpe-pied {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  align-items: baseline; gap: .3rem .8rem; margin-top: .55rem;
  padding-top: .45rem; border-top: 1px solid var(--border);
}
.rpe-pied-fin { display: inline-flex; align-items: baseline; gap: .35rem; }

/* ─── Les trois panneaux du bas ──────────────────────────────────── */
.rpe-grille { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: .5rem; }
.rpe-pile { display: grid; gap: .35rem; min-width: 0; }
.rpe-vide { font-size: .8rem; color: var(--text3); }

.rpe-jauge {
  border: 1px solid var(--border); border-radius: 6px;
  background: rgba(0, 0, 0, .22); padding: .35rem .5rem;
}
.rpe-jauge-lgn {
  display: flex; justify-content: space-between; gap: .5rem;
  font-family: var(--mono); font-size: .74rem; color: var(--text3); margin-bottom: 3px;
}
.rpe-jauge-val { font-family: var(--mono); font-size: .84rem; color: var(--text); }
.rpe-jauge-sur { color: var(--text3); font-size: .78rem; }
.rpe-barre {
  height: 4px; margin-top: 5px; border-radius: 3px;
  background: rgba(255, 255, 255, .06); overflow: hidden;
}
.rpe-barre i {
  display: block; height: 100%;
  background: linear-gradient(90deg, var(--turquoise), var(--accent2));
}
.rpe-barre.est-ambre i { background: linear-gradient(90deg, var(--gold), #fbbf24); }

.rpe-lgn {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: .5rem; min-width: 0; width: 100%;
  padding: 0; border: 0; background: none; text-align: left;
  color: inherit; font: inherit;
}
.rpe-lgn-txt {
  font-size: .82rem; color: var(--text2); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rpe-lgn-clic { cursor: pointer; border-radius: 4px; }
.rpe-lgn-clic:hover { background: rgba(255, 255, 255, .05); }
.rpe-lgn .rpe-val { font-size: .8rem; white-space: nowrap; }

/* ════════════════════════════════════════════════════════════════════
   PALIER INTERMÉDIAIRE — les trois panneaux passent à deux colonnes,
   le troisième prenant toute la largeur. La ligne, elle, tient encore.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 1100px) {
  .rpe-grille { grid-template-columns: 1fr 1fr; }
  .rpe-grille > .rpe-pnl:last-child { grid-column: 1 / -1; }
}

/* ════════════════════════════════════════════════════════════════════
   LE REPLI EN ÉTAGES — LA LIGNE SE REPLIE : nom et stock au-dessus,
   réglages et boutons en dessous. AUCUNE information ne disparaît, c'est
   la consigne de la maquette. Le repli en LARGEUR écoute le CONTENEUR
   (`@container escadrille`, plus bas — la fenêtre ne dit pas la largeur
   dont la ligne dispose) ; celui-ci écoute le MODE MOBILE, posé par
   mobile-mode.js sans regarder aucune largeur.
   ═══════════════════════════════════════════════════════════════════ */
body.mobile-mode .rpe-ligne {
  grid-template-columns: 10px 22px minmax(0, 1fr) auto;
  grid-template-areas: "led ico nom vol" "reg reg reg reg" "cmd cmd cmd cmd";
  row-gap: .35rem;
}
body.mobile-mode .rpe-reglages { flex-wrap: wrap; }
body.mobile-mode .rpe-cmd .exped2-actions { justify-content: flex-start; }
body.mobile-mode .rpe-cmd .exped2-rien { text-align: left; }
body.mobile-mode .rpe-nom { overflow: visible; }
body.mobile-mode .rpe-stock { white-space: normal; overflow: visible; text-overflow: clip; }

/* ════════════════════════════════════════════════════════════════════
   TÉLÉPHONE (≤ 700 px) — une colonne, et des cibles qu'un doigt attrape.
   ⚠️ Les trois défauts qu'on vérifie : rien ne DÉPASSE (la page ne défile
   jamais en largeur), rien n'est TRONQUÉ, tout reste ATTEIGNABLE.
   ═══════════════════════════════════════════════════════════════════ */
/* ⚠️ TROIS RÉGLAGES TÉLÉPHONE MANQUENT ICI, ET C'EST VOULU — l'écart de la
   bande d'état (`.rpe-strip`), la largeur libre des pastilles
   (`.rpe-pastilles`) et le retrait des boutons de commande. Ils étaient
   écrits, mais la v0.0.145 posait au-dessus d'eux un sélecteur plus lourd
   qui gagnait à TOUTES les largeurs, téléphone compris : depuis, ils
   n'avaient plus aucun effet. La v0.0.145 ayant fondu ses valeurs dans les
   règles de base, les réécrire ici CHANGERAIT l'écran du téléphone au lieu
   de le conserver. On garde donc ce que le joueur voit : écart 1,6 rem,
   pastilles à 186 px, boutons à 3px/11px, aux deux formats. */
@media (max-width: 700px) {
  .rpe-grille { grid-template-columns: 1fr; }
  .rpe-pnl { padding: .5rem .55rem; }
  .rpe-pastille { padding: 4px 10px; min-height: 32px; display: inline-flex;
                  align-items: center; }
  .rpe-cran { width: 32px; min-height: 32px; }
  .rpe-cran-val { width: 4ch; min-height: 30px; font-size: .82rem; }
  .rpe-nom { min-height: 32px; align-content: center; }
  .rpe-lgn-clic { min-height: 32px; padding: 2px 4px; align-items: center; }
  .rpe-cmd .exped2-actions { gap: 8px; }
  .rpe-cmd .exped2-actions button {
    flex: 1 1 auto; justify-content: center; min-height: 32px;
  }
  .rpe-reglages .exped2-selecteur-cible select { max-width: 100%; min-height: 32px; }
}
body.mobile-mode .rpe-grille { grid-template-columns: 1fr; }
body.mobile-mode .rpe-pnl { padding: .5rem .55rem; }
body.mobile-mode .rpe-pastille {
  padding: 4px 10px; min-height: 32px; display: inline-flex; align-items: center;
}
body.mobile-mode .rpe-cran { width: 32px; min-height: 32px; }
body.mobile-mode .rpe-cran-val { width: 4ch; min-height: 30px; font-size: .82rem; }
body.mobile-mode .rpe-nom { min-height: 32px; align-content: center; }
body.mobile-mode .rpe-lgn-clic { min-height: 32px; padding: 2px 4px; align-items: center; }
body.mobile-mode .rpe-cmd .exped2-actions { gap: 8px; }
body.mobile-mode .rpe-cmd .exped2-actions button {
  flex: 1 1 auto; justify-content: center; min-height: 32px;
}
body.mobile-mode .rpe-reglages .exped2-selecteur-cible select {
  max-width: 100%; min-height: 32px;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.145 — LES RÉGLAGES DE L'ESCADRILLE NE GROSSISSENT QUE LÀ OÙ LA
   LARGEUR EXISTE
   ════════════════════════════════════════════════════════════════════
   Mesuré : pastilles agrandies (186 px de piste) + molette font 294 px, la
   colonne fixe en offre 270 — la molette passerait SOUS les pastilles et
   chaque ligne gagnerait 24 px, exactement ce que « sans agrandir les
   cases » interdit. Au-dessus de 881 px de conteneur, la colonne des
   réglages s'élargit en prenant sur la place libre — la colonne du nom
   est élastique. Entre 901 et 1 199 px de fenêtre, cette place n'existe
   en général pas : les réglages reprennent leur taille serrée, le reste
   de la ligne reste agrandi. Sous 880 px de conteneur, la ligne est déjà
   repliée en étages : les réglages y ont leur rangée entière, rien à
   replier. */
/* ⚠️ LA COLONNE DES RÉGLAGES S'ÉLARGIT DANS LE CONTENEUR, PAS DANS LA
   FENÊTRE. L'Escadrille se replie sur la largeur de son CONTENEUR
   (v0.0.141), et `@container` ne donne AUCUNE spécificité : le sélecteur
   ci-dessous doit peser plus lourd que les autres règles de colonnes de
   `.rpe-ligne`, sinon la colonne resterait à 270 px — mesuré, la molette
   passait sous les pastilles et la ligne gagnait 24 px.
   ⚠️ AU-DESSUS DE 880 px DE CONTENEUR SEULEMENT : sous ce seuil la ligne se
   replie déjà en étages (le `@container max-width: 880px` juste plus bas), et
   forcer six colonnes ici casserait ce repli. `minmax(0, …)` garantit qu'on
   ne pousse jamais la grille dehors. */
@container escadrille (min-width: 881px) {
  .rpe .rpe-tableau .rpe-ligne {
    grid-template-columns: 10px 22px minmax(90px, 1fr)
                           minmax(0, 150px) minmax(0, 320px) minmax(0, 205px);
  }
}
@media (min-width: 901px) and (max-width: 1199px) {
  .rpe .rpe-pastilles { flex: 0 0 162px; }
  .rpe .rpe-pastille { font-size: .68rem; }
  .rpe .rpe-sur { font-size: .64rem; }
  .rpe .rpe-reglages .exped2-selecteur,
  .rpe .rpe-reglages .exped2-selecteur-cible select { font-size: .66rem; }
  .rpe .rpe-cmd .exped2-actions button { font-size: .72rem; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.136 — L'ÉCRAN DE LA BALANCE (Rampe, les quatre missions de
   logistique : Transport, Stationner, Commercer, Ferrailler ;
   maquette B10).
   Le rendu vit dans features/fleet-launcher/rampe-balance.js.

   TROIS COLONNES : ce que je charge à gauche, le fléau et la jauge au
   centre, le convoi à droite ; dessous, la destination en cartes et le
   reçu du devis. Toutes les classes sont préfixées `rpb-` : rien ici ne
   peut heurter une règle `rp-` existante.

   ⚠️ LES TROIS DÉFAUTS QUE CE PROJET ATTRAPE EN BOUCLE sont tous
   possibles sur cet écran, parce qu'il porte jusqu'à dix lignes de
   saisie à deux boutons chacune :
     · DÉBORDEMENT — d'où `min-width:0` sur chaque cellule de grille et
       aucune largeur fixe en px sur un conteneur ;
     · TRONCATURE — d'où `overflow-wrap:anywhere` sur les noms de
       vaisseaux (« Transporteur de Flotte » ne tient pas à 390 px) ;
     · CIBLE INATTEIGNABLE — d'où les molettes portées à 32 px au doigt.
   Les DEUX systèmes d'affichage mobile sont traités : la media query
   `max-width:700px` ET la classe `body.mobile-mode`. L'une sans l'autre
   ne mesure que la moitié du mode mobile.
   ════════════════════════════════════════════════════════════════════ */

.rpb { min-width: 0; }
.rpb-phrase {
  font-size: .74rem; line-height: 1.45; color: var(--text3);
  margin: 0 0 .7rem; max-width: 70ch;
}

/* ─── Les trois plateaux ─────────────────────────────────────────── */
.rpb-plateaux {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(240px, 300px) minmax(0, 1fr);
  gap: .8rem; align-items: start;
}
.rpb-bas {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: .8rem; margin-top: .8rem;
}
.rpb-plateau,
.rpb-pnl {
  border: 1px solid var(--border); border-radius: 8px;
  background: rgba(255, 255, 255, .02);
  padding: .6rem .7rem; min-width: 0;
}
.rpb-titre {
  font-family: var(--hud); font-size: .62rem; letter-spacing: .11em;
  text-transform: uppercase; color: var(--text3);
  display: flex; justify-content: space-between; align-items: baseline;
  gap: .5rem; margin: 0 0 .45rem; font-weight: 600;
}
.rpb-titre-n {
  font-family: var(--mono); color: var(--text); letter-spacing: 0;
  text-transform: none; font-size: .82rem; font-variant-numeric: tabular-nums;
  text-align: right; min-width: 0; overflow-wrap: anywhere;
}
.rpb-dim { color: var(--text3); font-family: var(--mono); font-size: .62rem;
  letter-spacing: 0; text-transform: none; }
.rpb-sous-titre {
  font-family: var(--hud); font-size: .6rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text3);
  margin: .6rem 0 .3rem; display: flex; flex-wrap: wrap; gap: .4rem;
  align-items: baseline;
}

/* ─── Une ligne de saisie (ressource, unité de fret, vaisseau) ───── */
.rpb-ligne {
  display: flex; align-items: center; gap: .5rem; min-width: 0;
  padding: .3rem 0; border-bottom: 1px solid var(--border); font-size: .76rem;
}
.rpb-ligne:last-child { border-bottom: none; }
.rpb-nom { flex: 1; min-width: 0; overflow-wrap: anywhere; color: var(--text2); }
.rpb-corps { flex: 1; min-width: 0; display: grid; }
.rpb-sous {
  font-family: var(--mono); font-size: .64rem; color: var(--text3);
  overflow-wrap: anywhere;
}
.rpb-ico { font-size: .95rem; flex: 0 0 auto; }
.rpb-pastille { width: 8px; height: 8px; border-radius: 2px; flex: 0 0 auto; }
.rpb-dispo {
  font-family: var(--mono); font-size: .66rem; color: var(--text3);
  flex: 0 0 auto; white-space: nowrap;
}
.rpb-tag {
  font-size: .56rem; text-transform: uppercase; letter-spacing: .08em;
  color: #9fd0ff; border: 1px solid rgba(126, 196, 255, .35);
  background: rgba(126, 196, 255, .08); border-radius: 4px;
  padding: 0 4px; flex: 0 0 auto;
}
.rpb-ligne--rare {
  padding-left: .45rem;
  border-left: 2px solid rgba(126, 196, 255, .45);
  background: rgba(126, 196, 255, .05);
}

/* La molette −/+ : deux cibles et un champ, jamais autre chose. */
.rpb-molette { display: inline-flex; align-items: center; gap: 2px; flex: 0 0 auto; }
.rpb-pas {
  width: 26px; height: 28px; line-height: 1; cursor: pointer;
  background: rgba(255, 255, 255, .04); border: 1px solid var(--border);
  border-radius: 5px; color: var(--text2); font: inherit; font-size: .9rem;
  padding: 0; flex: 0 0 auto;
}
.rpb-pas:hover { background: rgba(255, 255, 255, .09); color: var(--text); }
/* ⚠️ LE CHAMP EST UNE CIBLE AUTANT QUE LES DEUX BOUTONS : c'est là qu'on
   tape un nombre plutôt que de cliquer trente fois. Mesuré au navigateur,
   `.rp-input` seul donne 23 px de haut — sous la cible au doigt, et plus
   bas que les molettes qui l'encadrent. */
.rpb-input {
  width: 84px; text-align: right; padding: .3rem .45rem;
  min-height: 28px; box-sizing: border-box;
}

/* Interrupteur `rampeBesoinPorteurs` — le bouton « Besoin » d'un porteur.
   Il vit SOUS le sous-titre, dans la colonne du nom (`.rpb-corps`, une
   grille) : à côté de la molette, il écrasait cette colonne sur grand écran
   et s'en séparait au téléphone. `justify-self: start` le garde à sa taille.
   Texte au plancher de lisibilité du jeu. */
.rpb-besoin {
  justify-self: start; margin-top: 3px; min-height: 24px; padding: 2px .55rem;
  cursor: pointer; font: inherit; font-size: var(--badge-font-size, 11px);
  font-weight: 600; letter-spacing: .02em; line-height: 1.2;
  color: #9ff0d0; background: rgba(80, 220, 170, .08);
  border: 1px solid rgba(80, 220, 170, .45); border-radius: 5px;
}
.rpb-besoin:hover { background: rgba(80, 220, 170, .16); }
.rpb-besoin.est-calcul { opacity: .55; cursor: progress; }
@media (max-width: 700px) { .rpb-besoin { min-height: 32px; } }
body.mobile-mode .rpb-besoin { min-height: 32px; }

/* ⚠️ LES TROIS RARES RESTENT DANS LE DOM MÊME REPLIÉES : le JS ne pose
   `est-replie` que si les trois champs sont à zéro. Un champ masqué mais
   non nul serait quand même lu au départ, et partirait sans être vu. */
.rpb-rares.est-replie { display: none; }

/* ─── Le centre : le fléau, les jauges, la légende ───────────────── */
.rpb-centre { display: grid; gap: .6rem; justify-items: stretch; min-width: 0; }
.rpb-svg { display: block; width: 100%; max-width: 280px; height: auto; margin: 0 auto; }
.rpb-svg-pied, .rpb-svg-fil { stroke: var(--border2); stroke-width: 2; }
.rpb-svg-socle { fill: var(--border2); }
.rpb-svg-fleau { stroke: #5b8fb5; stroke-width: 4; stroke-linecap: round; }
.rpb-svg-axe { fill: var(--card); stroke: #5b8fb5; stroke-width: 2; }
.rpb-svg-plateau { fill: rgba(14, 165, 233, .18); stroke: var(--accent2); stroke-width: 1.4; }
.rpb-svg-plateau.est-charge { fill: rgba(124, 58, 237, .22); stroke: var(--accent3); }
.rpb-svg-lbl {
  font-family: var(--mono); font-size: 9px; fill: var(--text3); text-anchor: middle;
}
.rpb-svg-txt {
  font-family: var(--mono); font-size: 11px; fill: var(--text3); text-anchor: middle;
}

.rpb-gauge { width: 100%; min-width: 0; }
.rpb-gl {
  display: flex; justify-content: space-between; align-items: baseline; gap: .5rem;
  font-family: var(--hud); font-size: .58rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text3);
}
.rpb-pct { font-family: var(--mono); letter-spacing: 0; color: var(--turquoise); }
.rpb-gv {
  font-family: var(--mono); font-size: 1.05rem; color: var(--text);
  font-variant-numeric: tabular-nums; margin: .1rem 0 .25rem;
  overflow-wrap: anywhere;
}
.rpb-sur { font-size: .72rem; color: var(--text3); }
.rpb-bar {
  height: 8px; border-radius: 4px; background: rgba(255, 255, 255, .06);
  overflow: hidden;
}
.rpb-bar i {
  display: block; height: 100%; border-radius: 4px;
  background: linear-gradient(90deg, var(--turquoise), var(--accent2));
}
.rpb-reste {
  font-size: .72rem; line-height: 1.4; color: var(--text3); text-align: center;
}
.rpb-reste b { font-family: var(--mono); color: var(--turquoise); font-weight: 600; }

/* ⚠️ LE TON DE LA RACINE NE PEINT QUE CE QUI PARLE DE L'ÉCRAN ENTIER :
   le fléau, la légende et le bouton. Les BARRES, elles, portent leur
   propre ton — sans quoi une poche remplie à 12 % passait au rouge
   parce que l'AUTRE débordait, et l'écran accusait la mauvaise ligne.
   Le cas se produit dès qu'il y a deux jauges : les rares au Transport,
   les deux soutes au Ferraillage. */
.rpb.est-rouge .rpb-reste { color: #fca5a5; }
.rpb.est-rouge .rpb-reste b { color: #fecaca; }
.rpb.est-rouge .rpb-svg-fleau { stroke: var(--accent1); }
.rpb.est-rouge .rpb-svg-plateau.est-charge {
  fill: rgba(233, 69, 96, .22); stroke: var(--accent1);
}
/* Chaque poche dit sa propre vérité : cyan, ambre au-delà de 85 %,
   rouge en surcharge. */
.rpb-gauge.est-ambre .rpb-pct { color: var(--gold); }
.rpb-gauge.est-ambre .rpb-gv { color: #fde68a; }
.rpb-gauge.est-ambre .rpb-bar i {
  background: linear-gradient(90deg, var(--gold), #fbbf24);
}
.rpb-gauge.est-rouge .rpb-pct,
.rpb-gauge.est-rouge .rpb-gv { color: #fca5a5; }
.rpb-gauge.est-rouge .rpb-bar i {
  background: linear-gradient(90deg, #b91c1c, var(--accent1));
}

/* La fiche du Stationnement : pas de soute, un seul chiffre. */
.rpb-fiche {
  border: 1px solid var(--border); border-radius: 7px;
  background: rgba(0, 0, 0, .18); padding: .7rem .8rem; margin-bottom: .6rem;
}
.rpb-gros {
  font-family: var(--mono); font-size: 1.7rem; line-height: 1.1;
  color: var(--text); font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.rpb-gros-lbl {
  font-family: var(--hud); font-size: .58rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text3); margin-bottom: .5rem;
}
.rpb-ligne-fiche {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: .6rem; font-size: .72rem; color: var(--text3);
  padding: .22rem 0; border-top: 1px solid var(--border); min-width: 0;
}
.rpb-ligne-fiche b {
  font-family: var(--mono); color: var(--text2); font-weight: 600;
  text-align: right; overflow-wrap: anywhere;
}

/* ─── Le grand bouton d'envoi ────────────────────────────────────── */
/* ⚠️ IL EST HORS DU BLOC REPEINT et n'est JAMAIS désactivé : le devis
   peut être en retard d'une frappe, et c'est le serveur qui tranche. */
.rpb-centre .btn-primary.rpb-envoyer {
  width: 100%; margin-top: 0; padding: 10px 18px;
  border: none; border-radius: 6px; cursor: pointer;
  font-family: inherit; font-size: .86rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: #062a26;
  background: linear-gradient(90deg, var(--turquoise), var(--accent2));
}
.rpb-centre .btn-primary.rpb-envoyer:hover { filter: brightness(1.1); }
.rpb.est-rouge .rpb-centre .btn-primary.rpb-envoyer {
  background: linear-gradient(90deg, #b91c1c, var(--accent1)); color: #fff;
}

/* ─── Les destinations en cartes ─────────────────────────────────── */
/* ⚠️ Les injoignables sont MONTRÉES, grisées, avec leur raison : avant,
   un Vaisseau Mère en vol disparaissait de la liste sans un mot. */
.rpb-dests { display: flex; flex-direction: column; gap: .35rem; }
.rpb-dest {
  display: flex; align-items: center; gap: .5rem; width: 100%; min-width: 0;
  text-align: left; cursor: pointer; font: inherit;
  background: rgba(255, 255, 255, .02); border: 1px solid var(--border);
  border-radius: 7px; padding: .45rem .6rem; color: var(--text2);
}
.rpb-dest:hover:not(:disabled) { border-color: var(--border2); background: rgba(255, 255, 255, .05); }
.rpb-dest.is-active { border-color: var(--turquoise); background: rgba(45, 212, 191, .08); }
.rpb-dest.est-hors { opacity: .5; cursor: not-allowed; }
.rpb-dest-puce {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  border: 1px solid var(--border2); background: rgba(255, 255, 255, .05);
}
.rpb-dest.is-active .rpb-dest-puce {
  background: var(--turquoise); border-color: var(--turquoise);
}
.rpb-dest-corps { display: grid; min-width: 0; }
.rpb-dest-nom { font-size: .78rem; color: var(--text); overflow-wrap: anywhere; }
.rpb-dest-sous {
  font-size: .66rem; color: var(--text3); overflow-wrap: anywhere;
}
.rpb-coords { font-family: var(--mono); }

/* ⚠️ NEUTRALISATION DU DOUBLON DE FLÉAU. Sous le même interrupteur,
   `rampeDevisHtml` glisse déjà un PETIT fléau dans le reçu, à la place de
   la ligne « Soute de la flotte ». Dans cet écran il tomberait juste sous
   le grand, à trente centimètres. On masque son seul DESSIN — tous ses
   chiffres restent. */
.rpb-recu .rp-balance-svg { display: none; }
.rpb-recu .rp-balance { grid-template-columns: 1fr; padding: .35rem .5rem; }
.rpb-recu .rp-delais { margin-top: 0; }
/* ⚠️ ET SA LÉGENDE EST LE MÊME MOT À MÊME MOT que celle du centre, à
   trente centimètres au-dessus (« il reste 25 000 de place… »). On garde
   les deux CHIFFRES du reçu — c'est eux qui prouvent que le serveur a
   bien vu cette composition — et on retire la phrase en double. */
.rpb-recu .rp-balance-reste { display: none; }
/* Même raison pour l'en-tête : la vignette du reçu s'appelle déjà « Le
   devis », et le panneau qui la contient porte ce titre juste au-dessus.
   Deux fois le même mot l'un sous l'autre, c'est ce qui fait qu'un écran
   a l'air bâclé. */
.rpb-recu .rp-delais-tete { display: none; }

/* ════════════════════════════════════════════════════════════════════
   MOBILE — les DEUX systèmes d'affichage, media query et `mobile-mode`
   ════════════════════════════════════════════════════════════════════ */
/* Palier intermédiaire : la balance passe en tête et les deux plateaux se
   partagent la ligne du dessous. */
@media (max-width: 1000px) {
  .rpb-plateaux { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .rpb-centre { grid-column: 1 / -1; order: -1; }
}
@media (max-width: 700px) {
  .rpb-plateaux, .rpb-bas { grid-template-columns: 1fr; }
  .rpb-centre { grid-column: auto; order: -1; }
  /* Le fléau devient décoratif à cette taille — mais il RESTE, et le
     compteur avec lui (note explicite de la maquette). */
  .rpb-svg { max-width: 190px; }
  .rpb-ligne { flex-wrap: wrap; }
  .rpb-molette { margin-left: auto; }
  .rpb-input { width: 70px; min-height: 32px; }   /* cible au doigt */
  .rpb-pas { width: 32px; height: 32px; }
  .rpb-nom, .rpb-corps { flex: 1 1 60%; }
  .rpb-gros { font-size: 1.5rem; }
  .rpb-phrase { font-size: .72rem; }
}
body.mobile-mode .rpb-plateaux,
body.mobile-mode .rpb-bas { grid-template-columns: 1fr; }
body.mobile-mode .rpb-centre { grid-column: auto; order: -1; }
body.mobile-mode .rpb-svg { max-width: 190px; }
body.mobile-mode .rpb-ligne { flex-wrap: wrap; }
body.mobile-mode .rpb-molette { margin-left: auto; }
body.mobile-mode .rpb-input { width: 70px; min-height: 32px; }
body.mobile-mode .rpb-pas { width: 32px; height: 32px; }
body.mobile-mode .rpb-nom,
body.mobile-mode .rpb-corps { flex: 1 1 60%; }
body.mobile-mode .rpb-gros { font-size: 1.5rem; }
body.mobile-mode .rpb-phrase { font-size: .72rem; }

/* ─── LE VOLET DES VAISSEAUX QUI NE PORTENT RIEN ────────────────────
   La maquette ne montrait que les quatre porteurs. Les autres voyagent
   quand même (on déménage sa flotte avec un transport) : ils restent
   donc là, mais repliés — seize lignes en face de trois ressources
   déséquilibraient la balance. ⚠️ Le repli est PUREMENT VISUEL :
   `display: none` retirerait les champs de la mise en page, pas du DOM,
   et `rampeLireFlotte` continue de les lire. C'est voulu — un champ
   saisi rouvre le volet de lui-même (cf. `rpbAutresReplies`). */
.rpb-autres { border-top: 1px solid var(--border); margin-top: .35rem; padding-top: .35rem; }
.rpb-autres-bouton {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  font-family: inherit; font-size: .74rem; color: var(--text2);
  background: none; border: 0; cursor: pointer; padding: .3rem .2rem;
  text-align: left;
}
.rpb-autres-bouton:hover { color: var(--text); }
.rpb-autres-chevron { font-size: .7rem; transition: transform .15s; display: inline-block; }
.rpb-autres-aide { margin-left: auto; font-family: var(--mono); font-size: .62rem; color: var(--text3); }
.rpb-autres.est-replie .rpb-autres-chevron { transform: rotate(-90deg); }
.rpb-autres.est-replie .rpb-autres-corps { display: none; }
@media (max-width: 700px) { .rpb-autres-aide { display: none; } }
body.mobile-mode .rpb-autres-aide { display: none; }

/* ─── v0.0.143 : LE SOUS-MENU DE LA SOUTE
   Sous la ligne de son porteur, sur les onglets Transport et Stationner :
   ce qu'on met dans la soute d'un Transporteur de Flotte ou d'un Convoyeur
   Défensif, et « encore N possibles » par type d'unité.
   ⚠️ `est-absent` et `est-replie` MASQUENT SANS RETIRER : `rampeLireFret`
   lit le DOM, pas l'écran. Un champ enlevé ferait disparaître une saisie
   sans un mot — même règle que les trois rares et le volet des autres
   vaisseaux. Et le JS ne pose ces classes QUE si la poche est à zéro. */
.rpb-soute {
  margin: 0 0 .4rem .9rem;
  border-left: 2px solid rgba(126, 196, 255, .35);
  background: rgba(126, 196, 255, .045);
  border-radius: 0 6px 6px 0;
  padding: .1rem .5rem .25rem .55rem;
}
.rpb-soute.est-absent { display: none; }
.rpb-soute-bouton {
  display: flex; align-items: center; gap: .45rem; width: 100%;
  font-family: inherit; font-size: .72rem; color: var(--text2);
  background: none; border: 0; cursor: pointer; padding: .3rem .1rem;
  text-align: left;
}
.rpb-soute-bouton:hover { color: var(--text); }
.rpb-soute-chevron { font-size: .68rem; transition: transform .15s; display: inline-block; }
.rpb-soute.est-replie .rpb-soute-chevron { transform: rotate(-90deg); }
.rpb-soute.est-replie .rpb-soute-corps { display: none; }
.rpb-soute-etat {
  margin-left: auto; font-family: var(--mono); font-size: .64rem;
  color: var(--text3); white-space: nowrap; font-variant-numeric: tabular-nums;
}
.rpb-soute-etat.est-rouge { color: #fca5a5; }
.rpb-soute-jauge { margin: .1rem 0 .35rem; }
.rpb-soute-note {
  font-size: .68rem; color: var(--text3); line-height: 1.35; margin: .25rem 0 .1rem;
}
.rpb-soute-note b { color: var(--text2); }
.rpb-soute-vide { font-size: .68rem; color: var(--text3); padding: .3rem .1rem; }

/* « encore 47 / 181 » — la réponse à « combien je peux en mettre ». Le
   premier nombre est la place restante TRADUITE dans l'unité de la ligne,
   le second ce qui reste au hangar une fois retiré ce qui vole.
   ⚠️ IL VIT SOUS LE NOM, PAS À CÔTÉ : ce panneau est dans la colonne la plus
   étroite des trois, et deux textes côte à côte y cassaient le nom du
   vaisseau une lettre par ligne. */
.rpb-place { white-space: nowrap; margin-right: .4rem; }
.rpb-place-n { color: #9fd0ff; }
.rpb-place-n.est-plein { color: var(--text3); text-decoration: line-through; }
.rpb-place-dim { color: var(--text3); }

.rpb-soute .rpb-sous { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0; }
.rpb-soute .rpb-input { width: 68px; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.158 — LE CHAMP DE CARGAISON CESSE DE ROGNER CE QU'ON VIENT D'Y
   TAPER
   ════════════════════════════════════════════════════════════════════
   ⚠️ MESURÉ AU NAVIGATEUR, À 390 px. Le champ offre 55 px utiles (boîte de
   70, moins ses retraits) pour une police de 16 px en « Share Tech Mono ».
   Ce que ça donne, chiffre par chiffre :
     « 999 »     29 px   tient
     « 9999 »    39 px   tient
     « 60000 »   49 px   tient
     « 999999 »  58 px   ⚠️ ROGNÉ
     « 1234567 » 68 px   ⚠️ ROGNÉ
   La troncature commence donc à SIX chiffres — et six chiffres, ce n'est
   pas un cas d'école : un seul Cargo Stellaire porte 25 000, donc quatre
   cargos suffisent. La valeur PART bien, c'est un rognage d'affichage ;
   mais le joueur ne peut pas relire ce qu'il vient de taper, et un texte
   coupé au milieu d'un mot est le seul indice visible d'une largeur trop
   étroite.

   96 px de boîte laissent 81 px utiles : sept chiffres passent.

   ⚠️ TROIS RÈGLES, PAS DEUX, ET LA TROISIÈME EST UN PIÈGE. Le mode mobile
   a ses DEUX axes habituels (la largeur réelle et la classe), mais il
   existe en plus une règle de SOUTE plus lourde (`.rpb-soute .rpb-input`,
   deux classes) qui bat la règle de largeur dans le chemin des requêtes de
   média. L'oublier laisserait le champ de soute à 68 px chez le joueur qui
   a forcé l'affichage bureau sur son téléphone — précisément le cas que
   les consignes du projet demandent de vérifier.
   ⚠️ La hauteur ne bouge pas : c'est la cible au doigt, elle est déjà juste. */
@media (max-width: 700px) {
  .rpb-input { width: 96px; }
  .rpb-soute .rpb-input { width: 96px; }
}
body.mobile-mode .rpb-input { width: 96px; }
body.mobile-mode .rpb-soute .rpb-input { width: 96px; }


@media (max-width: 700px) {
  .rpb-soute { margin-left: .4rem; padding-right: .35rem; }
  .rpb-soute-etat { font-size: .6rem; }
}
body.mobile-mode .rpb-soute { margin-left: .4rem; padding-right: .35rem; }
body.mobile-mode .rpb-soute-etat { font-size: .6rem; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.136 — L'ÉCRAN DU VISEUR (Rampe, onglets Raid / Espionnage /
   Simulateur ; maquettes B12 et B12·bis).
   Le rendu vit dans features/fleet-launcher/rampe-viseur-vue.js.

   TROIS COLONNES : les contacts à portée à gauche, LE GRAND RÉTICULE au
   milieu avec ses quatre tuiles chiffrées dessous, le groupe d'assaut et
   le devis à droite. Toutes les classes d'ici sont préfixées `rpv-` :
   rien ne peut heurter une règle `rp-` existante.

   ⚠️ LE SIMULATEUR RESTE CYAN. Le Raid est rouge, le Simulateur est
   cyan : deux gestes aux conséquences opposées à un onglet de distance.
   AUCUN sélecteur de ce bloc n'atteint `.sim-go` — la seule chose qu'on
   fait au Simulateur, c'est le remettre en trois colonnes depuis
   `.rpv-simu` (B12·bis), sans toucher à simulateur.js.

   ⚠️ LES TROIS DÉFAUTS QUE CE PROJET ATTRAPE EN BOUCLE sont tous
   possibles ici, parce que l'écran porte un carré de 400 px, une liste
   de cartes et jusqu'à dix lignes de saisie à deux boutons :
     · DÉBORDEMENT — d'où `minmax(0, 1fr)` partout au lieu de `1fr`, et
       `min-width: 0` sur chaque cellule de grille ;
     · TRONCATURE — d'où `overflow-wrap: anywhere` sur les noms (un nom
       de Vaisseau Mère est saisi par un joueur, il peut être long), et
       une troncature CHOISIE dans le SVG côté JS ;
     · CIBLE INATTEIGNABLE — d'où les molettes portées à 34 px au doigt,
       et le réticule ramené à 320 px pour que le bouton de départ reste
       à un seul défilement.
   Les DEUX systèmes d'affichage mobile sont traités : la media query
   `max-width: 700px` ET la classe `body.mobile-mode`. La classe seule
   laisse un téléphone en mode `desktop` devant l'écran large ; la media
   query seule laisse le mode `mobile` forcé sur grand écran sans rien
   changer.
   ════════════════════════════════════════════════════════════════════ */

.rpv {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr) 320px;
  gap: .8rem; align-items: start; min-width: 0;
}

/* ─── Le vocabulaire commun : panneaux, étiquettes, chiffres ─────── */
.rpv-pnl {
  border: 1px solid var(--border2); border-radius: 8px;
  background: linear-gradient(180deg, #0e1729, #0a1120);
  padding: .6rem; min-width: 0;
}
.rpv-pnl-titre {
  display: flex; justify-content: space-between; align-items: baseline; gap: .5rem;
  font-family: var(--mono); font-size: .58rem; letter-spacing: .2em;
  text-transform: uppercase; color: var(--text3);
  margin: 0 0 .5rem; font-weight: 400;
}
.rpv-mini {
  font-family: var(--mono); font-size: .62rem; color: var(--text3);
  white-space: nowrap;
}
.rpv-lbl {
  font-family: var(--mono); font-size: .6rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text3);
}
.rpv-val {
  font-family: var(--mono); font-size: .82rem; color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* L'étiquette de nature : encadrée et teintée, comme la maquette — pas
   un texte coloré. Les cinq tons disent la nature d'un coup d'œil. */
.rpv-tag {
  display: inline-block; justify-self: start; margin-top: 2px;
  font-family: var(--mono); font-size: .58rem; letter-spacing: .12em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 4px;
  border: 1px solid transparent; max-width: 100%; overflow-wrap: anywhere;
}
.rpv-tag.est-rd { color: #ffb3c0; background: rgba(233, 69, 96, .12); border-color: rgba(233, 69, 96, .45); }
.rpv-tag.est-gd { color: #fde68a; background: rgba(245, 158, 11, .12); border-color: rgba(245, 158, 11, .45); }
.rpv-tag.est-cy { color: #bfe8ff; background: rgba(14, 165, 233, .12); border-color: rgba(14, 165, 233, .45); }
.rpv-tag.est-vi { color: #d8caff; background: rgba(124, 58, 237, .14); border-color: rgba(124, 58, 237, .5); }
.rpv-tag.est-nu { color: var(--text3); background: rgba(255, 255, 255, .04); border-color: var(--border2); }

/* ─── COLONNE 1 : les contacts à portée ──────────────────────────── */
.rpv-contacts-liste { display: grid; gap: .35rem; min-width: 0; }
/* `.rp-contact` porte déjà le fond, la bordure et l'état actif : on ne
   récrit que ce qui gênerait dans une grille (sa marge basse) et ce que
   la maquette ajoute. */
.rpv-contact { margin-bottom: 0; min-width: 0; align-content: start; }
.rpv-nom { overflow-wrap: anywhere; }
.rpv-lieu { overflow-wrap: anywhere; }
.rpv-coal {
  font-family: var(--mono); font-size: .6rem; color: var(--text3);
  margin-left: .3rem;
}
.rpv-survol { margin-top: 2px; }

/* ─── COLONNE 2 : le grand réticule ──────────────────────────────── */
.rpv-centre { display: grid; place-items: center; gap: .5rem; min-width: 0; }
.rpv-scope { display: block; width: 100%; max-width: 400px; }
.rpv-c1, .rpv-c2 { fill: none; stroke: var(--border2); stroke-width: 1; }
.rpv-c3 { fill: none; stroke: rgba(233, 69, 96, .55); stroke-width: 1.6; }
.rpv-br { stroke: var(--accent1); stroke-width: 2; }
.rpv-cr { stroke: var(--accent1); stroke-width: 2.4; fill: none; }
.rpv-halo-a, .rpv-halo-b { stop-color: var(--accent1); }
.rpv-ico { font-size: 26px; text-anchor: middle; }
.rpv-snom {
  font-family: var(--hud); font-size: 16px; fill: #ffb3c0;
  text-anchor: middle; letter-spacing: .08em;
}
.rpv-slieu, .rpv-snat {
  font-family: var(--mono); font-size: 11px; fill: var(--text3); text-anchor: middle;
}
.rpv-snat { fill: #ffb3c0; letter-spacing: .1em; }

/* Le ton du réticule suit la nature de la cible : c'est la même
   information que l'étiquette de la carte, dite en couleur. */
.rpv-scope.est-sonde .rpv-c3 { stroke: rgba(245, 158, 11, .55); }
.rpv-scope.est-sonde .rpv-br,
.rpv-scope.est-sonde .rpv-cr { stroke: var(--gold); }
.rpv-scope.est-sonde .rpv-halo-a,
.rpv-scope.est-sonde .rpv-halo-b { stop-color: var(--gold); }
.rpv-scope.est-sonde .rpv-snom,
.rpv-scope.est-sonde .rpv-snat { fill: #fde68a; }

.rpv-scope.est-bastion .rpv-c3 { stroke: rgba(124, 58, 237, .6); }
.rpv-scope.est-bastion .rpv-br,
.rpv-scope.est-bastion .rpv-cr { stroke: var(--accent3); }
.rpv-scope.est-bastion .rpv-halo-a,
.rpv-scope.est-bastion .rpv-halo-b { stop-color: var(--accent3); }
.rpv-scope.est-bastion .rpv-snom,
.rpv-scope.est-bastion .rpv-snat { fill: #d8caff; }

.rpv-scope.est-scanner .rpv-c3 { stroke: rgba(14, 165, 233, .55); }
.rpv-scope.est-scanner .rpv-br,
.rpv-scope.est-scanner .rpv-cr { stroke: var(--accent2); }
.rpv-scope.est-scanner .rpv-halo-a,
.rpv-scope.est-scanner .rpv-halo-b { stop-color: var(--accent2); }
.rpv-scope.est-scanner .rpv-snom,
.rpv-scope.est-scanner .rpv-snat { fill: #bfe8ff; }

/* ─── Les quatre tuiles chiffrées ────────────────────────────────── */
.rpv-tuiles {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: .5rem; width: 100%; text-align: center;
}
.rpv-tuile {
  display: grid; gap: 2px; padding: .4rem .3rem;
  border-top: 1px solid var(--border); min-width: 0;
}
.rpv-tuile .rpv-val { overflow-wrap: anywhere; }
.rpv-tuile.est-flou .rpv-val { color: var(--text3); opacity: .6; }
/* ⚠️ L'ÂGE DU RAPPORT EST L'APPORT PRINCIPAL DE CET ÉCRAN : il se voit.
   Le reléguer en petite ligne grise, c'est ce que faisait l'écran
   d'avant — et c'est précisément l'information qui manquait. */
.rpv-tuile.est-age .rpv-val { color: #fde68a; }
.rpv-tuile.est-age.est-flou .rpv-val { color: var(--text3); }

/* ⚠️ v0.0.150 — REMONTÉE AU PLANCHER DE LISIBILITÉ. Cette ligne passait à
   .68rem, soit 9,5 px pour une racine à 14 px — sous le plancher de 11 px
   (--badge-font-size) que Loryan a posé pour tout le site. Elle porte
   désormais la puissance à battre EN DEUX MOITIÉS, donc plus de texte : la
   laisser là aurait été la rendre illisible au téléphone au moment même où
   elle devient utile. La boîte n'est pas contrainte (simple bloc dans le
   flux), rien n'est donc tronqué ; elle gagne quelques pixels de hauteur. */
.rpv-sous { font-size: .79rem; color: var(--text3); text-align: center; line-height: 1.5; }
.rpv-sous b { font-family: var(--mono); color: var(--text2); font-weight: 400; }
.rpv-vu { font-size: .64rem; color: var(--text3); text-align: center; line-height: 1.4; }
/* Le viseur reste FLOU, et il le DIT : c'est le message, pas un vide. */
.rpv-flou {
  font-size: .68rem; color: #fbbf24; line-height: 1.45;
  border-left: 2px solid rgba(251, 191, 36, .5);
  padding: .4rem .6rem; text-align: left; width: 100%;
}

/* ─── COLONNE 3 : le groupe d'assaut, le devis, le départ ────────── */
.rpv-droite { display: grid; gap: .6rem; min-width: 0; align-content: start; }
.rpv-ligne {
  display: flex; justify-content: space-between; align-items: center;
  gap: .5rem; padding: .25rem 0; border-bottom: 1px solid var(--border);
  min-width: 0;
}
.rpv-unite {
  flex: 1 1 auto; min-width: 0; display: flex; gap: .35rem;
  align-items: baseline; font-size: .78rem; color: var(--text2);
}
.rpv-ico2 { flex: none; }
.rpv-nom2 { min-width: 0; overflow-wrap: anywhere; color: var(--text); }

/* ⚠️ LA MOLETTE ENCADRE UN VRAI CHAMP, ELLE NE LE REMPLACE PAS.
   `rampeLireFlotte` lit `document.getElementById('rp-ag-<ship>').value` :
   un compte gardé dans un `<b>` (comme sur la maquette) ferait partir la
   flotte à VIDE, sans la moindre erreur. */
.rpv-molette {
  flex: none; display: inline-flex; align-items: stretch;
  border: 1px solid var(--border2); border-radius: 5px; overflow: hidden;
}
.rpv-pas {
  width: 30px; min-height: 30px; padding: 0;
  background: #0f1a2c; color: var(--text2);
  font-family: var(--mono); font-size: .9rem; line-height: 1;
  border: 0; cursor: pointer; transition: .15s;
}
.rpv-pas:hover { background: var(--border2); color: var(--text); }
.rpv-champ {
  width: 56px; min-width: 0; text-align: center; border-radius: 0;
  border-top: 0; border-bottom: 0;
  border-left: 1px solid var(--border2); border-right: 1px solid var(--border2);
  -moz-appearance: textfield; appearance: textfield;
}
/* Les flèches natives feraient une seconde molette collée à la mienne. */
.rpv-champ::-webkit-outer-spin-button,
.rpv-champ::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.rpv-hint { font-size: .68rem; color: var(--text3); line-height: 1.45; margin-top: .5rem; }
.rpv-hint b { color: var(--text2); }

/* Le reçu du devis : lignes pointillées, chiffres en mono. */
.rpv-recu {
  border: 1px dashed rgba(42, 69, 112, .9); border-radius: 8px;
  padding: .5rem .6rem; min-width: 0;
}
.rpv-recu .l {
  display: flex; justify-content: space-between; gap: .7rem;
  padding: .22rem 0; font-size: .74rem; color: var(--text3); min-width: 0;
}
.rpv-recu .l + .l { border-top: 1px dotted var(--border); }
.rpv-recu .l span { min-width: 0; overflow-wrap: anywhere; }
.rpv-recu .l b {
  font-family: var(--mono); color: var(--text); font-weight: 400;
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
.rpv-recu .l b.good { color: #6ee7b7; }
.rpv-recu .l b.bad  { color: #ff8fa3; }
.rpv-devis-vide { font-size: .7rem; color: var(--text3); line-height: 1.45; }
.rpv-devis-vide.est-refus { color: #ff8fa3; }
.rpv-devis-aide { display: block; font-size: .64rem; color: var(--text3); margin-top: .25rem; }

/* Le bouton de départ : rouge assaut, pleine largeur, en capitales.
   ⚠️ LES CAPITALES VIENNENT D'ICI, PAS DU TEXTE : le libellé continue de
   DIRE la mission (« Lancer le raid orbital » n'est pas « Lancer le
   raid »), et cette distinction est une vraie règle de jeu. */
.rpv-go {
  width: 100%; font-family: var(--hud); font-size: .82rem;
  letter-spacing: .16em; text-transform: uppercase;
  padding: 11px 18px; border-radius: 7px; color: #fff2f4; cursor: pointer;
  background: linear-gradient(180deg, #e0304f, #8f1128);
  border: 1px solid #b31c36;
  box-shadow: 0 0 22px rgba(233, 69, 96, .28), inset 0 1px 0 rgba(255, 255, 255, .2);
  transition: .15s;
}
.rpv-go:hover { filter: brightness(1.1); }
.rpv-go:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; filter: none; }
/* L'espionnage n'est pas un assaut : son bouton ne se peint pas en sang. */
.rpv.est-spy .rpv-go {
  background: linear-gradient(180deg, #1e6fa8, #0d3a5c);
  border-color: #1f6f97; color: #e6f4ff;
  box-shadow: 0 0 22px rgba(14, 165, 233, .22), inset 0 1px 0 rgba(255, 255, 255, .18);
}

/* ─── v0.0.154 — « Tout embarquer » ────────────────────────────────────
   Le segment « Tout » s'ajoute À LA MOLETTE : double classe pour hériter
   des fonds et survols de `.rpv-pas`, largeur au contenu plutôt qu'au
   carré. Le bouton de panneau remplit toutes les lignes — rouge côté
   Raid, cyan côté Espionnage, la même dualité que le bouton de départ.
   Les deux textes se posent au plancher de lisibilité (11 px). */
.rpv-pas.rpv-tout {
  width: auto; padding: 0 .5rem;
  font-size: var(--badge-font-size, 11px); letter-spacing: .09em;
  text-transform: uppercase;
  border-left: 1px solid var(--border2);
}
.rpv-tout-groupe {
  width: 100%; margin-top: .55rem; padding: 8px 10px;
  border-radius: 6px; cursor: pointer; transition: .15s;
  font-family: var(--mono); font-size: var(--badge-font-size, 11px);
  letter-spacing: .14em; text-transform: uppercase;
  background: rgba(233, 69, 96, .08);
  border: 1px solid rgba(233, 69, 96, .45); color: #ffb3c0;
}
.rpv-tout-groupe:hover { background: rgba(233, 69, 96, .18); color: #fff2f4; }
.rpv.est-spy .rpv-tout-groupe {
  background: rgba(14, 165, 233, .08);
  border-color: rgba(14, 165, 233, .45); color: #bfe8ff;
}
.rpv.est-spy .rpv-tout-groupe:hover { background: rgba(14, 165, 233, .18); color: #e6f4ff; }
/* Un nom qui doit plier plie AVEC UN TRAIT D'UNION (la page est en `fr`),
   plutôt que coupé à la lettre par l'overflow-wrap. */
.rpv-ligne.est-tout .rpv-nom2 { hyphens: auto; }

/* ════════════════════════════════════════════════════════════════════
   B12·bis — LE SIMULATEUR EN TROIS COLONNES, PAR CSS SEULEMENT
   ════════════════════════════════════════════════════════════════════
   ⚠️ simulateur.js N'EST PAS TOUCHÉ. `display: contents` sur `.sim-duo`
   est le seul moyen d'atteindre les deux camps sans réécrire son HTML.
   Si sa structure interne change un jour, la grille se disloque — c'est
   pourquoi la règle est bornée à `.rpv-simu` (le Simulateur ouvert par
   un autre chemin garde sa mise en page) et pourquoi le repli sous
   1100 px est EXACTEMENT la mise en page d'aujourd'hui : si la règle se
   périme, l'écran étroit reste juste.
   ⚠️ ET AUCUNE RÈGLE D'ICI NE TOUCHE `.sim-go` : le bouton du Simulateur
   reste cyan, celui du Raid reste rouge. */
.rpv-simu { min-width: 0; }
.rpv-simu .sim {
  display: grid; align-items: start; gap: .8rem;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 340px;
  grid-template-areas:
    "bandeau bandeau bandeau"
    "moi     lui     reglages"
    "moi     lui     resultat";
}
.rpv-simu .sim-bandeau  { grid-area: bandeau; min-width: 0; }
.rpv-simu .sim-duo      { display: contents; }
.rpv-simu .sim-camp-moi { grid-area: moi; min-width: 0; }
.rpv-simu .sim-camp-lui { grid-area: lui; min-width: 0; }
.rpv-simu .sim-lancer   { grid-area: reglages; min-width: 0; }
.rpv-simu .sim-resultat { grid-area: resultat; min-width: 0; }

/* ════════════════════════════════════════════════════════════════════
   LES TROIS FORMATS — et les DEUX systèmes d'affichage mobile
   ════════════════════════════════════════════════════════════════════ */
/* Palier intermédiaire : le réticule garde sa place, la colonne de
   droite passe dessous et se coupe en deux (assaut | devis). */
@media (max-width: 1100px) {
  .rpv { grid-template-columns: 300px minmax(0, 1fr); }
  .rpv-droite {
    grid-column: 1 / -1;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .rpv-simu .sim { display: flex; flex-direction: column; }
}
@media (max-width: 700px) {
  .rpv { grid-template-columns: minmax(0, 1fr); }
  .rpv-droite { grid-column: auto; grid-template-columns: minmax(0, 1fr); }
  /* Les contacts en deux colonnes compactes : une liste verticale de
     cinq cartes pleine largeur repousserait le bouton de départ à deux
     écrans de défilement. */
  .rpv-contacts-liste { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
  .rpv-tuiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* 400 px de carré + deux rangées de tuiles mangeraient tout l'écran. */
  .rpv-scope { max-width: 320px; }
  .rpv-pas { width: 34px; min-height: 34px; }   /* la cible du doigt grandit */
  .rpv-champ { width: 52px; }
  .rpv-snom { font-size: 18px; }
  .rpv-slieu, .rpv-snat { font-size: 12px; }
  /* v0.0.154 : avec son 4e segment, la molette
     ne partage plus la ligne — le nom se couperait en plein mot. Elle
     descend SOUS le nom, calée à droite (pas de space-between : la rangée
     plie). Et le champ s'élargit : la garde anti-zoom iOS force ses
     chiffres à 16 px, où 52 px ne montrent que trois chiffres — un stock
     de 1 740 s'affichait « .740 ». */
  .rpv-ligne.est-tout { flex-wrap: wrap; justify-content: flex-start; row-gap: .25rem; }
  .rpv-ligne.est-tout .rpv-unite { flex: 1 1 100%; }
  .rpv-ligne.est-tout .rpv-molette { margin-left: auto; }
  .rpv-ligne.est-tout .rpv-champ { width: 88px; }
}
body.mobile-mode .rpv { grid-template-columns: minmax(0, 1fr); }
body.mobile-mode .rpv-droite { grid-column: auto; grid-template-columns: minmax(0, 1fr); }
body.mobile-mode .rpv-contacts-liste { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
body.mobile-mode .rpv-tuiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
body.mobile-mode .rpv-scope { max-width: 320px; }
body.mobile-mode .rpv-pas { width: 34px; min-height: 34px; }
/* v0.0.154 : le segment « Tout » garde sa largeur AU CONTENU — la règle du
   dessus (plus lourde que `.rpv-pas.rpv-tout` nu) l'écraserait à 34 px et
   tronquerait le mot. La media query 700 px, elle, perd déjà au poids. */
body.mobile-mode .rpv-pas.rpv-tout { width: auto; padding: 0 .5rem; }
body.mobile-mode .rpv-champ { width: 52px; }
/* v0.0.154 : le même repli de molette que la media query du dessus — les
   DEUX systèmes d'affichage mobile, comme partout dans cette section. */
body.mobile-mode .rpv-ligne.est-tout { flex-wrap: wrap; justify-content: flex-start; row-gap: .25rem; }
body.mobile-mode .rpv-ligne.est-tout .rpv-unite { flex: 1 1 100%; }
body.mobile-mode .rpv-ligne.est-tout .rpv-molette { margin-left: auto; }
body.mobile-mode .rpv-ligne.est-tout .rpv-champ { width: 88px; }
body.mobile-mode .rpv-snom { font-size: 18px; }
body.mobile-mode .rpv-slieu,
body.mobile-mode .rpv-snat { font-size: 12px; }
body.mobile-mode .rpv-simu .sim { display: flex; flex-direction: column; }
/* @@@ FIN REFONTE RAMPE v0.0.136 @@@ */

/* Le corps de la Rampe, sous le ruban du hangar. */
.rph-principal { min-width: 0; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.145 — LE HANGAR SUR UNE LIGNE EN HAUT (Rampe)
   Les vignettes des vaisseaux ET des défenses tiennent sur un seul
   ruban posé sous le sélecteur de Vaisseau Mère, le nombre en gros au
   milieu de chaque case ; toute la Rampe garde la largeur.
   Rendu par `rampeBandeauHtml` (rampe.js).
   ⚠️ Tout vit sous `.rhb-` — et surtout PAS `.rpb-`, qui est
   DÉJÀ la balance de la Rampe (`rampe-balance.js`, ~70 règles) : `.rpb`
   y vaut `min-width: 0` et `.rpb-nom` un `flex: 1` — le ruban aurait
   hérité des deux, et la balance de mes fonds noirs, sans qu'aucune
   erreur ne se déclenche.
   ════════════════════════════════════════════════════════════════════ */
.rhb {
  margin: 0 0 .7rem; padding: .45rem .5rem;
  background: rgba(255, 255, 255, .02);
  border: 1px solid var(--border); border-radius: 10px;
  /* ⚠️ UNE LIGNE, ET ELLE DÉFILE PLUTÔT QUE DE DÉBORDER. Vingt vaisseaux
     et six défenses ne tiennent pas sur 390 px : sans ce défilement
     INTÉRIEUR, c'est la PAGE qui partirait de travers — le défaut que ce
     projet attrape le plus souvent, et qui ne se signale par aucune
     erreur. */
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: thin;
}
.rhb-piste {
  display: flex; align-items: stretch; gap: .55rem;
  flex-wrap: nowrap; width: max-content; min-width: 100%;
}
.rhb-groupe {
  display: flex; align-items: center; gap: .3rem;
  padding-right: .55rem; border-right: 1px solid var(--border);
}
.rhb-groupe:last-child { border-right: 0; padding-right: 0; }

/* La famille se réduit à son émoji et à son total : dans un ruban, écrire
   « Expédition » en toutes lettres coûte trois vignettes de largeur pour
   une information que l'icône donne déjà. Le nom complet reste dans
   `title`. */
.rhb-fam {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 0 .15rem; color: var(--text2); cursor: default;
}
.rhb-fam-ico { font-size: .95rem; line-height: 1; }
.rhb-fam-n {
  font-family: var(--mono); font-size: .82rem; color: var(--text3);
}

.rhb-case {
  position: relative; flex: 0 0 auto;
  width: 58px; height: 58px; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--border); background: rgba(0, 0, 0, .35);
  display: grid; place-items: center; cursor: default;
}
.rhb-case:hover, .rhb-case:focus { border-color: var(--accent2); outline: none; }
/* L'émoji du catalogue est DESSOUS : une photo qui manque ne laisse pas un
   carré vide (même règle que la colonne). */
.rhb-repli { font-size: 1.3rem; opacity: .7; }
.rhb-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
}

/* ⚠️ LE NOMBRE EN GROS, C'EST LA DEMANDE ELLE-MÊME. Il est posé sur toute
   la largeur de la case, sur un dégradé sombre qui le décolle de la photo
   — un chiffre clair sur une image claire ne se lit pas, et une vignette
   sur deux l'est. */
.rhb-n {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: .18rem 2px .12rem; text-align: center;
  font-family: var(--mono); font-size: 1.05rem; font-weight: 700;
  line-height: 1.05; color: #f2f8ff;
  background: linear-gradient(180deg, rgba(4, 10, 18, 0), rgba(4, 10, 18, .9) 45%);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}
.rhb-vol {
  position: absolute; left: 2px; top: 2px;
  font-family: var(--mono); font-size: .56rem; color: #7dd3fc;
  background: rgba(4, 10, 18, .85); border-radius: 4px; padding: 0 3px;
}
/* Le nom recouvre la case au survol : une bulle qui déborderait ferait
   défiler le ruban toute seule.
   ⚠️ LA COUPURE DES MOTS FAIT PARTIE DE LA TAILLE, pas un ornement. La case
   fait 56 px (48 au téléphone) et ferme sur `overflow:hidden` : un nom trop
   long n'y déborde pas, il est TRONQUÉ sans une erreur. Mesuré sur les
   vingt-deux noms du catalogue : avec `overflow-wrap: anywhere`, rien ne se
   tronque jusqu'à 0,9 rem en large et 0,78 rem au téléphone — on s'arrête
   en dessous des deux. */
.rhb-nom {
  position: absolute; inset: 0; display: grid; place-items: center;
  padding: 2px 3px; text-align: center; line-height: 1.2;
  font-size: .82rem; overflow-wrap: anywhere;
  color: #eaf4ff; background: rgba(4, 10, 18, .9);
  opacity: 0; transition: opacity .12s;
}
/* `:focus` ET PAS `:focus-visible` : au doigt il n'y a pas de survol, et
   c'est justement au téléphone que les cases sont les plus petites. */
.rhb-case:hover .rhb-nom,
.rhb-case:focus .rhb-nom { opacity: 1; }

.rhb-vide { font-size: .7rem; color: var(--text3); line-height: 1.4; padding: .2rem .1rem; }

/* Téléphone : les cases rétrécissent pour qu'il en tienne davantage avant
   de devoir faire défiler, et le nombre reste lisible — c'est lui qu'on
   vient chercher. */
body.mobile-mode .rhb { padding: .35rem .4rem; }
body.mobile-mode .rhb-case { width: 50px; height: 50px; }
body.mobile-mode .rhb-n { font-size: .92rem; }
body.mobile-mode .rhb-nom { font-size: .72rem; }
@media (max-width: 700px) {
  .rhb { padding: .35rem .4rem; }
  .rhb-case { width: 50px; height: 50px; }
  .rhb-n { font-size: .92rem; }
  .rhb-nom { font-size: .72rem; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.138 — LE RÉCAP D'EXPÉRIENCE (section Modules)
   « Ce que tu peux monter » d'abord, « les prochains à se remplir »
   ensuite — à la place du débit global, juste mais indépensable.
   ════════════════════════════════════════════════════════════════════ */
.xp-recap { display: flex; flex-direction: column; gap: .3rem; }
.xp-recap-tete {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .6rem; margin-top: .35rem;
}
.xp-recap-tete:first-child { margin-top: 0; }
.xp-recap-titre {
  font-family: var(--hud); font-size: .74rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text2);
}
.xp-recap-compte { font-family: var(--mono); font-size: .74rem; color: var(--text3); }
.xp-recap-compte.est-pret { color: #6ee7b7; }
/* Le temps estimé, à côté du %.
   Même taille que la ligne ; plus pâle quand il n'y a pas d'estimation. */
.xp-recap-eta { margin-left: 6px; color: #7dd3fc; font-variant-numeric: tabular-nums; white-space: nowrap; }
.xp-recap-eta.est-inconnue { color: inherit; opacity: .6; font-style: italic; }
.xp-recap-chantier { font-size: .85em; }

.xp-recap-bloc {
  border: 1px solid var(--border); border-radius: 8px;
  background: rgba(255, 255, 255, .02); padding: .25rem .45rem;
}
.xp-recap-bloc.est-pret { border-color: rgba(110, 231, 183, .4); background: rgba(16, 185, 129, .07); }
.xp-recap-ligne {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: baseline; gap: .5rem; padding: .22rem 0; font-size: .84rem;
}
.xp-recap-ligne + .xp-recap-ligne { border-top: 1px solid var(--border); }
.xp-recap-nom { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xp-recap-niv { font-family: var(--mono); font-size: .76rem; color: var(--text3); }
.xp-recap-droite { font-family: var(--mono); font-size: .8rem; color: var(--text2); }
.xp-recap-bloc.est-pret .xp-recap-droite b { color: #6ee7b7; }
.xp-recap-vide { font-size: .78rem; color: var(--text3); padding: .3rem .1rem; }
.xp-recap-note { font-size: .76rem; color: var(--gold, #f5b301); margin-top: .2rem; line-height: 1.4; }
.xp-recap-aide { font-size: .76rem; color: var(--text3); margin-top: .35rem; line-height: 1.45; }

/* ⚠️ SANS COLONNE EXPLICITE, LE CHIFFRE PASSAIT DEVANT LE NOM. La grille
   place d'abord les éléments épinglés à une ligne (`.xp-recap-droite`,
   ligne 1) : il prenait donc la case de gauche, et le nom du module se
   retrouvait poussé à droite. Mesuré à 390 px. Chacun reçoit sa colonne. */
@media (max-width: 700px) {
  .xp-recap-ligne { grid-template-columns: minmax(0, 1fr) auto; }
  .xp-recap-nom { grid-column: 1; grid-row: 1; }
  .xp-recap-niv { grid-column: 1; grid-row: 2; }
  .xp-recap-droite { grid-column: 2; grid-row: 1; text-align: right; }
}
body.mobile-mode .xp-recap-ligne { grid-template-columns: minmax(0, 1fr) auto; }
body.mobile-mode .xp-recap-nom { grid-column: 1; grid-row: 1; }
body.mobile-mode .xp-recap-niv { grid-column: 1; grid-row: 2; }
body.mobile-mode .xp-recap-droite { grid-column: 2; grid-row: 1; text-align: right; }

/* v0.0.139 — « Ce qui vole déjà » défile plutôt que de pousser le reste
   hors du panneau (demande de Loryan : « fais défiler les compteurs »).
   Vingt-deux expéditions en l'air étiraient la colonne du milieu bien
   au-delà des deux autres. */
.rpe-pile.est-defile { max-height: 232px; overflow-y: auto; overscroll-behavior: contain; }
.rpe-pile.est-defile::-webkit-scrollbar { width: 8px; }
.rpe-pile.est-defile::-webkit-scrollbar-thumb {
  background: var(--border2, #2f5f92); border-radius: 4px;
}
/* Le nombre de vagues repliées sur une même minute d'arrivée. */
.rpe-vague {
  font-family: var(--mono); font-size: .58rem; color: #7fd0f5;
  background: rgba(14, 165, 233, .14); border-radius: 3px;
  padding: 0 4px; margin-left: .3rem;
}
@media (max-width: 700px) { .rpe-pile.est-defile { max-height: 200px; } }
body.mobile-mode .rpe-pile.est-defile { max-height: 200px; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.141 — L'ESCADRILLE SE REPLIE SUR SON CONTENEUR, PAS SUR LA
   FENÊTRE (classe `est-conteneur`)
   ════════════════════════════════════════════════════════════════════
   Signalement de Loryan, capture à l'appui : les boutons d'envoi
   sortaient du panneau et passaient par-dessus le rail des Vaisseaux
   Mères.

   ⚠️ LE DÉFAUT N'ÉTAIT PAS DANS LE REPLI, IL ÉTAIT DANS CE QU'IL
   REGARDAIT. Un repli au seuil de FENÊTRE ne mesure rien d'utile : la
   ligne ne dispose pas de la fenêtre. Entre le rail d'icônes, la colonne
   du hangar (232 px) et le rail des Vaisseaux Mères, il lui reste bien
   moins — mesuré, 736 px de conteneur pour une fenêtre de 1030, et même
   740 px pour une fenêtre de 1280. La ligne en exigeait ~815 : d'où 22 à
   162 px de débordement selon la largeur, jamais replié.

   Deux parades, et il faut les deux :
   1. AUCUNE COLONNE NE PEUT FORCER LA LARGEUR — les pistes de
      `.rpe-ligne` sont en `minmax(0, …)`, posées sur la règle de base de
      la ligne. C'est le filet : il tient même sans requête de conteneur.
   2. LE REPLI ÉCOUTE LE CONTENEUR (`@container`), pas la fenêtre. Un
      navigateur qui l'ignore garde la parade n° 1 : serré, mais dedans.
   ════════════════════════════════════════════════════════════════════ */
.rpe-tableau { container-type: inline-size; container-name: escadrille; }

@container escadrille (max-width: 880px) {
  .rpe-ligne {
    grid-template-columns: 10px 22px minmax(0, 1fr) auto;
    grid-template-areas: "led ico nom vol" "reg reg reg reg" "cmd cmd cmd cmd";
    row-gap: .35rem;
  }
  .rpe-reglages { flex-wrap: wrap; }
  .rpe-cmd .exped2-actions { justify-content: flex-start; }
  .rpe-cmd .exped2-rien { text-align: left; }
  /* Une cellule de nom devenue étroite tronquerait « 110 en trop » au
     milieu d'un mot — le stock se replie au lieu d'être coupé. */
  .rpe-nom { overflow: visible; }
  .rpe-stock { white-space: normal; overflow: visible; text-overflow: clip; }
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.147 — LE REPLI ÉTROIT REPLIE AUSSI LES COLONNES
   (classe `est-repli-entier`)
   ════════════════════════════════════════════════════════════════════
   Trouvé au passage mobile du correctif d'escorte : sur téléphone, le
   compteur « en vol » de CHAQUE ligne se lisait « en … » — 30 px de boîte
   pour 79 px de texte, mesuré.

   ⚠️ UN DUEL DE POIDS DE SÉLECTEURS, PAS UNE RÈGLE FAUSSE. Les replis
   posent leurs QUATRE colonnes via des sélecteurs légers (`.rpe-ligne`,
   `body.mobile-mode .rpe-ligne`) ; or une règle plus lourde repose SIX
   colonnes au-dessus de 880 px de conteneur (v0.0.145, `@container` ne
   donnant aucune spécificité). Un mode mobile forcé sur une fenêtre
   large, par exemple, replie alors les ZONES sans replier les COLONNES —
   et la zone « vol » atterrit dans une piste écrasée à 30 px. Chaque
   déclaration gagne son duel : c'est l'ATTELAGE qui serait dépareillé.
   Ces deux règles reposent donc les colonnes du repli avec un sélecteur
   qui gagne à tous les coups.

   ⚠️ IL Y AVAIT UNE TROISIÈME RÈGLE, AU SEUIL DE FENÊTRE (≤ 900 px), ET
   ELLE EST PARTIE AVEC SON ATTELAGE. Elle accompagnait le repli de FENÊTRE,
   qui posait à la fois les quatre colonnes ET les quatre zones ; ce repli-là
   a disparu quand l'Escadrille est passée sur la largeur de son CONTENEUR.
   Restée seule, elle aurait reposé QUATRE colonnes sous SIX zones — un
   attelage dépareillé qui n'a jamais existé à l'écran. Une fenêtre de
   900 px ne laisse de toute façon jamais 881 px à la ligne : le repli de
   conteneur ci-dessous couvre le cas, colonnes et zones ensemble. */
body.mobile-mode .rpe-tableau.est-conteneur.est-repli-entier .rpe-ligne {
  grid-template-columns: 10px 22px minmax(0, 1fr) auto;
}
@container escadrille (max-width: 880px) {
  .rpe-tableau.est-conteneur.est-repli-entier .rpe-ligne {
    grid-template-columns: 10px 22px minmax(0, 1fr) auto;
  }
}

/* v0.0.142 — LE RÉGLAGE D'ÉCHELLE DU RADAR.
   Deux boutons et la portée entre eux : sans l'étiquette du milieu, aucun
   des deux sens n'est évident — sur une carte « + » rapproche, sur une
   échelle de temps il éloigne. */
.mvr-echelle {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  margin: .5rem 0 0;
}
.mvr-ech-btn {
  font-family: var(--mono); font-size: 15px; line-height: 1;
  width: 30px; height: 30px; border-radius: 8px; cursor: pointer;
  color: var(--text2); background: rgba(255, 255, 255, .03);
  border: 1px solid var(--border); transition: .15s;
}
.mvr-ech-btn:hover:not(:disabled) { border-color: var(--accent2); color: var(--text); }
.mvr-ech-btn:disabled { opacity: .35; cursor: not-allowed; }
.mvr-ech-val {
  font-family: var(--mono); font-size: .82rem; color: var(--text);
  min-width: 62px; text-align: center;
}

/* v0.0.143 — LA LÉGENDE D'ÉCHELLE, SOUS LE CADRAN.
   ⚠️ ELLE EST DEHORS PARCE QU'IL N'Y A PAS DE PLACE DEDANS : l'angle d'une
   pastille vient d'un hachage et son rayon de son échéance, donc les deux
   couvrent tout le disque. Une étiquette posée à l'intérieur finit sous un
   compte à rebours, où qu'on la mette. */
.mvr-graduations {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: .35rem .9rem; margin: .55rem 0 0;
}
.mvr-grad {
  display: inline-flex; align-items: center; gap: .3rem;
  font-family: var(--mono); font-size: .68rem; color: var(--text3);
  white-space: nowrap;
}
/* L'anneau témoin reprend le rayon du cadran divisé par sept : l'œil
   retrouve de quel cercle on parle sans avoir à les compter. */
.mvr-grad i {
  display: inline-block; border-radius: 50%; flex-shrink: 0;
  border: 1px solid var(--border2, #2f5f92);
}
.mvr-grad:last-child { color: var(--text2); }
.mvr-grad:last-child i { border-color: var(--accent2); }
@media (max-width: 700px) { .mvr-graduations { gap: .3rem .6rem; } }
body.mobile-mode .mvr-graduations { gap: .3rem .6rem; }

/* @@@ DEBUT RADAR ELOIGNEMENT v0.0.144 @@@ */
/* v0.0.144 — L'ANNEAU « SUR PLACE ».
   Le cadran mesure maintenant une DISTANCE (le temps de vol qui sépare un
   mouvement de son Vaisseau Mère). Certaines missions n'en ont aucune : un
   Récolteur moissonne autour de sa propre planète, un Limier sur une comète
   travaille dans son propre système — leur éloignement est nul à toutes les
   secondes de leur vie.
   ⚠️ IL EST TRACÉ AUTREMENT DES QUATRE AUTRES, ET C'EST LE POINT : les
   graduations sont des mesures, celui-ci est un parking. Un cinquième anneau
   au même trait se lirait comme une cinquième distance, la plus courte —
   alors qu'il dit exactement le contraire, « ceux-là ne partent pas ». */
.mvr-anneau-place {
  stroke: var(--accent2, #2dd4bf); stroke-opacity: .30;
  stroke-dasharray: 2 4; stroke-width: 1.2;
}
/* La pastille d'un mouvement sur place : même dessin, halo tiède, pour
   qu'un groupe posé sur l'anneau se distingue d'un aller-retour qui passe
   par zéro au moment de son décollage. */
.mvr-blip.est-sur-place .mvr-cercle { stroke-dasharray: 3 3; }
/* ⚠️ ET LEUR COMPTE À REBOURS EST PLUS PETIT, pour une raison arithmétique :
   tous ces échos partagent le MÊME rayon, donc le même tour de piste. À dix
   Récolteurs, chacun n'a que 42 px d'arc — très exactement la largeur d'un
   « 12 m 17 s » en corps 10. Le corps 8 le ramène à 33 px et rend les dix
   lisibles. Ce n'est pas une hiérarchie décorative : c'est ce qui tient. */
.mvr-blip.est-sur-place .mvr-cdr { font-size: 8px; }
/* Le nom du moyeu, descendu en légende : il porte l'ancre (ou la navette
   d'un vaisseau en transit) et se détache des graduations — c'est un NOM,
   pas une mesure, il n'a donc pas d'anneau témoin. */
.mvr-grad.est-moyeu {
  color: var(--text2); gap: .2rem;
  padding-right: .55rem; margin-right: .1rem;
  border-right: 1px solid var(--border2, #2f5f92);
}
.mvr-grad.est-place { color: var(--text3); }
.mvr-grad.est-place i {
  border-color: var(--accent2, #2dd4bf); border-style: dashed; opacity: .75;
}
/* @@@ FIN RADAR ELOIGNEMENT v0.0.144 @@@ */

/* @@@ DEBUT ATELIER SOCIAL v0.0.146 @@@ */
/* ════════════════════════════════════════════════════════════════════
   v0.0.146 — L'ATELIER SOCIAL EN TROIS COLONNES
   ════════════════════════════════════════════════════════════════════
   Demande de Loryan : « à gauche le chat global, au milieu haut la liste
   des messages privés, au milieu bas le message privé sélectionné affiché
   en détail avec son contenu, à droite haut la liste des joueurs suivis,
   à droite bas la liste des joueurs bloqués ».

   C'est le seul écran Social : les cinq contenus sont à l'écran en même
   temps, chacun dans son panneau. */
#dsh-social-atelier { display: grid; }

/* ⚠️ LA VUE DEVIENT UNE COLONNE FLEX, ET C'EST CE QUI BORNE L'ATELIER.
   `.dsh-view.is-active` fait 100vh de haut : sans ce passage en flex,
   l'atelier prendrait la hauteur de son contenu, dépasserait par le bas et
   les cinq panneaux défileraient ENSEMBLE au lieu de défiler chacun chez
   soi — ce qui est exactement ce qu'on cherche à éviter en mettant tout à
   l'écran en même temps. */
#dsh-view-social.is-active {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
#dsh-view-social .dsh-view-header {
  flex: 0 0 auto;
  margin-bottom: var(--space-3, 12px);
}

#dsh-social-atelier {
  flex: 1 1 auto;
  min-height: 0;
  gap: var(--space-3, 12px);
  /* Trois colonnes, deux rangées. Le milieu est le plus large : c'est la
     seule case où l'on LIT un texte long et où l'on écrit. */
  grid-template-columns: minmax(300px, 1fr) minmax(330px, 1.2fr) minmax(290px, .95fr);
  grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas:
    "chat pmliste  suivis"
    "chat pmdetail bloques";
}
.soc-case-chat      { grid-area: chat; }
.soc-case-pm-liste  { grid-area: pmliste; }
.soc-case-pm-detail { grid-area: pmdetail; }
.soc-case-suivis    { grid-area: suivis; }
.soc-case-bloques   { grid-area: bloques; }

/* ⚠️ `min-width: 0` ET `min-height: 0` SUR CHAQUE PANNEAU. Sans eux, une
   case de grille refuse de descendre sous la taille de son contenu : un
   pseudo à rallonge ou une longue conversation pousserait la colonne, la
   page partirait de travers, et rien ne le signalerait. */
.soc-panneau {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--card, #0d1526);
  border: 1px solid var(--border2, #1e3060);
  border-radius: var(--radius-md, 8px);
  overflow: hidden;
}

/* Le bandeau de titre d'un panneau — même dessin que l'en-tête du panneau
   de chat, pour que les cinq cases se lisent comme une seule famille. */
.soc-titre {
  flex: 0 0 auto;
  padding: 10px 14px;
  font-weight: 600;
  font-size: .95rem;
  letter-spacing: .02em;
  color: var(--text, #cbd6ea);
  background: linear-gradient(135deg, #1e3a5f 0%, #1a2540 100%);
  border-bottom: 1px solid #324569;
}

/* Le corps d'un panneau : c'est LUI qui défile, jamais la page. */
.soc-corps {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 12px;
}
.soc-hote {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* ─── La colonne de gauche : le panneau de chat, ancré ─────────────
   ⚠️ C'EST LE MÊME NŒUD QUE LA BULLE FLOTTANTE, déplacé ici par
   `chatAncrer()`. Il est `position: fixed` dans sa vie normale : on le
   rend au flux, on lui retire son cadre (le panneau qui l'accueille en a
   déjà un) et on cache sa croix — dans une colonne, il n'y a rien à
   fermer. Le sélecteur porte un IDENTIFIANT pour repasser devant
   `body.mobile-mode .chat-panel`, qui impose sinon `height: calc(100vh -
   60px)` et ferait dépasser la colonne de tout l'écran. */
#dsh-social-atelier .chat-panel {
  position: static;
  width: 100%;
  height: auto;
  flex: 1 1 auto;
  min-height: 0;
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
  z-index: auto;
}
#dsh-social-atelier .chat-panel .chat-close { display: none; }
#dsh-social-atelier .chat-header { border-radius: 0; }
#dsh-social-atelier .chat-messages-container { min-height: 0; }
/* ⚠️ LA ZONE DE SAISIE TRONQUAIT SA PROPRE CONSIGNE. Le champ est haut de
   36 px et ne grandit pas ; sa phrase d'invite (« Écris un message (Entrée
   pour envoyer, Maj+Entrée pour saut de ligne) ») tient sur deux lignes à
   360 px et sur trois sur un téléphone — la dernière était coupée au milieu
   d'un mot. Dans une colonne pleine hauteur, la place ne manque pas. */
#dsh-social-atelier .chat-input { min-height: 58px; }
/* Même défaut, même remède, pour la zone de réponse d'une conversation. */
#soc-hote-pm-conv .pm-input { min-height: 58px; }

/* ─── Le milieu-bas : la conversation choisie ──────────────────────
   Son en-tête est celui de la fenêtre d'origine (`.modal-header`), qui
   porte déjà le pseudo cliquable et la croix : on l'habille comme les
   bandeaux de titre voisins plutôt que d'en empiler un second. */
#soc-hote-pm-conv > .modal-header {
  flex: 0 0 auto;
  padding: 8px 14px;
  align-items: center;
  background: linear-gradient(135deg, #1e3a5f 0%, #1a2540 100%);
  border-bottom: 1px solid #324569;
}
#soc-hote-pm-conv > .modal-header .modal-title { font-size: .95rem; }
#soc-hote-pm-conv > .modal-body {
  flex: 1 1 auto;
  min-height: 0;
}

/* L'écriteau du panneau vide — sans lui, la case du milieu-bas serait un
   rectangle vide dont personne ne devinerait l'usage. */
.soc-vide {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 20px;
  text-align: center;
  color: var(--text3, #6b8ab0);
  font-size: .85rem;
}
.soc-vide-ico { font-size: 1.8rem; opacity: .7; }
.soc-vide p { margin: 0; max-width: 26ch; }

/* La conversation qu'on est en train de lire, surlignée dans la liste du
   dessus : sans elle, on lit un message sans savoir de qui il vient. */
.pm-conv-row.is-active {
  background: #16243f;
  border-color: #60a5fa;
}

/* ─── DEUX COLONNES entre 761 et 1180 px ───────────────────────────
   Trois colonnes sous 1180 px donneraient moins de 300 px chacune : le
   pseudo d'un joueur et l'heure de son message ne tiennent plus sur la
   même ligne, et l'aperçu d'une conversation se coupe au milieu d'un mot. */
@media (max-width: 1180px) {
  /* ⚠️ ET LA VUE REDEVIENT DÉFILANTE ICI, PAS SEULEMENT SUR TÉLÉPHONE.
     Mesuré à 1000 x 800 : le bandeau des ressources passe sur DEUX lignes à
     cette largeur et ne laisse que 570 px de haut. Répartis entre trois
     rangées, cela fait 185 px par panneau — de quoi montrer un titre, un
     champ de saisie, et rien d'autre. Trois rangées en `vh` et un
     ascenseur de page donnent des panneaux qu'on peut réellement lire. */
  #dsh-view-social.is-active { overflow-y: auto; }
  #dsh-social-atelier {
    flex: 0 0 auto;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: 42vh 42vh 36vh;
    grid-template-areas:
      "chat pmliste"
      "chat pmdetail"
      "suivis bloques";
  }
}

/* ─── UNE COLONNE sur téléphone ────────────────────────────────────
   ⚠️ ICI LA VUE REDEVIENT DÉFILANTE, et chaque panneau prend une hauteur
   en `vh` : cinq panneaux qui se partagent un écran de 844 px de haut
   donneraient 130 px chacun, soit deux messages et demi. On empile, on
   défile, et chaque panneau garde son propre ascenseur à l'intérieur. */
@media (max-width: 760px) {
  #dsh-view-social.is-active { overflow-y: auto; }
  #dsh-social-atelier {
    flex: 0 0 auto;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
    grid-template-areas: "chat" "pmliste" "pmdetail" "suivis" "bloques";
  }
  .soc-case-chat      { height: 62vh; }
  .soc-case-pm-liste  { height: 38vh; }
  .soc-case-pm-detail { height: 55vh; }
  .soc-case-suivis    { height: 38vh; }
  .soc-case-bloques   { height: 38vh; }
  /* ⚠️ LE CHAMP « VISER UN PSEUDO » TRONQUAIT SA CONSIGNE. À 390 px, son
     bouton lui laisse 180 px et « Bloquer… (pseudo exact) » se coupe à
     « (pseudo exac ». Sur deux lignes, il retrouve toute la largeur. */
  .soc-corps .social-vise { flex-wrap: wrap; }
  .soc-corps .social-vise input,
  .soc-corps .social-vise button { flex: 1 1 100%; }
  /* ⚠️ 70 px ET PAS 58 : la consigne des deux zones de saisie tient sur
     DEUX lignes dans une colonne de bureau, sur TROIS dans une colonne de
     téléphone. Mesuré à 390 px : il en faut 66 pour la dernière ligne. */
  #dsh-social-atelier .chat-input,
  #soc-hote-pm-conv .pm-input { min-height: 70px; }
}

/* Le mode téléphone FORCÉ sur un écran large (le bouton en haut à gauche
   de la barre) : les media queries ne regardent que la largeur réelle et
   ne se déclenchent donc pas. Sans ces cinq lignes, un joueur qui choisit
   « mobile » sur un grand écran garderait les trois colonnes. */
body.mobile-mode #dsh-view-social.is-active { overflow-y: auto; }
body.mobile-mode #dsh-social-atelier {
  flex: 0 0 auto;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: none;
  grid-template-areas: "chat" "pmliste" "pmdetail" "suivis" "bloques";
}
body.mobile-mode .soc-case-chat      { height: 62vh; }
body.mobile-mode .soc-case-pm-liste  { height: 38vh; }
body.mobile-mode .soc-case-pm-detail { height: 55vh; }
body.mobile-mode .soc-case-suivis    { height: 38vh; }
body.mobile-mode .soc-case-bloques   { height: 38vh; }
body.mobile-mode .soc-corps .social-vise { flex-wrap: wrap; }
body.mobile-mode .soc-corps .social-vise input,
body.mobile-mode .soc-corps .social-vise button { flex: 1 1 100%; }
body.mobile-mode #dsh-social-atelier .chat-input,
body.mobile-mode #soc-hote-pm-conv .pm-input { min-height: 70px; }
/* @@@ FIN ATELIER SOCIAL v0.0.146 @@@ */

/* ════════════════════════════════════════════════════════════════════
   v0.0.146 — VUE RECHERCHE : LA TABLE DES ÉLÉMENTS
   ════════════════════════════════════════════════════════════════════
   ⚠️ CE BLOC EST TOUJOURS VIVANT, MALGRÉ SON TITRE. La grille de cases
   carrées qu'il décrivait a cédé la place au vrai tableau périodique — mais
   celui-ci REPREND ses classes telles quelles (`.res-el-*` pour les cases,
   `.res-fiche-*` pour la fiche de droite, `.res-el-legendes` pour les
   couleurs de branche). Les supprimer déshabillerait l'écran d'aujourd'hui.

   Structure :
     #view-research
       .res-table-wrap
         .res-table-col
           .res-table-grid   → les cases carrées, une par recherche
           .res-el-legendes  → la couleur de chaque branche
         .res-table-fiche    → la recherche choisie, en détail

   ⚠️ LA CASE EST CARRÉE (`aspect-ratio: 1`), ET C'EST TOUT L'INTÉRÊT.
   L'ancienne vignette portait l'effet en entier : celui du Brouillage
   Sensoriel fait quinze lignes, et une grille aligne les hauteurs d'une
   rangée sur sa plus haute — ses trois voisines héritaient de son vide.
   Un texte long ne peut plus déformer quoi que ce soit : il vit dans la
   fiche, où il est seul.
   ════════════════════════════════════════════════════════════════════ */

/* ⚠️ LA PLACE RÉSERVÉE À DROITE COMPTAIT DEUX FOIS LE PANNEAU DES
   VAISSEAUX MÈRES. `.view` est déjà posée à `right: var(--vm-panel-w)`
   (180 px) : la vue s'arrête donc DÉJÀ avant ce panneau. Le
   `padding-right` de `.res-categories-shrunk` lui en réservait 180 de
   plus, à l'intérieur d'une zone qui ne l'atteignait pas — d'où la
   grande bande noire de la capture. Ici on ne réserve que le panneau
   flottant des réductions, qui, lui, flotte VRAIMENT au-dessus. */
#view-research .res-categories-shrunk {
  padding-right: calc(var(--research-bonus-w) + 1rem);
}
#view-research .res-header-v3 {
  margin-right: calc(var(--research-bonus-w) + 1rem);
}

.res-table-wrap {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
}
.res-table-col { flex: 1; min-width: 0; }

.res-table-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 0.5rem;
}

/* ─── Une case ───────────────────────────────────────────────────── */
/* La couleur de la branche arrive par `--el`, posée par .cat-* : une
   seule règle décrit la case, six règles disent sa couleur. */
.res-el {
  position: relative;
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  padding: 0.4rem 0.3rem 0.35rem;
  background: var(--card);
  border: 1px solid var(--border2);
  border-radius: 4px;
  cursor: pointer;
  overflow: hidden;
  text-align: center;
  font-family: var(--body);
  transition: transform 0.12s, border-color 0.12s;
}
/* Le fond teinté de la branche, très en retrait : c'est la couleur qui
   groupe les cases, pas un cadre de plus. */
.res-el::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--el, var(--text3));
  /* ⚠️ 0,18 ET PAS 0,10 : mesuré au navigateur, un fond de branche à 10 %
     sur le bleu nuit du jeu ne se distingue plus du gris des cases sans
     couleur — la famille, qui est le sujet de cette grille, disparaissait. */
  opacity: 0.18;
  pointer-events: none;
}
.res-el:hover { border-color: var(--el, var(--text3)); transform: translateY(-1px); }
.res-el:focus-visible { outline: 2px solid var(--accent2); outline-offset: 2px; }

.res-el-niv {
  position: absolute;
  top: 4px;
  left: 7px;
  font-family: var(--mono);
  font-size: 0.79rem;          /* 11 px — plancher de lisibilité */
  color: var(--text2);
  line-height: 1;
}
.res-el-niv i { font-style: normal; color: var(--gold); font-size: 0.79rem; }
.res-el-sym {
  position: relative;
  font-family: var(--hud);
  font-weight: 700;
  font-size: 1.45rem;
  line-height: 1;
  color: var(--text);
  letter-spacing: 0.02em;
}
.res-el-nom {
  position: relative;
  /* ⚠️ LE NOM N'EST PAS EN ORBITRON, ET C'EST MESURÉ. Dans la police HUD,
     « SuperCalculateur » fait 106 px à 11 px — la case du téléphone n'en
     offre que 95 à l'intérieur : le mot était coupé net. La même chaîne
     dans la police du corps fait 85 px. Changer de police, plutôt que de
     descendre sous le plancher, permet de GROSSIR le nom (12 px sur grand
     écran) au lieu de le rapetisser. Le symbole, lui, reste en HUD. */
  font-family: var(--body);
  font-size: 0.86rem;          /* 12 px */
  line-height: 1.25;
  letter-spacing: 0;
  color: var(--text2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ─── Les quatre états d'une case ────────────────────────────────── */
/* La pastille en haut à droite double la couleur : jamais la couleur
   seule (GDD §21.4.2 — canaux redondants). */
.res-el::after {
  content: '';
  position: absolute;
  top: 5px;
  right: 5px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--border2);
}
.res-el.est-payable { border-color: var(--el, var(--text3)); }
.res-el.est-payable::after { background: var(--accent4); }
.res-el.est-file { border-color: var(--gold); }
.res-el.est-file::after { background: var(--gold); }
.res-el.est-attente::after { background: var(--text3); }
.res-el.est-verrou { opacity: 0.42; }
.res-el.est-verrou::after { background: transparent; }
.res-el.est-verrou .res-el-sym::after {
  content: '🔒';
  position: absolute;
  top: -2px;
  right: -14px;
  font-size: 0.6rem;
}
.res-el.est-choisie {
  border-color: var(--accent2);
  box-shadow: 0 0 0 1px var(--accent2), 0 4px 14px rgba(0, 0, 0, 0.5);
  opacity: 1;
}
.res-el.est-choisie .res-el-nom { color: var(--text2); }

/* La couleur de chaque branche, à un seul endroit. */
.cat-propulsion  { --el: var(--accent2); }
.cat-combat      { --el: var(--accent1); }
.cat-economic    { --el: var(--accent4); }
.cat-research    { --el: var(--accent3); }
.cat-exploration { --el: var(--turquoise); }
.cat-command     { --el: var(--gold); }

/* ─── La légende des branches ────────────────────────────────────── */
.res-el-legendes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  margin-top: 0.8rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border);
}
.res-el-leg {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--hud);
  font-size: 0.79rem;          /* 11 px — plancher de lisibilité */
  letter-spacing: 0.04em;
  color: var(--text3);
}
.res-el-leg i {
  width: 11px;
  height: 11px;
  border-radius: 2px;
  background: var(--el, var(--text3));
  flex: none;
}
.res-el-leg b { font-family: var(--mono); font-weight: 400; color: var(--text2); }

/* ─── La fiche de la case choisie ────────────────────────────────── */
.res-fiche {
  background: var(--card);
  border: 1px solid var(--border2);
  border-top: 2px solid var(--el, var(--text3));
  border-radius: 5px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0 0.8rem 0.8rem;
}
.res-fiche-vide {
  border: 1px dashed var(--border2);
  border-radius: 5px;
  padding: 1.4rem 1rem;
  text-align: center;
  font-size: 0.85rem;
  color: var(--text3);
}
.res-fiche-art {
  height: 104px;
  margin: 0 -0.8rem;
  background-size: cover;
  background-position: center;
  border-bottom: 1px solid var(--border2);
}
.res-fiche-hd {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-top: 0.7rem;
}
.res-fiche-sym {
  font-family: var(--hud);
  font-weight: 700;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--el, var(--text3));
  flex: none;
}
.res-fiche-titres { min-width: 0; }
.res-fiche-nom {
  font-family: var(--hud);
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  color: var(--text);
  line-height: 1.3;
}
.res-fiche-cat { font-size: 0.79rem; color: var(--text3); margin-top: 0.2rem; }
.res-fiche-etat { display: flex; align-items: center; gap: 0.4rem; }
.res-fiche-niv {
  font-family: var(--mono);
  font-size: 0.79rem;
  padding: 0.15rem 0.45rem;
  background: rgba(245, 158, 11, 0.15);
  border: 1px solid var(--gold);
  color: var(--gold);
  border-radius: 2px;
}
.res-fiche-niv i { font-style: normal; color: var(--accent2); }
.res-fiche-effet {
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--text2);
  font-style: italic;
  /* Un effet de quinze lignes ne pousse plus rien : il défile chez lui. */
  max-height: 15em;
  overflow-y: auto;
}
.res-fiche-cout {
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  padding: 0.45rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.res-fiche-cout-hd {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: var(--hud);
  font-size: 0.79rem;
  letter-spacing: 0.06em;
  color: var(--text3);
  text-transform: uppercase;
}
.res-fiche-duree { font-family: var(--mono); font-size: 0.85rem; color: var(--accent2); }
/* ⚠️ v0.0.162 — « depuis ce vaisseau », sous la durée de la fiche de
   recherche. LIGNE À PART, et jamais un troisième enfant de
   `.res-fiche-cout-hd` : cette rangée est en `space-between`, et la règle du
   projet l'interdit — la dernière ligne y prendrait des écarts absurdes.
   Posée au PLANCHER de lisibilité (`--badge-font-size`, 11 px) : c'est une
   précision, pas un chiffre, et la fiche est déjà basse. */
.res-fiche-depuis {
  font-family: var(--hud);
  font-size: var(--badge-font-size);
  letter-spacing: 0.04em;
  text-transform: lowercase;
  color: var(--text3);
  opacity: 0.85;
  text-align: right;
  margin-top: 0.12rem;
  line-height: 1.15;
}
/* ⚠️ v0.0.162 (rechercheMentionDuLanceur) — LE NOM D'UN VAISSEAU N'EST PAS UN
   MOT DE LA PHRASE. La règle juste au-dessus met la mention en minuscules ;
   sans ce correctif, « Comptoir-Beta » s'afficherait « comptoir-beta » et le
   joueur chercherait un vaisseau qu'il ne retrouve sous ce nom nulle part.
   ⚠️ `overflow-wrap` PARCE QUE LE NOM EST CHOISI PAR LE JOUEUR : vingt-quatre
   caractères sans espace tiennent dans la fiche large et débordent au
   téléphone. Il se coupe plutôt que de pousser sa boîte. */
.res-fiche-depuis--nom { text-transform: none; overflow-wrap: anywhere; }
/* La même mention, dans la ligne de durée d'une fiche composée et dans
   l'infobulle de survol. Elle vit DANS la valeur et non à côté : ces deux
   rangées sont en `space-between`, où la règle du projet interdit d'ajouter
   un enfant. Pas de taille imposée — elle suit celle du chiffre qu'elle
   accompagne, la faire plus grosse que lui serait pire que de la laisser
   petite. */
.st-depuis, .sf-depuis { color: var(--text3); opacity: .85; overflow-wrap: anywhere; }
.res-fiche-res {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--text);
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.35rem;
  align-items: center;
}
.res-fiche .tech-btn {
  width: 100%;
  /* ⚠️ SCOPÉ À LA FICHE, ET ON LE LAISSE AINSI. `.tech-btn` porte
     l'habillage général du bouton (plus haut) ; c'est ICI, dans la fiche,
     qu'on le grossit et qu'on l'étire sur toute la largeur. La fiche est
     aujourd'hui le seul endroit qui pose ce bouton, mais séparer les deux
     garde l'habillage réutilisable sans repeindre quoi que ce soit. */
  font-size: 0.79rem;
  letter-spacing: 0.08em;
  padding: 0.55rem 0.7rem;
}

/* ─── Écran moyen : la grille se resserre ────────────────────────── */
@media (max-width: 1100px) {
  .res-table-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
  #view-research .res-categories-shrunk,
  #view-research .res-header-v3 { padding-right: 0; margin-right: 0; }
}

/* ─── Téléphone : la fiche passe SOUS la grille ──────────────────── */
/* Deux déclencheurs, et il en faut deux : la classe `mobile-mode` (posée
   par mobile-mode.js, y compris sur un grand écran forcé en mobile) et la
   largeur réelle de la fenêtre. */
/* ⚠️ LE NOM PASSE À TROIS LIGNES SUR LES PETITES CASES, et c'est ce qui
   permet de tenir le plancher de 11 px sans couper un mot : « Technologie
   des Boucliers » n'entre pas en deux lignes dans 96 px de large. La case
   est carrée, elle a la hauteur pour la troisième ligne. */
@media (max-width: 760px) {
  .res-table-wrap { flex-direction: column; }
  .res-table-fiche { width: 100%; position: static; }
  .res-table-grid { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 0.4rem; }
  .res-el-sym { font-size: 1.3rem; }
  .res-el-nom { font-size: 0.79rem; -webkit-line-clamp: 3; }   /* 11 px, mesuré : le plus long mot tient */
  .res-el-niv { font-size: 0.79rem; }
  .res-fiche-art { height: 130px; }
}
body.mobile-mode .res-table-wrap { flex-direction: column; }
body.mobile-mode .res-table-fiche { width: 100%; position: static; }
body.mobile-mode .res-table-grid {
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 0.4rem;
}
body.mobile-mode .res-el-sym { font-size: 1.3rem; }
body.mobile-mode .res-el-nom { font-size: 0.79rem; -webkit-line-clamp: 3; }
body.mobile-mode .res-el-niv { font-size: 0.79rem; }

/* ════════════════════════════════════════════════════════════════════
   v0.0.149 — LE VRAI TABLEAU PÉRIODIQUE
   ════════════════════════════════════════════════════════════════════
   Le plan des cases est calculé dans tech.js (`_tpPlan`) et posé en
   `grid-column` / `grid-row` sur chaque case : la feuille de style ne
   décide QUE de l'habillage. Structure :

     .res-table-wrap.est-periodique
       .res-table-col
         .res-tp-scroll        → le seul endroit qui défile en travers
           .res-tp-table       → la grille, gouttière + N groupes
             .res-tp-grp       → l'en-tête d'un groupe (ligne 1)
             .res-tp-per       → le numéro d'une période (colonne 1)
             .res-el           → une case, posée à ses coordonnées
       .res-table-fiche        → collée au bord droit

   ⚠️ LE DÉFILEMENT HORIZONTAL VIT DANS `.res-tp-scroll`, JAMAIS DANS LA
   PAGE. Quatorze colonnes ne tiennent pas dans 325 px de téléphone, et
   les rétrécir jusque-là rendrait les cases illisibles. C'est la seule
   forme qui respecte « la page ne défile jamais en travers » sans casser
   le tableau : la boîte défile, la page ne bouge pas. */

/* ⚠️ PLUS RIEN À RÉSERVER À DROITE : le panneau flottant des réductions a
   été retiré (« supprime le truc à droite »), donc la place qu'il occupait
   revient à la fiche — c'est ça, « la fiche de droite mise sur la droite de
   l'écran ». ⚠️ CES DEUX LIGNES DOIVENT RESTER APRÈS celle qui réserve
   encore 146 px plus haut : à spécificité égale, c'est la dernière écrite
   qui gagne, et l'inverse remettrait la colonne vide. */
#view-research .res-categories-shrunk,
#view-research .res-header-v3 {
  padding-right: 0;
  margin-right: 0;
}

.res-table-wrap.est-periodique { gap: 0.9rem; }
.res-tp-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 0.3rem;
  /* ⚠️ LA BARRE DE DÉFILEMENT RESTE VISIBLE, ET C'EST VOULU. Sur téléphone
     la table est plus large que l'écran : sans ce trait de six pixels,
     rien ne dit qu'il y a dix colonnes de plus à droite — l'écran a
     simplement l'air coupé, et le geste ne part jamais. */
  scrollbar-width: thin;
  scrollbar-color: var(--border2) transparent;
}
.res-tp-scroll::-webkit-scrollbar { height: 6px; }
.res-tp-scroll::-webkit-scrollbar-track { background: transparent; }
.res-tp-scroll::-webkit-scrollbar-thumb {
  background: var(--border2);
  border-radius: 3px;
}
.res-tp-table {
  display: grid;
  /* La gouttière des périodes, puis les groupes. `--tp-cols` est écrit
     par le rendu : le nombre de colonnes est MESURÉ sur le catalogue,
     jamais écrit en dur ici. */
  /* ⚠️ `minmax(52px, 1fr)` ET SURTOUT PAS `min-width: max-content` : avec
     `max-content`, la grille prend sa taille NATURELLE (2 275 px mesurés,
     des cases de 148 px) et défile toujours, y compris sur un grand écran
     où elle tiendrait — c'est-à-dire l'inverse exact de « des cases plus
     petites ». Avec `minmax`, les cases se serrent jusqu'à 52 px pour
     occuper la largeur, et ce n'est qu'en dessous que le scroller prend le
     relais. */
  grid-template-columns: 16px repeat(var(--tp-cols, 14), minmax(52px, 1fr));
  gap: 3px;
  align-items: stretch;
}

/* ─── L'en-tête d'un groupe (la branche) ─────────────────────────── */
/* Il remplace les onglets de catégorie retirés : il dit le même nom et
   le même compte, et il ne cache rien. */
.res-tp-grp {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  min-width: 0;
  padding: 0.15rem 0.2rem 0.3rem;
  font-family: var(--hud);
  font-size: 0.79rem;          /* 11 px — plancher de lisibilité */
  letter-spacing: 0.04em;
  color: var(--el, var(--text3));
  border-bottom: 2px solid var(--el, var(--text3));
  white-space: nowrap;
  overflow: hidden;
}
.res-tp-grp b { font-family: var(--mono); font-weight: 400; color: var(--text3); }
.res-tp-grp-ico { flex: none; }
/* La note de lecture, sous la légende : deux axes, il faut le dire une
   fois. Sans elle, la forme est jolie et ne se lit pas. */
.res-tp-note {
  margin-top: 0.5rem;
  max-width: 62ch;
  font-size: 0.79rem;          /* 11 px — plancher de lisibilité */
  line-height: 1.5;
  color: var(--text3);
}
.res-tp-note b { color: var(--text2); font-weight: 600; }

/* ─── Le numéro de période ───────────────────────────────────────── */
.res-tp-per {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: 0.79rem;
  color: var(--text3);
  cursor: help;
}

/* ─── La case, plus petite, et sa planche ────────────────────────── */
/* « que les cases soient plus petites » : le plancher passe de 132 à
   52 px et la case ne s'étire plus au-delà de ce que la grille lui
   donne. Le nom descend à 11 px — le plancher, pas en dessous. */
.est-periodique .res-el {
  gap: 0.1rem;
  padding: 0.3rem 0.2rem 0.25rem;
  border-radius: 3px;
}
/* ⚠️ LA PLANCHE EST UN FOND, ET ELLE PASSE SOUS LA TEINTE DE BRANCHE.
   L'ordre des trois couches est le sujet : la photo (0), la teinte du
   groupe (1) — c'est elle qui fait lire la colonne d'un coup d'œil —,
   puis le texte (2). Inverser deux d'entre elles rend soit une case
   grise, soit un symbole illisible. */
.res-el-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.45;
  z-index: 0;
  pointer-events: none;
}
.res-el.a-planche::before { z-index: 1; opacity: 0.26; }
.res-el.a-planche .res-el-niv,
.res-el.a-planche .res-el-sym,
.res-el.a-planche .res-el-nom { z-index: 2; }
.res-el.a-planche .res-el-sym { text-shadow: 0 1px 4px rgba(0, 0, 0, 0.95); }
.res-el.a-planche .res-el-nom,
.res-el.a-planche .res-el-niv {
  color: var(--text);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95);
}
.res-el.a-planche::after { z-index: 2; }
.est-periodique .res-el-sym { font-size: 1.05rem; }
/* ⚠️ `overflow-wrap: anywhere` N'EST PAS UN DÉTAIL. Mesuré : la case fait
   54 px de large sur une fenêtre de 1500, et « Hyperspatiale » en demande
   72 à 11 px. Sans lui, le mot sortait de la case et `overflow: hidden`
   le coupait EN SILENCE, au milieu d'une lettre — « Propulsion
   yperspatia ». Avec lui, le mot se replie et le `line-clamp` termine par
   une ellipsis EXPLICITE ; le nom entier reste dans l'infobulle et dans
   la fiche de droite, où il a la place. */
/* ⚠️ UNE SEULE LIGNE, ET L'ELLIPSIS EST EXPLICITE. Le rendu ne pose déjà
   que le mot qui distingue (`_tpNomCourt`) ; les quatre plus longs
   (« Hyperspatiale », « SuperCalculateur », « Astrophysique »,
   « Commandement ») dépassent encore 54 px, et c'est `text-overflow` qui
   les termine — un « … » qu'on VOIT, pas une lettre coupée en deux par
   `overflow: hidden`. C'est exactement ce que fait la table des éléments
   dont Loryan a donné la capture (« Praséod… », « Molybdè… »). */
.est-periodique .res-el-nom {
  font-size: 0.79rem;          /* 11 px — plancher de lisibilité */
  line-height: 1.2;
  display: block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* La case n'est plus carrée : à 54 px de large, un carré ne laisse pas la
   hauteur du symbole ET de son nom. C'est la HAUTEUR qui les donne — la
   largeur, elle, est comptée par le nombre de colonnes. */
.est-periodique .res-el { aspect-ratio: auto; min-height: 64px; }
/* ⚠️ LE CADENAS QUITTE LE SYMBOLE POUR LE COIN DE LA CASE. Posé en
   `right: -14px` du symbole, il débordait sa boîte — mesurable, et à
   deux colonnes de moins il aurait fini coupé. Il prend la place de la
   pastille d'état, qui est de toute façon transparente sur un verrou. */
.est-periodique .res-el.est-verrou .res-el-sym::after { content: none; }
.est-periodique .res-el.est-verrou::after {
  content: '🔒';
  background: none;
  width: auto;
  height: auto;
  top: 2px;
  right: 3px;
  font-size: 0.6rem;
  line-height: 1;
}
.est-periodique .res-el-niv { top: 3px; left: 4px; font-size: 0.72rem; }
.est-periodique .res-el::after { top: 3px; right: 3px; width: 5px; height: 5px; }

/* ─── Le bloc « flèches + cumul », emprunté à la fiche de l'arbre ── */
/* Les classes `md-*` sont celles de `mdBlocProjection` : on ne les
   redéfinit PAS ici, on ne fait que leur donner leur place dans une
   fiche plus étroite. */
.res-fiche-proj { display: flex; flex-direction: column; gap: 0.1rem; }
.res-fiche-proj .md-sec { margin-top: 0.5rem; }
.res-fiche-proj .costs { margin-bottom: 0.2rem; }
.res-fiche-duree-seule { padding-top: 0.3rem; }

/* ─── Écran moyen, puis téléphone ────────────────────────────────── */
/* ⚠️ LE TABLEAU NE SE REPLIE PAS EN GRILLE ORDINAIRE SUR TÉLÉPHONE : ses
   colonnes PORTENT LE SENS (la branche) et ses lignes aussi (la
   profondeur). Le replier reviendrait à rendre exactement l'écran que
   Loryan a demandé de remplacer. Les cases rétrécissent jusqu'à 44 px,
   et au-delà c'est `.res-tp-scroll` qui prend le relais. */
@media (max-width: 760px) {
  /* ⚠️ `align-items: stretch` EST OBLIGATOIRE ICI, ET C'EST MESURÉ.
     `.res-table-wrap` est posé en `align-items: flex-start` : replié en
     COLONNE, ce réglage fait prendre à `.res-table-col` la largeur de son
     CONTENU (719 px mesurés) et non celle de l'écran. Le scroller interne
     ne servait alors à rien — il faisait 719 px de large lui aussi —, et
     la table était simplement COUPÉE au bord droit : dix colonnes sur
     quinze hors d'atteinte, sans un pixel de défilement pour y aller, et
     sans que la page déborde pour le signaler. */
  .res-table-wrap.est-periodique { flex-direction: column; align-items: stretch; }
  .est-periodique .res-table-fiche { width: 100%; position: static; }
  .res-tp-table { grid-template-columns: 14px repeat(var(--tp-cols, 14), minmax(44px, 1fr)); }
  .est-periodique .res-el-sym { font-size: 0.95rem; }
  /* ⚠️ LE NOM S'ENROULE VRAIMENT SUR LES CASES ÉTROITES (constat d'audit 3ᵉ
     passage). La règle de base pose `white-space: nowrap` : le mot était coupé
     net (« Orbit… »). Et comme le rendu ne pose qu'UN SEUL mot (`_tpNomCourt` :
     « Orbitale », « Hyperspatiale »…), `white-space: normal` ne suffit pas — un
     mot sans espace ne s'enroule pas. Il faut `overflow-wrap: anywhere` pour
     couper DANS le mot. On borne la hauteur à ~3 lignes, la case a la place
     (min-height 64px). */
  .est-periodique .res-el-nom {
    white-space: normal; overflow-wrap: anywhere; text-overflow: clip;
    line-height: 1.15; max-height: 3.6em; overflow: hidden;
  }
}
body.mobile-mode .res-table-wrap.est-periodique {
  flex-direction: column;
  align-items: stretch;        /* même raison qu'au-dessus : sans lui, coupée */
}
body.mobile-mode .est-periodique .res-table-fiche { width: 100%; position: static; }
body.mobile-mode .res-tp-table {
  grid-template-columns: 14px repeat(var(--tp-cols, 14), minmax(44px, 1fr));
}
body.mobile-mode .est-periodique .res-el-sym { font-size: 0.95rem; }
/* Même correctif que la media query ci-dessus, pour le mode mobile forcé. */
body.mobile-mode .est-periodique .res-el-nom {
  white-space: normal; overflow-wrap: anywhere; text-overflow: clip;
  line-height: 1.15; max-height: 3.6em; overflow: hidden;
}

/* ════════════════════════════════════════════════════════════════════
   v0.0.151 — LA FICHE EN BANDEAU, LE TABLEAU EN PLEINE LARGEUR
   ════════════════════════════════════════════════════════════════════
   Trois demandes de Loryan sur la même capture : « supprime la file en
   bas » · « que l'encart de recherche soit plutôt horizontal en bas » ·
   « que du coup ça permette d'étaler le tableau périodique sur toute la
   largeur ». C'est UN SEUL mouvement : la colonne de 360 px rendue par la
   fiche est exactement la largeur que le tableau récupère.

     .res-table-wrap.est-periodique.est-fiche-bas   → replié en COLONNE
       .res-table-col        → le tableau, pleine largeur
       .res-table-fiche      → le bandeau, posé au bas de la zone
         .res-fiche--bandeau → planche · identité+effet · coût · bouton

   ⚠️ `align-items: stretch` EST OBLIGATOIRE, ET C'EST LE MÊME PIÈGE QUE
   SUR TÉLÉPHONE (v0.0.149) : `.res-table-wrap` est posé en
   `align-items: flex-start`. Replié en colonne sans ce réglage, chaque
   enfant prend la largeur de son CONTENU et non celle de l'écran — le
   tableau resterait à sa largeur naturelle, c'est-à-dire l'inverse exact
   de « étaler sur toute la largeur ».
   ════════════════════════════════════════════════════════════════════ */

/* v0.0.151 : la barre des files (`#queues-bar`) a quitté cet écran, et les
   96 px qui lui réservaient le bas de la zone sont partis avec elle — il ne
   reste qu'un retrait de confort. La classe est posée par
   `renderResearchView()`. */
#view-research .res-categories-shrunk.a-fiche-bas { padding-bottom: 1rem; }

.res-table-wrap.est-periodique.est-fiche-bas {
  flex-direction: column;
  align-items: stretch;
  gap: 0.7rem;
}

/* ─── Le bandeau, en bas de la zone — et PAS collant ──────────────── */
/* ⚠️ LE COLLANT A ÉTÉ ESSAYÉ, MESURÉ, ET RETIRÉ. Collé en `bottom: 0`, le
   bandeau reste à l'écran quand la page défile — mais il PASSE PAR-DESSUS
   la légende des branches et la note de lecture, qui vivent juste au-dessus
   de lui. Vu au navigateur à 1 000 px : légende à y=525, bandeau à y=528,
   note entièrement recouverte. C'est très exactement le défaut n° 3 de la
   consigne (« est-ce que tout reste ATTEIGNABLE ? ») : deux zones lisibles
   masquées par un panneau collant, sans la moindre erreur pour le dire.
   Ce que le collant apportait — ne pas perdre le bouton de vue — est repris
   par `selectResearchTech`, qui amène le bandeau à l'écran à chaque choix
   de case. */
.est-fiche-bas .res-table-fiche {
  width: 100%;
  position: static;
}

/* ─── Le bandeau lui-même : quatre colonnes, une seule rangée ─────── */
/* Elles se replient d'elles-mêmes (`flex-wrap`) quand la largeur manque :
   la base de chacune est sa largeur MINIMALE lisible, pas une part fixe. */
.res-fiche--bandeau {
  display: flex;
  /* ⚠️ `row` EXPLICITE, ET C'EST INDISPENSABLE : `.res-fiche` est posée en
     `flex-direction: column` — sans cette ligne, les quatre colonnes du
     bandeau s'empilent et leurs `flex-basis` deviennent des HAUTEURS. Vu au
     navigateur : une fiche de 1 053 px de haut. */
  flex-direction: row;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 0 0.9rem;
  padding: 0;
}
/* La planche prend toute la hauteur du bandeau au lieu de le coiffer :
   c'est elle qui donne au bloc sa silhouette horizontale. */
.res-fiche--bandeau .res-fiche-art {
  flex: 0 0 190px;
  height: auto;
  min-height: 140px;
  margin: 0;
  border-bottom: none;
  border-right: 1px solid var(--border2);
}
.res-fiche-bloc {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  min-width: 0;
  padding: 0.75rem 0;
}
/* Sans planche (une fournée non déployée n'en a pas), c'est le premier
   bloc qui touche le bord : il prend alors le retrait à sa place. */
.res-fiche--bandeau > .res-fiche-bloc:first-child { padding-left: 0.9rem; }
/* Les largeurs de base des blocs, quand le bandeau se range en `flex`. La
   fiche de la vue Recherche (`.res-fiche--remplie`, pavé plus bas) passe en
   grille : c'est alors la grille qui décide des colonnes. */
.res-fiche-bloc--ident  { flex: 1 1 270px; }
.res-fiche-bloc--cout   { flex: 1 1 300px; justify-content: center; }
/* ⚠️ LE PLAFOND DE L'EFFET EST UN MULTIPLE EXACT DE L'INTERLIGNE (1,6 em),
   ET PAS UN CHIFFRE ROND. À 7,5 em, la dernière ligne visible était coupée
   EN DEUX DANS LA HAUTEUR — le genre de demi-ligne qu'on ne remarque qu'en
   capture. 8 × 1,6 = 12,8 em montre huit lignes ENTIÈRES.
   ⚠️ ET IL FAUT UN PLAFOND : l'effet du Brouillage Sensoriel fait quinze
   lignes, soit 290 px — le « bandeau » ferait alors un tiers de l'écran de
   haut. La colonne de droite le plafonnait déjà, à 15 em.
   ⚠️ SA BARRE DE DÉFILEMENT RESTE VISIBLE, comme celle du tableau : sans ce
   trait de six pixels, rien ne dit qu'il y a une suite — le texte a
   simplement l'air de s'arrêter là. */
.res-fiche--bandeau .res-fiche-effet {
  padding-right: 0.4rem;
  scrollbar-width: thin;
  scrollbar-color: var(--border2) transparent;
}
.res-fiche--bandeau .res-fiche-effet::-webkit-scrollbar { width: 6px; }
.res-fiche--bandeau .res-fiche-effet::-webkit-scrollbar-track { background: transparent; }
.res-fiche--bandeau .res-fiche-effet::-webkit-scrollbar-thumb {
  background: var(--border2);
  border-radius: 3px;
}
/* Deux blocs côte à côte, le trait qui les sépare a un sens : le coût est
   une autre information que l'identité. */
.res-fiche--bandeau .res-fiche-bloc--cout {
  border-left: 1px solid rgba(255, 255, 255, 0.06);
  padding-left: 0.9rem;
}
/* Dans le bandeau, le coût n'a plus besoin de ses deux traits horizontaux :
   la colonne le fait déjà. */
.res-fiche--bandeau .res-fiche-cout { border: 0; padding-top: 0; }
/* Le bouton n'a plus la largeur entière d'une colonne étroite : on le
   grossit, puisque c'est le geste de l'écran. */
.res-fiche--bandeau .tech-btn { padding: 0.7rem 0.7rem; font-size: 0.86rem; }

/* ─── Le tableau récupère la largeur ─────────────────────────────── */
/* Rien à écrire pour l'étalement : `.res-tp-table` est déjà en
   `minmax(52px, 1fr)` — les cases OCCUPENT ce qu'on leur donne, et on
   vient de leur donner 360 px de plus. Mesuré sur une fenêtre de 1 500 px :
   la case passe de 54 à 79 px de large.
   ⚠️ ET LE NOM RESTE À 11 px, MESURÉ, MALGRÉ CES 25 px DE PLUS. Le porter à
   13 px ferait tenir vingt noms sur vingt-quatre au lieu de vingt : «
   Hyperspatiale » demande 68 px à 11 px (il tient dans les 71 px utiles) et
   80 px à 13 px (il ne tient plus). La règle est de ne PAS couper un mot de
   plus pour grossir les autres — la largeur gagnée passe donc en AIR autour
   du nom, pas en corps de police. Le symbole, lui, monte : il fait deux
   lettres et ne peut pas être tronqué. */
@media (min-width: 1200px) {
  .est-fiche-bas .res-el-sym { font-size: 1.2rem; }
}

/* ─── Le bandeau OCCUPE le bas de la zone, il ne s'y pose pas ─────── */
/* ⚠️ SANS ÇA, LA PLACE QUE LA BARRE DES FILES LIBÈRE RESTE EN NOIR. Le
   tableau et sa légende font 570 px sur un écran de 900, et le bandeau
   218 px à sa hauteur naturelle : 110 px de vide restaient EN TRAVERS de
   l'écran, entre la note de lecture et lui. « Une colonne vide à droite est
   une largeur perdue » — une bande vide en bas est une hauteur perdue. Le
   bandeau s'étire donc jusqu'au bas de la zone, et ce qu'il gagne va à
   l'effet, le texte le plus souvent tronqué de la fiche.
   ⚠️ `margin-top: auto` RESTE, en plus du `flex` : il tient le bandeau EN
   BAS le jour où quelque chose l'empêcherait de s'étirer. */
#view-research .res-categories-shrunk.a-fiche-bas { flex: 1 0 auto; }
.res-table-wrap.est-periodique.est-fiche-bas { flex: 1 0 auto; }
.est-fiche-bas .res-table-fiche { margin-top: auto; flex: 1 1 auto; display: flex; }
.est-fiche-bas .res-fiche--bandeau { flex: 1 1 auto; min-height: 0; }
/* ⚠️ `0 1 auto` ET PAS `1 1 auto` SUR L'EFFET : il PREND ce qu'il lui faut,
   il ne s'étire pas. Étiré (`flex-grow: 1`), il ouvrait un trou de cent
   pixels entre sa dernière ligne et la ligne « Débloque : » — le vide
   changeait de place, il ne disparaissait pas. Il peut en revanche
   RÉTRÉCIR (`flex-shrink: 1` + le `min-height: 0` de sa colonne) : quand la
   place manque, il défile chez lui au lieu de pousser le bandeau.
   ⚠️ ET LE PLAFOND SE RÉÉCRIT ICI, PAS PLUS HAUT : cette règle porte trois
   classes, celle du dessus deux — écrit là-bas, il perdrait contre le
   `max-height` de cette règle-ci quoi qu'on mette après. Le piège du
   bandeau de production (v0.0.53), en plus discret. */
.est-fiche-bas .res-fiche--bandeau .res-fiche-effet {
  flex: 0 1 auto;
  max-height: 12.8em;          /* huit lignes entières (8 × 1,6) */
}
/* La colonne d'identité se centre comme celle du coût : le peu de place qui
   reste se répartit au-dessus et au-dessous du bloc, pas tout en bas. */
.est-fiche-bas .res-fiche-bloc--ident { min-height: 0; justify-content: center; }

/* ─── Écran moyen, puis téléphone ────────────────────────────────── */
/* Replié, le bandeau fait la hauteur de ses quatre blocs mis bout à bout :
   la planche passe en pleine largeur et les colonnes s'empilent, exactement
   comme la fiche de la colonne le faisait déjà à cette largeur. */
@media (max-width: 900px) {
  .res-fiche--bandeau .res-fiche-art { flex-basis: 160px; min-height: 130px; }
}
/* ⚠️ LA PLACE DU BOUTON FLOTTANT DES VAISSEAUX MÈRES SE RÉSERVE À NOUVEAU.
   `#vmp-mobile-toggle` est une pastille de 48 px posée en `fixed` au coin
   bas-droit ; les 130 px que `body.has-queues-bar` réservait la tenaient à
   distance, et ils partent avec la barre. Sans ce retrait, elle recouvre le
   bouton d'action du bandeau — le dernier élément de la page. */
@media (max-width: 700px) {
  #view-research .res-categories-shrunk.a-fiche-bas { padding-bottom: 78px; }
}
body.mobile-mode #view-research .res-categories-shrunk.a-fiche-bas { padding-bottom: 78px; }

@media (max-width: 760px) {
  .res-fiche--bandeau .res-fiche-art {
    flex: 1 1 100%;
    height: 130px;
    min-height: 0;
    border-right: none;
    border-bottom: 1px solid var(--border2);
  }
  .res-fiche--bandeau > .res-fiche-bloc { flex: 1 1 100%; padding-left: 0.9rem; }
  .res-fiche--bandeau .res-fiche-bloc--cout {
    border-left: 0;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
  }
  .est-fiche-bas .res-fiche--bandeau .res-fiche-effet { max-height: 17.6em; }  /* 11 lignes */
}
body.mobile-mode .res-fiche--bandeau .res-fiche-art {
  flex: 1 1 100%;
  height: 130px;
  min-height: 0;
  border-right: none;
  border-bottom: 1px solid var(--border2);
}
body.mobile-mode .res-fiche--bandeau > .res-fiche-bloc {
  flex: 1 1 100%;
  padding-left: 0.9rem;
}
body.mobile-mode .res-fiche--bandeau .res-fiche-bloc--cout {
  border-left: 0;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
/* Empilé, le bandeau n'a plus de voisin à ménager : l'effet peut tenir en
   entier (le plus long du jeu en fait quinze lignes). */
body.mobile-mode .est-fiche-bas .res-fiche--bandeau .res-fiche-effet { max-height: 17.6em; }

/* ════════════════════════════════════════════════════════════════════
   LA FICHE RECHERCHE — LE BANDEAU QUI OCCUPE SA PLACE
   ════════════════════════════════════════════════════════════════════
   « La partie basse est très peu remplie alors que y a bcp de place. »
   Le bandeau s'étire déjà jusqu'au bas de la zone (règle du dessus) : ce
   qui manquait, c'est de quoi remplir ce qu'il gagne. `_tpFicheRemplie`
   (features/tech/tech.js) y pose le tableau « Les niveaux suivants », les
   prérequis et la file ; ici, la mise en page et les tailles.

   ⚠️ UNE GRILLE, PLUS UN `flex-wrap`, ET C'EST MESURÉ. Replié par le
   `flex-wrap`, un bandeau à quatre colonnes envoie la DERNIÈRE seule sur
   une seconde rangée pleine largeur — c'est-à-dire le coût et le bouton,
   sous le pli. La grille décide, elle, QUI descend : le tableau des niveaux,
   le seul bloc qui gagne à être large.
   ⚠️ LES BLOCS COMMENCENT EN HAUT, ils ne se centrent plus : centrés, le
   vide se répartissait au-dessus ET au-dessous de chaque colonne, ce qui est
   précisément ce que la capture montrait. */
.res-fiche--bandeau.res-fiche--remplie {
  display: grid;
  grid-template-columns: 220px minmax(0, 1.05fr) minmax(0, 1.35fr) minmax(0, 0.95fr);
  grid-template-areas: "art ident paliers cout";
  align-items: stretch;
  gap: 0;
}
.res-fiche--bandeau.res-fiche--remplie.sans-paliers {
  grid-template-columns: 220px minmax(0, 1.3fr) minmax(0, 1fr);
  grid-template-areas: "art ident cout";
}
.res-fiche--remplie > .res-fiche-art            { grid-area: art; min-height: 0; }
.res-fiche--remplie > .res-fiche-bloc--ident    { grid-area: ident; }
.res-fiche--remplie > .res-fiche-bloc--paliers  { grid-area: paliers; }
.res-fiche--remplie > .res-fiche-bloc--cout     { grid-area: cout; }
.res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc {
  justify-content: flex-start;
  gap: 0.55rem;
  padding: 0.95rem 1.1rem;
  border-left: 1px solid rgba(255, 255, 255, 0.06);
  border-top: 0;
}
/* Sans planche, le symbole de la case, en grand, sur la couleur de sa
   branche : une colonne vide à gauche serait le défaut qu'on répare. */
.res-fiche--remplie > .res-fiche-art--sym {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.05), rgba(0, 0, 0, 0.25));
}
.res-fiche--remplie > .res-fiche-art--sym span {
  font-family: var(--hud);
  font-weight: 700;
  font-size: 3.4rem;
  color: var(--el, var(--text3));
  opacity: 0.8;
}

/* ─── Les tailles : tout monte, puisque la place est là ──────────── */
/* ⚠️ Règle de Loryan pour tout le site : « grossir ou espacer les textes
   pour occuper la largeur ». Aucun texte de ce bandeau n'est sous le
   plancher (0,79 rem = 11 px) — y compris le tableau et les pastilles de
   coût, qui venaient de la fiche au survol (0,64 rem) et du panneau de
   l'arbre (0,65 rem). */
.res-fiche--remplie .res-fiche-hd { padding-top: 0; gap: 0.8rem; }
.res-fiche--remplie .res-fiche-sym { font-size: 2.1rem; }
.res-fiche--remplie .res-fiche-nom { font-size: 1.3rem; }
.res-fiche--remplie .res-fiche-cat { font-size: 0.9rem; }
.res-fiche--remplie .res-fiche-niv { font-size: 0.9rem; }
/* Le badge ✓ ACTIF / À VENIR : 8 px dans l'habillage commun, mesuré ; il
   remonte au plancher ici. */
.res-fiche--remplie .tech-actif-badge,
.res-fiche--remplie .tech-avenir-badge { font-size: var(--badge-font-size); }
/* Douze lignes entières (12 × 1,6) : la colonne a la hauteur pour elles. */
.est-fiche-bas .res-fiche--remplie .res-fiche-effet {
  font-size: 0.97rem;
  max-height: 19.2em;
}
.res-fiche-sous {
  font-family: var(--hud);
  font-size: 0.79rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text3);
  margin-top: 0.35rem;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.res-fiche-reqs { display: flex; flex-direction: column; gap: 0.2rem; }
/* ⚠️ PAS DE `space-between` : la ligne se replie sur une fenêtre étroite,
   et la dernière prendrait des écarts absurdes. « tu as » part à droite par
   sa propre marge. */
.res-fiche-req {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.5rem;
  font-size: 0.9rem;
  color: var(--text2);
  background: none;
  border: 0;
  padding: 0.12rem 0;
  text-align: left;
  font-family: inherit;
}
button.res-fiche-req { cursor: pointer; }
button.res-fiche-req:hover .res-fiche-req-nom { color: var(--accent2); text-decoration: underline; }
.res-fiche-req-ico { font-weight: 700; width: 1em; }
.res-fiche-req.est-ok .res-fiche-req-ico { color: var(--accent4); }
.res-fiche-req.est-manque .res-fiche-req-ico { color: var(--accent1); }
.res-fiche-req.est-manque .res-fiche-req-nom { color: var(--text); }
.res-fiche-req-a {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--text3);
}
.res-fiche-puces { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.res-fiche-puce {
  font-size: 0.86rem;
  color: var(--accent2);
  border: 1px solid var(--border2);
  border-radius: 3px;
  padding: 0.15rem 0.5rem;
}

/* Le tableau des niveaux : celui de la fiche au survol, à la taille du
   bandeau. Ses classes `sf-*` ne sont PAS redéfinies, seulement agrandies. */
.res-fiche--remplie .sf-bloc-titre { font-size: 0.79rem; }
.res-fiche--remplie .sf-paliers { font-size: 0.86rem; }
.res-fiche--remplie .sf-paliers th { padding: 0 0.5rem 0.35rem; }
.res-fiche--remplie .sf-paliers td { padding: 0.24rem 0.5rem; }
.res-fiche--remplie .sf-paliers th.sfp-res .res-mini { width: 20px; height: 20px; }
.res-fiche--remplie .sfp-sous-item .res-mini { width: 17px; height: 17px; }
.res-fiche--remplie .sf-note { font-size: 0.82rem; }

/* Le coût et ses pastilles, venus du panneau de l'arbre. */
.res-fiche--remplie .res-fiche-cout-hd { font-size: 0.86rem; }
.res-fiche--remplie .res-fiche-duree { font-size: 1.05rem; }
.res-fiche--remplie .md-sec { font-size: 0.79rem; }
/* 0,86 rem et pas 0,82 : la racine descend à 12,8 px en mode mobile, et
   0,82 rem y rendait 10,5 px — mesuré, sous le plancher. */
.res-fiche--remplie .md-proj-btn,
.res-fiche--remplie .md-proj-lv { font-size: 0.86rem; }
.res-fiche--remplie .md-proj-btn { padding: 0.2rem 0.5rem; }
/* Le mode mobile agrandit la CIBLE des flèches (34 px) mais leur rend
   0,75 rem — sa règle porte `body.mobile-mode`, plus forte que celle du
   dessus. On ne touche qu'au corps, dans ce bandeau seulement. */
body.mobile-mode .res-fiche--remplie .md-proj-btn { font-size: 0.86rem; }
.res-fiche--remplie .costs { gap: 0.4rem; }
.res-fiche--remplie .cost { font-size: 0.9rem; padding: 0.3rem 0.6rem; }
.res-fiche--remplie .cost .res-mini { width: 20px; height: 20px; }
.res-fiche--remplie .md-cumul-duree { font-size: 0.86rem; }

/* Le geste, rangé sous le coût : on lit le prix, puis on clique. */
.res-fiche-geste {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin-top: 0.5rem;
}
.res-fiche--bandeau.res-fiche--remplie .tech-btn { padding: 0.85rem 0.8rem; font-size: 0.95rem; }
.res-fiche-file { display: flex; flex-direction: column; gap: 0.15rem; }
.res-fiche-file-l {
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.6rem;
  font-family: var(--mono);
  font-size: 0.86rem;
  color: var(--text3);
}
.res-fiche-file-l > span:last-child { margin-left: auto; }
.res-fiche-file-l.est-tete { color: var(--accent2); }

/* ─── Écran moyen : le tableau des niveaux descend sous le texte ─── */
/* ⚠️ C'EST LUI QUI DESCEND, ET PAS LE COÛT : le tableau est le seul bloc
   qui gagne à être large (huit colonnes de chiffres), et le bouton reste en
   haut, là où l'œil l'attend. La planche couvre les deux rangées. */
@media (max-width: 1500px) {
  .res-fiche--bandeau.res-fiche--remplie {
    grid-template-columns: 200px minmax(0, 1.15fr) minmax(0, 1fr);
    grid-template-areas:
      "art ident   cout"
      "art paliers paliers";
  }
  .res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc--paliers {
    border-top: 1px solid rgba(255, 255, 255, 0.06);
  }
}
@media (max-width: 900px) {
  .res-fiche--bandeau.res-fiche--remplie,
  .res-fiche--bandeau.res-fiche--remplie.sans-paliers {
    grid-template-columns: 150px minmax(0, 1fr);
    grid-template-areas:
      "art     ident"
      "cout    cout"
      "paliers paliers";
  }
  .res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc--cout {
    border-left: 0;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
  }
  .res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc--paliers { border-left: 0; }
}

/* ─── Téléphone : une seule colonne ─────────────────────────────── */
/* Les deux déclencheurs, comme partout sur cet écran : la largeur réelle
   ET la classe du mode mobile (un joueur qui le force sur une fenêtre
   large). La planche reprend la bande de 130 px des règles du dessus. */
@media (max-width: 760px) {
  .res-fiche--bandeau.res-fiche--remplie,
  .res-fiche--bandeau.res-fiche--remplie.sans-paliers {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "art" "ident" "cout" "paliers";
  }
  .res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc {
    border-left: 0;
    padding: 0.85rem 0.9rem;
  }
  .res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc--cout,
  .res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc--paliers {
    border-top: 1px solid rgba(255, 255, 255, 0.06);
  }
  .res-fiche--remplie > .res-fiche-art--sym { height: 90px; }
}
body.mobile-mode .res-fiche--bandeau.res-fiche--remplie,
body.mobile-mode .res-fiche--bandeau.res-fiche--remplie.sans-paliers {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "art" "ident" "cout" "paliers";
}
body.mobile-mode .res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc {
  border-left: 0;
  padding: 0.85rem 0.9rem;
}
body.mobile-mode .res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc--cout,
body.mobile-mode .res-fiche--bandeau.res-fiche--remplie > .res-fiche-bloc--paliers {
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
body.mobile-mode .res-fiche--remplie > .res-fiche-art--sym { height: 90px; }

/* ════════════════════════════════════════════════════════════════════
   Interrupteur empireChantiers — LE BERCEAU ET LA CHRYSALIDE DANS L'EMPIRE
   ════════════════════════════════════════════════════════════════════
   Maquettes validées par Loryan : « A+C ». Deux cartes illustrées en tête de
   l'onglet Chantier (features/command/chantiers-empire.js), deux tuiles en
   tête de la Passerelle. Au téléphone, l'image passe en bandeau au-dessus du
   texte et les lignes de prix se tiennent sur deux colonnes sans se couper.
   Mesuré au navigateur, en large et au téléphone. */
body.f-empireChantiers .ce-deux { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 14px; }
body.f-empireChantiers .ce-carte {
  display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 14px; padding: 12px;
  background: linear-gradient(180deg, #0f1a2e, #0b1322); border: 1px solid rgba(96, 165, 250, .28); border-radius: 12px;
}
body.f-empireChantiers .ce-carte.est-violet { border-color: rgba(167, 139, 250, .35); }
body.f-empireChantiers .ce-img {
  width: 150px; height: 150px; border-radius: 10px; object-fit: cover;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .12), 0 6px 18px rgba(0, 0, 0, .5);
}
body.f-empireChantiers .ce-nom { font-size: 17px; font-weight: 700; color: #eaf2ff; margin: 0; }
body.f-empireChantiers .ce-sous { font-size: 12.5px; color: #8fa3c0; margin: 2px 0 8px; line-height: 1.35; }
body.f-empireChantiers .ce-etat {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 700;
  padding: 3px 10px; border-radius: 999px; margin-bottom: 8px;
}
body.f-empireChantiers .ce-etat.est-lock { color: #fbbf24; background: rgba(251, 191, 36, .10); border: 1px solid rgba(251, 191, 36, .35); }
body.f-empireChantiers .ce-etat.est-run { color: #38bdf8; background: rgba(56, 189, 248, .10); border: 1px solid rgba(56, 189, 248, .35); }
body.f-empireChantiers .ce-etat.est-ok { color: #4ade80; background: rgba(74, 222, 128, .10); border: 1px solid rgba(74, 222, 128, .35); }
body.f-empireChantiers .ce-etat.est-loin { color: #a78bfa; background: rgba(167, 139, 250, .10); border: 1px solid rgba(167, 139, 250, .35); }
body.f-empireChantiers .ce-conds { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; }
body.f-empireChantiers .ce-cond {
  font-size: 12px; padding: 2px 8px; border-radius: 6px; white-space: nowrap;
  border: 1px solid rgba(248, 113, 113, .45); color: #fca5a5; background: rgba(255, 255, 255, .03);
}
body.f-empireChantiers .ce-cond.est-ok { border-color: rgba(74, 222, 128, .45); color: #86efac; }
body.f-empireChantiers .ce-cout { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px 10px; margin: 4px 0 10px; }
body.f-empireChantiers .ce-res { font-size: 12px; color: #cbd5e1; font-family: var(--mono, ui-monospace, monospace); white-space: nowrap; }
body.f-empireChantiers .ce-res .res-mini { width: 16px; height: 16px; vertical-align: -3px; margin-right: 4px; }
body.f-empireChantiers .ce-barre { height: 4px; border-radius: 2px; background: rgba(255, 255, 255, .08); margin-top: 3px; overflow: hidden; }
body.f-empireChantiers .ce-barre i { display: block; height: 100%; background: #f59e0b; }
body.f-empireChantiers .ce-res.est-ok .ce-barre i, body.f-empireChantiers .ce-avance .ce-barre i { background: #2dd4bf; }
body.f-empireChantiers .ce-avance { display: flex; align-items: center; gap: 10px; margin: 2px 0 10px; font-size: 12px; color: #cbd5e1; }
body.f-empireChantiers .ce-avance .ce-barre { flex: 1; margin: 0; }
/* Le bouton du geste (celui d'`empireBoutonBatir`, en version courte) prend
   l'allure de la maquette : lisible, à la taille de son texte. */
body.f-empireChantiers .ce-texte .cmd-goto {
  display: inline-flex; align-items: center; gap: 8px; width: auto; margin-top: 2px;
  font-family: inherit; font-size: 13px; font-weight: 700; letter-spacing: 0; text-transform: none;
  padding: 8px 14px; border-radius: 9px; border: 1px solid rgba(45, 212, 191, .45); border-style: solid;
  color: #2dd4bf; background: rgba(45, 212, 191, .08);
}
body.f-empireChantiers .ce-texte .cmd-goto:disabled { border-color: rgba(148, 163, 184, .3); color: #94a3b8; background: rgba(148, 163, 184, .06); cursor: default; }
body.f-empireChantiers .ce-note { font-size: 11.5px; color: #7c8aa5; margin-top: 6px; }
body.f-empireChantiers .ce-tuiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 10px; margin: 0 0 14px; }
body.f-empireChantiers .ce-tuile {
  display: flex; gap: 12px; align-items: center; text-align: left; cursor: pointer; font: inherit;
  background: rgba(15, 26, 46, .8); border: 1px solid rgba(96, 165, 250, .25); border-radius: 10px; padding: 10px 12px;
}
body.f-empireChantiers .ce-tuile:hover { border-color: rgba(45, 212, 191, .55); }
body.f-empireChantiers .ce-tuile img { width: 56px; height: 56px; border-radius: 8px; object-fit: cover; flex-shrink: 0; }
body.f-empireChantiers .ce-tuile-txt { display: flex; flex-direction: column; min-width: 0; }
body.f-empireChantiers .ce-t1 { font-family: var(--mono, monospace); font-size: 11px; letter-spacing: .12em; color: #8fa3c0; text-transform: uppercase; }
body.f-empireChantiers .ce-t2 { font-size: 14px; font-weight: 700; color: #eaf2ff; }
body.f-empireChantiers .ce-t3 { font-size: 12px; color: #fbbf24; }
body.f-empireChantiers .ce-t3.est-ok { color: #4ade80; }
body.f-empireChantiers .ce-t3.est-run { color: #38bdf8; }
body.f-empireChantiers .ce-t3.est-loin { color: #a78bfa; }
body.f-empireChantiers .ce-go { margin-left: auto; font-size: 12px; color: #2dd4bf; white-space: nowrap; }
@media (max-width: 700px) {
  body.f-empireChantiers .ce-deux, body.f-empireChantiers .ce-tuiles { grid-template-columns: minmax(0, 1fr); }
  body.f-empireChantiers .ce-carte { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  body.f-empireChantiers .ce-img { width: 100%; height: 120px; }
  body.f-empireChantiers .ce-cout { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur ficheEffetNiveau — L'EFFET DU NIVEAU VISÉ, EN FACE DE SON COÛT
   ════════════════════════════════════════════════════════════════════
   features/vm/effet-niveau.js. Même gabarit que la ligne d'Électryte qui
   peut la suivre (`.md-conso-el`), en vert : celle-ci dit ce que le niveau
   DONNE, l'autre ce qu'il tire. Texte à 0,86 rem (12 px), note au plancher
   de lisibilité du jeu (0,79 rem). Pas de `nowrap` : le panneau est étroit,
   la note passe à la ligne plutôt que de déborder.
   ⚠️ Écrit AVANT le bloc « écran étroit », qui doit rester en fin de fichier. */
body.f-ficheEffetNiveau .fen-ligne {
  margin: 6px 0 2px;
  padding: 6px 9px;
  border-radius: 7px;
  background: rgba(74, 222, 128, .07);
  border: 1px solid rgba(74, 222, 128, .2);
  font-size: .86rem;
  line-height: 1.45;
  color: var(--text2);
}
body.f-ficheEffetNiveau .fen-ligne b { color: var(--text, #e6ecff); }
/* `inline-block` et non `nowrap` : la parenthèse passe à la ligne d'un seul
   tenant (« 91.4k → » ne reste plus seul en bout de ligne), et peut encore se
   replier si elle dépassait la largeur du panneau. */
body.f-ficheEffetNiveau .fen-note { color: var(--text3); font-size: .79rem; display: inline-block; }
body.f-ficheEffetNiveau .fen-ecart { font-weight: 600; }
body.f-ficheEffetNiveau .fen-ecart.g { color: #4ade80; }
body.f-ficheEffetNiveau .fen-ecart.r { color: #f87171; }
/* Interrupteur ficheEffetEnergie — le niveau visé aggrave le manque
   d'Électryte : même boîte, en ambre (la couleur de la ligne d'Électryte),
   au plancher de lecture. */
body.f-ficheEffetNiveau .fen-ligne.fen-energie {
  background: rgba(251, 191, 36, .07);
  border-color: rgba(251, 191, 36, .28);
  font-size: .79rem;
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur enteteModuleVertical — LE TITRE AU-DESSUS DU BANDEAU
   ════════════════════════════════════════════════════════════════════
   Remontée de Loryan, capture du module Soutes à ~1 030 px : « fais en
   sorte que le texte soit au-dessus du bandeau dans un affichage plus
   vertical que horizontal », puis « en plus tu vois que ça a mangé une
   icône ». L'en-tête d'un module du vaisseau posait côte à côte le titre
   (avec sa description), la barre des compteurs et la pastille des
   bâtiments : le titre se tassait en colonne d'un mot par ligne, la barre
   sortait du cadre par la droite (sa dernière vignette coupée, en silence)
   et la pastille chevauchait la description.
   Allumé : le titre et sa description prennent toute la largeur ; la barre
   et la pastille passent dessous, à gauche. La barre peut passer à la ligne
   plutôt que de sortir du cadre — même geste que celui des Galons au
   téléphone — et son détail déplié s'ouvre vers la droite, puisqu'elle est
   désormais à gauche.
   ⚠️ Visé : les en-têtes du VAISSEAU (#ship-main) seulement — les autres vues
   ont leur propre `.pg-hdr` et rien à y gagner.
   ⚠️ Écrit AVANT le bloc « écran étroit », qui doit rester en fin de fichier. */
body.f-enteteModuleVertical #ship-main .pg-hdr {
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: .55rem .7rem;
}
body.f-enteteModuleVertical #ship-main .pg-hdr > div:first-child { flex: 1 0 100%; }
body.f-enteteModuleVertical #ship-main .pg-hdr .cpt-encart {
  margin-left: 0;
  flex-shrink: 1;
  min-width: 0;
  max-width: 100%;
}
body.f-enteteModuleVertical #ship-main .pg-hdr .cpt-barre {
  flex-wrap: wrap;
  white-space: normal;
  row-gap: 4px;
  max-width: 100%;
  border-radius: 22px;
}
/* Ouvert vers la droite, et jamais plus large que la barre au téléphone :
   mesuré à 390 px, 340 px fixes calés à gauche sortaient de 6 px de l'écran. */
body.f-enteteModuleVertical #ship-main .pg-hdr .cpt-detail { left: 0; right: auto; width: clamp(260px, 100%, 340px); }

/* ════════════════════════════════════════════════════════════════════
   Interrupteur arbreEnListe — L'ARBRE DE PROGRESSION EN LISTE
   ════════════════════════════════════════════════════════════════════
   features/arbre/arbre-liste.js — maquette « B + C » retenue par Loryan :
   la liste rangée par avancement à gauche ; à droite, le chemin « Il faut ▶
   élément ▶ Ça ouvre », puis le plan complet numéroté.
   Au téléphone (moins de 700 px, ou mode téléphone forcé) : la liste seule,
   puis la fiche seule avec « ‹ Liste » ; le chemin s'empile de haut en bas,
   et dans le plan le niveau passe sous le nom (sinon les noms longs se
   coupaient — mesuré sur la maquette à 390 px).
   Textes au plancher de lisibilité du jeu (11 px) ou au-dessus.
   ⚠️ Écrit AVANT le bloc « écran étroit », qui doit rester en fin de fichier. */
body.f-arbreEnListe .al-cols{display:grid;grid-template-columns:minmax(250px,32%) 1fr;gap:16px;align-items:start}
body.f-arbreEnListe .al-liste{background:linear-gradient(180deg,#0e1729,#0a1120);border:1px solid rgba(96,165,250,.22);border-radius:12px;padding:10px;min-width:0}
body.f-arbreEnListe .al-rech{width:100%;box-sizing:border-box;font-size:13px;color:var(--text);border:1px solid rgba(148,163,184,.25);border-radius:8px;padding:7px 10px;background:rgba(255,255,255,.02)}
body.f-arbreEnListe .al-filtres{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 2px}
body.f-arbreEnListe .al-f{font-size:12px;padding:3px 10px;border-radius:999px;border:1px solid rgba(148,163,184,.25);color:#94a3b8;background:transparent;cursor:pointer}
body.f-arbreEnListe .al-f em{font-style:normal;opacity:.7}
body.f-arbreEnListe .al-f.is-on{border-color:#2dd4bf;color:#2dd4bf;background:rgba(45,212,191,.08)}
body.f-arbreEnListe .al-liste-corps{max-height:72vh;overflow-y:auto;margin-top:4px;padding-right:2px}
body.f-arbreEnListe .al-grp{display:flex;align-items:center;gap:6px;width:100%;font-family:var(--mono,monospace);font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;color:#2dd4bf;margin:10px 0 4px;padding:0 2px;background:none;border:0;text-align:left}
body.f-arbreEnListe .al-grp em{font-style:normal;color:#64748b}
body.f-arbreEnListe .al-grp-dispo{color:#4ade80}
body.f-arbreEnListe .al-grp-presque{color:#fbbf24}
body.f-arbreEnListe .al-grp-loin{color:#94a3b8}
body.f-arbreEnListe .al-grp-bati{color:#7dd3fc;cursor:pointer}
body.f-arbreEnListe .al-plie{margin-left:auto;font-size:11px;letter-spacing:0;text-transform:none;color:#64748b}
body.f-arbreEnListe .al-li{display:flex;align-items:center;gap:8px;width:100%;padding:5px 6px;border-radius:8px;font-size:13px;color:#dbe4f5;border:1px solid transparent;background:none;text-align:left;cursor:pointer}
body.f-arbreEnListe .al-li:hover{background:rgba(255,255,255,.03)}
body.f-arbreEnListe .al-li.is-sel{background:rgba(56,189,248,.10);border-color:rgba(56,189,248,.5)}
body.f-arbreEnListe .al-li.al-loin .al-ln{color:#94a3b8}
body.f-arbreEnListe .al-lt{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
body.f-arbreEnListe .al-ln{line-height:1.25}
body.f-arbreEnListe .al-indice{font-size:11.5px;color:#fbbf24;font-family:var(--mono,monospace)}
body.f-arbreEnListe .al-nw{white-space:nowrap}
body.f-arbreEnListe .al-pt{font-size:11.5px;font-family:var(--mono,monospace);white-space:nowrap;color:#fbbf24}
body.f-arbreEnListe .al-dispo .al-pt{color:#4ade80}
body.f-arbreEnListe .al-bati .al-pt{color:#7dd3fc}
body.f-arbreEnListe .al-vide,body.f-arbreEnListe .al-rien{font-size:12.5px;color:#7c8aa5;padding:6px 2px}
body.f-arbreEnListe .al-vi{width:28px;height:28px;border-radius:6px;background-size:cover;background-position:center;flex-shrink:0;box-shadow:0 0 0 1px rgba(255,255,255,.12);display:inline-flex;align-items:center;justify-content:center;font-size:16px}
body.f-arbreEnListe .al-vi-moyen{width:44px;height:44px;border-radius:8px}
body.f-arbreEnListe .al-vi-gros{width:84px;height:84px;border-radius:12px;font-size:36px}
body.f-arbreEnListe .al-fiche{background:linear-gradient(180deg,#0f1a2e,#0b1322);border:1px solid rgba(96,165,250,.28);border-radius:12px;padding:14px;min-width:0}
body.f-arbreEnListe .al-fiche-vide{font-size:13px;color:#7c8aa5}
body.f-arbreEnListe .al-retour{display:none;font-size:13px;color:#2dd4bf;background:none;border:0;padding:0 0 8px;cursor:pointer}
body.f-arbreEnListe .al-chemin{display:grid;grid-template-columns:1fr 22px minmax(150px,.7fr) 22px 1fr;align-items:center;gap:6px}
body.f-arbreEnListe .al-col{display:flex;flex-direction:column;gap:7px;min-width:0}
body.f-arbreEnListe .al-bt{font-family:var(--mono,monospace);font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:#2dd4bf;margin:2px 0 4px}
body.f-arbreEnListe .al-carte{display:grid;grid-template-columns:44px 1fr;grid-template-rows:auto auto;column-gap:8px;align-items:center;padding:7px;border-radius:9px;border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.02);text-align:left;cursor:pointer;color:inherit}
body.f-arbreEnListe .al-carte .al-vi{grid-row:1/3}
body.f-arbreEnListe .al-carte.al-ko{border-color:rgba(248,113,113,.45)}
body.f-arbreEnListe .al-carte.al-ok{border-color:rgba(74,222,128,.35)}
body.f-arbreEnListe .al-carte.al-suite{border-color:rgba(167,139,250,.35)}
body.f-arbreEnListe .al-cn{font-size:13px;color:#eaf2ff;font-weight:600;line-height:1.2}
body.f-arbreEnListe .al-cv{font-size:12px;font-family:var(--mono,monospace);color:#cbd5e1}
body.f-arbreEnListe .al-cv .al-ko{color:#fca5a5}
body.f-arbreEnListe .al-cv .al-ok{color:#86efac}
body.f-arbreEnListe .al-tuas{display:block;color:#7c8aa5}
body.f-arbreEnListe .al-plus{font-size:12px;color:#a5b4fc;background:none;border:1px dashed rgba(167,139,250,.4);border-radius:8px;padding:5px;cursor:pointer}
body.f-arbreEnListe .al-fl{text-align:center;color:#2dd4bf;font-size:14px}
body.f-arbreEnListe .al-fl::before{content:"▶"}
body.f-arbreEnListe .al-centre{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;padding:12px 8px;border-radius:12px;border:1px solid rgba(56,189,248,.5);background:rgba(56,189,248,.06);min-width:0}
body.f-arbreEnListe .al-nom{font-size:18px;font-weight:700;color:#eaf2ff;line-height:1.2}
body.f-arbreEnListe .al-sous{font-size:12px;color:#8fa3c0}
body.f-arbreEnListe .al-etat{display:inline-flex;gap:6px;align-items:center;font-size:12px;font-weight:700;padding:3px 10px;border-radius:999px}
body.f-arbreEnListe .al-etat-lock{color:#fbbf24;background:rgba(251,191,36,.10);border:1px solid rgba(251,191,36,.35)}
body.f-arbreEnListe .al-etat-dispo{color:#4ade80;background:rgba(74,222,128,.10);border:1px solid rgba(74,222,128,.35)}
body.f-arbreEnListe .al-etat-bati{color:#7dd3fc;background:rgba(125,211,252,.10);border:1px solid rgba(125,211,252,.35)}
body.f-arbreEnListe .al-btn{font-size:12.5px;font-weight:700;padding:6px 12px;border-radius:9px;border:1px solid rgba(45,212,191,.45);color:#2dd4bf;background:rgba(45,212,191,.08);cursor:pointer}
body.f-arbreEnListe .al-note{font-size:12px;color:#7c8aa5;margin-top:10px}
body.f-arbreEnListe .al-sep{height:1px;background:linear-gradient(90deg,rgba(45,212,191,.35),transparent);margin:14px 0 10px}
body.f-arbreEnListe .al-plan{border-left:2px solid rgba(45,212,191,.35);margin:6px 0 0 12px;padding-left:12px}
body.f-arbreEnListe .al-etape{display:flex;align-items:center;gap:8px;width:100%;margin-bottom:7px;font-size:13px;color:#dbe4f5;position:relative;background:none;border:0;padding:0;text-align:left;cursor:pointer}
body.f-arbreEnListe .al-etape.al-fin{cursor:default}
body.f-arbreEnListe .al-num{position:absolute;left:-25px;width:22px;height:22px;border-radius:50%;background:#0b1322;border:1px solid #2dd4bf;color:#2dd4bf;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center}
body.f-arbreEnListe .al-fin .al-num{border-color:#facc15;color:#facc15}
body.f-arbreEnListe .al-et{flex:1;min-width:0;display:flex;align-items:center;justify-content:space-between;gap:8px}
body.f-arbreEnListe .al-niv{font-size:12px;font-family:var(--mono,monospace);white-space:nowrap;color:#cbd5e1}
body.f-arbreEnListe .al-niv b{color:#facc15}
@media (max-width: 700px) {
  body.f-arbreEnListe .al-cols{grid-template-columns:1fr}
  body.f-arbreEnListe .al-cols.al-voit-fiche .al-liste{display:none}
  body.f-arbreEnListe .al-cols:not(.al-voit-fiche) .al-fiche{display:none}
  body.f-arbreEnListe .al-retour{display:inline-block}
  body.f-arbreEnListe .al-chemin{grid-template-columns:1fr}
  body.f-arbreEnListe .al-fl::before{content:"▼"}
  body.f-arbreEnListe .al-vi-gros{width:64px;height:64px}
  body.f-arbreEnListe .al-liste-corps{max-height:none}
  body.f-arbreEnListe .al-et{flex-direction:column;align-items:flex-start;gap:0}
}
body.f-arbreEnListe.mobile-mode .al-cols{grid-template-columns:1fr}
body.f-arbreEnListe.mobile-mode .al-cols.al-voit-fiche .al-liste{display:none}
body.f-arbreEnListe.mobile-mode .al-cols:not(.al-voit-fiche) .al-fiche{display:none}
body.f-arbreEnListe.mobile-mode .al-retour{display:inline-block}
body.f-arbreEnListe.mobile-mode .al-chemin{grid-template-columns:1fr}
body.f-arbreEnListe.mobile-mode .al-fl::before{content:"▼"}
body.f-arbreEnListe.mobile-mode .al-vi-gros{width:64px;height:64px}
body.f-arbreEnListe.mobile-mode .al-liste-corps{max-height:none}
body.f-arbreEnListe.mobile-mode .al-et{flex-direction:column;align-items:flex-start;gap:0}
/* ⚠️ LE CHEMIN S'EMPILE AUSSI QUAND LA FICHE EST ÉTROITE, quelle que soit la
   fenêtre : mesuré à 1 034 px, la fiche ne fait que ~300 px à côté de la
   liste, et les trois colonnes se chevauchaient (noms débordant des cartes).
   On mesure donc la FICHE elle-même, pas l'écran. */
body.f-arbreEnListe .al-fiche{container-type:inline-size}
@container (max-width: 640px) {
  body.f-arbreEnListe .al-chemin{grid-template-columns:1fr}
  body.f-arbreEnListe .al-fl::before{content:"▼"}
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur vmRaie — LA RAIE, VUE GLOBALE DU VAISSEAU MÈRE
   ════════════════════════════════════════════════════════════════════
   features/vm/raie.js. L'image (une seule, celle de la vue) et le calque des
   zones sont superposés dans une scène au rapport 800 / 500 : les deux
   occupent exactement la même boîte, avec le même cadrage (« contain » pour
   l'image, `xMidYMid meet` pour le calque) — les zones restent alignées à
   toutes les largeurs.
   Les tailles de texte du calque sont en unités du dessin (800 de large) :
   14 unités donnent 11 px dès 630 px de scène. Au téléphone, les pastilles
   (illisibles à 390 px) se retirent : les cartes de module les remplacent,
   juste au-dessus et au-dessous.
   ⚠️ Écrit AVANT le bloc « écran étroit », qui doit rester en fin de fichier. */
body.f-vmRaie .rz-plan{--rz-a:#4fd1ff}
body.f-vmRaie .rz-c-economique{--rz-a:#ffa94d}
body.f-vmRaie .rz-c-exploration{--rz-a:#b57cff}
body.f-vmRaie .rz-c-chasseur{--rz-a:#ff6a3d}
body.f-vmRaie .rz-c-chercheur{--rz-a:#3ee08f}
body.f-vmRaie .rz-barre{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;padding:10px 12px;background:#070b11;border-top:1px solid var(--border)}
body.f-vmRaie .rz-info{display:flex;flex-direction:column;gap:2px;margin-right:auto;min-width:0}
body.f-vmRaie .rz-info b{font-family:var(--hud);font-size:15px;letter-spacing:.06em;color:var(--text)}
body.f-vmRaie .rz-info span{font-family:var(--mono);font-size:11.5px;letter-spacing:.12em;color:var(--text2)}
body.f-vmRaie .rz-vues{display:inline-flex;border:1px solid rgba(148,163,184,.25);border-radius:999px;overflow:hidden}
body.f-vmRaie .rz-vue{font-size:12.5px;padding:5px 12px;background:transparent;border:0;color:#94a3b8;cursor:pointer}
body.f-vmRaie .rz-vue + .rz-vue{border-left:1px solid rgba(148,163,184,.25)}
body.f-vmRaie .rz-vue.is-on{color:var(--rz-a);background:color-mix(in srgb,var(--rz-a) 14%,transparent)}
body.f-vmRaie .rz-classe{font-size:12px;padding:4px 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--rz-a) 55%,transparent);color:var(--rz-a)}
body.f-vmRaie .rz-fond{background:radial-gradient(ellipse at 50% 45%,#101b2e,#05080f 70%)}
body.f-vmRaie .rz-scene{position:relative;width:100%;max-width:1000px;margin:0 auto;aspect-ratio:800/500}
body.f-vmRaie .rz-img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;display:block;user-select:none}
body.f-vmRaie .rz-calque{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
/* Interrupteur planCalques — un calque posé RECADRE le dessin sur la coque
   (RZ_CADRE_CALQUE = 120 140 640 240, features/vm/raie.js) : la scène prend
   le rapport 640/240, l'image grandit de 800/640 = 125 % et recule de
   120/640 = 18,75 % et de 140/240 = 58,33 %. ⚠️ CES POURCENTAGES EN
   DÉCOULENT : changer le cadre sans eux décale le dessin sous ses régions.
   Les étiquettes du dessin s'effacent sous 700 px (elles y feraient 6 px) :
   la liste sous le dessin porte les mêmes chiffres, lisibles. */
body.f-vmRaie.f-planCalques .rz-avec-calque .rz-scene{aspect-ratio:640/240;overflow:hidden;max-width:1200px}
body.f-vmRaie.f-planCalques .rz-avec-calque .rz-img{inset:auto;left:-18.75%;top:-58.3333%;width:125%;height:208.3333%;max-width:none}
body.f-vmRaie.f-planCalques .rz-avec-calque .rz-g-pastilles{display:none}
body.f-vmRaie.f-planCalques .rz-region{cursor:pointer}
body.f-vmRaie.f-planCalques .rz-region:focus{outline:none}
body.f-vmRaie.f-planCalques .rz-region-f{fill:var(--rzc);fill-opacity:.45;stroke:var(--rzc);stroke-width:1.5}
body.f-vmRaie.f-planCalques .rz-region:hover .rz-region-f,body.f-vmRaie.f-planCalques .rz-region:focus .rz-region-f{fill-opacity:.7}
body.f-vmRaie.f-planCalques .rz-region-t{font-family:var(--mono);font-size:10px;font-weight:700;fill:#fff;text-anchor:middle;paint-order:stroke;stroke:#050810;stroke-width:2.5px;pointer-events:none}
body.f-vmRaie.f-planCalques .rz-calque-liste{display:flex;flex-wrap:wrap;gap:6px;padding:10px 12px;border-top:1px solid var(--border)}
body.f-vmRaie.f-planCalques .rz-calque-liste:empty{display:none}
body.f-vmRaie.f-planCalques .rz-cl{display:inline-flex;flex-wrap:wrap;align-items:center;gap:2px 6px;font:500 .82rem var(--body);color:var(--text);background:var(--bg2);
  border:1px solid color-mix(in srgb,var(--rzc) 55%,transparent);border-radius:6px;padding:4px 9px;cursor:pointer;max-width:100%;text-align:left}
body.f-vmRaie.f-planCalques .rz-cl i{width:10px;height:10px;border-radius:3px;background:var(--rzc);flex:none}
body.f-vmRaie.f-planCalques .rz-cl b{font-weight:600;white-space:nowrap}
body.f-vmRaie.f-planCalques .rz-cl-v{font-family:var(--mono);color:#fff}
body.f-vmRaie.f-planCalques .rz-cl-d{color:var(--text2)}
body.f-vmRaie.f-planCalques .rz-cl.est-calme{opacity:.65;border-color:var(--border)}
/* Au téléphone, les cinq boutons de calque tiennent sur la ligne (mesuré à
   390 px : « Apports » était coupé avec le retrait des boutons de vue). */
@media (max-width:700px){body.f-vmRaie.f-planCalques .rz-region-t{display:none}
  body.f-vmRaie.f-planCalques .rz-calques{max-width:100%}
  body.f-vmRaie.f-planCalques .rz-calques .rz-vue{padding:5px 8px}}
body.f-vmRaie .rz-zone{cursor:pointer;outline:none}
body.f-vmRaie .rz-zone-info{cursor:help}
body.f-vmRaie .rz-touche{fill:transparent;stroke:transparent;stroke-width:10;pointer-events:all}
body.f-vmRaie .rz-forme{fill:transparent;stroke:transparent;stroke-width:2;pointer-events:none;transition:fill .12s,stroke .12s}
body.f-vmRaie .rz-zone:hover .rz-forme,body.f-vmRaie .rz-zone:focus-visible .rz-forme{stroke:var(--rz-a);fill:color-mix(in srgb,var(--rz-a) 16%,transparent);filter:drop-shadow(0 0 4px var(--rz-a))}
body.f-vmRaie .rz-piste{fill:rgba(5,8,15,.72);stroke:rgba(255,255,255,.18);stroke-width:.5}
body.f-vmRaie .rz-cuve-t,body.f-vmRaie .rz-cuve-p{font-family:var(--mono);font-size:12px;font-weight:700;text-anchor:middle}
body.f-vmRaie .rz-sol{fill:none;stroke:#3b4a5c;stroke-width:1.4}
body.f-vmRaie .rz-sol-fond{fill:rgba(40,52,68,.35)}
body.f-vmRaie .rz-sol-t{font-family:var(--mono);font-size:14px;fill:#9fb2c8}
body.f-vmRaie .rz-derrick line,body.f-vmRaie .rz-derrick path{stroke:#8d9aa9;stroke-width:1.4;fill:#4b5563}
body.f-vmRaie .rz-derrick-t{font-family:var(--mono);font-size:13px;font-weight:700}
body.f-vmRaie .rz-ombilic{fill:none;stroke:var(--rz-a);stroke-width:1.6;stroke-dasharray:4 3;opacity:.8}
body.f-vmRaie .rz-past{cursor:pointer;outline:none}
body.f-vmRaie .rz-past-l{stroke:color-mix(in srgb,var(--rz-a) 70%,transparent);stroke-width:1}
body.f-vmRaie .rz-past-d{fill:var(--rz-a)}
body.f-vmRaie .rz-past-p{fill:rgba(7,12,22,.9);stroke:color-mix(in srgb,var(--rz-a) 60%,transparent);stroke-width:1}
body.f-vmRaie .rz-past:hover .rz-past-p,body.f-vmRaie .rz-past:focus-visible .rz-past-p{stroke:var(--rz-a);fill:rgba(12,30,48,.95)}
body.f-vmRaie .rz-past.is-alert .rz-past-p{stroke:#f87171}
body.f-vmRaie .rz-past.is-busy .rz-past-p{stroke:#facc15}
body.f-vmRaie .rz-past.is-empty{opacity:.65}
body.f-vmRaie .rz-past-i{font-size:13px;text-anchor:middle;dominant-baseline:middle}
body.f-vmRaie .rz-past-v{font-family:var(--mono);font-size:14px;font-weight:700;fill:#e3ebf4;dominant-baseline:middle}
body.f-vmRaie .rz-bulle{position:absolute;z-index:5;max-width:280px;padding:7px 10px;border-radius:8px;background:rgba(7,12,22,.96);border:1px solid var(--rz-a);box-shadow:0 8px 24px rgba(0,0,0,.55);pointer-events:none;font-size:12.5px;line-height:1.4;color:#cbd5e1}
body.f-vmRaie .rz-bulle b{display:block;color:#eaf2ff;font-size:13px}
body.f-vmRaie .rz-bulle span{display:block}
body.f-vmRaie .rz-bulle[hidden]{display:none}
/* Au téléphone, la scène fait ~320 px : un texte de 14 unités y tombe à
   5,6 px. Les pastilles et les petits textes du calque se retirent (les
   cartes de module, juste à côté, disent les mêmes chiffres) ; les jauges
   restent, elles se lisent à la couleur. */
@media (max-width: 700px) {
  body.f-vmRaie .rz-g-pastilles,body.f-vmRaie .rz-cuve-t,body.f-vmRaie .rz-cuve-p,body.f-vmRaie .rz-sol-t,body.f-vmRaie .rz-derrick-t{display:none}
  body.f-vmRaie .rz-touche{stroke-width:24}
}
body.f-vmRaie.mobile-mode .rz-g-pastilles,body.f-vmRaie.mobile-mode .rz-cuve-t,body.f-vmRaie.mobile-mode .rz-cuve-p,body.f-vmRaie.mobile-mode .rz-sol-t,body.f-vmRaie.mobile-mode .rz-derrick-t{display:none}
body.f-vmRaie.mobile-mode .rz-touche{stroke-width:24}

/* La miniature de la colonne des Vaisseaux Mères et du hangar de l'Empire :
   le dessin de la vue choisie, recadré sur la coque (fenêtres de
   `RZ_MINI`, cadre 800 × 500). De profil x 140 → 746, y 170 → 352 : l'image
   à 800/606 de la boîte, décalée de 140/606 en largeur et de 170/182 en
   hauteur. De dessus et de dessous x 140 → 750, y 40 → 474 : 800/610,
   140/610 et 40/434. */
body.f-vmRaie .vmp-raie{position:relative;display:block;width:100%;max-width:148px;margin:0 auto;aspect-ratio:606/182;overflow:hidden;flex-shrink:0}
body.f-vmRaie .vmp-raie img{position:absolute;left:-23.1023%;top:-93.4066%;width:132.0132%;height:auto;max-width:none;pointer-events:none}
body.f-vmRaie .vmp-raie.v-dessus,body.f-vmRaie .vmp-raie.v-dessous{aspect-ratio:610/434}
body.f-vmRaie .vmp-raie.v-dessus img,body.f-vmRaie .vmp-raie.v-dessous img{left:-22.9508%;top:-9.2166%;width:131.1475%}
body.f-vmRaie .vmp-pill.active .vmp-raie{filter:drop-shadow(0 0 5px rgba(var(--vmc-rgb),.7))}
/* En transit : la Cité-nef pointillait sa coque ; un dessin ne se pointille
   pas, le nom porte donc l'avion. */
body.f-vmRaie .vmp-pill.state-transit .vmp-name::before{content:'✈ '}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur colonneVmRepliable — LA COLONNE DES VAISSEAUX MÈRES, PLUS
   ÉTROITE ET REPLIABLE
   ════════════════════════════════════════════════════════════════════
   Loryan : « réduire la largeur de cette colonne, ou la rendre coulissante
   […] et que quand c'est déplié ça décale le reste vers la gauche comme
   c'est actuellement, juste moins large ». Dépliée, elle passe de 180 à
   124 px ; repliée, c'est une baguette de 34 px, une pastille par vaisseau.
   C'est `--vm-panel-w` qui change : vues, files, carte et panneaux
   flottants se calent déjà dessus, rien d'autre à pousser.

   ⚠️ LE COIN D'ALERTE GARDE SES 180 PX. Les deux bandeaux du haut, l'encart
   « Aucune alerte », le bandeau d'alerte et le bouchon du fond d'écran
   lisaient AUSSI `--vm-panel-w` : colonne repliée, l'alerte du coin aurait
   eu 18 px de large, et la barre de ressources aurait changé de retour à la
   ligne — donc de hauteur, donc décalé tout l'écran à chaque clic. Ils
   lisent ici `--vmp-coin`, l'ancienne largeur.
   ⚠️ BUREAU SEULEMENT. Au téléphone, la colonne est un tiroir qui ne pousse
   rien : la largeur et le repli n'y changent pas, et le bouton y redevient
   un simple titre. */
body.f-colonneVmRepliable .vmp-replier{all:unset;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 6px;cursor:pointer;font-family:var(--hud);font-size:11px;line-height:1.2;letter-spacing:.06em;text-transform:uppercase;text-align:center;color:var(--text3)}
body.f-colonneVmRepliable .vmp-replier:hover,body.f-colonneVmRepliable .vmp-replier:focus-visible{color:var(--text);background:rgba(255,255,255,.04)}
body.f-colonneVmRepliable .vmp-replier:focus-visible{outline:1px solid var(--accent);outline-offset:-2px}
body.f-colonneVmRepliable .vmp-replier-chev{font-size:17px;line-height:1;color:var(--accent)}
@media (max-width: 700px) {
  body.f-colonneVmRepliable .vmp-replier{pointer-events:none}
  body.f-colonneVmRepliable .vmp-replier-chev{display:none}
}
body.f-colonneVmRepliable.mobile-mode .vmp-replier{pointer-events:none}
body.f-colonneVmRepliable.mobile-mode .vmp-replier-chev{display:none}
/* Interrupteur vmReplieInitiales : les initiales n'existent qu'au bureau,
   colonne repliée (bloc `min-width: 701px` ci-dessous). */
body.f-colonneVmRepliable.f-vmReplieInitiales .vmp-replier-initiales{display:none}
@media (min-width: 701px) {
  body.f-colonneVmRepliable:not(.mobile-mode){--vm-panel-w:124px;--vmp-coin:180px}
  body.f-colonneVmRepliable.vmp-replie:not(.mobile-mode){--vm-panel-w:34px}
  body.f-colonneVmRepliable:not(.mobile-mode) #topbar,body.f-colonneVmRepliable:not(.mobile-mode) #resbar{right:var(--vmp-coin) !important}
  body.f-colonneVmRepliable:not(.mobile-mode) #aucune-alerte{width:calc(var(--vmp-coin) - 16px)}
  body.f-colonneVmRepliable.f-alerteCoinTaille:not(.mobile-mode) #prod-hint{width:calc(var(--vmp-coin) - 16px);max-width:calc(var(--vmp-coin) - 16px)}
  body.f-colonneVmRepliable.dsh-fond:not(.mobile-mode)::before{width:var(--vmp-coin)}
  body.f-colonneVmRepliable:not(.mobile-mode) .vmp-header{padding:0}
  /* Dépliée : la miniature prend la largeur, le nom passe sur deux lignes
     plutôt que d'être coupé (« Vaisseau Mère » / « #1 »). */
  body.f-colonneVmRepliable:not(.mobile-mode) .vmp-list{padding:6px 4px;gap:4px}
  body.f-colonneVmRepliable:not(.mobile-mode) .vmp-pill{padding:6px 4px 5px}
  body.f-colonneVmRepliable:not(.mobile-mode) .vmp-name{font-size:11px;line-height:1.25;white-space:normal;overflow-wrap:break-word;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
  /* « Coulissante de haut en bas » : au dépliage, la liste se déroule
     depuis l'en-tête. */
  body.f-colonneVmRepliable:not(.vmp-replie):not(.mobile-mode) .vmp-list{animation:vmp-derouler .28s ease-out}
  @keyframes vmp-derouler{from{clip-path:inset(0 0 100% 0)}to{clip-path:inset(0 0 0 0)}}
  /* Repliée : le titre se lit de bas en haut sous la flèche, chaque
     vaisseau n'est plus qu'une pastille à sa couleur — toujours cliquable,
     son nom en infobulle. */
  body.f-colonneVmRepliable.vmp-replie:not(.mobile-mode) #vm-panel-right{overflow-x:hidden}
  body.f-colonneVmRepliable.vmp-replie:not(.mobile-mode) .vmp-replier{flex-direction:column;gap:8px;padding:10px 0}
  body.f-colonneVmRepliable.vmp-replie:not(.mobile-mode) .vmp-replier-txt{writing-mode:vertical-rl;transform:rotate(180deg);letter-spacing:.12em}
  /* Interrupteur vmReplieInitiales : « VM » à l'horizontale, sous la flèche,
     au lieu du titre couché. Seulement ICI, au bureau et colonne repliée :
     partout ailleurs `.vmp-replier-initiales` reste caché (règle hors de ce
     bloc) et le titre entier reste. 14 px tiennent dans la baguette de
     34 px (mesuré au navigateur à 1 440 px : « VM » y prend 28 px). */
  body.f-colonneVmRepliable.f-vmReplieInitiales.vmp-replie:not(.mobile-mode) .vmp-replier-txt{display:none}
  body.f-colonneVmRepliable.f-vmReplieInitiales.vmp-replie:not(.mobile-mode) .vmp-replier-initiales{display:block;letter-spacing:.04em;font-size:14px;font-weight:700}
  body.f-colonneVmRepliable.vmp-replie:not(.mobile-mode) .vmp-list{padding:10px 0;gap:10px;align-items:center}
  body.f-colonneVmRepliable.vmp-replie:not(.mobile-mode) .vmp-pill{padding:5px;border-radius:50%;background:none}
  body.f-colonneVmRepliable.vmp-replie:not(.mobile-mode) .vmp-pill > *{display:none}
  body.f-colonneVmRepliable.vmp-replie:not(.mobile-mode) .vmp-pill::before{content:'';display:block;width:14px;height:14px;border-radius:50%;background:var(--vmc,var(--gold))}
  body.f-colonneVmRepliable.vmp-replie:not(.mobile-mode) .vmp-pill.active::before{box-shadow:0 0 0 2px rgba(5,8,16,.9),0 0 0 3.5px var(--vmc,var(--gold)),0 0 10px var(--vmc,var(--gold))}
}
@media (min-width: 701px) and (prefers-reduced-motion: reduce) {
  body.f-colonneVmRepliable .vmp-list{animation:none !important}
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur amiralReserveFusionnee — LA RÉSERVE ET LE BARÈME EN UN BLOC
   ════════════════════════════════════════════════════════════════════
   Loryan : « Fusionne les deux, mets une barre de remplissage pour indiquer
   les compétences prises et restantes ». La barre est graduée (un trait par
   point, `--am-n` = le nombre de points) ; un segment par vaisseau, à la
   couleur de sa classe (`--am-c`), le reste en creux. Ces classes ne
   s'écrivent que sous l'interrupteur (amiral.js). Plancher : 11 px. */
body.f-amiralReserveFusionnee .am-barre{position:relative;display:flex;height:16px;margin-top:16px;border-radius:var(--radius-pill);overflow:hidden;background:rgba(255,255,255,.07);box-shadow:inset 0 0 0 1px rgba(255,255,255,.08)}
body.f-amiralReserveFusionnee .am-barre::after{content:'';position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(to right,transparent 0 calc(100% / var(--am-n) - 1px),rgba(5,8,16,.75) calc(100% / var(--am-n) - 1px) calc(100% / var(--am-n)))}
body.f-amiralReserveFusionnee .am-seg{display:block;height:100%;flex:none;background:var(--am-c);box-shadow:inset 0 0 0 1px rgba(255,255,255,.18)}
body.f-amiralReserveFusionnee .am-seg.is-chantier{background:repeating-linear-gradient(135deg,rgba(148,163,184,.55) 0 5px,rgba(148,163,184,.2) 5px 10px)}
body.f-amiralReserveFusionnee .amiral-reserve.is-depasse .am-barre{box-shadow:inset 0 0 0 1px #f59e0b}
body.f-amiralReserveFusionnee .am-barre-leg{display:flex;justify-content:space-between;gap:12px;margin-top:5px;font-family:var(--font-mono);font-size:12px;color:var(--color-text-secondary)}
body.f-amiralReserveFusionnee .am-barre-leg span:last-child{color:#86efac;text-align:right}
body.f-amiralReserveFusionnee .amiral-reserve.is-depasse .am-barre-leg span:last-child{color:#fbbf24}
body.f-amiralReserveFusionnee .am-pastille{width:10px;height:10px;border-radius:3px;flex:none;align-self:center;background:var(--am-c);box-shadow:0 0 0 1px rgba(255,255,255,.22)}
body.f-amiralReserveFusionnee .am-pastille.is-chantier{background:repeating-linear-gradient(135deg,rgba(148,163,184,.7) 0 3px,transparent 3px 6px)}
body.f-amiralReserveFusionnee .am-sous{font-family:var(--font-mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-secondary);margin:20px 0 4px;font-weight:600;padding-top:16px;border-top:1px solid rgba(255,255,255,.08)}
body.f-amiralReserveFusionnee .am-bareme{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:6px}
body.f-amiralReserveFusionnee .am-bareme li{display:grid;grid-template-columns:1fr auto;align-items:baseline;gap:2px 10px;font-size:13px;padding:8px 12px 8px 14px;border-radius:10px;border:1px solid var(--color-border-subtle);background:rgba(8,12,24,.45);box-shadow:inset 3px 0 0 var(--am-c)}
body.f-amiralReserveFusionnee .am-bareme b{font-family:var(--font-mono);font-size:12.5px;color:#7dd3fc;white-space:nowrap}
body.f-amiralReserveFusionnee .am-etat{grid-column:1 / -1;font-size:12px;color:var(--color-text-secondary)}
body.f-amiralReserveFusionnee .am-etat.is-ok{color:#86efac}
body.f-amiralReserveFusionnee .am-etat.is-manque{color:#fbbf24}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur railReorganise — LE RAIL SANS RECHERCHE, DANS L'ORDRE DE LORYAN
   ════════════════════════════════════════════════════════════════════
   Carte, Amiral, Empire, Vaisseau Mère, puis le reste dans son ordre ; plus
   d'onglet Recherche. realm.js déplace le bouton du vaisseau et masque celui
   de la Recherche, mais seulement à l'arrivée des réglages : ces règles
   tiennent l'écran dès le premier affichage (la classe du corps est rejouée
   avant, depuis le cache du navigateur), sinon le rail sauterait. */
body.f-railReorganise #dsh-tabs-main > .dsh-tab{order:10}
body.f-railReorganise #dsh-tabs-main > #btn-map{order:1}
body.f-railReorganise #dsh-tabs-main > #btn-amiral{order:2}
body.f-railReorganise #dsh-tabs-main > #btn-command{order:3}
body.f-railReorganise #dsh-tabs-main > #btn-ship{order:4}
body.f-railReorganise #dsh-tabs-main > #btn-research{display:none !important}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur classementSucces — CLASSEMENTS ET SUCCÈS, UN SEUL ONGLET
   ════════════════════════════════════════════════════════════════════
   Le bouton des Succès quitte le rail (realm.js, et ici dès le premier
   affichage) ; les deux écrans portent en tête la barre « Classements ·
   Succès », née masquée dans la page. Plancher de lecture : 11 px. */
body.f-classementSucces #dsh-tabs-main > #btn-succes{display:none !important}
body.f-classementSucces .cs-onglets[hidden]{display:flex}
/* ⚠️ COLLÉE SOUS L'EN-TÊTE (sticky) : sur le téléphone de Loryan, l'écran
   s'ouvrait un peu défilé et la barre passait SOUS le haut fixe — on n'en
   voyait que le bord (« je ne vois pas les succès, regarde la page
   classement »). Collée, elle reste visible quel que soit le défilement ; le
   fond devient opaque pour que le tableau ne transparaisse pas dessous.
   ⚠️ `top: 4px` ET PAS LA HAUTEUR DE L'EN-TÊTE : c'est la vue elle-même qui
   défile, et la position collante se compte SOUS sa marge du haut — marge
   qui réserve déjà la place de l'en-tête (mesuré : avec la hauteur de
   l'en-tête, la barre se collait cent pixels trop bas). */
body.f-classementSucces .cs-onglets{position:sticky;top:4px;z-index:20;gap:6px;margin:0 0 14px;padding:5px;border-radius:14px;border:1px solid var(--color-border-subtle,rgba(255,255,255,.1));background:rgba(8,12,24,.94);box-shadow:0 6px 18px rgba(0,0,0,.45);width:100%;max-width:640px}
/* Après la fusion, « je ne vois plus les succès » : la barre prend la largeur
   et deux grands boutons — impossible à manquer en tête de l'écran. */
body.f-classementSucces .cs-onglet{flex:1;font:inherit;font-size:16px;font-weight:700;padding:12px 18px;border-radius:10px;border:1px solid transparent;background:none;color:var(--color-text-secondary,#94a3b8);cursor:pointer;white-space:nowrap}
body.f-classementSucces .cs-onglet:hover{color:var(--text,#e2e8f0);background:rgba(255,255,255,.05)}
body.f-classementSucces .cs-onglet.is-on{color:#0b1120;background:var(--accent,#22d3ee);border-color:var(--accent,#22d3ee);cursor:default}
body.f-classementSucces .cs-onglet:focus-visible{outline:2px solid var(--accent,#22d3ee);outline-offset:2px}
body.f-classementSucces .cs-compte{font-family:var(--font-mono);font-size:12.5px;font-weight:600;margin-left:6px;padding:2px 8px;border-radius:999px;background:rgba(255,255,255,.08)}
body.f-classementSucces .cs-compte:empty{display:none}
body.f-classementSucces .cs-onglet.is-on .cs-compte{background:rgba(11,17,32,.18)}
/* L'aperçu : une carte cliquable sous la barre, sur l'écran des Classements. */
body.f-classementSucces .cs-apercu[hidden]{display:none}
body.f-classementSucces .cs-apercu{display:flex;align-items:center;gap:14px;width:100%;max-width:640px;margin:0 0 18px;padding:12px 16px;font:inherit;text-align:left;color:inherit;cursor:pointer;border-radius:14px;border:1px solid rgba(245,158,11,.45);background:linear-gradient(135deg,rgba(245,158,11,.14),rgba(8,12,24,.55))}
body.f-classementSucces .cs-apercu:hover{border-color:rgba(245,158,11,.8)}
body.f-classementSucces .cs-apercu:focus-visible{outline:2px solid #f59e0b;outline-offset:2px}
body.f-classementSucces .cs-apercu-ico{font-size:28px;line-height:1}
body.f-classementSucces .cs-apercu-txt{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
body.f-classementSucces .cs-apercu-txt b{font-size:15px}
body.f-classementSucces .cs-apercu-chiffres{font-size:13px;color:var(--color-text-secondary,#94a3b8)}
/* Interrupteur succesRetires : plus de Succès — la barre « Classements ·
   Succès » et l'aperçu « Tes succès » disparaissent, le Classement reste
   seul dans son onglet. */
body.f-succesRetires .cs-onglets,body.f-succesRetires .cs-apercu{display:none!important}
body.f-classementSucces .cs-apercu-voir{font-size:13px;font-weight:700;color:#fbbf24;white-space:nowrap}
@media (max-width: 700px) {
  body.f-classementSucces .cs-onglets{width:auto;display:flex}
  body.f-classementSucces .cs-onglet{flex:1;padding:10px 8px;font-size:15px}
  body.f-classementSucces .cs-apercu{flex-wrap:wrap}
  body.f-classementSucces .cs-apercu-voir{width:100%;text-align:right}
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur classesRefondues — LES MODULES DE CLASSE
   ════════════════════════════════════════════════════════════════════
   Cinq blocs balisés : le module de classe (commun, la fondation), puis un
   par classe. ⚠️ CHAQUE CLASSE N'ÉCRIT QUE SOUS SA BALISE, et toute règle est
   préfixée `body.f-classesRefondues` (y compris dans les @media). Les blocs
   restent séparés par des lignes qui ne bougent pas, pour que des chantiers
   menés en parallèle ne se heurtent pas. Plancher de lecture : 11 px. */

/* ── classesRefondues : module de classe ── */
/* La couleur de la classe arrive en variable `--mc`, posée par
   features/classes/module-classe.js depuis VM_TYPES_CLIENT (que la Raie aligne
   sur ses teintes) : une seule source pour l'entrée de barre et la section. */
body.f-classesRefondues .sn-item.sn-classe{color:var(--mc);font-weight:600}
body.f-classesRefondues .sn-item.sn-classe:hover{color:var(--mc);border-left-color:color-mix(in srgb,var(--mc) 55%,transparent)}
body.f-classesRefondues .sn-item.sn-classe.active{color:var(--mc);border-left-color:var(--mc);
  background:color-mix(in srgb,var(--mc) 12%,transparent)}
body.f-classesRefondues .sn-sub .sn-item.sn-classe::before{border-top-color:color-mix(in srgb,var(--mc) 60%,transparent)}
body.f-classesRefondues .sec-classe .pg-title{color:var(--mc)}
/* La phrase du module, au plancher de lecture (mesurée à 10 px en large et
   9,2 px au téléphone avec la règle commune des en-têtes de module). */
body.f-classesRefondues #ship-main .sec-classe .pg-sub{font-size:12px}
body.f-classesRefondues #ship-main .sec-classe .module-col-hdr{font-size:11px}
body.f-classesRefondues .sec-classe .module-col-hdr{border-bottom-color:color-mix(in srgb,var(--mc) 45%,transparent)}
body.f-classesRefondues .sec-classe .mc-panneau{margin-top:1rem;padding:.85rem 1rem;border-radius:10px;
  border:1px solid color-mix(in srgb,var(--mc) 35%,transparent);
  background:color-mix(in srgb,var(--mc) 6%,transparent);font-size:13px}
body.f-classesRefondues .sec-classe .mc-panneau.is-vide{display:none}
@media (max-width: 700px) {
  body.f-classesRefondues .sec-classe .mc-panneau{padding:.75rem .8rem}
}
/* ── fin : module de classe ── */


/* ── classesRefondues : Économique ── */
/* Le panneau du Module d'Intendance (liaisons d'approvisionnement). */
/* La couleur de la classe arrive en `--mc` (posée sur la section par
   module-classe.js, et sur le récapitulatif de l'Empire par intendance.js).
   Plancher de lecture 11 px : rien de ce bloc ne descend en dessous. */
body.f-classesRefondues .int-panneau{display:flex;flex-direction:column;gap:.7rem;min-width:0}
body.f-classesRefondues .int-titre{margin:0;font-size:15px;font-weight:700;color:var(--mc)}
body.f-classesRefondues .int-reseau{font-size:13px;margin-top:.25rem;color:var(--text,#e2e8f0)}
body.f-classesRefondues .int-sous{font-size:12px;color:var(--muted,#94a3b8)}
body.f-classesRefondues .int-phrase{margin:.4rem 0 0;font-size:13px;line-height:1.45;color:var(--muted,#cbd5e1)}
body.f-classesRefondues .int-carte{display:flex;flex-direction:column;gap:.55rem;padding:.7rem .8rem;border-radius:8px;min-width:0;
  border:1px solid color-mix(in srgb,var(--mc) 30%,transparent);background:rgba(15,23,42,.45)}
body.f-classesRefondues .int-carte-tete{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:14px}
body.f-classesRefondues .int-etat{font-size:12px;font-weight:700;padding:1px 8px;border-radius:999px;white-space:nowrap}
body.f-classesRefondues .int-etat.est-actif{color:#4ade80;background:rgba(74,222,128,.12);border:1px solid rgba(74,222,128,.35)}
body.f-classesRefondues .int-etat.est-pause{color:#fbbf24;background:rgba(251,191,36,.12);border:1px solid rgba(251,191,36,.35)}
body.f-classesRefondues .int-debits{display:flex;flex-wrap:wrap;gap:.35rem .6rem}
body.f-classesRefondues .int-debit{font-size:13px;font-variant-numeric:tabular-nums;white-space:nowrap}
body.f-classesRefondues .int-debit.est-plein{color:#fbbf24}
body.f-classesRefondues .int-note{font-size:13px;color:var(--muted,#94a3b8);line-height:1.4}
body.f-classesRefondues .int-cases{display:flex;flex-wrap:wrap;gap:.35rem .9rem}
body.f-classesRefondues .int-case{display:inline-flex;align-items:center;gap:.3rem;font-size:13px;cursor:pointer;white-space:nowrap}
body.f-classesRefondues .int-champ{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:13px}
body.f-classesRefondues .int-champ select{min-width:0;max-width:100%;font-size:13px;padding:4px 6px;border-radius:6px;
  background:rgba(15,23,42,.8);color:inherit;border:1px solid color-mix(in srgb,var(--mc) 35%,transparent)}
body.f-classesRefondues .int-actions{display:flex;flex-wrap:wrap;gap:.5rem}
body.f-classesRefondues .int-btn{font-size:13px;padding:5px 12px;border-radius:6px;cursor:pointer;color:inherit;
  background:rgba(30,41,59,.8);border:1px solid color-mix(in srgb,var(--mc) 40%,transparent)}
body.f-classesRefondues .int-btn.est-fort{background:color-mix(in srgb,var(--mc) 30%,transparent);font-weight:700}
body.f-classesRefondues .int-btn.est-couper{border-color:rgba(248,113,113,.45);color:#fca5a5}
body.f-classesRefondues .int-btn[disabled]{opacity:.5;cursor:not-allowed}
/* Personne ne peut recevoir : la liste des candidats, chacun avec son refus. */
body.f-classesRefondues .int-cands{margin:0;padding:0 0 0 1.1rem;display:flex;flex-direction:column;gap:.2rem;font-size:13px;
  line-height:1.4;color:var(--muted,#cbd5e1);overflow-wrap:anywhere}
/* Le récapitulatif de l'onglet Empire (fin de la Passerelle). */
body.f-classesRefondues .int-empire{margin-top:1rem;padding:.8rem 1rem;border-radius:10px;display:flex;flex-direction:column;gap:.5rem;
  border:1px solid color-mix(in srgb,var(--mc) 35%,transparent);background:color-mix(in srgb,var(--mc) 6%,transparent)}
body.f-classesRefondues .int-empire-ligne{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem 1rem;padding:.45rem 0;
  border-top:1px solid rgba(148,163,184,.15)}
body.f-classesRefondues .int-empire-qui{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;font-size:14px;min-width:0}
body.f-classesRefondues .int-empire-quoi{display:flex;flex-wrap:wrap;align-items:center;gap:.3rem .7rem;min-width:0}
@media (max-width: 700px) {
  body.f-classesRefondues .int-carte{padding:.6rem .65rem}
  body.f-classesRefondues .int-champ select{flex:1 1 100%}
  /* Le dernier bloc de la Passerelle : il garde de quoi passer au-dessus du
     bouton flottant de la Rampe (mesuré au téléphone, 390 px). */
  body.f-classesRefondues .int-empire{padding:.7rem .75rem;margin-bottom:5rem}
}
/* ── fin : Économique ── */


/* ── classesRefondues : Exploration ── */
/* Le panneau du Module de Saut (Balises de Saut). La couleur de la classe
   arrive par `--mc` (posée sur la section par module-classe.js). Plancher de
   lecture : 11 px ; le texte courant est à 13 px, comme le conteneur. */
body.f-classesRefondues .ms-panneau{display:flex;flex-direction:column;gap:.6rem}
body.f-classesRefondues .ms-entete{display:flex;align-items:baseline;gap:.75rem;flex-wrap:wrap}
body.f-classesRefondues .ms-titre{margin:0;font-size:15px;color:var(--mc)}
body.f-classesRefondues .ms-compte{font-size:13px;font-weight:700;padding:.1rem .55rem;border-radius:999px;
  background:color-mix(in srgb,var(--mc) 18%,transparent);color:var(--mc)}
body.f-classesRefondues .ms-regle,
body.f-classesRefondues .ms-note{margin:0;font-size:13px;line-height:1.45;color:var(--text-muted,#9aa4b2)}
body.f-classesRefondues .ms-lieu{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem 1rem;margin:0;font-size:13px}
body.f-classesRefondues .ms-delai{font-size:12px;color:var(--text-muted,#9aa4b2)}
body.f-classesRefondues .ms-aide{font-size:12px;color:var(--text-muted,#9aa4b2);margin:0}
body.f-classesRefondues .ms-retour{margin:0;font-size:13px;padding:.4rem .6rem;border-radius:6px}
body.f-classesRefondues .ms-retour--ok{background:rgba(34,197,94,.12);color:#86efac}
body.f-classesRefondues .ms-retour--ko{background:rgba(239,68,68,.12);color:#fca5a5}
body.f-classesRefondues .ms-liste{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.5rem}
body.f-classesRefondues .ms-balise{padding:.55rem .7rem;border-radius:8px;display:flex;flex-direction:column;gap:.4rem;
  border:1px solid color-mix(in srgb,var(--mc) 25%,transparent);background:rgba(0,0,0,.18)}
body.f-classesRefondues .ms-balise--ouverte{border-color:color-mix(in srgb,var(--mc) 60%,transparent)}
body.f-classesRefondues .ms-balise-tete{display:flex;flex-wrap:wrap;align-items:baseline;gap:.3rem .6rem;font-size:14px}
body.f-classesRefondues .ms-gal{font-size:12px;color:var(--text-muted,#9aa4b2)}
body.f-classesRefondues .ms-etat{font-size:12px;font-weight:600;padding:.05rem .5rem;border-radius:999px}
body.f-classesRefondues .ms-etat--prete{background:rgba(34,197,94,.14);color:#86efac}
body.f-classesRefondues .ms-etat--recharge{background:rgba(234,179,8,.14);color:#fde68a}
body.f-classesRefondues .ms-etat--ouvert{background:color-mix(in srgb,var(--mc) 22%,transparent);color:var(--mc)}
body.f-classesRefondues .ms-trou{font-size:13px;overflow-wrap:anywhere}
body.f-classesRefondues .ms-faits{display:flex;flex-wrap:wrap;gap:.35rem 1.4rem;margin:0}
body.f-classesRefondues .ms-faits div{display:flex;flex-direction:column}
body.f-classesRefondues .ms-faits dt{font-size:11px;color:var(--text-muted,#9aa4b2)}
body.f-classesRefondues .ms-faits dd{margin:0;font-size:14px;font-weight:600}
body.f-classesRefondues .ms-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem .75rem}
body.f-classesRefondues .ms-choix{display:flex;align-items:center;gap:.4rem;font-size:13px}
body.f-classesRefondues .ms-select{font-size:13px;min-width:9rem;padding:.35rem .6rem;border-radius:5px;
  background:rgba(0,0,0,.35);border:1px solid var(--border);color:var(--text)}
body.f-classesRefondues .ms-btn{font-size:13px}
/* La fiche d'un trou PRIVÉ, sur la carte (features/map/trous-de-ver.js). */
body.f-classesRefondues .tdv-prive{color:#bae6fd}
@media (max-width: 700px) {
  body.f-classesRefondues .ms-actions{flex-direction:column;align-items:stretch}
  body.f-classesRefondues .ms-choix{justify-content:space-between}
  body.f-classesRefondues .ms-select{flex:1;min-width:0}
}
/* ── fin : Exploration ── */


/* ── classesRefondues : Chasseur ── */
/* Le panneau du Module d'Assaut (raids intra-système) — features/classes/assaut.js.
   Deux tuiles (Baie, Doctrine), la jauge des places, la liste des raids en vol.
   ⚠️ Plancher de lecture 11 px : rien ici sous 12 px. La couleur vient de `--mc`
   (posée par module-classe.js sur la section). */
body.f-classesRefondues .as-panneau{display:flex;flex-direction:column;gap:.75rem}
body.f-classesRefondues .as-tuiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.6rem}
body.f-classesRefondues .as-tuile{display:flex;flex-direction:column;gap:.2rem;min-width:0;padding:.6rem .75rem;
  border-radius:8px;border:1px solid color-mix(in srgb,var(--mc) 30%,transparent);
  background:color-mix(in srgb,var(--mc) 9%,transparent)}
body.f-classesRefondues .as-tuile-lbl{font-size:12px;color:var(--text2)}
body.f-classesRefondues .as-tuile-val{font-size:16px;color:var(--mc)}
body.f-classesRefondues .as-tuile-sous{font-size:12px;color:var(--text2);line-height:1.35}
/* Deux éléments qui ne se replient jamais : l'intitulé et le compte. */
body.f-classesRefondues .as-places-tete{display:flex;justify-content:space-between;align-items:baseline;gap:.5rem;font-size:13px}
body.f-classesRefondues .as-places-tete b{color:var(--mc);font-size:15px;white-space:nowrap}
body.f-classesRefondues .as-places-jauge{display:flex;gap:6px;margin-top:.35rem}
body.f-classesRefondues .as-place{flex:1;height:10px;border-radius:5px;
  border:1px solid color-mix(in srgb,var(--mc) 55%,transparent)}
body.f-classesRefondues .as-place.est-prise{background:var(--mc)}
body.f-classesRefondues .as-raids{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.4rem}
body.f-classesRefondues .as-raid{display:flex;flex-wrap:wrap;gap:.25rem .75rem;padding:.45rem .6rem;border-radius:6px;
  background:color-mix(in srgb,var(--mc) 5%,transparent);font-size:13px}
body.f-classesRefondues .as-raid.est-retour{opacity:.8}
body.f-classesRefondues .as-raid-qui{flex:1 1 220px;min-width:0;overflow-wrap:anywhere}
body.f-classesRefondues .as-raid-ou{font-size:12px;color:var(--text3);margin-left:.3rem;font-family:var(--mono)}
body.f-classesRefondues .as-raid-etat{font-size:12px;color:var(--text2)}
body.f-classesRefondues .as-raid-renfort{color:var(--mc)}
body.f-classesRefondues .as-vide{font-size:13px;color:var(--text2)}
body.f-classesRefondues .as-note{font-size:12px;color:var(--text2);line-height:1.4}
/* Le détail par flotte d'un raid rejoint, dans le rapport du Centre
   d'Information (centre.js) : la table commune y tombait à 10,4 px. */
body.f-classesRefondues .ci-raid-groupe th,
body.f-classesRefondues .ci-raid-groupe td{font-size:12px}
/* La carte de contact d'un raid à rejoindre, dans le Viseur de la Rampe. */
body.f-classesRefondues .rpv-contact.est-renfort{border-style:dashed}
/* Les trois textes du ralliement dans le Viseur, remontés au plancher de
   lecture : la note de règle (.rpv-hint tombait à 9,5 px), l'étiquette de la
   carte (8,1 px — capitales espacées, d'où l'espacement réduit pour que le
   nom du lanceur ne se coupe pas — et le retrait vertical rogné, pour que la
   carte garde sa hauteur) et la ligne « Arrive avec » du devis (10,4 px, sa
   ligne prend la hauteur de celle du Noctium). Rien n'est tronqué. */
body.f-classesRefondues .rpv-hint.est-renfort{font-size:12px}
/* Les notes du raid intra-système et du raid orbital écrites par la refonte
   (`est-classe`, posée par rampe-viseur-vue.js quand la note vient de
   features/classes/assaut.js) : même taille que celle du renfort — elles
   tombaient à 9,5 px. Les notes d'avant (espionnage) gardent la leur. */
body.f-classesRefondues .rpv-hint.est-classe{font-size:12px}
body.f-classesRefondues .rpv-contact.est-renfort .rpv-tag{font-size:11px;letter-spacing:.04em;padding-top:.5px;padding-bottom:.5px}
body.f-classesRefondues .rpv-recu .l.est-renfort{font-size:12px}
@media (max-width: 700px) {
  body.f-classesRefondues .as-tuiles{grid-template-columns:1fr}
  body.f-classesRefondues .as-tuile-val{font-size:15px}
}
/* ── fin : Chasseur ── */


/* ── classesRefondues : Chercheur ── */
/* Le panneau du Module d'Analyse (Ligne Parallèle). La couleur de la classe
   arrive en `--mc` (posée sur la section par module-classe.js). Plancher de
   lecture : 11 px partout, 12-13 px pour ce qui se lit d'abord. */
body.f-classesRefondues .an-panneau{display:flex;flex-direction:column;gap:.6rem}
body.f-classesRefondues .an-entete{display:flex;flex-wrap:wrap;align-items:baseline;gap:.4rem .9rem}
body.f-classesRefondues .an-titre{font-size:14px;font-weight:700;color:var(--mc,#c4b5fd)}
body.f-classesRefondues .an-vitesse,
body.f-classesRefondues .an-compte{font-size:12px;color:#cbd5e1}
body.f-classesRefondues .an-compte{margin-left:auto}
body.f-classesRefondues .an-note{margin:0;font-size:13px;line-height:1.45;color:#cbd5e1}
body.f-classesRefondues .an-sous{font-size:12px;font-weight:600;color:#94a3b8;margin-top:.2rem}
body.f-classesRefondues .an-ligne{display:flex;flex-direction:column;gap:.35rem;padding:.55rem .7rem;border-radius:8px;
  background:rgba(15,23,42,.45);border:1px solid color-mix(in srgb,var(--mc,#a78bfa) 25%,transparent)}
body.f-classesRefondues .an-ligne-ici{border-color:color-mix(in srgb,var(--mc,#a78bfa) 55%,transparent)}
body.f-classesRefondues .an-ligne-tete,
body.f-classesRefondues .an-ligne-pied{display:flex;flex-wrap:wrap;align-items:center;gap:.3rem .8rem;
  font-size:12px;color:#cbd5e1;min-width:0}
body.f-classesRefondues .an-ligne-nom{font-size:13px;color:#e2e8f0;overflow-wrap:anywhere}
body.f-classesRefondues .an-ligne-nom b{color:var(--mc,#c4b5fd)}
body.f-classesRefondues .an-porteur{font-size:12px;color:#94a3b8}
body.f-classesRefondues .an-barre{height:6px;border-radius:3px;background:rgba(148,163,184,.18);overflow:hidden}
body.f-classesRefondues .an-barre span{display:block;height:100%;background:var(--mc,#a78bfa)}
body.f-classesRefondues .an-gelee{color:#fbbf24}
body.f-classesRefondues .an-annuler{margin-left:auto;font-size:12px;padding:.2rem .65rem;border-radius:6px;cursor:pointer;
  background:transparent;color:#fca5a5;border:1px solid rgba(248,113,113,.45)}
body.f-classesRefondues .an-annuler:hover{background:rgba(248,113,113,.12)}
/* Au téléphone, la cible sous le doigt : 22 px de haut ne suffisaient pas
   (mesuré à 390 px). Le desktop garde son bouton discret. */
body.f-classesRefondues.mobile-mode .an-annuler{min-height:34px;padding:.35rem .9rem;font-size:13px}
/* Sur la fiche d'une recherche : le choix de la ligne, sous le bouton de la
   file principale. Deux lignes de texte dans le bouton (le choix, puis la
   vitesse et qui paie) : il s'allonge en hauteur, jamais en largeur. */
body.f-classesRefondues .an-choix{display:flex;flex-direction:column;gap:.4rem;margin-top:.6rem}
/* Le titre du choix reprend `md-sec` (7,7 px mesurés en large) : remonté au
   plancher de lecture, puisqu'il porte la seule explication du bouton. */
body.f-classesRefondues .an-choix .md-sec{font-size:11px}
body.f-classesRefondues .an-lancer{display:flex;flex-direction:column;align-items:center;gap:.15rem;
  white-space:normal;height:auto;line-height:1.25}
body.f-classesRefondues .an-lancer-txt{font-size:12px;overflow-wrap:anywhere}
body.f-classesRefondues .an-lancer-sous{font-size:11px;font-weight:400;opacity:.85;text-transform:none;letter-spacing:0}
/* ── fin : Chercheur ── */


/* ── classesRefondues : Retraits ── */
/* La classe par sa MÉCANIQUE (features/vm/classes.js) : la fiche du tiroir du
   Hangar et l'encart des quatre classes remplacent la matrice des
   pourcentages. Plancher de lecture 11 px (0,79 rem, racine à 14 px). */
/* ⚠️ LES VALEURS SONT DES PHRASES, PLUS DES CHIFFRES : la règle commune
   `.cls-val { white-space: nowrap }` les ferait sortir du tiroir au
   téléphone. Chaque ligne s'empile — l'intitulé, puis ce qu'il vaut. */
body.f-classesRefondues .cls-meca .cls-domaine-titre{font-size:.82rem;opacity:.9;color:var(--clsc,inherit)}
body.f-classesRefondues .cls-meca .cls-ligne{flex-direction:column;align-items:flex-start;gap:1px;font-size:.82rem;margin-top:4px}
body.f-classesRefondues .cls-meca .cls-val{white-space:normal}
body.f-classesRefondues .cls-meca-phrase,
body.f-classesRefondues .cls-meca-module{font-size:.82rem;line-height:1.45;color:var(--text2)}
body.f-classesRefondues .cls-meca-module{margin-top:6px;color:var(--text3)}
body.f-classesRefondues .cls-meca-vide{font-size:.82rem;line-height:1.45;color:var(--text3);margin-top:6px}
body.f-classesRefondues .cls-meca-sommeil .cls-domaine-titre{color:var(--text3)}
/* L'encart : quatre cartes qui se rangent d'elles-mêmes. La surimpression
   reçoit une LARGEUR (la grille n'en a pas à elle seule sous `max-content`). */
body.f-classesRefondues .clsm-encart .clse-tete{font-size:.8rem;white-space:normal}
body.f-classesRefondues .clsm-encart .clse-corps{width:min(600px,92vw)}
body.f-classesRefondues .clsm-grille{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:.55rem}
body.f-classesRefondues .clsm-carte{border:1px solid color-mix(in srgb,var(--clsc) 40%,transparent);border-left:3px solid var(--clsc);
  border-radius:6px;padding:.5rem .6rem;background:color-mix(in srgb,var(--clsc) 6%,transparent);min-width:0}
body.f-classesRefondues .clsm-nom{font-family:var(--hud);font-size:.86rem;font-weight:600;color:var(--clsc)}
body.f-classesRefondues .clsm-meca{font-size:.86rem;font-weight:600;margin-top:2px}
body.f-classesRefondues .clsm-phrase{font-size:.8rem;line-height:1.45;color:var(--text2);margin-top:2px}
body.f-classesRefondues .clsm-liste{list-style:none;margin:.35rem 0 0;padding:0;font-size:.8rem;line-height:1.45;color:var(--text2)}
body.f-classesRefondues .clsm-liste li{margin-top:2px}
body.f-classesRefondues .clsm-cle{font-family:var(--hud);font-size:.8rem;color:var(--text3);margin-right:.25rem}
body.f-classesRefondues .clsm-emp b{color:#34d399}
body.f-classesRefondues .clsm-encart .clse-note{font-size:.8rem;max-width:none}
@media (max-width: 700px) {
  body.f-classesRefondues .clsm-encart .clse-corps{width:auto}
  body.f-classesRefondues .clsm-grille{grid-template-columns:minmax(0,1fr)}
}
/* La fiche du sans-classe PÉRIMÉ : la matrice d'avant (onze effets) garde ses
   tailles anciennes ailleurs ; ici, sous l'interrupteur, le plancher de 11 px.
   Le titre du tiroir aussi (il chapeaute la fiche de classe). */
body.f-classesRefondues .cls-meca-perime .cls-domaine-titre,
body.f-classesRefondues .cls-meca-perime .cls-ligne{font-size:.82rem}
body.f-classesRefondues .hgr-tiroir .hgr-sous-titre,
body.f-classesRefondues .hgr-tiroir .cls-echeance{font-size:.82rem}
/* Simulateur, menu « Classe de son Vaisseau Mère » : la colonne prend la
   largeur du choix (libellés courts) au lieu des 5,2 rem des champs chiffrés,
   où « classé ou récent » était coupé au milieu d'un mot. Police au plancher
   (le mode mobile la porte à 16 px, en !important, contre le zoom d'iOS). */
body.f-classesRefondues .sim-ligne-tech.sim-ligne-classe{grid-template-columns:minmax(0,1fr) auto 6rem}
body.f-classesRefondues .sim-select-classe{width:auto;max-width:100%;font-size:.8rem}
/* ⚠️ AU TÉLÉPHONE, LE MENU PASSE SOUS SON NOM : à 16 px il fait près de
   200 px, et l'intitulé, serré à côté, se cassait en quatre lignes d'un mot
   (mesuré à 390 px). Empilés, les deux tiennent chacun sur une ligne. */
body.f-classesRefondues.mobile-mode .sim-ligne-tech.sim-ligne-classe{grid-template-columns:minmax(0,1fr);row-gap:4px}
body.f-classesRefondues.mobile-mode .sim-select-classe{width:100%}
@media (max-width: 700px) {
  body.f-classesRefondues .sim-ligne-tech.sim-ligne-classe{grid-template-columns:minmax(0,1fr);row-gap:4px}
  body.f-classesRefondues .sim-select-classe{width:100%}
}
/* Les cartes de métamorphose (features/vm/classes.js) : sous l'interrupteur,
   `.cmd-class-effect` porte la phrase « Mécanique : … · Bâtiment : … ·
   Recherche : … » (`vmTypeEffet`). Elle tombait à 9 px, en gris : plancher de
   lecture et la couleur du texte courant. La carte s'allonge d'une ligne au
   plus ; la grille garde ses quatre colonnes. */
body.f-classesRefondues .cmd-class-effect{font-size:.79rem;line-height:1.4;color:var(--text)}
/* ── fin : Retraits ── */


/* ════════════════════════════════════════════════════════════════════
   INTERRUPTEUR clans — LES ÉLÉMENTS NEUFS DE LA COALITION SIMPLIFIÉE
   ════════════════════════════════════════════════════════════════════
   ⚠️ PLANCHER DE LISIBILITÉ : rien sous --badge-font-size (11 px). Ces
   éléments sont neufs, ils naissent au plancher même quand leurs voisins
   (fiche de planète, tableaux) sont encore en dessous. */

/* Le tag du clan, devant un pseudo (chat, classement, fiche de planète). */
.cj-tag { font-family: ui-monospace, monospace; color: var(--accent2, #0ea5e9);
  font-weight: 700; font-size: max(var(--badge-font-size, 11px), .9em); }
/* Un pseudo qui ouvre la carte du joueur : il le dit au survol. */
.cj-pseudo { cursor: help; text-decoration: underline dotted rgba(141,164,196,.55);
  text-underline-offset: 2px; }

/* La carte du joueur. Elle reçoit la souris : on y entre pour cliquer. */
.cj-carte { position: fixed; z-index: 9000; width: 280px; max-width: calc(100vw - 16px);
  max-height: calc(100vh - 16px); overflow: hidden; overflow-y: auto;
  border: 1px solid rgba(110,130,170,.38); border-radius: 12px; background: #0c1222;
  box-shadow: 0 18px 48px rgba(0,0,0,.66); padding: 10px 12px 12px;
  color: var(--text, #e2e8f0); font-size: .86rem; line-height: 1.4; }
.cj-carte.est-epinglee { outline: 2px solid #fbbf24; outline-offset: -1px; padding-top: 0; }
.cj-carte.est-epinglee .epingle-barre { margin: 0 -12px 8px; }
.cj-attente { color: var(--text2, #8da4c4); padding: 4px 0; }
.cj-tete { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
.cj-nom { font-size: 1rem; overflow-wrap: anywhere; }
.cj-admin { background: #7c3aed; color: #fff; padding: 1px 6px; border-radius: 3px;
  font-size: var(--badge-font-size, 11px); }
.cj-bloc { border-top: 1px solid rgba(255,255,255,.07); border-bottom: 1px solid rgba(255,255,255,.07);
  padding: 6px 0; margin-bottom: 8px; }
.cj-ligne { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 2px 0; }
.cj-ligne > span { color: var(--text2, #8da4c4); white-space: nowrap; }
.cj-ligne > b { text-align: right; overflow-wrap: anywhere; font-weight: 600; }
.cj-sourdine { color: var(--text2, #8da4c4); font-weight: 400; }
.cj-gestes { display: flex; flex-wrap: wrap; gap: 6px; }
.cj-geste { flex: 1 1 auto; padding: 5px 9px; border-radius: 7px; cursor: pointer;
  border: 1px solid var(--border2, #324569); background: rgba(255,255,255,.03);
  color: var(--text, #e2e8f0); font-size: .82rem; white-space: nowrap; }
.cj-geste:hover { background: rgba(255,255,255,.08); }
.cj-geste:disabled { opacity: .6; cursor: default; }
.cj-inviter { flex-basis: 100%; border-color: rgba(14,165,233,.55); background: rgba(14,165,233,.12); }
.cj-soi { color: var(--text2, #8da4c4); font-style: italic; }
.cj-lien { display: block; margin-top: 8px; padding: 0; border: none; background: none;
  color: var(--accent2, #0ea5e9); cursor: pointer; font-size: .82rem; text-decoration: underline; }
/* Le bloc « clan » dans la fiche complète (fenêtre du milieu). */
.modal-body .cj-bloc { margin: 4px 0 10px; font-size: .86rem; }

/* La pastille du chat du clan : sur l'onglet du chat, et sur le rail. */
.chat-onglet.a-du-neuf::after { content: ''; display: inline-block; width: 7px; height: 7px;
  margin-left: 6px; border-radius: 50%; background: #f43f5e; vertical-align: middle;
  box-shadow: 0 0 6px rgba(244,63,94,.7); }
.dsh-tab[data-view="alliance"] { position: relative; }
.rail-pastille-clan { position: absolute; top: 6px; right: 6px; width: 9px; height: 9px;
  border-radius: 50%; background: #f43f5e; box-shadow: 0 0 6px rgba(244,63,94,.7);
  pointer-events: none; }
.chat-message-tag { margin-right: 4px; }

/* Le classement des clans : l'effectif, le recrutement, le bouton. */
.lb-col-recrute { white-space: nowrap; text-align: right; }
.lb-clan-etat { font-size: max(var(--badge-font-size, 11px), .85em); color: var(--text2, #8da4c4); }
.lb-clan-etat.est-envoyee { color: #34d399; }
.lb-postuler { white-space: nowrap; }
.lb-clan-desc { font-size: max(var(--badge-font-size, 11px), .82em); color: var(--text2, #8da4c4);
  margin-top: 2px; white-space: normal; overflow-wrap: anywhere; max-width: 36ch; }
/* ⚠️ AU TÉLÉPHONE, CHAQUE CLAN DEVIENT UNE FICHE. Mesuré à 390 px : le
   tableau coupait tout ce qui suit l'effectif, et le bouton « Postuler » —
   la raison d'être de cet écran — n'était atteignable qu'en faisant
   défiler le tableau de côté, ce que rien n'indique. */
@media (max-width: 700px) {
  .lb-table-clans thead { display: none; }
  .lb-table-clans, .lb-table-clans tbody, .lb-table-clans tr, .lb-table-clans td { display: block; width: auto; }
  .lb-table-clans tr { padding: 8px 12px; }
  .lb-table-clans td { border: none; padding: 2px 0; text-align: left; }
  .lb-table-clans td[data-label] { display: flex; justify-content: space-between; gap: 12px; }
  .lb-table-clans td[data-label]::before { content: attr(data-label); color: var(--text2, #8da4c4);
    font-family: var(--body); letter-spacing: 0; text-transform: none; }
  .lb-table-clans .lb-col-recrute { text-align: left; padding-top: 6px; white-space: normal; }
  .lb-table-container:has(.lb-table-clans) { overflow-x: visible; }
}
body.mobile-mode .lb-table-clans thead { display: none; }
body.mobile-mode .lb-table-clans, body.mobile-mode .lb-table-clans tbody,
body.mobile-mode .lb-table-clans tr, body.mobile-mode .lb-table-clans td { display: block; width: auto; }
body.mobile-mode .lb-table-clans tr { padding: 8px 12px; }
body.mobile-mode .lb-table-clans td { border: none; padding: 2px 0; text-align: left; }
body.mobile-mode .lb-table-clans td[data-label] { display: flex; justify-content: space-between; gap: 12px; }
body.mobile-mode .lb-table-clans td[data-label]::before { content: attr(data-label); color: var(--text2, #8da4c4); }
body.mobile-mode .lb-table-clans .lb-col-recrute { text-align: left; padding-top: 6px; white-space: normal; }

/* ════════════════════════════════════════════════════════════════════
   Interrupteur classementTelephone — LE CLASSEMENT DES JOUEURS AU TÉLÉPHONE
   ════════════════════════════════════════════════════════════════════
   Loryan : « corrige classement et vue tel ». Mesuré à 390 px : le tableau
   faisait 551 px de large dans une boîte de 316 — la colonne des points USF
   et les boutons (suivre, écrire, bloquer) n'étaient atteignables qu'en le
   faisant défiler de côté, ce que rien n'indique. Chaque joueur devient une
   fiche sur deux lignes : rang, pseudo et boutons ; puis les deux comptes de
   points, chacun avec son nom (même principe que le classement des clans).
   ⚠️ Les deux voies du mode téléphone : la largeur réelle ET `mobile-mode`. */
@media (max-width: 700px) {
  body.f-classementTelephone .lb-table-joueurs thead { display: none; }
  body.f-classementTelephone .lb-table-joueurs, body.f-classementTelephone .lb-table-joueurs tbody { display: block; width: auto; }
  body.f-classementTelephone .lb-table-joueurs tr { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
    column-gap: 10px; row-gap: 4px; align-items: center; padding: 8px 10px;
    border-bottom: 1px solid var(--border, #1f2a44); }
  body.f-classementTelephone .lb-table-joueurs td { display: block; width: auto; min-width: 0; padding: 0; border: none; }
  body.f-classementTelephone .lb-table-joueurs td.lb-col-rank { grid-column: 1; grid-row: 1; }
  body.f-classementTelephone .lb-table-joueurs td.lb-col-user { grid-column: 2; grid-row: 1; overflow-wrap: anywhere; }
  body.f-classementTelephone .lb-table-joueurs td.lb-col-actions { grid-column: 3; grid-row: 1; white-space: nowrap; }
  body.f-classementTelephone .lb-table-joueurs td.lb-col-points { grid-row: 2; text-align: left; font-size: .95rem; }
  body.f-classementTelephone .lb-table-joueurs td.lb-col-points:nth-of-type(3) { grid-column: 1 / 3; }
  body.f-classementTelephone .lb-table-joueurs td.lb-col-points:nth-of-type(4) { grid-column: 3; text-align: right; }
  body.f-classementTelephone .lb-table-joueurs td.lb-col-points[data-label]::before { content: attr(data-label) " ";
    font-family: inherit; font-weight: 400; font-size: max(var(--badge-font-size, 11px), .8rem); color: var(--text2, #8da4c4); }
  body.f-classementTelephone .lb-table-joueurs .lb-row.is-me td:first-child { border-left: none; }
  body.f-classementTelephone .lb-table-joueurs .lb-row.is-me { box-shadow: inset 3px 0 0 var(--gold, #f59e0b); }
  body.f-classementTelephone .lb-table-container:has(.lb-table-joueurs) { overflow-x: visible; }
}
body.f-classementTelephone.mobile-mode .lb-table-joueurs thead { display: none; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs, body.f-classementTelephone.mobile-mode .lb-table-joueurs tbody { display: block; width: auto; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs tr { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  column-gap: 10px; row-gap: 4px; align-items: center; padding: 8px 10px;
  border-bottom: 1px solid var(--border, #1f2a44); }
body.f-classementTelephone.mobile-mode .lb-table-joueurs td { display: block; width: auto; min-width: 0; padding: 0; border: none; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs td.lb-col-rank { grid-column: 1; grid-row: 1; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs td.lb-col-user { grid-column: 2; grid-row: 1; overflow-wrap: anywhere; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs td.lb-col-actions { grid-column: 3; grid-row: 1; white-space: nowrap; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs td.lb-col-points { grid-row: 2; text-align: left; font-size: .95rem; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs td.lb-col-points:nth-of-type(3) { grid-column: 1 / 3; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs td.lb-col-points:nth-of-type(4) { grid-column: 3; text-align: right; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs td.lb-col-points[data-label]::before { content: attr(data-label) " ";
  font-family: inherit; font-weight: 400; font-size: max(var(--badge-font-size, 11px), .8rem); color: var(--text2, #8da4c4); }
body.f-classementTelephone.mobile-mode .lb-table-joueurs .lb-row.is-me td:first-child { border-left: none; }
body.f-classementTelephone.mobile-mode .lb-table-joueurs .lb-row.is-me { box-shadow: inset 3px 0 0 var(--gold, #f59e0b); }
body.f-classementTelephone.mobile-mode .lb-table-container:has(.lb-table-joueurs) { overflow-x: visible; }

/* La fiche de planète : la flèche dépliante des structures de clan. */
.pp-depliable { display: block; width: 100%; text-align: left; padding: 0; border: none;
  background: none; cursor: pointer; font-size: var(--badge-font-size, 11px); color: var(--text2); }
.pp-depliable:hover { color: var(--text); }
.pp-fleche { display: inline-block; width: 1em; }
.pp-clan { margin: .2rem 0 .4rem; }
.pp-clan-row { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap;
  padding: .28rem 0; border-bottom: 1px solid var(--border); font-size: var(--badge-font-size, 11px); }
.pp-clan-row:last-of-type { border-bottom: none; }
.pp-clan-quoi { color: var(--text); font-weight: 600; }
.pp-clan-qui { color: var(--text2); overflow-wrap: anywhere; }
.pp-clan .pp-occ-note { font-size: var(--badge-font-size, 11px); }
/* Le « max » cliquable sous un champ de versement : il remplit le champ. */
.co-res-max { font-size: var(--badge-font-size, 11px); cursor: pointer; }
.co-res-max:hover { color: var(--text); text-decoration: underline; }

/* Interrupteur clans — LES TABLEAUX DU CLAN S'EMPILENT AU TÉLÉPHONE.
   Mesuré à 390 px : le tableau des membres sortait de 200 px de l'écran
   (les boutons de rang étaient inatteignables sans défiler le tableau), et
   le badge « Fondateur » chevauchait le pseudo. Chaque ligne devient une
   fiche : la tête (le pseudo), puis « libellé : valeur », puis les boutons.
   Les deux systèmes mobiles sont couverts — la largeur ET la classe. */
@media (max-width: 700px) {
  .co-table.cl-pile thead { display: none; }
  .co-table.cl-pile, .co-table.cl-pile tbody, .co-table.cl-pile tr, .co-table.cl-pile td { display: block; width: auto; }
  .co-table.cl-pile tr { padding: 8px 12px; border-bottom: 1px solid var(--border); }
  .co-table.cl-pile td { border: none; padding: 2px 0; }
  .co-table.cl-pile td[data-label] { display: flex; justify-content: space-between; gap: 12px; }
  .co-table.cl-pile td[data-label]::before { content: attr(data-label); color: var(--text2); }
  .co-table.cl-pile .cl-pile-tete { font-weight: 600; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
  .co-table.cl-pile .cl-pile-actions { padding-top: 6px; }
  .co-table.cl-pile .cl-pile-actions .co-actions { flex-wrap: wrap; }
  .co-table-enveloppe:has(.cl-pile) { overflow-x: visible; }
}
body.mobile-mode .co-table.cl-pile thead { display: none; }
body.mobile-mode .co-table.cl-pile, body.mobile-mode .co-table.cl-pile tbody,
body.mobile-mode .co-table.cl-pile tr, body.mobile-mode .co-table.cl-pile td { display: block; width: auto; }
body.mobile-mode .co-table.cl-pile tr { padding: 8px 12px; border-bottom: 1px solid var(--border); }
body.mobile-mode .co-table.cl-pile td { border: none; padding: 2px 0; }
body.mobile-mode .co-table.cl-pile td[data-label] { display: flex; justify-content: space-between; gap: 12px; }
body.mobile-mode .co-table.cl-pile td[data-label]::before { content: attr(data-label); color: var(--text2); }
body.mobile-mode .co-table.cl-pile .cl-pile-tete { font-weight: 600; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
body.mobile-mode .co-table.cl-pile .cl-pile-actions { padding-top: 6px; }


/* ════════════════════════════════════════════════════════════════════
   INTERRUPTEUR `diplomatie` — L'ONGLET DIPLOMATIE
   ════════════════════════════════════════════════════════════════════
   public/features/diplomatie/diplomatie.js. Gazette, Assemblée, Conseil
   des Clans (hémicycle), Relations, Votes du clan. Les couleurs de famille
   des sièges : Général (veto) or, Spécialité bleu, Exploits violet,
   Tirage sarcelle.
   ⚠️ Ces classes n'existent que si l'interrupteur est allumé : éteint,
   l'onglet dessine l'Assemblée d'avant, sans aucun `.dpl-*`. */
.dpl { display: flex; flex-direction: column; gap: 18px; max-width: 1376px; margin: 0 auto; padding: 0 20px 40px; min-width: 0; }
.dpl-entete { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 20px; }
.dpl-entete > div:first-child { flex: 1 1 360px; min-width: 0; }
.dpl-titre { margin: 0; font-family: var(--hud); font-size: 1.9rem; letter-spacing: .04em; color: #e6eefc; }
.dpl-sous { margin: 4px 0 0; color: var(--text2); }
.dpl-puces { display: flex; flex-wrap: wrap; gap: 8px; }
.dpl-puce { padding: 7px 12px; border: 1px solid var(--border2); border-radius: 10px; background: var(--bg2); color: var(--text2); font-size: .93rem; }
.dpl-or { color: #fbbf24; }
.dpl-sarcelle { color: #5eead4; }
.dpl-rouge { color: #fda4af; }
.dpl-gris { color: var(--text2); }
.dpl-mono { font-family: var(--mono); }
.dpl-onglets { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--border); }
.dpl-onglet { background: none; border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px; padding: 11px 16px;
  color: var(--text2); font-family: var(--body); font-size: 1rem; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; }
.dpl-onglet:hover { color: #e6eefc; }
.dpl-onglet.est-actif { color: #fff; border-bottom-color: #2ba39a; }
.dpl-compte { min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px; background: var(--accent1); color: #fff;
  font-size: .82rem; display: inline-flex; align-items: center; justify-content: center; }
.dpl-message { padding: 10px 14px; border-radius: 10px; border: 1px solid var(--border2); }
.dpl-message-ok { border-color: rgba(16, 185, 129, .5); color: #6ee7b7; }
.dpl-message-erreur { border-color: rgba(233, 69, 96, .55); color: #fda4af; }
.dpl-vide { padding: 40px; text-align: center; color: var(--text2); }
.dpl-colonnes { display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-start; }
.dpl-principal { flex: 999 1 540px; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.dpl-cote { flex: 1 1 290px; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
.dpl-carte { background: var(--bg2); border: 1px solid var(--border); border-radius: 14px; padding: 16px 20px;
  display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.dpl-h2 { margin: 0; font-size: 1.15rem; color: #e6eefc; }
.dpl-h3 { margin: 0; font-family: var(--hud); font-size: .86rem; letter-spacing: .12em; color: var(--text2); font-weight: 600; }
.dpl-texte { margin: 0; color: #aebfdc; line-height: 1.55; }
.dpl-ligne-titre { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px 14px; }
.dpl-liste { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.dpl-liste li { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.dpl-liste li small { display: block; color: var(--text2); }
.dpl-catalogue li { display: block; }
/* La Une et le fil de la Gazette */
.dpl-une { background: var(--bg2); border: 1px solid var(--border2); border-radius: 14px; padding: 22px 26px; display: flex; flex-direction: column; gap: 10px; }
.dpl-une-sur { display: flex; flex-wrap: wrap; gap: 10px; font-family: var(--hud); font-size: .82rem; letter-spacing: .14em; }
.dpl-une-sur .dpl-mono { letter-spacing: 0; color: var(--text2); }
.dpl-une-titre { margin: 0; font-size: 1.75rem; line-height: 1.25; color: #f1f5ff; }
.dpl-une-texte { margin: 0; color: #aebfdc; line-height: 1.6; max-width: 70ch; }
.dpl-filtres, .dpl-gestes, .dpl-rangee { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.dpl-filtre { min-height: 38px; padding: 0 14px; border-radius: 999px; border: 1px solid var(--border2); background: transparent;
  color: #e6eefc; font-family: var(--body); font-size: .96rem; cursor: pointer; }
.dpl-filtre.est-actif { background: var(--border2); border-color: #3b5a9a; }
.dpl-fil { list-style: none; margin: 0; padding: 0; background: var(--bg2); border: 1px solid var(--border); border-radius: 14px; }
.dpl-nouvelle { display: flex; flex-wrap: wrap; gap: 6px 14px; padding: 14px 20px; border-bottom: 1px solid var(--border); align-items: baseline; }
.dpl-nouvelle:last-child { border-bottom: 0; }
.dpl-date { flex: none; width: 116px; color: var(--text2); font-size: .9rem; }
.dpl-nouvelle-corps { flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.dpl-nouvelle-corps strong { color: #e6eefc; font-weight: 600; }
.dpl-nouvelle-corps span { color: var(--text2); line-height: 1.5; }
.dpl-etiquette { flex: none; padding: 2px 10px; border-radius: 999px; border: 1px solid currentColor; font-size: .82rem; font-weight: 600; }
.dpl-cat-guerre { color: #fda4af; }
.dpl-cat-conseil { color: #fcd34d; }
.dpl-cat-assemblee { color: #5eead4; }
.dpl-cat-clans { color: #7dd3fc; }
/* Le Conseil : calendrier, hémicycle, résolutions */
.dpl-calendrier { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 10px; }
.dpl-etape { flex: 1 1 180px; min-width: 0; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--border); display: flex; flex-direction: column; gap: 2px; }
.dpl-etape span { color: var(--text2); font-size: .93rem; }
.dpl-etape.est-cours { border-color: #f59e0b; }
.dpl-etape.est-fait strong { color: var(--text2); }
.dpl-hemi-cadre { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.dpl-hemi { position: relative; flex: 2 1 380px; min-width: 0; aspect-ratio: 2 / 1.08; }
.dpl-hemi-detail { flex: 1 1 220px; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.dpl-siege { position: absolute; transform: translate(-50%, -50%); width: 9.2%; aspect-ratio: 1; min-width: 34px; border-radius: 50%;
  border: 2px solid currentColor; background: var(--bg3); color: #fcd34d; font-family: var(--mono); font-size: .8rem; font-weight: 700;
  cursor: pointer; padding: 0; overflow: hidden; text-overflow: clip; white-space: nowrap; }
.dpl-siege.dpl-fam-specialite { color: #7dd3fc; }
.dpl-siege.dpl-fam-exploit { color: #c4b5fd; }
.dpl-siege.dpl-fam-tirage { color: #5eead4; }
.dpl-siege.est-moi { box-shadow: 0 0 0 3px rgba(255, 255, 255, .35); }
.dpl-siege.est-choisi { background: currentColor; }
.dpl-siege.est-choisi { color: #0a0f1e; }
.dpl-siege.dpl-fam-general.est-choisi { background: #fcd34d; }
.dpl-siege.dpl-fam-specialite.est-choisi { background: #7dd3fc; }
.dpl-siege.dpl-fam-exploit.est-choisi { background: #c4b5fd; }
.dpl-siege.dpl-fam-tirage.est-choisi { background: #5eead4; }
.dpl-hemi-centre { position: absolute; left: 50%; bottom: 0; transform: translateX(-50%); text-align: center; display: flex; flex-direction: column; gap: 2px; }
.dpl-hemi-centre b { font-family: var(--hud); font-size: .8rem; letter-spacing: .12em; color: var(--text2); }
.dpl-hemi-centre span { color: var(--text2); font-size: .86rem; }
.dpl-legende { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; color: var(--text2); }
.dpl-legende i { display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 8px; border: 2px solid currentColor; vertical-align: -1px; }
.dpl-legende i.dpl-fam-general { color: #fcd34d; }
.dpl-legende i.dpl-fam-specialite { color: #7dd3fc; }
.dpl-legende i.dpl-fam-exploit { color: #c4b5fd; }
.dpl-legende i.dpl-fam-tirage { color: #5eead4; }
.dpl-choisi { display: flex; flex-direction: column; gap: 4px; padding: 12px; border: 1px solid var(--border2); border-radius: 10px; }
.dpl-choisi small { color: var(--text2); }
.dpl-resolution { display: flex; flex-direction: column; gap: 6px; padding: 12px 0; border-top: 1px solid var(--border); }
.dpl-res-tete { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.dpl-res-titre { color: #e6eefc; }
.dpl-statut { margin-left: auto; font-family: var(--mono); color: var(--text2); }
.dpl-statut.est-adoptee { color: #6ee7b7; }
.dpl-statut.est-rejetee, .dpl-statut.est-veto { color: #fda4af; }
.dpl-citation { color: #aebfdc; font-style: italic; }
.dpl-barre { display: flex; height: 10px; border-radius: 5px; overflow: hidden; background: var(--bg3); }
.dpl-b-pour { background: #10b981; }
.dpl-b-contre { background: #e94560; }
.dpl-b-abst { background: #475569; }
.dpl-barre-legende { color: var(--text2); font-size: .93rem; }
/* La fenêtre de dépôt */
.dpl-modale { position: fixed; inset: 0; z-index: 9000; display: flex; align-items: center; justify-content: center; padding: 16px; }
.dpl-modale-fond { position: absolute; inset: 0; background: rgba(2, 6, 16, .78); }
.dpl-modale-boite { position: relative; width: min(860px, 100%); max-height: calc(100vh - 32px); overflow-y: auto; background: var(--bg2);
  border: 1px solid var(--border2); border-radius: 16px; padding: 20px 24px; display: flex; flex-direction: column; gap: 12px; }
.dpl-modale-boite > header { flex-wrap: nowrap; align-items: flex-start; }
.dpl-modale-boite > header > div { min-width: 0; }
.dpl-fermer { background: none; border: 1px solid var(--border2); border-radius: 8px; color: var(--text); width: 40px; height: 40px; cursor: pointer; flex: none; }
.dpl-mesures { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 8px; }
.dpl-mesure { text-align: left; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--border2); background: transparent; color: var(--text);
  font-family: var(--body); cursor: pointer; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.dpl-mesure strong { color: #e6eefc; }
.dpl-mesure small { color: var(--text2); }
.dpl-mesure.est-actif { border-color: #2ba39a; background: rgba(43, 163, 154, .14); }
.dpl-label { display: flex; flex-direction: column; gap: 4px; color: var(--text2); }
.dpl-champ { width: 100%; box-sizing: border-box; min-height: 40px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border2);
  background: var(--bg3); color: var(--text); font-family: var(--body); font-size: 1rem; }
.dpl-champ-court { width: 140px; }
.dpl-rangee .dpl-champ { flex: 1 1 200px; width: auto; }
.dpl-rangee .dpl-champ-court { flex: 0 0 140px; }
.dpl-coches { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 6px; max-height: 220px; overflow-y: auto; }
.dpl-coche { display: flex; gap: 8px; align-items: center; }
.dpl-depot-pied { display: flex; flex-wrap: wrap; gap: 6px 18px; padding-top: 6px; border-top: 1px solid var(--border); }
.dpl-boutons { justify-content: flex-end; }
/* Les relations */
.dpl-trio { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.dpl-trio > div { display: flex; flex-direction: column; gap: 2px; padding: 10px; border: 1px solid var(--border); border-radius: 10px; min-width: 0; }
.dpl-trio small { color: var(--text2); }
.dpl-coalition, .dpl-guerre { display: flex; flex-direction: column; gap: 6px; padding: 10px 0; border-top: 1px solid var(--border); }
.dpl-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.dpl-guerre .dpl-etiquette { align-self: flex-start; }
.dpl-carte > .dsh-btn, .dpl-carte > .dpl-rangee > .dsh-btn { align-self: flex-start; }
.dpl-tag { padding: 2px 8px; border: 1px solid var(--border2); border-radius: 999px; font-family: var(--mono); }
.dpl-table { width: 100%; border-collapse: collapse; }
.dpl-table th, .dpl-table td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--border); }
.dpl-table th { color: var(--text2); font-weight: 600; }
.dpl-rel.est-guerre { color: #fda4af; }
.dpl-rel.est-pacte { color: #6ee7b7; }
.dpl-rel.est-coalition { color: #7dd3fc; }
.dpl-rel.est-moi { color: #fcd34d; }
.dpl-badge { display: inline-block; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--accent1); color: #fda4af; font-size: .86rem; }
.dpl-recherche { flex-direction: row; align-items: center; gap: 8px; }
.dpl-recherche .dpl-champ { width: 220px; }
.dpl-chat-fil { max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; padding: 8px; background: var(--bg3); border-radius: 10px; }
.dpl-chat-msg { line-height: 1.45; overflow-wrap: anywhere; }
/* Les votes du clan */
.dpl-mesvoix small { color: var(--text2); }
.dpl-gros { font-family: var(--hud); font-size: 2.2rem; color: #5eead4; }
.dpl-vote { display: flex; flex-direction: column; gap: 6px; padding: 10px 0; }
.dpl-choix-rangee { display: flex; flex-wrap: wrap; gap: 8px; }
.dpl-choix { min-height: 40px; padding: 0 16px; border-radius: 10px; border: 1px solid var(--border2); background: transparent;
  color: var(--text); font-family: var(--body); font-size: 1rem; cursor: pointer; }
.dpl-choix small { color: var(--text2); }
.dpl-choix.est-actif { border-color: #2ba39a; background: rgba(43, 163, 154, .18); color: #fff; }
.dpl-choix.est-contre.est-actif { border-color: var(--accent1); background: rgba(233, 69, 96, .16); }
/* Le téléphone : une colonne, les sous-onglets en pastilles, le tableau
   des clans empilé en fiches (aucune page ne défile en largeur). */
@media (max-width: 700px) {
  .dpl { padding: 0 12px 32px; gap: 14px; }
  .dpl-titre { font-size: 1.5rem; }
  .dpl-onglets { border-bottom: 0; gap: 6px; }
  .dpl-onglet { min-height: 40px; padding: 0 14px; border: 1px solid var(--border2); border-radius: 999px; margin: 0; font-size: .96rem; }
  .dpl-onglet.est-actif { border-color: #2ba39a; background: rgba(43, 163, 154, .18); }
  .dpl-une { padding: 16px; }
  .dpl-une-titre { font-size: 1.3rem; }
  .dpl-nouvelle { padding: 12px 14px; }
  .dpl-carte { padding: 14px; }
  .dpl-trio { gap: 6px; }
  .dpl-trio > div { padding: 8px; overflow-wrap: anywhere; }
  .dpl-gestes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dpl-gestes .dpl-filtre { padding: 4px 8px; line-height: 1.25; }
  .dpl-table thead { display: none; }
  .dpl-table, .dpl-table tbody, .dpl-table tr, .dpl-table td { display: block; width: 100%; box-sizing: border-box; }
  .dpl-table tr { padding: 8px 0; border-bottom: 1px solid var(--border); }
  .dpl-table td { border: 0; padding: 3px 0; display: flex; gap: 8px; flex-wrap: wrap; }
  .dpl-table td[data-label]::before { content: attr(data-label); color: var(--text2); min-width: 88px; }
  .dpl-recherche { width: 100%; }
  .dpl-recherche .dpl-champ { width: auto; flex: 1 1 auto; min-width: 0; }
  /* L'hémicycle devient une rangée de pastilles : vingt cercles sur 330
     points ne laissent pas 11 px à un tag de cinq lettres. */
  .dpl-hemi { aspect-ratio: auto; display: flex; flex-wrap: wrap; gap: 8px; }
  .dpl-siege { position: static; transform: none; width: auto; min-width: 58px; aspect-ratio: auto;
    border-radius: 999px; padding: 7px 10px; font-size: .86rem; }
  .dpl-hemi-centre { display: none; }
  .dpl-mesures { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dpl-mesure { padding: 8px 10px; }
  .dpl-long { display: none; }
  .dpl-modale { padding: 8px; align-items: flex-start; }
  .dpl-modale-boite { padding: 16px; max-height: calc(100vh - 16px); }
}
body.mobile-mode .dpl { padding: 0 12px 32px; }
body.mobile-mode .dpl-long { display: none; }

/* ════════════════════════════════════════════════════════════════════
   LE VAISSEAU MÈRE EN « DESKTOP FORCÉ » SUR UN PETIT ÉCRAN
   ════════════════════════════════════════════════════════════════════
   Un joueur au téléphone qui choisit « Desktop forcé » — le réglage qui
   existe justement pour REFUSER la version réduite — trouvait un Vaisseau
   Mère vide. Mesuré au navigateur, 390 × 844 : la vue s'arrête au point 175
   (215 points réservés à droite pour le rail et la languette du tiroir), le
   menu des sections garde ses 190 points fixes, et il ne reste que 34 points
   au contenu. La première vignette est posée de 207 à 226, c'est-à-dire
   ENTIÈREMENT hors de la boîte qui la montre. ZÉRO vignette visible sur 42,
   et la page ne défile pas horizontalement : aucun geste ne peut aller
   chercher ce qui manque.

   ⚠️ LA CAUSE DOMINANTE EST LA LARGEUR RÉSERVÉE À DROITE, pas la mise en
   colonne. Rendre la colonne unique sans rendre ces 215 points laisserait la
   fenêtre à 127 points.

   ⚠️ UNE SEULE CLASSE, ET C'EST LA BONNE : `ecran-etroit` dit « la fenêtre est
   étroite » (posée par mobile-mode.js, indépendamment du réglage choisi).
   Posée sous le seuil de largeur uniquement, aucune règle ci-dessous ne peut
   atteindre un grand écran. Mesuré à 1280 et 1920 px : rigoureusement
   identique.

   ⚠️ LES TAILLES DE TEXTE NE SONT PAS RÉDUITES, et c'est la décision de
   Loryan : ce joueur demande la version grand écran, pas des textes plus
   petits. On lui rend la LARGEUR et on empile le menu — rien d'autre. C'est
   ce qui distingue ce bloc du mode téléphone, qui rapetisse aussi.

   ⚠️ LA SPÉCIFICITÉ EST CALCULÉE, PAS ESPÉRÉE : un identifiant et deux
   classes battent `body.qbar-tiroir #view-ship` (un identifiant, une
   classe). Le bloc est écrit en FIN DE FICHIER pour que l'ordre de source
   tranche aussi, sans dépendre d'un calcul.
   ════════════════════════════════════════════════════════════════════ */

/* (a) LES 215 POINTS RENDUS — la cause dominante. */
body.ecran-etroit.qbar-tiroir #view-ship,
body.ecran-etroit.qbar-tiroir.qbar-tiroir-ouvert #view-ship {
  right: 0;
}

/* (b) LE MENU DES SECTIONS S'EMPILE AU-DESSUS DU CONTENU — mêmes gestes que
   le mode téléphone, SANS les réductions de taille. */
body.ecran-etroit #ship-content { flex-direction: column; }
body.ecran-etroit #ship-nav {
  width: 100%;
  flex-direction: row;
  overflow-x: auto;
  overflow-y: hidden;
  gap: 0.3rem;
  border-right: none;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  scrollbar-width: none;
}
body.ecran-etroit #ship-nav::-webkit-scrollbar { display: none; }
/* Le titre « Modules » n'a plus de sens sur une barre horizontale. */
body.ecran-etroit .sn-sect { display: none; }
body.ecran-etroit .sn-item {
  flex-shrink: 0;
  border-left: none;
  border-bottom: 2px solid transparent;
}
body.ecran-etroit .sn-item.active {
  border-left: none;
  border-bottom-color: var(--accent2);
}
/* ⚠️ `display: contents` DISSOUT LA SOUS-LISTE DES MODULES : empilée telle
   quelle, elle fait 452 px de haut et pousse tout le contenu hors écran. Le
   filet vertical qui reliait ses entrées n'a plus de sens à l'horizontale. */
body.ecran-etroit #ship-nav .sn-sub { display: contents; }
body.ecran-etroit #ship-nav .sn-sub::before { content: none; }
body.ecran-etroit #ship-nav .sn-sub .sn-item::before { content: none; }
/* (c) LA GRILLE DES BÂTIMENTS EN UNE COLONNE — elle doit passer devant la
   règle `body .bld-grid` écrite plus haut, d'où les deux classes. */
body.ecran-etroit .bld-grid {
  grid-template-columns: 1fr;
  gap: 0.6rem;
}

/* ═══════════════════════════════════════════════════════════════════
   QUATRE VARIABLES DE STYLE RÉTABLIES
   ═══════════════════════════════════════════════════════════════════
   Constat 141. Quatre réglages étaient appelés par leur nom sans jamais
   avoir été créés : les déclarations qui les lisaient SANS repli étaient
   jetées en silence. On les définit ici. Décisions de
   Loryan : l'espacement manquant vaut 20 px (l'échelle avance de 4 en 4) ;
   la couche du petit menu flottant passe AU-DESSUS des modales (1300) ; la
   couleur mal orthographiée `--text1` est rétablie à sa valeur claire pour
   ses dix-huit lectures (les 15 à repli ne bougent pas, les 3 cassées
   s'allument). ⚠️ Définies sur `body` : les propriétés personnalisées
   cascadent vers tous les descendants qui les lisent (`.voie-vide`,
   l'inventaire, etc.). Effets visibles : le message d'attente des Voies
   retrouve son cadre aéré, les textes d'inventaire leur nuance claire.
   ─────────────────────────────────────────────────────────────────── */
body {
  --space-5: 20px;                  /* le cran manquant, entre --space-4 (16) et --space-6 (24) */
  --z-popover: 1305;                /* au-dessus des modales (1300), sous les notifications (1310) */
  --text1: #e5e8f5;                 /* faute de frappe pour --text, rétablie à sa valeur claire */
  --intel-accent: var(--accent2);   /* alignée sur ses trois voisines (var(--intel-accent, var(--accent2))) */
}


/* ═══════════════════════════════════════════════════════════════════
   Interrupteur mouvementsEntrants — CE QUI ARRIVE SUR LE VAISSEAU
   ═══════════════════════════════════════════════════════════════════
   Demande de Loryan : les flottes qui arrivent sur un Vaisseau Mère se
   voient dans ses Mouvements et sur son radar. Les ENNEMIES en rouge, ligne,
   groupe et écho : c'est ce qui les distingue d'un raid qu'on a lancé
   soi-même (déjà bordé de rouge, mais sans fond). Les sections par sens
   reprennent la tête de section par vaisseau ; seule la couleur du nom dit
   le sens. */
body.f-mouvementsEntrants .mv-row.est-hostile,
body.f-mouvementsEntrants .mv-groupe.est-hostile {
  border-left-color: #ef4444; background: rgba(239,68,68,.09);
}
body.f-mouvementsEntrants .est-hostile .mv-badge-aller { color: #f87171; }
body.f-mouvementsEntrants .mvr-blip.est-hostile .mvr-cercle { stroke: #f87171; }
body.f-mouvementsEntrants .mvr-blip.est-hostile .mvr-halo { fill: #ef4444; opacity: .3; }
body.f-mouvementsEntrants .mvr-blip.est-hostile .mvr-trace.est-fil { stroke: #f87171; }
body.f-mouvementsEntrants .mv-vm-bloc.est-hostile .mv-vm-nom { color: #fca5a5; }
body.f-mouvementsEntrants .mv-vm-bloc.est-entrant .mv-vm-nom { color: #86efac; }
body.f-mouvementsEntrants .mv-vm-bloc.est-sortant .mv-vm-nom { color: #93c5fd; }
/* Le plancher de lisibilité (11 px) pour le compte de la section. */
body.f-mouvementsEntrants .mv-vm-bloc .mv-vm-compte { font-size: var(--badge-font-size, 11px); white-space: nowrap; }
body.f-mouvementsEntrants .mv-vm-bloc .mv-vm-nom { white-space: nowrap; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur origineFlottesEnnemies — la ligne ennemie au téléphone
   ═══════════════════════════════════════════════════════════════════
   Mesuré à 390 px : dans un groupe d'espionnages, chaque ligne coupait son
   titre à « Espionn… ». Son badge « En approche » y répète mot pour mot
   celui du groupe (la phase fait partie de la clé du groupe) : il part, et
   le titre retrouve sa place. Hors groupe, le badge reste. */
body.f-origineFlottesEnnemies.mobile-mode .mv-groupe.est-hostile .mv-row.est-hostile .mv-badge { display: none; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur rapportEspionSubiDetaille — le rapport « espionné »
   ═══════════════════════════════════════════════════════════════════
   Capture de Loryan : « je veux un texte plus gros », et « repositionne moi
   le radar au centre de la fenêtre que tu réduiras pour éviter les espaces
   qui ne servent à rien ». Le radar occupait une colonne de 180 px à gauche,
   vide sous le verdict sur toute la hauteur de la fiche, et sa hauteur
   (160 px) creusait un trou sous le titre. La fiche passe en UNE colonne :
   le titre, le radar réduit et centré, le verdict, puis les données sur
   toute la largeur. ⚠️ Ces règles viennent APRÈS celles du radar (même
   spécificité + la classe du corps) : placées plus haut, elles perdraient. */
body.f-rapportEspionSubiDetaille .ci-fiche[data-forme="radar"] { display: grid; grid-template-columns: minmax(0, 1fr); }
body.f-rapportEspionSubiDetaille .ci-fiche[data-forme="radar"] .ci-fiche-tete { grid-column: 1; grid-row: 2; }
body.f-rapportEspionSubiDetaille .ci-fiche[data-forme="radar"] .ci-scope { grid-column: 1; grid-row: 3; width: 112px; margin: 4px auto 2px; }
body.f-rapportEspionSubiDetaille .ci-fiche[data-forme="radar"] .ci-verdict { grid-column: 1; grid-row: 4; margin-bottom: 8px; }
body.f-rapportEspionSubiDetaille .ci-fiche[data-forme="radar"] .ci-detail { grid-column: 1; grid-row: 5; }
body.f-rapportEspionSubiDetaille .ci-subi-grand .ci-alerte { font-size: .95rem; }
body.f-rapportEspionSubiDetaille .ci-subi-grand .ci-sub { font-size: .86rem; }
body.f-rapportEspionSubiDetaille .ci-subi-grand .ci-lbl { font-size: .82rem; }
body.f-rapportEspionSubiDetaille .ci-subi-grand .ci-val { font-size: 1rem; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur rapportVersSimulateur — « ⚖️ Simuler ce raid »
   ═══════════════════════════════════════════════════════════════════
   En haut à droite de l'en-tête, là où Loryan l'a dessiné. Au téléphone,
   l'en-tête n'a pas la place d'un coin libre : le bouton passe sous la
   ligne de date, à sa place dans le flux. */
body.f-rapportVersSimulateur .ci-fiche-tete { position: relative; }
body.f-rapportVersSimulateur .ci-fiche-simuler {
  position: absolute; top: 10px; right: 18px;
  padding: 6px 12px; border-radius: 999px; cursor: pointer;
  font-size: .86rem; font-weight: 600; white-space: nowrap;
  color: #eafff9; background: rgba(13, 148, 136, .85); border: 1px solid #2dd4bf;
}
body.f-rapportVersSimulateur .ci-fiche-simuler:hover { background: #0d9488; }
body.f-rapportVersSimulateur.mobile-mode .ci-fiche-simuler { position: static; margin-top: 8px; }
@media (max-width: 700px) {
  body.f-rapportVersSimulateur .ci-fiche-simuler { position: static; margin-top: 8px; }
}
/* Le titre ne passe pas sous le bouton (bureau). */
body.f-rapportVersSimulateur:not(.mobile-mode) .ci-fiche-tete:has(.ci-fiche-simuler) .ci-fiche-titre { padding-right: 170px; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur radarLegendeIcones — la légende des icônes sous le radar
   ═══════════════════════════════════════════════════════════════════
   Elle remplace le paragraphe d'explication : une puce par icône présente
   sur le cadran, sur une rangée qui passe à la ligne au lieu de couper. */
body.f-radarLegendeIcones .mvr-legende-ico {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 8px; margin: 10px 2px 2px;
}
body.f-radarLegendeIcones .mvr-leg-ico {
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  padding: 3px 9px 3px 4px; border-radius: 999px; font-size: 12px; color: var(--text2);
  border: 1px solid var(--border); background: rgba(255, 255, 255, .03);
}
body.f-radarLegendeIcones .mvr-leg-ico i {
  font-style: normal; display: inline-grid; place-items: center; width: 20px; height: 20px;
  border-radius: 50%; border: 1px solid var(--accent3, #60a5fa); font-size: 11px; line-height: 1;
}
body.f-radarLegendeIcones .mvr-leg-ico.est-hostile { color: #fca5a5; border-color: rgba(239, 68, 68, .45); }
body.f-radarLegendeIcones .mvr-leg-ico.est-hostile i { border-color: #f87171; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur occupantsBoutons — Espionner / Raid sur la ligne du vaisseau
   ═══════════════════════════════════════════════════════════════════
   La classe se range à gauche, contre le nom ; les boutons prennent la
   place qu'elle laissait à droite. La rangée passe à la ligne au lieu de
   couper le nom. */
body.f-occupantsBoutons .pp-occ-l1 { justify-content: flex-start; flex-wrap: wrap; align-items: center; }
body.f-occupantsBoutons .pp-occ-actions { margin-left: auto; display: inline-flex; flex-wrap: wrap; gap: 4px; }
body.f-occupantsBoutons .pp-occ-btn { font-size: 11px; padding: 2px 8px; white-space: nowrap; }
/* Le plancher de lisibilité pour ce qu'on vient de toucher : le nom et la
   classe étaient à 10 et 8,7 px. */
body.f-occupantsBoutons .pp-occ-l1 { font-size: 12px; column-gap: .45rem; }
body.f-occupantsBoutons .pp-occ-classe { font-size: 11px; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur fichePlaneteEpuree — la fiche de planète resserrée
   ═══════════════════════════════════════════════════════════════════
   Le système et le périclitement sur une ligne (le second passe dessous si
   la place manque, plutôt que d'être coupé) ; la capacité d'extraction et
   son compteur de météorites sur une ligne. */
/* ⚠️ Pas de `space-between` sur cette rangée repliable (règle du projet) :
   le périclitement suit le nom du système, à une gouttière près. */
body.f-fichePlaneteEpuree #planet-panel .pp-sub {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .6rem;
}
body.f-fichePlaneteEpuree .pp-sub-peri { white-space: nowrap; }
body.f-fichePlaneteEpuree .pp-impact-ok:not([hidden]) { display: flex; align-items: baseline; justify-content: space-between; gap: .6rem; }
body.f-fichePlaneteEpuree .pp-imp-compte { white-space: nowrap; font-size: 12px; color: #bbf7d0; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur fichesAstresEpurees — « Soleil Bleu · 4 planètes » à côté
   des coordonnées
   ═══════════════════════════════════════════════════════════════════
   La ligne du titre passe à la ligne au lieu de couper le nom ; la mention
   suit les coordonnées, au plancher de lisibilité. */
body.f-fichesAstresEpurees .sp-titre { flex-wrap: wrap; row-gap: .1rem; margin-bottom: .9rem; }
body.f-fichesAstresEpurees .sp-sous-ligne {
  font-family: var(--font, inherit); font-size: 11px; color: var(--text2); white-space: nowrap;
}
body.f-fichesAstresEpurees .sp-coords { font-size: 11px; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur rapportVersRaid — les boutons de l'en-tête du rapport
   ═══════════════════════════════════════════════════════════════════
   « ⚔️ Raider », « 🧑‍✈️ Préparer le raid » et « ⚖️ Simuler ce raid » côte à
   côte en haut à droite (sous la date au téléphone). Le bouton du
   Simulateur y perd sa position propre : c'est la rangée qui se place. */
body.f-rapportVersRaid .ci-fiche-actions {
  position: absolute; top: 10px; right: 18px; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px;
}
body.f-rapportVersRaid .ci-fiche-actions .ci-fiche-simuler { position: static; }
body.f-rapportVersRaid .ci-fiche-act {
  padding: 6px 12px; border-radius: 999px; cursor: pointer; font-size: .86rem; font-weight: 600; white-space: nowrap;
  color: #fff1f2; background: rgba(190, 18, 60, .82); border: 1px solid #fb7185;
}
body.f-rapportVersRaid .ci-fiche-act:hover { background: #be123c; }
body.f-rapportVersRaid .ci-fiche-act[data-ci-preparer] { background: rgba(124, 58, 237, .85); border-color: #a78bfa; color: #f5f3ff; }
body.f-rapportVersRaid .ci-fiche-act[data-ci-preparer]:hover { background: #7c3aed; }
body.f-rapportVersRaid .ci-fiche-tete { position: relative; }
body.f-rapportVersRaid:not(.mobile-mode) .ci-fiche-tete:has(.ci-fiche-actions) .ci-fiche-titre { padding-right: 440px; }
body.f-rapportVersRaid.mobile-mode .ci-fiche-actions { position: static; margin-top: 8px; justify-content: flex-start; }
@media (max-width: 700px) {
  body.f-rapportVersRaid .ci-fiche-actions { position: static; margin-top: 8px; justify-content: flex-start; }
}

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur diplomatiePleineLargeur — la Diplomatie sur toute la page
   ═══════════════════════════════════════════════════════════════════
   Capture de Loryan, deux croix sur les bandes vides : « la partie
   diplomatique a des côtés vides, mets-la sur toute la page ». La vue était
   bornée à 1 376 px et centrée ; elle prend désormais toute la largeur,
   gouttières de 20 px comprises. */
body.f-diplomatiePleineLargeur .dpl { max-width: none; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur lotsDixMille — la case de quantité tient « 10000 »
   ═══════════════════════════════════════════════════════════════════
   Mesuré au téléphone (390 px, police des champs à 16 px) : « 10000 » fait
   43 px, la case de la carte en offrait 52 et celle du panneau de droite 40
   — le dernier chiffre (carte) ou les deux derniers (panneau) passaient
   sous le bord. Le retrait intérieur passe de 8 à 4 px (la case des cartes
   garde ses 70 px, son bouton ne perd rien), et la case du panneau passe de
   58 à 70 px, comme celle des cartes. */
body.f-lotsDixMille .ship-qty-input { padding-left: 4px; padding-right: 4px; }
body.f-lotsDixMille .md-build-row .ship-qty-input { width: 70px; }
/* Le bandeau de statut, qui porte désormais le refus du chantier : posé à
   `left: 50%`, il ne pouvait pas dépasser la MOITIÉ de l'écran — 195 px sur
   390, mesuré, et un pavé de neuf lignes. `max-content` le laisse prendre la
   largeur de son texte, toujours borné par son `max-width` (560 px, 90 % de
   l'écran) : un message court reste court, un long s'étale au lieu de
   s'empiler. */
body.f-lotsDixMille #dsh-statut { width: max-content; }

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur fichesPlusLarges — les fiches de la carte, 25 % plus larges
   ═══════════════════════════════════════════════════════════════════
   Loryan : « On va élargir la taille des fiches de 25 % en plus sur la
   gauche ». Les deux panneaux sont collés au bord droit (`right: 0`) : les
   élargir les fait grandir vers la GAUCHE, le bord droit ne bouge pas. La
   fiche d'une lune vit dans celle de la planète et suit.
   Chaque largeur ci-dessous est celle de la règle de base multipliée par
   1,25, plafond en `vw` compris (test/fiches-repliables.test.js le relit).
   ⚠️ SEULEMENT AU-DESSUS DE 700 PX. Au téléphone, les deux fiches sont des
   tiroirs pleine largeur : un sélecteur `body.f-…` est plus spécifique que
   `body #soleil-panel` de la requête `max-width: 700px`, il y reprendrait
   la main et ferait sortir la fiche de l'écran. Le panneau de planète, lui,
   est tenu par des `!important` en mode mobile et sur écran étroit. */
@media (min-width: 701px) {
  body.f-fichesPlusLarges #planet-panel { width: 425px; max-width: 50vw; }
  body.f-fichesPlusLarges #soleil-panel { width: 400px; max-width: 50vw; }
}
@media (min-width: 701px) and (max-width: 1280px) {
  body.f-fichesPlusLarges #planet-panel { width: 375px; }
}

/* ═══════════════════════════════════════════════════════════════════
   Interrupteur fichesSectionsRepliables — le pli des sections des fiches
   ═══════════════════════════════════════════════════════════════════
   Chaque titre de section (`[data-repli]` dans une racine `[data-repli-type]`
   posée par features/galaxy/fiches-replis.js) devient un bouton : chevron à
   droite, ▾ déplié, ▸ replié. Le contenu replié porte `fr-cache`.
   ⚠️ LE CHEVRON EST UN ::after EN POSITION ABSOLUE, ET LE TITRE GARDE SON
   `display` : le passer en `flex` écraserait le `hidden` des titres nés
   cachés (Amas, Décombres…) — une règle qui pose `display` gagne sur la
   règle native, défaut déjà vécu trois fois sur cette fiche.
   ⚠️ LA ZONE DE TOUCHE DÉBORDE DU TITRE (::before, au-dessus et en dessous)
   sans rien déplacer : un titre d'une ligne est une cible trop petite pour
   un pouce. Elle reste dans la marge du titre, sans mordre sur son contenu. */
body.f-fichesSectionsRepliables [data-repli-type] [data-repli] {
  position: relative; cursor: pointer; padding-right: 1.6rem;
  -webkit-user-select: none; user-select: none;
}
body.f-fichesSectionsRepliables [data-repli-type] [data-repli]::before {
  content: ''; position: absolute; left: 0; right: 0; top: -.3rem; bottom: -.25rem;
}
body.f-fichesSectionsRepliables [data-repli-type] [data-repli]::after {
  content: '▾'; position: absolute; right: .15rem; top: 50%; transform: translateY(-50%);
  font-family: var(--body); font-size: 1rem; line-height: 1; letter-spacing: 0;
  color: var(--text2);
}
body.f-fichesSectionsRepliables [data-repli-type] [data-repli].fr-replie::after { content: '▸'; }
body.f-fichesSectionsRepliables [data-repli-type] [data-repli]:hover,
body.f-fichesSectionsRepliables [data-repli-type] [data-repli]:focus-visible,
body.f-fichesSectionsRepliables [data-repli-type] [data-repli]:hover::after { color: var(--text); }
body.f-fichesSectionsRepliables .fr-cache { display: none !important; }
/* Le titre d'une rubrique repliée du soleil ferme son cadre : sa marge du
   bas laissait un vide sous le mot. */
body.f-fichesSectionsRepliables #soleil-panel .sp-lbl.fr-replie { margin-bottom: 0; }
/* Planète et lune : leurs titres, devenus des boutons, passent au plancher de
   lisibilité (0,8 rem, comme ceux du soleil) ; l'espacement des lettres
   baisse avec, pour que le plus long tienne sur une ligne au téléphone.
   ⚠️ LA MARGE DU HAUT REND LES 2 PX que la ligne gagne (mesuré au navigateur :
   titre de 12 à 14 px) : la fiche ne s'allonge pas d'un titre à l'autre. */
body.f-fichesSectionsRepliables #planet-panel .pp-section {
  font-size: .8rem; letter-spacing: .08em; margin-top: calc(1.1rem - 2px);
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur accueilPrincipes — L'ACCUEIL À TROIS CHOIX ET LES GRANDS
   PRINCIPES
   ════════════════════════════════════════════════════════════════════
   Posés par features/principes/principes.js, qui ne crée rien hors
   interrupteur : sans lui, aucune de ces classes n'existe dans la page.
   ⚠️ AU-DESSUS DU GUIDE DES VOIES (9100) ET DU VOILE DU TUTORIEL (9000) :
   relus depuis le guide, les grands principes passent devant lui.
   ⚠️ LES SCHÉMAS SE REPLIENT À LA LARGEUR RÉELLE (media query à 700 px),
   pas à la classe `mobile-mode` : c'est la place disponible qui décide si
   une chaîne de quatre boîtes tient en ligne. */
.gp-voile {
  position: fixed; inset: 0; z-index: 9200;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
  background: rgba(2, 6, 16, .8); backdrop-filter: blur(3px);
}
.gp-voile[hidden] { display: none; }
.gp-carte {
  width: min(1000px, 100%); max-height: min(94vh, 1040px);
  display: flex; flex-direction: column; overflow: hidden;
  background: linear-gradient(180deg, #13233f 0%, #0b1527 100%);
  border: 2px solid rgba(56, 189, 248, .85); border-radius: 16px;
  box-shadow: 0 0 0 1px rgba(14, 165, 233, .3), 0 0 34px rgba(14, 165, 233, .42),
              0 24px 60px rgba(0, 0, 0, .6);
  color: #d7e2f3; font-family: var(--body);
}
/* Les couleurs de chapitre : une variable, lue par tout ce qui suit. */
.gp-ton-vm     { --gp-ton: #38bdf8; --gp-ton-fond: rgba(56, 189, 248, .12); }
.gp-ton-eco    { --gp-ton: #f59e0b; --gp-ton-fond: rgba(245, 158, 11, .12); }
.gp-ton-sci    { --gp-ton: #a78bfa; --gp-ton-fond: rgba(167, 139, 250, .13); }
.gp-ton-expl   { --gp-ton: #22d3ee; --gp-ton-fond: rgba(34, 211, 238, .11); }
.gp-ton-mil    { --gp-ton: #f87171; --gp-ton-fond: rgba(248, 113, 113, .12); }
.gp-ton-soc    { --gp-ton: #34d399; --gp-ton-fond: rgba(52, 211, 153, .12); }
.gp-ton-neutre { --gp-ton: #94a3b8; --gp-ton-fond: rgba(148, 163, 184, .1); }

/* ── L'accueil ── */
.gp-carte--accueil { width: min(780px, 100%); overflow-y: auto; }
.gp-media {
  position: relative; flex-shrink: 0;
  aspect-ratio: 16 / 9; max-height: 40vh; overflow: hidden;
  background: #020611; border-bottom: 1px solid rgba(56, 189, 248, .4);
}
.gp-media-attente {
  position: absolute; inset: -6%;
  display: flex; align-items: center; justify-content: center;
  background: url('/assets/fonds/nebuleuse-bleue.jpg') center / cover no-repeat;
  animation: gp-derive 26s ease-in-out infinite alternate;
}
.gp-media-etoiles {
  position: absolute; inset: 0;
  background-image:
    radial-gradient(1.5px 1.5px at 12% 22%, #fff 50%, transparent 51%),
    radial-gradient(1px 1px at 33% 71%, #cfe8ff 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 58% 14%, #fff 50%, transparent 51%),
    radial-gradient(1px 1px at 77% 63%, #e0f2fe 50%, transparent 51%),
    radial-gradient(2px 2px at 88% 28%, #fff 50%, transparent 51%),
    radial-gradient(1px 1px at 46% 88%, #fff 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 6% 80%, #bae6fd 50%, transparent 51%);
  animation: gp-scintille 4s ease-in-out infinite alternate;
}
.gp-media-logo {
  position: relative; height: 62%; max-width: 70%; object-fit: contain;
  filter: drop-shadow(0 0 26px rgba(56, 189, 248, .55));
}
@keyframes gp-derive { from { transform: scale(1) translate(0, 0); } to { transform: scale(1.08) translate(-2%, 1%); } }
@keyframes gp-scintille { from { opacity: .45; } to { opacity: 1; } }
.gp-media-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.gp-media--video .gp-media-attente { display: none; }
.gp-media-son {
  position: absolute; right: 12px; bottom: 12px;
  width: 40px; height: 40px; border-radius: 50%; cursor: pointer;
  background: rgba(2, 6, 16, .7); border: 1px solid rgba(56, 189, 248, .6);
  color: #fff; font-size: 1.1rem;
}
.gp-accueil-texte { padding: 18px 30px 4px; }
.gp-accueil-titre {
  margin: 0 0 10px; text-align: center;
  font-family: var(--hud); font-size: 1.45rem; font-weight: 700; letter-spacing: .03em; color: #fff;
}
.gp-accueil-corps { font-size: 1.1rem; line-height: 1.6; }
.gp-accueil-corps p { margin: 0 0 .65em; }
.gp-accueil-corps b { color: #fff; }
.gp-choix { display: flex; flex-direction: column; gap: 10px; padding: 8px 30px 26px; }
.gp-choix-btn {
  position: relative; display: flex; align-items: center; gap: 14px; text-align: left;
  padding: 14px 16px; border-radius: 12px; cursor: pointer;
  border: 1px solid var(--border2, #1e3060); background: rgba(10, 16, 32, .6);
  color: #dbe7fb; font: inherit;
  transition: border-color .15s, background .15s, transform .1s;
}
.gp-choix-btn:hover { transform: translateY(-1px); border-color: rgba(56, 189, 248, .7); }
.gp-choix-ico { font-size: 1.9rem; line-height: 1; flex-shrink: 0; }
.gp-choix-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.gp-choix-txt b { font-size: 1.12rem; color: #fff; }
.gp-choix-txt small { font-size: .95rem; color: var(--text2, #8aa4c8); line-height: 1.4; }
.gp-choix-btn--principes {
  border: 2px solid #38bdf8;
  background: linear-gradient(180deg, rgba(14, 165, 233, .26), rgba(14, 165, 233, .08));
  box-shadow: 0 0 22px rgba(14, 165, 233, .25);
}
.gp-choix-btn--tuto { border-color: rgba(56, 189, 248, .45); }
.gp-choix-btn--non { background: transparent; }
.gp-choix-btn--non b { color: #c8d6f0; }
.gp-choix-badge {
  flex-shrink: 0; align-self: flex-start;
  padding: 3px 9px; border-radius: 999px;
  background: #38bdf8; color: #04101c;
  font-size: .8rem; font-weight: 800; letter-spacing: .03em;
}

/* ── La visionneuse ── */
.gp-entete {
  display: flex; align-items: center; gap: 10px; flex-shrink: 0;
  padding: 11px 12px 11px 16px;
  background: linear-gradient(90deg, rgba(14, 165, 233, .34) 0%, rgba(14, 165, 233, .08) 100%);
  border-bottom: 1px solid rgba(56, 189, 248, .5);
}
.gp-entete-ico { font-size: 1.4rem; line-height: 1; }
.gp-entete-sur { flex: 1; min-width: 0; font-family: var(--hud); font-size: 1rem; color: #fff; letter-spacing: .03em; }
.gp-fermer {
  width: 38px; height: 36px; border-radius: 8px; cursor: pointer;
  background: rgba(2, 6, 16, .35); border: 1px solid rgba(56, 189, 248, .45);
  color: #e0f2fe; font-size: 1.05rem;
}
.gp-fermer:hover { background: rgba(14, 165, 233, .3); }
.gp-sommaire {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; flex-shrink: 0;
  padding: 10px 14px; border-bottom: 1px solid rgba(56, 189, 248, .18);
}
.gp-puce {
  width: 36px; height: 36px; border-radius: 50%; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(10, 16, 32, .7); border: 1px solid var(--border2, #1e3060);
  font-size: 1.05rem; line-height: 1; opacity: .55;
  transition: opacity .15s, border-color .15s, transform .1s;
}
.gp-puce:hover { opacity: 1; transform: translateY(-1px); }
.gp-puce.is-vu { opacity: .85; border-color: rgba(52, 211, 153, .6); }
.gp-puce.is-ici {
  opacity: 1; border: 2px solid var(--gp-ton, #38bdf8);
  box-shadow: 0 0 14px var(--gp-ton, #38bdf8);
}
.gp-page { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 30px 20px; }
.gp-sur-titre {
  font-family: var(--hud); font-size: .82rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--gp-ton, #38bdf8);
}
.gp-titre { margin: 6px 0 8px; font-size: 1.6rem; font-weight: 700; line-height: 1.25; color: #fff; }
.gp-chapeau { margin: 0 0 14px; font-size: 1.12rem; line-height: 1.6; }
.gp-chapeau b, .gp-retenir b, .gp-note b { color: #fff; }
.gp-schema {
  margin: 0 0 16px; padding: 16px; border-radius: 14px;
  background: rgba(4, 10, 24, .55); border: 1px solid rgba(56, 189, 248, .2);
  display: flex; flex-direction: column; gap: 14px;
}
.gp-schema-titre {
  margin-top: 4px; font-size: .9rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text2, #8aa4c8);
}
.gp-retenir {
  padding: 12px 16px; border-radius: 0 10px 10px 0;
  background: var(--gp-ton-fond, rgba(56, 189, 248, .1));
  border-left: 4px solid var(--gp-ton, #38bdf8);
}
.gp-retenir-titre {
  font-size: .85rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: var(--gp-ton, #38bdf8); margin-bottom: 6px;
}
.gp-retenir ul { margin: 0; padding-left: 1.2rem; }
.gp-retenir li { margin-bottom: .4em; font-size: 1.05rem; line-height: 1.5; }
.gp-retenir li:last-child { margin-bottom: 0; }
.gp-pied {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-shrink: 0;
  padding: 12px 16px; border-top: 1px solid rgba(56, 189, 248, .3);
}
.gp-compteur { font-family: var(--mono); font-size: .95rem; color: var(--text2, #8aa4c8); }
.gp-btn-ok {
  padding: 11px 18px; border-radius: 9px; cursor: pointer;
  background: #38bdf8; border: none; color: #04101c;
  font: inherit; font-size: 1.05rem; font-weight: 800;
}
.gp-btn-ok:hover { filter: brightness(1.1); }
.gp-btn-sec {
  padding: 11px 14px; border-radius: 9px; cursor: pointer;
  background: transparent; border: 1px solid var(--border2, #1e3060);
  color: var(--text2, #8aa4c8); font: inherit; font-size: 1.02rem;
}
.gp-btn-sec:hover:not([disabled]) { color: #fff; border-color: #38bdf8; }
.gp-btn-sec[disabled] { opacity: .35; cursor: default; }
.gp-fin {
  margin-top: 16px; padding: 16px 18px; border-radius: 12px; text-align: center;
  background: rgba(14, 165, 233, .12); border: 2px solid rgba(56, 189, 248, .7);
}
.gp-fin-titre { font-size: 1.2rem; font-weight: 800; color: #fff; margin-bottom: 6px; }
.gp-fin p { margin: 0 0 6px; font-size: 1.05rem; line-height: 1.55; }
.gp-lien {
  background: none; border: none; cursor: pointer; padding: 4px;
  color: var(--text2, #8aa4c8); text-decoration: underline; font: inherit; font-size: .98rem;
}
.gp-lien:hover { color: #fff; }

/* ── Les pièces des schémas ── */
.gp-noeud {
  width: 100%; min-width: 0; height: 100%;
  display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;
  padding: 10px 8px; border-radius: 11px;
  background: var(--gp-ton-fond); border: 1px solid var(--gp-ton);
}
.gp-noeud--img { padding-top: 0; overflow: hidden; }
.gp-noeud-img {
  display: block; width: calc(100% + 16px); margin: 0 -8px; height: 72px; flex-shrink: 0;
  background-size: cover; background-position: center 70%;
  border-bottom: 1px solid var(--gp-ton);
}
.gp-noeud--contain .gp-noeud-img {
  background-size: contain; background-repeat: no-repeat; background-position: center;
  background-color: rgba(2, 6, 16, .45); padding: 6px; background-origin: content-box;
}
.gp-noeud-ico { font-size: 1.7rem; line-height: 1.1; }
.gp-noeud-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.gp-noeud-txt b { font-size: 1rem; color: #fff; line-height: 1.25; }
.gp-noeud-txt small { font-size: .88rem; line-height: 1.4; color: #b9c8de; }
.gp-noeud--large { max-width: 460px; margin: 0 auto; }
.gp-res-ico { width: 1.5em; height: 1.5em; vertical-align: middle; object-fit: contain; }
.gp-res-liste { display: inline-flex; gap: 2px; }

.gp-chaine { display: flex; align-items: stretch; }
.gp-chaine-pas { flex: 1 1 0; min-width: 0; display: flex; }
.gp-fleche {
  flex: 0 0 40px; position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
}
.gp-fleche-trait {
  position: relative; display: block; width: 100%; height: 2px;
  background: var(--gp-ton, #38bdf8); opacity: .85;
}
.gp-fleche-trait::after {
  content: ''; position: absolute; right: -2px; top: 50%; transform: translateY(-50%);
  border-left: 8px solid var(--gp-ton, #38bdf8);
  border-top: 6px solid transparent; border-bottom: 6px solid transparent;
}
.gp-fleche-txt {
  font-size: .82rem; line-height: 1.2; text-align: center; color: var(--text2, #8aa4c8);
  max-width: 76px;
}
.gp-fleche:has(.gp-fleche-txt) { flex-basis: 64px; }

.gp-convergence { display: flex; align-items: center; }
.gp-convergence-entrees {
  flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 10px;
  padding-right: 12px; border-right: 3px solid var(--gp-ton, #38bdf8);
}
.gp-convergence-entree { display: flex; }
.gp-convergence-entree > .gp-noeud { width: auto; flex: 1; }
.gp-convergence-sortie { flex: 0 0 210px; display: flex; }

.gp-boucle { display: flex; flex-direction: column; gap: 6px; }
.gp-boucle-retour {
  align-self: stretch; text-align: center; padding: 6px 10px;
  border: 2px dashed var(--gp-ton, #38bdf8); border-top: none; border-radius: 0 0 14px 14px;
  font-size: .95rem; color: #c8d6f0;
}
.gp-boucle-retour span { font-size: 1.2rem; color: var(--gp-ton, #38bdf8); }

.gp-leviers { display: flex; flex-direction: column; gap: 14px; }
.gp-leviers-centre { display: flex; justify-content: center; }
.gp-leviers-listes { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.gp-leviers-liste { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.gp-leviers-tete { font-size: .9rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
.gp-leviers-tete--plus { color: #34d399; }
.gp-leviers-tete--moins { color: #f87171; }
.gp-levier {
  display: flex; align-items: flex-start; gap: 10px; padding: 9px 11px; border-radius: 10px;
  background: rgba(10, 16, 32, .6); border: 1px solid var(--border2, #1e3060);
}
.gp-levier--plus { border-left: 3px solid #34d399; }
.gp-levier--moins { border-left: 3px solid #f87171; }
.gp-levier-signe { font-size: .85rem; line-height: 1.6; flex-shrink: 0; }
.gp-levier--plus .gp-levier-signe { color: #34d399; }
.gp-levier--moins .gp-levier-signe { color: #f87171; }
.gp-levier-ico { font-size: 1.3rem; line-height: 1.2; flex-shrink: 0; }
.gp-levier-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.gp-levier-txt b { font-size: 1rem; color: #fff; }
.gp-levier-txt small { font-size: .88rem; line-height: 1.4; color: #b9c8de; }

.gp-cartes { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.gp-carte-mini {
  display: flex; flex-direction: column; gap: 6px; min-width: 0;
  padding: 12px; border-radius: 12px;
  background: var(--gp-ton-fond); border: 1px solid var(--gp-ton);
}
.gp-carte-mini-img {
  display: block; height: 84px; margin: -12px -12px 4px; border-radius: 11px 11px 0 0;
  background: rgba(2, 6, 16, .45) center / contain no-repeat; background-origin: content-box; padding: 8px;
  border-bottom: 1px solid var(--gp-ton);
}
.gp-carte-mini-tete { display: flex; align-items: center; gap: 8px; }
.gp-carte-mini-tete b { font-size: 1.05rem; color: #fff; }
.gp-carte-mini-ico { font-size: 1.4rem; line-height: 1; }
.gp-carte-mini-sous { font-size: .95rem; line-height: 1.45; color: #c8d6f0; }
.gp-carte-mini ul { margin: 0; padding: 0; list-style: none; font-size: .88rem; color: var(--gp-ton); }

.gp-pastilles { display: flex; flex-wrap: wrap; gap: 6px; }
.gp-pastille {
  padding: 5px 10px; border-radius: 999px; font-size: .92rem;
  background: rgba(56, 189, 248, .1); border: 1px solid rgba(56, 189, 248, .4); color: #e0f2fe;
}
.gp-note {
  padding: 10px 12px; border-radius: 10px; font-size: 1rem; line-height: 1.5;
  background: rgba(250, 204, 21, .07); border: 1px solid rgba(250, 204, 21, .35);
}

/* Le bouton qui rouvre les grands principes, dans le guide et le sommaire. */
.gp-relire {
  background: none; border: none; cursor: pointer; padding: 2px 0;
  color: #7dd3fc; font: inherit; font-size: inherit; text-decoration: underline;
}
.gp-relire:hover { color: #fff; }

/* ── Au téléphone : les chaînes se replient de haut en bas ── */
@media (max-width: 700px) {
  .gp-voile { padding: 8px; }
  .gp-carte { max-height: calc(100dvh - 16px); border-radius: 14px; }
  .gp-media { max-height: 28vh; }
  .gp-accueil-texte { padding: 14px 16px 2px; }
  .gp-accueil-titre { font-size: 1.2rem; }
  .gp-accueil-corps { font-size: 1.02rem; }
  .gp-choix { padding: 6px 14px 16px; gap: 8px; }
  .gp-choix-btn { padding: 12px; gap: 10px; }
  .gp-choix-ico { font-size: 1.6rem; }
  .gp-choix-txt b { font-size: 1.04rem; }
  .gp-choix-txt small { font-size: .9rem; }
  .gp-choix-badge { position: absolute; top: -9px; right: 10px; }
  .gp-sommaire { gap: 4px; padding: 8px 8px; }
  .gp-puce { width: 30px; height: 30px; font-size: .95rem; }
  .gp-page { padding: 14px 14px 16px; }
  .gp-titre { font-size: 1.32rem; }
  .gp-chapeau { font-size: 1.04rem; }
  .gp-schema { padding: 10px; }
  .gp-chaine { flex-direction: column; }
  .gp-fleche, .gp-fleche:has(.gp-fleche-txt) { flex: 0 0 30px; flex-direction: row; gap: 8px; }
  .gp-fleche-trait { width: 2px; height: 100%; }
  .gp-fleche-trait::after {
    right: auto; left: 50%; top: auto; bottom: -2px; transform: translateX(-50%);
    border-left: 6px solid transparent; border-right: 6px solid transparent;
    border-top: 8px solid var(--gp-ton, #38bdf8); border-bottom: none;
  }
  .gp-fleche-txt { max-width: none; text-align: left; }
  /* Une boîte couchée : l'image ou l'émoji à gauche, le texte à droite. */
  .gp-noeud { flex-direction: row; text-align: left; align-items: center; gap: 10px; padding: 8px 10px; }
  .gp-noeud--img { padding: 0 10px 0 0; }
  .gp-noeud-img { width: 84px; height: auto; align-self: stretch; min-height: 62px; margin: 0;
    border-bottom: none; border-right: 1px solid var(--gp-ton); }
  .gp-noeud-ico { width: 40px; text-align: center; flex-shrink: 0; }
  .gp-convergence { flex-direction: column; align-items: stretch; }
  .gp-convergence-entrees { padding-right: 0; padding-bottom: 10px; border-right: none; border-bottom: 3px solid var(--gp-ton, #38bdf8); }
  .gp-convergence > .gp-fleche { flex: 0 0 30px; }
  .gp-convergence-sortie { flex: 0 0 auto; }
  .gp-leviers-listes { grid-template-columns: 1fr; }
  .gp-cartes { grid-template-columns: 1fr; }
  .gp-pied { padding: 10px 12px; }
  .gp-btn-ok, .gp-btn-sec { padding: 10px 12px; font-size: 1rem; }
}
.tuto-panneau-principes { padding: 0 16px 10px; font-size: .9rem; }
/* Corrections après la première passe au navigateur : les flèches à
   étiquette prennent la largeur de leur mot, les rangées d'une convergence
   remplissent leur colonne, et la fenêtre garde la même taille d'une page à
   l'autre (elle « sautait » de 120 px en passant de page en page). */
.gp-fleche:has(.gp-fleche-txt) { flex-basis: 84px; }
.gp-fleche-txt { max-width: 82px; }
.gp-convergence-entree { flex-direction: column; gap: 6px; }
.gp-convergence-entree > .gp-chaine, .gp-convergence-entree > .gp-noeud { flex: 1; min-width: 0; width: 100%; }
.gp-convergence-titre {
  font-size: .86rem; font-weight: 700; letter-spacing: .04em; color: var(--text2, #8aa4c8);
}
.gp-carte--principes { position: relative; height: min(94vh, 1040px); }
.gp-suite {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 72px; z-index: 2;
  padding: 5px 14px; border-radius: 999px; pointer-events: none;
  background: rgba(2, 6, 16, .85); border: 1px solid var(--gp-ton, #38bdf8);
  color: #e0f2fe; font-size: .88rem; white-space: nowrap;
  opacity: 0; transition: opacity .2s;
}
.gp-carte--principes.gp-a-suivre .gp-suite { opacity: 1; animation: gp-rebond 1.6s ease-in-out infinite; }
@keyframes gp-rebond { 50% { transform: translate(-50%, 4px); } }
@media (max-width: 700px) {
  .gp-carte--principes { height: calc(100dvh - 16px); }
  .gp-suite { bottom: 66px; }
  .gp-fleche:has(.gp-fleche-txt) { flex-basis: 30px; }
  .gp-fleche-txt { max-width: none; }
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur simulateurEpure — LE SIMULATEUR DE COMBAT ÉPURÉ
   ════════════════════════════════════════════════════════════════════
   Loryan, captures barrées à l'appui : « grossis les chiffres ×1,5 et
   retire ce que j'ai barré » (la colonne attaque/bouclier/structure, le
   sous-titre de Ma flotte, le paragraphe du rapport complet). La fiche
   d'une unité reste lisible au SURVOL de sa ligne (son `title`).
   ⚠️ GROSSIR LE CHIFFRE NE DOIT PAS GROSSIR LA CASE : la police du compteur
   passe de .78 à 1.17 rem, son retrait vertical baisse d'autant — la
   rangée garde sa hauteur (mesurée au navigateur avant et après). */
body.f-simulateurEpure .sim-ligne .sim-fiche { display: none; }
body.f-simulateurEpure .sim-ligne,
body.f-simulateurEpure .sim-ligne-res { grid-template-columns: 1.2rem minmax(0,1fr) 7.4rem 5.4rem; }
body.f-simulateurEpure .sim-ligne-tech { grid-template-columns: minmax(0,1fr) 7.4rem 6rem; }
body.f-simulateurEpure .sim-ligne .rp-input {
  font-size: 1.17rem; padding-top: .1rem; padding-bottom: .1rem; padding-left: .45rem; padding-right: .45rem;
}
body.f-simulateurEpure .sim-ligne button.sim-max { font-size: .96rem; }
/* Le titre d'un camp et ce qui l'accompagne sur SA ligne : la liste des
   Vaisseaux Mères à gauche d'« En face », le badge du rapport complet. */
body.f-simulateurEpure .sim-camp-ligne {
  display: flex; align-items: center; gap: .6rem; min-width: 0;
}
body.f-simulateurEpure .sim-camp-ligne .sim-camp-titre { flex: 0 0 auto; }
body.f-simulateurEpure .sim-vm-choix {
  margin-left: auto; width: auto; min-width: 0; max-width: 66%;
  font-family: var(--body); font-size: .92rem; letter-spacing: 0; padding: .3rem .5rem;
}
body.f-simulateurEpure .sim-badge-ok {
  padding: 2px 10px; border-radius: 999px; white-space: nowrap;
  background: rgba(16,185,129,.14); border: 1px solid rgba(16,185,129,.55);
  color: #6ee7b7; font-size: .8rem; font-weight: 700;
}
/* Au téléphone (les deux systèmes : la classe ET la largeur réelle). */
body.f-simulateurEpure.mobile-mode .sim-ligne,
body.f-simulateurEpure.mobile-mode .sim-ligne-res { grid-template-columns: 1.2rem minmax(0,1fr) 6rem 4.4rem; }
body.f-simulateurEpure.mobile-mode .sim-ligne-tech { grid-template-columns: minmax(0,1fr) 6rem; }
body.f-simulateurEpure.mobile-mode .sim-ligne .rp-input { padding-top: .3rem; padding-bottom: .3rem; }
/* La liste des Vaisseaux Mères passe sous le titre, en pleine largeur : à
   côté de lui, le nom de la classe était coupé (mesuré à 390 px). */
body.f-simulateurEpure.mobile-mode .sim-camp-moi .sim-camp-ligne { flex-wrap: wrap; }
body.f-simulateurEpure.mobile-mode .sim-vm-choix { margin-left: 0; width: 100%; max-width: 100%; font-size: 16px; }
@media (max-width: 700px) {
  body.f-simulateurEpure .sim-camp-moi .sim-camp-ligne { flex-wrap: wrap; }
  body.f-simulateurEpure .sim-vm-choix { margin-left: 0; width: 100%; max-width: 100%; font-size: 16px; }
  body.f-simulateurEpure .sim-ligne,
  body.f-simulateurEpure .sim-ligne-res { grid-template-columns: 1.2rem minmax(0,1fr) 6rem 4.4rem; }
  body.f-simulateurEpure .sim-ligne-tech { grid-template-columns: minmax(0,1fr) 6rem; }
  body.f-simulateurEpure .sim-ligne .rp-input { padding-top: .3rem; padding-bottom: .3rem; }
}
/* simulateurEpure — « Tous les bonus en jeu » : le niveau (ou le vaisseau,
   ou le budget des Dômes) monte sur la ligne du bonus ; le paragraphe
   d'explication passe au survol de la ligne. */
body.f-simulateurEpure .sim-bonus-src-ligne {
  font-size: .82rem; font-style: italic; color: var(--text2);
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur rampeRapportsDuo — LES RAPPORTS DE LA RAMPE CÔTE À CÔTE
   ════════════════════════════════════════════════════════════════════
   Loryan : « les rapports d'espionnage à gauche, les rapports de combat à
   droite ». Deux colonnes FIXES : un camp vide laisse sa moitié vide plutôt
   que de prêter sa place à l'autre — l'espionnage reste toujours à gauche.
   Au téléphone (la classe ET la largeur réelle), les deux listes
   s'empilent comme avant. */
body.f-rampeRapportsDuo .fl-rapports {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0 14px; align-items: start;
}
body.f-rampeRapportsDuo .fl-rapports > #fl-spy-section { grid-column: 1; }
body.f-rampeRapportsDuo .fl-rapports > #fl-raid-section { grid-column: 2; }
body.f-rampeRapportsDuo.mobile-mode .fl-rapports { display: block; }
@media (max-width: 700px) {
  body.f-rampeRapportsDuo .fl-rapports { display: block; }
}

/* ════════════════════════════════════════════════════════════════════
   Interrupteur classementConsolide — L'ÉCRAN CONSOLIDÉ DES CLASSEMENTS
   ════════════════════════════════════════════════════════════════════
   Barre latérale des classements (ta place dans chacun), podium or /
   argent / bronze, puis le classement complet avec la composition des
   points (features/leaderboard/leaderboard.js, `_lbcRendu`). Sous
   classementBandeau, un bandeau en tête.
   ⚠️ LES COULEURS DE COMPOSITION SONT VOLONTAIREMENT ADOUCIES (demande de
   Loryan : « des couleurs un peu moins vives pour les barres ») : ce ne
   sont PAS celles du camembert des Statistiques, et il ne faut pas les
   « harmoniser » avec elles.
   ⚠️ TOUTE RÈGLE TÉLÉPHONE EXISTE DEUX FOIS, à l'identique : sous
   `@media (max-width: 700px)` ET sous `body.mobile-mode` (voir CLAUDE.md,
   les deux systèmes d'affichage mobile). */
body.f-classementConsolide #lb-cat-tabs,
body.f-classementConsolide #dsh-view-leaderboard .dsh-view-subtitle { display: none; }
body.f-classementConsolide .lbc {
  --lbc-economy: #c49a48; --lbc-research: #9688c6; --lbc-fleet: #c27777;
  --lbc-defense: #57a58a; --lbc-exploration: #569ec2; --lbc-exploit: #c98d5c; --lbc-command: #b07fb8;
  display: flex; flex-direction: column; gap: 16px; }
body.f-classementConsolide .lbc-seg-economy { background: var(--lbc-economy); }
body.f-classementConsolide .lbc-seg-research { background: var(--lbc-research); }
body.f-classementConsolide .lbc-seg-fleet { background: var(--lbc-fleet); }
body.f-classementConsolide .lbc-seg-defense { background: var(--lbc-defense); }
body.f-classementConsolide .lbc-seg-exploration { background: var(--lbc-exploration); }
body.f-classementConsolide .lbc-seg-exploit { background: var(--lbc-exploit); }
body.f-classementConsolide .lbc-seg-command { background: var(--lbc-command); }
body.f-classementConsolide .lbc-seg-general { background: var(--lbc-economy); }
body.f-classementConsolide .lbc-txt-economy { color: var(--lbc-economy); }
body.f-classementConsolide .lbc-txt-research { color: var(--lbc-research); }
body.f-classementConsolide .lbc-txt-fleet { color: var(--lbc-fleet); }
body.f-classementConsolide .lbc-txt-defense { color: var(--lbc-defense); }
body.f-classementConsolide .lbc-txt-exploration { color: var(--lbc-exploration); }

/* Le bandeau : six cases, une ligne. */
body.f-classementConsolide .lbc-bandeau { display: grid; grid-template-columns: minmax(0, 1.25fr) repeat(4, minmax(0, 1fr)) minmax(0, 1.4fr);
  border: 1px solid var(--border2, #1e3060); border-radius: 12px; overflow: hidden;
  background: linear-gradient(90deg, rgba(45, 212, 191, .10), rgba(8, 16, 31, .9) 55%); }
body.f-classementConsolide .lbc-bandeau > div { display: flex; flex-direction: column; justify-content: center; gap: 3px; padding: 14px 16px; min-width: 0; }
body.f-classementConsolide .lbc-bandeau > div + div { border-left: 1px solid #162040; }
body.f-classementConsolide .lbc-b-lab { font-family: var(--hud); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--text2, #8da4c4); }
body.f-classementConsolide .lbc-b-toi { color: #2dd4bf; }
body.f-classementConsolide .lbc-b-nom { font-size: 1.3rem; font-weight: 700; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.f-classementConsolide .lbc-b-val { font-family: var(--mono); font-size: 1.75rem; font-weight: 700; color: #fff; line-height: 1.1; font-variant-numeric: tabular-nums; }
body.f-classementConsolide .lbc-b-val .lbc-ev { font-size: .9rem; vertical-align: 4px; }
body.f-classementConsolide .lbc-b-retard { color: #e9a467; }
body.f-classementConsolide .lbc-b-bon { color: #57c79d; }
body.f-classementConsolide .lbc-b-sous { font-size: 12px; color: var(--text2, #8da4c4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.f-classementConsolide .lbc-b-compo .lbc-barre { height: 12px; margin: 4px 0 2px; }
/* Interrupteur classementBandeauSuit : dans un classement précis, sa part
   ressort de la composition (les autres pâlissent), et une part simple se
   lit sur un fond de barre. */
body.f-classementConsolide .lbc-seg-pale { opacity: .28; }
body.f-classementConsolide .lbc-b-part { background: #111a33; }

/* Le corps : la barre latérale, puis le principal. */
body.f-classementConsolide .lbc-corps { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 18px; align-items: start; }
body.f-classementConsolide .lbc-cote { display: flex; flex-direction: column; gap: 3px; padding: 12px 10px; background: rgba(8, 16, 31, .85);
  border: 1px solid #162040; border-radius: 12px; position: sticky; top: 8px; }
body.f-classementConsolide .lbc-famille { font-family: var(--hud); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: #6b8ab0; margin: 8px 6px 4px; }
body.f-classementConsolide .lbc-famille:first-child { margin-top: 2px; }
body.f-classementConsolide .lbc-cat { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto 30px; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border-radius: 8px; border: 1px solid transparent; background: transparent; color: var(--text2, #8da4c4);
  font: inherit; font-size: 1rem; text-align: left; cursor: pointer; }
body.f-classementConsolide .lbc-cat:hover { background: rgba(14, 165, 233, .08); color: var(--text, #c8d6f0); }
body.f-classementConsolide .lbc-cat:focus-visible { outline: 2px solid var(--accent2, #0ea5e9); outline-offset: 1px; }
body.f-classementConsolide .lbc-cat.on { border-color: var(--gold, #f59e0b); background: rgba(245, 158, 11, .12); color: var(--gold, #f59e0b); font-weight: 600; }
body.f-classementConsolide .lbc-cat-nom { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.f-classementConsolide .lbc-cat-rang { font-family: var(--mono); font-size: 12px; color: #2dd4bf; background: rgba(45, 212, 191, .12); border-radius: 5px; padding: 2px 6px; }
body.f-classementConsolide .lbc-cat-rang.lbc-vide { color: #6b8ab0; background: transparent; }
body.f-classementConsolide .lbc-ev { font-family: var(--mono); font-size: 11px; text-align: right; color: #6b8ab0; }
body.f-classementConsolide .lbc-ev-haut { color: #57c79d; }
body.f-classementConsolide .lbc-ev-bas { color: #e07b7b; }
body.f-classementConsolide .lbc-cote-note { margin: 10px 6px 2px; font-size: 11px; color: #6b8ab0; }

body.f-classementConsolide .lbc-principal { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
body.f-classementConsolide .lbc-tete { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
body.f-classementConsolide .lbc-tete-txt { min-width: 0; flex: 1 1 320px; }
body.f-classementConsolide .lbc-titre { margin: 0; font-size: 1.6rem; color: var(--gold, #f59e0b); }
body.f-classementConsolide .lbc-desc { margin: 4px 0 0; font-size: .93rem; color: var(--text2, #8da4c4); }
body.f-classementConsolide .lbc-reglages { display: flex; gap: 10px; flex-wrap: wrap; }
body.f-classementConsolide .lbc-choix { display: flex; border: 1px solid var(--border2, #1e3060); border-radius: 8px; overflow: hidden; }
body.f-classementConsolide .lbc-choix button { font: inherit; font-size: .93rem; padding: 7px 12px; border: 0; background: transparent; color: var(--text2, #8da4c4); cursor: pointer; white-space: nowrap; }
body.f-classementConsolide .lbc-choix button.on { background: rgba(14, 165, 233, .16); color: #e6eeff; }
body.f-classementConsolide .lbc-choix button:focus-visible { outline: 2px solid var(--accent2, #0ea5e9); outline-offset: -2px; }

/* Le podium : un escalier discret, or au centre. */
body.f-classementConsolide .lbc-podium { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; align-items: end; padding: 0 4%; }
body.f-classementConsolide .lbc-marche { display: flex; align-items: center; gap: 14px; padding: 12px 18px; border: 1px solid; border-radius: 12px 12px 4px 4px; min-width: 0; }
body.f-classementConsolide .lbc-place-1 { order: 2; min-height: 108px; }
body.f-classementConsolide .lbc-place-2 { order: 1; min-height: 96px; }
body.f-classementConsolide .lbc-place-3 { order: 3; min-height: 90px; }
body.f-classementConsolide .lbc-medaille { font-size: 2rem; line-height: 1; }
body.f-classementConsolide .lbc-marche-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
body.f-classementConsolide .lbc-marche-metal { font-family: var(--hud); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; }
body.f-classementConsolide .lbc-marche-nom { font-size: 1.15rem; font-weight: 700; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.f-classementConsolide .lbc-marche-val { font-family: var(--mono); font-size: 1.6rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
body.f-classementConsolide .lbc-marche-sous { font-family: var(--mono); font-size: 12px; color: var(--text2, #8da4c4); }
body.f-classementConsolide .lbc-or { border-color: #d4af37; background: linear-gradient(135deg, rgba(212, 175, 55, .34), rgba(255, 233, 150, .10) 45%, rgba(150, 110, 20, .30)), #0d1526;
  box-shadow: inset 0 1px 0 rgba(255, 240, 190, .40), 0 0 22px rgba(212, 175, 55, .14); }
body.f-classementConsolide .lbc-or .lbc-marche-metal, body.f-classementConsolide .lbc-or .lbc-marche-val { color: #f0cf6a; }
body.f-classementConsolide .lbc-argent { border-color: #c0c8d4; background: linear-gradient(135deg, rgba(192, 200, 212, .30), rgba(240, 245, 250, .08) 45%, rgba(110, 120, 135, .28)), #0d1526;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35); }
body.f-classementConsolide .lbc-argent .lbc-marche-metal, body.f-classementConsolide .lbc-argent .lbc-marche-val { color: #e2e8f0; }
body.f-classementConsolide .lbc-bronze { border-color: #cd7f32; background: linear-gradient(135deg, rgba(205, 127, 50, .32), rgba(240, 180, 120, .08) 45%, rgba(120, 70, 25, .30)), #0d1526;
  box-shadow: inset 0 1px 0 rgba(250, 205, 160, .35); }
body.f-classementConsolide .lbc-bronze .lbc-marche-metal, body.f-classementConsolide .lbc-bronze .lbc-marche-val { color: #e9a467; }
body.f-classementConsolide .lbc-marche.est-moi { outline: 2px solid #2dd4bf; outline-offset: 2px; }

/* Le classement complet : une grille par ligne. */
body.f-classementConsolide .lbc-sous-titre { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  font-family: var(--hud); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: #6b8ab0; }
body.f-classementConsolide .lbc-legende { display: flex; flex-wrap: wrap; gap: 14px; font-family: var(--body); font-size: 13px; letter-spacing: 0; text-transform: none; color: var(--text, #c8d6f0); }
body.f-classementConsolide .lbc-legende i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 5px; }
body.f-classementConsolide .lbc-table { border: 1px solid var(--border2, #1e3060); border-radius: 10px; overflow: hidden; }
body.f-classementConsolide .lbc-entete, body.f-classementConsolide .lbc-ligne { display: grid; gap: 12px; align-items: center; padding: 0 14px; }
body.f-classementConsolide .lbc-mode-general .lbc-entete, body.f-classementConsolide .lbc-mode-general .lbc-ligne { grid-template-columns: 72px minmax(100px, 1fr) minmax(120px, 2.2fr) 80px 84px 128px 104px; }
body.f-classementConsolide .lbc-mode-cat .lbc-entete, body.f-classementConsolide .lbc-mode-cat .lbc-ligne { grid-template-columns: 72px minmax(100px, 1fr) minmax(120px, 2.2fr) 80px 84px 104px; }
body.f-classementConsolide .lbc-mode-exploit .lbc-entete, body.f-classementConsolide .lbc-mode-exploit .lbc-ligne { grid-template-columns: 72px minmax(100px, 1fr) minmax(120px, 2.2fr) 110px 104px; }
body.f-classementConsolide .lbc-entete { padding-block: 10px; background: #0a1124; border-bottom: 2px solid var(--accent2, #0ea5e9);
  font-family: var(--hud); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: #38bdf8; }
body.f-classementConsolide .lbc-entete > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.f-classementConsolide .lbc-entete .lbc-d { text-align: right; }
body.f-classementConsolide .lbc-entete .is-tri { color: #e6eeff; }
body.f-classementConsolide .lbc-ligne { min-height: 46px; border-bottom: 1px solid #111a33; }
body.f-classementConsolide .lbc-ligne:last-child { border-bottom: 0; }
body.f-classementConsolide .lbc-ligne:hover { background: rgba(14, 165, 233, .05); }
body.f-classementConsolide .lbc-ligne.est-moi { background: rgba(45, 212, 191, .09); }
body.f-classementConsolide .lbc-joueur { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1rem; color: #e6eeff; }
body.f-classementConsolide .lbc-barre-cell { min-width: 0; }
body.f-classementConsolide .lbc-barre { display: flex; gap: 2px; height: 14px; border-radius: 4px; overflow: hidden; }
body.f-classementConsolide .lbc-barre i { display: block; height: 100%; }
body.f-classementConsolide .lbc-num { font-family: var(--mono); text-align: right; font-variant-numeric: tabular-nums; color: #6b8ab0; font-size: .93rem; }
body.f-classementConsolide .lbc-num.is-tri { color: #fff; font-weight: 700; font-size: 1rem; }
body.f-classementConsolide .lbc-fort { font-size: .93rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.f-classementConsolide .lbc-actions { display: flex; justify-content: flex-end; gap: 4px; }

/* Grand écran étroit : la colonne « Point fort » cède sa place à la barre. */
@media (min-width: 701px) and (max-width: 1250px) {
  body.f-classementConsolide .lbc-mode-general .lbc-entete, body.f-classementConsolide .lbc-mode-general .lbc-ligne { grid-template-columns: 64px minmax(90px, 1fr) minmax(110px, 2fr) 76px 80px 104px; }
  body.f-classementConsolide .lbc-mode-general .lbc-fort, body.f-classementConsolide .lbc-mode-general .lbc-entete > span:nth-child(6) { display: none; }
  body.f-classementConsolide .lbc-mode-cat .lbc-entete, body.f-classementConsolide .lbc-mode-cat .lbc-ligne { grid-template-columns: 64px minmax(90px, 1fr) minmax(100px, 2fr) 72px 76px 100px; gap: 10px; }
  body.f-classementConsolide .lbc-mode-exploit .lbc-entete, body.f-classementConsolide .lbc-mode-exploit .lbc-ligne { grid-template-columns: 64px minmax(90px, 1fr) minmax(100px, 2fr) 100px 100px; gap: 10px; }
  body.f-classementConsolide .lbc-bandeau { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  body.f-classementConsolide .lbc-bandeau > div:nth-child(4) { border-left: 0; }
  body.f-classementConsolide .lbc-bandeau > div:nth-child(n+4) { border-top: 1px solid #162040; }
}

/* Téléphone, par la largeur réelle de la fenêtre. */
@media (max-width: 700px) {
body.f-classementConsolide .lbc-bandeau { grid-template-columns: repeat(2, minmax(0, 1fr)); }
body.f-classementConsolide .lbc-bandeau > div { padding: 10px 12px; border-top: 1px solid #162040; }
body.f-classementConsolide .lbc-bandeau > div + div { border-left: 0; }
body.f-classementConsolide .lbc-bandeau > div:nth-child(even) { border-left: 1px solid #162040; }
body.f-classementConsolide .lbc-bandeau > .lbc-b-id, body.f-classementConsolide .lbc-bandeau > .lbc-b-compo { grid-column: 1 / -1; border-left: 0; }
body.f-classementConsolide .lbc-bandeau > .lbc-b-id { border-top: 0; }
body.f-classementConsolide .lbc-b-val { font-size: 1.4rem; }
body.f-classementConsolide .lbc-b-sous { white-space: normal; }
body.f-classementConsolide .lbc-corps { grid-template-columns: minmax(0, 1fr); gap: 12px; }
body.f-classementConsolide .lbc-cote { position: static; flex-direction: row; overflow-x: auto; gap: 6px; padding: 6px; scrollbar-width: thin; }
body.f-classementConsolide .lbc-famille, body.f-classementConsolide .lbc-cote-note { display: none; }
body.f-classementConsolide .lbc-cat { flex: none; width: auto; grid-template-columns: auto auto auto auto; gap: 6px; padding: 7px 10px; border-color: var(--border2, #1e3060); }
body.f-classementConsolide .lbc-titre { font-size: 1.35rem; }
body.f-classementConsolide .lbc-reglages, body.f-classementConsolide .lbc-choix { width: 100%; }
body.f-classementConsolide .lbc-choix button { flex: 1; padding: 8px 6px; }
body.f-classementConsolide .lbc-podium { grid-template-columns: minmax(0, 1fr); gap: 8px; padding: 0; }
body.f-classementConsolide .lbc-place-1, body.f-classementConsolide .lbc-place-2, body.f-classementConsolide .lbc-place-3 { order: 0; min-height: 0; }
body.f-classementConsolide .lbc-marche { padding: 8px 12px; border-radius: 10px; }
body.f-classementConsolide .lbc-medaille { font-size: 1.6rem; }
body.f-classementConsolide .lbc-marche-txt { flex: 1; display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; }
body.f-classementConsolide .lbc-marche-metal { grid-column: 1 / -1; }
body.f-classementConsolide .lbc-marche-val { font-size: 1.25rem; text-align: right; }
body.f-classementConsolide .lbc-marche-sous { grid-column: 1 / -1; }
body.f-classementConsolide .lbc-entete { display: none; }
body.f-classementConsolide .lbc-table .lbc-ligne { grid-template-columns: 48px minmax(0, 1fr) auto auto; grid-template-areas: "rang joueur val act" "barre barre barre barre";
  row-gap: 6px; column-gap: 8px; padding: 8px 12px; }
body.f-classementConsolide .lbc-ligne .lbc-rang { grid-area: rang; }
body.f-classementConsolide .lbc-ligne .lbc-joueur { grid-area: joueur; }
body.f-classementConsolide .lbc-ligne .lbc-num.is-tri { grid-area: val; }
body.f-classementConsolide .lbc-ligne .lbc-actions { grid-area: act; }
body.f-classementConsolide .lbc-ligne .lbc-barre-cell { grid-area: barre; }
body.f-classementConsolide .lbc-ligne .lbc-num:not(.is-tri), body.f-classementConsolide .lbc-ligne .lbc-fort { display: none; }
body.f-classementConsolide .lbc-ligne .lbc-barre { height: 8px; }
}
/* Téléphone, par le mode mobile (même règles, à l'identique). */
body.f-classementConsolide.mobile-mode .lbc-bandeau { grid-template-columns: repeat(2, minmax(0, 1fr)); }
body.f-classementConsolide.mobile-mode .lbc-bandeau > div { padding: 10px 12px; border-top: 1px solid #162040; }
body.f-classementConsolide.mobile-mode .lbc-bandeau > div + div { border-left: 0; }
body.f-classementConsolide.mobile-mode .lbc-bandeau > div:nth-child(even) { border-left: 1px solid #162040; }
body.f-classementConsolide.mobile-mode .lbc-bandeau > .lbc-b-id, body.f-classementConsolide.mobile-mode .lbc-bandeau > .lbc-b-compo { grid-column: 1 / -1; border-left: 0; }
body.f-classementConsolide.mobile-mode .lbc-bandeau > .lbc-b-id { border-top: 0; }
body.f-classementConsolide.mobile-mode .lbc-b-val { font-size: 1.4rem; }
body.f-classementConsolide.mobile-mode .lbc-b-sous { white-space: normal; }
body.f-classementConsolide.mobile-mode .lbc-corps { grid-template-columns: minmax(0, 1fr); gap: 12px; }
body.f-classementConsolide.mobile-mode .lbc-cote { position: static; flex-direction: row; overflow-x: auto; gap: 6px; padding: 6px; scrollbar-width: thin; }
body.f-classementConsolide.mobile-mode .lbc-famille, body.f-classementConsolide.mobile-mode .lbc-cote-note { display: none; }
body.f-classementConsolide.mobile-mode .lbc-cat { flex: none; width: auto; grid-template-columns: auto auto auto auto; gap: 6px; padding: 7px 10px; border-color: var(--border2, #1e3060); }
body.f-classementConsolide.mobile-mode .lbc-titre { font-size: 1.35rem; }
body.f-classementConsolide.mobile-mode .lbc-reglages, body.f-classementConsolide.mobile-mode .lbc-choix { width: 100%; }
body.f-classementConsolide.mobile-mode .lbc-choix button { flex: 1; padding: 8px 6px; }
body.f-classementConsolide.mobile-mode .lbc-podium { grid-template-columns: minmax(0, 1fr); gap: 8px; padding: 0; }
body.f-classementConsolide.mobile-mode .lbc-place-1, body.f-classementConsolide.mobile-mode .lbc-place-2, body.f-classementConsolide.mobile-mode .lbc-place-3 { order: 0; min-height: 0; }
body.f-classementConsolide.mobile-mode .lbc-marche { padding: 8px 12px; border-radius: 10px; }
body.f-classementConsolide.mobile-mode .lbc-medaille { font-size: 1.6rem; }
body.f-classementConsolide.mobile-mode .lbc-marche-txt { flex: 1; display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; }
body.f-classementConsolide.mobile-mode .lbc-marche-metal { grid-column: 1 / -1; }
body.f-classementConsolide.mobile-mode .lbc-marche-val { font-size: 1.25rem; text-align: right; }
body.f-classementConsolide.mobile-mode .lbc-marche-sous { grid-column: 1 / -1; }
body.f-classementConsolide.mobile-mode .lbc-entete { display: none; }
body.f-classementConsolide.mobile-mode .lbc-table .lbc-ligne { grid-template-columns: 48px minmax(0, 1fr) auto auto; grid-template-areas: "rang joueur val act" "barre barre barre barre";
  row-gap: 6px; column-gap: 8px; padding: 8px 12px; }
body.f-classementConsolide.mobile-mode .lbc-ligne .lbc-rang { grid-area: rang; }
body.f-classementConsolide.mobile-mode .lbc-ligne .lbc-joueur { grid-area: joueur; }
body.f-classementConsolide.mobile-mode .lbc-ligne .lbc-num.is-tri { grid-area: val; }
body.f-classementConsolide.mobile-mode .lbc-ligne .lbc-actions { grid-area: act; }
body.f-classementConsolide.mobile-mode .lbc-ligne .lbc-barre-cell { grid-area: barre; }
body.f-classementConsolide.mobile-mode .lbc-ligne .lbc-num:not(.is-tri), body.f-classementConsolide.mobile-mode .lbc-ligne .lbc-fort { display: none; }
body.f-classementConsolide.mobile-mode .lbc-ligne .lbc-barre { height: 8px; }

/* ════════════════════════════════════════════════════════════════════
   Réglage serveur `connexionEmail` — L'ÉCRAN DE CONNEXION PAR E-MAIL
   ════════════════════════════════════════════════════════════════════
   ⚠️ TOUT EST SOUS `.auth-mode-email` (posé par AuthUI seulement quand
   l'écran e-mail est actif) ou porte sur un élément NEUF : l'écran d'avant
   ne bouge pas d'un pixel tant que le réglage est éteint. Les deux règles
   `:empty` / `display:none` existent pour la même raison — un élément vide
   dans une rangée en colonne compterait quand même un écart.

   Les tailles visent le plancher de lisibilité du jeu (`--badge-font-size`),
   que l'écran d'avant n'atteint pas : on remonte ce qu'on touche. */
.auth-pseudo-etat:empty{display:none}
.auth-pseudo-etat{font-size:.82rem;line-height:1.3;color:var(--text2)}
.auth-pseudo-etat.libre{color:#4ade80}
.auth-pseudo-etat.pris{color:var(--accent1)}
.auth-lien-oubli{
  align-self:flex-end;font-size:.82rem;color:var(--accent2);text-decoration:none;
}
.auth-lien-oubli:hover{text-decoration:underline}
/* Quatre champs et une ligne d'état ne tiennent pas toujours en hauteur sur
   un téléphone : la fenêtre défile, et la boîte reste centrée tant qu'il y
   a la place (`margin:auto` dans un conteneur flex qui défile, là où un
   centrage par `align-items` couperait le haut de la boîte). */
#auth-modal.auth-mode-email{overflow-y:auto;align-items:flex-start;padding-top:64px}
/* ⚠️ LE RETRAIT DU HAUT laisse la place au logo et au bouton de mode, qui
   restent peints PAR-DESSUS la fenêtre de connexion : sans lui, au téléphone,
   le formulaire d'inscription (le plus haut) glissait sous eux. */
#auth-modal.auth-mode-email .auth-box{margin:auto}
#auth-modal.auth-mode-email .auth-subtitle{font-size:.86rem}
#auth-modal.auth-mode-email .auth-row label{font-size:.8rem;letter-spacing:.08em}
#auth-modal.auth-mode-email .auth-error{font-size:.86rem;line-height:1.45}
#auth-modal.auth-mode-email .auth-switch{font-size:.86rem}
#auth-modal.auth-mode-email .auth-submit{font-size:.86rem}
/* La modale « Mon adresse e-mail » (même habillage que le mot de passe). */
.compte-menu-alerte{
  margin-left:auto;padding:1px 7px;border-radius:9px;font-size:.8rem;
  background:rgba(251,191,36,.15);color:var(--gold);
}
.compte-email-actuelle{margin:0 0 12px;font-size:.86rem;line-height:1.6;color:var(--text);overflow-wrap:anywhere}
.compte-email-etat{font-size:.82rem}
.compte-email-etat.ok{color:#4ade80}
.compte-email-etat.attente{color:var(--gold)}
.compte-email-renvoyer{
  margin-left:6px;padding:3px 9px;border-radius:6px;cursor:pointer;font:inherit;font-size:.82rem;
  background:rgba(56,189,248,.1);border:1px solid rgba(56,189,248,.4);color:var(--accent2);
}
.compte-email-renvoyer:disabled{opacity:.7;cursor:default}
/* L'avis aux anciens joueurs : « c'est avec ton e-mail que tu te connecteras
   désormais ». Un encadré, parce qu'une phrase de plus dans le sous-titre se
   lit en diagonale — et celle-ci change la façon d'entrer dans le jeu. */
#auth-modal.auth-mode-email .auth-avis{
  margin-top:.7rem;padding:.6rem .75rem;text-align:left;line-height:1.5;
  border-left:3px solid var(--gold);border-radius:4px;
  background:rgba(251,191,36,.08);color:var(--text);
}
.compte-email-delai{display:inline-block;margin-top:6px;font-size:.82rem;color:var(--text2);line-height:1.45}
