/* ===========================================================================
   DESIGN SYSTEM — DOFUS BRISAGE

   🎯 LE REGISTRE : outil de decision dense, consulte DEVANT L'HDV, souvent en
      alt-tab depuis le jeu. On doit lire un verdict en une seconde.

   ⚠️ ECART ASSUME AVEC FIDUHELP : la structure de tokens est reprise (trois
      fonds, trois textes, deux filets — elle a fait ses preuves), mais PAS la
      palette. Fiduhelp est un outil de cabinet, son bleu d'encre porte une
      identite de marque. Ici on est a cote d'un jeu : l'accent est chaud, et
      les couleurs d'ETAT (rentable / perte) priment sur l'identite.

   🔴 LA COULEUR PORTE UNE DECISION FINANCIERE ICI. Vert = tu gagnes, rouge =
      tu perds. ⛔ Donc : jamais de vert ou de rouge decoratif, nulle part.
      Une couleur d'etat utilisee pour faire joli rend l'ecran illisible au
      moment ou il compte.

   🔴 CE PRODUIT EST SOMBRE PAR DEFAUT, en NOIR NEUTRE. Ce n'est pas une
      preference d'affichage : l'outil est consulte en alt-tab depuis Dofus,
      et un ecran blanc entre deux allers-retours dans le jeu eblouit a chaque
      bascule.

   🔴 LE VIRAGE DU 2026-09-17 — « SALLE DES MARCHES », d'apres l'onglet RECAP
      de STAXIS. ⚠️ CE N'EST PAS UN CHANGEMENT DE COULEUR. Une premiere passe
      s'est contentee de repeindre les variables : Benjamin l'a refusee en
      regardant l'ecran — « ca a juste change de couleur ». La DA tient a TROIS
      GESTES DE STRUCTURE, dans cet ordre d'importance :

        1. ⛔ IL N'Y A PLUS DE CARTES. Aucune surface, aucun cadre, aucun fond
           de bloc. Le fond `#07080a` court sur toute la page. La structure est
           portee par les FILETS et l'ESPACEMENT — c'est CE geste qui fait la
           DA. Cf. `.carte`, videe de sa substance plus bas.
        2. L'echelle typographique et son LETTER-SPACING QUI AUGMENTE QUAND LA
           TAILLE DIMINUE (26px → 0 ; 15px → .12em ; 9px → .14em).
        3. L'inertie : ⛔ zero transition, zero keyframe, zero glow. Le
           mouvement vient de la DONNEE qui se met a jour, pas de l'interface
           qui s'agite.

   🔴 LE NOIR EST NEUTRE, PAS BLEU. Ecart bleu-rouge du fond : +3, une valeur
      mesuree et non un gout. `leagues.css` documente qu'un `#0b0d12` (ecart
      +7) a ete ESSAYE puis REFUSE comme « trop bleute » — une dalle qui couvre
      100 % de l'ecran donne sa teinte a tout le reste. Une passe precedente de
      ce fichier etait a +10 sur les fonds et +34 sur un texte : refusee pour
      cette raison exacte.
   ⛔ Ne PAS « rechauffer » ni « refroidir » ces fonds : ils viennent de STAXIS
      a la valeur pres.

   ⚠️ CONSEQUENCE SUR L'ORDRE DES BLOCS : `:root` porte le SOMBRE. Le clair
      n'existe que sous `[data-theme="light"]`, pour qui le voudrait. C'est
      l'inverse de la convention habituelle, et c'est delibere — un `:root`
      clair suivi d'un override sombre laisserait passer un flash blanc au
      chargement.

   ⚠️ Un theme REDEFINIT les memes tokens — il n'ajoute JAMAIS de regle propre.
      Une couleur ecrite en dur ailleurs est un bug qui ne se verra que dans
      l'autre theme, donc tard.
   =========================================================================== */

:root {
    /* ── Fonds — VALEURS LITTERALES DE STAXIS / RECAP ──────────────────────
       🔴 `--fond` COURT SUR TOUTE LA PAGE, sans interruption. Il n'y a plus de
          carte pour le recouvrir (cf. `.carte`).
       🔴 ECART BLEU-ROUGE : +3. Le noir est NEUTRE, pas bleu.
       ⚠️ `--fond-carte` ne dessine plus rien : il vaut `--fond`. `--fond-eleve`
          ne sert qu'a ce qui FLOTTE (infobulle, modal, menu), qui doit etre
          opaque par-dessus la page. Les DEUX NOMS SONT CONSERVES parce que les
          13 onglets les referencent 76 fois : les renommer aurait ete un
          chantier de renommage, pas de design. */
    --fond:            #07080a;
    --fond-carte:      #07080a;   /* = le fond : plus aucune carte */
    --fond-eleve:      #111318;   /* ce qui FLOTTE seulement */
    --fond-survol:     rgba(255, 255, 255, .04);   /* hover ACHROMATIQUE */

    /* ── Texte ─────────────────────────────────────────────────────────────
       🔴 CONTRASTES MESURES SUR `--fond` #07080a (il n'y a plus qu'un seul
          fond, donc plus de « cas defavorable » a chercher) :
          texte 16,63:1 · attenue 6,41:1 · faible 5,10:1.
       🔴 LES NIVEAUX ATTENUES SONT OPAQUES, PAS DES rgba. Raison ecrite dans
          `leagues.css` : un `rgba(255,255,255,.35)` mesurait 2,87:1, sous AA.
          Un alpha se lit differemment selon ce qu'il y a dessous ; une couleur
          opaque se mesure une fois pour toutes. */
    --texte:           #e8eaee;
    --texte-attenue:   #8b92a0;
    /* ⚠️ Ce troisieme niveau n'existe pas chez RECAP (qui n'en a que deux). Il
       est conserve parce que les 13 onglets l'utilisent — il ne doit JAMAIS
       porter un chiffre, seulement une mention de service.
       ⚠️ La transposition litterale de RECAP le posait a #6f7787 = 4,45:1,
          SOUS le seuil AA. Eclairci a 5,10:1 en gardant l'ecart bleu de son
          voisin (+22 contre +21). Un token sous 4,5:1 se corrige. */
    --texte-faible:    #79818f;

    /* ── Filets — TROIS EPAISSEURS, TROIS ROLES ────────────────────────────
       🔴 C'EST LA STRUCTURE DE LA PAGE, maintenant qu'il n'y a plus de cartes.
          Un filet n'est pas une decoration ici : c'est la seule chose qui
          separe deux blocs.
            2px `--accent`  → les DEUX SEULS titres de section majeurs ;
            1px `--bordure` → toute structure (titre de module, rangee, filet
                              VERTICAL entre deux colonnes) ;
            1px `--filet`   → uniquement les lignes tres repetees (45 runes) ;
            1px dashed      → l'optionnel (note, « voir plus »).
       🔴 REGLE DES DOUBLES FILETS (documentee 3 fois chez STAXIS) : c'est
          l'element SUIVANT qui renonce a son filet, JAMAIS le titre.
            `.titre + .ligne { border-top: 0 }` */
    --filet:           rgba(255, 255, 255, .03);
    --bordure:         #1d2129;
    --bordure-forte:   #2a2f38;

    /* ── L'ACCENT : or ─────────────────────────────────────────────────────
       🎯 La couleur du kama. ⛔ INCHANGE PAR LA REFONTE : c'est l'identite
          Dofus et la couleur de la monnaie que l'outil compte. 9,59:1 sur le
          fond — l'or et le noir neutre se detachent tres bien.
       ⛔ JAMAIS `#c8aa6e` : chez STAXIS c'est le repli d'une panne connue, pas
          une couleur.
       🔴 L'or est le SEUL ACCENT STRUCTUREL : un filet de 2px sous les DEUX
          titres de section majeurs, et rien d'autre.
       ⛔ Jamais sur un element repete a chaque ligne : il perdrait son sens
          d'etat (entree courante, filtre actif, focus).
       ⚠️ `--accent-fond` / `--accent-filet` sont des restes de l'epoque des
          cartes : ils ne peignent plus de bloc, ils ne servent qu'aux deux
          derniers aplats justifies (bouton principal, champ rempli). */
    --accent:          #e0aa54;
    --accent-clair:    #efc172;
    --accent-texte:    #07080a;
    --accent-fond:     #1a1710;
    --accent-filet:    #3a3222;

    /* 🎯 Le fond d'un champ de saisie REMPLI : sur 45 champs, il faut
       retrouver d'un coup d'oeil ceux qu'on a saisis.
       ⚠️ C'est l'UN DES DEUX SEULS aplats qui subsistent (avec le bouton
          principal) : ici un fond porte une information que ni un filet ni un
          espacement ne peuvent porter. */
    --champ-rempli:    #1c1b14;

    /* ── LES ETATS FINANCIERS ──────────────────────────────────────────────
       🔴 CE SONT LES DEUX COULEURS QUI PORTENT LA DECISION, et leur REGLE ne
          bouge pas : vert = tu gagnes, rouge = tu perds, NULLE PART AILLEURS.
          Benjamin l'a confirmee le 2026-09-17 en connaissance du virage
          trading. Une DA de salle des marches met du vert et du rouge partout ;
          ici ils restent reserves a l'argent.
       ⚠️ LEURS VALEURS viennent maintenant de RECAP (`--rc-win` / `--rc-loss`) :
          les pastels precedents (#5fcf8e / #f4938a) etaient calibres pour un
          fond brun et vibraient sur le noir.
       🔴 Mesures sur `--fond` : gain 6,98:1, perte 4,77:1.
       ⚠️ LA PERTE EST LE TOKEN LE PLUS JUSTE DU FICHIER (4,77:1 contre 7,62:1
          avant). Elle tient AA, mais de peu — arbitrage assume au profit de la
          fidelite a la reference. ⛔ Si un chiffre de perte devait passer sous
          12px, il faudrait l'ECLAIRCIR plutot que le reduire. */
    --gain:            #5aa87a;
    --perte:           #c45b5b;
    /* ⛔ LES FONDS D'ETAT NE PEIGNENT PLUS DE BLOC : sans card, un fond colore
       redevient une BANDE visible sur le fond de page. Ils ne subsistent que
       pour les pastilles (`.puce`), ou le fond EST le composant. */
    --gain-fond:       #10201a;
    --perte-fond:      #241416;

    /* ⚠️ L'alerte n'est PAS une perte : elle signale une donnee PEU FIABLE
          (prix perime, prix manquant), pas de l'argent perdu. Elle reprend
          l'or de l'accent, distincte du rouge — exprès. */
    --alerte:          #e0aa54;
    --alerte-fond:     #1a1710;

    /* 🎯 L'EGALITE / LE NEUTRE de RECAP : un verdict qui ne penche d'aucun
       cote. 6,97:1 sur le fond. */
    --neutre:          #b5952f;

    /* ── LES GRAPHES — theme STAXIS (`overview.js` l.2334-2360) ────────────
       🔴 Le graphe n'a PAS de fond propre : il se pose sur `--fond`.
       ⛔ Grille et axes NEUTRES, jamais verts « pour faire terminal ». */
    --graphe-grille:   rgba(255, 255, 255, .05);
    --graphe-axe:      rgba(255, 255, 255, .09);

    /* ── Rythme — DENSITE MESUREE SUR RECAP ────────────────────────────────
       page 16px 20px 26px · rangee de liste 4px 0 (~30px) · ligne dense
       3px 4px (~22px) · titre de module padding-bottom 6px + margin 6px ·
       12px entre blocs, 6-9px dans une ligne. */
    --r-xs: 4px;  --r-sm: 6px;  --r-md: 9px;
    --r-lg: 12px; --r-xl: 20px; --r-2xl: 26px;

    /* 🔴 RAYONS : 2 / 3 / 4 / 5 / 6 / 7px. ⛔ RIEN AU-DESSUS DE 7px.
       `DESIGN_REFERENCE.md` liste « les rayons de 16 a 24px appliques
       uniformement » parmi les marqueurs d'« AI Designed ». */
    --arrondi:     4px;
    --arrondi-sm:  3px;
    --arrondi-lg:  7px;

    /* 🔴 LES OMBRES NE SERVENT QU'A CE QUI FLOTTE (popover, modal, menu).
       ⛔ JAMAIS sur un element EN FLUX : sans card, une ombre sur un bloc pose
          a plat dessine un halo autour de rien.
       ⚠️ `--ombre-sm` vaut `none` plutot que d'etre supprimee : elle est
          referencee par des regles des 13 onglets, et la retirer aurait
          demande de tous les relire. Elle ne dessine simplement plus rien. */
    --ombre-sm: none;
    --ombre:    0 8px 24px rgba(0, 0, 0, .55);
    --ombre-lg: 0 10px 26px rgba(0, 0, 0, .6);

    /* 🔴 OUTFIT, la police de STAXIS (chargee dans `index.html`).
       ⛔ PAS DE MONO NULLE PART. STAXIS l'a essayee puis RETIREE : « IBM Plex
          Mono + gros letter-spacing faisait AI generated ». L'alignement des
          chiffres se fait par `font-variant-numeric: tabular-nums`, pose UNE
          SEULE FOIS sur le body et herite partout.
       ⚠️ `--police-chiffres` est conservee et vaut desormais la MEME chose que
          `--police` : une trentaine de regles des 13 onglets la referencent, et
          la supprimer aurait fait 30 retouches au lieu d'une. */
    --police: Outfit, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
              "Helvetica Neue", Arial, sans-serif;
    --police-chiffres: var(--police);
}


/* ═══ THEME CLAIR — OPTIONNEL, sur demande explicite ═══════════════════════
   ⚠️ Ne s'applique QUE si `<html data-theme="light">`. Il n'est PAS branche
      sur `prefers-color-scheme` : le sombre est l'identite du produit, pas un
      reglage a deviner depuis l'OS.

   🔴 VALEURS DE STAXIS : `--stx-light-bg #F2F3F5`, `--stx-light-surface
      #FFFFFF`, `--stx-light-text #1A1C24`. Le clair etait un PARCHEMIN chaud
      assorti aux bruns ; les bruns etant partis, il suit.

   ⚠️ IL N'Y A PLUS DE CARTE ICI NON PLUS : `--fond-carte` vaut `--fond`. Le
      blanc `#FFFFFF` ne sert qu'a ce qui FLOTTE (popover, modal), exactement
      comme en sombre. La symetrie entre les deux themes est stricte.

   🔴 CONTRASTES MESURES SUR LE FOND #f2f3f5 (il n'y a plus qu'un fond) :
      texte 15,31:1 · attenue 5,63:1 · faible 5,63:1 · accent 5,32:1 ·
      gain 5,97:1 · perte 6,33:1 · neutre 4,86:1.
   ⚠️ `--neutre` a ete FONCE (#b5952f → #7a600f) : la valeur sombre sortait a
      4,42:1 sur ce fond, sous le seuil.
   ⚠️ Un theme REDEFINIT les memes tokens — ⛔ aucune regle propre ici.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="light"] {
    --fond:          #f2f3f5;
    --fond-carte:    #f2f3f5;   /* = le fond : plus aucune carte */
    --fond-eleve:    #ffffff;   /* ce qui FLOTTE seulement */
    --fond-survol:   rgba(0, 0, 0, .04);   /* achromatique, comme en sombre */

    --texte:         #1a1c24;
    --texte-attenue: #59616e;
    --texte-faible:  #59616e;

    --filet:         rgba(0, 0, 0, .05);
    --bordure:       #dcdfe4;
    --bordure-forte: #c3c8d0;

    /* ⛔ L'accent reste l'or, fonce pour tenir sur du clair. */
    --accent:        #8a5a15;
    --accent-clair:  #a86f1d;
    --accent-texte:  #ffffff;
    --accent-fond:   #f7ecd8;
    --accent-filet:  #e3cea6;

    /* ⚠️ En clair on FONCE le champ rempli au lieu de l'eclaircir : sur un fond
       deja clair, un fond plus clair serait invisible. */
    --champ-rempli:  #f2e2c4;

    --gain:          #136a41;
    --gain-fond:     #e4f2ea;
    --perte:         #a8281d;
    --perte-fond:    #fbeae8;

    --alerte:        #8a5a15;
    --alerte-fond:   #f7ecd8;

    --neutre:        #7a600f;

    /* ⚠️ INVERSES : un blanc a 5 % serait invisible sur un fond clair. */
    --graphe-grille: rgba(0, 0, 0, .06);
    --graphe-axe:    rgba(0, 0, 0, .11);

    --ombre-sm: none;
    --ombre:    0 8px 24px rgba(26, 28, 36, .12);
    --ombre-lg: 0 10px 26px rgba(26, 28, 36, .16);
}

/* ═══ BASE ═════════════════════════════════════════════════════════════ */
* { box-sizing: border-box; }

/* 🔴 `color-scheme` : indispensable, et facile a oublier. Il dit au NAVIGATEUR
   de dessiner ses propres elements — barres de defilement, menu deroulant d'un
   <select>, curseur de saisie, autofill — assortis au theme. Sans lui, on
   obtient des scrollbars blanches criardes et une liste de serveurs en blanc
   sur une page brune : le detail qui trahit une page « sombre en surface ».
   ⚠️ Il vit sur `html` et pas sur `:root` dans les blocs de theme, parce qu'il
   doit suivre `data-theme` et non le reglage de l'OS. */
html { color-scheme: dark; }
html[data-theme="light"] { color-scheme: light; }

body {
    margin: 0;
    font-family: var(--police);
    font-size: 13px;
    line-height: 1.5;
    color: var(--texte);
    background: var(--fond);
    -webkit-font-smoothing: antialiased;
    /* 🔴 `tabular-nums` POSE UNE SEULE FOIS ET HERITE PARTOUT. C'est ce qui
       aligne les colonnes de kamas — ⛔ PAS une police a chasse fixe, que
       STAXIS a essayee puis retiree (« faisait AI generated »).
       ⚠️ Sans lui, un « 1 » est plus etroit qu'un « 8 » et 45 prix ondulent
          d'une ligne a l'autre. */
    font-variant-numeric: tabular-nums;
    /* ⛔ PLUS DE TRANSITION DE THEME : la DA est inerte. Le changement de
       theme est un fait instantane, pas une animation. */
}

/* 🔴 L'ECHELLE TYPO — 26 / 17 / 15 / 13 / 12 / 11.5 / 11 / 10 / 9 / 8.5 px.
   🎯 LE GESTE CENTRAL : LE LETTER-SPACING AUGMENTE QUAND LA TAILLE DIMINUE.
        26px → 0     (valeur heros)
        17px → .04em (nom principal, uppercase)
        15px → .12em (titre de colonne, uppercase)
        11px → .14em (titre de module, uppercase)
         9px → .14em (separateur, uppercase)
      C'est la signature de la DA : un petit texte serre fait « dense et
      brouillon », le meme espace fait « grave ».
   ⛔ AUCUNE GRAISSE > 500 DANS UN SVG : Outfit n'est chargee que jusqu'a 600,
      au-dela elle serait synthetisee et baverait. */
h1, h2, h3 { margin: 0; font-weight: 600; }
h1 { font-size: 17px; letter-spacing: .04em; text-transform: uppercase; }
h2 { font-size: 15px; letter-spacing: .12em; text-transform: uppercase; }
h3 { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; }

/* 🎯 LA VALEUR HEROS — 26px, letter-spacing 0. Le seul endroit de l'outil ou
   un chiffre a le droit d'etre gros : le verdict qu'on lit en une seconde. */
.valeur-heros {
    font-size: 26px;
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
}

/* ⚠️ Ces classes ne changent plus de POLICE : tout le document est deja en
   `tabular-nums` (cf. `body`). La regle survit parce que ~40 endroits la
   posent, et parce que `td.num` porte l'alignement a droite des colonnes. */
.chiffre, td.num, .valeur { font-variant-numeric: tabular-nums; }

/* ═══════════════════════════════════════════════════════════════════════
   MISE EN PAGE — LA SIDEBAR (2026-09-17)

   🔴 LE BANDEAU HORIZONTAL A DISPARU. A 13 onglets il DEBORDAIT reellement :
      « MES ITEMS » passait sur deux lignes et le bouton de theme se retrouvait
      decroche sous la barre. Une colonne grandit vers le bas, ou il reste de
      la place ; une barre se bat pour une largeur finie.

   ⛔ PAS DE SURFACE POUR LA SIDEBAR : meme `--fond` que la page. Sans card,
      une bande plus claire est exactement ce que STAXIS refuse. Elle se separe
      du contenu par UN FILET VERTICAL 1px, rien d'autre.

   🔴 ELLE EST DANS LE FLUX (`body` en grille), elle ne flotte pas : quand on
      la deplie, le contenu se REDIMENSIONNE. Un overlay recouvrirait le
      tableau qu'on est en train de lire.
   ═══════════════════════════════════════════════════════════════════════ */

/* ⚠️ La grille porte la largeur du rail dans une variable : c'est elle qu'on
   anime, et elle sert aussi au calcul de la largeur du contenu.

   🔴 `body:has(.rail)` ET NON `body` SEUL — corrige le 2026-09-17, constate sur
      la page de connexion en navigation privee.

   ⛔ CE QUE LA VERSION SANS `:has` A CASSE : la grille s'appliquait a TOUTES
      les pages, y compris celles qui n'ont pas de rail (`login.html`,
      `admin.html`). Leur unique conteneur devenait alors le premier enfant de
      la grille, donc enferme dans LA COLONNE DU RAIL : la carte de connexion,
      prevue en 380 px centres, se retrouvait ecrasee a 188 px contre le bord
      gauche, titre coupe en deux lignes et bouton debordant.

   🎯 POURQUOI CONDITIONNER PLUTOT QUE MARQUER CHAQUE PAGE : une classe
      `.page-app` sur `<body>` aurait marche, mais il aurait fallu y penser sur
      chaque page future — et l'oubli se serait vu comme ici, apres coup, sur
      un ecran qu'on ne regarde plus. Avec `:has(.rail)`, une page sans rail
      retrouve un flux normal sans que personne ait rien a declarer. */
body:has(.rail) {
    display: grid;
    grid-template-columns: var(--rail-l, 188px) minmax(0, 1fr);
    /* 🔴 `minmax(0, 1fr)` et non `1fr` : sans lui, la largeur minimale de la
       colonne de contenu est celle de son contenu (un tableau large), et la
       page entiere deborde horizontalement au lieu de laisser le tableau
       defiler dans sa propre zone. */
    min-height: 100vh;
}

