/* web/css/style.css — direction « Atelier ».
   Aucune police ni feuille de style externe : la page doit se servir hors ligne.

   CE QUI FAIT CETTE DIRECTION, et pourquoi elle a été retenue contre trois
   autres : c'est la seule où l'habillage TRAVAILLE. Chaque métier porte sa
   couleur, et on la retrouve sur la tuile de la barre latérale, sur la ressource
   sélectionnée, sur la puce de l'étape et sur le marqueur de la carte. Sur une
   boucle qui mêle Salikrone (Alchimiste) et Écume de mer (Mineur), le
   propriétaire voit quelle étape relève de quel métier SANS LIRE. Les trois
   directions écartées étaient des ambiances : une seule couleur d'accent y
   faisait tout le travail.

   Les sept couleurs ne sont PAS ici. Elles vivent dans `web/js/palette.js`, qui
   est lu par quatre modules d'interface et vérifié sous Node ; cette feuille ne
   connaît que les deux propriétés qu'ils posent sur les nœuds :

       --job        la teinte pleine  — pastille, filet, liseré
       --job-wash   son lavis à 12 %  — tout ce qui sert de fond

   Toute règle qui en dépend porte un repli (`var(--job, var(--line))`) : un nœud
   sans métier ne doit pas devenir invisible.

   LE THÈME SOMBRE A ÉTÉ PROPOSÉ ET ÉCARTÉ. Il n'y en a pas ici, et il ne faut
   pas en bâtir un « au cas où » : deux thèmes, c'est deux fois la surface où la
   couleur de métier peut cesser d'être lisible. */

:root {
  /* Les jetons de la direction, validés en aperçu. Les noms de variables sont
     restés ceux d'avant l'habillage — renommer 500 lignes n'aurait rien
     appris — mais chaque valeur est celle du jeton nommé en commentaire. */
  --bg: #FAF9F6;            /* fond */
  --panel: #FFFFFF;         /* panneau */
  --panel-2: #F5F3EE;       /* fond, un cran sous le panneau */
  --line: #E4E1DA;          /* filet */
  --text: #23211D;          /* encre */
  --muted: #6E6A61;         /* encre douce */
  --faint: #938F86;         /* encre pâle */
  --accent: #6F66CF;        /* accent */
  --accent-ink: #FFFFFF;
  --sel-bg: #EFEDFA;        /* sélection — fond */
  --sel-line: #C9C3F0;      /* sélection — bord */
  --sel-ink: #3E3673;       /* sélection — encre */

  /* Alerte et avertissement, ramenés sur fond clair. Ce ne sont pas des
     couleurs de métier et elles ne doivent surtout pas y ressembler : le rouge
     et l'ocre d'alerte sont hors de la palette des sept. */
  --danger: #A2372A;
  --danger-bg: #FBEBE7;
  --warn: #8A5A12;
  --warn-bg: #FBF3E3;
  --warn-line: #E2CB99;

  /* UNE SEULE FAMILLE, SANS EMPATTEMENT, partout — y compris pour les commandes
     /travel, qui étaient en chasse fixe. Ce que la chasse fixe apportait là,
     c'était l'alignement des chiffres : `tabular-nums` le rend sans changer de
     famille, et la commande reste distincte par son fond et sa couleur. */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { height: 100%; }

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.45 var(--font);
}

/* LE FOCUS CLAVIER RESTE VISIBLE PARTOUT. C'est une interface qu'on utilise une
   main sur le clavier, l'autre sur le jeu.

   Le cerne est en ENCRE, et c'est un choix contre l'accent : l'accent de la
   direction (#6F66CF) est aussi, au bit près, la couleur du métier Alchimiste.
   Un cerne d'accent posé sur une tuile de Bûcheron dirait « Alchimiste » à
   l'endroit exact où la couleur est censée désigner un métier. L'encre ne
   désigne aucun métier, et c'est le plus fort contraste dont dispose la page. */
:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ---- En-tête ---- */

header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 1rem;
  padding: 0.65rem 1rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}
header h1 { margin: 0; font-size: 1rem; font-weight: 600; letter-spacing: 0.01em; }
.muted { margin: 0; color: var(--muted); font-size: 0.85rem; }

/* ---- Erreurs et avertissements ---- */

#error {
  margin: 0;
  padding: 0.8rem 1rem;
  background: var(--danger-bg);
  color: var(--danger);
  border-bottom: 1px solid var(--line);
  /* `pre-wrap` et non `pre-line` : le message d'erreur indente ses commandes de
     quatre espaces, et `pre-line` les aurait réduites à une seule (constaté à la
     capture). `pre-wrap` garde les espaces et continue de replier les lignes
     longues. */
  white-space: pre-wrap;
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

.warn {
  margin: 0.5rem 0 0;
  padding: 0.45rem 0.6rem;
  background: var(--warn-bg);
  color: var(--warn);
  border-left: 3px solid var(--warn-line);
  border-radius: 3px;
  font-size: 0.82rem;
}

/* ---- Grille trois colonnes ---- */

