/* =============================================================================
   ForgeHub — la DA d'Unreal, telle quelle.

   Les jetons sont ceux de `DevInterface/src/styles/index.css` : mêmes rubis,
   mêmes rayons, mêmes biseaux. Ce n'est pas de la coquetterie — le panneau doit
   se lire comme faisant partie du même serveur que le jeu, pas comme un outil
   tiers collé à côté.

   La première version était en `system-ui` sur des aplats gris. Elle
   fonctionnait et ne ressemblait à rien.
   ========================================================================== */

/* Montserrat, SERVIE PAR NOUS.

   Jamais depuis Google : le panneau s'atteint parfois depuis un réseau
   d'entreprise, et une police qui ne charge pas fait basculer toute la mise en
   page au pire moment. C'est le même fichier que le jeu — une police VARIABLE,
   d'où l'axe `100 900` déclaré une fois. */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  src: url('/fonts/montserrat-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  src: url('/fonts/montserrat-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

:root {
  --veqta-bg: #0b0d12;
  --veqta-surface: rgba(28, 32, 41, 0.92);
  --veqta-border: rgba(255, 255, 255, 0.08);

  --veqta-text: #e8eaef;
  --veqta-text-muted: #8b93a5;

  --veqta-accent: #4c8dff;
  --veqta-success: #3ecf8e;
  --veqta-warning: #f5a524;
  --veqta-error: #f04438;

  /* LE VIOLET D'UNREAL, RELEVE SUR LE LOGO LUI-MEME.

     Pas choisi a l'oeil : echantillonne sur `DA/UnrealGIF.gif`, dont les teintes
     dominantes vont de #501080 au coeur a #a040e0 sur les eclats. La premiere
     version du panneau etait en rubis — c'est la teinte de `logo-plain.webp`,
     l'autre variante de la marque, et pas celle du logo anime. */
  --unreal-start: #7c2ae8;
  --unreal-end: #3d0f7a;
  --unreal-deep: #200040;
  --unreal-bright: #a44dff;
  --unreal-soft: #c9a4ff;
  --unreal-pale: #ece0ff;

  --unreal-start-rgb: 124, 42, 232;
  --unreal-end-rgb: 61, 15, 122;

  --unreal-gradient: linear-gradient(90deg, var(--unreal-start) 0%, var(--unreal-end) 100%);
  /* CES DEUX-LA ETAIENT ECRITES EN DUR, ET ELLES ONT SURVECU AU PASSAGE AU
     VIOLET : renommer `--ruby-*` en `--unreal-*` a change les NOMS, pas les
     valeurs. Le lisere de la carte de connexion et toutes les lueurs de
     l'interface sont restes rubis alors que le reste avait bascule — un rouge
     qu'on voyait sans trouver d'ou il venait, puisque plus aucune regle ne
     nommait le rouge. */
  --unreal-border: rgba(124, 42, 232, 0.4);
  --unreal-glow: rgba(164, 77, 255, 0.35);

  --unreal-round: 10px;
  --unreal-round-lg: 14px;

  /* La lumière vient du haut-gauche, comme sur une pierre posée. */
  --unreal-bevel-light: rgba(255, 255, 255, 0.14);
  --unreal-bevel-dark: rgba(0, 0, 0, 0.5);

  --veqta-font: 'Montserrat', system-ui, 'Segoe UI', -apple-system, sans-serif;
  --mono: ui-monospace, 'Cascadia Mono', Menlo, Consolas, monospace;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* `hidden` DOIT l'emporter sur nos `display`.

   La règle du navigateur — `[hidden] { display: none }` — a une priorité très
   faible : n'importe quel sélecteur de classe la bat. `.ecran { display: flex }`
   et `.panneau { display: grid }` la battaient toutes les deux, et le formulaire
   de connexion restait AFFICHÉ par-dessus le panneau après une connexion
   réussie — « j'appuie sur se connecter, ça fait rien ».

   Le `!important` est justifié : `hidden` n'est pas un style, c'est un état du
   document. Rien ne doit pouvoir le contredire. */
[hidden] { display: none !important; }

body {
  min-height: 100vh;

  /* Une lueur rubis qui monte du bas, sur un fond presque noir. C'est le geste
     de toutes nos surfaces — voir les tuiles du HUD et la coque de la banque. */
  background:
    radial-gradient(1200px 600px at 50% 120%, rgba(var(--unreal-start-rgb), 0.16) 0%, transparent 70%),
    var(--veqta-bg);

  color: var(--veqta-text);
  font-family: var(--veqta-font);
  font-size: 14px;
  line-height: 1.55;
  letter-spacing: 0.005em;

  -webkit-font-smoothing: antialiased;
  font-feature-settings: 'kern' 1, 'liga' 1;
}

::selection { background: rgba(var(--unreal-start-rgb), 0.35); }

/* Une barre de défilement qui appartient à la DA. La barre système, claire et
   large, trahit le navigateur au milieu d'une interface sombre. */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  border-radius: 8px;
  background-clip: content-box;
  background-color: rgba(255, 255, 255, 0.14);
}
::-webkit-scrollbar-thumb:hover { background-color: rgba(var(--unreal-start-rgb), 0.6); }

/* ── La marque ───────────────────────────────────────────────────────────── */

/* Le sceau d'Unreal sur l'écran de connexion.

   En grand et animé : c'est la seule page où l'on a le temps de le regarder, et
   c'est elle qui doit dire de quel serveur il s'agit avant même qu'on ait tapé
   un identifiant. `screen` efface le fond noir du GIF — sans lui, un carré mat
   flotte au-dessus de la carte translucide. */
.sceau {
  display: block;
  width: 96px;
  height: 96px;
  margin: 0 auto 14px;

  /* LE GIF EST UN CARRE OPAQUE, PAS UN LOGO DETOURE.

     `mix-blend-mode: screen` efface son fond noir sur une surface sombre, mais
     la carte de connexion a un degrade violet en haut : le noir du GIF y
     redevient visible et dessine un carre net autour du logo. Le rayon et le
     recadrage rendent ce bord acceptable — et le masque radial mange les quatre
     angles, la ou le fond du GIF depasse le plus. */
  border-radius: 22px;
  object-fit: cover;

  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 62%, transparent 88%);
  mask-image: radial-gradient(circle at 50% 50%, #000 62%, transparent 88%);

  mix-blend-mode: screen;
  filter: drop-shadow(0 6px 26px var(--unreal-glow));
}

.marque {
  font-size: 27px;
  font-weight: 800;
  letter-spacing: 3px;
  line-height: 1;
}

.marque-un { color: #fff; }

/* Seul « REAL » porte le dégradé : le mot entier en couleur perd sa lisibilité
   sur fond sombre, et la coupure fait la signature. */
.marque-real {
  background: var(--unreal-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.sous-marque {
  margin: 6px 0 28px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--veqta-text-muted);
}

/* ── Connexion ───────────────────────────────────────────────────────────── */

.ecran {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 20px;
}

.carte-connexion {
  width: min(410px, 100%);
  padding: 40px 34px 34px;

  border: 1px solid var(--unreal-border);
  border-radius: var(--unreal-round-lg);

  background:
    radial-gradient(600px 200px at 50% -10%, rgba(var(--unreal-start-rgb), 0.18) 0%, transparent 70%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, transparent 40%),
    rgba(10, 11, 15, 0.96);

  box-shadow:
    inset 0 1px 0 var(--unreal-bevel-light),
    0 40px 120px rgba(0, 0, 0, 0.7),
    0 0 60px rgba(var(--unreal-start-rgb), 0.08);

  animation: fh-in 300ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes fh-in {
  from { opacity: 0; transform: translateY(16px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

label { display: block; margin-bottom: 18px; }

label > span {
  display: block;
  margin-bottom: 7px;

  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--veqta-text-muted);
}

input, select, textarea {
  width: 100%;
  padding: 12px 14px;

  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--unreal-round);
  background: rgba(255, 255, 255, 0.035);

  font: inherit;
  color: var(--veqta-text);
  outline: none;

  transition: border-color 140ms, box-shadow 140ms, background 140ms;
}

input:focus, select:focus, textarea:focus {
  border-color: var(--unreal-bright);
  background: rgba(255, 255, 255, 0.055);
  box-shadow: 0 0 0 3px rgba(var(--unreal-start-rgb), 0.16);
}

input::placeholder { color: rgba(255, 255, 255, 0.28); }

button {
  padding: 11px 20px;

  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--unreal-round);
  background: rgba(255, 255, 255, 0.045);

  font: inherit;
  font-weight: 600;
  color: var(--veqta-text);
  cursor: pointer;

  transition: background 140ms, filter 140ms, transform 90ms, border-color 140ms;
}

button:hover { background: rgba(255, 255, 255, 0.09); border-color: rgba(255, 255, 255, 0.18); }
button:active { transform: translateY(1px); }
button:disabled { opacity: 0.35; cursor: default; transform: none; }

.carte-connexion button[type="submit"],
button.primaire {
  border-color: transparent;
  background: var(--unreal-gradient);
  color: #fff;
  box-shadow:
    inset 0 1px 0 var(--unreal-bevel-light),
    0 6px 20px rgba(var(--unreal-start-rgb), 0.28);
}

.carte-connexion button[type="submit"] { width: 100%; margin-top: 10px; padding: 13px; }
.carte-connexion button[type="submit"]:hover,
button.primaire:hover { filter: brightness(1.14); }

button.danger { color: #ff8f9c; border-color: rgba(240, 68, 56, 0.28); }
button.danger:hover { background: rgba(240, 68, 56, 0.14); }

.erreur {
  margin: 0 0 16px;
  padding: 11px 13px;

  border-left: 3px solid var(--veqta-error);
  border-radius: 0 var(--unreal-round) var(--unreal-round) 0;
  background: rgba(240, 68, 56, 0.1);

  font-size: 13px;
  color: var(--unreal-soft);
}

/* ── La coque ────────────────────────────────────────────────────────────────

   UNE FENÊTRE POSÉE SUR LE FOND, PAS UNE PAGE COLLÉE AUX BORDS.

   Le panneau touchait les quatre bords de l'écran, comme une page de
   documentation. On l'a décollé : marge tout autour, gros rayon, et la lueur
   rubis du `body` qui passe derrière. C'est le même geste que les surfaces du
   jeu — une plaque posée sur une lumière, pas un fond peint.

   `100dvh` et non `100vh` : sur un portable, la barre d'adresse mobile mange
   une centaine de pixels que `vh` ignore, et le bas du panneau passait sous
   elle sans qu'on puisse l'atteindre. */
.panneau {
  display: flex;
  flex-direction: column;

  height: 100dvh;
  padding: 14px;
  gap: 12px;
}

/* ── La barre du haut ───────────────────────────────────────────────────── */

.barre {
  display: flex;
  align-items: center;
  gap: 20px;

  flex: none;
  padding: 10px 14px 10px 18px;

  border: 1px solid var(--veqta-border);
  border-radius: 18px;

  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.045) 0%, rgba(255, 255, 255, 0.012) 100%),
    rgba(14, 15, 20, 0.86);

  /* Elle ne défile pas avec le contenu : `position: sticky` aurait suffi si le
     contenu défilait avec la page, mais c'est `main` qui défile — la barre est
     simplement hors de la zone qui bouge. */
  box-shadow: inset 0 1px 0 var(--unreal-bevel-light), 0 12px 30px -22px #000;

  /* Rien ne dépasse du rayon : le rail des pilules déborde volontiers sur un
     écran étroit. */
  min-width: 0;
}

.barre-marque {
  display: flex;
  align-items: center;
  gap: 11px;
  flex: none;
}

/* Le logo animé d'Unreal. Il ne fait que 34 px : le GIF d'origine, 400 px et
   9,7 Mo, a été réduit à 128 px et une image sur deux — 735 ko, invisible à
   l'œil à cette taille, et rechargé vingt fois par jour. */
.barre-marque img {
  width: 34px;
  height: 34px;
  border-radius: 11px;

  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 60%, transparent 86%);
  mask-image: radial-gradient(circle at 50% 50%, #000 60%, transparent 86%);

  /* Le GIF a un fond noir franc ; `screen` le fait disparaître dans la barre et
     ne garde que la lumière du logo. Sans ça, un carré noir mat flotte sur une
     surface qui, elle, est translucide. */
  mix-blend-mode: screen;
}

.barre .marque { font-size: 16px; letter-spacing: 1.9px; }

/* ── Les pilules ────────────────────────────────────────────────────────────

   Elles vivent dans leur propre gouttière sombre : un groupe de huit boutons
   posés à même la barre se lisait comme huit éléments indépendants, alors que
   c'est UNE navigation. Le creux les rassemble. */
.pilules {
  display: flex;
  align-items: center;
  gap: 3px;

  flex: 1;
  min-width: 0;
  padding: 3px;

  border-radius: 13px;
  background: rgba(0, 0, 0, 0.34);

  /* Sur un écran étroit, on fait défiler la navigation plutôt que de la
     replier : un menu caché derrière un bouton coûte un clic à chaque
     changement d'écran. */
  overflow-x: auto;
  scrollbar-width: none;
}

.pilules::-webkit-scrollbar { height: 0; }

#onglets button {
  display: flex;
  align-items: center;
  gap: 8px;

  flex: none;
  padding: 8px 13px;

  border-color: transparent;
  border-radius: 10px;
  background: transparent;
  color: var(--veqta-text-muted);

  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

#onglets button:hover { background: rgba(255, 255, 255, 0.05); color: var(--veqta-text); }

#onglets button.actif {
  background: var(--unreal-gradient);
  color: #fff;
  box-shadow: inset 0 1px 0 var(--unreal-bevel-light), 0 4px 16px rgba(var(--unreal-start-rgb), 0.28);
}

/* L'icône. Les tracés sont dans `app.js` — huit chemins SVG écrits à la main,
   plutôt qu'une police d'icônes qui serait un fichier de plus à servir et des
   carrés vides tant qu'elle n'est pas chargée.

   Elle est légèrement en retrait quand l'onglet dort : c'est le libellé qu'on
   lit, l'icône n'est là que pour retrouver l'entrée du coin de l'œil. */
#onglets button svg {
  flex-shrink: 0;
  opacity: 0.68;
  transition: opacity 120ms;
}

#onglets button:hover svg,
#onglets button.actif svg { opacity: 1; }

/* Sous 1080 px, on ne garde que les glyphes : huit libellés ne tiennent pas,
   et les faire défiler horizontalement à chaque clic serait pire. */
@media (max-width: 1080px) {
  #onglets button span { display: none; }
  #onglets button { padding: 8px 9px; }
  .barre .marque { display: none; }
}

/* ── Le coin droit ──────────────────────────────────────────────────────── */

.barre-droite {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}

/* L'ÉTAT DU SERVEUR SUIT LA NAVIGATION.

   Il ne vivait que sur le tableau de bord. On passait donc à la console pour
   lire une erreur, le serveur tombait pendant qu'on lisait, et rien à l'écran
   ne le disait. */
.etat-vif {
  display: flex;
  align-items: center;
  gap: 7px;

  padding: 6px 12px 6px 10px;
  border: 1px solid var(--veqta-border);
  border-radius: 99px;
  background: rgba(0, 0, 0, 0.3);

  font-size: 12px;
  font-weight: 600;
}

.etat-vif .pouls { width: 8px; height: 8px; }

.qui {
  padding: 6px 12px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.05);

  font-size: 12.5px;
  font-weight: 600;
  color: var(--veqta-text-muted);
  white-space: nowrap;
}