.rail {
    /* 🔴 160px ET NON 188 (decision Benjamin, 2026-09-17 : « la plus petite
       possible »). ⛔ Ce n'est PAS un reglage libre, et la borne se CALCULE :
       le plus long libelle est « PRIX DES RUNES », soit ~94px a 10px avec
       .12em d'interlettrage. Le rail doit donc porter 94px de texte + l'icone
       (17) + le gap (10) + le padding de l'onglet (10+8) + celui du rail
       (8+8) = ~155px. A 160px il reste ~5px de marge ; en dessous, le libelle
       s'ellipse et un menu dont les entrees sont coupees ne se lit plus.
       ⚠️ 28px rendus a la page par rapport aux 188px d'origine.
       🎯 Pour aller plus loin, le geste est le REPLI (56px, deja la), pas un
          retrecissement qui degrade tous les etats a la fois. */
    --rail-l: 160px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 14px 8px 12px;
    background: var(--fond);
    /* ⛔ Le SEUL separateur : un filet vertical. Pas d'ombre, pas de surface. */
    border-right: 1px solid var(--bordure);

    /* 🔴 LE RAIL NE DEFILE PAS AVEC LA PAGE : il est colle en haut et tient sur
       toute la hauteur de l'ecran. Sans ca, en descendant dans un tableau de
       4 000 lignes on perdrait le menu de vue.
       ⚠️ `height: 100vh` + `sticky` : le rail a sa PROPRE zone de defilement
          (cf. `.onglets`), et le contenu a la sienne. Les deux ne se
          concurrencent pas — la double scrollbar arrive quand DEUX conteneurs
          defilent sur la MEME zone, pas quand chacun a la sienne. */
    position: sticky;
    top: 0;
    height: 100vh;
    z-index: 20;
}

/* ⚠️ La largeur vit sur `body` (c'est la grille qui la consomme) mais elle est
   DECIDEE par l'etat du rail : on la recopie donc au niveau du document. */
body:has(.rail.est-replie) { --rail-l: 56px; }

/* 🔴 LA TRANSITION DE LARGEUR — la SEULE animation de layout du produit, et
   elle se justifie : replier n'est pas un etat de survol, c'est un CHANGEMENT
   DE MISE EN PAGE. Sans elle, la page saute d'un coup et l'oeil doit
   reconstruire ou est passe le contenu ; avec elle, il SUIT le mouvement.
   `DESIGN_REFERENCE.md` autorise « les animations courtes qui expliquent un
   changement d'etat ».
   ⚠️ 140ms : sous les 200ms demandes. Au-dela, on attend l'interface.
   ⛔ Elle ne porte QUE la largeur — ni la couleur, ni les etats, qui restent
      instantanes comme le reste de la DA. */
/* ⚠️ `:has(.rail)` comme la grille elle-meme : animer une propriete de grille
      sur une page qui n'en a pas est sans effet, mais laisser les deux regles
      diverger inviterait a « reparer » l'une sans l'autre. */
body:has(.rail) { transition: grid-template-columns .14s ease; }

/* ⚠️ Mouvement reduit : le repli reste, le glissement part. */
@media (prefers-reduced-motion: reduce) {
    body:has(.rail) { transition: none; }
}

/* ── La marque, en haut du rail ────────────────────────────────────────── */
.rail-marque {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 8px 12px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--bordure);
    font-weight: 600;
    font-size: 11px;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--texte);
    white-space: nowrap;
    overflow: hidden;
}
/* La puce or : le seul element de marque qui survit au repli. */
.rail-marque-puce { color: var(--accent); flex: none; font-size: 13px; }

/* ── Le bouton de repli ────────────────────────────────────────────────── */
/* ⚠️ Il vit SOUS la marque et au-dessus des onglets : au repos il est discret,
   mais il ne disparait jamais — un bouton qu'on ne trouve qu'au survol ne se
   decouvre pas. */
.rail-bascule {
    align-self: flex-end;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    margin: 0 2px 6px;
    padding: 0;
    border: 0;
    border-radius: var(--arrondi-sm);
    background: transparent;
    color: var(--texte-faible);
    cursor: pointer;
    flex: none;
}
.rail-bascule:hover { color: var(--texte); background: var(--fond-survol); }
.rail-bascule:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.rail-chevron { width: 16px; height: 16px; }
/* Replie, le chevron pointe dans l'autre sens : il montre ce que le clic fait. */
.rail.est-replie .rail-bascule { align-self: center; margin-right: 0; }
.rail.est-replie .rail-chevron { transform: rotate(180deg); }

/* ── La liste des onglets ──────────────────────────────────────────────── */
/* 🔴 C'EST LA LISTE QUI DEFILE, PAS LE RAIL. 13 entrees + marque + pied
   tiennent a 1080px (mesure : ~13x30 + 150 = ~540px). En dessous de ~700px de
   haut, c'est cette zone — et elle seule — qui prend une barre de defilement,
   pendant que la marque et le pied restent visibles.
   ⚠️ `min-height: 0` : sans lui, un enfant de flex refuse de se retracter en
      dessous de son contenu et le `overflow` ne s'active jamais. */
.onglets {
    display: flex;
    flex-direction: column;
    gap: 1px;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;

    /* 🔴 LA BARRE DE DEFILEMENT S'EFFACE (2026-09-18). La barre native de
       Windows fait ~15px et se dessine en gris clair : dans un rail de 172px
       elle mangeait 9 % de la largeur et attirait l'oeil plus que les onglets
       eux-memes, alors qu'elle n'est qu'un indicateur de position.
       ⚠️ ELLE N'EST PAS SUPPRIMEE, elle est amincie. Un rail qui defile SANS
          aucune marque laisserait croire que la liste s'arrete a « Operations »
          — c'est justement le bug qu'on ne veut pas : un ecran tronque qui a
          l'air complet.
       ⚠️ `thin` plutot qu'une barre recreee a la main : on garde le
          comportement du systeme (clic, glisser, survol), on ne demande que
          la variante fine. Meme geste que `.flux-liste`. */
    scrollbar-width: thin;
    scrollbar-color: var(--bordure) transparent;
    /* ⚠️ La gouttiere est RESERVEE : sans elle, l'apparition de la barre
       decalerait les 13 libelles d'un coup a l'ouverture d'une petite
       fenetre. */
    scrollbar-gutter: stable;
}

/* Le pendant WebKit : `scrollbar-width` ne suffit pas sur Chrome/Edge, qui
   suivent encore la forme `::-webkit-scrollbar`.
   ⚠️ 5px : assez pour rester attrapable a la souris, trois fois moins que la
      barre native. */
.onglets::-webkit-scrollbar { width: 5px; }
.onglets::-webkit-scrollbar-track { background: transparent; }
.onglets::-webkit-scrollbar-thumb {
    background: var(--bordure);
    border-radius: 3px;
}
/* ⛔ Hover ACHROMATIQUE, comme tous les survols du produit. Le curseur ne se
   revele qu'au survol du rail : au repos il se fond dans le fond. */
.onglets:hover::-webkit-scrollbar-thumb { background: var(--texte-faible); }

/* 🔴 L'ONGLET ACTIF SE MARQUE PAR UN FILET, PAS PAR UNE PASTILLE. Un fond
   arrondi sous le libelle etait une capsule — marqueur n°1 d'« AI Designed ».
   ⚠️ Le filet passe A GAUCHE (`inset 2px 0 0`) : en colonne, c'est le bord qui
      longe la liste, l'equivalent vertical du soulignement d'une barre.
   ⛔ Pas de transition : un etat est instantane. */
.onglet {
    display: flex;
    align-items: center;
    gap: 10px;
    /* ⚠️ Le padding gauche vaut 10px, dont 2 sont manges par le filet de
       l'actif : l'icone ne bouge donc PAS entre un onglet actif et un autre. */
    padding: 7px 8px 7px 10px;
    border: 0;
    border-radius: var(--arrondi-sm);
    background: transparent;
    color: var(--texte-attenue);
    font: inherit;
    font-size: 10px;
    font-weight: 500;
    letter-spacing: .12em;
    text-transform: uppercase;
    text-align: left;
    cursor: pointer;
    white-space: nowrap;
    flex: none;
}
/* ⛔ Hover ACHROMATIQUE : une ligne survolee ne porte aucun verdict. */
.onglet:hover { color: var(--texte); background: var(--fond-survol); }
.onglet:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.onglet[aria-selected="true"] {
    color: var(--accent);
    box-shadow: inset 2px 0 0 var(--accent);
}

/* 🔴 L'ICONE SUIT `color` PAR `currentColor` : active elle passe a l'or, au
   repos elle est attenuee — exactement comme le texte a cote. C'est ce qui
   evite d'avoir deux grammaires (couleur pour le mot, opacite pour l'image)
   pour une seule information. */
.onglet-icone {
    width: 17px;
    height: 17px;
    flex: none;
}

/* Le libelle s'efface au repli. ⚠️ `display:none` et non `opacity:0` : a
   opacite nulle il occuperait encore sa largeur et le rail ne se replierait
   pas. */
.onglet-nom { overflow: hidden; text-overflow: ellipsis; }
.rail.est-replie .onglet-nom,
.rail.est-replie .rail-marque-nom,
.rail.est-replie .rail-reglage-libelle { display: none; }

/* Replie : l'icone se centre, le bouton devient un carre. */
.rail.est-replie .onglet { justify-content: center; padding: 7px 0; gap: 0; }
.rail.est-replie .rail-marque { justify-content: center; padding-left: 0; padding-right: 0; }

/* La pastille « Direct » se pousse a droite quand le libelle est la, et se
   colle a l'icone quand il ne l'est pas. */
.onglet .pastille-direct { margin-left: auto; margin-right: 0; }
.rail.est-replie .onglet .pastille-direct {
    /* ⚠️ Repliee, elle se pose EN HAUT A DROITE de l'icone plutot que de
       pousser celle-ci hors de l'axe des 12 autres. */
    position: absolute;
    margin: 0;
    transform: translate(9px, -8px);
}
.rail.est-replie .onglet[data-page="direct"] { position: relative; }

/* ── Le pied du rail ───────────────────────────────────────────────────── */
.rail-pied {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding-top: 10px;
    margin-top: 4px;
    border-top: 1px solid var(--bordure);
    flex: none;
}

.rail-reglage { min-width: 0; }
.rail-reglage-libelle {
    display: block;
    font-size: 9px;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--texte-faible);
    margin-bottom: 3px;
}
.rail-serveur { width: 100%; font-size: 11px; }

/* 🔴 EN MODE REPLIE, LE SELECTEUR DE SERVEUR NE TIENT PAS dans 56px — un nom
   comme « Draconiros » y serait coupe au milieu. Il reste present et
   CLIQUABLE, reduit a une bande : le menu natif qu'il ouvre, lui, s'affiche a
   sa taille normale par-dessus la page, donc le choix reste entier.
   ⛔ On ne le masque pas : changer de serveur depuis le rail replie doit
      rester possible, sinon il faut deplier pour une action d'un clic.
   ⚠️ `text-indent` negatif cache le texte trop long sans le tronquer
      visuellement a l'ecran — le `title` pose par `app.js` dit lequel. */
.rail.est-replie .rail-serveur {
    padding: 4px 2px;
    text-indent: -1px;
    font-size: 10px;
}
.rail.est-replie .rail-reglage { text-align: center; }

.rail-theme { align-self: flex-start; }
.rail.est-replie .rail-theme { align-self: center; }

/* 🎯 L'ACCES A L'ADMINISTRATION, a cote du theme : deux boutons de pied de
      rail, meme traitement. Il n'apparait que pour l'administrateur (`hidden`
      retire par `app.js` — cf. `montreAdminSiAdmin`).
   ⚠️ C'est un <a>, pas un <button> : il MENE quelque part. Un bouton ne se
      ferait pas ouvrir dans un nouvel onglet par un clic du milieu, et
      n'annoncerait pas « lien » au lecteur d'ecran. */
.rail-admin {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--texte-attenue);
    text-decoration: none;
}
.rail-admin svg { width: 15px; height: 15px; }
/* ⛔ Pas d'or au repos : ce n'est pas une cible de lecture, c'est une porte de
      service. L'accent arrive au survol, comme le reste du rail. */
.rail-admin:hover { color: var(--accent); }
.rail.est-replie .rail-admin { align-self: center; }

/* 🎯 LE TELECHARGEMENT DU READER, pour tous les comptes. Contrairement a
      l'admin, il porte un LIBELLE : c'est un geste que des amis doivent
      trouver sans qu'on le leur montre, pas une porte de service.
   ⚠️ Replie (et sous 900px), il ne garde que l'icone, comme les onglets. */
.rail-telecharger {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    font-size: 11px;
    color: var(--texte-attenue);
    text-decoration: none;
    border: 1px solid var(--bordure);
    border-radius: var(--arrondi-sm);
    min-width: 0;
}
.rail-telecharger svg { width: 15px; height: 15px; flex: none; }
.rail-telecharger-nom { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-telecharger:hover { color: var(--accent); border-color: var(--accent); }
.rail.est-replie .rail-telecharger { justify-content: center; padding: 6px 0; }
.rail.est-replie .rail-telecharger-nom { display: none; }
@media (max-width: 900px) {
    .rail .rail-telecharger { justify-content: center; padding: 6px 0; }
    .rail .rail-telecharger-nom { display: none; }
}

/* ── Sous 900px : le rail se replie et ne se deplie plus ───────────────── */
/* ⚠️ Benjamin travaille en ~1900px de large. On ne fabrique pas un mobile dont
   personne n'a besoin : sous 900px, le rail passe simplement en mode icones et
   la largeur est forcee, quel que soit l'etat memorise. L'outil reste
   utilisable, il ne devient pas une application telephone. */
@media (max-width: 900px) {
    /* ⚠️ `:has(.rail)` ici aussi : sans lui, cette regle re-imposait la grille
          aux pages sans rail sous 900 px — le bug de la carte de connexion
          serait revenu par la fenetre. */
    body:has(.rail) { grid-template-columns: 56px minmax(0, 1fr); }
    .rail .onglet-nom,
    .rail .rail-marque-nom,
    .rail .rail-reglage-libelle { display: none; }
    .rail .onglet { justify-content: center; padding: 7px 0; gap: 0; }
    .rail-bascule { display: none; }   /* deplier n'aurait pas la place */
}

.pousse-droite { margin-left: auto; }

/* 🔴 DENSITE DE RECAP : `16px 20px 26px`. */
/* 🔴 DENSITE DE RECAP : `16px 20px 26px`.
   ⚠️ `margin: 0` et non `0 auto` : le contenu est desormais la 2e colonne d'une
      grille, et le rail occupe la 1re. Le centrer DANS sa colonne le
      decollerait du rail d'un cote et laisserait un vide de l'autre — la page
      ne serait plus alignee sur son menu.
   ⚠️ `min-width: 0` : meme raison que la colonne de grille — sans lui, un
      tableau large pousse la page au lieu de defiler dans sa zone. */
.page {
    padding: 16px var(--r-xl) var(--r-2xl);
    max-width: 1500px;
    margin: 0;
    min-width: 0;
}

/* ═══════════════════════════════════════════════════════════════════════
   ⛔ LES CARTES N'EXISTENT PLUS — LE GESTE CENTRAL DE LA REFONTE

   🔴 DECISION DE BENJAMIN, 2026-09-17, apres avoir vu l'ecran : « on supprime
      les cartes ». RECAP n'a AUCUNE card, aucun cadre, aucune surface : le fond
      court sur toute la page, et la structure passe par les FILETS et
      l'ESPACEMENT. C'est CE geste qui fait la DA — pas la couleur. Une premiere
      passe s'etait contentee de repeindre les variables, et a ete refusee.

   ⚠️ LA CLASSE EST CONSERVEE, VIDEE DE SA SUBSTANCE. `.carte` est ecrite 76
      fois dans les 13 onglets : la supprimer aurait demande de reecrire 13
      fichiers JS pour un resultat identique a l'ecran. Elle ne dessine
      simplement plus rien, et aucune page n'a a changer.
   ⛔ NE PAS lui redonner un fond ou une bordure « pour separer un bloc » : un
      bloc se separe par son titre et son filet.
   ═══════════════════════════════════════════════════════════════════════ */
.carte {
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

/* ═══ LES SOUS-ONGLETS ══════════════════════════════════════════════════
   🎯 Deux vues d'un MEME onglet, cote a cote (« Prix » / « Fusion » dans
      « Prix des runes »). ⛔ Ce n'est PAS une seconde navigation : un
      sous-onglet ne change pas de sujet, il change d'angle sur le meme.

   🔴 MEME GRAMMAIRE QUE LE RAIL, TOURNEE DE 90° : l'actif se marque par un
      FILET, jamais par une pastille ou une capsule arrondie — c'est la regle
      posee sur `.onglet`, et deux grammaires pour une meme information
      rendraient l'ecran illisible. Le filet passe SOUS le libelle parce
      qu'ici la liste est horizontale : le bord qui la longe est le bas.
   ⛔ Pas de transition : un etat est instantane. */
.sous-onglets {
    display: flex;
    gap: var(--r-lg);
    margin-bottom: var(--r-md);
    border-bottom: 1px solid var(--bordure);
}

.sous-onglet {
    /* ⚠️ Le padding bas vaut 7px dont 2 sont manges par le filet de l'actif :
       le libelle ne bouge donc PAS d'un sous-onglet a l'autre. */
    padding: 4px 2px 7px;
    border: 0;
    background: transparent;
    color: var(--texte-attenue);
    font: inherit;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: .12em;
    text-transform: uppercase;
    cursor: pointer;
    /* Le filet de l'actif doit recouvrir celui du conteneur, pas s'ajouter
       au-dessus : -1px le pose exactement dessus. */
    margin-bottom: -1px;
}
/* ⛔ Hover ACHROMATIQUE : un survol ne porte aucun verdict. */
.sous-onglet:hover { color: var(--texte); }
.sous-onglet:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sous-onglet[aria-selected="true"] {
    color: var(--accent);
    box-shadow: inset 0 -2px 0 var(--accent);
}

/* 🎯 Un tableau etale sur 1500 px oblige l'oeil a traverser du vide entre le
   libelle et sa valeur. On plafonne : le tableau reste dense, la page respire. */
.carte-etroite { max-width: 900px; }

/* 🔴 L'EN-TETE DE BLOC — c'est le `.rc-mod-head` de RECAP. Il porte le filet
   1px qui remplace le cadre de la carte : sans carte, c'est le titre qui ouvre
   le bloc et le separe du precedent.
   ⚠️ `padding-bottom` ET `margin-bottom` a 6px : le filet colle au titre et
      respire avant le contenu. Inverser les deux collerait le trait au contenu
      et le detacherait de son titre. */
.carte-entete {
    display: flex;
    align-items: baseline;
    gap: var(--r-lg);
    padding: 0 2px 6px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--bordure);
}

/* ⛔ PLUS DE PADDING DE CARTE : le corps ne vit plus dans une boite, il coule
   sur la page. Le `2px` lateral l'aligne sur le titre, qui en porte autant. */
.carte-corps { padding: 0 2px; }

.libelle {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .14em;
    color: var(--texte-faible);
}

/* ═══ FORMULAIRES ══════════════════════════════════════════════════════ */
label { display: block; font-size: 11px; color: var(--texte-attenue); margin-bottom: 4px; }

/* ⚠️ Un champ garde un fond et un filet : c'est un CREUX dans la page, pas un
   bloc pose dessus. C'est le seul endroit ou une surface reste justifiee — on
   doit voir OU ON TAPE. */
input, select, textarea {
    width: 100%;
    padding: 5px 8px;
    font: inherit;
    font-size: 12px;
    color: var(--texte);
    background: var(--fond-eleve);
    border: 1px solid var(--bordure);
    border-radius: var(--arrondi-sm);
}
/* ⛔ PAS DE HALO (`box-shadow` de 3px) : c'etait un glow, et la DA n'en a
   aucun. Le focus se marque par le filet qui prend l'accent. */
input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--accent);
}
input[type="number"] { font-variant-numeric: tabular-nums; }
/* Les fleches de spinner volent 20px de large sur 45 champs de saisie. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none; margin: 0;
}
input[type="number"] { -moz-appearance: textfield; }

/* 🔴 UN BOUTON SECONDAIRE EST UN FILET, PAS UN APLAT. C'est le `.rc-split-edit`
   de RECAP : fond transparent, filet 1px, 10px + .08em en capitales. Sur une
   page sans surface, un bouton plein serait la seule tache de matiere de
   l'ecran et tirerait l'oeil plus que la donnee. */
.bouton {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    font: inherit;
    font-size: 10px;
    font-weight: 500;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--texte-attenue);
    background: transparent;
    border: 1px solid var(--bordure);
    border-radius: var(--arrondi-sm);
    cursor: pointer;
    white-space: nowrap;
}
/* ⛔ Le survol change la COULEUR, il n'allume pas un fond. */
.bouton:hover { color: var(--texte); border-color: var(--bordure-forte); }
.bouton:disabled { opacity: .5; cursor: not-allowed; }

.bouton-principal {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-texte);
    font-weight: 600;
}
.bouton-principal:hover { background: var(--accent-clair); border-color: var(--accent-clair); }

/* Le bouton de bascule de theme : carre, l'icone seule. */
.bouton-icone {
    width: 30px;
    padding: 0;
    justify-content: center;
    font-size: 14px;
    line-height: 1;
    color: var(--texte-attenue);
}
.bouton-icone:hover { color: var(--accent); }

/* ═══ TABLEAUX ═════════════════════════════════════════════════════════
   🔴 UN SEUL STYLE DE TABLE, partage par les 13 onglets. Repris de
      `leagues.css` l.1255-1320.
   ⛔ ZEBRAGE INTERDIT — regle ecrite chez STAXIS : « sans card, un fond de
      ligne redevient une BANDE visible sur le fond. La ligne se lit par son
      filet inferieur ; le fond ne sert qu'au hover. »
   ═════════════════════════════════════════════════════════════════════ */
table { width: 100%; border-collapse: collapse; font-size: 12px; }

/* 🔴 L'EN-TETE EST COLLE, ET SON FOND DOIT EGALER LE FOND REEL DERRIERE LA
   TABLE. Sans card, c'est `--fond` : ⛔ une surface y ferait une BANDE PLUS
   CLAIRE flottante au scroll. Il doit rester OPAQUE — les lignes defilent
   dessous.
   🎯 « Un EN-TETE de colonne est un MOT ; seule la CELLULE est une donnee » :
      d'ou le 10px attenue en capitales, face au 12px pleine encre des
      cellules. */
thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--fond);
    text-align: left;
    padding: 8px 10px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--texte-attenue);
    border-bottom: 1px solid var(--bordure);
    white-space: nowrap;
}

td { padding: 7px 10px; border-bottom: 1px solid var(--filet); }
tbody tr:hover { background: var(--fond-survol); }
tbody tr:last-child td { border-bottom: 0; }

th.num, td.num { text-align: right; }

/* ⚠️ Un conteneur qui defile HORIZONTALEMENT autour des tableaux larges :
   le body ne doit jamais defiler lateralement. */
.defile-x { overflow-x: auto; }

/* ═══ LA RECHERCHE D'ITEM ══════════════════════════════════════════════
   🎯 Choisir un item du jeu pour pre-remplir les 45 jets, avec son icone —
      on reconnait une Araknacoiffe a son image bien avant de lire son nom. */
.champ-recherche { position: relative; }

.suggestions {
    position: absolute;
    z-index: 40;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 3px;
    max-height: 340px;
    overflow-y: auto;
    /* 🔴 `--fond-eleve` ET NON `--fond-carte` : cette liste FLOTTE par-dessus
       la page. Depuis que `--fond-carte` vaut le fond de page (suppression des
       cartes, 2026-09-17), l'utiliser ici laisserait voir le contenu DESSOUS a
       travers les suggestions. Ce qui flotte doit etre opaque et distinct. */
    background: var(--fond-eleve);
    border: 1px solid var(--bordure-forte);
    border-radius: var(--arrondi);
    box-shadow: var(--ombre-lg);
}

.suggestion {
    display: flex;
    align-items: center;
    gap: var(--r-sm);
    padding: 5px var(--r-sm);
    cursor: pointer;
    border-bottom: 1px solid var(--filet);
}
.suggestion:last-child { border-bottom: 0; }

/* ⚠️ `.survolee` est pose par le CLAVIER (fleches), `:hover` par la souris.
   Les deux doivent se ressembler : l'utilisateur passe de l'un a l'autre. */
.suggestion:hover, .suggestion.survolee {
    background: var(--accent-fond);
}

/* 🔴 Taille FIXE sur l'icone : les images arrivent du reseau, et sans
   dimensions imposees chaque chargement decalerait la liste sous le curseur
   — on cliquerait sur la mauvaise ligne. */
.suggestion img,
.suggestion .icone-absente {
    width: 30px;
    height: 30px;
    flex: none;
    object-fit: contain;
    border-radius: var(--arrondi-sm);
    background: var(--fond-eleve);
}

.suggestion-nom {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
}

.suggestion-meta {
    flex: none;
    font-size: 11px;
    color: var(--texte-faible);
    font-variant-numeric: tabular-nums;
}

.suggestion-vide {
    padding: var(--r-md);
    font-size: 12px;
    color: var(--texte-faible);
    text-align: center;
}
.suggestion-vide code {
    color: var(--accent);
    font-family: var(--police-chiffres);
    font-size: 11px;
}

/* 🔴 LE RAPPEL QUI EVITE UNE ERREUR COUTEUSE : le catalogue donne le jet
   theorique MAXIMUM, jamais l'objet reel. Sans ce bandeau, on simulerait sur
   une borne haute en croyant simuler son objet — et on surestimerait sa
   valeur, donc on paierait trop cher. */
.origine-catalogue {
    margin-top: var(--r-md);
    padding: var(--r-sm) var(--r-md);
    background: var(--alerte-fond);
    border: 1px solid var(--accent-filet);
    border-radius: var(--arrondi-sm);
    font-size: 12px;
    color: var(--texte-attenue);
    line-height: 1.45;
}
.origine-catalogue strong { color: var(--alerte); }

/* Un bouton qui a l'aspect d'un lien : action secondaire dans un bloc de
   texte, la ou un vrai bouton casserait la phrase. */
.lien-bouton {
    display: inline;
    padding: 0;
    margin-left: 4px;
    border: 0;
    background: none;
    font: inherit;
    color: var(--accent);
    text-decoration: underline;
    cursor: pointer;
}
.lien-bouton:hover { color: var(--accent-clair); }

/* ═══ L'ETOILE DE FAVORI ═══════════════════════════════════════════════
   🎯 Poser une etoile pendant un scan doit etre un geste d'UN clic, sans
      quitter la liste ni ouvrir de formulaire.
   ⚠️ Elle vit DANS une ligne cliquable : le JS arrete la propagation du clic
      (cf. `brancheLignes`), sans quoi etoiler ouvrirait aussi le simulateur. */
.etoile {
    padding: 2px 4px;
    border: 0;
    background: none;
    /* Assez grande pour etre visee a la souris sans elargir la colonne. */
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
    /* ⚠️ Le creux (☆) est ATTENUE, pas invisible : une etoile qu'on ne voit
       pas ne se clique jamais. */
    color: var(--texte-attenue);
    /* ⛔ Ni transition ni `scale` au survol : la DA est inerte, et un
       grossissement est un effet qui n'ajoute aucune information. */
}
.etoile:hover:not(:disabled) { color: var(--alerte); }
/* 🔴 L'etoile pleine porte la couleur d'alerte (or) : c'est le seul jeton du
      design system qui lit « signale, a retenir » sans dire « gain » (vert,
      reserve a l'argent) ni « perte ». */
.etoile.est-favori { color: var(--alerte); }
.etoile:disabled { opacity: .45; cursor: default; }
.etoile:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--arrondi-sm);
}

/* Dans le bandeau d'origine du simulateur, l'etoile suit le texte. */
.etoile-simulateur { margin-left: 6px; vertical-align: middle; }

/* La note d'un favori : le texte prend la place, le bouton reste a droite.
   ⚠️ `min-width: 0` sur le texte — sans lui, une note longue pousserait le
      bouton hors de la cellule au lieu de se laisser tronquer. */
.favori-note {
    display: flex;
    align-items: center;
    gap: var(--r-sm);
    justify-content: space-between;
}
.favori-note > span {
    min-width: 0;
    max-width: 320px;
    font-size: 12px;
    color: var(--texte-attenue);
    overflow-wrap: anywhere;
}

/* ═══ LA GRILLE DES PRIX ═══════════════════════════════════════════════
   🎯 Les 45 runes sur UN ecran, sans defilement. Cf. l'en-tete de
      `js/pages/prix.js` pour ce que cette contrainte a coute.
   ⚠️ `align-items: start` : sans lui, la derniere colonne (plus courte de
      quelques lignes) etirerait ses lignes pour combler la hauteur. */
.grille-prix {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0 var(--r-xl);
    align-items: start;
}

/* Sous 1250 px, 3 colonnes tassent les noms de runes : on repasse a 2, puis
   a 1. Le defilement revient — c'est le compromis, et il vaut mieux qu'un
   tableau illisible. */
@media (max-width: 1250px) { .grille-prix { grid-template-columns: repeat(2, 1fr); } }
@media (max-width:  820px) { .grille-prix { grid-template-columns: 1fr; } }

/* 🔴 Lignes COMPACTES : c'est ce qui fait tenir 15 lignes par colonne. Le
   padding vertical tombe a 3px — la ligne fait ~28 px au lieu de 50. */
.table-prix td { padding: 3px 8px; }
.table-prix th { padding: 4px 8px; }

.table-prix .cellule-nom {
    max-width: 0;          /* force le tableau a respecter les largeurs */
    width: 55%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Le champ de saisie : compact, aligne a droite comme les chiffres. */
.table-prix input {
    width: 74px;
    padding: 2px 6px;
    text-align: right;
    font-size: 12px;
}

/* ⚠️ Un prix saisi mais PAS ENCORE enregistre doit se voir : sans ca, sur 45
   champs, on ne sait plus ce qu'on a tape ni si le bouton a fait effet. */
.table-prix input:not(:placeholder-shown) {
    border-color: var(--accent);
    background: var(--champ-rempli);
    font-weight: 600;
}

/* ── L'AGE DU PRIX, porte par la ligne ──────────────────────────────────
   🔴 Remplace les colonnes « Releve » et « Source », qui coutaient ~180 px.
      L'infobulle (`title`) porte le detail exact ; la couleur porte l'alerte.
   ⛔ PAS de vert pour « prix frais » : le vert signifie BENEFICE partout
      ailleurs dans le produit. Un prix frais n'a simplement aucune marque. */
.table-prix .ligne-perimee .cellule-nom,
.table-prix .ligne-perimee .chiffre { color: var(--alerte); }

.table-prix .ligne-sans-prix .cellule-nom { color: var(--texte-faible); }

/* ── LA DATE DU DERNIER RELEVE, sous le nom de la statistique ───────────
   🎯 POURQUOI SOUS LE NOM ET NON EN COLONNE : une colonne « Releve » coutait
      ~90 px de LARGEUR (cf. le bloc ci-dessus, qui l'avait justement retiree)
      et la page tient en 3 colonnes serrees. Une seconde ligne ne coute que
      de la HAUTEUR, dont il reste.

   🔴 POURQUOI PAR LIGNE ALORS QUE L'EN-TETE DONNE DEJA LA DERNIERE MAJ : on
      ne scanne presque jamais les 45 runes d'un coup. On en releve trois ou
      quatre, et il faut alors savoir lesquelles viennent d'etre lues — une
      date globale ne le dit pas.

   ⚠️ En italique et attenuee : c'est un REPERE, pas une donnee qu'on compare.
      Elle ne doit jamais concurrencer le prix a l'oeil. */
.table-prix .date-releve {
    display: block;
    font-size: 10px;
    font-style: italic;
    color: var(--texte-faible);
    line-height: 1.3;
    /* ⚠️ `cellule-nom` coupe a l'ellipse (nowrap + overflow hidden) pour que
       les noms longs ne poussent pas la colonne. La date, elle, est courte :
       on la laisse s'afficher entiere plutot que de risquer « il y a 3 m… ». */
    overflow: visible;
    text-overflow: clip;
    /* 🔴 La barre de score occupe la DROITE de cette meme ligne (cf. `.score`,
       en absolu). Cette marge lui reserve sa place : sans elle, « HDV · il y a
       3 min » passerait dessous et le degrade rognerait la date.
       ⚠️ 100 px = la piste (72) + le rang (11) + les ecarts. A retoucher si
          `.score-piste` change de largeur. */
    padding-right: 100px;
}

/* Un relevé périmé : la date passe en ambre comme le reste de la ligne. */
.table-prix .ligne-perimee .date-releve { color: var(--alerte); }

/* ⛔ PAS DE COULEUR POUR DISTINGUER LA SOURCE. `--accent` et `--alerte` ont la
   MEME valeur dans les deux themes (#e0aa54 / #8a5a15) : colorer « lu
   automatiquement » en accent le rendrait indistinguable d'un prix PERIME,
   c'est-a-dire de son contraire. La source est portee par un marqueur
   TEXTUEL (cf. `ligneFraicheur` dans pages/prix.js), qui ne peut pas se
   confondre. */

/* ═══ LE SCORE DE MARCHE D'UNE RUNE ════════════════════════════════════
   🎯 CE QU'IL MONTRE : le prix d'UN POINT DE POIDS (la colonne RATIO du
      classeur), rapporte a la meilleure rune de l'ecran. C'est le seul
      chiffre de cette page qui se compare d'une ligne a l'autre — le prix
      brut, lui, classe surtout les runes par leur poids.

   🔴 POURQUOI UNE BARRE ET PAS UNE COLONNE DE PLUS : la page tient 45 runes
      sans defilement (cf. `.grille-prix`), et il n'y a PAS 40 px de largeur
      pour un 4e nombre. Surtout, un nombre de plus ne se balaye pas : sur 45
      lignes, on compare des LONGUEURS, pas des chiffres. Le ratio exact
      reste dans l'infobulle, pour quand on s'arrete sur une ligne.

   🔴 POURQUOI CA NE COUTE AUCUNE HAUTEUR : la barre partage la ligne de la
      date (`.date-releve`), qui etait deja la et occupait moins de la moitie
      de la largeur du nom. La ligne de rune garde ses ~28 px.
   ═════════════════════════════════════════════════════════════════════ */
/* Le fond effectif de la ligne, qui SUIT le survol. Le degrade qui masque la
   fin d'un nom trop long s'y accroche.
   ⚠️ `--fond` et non `--fond-carte` : il n'y a plus de carte, la ligne repose
      sur le fond de page.
   ⚠️ Le survol etant devenu un rgba TRANSPARENT (achromatique), on ne peut pas
      l'utiliser tel quel dans un degrade — il laisserait voir le texte dessous.
      On compose donc la couleur opaque equivalente. */
.table-prix tbody tr { --fond-ligne: var(--fond); }
.table-prix tbody tr:hover { --fond-ligne: #101216; }

.table-prix .cellule-nom { position: relative; }

/* 🔴 LA VALEUR EST AU-DESSUS DE LA BARRE (repris de `.lg-ld`, leagues.css).
   La barre est un RENFORT, pas le porteur de l'information : c'est le chiffre
   qu'on lit, la longueur ne sert qu'a comparer du coin de l'oeil.
   ⚠️ Benjamin, 2026-09-17 : « tes barres sont grosses, a gauche du chiffre, et
      ne disent pas leur echelle ». Les trois points sont corriges ici — 3px au
      lieu de 5, le chiffre au-dessus, et le % affiche. */
.score {
    position: absolute;
    right: 8px;
    bottom: 3px;
    display: inline-flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
    background: linear-gradient(90deg, transparent, var(--fond-ligne) 6px);
    padding-left: 10px;
}

/* La valeur du score, en % du meilleur de l'ecran. 9px + .1em : c'est une
   mention de service, elle ne doit pas concurrencer le prix. */
.score-valeur {
    font-size: 9px;
    letter-spacing: .1em;
    line-height: 1;
    color: var(--texte-faible);
    font-variant-numeric: tabular-nums;
}

/* La piste : le 100 %, toujours visible. ⚠️ Sans elle, une barre courte ne se
   distingue pas d'une barre absente — or « paie mal » et « on ne sait pas »
   sont deux verdicts opposes.
   🔴 3px, rayon 2px, fond `rgba(255,255,255,.05)` — les valeurs exactes de
      `.lg-ld-bar`. L'ancienne piste faisait 5px : sur 45 lignes, c'est une
      colonne de barreaux qui pesait plus que les chiffres.
   ⚠️ La perte de lisibilite des ecarts (l'argument qui avait fait passer de 3 a
      5px) est compensee par le CHIFFRE, qui est maintenant affiche au-dessus :
      on ne demande plus a la barre seule de porter la comparaison fine. */
.score-piste {
    position: relative;
    width: 72px;
    height: 3px;
    border-radius: 2px;
    background: rgba(255, 255, 255, .05);
    overflow: hidden;
}

/* 🔴 LA JAUGE EST NEUTRE (texte attenue), PAS VERTE NI OR :
   - le VERT signifie BENEFICE partout dans le produit, et un bon ratio n'est
     pas un benefice : c'est un prix eleve, qui est une mauvaise nouvelle
     quand c'est vous qui ACHETEZ la rune ;
   - l'OR (`--accent`) est reserve aux etats, et 45 barres or feraient de
     l'accent une couleur de decor — il ne signalerait plus rien. */
.score-jauge {
    position: absolute;
    inset: 0 auto 0 0;
    /* ⚠️ `--texte-attenue` et non `--texte-faible` : a 3 px de haut, la jauge
       n'a aucune masse pour porter une couleur discrete. */
    background: var(--texte-attenue);
    border-radius: 2px;
}

/* Les runes de TETE sont bien un etat : trois lignes sur 45. L'or y retrouve
   son sens — « c'est celle-ci, maintenant ». */
.score-piste.est-en-tete { background: var(--accent-filet); }
.score-piste.est-en-tete .score-jauge { background: var(--accent); }

/* ═══ LA LIQUIDITE DANS LA BARRE ════════════════════════════════════════
   🎯 LA DIMENSION AJOUTEE LE 2026-08-30 : la barre porte le SCORE (ratio x
      liquidite), pas le ratio seul. Restait a montrer QUAND ce score repose
      sur un volume connu — sans ajouter ni colonne ni hauteur.

   🔴 DEUX ETATS QU'IL NE FAUT SURTOUT PAS CONFONDRE :
        `volume-inconnu` = on n'a jamais releve le volume de cette rune ;
        `volume-faible`  = on l'a releve, et il est mauvais.
      Le premier est une LACUNE de notre cote, le second un fait du marche.
      Les rendre identiques ferait lire « ne se vend pas » sur 42 runes dont
      on ignore tout — l'erreur exacte de [dofus-couverture-prix-partielle].
   ═════════════════════════════════════════════════════════════════════ */

/* VOLUME JAMAIS RELEVE — la piste passe en pointilles : la barre montre le
   ratio SEUL, et le pointille dit « mesure incomplete » sans rien affirmer sur
   le marche.
   ⚠️ On evide la piste au lieu de la colorer : une couleur serait un verdict,
      alors qu'ici on n'en a pas. Et `--alerte` est deja pris par le prix
      perime — le reutiliser rendrait les deux illisibles. */
.score-piste.volume-inconnu {
    background: transparent;
    border: 1px dashed var(--bordure);
    /* La bordure mange 2 px de hauteur interieure : sans ca, la jauge d'une
       piste en pointilles serait plus fine que celle des autres, et paraitrait
       plus courte a longueur egale. (3 + 2 = 5) */
    height: 5px;
}
.score-piste.volume-inconnu .score-jauge {
    /* Attenuee : ce score est provisoire, il bougera quand le volume arrivera. */
    opacity: 0.55;
}

/* VOLUME RELEVE ET FAIBLE — un point plein en bout de piste. La barre est
   DEJA raccourcie par le calcul ; ce point dit POURQUOI elle l'est, sinon on
   croirait a un simple prix bas. */
.score-piste.volume-faible::after {
    content: '';
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 3px;
    border-radius: 50%;
    background: var(--alerte);
}

/* Le rang, en chiffre : 1, 2, 3. ⚠️ Un chiffre et pas une medaille/emoji —
   il se lit a 9px, se traduit tout seul, et ne depend pas de la police.

   🔴 IL VIT SUR LA LIGNE DU CHIFFRE, a gauche du pourcentage — plus dans une
      gouttiere a cote de la piste. Depuis que la valeur est passee AU-DESSUS
      de la barre (2026-09-17), cette ligne existe et le rang y a sa place
      naturelle ; la piste garde son bord gauche commun aux 45 lignes, ce qui
      etait la raison d'etre de l'ancienne gouttiere. */
.score-rang {
    font-size: 9px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: .1em;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
    margin-right: 4px;
}

/* ⛔ LA GOUTTIERE DU RANG A ETE RETIREE (2026-09-17). Elle reservait 11px a
   GAUCHE DE LA PISTE pour que les 45 barres partent du meme bord. Depuis que
   le rang vit sur la ligne du chiffre (au-dessus de la barre), la piste est
   seule sur sa ligne et son bord gauche est commun par construction.
   ⚠️ La raison d'etre de l'ancienne gouttiere — « comparer des longueurs
      suppose une origine commune » — reste donc satisfaite, autrement. */

/* La ligne du haut : le rang (s'il existe) et la valeur. */
.score-tete {
    display: inline-flex;
    align-items: baseline;
    justify-content: flex-end;
}

/* ⛔ PAS DE STYLE « HORS CLASSEMENT » ICI, et c'est deliberé. Initiative,
   Vitalite et Pods ont un temps ete ECARTEES du classement parce que leur
   ratio sortait a 790/780/580 — mais c'etait un BUG DE DIVISION (leur prix
   etait rapporte au poids d'UN POINT, alors qu'une rune en donne 10, 5 et 10),
   pas une propriete de ces runes. Le calcul corrige (cf. `Rune.poids_rune`),
   elles se classent normalement : 17e, 29e et 36e sur les prix du 29/08.
   🔴 SI UNE RUNE PARAIT A NOUVEAU ABERRANTE : corriger le calcul, ne pas la
      sortir du classement. C'est l'erreur qui a ete faite ici. */

/* ── LE VERDICT, en haut de la carte ────────────────────────────────────
   🎯 Les barres servent a COMPARER ; cette ligne sert a DECIDER. On arrive
      sur l'ecran en voulant savoir QUOI focus, et la reponse doit etre
      ecrite — pas a reconstruire en cherchant la plus longue barre sur 45. */
.bandeau-score {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--r-xs) var(--r-md);
    padding: var(--r-sm) var(--r-lg);
    border-bottom: 1px solid var(--filet);
    font-size: 12px;
}
.bandeau-score-titre {
    color: var(--texte-faible);
    text-transform: uppercase;
    letter-spacing: .06em;
    font-size: 10px;
    font-weight: 600;
}
.teinte-accent { color: var(--accent); font-weight: 600; }

/* ═══════════════════════════════════════════════════════════════════════
   LA SERIE DE MARCHE — la courbe d'une rune, a la demande (PAGE PILOTE)

   🎯 POSE LE 2026-09-17 avec la DA « salle des marches ». Les 45 lignes disent
      le prix MAINTENANT ; elles ne disent pas s'il MONTE. Cette zone repond a
      la seule question de l'ecran qu'un tableau ne peut pas porter.

   🔴 MOTIF DESTINE A LA PROPAGATION (apres validation) : `.zone-serie`,
      `.courbe-marche`, `.courbe-trace`, `.courbe-bornes`, `.courbe-vide`. Les
      onglets Direct, Scouting et Craft ont la meme matiere (une valeur relevee
      dans le temps) et devront reutiliser ces classes plutot que d'en
      reinventer. ⚠️ `scouting.js` a deja sa propre `.scout-courbe`, ecrite
      avant : elle n'est PAS touchee ici, elle sera ralliee a la propagation.

   ⚠️ TOUT EST SCOPE SOUS `.zone-serie` / `.courbe-*` : ces classes n'existaient
      pas, donc aucune regle existante des 13 onglets n'est repeinte.
   ═══════════════════════════════════════════════════════════════════════ */
/* ⛔ AUCUN FOND, AUCUN CADRE : la serie n'est pas un panneau pose sur la page,
   c'est une zone DE la page. Elle est bornee par un filet en haut et en bas,
   comme un module de RECAP. */
.zone-serie {
    padding: var(--r-lg) 2px;
    border-bottom: 1px solid var(--bordure);
}

/* Le titre de la serie — c'est un titre de module : 11px + .14em. */
.courbe-titre {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--r-md);
    margin-bottom: 6px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--bordure);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--texte-attenue);
}
.courbe-titre strong { color: var(--texte); font-weight: 600; }