#layout {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 330px minmax(0, 1fr) 360px;
  gap: 1px;
  background: var(--line);
}
#layout > section {
  min-height: 0;
  overflow: auto;
  padding: 0.9rem;
  background: var(--panel);
}
/* ⚠ `#layout > #map` ET NON `#map` : écrite `#map`, cette règle était MORTE PAR
   SPÉCIFICITÉ, et ses DEUX déclarations avec elle. `#layout > section` vaut
   (0,1,1) — un identifiant et un type — contre (0,1,0) pour `#map` seul : c'est
   le voisin qui l'emportait sur les deux propriétés, et être écrit plus bas dans
   la feuille n'y changeait rien, l'ordre ne départage qu'à spécificité égale.

   Mesuré dans Chrome à 1440 × 900, `getComputedStyle('#map')` AVANT correction :
   `padding` 14,4 px sur les quatre côtés, `background-color` rgb(255, 255, 255)
   — donc `--panel`, jamais `--bg`. Le canvas y perdait 28,8 px dans CHAQUE
   dimension : 719,20 × 757,54 px pour une section de 748 × 786,34. Soit
   544 824 px² de carte au lieu de 588 179 : 43 355 px² retrouvés, 7,96 % de plus.

   Un cran de descente suffit : (0,2,0) l'emporte sans `!important`. La règle
   suivante ne pose pas le problème — `#map canvas` porte déjà un type de plus et
   ne concurrence aucune autre. */
#layout > #map { padding: 0; background: var(--bg); }
#map canvas { display: block; width: 100%; height: 100%; }

/* ---- Formulaire de filtres ---- */

.group { margin-bottom: 1.1rem; }
.group > h2 {
  margin: 0 0 0.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--faint);
}
.field { display: block; margin-bottom: 0.55rem; }
.field-label { display: block; margin-bottom: 0.2rem; color: var(--muted); font-size: 0.8rem; }

/* UNE BASCULE, ET ELLE DOIT SE LIRE COMME TELLE : la case d'abord, son libellé
   juste après, sur la même ligne. `.field` met le libellé AU-DESSUS de son
   champ, ce qui convient à un menu ou à un nombre et pas du tout à une case —
   une case posée sous son propre libellé se lit comme un champ vide.

   Le libellé porte l'encre pleine et non `--muted` : ce n'est pas une légende,
   c'est la question posée. Ce qui l'explique, la note en dessous, reste discret.

   La règle de largeur juste au-dessous met tous les `input` à 100 % ; une case à
   cocher étirée sur la colonne serait un rectangle qu'on ne reconnaîtrait pas.
   D'où la remise à sa taille propre, ici et pas ailleurs. */
.toggle {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  cursor: pointer;
}
.toggle input[type="checkbox"] {
  width: auto;
  flex: none;
  margin: 0;
  padding: 0;
  cursor: pointer;
  /* L'accent de la direction sur la case cochée : c'est la même encre que la
     sélection, et elle dit « ce réglage est actif » sans rien ajouter. */
  accent-color: var(--accent);
}
.toggle-label { font-size: 0.9rem; }
/* La note du réglage : elle change à chaque bascule et porte des nombres
   mesurés, d'où l'alignement des chiffres. */
.zaap-note { font-variant-numeric: tabular-nums; }

input, select, button {
  font: inherit;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 0.35rem 0.45rem;
  width: 100%;
}
/* Tous les champs numériques : les chiffres doivent s'aligner d'un cran à
   l'autre, sinon le plafond de déplacements « danse » pendant qu'on le règle. */
input[type="number"], select { font-variant-numeric: tabular-nums; }
input[type="range"] { padding: 0; }

button.primary {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
  font-weight: 600;
  cursor: pointer;
}
button.primary:hover:not(:disabled) { background: var(--sel-ink); border-color: var(--sel-ink); }
button.primary:disabled { opacity: 0.45; cursor: not-allowed; }

button.secondary { cursor: pointer; }
button.secondary:hover:not(:disabled) { border-color: var(--faint); background: var(--panel-2); }

.start-value {
  display: block;
  margin-bottom: 0.35rem;
  padding: 0.35rem 0.45rem;
  background: var(--panel-2);
  border: 1px dashed var(--line);
  border-radius: 5px;
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}

/* ---- Tuiles de métier ----

   Le choix se fait au clic sur des images, sans champ de saisie : une rangée de
   métiers dans la colonne, puis la boîte du métier ouvert par-dessus la carte.
   LE NOM RESTE ÉCRIT SOUS CHAQUE TUILE — 485 ressources pour 356 icônes
   distinctes, 59 iconId partagés par 188 objets : une grille purement visuelle
   serait ambiguë, la couleur de métier n'y change rien. */

.jobs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: 0.3rem;
}

.job-tile, .resource-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  padding: 0.3rem 0.15rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  text-align: center;
  line-height: 1.15;
}

/* LE FILET DE MÉTIER, sous la tuile, TOUJOURS VISIBLE. C'est ici que le
   propriétaire apprend la couleur d'un métier — sur la seule surface de la page
   où les sept sont côte à côte. S'il fallait ouvrir la tuile pour la voir, la
   couleur ne serait plus qu'une décoration d'état. */