button.icone {
  display: grid;
  place-items: center;

  width: 34px;
  height: 34px;
  padding: 0;

  border-radius: 10px;
  color: var(--veqta-text-muted);
}

button.icone:hover { color: var(--unreal-soft); border-color: var(--unreal-border); }

@media (max-width: 860px) {
  .qui, .etat-vif b { display: none; }
}

/* ── La zone de travail ─────────────────────────────────────────────────── */

main {
  flex: 1;
  min-height: 0;
  min-width: 0;

  padding: 22px clamp(16px, 2.4vw, 30px) 26px;

  border: 1px solid var(--veqta-border);
  border-radius: 20px;
  background: rgba(11, 12, 17, 0.72);

  overflow-y: auto;

  /* Chaque écran entre en fondu montant. Sans transition, changer d'onglet
     remplace tout d'un coup et l'on ne sait pas si la page a répondu ou si
     elle n'a jamais bougé. */
  animation: fh-in 220ms ease-out;
}

/* SUR UN ÉCRAN LARGE, LE CONTENU S'ARRÊTE.

   Sans plafond, une carte de 2500 px de large étire ses colonnes jusqu'à ce que
   l'œil doive traverser tout le moniteur pour relier un libellé à sa valeur. On
   centre plutôt que d'étirer — mais on centre le CONTENU, pas la surface : un
   `main` étroit au milieu d'un fond vide donnerait une fenêtre dans la fenêtre. */