/* 🔴 LE GRAPHE N'A PAS DE FOND PROPRE — thème STAXIS (`overview.js` l.2334) :
      « fond du graphe : TRANSPARENT (il se pose sur #07080a) ».
   ⛔ Le cadre et le fond `--fond-carte` de la premiere version ont ete
      retires : c'etait une card autour d'un graphe, sur une page qui n'en a
      plus.
   ⚠️ `height` explicite ET `width:100%` : le SVG est en `preserveAspectRatio:
      none`, donc sans hauteur fixee il s'effondrerait a 0 px. */
.courbe-svg {
    width: 100%;
    height: 132px;
    display: block;
    background: transparent;
    /* 🔴 Le point aberrant SORT du cadre (cf. `bornesRobustes` dans prix.js) :
       sans cette coupe, il dessinerait un pic par-dessus le tableau. Il reste
       trace — on le borne a l'affichage, on ne le supprime pas. */
    overflow: hidden;
}

/* 🔴 LE TRACE EST NEUTRE, PAS VERT NI OR.
   - le VERT signifie BENEFICE partout dans le produit : une courbe verte
     dirait « tu gagnes » alors qu'elle ne dit que « le prix a bouge », et sa
     couleur ne changerait meme pas quand elle descend ;
   - l'OR est reserve aux etats, et une courbe n'est pas un etat.
   ⚠️ C'est la VARIATION chiffree, sous la courbe, qui porte le gain/perte —
      avec son signe ecrit, pas la teinte seule. */
.courbe-trace { stroke: var(--texte-attenue); }

/* 🔴 LA GRILLE ET LES AXES — valeurs du theme STAXIS (`overview.js` l.2334) :
      grille `rgba(255,255,255,.05)` · axes/zero `rgba(255,255,255,.09)`.
   ⛔ NEUTRES, jamais verts « pour faire terminal » : la regle du design-system
      classe axes, grilles et graduations parmi les elements qui ne portent
      aucun verdict. */
.courbe-grille { stroke: var(--graphe-grille); stroke-width: 1; }
.courbe-axe    { stroke: var(--graphe-axe);    stroke-width: 1; }

/* Les graduations : 9px, le bas de l'echelle typo. ⛔ Pas de legende, pas de
   barre d'outils — le theme STAXIS les exclut explicitement. */
.courbe-tick {
    font-size: 9px;
    fill: var(--texte-faible);
    font-variant-numeric: tabular-nums;
}

/* ⚠️ `--graphe-grille` / `--graphe-axe` sont definis avec les autres tokens,
   et REDEFINIS par le theme clair : un blanc a 5 % serait invisible sur un
   fond clair. ⛔ Pas de regle `[data-theme="light"] .courbe-*` ici — un theme
   redefinit des variables, il n'ajoute jamais de regle propre. */

.courbe-bornes {
    display: flex;
    justify-content: space-between;
    gap: var(--r-sm);
    margin-top: 4px;
    flex-wrap: wrap;
}

/* ⚠️ Remise a zero de la reserve de 100 px que `.table-prix .date-releve`
   garde pour la barre de score : il n'y a pas de barre ici, et la marge
   decalerait les trois legendes. Meme correction que `.table-paliers`. */
.courbe-bornes .date-releve { padding-right: 0; }

/* 🔴 L'ETAT VIDE PORTE LA RAISON, jamais un joli pictogramme. Le projet a deja
   paye ce piege : un cadre vide se lit « ce marche est mort » alors qu'il veut
   dire « on n'a pas releve ». Cf. [dofus-couverture-prix-partielle]. */
.courbe-vide {
    padding: var(--r-lg);
    text-align: center;
    font-size: 12px;
    color: var(--texte-faible);
    /* ⛔ Pas de fond : le pointille suffit a dire « zone sans contenu ». Le
       dashed est le filet de « l'optionnel » de la grammaire STAXIS. */
    background: transparent;
    border: 1px dashed var(--bordure);
    border-radius: var(--arrondi-sm);
}

/* La ligne dont la serie est ouverte. ⚠️ L'accent est ICI a sa place : c'est un
   etat (« la ligne courante »), et il n'y en a qu'UNE sur les 45 — il ne se
   repete pas a chaque ligne. */
.table-prix tbody tr.est-serie-ouverte {
    --fond-ligne: var(--fond-eleve);
    background: var(--fond-eleve);
}
.table-prix tbody tr.est-serie-ouverte .cellule-nom {
    box-shadow: inset 2px 0 0 var(--accent);
}

/* 🔴 La ligne est cliquable ET focusable depuis le 2026-09-17 (elle ouvre la
   serie). Sans curseur ni focus visible, la fonctionnalite reste invisible —
   meme regle que `.ligne-cliquable` du scouting. */
.table-prix tbody tr[data-cle] { cursor: pointer; }
.table-prix tbody tr[data-cle]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* ═══ L'ARBITRAGE DE FUSION ════════════════════════════════════════════
   🎯 « Le geste » est la colonne qui dit QUOI FAIRE — acheter 3 runes a tel
      prix, revendre a tel autre. C'est la seule colonne de l'outil qui porte
      une instruction plutot qu'un chiffre, donc elle respire.
   ⚠️ Elle ne doit PAS etre en chasse fixe : ce n'est pas un nombre qu'on
      aligne, c'est une phrase qu'on lit. */
.fusion-geste {
    font-size: 12px;
    color: var(--texte-attenue);
    line-height: 1.45;
}
/* Le nom de la rune porte l'information ; la route (« 3 basiques → 1 Pa ») est
   un sous-titre, comme la date sous une rune dans l'ecran des prix. */
.fusion-geste + td { white-space: nowrap; }

/* ── LA COLONNE FUSION, dans l'ecran des prix ───────────────────────────
   🎯 Elle remplace la colonne de saisie « Nouveau » (mesure : 44 des 45 prix
      viennent du reader, la saisie n'avait servi que 4 fois). Elle repond a
      « quelle rune vaut le coup d'etre achetee puis fusionnee », d'un coup
      d'oeil, sur l'ecran qu'on regarde le plus.

   ⚠️ DEUX INFORMATIONS DANS UNE CELLULE ETROITE : la route (« ×3→Pa ») et la
      rentabilite. La route est en petit et attenuee, la renta porte la
      couleur — c'est elle qu'on balaye. */
.fusion-cell {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    justify-content: flex-end;
    white-space: nowrap;
}
.fusion-route {
    font-size: 10px;
    font-family: var(--police-chiffres);
    color: var(--texte-faible);
}
.fusion-renta {
    font-family: var(--police-chiffres);
    font-size: 12px;
    font-weight: 600;
    color: var(--gain);
}

/* 🔴 Une marge calculee sur deux prix d'ages differents mesure surtout le
   TEMPS ECOULE. Elle est attenuee et prefixee d'un « ~ » (pose par le JS) —
   jamais masquee : cf. l'onglet Fusion, meme regle, memes mots. */
.fusion-cell.est-doute { opacity: .55; }
.fusion-cell.est-doute .fusion-renta { color: var(--texte-attenue); }

/* « Pas rentable » et « on ne sait pas » se ressemblent a l'ecran : le tiret
   est donc discret et son infobulle dit lequel des deux c'est. */
.fusion-vide { color: var(--texte-faible); }

/* Un bouton qui porte un MODE en cours (« la saisie est ouverte »), et non une
   action ponctuelle. L'accent est ici a sa place : c'est un etat. */
.bouton.est-actif {
    border-color: var(--accent);
    color: var(--accent);
    background: var(--accent-fond);
}

/* ── LE TABLEAU DES TROIS PALIERS ───────────────────────────────────────
   🎯 MEME GRAMMAIRE QUE `.grille-prix` : plusieurs colonnes cote a cote pour
      voir un maximum de runes d'un coup d'oeil, au lieu d'une liste a
      derouler. Cf. `js/pages/fusion.js` pour le decoupage.

   ⚠️ DEUX colonnes et non trois : chaque table porte 4 colonnes (le nom + les
      trois paliers) la ou l'ecran des prix n'en a que 3. A trois exemplaires
      sur 1500 px, les prix se toucheraient et les noms seraient tronques. */
.grille-paliers {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0 var(--r-xl);
    align-items: start;
}
@media (max-width: 1100px) { .grille-paliers { grid-template-columns: 1fr; } }

/* ⛔ PAS DE DATE SOUS CHAQUE PRIX ICI. Une ligne porte trois prix : trois
   dates en feraient trois lignes de texte, ecraseraient les colonnes et
   brouilleraient la comparaison qu'on vient justement chercher. La date vit
   sous le NOM (cf. `dateLigne`), et porte l'age du releve le PLUS ANCIEN —
   le maillon faible de la ligne. */
.table-paliers .cellule-nom { width: 40%; }
.table-paliers td.num { white-space: nowrap; }

/* 🔴 La reserve de 100 px que `.table-prix .date-releve` garde pour la barre
   de score n'a pas lieu d'etre ici : cet ecran n'a pas de barre, et la marge
   tronquerait les noms longs pour rien. */
.table-paliers .date-releve { padding-right: 0; }

.sans-prix {
    color: var(--alerte);
    font-weight: 600;
    font-size: 11px;
}

.teinte-alerte { color: var(--alerte); font-weight: 600; }

/* 🔴 Une ligne dont le chiffre est SUSPECT (focus ressortant plus de POIDS que
   l'objet n'en contient). ⚠️ Attenuee, PAS masquee : on montre la donnee et on dit
   pourquoi on ne s'y fie pas — la cacher ferait croire a un oubli, et on ne
   verrait plus jamais le probleme. */
tr.ligne-douteuse td { opacity: .55; }
tr.ligne-douteuse:hover td { opacity: .8; }

/* ═══ ETATS FINANCIERS ═════════════════════════════════════════════════ */
.gain  { color: var(--gain);  font-weight: 600; }
.perte { color: var(--perte); font-weight: 600; }

.puce {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 7px;
    font-size: 11px;
    font-weight: 600;
    /* 🔴 Rayon 3px et non 20px : une capsule entierement arrondie est un
       marqueur d'« AI Designed », et l'echelle du projet s'arrete a 7px. */
    border-radius: 3px;
    white-space: nowrap;
}
.puce-gain   { background: var(--gain-fond);   color: var(--gain); }
.puce-perte  { background: var(--perte-fond);  color: var(--perte); }
.puce-alerte { background: var(--alerte-fond); color: var(--alerte); }
.puce-neutre { background: var(--fond-eleve);  color: var(--texte-attenue); }

/* ═══ LE VERDICT ═══════════════════════════════════════════════════════
   🎯 LE bloc qu'on lit en une seconde devant l'HDV : « paye au maximum X ».
      Il est volontairement le plus gros element de l'ecran. */
.verdict {
    display: flex;
    flex-wrap: wrap;
    gap: var(--r-lg);
    padding: var(--r-lg);
    background: var(--accent-fond);
    border: 1px solid var(--accent-filet);
    border-radius: var(--arrondi-lg);
}

.verdict-bloc { flex: 1; min-width: 150px; }

.verdict-valeur {
    font-family: var(--police-chiffres);
    font-variant-numeric: tabular-nums;
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.15;
    color: var(--texte);
}

.verdict-libelle {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--texte-attenue);
    margin-bottom: 3px;
}

.verdict-note { font-size: 12px; color: var(--texte-attenue); margin-top: 3px; }

/* ═══ AVERTISSEMENTS ═══════════════════════════════════════════════════
   🔴 Un prix manquant ou perime doit se VOIR. C'est le bloc qui empeche de
      prendre une decision sur des donnees creuses. */
.avertissement {
    display: flex;
    gap: var(--r-sm);
    padding: var(--r-sm) var(--r-md);
    background: var(--alerte-fond);
    border: 1px solid var(--accent-filet);
    border-radius: var(--arrondi-sm);
    font-size: 12px;
    color: var(--texte-attenue);
}
.avertissement strong { color: var(--alerte); }

.vide {
    padding: var(--r-2xl) var(--r-lg);
    text-align: center;
    color: var(--texte-faible);
    font-size: 13px;
}

/* ═══ GRILLES ══════════════════════════════════════════════════════════ */
.grille { display: grid; gap: var(--r-lg); }
.grille-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }

/* ── La grille du simulateur ────────────────────────────────────────────
   🎯 DEUX ETATS, et c'est le point : au chargement le formulaire occupe
      TOUTE la largeur (les 45 jets tiennent alors sur 6-7 lignes au lieu de
      15). Des qu'un resultat existe, la vue se scinde et le verdict s'affiche
      a droite, le formulaire restant visible pour ajuster un jet.
   ⚠️ Sans ce second etat, on relirait le formulaire par-dessus le resultat a
      chaque correction. */
.simu { display: grid; gap: var(--r-lg); align-items: start; }

.simu-resultat {
    grid-template-columns: minmax(380px, 4fr) minmax(420px, 5fr);
}

/* En dessous, deux colonnes tasseraient les deux : on empile, resultat en
   PREMIER — c'est lui qu'on vient lire. */
@media (max-width: 1100px) {
    .simu-resultat { grid-template-columns: 1fr; }
    .simu-resultat > #sim-resultat { order: -1; }
}

/* La grille de saisie des 45 jets : dense, mais pas illisible.
   ⚠️ `max-width` sur la cellule : sans lui, en pleine largeur les colonnes
      s'etirent a 300 px et le libelle se retrouve loin de son champ — l'oeil
      doit alors traverser du vide pour associer les deux. */
.grille-jets {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
    gap: var(--r-sm) var(--r-md);
}

.champ-jet {
    display: flex;
    align-items: center;
    gap: var(--r-sm);
    max-width: 240px;
}
.champ-jet label {
    flex: 1;
    margin: 0;
    font-size: 12px;
    color: var(--texte-attenue);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.champ-jet input { width: 62px; flex: none; text-align: right; }

/* 🔴 Un jet saisi doit se distinguer d'un champ vide au premier coup d'oeil :
   sur 45 champs, sans ca on ne retrouve pas ce qu'on vient de taper.
   ⚠️ TROIS signaux cumules, et ce n'est pas du zele : la couleur seule ne
      suffit pas (elle n'existe pas pour un daltonien), le gras seul est
      discret a 13px. Fond + bordure + graisse se lisent tous les trois. */
.champ-jet input:not(:placeholder-shown) {
    border-color: var(--accent);
    background: var(--champ-rempli);
    color: var(--texte);
    font-weight: 600;
}

.ligne-boutons { display: flex; gap: var(--r-sm); flex-wrap: wrap; align-items: center; }

/* 🔴 `hidden` DOIT GAGNER sur le `display` des boutons et cellules. Sans cette
   regle, un `display` explicite (celui de `.bouton`, celui d'une cellule de
   tableau) l'emporte sur l'attribut et l'element reste visible alors que le
   code le croit cache — un bouton « Enregistrer » offert hors du mode saisie,
   par exemple. */
[hidden] { display: none !important; }

.discret { color: var(--texte-faible); font-size: 12px; }

/* ═══ LE SCOUTING — le classement des cibles de brisage ═══════════════════════
   🎯 L'ecran repond a « quel item aller regarder ? », la ou le simulateur
      repond « que vaut CET item ? ». Sa matiere est donc une LISTE longue
      qu'on balaye, pas un verdict qu'on lit.

   ⚠️ Aucun token nouveau ici : tout reprend la palette existante. Une couleur
      inventee pour cet ecran ne vivrait que dans un theme sur deux. */

/* La ligne entiere est cliquable (elle ouvre le simulateur pre-rempli).
   🔴 Le `cursor:pointer` et le survol sont donc OBLIGATOIRES, pas cosmetiques :
      sans eux, rien n'indique que la ligne est actionnable, et la
      fonctionnalite reste invisible. */
.ligne-cliquable { cursor: pointer; }
.ligne-cliquable:hover { background: var(--fond-survol); }

/* ⚠️ Le focus clavier doit rester visible : cette table se parcourt aussi au
      clavier, et un `outline:none` rendrait la navigation aveugle. */
.ligne-cliquable:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* L'icone de l'item dans la liste. Taille alignee sur `.suggestion img` pour
   que les deux listes d'items du produit se ressemblent. */
.scout-icone {
    width: 28px; height: 28px;
    object-fit: contain;
    vertical-align: middle;
    flex: none;
}

.scout-nom { display: flex; align-items: center; gap: var(--r-sm); }


/* 🎯 L'ECART est LE chiffre du classement : c'est lui qui dit pourquoi l'item
      est dans la liste. Il est donc plus gros que le reste de la ligne.
   ⛔ Il n'est PAS colore en vert : le vert porte « tu gagnes de l'argent »
      (cf. l'en-tete de ce fichier), or un ecart eleve n'est pas encore un
      benefice — il faut acheter au bon prix pour cela. Il porte l'accent. */
.scout-ecart {
    font-family: var(--police-chiffres);
    font-size: 15px;
    font-weight: 700;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
}

/* ═══ LA RUNE PRODUITE PAR LE FOCUS ════════════════════════════════════
   🎯 Le rang de la rune obtenue, dans le classement de la page Prix (prix à
      poids égal × volume). Il répond à « cette stat, ça se revend ? », que
      l'écart focus ne pose pas.

   🔴 SEULEMENT DU MAUVAIS COTE. Mesuré : marquer aussi le podium donnait 19
      marques sur 40 lignes (« % Critique » est 3ᵉ et domine le catalogue) —
      une marque sur la moitié de l'écran décore au lieu d'informer, et elle
      noyait les rares lignes à problème. Cf. `marqueRune` dans
      pages/scouting.js.
   ═════════════════════════════════════════════════════════════════════ */
.rune-marque {
    display: inline-block;
    margin-top: 2px;
    font-size: 10px;
    line-height: 1.3;
    font-weight: 600;
}

/* ⚠️ Une rune qui s'écoule mal est un AVERTISSEMENT, pas une erreur : l'item
   reste une cible valable, il se revendra lentement. Même ambre que les prix
   périmés — dans les deux cas « c'est utilisable, mais regarde-y à deux
   fois ». */
.rune-marque.est-peu-liquide { color: var(--alerte); }

/* La barre des filtres : dense, sur une ligne, au-dessus du tableau. */
.scout-filtres {
    display: flex;
    flex-wrap: wrap;
    gap: var(--r-md);
    align-items: flex-end;
}

.scout-filtres label { margin-bottom: 4px; }
.scout-filtres input[type="number"] { width: 90px; }
.scout-filtres select { width: auto; min-width: 150px; }

/* Une case a cocher avec son libelle : `label` est en `display:block` par
   defaut dans ce design system, il faut donc le remettre en ligne ici. */
.scout-case {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--texte-attenue);
    cursor: pointer;
    margin-bottom: 7px;
    white-space: nowrap;
}

.scout-case input { width: auto; margin: 0; cursor: pointer; }

/* Le lien « cours » dans la cellule de prix. Un bouton, mais qui doit se lire
   comme un lien discret : il vit sous un chiffre, dans une ligne deja
   cliquable, et un vrai bouton y attirerait plus l'oeil que la donnee.

   ⛔ PAS `--accent` ICI, malgre l'envie : ce lien est repete a CHAQUE ligne
      cotee, et l'or y perdrait son sens d'etat (cf. le token). On prend le
      texte attenue, et l'accent n'arrive qu'au survol et au focus — la, il
      designe bien UN element courant. */
.lien-cours {
    background: none;
    border: none;
    padding: 0;
    margin-left: 4px;
    font: inherit;
    font-size: 11px;
    color: var(--texte-attenue);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.lien-cours:hover { color: var(--accent); }

/* ⚠️ Le focus clavier doit rester VISIBLE : le lien est minuscule et sur fond
   sombre, sans cet outline on ne sait plus ou on est en tabulant. */
.lien-cours:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 2px;
}

/* La courbe du cours d'un item — SVG inline, sans librairie. */
.scout-courbe { margin: var(--r-md) 0; }

/* ⚠️ `height` explicite ET `width:100%` : le SVG est en `preserveAspectRatio:
   none` (il s'etire pour remplir la largeur disponible), donc sans hauteur
   fixee il s'effondrerait a 0 px dans un conteneur flex. */
.scout-courbe svg {
    width: 100%;
    height: 120px;
    display: block;
    background: var(--fond);
    border: 1px solid var(--filet);
    border-radius: var(--r-sm);
}