.job-tile { border-bottom: 3px solid var(--job, var(--line)); }
.job-tile:hover { background: var(--job-wash, var(--panel-2)); }
.job-tile[aria-expanded="true"] {
  background: var(--job-wash, var(--panel-2));
  border-color: var(--job, var(--accent));
}

.tile-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
}
.tile-icon img { max-width: 100%; max-height: 100%; }

/* Monogramme DESSINÉ. Il n'est plus le cas nominal d'aucune tuile — « Base » et
   « Succès » empruntent désormais l'image de ce qu'elles contiennent, cf.
   `catalog.js` — mais il reste le repli de toute image qui ne CHARGE PAS : la
   spec pose les images comme facultatives, et le réseau peut manquer. */
.tile-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  border: 1px dashed var(--line);
  border-radius: 4px;
  color: var(--faint);
  font-size: 0.7rem;
  letter-spacing: 0.03em;
}

.tile-name {
  font-size: 0.68rem;
  overflow-wrap: anywhere;
}
.tile-count, .tile-level {
  color: var(--muted);
  font-size: 0.62rem;
  font-variant-numeric: tabular-nums;
}

/* Le sous-succès d'un objet de la tuile « Succès ». Plus discret que le nom
   qu'il accompagne, mais LISIBLE : c'est lui qui dit pourquoi cet objet-là est
   resté quand 287 autres sont partis. */
.tile-note {
  color: var(--muted);
  font-size: 0.6rem;
  overflow-wrap: anywhere;
}

/* ---- La boîte à ressources ----

   EN SURVOL, PAR-DESSUS LA CARTE, ANCRÉE SUR LA TUILE CLIQUÉE. Deux autres
   dispositions ont été montrées au propriétaire et écartées : la colonne qui
   pousse la carte, et le tiroir permanent. La carte ne bouge donc pas et ne
   rétrécit pas — la boîte est en `position: fixed`, hors du flux, et c'est
   `placeToolbox()` qui lui donne ses coordonnées.

   Elle est posée sur le `body` et non dans #filters, qui porte `overflow: auto`
   et l'aurait DÉCOUPÉE au bord de la colonne. */

.toolbox {
  position: fixed;
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.7rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-top: 3px solid var(--job, var(--accent));
  border-radius: 8px;
  /* L'ombre est ce qui dit « au-dessus ». Sans elle, la boîte se confondrait
     avec la carte qu'elle recouvre. */
  box-shadow: 0 10px 30px rgba(35, 33, 29, 0.18), 0 2px 6px rgba(35, 33, 29, 0.10);
  /* LA HAUTEUR EST POSÉE PAR `placeToolbox`, en ligne, à chaque placement : elle
     vaut la place réellement disponible SOUS LA TUILE. C'est ce qui garde la
     boîte ancrée — on la réduit avant de la déplacer, jamais l'inverse. Le
     `100vh` ci-dessous n'est que la borne du premier rendu, avant que le
     placement n'ait mesuré quoi que ce soit.

     ⚠ `overflow: visible` ET NON `auto` : c'est la GRILLE qui défile, pas la
     boîte. Deux raisons, et la première est un défaut constaté — un
     `overflow: auto` ici DÉCOUPE la pointe, qui est posée à -8 px du bord. La
     seconde tient à l'usage : l'en-tête nomme le métier ouvert et la ligne
     d'aide dit que la boîte reste ouverte ; les faire défiler hors de vue au
     premier coup de molette retirerait justement ce qu'on vient d'y écrire. */
  max-height: calc(100vh - 16px);
  overflow: visible;
  /* La largeur est celle de la grille — six colonnes, 540 px mesurés. Sur une
     fenêtre plus étroite que ça, `placeToolbox` colle la boîte au bord et le
     reste sortirait de l'écran : ce plafond-ci l'en empêche, et c'est la grille
     qui défile alors en travers. Le cas ne concerne pas l'usage visé — la
     disposition en trois colonnes se replie déjà à 1100 px — mais une boîte
     tronquée hors écran serait un cul-de-sac, pas une gêne. */
  max-width: calc(100vw - 16px);
}

/* LA POINTE : ce qui dit d'où la boîte sort.

   Les bords hauts de la boîte et de la tuile sont déjà alignés dans le cas
   courant, mais un bord commun se lit mal entre une tuile de 74 px et une boîte
   de 446 px. La pointe désigne le MILIEU de la tuile — `placeToolbox` en calcule
   la hauteur — et elle reste juste quand la boîte a dû remonter, cas où elle est
   la seule chose qui relie encore les deux.

   Elle est bâtie en bordures et non en `transform: rotate` : une boîte tournée
   à 45° donnerait une ombre portée fausse sur deux de ses côtés. */