main > * { max-width: 1720px; margin-inline: auto; }

h1 {
  font-size: 25px;
  font-weight: 800;
  letter-spacing: -0.2px;
}

h2 {
  margin: 26px 0 12px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--unreal-soft);
}

.chapeau {
  margin: 3px 0 26px;
  font-size: 14px;
  color: var(--veqta-text-muted);
}

/* ── Tuiles ──────────────────────────────────────────────────────────────── */

.tuiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
  gap: 12px;
  margin-bottom: 26px;
}

.tuile {
  padding: 17px 19px;

  border: 1px solid var(--veqta-border);
  border-radius: var(--unreal-round-lg);

  /* La même matière que les tuiles du HUD : une lueur qui monte du bas, un
     reflet en haut, un fond presque noir. */
  background:
    radial-gradient(ellipse at 50% 140%, rgba(var(--unreal-start-rgb), 0.16) 0%, transparent 72%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.045) 0%, transparent 55%),
    rgba(13, 14, 19, 0.9);

  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.tuile b {
  display: block;
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums;
}

.tuile span {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--veqta-text-muted);
}

.pastille {
  display: inline-block;
  width: 9px;
  height: 9px;
  margin-right: 9px;
  border-radius: 50%;
  background: var(--veqta-text-muted);
  vertical-align: middle;
}