.scout-courbe-bornes {
    display: flex;
    justify-content: space-between;
    gap: var(--r-sm);
    margin-top: 4px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   LE TABLEAU DU CRAFT — 11 colonnes, dont 8 de chiffres

   🎯 POURQUOI IL A SON PROPRE BLOC : `.table-prix` a ete dessine pour 4
      colonnes affichees en 3 grilles cote a cote. Le craft en a 11 sur toute
      la largeur, et deux regles de `.table-prix` s'y retournent contre lui :

      1. `.date-releve { overflow: visible }` — utile sous un nom d'item (« il
         y a 3 j » ne doit pas s'elider), mais dans une colonne etroite alignee
         a droite, le texte DEBORDE sur la voisine. C'est ce qui faisait fuir
         « −13,4 % » sous la colonne « Gain / jour ».

      2. `.cellule-nom { width: 55% }` — raisonnable sur 4 colonnes, ecrasant
         sur 11 : il ne resterait ~4 % par colonne de chiffres.
   ═══════════════════════════════════════════════════════════════════════════ */
.table-craft { width: 100%; }

/* La colonne des noms reprend la main sur la largeur, sans etouffer le reste. */
.table-craft .cellule-nom { width: auto; min-width: 190px; max-width: 320px; }

/* Meme remise a zero sous le nom : la reserve de 100 px pour la barre de score
   n'a pas lieu d'etre ici, et elle tronquait la composition de la recette. */
.table-craft .cellule-nom .date-releve {
    padding-right: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 🔴 Les colonnes de chiffres ne se coupent JAMAIS sur deux lignes : un
      « 271,5 » qui passe a la ligne se lit comme deux nombres. */
.table-craft td.num, .table-craft th.num { white-space: nowrap; }

/* ⛔ La correction du debordement : dans une cellule de chiffres, la mention
      secondaire (valeur « fabrique », tendance) reste DANS sa colonne.
   🎯 Elle porte la MEME graisse et le meme alignement que la valeur du dessus :
      les deux se lisent comme deux variantes d'un meme nombre, pas comme un
      chiffre et sa note de bas de page. */
.table-craft td.num .date-releve {
    /* 🔴 LE COUPABLE DU DECALAGE : `.table-prix .date-releve` pose un
          `padding-right: 100px` — une reserve pour la barre de score de
          l'ecran des prix, qui n'existe pas ici. Sans cette remise a zero, la
          sous-ligne fuit de 100 px vers la gauche, loin du nombre qu'elle
          commente. */
    padding-right: 0;
    display: block;
    text-align: right;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-style: normal;
    font-variant-numeric: tabular-nums;
}

/* 🎯 Un peu d'air vertical : 11 colonnes serrees ont besoin de respirer plus
      que les 45 lignes compactes de l'ecran des prix. */
.table-craft td { padding: 6px 10px; }
.table-craft th { padding: 6px 10px; }

/* L'en-tete d'une colonne dont les cellules portent une mention secondaire
   s'aligne en BAS : sinon « VENTES / J » flotte au-dessus de « GAIN / JOUR ». */
.table-craft thead th { vertical-align: bottom; }

/* Les cellules s'alignent en HAUT : la valeur principale de chaque colonne se
   lit alors sur une meme ligne horizontale, la mention secondaire en dessous. */
.table-craft tbody td { vertical-align: top; }

/* ── LES RECETTES INCOMPLETES ───────────────────────────────────────────────
   🎯 Ce que le jeu propose et qu'on n'a pas encore releve. Elles sont VISIBLES
      (sans elles, l'ecran laisse croire que le jeu compte 39 recettes au lieu
      de 869) mais nettement EN RETRAIT : on ne peut rien decider dessus.

   🔴 PAS D'AMBRE ICI, contrairement aux prix perimes. L'ambre signale une
      donnee PEU FIABLE sur laquelle on pourrait quand meme agir ; une recette
      incomplete n'est pas peu fiable, elle est INCONNUE. Le gris dit « pas
      encore lu », l'ambre dirait « attention au chiffre » — or il n'y a
      justement aucun chiffre.
   ═══════════════════════════════════════════════════════════════════════════ */
.table-craft .ligne-incomplete td,
.table-craft .ligne-incomplete .cellule-nom { color: var(--texte-faible); }

/* L'icone aussi passe en retrait : a pleine couleur, elle attire l'oeil sur
   la seule partie de la ligne qui n'apprend rien. */
.table-craft .ligne-incomplete .scout-icone { opacity: .45; }

/* Le survol reste actif : ces lignes s'ouvrent, et leur detail dit exactement
   quels prix relever. */
.table-craft .ligne-incomplete:hover td { color: var(--texte-attenue); }


/* ═══════════════════════════════════════════════════════════════════════════
   L'ARBRE DE CRAFT — descendre d'un cran depuis la fiche d'une recette

   🎯 Chaque ingredient craftable peut etre ACHETE ou FABRIQUE, au choix, et sa
      propre recette se deplie sous sa ligne. Le nom devient cliquable : c'est
      la facon naturelle de descendre.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Un nom d'item cliquable. ⚠️ C'est un <button> pour rester accessible au
   clavier, mais il doit se lire comme le texte qu'il remplace — d'ou la remise
   a zero complete du style de bouton. */
.lien-item {
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    /* Le soulignement discret dit « ca s'ouvre » sans crier. */
    border-bottom: 1px dotted var(--texte-attenue);
}
.lien-item:hover { color: var(--accent); border-bottom-color: var(--accent); }
.lien-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Le bouton qui deplie l'etape supplementaire. Volontairement petit : c'est une
   commande secondaire, la decision se prend sur la case a cocher. */
.deplie-sous {
    background: none;
    border: 1px solid var(--filet);
    border-radius: var(--arrondi-sm, 4px);
    padding: 1px 6px;
    margin-left: 6px;
    font-size: 11px;
    color: var(--texte-attenue);
    cursor: pointer;
    white-space: nowrap;
    /* La cellule passe en flex des qu'elle porte un bouton « copier » (cf. plus
       bas) : sans ca, « ▸ etape » se ferait comprimer par le nom voisin. */
    flex-shrink: 0;
}
.deplie-sous:hover { color: var(--accent); border-color: var(--accent); }

/* ── LE BOUTON « COPIER LE NOM » ────────────────────────────────────────────
   🎯 La fiche est une liste de courses qu'on execute DANS LE JEU : ce bouton
      evite de retaper « Ouvrage magique d'Aloevee Rate » dans la recherche
      de l'HDV.

   ⚠️ VOLONTAIREMENT DISCRET AU REPOS (opacite .35) : il y en a un par ligne,
      et a pleine encre six glyphes identiques feraient une colonne parasite
      qui tire l'oeil plus que les prix. Il se revele au survol de la ligne.

   ⛔ IL NE DISPARAIT PAS AU REPOS (`opacity: 0`) : un bouton invisible tant
      qu'on ne survole pas la bonne ligne ne se decouvre jamais. */
/* 🔴 POURQUOI LA CELLULE PASSE EN FLEX : `.cellule-nom` coupe a l'ellipse
      (`overflow:hidden` + `nowrap`), et le bouton vient APRES le nom. Sur
      « Ouvrage magique d'Aloevee Rate », c'est LUI qui serait rogne le
      premier — la ligne la plus penible a retaper serait justement la seule
      qu'on ne pourrait pas copier. En flex, le nom se retracte (`min-width:0`
      autorise l'ellipse) et le bouton garde sa place.

   ⚠️ Cible restreinte au tableau du modal : `.cellule-nom` sert aussi dans le
      tableau principal, ou elle porte une seconde ligne (la date de releve)
      qu'un `display:flex` mettrait a l'horizontale. */
.table-craft .cellule-nom:has(> .copie-nom) {
    display: flex;
    align-items: center;
}
.table-craft .cellule-nom > .lien-item,
.table-craft .cellule-nom > .scout-nom {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.copie-nom {
    background: none;
    border: 0;
    padding: 0 4px;
    margin-left: 4px;
    font-size: 12px;
    line-height: 1;
    color: var(--texte-attenue);
    cursor: pointer;
    opacity: .35;
    flex-shrink: 0;
    /* ⛔ Pas de transition : la revelation au survol est instantanee. */
}
tr:hover > .cellule-nom > .copie-nom { opacity: .8; }
/* 🔴 LE SCOUTING LOGE SON BOUTON DANS `.scout-nom`, PAS EN ENFANT DIRECT DE LA
      CELLULE : la regle ci-dessus ne l'atteindrait pas, et il resterait a .35
      au survol — donc il ne se revelerait jamais, ce que tout le bloc
      ci-dessus cherche precisement a eviter. `.scout-nom` est deja en flex
      (cf. plus haut), le bouton s'y pose sans autre reglage. */
tr:hover .scout-nom > .copie-nom { opacity: .8; }
.copie-nom:hover { color: var(--accent); opacity: 1; }
.copie-nom:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    opacity: 1;
}

/* La confirmation : le presse-papier est invisible, le retour ne l'est pas.
   🔴 Elle force l'opacite pleine — sinon le ✓ s'afficherait a .35 sur une
      ligne non survolee (copie au clavier), et passerait inapercu. */
.copie-nom.copie-faite { color: var(--gain); opacity: 1; }
.copie-nom.copie-ratee { color: var(--alerte); opacity: 1; }

/* La case « je le fabrique », dans la colonne « Si fabrique ». */
.choix-fab {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    cursor: pointer;
    justify-content: flex-end;
}
.choix-fab input { cursor: pointer; margin: 0; }

/* 🎯 UNE LIGNE DONT LE TOTAL VIENT DE LA FABRICATION SE VOIT. Sans ce repere,
      deux lignes au meme prix mais produites differemment sont indiscernables,
      et on part acheter a l'HDV un item qu'on avait decide de fabriquer. */
.via-fabrication { border-right: 2px solid var(--accent); }

/* Le detail d'une sous-recette, sous la ligne de son ingredient. Le fond
   legerement enfonce dit « ceci est un cran plus bas ». */
.sous-recette > td {
    background: var(--fond-eleve);
    border-left: 2px solid var(--accent);
    padding: 8px 10px 10px 18px !important;
}
.sous-titre { margin-bottom: .4rem; font-size: 12px; }
.table-sous { width: 100%; }
.table-sous td { padding: 3px 8px; font-size: 12px; }
/* ⛔ Pas de bordure de tableau imbrique : deux grilles emboitees deviennent
      illisibles. La bande de gauche suffit a marquer le niveau. */
.table-sous, .table-sous td { border: 0; }


/* ═══════════════════════════════════════════════════════════════════════════
   LE MODAL — une fiche par-dessus la page, sans la quitter

   🎯 POURQUOI : le detail d'une recette s'ouvrait EN BAS de la page. Sur un
      classement de 30 lignes, il fallait descendre le chercher a chaque clic,
      puis remonter. On perd la ligne qu'on regardait.

   ⚠️ Les couleurs viennent des variables : ce bloc suit donc le theme clair
      comme le sombre, sans etre redefini deux fois.
   ═══════════════════════════════════════════════════════════════════════════ */
.voile {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .55);
    display: flex;
    /* ⛔ PAS `align-items: center` ICI, ET C'EST LE PIEGE A EVITER : sur un
          conteneur qui DEFILE, centrer un enfant plus haut que l'ecran fait
          deborder son haut dans la zone inatteignable du scroll — on perd le
          titre de la fiche, definitivement. Le centrage se fait par
          `margin: auto` sur `.modal` (voir le commentaire la-bas), qui centre
          quand ca rentre et s'ancre en haut quand ca deborde. */
    align-items: flex-start;
    justify-content: center;
    padding: var(--r-xl) var(--r-md);
    overflow-y: auto;          /* une recette a 8 ingredients doit defiler */
    z-index: 50;
}

.modal {
    /* 🔴 `--fond-eleve` : le modal FLOTTE au-dessus de la page (cf.
       `.suggestions`). Avec `--fond-carte`, devenu le fond de page, on verrait
       le tableau a travers la fiche. */
    background: var(--fond-eleve);
    border: 1px solid var(--bordure);
    border-radius: var(--arrondi-lg);
    box-shadow: var(--ombre-lg);
    width: min(760px, 100%);
    /* 🎯 LA FICHE EST CENTREE VERTICALEMENT (Benjamin, 2026-09-19) : collee en
          haut, une recette de 3 ingredients laissait un grand vide sous elle
          et l'oeil devait remonter chercher le titre.

       🔴 `margin: auto` SUR L'ENFANT FLEX, ET SURTOUT PAS `align-items:
          center` SUR LE VOILE. Les deux centrent tant que la fiche rentre,
          mais ils divergent des qu'elle deborde : `align-items: center`
          pousse le haut de la fiche HORS de la zone atteignable par le
          scroll (le titre devient inaccessible), la ou `margin: auto` se
          reduit a zero et laisse la fiche s'ancrer en haut, comme avant. Le
          `flex-start` du voile est ce qui rend ce repli possible : ne pas le
          changer. */
    margin: auto;
}

/* ═══════════════════════════════════════════════════════════════════════════
   LE TRIANGLE « CE PRIX EST EN RETARD » — sur la ligne, pas dans un encadre

   🎯 La puce du classement dit QU'UN prix est en retard ; ce triangle dit
      LEQUEL, pose a cote du ⧉ de l'item concerne. Un encadre en tete de fiche
      repetait des noms deja presents dans le tableau juste en dessous.

   🔴 IL RESTE VISIBLE EN PERMANENCE, contrairement au ⧉ qui ne se revele
      qu'au survol de sa ligne : c'est un AVERTISSEMENT, et un avertissement
      qu'il faut chercher avec la souris n'avertit personne. Seul son detail
      (l'age exact) est au survol.
   ═══════════════════════════════════════════════════════════════════════════ */
.marque-scout {
    color: var(--alerte);
    font-size: 12px;
    line-height: 1;
    margin-left: 2px;
    cursor: help;
    flex-shrink: 0;
}

/* Le focus clavier doit se voir : le triangle est `tabindex=0` pour que son
   infobulle soit atteignable sans souris. */
.marque-scout:focus-visible {
    outline: 1px solid var(--alerte);
    outline-offset: 2px;
    border-radius: var(--arrondi-sm);
}

/* ═══════════════════════════════════════════════════════════════════════════
   LA BARRE DE FILTRES DU CRAFT — recherche, metier, tranche de niveau.

   🎯 CE QU'ELLE REPOND : « je monte Bucheron du 60 au 80, que dois-je
      fabriquer ? ». Les trois controles tiennent sur UNE ligne parce qu'ils
      forment une seule question ; les empiler les ferait lire comme trois
      reglages independants.

   ⚠️ `flex-wrap` : sur un ecran etroit ils passent a la ligne plutot que de
      comprimer le champ de recherche jusqu'a l'illisible.
   ═══════════════════════════════════════════════════════════════════════════ */
.barre-filtres {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--r-sm, 8px);
    margin: 0 0 1rem;
}

/* 🔴 `label` est en `display:block` plus haut (ligne 300) : sans cette reprise,
      chaque filtre occuperait toute la largeur et casserait la ligne. */
.filtre-metier, .filtre-niveau {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 0;
}

/* ⚠️ `input, select` est en `width:100%` plus haut : dans une barre en flex,
      cela les ferait s'etirer chacun sur toute la place libre. On leur rend une
      largeur propre a leur contenu. */
.filtre-metier select { width: auto; min-width: 190px; }

/* 🔴 MEME PIEGE QUE CI-DESSUS, POUR LES CASES A COCHER. `input { width:100% }`
      s'applique aussi a un `type=checkbox` : la case s'etire sur toute la place
      libre et repousse son propre libelle sur trois lignes. `width:auto` seul
      ne suffit pas — sans `flex-shrink:0`, la case se fait ecraser a 0 px des
      que le libelle est long. */
.filtre-metier input[type="checkbox"] {
    width: auto;
    flex-shrink: 0;
    margin: 0;
}

/* ⚠️ Et le libelle ne doit pas se couper : « Inclure Dofus & familiers » sur
      trois lignes deforme la barre de filtres entiere. */
.filtre-metier { white-space: nowrap; }

/* Trois chiffres, pas plus : « 200 » est le maximum du jeu. Un champ large
   suggererait qu'on peut y taper autre chose. */
.filtre-niveau input { width: 5.5ch; text-align: center; padding: 7px 6px; }

/* ⛔ Les fleches de `type=number` volent la moitie d'un champ de 5 caracteres
      et ne servent a rien pour sauter de 60 a 80. On les retire, la saisie
      reste numerique (et `inputmode` garde le pave numerique sur mobile). */
.filtre-niveau input::-webkit-outer-spin-button,
.filtre-niveau input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.filtre-niveau input { -moz-appearance: textfield; appearance: textfield; }

/* La colonne « Niv. » : etroite, et son metier tient dessous sans l'elargir. */
.table-craft td.num .date-releve { max-width: 100%; }

/* ═══════════════════════════════════════════════════════════════════════════
   LES EN-TETES DE COLONNE CLIQUABLES

   🎯 Ils doivent AVOIR L'AIR cliquables sans crier : un en-tete de tableau qui
      ressemble a un bouton alourdit une ligne deja dense de huit colonnes. Le
      curseur et un leger eclaircissement au survol suffisent a l'annoncer.
   ═══════════════════════════════════════════════════════════════════════════ */
.tri-entete {
    cursor: pointer;
    user-select: none;   /* un double-clic ne doit pas selectionner le libelle */
    white-space: nowrap;
}
.tri-entete:hover { color: var(--texte); }

/* 🔴 La colonne qui gouverne l'ordre se distingue des sept autres : sans ce
      contraste, la fleche seule est trop discrete pour etre trouvee du regard
      dans un tableau dense. */
.tri-actif { color: var(--accent); }

/* ⚠️ La fleche ne pousse PAS le libelle : `margin-left` sur un element inline
      suffit, et la colonne ne change pas de largeur quand le tri se deplace —
      sinon tout le tableau tressaute a chaque clic. */
.tri-fleche { margin-left: 4px; font-size: 9px; vertical-align: middle; }

/* L'acces clavier doit se VOIR : `role=button` + `tabindex=0` promettent le
   focus, un contour invisible en ferait une promesse vide. */
.tri-entete:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* Le pied du tableau : « 150 sur 4 858 affichées » + le bouton d'extension.
   🔴 Le total y est TOUJOURS visible : sans denominateur, une liste tronquee
      a l'air complete. */
.pied-tableau {
    display: flex;
    align-items: center;
    gap: var(--r-sm, 8px);
    flex-wrap: wrap;
    margin-top: .75rem;
}

/* ═══ INFOBULLES ═══════════════════════════════════════════════════════
   🎯 CE PRODUIT EXPLIQUE SES CHIFFRES. « FM rentable ? », « marché saturé »,
      « Travail ×2,8 » : chaque libelle court cache une nuance qui change la
      decision, et cette nuance vit dans un `title`. L'infobulle n'est donc pas
      du confort — c'est la moitie du contenu de l'outil.

   🔴 POURQUOI REMPLACER LE `title` NATIF. Le navigateur l'affiche apres ~1,5 s,
      en petit, sur un fond systeme CLAIR — c'est-a-dire un rectangle blanc
      eblouissant sur un ecran consulte en alt-tab depuis le jeu, exactement ce
      que le theme sombre existe pour eviter. Il coupe les textes longs, ignore
      les retours a la ligne, et ne connait aucun de nos tokens.

   ⚠️ AUCUN `title=` N'A ETE TOUCHE dans les pages. Le script `infobulle.js`
      lit l'attribut existant et le remplace au survol. Une page qui ajoute un
      `title` demain est prise en charge sans rien declarer : c'est ce qui rend
      ces 107 infobulles tenables.
   ⚠️ Consequence : si JS est coupe, le `title` natif reprend la main. Degrade,
      jamais muet.

   🔴 Le CONTENANT porte un ton, comme les puces : une infobulle attachee a une
      alerte prend le filet or. La couleur n'est jamais decorative ici — elle
      redit le verdict de l'element survole. */
.infobulle {
    position: fixed;
    z-index: 300;
    max-width: 340px;
    padding: 9px 12px;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--texte);
    background: var(--fond-eleve);
    border: 1px solid var(--bordure-forte);
    border-radius: var(--arrondi);
    box-shadow: var(--ombre-lg);

    /* ⛔ Jamais d'evenement de souris dessus : l'infobulle suit le curseur de
       pres, et un survol d'elle-meme ferait clignoter l'element en dessous. */
    pointer-events: none;

    /* 🎯 Apparition en fondu + 3px de montee : assez pour que l'oeil accroche
       le nouvel element, assez court pour ne pas retarder un balayage.
       ⚠️ La transition ne porte QUE sur l'apparition. Le retrait est immediat
          (le noeud est supprime) : une infobulle qui s'attarde en sortant se
          retrouve a flotter sur la ligne suivante. */
    /* ⚠️ L'infobulle FLOTTE : c'est le seul cas ou une apparition en fondu
       reste justifiee (elle surgit sous le curseur, un affichage sec y est
       brutal). ⛔ Le GLISSEMENT de 3px est retire — c'etait une animation
       d'entree, et la DA n'en a pas. */
    opacity: 0;
    transition: opacity .1s linear;
}

.infobulle.est-visible { opacity: 1; }

/* La fleche, dessinee en carre pivote pour heriter du fond ET du filet.
   `--fleche-x` est pose par le script : quand l'infobulle a ete recalee pour ne
   pas sortir de l'ecran, la fleche doit rester sur l'element, pas sur la boite. */
.infobulle::after {
    content: "";
    position: absolute;
    left: var(--fleche-x, 50%);
    width: 8px;
    height: 8px;
    margin-left: -4px;
    background: var(--fond-eleve);
    border: 1px solid var(--bordure-forte);
    transform: rotate(45deg);
}

/* Dessus : la fleche pointe vers le bas, et on ne garde que les deux cotes
   visibles du carre — sinon un trait traverse la boite. */
.infobulle[data-cote="haut"]::after {
    bottom: -5px;
    border-top: 0;
    border-left: 0;
}

.infobulle[data-cote="bas"]::after {
    top: -5px;
    border-bottom: 0;
    border-right: 0;
}

/* ── Les tons ──────────────────────────────────────────────────────────
   🔴 Le fond reste `--fond-eleve` dans tous les cas : les fonds d'etat
      (`--gain-fond`, `--perte-fond`) sont calibres pour une puce de 11px, pas
      pour un paragraphe de trois lignes qui deviendrait penible a lire. Ce qui
      porte le ton, c'est le FILET et le liseré de gauche. */
.infobulle[data-ton="gain"]   { border-color: var(--gain);   box-shadow: var(--ombre-lg); }
.infobulle[data-ton="perte"]  { border-color: var(--perte); }
.infobulle[data-ton="alerte"] { border-color: var(--accent-filet); }

.infobulle[data-ton="gain"]   { border-left: 3px solid var(--gain); }
.infobulle[data-ton="perte"]  { border-left: 3px solid var(--perte); }
.infobulle[data-ton="alerte"] { border-left: 3px solid var(--alerte); }

/* Le titre : le libelle de l'element survole, repris en tete.
   🎯 Il ancre l'infobulle a ce qu'on survole. Sur un tableau dense, le curseur
      a souvent deja glisse quand on finit de lire. */
.infobulle-titre {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--texte-faible);
    margin-bottom: 4px;
}

.infobulle[data-ton="gain"]   .infobulle-titre { color: var(--gain); }
.infobulle[data-ton="perte"]  .infobulle-titre { color: var(--perte); }
.infobulle[data-ton="alerte"] .infobulle-titre { color: var(--alerte); }

/* 🔴 LA MISE EN GARDE SE DETACHE DU CORPS. Beaucoup de ces textes finissent par
   « ⚠️ Ce n'est PAS une marge » ou « ⚠️ Tu ne toucheras ce prix haut qu'en
   travaillant l'item » : c'est la phrase qui empeche une mauvaise decision, et
   noyee en fin de paragraphe elle se lit en dernier, quand on a deja conclu.
   Le script la detecte sur le `⚠️` et la sort dans ce bloc. */
.infobulle-garde {
    display: flex;
    gap: 6px;
    margin-top: 7px;
    padding-top: 7px;
    border-top: 1px solid var(--bordure);
    font-size: 12px;
    color: var(--texte-attenue);
}

.infobulle-garde::before {
    content: "⚠";
    flex: none;
    color: var(--alerte);
    font-weight: 700;
}