.toolbox::after {
  content: "";
  position: absolute;
  top: var(--toolbox-pointer, 24px);
  width: 0;
  height: 0;
  margin-top: -8px;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
}
.toolbox[data-side="right"]::after {
  left: -8px;
  border-right: 8px solid var(--job, var(--accent));
}
.toolbox[data-side="left"]::after {
  right: -8px;
  border-left: 8px solid var(--job, var(--accent));
}

.toolbox-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.toolbox-dot {
  flex: none;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  background: var(--job, var(--muted));
}
.toolbox-title { font-weight: 600; }
.toolbox-count {
  flex: 1;
  color: var(--muted);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}
.toolbox-close {
  flex: none;
  width: 1.6rem;
  padding: 0;
  border: none;
  border-radius: 5px;
  background: transparent;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.toolbox-close:hover { background: var(--panel-2); color: var(--text); }
.toolbox-hint { font-size: 0.72rem; }

/* LA GRILLE EST DIMENSIONNÉE POUR LA PLUS GROSSE TUILE, une fois pour toutes :
   le Pêcheur et ses 23 ressources, contre 289 pour l'ancienne tuile « Sans
   métier ». Six colonnes de 82 px y font quatre rangées — mesuré dans Chrome :
   grille de 366 px de haut dans une boîte de 540 × 446. La largeur ne dépend
   PAS du métier ouvert — une boîte qui rétrécirait sur la tuile Base, qui n'a
   qu'une ressource, sauterait de taille sous le curseur à chaque changement de
   métier. La hauteur, elle, suit le contenu.

   Plus de plafond de 220 px ni de défilement interne : c'était la conséquence
   des 289 ressources d'avant les coupes, et la boîte flottante n'a plus la
   colonne de filtres à ménager. */
.resource-grid {
  display: grid;
  grid-template-columns: repeat(var(--toolbox-columns, 6), 82px);
  gap: 0.3rem;
  /* `min-height: 0` est ce qui autorise un enfant de flexbox à RÉTRÉCIR sous sa
     taille de contenu. Sans lui, la grille imposerait sa hauteur naturelle, la
     boîte dépasserait son propre plafond, et le défilement n'aurait jamais
     lieu — c'est le piège classique de `flex` et `overflow`. */
  min-height: 0;
  overflow-y: auto;
  overflow-x: auto;
}

.resource-tile:hover { background: var(--job-wash, var(--panel-2)); }
/* LA RESSOURCE PRISE PORTE LA COULEUR DE SON MÉTIER, et non une couleur de
   sélection générique : c'est la même que la tuile au-dessus, la même que la
   puce qui va apparaître dans la colonne, la même que le marqueur sur la carte.
   Une pastille pleine en haut à droite, parce que le fond lavé seul ne suffit
   pas à distinguer « pris » de « survolé ». */
.resource-tile.picked {
  background: var(--job-wash, var(--sel-bg));
  border-color: var(--job, var(--sel-line));
}
.resource-tile.picked::after {
  content: "";
  position: absolute;
  top: 4px;
  right: 4px;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--job, var(--accent));
  box-shadow: 0 0 0 2px var(--panel);
}

/* ---- Zones proposées ---- */

.zone-list { display: flex; flex-direction: column; gap: 0.3rem; margin-top: 0.4rem; }

/* UNE ZONE SE LIT EN DEUX COLONNES : son image, puis ce qu'on en dit.

   « Les zone porposées manque de clareté, peux-tu mettre une image/icone pour
   reconnaitre facilement la zone ? » Il n'existe AUCUNE icône de zone dans
   DofusDB — ni les 562 sous-zones ni les 69 aires n'ont de champ d'image — donc
   c'est la vignette d'une map de la zone qui sert, et elle est à GAUCHE du
   texte : on balaie une colonne d'images, pas une colonne de titres. */
.zone {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.4rem 0.5rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: 5px;
  text-align: left;
  cursor: pointer;
}
.zone-body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  /* `min-width: 0` : sans lui, un titre long refuse de se replier et pousse la
     ligne au-delà de la colonne — le défaut classique d'un enfant de flex. */
  min-width: 0;
  flex: 1;
}

/* LA VIGNETTE. Boîte à taille FIXE, et l'image la remplit en `cover` : les
   vignettes de DofusDB font 1,97 de rapport à l'échelle 0.25 (478 × 243 px
   mesurés), la boîte 1,91 — le recadrage se compte en pixels, et une hauteur
   variable ferait danser la liste d'une zone à l'autre.

   Le fond gris est ce qu'on voit AVANT que l'image n'arrive : elles se chargent
   à la demande, et une case vide se lit mieux qu'un trou de la taille d'une
   image. Une URL qui échoue retire la boîte entière — la ligne se referme alors
   sur son texte, qui a toujours suffi à désigner la zone. */
.zone-thumb {
  flex: none;
  width: 88px;
  height: 46px;
  overflow: hidden;
  border-radius: 4px;
  background: var(--panel-2);
}
.zone-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.zone:hover { background: var(--panel-2); }
/* Une zone n'appartient à AUCUN métier : elle porte donc la sélection
   générique, la seule de la page — celle des jetons de la direction. */