.pastille.vivant {
  background: var(--veqta-success);
  box-shadow: 0 0 0 3px rgba(62, 207, 142, 0.2);
}

.pastille.mort { background: var(--veqta-error); box-shadow: 0 0 0 3px rgba(240, 68, 56, 0.18); }

/* ── Tableaux ────────────────────────────────────────────────────────────── */

.carte {
  padding: 20px 22px;
  margin-bottom: 14px;

  border: 1px solid var(--veqta-border);
  border-radius: var(--unreal-round-lg);
  background: rgba(255, 255, 255, 0.022);
}

.carte h2 { margin-top: 0; }

.carte h2 code, .carte h3 code {
  margin-left: 9px;
  padding: 2px 8px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.07);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--veqta-text-muted);
}

.carte h3 {
  font-size: 16px;
  font-weight: 700;
}

table { width: 100%; border-collapse: collapse; }

th {
  padding: 10px 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);

  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  text-align: left;
  color: var(--veqta-text-muted);
}

td {
  padding: 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.045);
  font-variant-numeric: tabular-nums;
}

tbody tr { transition: background 120ms; }
tbody tr:hover { background: rgba(255, 255, 255, 0.025); }

td .actions { display: flex; gap: 6px; }
td button { padding: 6px 12px; font-size: 12.5px; }