/* Le corps. `strong` sert aux passages que les textes ecrivent en MAJUSCULES
   (« Ce n'est PAS un nombre de ventes ») : le script les repere et les
   remplace, les majuscules criant moins fort en gras. */
.infobulle-corps strong { color: var(--texte); font-weight: 700; }

/* ⚠️ Le survol ne suffit pas : la moitie de ces infobulles vivent sur des
   en-tetes de colonne triables, atteignables au clavier. Le script ecoute
   `focus`, et ce curseur dit qu'il y a quelque chose a survoler. */
[data-infobulle] { cursor: help; }

/* ⚠️ Sur un element deja cliquable (en-tete de tri, lien d'item), le curseur
   d'action prime : il annonce ce que le CLIC fait, information plus rare. */
.tri-entete[data-infobulle],
a[data-infobulle],
button[data-infobulle],
label[data-infobulle] { cursor: pointer; }

/* ⚠️ Mouvement reduit : on garde l'infobulle, on retire jusqu'au fondu. */
@media (prefers-reduced-motion: reduce) {
    .infobulle { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   LE TABLEAU DE L'ONGLET DIRECT

   🔴 POURQUOI IL LUI FAUT SES PROPRES REGLES : il reutilise `.table-prix`,
      qui porte deux choix propres a l'ecran des PRIX et faux ici —
        1. `.date-releve { display: block }` : la date y vit SOUS le nom
           d'item, pas dans sa propre colonne ;
        2. une reserve de `padding-right: 100px` pour la barre de score.
      Les deux desalignaient la colonne « Dernier » par rapport a « Valeur »,
      ce que Benjamin a vu tout de suite (« il y a des endroits pas bien
      alignes entre VALEUR et DERNIER »).

   ⚠️ On corrige SOUS `.table-direct` uniquement : toucher `.table-prix`
      casserait l'ecran des prix, ou ces deux choix sont justes.
   ═══════════════════════════════════════════════════════════════════════ */
.table-direct .date-releve {
    display: table-cell;
    padding-right: 8px;
    text-align: right;
    white-space: nowrap;
}

/* 🔴 LE TABLEAU NE S'ETIRE PAS SUR TOUTE LA LARGEUR DE L'ECRAN.
   ⚠️ Sur un 1500 px, les cinq colonnes se retrouvaient separees par des vides
      de 300 px : l'oeil ne relie plus « Vitalité » a sa valeur, et il faut
      balayer tout l'ecran pour lire une ligne. Une largeur bornee garde les
      chiffres a cote de leur libelle. */
.table-direct { width: 100%; }

/* La colonne des noms de rune prend ce qui reste : les colonnes de chiffres
   n'ont besoin que de leur contenu. */
.table-direct td:first-child,
.table-direct th:first-child { width: 40%; }

/* 🔴 LA CARTE ELLE-MEME EST BORNEE, pas seulement son tableau.
   ⚠️ Borner le tableau seul laissait un fond qui s'etirait sur tout l'ecran
      avec 700 px de vide a droite : le bloc paraissait vide et le regard
      partait chercher une information qui n'existe pas. */
/* ⚠️ `margin: auto` en plus de `max-width` : borner la largeur SANS centrer
   collait toutes les cartes à gauche, avec la moitié de l'écran vide à
   droite — le bloc paraissait mal aligné plutôt que compact. */
.carte-direct {
    max-width: 800px;
    /* Centré dans le viewport, et espacé de la carte suivante : sans la marge
       basse, les cartes d'items se touchaient et se lisaient comme un seul
       tableau continu. */
    margin: 0 auto var(--r-md);
}

.carte-direct .carte-entete { padding: var(--r-sm) var(--r-md); }

/* 🎯 LE TOTAL DE L'ITEM POUSSE A DROITE, sur la meme ligne que son nom.
   C'est le chiffre qu'on cherche en premier sur un item : il doit tomber au
   bout de la ligne, aligne avec la colonne « Valeur » du tableau dessous. */
.carte-direct .item-total {
    margin-left: auto;
    text-align: right;
    white-space: nowrap;
    font-family: var(--police-chiffres);
    font-variant-numeric: tabular-nums;
}

.carte-direct .item-total-valeur {
    font-size: 16px;
    font-weight: 700;
    color: var(--accent-clair);
}

/* Le nombre de runes : la meme ligne, mais en retrait — c'est le contexte du
   montant, pas le montant. */
.carte-direct .item-total-runes {
    display: block;
    font-size: 11px;
    color: var(--texte-faible);
    font-family: var(--police);
}

/* 🎯 QUI A FORGE L'ITEM — une pastille a cote du nom.
   Sur une page qui agrege les sessions de plusieurs joueurs, un item sans nom
   se lit comme le sien : l'auteur fait partie de l'identite de la carte, il se
   pose donc a cote du nom et non dans un coin.

   ⛔ PAS EN `--accent` : l'accent porte les CHIFFRES (le total de l'item, juste
      a droite). Un nom dore entrerait en concurrence avec le montant, qui reste
      ce qu'on cherche en premier. La pastille se lit sans crier. */
.carte-direct .etiquette-auteur {
    flex: none;
    padding: 1px 7px;
    border: 1px solid var(--bordure-forte);
    border-radius: 10px;
    background: var(--fond-eleve);
    font-size: 11px;
    font-weight: 600;
    color: var(--texte-faible);
    white-space: nowrap;
}

/* Les en-tetes de la colonne d'heure suivent le meme alignement que leurs
   cellules : un titre a gauche au-dessus de valeurs a droite se lit mal. */
.table-direct th:last-child { text-align: right; }

/* ═══════════════════════════════════════════════════════════════════════
   LE TOTAL DE FORGEMAGIE — DANS LE PIED DU RAIL

   🎯 Visible depuis TOUS les onglets : ce que la session de FM a coute est le
      chiffre qu'on veut garder sous les yeux en jouant. Le confier a l'onglet
      Direct obligerait a y aller pour le lire.

   ⚠️ Masque tant qu'aucune rune n'est depensee (`hidden` en HTML) : un « 0 »
      permanent serait du bruit sur les douze autres ecrans.

   ⛔ LE DEGRADE, LA BORDURE OR ET L'OMBRE ONT ETE RETIRES (2026-09-17) :
      c'etait une carte, sur un produit qui n'en a plus. Il se pose maintenant
      a plat dans le pied du rail, ou son voisinage (un filet au-dessus) suffit
      a le detacher.
   ═══════════════════════════════════════════════════════════════════════ */
.total-fm {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 0 2px;
    /* Chiffres tabulaires : le total change toutes les 3 s et ne doit pas
       faire sautiller le rail a chaque mise a jour. */
    font-variant-numeric: tabular-nums;
}

.total-fm-ligne { display: flex; align-items: baseline; gap: 5px; }

/* 🔴 EN MODE REPLIE, LE TOTAL LONG DISPARAIT AU PROFIT DE `.total-fm-court`.
   Un montant a 6 chiffres ne tient pas dans 56px, et ⛔ le tronquer serait
   pire que le masquer : « 270 6… » se lit comme un montant, faux. */
.rail.est-replie .total-fm { display: none; }

/* La pastille compacte du mode replie : l'ORDRE DE GRANDEUR seulement
   (« 271 k »), jamais un chiffre coupe. Le montant exact est dans l'infobulle
   et revient au depli. */
.total-fm-court {
    padding: 3px 2px;
    text-align: center;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.2;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
    cursor: help;
}
/* ⚠️ Il n'existe QUE replie : deplie, le total complet est deja la. */
.rail:not(.est-replie) .total-fm-court { display: none; }
/* ⚠️ Il suit aussi `hidden` : pas de rune depensee, pas de pastille. */
.total-fm-court[hidden] { display: none; }

/* ⚠️ 17px et non 20 : dans une colonne de 188px, le total partage la place
   avec son unite et son selecteur. La graisse 600 (et non 700) suit la limite
   d'Outfit — au-dela, le navigateur synthetise. */
.total-fm-valeur {
    font-size: 17px;
    font-weight: 600;
    color: var(--accent);
    line-height: 1.15;
    letter-spacing: 0;
}

/* 🎯 L'UNITE « KAMAS ». ⛔ LE JETON ROND PLEIN A ETE RETIRE (2026-09-17) :
   un rond parfait a fond accent etait une pastille de plus, et son rayon 50 %
   sortait de l'echelle (≤7px). L'unite redevient ce qu'elle est — une lettre
   attenuee a cote du chiffre, comme « k » l'est partout ailleurs dans l'outil. */
.total-fm-kamas {
    font-size: 11px;
    font-weight: 500;
    line-height: 1;
    color: var(--texte-faible);
}

/* Le selecteur de periode : present, mais il ne doit pas concurrencer le
   chiffre. ⚠️ Pas de bordure ni de fond — c'est un reglage, pas un champ. */
.total-fm-periode {
    width: 100%;
    min-width: 0;
    padding: 0;
    border: none;
    background: transparent;
    box-shadow: none;
    color: var(--texte-faible);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: .14em;
    cursor: pointer;
}

.total-fm-periode:hover { color: var(--texte-attenue); }

/* 🔴 PERIODE VIDE : le chiffre s'efface, LE SELECTEUR RESTE.
   ⚠️ Masquer le bloc entier (ce que faisait la premiere version) supprimait
      le seul moyen de changer de journee — on restait bloque sur une fenetre
      vide sans pouvoir remonter a hier. */
.total-fm.est-vide { opacity: .75; }
.total-fm.est-vide .total-fm-valeur { color: var(--texte-faible); }

/* ⚠️ Le rail est deja etroit et ne change pas de largeur avec le viewport :
   ces retailles n'ont plus lieu d'etre, seul le corps du total est ajuste. */
@media (max-width: 700px) {
    .total-fm-valeur { font-size: 15px; }
    .total-fm-periode { font-size: 10px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   LA PASTILLE « DIRECT » — l'onglet qui se rafraichit tout seul.

   🔴 ELLE NE CLIGNOTE QUE QUAND LE READER LIT VRAIMENT L'ATELIER. Une
      pastille « live » sur un ecran qui ne lit rien serait un mensonge
      visuel : l'utilisateur en deduirait que sa session est suivie, et
      lirait un total de 0 comme « je n'ai rien depense ».
      C'est `direct.js` qui pose `.est-vivante`, jamais le CSS seul.

   ⚠️ ETEINTE, elle reste VISIBLE mais grise : l'onglet garde son identite
      (on le reconnait a sa pastille) sans annoncer une activite absente.
   ═══════════════════════════════════════════════════════════════════════ */
.pastille-direct {
    display: inline-block;
    width: 7px;
    height: 7px;
    margin-right: 6px;
    border-radius: 50%;
    background: var(--texte-faible);
    vertical-align: middle;
    /* ⛔ PLUS DE TRANSITION : la DA est inerte. L'allumage est un fait. */
}

/* 🔴 L'ACCENT ET NON LE ROUGE (corrige le 2026-09-17). Le rouge signifie « tu
   perds de l'argent » dans tout le produit ; une pastille live rouge annoncait
   donc une perte a chaque lecture du reader. La regle est explicite : « le fait
   qu'une ligne arrive n'est ni un gain ni une perte » — neutre ou accent. */
.pastille-direct.est-vivante {
    background: var(--accent);
    animation: pouls-direct 1.6s ease-in-out infinite;
}

/* ⚠️ LA SEULE ANIMATION DU PRODUIT, et elle est justifiee : elle ne decore pas,
   elle dit que le reader lit EN CE MOMENT.
   ⛔ Le `box-shadow` pulsant a ete RETIRE : c'etait un halo, et la DA n'en a
      aucun. Seule l'opacite varie — le battement reste lisible sans glow. */
@keyframes pouls-direct {
    0%, 100% { opacity: 1; }
    50%      { opacity: .45; }
}

/* ⚠️ Mouvement reduit : la pastille reste ROUGE (l'information « ca lit »
   est portee par la couleur), seul le clignotement disparait. Retirer la
   couleur aussi supprimerait l'information, pas l'animation. */
@media (prefers-reduced-motion: reduce) {
    .pastille-direct.est-vivante { animation: none; }
}

@media (max-width: 700px) {
    .page { padding: var(--r-md); }
    /* ⛔ PLUS DE RETAILLE DE `.onglet` ICI : cette regle datait du bandeau
       horizontal, ou il fallait comprimer 13 boutons sur une ligne. Dans le
       rail, elle rendait un padding lateral a un bouton centre et decalait les
       icones. Le rail a sa propre regle a 900px. */
    .verdict-valeur { font-size: 21px; }
    .voile { padding: var(--r-md) var(--r-sm); }
    /* Sur mobile la recherche prend toute la ligne, les filtres la suivante. */
    .barre-filtres .champ-recherche { flex: 1 1 100%; max-width: none; }
    /* 340px sur un ecran de 360 laisserait 10px de marge de chaque cote. */
    .infobulle { max-width: calc(100vw - 24px); }
}

/* Familiers : liste dense et simulateur de budget sur la droite. */
.fam-entete { display:flex; justify-content:space-between; align-items:center; gap:16px; margin-bottom:20px; }
.fam-layout { display:grid; grid-template-columns:minmax(0,1fr) 310px; gap:20px; align-items:start; }
.fam-layout aside { display:grid; gap:16px; }
.fam-layout h2 { margin:0 0 14px; font-size:17px; }
.fam-layout h3 { font-size:14px; }
.fam-layout label { display:grid; gap:6px; margin-bottom:14px; }
.fam-layout label input, .fam-layout label select { width:100%; }
.fam-layout small { display:block; font-size:11px; color:var(--texte-faible); white-space:normal; }
.fam-filtres { display:flex; gap:12px; flex-wrap:wrap; }
.fam-filtres input { flex:1; min-width:160px; }
.fam-table-scroll { overflow:auto; max-height:72vh; }
.fam-table-scroll table { width:100%; }
/* 🔴 `--fond` et non `--fond-eleve` : le fond d'un en-tete colle doit egaler le
   fond REEL derriere la table, sinon il dessine une BANDE PLUS CLAIRE flottante
   au scroll (regle de `leagues.css`). Sans carte, ce fond reel est `--fond`. */
.fam-table-scroll th { position:sticky; top:0; background:var(--fond); z-index:1; }
.fam-tri { border:0; background:transparent; color:inherit; padding:0; font:inherit; cursor:pointer; }
.fam-onglets { display:flex; gap:4px; margin:0 0 16px; border-bottom:1px solid var(--bordure); }
.fam-onglet { border:0; border-bottom:2px solid transparent; background:transparent; color:var(--texte-attenue);
    padding:9px 18px; font:inherit; font-weight:600; cursor:pointer; margin-bottom:-1px; }
.fam-onglet:hover { color:var(--texte); }
.fam-onglet[aria-selected="true"] { color:var(--accent); border-bottom-color:var(--accent); }
.fam-layout th[aria-sort="ascending"] button::after { content:' ↑'; }
.fam-layout th[aria-sort="descending"] button::after { content:' ↓'; }
/* Le bandeau « l'XP publiee n'est pas fiable » a ete retire : les XP viennent
   des donnees du jeu depuis le 2026-09-18. Les 14 lignes encore issues du
   vieux tableau se signalent seules, dans leur propre cellule. */
.fam-xp-douteuse { color:var(--perte); font-style:italic; }
/* 🔴 LA LIGNE VERTE EST UNE AFFAIRE VERIFIEE, jamais une estimation : prix
   REELLEMENT releve en HDV, sous le plafond de rentabilite. Le vert ne doit
   donc JAMAIS toucher une ressource dont le prix est inconnu — ce serait
   promettre une marge sur un chiffre qu'on n'a pas. */
.fam-liste tr.fam-affaire { background:var(--gain-fond, rgba(80,200,120,.07)); }
.fam-liste tr.fam-affaire td:first-child { box-shadow:inset 3px 0 0 var(--gain); }
.fam-col-operation strong { font-size:13px; }
/* Un plafond a verifier en HDV se lit autrement qu'un benefice acquis. */
.fam-a-relever { color:var(--fam-piste); }
.fam-liste td small.fam-a-relever-note { color:var(--fam-piste); font-style:italic; }
/* Le detail de l'operation : chaque ligne est un terme du calcul, aligne a
   droite pour que les montants se comparent d'un coup d'oeil. */
.fam-operation-detail { display:grid; grid-template-columns:1fr auto; gap:2px 12px; margin:10px 0 0; font-size:12px; }
.fam-operation-detail dt { color:var(--texte-attenue); }
.fam-operation-detail dd { margin:0; text-align:right; font-variant-numeric:tabular-nums; }
#fam-operation label { display:block; margin-bottom:10px; }
.fam-suivi td small.fam-xp-mesuree, .fam-liste td small.fam-xp-mesuree { color:var(--gain); font-weight:700; font-size:10px; }
/* Le seuil d'une piste n'est PAS un prix releve : il ne doit jamais se lire
   comme les chiffres de la colonne, d'ou l'italique et la teinte a part. */
.fam-seuil { color:var(--fam-piste); font-style:italic; }
/* 🔴 SANS XP, UNE LIGNE N'EST PAS UN RESULTAT — c'est un appel a mesurer.
   Le tableau publie ignore 1 144 des ressources nourrissables du catalogue :
   elles figurent pour qu'on voie leur prix HDV et ce qu'il reste a relever,
   mais elles ne se chiffrent pas. Le grise dit « pas de donnees » la ou un
   affichage normal ferait lire « pas d'affaires ». */
.fam-liste tr.fam-sans-xp { color:var(--texte-faible); }
.fam-liste tr.fam-sans-xp .fam-copie-nom { color:inherit; }
.fam-xp-absente { color:var(--texte-attenue); font-style:italic; }
.fam-liste { --fam-piste:#9fb6c9; }
[data-theme="light"] .fam-liste { --fam-piste:#4c6478; }
.fam-note, #fam-statut { font-size:12px; color:var(--texte-attenue); line-height:1.6; }
#fam-statut { padding:10px 16px; }
.fam-xp { font-size:21px; font-weight:650; }
#fam-erreur { color:var(--perte); margin-top:12px; }
.fam-layout small.fam-ancien { color:var(--accent); }
@media(max-width:1100px) { .fam-layout { grid-template-columns: minmax(0,1fr); } .fam-layout aside { grid-row:1; grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media(max-width:650px) { .fam-layout aside { grid-template-columns:1fr; } .fam-entete { align-items:start; flex-direction:column; } }
/* Un onglet de plus : garder tous les accès et le serveur lisibles. */
@media(max-width:1600px) {
    .entete { height:auto; min-height:52px; flex-wrap:wrap; padding-top:8px; padding-bottom:8px; }
    .onglets { min-width:0; max-width:100%; overflow-x:auto; margin-left:0; }
    .onglet { white-space:nowrap; flex-shrink:0; }
}
.fam-filtres select { width:auto; }
.fam-liste, .fam-layout aside, .fam-layout aside > * { min-width:0; }

/* Repères du tableau Familiers : XP violette, efficacité bleue, kamas dorés. */
.fam-layout { --fam-violet:#c4adff; --fam-bleu:#8dcde7; }
[data-theme="light"] .fam-layout { --fam-violet:#7041a2; --fam-bleu:#216780; }
.fam-ressource { display:flex; align-items:center; gap:10px; min-width:190px; }
.fam-icone { position:relative; display:grid; place-items:center; width:38px; height:38px; flex:0 0 38px; color:var(--texte-faible); overflow:hidden; }
.fam-icone img { position:absolute; inset:0; width:100%; height:100%; object-fit:contain; padding:3px; }
.fam-layout .fam-col-xp { color:var(--fam-violet); }
.fam-layout .fam-col-kamas_par_xp { color:var(--fam-bleu); }
.fam-layout .fam-col-prix_unitaire, .fam-layout .fam-col-prix_max { color:var(--accent); }
.fam-meilleure td { background:color-mix(in srgb, var(--accent) 5%, transparent); }
#fam-lignes td:first-child { border-left:3px solid transparent; }
#fam-lignes .fam-meilleure td:first-child { border-left-color:var(--accent); }
#fam-lignes .fam-budget-ok td:first-child { border-left-color:var(--gain); }
#fam-lignes .fam-budget-non td:first-child { border-left-color:var(--perte); }
.fam-budget-ok td.gain { background:var(--gain-fond); }
.fam-budget-non td.perte { background:var(--perte-fond); }
.fam-layout td.gain small { color:var(--gain); }
.fam-layout td.perte small { color:var(--perte); }
#fam-lignes tr:hover td { background:var(--fond-survol); }

.fam-layout .fam-meilleur-texte { margin-top:3px; color:var(--accent); }
.fam-layout td.fam-col-kamas_par_xp { font-weight:700; }

/* Limite de quantite, appliquee immediatement a la liste et au conseil. */
.fam-filtres { align-items:center; }
.fam-layout label.fam-max-achat { display:flex; align-items:center; gap:8px; margin:0; white-space:nowrap; font-size:12px; color:var(--texte-attenue); }
.fam-layout .fam-max-achat input { width:115px; min-width:0; flex:none; }
.fam-max-achat input:invalid { border-color:var(--perte); }

/* Le nom reste du texte, cliquable aussi au clavier, sans badge. */
.fam-copie-nom { border:0; border-radius:0; padding:0; background:transparent; color:var(--texte); font:inherit; font-weight:700; text-align:left; cursor:pointer; }
.fam-copie-nom:hover { color:var(--accent); text-decoration:underline; text-underline-offset:3px; }
.fam-copie-nom:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
.fam-layout .fam-copie-retour:empty { display:none; }
.fam-layout .fam-copie-retour.gain { color:var(--gain); }
.fam-layout .fam-copie-retour.perte { color:var(--perte); }

.fam-suivi { margin-bottom: 24px; }
.fam-suivi .fam-filtres { display:flex; flex-wrap:wrap; gap:12px; align-items:center; }
.fam-suivi .fam-table-scroll { max-height:55vh; }
.fam-suivi table { min-width:960px; }
.fam-suivi td small { display:block; color:var(--texte-attenue); }

.fam-liste tr[data-ressource] { cursor:pointer; }
.fam-liste tr.fam-selectionnee { outline:2px solid var(--accent); outline-offset:-2px; background:rgba(204,155,61,.12); }
.fam-copier { margin-left:8px; font-size:11px; background:transparent; color:inherit; border:1px solid currentColor; border-radius:4px; cursor:pointer; }

/* Repères du marché : achat doré, moyenne bleue, montée violette. */
/* L'en-tete du suivi est reduit au strict necessaire : chaque ligne de texte
   ici est prise sur la hauteur du tableau, qui est le sujet de l'ecran. */
.fam-suivi-entete { padding-bottom:10px; }
.fam-suivi-entete h2 { margin:0 0 10px; }
.fam-suivi-entete .fam-filtres { margin:0; }
.fam-suivi-entete #fam-suivi-statut,
.fam-suivi-entete #fam-suivi-bilan { margin:8px 0 0; font-size:12px; color:var(--texte-attenue); }
.fam-suivi .fam-table-scroll { max-height:78vh; }
.fam-suivi { --fam-bleu:#8dcde7; --fam-violet:#c4adff; --fam-estime:#9fb6c9; }
[data-theme="light"] .fam-suivi { --fam-bleu:#216780; --fam-violet:#7041a2; --fam-estime:#4c6478; }
.fam-suivi .fam-prix, .fam-suivi .fam-mise { color:var(--accent); }
.fam-suivi .fam-moyen, .fam-suivi .fam-volume { color:var(--fam-bleu); }
.fam-suivi .fam-montee { color:var(--fam-violet); }
/* L'estimation n'est pas un relevé : teinte distincte et discrète. */
.fam-suivi .fam-estime { color:var(--fam-estime); font-style:italic; }
.fam-suivi th.fam-col-prix_monte_estime,
.fam-suivi th.fam-col-marge_monte,
.fam-suivi th.fam-col-rentabilite_monte { color:var(--fam-estime); }
.fam-suivi th[aria-sort="ascending"] button::after { content:' ↑'; }
.fam-suivi th[aria-sort="descending"] button::after { content:' ↓'; }
.fam-suivi .fam-revente { color:var(--gain); }
.fam-suivi .fam-inconnu { color:var(--texte-faible); }
.fam-suivi td.fam-montee { background:color-mix(in srgb, var(--fam-violet) 6%, transparent); }
.fam-suivi td.gain { color:var(--gain); background:var(--gain-fond); }
.fam-suivi td.perte { color:var(--perte); background:var(--perte-fond); }
.fam-suivi td small.fam-ancien { color:var(--accent); }
.fam-suivi tr:hover td { background:var(--fond-survol); }
.fam-table-scroll { scrollbar-gutter:stable; overflow-anchor:none; }

/* Volumes élevés : repère bleu indépendant de la rentabilité. */
.fam-suivi table { min-width:1250px; }
.fam-suivi td:first-child { border-left:3px solid transparent; }
.fam-suivi .fam-ventes-fortes td:first-child,
.fam-suivi .fam-ventes-tres-fortes td:first-child { border-left-color:var(--fam-bleu); }
.fam-suivi .fam-ventes-tres-fortes td:first-child { background:color-mix(in srgb, var(--fam-bleu) 10%, transparent); }
.fam-suivi .fam-ventes-fortes .fam-volume,
.fam-suivi .fam-ventes-tres-fortes .fam-volume { background:color-mix(in srgb, var(--fam-bleu) 12%, transparent); }
.fam-suivi .fam-ventes-tres-fortes .fam-volume strong { font-size:16px; }
.fam-volume-barre { display:block; height:4px; width:76px; margin:6px 0 0 auto; border-radius:3px; background:color-mix(in srgb, var(--fam-bleu) 15%, transparent); overflow:hidden; }
.fam-volume-barre > span { display:block; height:100%; background:var(--fam-bleu); border-radius:inherit; }
/* Le seuil de volume se lit deja a la couleur de la ligne et a la barre :
   un badge en plus repetait la meme chose sous chaque nombre. */
/* La seule mention qui reste sous un prix est celle qui VARIE d'une ligne a
   l'autre : un niveau reellement confirme, et l'age du releve. */
.fam-suivi td small.fam-confirme { color:var(--gain); font-size:10px; font-weight:700; }

/* ═══ ÉLEVAGE ══════════════════════════════════════════════════════════
   Même gabarit que Familiers : résultats à gauche, formulaires à droite.
   Réutilise .fam-onglets pour les deux vues et .verdict pour les chiffres
   qui décident. Aucune couleur en dur : gain/perte disent l'argent, l'ambre
   dit « estimé » ou « saturé ». */
.elv-entete { margin-bottom:16px; }
.elv-entete p { margin:4px 0 0; color:var(--texte-attenue); }
.elv-reserves { margin-bottom:var(--r-md); line-height:1.6; }
.elv-reserves strong { margin-right:2px; }
.elv-layout { display:grid; grid-template-columns:minmax(0,1fr) 330px; gap:20px; align-items:start; }
.elv-layout aside { display:grid; gap:16px; min-width:0; }
.elv-principal { display:grid; gap:var(--r-lg); min-width:0; }
.elv-layout h2 { margin:0 0 12px; font-size:17px; }
.elv-layout .carte-entete h2 { margin:0; }
.elv-layout form h2 + label, .elv-layout form h2 + p { margin-top:0; }
.elv-layout form h2:not(:first-child) { margin-top:18px; }
.elv-layout label { display:grid; gap:6px; margin-bottom:12px; }
.elv-layout label input, .elv-layout label select { width:100%; }
.elv-layout label small { font-size:11px; color:var(--texte-faible); white-space:normal; line-height:1.5; }
.elv-raccourcis { font-size:12px; color:var(--texte-attenue); display:flex; align-items:center; gap:4px; flex-wrap:wrap; }
.elv-raccourcis input { width:64px !important; padding:2px 6px; font-size:12px; }
.elv-case { display:flex !important; gap:8px; align-items:start; flex-wrap:wrap; }
.elv-case input { width:auto !important; margin-top:3px; }
.elv-case span { flex:1; min-width:0; }
.elv-case small { flex-basis:100%; }
.elv-prix-ligne { display:flex; align-items:center; gap:8px; }
.elv-prix-ligne input { flex:1; min-width:0; }
.elv-prix-ligne small { flex:0 0 76px; text-align:right; white-space:nowrap; font-family:var(--police-chiffres); font-variant-numeric:tabular-nums; }
.elv-erreur { color:var(--perte); font-size:12px; margin-top:8px; }
.elv-erreur:empty { display:none; }
.elv-note { font-size:12px; color:var(--texte-attenue); line-height:1.6; }
.elv-note p { margin:6px 0 0; }
.elv-verdict .verdict-bloc { min-width:170px; }
.elv-verdict .verdict-valeur { font-size:22px; }
.elv-table td small { display:block; }
/* La ligne choisie / recommandée : un liseré doré, comme la meilleure
   ressource des Familiers. */
.elv-table tr td:first-child, #elv-planning tr td:first-child { border-left:3px solid transparent; }
tr.elv-choisie td { background:color-mix(in srgb, var(--accent) 6%, transparent); }
tr.elv-choisie td:first-child { border-left-color:var(--accent); }
tr.elv-total td { font-weight:700; border-top:2px solid var(--bordure-forte); }
.elv-deux-colonnes { display:grid; grid-template-columns:repeat(auto-fit, minmax(280px, 1fr)); gap:var(--r-lg); padding:0 var(--r-md) var(--r-md); }
.elv-a-sec { font-size:14px; margin:0 0 8px; }
.elv-a-sec strong { color:var(--gain); }
.elv-saisie input { width:100%; min-width:110px; }
.elv-saisie td { padding-top:6px; padding-bottom:6px; }
.elv-saisie td:last-child { width:40px; }
@media(max-width:1100px) { .elv-layout { grid-template-columns:minmax(0,1fr); } .elv-layout aside { grid-row:1; grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media(max-width:650px) { .elv-layout aside { grid-template-columns:1fr; } }
/* Les carburants au marché : cinq tailles par catégorie, la moins chère au
   point en vert. Gris = jamais relevée, ambre = relevé de plus de 30 jours. */
.elv-cat { padding:10px 0; border-top:1px solid var(--filet); }
.elv-cat-tete { display:flex; align-items:baseline; gap:6px; flex-wrap:wrap; font-size:13px; margin-bottom:6px; }
.elv-cat-meilleure { margin-left:auto; font-size:12px; color:var(--texte-attenue); font-family:var(--police-chiffres); }
.elv-tailles { display:grid; grid-template-columns:repeat(5, minmax(0,1fr)); gap:4px; margin-bottom:8px; }
.elv-taille { display:grid; gap:1px; padding:5px 4px; border-radius:var(--arrondi-sm); border:1px solid var(--bordure); background:var(--fond-eleve); text-align:center; font-size:10px; color:var(--texte-attenue); cursor:help; min-width:0; }
.elv-taille strong { font-family:var(--police-chiffres); font-variant-numeric:tabular-nums; font-size:12px; color:var(--texte); }
.elv-taille-nom { text-transform:capitalize; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.elv-taille-pts { color:var(--texte-faible); }
.elv-taille-meilleure { border-color:var(--gain); background:var(--gain-fond); }
.elv-taille-meilleure strong { color:var(--gain); }
.elv-taille-inconnue { opacity:.55; }
.elv-taille-ancienne strong { color:var(--accent); }
.elv-cat .elv-prix { margin-bottom:0; }
.elv-prix-libelle { font-size:11px; color:var(--texte-faible); }
#elv-suivre { margin-top:10px; width:100%; }
/* Le marché des montures : toutes celles du catalogue, vides comprises. */
.elv-filtres-montures { padding-top:0; }
.elv-filtres-montures select { width:auto; }
.elv-case-inline { display:flex !important; align-items:center; gap:6px; margin:0 !important; font-size:12px; color:var(--texte-attenue); white-space:nowrap; }
.elv-case-inline input { width:auto !important; }
.elv-montures-scroll { max-height:60vh; }
.elv-montures-scroll td small { display:block; }
.elv-inconnu { color:var(--texte-faible); font-style:italic; }
.elv-ligne-vide td { opacity:.6; }
.elv-ligne-vide:hover td { opacity:1; }
.elv-volume strong { color:var(--fam-bleu, #8dcde7); }
.elv-comparer { padding:3px 9px; font-size:11px; white-space:nowrap; }

/* ── SESSION EXPIREE : l'ecran ne se met plus a jour ─────────────────────────
   🔴 POURQUOI CE BANDEAU EXISTE. Le 2026-09-17, Benjamin a releve des prix a
      l'HDV et son onglet Craft n'a pas bouge. Les prix etaient bien en base :
      sa session avait expire, le rafraichissement recevait des 401 et les
      avalait en silence. L'ecran restait credible et perime — le defaut que ce
      projet traque partout ailleurs.

   🎯 EN HAUT ET DANS LE FLUX, pas en surimpression : un classement perime reste
      utile a lire, il ne devient dangereux que si on le croit frais. On avertit,
      on ne masque pas.

   ⛔ EN `--perte` ET PAS EN `--accent` : c'est un avertissement, pas une mise en
      valeur. L'or du kama sert a designer la monnaie et les cibles, jamais une
      alerte — cf. la regle d'usage des couleurs d'etat en tete de fichier. */
.bandeau-session-expiree {
    margin: 0 0 var(--espace-3);
    padding: var(--espace-3) var(--espace-4);
    border: 1px solid var(--perte);
    /* ⚠️ `--perte-fond` et PAS un color-mix improvise : la variable est definie
       dans les DEUX themes (sombre #241416, clair #fbeae8), donc le bandeau
       suit la bascule sans code en plus. */
    background: var(--perte-fond);
    border-radius: var(--arrondi);
    color: var(--texte);
    font-size: 0.9rem;
    line-height: 1.5;
}

.bandeau-session-expiree strong {
    display: block;
    margin-bottom: 2px;
    color: var(--perte);
}

.bandeau-session-expiree a {
    color: var(--accent);
    font-weight: 600;
    text-decoration: underline;
    margin-left: var(--espace-2);
}


/* ═══════════════════════════════════════════════════════════════════════════
   LE FLUX DES LECTURES — la fenetre vivante, en pied de rail

   🎯 Elle occupe la place vide entre les onglets et le pied. ⛔ Elle ne la
      PREND pas : `.onglets` garde son `flex: 1`, et c'est cette fenetre qui est
      bornee. Sur un petit ecran, ce sont donc les onglets qui defilent — jamais
      le menu qui disparait sous le flux.

   ⚠️ DENSITE ASSUMEE : 10px de police, deux lignes par entree, dans 172px de
      large. C'est le format d'une liste qu'on balaie du coin de l'oeil en
      jouant, pas d'un tableau qu'on lit. Chaque ligne porte son detail complet
      dans son `title` (heure exacte, taille de lot, serveur), repris par
      `infobulle.js`.
   ═══════════════════════════════════════════════════════════════════════════ */
.flux {
    display: flex;
    flex-direction: column;
    min-height: 0;
    margin-top: 6px;
    padding-top: 8px;
    border-top: 1px solid var(--bordure);
    flex: none;
}

/* 🔴 LA HAUTEUR EST BORNEE EN `vh`, ET C'EST CE QUI PROTEGE LE MENU. A hauteur
   libre, une fenetre de 40 lignes pousserait les onglets hors de l'ecran : on
   perdrait la navigation pour gagner un flux — un mauvais echange sur un outil
   dont le rail EST la navigation.
   ⚠️ `28vh` : environ 6 lignes sur un 1080p. Assez pour voir une activite, trop
      peu pour se transformer en archive — c'est le role de l'ecran Craft et de
      l'historique des prix, pas celui d'une sidebar.

   🔴 MAIS LE `vh` SEUL NE PROTEGE PAS LE MENU, contrairement a ce que la
      premiere version supposait, ET C'EST UNE ERREUR D'ECHELLE : le flux
      maigrit proportionnellement a la fenetre, alors que le besoin des ONGLETS
      est FIXE (13 entrees x ~30px = ~403px, quelle que soit la hauteur
      d'ecran). A 700px de haut — une fenetre non maximisee, cas courant quand
      on alt-tab depuis le jeu — le rail demande ~690px : les onglets perdent
      quatre entrees derriere une barre de defilement pour nourrir un flux qui,
      lui, garde ses 196px. C'est exactement l'echange que l'en-tete de ce bloc
      dit refuser.
   ⚠️ D'ou le second plafond en `px`, et la reduction sous 820px de haut : le
      flux cede la place AVANT la navigation. Il reste alors ~3 lignes —
      assez pour repondre « ca bouge / ca ne bouge pas », qui est sa question. */
.flux-liste {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: min(28vh, 200px);
    overflow-y: auto;
    overflow-x: hidden;
    /* ⚠️ La gouttiere est RESERVEE meme sans barre : sans elle, la barre
       apparait a la 4e lecture et rogne d'un coup ~15px sur 138px de corps —
       tous les noms se re-ellipsent et la colonne des prix saute. Dans 172px,
       15px ne sont pas un detail. Meme geste que `.fam-table-scroll`. */
    scrollbar-gutter: stable;
    /* ⛔ Le navigateur ne doit PAS tenter de « garder la position » quand la
       liste se reconstruit : ici le contenu arrive par le HAUT, et l'ancrage
       ferait descendre la vue pour suivre les anciennes lignes. */
    overflow-anchor: none;

    /* 🔴 LA BARRE DE DEFILEMENT EN FIN (corrige le 2026-09-17). Par defaut le
       navigateur pose la barre NATIVE de Windows, large d'environ 15px — soit
       ~10 % d'un rail de 160px, pris sur les noms d'items qui manquent deja de
       place. C'est le seul conteneur du produit assez etroit pour que ce cout
       se voie, d'ou un reglage LOCAL et non global.
       ⚠️ `thin` plutot qu'une barre dessinee a la main : on garde la barre du
          systeme (donc son comportement, son clic, son survol), on ne fait que
          demander la variante fine. Une barre entierement recreee en CSS se
          met toujours a diverger du reste de l'OS. */
    scrollbar-width: thin;
    scrollbar-color: var(--bordure) transparent;
}

/* Le pendant WebKit : `scrollbar-width` ne suffit pas sur Chrome/Edge, qui
   suivent encore la forme `::-webkit-scrollbar`.
   ⚠️ 6px : assez large pour rester attrapable a la souris, trois fois moins
      que la barre native. */
.flux-liste::-webkit-scrollbar { width: 6px; }
.flux-liste::-webkit-scrollbar-track { background: transparent; }
.flux-liste::-webkit-scrollbar-thumb {
    background: var(--bordure);
    border-radius: 3px;
}
/* ⛔ Hover ACHROMATIQUE, comme tous les survols du produit. */
.flux-liste::-webkit-scrollbar-thumb:hover { background: var(--texte-faible); }

/* 🔴 LE RAIL COURT : LE MENU PASSE AVANT LE FLUX. En dessous de ~820px, la
   somme marque + 13 onglets + flux + pied depasse l'ecran ; c'est le flux qui
   recule, parce que le rail EST la navigation et qu'un onglet qu'on ne voit
   plus est un ecran qu'on n'ouvre plus. */
@media (max-height: 820px) {
    .flux-liste { max-height: 108px; }
}
@media (max-height: 640px) {
    .flux-liste { max-height: 72px; }
}

/* Replie : l'en-tete reste, la liste part. ⚠️ `display: none` et non une
   hauteur nulle — une liste a 0px continuerait de recevoir le focus clavier. */
.flux.est-ferme .flux-liste { display: none; }

/* ── L'en-tete : pastille, titre, compteur, bascule ───────────────────────── */
.flux-entete {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 4px 6px;
    font-size: 9px;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--texte-faible);
}

.flux-titre { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* La pastille reprend EXACTEMENT la grammaire de `.pastille-direct` : eteinte
   elle est grise, vivante elle passe a l'accent et bat. ⛔ Deux pastilles
   « live » dans le meme rail doivent se ressembler, sinon on croit qu'elles
   disent deux choses differentes. */
.flux-pastille {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--texte-faible);
    flex: none;
}
.flux-pastille.est-vivante {
    background: var(--accent);
    animation: pouls-direct 1.6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
    .flux-pastille.est-vivante { animation: none; }
}

/* Le nombre de readers qui lisent. ⚠️ Cache quand il vaut 0 (cf. le JS) : un
   « 0 » a cote d'une pastille eteinte repeterait la meme information deux fois.
   ⛔ IL N'EST PAS A L'ACCENT (corrige) : l'or est deja porte par la pastille,
      a 6px de la. Deux marques or cote a cote pour dire la meme chose (« ca
      lit ») usent l'accent sans rien ajouter — la regle du fichier est qu'il
      ne se pose pas sur un element qui revient a chaque mise a jour. Le
      COMPTE, lui, est une quantite : il se lit en texte. */
.flux-compte {
    font-size: 9px;
    color: var(--texte-attenue);
    font-variant-numeric: tabular-nums;
    flex: none;
}

/* 🔴 L'IMPORT HUZOUNET EN PANNE. Une pastille rouge et un mot, dans la meme
   grammaire que `.flux-pastille` (6px, rond). ⛔ ELLE N'EXISTE QU'EN PANNE :
   une pastille verte permanente serait un decor qu'on cesse de voir, et le
   jour ou elle changerait de couleur personne ne le remarquerait. */
.flux-import {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--perte);
    flex: none;
    cursor: help;
}
.flux-import[hidden] { display: none; }
.flux-import::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--perte);
}

.flux-bascule {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    padding: 0;
    border: 0;
    border-radius: var(--arrondi-sm);
    background: transparent;
    color: var(--texte-faible);
    cursor: pointer;
    flex: none;
}
.flux-bascule:hover { color: var(--texte); background: var(--fond-survol); }
.flux-bascule:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.flux-bascule svg { width: 13px; height: 13px; }
/* Ferme, le chevron pointe vers le haut : il montre ou la liste reviendra. */
.flux.est-ferme .flux-bascule svg { transform: rotate(180deg); }

/* ── Une ligne : icone, puis deux etages (nom/prix, pseudo/heure) ─────────── */
/* ⚠️ LE PADDING HORIZONTAL EST A 2px, PAS 4px, ET C'EST MESURE. Le corps de la
   ligne ne dispose que de ~123px une fois l'icone (20), le gap (6) et la
   gouttiere de defilement (15) retires ; a 4px de chaque cote, un nom d'item
   tombait a ~12 caracteres et « Anneau du Bouftou Royal » / « ... Ardent » se
   coupaient sur la MEME chaine visible, avec deux prix differents. Les 4px
   rendus vont au nom. Le padding VERTICAL, lui, ne bouge pas : c'est lui qui
   fait la densite de la ligne. */
.flux-ligne {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 2px;
    border-radius: var(--arrondi-sm);
}
/* ⛔ Hover ACHROMATIQUE, comme partout : une ligne survolee ne porte aucun
   verdict. Elle n'est pas cliquable — c'est un flux, pas un menu. */
.flux-ligne:hover { background: var(--fond-survol); }

.flux-icone {
    width: 20px;
    height: 20px;
    object-fit: contain;
    flex: none;
}

/* 🔴 LA PASTILLE DE RUNE — parce qu'une rune N'A PAS D'ICONE en base, et qu'on
   ne lui en invente pas (cf. `services/lectures.py`). Ce losange occupe la
   meme case que l'image : sans lui, les lignes de runes seraient decalees de
   20px vers la gauche et la colonne des noms ne s'alignerait plus. */
/* 🔴 LE LOSANGE N'EST PLUS QUE LE REPLI (2026-09-17). Les runes ONT desormais
   leur vraie icone de jeu, palier compris — cf. `services/lectures._icones_runes`
   (97 paires resolues, 45 basiques + 34 Pa + 18 Ra). Ce marqueur ne sert donc
   plus qu'au cas ou le pont n'aboutit pas.
   ⛔ ON LE GARDE QUAND MEME : une rune dont l'icone manquerait doit occuper sa
      case, sinon sa ligne se decalerait de 20px et la colonne des noms
      cesserait de s'aligner. */
.flux-rune {
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    color: var(--accent);
    border: 1px solid var(--bordure);
    border-radius: var(--arrondi-sm);
    flex: none;
}
/* Une icone d'item absente du catalogue : meme case, mais RIEN a annoncer —
   ni accent ni cadre. ⛔ Un item sans icone n'est pas une rune : lui laisser le
   meme marqueur ferait compter des ressources comme des runes a l'oeil. */
.flux-rune-vide {
    color: var(--texte-faible);
    border-color: transparent;
    opacity: .5;
}

.flux-corps { flex: 1; min-width: 0; }

.flux-haut, .flux-bas {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
}

/* ⚠️ `min-width: 0` + ellipse : « Substrat de Futaie Sombre » doit se couper,
   pas pousser le prix hors de la fenetre. Le nom entier reste dans le `title`. */
.flux-nom {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11px;
    color: var(--texte);
}

/* 🎯 LE PRIX NE PEUT PAS SE FAIRE ROGNER : c'est le chiffre de la ligne.
   ⛔ Il n'est ni vert ni rouge — le fait qu'une lecture arrive n'est ni un gain
      ni une perte. Neutre, comme la pastille. */
.flux-prix {
    flex: none;
    font-size: 11px;
    font-weight: 500;
    color: var(--texte);
    font-variant-numeric: tabular-nums;
}

.flux-bas { font-size: 9px; color: var(--texte-faible); }

.flux-pseudo {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.flux-heure { flex: none; font-variant-numeric: tabular-nums; }

/* 🎯 LE PRIX DE VENTE, EN SECOND (2026-09-17). Le chiffre du haut est le prix
   d'ACHAT — le moins cher en rayon, celui qu'on paye. Celui-ci est le plancher
   auquel on revend. Les deux sont justes ; ce qui manquait, c'est de dire
   lequel est lequel (cf. [dofus-prix-achat-vs-vente]).

   ⚠️ C'EST LE PSEUDO QUI SE FAIT ROGNER, PAS CE PRIX. `flex: none` sur 180px de
      rail : sans lui, « vend 21,9 k » passerait a la ligne et casserait la
      hauteur reguliere des lignes. Le pseudo, lui, a deja son ellipse et son
      nom complet dans le `title`.

   ⛔ PLUS PALE QUE L'HEURE NE SERAIT PAS LISIBLE : il reste sur
      `--texte-faible` comme le reste de la ligne basse, et c'est le mot
      « vend » qui porte la distinction, pas la couleur. Un chiffre qu'on
      n'arrive pas a lire vaut un chiffre absent. */
.flux-vente {
    flex: none;
    font-variant-numeric: tabular-nums;
    opacity: .85;
}

/* Le mot quand il n'y a rien. ⚠️ Il DIT lequel des deux silences c'est
   (personne connecte / personne qui lit) — cf. `flux-lectures.js`. */
.flux-vide {
    padding: 8px 4px;
    font-size: 10px;
    line-height: 1.45;
    color: var(--texte-faible);
    white-space: normal;
}

/* 🔴 RAIL REPLIE : LA FENETRE DISPARAIT ENTIEREMENT. Dans 56px, un pseudo et un
   prix seraient tous deux tronques a deux caracteres — on afficherait des
   chiffres illisibles, donc potentiellement mal lus. Mieux vaut ne rien montrer
   que montrer un montant coupe (meme raisonnement que `.total-fm`, l. 2468).
   ⚠️ La pastille de l'onglet « Direct » reste visible : l'information « ca
      bouge » ne se perd pas au repli. */
.rail.est-replie .flux { display: none; }

/* Sur un rail etroit force (< 900px), meme raison. */
@media (max-width: 900px) {
    .rail .flux { display: none; }
}

/* Un ecran bas : la fenetre se reduit avant de disparaitre, pour que les
   onglets gardent la priorite. */
@media (max-height: 760px) {
    .flux-liste { max-height: 20vh; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   FARM — les monstres classés par kamas espérés par combat.

   🎯 L'écran réutilise `.table-prix`, `.tri-entete`, `.ligne-cliquable` et
      `.defile-x` : rien de neuf pour le tableau lui-même. Ce bloc ne porte
      que ce qui lui est PROPRE — la barre de filtres et les deux badges.
   ═══════════════════════════════════════════════════════════════════════════ */

.farm-filtres {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 18px;
    align-items: flex-end;
}

.farm-filtres label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 12px;
    color: var(--texte-faible);
}

.farm-filtres input[type="number"] { width: 82px; }
.farm-filtres .farm-recherche { flex: 1 1 180px; }
.farm-filtres .farm-recherche input { width: 100%; }

/* Les cases à cocher se lisent en ligne, pas en colonne comme les champs. */
.farm-filtres .farm-case {
    flex-direction: row;
    align-items: center;
    gap: 6px;
}

.farm-avertissement { margin: 0; line-height: 1.5; }

/* 🔴 LES BADGES NE PORTENT PAS L'ACCENT. Règle du design-system : l'or est
      réservé aux états (filtre actif, focus) et aux deux titres majeurs — sur
      une ligne répétée il perdrait ce sens. Ici ce sont des étiquettes
      neutres, en filet, qui se lisent sans crier. */
.badge-archi,
.badge-boss {
    display: inline-block;
    margin-left: 6px;
    padding: 0 5px;
    border: 1px solid var(--bordure-forte);
    border-radius: 3px;
    font-size: 10px;
    line-height: 15px;
    color: var(--texte-faible);
    vertical-align: middle;
    cursor: help;
}

/* 🔴 LA PASTILLE « 1 MONSTRE » DIT COMMENT LIRE LE GAIN, PAS QU'IL EST MAUVAIS.
      Elle marque une zone dont plus de 60 % du gain vient d'un seul habitant
      (mesure du 2026-09-18 : 29 des 60 premières zones, la Forêt Sombre à 99 %
      sur le seul Coffre Sombre). C'est la même nature d'information que la
      couverture — d'où l'`--alerte` (or) et surtout PAS le rouge de la perte,
      qui ferait lire « mauvaise affaire » là où il faut lire « fragile ».
   ⚠️ Elle reprend la forme en filet de `.badge-archi` : ce sont les mêmes
      étiquettes neutres, seule la teinte change. */
.badge-concentre {
    display: inline-block;
    margin-left: 6px;
    padding: 0 5px;
    border: 1px solid var(--alerte);
    border-radius: 3px;
    font-size: 10px;
    line-height: 15px;
    color: var(--alerte);
    vertical-align: middle;
    cursor: help;
    white-space: nowrap;
}

/* Le nom de la zone porte le même poids que celui du monstre : c'est l'entrée
   de la ligne, ce qu'on cherche des yeux en parcourant le classement. */
.nom-zone { font-weight: 600; }

/* ⚠️ Le MVP ne doit pas passer pour l'entrée de la ligne : la zone reste le
      sujet, le MVP est ce qu'on y vise. D'où un poids normal et une taille
      légèrement réduite — assez lisible pour le repérer, assez discret pour
      ne pas concurrencer le nom de la zone à gauche. */
.cellule-mvp { font-size: 12px; white-space: nowrap; }

/* 🔴 UN DÉPLIANT DANS UN DÉPLIANT. Les drops d'un habitant s'ouvrent DANS le
      dépliant de la zone, qui porte déjà `--fond-survol`. Sans distinction, les
      deux niveaux se fondraient et on ne saurait plus quel tableau appartient à
      quoi. Le liseré à gauche rattache le bloc à la ligne du monstre au-dessus,
      comme un fil de retrait — plutôt qu'un troisième gris, qui aurait dérivé
      vers le fond de la page. */
.ligne-detail-habitant > td {
    background: var(--fond);
    box-shadow: inset 3px 0 0 var(--bordure-forte);
}

/* ⚠️ La ligne hors calcul reste LISIBLE, juste en retrait : on la montre
      exprès (une âme masquée passerait pour un prix manquant). */
.table-farm + .defile-x .ligne-hors td,
.ligne-hors td { opacity: .55; }

/* Le détail des drops, déplié sous la ligne du monstre. */
.ligne-detail > td {
    padding: 8px 12px 12px;
    background: var(--fond-survol);
}

/* ── FARM : icônes, détail déplié et copie ─────────────────────────────────
   🎯 Les icônes donnent au tableau la même lecture qu'en jeu : on reconnaît
      un Bouftou ou une Pierre de Diamant à la forme avant de lire le nom.
   ⚠️ Taille FIXE et `flex-shrink:0` : une image qui arrive après coup sans
      dimensions réservées ferait sauter toutes les lignes (reflow). */
.icone-monstre,
.icone-item {
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    object-fit: contain;
    vertical-align: middle;
}

.icone-item { width: 22px; height: 22px; }

/* L'espace réservé quand l'image manque (74 drops sans icône) ou casse : un
   creux discret, jamais l'icône brisée du navigateur qui se lit comme un bug. */
.icone-vide {
    display: inline-block;
    border: 1px dashed var(--bordure-forte);
    border-radius: 3px;
    opacity: .45;
}

.cellule-monstre,
.cellule-drop {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Le nom ne doit pas se couper au milieu à cause de l'icône. */
.cellule-monstre .nom-monstre { min-width: 0; }

/* La ligne dépliée reste visuellement rattachée à son détail. */
.ligne-ouverte > td { background: var(--fond-survol); }

.detail-entete {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.detail-boutons { display: flex; gap: 6px; }

/* 🔴 PAS D'ACCENT ICI NON PLUS : ces boutons apparaissent à chaque ligne
      dépliée. L'or reste réservé aux états et aux deux titres majeurs. */
.bouton-copie {
    padding: 3px 9px;
    border: 1px solid var(--bordure-forte);
    border-radius: 3px;
    background: transparent;
    color: var(--texte-faible);
    font: inherit;
    font-size: 11px;
    cursor: pointer;
}

.bouton-copie:hover {
    color: var(--texte);
    border-color: var(--texte-faible);
}

.bouton-copie:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* ⚠️ L'échec de copie se VOIT : un échec muet ferait coller l'ancien contenu
      du presse-papier sans que rien ne l'indique. */
.bouton-copie-ko {
    color: var(--alerte);
    border-color: var(--alerte);
}

/* ── FARM : la couleur comme lecture, pas comme décoration ─────────────────
   🔴 DEUX LANGAGES DISTINCTS, ET ILS NE SE MÉLANGENT PAS :
        vert (`--gain`)   = de l'argent, l'intensité dit combien ;
        or   (`--alerte`) = une donnée PEU FIABLE, pas une perte.
      C'est la sémantique déjà posée plus haut dans ce fichier. Peindre une
      couverture faible en rouge ferait lire « mauvaise affaire » là où il
      faut lire « on ne sait pas » — la confusion la plus coûteuse ici.

   ⛔ PAS DE FOND COLORÉ SUR LES LIGNES : sans carte, un fond redevient une
      bande visible en travers de la page (cf. `--gain-fond` plus haut). La
      couleur est portée par le TEXTE. */

/* Le gain, sur trois paliers d'une même teinte : l'œil trouve les grosses
   lignes sans lire les chiffres, et l'échelle reste une seule couleur. */
.table-farm .gain-fort {
    color: var(--gain);
    font-weight: 600;
}

.table-farm .gain-moyen { color: var(--gain); }

/* ⚠️ Le palier faible n'est PAS vert : au-delà de trois intensités, la
   nuance ne se lit plus. Il reste au texte normal. */
.table-farm .gain-faible { color: var(--texte); }

/* Un gain nul veut dire « aucun drop coté », pas « ne rapporte rien ». */
.table-farm .gain-nul { color: var(--texte-faible); }

/* La couverture : de l'or qui s'assombrit quand la donnée se dégrade. */
.table-farm .couv-bonne   { color: var(--texte-faible); }
.table-farm .couv-moyenne { color: var(--alerte); }
.table-farm .couv-faible  { color: var(--alerte); font-weight: 600; }

/* Le détail déplié : le taux et l'apport, les deux chiffres qu'on y lit. */
.ligne-detail .taux-haut { color: var(--gain); font-weight: 600; }
.ligne-detail .taux-bas  { color: var(--texte-faible); }
.ligne-detail .apport-fort { color: var(--gain); }

/* 🎯 Un filet vertical léger sépare le bloc des chiffres du bloc des noms :
   sur 300 lignes, c'est ce qui évite de suivre une ligne du doigt. */
.table-farm td:nth-child(3),
.table-farm th:nth-child(3) { border-left: 1px solid var(--filet); }

/* Les lignes alternées : le repère le moins coûteux sur une longue liste.
   ⚠️ `--filet` et pas un gris opaque — il doit rester sous le survol. */
.table-farm tbody tr.ligne-cliquable:nth-child(odd) { background: var(--filet); }

/* ── FARM : copier un objet d'un clic sur son nom ──────────────────────────
   🎯 Les deux boutons d'en-tête copient toute la liste. Ici on copie UN objet
      — le geste qu'on fait vraiment pour aller le chercher à l'HDV.
   ⚠️ C'est un `<button>`, donc il faut annuler l'apparence de bouton : sans
      ça, quatre gros boutons gris remplaceraient la liste d'objets. */
.drop-copiable {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    position: relative;
}

/* 🔴 L'AFFORDANCE EST DISCRÈTE MAIS RÉELLE : un soulignement pointillé qui
      n'apparaît qu'au survol. Souligner en permanence ferait quatre liens
      dans un tableau qui n'en contient aucun autre. */
.drop-copiable:hover span { text-decoration: underline dotted; }
.drop-copiable:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 2px;
}

/* ⛔ LA COCHE EST AJOUTÉE PAR LE CSS, JAMAIS PAR `textContent` : remplacer le
      texte du bouton effacerait l'icône ET le nom, et la ligne reviendrait
      sans son image.
   ⚠️ `position:absolute` : la coche ne pousse rien, les colonnes ne bougent
      pas au moment de la copie. */
.drop-copiable.copie-ok::after,
.drop-copiable.copie-ko::after {
    position: absolute;
    left: 100%;
    margin-left: 8px;
    white-space: nowrap;
    font-size: 11px;
}

.drop-copiable.copie-ok::after {
    content: "copié ✓";
    color: var(--gain);
}

.drop-copiable.copie-ko::after {
    content: "échec";
    color: var(--perte);
}

/* ═══ SIMULATEUR › BRISAGE — le seuil de rentabilite craft → brisage ═════
   🎯 Un tableau qu'on lit sur le SECOND ECRAN, le jeu ouvert a cote : le
      seuil doit sauter aux yeux, le reste reste discret. */
.table-brisage { width: 100%; }
.table-brisage td.num, .table-brisage th.num { white-space: nowrap; }
.table-brisage tbody td { vertical-align: top; }

/* Le seuil est LE chiffre de la ligne : plus gros que les autres. */
.table-brisage .bri-seuil > span:first-child { font-size: 15px; }

/* ⚠️ `input { width:100% }` plus haut : sans reprise, le champ s'etirerait
      sur toute la cellule. Meme gabarit que la saisie des prix. */
.table-brisage .bri-coeff {
    width: 72px;
    padding: 2px 6px;
    text-align: right;
}

/* Un coefficient SAISI se distingue du defaut : sur 100 lignes, on doit voir
   d'un coup d'oeil celles qu'on a deja relevees en jeu. */
.table-brisage .bri-coeff:not(:placeholder-shown) {
    border-color: var(--accent);
    background: var(--champ-rempli);
    font-weight: 600;
}

/* Le nom est un bouton (il ouvre le Simulateur) mais se lit comme un nom. */
.table-brisage .bri-nom { color: var(--texte); text-align: left; }
.table-brisage .bri-nom:hover { color: var(--accent-clair); }

.table-brisage .ligne-incomplete td { color: var(--texte-faible); }
.table-brisage .ligne-incomplete .scout-icone { opacity: .45; }

/* ── BRISAGE › MA SELECTION ─────────────────────────────────────────────
   ⚠️ `input { width:100% }` : la case a cocher s'etirerait sur la cellule. */
.table-brisage .bri-choix { width: auto; margin: 0; cursor: pointer; }

/* Une ligne choisie se reconnait dans le grand tableau, sans lire la case. */
.table-brisage tbody tr.est-choisi { background: var(--accent-fond); }

/* La selection est un bloc a part, separe du catalogue qu'on parcourt dessous. */
.bri-selection {
    margin-bottom: var(--r-lg);
    padding-bottom: var(--r-md);
    border-bottom: 1px solid var(--filet);
}
.table-selection [data-retire] { color: var(--texte-faible); font-size: 14px; }
.table-selection [data-retire]:hover { color: var(--perte); }

/* ── BRISAGE › LE MENU DES METIERS A CASES A COCHER ───────────────────────
   🎯 Un bouton qui a l'allure d'un `select`, et un panneau qui FLOTTE dessous
      (`--fond-eleve`, comme les suggestions et le modal). */
.bri-metiers { position: relative; }
.bri-metiers-bouton {
    min-width: 190px;
    padding: 5px 26px 5px 8px;
    font-size: 12px;
    text-align: left;
    background: var(--fond-eleve);   /* le fond des champs du site */
    border: 1px solid var(--bordure);
    border-radius: var(--arrondi-sm);
    color: var(--texte);
    font-family: inherit;
    cursor: pointer;
    position: relative;
    white-space: nowrap;
}
/* La fleche du « select », pour que le bouton se lise comme un menu. */
.bri-metiers-bouton::after {
    content: "▾";
    position: absolute;
    right: 10px;
    color: var(--texte-attenue);
}
/* Un filtre actif se voit, meme panneau ferme. */
.bri-metiers-bouton.est-filtre { border-color: var(--accent); }
.bri-metiers-panneau {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 40;
    min-width: 240px;
    max-height: 360px;
    overflow-y: auto;
    padding: 6px 10px 8px;
    background: var(--fond-eleve);
    border: 1px solid var(--bordure);
    border-radius: var(--arrondi);
    box-shadow: var(--ombre);
}
.bri-metier-choix {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 4px 2px;
    cursor: pointer;
    white-space: nowrap;
}
/* ⚠️ `input { width:100% }` : la case s'etirerait sur toute la ligne. */
.bri-metier-choix input { width: auto; margin: 0; flex-shrink: 0; }
.bri-metier-choix:hover { color: var(--accent-clair); }


/* ═══════════════════════════════════════════════════════════════════════════
   PRIX DES RUNES › XP FORGEMAGIE
   🎯 L'ecran reutilise `.table-prix`, `.scout-icone`, `.scout-nom` et
      `.tri-entete`. Ce bloc ne porte que ce qui lui est PROPRE : la pastille
      du palier et la teinte des tiers de prix.
   ═══════════════════════════════════════════════════════════════════════════ */

/* La pastille Pa / Ra, collee au nom. Ra plus appuyee que Pa : elle pese dix
   fois la basique, Pa trois fois — l'oeil doit les distinguer sans lire. */
.xpfm-palier {
    display: inline-block;
    margin-left: 6px;
    padding: 0 5px;
    border-radius: var(--arrondi-sm);
    font-size: 10px;
    font-weight: 700;
    line-height: 15px;
    vertical-align: 1px;
}
.xpfm-palier-pa { color: var(--accent); border: 1px solid var(--accent-filet); }
.xpfm-palier-ra { color: var(--accent-texte); background: var(--accent); }

/* Le chiffre qui classe, en pastille teintee par TIERS de prix (relatif aux
   autres runes, cf. `bornesTiers`). Le tiers du milieu reste neutre : trois
   couleurs feraient un sapin, deux suffisent a guider l'oeil vers les bouts. */
.xpfm-kpp {
    display: inline-block;
    min-width: 42px;
    padding: 1px 6px;
    border-radius: var(--arrondi-sm);
    font-weight: 700;
    text-align: right;
}
.xpfm-bon  { color: var(--gain);  background: var(--gain-fond); }
.xpfm-cher { color: var(--perte); background: var(--perte-fond); }
/* Dans la legende du sous-titre, la pastille reste a la taille du texte. */
.carte-entete .xpfm-kpp { min-width: 0; font-weight: 600; text-align: left; }

/* ── FARM › ARCHIMONSTRES : capturer ou acheter l'âme ─────────────────────
   🎯 Quatre paliers validés par Benjamin (2026-10-09) : rouge sous 0, gris
      sous 20 000, vert au-dessus, « vert foncé » à 100 000. Le vert foncé est
      une PASTILLE (fond `--gain-fond`) et pas une seconde teinte de vert :
      deux verts voisins ne se distinguent pas d'un coup d'œil, un fond si.
   ⚠️ Ces classes servent AUSSI dans la légende, en ligne dans le texte. */
.archi-perte  { color: var(--perte); font-weight: 600; }
.archi-faible { color: var(--texte-faible); }
.archi-bon    { color: var(--gain); font-weight: 600; }
.archi-fort   { color: var(--gain); font-weight: 700; }
.archi-fort .archi-chiffre,
.archi-chiffre.archi-fort {
    display: inline-block;
    padding: 1px 6px;
    border-radius: var(--arrondi-sm);
    background: var(--gain-fond);
}
/* Un prix de plus de 7 jours garde sa couleur, estompée : le chiffre se lit,
   et se lit comme vieux. */
.archi-perime { opacity: .5; }

/* « si Tofu Maléfique : 51 118 » — le chiffre avec la pierre au-dessus, sous
   le chiffre retenu. Discret : c'est une hypothèse, pas la lecture principale. */
.table-archis .archi-si {
    display: block;
    font-size: 11px;
    font-weight: 400;
    color: var(--texte-faible);
    white-space: nowrap;
}
.table-archis .archi-zones {
    display: block;
    font-size: 11px;
    color: var(--texte-faible);
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.table-archis .archi-pierre { white-space: nowrap; }
.table-archis .archi-pierre > * { vertical-align: middle; }
.table-archis .archi-pierre .icone-item { margin-right: 4px; }
.table-archis .archi-pierre small,
.table-archis .archi-ame small { display: block; font-size: 11px; }

.archi-legende {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    margin-bottom: 10px;
}
.archi-legende-pierre {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: help;
}

/* La case « j'ai son âme » : toute la cellule est cliquable, pas seulement
   les 13 px de la case. */
.table-archis .archi-case-td { width: 1%; padding: 0; }
.table-archis .archi-case {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 6px 10px;
    cursor: pointer;
}
.table-archis .archi-case input {
    width: 16px;
    height: 16px;
    margin: 0;
    cursor: pointer;
    accent-color: var(--gain);
}
/* 🎯 Une âme possédée s'efface : l'œil va droit à ce qui reste à faire.
   ⚠️ Tout SAUF la case, qui doit rester nette pour qu'on puisse décocher. */
.table-archis tr.archi-possede > td:not(.archi-case-td) { opacity: .4; }

.archi-progression { margin-bottom: 12px; }
.archi-progression p { margin: 4px 0 0; }
.archi-progres-ligne {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}
.archi-barre {
    flex: 1 1 160px;
    max-width: 320px;
    height: 8px;
    border-radius: 4px;
    background: var(--bordure-forte);
    overflow: hidden;
}
.archi-barre > span {
    display: block;
    height: 100%;
    background: var(--gain);
}