.zone.chosen { border-color: var(--sel-line); background: var(--sel-bg); color: var(--sel-ink); }
.zone-head { font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.zone-sub { color: var(--muted); font-size: 0.73rem; font-variant-numeric: tabular-nums; }
/* LA COMMANDE D'UNE DESTINATION.

   C'est ce qui fait d'une map seule ADRESSABLE autre chose qu'une ligne morte :
   on peut y aller sans rien calculer. Elle n'apparaît que là — une entrée au
   repli n'y a pas droit, sa commande mènerait au Village d'Amakna.

   `user-select: text` dans un bouton : c'est déjà ce que fait `.steps-travel`,
   et pour la même raison — il n'y a aucun bouton de copie dans ce projet, on
   sélectionne et on copie. Le clic sur le bouton continue de choisir la zone. */
.zone-travel {
  align-self: flex-start;
  margin-top: 0.15rem;
  padding: 0.05rem 0.35rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--sel-ink);
  font-family: var(--font);
  font-size: 0.73rem;
  font-variant-numeric: tabular-nums;
  user-select: text;
}

/* LES ENTRÉES MORTES, REPLIÉES — « à mentionner sans encombrer ».

   Un `<details>` natif, fermé au départ : l'intertitre tient sur UNE ligne, et
   les boutons ne se déroulent que si on le demande. Ce sont des maps que DofusDB
   pose en (0, 0) faute de savoir où elles sont : il n'y a pas d'endroit où
   aller, mais elles portent bel et bien les ressources demandées, et l'outil ne
   cache pas ce qu'il a trouvé.

   La couleur des avertissements sur le seul résumé : la réserve porte sur le
   groupe, et les boutons qu'il contient restent des boutons ordinaires. */
.zone-dead { margin-top: 0.4rem; }
.zone-dead-summary {
  cursor: pointer;
  color: var(--warn);
  font-size: 0.76rem;
  line-height: 1.35;
}
.zone-dead .note { margin-top: 0.3rem; }

/* ---- Puces de ressources ----

   LE MÊME OBJET DANS TROIS ÉCRANS : la sélection dans la colonne, la récolte
   d'une étape dans la liste, la récolte de l'étape courante en mode image. Une
   seule règle, donc une seule apparence — et la pastille porte la couleur du
   métier de SA ressource, jamais celle de l'étape qui la contient. */

.chips { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.4rem; }
.resource-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.5rem;
  background: var(--job-wash, var(--panel-2));
  border: 1px solid var(--job, var(--line));
  border-radius: 999px;
  color: var(--text);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}
.chip-dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--job, var(--muted));
}
.resource-chip .chip-remove {
  width: 1.2rem;
  margin-left: 0.1rem;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  line-height: 1;
  cursor: pointer;
}
.resource-chip .chip-remove:hover { color: var(--danger); }

/* ---- Notes, aperçu de commande ---- */

/* Les notes disent ce que l'interface ne peut pas montrer : d'où vient un
   chiffre, ce qu'il coûte, ce qu'il ne garantit pas. Elles sont sobres mais
   jamais masquées — c'est leur seule raison d'être. */
.note {
  margin: 0.35rem 0 0;
  padding-left: 0.55rem;
  border-left: 2px solid var(--line);
  font-size: 0.76rem;
  line-height: 1.35;
}

/* La commande /travel. Plus de chasse fixe — une seule famille sur la page —
   mais tout ce qui la distinguait reste : le fond, le liseré, et surtout des
   chiffres de largeur fixe. */
.template-out {
  display: block;
  padding: 0.3rem 0.45rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 5px;
  color: var(--sel-ink);
  font-family: var(--font);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}

/* ---- Actions ----

   ⚠ LA BARRE EST COLLÉE EN BAS DE LA COLONNE, et c'est une demande directe :
   « MEttre le bouton "Calculer" en sticky en bas de la sidebar de gauche. »
   Mesuré avant : `#filters` défile sur 1 851 px pour 762 px visibles, et les
   boutons étaient tout en bas.

   `position: sticky` et non `fixed` : la barre appartient à SA colonne, elle
   doit en suivre la largeur et disparaître avec elle quand la disposition se
   replie à 1100 px. `#layout > section` porte `overflow: auto`, donc la colonne
   EST le conteneur de défilement — c'est ce qui rend le collage possible ici et
   pas ailleurs.

   ⚠ LA PILE, ET LA BOÎTE À RESSOURCES. `.toolbox` est en `position: fixed` sur
   le `body`, à `z-index: 30`, et `#filters` ne crée AUCUN contexte
   d'empilement — ni `z-index`, ni `transform`, ni `opacity` ; `overflow: auto`
   n'en crée pas. Les deux vivent donc dans la même pile racine, et 30 l'emporte
   sur le 2 ci-dessous : la boîte passe AU-DESSUS de la barre collée, jamais
   l'inverse. Vérifié au pixel dans Chrome par `elementFromPoint` sur le point
   de recouvrement. Le 2, lui, n'est pas décoratif : sans lui, le tableau
   comparatif qui défile passerait PAR-DESSUS la barre.

   Les marges négatives font saigner la barre jusqu'aux bords de la colonne :
   sans elles, le contenu défilerait visiblement dans les 0,9 rem de remplissage
   de chaque côté. */