td b { color: #fff; margin-right: 8px; }
td small { color: rgba(255, 255, 255, 0.32); }

.vide {
  padding: 46px;
  text-align: center;
  color: var(--veqta-text-muted);
}

/* ── Barre d'outils ──────────────────────────────────────────────────────── */

.outils {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

.outils input { width: auto; flex: 1; min-width: 210px; }
.outils span { font-size: 13px; color: var(--veqta-text-muted); }

.note {
  margin-top: 20px;
  padding: 13px 15px;

  border-left: 3px solid var(--unreal-bright);
  border-radius: 0 var(--unreal-round) var(--unreal-round) 0;
  background: rgba(var(--unreal-start-rgb), 0.07);

  font-size: 13px;
  line-height: 1.6;
  color: var(--veqta-text-muted);
}

.note code {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--unreal-pale);
}

.badge {
  padding: 3px 9px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.08);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--veqta-text-muted);
}

.badge.on { background: var(--unreal-gradient); color: #fff; }

/* ── La console ──────────────────────────────────────────────────────────── */

.console-boite {
  display: flex;
  flex-direction: column;
  height: calc(100vh - 230px);
  min-height: 320px;

  border: 1px solid var(--veqta-border);
  border-radius: var(--unreal-round-lg);
  background: #06070b;
  overflow: hidden;
}

/* Interligne serré et chasse fixe : on lit une console en balayant des
   colonnes, pas des phrases. */
#console {
  flex: 1;
  padding: 15px 17px;

  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.5;

  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

#console .ligne { color: rgba(255, 255, 255, 0.68); }
#console .ligne.err { color: #ff8b8b; }
#console .ligne.ok { color: #86efac; }
#console .ligne.warn { color: #fcd34d; }
#console .ligne.moi { color: var(--unreal-soft); font-weight: 600; }

.console-saisie {
  display: flex;
  gap: 9px;
  padding: 12px 14px;
  border-top: 1px solid var(--veqta-border);
  background: rgba(255, 255, 255, 0.02);
}

.console-saisie input {
  font-family: var(--mono);
  font-size: 13px;
}

/* ── Permissions ─────────────────────────────────────────────────────────── */

.perm {
  display: flex;
  align-items: flex-start;
  gap: 12px;

  padding: 10px 13px;
  margin-bottom: 5px;

  border-radius: var(--unreal-round);
  background: rgba(255, 255, 255, 0.025);
  cursor: pointer;

  transition: background 120ms;
}

.perm:hover { background: rgba(255, 255, 255, 0.055); }
.perm input { width: 17px; height: 17px; margin-top: 3px; accent-color: var(--unreal-bright); }
.perm b { display: block; font-size: 13.5px; }
.perm small { color: var(--veqta-text-muted); line-height: 1.5; }

/* ── Le graphique des arrivées ───────────────────────────────────────────── */

/* =============================================================================
   LE TABLEAU DE BORD.

   Un bandeau plein format, puis une grille de douze colonnes. Voir le
   commentaire de `rendreBord` dans `app.js` pour la hiérarchie — vivant en
   haut, froid en bas.
   ========================================================================== */

/* ── Le bandeau ──────────────────────────────────────────────────────────────

   IL PORTE LA LUEUR, ET LES CARTES NON.

   Toutes les surfaces avaient le même fond translucide, donc le même poids :
   rien ne disait où regarder d'abord. Le bandeau est la seule chose de l'écran
   qui reçoive un dégradé rubis et une bordure teintée. C'est le contraste qui
   hiérarchise, pas la taille du texte. */
.bandeau {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 26px;

  padding: 24px 28px;
  margin-bottom: 18px;

  border: 1px solid var(--unreal-border);
  border-radius: var(--unreal-round-lg);

  background:
    radial-gradient(720px 200px at 0% 0%, rgba(var(--unreal-start-rgb), 0.20) 0%, transparent 72%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.014) 100%),
    rgba(20, 12, 16, 0.55);

  /* Le biseau : lumière en haut, ombre portée en bas. Même geste que les
     surfaces du jeu — il donne au bandeau son épaisseur de pierre. */
  box-shadow:
    inset 0 1px 0 var(--unreal-bevel-light),
    0 18px 40px -24px rgba(0, 0, 0, 0.9);
}

/* Serveur arrêté : la lueur rubis disparaît. Un bandeau qui brille pareil
   qu'il tourne ou non ne sert à rien — on le regarde justement pour ça. */
.bandeau--mort {
  border-color: rgba(240, 68, 56, 0.35);
  background:
    radial-gradient(720px 200px at 0% 0%, rgba(240, 68, 56, 0.12) 0%, transparent 72%),
    rgba(22, 14, 14, 0.6);
}

.bandeau-pouls {
  display: flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
}

.bandeau h1 { font-size: 23px; line-height: 1.2; }

.bandeau-detail {
  margin-top: 3px;
  font-size: 13px;
  color: var(--veqta-text-muted);
}

/* Le pouls bat. Une pastille FIXE ne distingue pas « en marche » de « figée
   sur un écran qu'on n'a pas rafraîchi depuis une heure » ; le battement dit
   que ce qu'on regarde est vivant. */
.pouls {
  flex: none;
  width: 13px;
  height: 13px;
  border-radius: 50%;
}

.pouls--vivant {
  background: var(--veqta-success);
  box-shadow: 0 0 0 0 rgba(62, 207, 142, 0.55);
  animation: pouls 2.4s ease-out infinite;
}

.pouls--mort {
  background: var(--veqta-error);
  box-shadow: 0 0 0 4px rgba(240, 68, 56, 0.16);
}

@keyframes pouls {
  0%   { box-shadow: 0 0 0 0 rgba(62, 207, 142, 0.5); }
  70%  { box-shadow: 0 0 0 12px rgba(62, 207, 142, 0); }
  100% { box-shadow: 0 0 0 0 rgba(62, 207, 142, 0); }
}

/* Le mouvement est agréable jusqu'au jour où il donne la nausée. Le système
   sait qui est concerné ; on l'écoute. */
@media (prefers-reduced-motion: reduce) {
  .pouls--vivant { animation: none; box-shadow: 0 0 0 4px rgba(62, 207, 142, 0.18); }
}

.bandeau-jauge { min-width: 210px; }