.actions-bar {
  position: sticky;
  /* ⚠ `-0.9rem` ET NON `0`, ET C'EST MESURÉ. Le rectangle de collage d'un
     élément `sticky` est la boîte de CONTENU du conteneur de défilement : à
     `bottom: 0`, la barre s'arrêtait à 852,13 px pour une colonne finissant à
     866,52 — les 14,39 px du remplissage de `#layout > section` restaient
     découverts, et le contenu défilait VISIBLEMENT dans cette bande, juste sous
     les boutons. Le décalage négatif la laisse descendre jusqu'au bord réel, et
     le remplissage bas repeint ce qu'elle recouvre. */
  bottom: -0.9rem;
  z-index: 2;
  margin: 0.6rem -0.9rem 0;
  padding: 0.55rem 0.9rem 0.9rem;
  background: var(--panel);
  border-top: 1px solid var(--line);
}
.actions { display: flex; gap: 0.4rem; }
.actions button { flex: 1; }
button.secondary:disabled { opacity: 0.45; cursor: not-allowed; }
button.secondary.cancel { border-color: var(--danger); color: var(--danger); }

/* Pendant un calcul, les réglages sont grisés : les modifier n'agit pas sur la
   série en cours. Le tableau et les boutons, eux, restent pleinement lisibles —
   c'est là que l'avancement s'affiche et que l'interruption se demande. */
#filters.busy .group { opacity: 0.55; }

.progress {
  margin-top: 0.45rem;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}

button.armed { border-color: var(--accent); color: var(--sel-ink); background: var(--sel-bg); }

/* ---- Tableau comparatif ---- */

.comparison { margin-top: 1rem; }
.comparison > h2 {
  margin: 0 0 0.4rem;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--faint);
}

/* Cinq colonnes dans une colonne de 330 px : le tableau défile horizontalement
   plutôt que de tronquer un chiffre. */
.compare-table {
  display: block;
  overflow-x: auto;
  width: 100%;
  border-collapse: collapse;
  font-size: 0.76rem;
  font-variant-numeric: tabular-nums;
}
.compare-table th, .compare-table td {
  padding: 0.25rem 0.35rem;
  border-bottom: 1px solid var(--line);
  text-align: left;
  white-space: nowrap;
}
.compare-table th { color: var(--muted); font-weight: 600; }
.compare-table td.num, .compare-table th.num { text-align: right; }
.compare-table td.failure { color: var(--danger); white-space: normal; }
.compare-table tbody tr { cursor: pointer; }
.compare-table tbody tr:hover { background: var(--panel-2); }
.compare-table tbody tr.best { color: var(--sel-ink); font-weight: 600; }
.compare-table tbody tr.active { background: var(--sel-bg); }

.badge {
  margin-left: 0.3rem;
  padding: 0 0.28rem;
  border: 1px solid currentColor;
  border-radius: 999px;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.badge.truncated-badge { border-color: var(--warn); color: var(--warn); }

/* ---- Liste d'étapes ---- */

/* LE FILET DE GAUCHE PORTE LE MÉTIER DE L'ÉTAPE. C'est la marque qui se lit sans
   rien parcourir : sur une boucle qui mêle Alchimiste et Mineur, la colonne des
   filets dit à elle seule quelle étape relève de qui. `--step-stripe` vaut une
   couleur pleine pour un métier, et des bandes FRANCHES pour plusieurs — jamais
   un dégradé, qui fabriquerait une teinte intermédiaire n'appartenant à
   personne. Le calcul est dans `palette.js`. */
.step {
  position: relative;
  display: block;
  padding: 0.5rem 0.5rem 0.5rem calc(0.5rem + 5px);
  margin-bottom: 0.4rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 5px;
  cursor: pointer;
  overflow: hidden;
}
.step::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 5px;
  background: var(--step-stripe, var(--line));
}
.step:hover { background: var(--panel-2); }
/* L'étape active est désignée par son CADRE et son fond, jamais par une couleur
   qui remplacerait celle de son métier : le filet de gauche reste celui du
   métier, y compris sur la ligne qu'on regarde. */
.step.active { border-color: var(--sel-line); background: var(--sel-bg); }
/* LA VIGNETTE DE LIGNE A DISPARU, et ses règles avec elle : « le mode normal tu
   enlèves l'image ». Une colonne de 72 px, un `object-fit`, un repli pour la
   ligne sans image — plus rien de tout ça n'a de nœud à styler. La seule
   vignette du projet est celle du mode image, plus bas. */

.steps-toolbar { display: flex; gap: 0.4rem; margin-bottom: 0.6rem; }
.steps-toolbar button { flex: 1; cursor: pointer; }
.steps-list { list-style: none; margin: 0; padding: 0; }

/* ---- La tête de liste : LA SEULE COMMANDE, puis la légende des flèches ----

   Elle est encadrée et non fondue dans la liste : c'est le geste qu'on fait UNE
   fois, avant de marcher, et il ne doit pas se lire comme une ligne d'étape. */
.steps-head {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-bottom: 0.6rem;
  padding: 0.5rem 0.6rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 5px;
}
/* ---- LE RÉSUMÉ DE LA BOUCLE, ce qui reste de la bande `#stats` ----

   La bande faisait 1440 × 59,19 px sous la grille et joignait trois cents
   caractères par des points médians. Ce qu'elle disait tient désormais dans
   cette colonne, en deux endroits qui ne disent pas la même chose : ici, ce que
   la boucle EST ; sous la liste, ce qu'elle N'A PAS. */

/* LES TROIS CHIFFRES. La valeur porte le poids, le libellé reste une étiquette :
   c'est l'inverse de la bande, où « 54 » et « déplacements » avaient exactement
   le même corps et où rien ne disait où une rubrique finissait. */
.steps-figures { display: flex; flex-wrap: wrap; gap: 0.15rem 0.9rem; }
.figure { display: flex; align-items: baseline; gap: 0.22rem; }
.figure-value {
  font-size: 1.05rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.figure-label { color: var(--muted); font-size: 0.73rem; }

/* LA RÉCOLTE EN PUCES, et non en chaîne. C'est le composant que la liste et le
   mode image emploient déjà pour « N × ressource » : chaque puce porte la
   couleur du métier de SA ressource, et le liseré de la puce fait le travail que
   le point médian faisait mal — dire où une ressource finit. */
.steps-gain { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-top: 0.1rem; }
.steps-gain .resource-chip { padding: 0.1rem 0.45rem; font-size: 0.75rem; }

.steps-start { display: flex; align-items: baseline; gap: 0.5rem; margin: 0; }
.steps-start-label {
  flex: none;
  font-size: 0.73rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.steps-travel {
  flex: 1;
  min-width: 0;
  font-family: var(--font);
  font-size: 0.95rem;
  font-variant-numeric: tabular-nums;
  color: var(--sel-ink);
  overflow-wrap: anywhere;
  /* La commande reste SÉLECTIONNABLE À LA SOURIS : c'est ce qui remplace le
     bouton de copie retiré, et rien d'autre ne le remplace. */
  user-select: text;
}
.steps-note { margin: 0; }
.steps-legend { margin: 0; font-size: 0.73rem; }
/* Le retour au départ ferme la boucle : il est SOUS la liste, à sa place dans
   le trajet, et compté dans le total des courses. */
.steps-back {
  margin: 0.4rem 0 0;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}

/* LES RÉSERVES DE LA BOUCLE : ce qu'elle ne contient pas. Sous la liste, à côté
   du retour au départ — on les lit une fois, pas en jouant, et les mettre en
   tête repousserait l'itinéraire de cinq lignes.

   UNE PAR LIGNE, et c'est la moitié du correctif : jointes par des points
   médians dans la bande, ces cinq phrases formaient à elles seules les deux
   tiers de ce que le propriétaire a refusé de lire. Le liseré de gauche est
   celui des notes de la page, pas celui des avertissements : ce sont des
   réserves honnêtes, pas des erreurs. */
.steps-reserves {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.steps-reserves li {
  padding-left: 0.55rem;
  border-left: 2px solid var(--line);
  color: var(--muted);
  font-size: 0.73rem;
  line-height: 1.35;
  font-variant-numeric: tabular-nums;
}

.step-body { min-width: 0; }
.step-head { display: flex; align-items: baseline; gap: 0.4rem; }
/* L'INDICATION EST CE QU'ON LIT EN JOUANT : elle occupe la place que tenait la
   commande, avec le même poids. Les chiffres s'alignent d'une ligne à l'autre —
   une colonne de « 1↑ » et de « 12→ » qui danse se relit à chaque fois. */
.step-dir {
  flex: 1;
  min-width: 0;
  font-size: 0.95rem;
  font-variant-numeric: tabular-nums;
  color: var(--sel-ink);
  overflow-wrap: anywhere;
}
/* Un tronçon de marche : « 4→ 1↑ ». En ligne, il s'enchaîne au voisin. */
.step-run { display: inline; }
/* UN SAUT DE ZAAP PREND SA PROPRE LIGNE, et c'est toute la raison de cette
   règle : `display: block` le sort de la chaîne des flèches. Un saut n'a aucune
   direction, il ne peut pas se lire au milieu d'un « 4→ 1↑ », et il porte une
   destination qu'aucune flèche ne pourrait porter. Les tronçons de marche
   d'avant et d'après restent à leur place, au-dessus et en dessous : la suite se
   lit dans l'ordre où on la fait.

   Un liseré et le fond de SÉLECTION, jamais une couleur de métier : les sept
   teintes appartiennent aux métiers, et un geste de déplacement n'en est pas un.
   `--sel-*` est déjà ce qui marque la commande de départ, l'autre chose qu'on
   fait sans marcher. */
.step-zaap {
  display: block;
  margin: 0.2rem 0;
  padding: 0.12rem 0.4rem;
  border-left: 2px solid var(--sel-line);
  border-radius: 3px;
  background: var(--sel-bg);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}
/* Le rang d'une salle sur une case partagée. Discret : c'est une précision, pas
   une direction. */
.step-room { margin: 0.15rem 0 0; font-size: 0.73rem; font-variant-numeric: tabular-nums; }
.step-number {
  flex: none;
  min-width: 1.5rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--muted);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.step-place { margin: 0.2rem 0 0; font-size: 0.73rem; font-variant-numeric: tabular-nums; }
.step-harvest { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-top: 0.3rem; }
.step-harvest .resource-chip { padding: 0.1rem 0.45rem; font-size: 0.75rem; }

/* ---- Mode image ---- */

/* Plein écran opaque : le jeu est dans l'autre fenêtre, et ce qu'on vient
   chercher ici doit se lire d'un coup d'œil, sans rien parcourir. */
.image-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(35, 33, 29, 0.55);
}
.image-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: min(720px, 100%);
  max-height: 100%;
  overflow: auto;
  padding: 1rem 1rem 1rem calc(1rem + 6px);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 20px 60px rgba(35, 33, 29, 0.30);
}
/* Le même filet de métier que la ligne dont ce plein écran vient. */
.image-panel::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 6px;
  background: var(--step-stripe, var(--line));
}
.image-counter {
  margin: 0;
  color: var(--faint);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
/* L'INDICATION, en tête de panneau : c'est elle qu'on lit d'une étape à la
   suivante, la commande n'étant plus qu'un moyen de rejoindre le départ. */
.image-direction {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* Le saut de zaap du plein écran, SA PROPRE LIGNE comme dans la liste, et le
   même repère visuel : le liseré et le fond de sélection. « Y aller : 4→ zaap
   2↑ » dit qu'il y a un zaap, cette ligne-ci dit lequel. */
.image-zaap {
  margin: 0;
  padding: 0.15rem 0.5rem;
  border-left: 3px solid var(--sel-line);
  border-radius: 3px;
  background: var(--sel-bg);
  color: var(--sel-ink);
  font-variant-numeric: tabular-nums;
}
.image-room { margin: 0; font-variant-numeric: tabular-nums; }
/* La commande reste grosse et à chiffres alignés, déjà sélectionnée à
   l'ouverture comme à chaque changement d'étape. Aucun bouton de copie — il a
   été retiré, ⌘C suffit. */
.image-travel {
  font-family: var(--font);
  font-size: 1.6rem;
  font-variant-numeric: tabular-nums;
  color: var(--sel-ink);
  text-align: center;
  background: var(--panel-2);
}
.image-place { margin: 0; font-variant-numeric: tabular-nums; }
.image-harvest { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; }
.image-note { margin: 0; }
.image-thumb {
  display: block;
  width: 100%;
  max-height: 42vh;
  object-fit: contain;
  border-radius: 5px;
  background: var(--panel-2);
}
.image-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.image-actions button { flex: 1; cursor: pointer; }
.image-help { margin: 0; font-size: 0.78rem; }

/* ---- Pied de page ----

   LA BANDE `#stats` VIVAIT ICI, et ses règles sont parties avec elle : une
   bande pleine largeur, `min-height: 2.2rem`, mesurée à 59,19 px de haut sur
   la boucle de référence à 1440 × 900. « N'apporte rien et prend de la place. »
   Ce qu'elle disait est rendu par `.steps-figures`, `.steps-gain` et
   `.steps-reserves`, plus haut dans cette feuille. Un test refuse que `#stats`
   reparaisse ici. */

footer {
  padding: 0.45rem 1rem;
  background: var(--panel);
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.78rem;
}
footer p { margin: 0; }

/* ---- Mouvement ----

   L'ANIMATION EST L'EXCEPTION, PAS LA RÈGLE. Elle n'est déclarée que sous
   `prefers-reduced-motion: no-preference` : un navigateur qui demande moins de
   mouvement n'a donc rien à désactiver, il n'y a simplement aucune règle
   d'animation pour lui. C'est l'inverse d'un `animation: none` en surcharge, qui
   laisse la règle exister et dépend de l'ordre de la cascade. */
@media (prefers-reduced-motion: no-preference) {
  /* FONDU SEUL, aucun déplacement. Une ouverture qui glisse de quelques pixels
     décale la boîte de sa tuile pendant qu'on la regarde arriver, et c'est
     exactement le lien qu'elle doit établir. Mesuré au passage dans un onglet
     d'arrière-plan, où Chrome suspend les animations : la boîte y restait figée
     sur son image de départ, donc 4 px au-dessus de son ancre. Un fondu figé,
     lui, ne déplace rien. */
  .toolbox:not([hidden]) { animation: toolbox-apparait 110ms ease-out; }
  @keyframes toolbox-apparait {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}

@media (max-width: 1100px) {
  #layout { grid-template-columns: 1fr; grid-auto-rows: minmax(220px, auto); }
}