.jauge-compte {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.jauge-compte b {
  font-size: 34px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -1px;

  /* Le seul chiffre de l'écran taillé dans le rubis : c'est celui qu'on vient
     chercher en ouvrant le panneau. */
  background: linear-gradient(180deg, #fff 0%, var(--unreal-soft) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.jauge-compte span { font-size: 13px; color: var(--veqta-text-muted); }

.jauge-rail {
  height: 7px;
  margin-top: 9px;
  overflow: hidden;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.07);
}

.jauge-rail--fine { height: 5px; margin-top: 7px; }

.jauge-rail i {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: var(--unreal-gradient);
  box-shadow: 0 0 12px var(--unreal-glow);

  /* La largeur s'anime : un remplissage qui saute d'une valeur à l'autre se lit
     comme un rechargement de page, pas comme un joueur qui vient d'arriver. */
  transition: width 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

.bandeau-actions { display: flex; gap: 9px; }

.note--large { grid-column: 1 / -1; margin-top: 4px; }

/* ── La grille ───────────────────────────────────────────────────────────────

   Douze colonnes, comme partout. Sous 1180 px, tout retombe sur une seule :
   deux colonnes de 300 px côte à côte rendent la liste des joueurs et la
   console illisibles toutes les deux. */
.grille {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
}

.col-4  { grid-column: span 4; }
.col-8  { grid-column: span 8; }
.col-12 { grid-column: span 12; }

@media (max-width: 1180px) {
  .col-4, .col-8 { grid-column: span 12; }
  .bandeau { grid-template-columns: minmax(0, 1fr); }
}

/* La carte de la grille annule la marge du bas héritée : c'est `gap` qui
   espace ici, et cumuler les deux crée des gouttières inégales entre les
   rangées et les colonnes. */
.grille .carte { margin-bottom: 0; }

/* Les deux zones qui bougent d'elles-mêmes ont une hauteur FIXE.

   Sans elle, la carte grandit à chaque ligne de console et à chaque joueur qui
   se connecte : la page s'allonge toute seule, et ce qu'on lisait en bas
   descend hors de l'écran pendant qu'on le lit. */
.carte--vive { display: flex; flex-direction: column; height: 340px; }
.carte--vive > h2 { flex: none; }

.liste-vivante,
.console-apercu {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* ── Joueurs en ligne ────────────────────────────────────────────────────── */

.vivant-rangee {
  display: flex;
  align-items: center;
  gap: 11px;

  padding: 7px 8px;
  border-radius: var(--unreal-round);
}

.vivant-rangee:hover { background: rgba(255, 255, 255, 0.045); }

.pastille-nom {
  flex: none;
  display: grid;
  place-items: center;

  width: 30px;
  height: 30px;

  border-radius: 50%;
  background: var(--unreal-gradient);
  box-shadow: inset 0 1px 0 var(--unreal-bevel-light);

  font-size: 13px;
  font-weight: 700;
  color: #fff;
}

.vivant-nom { min-width: 0; flex: 1; }

.vivant-nom b {
  display: block;
  font-size: 13.5px;

  /* Un pseudo à rallonge pousserait le `#12` hors de la carte. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.vivant-nom span { font-size: 11.5px; color: var(--veqta-text-muted); }

.vivant-id {
  flex: none;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--veqta-text-muted);
}

/* ── Aperçu de la console ────────────────────────────────────────────────── */

.console-apercu {
  padding: 10px 12px;
  border: 1px solid var(--veqta-border);
  border-radius: var(--unreal-round);
  background: rgba(0, 0, 0, 0.35);

  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.65;

  /* Les lignes du serveur sont longues et contiennent des chemins qu'on veut
     lire en entier : on coupe le mot plutôt que de masquer la fin. */
  overflow-wrap: anywhere;
}

.console-apercu .ligne { color: #b9c0cf; }
.console-apercu .err  { color: #ff8b8b; }
.console-apercu .warn { color: var(--veqta-warning); }
.console-apercu .ok   { color: var(--veqta-success); }

/* ── Les chiffres ────────────────────────────────────────────────────────── */

.chiffres {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}

.chiffre {
  padding: 13px 15px;
  border: 1px solid var(--veqta-border);
  border-radius: var(--unreal-round);
  background: rgba(255, 255, 255, 0.022);
}

.chiffre b {
  display: block;
  font-size: 22px;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.5px;
}

.chiffre span {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--veqta-text-muted);
}

/* Un compteur de bannis à zéro est une bonne nouvelle et doit se lire comme le
   reste. Il ne rougit que lorsqu'il y a quelque chose à regarder. */
.chiffre--rouge b { color: #ff8b8b; }
.chiffre--rouge { border-color: rgba(240, 68, 56, 0.28); }

/* ── Économie ────────────────────────────────────────────────────────────── */

.economie-poste + .economie-poste { margin-top: 16px; }

.economie-poste b {
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -0.4px;
}

.economie-poste span {
  display: block;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--veqta-text-muted);
}

/* Deux teintes pour deux natures d'argent : le liquide brûle, la banque dort.
   Une seule couleur aurait fait deux barres qu'on doit lire pour distinguer. */
.part--liquide { background: var(--unreal-gradient) !important; }
.part--banque  { background: linear-gradient(90deg, #4c8dff 0%, #1e3f80 100%) !important; }

.economie-total {
  margin-top: 18px;
  padding-top: 13px;
  border-top: 1px solid var(--veqta-border);
  font-size: 12px;
  color: var(--veqta-text-muted);
}

/* ── Le graphe ───────────────────────────────────────────────────────────────

   Des barres en CSS pur, pas une bibliothèque : quatorze valeurs ne justifient
   pas trois cents kilo-octets de JavaScript sur un panneau qu'on veut pouvoir
   relire en entier.

   Chaque colonne porte SA valeur au-dessus. Sans elle, il fallait survoler
   chaque barre pour savoir ce qu'elle vaut — donc quatorze survols pour lire
   quatorze nombres. */
.graphe {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 150px;
  padding-top: 10px;
}

.graphe-colonne {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: stretch;
  height: 100%;
}

.graphe-valeur {
  margin-bottom: 5px;
  font-size: 10.5px;
  font-weight: 700;
  text-align: center;
  color: var(--veqta-text-muted);
}

.graphe-colonne i {
  min-height: 2px;
  border-radius: 5px 5px 2px 2px;
  background: var(--unreal-gradient);
  opacity: 0.82;
  transition: opacity 120ms;
}

.graphe-colonne:hover i { opacity: 1; box-shadow: 0 0 14px var(--unreal-glow); }
.graphe-colonne:hover .graphe-valeur { color: var(--veqta-text); }

/* =============================================================================
   LE MOUVEMENT.

   Trois règles, et une quatrième qui les annule toutes.

   Rien ici n'est décoratif : chaque animation répond à une question que
   l'utilisateur se pose au moment où elle joue — « est-ce que ça a répondu ? »,
   « qu'est-ce qui vient de changer ? », « où est-ce que je regarde ? ».
   ========================================================================== */

/* 1. L'ENTRÉE DÉCALÉE DES CARTES.

   `--rang` est posé par `app.js` dans l'ordre où les cartes sont rangées. Le
   retard reste plafonné : sans `min()`, un écran à quinze cartes ferait
   attendre la dernière une demi-seconde, et l'on croirait la page bloquée. */
.grille > .carte {
  animation: fh-monte 380ms cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: calc(min(var(--rang, 0), 6) * 45ms);
}

@keyframes fh-monte {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

/* 2. LA CARTE RÉPOND AU SURVOL.

   Un liseré qui s'allume, pas un déplacement : une carte qui bouge sous la
   souris décale ce qu'on était en train de lire. */
.carte {
  transition: border-color 160ms, background 160ms, box-shadow 160ms;
}

.grille > .carte:hover {
  border-color: rgba(var(--unreal-start-rgb), 0.3);
  box-shadow: 0 0 0 1px rgba(var(--unreal-start-rgb), 0.08),
              0 14px 34px -26px rgba(var(--unreal-start-rgb), 0.6);
}

/* 3. LE BOUTON S'ENFONCE.

   Quatre-vingt-dix millisecondes et un pixel : c'est l'accusé de réception du
   clic. Sans lui, un bouton dont l'action met une seconde à répondre semble
   n'avoir rien fait, et on le clique deux fois. */
button { transition: transform 90ms, background 140ms, border-color 140ms, color 140ms; }
button:active { transform: translateY(1px); }

/* Le bouton de son éteint reste lisible mais s'efface : c'est un réglage, pas
   une commande. */
button.icone.eteint { opacity: 0.5; }

/* Les lignes de console arrivent par la gauche. Le mouvement dit « ceci est
   NOUVEAU » sur un mur de texte où tout se ressemble. */
.console-apercu .ligne { animation: fh-glisse 200ms ease-out both; }

@keyframes fh-glisse {
  from { opacity: 0; transform: translateX(-6px); }
  to   { opacity: 1; transform: none; }
}

/* Les barres du graphe poussent depuis le bas, une fois, à l'ouverture. */
.graphe-colonne i {
  transform-origin: bottom;
  animation: fh-pousse 460ms cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: calc(var(--rang, 0) * 22ms);
}

@keyframes fh-pousse {
  from { transform: scaleY(0); opacity: 0; }
  to   { transform: scaleY(1); opacity: 0.82; }
}

/* Une arrivée dans la liste des joueurs. */
.vivant-rangee { animation: fh-glisse 240ms ease-out both; }

/* 4. ET TOUT CECI S'EFFACE SI LE SYSTÈME LE DEMANDE.

   Ce n'est pas une politesse : le mouvement déclenche des migraines et des
   nausées chez une partie des gens, et un panneau d'administration reste
   ouvert des heures. `prefers-reduced-motion` est le réglage par lequel ils
   l'ont déjà dit — une seule fois, au système, plutôt qu'à chaque site. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ── La connexion Discord ─────────────────────────────────────────────────── */

/* Un trait coupé par le mot « ou ». Deux boutons empilés sans séparation se
   lisent comme deux étapes d'un même formulaire, alors que ce sont deux
   chemins qui mènent au même endroit. */
.ou {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 0 14px;
  color: var(--veqta-text-muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.6px;
}

.ou::before, .ou::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--veqta-border);
}

/* Le bleu de Discord, et pas notre violet : c'est un bouton qui mène CHEZ EUX.
   Le peindre à nos couleurs ferait croire qu'il reste sur le panneau. */
.discord {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;

  padding: 12px;

  border: 1px solid rgba(88, 101, 242, 0.5);
  border-radius: var(--unreal-round);
  background: rgba(88, 101, 242, 0.14);

  color: #c8cdff;
  font-size: 13.5px;
  font-weight: 700;
  text-decoration: none;

  transition: background 150ms, border-color 150ms, transform 90ms;
}

.discord:hover {
  background: rgba(88, 101, 242, 0.24);
  border-color: rgba(88, 101, 242, 0.75);
  color: #fff;
}

.discord:active { transform: translateY(1px); }

.mention {
  margin-top: 10px;
  text-align: center;
  font-size: 11.5px;
  color: var(--veqta-text-muted);
}

/* ── Les outils de la console ─────────────────────────────────────────────── */

.console-outils {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.console-recherche {
  flex: 1;
  min-width: 0;
  padding: 9px 12px;
  font-family: var(--mono);
  font-size: 12px;
}

.console-compte {
  flex: none;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--veqta-text-muted);
  white-space: nowrap;
}

/* `display: none` et pas `visibility` : une ligne masquee ne doit pas garder
   sa hauteur, sinon filtrer laisse un mur de trous a la place du bruit. */
#console .ligne.masquee { display: none; }
