/* Three selectable themes (user-requested 2026-09-06) — "Original" (this
   block, the site's default look, unchanged), "Sombre" and "Clair" below,
   toggled via [data-theme] on <html> (js/ui.js's settings-panel switcher,
   applied without a flash by the tiny inline script at the top of
   index.html's <head>, before the rest of the page paints). Every themeable
   color in the whole stylesheet routes through these ~11 custom properties
   — --accent-text is the one addition beyond what "Original" already had
   (was a hardcoded #0d1017 wherever text sits directly on --accent; Clair's
   accent is dark enough to need white text there instead, so it had to
   become a variable too). Everywhere else (rarity colors, element icons,
   chasse colors, FightLab's own log/damage colors) is intentionally left
   as its own fixed color in every theme — those are semantic/branding
   colors (legendary = gold, fire = red, etc.), not part of the page's own
   light/dark presentation. */
:root {
  --bg: #16131e;
  --bg-panel: #1c1826;
  --bg-panel-2: #241f3238;
  --bg-elevated: #262232;
  --border: #362f45;
  --text: #e2dfe8;
  --text-dim: #948dab;
  --accent: #9482c9;
  --accent-2: #c391ab;
  --danger: #c15a5e;
  --accent-text: #0d1017;
  --radius: 8px;
  --font: "Segoe UI", system-ui, -apple-system, sans-serif;
}

/* 2026-09-27 rework (user): the themes are now a base color + a chosen
   accent. "Original" (the :root block above) is unchanged, violet on
   violet. "Noir" and "Blanc" only define the neutrals here; --accent,
   --accent-2 and --accent-text are set inline on <html> by applyTheme()
   (index.html bootstrap + js/ui.js settings) from the user's accent color.
   The fallbacks below are only used before that script runs. */
:root[data-theme="noir"] {
  --bg: #0b0b0e;
  --bg-panel: #141418;
  --bg-panel-2: #1c1c2238;
  --bg-elevated: #1d1d23;
  --border: #2b2b33;
  --text: #e6e6ea;
  --text-dim: #8f8f99;
  --accent: #9482c9;
  --accent-2: #c391ab;
  --danger: #c15a5e;
  --accent-text: #0d1017;
}
/* Blanc = off-white/cream (2026-09-27, user: "blanc cassé/crème qui
   agresse moins la vue") — no pure #fff anywhere, warm low-glare tones. */
:root[data-theme="blanc"] {
  --bg: #ece6da;
  --bg-panel: #f7f3ea;
  --bg-panel-2: #e6dfd0cc;
  --bg-elevated: #efe9dd;
  --border: #d9d0bf;
  --text: #2a2620;
  --text-dim: #6b6356;
  --accent: #3b6fd6;
  --accent-2: #2f93c9;
  --danger: #d64545;
  --accent-text: #ffffff;
}

* { box-sizing: border-box; }

* {
  scrollbar-width: thin;
  scrollbar-color: var(--border) var(--bg-panel);
}
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: var(--bg-panel); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 6px; border: 2px solid var(--bg-panel); }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
::-webkit-scrollbar-corner { background: var(--bg-panel); }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  min-height: 100vh;
}
/* User-requested (2026-09-05): a stray click-drag anywhere on the page
   (dragging the doll, a slider, a card) was highlighting text like a
   normal text selection — disabled site-wide, re-enabled only where actual
   text entry/selection is the point (typed fields, editable/copyable
   text). */
body { user-select: none; }
input, textarea, select, [contenteditable], .fightlab-log, code, pre {
  user-select: text;
}

/* Film-grain texture behind the page content for a less flat, more tactile
   dark backdrop. Sits at z-index -1 so it only shows through the bare body
   background, not on top of panels/buttons/inputs (which all paint their
   own opaque background). feColorMatrix punches up the contrast of the raw
   turbulence noise so individual grain specks actually read. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  opacity: 0.16;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch' result='t'/%3E%3CfeColorMatrix in='t' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3.5 -1.2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

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

/* ---------- Header / nav ---------- */

header.topbar {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 5px 18px;
  /* Was a fixed dark-purple gradient regardless of theme — user-reported
     (2026-09-06): switching to Sombre/Clair left the top bar looking like
     Original's own colors. Built from the same --bg/--bg-elevated tokens
     every theme already defines instead of its own hardcoded pair. */
  background: linear-gradient(180deg, var(--bg-elevated), var(--bg));
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 50;
}

header.topbar .logo {
  white-space: nowrap;
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
header.topbar .logo-img { height: 26px; width: auto; display: block; filter: saturate(70%) brightness(0.92); }
/* Real blue-toned artwork for Clair (logo/stasisbuilder-logo-clair.png,
   user-provided 2026-09-06) instead of the hue-rotate() approximation this
   replaced — swapped via plain CSS visibility so it's correct the instant
   [data-theme] is set, no JS involved. Hidden by default (Original/Sombre
   keep the native violet artwork, logo-img-dark).
   logo-img-light-wrap crops the source PNG's own empty vertical margin
   (measured via its real alpha bounding box: lettering is 88px tall inside
   a 200px canvas, vs logosite.png's 88px inside 100px) back down to the
   navbar's row height — without this the lettering rendered roughly HALF
   logosite.png's size at the same element height (user-reported
   2026-09-06). The 52px inner height is the scale factor that makes the
   lettering itself match logosite.png's real rendered height (26px ×
   100/88 canvas ratio × 200/100 source-canvas ratio); flex-centering the
   oversized image inside the 26px clipped window then lands the lettering
   centered with the same margin logosite.png already has. */
header.topbar .logo-img-light-wrap { display: none; height: 26px; width: 141px; overflow: hidden; align-items: center; justify-content: center; }
.logo-img-light { height: 52px; width: auto; display: block; filter: saturate(70%) brightness(0.92); }
/* 2026-09-27 (user): the original violet logo in every theme — the blue
   light-theme artwork swap is gone (logo-img-light-wrap stays hidden). */

/* Shown only while viewing an unedited, un-saved shared build (see
   index.html's own comment, js/planner.js loadFromHash/save) — an amber
   "you're looking at someone else's build" cue, not an error/warning
   color, since nothing is actually wrong: it just hasn't become one of
   your own builds yet. Restyled (user-requested 2026-09-07, same #e0a030
   kept exactly) — a flat 14%-tint fill read as thin/washed-out next to the
   topbar's own real buttons; a soft diagonal gradient plus a subtle outer
   glow and an inset top highlight give it real depth instead, still
   readably the same amber pill at a glance. */
.build-shared-status {
  display: flex; align-items: center; gap: 6px; flex-shrink: 0;
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.1px; color: #e0a030;
  background: linear-gradient(155deg, color-mix(in srgb, #e0a030 22%, var(--bg-elevated)), color-mix(in srgb, #e0a030 10%, var(--bg-elevated)));
  border: 1px solid color-mix(in srgb, #e0a030 48%, transparent);
  border-radius: 999px; padding: 5px 13px; white-space: nowrap;
  box-shadow: 0 1px 2px color-mix(in srgb, #e0a030 25%, transparent),
    0 0 0 1px color-mix(in srgb, #e0a030 12%, transparent),
    inset 0 1px 0 color-mix(in srgb, #e0a030 20%, transparent);
}

nav.tabs {
  display: flex;
  gap: 4px;
  flex: 0 0 auto;
}

nav.tabs button {
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-dim);
  padding: 5px 12px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 0.82rem;
}
nav.tabs button:hover { color: var(--text); background: var(--bg-elevated); }
nav.tabs button.active {
  color: var(--bg);
  background: var(--accent);
  font-weight: 600;
}

/* In-page jump links for the builder's sections, and the dropdown that
   replaced the old Base de données/Sorts&Classes/Sublimations tabs — see
   index.html's topbar and the scrollspy wiring in its own <script>. */
nav.quick-nav { display: flex; gap: 4px; flex: 1; overflow-x: auto; min-width: 0; }
.quick-nav-btn {
  background: transparent; border: 1px solid transparent; color: var(--text-dim);
  padding: 5px 12px; border-radius: var(--radius); cursor: pointer; font-size: 0.8rem; white-space: nowrap;
  transition: color 0.15s, background-color 0.15s;
}
.quick-nav-btn:hover { color: var(--text); background: var(--bg-elevated); }
.quick-nav-btn.active {
  color: var(--accent-2);
  background: color-mix(in srgb, var(--accent-2) 16%, transparent);
  border-color: color-mix(in srgb, var(--accent-2) 45%, transparent);
  font-weight: 600;
}

/* margin-left:auto removed (user-requested 2026-09-07) — .topbar-center-slot
   (its own flex:1, see below) already pushes this to the row's end on its
   own, so this doesn't also need to grab the leftover space itself. */
.topbar-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
/* The build-shared-status badge's own centering slot — see index.html's
   comment on .topbar-center-slot for why this exists instead of just
   letting the badge sit inline between .quick-nav and .topbar-right. */
.topbar-center-slot { flex: 1; display: flex; align-items: center; justify-content: center; min-width: 0; }
/* Bug-report/Paramètres header buttons — plain emoji glyphs replaced with
   themed inline SVGs (currentColor, so they follow the button's own
   text/hover color like every other icon in the app). The BUTTON itself
   stays at the row's own natural ~32px height (matching the "Autres vues"
   trigger next to it) — going taller than that was what pushed the whole
   topbar bar shorter/taller open in the first place. The icon is sized as
   large as that fixed box allows instead (28px, ~3px of breathing room on
   each side) rather than growing the button to fit a bigger icon. */
.topbar-icon-btn { width: 34px; height: 34px; padding: 0; display: flex; align-items: center; justify-content: center; position: relative; }
/* Cart item-count badge (js/shoppinglist.js's initCartBadge) — classic
   top-right notification-dot placement, hidden entirely (not just empty)
   while the list is empty via the [hidden] attribute. */
.shoppinglist-cart-badge {
  position: absolute; top: -4px; right: -4px; min-width: 15px; height: 15px; padding: 0 3px;
  border-radius: 999px; background: var(--danger); color: #fff; font-size: 0.62rem; font-weight: 800;
  display: flex; align-items: center; justify-content: center; line-height: 1;
  box-shadow: 0 0 0 2px var(--bg-panel);
}
/* 22px (was 28px) — user-requested (2026-09-07): shrunk to match the
   Discord icon's own 22px. */
.topbar-icon-btn svg { width: 22px; height: 22px; flex-shrink: 0; }
/* Discord icon (logo/discord-ico.png) is a plain white raster PNG — was a
   flat <img>, so it stayed white regardless of theme, unreadable on a
   light background (real bug, user-reported 2026-09-07, after Chaleureux/
   Doux made that a real scenario for the first time). Same mask-image
   recolor technique already used for rarete.png/entite.png elsewhere in
   this file: the PNG's own alpha shape becomes a mask over a plain
   currentColor fill, so it inherits .btn's own themed text color (and its
   :hover/.active accent tint) exactly like the inline-SVG icons beside it,
   instead of needing its own theme-aware image asset. */
.discord-icon-btn::before {
  content: ""; display: block; width: 22px; height: 22px; flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask-image: url(../logo/discord-ico.png); mask-image: url(../logo/discord-ico.png);
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
}
.topbar-icon-btn:hover { border-color: var(--accent); color: var(--accent); }
/* "Copier le nom..." topbar shortcut's own pressed/on state (user-requested
   2026-09-06) — same accent treatment other active toggles in this app use. */
.topbar-icon-btn.active { border-color: var(--accent); color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); }

/* Standalone "Builds publics" topbar button (user-requested 2026-09-06,
   moved out of "Autres vues" to sit directly on the bar) — same active
   treatment as .view-dropdown-trigger.active right below, so it reads
   consistently with the rest of the topbar when it's the current view. */
.topbar-right > .tab-btn.active { color: var(--bg); background: var(--accent); border-color: var(--accent); font-weight: 600; }

.view-dropdown { position: relative; }
.view-dropdown-trigger { display: flex; align-items: center; gap: 5px; white-space: nowrap; }
.view-dropdown-trigger.active { color: var(--bg); background: var(--accent); border-color: var(--accent); font-weight: 600; }
/* Both `.active` rules above tie the generic `button.btn:hover` rule
   (js/style.css further up) on specificity — two classes each — and that
   generic rule wins the tie-break (it also carries the `button` element),
   so hovering an already-active/selected button repainted its text back to
   var(--accent), i.e. the exact color of its own accent background:
   invisible text (user-reported 2026-09-07, screenshotted on the account
   modal's "Connexion" tab). These explicit :hover overrides — one extra
   class each — outrank it instead of touching the shared hover rule. */
.topbar-right > .tab-btn.active:hover, .view-dropdown-trigger.active:hover { color: var(--bg); }
/* Logged-in pseudo button (user-requested 2026-09-07) — was plain, styled
   identically to "Autres vues" right next to it, easy to miss as proof of
   being connected. A small glowing green "online" dot (::before, lines up
   for free via this trigger's own flex+gap) plus an accent-tinted
   border/background makes it read at a glance as "you", not just another
   menu button. */
#account-dropdown-trigger {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-elevated));
  color: var(--text); font-weight: 600;
}
#account-dropdown-trigger:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 22%, var(--bg-elevated)); }
#account-dropdown-trigger::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
  background: #4caf50; box-shadow: 0 0 5px #4caf50cc;
}
.view-dropdown-caret { font-size: 0.65em; transition: transform 0.15s; }
.view-dropdown.open .view-dropdown-caret { transform: rotate(180deg); }
.view-dropdown-menu {
  position: absolute; top: calc(100% + 6px); right: 0; min-width: 180px;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0,0,0,0.45); padding: 4px; display: none; flex-direction: column; gap: 2px; z-index: 60;
}
.view-dropdown.open .view-dropdown-menu { display: flex; }
.view-dropdown-menu .tab-btn { text-align: left; width: 100%; }
.view-dropdown-menu .tab-btn.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }

.lang-select {
  background: var(--bg-elevated);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 10px;
}

main { padding: 18px 20px 60px; max-width: 1500px; margin: 0 auto; }
/* While the item picker's open (see planner.js's openSlotPicker), the
   footer's hidden and this bottom padding is the only thing left the page
   could still be scrolled past — trimmed down so the picker's own bottom
   edge is effectively the end of the page. */
body.picker-open main { padding-bottom: 12px; }
.view { display: none; }
/* User-requested (2026-09-06): switching between Équipement/Base de
   données/Sorts&Classes/Sublimations was a hard display:none<->block swap,
   no transition possible on display itself — a short fade-in on activation
   reads as a fluid handoff instead of a hard cut. */
.view.active { display: block; animation: view-fade-in 0.18s ease; }
@keyframes view-fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ---------- generic ---------- */

.panel {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 14px 14px;
}

h2.section-title {
  margin: 0 0 10px;
  font-size: 1.05rem;
  color: var(--accent);
  font-weight: 600;
  line-height: 1.1;
}

input[type="text"], input[type="number"], input[type="email"], input[type="password"], select {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  padding: 7px 10px;
  font-family: inherit;
  font-size: 0.9rem;
}
input[type="text"]:focus, input[type="email"]:focus, input[type="password"]:focus, select:focus { outline: 1px solid var(--accent); }
/* Chrome/Edge paint their own light autofill background (a saved-login
   field like account-login-login/-password) straight over ours, ignoring
   `background` entirely — this is the standard workaround: an absurdly long
   background-color transition combined with a giant inset box-shadow in our
   own color effectively repaints over the browser's autofill tint instead of
   fighting it directly (user-reported 2026-09-07, seen on the Connexion
   field after the browser suggested a saved pseudo). */
input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--bg-elevated) inset;
  box-shadow: 0 0 0 1000px var(--bg-elevated) inset;
  transition: background-color 9999s ease-in-out 0s;
}
/* Hide native number-input spinner arrows: they eat width in compact
   fields (like the aptitude point boxes), cramping the digits — and we
   already have our own +/- buttons wherever it matters. */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

button.btn {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  padding: 7px 14px;
  cursor: pointer;
  font-size: 0.9rem;
  /* User-requested (2026-09-06): every button on the site snapped its
     hover/active colors instantly — this is the shared base class nearly
     every button uses, so one transition here smooths that out everywhere
     at once instead of chasing it button-by-button. */
  transition: border-color 0.15s, color 0.15s, background-color 0.15s, filter 0.15s, transform 0.08s;
}
/* Tactile press feedback (user-requested 2026-09-06: "réactif") — scoped to
   button.btn specifically so it doesn't fight unrelated transform uses on
   other elements (drag handles, item-card's own hover lift, etc). */
button.btn:active { transform: scale(0.96); }
/* Discord topbar link (user-requested 2026-09-07) — a real <a href> (right-
   click "ouvrir dans un nouvel onglet", middle-click, no JS needed to
   navigate), unlike every other topbar icon here which is an in-app
   <button>. .btn/.btn-icon/.topbar-icon-btn's own rules above are scoped
   to `button.btn` specifically, so this mirrors just what an anchor needs
   to render identically instead of broadening those selectors everywhere
   they're used. */
a.btn {
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text);
  border-radius: 6px; padding: 7px 14px; cursor: pointer; font-size: 0.9rem; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center;
  transition: border-color 0.15s, color 0.15s, background-color 0.15s, filter 0.15s, transform 0.08s;
}
a.btn:active { transform: scale(0.96); }
a.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn-icon { padding: 7px 10px; font-size: 1rem; line-height: 1; }

/* Global default element order for random mastery/resist bonuses — always
   visible next to the doll (not tucked behind a click-to-open button). */
.elem-priority-inline {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  /* Extra top/left padding — the numbered rank badge sits -5px outside its
     own button (top-left corner), so a flat 8px everywhere left it almost
     touching the container's edge/border on that side. */
  padding: 12px 8px 8px 12px; width: 144px; flex-shrink: 0;
}
.elem-priority-group { margin-bottom: 8px; }
.elem-priority-group:last-child { margin-bottom: 0; }
.elem-priority-group h5 { margin: 0 0 5px; font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); }
.elem-priority-row { display: flex; gap: 4px; }
.elem-priority-btn {
  position: relative; width: 26px; height: 26px; border-radius: 5px; padding: 0;
  border: 1px solid var(--border); background: var(--bg-elevated); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.elem-priority-btn:hover { border-color: var(--accent); }
.elem-priority-btn.dragging { opacity: 0.4; }
.elem-priority-btn { cursor: grab; }
.elem-priority-btn:active { cursor: grabbing; }
.elem-priority-btn img { width: 22px; height: 22px; object-fit: contain; }
.elem-priority-btn .rank {
  position: absolute; top: -6px; left: -6px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent); color: var(--accent-text); font-size: 0.62rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center; line-height: 1;
  border: 1.5px solid var(--bg-panel); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
.elem-priority-btn:first-child .rank { background: #e0c34f; color: #3a2c05; }
button.btn:hover { border-color: var(--accent); color: var(--accent); }
button.btn.primary { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }
button.btn.primary:hover { filter: brightness(1.08); }
button.btn.danger:hover { border-color: var(--danger); color: var(--danger); }

.pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  border: 1px solid currentColor;
}

/* ---------- Database view ---------- */

.db-layout {
  display: grid;
  grid-template-columns: 330px 1fr;
  gap: 16px;
  align-items: start;
}

/* Results scroll inside their own pane (matching the sticky filters column
   next to them) instead of growing the whole page — there's no pagination
   any more, every matching item renders at once, so without this the page
   itself would grow arbitrarily tall. */
#db-grid, #build-picker-grid {
  position: sticky; top: 62px;
  max-height: calc(100vh - 74px);
  overflow-y: auto;
  /* User-reported (2026-09-05): scrolling up inside this pane past its own
     top used to "spill over" into the outer page scroll instead of just
     stopping — standard scroll-chaining, fixed by containing the pane's own
     overscroll instead of letting it hand off to whatever's behind it. */
  overscroll-behavior: contain;
}
.picker-inner #build-picker-grid {
  /* +8px on both vs before — the sticky header grew by that much when its
     doll slots went from 46px to 54px (see .picker-sticky-header
     .doll-compact .slot). Kept in lockstep: shorter than the offset would
     strictly need, same reasoning as before — without the extra margin the
     grid gets squeezed above top and slides under the sticky header at max
     scroll. */
  top: 124px;
  max-height: calc(100vh - 158px);
}

.picker-wrap { display: flex; align-items: flex-start; gap: 16px; }
.picker-wrap .picker-inner { flex: 1; min-width: 0; }

/* Anchored to the actual browser window's left edge — outside main's own
   centered content box entirely — stacked directly below the Build panel
   (same width, top set in JS to Build's own measured bottom edge — see
   applyPickerStatsWidth) so the two fixed panels never overlap each other
   regardless of the page's scroll position (user-requested 2026-09-07,
   after a brief detour through normal/static flow that put the mini-stats
   card far down the page instead of in this left-hand column: "la
   mini-fiche de stats doit rester dans le container gauche de la page et
   descendre avec nous pendant le scroll" — reverted back to this). */
.picker-side-float {
  position: fixed; top: 50px; left: 12px; width: 210px; box-sizing: border-box;
  z-index: 16;
}
@media (max-width: 1300px) {
  .picker-side-float { display: none; }
}
/* Pinned-items tray — same floating technique as the mini-stats panel, just
   mirrored to the right side. No Build-sidebar to dodge over there, so
   (unlike the left one) its top offset is a plain fixed value, not JS-driven.
   Its `left` IS JS-driven though (applyPinnedPanelWidth) — positioned a
   fixed padding beyond the search grid's own right edge rather than
   anchored to the raw viewport with `right:12px`, so it stays visually
   attached to the content it relates to instead of drifting further away
   as the window gets wider. The grid's own width is never touched to make
   room — this only ever shows up in whatever real margin already exists. */
.picker-side-float-right { top: var(--builder-top, 65px); width: 215px; max-height: calc(100vh - 85px); overflow-y: auto; overscroll-behavior: contain; }
/* applyPinnedPanelWidth toggles this once it's measured there's genuinely
   not enough margin beyond the grid to fit the tray — hidden instead of
   overlapping it. */
.picker-side-float-right.no-room { display: none; }
.pinned-list { display: flex; flex-direction: column; gap: 6px; }
.pinned-card {
  display: flex; align-items: center; gap: 6px; position: relative;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 7px; padding: 5px 24px 5px 6px;
  cursor: default;
}
/* Self-contained instead of reusing .item-icon-frame — that class's own
   img.icon rule hardcodes `inset:6px` sized for its normal 48px box, which
   fought with a narrower width override here and rendered the icon cropped
   and offset inside the frame instead of filling it. */
/* No background — same reasoning as .item-icon-frame above (user-reported
   2026-09-06): the rarity frame has no backdrop of its own. */
.pinned-card-icon { position: relative; width: 30px; height: 30px; flex-shrink: 0; border-radius: 6px; }
.pinned-card-icon img.icon { position: absolute; inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); object-fit: contain; }
.pinned-card-icon img.frame { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; image-rendering: pixelated; }
.pinned-card-info { flex: 1; min-width: 0; }
.pinned-card-name { font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pinned-card-level { font-size: 0.7rem; color: var(--text-dim); }
/* User-requested (2026-09-05): the item-type picto, after a "•" separator
   on the same line as the level. No trailing text follows it here (unlike
   itemTypeLabelHtml's own icon+name usage elsewhere), so the shared
   .item-type-icon's own margin-right is dropped. */
.pinned-card-level .pinned-card-type-icon { margin-right: 0; margin-left: 2px; vertical-align: middle; }
.pinned-card-stats { display: flex; gap: 8px; margin-top: 2px; }
.pinned-card-stat { display: flex; align-items: center; gap: 2px; font-size: 0.7rem; }
.pinned-card-stat img { width: 13px; height: 13px; }
.pinned-card-remove {
  position: absolute; top: 3px; right: 3px; width: 16px; height: 16px; line-height: 14px; text-align: center;
  border-radius: 4px; border: 1px solid var(--border); background: var(--bg-panel); color: var(--text-dim);
  font-size: 0.7rem; padding: 0; cursor: pointer;
}
.pinned-card-remove:hover { color: var(--danger); border-color: var(--danger); }
.pinned-card-equip {
  position: absolute; bottom: 3px; right: 3px; width: 16px; height: 16px; line-height: 14px; text-align: center;
  border-radius: 4px; border: 1px solid var(--accent); background: var(--bg-panel); color: var(--accent);
  font-size: 0.7rem; padding: 0; cursor: pointer;
}
.pinned-card-equip:hover { background: var(--accent); color: var(--accent-text); }
/* A cheap visual cue on search-result cards so it's clear at a glance which
   ones are already in the comparison tray (right-click toggles this). */
.item-card.pinned { outline: 2px solid var(--accent-2); outline-offset: -1px; }
.mini-stats-wrap { box-sizing: border-box; padding: 10px 12px; }
.mini-stats-title { font-size: 0.78rem; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; margin: 7px 0 4px; }
.mini-stats-title:first-child { margin-top: 0; }
.mini-stats-title-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.info-btn-sm { width: 16px; height: 16px; border-radius: 4px; font-size: 0.62rem; flex-shrink: 0; }
.mini-stat-row { display: flex; align-items: center; gap: 6px; padding: 3px 2px; border-bottom: 1px solid #ffffff0a; font-size: 0.76rem; min-height: 25px; box-sizing: border-box; }
.mini-stat-row .stat-icon { width: 15px; height: 15px; flex-shrink: 0; }
.mini-stat-label { flex: 1; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mini-stat-value { font-weight: 700; color: var(--accent); flex-shrink: 0; }
/* A fixed 4-column grid (one cell per element) instead of a wrapping flex
   row: that let the row silently reflow between 1 and 2 lines depending on
   how much text a cell happened to hold (e.g. a hover diff appearing),
   which read as visually broken. Each cell now stacks its own diff/value/%
   vertically, so the row itself never reflows — only a cell's own height
   changes. */
.mini-elem-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px; padding: 3px 0 4px; border-bottom: 1px solid #ffffff0a; }
.mini-elem {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  font-size: 0.86rem; font-weight: 700; color: var(--accent); text-align: center; min-width: 0;
}
.mini-elem-main { display: flex; align-items: center; gap: 3px; white-space: nowrap; }
.mini-elem img { width: 16px; height: 16px; object-fit: contain; flex-shrink: 0; }
.mini-elem small { font-size: 0.7rem; font-weight: 600; color: var(--text-dim); white-space: nowrap; }
.mini-stat-row.clickable { cursor: pointer; border-radius: 5px; padding-left: 4px; margin-left: -4px; transition: opacity 0.12s, background 0.12s; }
.mini-stat-row.clickable:hover { background: #ffffff08; }
.mini-stat-row.clickable.mc-inactive { opacity: 0.45; }
.mini-stat-row.clickable.mc-active .mini-stat-label { color: var(--accent-2); }
.mini-elem.clickable { cursor: pointer; border-radius: 5px; transition: opacity 0.12s, background 0.12s; }
.mini-elem.clickable:hover { background: #ffffff08; }
.mini-elem.clickable.mc-inactive { opacity: 0.45; }
.mini-diff { font-weight: 700; font-size: 0.7rem; line-height: 1.3; }
.mini-diff.pos { color: #18c372; }
.mini-diff.neg { color: var(--danger); }
/* line-height: inherit — the 1.3 above grew the value's line box on hover and moved the number by 1px (user, 2026-10-24) */
.mini-stat-value .mini-diff { font-size: inherit; line-height: inherit; margin-right: 2px; }
.mini-elem .invisible { visibility: hidden; }

/* User-requested (2026-09-06): a visual break between the stat-presence
   filters (élémentaire/secondaire/Dénouement) and the favorites ones — two
   different kinds of filter, not obviously one continuous list. */
.filters-divider { width: 100%; border: none; border-top: 1px solid var(--border); margin: 0; flex-shrink: 0; }
.filters {
  display: flex; flex-direction: column; gap: 12px;
  position: sticky; top: 62px;
  max-height: calc(100vh - 74px);
  overflow-y: auto;
  overscroll-behavior: contain;
  z-index: 10;
}
/* Inside the item picker, the sticky slot-switcher row sits above the
   filters at the same top:62px — without its own offset the filters column
   (and its "Nom de l'objet" search field) sticks right underneath it and
   gets covered. Pushed down by the header's own height (62px, since its
   doll slots grew to 54px) instead. */
.picker-inner .filters {
  top: 124px;
  max-height: calc(100vh - 158px);
}
.filters label { font-size: 0.78rem; color: var(--text-dim); display: block; margin-bottom: 4px; }
.filters .field { display: flex; flex-direction: column; }
.filters select, .filters input { width: 100%; }
.f-text-wrap { position: relative; }
.f-text-wrap input { padding-right: 26px; }
.f-text-clear {
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  width: 18px; height: 18px; display: flex; align-items: center; justify-content: center;
  border: none; background: none; color: var(--text-dim); font-size: 1rem; line-height: 1;
  cursor: pointer; border-radius: 4px; padding: 0;
}
.f-text-clear:hover { color: var(--text); background: var(--bg-elevated); }
/* "Trier par" custom dropdown (js/ui.js sortDropdownHtml) — a real icon
   next to each option's label, which a native <select><option> can never
   render (browsers strip any markup inside <option>, text only). Same
   background/border chrome as the plain <select>/<input> rule above so it
   reads as one consistent control, not a bolted-on widget. */
.custom-select { position: relative; }
.custom-select-btn {
  width: 100%; display: flex; align-items: center; gap: 6px;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text);
  border-radius: 6px; padding: 7px 10px; font-family: inherit; font-size: 0.9rem;
  cursor: pointer; text-align: left;
}
.custom-select-btn:focus { outline: 1px solid var(--accent); }
.custom-select-btn span:not(.custom-select-caret) { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.custom-select-caret { color: var(--text-dim); font-size: 0.7rem; flex-shrink: 0; }
.custom-select-list {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 20;
  max-height: 280px; overflow-y: auto;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px;
  box-shadow: 0 4px 14px rgba(0,0,0,0.4); padding: 4px;
}
.custom-select-option {
  width: 100%; display: flex; align-items: center; gap: 6px;
  background: none; border: none; color: var(--text); border-radius: 4px;
  padding: 6px 8px; font-family: inherit; font-size: 0.85rem; text-align: left; cursor: pointer;
}
.custom-select-option:hover { background: var(--bg-hover, rgba(255,255,255,0.06)); }
.custom-select-option.active { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent); }
/* "Trier par" + "Ordre" on one row (user-requested 2026-09-05) — the sort
   dropdown takes the remaining space, the direction toggle is a fixed-width
   arrow button beside it instead of its own separate <select>/field. */
.sort-row { display: flex; align-items: center; gap: 6px; }
.sort-row .custom-select { flex: 1; min-width: 0; }
.f-dir-toggle {
  flex-shrink: 0; width: 34px; height: 33px; display: flex; align-items: center; justify-content: center;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text);
  border-radius: 6px; cursor: pointer; padding: 0;
}
.f-dir-toggle:hover { border-color: var(--accent); color: var(--accent); }
.f-dir-toggle svg { display: block; }
.f-dir-toggle.asc svg { transform: scaleY(-1); }
.stat-icon-spacer { display: inline-block; width: 18px; height: 18px; flex-shrink: 0; }
.level-range { display: flex; gap: 8px; align-items: center; }
.level-range input { width: 70px; }

.level-slider { position: relative; height: 26px; margin-top: 18px; }
.level-slider-track {
  position: absolute; top: 50%; left: 0; right: 0; height: 4px; transform: translateY(-50%);
  background: var(--border); border-radius: 2px;
}
.level-slider-fill { position: absolute; top: 0; bottom: 0; background: var(--accent); border-radius: 2px; }
.level-slider input[type="range"] {
  position: absolute; top: 0; left: 0; width: 100%; height: 26px; margin: 0;
  background: transparent; -webkit-appearance: none; appearance: none; pointer-events: none;
}
.level-slider input[type="range"]::-webkit-slider-runnable-track { background: transparent; height: 4px; }
.level-slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--accent-text); cursor: pointer; margin-top: -6px;
}
.level-slider input[type="range"]::-moz-range-thumb {
  pointer-events: auto; width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--accent-text); cursor: pointer;
}
.level-slider input[type="range"]::-moz-range-track { background: transparent; height: 4px; }
.level-slider-label { margin-top: 6px; font-size: 0.78rem; color: var(--text-dim); text-align: center; }
.level-slider-tag {
  position: absolute; top: -16px; transform: translateX(-50%);
  font-size: 0.7rem; font-weight: 600; color: var(--accent);
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 4px;
  padding: 1px 4px; pointer-events: none; white-space: nowrap;
}

#build-level {
  -webkit-appearance: none; appearance: none; width: 100%; height: 20px; margin: 6px 0 0;
  background: transparent; cursor: pointer;
}
#build-level::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: var(--border); }
#build-level::-webkit-slider-thumb {
  -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--accent-text); cursor: pointer; margin-top: -6px;
}
#build-level::-moz-range-track { height: 4px; border-radius: 2px; background: var(--border); }
#build-level::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--accent-text); cursor: pointer;
}

.apt-code-row { display: flex; align-items: center; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.apt-code-row label { font-size: 0.78rem; color: var(--text-dim); white-space: nowrap; }
.apt-code-row input { flex: 1; min-width: 160px; font-family: monospace; font-size: 0.8rem; }

.icon-toggle-group { display: flex; flex-wrap: wrap; gap: 6px; }
.icon-toggle {
  width: 40px; height: 40px; padding: 0;
  background: var(--bg-elevated); border: 1.5px solid var(--border); border-radius: 8px;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: border-color 0.15s, background-color 0.15s;
}
.icon-toggle img { width: 24px; height: 24px; object-fit: contain; }
.icon-toggle:hover { border-color: var(--accent); }
.icon-toggle.active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 13%, transparent); box-shadow: 0 0 0 1px var(--accent) inset; }
.icon-toggle-group.malus .icon-toggle.active { border-color: var(--danger); background: #e04c4c22; box-shadow: 0 0 0 1px var(--danger) inset; }
/* User-requested (2026-09-06): "Bonus"/"Malus" use the exact same icon set,
   hard to tell apart at a glance — a faint blue/red tint on each icon's own
   background (not just active ones) reads as "which section am I looking
   at" before even reading the label above it. */
.icon-toggle-group[data-toggle-group="majeur"] .icon-toggle:not(.active) { background: color-mix(in srgb, #4f8fd6 14%, var(--bg-elevated)); }
.icon-toggle-group[data-toggle-group="malus"] .icon-toggle:not(.active) { background: color-mix(in srgb, #e04c4c 14%, var(--bg-elevated)); }
.icon-toggle-group[data-toggle-group="rarity"] { flex-wrap: nowrap; gap: 4px; }
/* Type filter picker (js/ui.js typeIconGridHtml) — same .icon-toggle
   buttons as Rareté/Résistances/Maîtrises above, single-select instead of
   multi. Fixed 5×3 grid (user-requested 2026-09-05) instead of the shared
   group's own flex-wrap flow — 15 buttons (Tous + 14 real categories) fill
   it exactly, so auto-flowing rows needs no explicit row count. */
/* justify-content centers the fixed-width 5-column block within the
   grid's own box — .field (flex column) stretches that box to the full
   sidebar width by default, so without this the grid itself sat flush
   left inside it (user-requested 2026-09-05: center it). */
.type-icon-grid { display: grid; grid-template-columns: repeat(5, 40px); gap: 6px; justify-content: center; }
.type-icon-btn { font-size: 0.62rem; font-weight: 700; color: var(--text-dim); line-height: 1.1; }
.type-icon-btn.active { color: var(--accent); }
/* Real rarity gem picto (logo/rarete.png, user-provided) instead of a
   plain CSS-drawn diamond. Two layers (user-reported 2026-09-05, replacing
   a flat mask-image fill that erased the gem's own facet linework — see
   rarityToggleBtn's own comment in js/ui.js): the real artwork underneath
   at full detail, a mix-blend-mode: color tint on top recoloring hue only. */
/* No saturate() filter here any more (tried 1.5, then 1.25, on 2026-09-05
   to fix Épique/Relique reading too close together) — replaced the same
   day by the real fix, .rarity-toggle-base's own contrast/brightness
   correction below, which measurably restores the true hex instead of
   just boosting saturation blindly. Re-adding a saturate() filter on top
   of THAT fix overshoots (verified: pushes blue +20 over target) since
   there's no more washed-out color left to compensate for. */
.rarity-toggle { position: relative; width: 32px; height: 32px; flex-shrink: 0; }
/* Two classes (not the bare element type) so this reliably beats the
   generic .icon-toggle img { width: 24px; height: 24px } rule regardless
   of source order — same specificity trick used elsewhere in this file. */
.rarity-toggle .rarity-toggle-base, .rarity-toggle .rarity-toggle-tint {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 20px; height: 20px; pointer-events: none;
}
/* contrast(0.7) — user-reported (2026-09-05): the tint didn't read as the
   real rarity colors. Measured why (sampling the actual rendered pixels
   via a canvas 'color' composite matching mix-blend-mode: color exactly):
   that blend mode pins the RESULT's luminance to whatever the base
   artwork's own pixel luminance already is at that spot, so its near-black
   shadow and near-white highlight areas both blend toward black/white
   almost regardless of the tint color — the average rendered color drifts
   from the true hex. Compressing the base image's own tonal range toward
   mid-gray first keeps every pixel closer to a luminance the blend can
   actually render in the target's true hue. First attempt used
   contrast(0.35), accurate but squashed the shading flat enough that two
   other rarities read as a uniform blob instead of a faceted gem — 0.7
   keeps roughly twice the tonal range while still landing close.
   brightness comes from --rarity-brightness (js/ui.js RARITY_TINT_
   BRIGHTNESS) instead of one fixed value here — every rarity's own hue
   needed a DIFFERENT brightness to land on its real average (0.46-1.18
   across the 8, measured the same way): fixing whichever rarity had just
   been reported kept measurably worsening at least one other under a
   single shared value. Contrast is a var() too (--rarity-contrast,
   defaulting to this file's 0.7) for the same reason — Épique specifically
   still read as too flat a color fill even at 0.7 (facet linework not
   visible), so js/ui.js's RARITY_TINT_CONTRAST overrides it back to 1 (no
   compression) for that one rarity only, with its own brightness re-tuned
   to match. */
.rarity-toggle-base { object-fit: contain; filter: contrast(var(--rarity-contrast, 0.7)) brightness(var(--rarity-brightness, 1)); }
.rarity-toggle-tint {
  background: var(--rarity-color); mix-blend-mode: color; opacity: 0.9;
  -webkit-mask-image: url(../logo/rarete.png); mask-image: url(../logo/rarete.png);
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  filter: drop-shadow(0 0 5px color-mix(in srgb, var(--rarity-color) 65%, transparent));
}
.rarity-toggle.active { border-color: var(--rarity-color); box-shadow: 0 0 0 1px var(--rarity-color) inset; }
.rarity-toggle.active .rarity-toggle-tint { filter: drop-shadow(0 0 8px color-mix(in srgb, var(--rarity-color) 85%, transparent)); }

.text-toggle-group { display: flex; flex-wrap: wrap; gap: 6px; }
/* User-requested (2026-09-05): the 4 "Maîtrises élémentaires présentes"
   options (1/2/3/toutes) always fit on one line instead of wrapping —
   narrower padding/gap and capped icon size so even the 4-icon "any"
   button stays inside the filters column's own width. */
.elem-count-group { flex-wrap: nowrap; gap: 4px; }
.elem-count-toggle { padding: 6px 6px; display: inline-flex; gap: 2px; }
.elem-count-toggle img { width: 16.2px; height: 16.2px; }
.text-toggle {
  padding: 6px 10px; background: var(--bg-elevated); border: 1.5px solid var(--border); border-radius: 8px;
  color: var(--text-dim); cursor: pointer; font-size: 0.78rem;
}
.text-toggle:hover { border-color: var(--accent); color: var(--text); }
.text-toggle.active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 13%, transparent); color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }

.yesno-toggle { display: flex; gap: 6px; }
.yesno-toggle button {
  flex: 1; padding: 6px 0; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px;
  color: var(--text-dim); cursor: pointer; font-size: 0.8rem;
}
.yesno-toggle button.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }
/* Label + Oui/Non on one row instead of stacked — user-requested
   (2026-09-06): the picker's filter column has 5 of these in a row
   (élémentaire/secondaire/Dénouement/favoris/favoris en priorité), each
   taking a full label-then-buttons block worth of vertical space for what's
   really a single yes/no choice. :has() targets any .field built around a
   yesno-toggle without needing a new class on each of the 5 HTML blocks. */
.filters .field:has(> .yesno-toggle) { flex-direction: row; align-items: center; justify-content: space-between; gap: 10px; }
.filters .field:has(> .yesno-toggle) > label { margin-bottom: 0; flex: 1; min-width: 0; }
.filters .field:has(> .yesno-toggle) > .yesno-toggle { flex-shrink: 0; width: 108px; }

.result-count { color: var(--text-dim); font-size: 0.85rem; margin-bottom: 10px; }

.item-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 10px;
}

.item-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--rarity-color, var(--border));
  border-radius: var(--radius);
  padding: 10px;
  display: flex;
  gap: 10px;
  cursor: pointer;
  position: relative;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  transition: transform 0.08s, border-color 0.08s, box-shadow 0.08s;
}
.item-card:hover { border-color: var(--accent); border-left-color: var(--rarity-color, var(--accent)); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.35); }
/* Keyboard focus (arrow-key navigation between results, see ui.js
   createBrowser) — same visual treatment as :hover so keyboard and mouse
   users get an identical "this is the selected one" cue, plus a themed
   outline since this is now a real focusable element (tabindex="0"). */
.item-card:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
  border-color: var(--accent); border-left-color: var(--rarity-color, var(--accent));
  transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.35);
}

/* Stats-mode cards stack vertically instead: a name+icon header row (icon
   at the right, out of the way), then the effect list gets the card's full
   width below instead of being squeezed into a column next to the icon. */
.item-card.with-stats { flex-direction: column; }
.card-header { display: flex; gap: 10px; align-items: flex-start; }
.card-header .info { min-width: 0; flex: 1; }

/* Épinglé/Favoris stack — the 2nd column of a stats-mode card's header,
   next to the name block. Labeled pills rather than icon-only badges so
   each one's purpose (and current on/off state) reads at a glance, without
   needing to already know the app's own icon conventions. */
/* Centered (not the header row's default flex-start) so the pin/fav stack
   sits level with the icon+name column instead of hugging the top. */
.card-actions { display: flex; flex-direction: column; gap: 4px; flex-shrink: 0; align-self: center; }
.card-action-pill {
  font: inherit; font-size: 0.7rem; font-weight: 700; padding: 3px 10px; border-radius: 5px;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim);
  cursor: pointer; white-space: nowrap; text-align: center;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
/* Reserves its slot (so Favoris below it stays the same stretched width)
   but stays invisible — and non-interactive, visibility:hidden drops it
   from hit-testing — until the item is actually pinned (via right-click on
   the card). Once pinned: filled solid instead of just outlined, same
   light-bg/dark-icon flip as the favorite star's own active state. */
.card-action-pill.pin {
  visibility: hidden; border-color: var(--danger); color: var(--danger);
  display: inline-flex; align-items: center; justify-content: center;
  /* Padding trimmed to offset the bigger icon below — keeps the button's
     own footprint (not just its width/height) unchanged. */
  padding: 1.5px 8.5px;
}
.card-action-pill.pin svg { width: 14px; height: 14px; }
.card-action-pill.pin.active { visibility: visible; background: var(--danger); color: #2a1113; }
.card-action-pill.fav {
  font-size: 0.85rem; line-height: 1; padding: 1.5px 8.5px;
  display: inline-flex; align-items: center; justify-content: center;
}
.card-action-pill.fav:hover, .card-action-pill.fav.active { background: #e0c34f; border-color: #e0c34f; color: #17130a; }
/* Recipe pill (stats mode) — always visible when the item actually has a
   known recipe (unlike .pin/.equip's hidden-until-active pattern, this has
   nothing to toggle, it's a pure hover-for-info button), a bronze/amber
   "craft" color distinct from the purple accent used elsewhere. */
.card-action-pill.recipe {
  border-color: #c98a3e; color: #c98a3e; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 1.5px 8.5px; cursor: help;
}
.card-action-pill.recipe:hover { background: #c98a3e; color: #221604; }
/* Same hidden-until-active treatment as the pin pill (see .pin above) —
   equipping already happens by clicking the card itself, so this is purely
   a status readout, reserved-but-invisible until this item is actually the
   one in the slot. Width matches Épinglé/Favoris exactly (33px) rather than
   sizing to its own content. */
.card-action-pill.equip {
  visibility: hidden; border-color: #6fb3e0; color: #6fb3e0; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 33px;
  /* Matches .item-card-badge's own 2px vertical padding, so it sits at the
     same height as the mastery/resist badges on the same row. */
  padding-top: 2px; padding-bottom: 2px;
}
.card-action-pill.equip.active { visibility: visible; background: #6fb3e0; color: #0d1017; }
.equip-slot-chip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; border-radius: 4px; flex-shrink: 0;
  background: rgba(0,0,0,0.25); font-size: 0.62rem; font-weight: 800;
}

/* Mastery/resistance totals and the Équiper action all read on one line —
   stats-mode only; non-stats cards keep the older absolute top-right corner
   stack (see itemCardHtml's legacyControlsHtml). */
/* .item-card is a flex column (with-stats mode) with its own 10px `gap`
   between every direct child — margin:0 alone doesn't remove that, so this
   pulls the row back in by exactly that much on both sides. */
/* margin-top only -6px (not the full -10px gap) so Équiper sits the same
   ~4px below Favoris as Favoris sits below Épinglé, instead of touching it
   directly — margin-bottom stays at -10px, fully closing the gap to the
   effect list below. */
.card-stat-row { display: flex; align-items: center; gap: 6px; margin: -6px 0 -10px; }
.card-stat-row .item-card-badges { margin: 0; }
.card-stat-row .card-action-pill.equip { margin-left: auto; }
.item-card.with-stats .item-card-badges { position: static; flex-direction: row; align-items: center; }
/* Tooltip's own badges row (js/ui.js's tooltipHtml) — rendered as a
   standalone row below the whole .tt-head block (icon + name + border
   accent), not nested inside it, so it reads as clearly separate from the
   item's icon frame instead of the card's default absolute top-right
   corner placement. */
.item-tooltip .item-card-badges { position: static; flex-direction: row; align-items: center; margin-bottom: 8px; }
.item-card-badges { position: absolute; top: 6px; right: 6px; display: flex; flex-direction: column; gap: 3px; align-items: flex-end; }
.item-card-badge {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 0.7rem; font-weight: 700; padding: 2px 6px; border-radius: 5px;
  background: var(--bg-elevated); border: 1px solid var(--border);
}
.item-card-badge img { width: 12px; height: 12px; object-fit: contain; flex-shrink: 0; }
.item-card-badge.mastery { color: var(--accent); border-color: var(--accent); }
.item-card-badge.resist { color: #6fb3e0; border-color: #6fb3e0; }
/* User-requested (2026-09-05): per-item "Poids" badge, same shape as
   mastery/resist above but its own color (gold — matches "value/power
   score" better than reusing an already-taken hue). */
.item-card-badge.weight { color: #d4af37; border-color: #d4af37; }
/* Real HDV price badge (data/prices.json, see js/app.js's loadPrices — a
   static 2026-09-16 snapshot, only ~1100 items) — same gold as .weight
   since both read as "value", kamas icon instead of a stat icon. */
.item-card-badge.price { color: #d4af37; border-color: #d4af37; }
/* A plain colored outline alone doesn't self-explain "pinned" to someone
   who hasn't already learned that convention — this small badge names it
   directly. Stacks inside .item-card-badges now (same badge shape as the
   mastery/resist ones, first in the column) instead of its own separate
   corner overlay, only ever present at all while the card is pinned. */
.item-card-badge.item-card-pin-badge { color: var(--accent-2); border-color: var(--accent-2); font-weight: 700; }

/* The item icon sits top-left in every mode now (see itemCardHtml), so
   bottom-right is the one corner that's never underneath it regardless of
   how short the card is (e.g. a 0-effect item leaves barely more than the
   header's own height). */
.fav-star-btn {
  position: absolute; bottom: 6px; right: 6px; z-index: 2;
  width: 26px; height: 26px; padding: 0; border-radius: 50%;
  background: var(--bg-elevated); border: 2px solid var(--border); color: var(--text-dim);
  font-size: 1.05rem; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 1px 4px rgba(0,0,0,0.4);
  transition: background 0.12s, color 0.12s, border-color 0.12s, transform 0.12s;
}
.fav-star-btn:hover { border-color: #e0c34f; color: #e0c34f; transform: scale(1.08); }
.fav-star-btn.active { color: #17130a; background: #e0c34f; border-color: #e0c34f; box-shadow: 0 0 8px #e0c34f66, 0 1px 4px rgba(0,0,0,0.4); }
/* Legacy/tooltip-mode recipe button — same circular corner treatment as
   .fav-star-btn just above, offset to its left so the two sit side by side
   instead of overlapping (both anchor bottom-right of the card). */
.recipe-corner-btn {
  position: absolute; bottom: 6px; right: 38px; z-index: 2;
  width: 26px; height: 26px; padding: 0; border-radius: 50%;
  background: var(--bg-elevated); border: 2px solid #c98a3e; color: #c98a3e;
  cursor: help; display: flex; align-items: center; justify-content: center;
  box-shadow: 0 1px 4px rgba(0,0,0,0.4);
  transition: background 0.12s, color 0.12s, transform 0.12s;
}
.recipe-corner-btn:hover { background: #c98a3e; color: #221604; transform: scale(1.08); }

.item-icon {
  width: 48px;
  height: 48px;
  border-radius: 6px;
  background: var(--bg-elevated);
  flex-shrink: 0;
  object-fit: contain;
  border: 1px solid var(--border);
}

/* Item icon + rarity frame texture overlay (textures/{rarity}.png) — user-
   reported (2026-09-06), same root cause as the doll's own .slot.filled
   fix: the rarity frame is a thin border with a transparent center, never
   its own backdrop, so this class's flat background fill showed through
   around every icon's real (non-square) silhouette as an unrelated dark
   box. Only the icon + frame should be visible. */
.item-icon-frame { position: relative; z-index: 0; width: 48px; height: 48px; flex-shrink: 0; border-radius: 6px; }
.item-icon-frame img.icon { position: absolute; inset: 6px; width: calc(100% - 12px); height: calc(100% - 12px); object-fit: contain; }
.item-icon-frame img.frame { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; image-rendering: pixelated; }
.item-icon-frame.lg { width: 64px; height: 64px; }
.item-icon-frame.sm { width: 36px; height: 36px; }

.item-card .info { min-width: 0; flex: 1; }
.item-card .name { font-weight: 600; font-size: 0.88rem; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.item-card .sub { font-size: 0.75rem; color: var(--text-dim); line-height: 1.2; margin-top: 0; }
.item-type-icon { width: 13px; height: 13px; object-fit: contain; vertical-align: -2px; margin-right: 3px; }
.item-card.with-stats { grid-column: span 1; }
.item-grid.has-stats-cards { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.effect-list.card-effect-list { margin: 0; }
.card-effect-list li { padding: 2px 6px; font-size: 0.78rem; }
.card-effect-list .stat-icon { width: 19px; height: 19px; }
/* Short label swapped in only on mobile with "cartes compactes" on (see
   body.compact-cards, <=767px block) — full names everywhere else,
   including this same list rendered inside a tooltip or the detail modal. */
.stat-label-short { display: none; }

.item-condition {
  margin-top: 6px; padding: 4px 8px; border-radius: 6px;
  background: #e8a23722; border: 1px solid #e8a23755; color: #e8a237; font-size: 0.78rem; font-weight: 600;
}
.item-condition-sm { font-size: 0.7rem; padding: 3px 6px; margin: 4px 0 0; }
/* Full-width now (a direct .item-card child, not nested in .info) so it can
   wrap under the icon too instead of being squeezed into the name column's
   width. .item-card's own flex `gap:10px` would otherwise open a 14px gap
   above it (10 + this rule's own 4px) — pulled back to the same ~4px used
   between every other stacked bit of this card. */
.item-card.with-stats .item-condition-sm { margin-top: -6px; }

.item-card.copied { border-color: var(--accent-2); }

/* Spell recharge/max-per-turn/max-per-target chips (js/classes.js's
   spellConditionsHtml) — a dedicated "info" tone distinct from
   .item-condition's amber (that one flags a gating requirement on an
   ITEM; this is just a neutral combat-mechanic fact about a spell), and
   one pill per condition rather than one dense sentence so each stays
   readable even with 2-3 stacked on the same spell. */
.spell-conditions { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.spell-condition-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 8px; border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 18%, var(--bg-elevated));
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--text); font-size: 0.78rem; font-weight: 600; line-height: 1.3;
}
.item-card.copied .name::after {
  content: "✓ Copié"; margin-left: 8px; color: var(--accent-2); font-size: 0.78rem; font-weight: 700;
}

/* ---------- Hover tooltip ---------- */

.item-tooltip {
  position: fixed; z-index: 600; pointer-events: none;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px; width: 260px; max-height: min(520px, calc(100vh - 20px)); overflow-y: auto; overflow-x: hidden;
  box-shadow: 0 8px 24px rgba(0,0,0,0.45);
  opacity: 0; visibility: hidden; transition: opacity 0.08s;
}
.item-tooltip.active { opacity: 1; visibility: visible; }
/* Tap-triggered replacement for the native title tooltip on compact-mode
   aptitude stat icons (see aptitudes.js showStatTooltip) — small enough to
   just be a name label. left/top are set directly in JS (clamped to the
   viewport) rather than via a centering transform, so it never runs off the
   edge of the page for a stat row near the left/right border. */
.apt-icon-tooltip {
  position: fixed; z-index: 600; pointer-events: none;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 6px;
  padding: 4px 8px; font-size: 0.78rem; font-weight: 600; white-space: nowrap;
  box-shadow: 0 4px 14px rgba(0,0,0,0.4);
  opacity: 0; visibility: hidden; transition: opacity 0.1s;
}
.apt-icon-tooltip.active { opacity: 1; visibility: visible; }
.item-tooltip .tt-head { border-left: 3px solid; padding-left: 8px; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }
.shard-tooltip { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; font-weight: 600; }
.shard-tooltip .stat-icon { width: 22px; height: 22px; }
.item-tooltip .tt-name { font-weight: 700; font-size: 0.88rem; }
.item-tooltip .tt-sub { font-size: 0.78rem; }
.tt-sub-level { font-size: 0.78rem; font-weight: 600; color: var(--accent-2); }
.tt-type-icon { width: 24px; height: 24px; object-fit: contain; flex-shrink: 0; }
.item-tooltip .effect-list li { padding: 3px 6px; font-size: 0.78rem; }

/* Recipe tooltip (js/ui.js's recipeTooltipHtml) — same list shape as the
   item tooltip's own .effect-list, one row per ingredient. */
.recipe-ingredient-list { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.recipe-ingredient-row {
  display: flex; align-items: center; gap: 6px; font-size: 0.78rem;
  padding: 3px 6px; background: var(--bg-elevated); border-radius: 5px;
  cursor: pointer; border: 1px solid transparent;
}
.recipe-ingredient-row.copied { border-color: var(--accent-2); }
.recipe-ingredient-icon { position: relative; width: 26px; height: 26px; flex-shrink: 0; }
.recipe-ingredient-icon .icon { position: absolute; inset: 2px; width: calc(100% - 4px); height: calc(100% - 4px); object-fit: contain; border-radius: 3px; }
.recipe-ingredient-icon .frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
.recipe-ingredient-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recipe-ingredient-qty { color: #c98a3e; font-weight: 700; flex-shrink: 0; }
.recipe-extra-note { margin-top: 6px; font-size: 0.78rem; color: var(--text-dim); font-style: italic; }
.recipe-plan-note {
  display: flex; align-items: center; gap: 5px; margin-top: 6px; padding: 4px 7px;
  background: color-mix(in srgb, #c98a3e 16%, var(--bg-elevated)); border-radius: 5px;
  font-size: 0.78rem; color: #d9a45e; font-weight: 600;
}
.recipe-plan-note svg { flex-shrink: 0; }
/* Other rarity variants of the same craftable item (e.g. "La Huccino" at
   both Rare and Mythique), shown in the recipe tooltip so a big outlier
   sell price on one variant is easy to sanity-check against its siblings
   at a glance — user-requested (2026-09-17). */
.recipe-variants { margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--border); }
.recipe-variants-label { font-size: 0.7rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; }
.recipe-variant-row { display: flex; align-items: center; gap: 6px; font-size: 0.76rem; padding: 2px 0; }
.recipe-variant-icon { position: relative; width: 22px; height: 22px; flex-shrink: 0; }
.recipe-variant-icon .icon { position: absolute; inset: 2px; width: calc(100% - 4px); height: calc(100% - 4px); object-fit: contain; border-radius: 3px; }
.recipe-variant-icon .frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
.recipe-variant-level { color: var(--text-dim); flex-shrink: 0; }
.recipe-variant-sell { color: #d4af37; font-weight: 600; }
.recipe-variant-cost { color: #c98a3e; margin-left: auto; }

/* ---------- Liste de courses (js/shoppinglist.js) ---------- */

/* 4 columns: classic equipment filter sidebar, its results grid, the
   wanted-recipes list, then the aggregated Courses total — same "filters |
   results" pairing as db-layout (js/database.js), with the shopping-list-
   specific columns appended after (2026-09-16, user: "ajoute les filtres
   équipement, tranches etc avec les filtres classiques"). */
.shoppinglist-layout { display: grid; grid-template-columns: 260px 1.3fr 1fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 1100px) { .shoppinglist-layout { grid-template-columns: 1fr 1fr; } }
@media (max-width: 700px) { .shoppinglist-layout { grid-template-columns: 1fr; } }
.shoppinglist-col { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 10px; padding: 12px; min-height: 120px; }
/* The classic filter sidebar (#shoppinglist-filters, WUI.createBrowser's
   own filterBar target) already renders its own .panel.filters box exactly
   like db-filters does — this column is just its grid cell, no second
   nested panel background/border on top of it. */
.shoppinglist-filters-col { background: none; border: none; padding: 0; min-height: 0; }
.shoppinglist-results-col { background: none; border: none; padding: 0; min-height: 0; }
.shoppinglist-col h3 { margin: 0 0 10px; font-size: 0.95rem; color: var(--accent-2); }
.shoppinglist-col input[type="text"] { width: 100%; margin-bottom: 10px; }
.shoppinglist-hint { font-size: 0.8rem; color: var(--text-dim); padding: 8px 2px; }

.shoppinglist-icon { position: relative; width: 30px; height: 30px; flex-shrink: 0; }
.shoppinglist-icon .icon { position: absolute; inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); object-fit: contain; border-radius: 4px; }
.shoppinglist-icon .frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }

.shoppinglist-recipe-row, .shoppinglist-course-row {
  display: flex; align-items: center; gap: 8px; padding: 5px 6px; border-radius: 6px;
  background: var(--bg-elevated); margin-bottom: 4px; font-size: 0.82rem;
  transition: border-color 0.15s;
  border: 1px solid transparent;
}
.shoppinglist-search-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shoppinglist-search-level { color: var(--text-dim); font-size: 0.78rem; flex-shrink: 0; }

.shoppinglist-qty-stepper { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
.shoppinglist-qty-stepper button {
  width: 20px; height: 20px; border-radius: 4px; border: 1px solid var(--border);
  background: var(--bg-panel); color: var(--text); cursor: pointer; font-weight: 700; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.shoppinglist-qty-stepper button:hover { border-color: var(--accent); color: var(--accent); }
.shoppinglist-qty-stepper span { min-width: 18px; text-align: center; font-weight: 700; }
.shoppinglist-remove {
  flex-shrink: 0; width: 20px; height: 20px; border-radius: 4px; border: 1px solid var(--border);
  background: var(--bg-panel); color: var(--text-dim); cursor: pointer; line-height: 1; padding: 0;
  display: flex; align-items: center; justify-content: center; font-size: 0.7rem;
}
.shoppinglist-remove:hover { border-color: var(--danger); color: var(--danger); }

.shoppinglist-course-row { cursor: default; }
.shoppinglist-course-row[data-copy-name] { cursor: pointer; }
.shoppinglist-course-row.copied { border-color: var(--accent-2); }
.shoppinglist-course-qty { color: #c98a3e; font-weight: 700; flex-shrink: 0; }
/* Real Hôtel de Vente price per row (js/app.js's data/prices.json) — gold,
   distinct from the bronze quantity, matches this project's existing
   kamas-value convention elsewhere (e.g. .item-card-badge.weight's gold). */
.shoppinglist-course-price { color: #d4af37; font-weight: 700; flex-shrink: 0; font-size: 0.78rem; white-space: nowrap; }
.shoppinglist-course-total {
  display: flex; align-items: center; gap: 8px; margin-top: 8px; padding: 8px 6px;
  border-top: 1px solid var(--border); font-size: 0.85rem;
}
.shoppinglist-course-total-value { color: #d4af37; font-weight: 800; margin-left: auto; }
.shoppinglist-course-total-missing { color: var(--text-dim); font-size: 0.78rem; font-style: italic; }
/* Toggled ON (expanded into its own sub-ingredients instead of counted as
   a raw need) reads as a distinct sub-state of the row — left accent bar,
   same bronze "craft" color as the recipe tooltip's own qty numbers. */
.shoppinglist-course-row.expanded { border-left: 3px solid #c98a3e; padding-left: 4px; }
.shoppinglist-expand-toggle {
  flex-shrink: 0; width: 22px; height: 22px; border-radius: 5px; border: 1px solid var(--border);
  background: var(--bg-panel); color: var(--text-dim); cursor: pointer; font-size: 0.85rem; line-height: 1;
}
.shoppinglist-expand-toggle:hover { border-color: #c98a3e; color: #c98a3e; }
.shoppinglist-expand-toggle.active { background: #c98a3e; border-color: #c98a3e; color: #221604; }
/* Checked (bought/already owned) — user-requested (2026-09-16): "une
   option pour valider les ressources... pour les compos acheté/déja
   possédés". Dims + strikes the row so a partly-gathered list stays
   scannable; the checkbox itself is a small square, not a native
   <input type="checkbox"> (this codebase's own pattern for every other
   toggle pill — pin/fav/equip — is a styled <button>, not a native input). */
.shoppinglist-check-toggle {
  flex-shrink: 0; width: 18px; height: 18px; border-radius: 4px; border: 1.5px solid var(--border);
  background: var(--bg-panel); color: #17130a; cursor: pointer; font-size: 0.7rem; font-weight: 800; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.shoppinglist-check-toggle:hover { border-color: #6fce7a; }
.shoppinglist-check-toggle.active { background: #6fce7a; border-color: #6fce7a; }
.shoppinglist-course-row.checked { opacity: 0.5; }
.shoppinglist-course-row.checked .shoppinglist-search-name { text-decoration: line-through; }

/* ---------- Rentabilité craft (js/craftprofit.js) ---------- */

/* Same "filters | results" pairing as .db-layout (js/database.js) — a
   fixed sticky sidebar (#craftprofit-filters reuses the shared .panel.filters
   look, vertical .field stack, same as the database/build-picker filter
   bars) next to the results column, instead of a wide horizontal filter bar
   above a full-width table (2026-09-17 bug report: the two ended up side by
   side but unconstrained, table eating ~70% and filters squeezed left with
   no defined column width). */
.craftprofit-layout { display: grid; grid-template-columns: 260px 1fr; gap: 16px; align-items: start; }
.craftprofit-main { min-width: 0; }
.craftprofit-filter-checkbox { display: flex; align-items: center; gap: 6px; font-size: 0.82rem; color: var(--text-dim); cursor: pointer; }
.field-inline-checkbox { margin-top: 4px; }
.craftprofit-range-fields { display: flex; align-items: center; gap: 6px; }
/* This sidebar is narrower (260px) than "Base de données"'s own filter
   column (330px, where .icon-toggle-group[data-toggle-group="rarity"]'s
   global flex-wrap:nowrap already fits 6 pictos comfortably) — at 260px
   that same nowrap row overflowed its container and forced a horizontal
   scrollbar onto the whole sidebar (2026-09-17 bug report: "retire la
   barre de scrolling en bas"). Wrap onto 2 lines here instead, scoped to
   this narrower sidebar only — the global rule elsewhere is untouched.
   overflow-x:hidden is a defensive backstop against any other stray
   overflow in this column, not the actual fix. */
#craftprofit-filters { overflow-x: hidden; }
#craftprofit-rarity-group { flex-wrap: wrap; }
.craftprofit-range-fields input { width: 0; flex: 1; min-width: 0; }
.craftprofit-range-fields span { color: var(--text-dim); flex-shrink: 0; }

.craftprofit-summary { font-size: 0.78rem; color: var(--text-dim); margin-bottom: 10px; }
/* Visible "prices last updated" label — user-requested (2026-09-18):
   "affiche date/heure de mise a jour des prix". Shared class, used both
   in the craftprofit hint text and next to "Liste de courses"' own
   "Courses" header (js/shoppinglist.js). */
.craftprofit-prices-date { color: #d4af37; font-weight: 600; font-size: 0.85em; }

.craftprofit-table { display: flex; flex-direction: column; gap: 4px; overflow-x: auto; }
.craftprofit-row {
  display: grid;
  /* minmax() floors on the `fr` tracks — same fix as .stats-row below:
     .craftprofit-name has overflow:hidden (its own ellipsis truncation),
     which per spec forces that grid item's automatic minimum size to 0,
     so a bare `1.6fr` can collapse to literally 0px rather than just
     shrinking once the fixed-px columns alone exceed the container width. */
  grid-template-columns: 34px minmax(110px, 1.6fr) minmax(70px, 1fr) 56px 120px 120px 120px 90px 28px;
  align-items: center; gap: 10px; padding: 6px 10px; border-radius: 6px;
  background: var(--bg-elevated); border: 1px solid transparent; font-size: 0.82rem;
}
.craftprofit-head {
  background: none; border: none; color: var(--text-dim); font-size: 0.78rem;
  text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 10px;
}
/* Click a column name to sort by it, click again to flip direction —
   user-requested (2026-09-17 bug report on the old dropdown-only sort):
   "en cliquant sur le nom des catégorie on filtre en croissant/décroissant". */
.craftprofit-sortable { cursor: pointer; user-select: none; }
.craftprofit-sortable:hover { color: var(--accent-2); }
.craftprofit-sortable.active { color: var(--accent-2); font-weight: 700; }
.craftprofit-row.profit:not(.craftprofit-head) { border-left: 3px solid #6fce7a; }
.craftprofit-row.loss:not(.craftprofit-head) { border-left: 3px solid var(--danger); opacity: 0.75; }
/* Hover highlight — user-requested (2026-09-18): "mets en surbrillance la
   ligne de rentabilité craft quand on passe la souris dessus". */
.craftprofit-row:not(.craftprofit-head):hover { background: color-mix(in srgb, var(--accent) 12%, var(--bg-elevated)); }
.craftprofit-icon { position: relative; width: 30px; height: 30px; }
.craftprofit-icon .icon { position: absolute; inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); object-fit: contain; border-radius: 4px; }
.craftprofit-icon .frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
.craftprofit-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.craftprofit-plan-badge { display: inline-flex; vertical-align: -2px; margin-left: 5px; color: #d9a45e; opacity: 0.85; }
.craftprofit-job, .craftprofit-level { color: var(--text-dim); font-size: 0.78rem; }
.craftprofit-kamas { color: #d4af37; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.craftprofit-profit-value.positive { color: #6fce7a; font-weight: 700; }
.craftprofit-profit-value.negative { color: var(--danger); font-weight: 700; }
.craftprofit-margin { font-variant-numeric: tabular-nums; font-weight: 700; }
.craftprofit-margin.positive { color: #6fce7a; }
.craftprofit-margin.negative { color: var(--danger); }
/* Add-to-Liste-de-courses button (js/shoppinglist.js's WShoppingList) —
   user-requested (2026-09-17): "faut qu'on puisse ajouter les items a la
   liste de courses" directly from this table. Same small-square-button
   look as .shoppinglist-qty-stepper button. */
.craftprofit-add-btn {
  width: 22px; height: 22px; border-radius: 5px; border: 1px solid var(--border);
  background: var(--bg-panel); color: var(--text-dim); cursor: pointer; font-weight: 700;
  line-height: 1; display: flex; align-items: center; justify-content: center; font-size: 0.85rem;
}
.craftprofit-add-btn:hover { border-color: var(--accent); color: var(--accent); }
.craftprofit-add-btn.added { border-color: #6fce7a; color: #6fce7a; }
@media (max-width: 900px) {
  .craftprofit-row { grid-template-columns: 30px 1.4fr 90px 90px 26px; }
  .craftprofit-row > span:nth-child(3), .craftprofit-row > span:nth-child(4),
  .craftprofit-row > span:nth-child(5), .craftprofit-row > span:nth-child(8) { display: none; }
}

/* ---------- Statistiques (js/stats.js) ---------- */

.stats-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin-bottom: 14px; }
.stats-tile {
  display: flex; flex-direction: column; gap: 2px; padding: 10px 12px;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
}
.stats-tile-value { font-size: 1.15rem; font-weight: 800; color: var(--accent-2); font-variant-numeric: tabular-nums; }
.stats-tile-label { font-size: 0.7rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.03em; }

.stats-table { display: flex; flex-direction: column; gap: 4px; overflow-x: auto; }
/* minmax() floors on the two `fr` tracks, not bare `1.4fr`/`1.3fr` — a grid
   item with overflow:hidden (.stats-group-label's own ellipsis truncation)
   has its automatic minimum size forced to 0 per spec, so when the fixed-px
   columns alone already exceed the container's width, an `fr` track gets
   squeezed to literally 0px instead of just shrinking — the label read as
   genuinely missing (confirmed via getBoundingClientRect: width 0), not a
   scroll-position illusion. The floor makes any real overflow scroll
   horizontally (.stats-table already has overflow-x:auto) instead of
   silently vanishing. */
.stats-row {
  display: grid;
  grid-template-columns: minmax(140px, 1.4fr) 100px 100px 100px minmax(130px, 1.3fr) 110px 110px 110px;
  align-items: center; gap: 10px; padding: 6px 10px; border-radius: 6px;
  background: var(--bg-elevated); font-size: 0.82rem;
}
.stats-row:not(.stats-head):hover { background: color-mix(in srgb, var(--accent) 12%, var(--bg-elevated)); }
.stats-head {
  background: none; color: var(--text-dim); font-size: 0.78rem;
  text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 10px;
}
.stats-group-label { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stats-coverage { font-variant-numeric: tabular-nums; color: var(--text-dim); }
.stats-kamas { color: #d4af37; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Relative-to-max-in-view bar behind the "Prix moyen" value — a cheap CSS
   gauge instead of a charting library, enough for a scannable at-a-glance
   comparison across groups. */
.stats-avg-cell { position: relative; display: flex; align-items: center; height: 20px; }
.stats-bar {
  position: absolute; left: 0; top: 0; bottom: 0; width: var(--pct, 0%);
  background: color-mix(in srgb, #d4af37 28%, transparent); border-radius: 3px;
}
.stats-avg-cell .stats-kamas { position: relative; padding-left: 4px; }
@media (max-width: 1000px) {
  .stats-row { grid-template-columns: 1.2fr 70px 70px 70px 1fr 90px; }
  .stats-row > span:nth-child(7), .stats-row > span:nth-child(8) { display: none; }
}

/* ---------- Item detail modal ---------- */

.modal-overlay {
  position: fixed; inset: 0; background: rgba(5,6,10,0.72);
  display: none; align-items: center; justify-content: center; z-index: 200; padding: 20px;
}
.modal-overlay.active { display: flex; }
.modal {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  max-width: 480px; width: 100%;
  max-height: 85vh; overflow-y: auto;
  padding: 18px;
  position: relative;
}
.modal .close-btn {
  position: absolute; top: 10px; right: 12px;
  background: none; border: none; color: var(--text-dim); font-size: 1.3rem; cursor: pointer;
}
.modal .close-btn:hover { color: var(--text); }
.modal-header { display: flex; gap: 14px; align-items: center; margin-bottom: 10px; }
.modal-header h3 { margin: 0 0 4px; font-size: 1.15rem; }
.modal-desc { font-style: italic; color: var(--text-dim); font-size: 0.85rem; margin: 10px 0; line-height: 1.4; }
.effect-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.effect-list li { font-size: 0.88rem; padding: 4px 8px; background: var(--bg-elevated); border-radius: 5px; display: flex; align-items: center; gap: 6px; }
.effect-list li.state { color: var(--accent-2); }
.effect-list li.negative { color: var(--danger); }
.random-dice { margin-left: auto; font-size: 0.85rem; flex-shrink: 0; }
.effect-elem-icons { display: inline-flex; gap: 2px; flex-shrink: 0; }
.effect-elem-icons img { width: 18px; height: 18px; object-fit: contain; }
.card-effect-list .effect-elem-icons img, .item-tooltip .effect-elem-icons img { width: 16px; height: 16px; }
.stat-icon { width: 18px; height: 18px; object-fit: contain; flex-shrink: 0; }
.modal-actions { margin-top: 14px; display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------- Spell zone editor (js/spellzoneeditor.js) ---------- */
/* Matches the "joueur = rectangle bleu" convention from the FightLab
   reference spec — one-off value, no shared blue token exists elsewhere. */
.zone-editor-overlay { --zone-caster-color: #4f8fd6; }
.zone-editor-overlay .modal { max-width: 620px; }
.zone-editor-head { display: flex; align-items: center; gap: 10px; margin: 4px 32px 0 0; }
.zone-editor-spell-icon { width: 40px; height: 40px; object-fit: contain; border-radius: 6px; background: var(--bg-elevated); flex-shrink: 0; }
/* Fixed-size window onto the (much bigger, once zoomed) grid — scrollable
   so zooming in past the viewport's own size doesn't just clip content. */
.zone-editor-viewport {
  width: 100%; height: 380px; margin: 10px 0; overflow: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
}
.zone-editor-grid { position: relative; flex-shrink: 0; transform-origin: center center; transition: transform 0.1s; }
.zone-editor-tile {
  position: absolute; width: 48px; height: 24px;
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  background: var(--bg-elevated); border: 1px solid var(--border);
  cursor: pointer; transition: background 0.1s;
  display: flex; align-items: center; justify-content: center; font-size: 0.8rem; line-height: 1;
}
/* Checkerboard shading, like the in-game grid reference (logo/grille.png)
   — purely visual, alternates by (row+col) parity. */
.zone-editor-tile.alt { background: color-mix(in srgb, var(--bg-elevated) 85%, black); }
/* Tiles the spell can actually be cast AT from wherever the caster
   currently stands, derived automatically from spell.rangeMin/rangeMax and
   spell.line ("tir en ligne"). A plain fill, not an inset box-shadow —
   clip-path (the tiles' diamond shape) clips box-shadow into broken
   triangular slivers instead of a clean outline in practice. Placed
   BEFORE target/selected/caster below so those still win when a tile
   happens to be both. */
.zone-editor-overlay { --zone-range-color: #7fce7a; }
.zone-editor-tile.in-range { background: color-mix(in srgb, var(--zone-range-color) 40%, var(--bg-elevated)); }
.zone-editor-tile:hover { background: var(--accent-2); opacity: 0.6; }
.zone-editor-tile.target { background: var(--accent); cursor: default; }
.zone-editor-tile.selected { background: var(--accent-2); }
/* The caster tile — a solid, unmistakable fill (same blue the eventual
   FightLab board uses for the player) plus an icon and a persistent
   text label underneath, rather than the small hover-only arrow this
   used to be. Repositionable via the "Déplacer le lanceur" mode, so it
   stays a normal clickable tile — js/spellzoneeditor.js's own click
   handler is what stops it being toggled as a zone cell. */
.zone-editor-tile.caster { background: var(--zone-caster-color); border-color: var(--zone-caster-color); cursor: default; }
.zone-editor-caster-label {
  position: absolute; width: 48px; text-align: center; pointer-events: none;
  font-size: 0.65rem; font-weight: 700; color: var(--zone-caster-color); white-space: nowrap;
}
.zone-editor-zoom-row { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
.zone-editor-zoom-icon { font-size: 0.8rem; color: var(--text-dim); user-select: none; }
.zone-editor-zoom-slider { flex: 1; accent-color: var(--accent); }
.zone-editor-zoom-value { font-size: 0.78rem; color: var(--text-dim); width: 42px; text-align: right; flex-shrink: 0; }
/* "Déplacer la cible"/"Déplacer le lanceur" — active while the next tile
   click is armed to relocate that marker instead of toggling a zone cell. */
.zone-editor-overlay .modal-actions .btn.active { background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 600; }
.zone-editor-legend { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 10px; font-size: 0.78rem; color: var(--text-dim); }
.zone-editor-legend span { display: inline-flex; align-items: center; gap: 5px; }
.zone-editor-swatch { width: 14px; height: 14px; border-radius: 3px; display: inline-block; }
.zone-editor-swatch.target { background: var(--accent); }
.zone-editor-swatch.selected { background: var(--accent-2); }
.zone-editor-swatch.caster { background: var(--zone-caster-color); }
.zone-editor-swatch.in-range { background: color-mix(in srgb, var(--zone-range-color) 40%, var(--bg-elevated)); }
.zone-editor-export-panel { margin-top: 10px; border-top: 1px solid var(--border); padding-top: 10px; }
.zone-editor-export-text {
  width: 100%; height: 140px; font-family: monospace; font-size: 0.78rem;
  background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border); border-radius: 6px; padding: 8px;
  resize: vertical;
}

/* ---------- FightLab (js/fightlab.js) ---------- */
/* Real game values, pulled straight from the client's own theme file
   (theme_dark2/theme/colors/colors.xml — "MODE TACTIQUE" and the
   fight/HP/AP/MP sections) rather than improvised, so the board reads as
   the actual Wakfu combat palette instead of just this app's own purple
   accent scheme. */
/* :root-scoped (not just #fightlab-panel) since .fightlab-tooltip is
   appended to document.body, a sibling of the panel, not a descendant. */
:root {
  --wk-bg: #0f1011;             /* tacticalModeBackgroundColor */
  --wk-cell-even: #282c2e;      /* tacticalModeEvenCellColor */
  --wk-cell-odd: #323335;       /* tacticalModeOddCellColor */
  --wk-range: #af775e;          /* fightRangeColor */
  --wk-zone: #ad2b2b;           /* fightZoneEffectColor */
  --wk-path: #55893e;           /* fightPathColor */
  --wk-path-error: #a95c31;     /* fightPathErrorColor */
  --wk-resist-bg: #282c2e;      /* fightResistancesBackgroundColor */
  --wk-resist-text: #d2d6d8;    /* fightResistancesTextColor */
  --wk-hp-ally: #a42100;        /* HPColor / playerTeamHPColor */
  --wk-hp-enemy: #3550bd;       /* enemyTeamHPColor */
  --wk-ap: #1d5b82;             /* APColor */
  --wk-mp: #596b27;             /* MPColor */
  --wk-qb: #d99115;             /* QBColor */
  --wk-player-tile: #4f8fd6;    /* not a client color — matches the session's own "player = blue rectangle" spec */
  --wk-armor: #218246;          /* not a client color — sampled from the real characteristics/ARMOR_GIVEN picto's own mid-tone green */
}
.fightlab-top { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start; }
/* Real bug (user-reported 2026-09-02): align-items/justify-content:center
   on a scrollable flex container is a well-known trap — Chromium won't let
   you scroll PAST center to reveal the start-side overflow (only the end
   side), so panning/zooming the board left it impossible to ever reach the
   board's own top-left corner. `margin: auto` on the child (.fightlab-
   board, below) gives the exact same "centered when it fits" look without
   that limitation — auto-margin centering plays correctly with overflow
   scrolling in every direction, unlike flex/grid alignment properties. */
/* Real bug (user-reported 2026-09-11, "ça crop"): the states panel used to
   be a position:absolute CHILD of .fightlab-viewport itself — since that
   element is the overflow:auto SCROLLING container (pan/zoom scrolls its
   own content), an absolutely-positioned child scrolls right along with
   the board underneath it instead of staying pinned to the visible corner,
   so panning down/right pushed the panel up out of the visible clipped
   area entirely. Fixed by splitting the old .fightlab-viewport into this
   non-scrolling wrap (carries the old flex/sizing role) plus the actual
   scrollable .fightlab-viewport filling it via inset:0 — the states panel
   now anchors to the WRAP, which never scrolls, so top/right stay true to
   the visible corner regardless of how far the board itself is panned. */
.fightlab-viewport-wrap { flex: 1; min-width: 320px; height: 520px; position: relative; }
.fightlab-viewport {
  position: absolute; inset: 0; overflow: auto;
  background: var(--wk-bg); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.3) inset;
  display: flex;
  cursor: grab;
  /* User-requested (2026-09-02): free drag-to-pan scrolling stays, just the
     scrollbar track itself hidden — the board's own draggability (see
     bindViewportPan, fightlab.js) already covers real navigation, so the
     bars were pure visual clutter, not the only way to move the view. */
  scrollbar-width: none; -ms-overflow-style: none;
}
.fightlab-viewport::-webkit-scrollbar { display: none; }
.fightlab-viewport.panning { cursor: grabbing; }
.fightlab-viewport .fightlab-board { margin: auto; }
/* Intro splash, not a persistent watermark — shows the real logo briefly
   when the board first mounts, then fades out for good (animation-fill-mode
   forwards keeps it at opacity 0 afterward). Pointer-events:none throughout
   so it never blocks tile clicks even while visible. z-index:3 used to sit
   BELOW every entity (.fightlab-tile.fightlab-entity's own z-index is
   `100 + row + col`, i.e. always ≥100) — user-reported (2026-09-02): the
   logo should show above them, not get covered by whichever entity's tile
   happens to overlap it. 600 clears even .fightlab-fx-layer's own 500
   (damage numbers etc.), so it stays on top of literally everything else
   on the board for as long as it's visible. */
.fightlab-board-splash {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  pointer-events: none; z-index: 600;
  animation: fightlab-splash-intro 3s ease forwards;
}
.fightlab-board-splash img { width: min(85%, 620px); height: auto; }
@keyframes fightlab-splash-intro {
  0% { opacity: 0; transform: scale(0.92); }
  20% { opacity: 0.9; transform: scale(1); }
  65% { opacity: 0.9; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.06); }
}
/* Whose-turn banner floating over the board (user-reported 2026-09-08:
   the Chacha Noir's own "Tour du Chacha Noir" label used to sit in the
   narrow .fightlab-bottom-info column and deformed that whole row) —
   an absolutely-positioned overlay instead, so it never competes for
   space with the PA/PM/PW pills or the pass-turn button. Empty text
   (no active sub-turn) collapses to nothing via :empty. */
.fightlab-turn-banner {
  position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
  padding: 4px 14px; border-radius: 999px; background: var(--wk-player-tile);
  color: #0d1017; font-weight: 700; font-size: 0.85rem; white-space: nowrap;
  pointer-events: none; z-index: 610; box-shadow: 0 2px 8px rgba(0,0,0,0.35);
}
.fightlab-turn-banner:empty { display: none; }
/* Always-visible states/buffs panel, top-right of the FightLab viewport
   (user-requested 2026-09-08, from a real Sacrieur screenshot) — mirrors
   the real client's own buff bar: a small joueur/invocation switch, then
   a compact scrollable stack of active-state cards below it. Positioned
   the same way #fightlab-turn-banner is (see its own comment) so it stays
   pinned to the corner regardless of board pan/zoom. */
.fightlab-states-panel {
  position: absolute; top: 8px; right: 8px; z-index: 605;
  width: 190px; max-height: calc(100% - 16px);
  display: flex; flex-direction: column; gap: 6px;
  pointer-events: none;
}
.fightlab-states-panel-switch {
  display: flex; gap: 2px; background: color-mix(in srgb, var(--bg) 85%, transparent);
  border: 1px solid var(--border); border-radius: 6px; padding: 2px;
  pointer-events: auto;
}
.fightlab-states-panel-switch button {
  flex: 1; border: none; background: transparent; color: var(--text-muted, inherit);
  font-size: 0.78rem; padding: 4px 6px; border-radius: 4px; cursor: pointer;
}
.fightlab-states-panel-switch button.active { background: var(--accent); color: #fff; }
.fightlab-states-panel-cards {
  display: flex; flex-direction: column; gap: 4px; overflow-y: auto;
  pointer-events: auto;
}
.fightlab-states-panel-card {
  display: flex; align-items: center; gap: 6px;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px;
  font-size: 0.78rem; line-height: 1.2;
}
.fightlab-states-panel-card-icon { width: 18px; height: 18px; flex-shrink: 0; object-fit: contain; }
.fightlab-states-panel-card-icon.placeholder {
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); color: #fff; border-radius: 50%; font-weight: 700; font-size: 0.65rem;
}
.fightlab-states-panel-empty {
  font-size: 0.7rem; color: var(--text-muted, inherit); opacity: 0.6;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  border: 1px dashed var(--border); border-radius: 6px; padding: 6px 8px; text-align: center;
  pointer-events: auto;
}
.fightlab-board { position: relative; flex-shrink: 0; transform-origin: center center; transition: transform 0.1s; z-index: 2; }
.fightlab-tiles-layer { position: absolute; inset: 0; }
/* Sibling to .fightlab-tiles-layer, never touched by renderBoard's own
   innerHTML rebuild (that happens on every hover/turn/etc.) — floating
   damage numbers live here so they survive board re-renders long enough
   to finish their own animation and self-remove. */
.fightlab-fx-layer { position: absolute; inset: 0; pointer-events: none; z-index: 500; }
.fightlab-dmg-number {
  position: absolute; transform: translate(-50%, 0); white-space: nowrap;
  font-weight: 800; font-size: 1rem; color: #ff5c5c; pointer-events: none;
  text-shadow: 0 0 3px #000, 0 0 3px #000, 0 1px 2px #000;
  animation: fightlab-dmg-float 1.1s ease-out forwards;
}
.fightlab-dmg-number.zero { color: #cfd3d6; }
.fightlab-dmg-number.heal { color: #18c372; }
.fightlab-dmg-number.armor { color: var(--wk-armor); }
.fightlab-dmg-number.resource { display: inline-flex; align-items: center; gap: 3px; }
.fightlab-dmg-number.resource img { width: 15px; height: 15px; }
.fightlab-dmg-number.resource-pa { color: #339CE6; }
.fightlab-dmg-number.resource-pm { color: #B0D44E; }
.fightlab-dmg-number.resource-pw { color: #3ADBB5; }
.fightlab-dmg-number.resource-po { color: #C48CFF; }
/* Crits get their own bigger, punchier animation — a sharp overshoot pop
   instead of the normal gentle float — plus gold color, so a critical hit
   reads as clearly different at a glance, not just a bigger number. */
.fightlab-dmg-number.crit {
  font-size: 1.5rem; color: #ffcc33;
  text-shadow: 0 0 4px #000, 0 0 6px #000, 0 0 10px rgba(255,204,51,0.7);
  animation: fightlab-dmg-crit 1.3s ease-out forwards;
}
/* Static (no animation, unlike .fightlab-dmg-number) damage preview badge —
   shown while hovering a target with a damage spell armed (previewDamageAt,
   fightlab.js), sits just above the armor/HP bars so it never overlaps
   them. A small pill background (unlike the plain floating numbers) since
   this one has to stay legibly readable at rest, not just for its own
   brief animated pop. */
.fightlab-dmg-preview {
  position: absolute; bottom: 74px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 4px;
  white-space: nowrap; font-weight: 700; font-size: 0.62rem; letter-spacing: 0.2px;
  line-height: 1.5; pointer-events: none; z-index: 4;
  background: linear-gradient(180deg, rgba(38,12,16,0.92), rgba(14,8,12,0.88));
  border: 1px solid rgba(255,120,90,0.4); border-radius: 999px;
  padding: 0 6px; box-shadow: 0 2px 5px rgba(0,0,0,0.4);
  text-shadow: 0 1px 1px rgba(0,0,0,0.6);
}
.fightlab-dmg-preview .fdp-base { color: #ffb199; }
/* Crit figure, same gold used everywhere else a real crit shows (the
   animated .fightlab-dmg-number.crit) — separated by a thin divider so the
   two aren't misread as one long number. */
.fightlab-dmg-preview .fdp-crit { color: #ffcc33; position: relative; padding-left: 5px; }
.fightlab-dmg-preview .fdp-crit::before { content: ""; position: absolute; left: 0; top: 1px; bottom: 1px; width: 1px; background: rgba(255,255,255,0.2); }
@keyframes fightlab-dmg-float {
  0% { opacity: 0; transform: translate(-50%, 6px) scale(0.85); }
  18% { opacity: 1; transform: translate(-50%, -10px) scale(1.15); }
  35% { opacity: 1; transform: translate(-50%, -16px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -48px) scale(1); }
}
@keyframes fightlab-dmg-crit {
  0% { opacity: 0; transform: translate(-50%, 10px) scale(0.5) rotate(-6deg); }
  15% { opacity: 1; transform: translate(-50%, -14px) scale(1.45) rotate(4deg); }
  30% { transform: translate(-50%, -20px) scale(1.15) rotate(-2deg); }
  45% { transform: translate(-50%, -24px) scale(1.25) rotate(0deg); }
  100% { opacity: 0; transform: translate(-50%, -58px) scale(1.1) rotate(0deg); }
}
/* Étendard summon/de-summon "poof". Spawn plays directly on the real
   entity element (its own tile — fresh geometry, so no left/top of its
   own needed); de-summon has no real element left to animate (it's
   already gone from state by the time this fires), so it's a standalone
   fx-layer copy instead, positioned the same way the damage/heal numbers
   above are. */
.fightlab-tile.fightlab-entity.etendard-spawn { animation: fightlab-etendard-spawn 0.4s ease-out; }
@keyframes fightlab-etendard-spawn {
  0% { opacity: 0; transform: scale(0.4) translateY(14px); }
  60% { opacity: 1; transform: scale(1.08) translateY(-3px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}
.fightlab-etendard-ghost {
  position: absolute; width: 44px; height: 44px;
  object-fit: contain; pointer-events: none;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.7));
  animation: fightlab-etendard-despawn 0.4s ease-in forwards;
}
/* The spawn keyframes above, played in reverse — same scale/translateY
   curve, just run 100%→0% instead of 0%→100%, folding the ghost's own
   -50%/-100% anchor (it has no tile container to sit inside, unlike the
   real entity spawn plays on) into each step via calc(). */
@keyframes fightlab-etendard-despawn {
  0% { opacity: 1; transform: translate(-50%, calc(-100% + 0px)) scale(1); }
  40% { opacity: 1; transform: translate(-50%, calc(-100% - 3px)) scale(1.08); }
  100% { opacity: 0; transform: translate(-50%, calc(-100% + 14px)) scale(0.4); }
}
.fightlab-tile {
  position: absolute; width: 52px; height: 26px;
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  background: var(--wk-cell-even); border: 1px solid var(--border);
  cursor: pointer; transition: background 0.1s;
}
.fightlab-tile.alt { background: var(--wk-cell-odd); }
/* Dungeon room (data/fight-rooms.json): the room's picture sits under the board, the cells are drawn as a faint grid over it (state colours below still win). */
.fightlab-room-bg { position: absolute; max-width: none; pointer-events: none; z-index: 0; user-select: none; }
:where(.fightlab-board.has-room-bg) .fightlab-tile { background: rgba(255, 255, 255, 0.03); border-color: rgba(255, 255, 255, 0.2); }
:where(.fightlab-board.has-room-bg) .fightlab-tile.alt { background: rgba(255, 255, 255, 0.07); }
.fightlab-tile.reachable { background: color-mix(in srgb, var(--wk-ap) 45%, var(--wk-cell-even)); }
.fightlab-tile.path { background: color-mix(in srgb, var(--wk-path) 70%, var(--wk-cell-even)); }
.fightlab-tile.in-range { background: color-mix(in srgb, var(--wk-range) 55%, var(--wk-cell-even)); cursor: pointer; }
/* In range but blocked by another entity's line of sight — not clickable,
   distinct from both "in range" (colored) and plain out-of-range tiles. */
.fightlab-tile.no-los { background: color-mix(in srgb, var(--wk-path-error) 45%, var(--wk-cell-even)); cursor: not-allowed; }
.fightlab-tile.aoe-preview { background: color-mix(in srgb, var(--wk-zone) 70%, var(--wk-cell-even)); }
/* Aura de bravoure — always-on (not a targeting preview), a red ground
   glyph per tile matching the real client's own look (a ring rune on each
   of the 8 tiles around the Étendard, not a flat color fill) — background
   IMAGE only, so whatever background-COLOR another state class on the same
   tile (reachable/in-range/path) already set stays visible underneath. */
.fightlab-tile.etendard-aura {
  background-image: radial-gradient(ellipse at center, transparent 56%, rgba(214,42,42,0.9) 62%, rgba(214,42,42,0.9) 76%, transparent 84%);
}
/* Matches the real client's own look: a ground-shadow ring at the entity's
   feet (not a big ring around the whole sprite), colored by side, with the
   character art standing taller above it. Real orientation art (4 fixed
   images — logo/haut|bas|gauche|droite.png, an oval with the direction
   baked into its own outline) recolored per side via mask-image, rather
   than a rotated glyph: a CSS rotate() of a single image would squash an
   isometric ellipse's proportions wrong on the cross-axis. */
.fightlab-tile.fightlab-entity {
  cursor: default; z-index: 5; clip-path: none; border: none; background: none;
  /* Only ever fires when JS mutates an EXISTING element's left/top in
     place (movement/swap/pull/push/teleport animation) — a normal render
     rebuilds the whole tile grid from scratch each time, so a fresh
     element has no prior position to transition from and this is a no-op
     there. */
  transition: left 0.15s linear, top 0.15s linear;
}
.fightlab-tile.fightlab-entity.draggable { cursor: grab; }
/* Crâ's Tir précis toggle (id 910900) — a green outline hugging the player
   sprite's own silhouette while the mode is active, since it has no other
   persistent visual otherwise (the deck-slot's own "armed" highlight only
   shows while a DIFFERENT spell isn't selected, not as a standing on/off
   indicator). User-requested (2026-09-01): a real contour on the sprite
   itself, not a generic circular glow around the whole tile — stacked
   drop-shadows (each one offset a pixel in a different direction) trace
   the sprite's own alpha silhouette instead of a rectangle/circle, the
   standard CSS way to fake an outline on a transparent-background image. */
.fightlab-tirprecis-active {
  /* .fightlab-entity-sprite's own drop-shadow(0 2px 3px …) folded in here
     too — filter is a single property, so this rule replaces it outright
     rather than adding to it; dropping the base shadow would lose the
     sprite's usual ground-contact shading while this mode is on. */
  filter:
    drop-shadow(0 2px 3px rgba(0,0,0,0.7))
    drop-shadow(1.5px 0 0 #3ddc6f) drop-shadow(-1.5px 0 0 #3ddc6f)
    drop-shadow(0 1.5px 0 #3ddc6f) drop-shadow(0 -1.5px 0 #3ddc6f)
    drop-shadow(0 0 4px #3ddc6faa);
  animation: fightlab-tirprecis-pulse 1.6s ease-in-out infinite;
}
@keyframes fightlab-tirprecis-pulse { 0%, 100% { opacity: 0.7; } 50% { opacity: 1; } }
/* Real 3×3 boss footprint (t.size, ~10 monsters out of 2600+ — see
   targetCells) — a tinted ground diamond under every cell besides the
   center one (the main entity tile already covers that), same idea as the
   Étendard's own aura ring just above. */
.fightlab-tile.fightlab-boss-footprint {
  background: color-mix(in srgb, var(--wk-hp-enemy) 35%, var(--wk-cell-even));
  border-color: color-mix(in srgb, var(--wk-hp-enemy) 60%, var(--border));
  cursor: default; pointer-events: none;
}
/* Sprite scaled up ×3 to visually fill its 3×3 footprint — anchored at its
   own bottom edge (already `bottom:12px` on .fightlab-entity-sprite) so it
   grows up/outward from its feet instead of drifting off the ground. */
.fightlab-tile.fightlab-entity.boss .fightlab-entity-sprite { transform: scale(3); transform-origin: 50% 100%; }
/* User-requested (2026-09-01): the ground ring stayed sized for a normal
   1-tile entity even on a boss, reading as noticeably smaller than the
   real 3×3 footprint (.fightlab-boss-footprint) tinted right around it —
   scaled up to actually reach it, same bottom-anchored transform-origin as
   the sprite scale just above so both grow from the boss's own feet. Real
   targeting/blocking/line-of-sight already used the full 3×3 footprint
   (targetCells(), shared by every hit/LOS/movement check) — this is a
   purely visual gap, not a logic one. */
.fightlab-tile.fightlab-entity.boss .fightlab-orientation-ring { transform: scale(3.2); transform-origin: 50% 100%; }
#fightlab-move-targets.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }
#fightlab-toggle-sprites.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }
#fightlab-toggle-hpbars.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }
/* Sprites only — the orientation rings stay fully opaque so who's-who and
   facing both stay readable even with this on. */
.fightlab-board.sprites-transparent .fightlab-entity-sprite { opacity: 0.3; }
.fightlab-board.hp-bars-hidden .fightlab-entity-hpbar,
.fightlab-board.hp-bars-hidden .fightlab-entity-armorbar { display: none; }
.fightlab-orientation-ring {
  position: absolute; bottom: 1px; left: 50%; margin-left: -18px;
  width: 36px; height: 28px; z-index: 1;
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
}
.fightlab-tile.fightlab-entity.player .fightlab-orientation-ring { background: var(--wk-player-tile); }
/* var(--danger), not --wk-hp-enemy (that one's blue in the client's own
   theme, used for the enemy HP BAR specifically) — the player ring is
   already blue, so the enemy ring needs a clearly different color to
   stay tellable apart at a glance; red reads as "threat" regardless. */
.fightlab-tile.fightlab-entity.enemy .fightlab-orientation-ring { background: var(--danger); }
/* Stationary "mechanism" entities (Iop's Étendard, and same convention for
   any future one — Xélor's Cadran, Crâ's balises, etc.) get this plain
   ring (logo/entite.png) instead of the facing-arrow ring above — they
   have no orientation to show, just a footprint marking them as "not a
   character". Same blue as the player's own ring since these are the
   caster's own summons, not neutral/enemy. */
/* Cataclysme's own delayed-trigger tile (Éliotrope, Serein mode) — a
   flattened blue ground disc (not the thin mechanism-ring above, which
   reads as a faint outline mostly hidden under the spell icon on top of
   it) so the tile it'll detonate on is unmistakable at a glance, per
   direct user request ("fais un cercle bleu sur la case en question"). */
.fightlab-cataclysme-glyph {
  /* Sized to the real tile footprint (TILE_W/TILE_H, 52×26) — no icon
   sits on top of it anymore (removed per direct user request: "retire
   la grosse icone sur le glyphe"), so the glyph itself IS the tile's
   whole visual now, not a small accent under a sprite. */
  position: absolute; bottom: 0; left: 50%; margin-left: -22px;
  width: 44px; height: 22px; border-radius: 50%; z-index: 1;
  background: radial-gradient(ellipse at center, color-mix(in srgb, var(--wk-player-tile) 55%, transparent) 0%, color-mix(in srgb, var(--wk-player-tile) 25%, transparent) 65%, transparent 100%);
  border: 2px solid color-mix(in srgb, var(--wk-player-tile) 85%, white 15%);
  pointer-events: none;
}
.fightlab-mechanism-ring {
  position: absolute; bottom: 1px; left: 50%; margin-left: -18px;
  width: 36px; height: 28px; z-index: 1; background: var(--wk-player-tile);
  /* Relative to THIS file (css/style.css), not the page — ../logo, not
     logo, unlike every other logo/textures reference in this app, which
     are all set via inline style="" from JS and resolve against the page
     itself instead. */
  -webkit-mask-image: url(../logo/entite.png); mask-image: url(../logo/entite.png);
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
}
.fightlab-entity-sprite {
  position: absolute; bottom: 12px; left: 50%; margin-left: -22px;
  width: 44px; height: 44px; object-fit: contain; pointer-events: none; z-index: 2;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.7));
}
/* The Étendard's own sprite (a sword driven into a rock/monster texture,
   monsters/100503793) sits noticeably higher in its own image box than
   the character portraits every other entity uses — nudged down so its
   base actually reads as standing on the tile instead of floating. */
.fightlab-tile.fightlab-entity[data-entity="etendard"] .fightlab-entity-sprite { bottom: 8px; }
/* Real per-class player render (a community site skinator, textures/characters/)
   — full-body portrait with real padding baked into the PNG (character
   only fills roughly the middle 60% vertically), unlike the square
   monster-icon .fightlab-entity-sprite above, so it needs its own
   width/anchor rather than reusing the 44×44 icon box. Width fixed,
   height auto (preserves each render's own aspect ratio); bottom pulled
   negative to compensate for the image's own bottom padding so the feet
   land on the tile instead of floating above it. */
.fightlab-character-sprite {
  width: 45px; height: auto; margin-left: -24px; bottom: -3px;
}
/* Éliotrope's real Exalté "chakra" flame (2026-09-11) — after 2 CSS-only
   attempts both read as unconvincing to the user ("c'est moche ton truc",
   "c'est pas assez wow on dirait pas des flammes"), replaced entirely with
   a real WebGL fragment shader (js/fightlab.js, ELIOTROPE_FIRE_FRAGMENT_SRC
   — the user's own supplied Shadertoy source, view/XsXSWS) rendered on a
   persistent `<canvas>` appended to `<body>` (position:fixed, not inside
   the board's own regenerated tile HTML — see updateEliotropeFireOverlay's
   own comment). This class only positions/sizes that canvas; JS drives its
   left/top to track the player's current screen position every render. */
.fightlab-exalte-fire-canvas {
  position: fixed; width: 92px; height: 150px; pointer-events: none; z-index: 500;
  display: none; opacity: 0.5;
}
/* Éliotrope portal — real user-supplied art (textures/portail2.png,
   2026-09-11) with a few CSS-only "make it feel alive" effects layered on
   top (the static PNG alone doesn't breathe/twinkle): a slow glow-pulse +
   gentle float on the sprite itself, plus 4 small staggered twinkle
   sparkles. Positioned like .fightlab-entity-sprite above (same footprint
   on the tile) since it fully replaces that element for this one entity
   type.
   3rd pass, same day — the user pinpointed one exact pixel on the real
   source art (a screenshot circling the droplet tip at the base of the
   ring's own "stem", between the 2 ground-ripple ellipses) that has to
   land on the tile's true center. `object-fit`/`object-position`
   percentage tricks can only steer the WHOLE image's letterboxing, not
   pin one arbitrary interior point, so positioning moved to
   `.fightlab-eliotrope-portal-art`: the outer `.fightlab-eliotrope-
   portal-fx` is a zero-size anchor at the tile's real center (left:50%,
   bottom:13px — half the 26px tile box's own height), and `.fightlab-
   eliotrope-portal-art` renders the full undistorted square canvas at a
   fixed size then `translate()`s itself by that pixel's own measured
   fraction of the canvas (see eliotropePortalMarkupHtml's own comment for
   the exact numbers) so THAT pixel, not the image's geometric center,
   sits on the anchor. Also: opacity dropped further (0.6) and the round
   twinkle dots became 4-pointed "+" sparkles (2 crossed glowing bars),
   both per direct request. */
.fightlab-eliotrope-portal-fx {
  position: absolute; bottom: 13px; left: 50%;
  pointer-events: none; z-index: 2;
}
.fightlab-eliotrope-portal-art {
  position: absolute; left: 0; top: 0; width: 64px; height: 64px;
  /* User reverted (2026-09-11, "replace les portails comme avant tu as
     bougé le sprite") — back to the original measured anchor, the
     re-measurement against the newer portail2.png (55.3%/94.3%) moved
     the sprite in a way the user didn't want. */
  transform: translate(-50.8%, -91.9%);
}
.fightlab-eliotrope-portal-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0.6;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.55)) drop-shadow(0 0 6px rgba(60,225,210,0.55));
  animation: fightlab-portal-pulse 2.8s ease-in-out infinite, fightlab-portal-float 3.4s ease-in-out infinite;
}
/* User-shared reference image (2026-09-11): a real concave-sided 4-point
   sparkle glyph, not the plain "+" cross bars this replaced — also
   "elles sont trop visibles", so both size and peak opacity came down
   from the cross version. */
.fightlab-eliotrope-portal-spark {
  position: absolute; width: 7px; height: 7px;
  fill: #eafffb; filter: drop-shadow(0 0 2px rgba(200,255,250,0.8));
  opacity: 0; animation: fightlab-portal-spark 2.5s ease-in-out infinite;
}
@keyframes fightlab-portal-pulse {
  0%, 100% { filter: drop-shadow(0 2px 4px rgba(0,0,0,0.55)) drop-shadow(0 0 4px rgba(60,225,210,0.4)); }
  50% { filter: drop-shadow(0 2px 4px rgba(0,0,0,0.55)) drop-shadow(0 0 9px rgba(60,225,210,0.8)); }
}
@keyframes fightlab-portal-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-2px); }
}
@keyframes fightlab-portal-spark {
  0%, 100% { opacity: 0; transform: scale(0.4) rotate(0deg); }
  50% { opacity: 0.55; transform: scale(1) rotate(45deg); }
}
@media (prefers-reduced-motion: reduce) {
  .fightlab-eliotrope-portal-img, .fightlab-eliotrope-portal-spark { animation: none; }
}
/* Spawn/despawn (2026-09-11, user-requested: "fais une animation pour
   l'apparition et la disparition/retrait des portails") — applied to the
   OUTER tile div (js only adds these classes once, on a portal's real
   first render or its real removal — see eliotropePortalSpawnedIds/
   eliotropePortalsExit's own comments — never on every incidental
   re-render). Exit duration (380ms) matches ELIOTROPE_PORTAL_EXIT_MS in
   fightlab.js exactly — that's how long the JS keeps the element around
   before actually dropping it from the exiting-portals list. */
.fightlab-eliotrope-portal-spawn { animation: fightlab-portal-spawn 0.45s cubic-bezier(0.2, 1.4, 0.4, 1) both; }
.fightlab-eliotrope-portal-exit { animation: fightlab-portal-exit 0.38s ease-in both; }
@keyframes fightlab-portal-spawn {
  0% { opacity: 0; transform: scale(0.3); }
  70% { opacity: 1; transform: scale(1.12); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes fightlab-portal-exit {
  0% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.4); }
}
@media (prefers-reduced-motion: reduce) {
  .fightlab-eliotrope-portal-spawn, .fightlab-eliotrope-portal-exit { animation: none; }
}
/* Capped to the board's own height (520px) so a long target list scrolls
   inside its own panel instead of stretching the side column past the
   board and breaking the two-column layout. */
.fightlab-side { width: 380px; flex-shrink: 0; display: flex; flex-direction: column; gap: 8px; max-height: 520px; }
#fightlab-targets-panel { flex: 1; min-height: 0; overflow-y: auto; padding-right: 4px; }
/* Pinned above the scrolling card list instead of scrolling away with it —
   own background so cards scrolling underneath don't show through, and a
   bottom border to read as a distinct anchored bar. */
.fightlab-targets-actions {
  position: sticky; top: 0; z-index: 5; margin: 0 0 10px; padding-bottom: 10px;
  background: var(--bg-panel); border-bottom: 1px solid var(--border);
}
.fightlab-bottom { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 10px; }
.fightlab-bottom-log { width: 380px; flex-shrink: 0; }
/* Turn pill/PA-PM-PW, class gauges, and the mini stats card — its own
   middle column between the log and the spell bar, not squeezed above the
   spells anymore. Takes whatever width the spell bar (sized to its own
   content, not stretched) doesn't need. */
.fightlab-bottom-info { flex: 1; min-width: 240px; max-width: 340px; }
/* Sized to its own grid content (deck-slot icons cap at 62px each, see
   .fightlab-spell-grid below) rather than flex-stretching to fill
   whatever's left — that used to inflate every icon well past its
   intended size and starve the info column next to it. */
.fightlab-bottom-spells { flex: 0 0 auto; max-width: 780px; }
/* nowrap (was wrap) — user-requested (2026-09-05): "Tour N", PA/PM/PW and
   "Passer" all belong on one line; .fightlab-bottom-info's own 340px cap
   left no room for .fightlab-resource-row next to the turn pill, so it
   dropped to its own line below. Fits now that the button itself is
   shorter too (see .fightlab-pass-turn-btn below). */
.fightlab-turn-info { display: flex; align-items: center; gap: 10px; flex-wrap: nowrap; margin-bottom: 8px; }
.fightlab-turn-pill { display: inline-block; padding: 3px 10px; border-radius: 999px; background: var(--accent); color: var(--accent-text); font-weight: 700; font-size: 0.8rem; }
/* Shown only while it's not the player's own turn in the chain (e.g. the
   Gobgob's turn, right after the Osamodas's own — see advanceTurn) —
   distinct from the round pill above so "which round" and "whose turn
   within it" never get visually confused. */
.fightlab-active-turn-pill { display: inline-block; padding: 3px 10px; border-radius: 999px; background: var(--wk-player-tile); color: #0d1017; font-weight: 700; font-size: 0.8rem; margin-left: 6px; }
/* PA/PM/PW as plain neutral pills — no per-resource color fill (that
   read as loud/messy), just the icon doing the differentiating, like the
   rest of the app's own stat rows. */
.fightlab-resource-row { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
.fightlab-resource-row #fightlab-pass-turn { margin-left: auto; }
/* Shorter label ("Passer" instead of "Passer le tour") + tighter padding
   (user-requested 2026-09-05) so the button doesn't push the rest of the
   row — PA/PM/PW pills — off the end of .fightlab-bottom-info's narrow
   column; full wording still reachable via its title tooltip. */
.fightlab-pass-turn-btn { padding: 5px 10px; font-size: 0.8rem; flex-shrink: 0; }
.fightlab-resource-pill {
  display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px;
  border-radius: 999px; font-weight: 700; font-size: 0.85rem; color: var(--text);
  background: var(--bg-elevated); border: 1px solid var(--border);
}
.fightlab-resource-pill img { width: 15px; height: 15px; }
/* Class resource gauges (Rage, Précision, Veine, …) — manual trackers, see
   CLASS_RESOURCES in js/fightlab.js for what each cap is sourced from. */
.fightlab-class-resources { display: flex; flex-direction: column; gap: 5px; margin-bottom: 10px; }
.fightlab-gauge { display: flex; align-items: center; gap: 8px; font-size: 0.78rem; }
.fightlab-gauge-icon { width: 18px; height: 18px; flex-shrink: 0; border-radius: 3px; }
.fightlab-gauge-name { width: 92px; flex-shrink: 0; color: var(--text-dim); font-weight: 600; }
.fightlab-gauge-track { flex: 1; height: 10px; border-radius: 999px; background: var(--bg-elevated); border: 1px solid var(--border); overflow: hidden; cursor: pointer; }
.fightlab-gauge-fill { height: 100%; background: var(--accent); border-radius: 999px 0 0 999px; transition: width 0.1s; }
.fightlab-gauge-value { width: 52px; flex-shrink: 0; text-align: right; color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }
/* No documented cap — a stepper instead of a fill bar, so nothing implies
   a ceiling that isn't real. */
.fightlab-gauge.open .fightlab-gauge-stepper { display: flex; align-items: center; gap: 6px; }
.fightlab-gauge.open .fightlab-gauge-value { width: 28px; text-align: center; }
.fightlab-gauge-btn {
  width: 20px; height: 20px; border-radius: 5px; border: 1px solid var(--border);
  background: var(--bg-elevated); color: var(--text); font-weight: 700; cursor: pointer; line-height: 1;
}
.fightlab-gauge-btn:hover { border-color: var(--accent); color: var(--accent); }
/* Derived gauge (Sacrieur's Fureur, computed live from HP) — no
   click-to-set affordance since the player doesn't drive it directly. */
.fightlab-gauge.readonly .fightlab-gauge-track { cursor: default; }
/* Écaflip's tarot deck readout (Main/Pioche/Défausse) — same compact
   label+value row shape as .fightlab-gauge, just without a fill bar.
   Card color (blanc/noir, real per-card data) shown as a small tinted
   border rather than inventing new iconography for it. */
.fightlab-tarot-deck { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; font-size: 0.78rem; }
.fightlab-tarot-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fightlab-tarot-label { width: 92px; flex-shrink: 0; color: var(--text-dim); font-weight: 600; }
.fightlab-tarot-card {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px 2px 2px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg-elevated); font-size: 0.78rem; white-space: nowrap;
}
.fightlab-tarot-card img { width: 20px; height: 20px; border-radius: 999px; flex-shrink: 0; }
.fightlab-tarot-card.blanc { border-color: #d9d0b8; color: #e8e2cc; }
.fightlab-tarot-card.noir { border-color: #4a4358; color: #b7aed0; }
/* Reuses .mini-stat-row/.mini-stats-title as-is (js/planner.js's own
   picker-side mini stats card) — just needs the same bottom spacing its
   siblings (.fightlab-turn-info, .fightlab-class-resources) already have. */
#fightlab-mini-stats { margin-bottom: 10px; }
#fightlab-mini-stats .mini-stat-row { padding-left: 0; padding-right: 0; }
/* Bigger, more prominent spell icons than Combo Maker's own quick-pick
   grid uses — this is the panel's main interactive focus, not a secondary
   list. */
/* Sized down a bit from the original 56px/76px/gap:10px — with the innate
   spells row added above the deck bar, keeping the old size would have
   pushed the panel noticeably taller. */
/* Slot size and icon fill both bumped per user bug report (2026-08-29):
   buttons 15% smaller (46px/62px floor/cap -> 39px/53px) so the spell bar
   needs less scrolling, icons filling the full slot ("comme si on avait
   zoomé") instead of leaving a visible margin around them. */
/* width: max-content (not the old plain max-width: none) — user-reported
   (2026-09-05): once .fightlab-bottom-spells grew wider to fit the new
   library panel next to the deck row, this block-level grid (100% width by
   default) started stretching its own 6 columns across that whole extra
   width, spreading the innate/gobgob tiles apart instead of sitting
   compact like the deck row now does (flex: 0 0 auto, see below) — shrinks
   every .fightlab-spell-grid back to its own content's width regardless of
   how wide the column around it gets, matching how it always looked.
   Fixed 53px columns (not minmax(39px, 1fr)) — user-reported (2026-09-05):
   with the deck grid always showing all 12 positions now (filled or not,
   see emptyDeckSlotHtml in js/fightlab.js), an all-empty deck had every
   column's max-content basis shrink to just the tiny "+" glyph (down near
   the 39px floor) instead of the full 53px a column with a real spell icon
   resolves to — 1fr tracks size independently per column under max-content
   sizing, so a completely empty grid never had anything pulling any column
   up to 53px. A flat 53px removes that content-dependency outright: every
   column is always exactly 53px, empty deck or full. */
/* .spell-deck-grid.fightlab-spell-grid (two classes, not one) — the base
   .spell-deck-grid rule (js/spelldeck.js's own grid, declared later in
   this file) sets grid-template-columns/gap/max-width too; same
   specificity + later source order meant IT was silently winning over
   every property this rule also sets, no matter what values were written
   here — including the 53px fix below, which had no visible effect until
   this was caught. Two classes beats one regardless of source order. */
.spell-deck-grid.fightlab-spell-grid { width: max-content; max-width: none; flex-shrink: 0; grid-template-columns: repeat(6, 53px); gap: 7px; }
.fightlab-spell-grid + .fightlab-spell-grid { margin-top: 8px; }
/* Innate/deck/gobgob grids (left column) + "Sorts disponibles" library,
   side by side (user-requested 2026-09-05, replacing the old click-to-open
   "+" popover) — wraps to stack on narrow screens rather than overflowing
   .fightlab-bottom-spells. align-items stretch (the flex default, set
   explicitly for intent) is what makes the library span the whole left
   column's height (user-requested 2026-09-05) instead of just sitting
   beside the deck row alone. flex: 0 0 auto (not 1 1 auto) on the left
   column is deliberate: user-reported (2026-09-05) that letting it
   flex-grow/shrink changed the bar's own wrapping/disposition from how it
   always looked — 0 0 auto keeps every grid inside it sized exactly the
   same as when they were lone block-level children, letting the library
   take whatever room is left instead. */
.fightlab-spell-bar-row { display: flex; gap: 12px; flex-wrap: wrap; align-items: stretch; }
/* min-width: 0 + overflow-x: auto (user-reported 2026-09-08: "tout se
   superpose c'est ignoble") — flex items default to min-width: auto, which
   floors this column at its own content width (353px once the innate row
   regularly fills all 6 slots, Chafouin included) and refuses to shrink
   even inside a flex-wrap row; on a narrow window that content width beats
   the space actually available and spills rightward UNCLIPPED onto the
   stats/log column next to it instead of wrapping, reading as everything
   overlapping. Doesn't touch the deliberate flex: 0 0 auto sizing itself
   (still never stretches wider on a roomy screen) — only lets it shrink to
   its line and scroll its own overflow internally when the line is tight. */
.fightlab-spell-bar-left { display: flex; flex-direction: column; flex: 0 0 auto; min-width: 0; overflow-x: auto; }
/* User-requested (2026-09-05): the results grid centers both ways within
   this wrapper (previously just top-left block flow) — matters now that
   the wrapper itself is stretched to the full height of the innate+deck
   column beside it (see .fightlab-spell-bar-row's align-items: stretch). */
.fightlab-spell-library {
  width: 210px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-left: 1px solid var(--border); padding-left: 12px;
}
/* Icon-only, 4 per row, no scrollbar (user-requested 2026-09-05 — grows to
   fit every candidate instead of scrolling within a fixed height) — same
   look as "Deck de sorts"'s own library (.lib-spell-tile, js/spelldeck.js)
   instead of the wider icon+name row this used to be. */
/* User-requested (2026-09-05): 4 rows (16 tiles) visible before scrolling
   kicks in. max-height/overflow only apply via .lib-results-scrollable
   (toggled in fightlab.js's renderResults, tile count > 16) rather than
   living on the bare class — user-reported (2026-09-06): even well under 4
   rows (12 tiles), the grid's own scrollHeight came out 1px taller than its
   clientHeight (a CSS Grid row-sizing sub-pixel rounding quirk, not real
   overflow), which was enough on its own for overflow-y:auto to draw a
   scrollbar regardless of what max-height was set to — bumping max-height
   didn't help since the box wasn't even being clamped by it at 3 rows, it
   was undersized relative to its OWN content. Gating on the real tile count
   sidesteps the pixel math entirely instead of chasing the rounding fuzz. */
.fightlab-spell-library-results { display: grid; grid-template-columns: repeat(4, 42px); gap: 6px; justify-content: center; }
.fightlab-spell-library-results.lib-results-scrollable { max-height: 186px; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }
.fightlab-spell-grid .deck-slot { max-width: 53px; }
/* Opposite corner from .fightlab-cooldown-badge (top-right) — a spell on
   cooldown can still show both at once while hovered. */
.fightlab-spell-grid .deck-slot-remove { left: -6px; right: auto; }
.fightlab-spell-grid .deck-slot img { width: 100%; height: 100%; }
/* Innate spells (always available, not deck-slotted) read as a distinct
   row via a subtle accent border, separate from the "these are your
   chosen deck spells" look below. */
.fightlab-spell-grid.innate .deck-slot { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
/* Sorts innés are hover-for-info only in FightLab (no gfx, no backing
   game logic outside Gobgob) — no pointer/lift affordance implying a
   click would do something. */
.fightlab-innate-tile { cursor: default; }
.fightlab-innate-tile:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); transform: none; }
/* Écaflip's 3 hand-card tiles (user-clarified 2026-09-07) — unlike the
   plain display-only innate tiles above, these ARE real, clickable
   actions (bindEcaflipHand), so cursor/hover need to say so. Same
   blanc/noir border tint as the deck-panel's own .fightlab-tarot-card
   pill for visual consistency between the two places a card shows up. */
/* Same full-size icon as every other spell-bar tile (.fightlab-spell-grid
   .deck-slot img, above) — user-requested 2026-09-07 ("agrandit le picto
   des cartes... même taille que ceux des sorts, sans changer la taille
   des boutons"). Declared with the same descendant-selector specificity
   so it wins regardless of rule order, rather than relying on the
   generic rule alone reaching this tile. */
.fightlab-spell-grid .fightlab-tarot-tile img { width: 100%; height: 100%; object-fit: contain; }
/* Real root cause found live: unlike every other spell-bar tile (a
   <div>), this one is a real <button> (see ecaflipCardTileHtml) — the
   browser's own default button padding (Chrome: "1px 6px", asymmetric)
   was shrinking the icon into a non-square box even with width/height:
   100% above, since that resolves against the padded CONTENT box. */
.fightlab-tarot-tile { cursor: pointer; padding: 0; }
/* Le Chacha Noir, played at Veine 100 (user-clarified 2026-09-07: "le
   super chacha noir... doit avoir le même sprite mais un peu plus
   grand") — a modest scale, unlike the real 3×3 boss's own scale(3)
   (.fightlab-entity.boss) which assumes a matching 3-tile footprint this
   single-tile entity doesn't have. Both variants halved again per direct
   user feedback 2026-09-07 ("les chacha noir sont beaucoup trop gros" /
   "réduit leurs tailles de 50%") — base 1 -> 0.5, super 1.4 -> 0.7. */
.fightlab-tile.fightlab-entity[data-entity^="chacha-noir"] .fightlab-entity-sprite { transform: scale(0.5); transform-origin: 50% 100%; }
.fightlab-tile.fightlab-entity.fightlab-chacha-noir-super .fightlab-entity-sprite { transform: scale(0.7); transform-origin: 50% 100%; }
.fightlab-tarot-tile:hover { transform: translateY(-2px); }
.fightlab-tarot-tile.blanc { border-color: #d9d0b8; }
.fightlab-tarot-tile.noir { border-color: #4a4358; }
.fightlab-tarot-tile.boosted { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent); }
/* Gobgob's 5 support abilities are synthetic spells with no real gfx id —
   shown as a short text label instead of an icon. */
.fightlab-spell-grid.gobgob .deck-slot.gobgob-ability { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2px; gap: 1px; }
.gobgob-ability-label { font-size: 0.62rem; font-weight: 700; line-height: 1.15; text-align: center; color: var(--text); }
/* Real spell-type badge (Attaque × élément) on a summon's own attack
   spells — see ATTACK_TYPE_ICON. */
.osamodas-attack-type-icon { width: 18px; height: 18px; display: block; margin: 0 auto 2px; }
/* No real sprite exists for the Gobgob pet in the asset set this app uses
   — a simple lettered badge stands in for it, both on the board and on
   its summon tile in the spell bar. */
.fightlab-gobgob-badge {
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 55%, var(--bg-elevated));
  color: #fff; font-weight: 800; font-size: 0.85rem;
}
/* On the board it reuses .fightlab-entity-sprite's own 44px/absolute
   positioning — only the round-badge look is added here. On the spell-bar
   summon tile it just fills the slot like any other icon would. */
.deck-slot .fightlab-gobgob-badge { width: 100%; height: 100%; font-size: 1.1rem; }
/* Wraps the bar + its optional %-beside-the-bar label (hpBarHtml's
   showPct, HP only — not the armor bar reusing the same markup) on one
   row, same overall 210px width the bar alone used to claim by itself. */
.fightlab-hpbar-row { display: flex; align-items: center; gap: 4px; width: 100%; max-width: 160px; }
.fightlab-hpbar-row .fightlab-hpbar { max-width: none; flex: 1; }
.fightlab-hpbar-pct { font-size: 0.62rem; font-weight: 700; color: var(--text-dim); white-space: nowrap; flex-shrink: 0; }
.fightlab-hpbar { position: relative; width: 100%; max-width: 160px; height: 13px; border-radius: 999px; background: var(--bg-elevated); border: 1px solid var(--border); overflow: hidden; }
.fightlab-hpbar-fill { position: absolute; inset: 0; width: 0; background: var(--wk-hp-ally); transition: width 0.15s; }
.fightlab-hpbar.enemy .fightlab-hpbar-fill { background: var(--wk-hp-enemy); }
/* 11px, not the HP bar's own 13px (still reads as the visually thinner,
   secondary bar) — but tall enough that its "X / Y" label (same font-size
   as the HP bar's own) doesn't get clipped top/bottom. */
.fightlab-hpbar.armor { height: 11px; }
.fightlab-hpbar.armor .fightlab-hpbar-fill { background: var(--wk-armor); }
.fightlab-hpbar span { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; font-size: 0.56rem; font-weight: 700; color: #fff; text-shadow: 0 0 2px rgba(0,0,0,0.7); }
/* Small always-visible HP bar floating above each entity on the board
   itself (not just the hover tooltip) — same --wk-hp-ally/--wk-hp-enemy
   colors as .fightlab-hpbar so a glance at the color alone (not just
   position) says ally or enemy, matching the real client's own convention. */
.fightlab-entity-hpbar {
  position: absolute; bottom: 58px; left: 50%; margin-left: -17px; z-index: 3;
  width: 34px; height: 5px; border-radius: 999px; background: rgba(0,0,0,0.55);
  border: 1px solid rgba(0,0,0,0.6); overflow: hidden; box-shadow: 0 1px 2px rgba(0,0,0,0.6);
}
.fightlab-entity-hpbar-fill { height: 100%; background: var(--wk-hp-ally); transition: width 0.15s; }
.fightlab-entity-hpbar.enemy .fightlab-entity-hpbar-fill { background: var(--wk-hp-enemy); }
/* Armure — same bar chrome as the HP bar just below it (bottom: 58px + its
   own 5px height + 2px gap = 65px), only shown once the entity actually
   HAS armor (armor > 0) — not merely a nonzero cap. */
.fightlab-entity-armorbar {
  position: absolute; bottom: 65px; left: 50%; margin-left: -17px; z-index: 3;
  width: 34px; height: 4px; border-radius: 999px; background: rgba(0,0,0,0.55);
  border: 1px solid rgba(0,0,0,0.6); overflow: hidden; box-shadow: 0 1px 2px rgba(0,0,0,0.6);
}
.fightlab-entity-armorbar-fill { height: 100%; background: var(--wk-armor); transition: width 0.15s; }
/* At-a-glance state dots (Transparent/Échaudé/Criblé), above the HP/armor
   bars (which top out at bottom:69px) so nothing overlaps. Full name+level
   is in the title attr and in the hover tooltip's own chip row. */
.fightlab-board-states {
  position: absolute; bottom: 70px; left: 50%; transform: translateX(-50%); z-index: 3;
  display: flex; gap: 2px; pointer-events: none;
}
.fightlab-board-state-dot {
  position: relative;
  width: 14px; height: 14px; border-radius: 4px; font-size: 0.52rem; font-weight: 700;
  line-height: 14px; text-align: center; color: #1a1006; background: var(--accent);
  border: 1px solid rgba(0,0,0,0.5); box-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
.fightlab-board-state-dot img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 3px; }
/* Real stack count (e.g. Séisme's own normal+crit tally) as a small corner
   pastille — same visual language as .fightlab-cooldown-badge on spell
   tiles, scaled down to fit this much smaller 14px dot. */
.fightlab-board-state-count {
  position: absolute; bottom: -3px; right: -3px; min-width: 10px; height: 10px; padding: 0 2px;
  border-radius: 999px; background: var(--danger); color: #fff; font-size: 0.5rem; font-weight: 800;
  display: flex; align-items: center; justify-content: center; line-height: 1;
  border: 1px solid rgba(0,0,0,0.6); z-index: 1;
}
/* The whole card (name/id, HP bar, armor bar, resistances) centered as one
   column — .fightlab-tooltip is a 2nd class alongside the shared
   .item-tooltip base, so this only touches FightLab's own board tooltip. */
/* User-requested (2026-09-02): more compact than the shared .item-tooltip
   base (260px, meant for a full item stat card) — this one only ever shows
   a name/level/HP/armor/resist summary, so a narrower card with tighter
   padding/gaps reads just as clearly at a fraction of the size. Slight
   transparency (color-mix, not an opaque background) so the board stays
   partly visible underneath while hovering. */
.item-tooltip.fightlab-tooltip { width: 190px; padding: 8px; background: color-mix(in srgb, var(--bg-panel) 88%, transparent); }
.fightlab-tooltip { display: flex; flex-direction: column; align-items: center; gap: 5px; text-align: center; }
.fightlab-tooltip-head { display: flex; align-items: center; justify-content: center; gap: 6px; }
.fightlab-tooltip-portrait {
  width: 30px; height: 30px; object-fit: contain; border-radius: 50%;
  background: var(--bg-elevated); border: 1px solid var(--border); padding: 2px;
}
.fightlab-tooltip-head-text { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; }
.fightlab-tooltip-level { font-size: 0.62rem; color: var(--text-dim); }
.fightlab-tooltip-name { font-weight: 700; font-size: 0.78rem; color: var(--text); display: flex; align-items: center; gap: 5px; }
.fightlab-tooltip-id { font-size: 0.62rem; color: var(--text-dim); background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 999px; padding: 1px 5px; }
.fightlab-tooltip-resist {
  width: 100%; max-width: 160px; padding: 5px 8px; border-radius: 6px; background: var(--wk-resist-bg); color: var(--wk-resist-text);
  font-size: 0.66rem; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; justify-items: center;
}
.fightlab-tooltip-states { display: flex; flex-wrap: wrap; gap: 4px; justify-content: center; }
.fightlab-state-chip {
  font-size: 0.62rem; font-weight: 600; color: var(--accent); background: var(--bg-elevated);
  border: 1px solid var(--accent); border-radius: 999px; padding: 1px 7px 1px 3px;
  display: inline-flex; align-items: center; gap: 3px;
}
.fightlab-state-chip-icon { width: 14px; height: 14px; border-radius: 3px; object-fit: cover; }
/* Follows the mouse while a spell is armed — pinned by its own
   bottom-right corner to the cursor tip (translate -100%,-100%), so the
   icon sits above-left of the pointer without covering the tile under it. */
.fightlab-cursor-icon {
  position: fixed; top: 0; left: 0; width: 30px; height: 30px;
  transform: translate(-100%, -100%); margin: -6px;
  background-size: contain; background-repeat: no-repeat; background-position: center;
  border-radius: 6px; border: 2px solid var(--accent);
  background-color: var(--bg-elevated);
  box-shadow: 0 2px 6px rgba(0,0,0,0.5);
  pointer-events: none; z-index: 10000;
  opacity: 0; visibility: hidden;
}
.fightlab-cursor-icon.active { opacity: 1; visibility: visible; }
.fightlab-resist-item { display: inline-flex; align-items: center; gap: 3px; }
.fightlab-resist-item img.stat-icon { width: 12px; height: 12px; }
.fightlab-cooldown-badge {
  position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px; padding: 0 3px;
  border-radius: 999px; background: var(--danger); color: #fff; font-size: 0.65rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
/* "Configuration des cibles" — a card per target preset (sprite + name up
   top, PV/Parade on their own row, resistances below, skin id last), not
   one dense 9-columns-in-a-row grid — needs room to actually read. */
.fightlab-targets-list { display: flex; flex-direction: column; gap: 10px; }
.fightlab-target-card {
  display: flex; flex-direction: column; gap: 7px; padding: 8px; border-radius: 8px;
  background: var(--bg-elevated); border: 1px solid var(--border);
}
.fightlab-target-card.active { border-color: var(--wk-zone, var(--danger)); }
.fightlab-target-card.hidden { opacity: 0.45; }
.fightlab-target-card-top { display: flex; align-items: center; gap: 8px; }
.fightlab-target-sprite {
  width: 36px; height: 36px; flex-shrink: 0; object-fit: contain; border-radius: 6px;
  background: var(--bg-panel); border: 1px solid var(--border);
}
.fightlab-target-sprite.placeholder {
  display: flex; align-items: center; justify-content: center;
  color: var(--text-dim); font-weight: 700; font-size: 1rem;
}
.fightlab-target-name { flex: 1; min-width: 0; font-size: 0.82rem; font-weight: 600; padding: 4px 6px; }
.fightlab-target-select { background: none; border: none; cursor: pointer; font-size: 1rem; padding: 0 2px; color: var(--text-dim); flex-shrink: 0; }
.fightlab-target-card.active .fightlab-target-select { color: var(--text); }
.fightlab-target-remove { background: none; border: none; color: var(--text-dim); cursor: pointer; font-size: 1.1rem; padding: 0 2px; flex-shrink: 0; }
.fightlab-target-remove:hover { color: var(--danger); }
/* "ID" button (sprite skin id) — a small floating popover instead of an
   always-visible text field taking up its own row on every card. */
.fightlab-target-id-wrap { position: relative; flex-shrink: 0; }
.fightlab-target-id-btn {
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 5px;
  color: var(--text-dim); font-size: 0.65rem; font-weight: 700; letter-spacing: 0.02em;
  padding: 3px 7px; cursor: pointer;
}
.fightlab-target-id-btn:hover { color: var(--text); border-color: var(--accent); }
.fightlab-target-id-popover {
  position: absolute; top: calc(100% + 4px); left: 50%; transform: translateX(-50%);
  z-index: 20; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 6px;
  padding: 6px; box-shadow: 0 6px 18px rgba(0,0,0,0.4);
}
.fightlab-target-id-popover input { width: 130px; font-size: 0.75rem; padding: 3px 5px; }
/* "+ Monstre" — search-as-you-type popover over the real 2800+-monster
   database (tools/build-monsters.mjs), same floating-popover convention as
   the ID button just above. */
.fightlab-monster-picker-wrap { position: relative; }
/* Fixed (not absolute) so it floats above the targets sidebar's own
   scroll/overflow instead of being clipped by it — the sidebar list and
   the sticky actions bar above it both clip absolutely-positioned
   descendants at their own edges. top/left are set from JS (see
   bindMonsterPicker's positionPopover) to the button's real screen
   position, clamped to stay inside the viewport. */
.fightlab-monster-picker-popover {
  position: fixed; z-index: 200; width: 360px; max-height: min(480px, calc(100vh - 24px)); overflow-y: auto;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px; box-shadow: 0 12px 32px rgba(0,0,0,0.5);
}
.fightlab-monster-picker-popover input { font-size: 0.8rem; padding: 5px 8px; }
.fightlab-monster-picker-filters { display: flex; flex-direction: column; gap: 6px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
.fightlab-monster-picker-filters > input { width: 100%; }
.fightlab-monster-picker-filter-row { display: flex; gap: 10px; align-items: flex-end; }
.fightlab-monster-picker-filter-row label {
  display: flex; flex-direction: column; gap: 3px; font-size: 0.65rem; color: var(--text-dim); font-weight: 600;
}
.fightlab-monster-picker-filter-row label:first-child { flex-direction: row; align-items: center; }
.fightlab-monster-picker-filter-row label:first-child span { color: var(--text-dim); }
#fightlab-monster-lvl-min, #fightlab-monster-lvl-max { width: 48px; }
.fightlab-monster-picker-filter-row label:last-child { flex: 1; min-width: 0; }
#fightlab-monster-dungeon { width: 100%; font-size: 0.75rem; padding: 5px 6px; }
.fightlab-monster-picker-results { max-height: 320px; overflow-y: auto; margin-top: 6px; display: flex; flex-direction: column; gap: 2px; }
.fightlab-monster-picker-row {
  display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 5px; cursor: pointer;
}
.fightlab-monster-picker-row:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.fightlab-monster-picker-row img { width: 26px; height: 26px; object-fit: contain; border-radius: 4px; background: var(--bg-elevated); flex-shrink: 0; }
.fightlab-monster-picker-name { flex: 1; min-width: 0; font-size: 0.78rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fightlab-monster-picker-level { font-size: 0.7rem; color: var(--text-dim); flex-shrink: 0; }
.fightlab-monster-picker-bracket {
  font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--accent); padding: 5px 6px 2px; position: sticky; top: 0; background: var(--bg-panel);
}
.fightlab-monster-picker-bracket:first-child { padding-top: 0; }
/* User-reported (2026-09-05): PV/PM/Stasis/Parade/orientation all packed
   into one no-wrap row overflowed the sidebar's own fixed 380px width
   (.fightlab-side) once a monster-sourced target's extra Stasis field was
   present — content got clipped at the card's own edge instead of
   reflowing. wrap lets it settle onto 2 lines instead. */
.fightlab-target-card-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
.fightlab-target-card-field {
  display: flex; align-items: center; gap: 5px; font-size: 0.7rem; color: var(--text-dim);
}
.fightlab-target-card-field input { width: 90px; font-size: 0.75rem; padding: 3px 5px; }
/* User-requested (2026-09-05): PV field was wider than it needed to be —
   narrowed to just past "1 000 000" (9 chars incl. thousands spaces) at
   this font-size, still without truncating a real max-HP value. PM rarely
   needs more than 2 digits, narrower still. */
.fightlab-target-card-field input[data-field="maxHp"] { width: 68px; }
.fightlab-target-card-field input[data-field="pm"] { width: 46px; }
/* Icon + ✓/✗ only (user-requested 2026-09-05: drop the "Parade : Oui/Non"
   text, it was one of the widest things in the row) — full state still
   reads from the title tooltip on hover. */
.fightlab-target-parade {
  font-size: 0.8rem; font-weight: 700; padding: 4px 8px; border-radius: 999px;
  background: var(--bg-panel); border: 1px solid var(--border); color: var(--text-dim); cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px;
}
.fightlab-target-parade .stat-icon { width: 13px; height: 13px; }
.fightlab-target-parade-mark { color: var(--danger); }
.fightlab-target-parade.active .fightlab-target-parade-mark { color: var(--wk-hp-ally, #4caf50); }
.fightlab-target-parade.active { background: color-mix(in srgb, var(--accent) 30%, var(--bg-panel)); border-color: var(--accent); color: var(--text); }
/* Cycles the target's facing (haut/droite/bas/gauche) — same small round
   chip look as the ID button, just an icon instead of text. */
.fightlab-target-facing-btn {
  background: color-mix(in srgb, var(--accent) 22%, var(--bg-elevated)); border: 1px solid var(--accent); border-radius: 999px;
  width: 26px; height: 26px; padding: 0; cursor: pointer; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.fightlab-target-facing-btn:hover { background: color-mix(in srgb, var(--accent) 40%, var(--bg-elevated)); }
.fightlab-target-facing-btn img { width: 16px; height: 16px; }
.fightlab-target-card-resist { display: flex; flex-wrap: wrap; gap: 8px; }
.fightlab-target-res-field { display: flex; align-items: center; gap: 4px; font-size: 0.75rem; }
.fightlab-target-res-field img.stat-icon { width: 16px; height: 16px; }
.fightlab-target-res-field input { width: 48px; font-size: 0.78rem; padding: 3px 4px; }
/* Board entity currently being hovered over in the log gets a bright ring
   pulled forward above everything else so it's unmistakable which one the
   row refers to. */
.fightlab-tile.fightlab-entity.log-highlight { z-index: 999 !important; filter: drop-shadow(0 0 6px var(--accent)) drop-shadow(0 0 10px var(--accent)); }
.fightlab-log { max-height: 238px; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; }
.fightlab-log-turn { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px; }
/* The live turn (still in progress) stands out from finished ones below
   it — accent border + a small pulsing dot, so "this is updating right
   now" reads at a glance rather than looking like just another entry. */
.fightlab-log-turn.live { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--bg-elevated)); }
.fightlab-log-turn-head { display: flex; align-items: center; gap: 6px; font-weight: 700; font-size: 0.85rem; margin-bottom: 6px; }
.fightlab-log-live-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex-shrink: 0;
  animation: fightlab-log-pulse 1.4s ease-in-out infinite;
}
@keyframes fightlab-log-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
.fightlab-log-rows { display: flex; flex-direction: column; gap: 3px; }
.fightlab-log-row {
  display: grid; grid-template-columns: 20px 1fr auto auto auto auto auto; align-items: center; gap: 8px;
  font-size: 0.78rem; color: var(--text-dim); padding: 3px 4px; border-radius: 4px;
  transition: background 0.1s;
}
.fightlab-log-row:nth-child(odd) { background: color-mix(in srgb, var(--bg-panel) 60%, transparent); }
.fightlab-log-row[data-target-id]:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); cursor: default; }
.fightlab-log-spell-icon { width: 20px; height: 20px; border-radius: 4px; object-fit: contain; }
/* min-width:0 is required for a text-bearing grid item to actually shrink
   below its own content's intrinsic width instead of overflowing into the
   neighboring columns (the classic grid/flex min-width:auto gotcha) —
   without it, a longer name like "Wakméha (Portail)" spilled over and
   visually overlapped the target column next to it (2026-09-11, user-
   reported: "certains nom se superpose"). */
.fightlab-log-spell-name { color: var(--text); font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fightlab-log-combo-icon { width: 14px; height: 14px; margin-left: 4px; vertical-align: -2px; border-radius: 3px; }
.fightlab-log-target { color: var(--text-dim); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fightlab-log-position { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.02em; color: var(--text-dim); }
.fightlab-log-position.back { color: var(--danger); }
.fightlab-log-position.side { color: var(--wk-qb, #d99115); }
.fightlab-log-damage { color: var(--danger); font-weight: 700; text-align: right; }
.fightlab-log-damage.zero { color: var(--text-dim); font-weight: 400; }
.fightlab-log-damage.crit { color: #ffcc33; }
.fightlab-log-heal { color: #18c372; font-weight: 700; text-align: right; }
.fightlab-log-armor { color: var(--wk-armor); font-weight: 700; text-align: right; }

/* ---------- Builder view ---------- */

/* Shown only until index.html's WDB.load().then(...) callback removes
   #view-builder's own .builder-not-ready class — user-reported (2026-09-06):
   the panel headers/drag handles below are static markup already present in
   the page, so a fast load would flash them empty for a moment before their
   JS-built content (doll, chasses, aptitudes, deck) existed. */
.builder-loading { display: none; flex-direction: column; align-items: center; justify-content: center; gap: 18px; min-height: 50vh; color: var(--text-dim); }
#view-builder.builder-not-ready .builder-loading { display: flex; }
#view-builder.builder-not-ready .builder-float-sidebar,
#view-builder.builder-not-ready .builder-layout,
#view-builder.builder-not-ready .picker-wrap { display: none; }
.builder-loading-logo { width: 165px; max-width: 45vw; height: auto; }
/* Same real-artwork swap as header.topbar .logo-img above (same source
   file), same reasoning. */
.builder-loading-logo-light { display: none; }

.builder-loading-spinner { width: 34px; height: 34px; border: 3px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: builder-loading-spin 0.8s linear infinite; }
@keyframes builder-loading-spin { to { transform: rotate(360deg); } }

/* User-requested (2026-09-06): once the loading spinner above hands off to
   the real panels, they were all popping in at once in a single frame — a
   staggered top-to-bottom fade reads as progressive loading instead, even
   though every panel is actually already fully built and ready at that
   point (the data all arrives together; this is purely a reveal order).
   Keyed off #view-builder losing .builder-not-ready, so it only plays once
   on that transition — the panel elements themselves are static and never
   recreated, so later re-renders (equipping an item, etc.) never replay it.
   `backwards` only, NOT `both` — user-reported (2026-09-06): `both` keeps
   the animation's own final `transform` (translateY(0), a no-op visually,
   but still a real non-"none" transform value) applied to the panel
   forever after it finishes, and ANY transform other than none creates a
   new CSS stacking context — which silently capped every z-index inside
   that panel to nowhere higher than the panel's own stacking level,
   trapping any dropdown that needs to visually escape past its own panel's
   edge (e.g. the épique/relique sub search results list, which opened
   underneath the next panel down instead of over it). `backwards` alone
   still shows the "from" state during each panel's own animation-delay
   (so nothing flashes at full opacity before its turn), but stops holding
   the "to" keyframe once the animation actually completes — the panel then
   reverts to its real, transform-less style same as any static element. */
@keyframes builder-panel-fade-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
/* :not(.builder-fade-played) added (user-reported 2026-09-07, real bug —
   see the JS comment where that class gets added in index.html) — without
   it these selectors kept matching for the rest of the session, so every
   later switch away from and back to view-builder (e.g. "Voir" on a public
   build) replayed this animation on a `display:none`→`block` cycle, which
   could get stuck holding the `backwards`-filled "from" keyframe
   (opacity:0) until something else forced a reflow (the page being
   scrolled) — meant to be a one-time page-load reveal, never a replay. */
#view-builder:not(.builder-not-ready):not(.builder-fade-played) .builder-float-sidebar,
#view-builder:not(.builder-not-ready):not(.builder-fade-played) .builder-layout > .layout-panel {
  animation: builder-panel-fade-in 0.32s ease backwards;
}
#view-builder:not(.builder-not-ready):not(.builder-fade-played) .builder-float-sidebar { animation-delay: 0s; }
#view-builder:not(.builder-not-ready):not(.builder-fade-played) .builder-layout > .layout-panel:nth-child(1) { animation-delay: 0.04s; }
#view-builder:not(.builder-not-ready):not(.builder-fade-played) .builder-layout > .layout-panel:nth-child(2) { animation-delay: 0.09s; }
#view-builder:not(.builder-not-ready):not(.builder-fade-played) .builder-layout > .layout-panel:nth-child(3) { animation-delay: 0.14s; }
#view-builder:not(.builder-not-ready):not(.builder-fade-played) .builder-layout > .layout-panel:nth-child(4) { animation-delay: 0.19s; }
#view-builder:not(.builder-not-ready):not(.builder-fade-played) .builder-layout > .layout-panel:nth-child(5) { animation-delay: 0.24s; }

.builder-layout { display: flex; flex-wrap: wrap; gap: 18px; align-items: flex-start; }
/* Quick-nav's scrollIntoView (index.html) would otherwise land the panel's
   own top edge flush with the viewport top — right where the sticky
   topbar (~48px tall) sits, cropping the panel's title behind it. Native
   scroll-margin-top makes every scrollIntoView call already account for
   that, no per-call offset math needed. */
.layout-panel { flex-grow: 1; flex-basis: 100%; min-width: 260px; container-type: inline-size; scroll-margin-top: 60px; }
/* User-requested (2026-09-01): the class's own official character-sheet
   illustration (ressources wakfu/breeds/characterSheetIllustrations,
   converted to PNG under textures/breed-illustrations/<classId>.png — TGA
   has no native browser support) as a faint background flourish at the top
   of the Statistiques panel. #build-stats-illustration's own inline
   background-image is set per-render by renderStats() (planner.js), since
   it depends on the build's own class — a real element (not a ::before +
   CSS custom property, which turned out to resolve a relative url()
   against css/style.css instead of the page) positioned under the real
   content, clipped to the panel's own rounded corners via the panel's own
   overflow:hidden below. */
/* position:relative only now (2026-09-06) — the panel's own overflow:hidden
   used to also clip the "Sublimations" ⓘ tooltip below it (user-reported:
   cropped against this panel's own left edge, .info-tip pokes 260px left
   of its button via `right:0`). The illustration's rounded top corners
   don't need the PARENT to clip them — giving the illustration the same
   border-radius directly (below) rounds its own background-image just as
   well, since a plain border-radius clips an element's own background with
   no overflow:hidden required — only an element's escaping CHILDREN need
   that, and this illustration (position:absolute, no children of its own)
   never had any. */
.panel.layout-panel[data-layout-id="build-stats"] { position: relative; }
.build-stats-illustration {
  position: absolute; inset: 0; height: 150px; border-radius: var(--radius);
  background-position: top center; background-repeat: no-repeat; background-size: cover;
  -webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 55%, transparent 100%);
  opacity: 0.2; pointer-events: none; z-index: 0;
}
.panel.layout-panel[data-layout-id="build-stats"] > *:not(.build-stats-illustration) { position: relative; z-index: 1; }
@media (max-width: 1300px) {
  .builder-layout { margin-left: 0 !important; }
}

/* The Build module is anchored to the actual browser window's left edge,
   outside main's own centered content box. Unlike the item-picker's
   floating side panel (a bonus overlay that slides off-screen if the gutter
   is too narrow), Build is core UI that must stay visible on a real 1920px
   screen — where main's centered gutter (~210px) is nowhere near wide
   enough on its own. So instead .builder-layout below carries a matching
   margin-left (set in JS, kept in sync with this width) to physically make
   room for it, rather than relying on leftover gutter space. */
.builder-float-sidebar {
  /* Matches the main content column's own natural top offset (topbar
     height + main's own top padding) so the fixed-position Build sidebar
     lines up exactly with the first in-flow panel instead of floating a
     few px off from it — see the equivalent match-up on
     .picker-side-float-right below. */
  position: fixed; top: var(--builder-top, 65px); left: 12px; width: 460px; box-sizing: border-box;
  max-height: calc(100vh - 79px); overflow-y: auto; z-index: 15;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
/* Animated appear/disappear (user-requested 2026-09-07) — updateBuildVisibility
   (js/planner.js) toggles this class instead of `display:none` directly, so
   the fade/slide above actually has something to animate; `visibility` (not
   display) is what removes it from hit-testing once fully faded, kept in
   step with the same transition so it only switches at the very end. */
.builder-float-sidebar.build-hidden {
  opacity: 0; transform: translateY(-10px); pointer-events: none; visibility: hidden;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0.18s;
}
@media (max-width: 1300px) {
  .builder-float-sidebar { display: none; }
}

/* Drag-and-drop panel reordering — the other half of the layout editor. */
.layout-panel.dragging { opacity: 0.4; }
.layout-panel.drag-over { outline: 2px dashed var(--accent); outline-offset: 4px; }

/* A panel set to Étroit/Moyen can be much narrower than the old fixed 2-col
   layout ever was — these rules re-flow the internal grids that assumed
   plenty of horizontal room, based on the PANEL's own width rather than the
   viewport's (a plain @media query can't see that). */
@container (max-width: 480px) {
  .chasse-layout { grid-template-columns: 1fr; }
  .chasse-row { flex-direction: column; align-items: stretch; }
  .chasse-item-label { width: auto; }
  .elemental-grid { grid-template-columns: repeat(2, 1fr); }
  .apt-grid { grid-template-columns: 1fr; }
}
@container (min-width: 481px) and (max-width: 860px) {
  .apt-grid { grid-template-columns: repeat(2, 1fr); }
}

.doll {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 8px;
  padding: 4px 4px 10px;
  overflow-x: auto;
}

/* Per-slot element-choice grids, attached directly under a doll icon. */
.doll-slot-col { display: flex; flex-direction: column; align-items: center; gap: 4px; flex-shrink: 0; }
.doll-slot-toggles { display: flex; flex-direction: column; gap: 4px; }
.elem-choice-mini { display: grid; grid-template-columns: repeat(2, 1fr); gap: 3px; }
.elem-choice-mini-btn {
  width: 20px; height: 20px; padding: 2px; flex-shrink: 0;
  background: var(--bg-elevated); border: 1.5px solid var(--border); border-radius: 4px; cursor: pointer; opacity: 0.5;
}
.elem-choice-mini-btn:hover { opacity: 0.85; border-color: var(--text-dim); }
.elem-choice-mini-btn img { width: 100%; height: 100%; object-fit: contain; }
.elem-choice-mini-btn.active { opacity: 1; background: color-mix(in srgb, var(--accent) 19%, transparent); border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }

.slot {
  width: 64px; height: 64px; flex-shrink: 0;
  /* Lighter/thicker than --border alone (previously 1.5px dashed var(--border))
     so an empty slot actually reads as "click here" against the panel
     background instead of nearly disappearing — filled slots switch to a
     solid border below and don't rely on this. */
  border: 2px dashed color-mix(in srgb, var(--text-dim) 55%, var(--border));
  border-radius: 8px;
  background: var(--bg-elevated);
  cursor: pointer;
  position: relative;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.slot:hover { border-color: var(--accent); }
.slot img.icon { position: absolute; inset: 8px; width: calc(100% - 16px); height: calc(100% - 16px); object-fit: contain; }
.slot img.frame { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.slot .slot-placeholder {
  width: 30px; height: 30px; color: var(--text-dim);
  display: flex; align-items: center; justify-content: center;
}
.slot .slot-placeholder svg { width: 100%; height: 100%; }
.slot .slot-placeholder img { width: 100%; height: 100%; object-fit: contain; filter: grayscale(1) brightness(1.6) opacity(0.7); }
.slot:hover .slot-placeholder { color: var(--accent); }
.slot:hover .slot-placeholder img { filter: none; }
.slot .slot-label {
  position: absolute; bottom: 1px; left: 0; right: 0;
  font-size: 0.55rem; text-align: center; color: var(--text-dim);
  pointer-events: none;
}
/* User-reported (2026-09-06): a filled slot kept the same generic border
   as an empty one (just dashed->solid) — the rarity frame texture
   (img.frame, inset:0, drawn on top of the icon) already IS the item's own
   border art, so the plain CSS ring around it just doubled up as an
   unrelated extra outline. Empty slots still need their own dashed ring
   (no frame texture to show instead), so this only clears it once filled. */
.slot.filled, .slot.filled:hover { border-color: transparent; }
/* "Copier le nom..." feedback on a doll slot (picker's compact one) —
   user-requested (2026-09-06), toned down after a first pass covered the
   whole icon and read as too loud: a thin ring plus a small corner badge
   (bottom-right — top-left is altslot-index, top-right is remove-x)
   instead, same scale as the doll's other small status badges. */
.slot.copied { box-shadow: 0 0 0 1px var(--accent-2); transition: box-shadow 0.15s ease; }
.slot.copied::after {
  content: "✓"; position: absolute; bottom: -3px; right: -3px;
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--accent-2); color: var(--accent-text); font-size: 0.62rem; font-weight: 700; line-height: 15px; text-align: center;
  pointer-events: none;
}
/* User-reported (2026-09-06): a filled slot kept .slot's own flat
   background fill too — the item's icon art rarely fills the whole square
   (irregular sprite silhouette, real transparent padding baked into every
   item PNG), so that fill showed through as an unrelated dark box behind
   the icon, on top of which the rarity frame's own thin border (transparent
   center, see textures/legendary.png etc.) draws — never a backdrop of its
   own. Only the icon + frame should be visible once filled. */
.slot.filled { background: transparent; }
/* Exception (user-reported 2026-09-06): .altslot-box lives inside
   .altslot-float, a `position:fixed` popover with no background of its
   own — sitting directly over whatever else is on the page behind it
   (another panel, an image, anything), so a filled one without its own
   backdrop was nearly impossible to make out. The doll's own regular
   slots stay transparent (they already sit on the panel's solid
   background); only this floating context needs its own fill back. */
.slot.altslot-box.filled { background: var(--bg-elevated); }
/* Reworked (2026-09-01): was a hard display:none/block toggle (an instant
   pop, no feedback of its own) — now always in flow, faded/scaled in on
   reveal so it eases in instead of snapping, and grows slightly on its own
   :hover so the exact clickable circle is obvious right before the click. */
.slot .remove-x {
  position: absolute; top: -1px; right: -1px;
  background: var(--danger); color: #fff;
  width: 16px; height: 16px; border-radius: 50%;
  font-size: 0.65rem; line-height: 16px; text-align: center;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  opacity: 0; transform: scale(0.6); pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.slot.filled:hover .remove-x { opacity: 1; transform: scale(1); pointer-events: auto; }
.slot.filled:hover .remove-x:hover { background: #ff6b6b; transform: scale(1.15); }
/* User-requested (2026-09-01): the picker's mini doll used to only ever
   render the × on the active slot at all (see renderPickerDoll) — every
   OTHER filled slot there had no remove button whatsoever, not even on
   hover, since the markup itself was never emitted for them. Now rendered
   for every filled slot there too, hover-revealed same as the main doll;
   the active slot alone keeps its own always-visible convenience below
   (bigger, already-focused target, no hover precision needed for it). */
#build-picker-doll .slot.filled.active .remove-x { opacity: 1; transform: scale(1); pointer-events: auto; }
.slot.active { border-color: var(--accent); border-style: solid; box-shadow: 0 0 0 2px var(--accent) inset; }
/* Not an equipment slot — a shortcut to the picker with no Type filter
   applied, sitting at the end of the doll row so it reads as "search
   everything" rather than one more thing to equip. Resets the <button>
   defaults .slot doesn't already cover (font/padding), and stays
   accent-colored at rest (not just on :hover like a real slot) so it's
   never mistaken for an empty slot waiting to be filled. */
.slot-search-btn { font: inherit; padding: 0; color: var(--accent); border-style: solid; border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.slot-search-btn .slot-placeholder { color: inherit; }
.slot-search-btn:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--bg-elevated)); }
/* Hints that right-click has something to offer, even before the 2 alt
   boxes below have ever been revealed — otherwise it's a fully hidden
   feature with zero on-screen affordance. Kept inside the slot's own bounds
   since .slot clips overflow. */
.altslot-hint-dot {
  position: absolute; bottom: 3px; left: 3px; z-index: 1;
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent-2);
  box-shadow: 0 0 0 1.5px var(--bg-elevated);
}
/* The 2 alt boxes revealed below a slot on right-click — full-size versions
   of the main .slot (class="slot altslot-box"), each independently holding
   its own remembered item; clicking one swaps it with whatever's equipped.
   position:fixed and appended straight to <body> (see planner.js's
   altFloat) rather than nested in the doll — nested, it kept getting
   clipped by an ancestor panel's own containing-block behavior instead of
   rendering above everything on the page. */
.altslot-float {
  position: fixed; z-index: 500; display: none; flex-direction: column; gap: 4px;
  /* The container itself is see-through to clicks (only the boxes below
     opt back in) — positioned right under the compact doll, it can end up
     overlapping the search grid's first row, and without this the 4px gap
     between the two boxes silently ate clicks meant for whatever card sat
     underneath instead of passing them through. */
  pointer-events: none;
}
.altslot-float.active { display: flex; }
.altslot-box { pointer-events: auto; }
.altslot-box { width: var(--altslot-size, 64px); height: var(--altslot-size, 64px); }
.altslot-index {
  position: absolute; top: -1px; left: -1px; z-index: 1;
  min-width: 14px; height: 14px; padding: 0 2px; border-radius: 3px 0 4px 0;
  background: var(--bg-elevated); border: 1px solid var(--border);
  color: var(--text-dim); font-size: 0.6rem; line-height: 12px; text-align: center;
}
/* Same badge, but the slot actually HAS saved loadout alternatives behind
   it (see planner.js renderDoll's own altIndexActiveCls) — accent-colored
   and bold instead of muted, so "this number means something, right-click
   it" reads at a glance instead of needing the separate dot + a hover. */
.altslot-index-active {
  background: var(--accent); border-color: var(--accent); color: var(--accent-text); font-weight: 700;
}
/* Drawn with two bars instead of a literal "+" glyph — font glyph metrics
   (side-bearings, hinting) made a text "+" sit off-center by a px or two
   depending on the browser, no amount of line-height tweaking fixed it
   reliably. This is geometric, not font-dependent, so it's exact. */
.altslot-add-icon { position: relative; width: 30px; height: 30px; }
.altslot-add-icon::before, .altslot-add-icon::after {
  content: ""; position: absolute; top: 50%; left: 50%; background: currentColor; border-radius: 1px;
}
.altslot-add-icon::before { width: 16px; height: 2px; transform: translate(-50%, -50%); }
.altslot-add-icon::after { width: 2px; height: 16px; transform: translate(-50%, -50%); }

.doll-compact { padding: 4px 4px 12px; margin-bottom: 10px; border-bottom: 1px solid var(--border); }
.doll-compact .slot { width: 50px; height: 50px; }
.doll-compact .slot .slot-placeholder { width: 22px; height: 22px; }

/* Stays pinned under the topbar while scrolling through search results, so
   the slot-switcher row (and its highlighted active slot) is always one
   click away without scrolling back up. */
.picker-sticky-header {
  /* Same 65px offset every other fixed/scrolled-to element in the builder
     lines up on (topbar height + main's own top padding) — kept in sync so
     this sticks exactly where it naturally lands when the picker panel is
     scrolled into view (see #build-picker-panel's own scroll-margin-top
     below), instead of snapping down those few px the moment it starts
     sticking. */
  position: sticky; top: 65px; z-index: 25;
  background: var(--bg-panel);
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0; margin: -10px 0 8px;
}
.picker-sticky-header .doll-compact { flex: 1; margin-bottom: 0; padding: 0; border-bottom: none; min-width: 0; }
.picker-sticky-header .doll-compact .slot { width: 54px; height: 54px; }

/* Scrolling the picker into view (index.html's goToSearch, and
   openSlotPicker's own equivalent call) targets the whole panel rather
   than the filters column specifically — lands its top at the same 65px
   offset .builder-float-sidebar/.picker-side-float-right already use, so
   the pinned-items tray (fixed-position, floats alongside) lines up with
   it instead of sitting a bit higher. */
#build-picker-panel { scroll-margin-top: 65px; }

.build-meta { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }
.build-meta select, .build-meta input[type=text] { flex: 1; min-width: 120px; }

/* Build switcher — a plain <select> replaced by a trigger button + a
   floating popover so each build can show a real preview (class portrait,
   level, Maîtrise cumulée/Résistances, equipped-item strip) instead of just
   its name. See planner.js renderBuildSwitcher/buildSwitcherRowHtml. */
.build-switcher-wrap { position: relative; width: 100%; }
.build-select-trigger {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 6px 10px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg-elevated); color: var(--text); cursor: pointer; font: inherit;
}
.build-select-trigger:hover { border-color: var(--accent); }
.build-select-trigger-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; font-size: 0.85rem; }
.build-select-trigger-caret { color: var(--text-dim); flex-shrink: 0; }
.build-row-portrait {
  position: relative; display: inline-block; width: 28px; height: 28px; border-radius: 6px; flex-shrink: 0;
  background-color: var(--bg-panel); background-repeat: no-repeat;
}
.build-row-portrait.small { width: 22px; height: 22px; }
/* Public/privé badge overlaid on a build's own portrait icon (user-
   requested 2026-09-07) — bottom-left corner, same public/privé wording and
   accent-vs-neutral coloring as .build-visibility-btn in the Build panel
   itself. Only ever rendered for an account-owned, actually-saved build
   (see the JS call sites) — a guest build has no public/private concept,
   and a currently-viewed shared/unsaved build hasn't been saved as
   anyone's yet either. */
.build-row-visibility-badge {
  position: absolute; bottom: -3px; left: -3px; width: 13px; height: 13px;
  border-radius: 50%; display: flex; align-items: center; justify-content: center;
  font-size: 8px; line-height: 1; background: var(--bg-panel); border: 1px solid var(--border); color: var(--text-dim);
}
.build-row-visibility-badge.public { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 35%, var(--bg-panel)); color: var(--text); }
/* position:fixed (not absolute) so it floats above every panel's own
   scroll/overflow instead of being clipped by it — top/left/width set from
   JS (positionSwitcherPopover) to the trigger's real screen rect, clamped
   to stay inside the viewport. Same convention as FightLab's own
   monster-picker popover. */
.build-switcher-popover {
  position: fixed; z-index: 600; overflow-y: auto; scrollbar-gutter: stable;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 10px;
  /* Extra right padding (vs. the other 3 sides) so the native scrollbar
     track doesn't sit flush against — and crop — the last item icon
     (Monture) of each row's item strip. */
  padding: 6px 12px 6px 6px; box-shadow: 0 12px 32px rgba(0,0,0,0.5);
  display: flex; flex-direction: column; gap: 2px;
}
/* --row-accent/--row-accent-a (set inline per row, see buildSwitcherRowHtml)
   are that build's class's own portrait background color, sampled once
   client-side — the alpha is its own separate variable (rather than baked
   into one fixed 0.16 always applied to a 0,0,0 fallback) specifically so a
   row with no class yet / colors not warmed up on this render composites as
   truly transparent instead of a plain grey-black tint. :hover/.active
   still fully replace the background (the deliberate accent-purple
   highlight stays the way to show selection/hover, the class tint is just
   the resting-state identity cue). */
.build-switcher-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 7px; cursor: pointer; background: rgba(var(--row-accent, 0, 0, 0), var(--row-accent-a, 0)); }
.build-switcher-row:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.build-switcher-row.active { background: color-mix(in srgb, var(--accent) 26%, transparent); }
/* Spans all 3 lines of its row (name/level/icons, numeric stats, item
   strip) instead of sitting inline with just the top line. A fixed square
   size rather than aspect-ratio + align-items:stretch — that combination
   left the width stuck at 0 (Chromium doesn't reliably derive an
   aspect-ratio width from a flex item's stretched cross-size when its own
   content is empty). User-requested (2026-09-06) reorganization — tried a
   4-line split first (each kind of data on its own row), reported "trop
   grand"/"3 lignes max", settled here: identity+priority icons together,
   every numeric stat (mastery/résist/PV/PA/PM/PW/%CC) as one consistent
   row, equipment its own row. */
.build-row-portrait-big {
  position: relative; width: 60px; height: 60px; border-radius: 9px; flex-shrink: 0; align-self: center;
  background-color: var(--bg-panel); background-repeat: no-repeat;
}
/* Bigger variant of .build-row-visibility-badge for this 60px portrait
   (the .small trigger one above is only 22px, needs a much smaller badge). */
.build-row-portrait-big .build-row-visibility-badge {
  bottom: -4px; left: -4px; width: 18px; height: 18px; font-size: 10px; border-width: 1.5px;
}
.build-switcher-row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 3px; }
.build-switcher-row-top { display: flex; align-items: center; gap: 7px; }
/* flex:1 + ellipsis — this line also carries level + priority icons now, so
   a very long name still truncates, but the wider popover (580px, see
   positionSwitcherPopover) gives it much more room than before the numeric
   stats moved off onto their own line. */
.build-row-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; font-size: 0.8rem; }
.build-row-level { font-size: 0.66rem; color: var(--text-dim); flex-shrink: 0; }
.build-row-tags { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; }
.build-row-role-icon { width: 15px; height: 15px; object-fit: contain; border-radius: 3px; }
.build-row-budget-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--budget-color); flex-shrink: 0; }
.build-switcher-row-stats { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.build-row-stat {
  display: inline-flex; align-items: center; gap: 3px; font-size: 0.7rem; color: var(--text-dim); flex-shrink: 0;
}
.build-row-stat img { width: 13px; height: 13px; }
.build-row-elems { display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0; }
.build-row-elem-icon { width: 15px; height: 15px; object-fit: contain; }
.build-switcher-row-items { display: flex; gap: 3px; flex-wrap: nowrap; }
.build-row-item {
  display: block; width: 26px; height: 26px; border-radius: 4px; background: var(--bg-elevated);
  border: 1px solid transparent; flex-shrink: 0;
}
.build-row-item.filled { border-color: var(--border); }
.build-row-item img { width: 100%; height: 100%; object-fit: contain; border-radius: 3px; }
.build-row-item.empty img { padding: 3px; filter: grayscale(1); opacity: 0.45; }

.stats-summary { margin-top: 0; }
.stats-group { margin-bottom: 16px; }
.stats-group h4 { margin: 0 0 8px; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); }

.stat-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); gap: 6px; }
.stat-tile {
  background: var(--bg-elevated); border: 1px solid var(--border-soft, #ffffff14); border-radius: 6px;
  padding: 7px 8px; display: flex; align-items: center; justify-content: center; gap: 6px;
}
.stat-tile img { width: 17px; height: 17px; flex-shrink: 0; }
.stat-tile .t-value { font-size: 0.92rem; font-weight: 700; color: var(--accent); line-height: 1.2; }
.stat-tile .t-value.neg { color: var(--danger); }
/* User-requested (2026-09-02): the Statistiques panel's own PV/PA/PM/PW
   tiles sat fully opaque right over the class illustration background
   (#build-stats-illustration) added just above the "Principal" section,
   hiding most of it — scoped to just this panel (.stat-tile is shared with
   Combo Maker's own resource tiles elsewhere, which never sit over any
   illustration and should stay fully opaque). */
.panel.layout-panel[data-layout-id="build-stats"] .stat-tile { background: color-mix(in srgb, var(--bg-elevated) 55%, transparent); }

/* Derived/summary stats (Maîtrise cumulée, Résistances totales, Poids) —
   just icon + number, no separate label line, in their own compact row
   right under the main PV/PA/PM/PW tiles. */
.stat-tiles-compact-row { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px; margin-top: 6px; }
.stat-tile-compact {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  background: var(--bg-elevated); border: 1px solid var(--border-soft, #ffffff14); border-radius: 999px;
  padding: 4px 10px 4px 8px;
}
.stat-tile-compact img { width: 15px; height: 15px; flex-shrink: 0; }
.stat-tile-compact .t-value { font-size: 0.85rem; font-weight: 700; color: var(--accent); }

/* Rôle / Budget tags (user-requested 2026-09-06) — pure self-declared
   labels shown here, on the build-switcher card, and on the share-link
   preview image; click-to-toggle single-select (see renderMeta). One
   centered row, no group labels (user-requested — "Rôle"/"Budget" text
   above each group read as redundant clutter at this size). A thin
   vertical rule separates the two groups since they're otherwise visually
   distinct enough (square grid vs. pills) not to need one, but it still
   reads cleaner as one row. */
.build-role-budget-row { display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; width: 100%; margin-top: 4px; }
.build-role-budget-divider { width: 1px; align-self: stretch; background: var(--border); flex-shrink: 0; }
/* Single row (user-requested 2026-09-06 — tried a 2×2 square first) — full
   color always (an earlier grayscale-until-active version read as
   broken/empty buttons, user-reported "horrible"), accent border + tint
   when active. Icon fills the button via object-fit:contain with a small
   padding — a "zoomed"/cropped version (object-fit:cover + scale) was tried
   too but then dropped, same for the preview-image badge. */
.build-role-buttons { display: flex; gap: 6px; }
.build-role-btn {
  display: flex; box-sizing: border-box; width: 32px; height: 32px; padding: 0; overflow: hidden;
  background: var(--bg-elevated); border: 1.5px solid var(--border); border-radius: 7px; cursor: pointer;
}
/* The role icons are full square tiles (their own painted background), so
   they fill the whole button like the budget button's color does — zoomed
   slightly (clipped by the button's overflow:hidden) to hide the tiles'
   own white 2px rim and rounded corners. */
.build-role-btn img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.2); }
.build-role-btn:hover { border-color: var(--accent); }
/* The icon now covers the whole button, so the selection is shown outside
   it (ring + glow), and unselected roles are dimmed once one is picked. */
.build-role-btn { transition: filter 0.15s, opacity 0.15s, box-shadow 0.15s; }
.build-role-btn.active {
  border-color: var(--bg-panel);
  box-shadow: 0 0 0 2px var(--accent), 0 0 10px color-mix(in srgb, var(--accent) 60%, transparent);
}
.build-role-buttons:has(.build-role-btn.active) .build-role-btn:not(.active) { filter: grayscale(0.7) brightness(0.75); opacity: 0.7; }
.build-role-buttons:has(.build-role-btn.active) .build-role-btn:not(.active):hover { filter: none; opacity: 1; }
/* Single button, cycles Bas→Moyen→Haut on click (user-requested
   2026-09-06, replacing 3 separate pills) — icon-only (the green/orange/red
   background already carries the tier), same size as the role buttons so
   the row reads as one consistent height. */
.build-budget-btn {
  display: flex; align-items: center; justify-content: center; box-sizing: border-box; width: 32px; height: 32px; padding: 0;
  background: color-mix(in srgb, var(--budget-color) 22%, var(--bg-elevated));
  border: 1.5px solid color-mix(in srgb, var(--budget-color) 45%, var(--border)); border-radius: 7px; cursor: pointer;
}
.build-budget-btn img { width: 17px; height: 17px; object-fit: contain; }
.build-budget-btn:hover { border-color: var(--budget-color); }

/* Public/Privé toggle (Phase 3, 2026-09-06) — only rendered for an
   account-owned build that isn't currently a shared/unsaved view (see
   renderMeta). Privé (default) reads as neutral, Public gets a distinct
   accent tint so it's obvious at a glance this build is visible to anyone
   on Builds publics. */
.build-visibility-btn {
  width: 100%; margin-top: 8px; padding: 6px 10px; font-size: 0.8rem; font-weight: 600;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim);
  border-radius: 6px; cursor: pointer;
}
.build-visibility-btn:hover { border-color: var(--accent); color: var(--text); }
.build-visibility-btn.public {
  background: color-mix(in srgb, var(--accent) 18%, var(--bg-elevated));
  border-color: var(--accent); color: var(--text);
}
/* Non-interactive <span> shown instead of the real toggle while viewing
   someone else's build (user-reported 2026-09-07) — dashed border and no
   pointer cursor read as "locked", not just "a different-colored button". */
.build-visibility-locked {
  cursor: default; opacity: 0.75; border-style: dashed; font-weight: 500;
  display: inline-block; text-align: center;
}

/* Builds publics view (Phase 3, 2026-09-06) */
.publicbuilds-grid {
  /* Exactly 2 per row (user-requested 2026-09-06, was auto-fill/minmax —
     fit 3+ across on a wide screen, read as too small/cramped) — bigger
     cards, bigger text/icons throughout (see below). Single column on
     narrow viewports, same 700px breakpoint the legal corner links use. */
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
}
@media (max-width: 700px) {
  .publicbuilds-grid { grid-template-columns: 1fr; }
}
.pb-card {
  background-color: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  padding: 16px; display: flex; flex-direction: column; gap: 10px;
  /* Per-class tint (user-requested 2026-09-06), same --pb-accent "r,g,b"
     custom property js/publicbuilds.js sets from WUI.classAccentColor — a
     flat-color background-image layer (not `background`, which would
     replace background-color entirely) so it overlays the card's own
     elevated surface instead of fighting it. Defaults to fully transparent
     when no class/accent is known. */
  background-image: linear-gradient(rgba(var(--pb-accent, 0, 0, 0), var(--pb-accent-a, 0)), rgba(var(--pb-accent, 0, 0, 0), var(--pb-accent-a, 0)));
}
.pb-card-top { display: flex; gap: 14px; align-items: center; }
/* .build-row-portrait-big (js/planner.js's own build-switcher row) is 60px
   square with align-self:center — enlarged here (2 cards/row leaves plenty
   of room) rather than shrunk like the first cramped pass. */
.pb-card-top .build-row-portrait-big { width: 68px; height: 68px; }
.pb-card-identity { min-width: 0; flex: 1; }
.pb-card-name { font-weight: 700; font-size: 1.15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pb-card-classlevel { font-size: 0.95rem; color: var(--accent-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pb-card-meta { font-size: 0.85rem; color: var(--text-dim); margin-top: 2px; }
/* Rôle/Budget badges (user-requested 2026-09-06) — same icons/colors as the
   Build panel's own role icon + .build-budget-btn (kamas icon tinted via a
   colored pill, not the image itself), just read-only here. */
.pb-card-tags { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; }
.pb-card-role-icon { width: 24px; height: 24px; object-fit: contain; border-radius: 4px; }
.pb-card-budget {
  display: inline-flex; align-items: center; gap: 4px; font-size: 0.78rem; font-weight: 600; white-space: nowrap;
  padding: 3px 7px; border-radius: 6px;
  background: color-mix(in srgb, var(--budget-color) 22%, var(--bg-elevated));
  border: 1px solid color-mix(in srgb, var(--budget-color) 45%, var(--border));
}
.pb-card-budget img { width: 13px; height: 13px; object-fit: contain; }
/* Stat chips enlarged to match (base .build-row-stat is 0.7rem/13px icons,
   sized for the much narrower build-switcher popover). */
.pb-card .build-switcher-row-stats { gap: 12px; }
.pb-card .build-row-stat { font-size: 0.88rem; gap: 5px; }
.pb-card .build-row-stat img { width: 18px; height: 18px; }
/* Stats block: two rows of chips (icons+Maîtrise/Résistances, then PV/PA/
   PM/PW/%CC) beside a 2×2 grid of elemental resistance %ages, split by a
   vertical divider — mirrors the share-link preview image's own layout
   (backend/og-image.mjs), user-requested 2026-09-07 ("faut que ça
   ressemble à ça", reference screenshot) after an earlier single-row-per-
   thing version read as cramped. */
.pb-card-stats-block { display: flex; align-items: center; gap: 14px; }
.pb-card-stats-main { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0; }
.pb-card-stats-divider { align-self: stretch; width: 1px; background: var(--border); flex-shrink: 0; }
.pb-card-elem-resist {
  display: grid; grid-template-columns: repeat(2, auto); gap: 4px 12px; flex-shrink: 0;
}
.pb-card-elem-resist .build-row-stat { font-size: 0.85rem; }
.pb-card-elem-resist .build-row-stat img { width: 16px; height: 16px; }
/* One line, matching the share-link preview image (user-requested
   2026-09-06) — at 2 cards/row there's enough width for all 14 icons at a
   larger size than the local switcher's own 26px and still fit on one
   line. Stretched to fill the card's own full width (user-requested
   2026-09-07) instead of a fixed 30px that left empty space on the right —
   flex:1 splits the row evenly across however many px the card actually
   has, `aspect-ratio:1` keeps each icon square (so it grows taller along
   with wider, same intent as the user's own "quitte à augmenter la hauteur
   du container parent" — the row's height is simply whatever a square at
   that width needs, not a separately fixed number). */
.pb-card .build-row-item {
  position: relative; flex: 1 1 0; width: auto; height: auto; aspect-ratio: 1 / 1; box-sizing: border-box;
  /* min-width:0 (real bug, user-reported 2026-09-07: icons overflowing past
     the card's own right edge) — a flex item's default min-width is `auto`,
     not 0, which resolves to its own content/intrinsic minimum size rather
     than "whatever flex-grow computed": with flex-basis:0, every one of the
     14 items was still floored at ~36px (its own content-driven minimum)
     regardless of how little room 14-across actually left inside the card,
     overflowing the row by however much the card was narrower than
     14 × 36px + 13 × 4px gaps. This override lets each item actually shrink
     to fit. */
  min-width: 0;
}
/* Real bug (user-reported 2026-09-07, follow-up screenshot: 32px wide ×
   36px tall, not square) — the base rule's <img> (width:100%;height:100%)
   is a normal-flow child of a `height:auto` box: a percentage height only
   resolves against a "definite" parent height, and browsers don't treat an
   aspect-ratio-DERIVED auto-height as definite for that purpose, so the img
   fell back to its own intrinsic pixel size instead (most item-type icons
   aren't natural squares) — which then, being real in-flow content, grew
   the "auto" parent height to fit it, overriding aspect-ratio entirely.
   Taking the image out of flow (absolute, positioned by the now-relative
   parent) removes it from that auto-height calculation altogether, so
   aspect-ratio is the only thing left deciding the item's height. */
.pb-card .build-row-item img { position: absolute; inset: 0; }
.pb-card .build-switcher-row-items { gap: 4px; }
.pb-card-actions { display: flex; gap: 10px; margin-top: auto; }
.pb-card-actions .btn { flex: 1; padding: 8px 10px; font-size: 0.92rem; }
/* Like button (user-requested 2026-09-07) — a compact heart+count, not a
   flex:1 sibling like Voir/Dupliquer (a count never needs that much
   width). */
.pb-card-actions .pb-like-btn { flex: 0 0 auto; display: flex; align-items: center; gap: 5px; }
.pb-card-actions .pb-like-btn.liked { border-color: #e0507a; color: #e0507a; }
.pb-card-actions .pb-like-btn:disabled { opacity: 0.5; cursor: default; }
.pb-like-count { font-variant-numeric: tabular-nums; }
/* Pseudo link on a build card (user-requested 2026-09-07: profile pages) —
   reads as plain text (matches the "par <b>pseudo</b>" it replaces) but is
   a real button underneath. */
.pb-card-pseudo-link {
  background: none; border: none; padding: 0; margin: 0; font: inherit; font-weight: 700;
  color: var(--text); cursor: pointer; text-decoration: underline; text-decoration-color: transparent;
}
.pb-card-pseudo-link:hover { color: var(--accent); text-decoration-color: currentColor; }
.publicbuilds-empty { color: var(--text-dim); font-size: 0.9rem; padding: 24px 0; text-align: center; }

/* Profile page (user-requested 2026-09-07). */
.profile-header { display: flex; flex-direction: column; gap: 10px; padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid var(--border); }
.profile-identity { display: flex; align-items: baseline; gap: 10px; }
.profile-bio { margin: 0; color: var(--text); white-space: pre-wrap; max-width: 60ch; }
.profile-bio-edit { display: flex; flex-direction: column; gap: 6px; max-width: 60ch; }
.profile-bio-edit textarea {
  width: 100%; min-height: 64px; resize: vertical; font-family: inherit; font-size: 0.9rem;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text);
  border-radius: 6px; padding: 8px 10px;
}
.profile-bio-edit-row { display: flex; align-items: center; gap: 10px; }
/* Same .pb-card grid as Builds publics (user-requested 2026-09-07: "le même
   que dans les builds publics, mais 1 par ligne") — .publicbuilds-grid's own
   2-per-row default overridden back down to one, since a profile's own
   build list reads better as a single column than js/publicbuilds.js's
   wider browsing grid. Also capped instead of stretching to
   .publicbuilds-layout's full left column (earlier user-reported: "la barre
   où sont les builds est trop large" — a full-bleed single card looked just
   as stretched as the row version it replaced). */
.profile-builds { grid-template-columns: 1fr; max-width: 640px; }
/* Lock badge (cardHtml, js/publicbuilds.js) — legacy fallback only; a build
   you own now always gets the real toggle below instead, so this never
   actually renders any more (kept as dead-code-safe defensive markup, see
   cardHtml's own comment). */
.pb-card-private-badge {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
  color: var(--text-dim); font-size: 0.88rem; font-weight: 600;
}
/* Visibility toggle on a build you own (user-requested 2026-09-07: "on doit
   pouvoir activer ou non le public/privé depuis notre profil") — same
   public/private accent-tint logic as .build-visibility-btn (Build panel),
   sized to sit inline in the card's action row instead of full-width. */
.pb-visibility-toggle {
  flex: 1; font-weight: 600; background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim);
}
.pb-visibility-toggle:hover { border-color: var(--accent); color: var(--text); }
.pb-visibility-toggle.public {
  background: color-mix(in srgb, var(--accent) 18%, var(--bg-elevated));
  border-color: var(--accent); color: var(--text);
}

/* Filters sidebar (user-requested 2026-09-06) — grid on the left, filters
   on the right; same .panel.filters/.field/label chrome as the Base de
   données filter column (css rules around line 527) for visual consistency,
   just on the opposite side and without that column's sticky positioning
   (this page's own grid is short enough not to need it). */
.publicbuilds-layout { display: grid; grid-template-columns: 1fr 260px; gap: 16px; align-items: start; }
/* Profile page only (user-requested 2026-09-07) — Builds publics' own grid
   genuinely needs the full panel width for its multi-card rows, but a
   profile's build column is capped at 640px (.profile-builds above) so the
   shared 1fr left column left a wide dead gap before the 260px filters
   sidebar, pushing it far from the builds it filters. Capping the whole
   layout to fit builds+gap+sidebar exactly and centering it puts the two
   side by side with no dead space, instead of edge-to-edge across the
   panel. */
#view-profile .publicbuilds-layout { max-width: 920px; margin: 0 auto; }
#publicbuilds-filters { display: flex; flex-direction: column; gap: 14px; }
#publicbuilds-filters .field { display: flex; flex-direction: column; }
.pbf-class-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
/* background-position/-size come inline per button from WUI.classPortraitStyle
   (same sprite-sheet math the class picker itself uses) — only the chrome
   lives here. */
.pbf-class-btn {
  aspect-ratio: 1 / 1; border-radius: 6px; padding: 0; cursor: pointer;
  background-color: var(--bg-elevated); background-repeat: no-repeat;
  border: 1.5px solid var(--border); opacity: 0.55; filter: grayscale(0.6);
}
.pbf-class-btn:hover { border-color: var(--accent); opacity: 0.85; }
.pbf-class-btn.active { border-color: var(--accent); opacity: 1; filter: none; }
#publicbuilds-filters #pbf-reset { align-self: flex-start; }
@media (max-width: 860px) {
  .publicbuilds-layout { grid-template-columns: 1fr; }
}

.mc-total { color: var(--accent-2); font-weight: 800; margin-left: 6px; }

.stats-toggle-row { display: flex; gap: 8px; align-items: stretch; }
/* The active ("✓ ... incluses") label is bold and a bit longer than the
   inactive one, so at this button width it can wrap to a 2nd line while the
   inactive label stays on one — min-height + a fixed line-height reserves
   room for that 2nd line up front so toggling never visibly resizes the
   button. */
.stats-toggle-row > .btn { flex: 1; min-width: 0; min-height: 50px; padding-top: 4px; padding-bottom: 4px; line-height: 1.25; display: flex; align-items: center; justify-content: center; }
.stats-toggle-with-info { display: flex; flex: 1; min-width: 0; gap: 4px; }
.stats-toggle-with-info > .btn { flex: 1; min-width: 0; min-height: 50px; padding-top: 4px; padding-bottom: 4px; line-height: 1.25; display: flex; align-items: center; justify-content: center; }
.info-btn {
  position: relative; flex-shrink: 0; width: 30px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-elevated); border: 1.5px solid var(--border); border-radius: 6px;
  color: var(--text-dim); font-size: 0.9rem; cursor: help;
}
.info-btn:hover, .info-btn:focus { color: var(--accent); border-color: var(--accent); outline: none; }
/* The real .info-tip nested in each .info-btn (planner.js/calculator.js
   markup) is a data source ONLY, never shown directly — js/ui.js's shared
   `infoTip` portal copies its innerHTML out and renders that instead (see
   the long comment there for why: two rounds of user-reported bugs, both
   ultimately "a positioned ancestor traps z-index, even for
   position:fixed descendants — the only real escape is a single element
   parented straight on <body>", same technique as the established
   `tooltip` singleton for item/spell/sub hovers). */
.info-btn .info-tip { display: none; }
.shared-info-tip {
  position: fixed; width: 260px;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; font-size: 0.78rem; font-weight: 400; color: var(--text-dim); text-align: left;
  line-height: 1.55; box-shadow: 0 8px 24px rgba(0,0,0,0.5);
  opacity: 0; visibility: hidden; transform: translateY(4px); transition: opacity 0.15s, transform 0.15s;
  pointer-events: none; z-index: 600;
}
.shared-info-tip.active { opacity: 1; visibility: visible; transform: translateY(0); }
.shared-info-tip.compact { width: 200px; font-size: 0.7rem; text-transform: none; letter-spacing: normal; font-weight: 400; }
.shared-info-tip b { color: var(--text); }

.eg-row-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); margin: 8px 0 4px; }
.eg-row-label:first-of-type { margin-top: 0; }
.elemental-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.elemental-tile {
  display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 5px;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 7px; padding: 5px 4px;
}
.elemental-tile img { width: 18px; height: 18px; flex-shrink: 0; }
.elemental-tile .v { font-weight: 700; font-size: 0.9rem; color: var(--accent); }
.elemental-tile .v.neg { color: var(--danger); }
.elemental-tile .v-pct { margin-left: -2px; }
.elemental-tile.clickable { cursor: pointer; transition: opacity 0.12s, border-color 0.12s; }
.elemental-tile.clickable.mc-inactive { opacity: 0.45; }
.elemental-tile.clickable.mc-active { border-color: var(--accent); }

.stat-row-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px 14px; }
/* Column-major variant (Secondaire): fills the left column first, so the
   masteries stay together; plain row flow again wherever it's 1 column. */
.stat-row-grid-cols { grid-auto-flow: column; grid-template-rows: repeat(var(--rows, 6), auto); }
@container (max-width: 340px) {
  .stat-row-grid { grid-template-columns: 1fr; }
  .stat-row-grid-cols { grid-auto-flow: row; grid-template-rows: none; }
}
.stat-row { display: flex; align-items: center; gap: 6px; justify-content: space-between; font-size: 0.85rem; padding: 3px 0; border-bottom: 1px solid #ffffff0a; }
.stat-row .label-group { display: flex; align-items: center; gap: 6px; min-width: 0; }
.stat-row .label-group img { width: 16px; height: 16px; flex-shrink: 0; }
.stat-row .v { font-weight: 600; color: var(--accent); white-space: nowrap; }
.stat-row .v.neg { color: var(--danger); }
.stat-row.clickable { cursor: pointer; border-radius: 5px; padding-left: 4px; margin-left: -4px; transition: opacity 0.12s, background 0.12s; }
.stat-row.clickable:hover { background: #ffffff08; }
.stat-row.clickable.mc-inactive { opacity: 0.45; }
.stat-row.clickable.mc-active .label-group span { color: var(--accent-2); }

.slot-picker-hint { font-size: 0.78rem; color: var(--text-dim); margin: 0 0 10px; }

/* ---------- Aptitudes (stat points) ---------- */

.apt-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
.apt-category { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
.apt-category-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.apt-category-head h4 { margin: 0; font-size: 0.85rem; color: var(--accent); }
.apt-remaining { font-size: 0.75rem; color: var(--text-dim); display: flex; align-items: center; gap: 6px; }
.apt-remaining b { color: var(--accent-2); }
.apt-remaining b.neg { color: var(--danger); }
.apt-remaining input { padding: 2px 4px; font-size: 0.75rem; }
.apt-stat-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; border-bottom: 1px solid #ffffff0a; }
.apt-stat-row .stat-icon { width: 24px; height: 24px; }
.apt-stat-name { flex: 1; font-size: 0.78rem; color: var(--text-dim); }
.apt-stat-name small { color: var(--accent-2); font-weight: 600; }
.apt-stat-controls { display: flex; align-items: center; gap: 4px; }
.apt-stat-controls input { width: 48px; text-align: center; }
.apt-adj-btn {
  width: 24px; height: 24px; padding: 0; flex-shrink: 0;
  background: var(--bg-elevated); border: 1.5px solid var(--border); border-radius: 5px;
  font-weight: 800; font-size: 1rem; line-height: 1; cursor: pointer;
}
/* Structural (first/last button in the pair) rather than keying off the
   data-adj value, so "−" and "+" stay reliably color-coded regardless of
   which stat key they belong to. */
.apt-stat-controls .apt-adj-btn:first-child { color: #e5898f; border-color: #6b3a3f; }
.apt-stat-controls .apt-adj-btn:last-child { color: #7fe0a0; border-color: #3a6b4a; }
.apt-adj-btn:hover { filter: brightness(1.35); border-color: var(--accent); }
/* Desktop never needs this — the "Mode compact" toggle (and what it does)
   only makes sense on the narrow mobile layout, see the <=767px rules.
   `label.apt-compact-toggle` (not just `.apt-compact-toggle`) so this
   actually outranks .slide-toggle's own `display:inline-flex` — same
   class-vs-class, source-order tie this file keeps running into. */
label.apt-compact-toggle { display: none; margin-bottom: 10px; }

/* ---------- Chasse enchantments ---------- */

.chasse-header { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
/* Column now (was one long wrapping row) — user-requested (2026-09-05):
   the tier slider, its per-châsse cost, the running total, "Réarranger"
   and the code import/export row all crammed onto one line read as a
   cluttered scrap heap. Row 1 (.chasse-header-row) groups the "live"
   controls; the code row sits on its own line below. */
.chasse-header-right { display: flex; flex-direction: column; align-items: stretch; gap: 8px; margin-left: auto; }
.chasse-header-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Reusable iOS-style sliding switch (currently just "Sublis niveau max",
   but generic — .slide-toggle-label is optional). Slightly enlarged +
   given a hover/checked-label state (2026-09-06, user-requested visual
   pass) — the original 34×18 track with an always-text-dim label read as
   too small/muted next to the app's own accent-heavy styling elsewhere,
   and had no hover feedback at all (only :focus-visible). */
.slide-toggle { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font-size: 0.8rem; color: var(--text-dim); user-select: none; transition: color 0.15s; }
.slide-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.slide-toggle-track {
  position: relative; width: 38px; height: 21px; border-radius: 11px; flex-shrink: 0;
  background: var(--bg-elevated); border: 1px solid var(--border); transition: background 0.15s, border-color 0.15s;
}
.slide-toggle-thumb {
  position: absolute; top: 1px; left: 1px; width: 17px; height: 17px; border-radius: 50%;
  background: var(--text-dim); transition: transform 0.15s, background 0.15s;
}
.slide-toggle:hover .slide-toggle-track { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.slide-toggle input:checked + .slide-toggle-track { background: color-mix(in srgb, var(--accent) 30%, var(--bg-elevated)); border-color: var(--accent); }
.slide-toggle input:checked + .slide-toggle-track .slide-toggle-thumb { transform: translateX(17px); background: var(--accent); }
.slide-toggle input:checked ~ .slide-toggle-label { color: var(--text); font-weight: 600; }
.slide-toggle input:focus-visible + .slide-toggle-track { outline: 2px solid var(--accent); outline-offset: 1px; }
.chasse-cost-badge {
  background: var(--bg-elevated); border: 1px solid var(--accent-2); color: var(--accent-2);
  font-size: 0.8rem; font-weight: 600; padding: 4px 10px; border-radius: 999px; white-space: nowrap;
}

.chasse-layout { display: grid; grid-template-columns: 1fr 200px; gap: 10px; align-items: start; }
/* User-requested (2026-09-06): moved from the header's own wide inline row
   (nowrap, fixed 110px input) to sitting right under the palette instead —
   resized for that ~184px column: wraps onto its own lines rather than
   forcing everything into one row that no longer fits. */
.chasse-code-row-inline { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); flex-wrap: wrap; gap: 6px; }
.chasse-code-row-inline label { white-space: nowrap; }
.chasse-code-row-inline input { width: 100%; min-width: 0; flex: 1 1 100%; }

.chasse-palette { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; padding: 8px; }
/* User-requested (2026-09-06): toggles the palette between the real
   in-game color grouping and a Maîtrises/Reste/Résistances grouping. */
.chasse-palette-sort-row { display: flex; justify-content: flex-end; margin-bottom: 6px; }
.chasse-palette-sort-row .btn-icon { padding: 3px 7px; font-size: 0.85rem; }
.chasse-palette-group { display: flex; flex-direction: column; gap: 3px; margin-bottom: 6px; }
.chasse-palette-group:last-child { margin-bottom: 0; }
.chasse-palette-group-label { font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; }
.chasse-palette-group-label.red { color: #e05c5c; }
.chasse-palette-group-label.green { color: #5cd66f; }
.chasse-palette-group-label.blue { color: #4fc3e0; }
.chasse-palette-icons { display: flex; flex-direction: column; gap: 4px; }
.chasse-palette-chip {
  width: 100%; padding: 5px 6px; gap: 6px;
  background: var(--bg-panel); border: 1.5px solid var(--border); border-radius: 6px;
  cursor: pointer; color: var(--text); display: flex; align-items: center;
}
.chasse-palette-chip img { width: 22px; height: 22px; object-fit: contain; flex-shrink: 0; }
.chasse-palette-chip-label { font-size: 0.78rem; text-align: left; }
/* Which equipped-slot types get x2 on this stat — pushed to the chip's own
   right edge, small enough (14px) to read as a secondary hint rather than
   competing with the chip's own 22px stat icon. */
.chasse-palette-chip-doubled { margin-left: auto; display: flex; gap: 3px; flex-shrink: 0; opacity: 0.85; }
.chasse-palette-chip .chasse-palette-chip-doubled img,
.chasse-palette-chip .chasse-palette-chip-doubled svg { width: 20px; height: 20px; object-fit: contain; color: var(--text-dim); }
/* User-reported (2026-09-06): a 1.5px border tinted at 40% alpha was too
   faint to actually read as "this chip's châsse color" at a glance,
   especially now the category-sorted view puts different colors right next
   to each other in the same group. A solid, thicker left edge reads as a
   real color tag the way item-card's own rarity-color left border already
   does elsewhere in this app, backed by a faint same-color wash on the
   whole chip so it's not just a thin line easy to miss. */
.chasse-palette-chip.red { border-color: #e05c5c66; border-left: 4px solid #e05c5c; background: color-mix(in srgb, #e05c5c 10%, var(--bg-panel)); }
.chasse-palette-chip.green { border-color: #5cd66f66; border-left: 4px solid #5cd66f; background: color-mix(in srgb, #5cd66f 10%, var(--bg-panel)); }
.chasse-palette-chip.blue { border-color: #4fc3e066; border-left: 4px solid #4fc3e0; background: color-mix(in srgb, #4fc3e0 10%, var(--bg-panel)); }
.chasse-palette-chip:hover { border-color: var(--text-dim); }
/* User-reported (2026-09-06): once every resting chip also got a colored
   left border + faint tint (right above), the active one barely stood out
   from the rest any more — its own background was only a few % darker than
   theirs. A solid-color left edge (not just tinted) + a much stronger fill
   + an inset ring around the WHOLE chip now makes "this one's selected"
   unmistakable regardless of which color it happens to be. */
.chasse-palette-chip.active.red { border-color: #e05c5c; border-left-color: #e05c5c; background: #e05c5c2e; box-shadow: 0 0 0 1px #e05c5c inset; }
.chasse-palette-chip.active.green { border-color: #5cd66f; border-left-color: #5cd66f; background: #5cd66f2e; box-shadow: 0 0 0 1px #5cd66f inset; }
.chasse-palette-chip.active.blue { border-color: #4fc3e0; border-left-color: #4fc3e0; background: #4fc3e02e; box-shadow: 0 0 0 1px #4fc3e0 inset; }

.chasse-list { display: flex; flex-direction: column; gap: 4px; user-select: none; }
.chasse-row {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 7px; padding: 5px 8px;
  transition: border-color 0.1s, background 0.1s;
}
.chasse-row-double-hint { border-color: var(--accent-2); background: color-mix(in srgb, var(--accent-2) 8%, var(--bg-elevated)); }
.chasse-palette-chip-double-hint { border-color: var(--accent-2); background: color-mix(in srgb, var(--accent-2) 12%, var(--bg-elevated)); }
/* Slots with nothing equipped still show their 4 châsses and sub slot,
   fully paintable/selectable ahead of actually owning the gear — only the
   item label/icon is muted to signal "not equipped yet" (stats from an
   unequipped slot's châsses/sublimations don't count in the totals, see
   computeChasseTotals/subEffectsFor's own `if (!build.slots[slotId])`). */
.chasse-row-empty .chasse-item-label { opacity: 0.5; }
.chasse-item-label { display: flex; align-items: center; gap: 5px; width: 148px; flex-shrink: 0; font-size: 0.78rem; }
.chasse-item-icon-placeholder { width: 30px; height: 30px; border-radius: 5px; flex-shrink: 0; }
/* No background — same reasoning as .item-icon-frame above (user-reported
   2026-09-06): the rarity frame has no backdrop of its own. */
.chasse-item-icon-frame { position: relative; width: 30px; height: 30px; border-radius: 5px; flex-shrink: 0; }
.chasse-item-icon-frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.chasse-item-icon-frame img.frame { pointer-events: none; image-rendering: pixelated; }
.chasse-item-icon-placeholder {
  background: var(--bg-panel); border: 1px dashed var(--border);
  display: flex; align-items: center; justify-content: center; color: var(--text-dim);
}
.chasse-item-icon-placeholder img, .chasse-item-icon-placeholder svg { width: 60%; height: 60%; object-fit: contain; }
.chasse-item-icon-placeholder img { filter: grayscale(1) brightness(1.6) opacity(0.7); }
.chasse-item-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chasse-shards { display: flex; gap: 3px; flex-wrap: wrap; flex: 0 0 auto; user-select: none; }
.chasse-row-recap { flex-basis: 100%; font-size: 0.8rem; color: var(--text-dim); order: 10; }
.chasse-tier-picker { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
/* Same custom track/thumb treatment as the build-level and item-search
   level sliders, instead of the browser's plain default thumb — the 11
   tier graduations move onto the (now explicitly-sized) track itself so
   they still line up correctly with an unstyled thumb removed. */
.chasse-tier-picker input[type="range"] {
  width: 100px; height: 20px; margin: 0;
  -webkit-appearance: none; appearance: none; background: transparent; cursor: pointer;
}
.chasse-tier-picker input[type="range"]::-webkit-slider-runnable-track {
  height: 4px; border-radius: 2px;
  background: repeating-linear-gradient(to right,
    var(--text-dim) 0, var(--text-dim) 1px,
    var(--border) 1px, var(--border) calc((100% - 1px) / 10)
  );
}
.chasse-tier-picker input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--accent-text); cursor: pointer; margin-top: -6px;
}
.chasse-tier-picker input[type="range"]::-moz-range-track {
  height: 4px; border-radius: 2px;
  background: repeating-linear-gradient(to right,
    var(--text-dim) 0, var(--text-dim) 1px,
    var(--border) 1px, var(--border) calc((100% - 1px) / 10)
  );
}
.chasse-tier-picker input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--accent-text); cursor: pointer;
}
/* Fixed width ("T11" is the longest this ever gets, but keeping it pinned
   still stops the slider from shifting horizontally as the digit changes
   width while dragging). The per-châsse/total cost text moved out into
   .chasse-budget-detail (see below) — this only ever shows "T<n>" now. */
.chasse-tier-value { font-size: 0.7rem; color: var(--accent-2); font-weight: 700; white-space: nowrap; display: inline-flex; align-items: center; width: 26px; flex-shrink: 0; }
.eclat-icon { width: 14px; height: 14px; object-fit: contain; vertical-align: middle; }
/* Tier dial + its two éclat figures (cost per châsse at this tier, and the
   running total already spent) as one grouped panel — user-requested
   (2026-09-05) — instead of two disconnected inline scraps a reader had to
   guess were related. */
.chasse-budget-panel {
  display: flex; align-items: center; gap: 10px;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 12px;
}
.chasse-budget-sep { width: 1px; align-self: stretch; background: var(--border); flex-shrink: 0; }
/* min-width sized for the longest either line can realistically get
   ("496 600" — 40 shards at the top T11 tier, the actual maximum this
   build can ever reach — "[icon] éclats au total") — user-reported
   (2026-09-05): dragging the tier slider changed the digit count on both
   lines, which resized this column and visibly shifted "Réarranger" (and
   the whole row's wrap point) next to it on every tick. tabular-nums keeps
   individual digit widths from jittering too. */
.chasse-budget-detail { display: flex; flex-direction: column; gap: 2px; min-width: 172px; font-variant-numeric: tabular-nums; }
.chasse-budget-line { display: inline-flex; align-items: center; gap: 4px; font-size: 0.78rem; color: var(--text-dim); white-space: nowrap; }
.chasse-budget-total { color: var(--accent-2); font-weight: 700; }
.chasse-clear-btn {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
  font-size: 1rem; line-height: 1; padding: 4px 7px; color: var(--text-dim); flex-shrink: 0;
}
.chasse-clear-btn:hover:not(:disabled) { color: var(--danger); border-color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, var(--bg-elevated)); }
.chasse-clear-btn:disabled { opacity: 0.4; cursor: default; }

.chasse-sub-slot { flex: 1 1 187px; min-width: 153px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 6px; padding: 6px; font-size: 0.82rem; }
.chasse-sub-slot-empty { border-style: dashed; position: relative; padding: 3px 6px; display: flex; align-items: center; }
input.chasse-sub-search { width: 100%; padding: 3px 6px; font-size: 0.78rem; }
/* Osamodas's 10-slot creature loadout (real 5x2 grid) — icon-forward
   cards (icon on top, name below) instead of the sublimation pickers'
   list-row look, since a creature reads better as a portrait than a row. */
.osamodas-loadout-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; margin-top: 6px; }
.osamodas-loadout-slot {
  flex: none; min-width: 0; position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 3px; padding: 6px 2px; text-align: center; cursor: pointer;
}
.osamodas-loadout-slot.active { box-shadow: 0 0 0 2px var(--accent); }
.osamodas-loadout-slot.disabled { opacity: 0.45; cursor: not-allowed; }
.osamodas-loadout-slot .spell-name { font-size: 0.7rem; line-height: 1.15; white-space: normal; }
.osamodas-loadout-slot [data-loadout-remove] { position: absolute; top: 2px; right: 2px; padding: 0 4px; }
.osamodas-loadout-slot.chasse-sub-slot-empty { border-left: none; }
.osamodas-loadout-slot input.dedicated-sub-search { width: 100%; text-align: center; padding: 3px 2px; font-size: 0.78rem; }
/* Real monster-family icon, two layers: the real pale/shaded artwork
   underneath (osamodas-family-icon-base) plus a color-only tint on top
   (mix-blend-mode: color — recolors hue/saturation, keeps the base
   layer's own shading/detail intact, unlike a flat mask-image fill which
   replaced every pixel with one solid color and erased all detail). */
.osamodas-family-icon-wrap { position: relative; display: inline-block; flex-shrink: 0; width: 28px; height: 28px; }
.osamodas-family-icon-base { display: block; width: 100%; height: 100%; object-fit: contain; }
.osamodas-family-icon-tint {
  position: absolute; inset: 0; mix-blend-mode: color; opacity: 0.9; pointer-events: none;
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
}
/* Search popover — a real floating card (not inline-pushing the layout
   around it like the sublimation pickers' own dropdown does), grid of
   icon-forward result cards instead of list rows. */
.osamodas-picker-popover {
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%); margin-top: 4px;
  z-index: 40; width: 300px; max-width: calc(100vw - 24px); max-height: 320px;
  overflow-y: auto; overflow-x: hidden; box-sizing: border-box;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 10px 28px rgba(0,0,0,0.55); padding: 8px;
}
.osamodas-picker-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.osamodas-picker-card {
  aspect-ratio: 1; box-sizing: border-box; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 2px; overflow: hidden; padding: 4px 2px;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 6px;
  cursor: pointer; text-align: center; font-size: 0.62rem; line-height: 1.1; color: var(--text);
}
.osamodas-picker-card .osamodas-family-icon-wrap { width: 22px; height: 22px; }
.osamodas-picker-card span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.osamodas-picker-card:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--bg-panel)); }
.osamodas-picker-card .osamodas-picker-level { color: var(--text-dim); font-size: 0.58rem; }
.sub-pattern-swatch { display: inline-flex; gap: 2px; margin-top: 2px; vertical-align: middle; }
.pattern-shard-icon { width: 16px; height: 16px; object-fit: contain; }

/* Sublimation tooltip's illustrative range preview (Embuscade, Embuscade
   écartée, Longueur, Acribie) — a small standalone isometric grid, same
   diamond-tile look/colors as FightLab's own board (js/fightlab.js), scaled
   per sub (js/ui.js's subRangePreviewHtml) and centered under the
   description text rather than left-hugging the tooltip's own width. */
.sub-range-preview { position: relative; margin: 8px auto 0; }
.sub-range-tile {
  position: absolute;
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  background: var(--wk-cell-even);
}
.sub-range-tile.alt { background: var(--wk-cell-odd); }
.sub-range-tile.active { background: var(--wk-zone); }
.sub-range-caster-ring {
  position: absolute; transform: translate(-50%, -20%);
  border-radius: 50%; background: var(--wk-player-tile); opacity: 0.85; pointer-events: none;
}
.sub-range-caster {
  position: absolute; object-fit: contain;
  transform: translate(-50%, -75%); pointer-events: none;
}

/* Compact attached-sublimation row: name + level only, hover for the rest. */
.chasse-sub-slot-attached {
  display: flex; align-items: center; gap: 6px; cursor: pointer;
  border-left: 3px solid var(--text-dim); padding: 3px 6px;
}
.chasse-sub-slot-attached:hover { background: color-mix(in srgb, var(--accent) 6%, var(--bg-panel)); }
.chasse-sub-slot-attached.valid { border-left-color: #7ee08a; }
.chasse-sub-slot-attached.invalid { border-left-color: var(--danger); }
.chasse-sub-icon { width: 20px; height: 20px; border-radius: 4px; flex-shrink: 0; }
/* User-requested (2026-09-05): the required châsse colors now show next to
   the sub's name in this compact attached-slot row (previously only
   visible on hover) — same swatch as the search results list
   (.sub-pattern-swatch/patternSwatchHtml). Row, not column (was stacked
   below the name at first — moved to the right of it per follow-up
   feedback the same day): name truncates first, swatch stays fixed-width
   right after it. */
.sub-name-pattern-col { display: flex; flex-direction: row; align-items: center; flex: 1; min-width: 0; gap: 6px; }
.chasse-sub-slot-attached .spell-name { flex: 1; min-width: 0; font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chasse-sub-slot-attached .sub-pattern-swatch { flex-shrink: 0; margin-top: 0; }
.chasse-sub-slot-attached select { width: auto; flex-shrink: 0; margin: 0; padding: 2px 6px; font-size: 0.78rem; }
.chasse-sub-slot-attached .btn.danger { padding: 2px 8px; font-size: 0.85rem; line-height: 1.2; }
.spell-rank-badge {
  flex-shrink: 0; font-size: 0.78rem; font-weight: 600; color: var(--text-dim);
  border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px;
}

/* Picking a sublimation: the icon grows in with a little tilt settling at
   the end (same motif as the châsse pop-in), and the name types itself out
   letter by letter — both happen right where the search field was, instead
   of animating in FROM the dropdown. Removing one reverses both: the icon
   shrinks away and the letters erase themselves from the end backwards. */
@keyframes sub-icon-pop-in {
  0% { transform: scale(0) rotate(0deg); }
  55% { transform: scale(1.28) rotate(-10deg); }
  75% { transform: scale(0.92) rotate(5deg); }
  90% { transform: scale(1.05) rotate(-2deg); }
  100% { transform: scale(1) rotate(0deg); }
}
@keyframes sub-icon-pop-out {
  0% { transform: scale(1) rotate(0deg); opacity: 1; }
  100% { transform: scale(0) rotate(8deg); opacity: 0; }
}
.sub-icon-wrap.sub-anim-in { animation: sub-icon-pop-in 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); }
.sub-icon-wrap.sub-anim-out { animation: sub-icon-pop-out 0.38s ease-in; }

@keyframes sub-letter-in {
  0% { opacity: 0; transform: scale(0.85) translateY(2px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes sub-letter-out {
  0% { opacity: 1; transform: scale(1) translateY(0); }
  100% { opacity: 0; transform: scale(0.85) translateY(2px); }
}
.sub-name-letter { display: inline-block; transform-origin: bottom center; }
.sub-name-letter.sub-letter-in { animation: sub-letter-in 0.2s ease-out both; }
.sub-name-letter.sub-letter-out { animation: sub-letter-out 0.18s ease-in both; }

.sub-search-results {
  position: absolute; top: 100%; left: 0; right: 0; margin-top: 3px;
  max-height: 320px; overflow-y: auto; overflow-x: hidden;
  grid-template-columns: 1fr !important;
  gap: 4px !important;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px;
  padding: 6px; box-shadow: 0 8px 20px rgba(0,0,0,0.45); z-index: 30;
}
.sub-result-row { cursor: pointer; }
.sub-result-row:hover { border-color: var(--accent); background: var(--bg-elevated); }

@container (max-width: 480px) {
  .chasse-sub-slot { flex-basis: 100%; }
}

.shard-btn {
  position: relative; width: 34px; height: 34px; padding: 0; border: none; background: transparent; cursor: pointer;
  border-radius: 8px;
}
.shard-btn .shard-bg { width: 100%; height: 100%; object-fit: contain; }
.shard-btn .shard-stat-icon { position: absolute; bottom: 1px; left: 1px; width: 18px; height: 18px; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.8)); }
.shard-btn:hover .shard-bg, .shard-btn.open .shard-bg { filter: brightness(1.25); }
/* Wraps each shard button so a doubled-effect one can carry a small "X2"
   chip directly below it, in the chasse's own paint color. */
/* Badge overlaid on the shard's top-right corner (user-requested
   2026-09-24) instead of stacked below it, so doubled shards don't make
   their row taller. */
.shard-wrap { position: relative; display: flex; }
.shard-x2-badge {
  position: absolute; top: -4px; right: -5px; z-index: 2; pointer-events: none;
  padding: 0 3px; height: 11px; border-radius: 3px;
  background: var(--chasse-color); color: #0d1017;
  box-shadow: 0 0 0 1.5px var(--bg-elevated);
  font-size: 0.5rem; font-weight: 800; line-height: 11px; text-align: center;
}

/* Quick paint-in / clear-out flourish (see draw()'s pendingShardAnim in
   chasses.js) — grows from the center with a little tilt settling at the
   end when a stat is painted on; shrinks away then eases back to full size
   when a shard is cleared. Both end at the SAME normal, fully-visible
   scale/rotation on purpose — every shard slot stays in the DOM either way
   (an emptied one just shows its default placeholder icon instead of
   vanishing), so ending shard-pop-out invisible meant the moment the
   animation class was removed it had to snap straight back to visible,
   which read as an abrupt flash rather than a smooth clear. */
@keyframes shard-pop-in {
  0% { transform: scale(0) rotate(0deg); }
  60% { transform: scale(1.18) rotate(-6deg); }
  80% { transform: scale(0.96) rotate(3deg); }
  100% { transform: scale(1) rotate(0deg); }
}
@keyframes shard-pop-out {
  0% { transform: scale(1) rotate(0deg); }
  40% { transform: scale(0.25) rotate(9deg); }
  75% { transform: scale(0.85) rotate(-3deg); }
  100% { transform: scale(1) rotate(0deg); }
}
.shard-btn.shard-anim-in { animation: shard-pop-in 0.32s ease-out; }
.shard-btn.shard-anim-out { animation: shard-pop-out 0.4s ease-in-out; }

.chasse-editor {
  display: flex; gap: 8px; align-items: center; margin-top: 6px; padding-top: 8px;
  border-top: 1px dashed var(--border); width: 100%;
}
.chasse-editor select { flex: 1; }
.chasse-editor input { width: 60px; }
.chasse-editor-color { font-size: 0.78rem; color: var(--text-dim); white-space: nowrap; }

/* ---------- Damage calculator view ---------- */

.calc-subtitle { margin: 12px 0 8px; font-size: 1rem; color: var(--accent); font-weight: 600; }
.calc-subtitle:first-of-type { margin-top: 6px; }
.calc-subtitle-with-info, .calc-drawer-title-row { display: flex; align-items: center; gap: 8px; }
.calc-result-subtitle { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); font-weight: 400; }

.calc-drawer-overlay {
  position: fixed; inset: 0; background: rgba(5,6,10,0.55); z-index: 250;
  opacity: 0; visibility: hidden; transition: opacity 0.18s;
}
.calc-drawer-overlay.active { opacity: 1; visibility: visible; }

.calc-drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 92vw);
  background: var(--bg-panel); border-left: 1px solid var(--border);
  box-shadow: -12px 0 32px rgba(0,0,0,0.5);
  z-index: 260; padding: 12px 16px; overflow-y: auto;
  transform: translateX(100%); transition: transform 0.22s ease;
}
.calc-drawer.open { transform: translateX(0); }
.calc-drawer-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2px; }
.calc-drawer-header .btn { padding: 4px 10px; font-size: 1rem; line-height: 1; }
.calc-drawer-header h2.section-title { font-size: 1.25rem; margin: 0; }

/* Login/signup modal (user-requested 2026-09-06) — centered rather than a
   side drawer like .calc-drawer (shares its overlay/header classes, see
   index.html), since it's small, single-purpose, and a side-anchored panel
   would read oddly for a plain form like this. */
.account-modal {
  position: fixed; top: 50%; left: 50%; width: min(360px, 92vw);
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 16px 40px rgba(0,0,0,0.5); z-index: 260; padding: 14px 18px 18px;
  transform: translate(-50%, -50%) scale(0.96); opacity: 0; visibility: hidden;
  transition: transform 0.16s ease, opacity 0.16s ease, visibility 0.16s;
}
.account-modal.open { transform: translate(-50%, -50%) scale(1); opacity: 1; visibility: visible; }
.account-modal-tabs { display: flex; gap: 6px; margin: 10px 0 14px; }
.account-modal-tab { flex: 1; background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim); }
.account-modal-tab.active { background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 600; }
/* Same specificity-tie fix as .tab-btn.active/.view-dropdown-trigger.active
   above (user-reported 2026-09-07, screenshotted here): without this,
   hovering the already-active tab repainted its text to var(--accent) —
   the same color as its own background. */
.account-modal-tab.active:hover { color: var(--bg); }
.account-modal-form { display: flex; flex-direction: column; gap: 10px; }
/* This class's own `display:flex` (an author rule) otherwise beats the
   browser's default [hidden]{display:none} (a user-agent rule) regardless
   of matching specificity — author styles always win over UA defaults, so
   without this override both the login and signup forms rendered stacked
   on top of each other despite `.hidden = true` being set correctly. */
.account-modal-form[hidden] { display: none; }
.account-modal-form label { display: flex; flex-direction: column; gap: 4px; font-size: 0.78rem; color: var(--text-dim); }
.account-modal-form input { width: 100%; }
.pw-field-wrap { position: relative; display: block; }
.pw-field-wrap input { padding-right: 38px; }
/* Eye / crossed-out-eye line icons (2026-09-23, replacing the 👁/🙈 emoji):
   both SVGs live in the button, .showing (set by index.html's toggle
   handler while the password is visible) swaps which one is displayed. */
.pw-toggle-btn {
  position: absolute; top: 50%; right: 5px; transform: translateY(-50%);
  width: 28px; height: 28px; padding: 0; border: none; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; color: var(--text-dim); cursor: pointer;
  transition: color 0.15s, background-color 0.15s;
}
.pw-toggle-btn:hover, .pw-toggle-btn:focus-visible { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); outline: none; }
.pw-toggle-btn svg { width: 18px; height: 18px; }
.pw-toggle-btn .pw-icon-hide { display: none; }
.pw-toggle-btn.showing { color: var(--accent); }
.pw-toggle-btn.showing .pw-icon-show { display: none; }
.pw-toggle-btn.showing .pw-icon-hide { display: block; }
.account-modal-form button[type="submit"] {
  margin-top: 4px; background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 600;
}
.account-modal-form button[type="submit"]:hover { filter: brightness(1.08); border-color: var(--accent); color: var(--bg); }
.account-modal-error {
  background: color-mix(in srgb, var(--danger) 16%, transparent); border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
  color: var(--text); border-radius: 6px; padding: 6px 10px; font-size: 0.8rem;
}
.account-modal-note { font-size: 0.78rem; color: var(--text-dim); margin: -2px 0 0; }
#calc-spell-preview .spell-effect, #calc-result .spell-effect { padding: 6px 8px; line-height: 1.4; }
.calc-field { margin-bottom: 6px; }
.calc-field label { display: block; font-size: 0.78rem; color: var(--text-dim); margin-bottom: 2px; }
.calc-field select, .calc-field input[type="number"] { width: 100%; padding: 4px 7px; }
.calc-row { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.calc-check { display: flex; align-items: center; gap: 6px; font-size: 0.85rem; margin-bottom: 5px; }
.calc-check input { width: auto; }
/* 3-way theme switcher (Paramètres panel) — user-requested (2026-09-06):
   "un bouton en 3 parties où on peut switch direct de thèmes avec des cases
   colorées correspondant à la couleur du thème". One segmented control,
   each segment showing a live diagonal-split swatch of that theme's own
   background/accent (see js/ui.js's THEME_SWATCH_COLORS) rather than a
   plain text label. */
.theme-switcher { display: flex; gap: 6px; margin-top: 4px; }
.theme-switch-btn {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 8px 6px; background: var(--bg-elevated); border: 1.5px solid var(--border); border-radius: 8px;
  cursor: pointer; color: var(--text-dim); font-size: 0.78rem;
  transition: border-color 0.15s, color 0.15s;
}
.theme-switch-btn:hover { border-color: var(--accent); color: var(--text); }
.theme-switch-btn.active { border-color: var(--accent); color: var(--text); font-weight: 600; box-shadow: 0 0 0 1px var(--accent) inset; }
.theme-swatch { width: 32px; height: 32px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.15); }
.calc-section-label { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); margin: 6px 0 4px; }
.calc-section-label:first-child { margin-top: 0; }
/* Checkbox row that also carries a trailing info button — keeps the (i)
   pinned to the row's right edge instead of crowding the label text, and
   (since .info-tip anchors right:0 off the button) keeps the tooltip's
   260px width inside the drawer instead of spilling past its left edge. */
.calc-check-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.calc-check-row .calc-check { margin-bottom: 0; }
.calc-row-with-info { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.calc-row-with-info .calc-row { flex: 1; }
.calc-row-with-info .info-btn { margin-top: 18px; }

.calc-steps { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.calc-step { display: flex; justify-content: space-between; font-size: 0.85rem; padding: 5px 8px; background: var(--bg-elevated); border-radius: 5px; }
.calc-step .k { color: var(--text-dim); }
.calc-final { text-align: center; padding: 10px; background: var(--bg-elevated); border: 1px solid var(--accent); border-radius: 8px; }
.calc-final .num { font-size: 1.8rem; font-weight: 800; color: var(--accent-2); }
.calc-final .lbl { font-size: 0.8rem; color: var(--text-dim); margin-top: 2px; }
.calc-note { font-size: 0.75rem; color: var(--text-dim); line-height: 1.5; margin-top: 8px; }
.dmg-line { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.dmg-badge { color: var(--accent-2); font-weight: 700; white-space: nowrap; }
/* Recoil/self-damage lines (e.g. Sang brûlant's "Retour de flamme") — a cost
   to the caster, not damage dealt to the target, so visually distinct from
   the normal accent-colored damage badges instead of reading as more hits
   against the enemy. */
.dmg-badge.self { color: var(--danger); font-weight: 600; }

/* ---------- Combo Maker ---------- */
.local-tab-bar { display: flex; gap: 6px; }
.local-tab-btn {
  background: var(--bg-panel); border: 1px solid var(--border); color: var(--text-dim);
  padding: 5px 12px; border-radius: 6px; cursor: pointer; font-size: 0.8rem;
  transition: color 0.15s, background-color 0.15s, border-color 0.15s;
}
.local-tab-btn:hover { color: var(--text); }
.local-tab-btn.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }
.fightlab-tab-btn { padding: 5px 12px; display: flex; align-items: center; justify-content: center; }
.fightlab-tab-btn img { height: 15px; width: auto; display: block; }

.combo-top-columns { display: flex; gap: 16px; align-items: flex-start; margin-top: 4px; }
.combo-col-spells { flex: 1 1 33%; min-width: 0; }
.combo-col-settings { flex: 2 1 67%; min-width: 0; }
@media (max-width: 640px) {
  .combo-top-columns { flex-direction: column; }
}
/* Compact, grouped together (not the shared .stat-tiles grid's auto-fit
   stretch, which was spacing PA/PM/PW far apart across this narrower
   column) — just 3 small badges side by side under the spell picker. */
#combo-resources .stat-tiles { display: flex; justify-content: flex-start; gap: 6px; }
#combo-resources .stat-tile { flex: 0 0 auto; padding: 6px 10px; }
.combo-slot-subnote { font-size: 0.56rem; color: var(--accent-2); }
/* All 12 in one row (shrinks to fit rather than wrapping) — a chevron in
   the gap between each pair (not after the last) shows cast order left to
   right, since that's the whole point of a *sequential* slot list. */
.combo-slots { display: flex; flex-wrap: nowrap; gap: 20px; margin-top: 8px; overflow-x: auto; padding-bottom: 4px; }
.combo-slot {
  flex: 1 1 0; min-width: 0; width: 0; min-height: 108px; padding: 6px 3px 4px;
  border: 1.5px dashed color-mix(in srgb, var(--text-dim) 55%, var(--border)); border-radius: 7px;
  background: var(--bg-elevated); position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 2px; text-align: center;
}
.combo-slot.filled { border-style: solid; border-color: var(--border); }
.combo-slot.over-cap { border-color: var(--danger); }
.combo-slot.empty { cursor: pointer; justify-content: center; color: var(--text-dim); font-size: 1.2rem; }
.combo-slot.empty:hover { border-color: var(--accent); color: var(--accent); }
.combo-slot-index { position: absolute; top: 2px; left: 4px; font-size: 0.56rem; color: var(--text-dim); }
.combo-slot .remove-x { position: absolute; top: -1px; right: -1px; background: var(--danger); color: #fff; width: 14px; height: 14px; border-radius: 50%; font-size: 0.6rem; line-height: 14px; text-align: center; cursor: pointer; border: none; padding: 0; }
/* wakassets spell icons carry a faint light border baked into the image
   itself — scaled up and clipped by the wrapper's overflow so that edge
   falls outside the visible circle instead of ringing every icon. */
.combo-slot-icon-wrap { width: 28px; height: 28px; border-radius: 6px; overflow: hidden; background: var(--bg-panel); flex-shrink: 0; }
.combo-slot-icon { width: 100%; height: 100%; object-fit: cover; transform: scale(1.18); }
.combo-slot:not(:last-child)::after {
  content: "›"; position: absolute; top: 50%; right: -14px; transform: translateY(-50%);
  color: var(--text-dim); font-size: 1rem; font-weight: 700; line-height: 1; pointer-events: none;
}
.combo-slot-cost { display: flex; gap: 6px; font-size: 0.7rem; color: var(--text-dim); flex-wrap: wrap; justify-content: center; }
.combo-slot-value { display: flex; align-items: center; gap: 3px; font-size: 0.78rem; }
.combo-slot-value.self-dmg { color: var(--danger); }
.combo-slot-total { font-size: 0.9rem; font-weight: 800; color: var(--accent-2); }
.combo-proc-btn {
  font-size: 0.58rem; width: 100%; margin: 2px 0 0; padding: 2px 4px; line-height: 1.3;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; color: var(--text-dim); cursor: pointer;
}
.combo-proc-btn:hover { color: var(--text); border-color: var(--text-dim); }
.combo-proc-btn.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }

.spell-icon-grid { display: flex; flex-wrap: wrap; gap: 5px; }
.spell-icon-btn {
  position: relative; width: 34px; height: 34px; padding: 0; border-radius: 7px;
  background: var(--bg-elevated); border: 1.5px solid var(--border); cursor: pointer;
}
.spell-icon-btn img { width: 100%; height: 100%; object-fit: contain; border-radius: 7px; }
.spell-icon-btn:hover { border-color: var(--accent); }
.spell-icon-btn.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }

.spell-icon-wrap { position: relative; flex-shrink: 0; }
/* Sorts innés have no real gfx id (synthetic entries, see
   innateSpellCardHtml) — a lettered badge stands in for the icon. */
.spell-icon-placeholder {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 45%, var(--bg-elevated));
  border: 1px solid var(--border); color: #fff; font-weight: 800; font-size: 0.8rem;
}
.spell-ratio-badge {
  position: absolute; bottom: -3px; right: -5px; z-index: 1;
  font-size: 0.55rem; font-weight: 800; line-height: 1; white-space: nowrap;
  padding: 2px 3px; border-radius: 4px; background: var(--bg-elevated); border: 1px solid var(--border);
}
.spell-ratio-badge.dmg { color: var(--accent-2); }
.spell-ratio-badge.heal { color: #7ee08a; }
.spell-ratio-badge small { font-size: 0.75em; font-weight: 600; opacity: 0.8; }

.spell-pick-item {
  display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 0.82rem;
}
.spell-pick-item:hover { background: var(--bg-elevated); }
.spell-pick-item img { width: 24px; height: 24px; border-radius: 4px; }
.spell-pick-item.active { background: var(--bg-elevated); outline: 1px solid var(--accent); }

/* ---------- Classes & spells view ---------- */

.class-nav {
  display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 18px;
}
.class-btn {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  width: 70px; padding: 8px 4px;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 8px;
  color: var(--text-dim); cursor: pointer; font-size: 0.7rem;
}
.class-btn img { width: 32px; height: 32px; object-fit: contain; }
.class-nav-portrait { display: block; width: 32px; height: 32px; border-radius: 6px; background-repeat: no-repeat; }
.class-btn:hover { border-color: var(--accent); color: var(--text); }
.class-btn.active { border-color: var(--accent); background: var(--bg-elevated); color: var(--accent); font-weight: 600; }

/* min-content columns + justify-content:center shrink-wrap the 3x6 grid to
   its buttons' own (now smaller) size and center that block horizontally,
   instead of stretching each button to fill a 1/3-width column — the gap
   between buttons stays exactly 8px either way. align-content:center then
   centers the rows vertically too, so the whole grid sits centered in
   whatever space is left under the drawer's header instead of pinned to
   the top — small enough now that all 6 rows fit without scrolling. */
.class-drawer { display: flex; flex-direction: column; }
.class-nav-portraits {
  display: grid; grid-template-columns: repeat(3, min-content);
  justify-content: center; align-content: center;
  gap: 8px; flex: 1; min-height: 0;
}
.class-btn.portrait { width: 96px; padding: 6px; font-size: 0.78rem; }
.class-btn.portrait .portrait-sprite { display: block; width: 100%; aspect-ratio: 1 / 1; border-radius: 6px; background-repeat: no-repeat; }

/* Class picker drawer — same slide-in mechanics as .calc-drawer, mirrored
   to the left edge instead of the right. */
.class-drawer-overlay { z-index: 240; }
.class-drawer {
  left: 0; right: auto; z-index: 250;
  border-left: none; border-right: 1px solid var(--border);
  box-shadow: 12px 0 32px rgba(0,0,0,0.5);
  transform: translateX(-100%);
}
.class-drawer.open { transform: translateX(0); }

#class-group-tabs { display: flex; gap: 6px; }
#class-group-tabs .tab-btn {
  background: var(--bg-panel); border: 1px solid var(--border); color: var(--text-dim);
  padding: 7px 14px; border-radius: 6px; cursor: pointer; font-size: 0.85rem;
}
#class-group-tabs .tab-btn:hover { color: var(--text); }
#class-group-tabs .tab-btn.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }

.spell-element-title {
  display: flex; align-items: center; gap: 8px; font-size: 0.95rem; color: var(--accent);
  margin: 18px 0 10px;
}
.spell-element-title img { width: 22px; height: 22px; }
.spell-element-title:first-child { margin-top: 0; }

.spell-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
.spell-card { position: relative; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 8px; padding: 12px; }
#epic-relic-panel { margin-top: 12px; }
.epic-relic-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; margin-top: 4px; }
.dedicated-sub-slot {
  background: var(--bg-panel); border: 1px solid var(--border); border-left-width: 3px; border-radius: 6px; position: relative;
}
.dedicated-sub-slot-empty { border-style: dashed; display: flex; flex-direction: column; gap: 4px; padding: 6px; }
.dedicated-sub-slot-empty .pill { align-self: flex-start; font-size: 0.7rem; }
.dedicated-sub-search { width: 100%; padding: 3px 6px; font-size: 0.78rem; }

@container (max-width: 340px) {
  .epic-relic-grid { grid-template-columns: 1fr; }
}

.sub-icon-wrap { position: relative; flex-shrink: 0; }
.sub-icon-wrap img.sub-type-icon {
  position: absolute; bottom: -3px; right: -3px; width: 15px !important; height: 15px !important; z-index: 1;
  border-radius: 50%; background: var(--bg-elevated); border: 1px solid var(--border); padding: 1px; object-fit: contain;
}
.sub-icon-wrap-sm { display: inline-flex; }
.sub-icon-wrap-sm img.sub-type-icon { width: 11px !important; height: 11px !important; bottom: -2px; right: -2px; }

.sub-category-filter { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.sub-cat-toggle {
  width: auto; height: auto; padding: 5px 12px; border-radius: 999px;
  font-size: 0.8rem; font-weight: 600; color: var(--rarity-color); border-color: var(--border);
}
.sub-cat-toggle.active { border-color: var(--rarity-color); background: color-mix(in srgb, var(--rarity-color) 18%, transparent); box-shadow: 0 0 0 1px var(--rarity-color) inset; }
.sub-category-badges { display: flex; flex-wrap: wrap; gap: 4px; margin: 4px 0; }
.sub-cat-pill { font-size: 0.65rem; border: 1px solid currentColor; border-radius: 999px; padding: 1px 8px; }
.spell-head { display: flex; align-items: flex-start; gap: 10px; }
.spell-head img { width: 40px; height: 40px; border-radius: 6px; background: var(--bg-elevated); border: 1px solid var(--border); flex-shrink: 0; }
.spell-head-info { flex: 1; min-width: 0; }
.spell-name { font-weight: 600; font-size: 0.95rem; }
.spell-costs { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
.spell-cost { display: flex; align-items: center; gap: 3px; font-size: 0.78rem; color: var(--text-dim); }
.spell-cost img { width: 15px; height: 15px; }
.spell-rank-select { width: 100%; margin: 8px 0; }
.spell-crit-toggle {
  flex-shrink: 0; width: 26px; height: 26px; padding: 0; border-radius: 6px;
  background: var(--bg-elevated); border: 1px solid var(--border); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.spell-crit-toggle img { width: 16px; height: 16px; background: none; border: none; border-radius: 0; }
.spell-crit-toggle:hover { border-color: var(--accent-2); }
.spell-crit-toggle.active { background: var(--accent-2); border-color: var(--accent-2); }
.spell-zone-btn {
  flex-shrink: 0; width: 26px; height: 26px; padding: 0; border-radius: 6px;
  background: var(--bg-elevated); border: 1px solid var(--border); cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-size: 0.85rem; line-height: 1;
}
.spell-zone-btn:hover { border-color: var(--accent-2); }
.spell-effect { font-size: 0.83rem; line-height: 1.5; color: var(--text); background: var(--bg-elevated); border-radius: 6px; padding: 8px 10px; }
.spell-effect img { height: 16px; vertical-align: -3px; margin: 0 2px; }
/* Hoverable spell/state references inside effect text (js/glossary.js) —
   the nested tooltip. A dotted underline + accent colour cues
   that there's more to see on hover; help cursor confirms it. */
.effect-ref { color: var(--accent); text-decoration: underline dotted; text-underline-offset: 2px; cursor: help; }
.effect-ref:hover { text-decoration-style: solid; }

/* ---------- Build spell deck (js/spelldeck.js) ---------- */

.spell-deck-section { margin-bottom: 14px; }
.spell-deck-heading { margin: 0 0 8px; font-size: 0.85rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; }
.spell-deck-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; max-width: 460px; }
.spell-deck-grid.passifs { grid-template-columns: repeat(6, 1fr); max-width: 460px; }
.deck-slot {
  position: relative; aspect-ratio: 1; border-radius: 8px;
  background: var(--bg-elevated); border: 2px dashed var(--border);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: border-color 0.12s, transform 0.1s;
}
.deck-slot:hover { border-color: var(--accent); transform: translateY(-1px); }
.deck-slot.filled { border-style: solid; border-color: var(--border); }
.deck-slot.filled:hover { border-color: var(--accent); }
/* Combo Maker's spell picker lists the whole class, not just the deck —
   this marks the ones actually in the build's own "Deck de sorts" so they
   still stand out from the rest of the class list. */
.deck-slot.filled.in-deck { border-color: var(--accent-2); box-shadow: 0 0 0 1px var(--accent-2) inset; }
/* FightLab's spell bar — a spell too expensive/on cooldown/over its
   per-turn cap for right now, not clickable. */
.deck-slot.disabled { opacity: 0.35; cursor: not-allowed; }
.deck-slot.disabled:hover { border-color: var(--border); transform: none; }
.deck-slot img { width: 78%; height: 78%; object-fit: contain; border-radius: 5px; pointer-events: none; }
.deck-slot-empty { color: var(--text-dim); font-size: 1.3rem; font-weight: 300; pointer-events: none; }
.deck-slot-remove {
  position: absolute; top: -6px; right: -6px;
  background: var(--danger); color: #fff; width: 16px; height: 16px; border-radius: 50%;
  font-size: 0.65rem; line-height: 16px; text-align: center; cursor: pointer; display: none;
}
.deck-slot.filled:hover .deck-slot-remove { display: block; }
@media (max-width: 767px) { .deck-slot-remove { display: block; } }
.deck-slot.drag-over { border-color: var(--accent); border-style: solid; box-shadow: 0 0 0 2px var(--accent) inset; }
/* FightLab implementation-status badge (user-requested 2026-09-08) — a
   small, always-visible (not hover-gated like deck-slot-remove above)
   corner mark so it reads at a glance, opposite corner from the remove
   button so the two never overlap. */
.deck-slot-status {
  position: absolute; bottom: -5px; right: -5px;
  width: 15px; height: 15px; border-radius: 50%;
  font-size: 0.6rem; line-height: 15px; text-align: center; font-weight: 700;
  color: #fff; pointer-events: none; box-shadow: 0 0 0 2px var(--bg, #1a1a1f);
}
.deck-slot-status.ok { background: var(--success, #3ca55c); }
.deck-slot-status.gap { background: var(--danger, #c0392b); }
.deck-slot-status.unsure { background: var(--warning, #d68a1a); }

/* 3 columns: deck slots, the clicked spell's description, and the full
   spell library to pick from — all visible at once, no modal in between. */
.spell-deck-layout { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }
.spell-deck-left { flex: 1 1 320px; min-width: 280px; }
.spell-deck-detail { flex: 1 1 260px; min-width: 240px; align-self: stretch; display: flex; flex-direction: column; }
.spell-deck-detail .spell-card { margin: 0; }
/* Pinned to the very bottom of this column (not just after the card in
   normal flow) — margin-top:auto only pushes down as far as .spell-deck-
   library's own height (align-self:stretch above) actually reaches. */
.spell-calc-btn { margin-top: auto; align-self: stretch; }
.spell-deck-library { flex: 1.4 1 360px; min-width: 300px; max-height: 640px; overflow-y: auto; padding-right: 4px; }

/* Spell library — grouped like the in-game/Gearfu browser: Sorts
   Élémentaires and Sorts Neutres side by side, then Passifs Généraux and
   the class's own Passifs each spanning full width below, each icon
   toggling in/out of the deck (and always previewable in .spell-deck-detail). */
.lib-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; margin-bottom: 12px; }
.lib-element-row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.lib-element-icon { width: 20px; height: 20px; flex-shrink: 0; }
.lib-spell-row { display: flex; flex-wrap: wrap; gap: 6px; }
.lib-spell-row.wrap { margin-bottom: 12px; }
/* Each element's own row, fixed at 5 across rather than however many a
   plain wrap fits at the current width. */
.lib-element-row .lib-spell-row { display: grid; grid-template-columns: repeat(5, 42px); }
/* Sorts Neutres sits in the right half of .lib-columns (half the library's
   own width) — a plain wrap wound up fitting 5 across there, cramped next
   to the elements column; a fixed 3-column grid reads much cleaner at that
   width. */
.lib-columns .lib-spell-row.wrap { display: grid; grid-template-columns: repeat(3, 42px); }
.lib-spell-tile {
  position: relative; width: 42px; height: 42px; border-radius: 7px; flex-shrink: 0;
  background: var(--bg-elevated); border: 2px solid var(--border); padding: 0; cursor: pointer;
  transition: border-color 0.12s, transform 0.1s;
}
.lib-spell-tile:hover { border-color: var(--accent); transform: translateY(-1px); }
.lib-spell-tile img { width: 100%; height: 100%; object-fit: contain; border-radius: 5px; pointer-events: none; }
.lib-spell-tile.selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.lib-spell-check {
  position: absolute; bottom: -5px; right: -5px;
  width: 15px; height: 15px; border-radius: 50%; background: var(--accent); color: var(--accent-text);
  font-size: 0.6rem; font-weight: 800; line-height: 15px; text-align: center; pointer-events: none;
}

/* Replaces spellCardHtml's plain rank <select> in the deck's own detail
   column (see attachLevelSlider in spelldeck.js) — the select itself stays
   in the DOM (display:none) still driving the card's own update() via the
   same change event, this is purely a nicer control bound to it. */
.spell-level-slider { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
.spell-level-slider input[type="range"] { flex: 1; accent-color: var(--accent); }
.spell-level-slider-label { font-size: 0.78rem; color: var(--text-dim); white-space: nowrap; }

footer.site-footer {
  text-align: center; color: var(--text-dim); font-size: 0.75rem; padding: 30px 20px 40px;
}
footer.site-footer a { color: var(--text-dim); text-decoration: underline; }

/* Fixed bottom-right legal links (user-requested 2026-09-06) — small and
   unobtrusive, but always reachable without scrolling to the footer.
   z-index kept below modals/overlays (lowest of the position:fixed values
   in this file) so it never blocks anything opened on top of it. */
.legal-corner-links {
  position: fixed; right: 10px; bottom: 8px; z-index: 50; width: 215px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px; font-size: 0.78rem; color: var(--text-dim);
  opacity: 0.85; transition: opacity 0.15s;
}
/* Same layout as the "Objets épinglés" tray (2026-10-25, user): panel, uppercase title over a rule, left-aligned. */
.legal-corner-title { align-self: stretch; padding-bottom: 8px; margin-bottom: 4px; border-bottom: 1px solid var(--border); }
.legal-corner-links:hover { opacity: 1; }
/* On the builder, under the "Objets épinglés" tray: same left edge (planner.js applyPinnedPanelWidth sets --pinned-left). */
body:has(#view-builder.active) .legal-corner-links.docked { left: var(--pinned-left); right: auto; }
.legal-corner-links a { color: var(--text-dim); text-decoration: none; align-self: flex-end; }
.legal-corner-links a:hover { color: var(--text); text-decoration: underline; }
/* Stacked links + the non-affiliation notice (2026-10-25, user) */
.legal-corner-note { margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); font-size: 0.68rem; line-height: 1.4; text-align: right; align-self: stretch; color: var(--text-dim); }
@media (max-width: 700px) {
  .legal-corner-links { display: none; }
}

/* Standalone legal pages (confidentialite/conditions/mentions-legales.html,
   Phase 4, 2026-09-06) — same page background/text tokens as the app shell,
   just a plain readable column instead of the builder layout. */
.legal-page { max-width: 760px; margin: 0 auto; padding: 40px 20px 60px; line-height: 1.6; }
.legal-page h1 { font-size: 1.5rem; margin: 0 0 4px; }
.legal-page .legal-updated { color: var(--text-dim); font-size: 0.8rem; margin-bottom: 28px; }
.legal-page h2 { font-size: 1.1rem; color: var(--accent); margin: 32px 0 10px; }
.legal-page p { margin: 0 0 12px; color: var(--text); }
.legal-page ul { margin: 0 0 12px; padding-left: 22px; }
.legal-page li { margin-bottom: 6px; }
.legal-page a { color: var(--accent); }
.legal-page .legal-note {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  padding: 12px 14px; font-size: 0.85rem; color: var(--text-dim); margin: 20px 0;
}
.legal-page .legal-back { display: inline-block; margin-bottom: 20px; font-size: 0.85rem; }

@media (max-width: 860px) {
  .db-layout, .calc-layout, .chasse-layout, .craftprofit-layout { grid-template-columns: 1fr; }
  .layout-panel { flex-basis: 100% !important; }
  .filters { position: static; max-height: none; }
  #db-grid, #build-picker-grid { position: static; max-height: none; }
  .picker-wrap { flex-direction: column; }
  .mini-stats { position: static; max-height: none; width: auto; }
  .builder-layout { flex-direction: column; }
}

/* ---------- Bug report (js/bugreport.js) ---------- */
body.bugreport-picking, body.bugreport-picking * { cursor: crosshair !important; }
.bugreport-highlight {
  position: fixed; z-index: 500; display: none; pointer-events: none;
  border: 2px solid var(--danger); background: color-mix(in srgb, var(--danger) 15%, transparent);
  border-radius: 4px; left: 0; top: 0; transition: transform 0.05s, width 0.05s, height 0.05s;
}
.bugreport-highlight.active { display: block; }
.bugreport-hint {
  position: fixed; top: 0; left: 0; right: 0; z-index: 501; display: none;
  text-align: center; padding: 8px; font-size: 0.85rem; font-weight: 600;
  background: color-mix(in srgb, var(--danger) 82%, #000); color: #fff; pointer-events: none;
}
.bugreport-hint.active { display: block; }
.bugreport-target-preview { margin: 10px 0; }
.bugreport-target-item { position: relative; margin-bottom: 8px; }
.bugreport-target-item:last-child { margin-bottom: 0; }
.bugreport-target-label { font-size: 0.7rem; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
.bugreport-target-code {
  display: block; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 34px 6px 8px; font-size: 0.8rem; word-break: break-all;
}
.bugreport-target-remove {
  position: absolute; top: 22px; right: 6px; width: 20px; height: 20px; line-height: 1;
  background: none; border: none; color: var(--text-dim); cursor: pointer; font-size: 1rem;
}
.bugreport-target-remove:hover { color: var(--danger); }
.bugreport-desc {
  width: 100%; resize: vertical; margin-top: 10px; padding: 8px 10px; font-family: var(--font); font-size: 0.85rem;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; color: var(--text);
}

/* ================================================================
   Mobile-only builder layout (<=767px). Purely additive: every rule
   here lives inside the media query (or, for the 2 drawer buttons,
   defaults to display:none outside it) so nothing here ever reaches
   a desktop viewport — the wider layout above is untouched.
   ================================================================ */
.mobile-drawer-btn {
  display: none; position: fixed; left: 10px; z-index: 230;
  width: 42px; height: 42px; border-radius: 50%; align-items: center; justify-content: center;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text);
  font-size: 1.15rem; box-shadow: 0 4px 14px rgba(0,0,0,0.45); cursor: pointer; padding: 0;
}
/* Bottom-anchored rather than up near the topbar — sat right on top of the
   first panel's own rounded corner up there, and was awkward for a thumb
   to reach one-handed anyway. */
#mobile-build-btn { bottom: 76px; }
#mobile-filters-btn { bottom: 20px; }
.mobile-drawer-overlay {
  position: fixed; inset: 0; background: rgba(5,6,10,0.55); z-index: 220;
  opacity: 0; visibility: hidden; transition: opacity 0.18s; display: none;
}
/* Inline in the picker's sticky header (not floating like the 2 drawer
   buttons above) — desktop never shows it, see the <=767px override. */
.compact-cards-toggle-btn { display: none; }
.mobile-drawer-overlay.active { opacity: 1; visibility: visible; }

@media (max-width: 767px) {
  /* Touch has no :hover, so a filled slot's × (only ever shown on hover on
     desktop) was simply unreachable on mobile without first tapping into
     the picker. Shown outright here on any filled slot, not just the
     picker doll's active one — hovering precisely enough to reveal it isn't
     a concern on a screen this size the way it is on the cramped mini doll. */
  .slot.filled .remove-x { opacity: 1; transform: scale(1); pointer-events: auto; }

  /* Closed slide-in drawers that pre-date this breakpoint (the damage
     calculator, the class picker) sit just off-screen via `transform`, not
     `display:none` — that still counts toward the page's scrollable width
     at ANY viewport size (their full desktop width, ~420px, past the
     right edge), just never mattered until now because it stayed too
     small a fraction of a wide screen to produce a visible scrollbar. On a
     375px phone it's most of another screen's worth of phantom horizontal
     scroll. Clipped here rather than touching those drawers themselves. */
  html, body { overflow-x: hidden; }

  /* Deck de sorts library (found during a full feature sweep, 2026-09-07):
     .lib-columns splits "Sorts élémentaires"/"Sorts neutres" into two fixed
     side-by-side halves, each containing a spell-tile grid pinned to a
     fixed pixel width (5×42px / 3×42px, see the desktop rules above) —
     together wider than a 375px phone. Since html/body clip overflow-x
     instead of scrolling (the rule right above this one), "Sorts neutres"
     wasn't just cramped, it was entirely invisible and unreachable on
     mobile. Stacking to one column and letting each tile grid wrap by
     available width instead of a fixed count fixes both column widths at
     once without touching the desktop layout above. */
  .lib-columns { grid-template-columns: 1fr; }
  .lib-element-row .lib-spell-row, .lib-columns .lib-spell-row.wrap {
    grid-template-columns: repeat(auto-fill, 42px);
  }
  /* .lib-element-row is a flex row (icon + this grid) — a grid child with
     no definite width can't resolve auto-fill against anything and
     collapses to a single track (verified live: only 1 tile per row
     instead of several), so this needs an explicit flex-basis to size
     against. */
  .lib-element-row .lib-spell-row { flex: 1; min-width: 0; }

  /* Topbar: 4 nav labels ("Planificateur de build" alone is 22 characters)
     plus the logo just don't fit a ~375px phone at their desktop size —
     they wrapped mid-word before this. Shrunk down and let the tab row
     scroll horizontally on its own rather than wrap, so the bar itself
     always stays one line tall regardless of exactly how narrow the
     phone is. */
  header.topbar { padding: 5px 10px; gap: 8px; flex-wrap: wrap; }
  header.topbar .logo-img { height: 22px; }
  nav.tabs { overflow-x: auto; flex-wrap: nowrap; gap: 2px; }
  nav.tabs button { padding: 5px 8px; font-size: 0.78rem; white-space: nowrap; flex-shrink: 0; }
  /* Quick-nav wraps to its own full-width row below the logo/tab/dropdown
     row rather than squeezing 4 more labels into that same line — same
     "scroll instead of wrap mid-word" treatment as nav.tabs otherwise gets,
     just given its own line since there isn't room to share one on a phone. */
  nav.quick-nav { flex: 1 1 100%; order: 3; gap: 2px; }
  /* User-requested (2026-09-01): "Deck de sorts"/"FightLab" used to sit in
     this same horizontally-SCROLLING row as Équipement/Stats/Aptitudes
     (overflow-x:auto above), so they were off-screen to the right — still
     visually "at the very top" of the page, just hidden past the fold
     instead of scrolled to. Wrapping instead means the 6 buttons (DOM
     order: Équipement, Stats, Aptitudes, Deck de sorts, FightLab,
     Recherche — index.html) flow onto as many lines as needed, so "Deck de
     sorts"/"FightLab" land below Aptitudes as soon as they no longer fit
     the first line, always reachable without a horizontal scroll. */
  nav.quick-nav { flex-wrap: wrap; overflow-x: visible; }
  .quick-nav-btn { padding: 5px 8px; font-size: 0.76rem; white-space: nowrap; flex-shrink: 0; }
  .mobile-drawer-btn { display: flex; }
  /* #mobile-filters-btn is JS-controlled (openSlotPicker/closeSlotPicker,
     planner.js), only meant to be visible while the item picker itself is
     open — but a fresh page load never runs either of those, so this
     class's own blanket `flex` above was the only rule in effect, leaving
     it shown everywhere (even outside the equip flow entirely — e.g.
     overlapping FightLab's own gauge column, which happens to render text
     in the same bottom-left corner). Overridden back to hidden by default
     here; the picker's inline `style.display = ""` still wins over this
     when it actually opens, same as it already did over the desktop
     default. #mobile-build-btn has no such toggle — it's meant to stay a
     permanent shortcut regardless of view, so it keeps the plain rule. */
  #mobile-filters-btn { display: none; }
  .mobile-drawer-overlay { display: block; }

  /* FightLab's class-resource gauges (Insaisissable/Affûtage/Précision,
     Fureur, etc.) wrap onto their own row below the board on a narrow
     screen (.fightlab-bottom's 3 columns don't fit side by side any more)
     — landing them left-aligned right where #mobile-build-btn's own fixed
     42px circle already sits (10-52px from the left edge), so its opaque
     background covered the first few letters of each gauge's name while
     scrolling past that band. Padding pushes the whole gauge list clear of
     it instead of only fixing this one label. */
  .fightlab-class-resources { padding-left: 56px; }

  /* Real bug: on touch devices a tap leaves the tapped element stuck in
     :hover (sticky-hover) until the user taps elsewhere. "Autres vues" also
     picks up .active while its own view is open, and button.btn:hover
     (style.css ~line 233, general/desktop rule) has one more specificity
     point than .view-dropdown-trigger.active (extra "button" type
     selector), so its color: var(--accent) wins over the active rule's
     color: var(--bg) — text becomes the exact same purple as the button's
     own background and disappears entirely. Verified live: computed color
     matched computed background-color after a tap. Scoped override here
     (3 classes beats button+class+pseudo) restores the readable dark-on-
     accent text without touching the desktop-applicable rules themselves. */
  .view-dropdown-trigger.active:hover, .view-dropdown-trigger.active:active { color: var(--bg); }

  /* The 260px desktop min-width (there to keep a panel legible when 2-3
     sit side by side on a wide screen) was the actual reason everything
     below looked cramped — on a ~335px-wide phone, with exactly one panel
     per row anyway, it was clamping every panel ~75px narrower than the
     screen actually allows before a single slot's own sizing even entered
     the picture.
     `flex` (the shorthand, !important) rather than just `flex-basis` —
     planner.js's applyLayoutWidths sets the whole `flex` shorthand inline
     per the user's saved width preset (e.g. "flex: 1 0 calc(33% - 6px)"
     for a panel set to Étroit); overriding only the flex-basis longhand
     left flex-grow/shrink from that shorthand still in effect, which was
     enough on its own to collapse the panel back down to a sliver. */
  .layout-panel { min-width: 0; flex: 1 1 100% !important; }
  /* The existing <=860px rule sets flex-direction:column on this container
     to stack panels — but that makes `flex-basis` (and the 100% above)
     apply to HEIGHT instead of width, since column mode swaps the main
     axis. With no explicit height, every panel's "100%" resolved against
     basically nothing, collapsing them all to a sliver. Row (the default)
     plus flex-wrap:wrap already stacks 100%-basis items one per row on its
     own, correctly, along the axis that actually matters here. */
  .builder-layout { flex-direction: row; }
  .doll, .doll-compact { padding-left: 0; padding-right: 0; gap: 6px; }
  .panel { padding-left: 10px; padding-right: 10px; }

  /* Élément priority ("Ordre maîtrise" / "Ordre résistances"): fixed at a
     168px desktop width, sized to sit in the narrow gutter beside the
     doll — on a phone that gutter doesn't exist (the doll already takes
     the full row), so it fell to its own line below at that same cramped
     168px instead of using the space actually available. All 8 buttons
     (4 + 4) now sit in one row across the panel's full width, the two
     groups told apart by a wider gap between them instead of separate
     stacked blocks with their own heading line each. */
  .elem-priority-inline { width: 100%; display: flex; align-items: flex-start; justify-content: center; gap: 18px; }
  .elem-priority-group { margin-bottom: 0; }
  .elem-priority-group h5 { white-space: nowrap; }

  /* These same panels already have a "narrow panel" @container(max-width)
     fallback (for when a panel's set to Étroit/Moyen on a wide desktop
     screen) — but each one's plain, unconditional base rule was defined
     LATER in this file than that container query, so at equal specificity
     it always won regardless of actual width, on desktop too. A full
     4/5-equal-column grid stayed forced into a ~300px-wide mobile panel,
     which is what was actually producing the squeezed/overlapping look.
     Fixed the same way as everywhere else here: redeclared inside this
     block so it wins by simply coming last, without touching the shadowed
     rules (or their own reordering) at all. */
  .elemental-grid { grid-template-columns: repeat(2, 1fr) !important; }
  .apt-grid { grid-template-columns: 1fr !important; }
  .chasse-layout { grid-template-columns: 1fr !important; }
  /* .spell-grid (Sublimations list, sub-search-results, class spell picker —
     shared class, see sublimations.js/classes.js/chasses.js) stayed at its
     desktop repeat(5, 1fr) here too, forcing 5 equal ~200px-min tracks into
     a 375px screen — real horizontal page overflow confirmed live
     (body.scrollWidth 1079px vs innerWidth 375px), truncating every card's
     title/description on the right edge. Sublimation cards carry full
     paragraphs of description text, so 1 column (not 2) is what actually
     stays readable at phone width. */
  .spell-grid { grid-template-columns: 1fr !important; }

  /* Aptitudes "Mode compact" — on by default (WSettings' aptCompactMobile),
     toggle only shown here since it only ever does anything at this width.
     Drops each stat row's name, leaving icon + −/value/+ only; the name
     was the single biggest reason this list ran so long on a phone. */
  label.apt-compact-toggle { display: inline-flex; }
  .apt-compact .apt-stat-name { display: none; }
  .apt-compact .apt-stat-controls { margin-left: auto; }
  /* Intelligence+Force and Agilité+Chance paired up 2-per-row (CATEGORIES'
     own object order in aptitudes.js is intelligence, force, agilite,
     chance, majeur — a plain 2-column grid lands them in exactly these
     pairs with no extra markup needed). Majeur (always last) spans both
     columns instead, and its own stat list becomes 2 columns in turn —
     it's the longest category, most in need of the width. */
  .apt-compact .apt-grid { grid-template-columns: repeat(2, 1fr) !important; gap: 10px 12px; }
  .apt-compact .apt-category:last-child {
    grid-column: 1 / -1;
    display: grid; grid-template-columns: repeat(2, 1fr); column-gap: 12px; align-content: start;
  }
  .apt-compact .apt-category:last-child .apt-category-head { grid-column: 1 / -1; }

  /* Item search "cartes compactes" — on by default (WSettings' compactCards)
     — 2 cards per row instead of 1, made possible by a smaller card overall
     (tighter padding/icons) and a smaller stat-line font rather than
     abbreviating the stat names themselves (see .card-effect-list li font-
     size below; .stat-label-short — see resolvedEffectLiHtml in ui.js — is
     unused here but kept for any other consumer). Toggled by
     #compact-cards-toggle-btn (hidden on desktop by default, see .btn-icon
     rules above). */
  .compact-cards-toggle-btn { display: inline-flex; }
  body.compact-cards #build-picker-grid .item-grid,
  body.compact-cards #build-picker-grid .item-grid.has-stats-cards {
    /* minmax(0, 1fr), not plain 1fr — plain 1fr is really minmax(auto, 1fr),
       so a card with wider min-content (e.g. a long name) could still force
       its own track wider than the other despite both being "1fr". The
       explicit 0 floor removes that, forcing genuinely equal columns. */
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 6px;
  }
  /* Full stat names again (not the abbreviated .stat-label-short) — the
     abbreviations themselves were the complaint; a smaller font that still
     spells "Maîtrise Distance" out in full is preferred over "Dist" fitting
     more comfortably. Wraps onto a 2nd line rather than truncating when a
     long label still doesn't fit even at this size. */
  /* Tighter padding/icons/text to actually fit two per row at this width. */
  body.compact-cards #build-picker-grid .item-card { padding: 6px; }
  body.compact-cards #build-picker-grid .card-header { gap: 6px; }
  body.compact-cards #build-picker-grid .item-card .name { font-size: 0.82rem; }
  body.compact-cards #build-picker-grid .item-card .sub { font-size: 0.7rem; }
  body.compact-cards #build-picker-grid .card-effect-list li { padding: 1px 4px; font-size: 0.6rem; line-height: 1.25; }
  body.compact-cards #build-picker-grid .card-effect-list .stat-icon { width: 15px; height: 15px; }
  body.compact-cards #build-picker-grid .item-card-badges { position: static; flex-direction: row; margin: 4px 0 0; }
  body.compact-cards #build-picker-grid .item-card-badge { font-size: 0.62rem; padding: 1px 4px; }

  /* Enchantements (Chasses): the header's toggle + tier slider + cost
     badge, and each row's item label, were all fighting for space on one
     line and losing — "Sublis niveau max" alone wrapped 3 words deep, and
     every row (label, 4 shards, clear button, recap line) got squeezed or
     stacked with no rhyme to it. Each control gets its own full-width line
     in the header; each row keeps the label on its own line but lets the
     small shard buttons + clear button share the next one, which is
     compact without the "flex-direction:column" that used to also split
     those apart onto separate lines for no reason. */
  .chasse-header-right { flex-direction: column; align-items: stretch; width: 100%; gap: 10px; }
  .chasse-tier-picker { width: 100%; }
  .chasse-tier-picker input[type="range"] { flex: 1; width: auto; }
  /* Was: label (own line) / shards (own line) / attached-sub row (own
     line) / clear button, alone, on a 4th line — one extra line per row
     just for a single trash icon, times up to 10 rows. Reordered (DOM
     stays untouched, only paint order moves) so the clear button shares
     the label's own first line instead, and everything below it stays
     full-width on its own line as before. */
  /* flex-direction:row explicitly, not just left at its default — an
     older @container(max-width:480px) rule (fires off this panel's own
     ~325px width, independent of this viewport media query) still sets
     column here, which was quietly overriding every `order` below back to
     "each on its own line" regardless. */
  .chasse-row { flex-direction: row; align-items: flex-start; gap: 4px; padding: 6px; }
  .chasse-item-label { order: 1; flex: 1 1 auto; width: auto; min-width: 0; }
  /* User-requested (2026-09-01): the 4 châsses used to drop to their own
     full-width line below the name (flex-basis:100%), wasting the row's
     right side that the short "Anneau 2 (vide)"-style names never filled.
     Kept inline here instead — .chasse-item-label's flex:1 1 auto plus
     .chasse-item-name's existing overflow-ellipsis (style.css ~line 1880)
     shrinks the name first when space is tight, same as it already did
     next to the clear button on this same line. */
  .chasse-shards { order: 2; flex-basis: auto; flex-shrink: 0; }
  .chasse-clear-btn { order: 3; }
  .chasse-row-recap { order: 4; }
  .chasse-sub-slot { order: 5; flex-basis: 100%; width: 100%; min-width: 0; }

  /* The stat-chip palette at the bottom (Rouge/Vert/Bleu) is sized for the
     desktop's narrow ~200px sidebar column — one chip per line, full width.
     Stretched across a phone's full ~330px panel width, each one-word chip
     was wasting most of its own line. 2 per line fits every label
     (Initiative's the longest) without truncating. */
  .chasse-palette-icons { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; }
  .chasse-palette-chip { width: auto; }


  /* Touch targets: .btn-icon (close/favorites in the picker's sticky
     header, the topbar's bug-report/settings buttons, etc.) sized down to
     ~30px tall on their normal padding — under the ~44px a thumb needs to
     reliably hit. */
  .btn-icon { padding: 11px 13px; }

  /* Equipment doll: exactly 2 rows of 7 slots (14 total) instead of one
     endlessly horizontally-scrolling row — the main reason this breakpoint
     exists at all, since that scroll is awkward with a thumb. Slot size
     becomes a share of the row's own width instead of a fixed 64/50px so
     it always fits regardless of phone width. */
  .doll, .doll-compact { flex-wrap: wrap; overflow-x: visible; }
  .doll .doll-slot-col, .doll-compact .doll-slot-col,
  .doll-compact .slot {
    flex: 0 0 calc((100% - 6 * 6px) / 7); width: auto; min-width: 0;
  }
  .doll .slot, .doll-compact .slot { width: 100%; height: auto; aspect-ratio: 1; }
  .doll .slot .slot-placeholder, .doll-compact .slot .slot-placeholder { width: 55%; height: 55%; }
  .doll .slot img.icon, .doll-compact .slot img.icon { inset: 4px; width: calc(100% - 8px); height: calc(100% - 8px); }
  /* Per-slot element-priority toggles (hat/cape/ring with a random-element
     bonus) need ~46px just for their own 2x2 grid — wider than a single
     7-per-row slot has to give, which was forcing that one column wider
     than its neighbors and throwing the whole grid's alignment off. The
     build's global "Ordre Maîtrise/Résistances" panel already covers the
     same setting; the rarer per-slot override just isn't reachable from
     this cramped a grid. */
  .doll .doll-slot-toggles { display: none; }
  .picker-sticky-header { flex-wrap: wrap; }
  .picker-sticky-header .doll-compact { flex-basis: 100%; }

  /* Section order: équipement, stats, aptitudes, then everything else —
     overrides only the CSS `order` (never touching the user's own saved
     drag-reorder, which moves the actual DOM nodes and takes priority on
     desktop) so this doesn't fight that preference, just sets a sane
     default read order for a phone screen. */
  .layout-panel[data-layout-id="equipment"] { order: 1; }
  .layout-panel[data-layout-id="build-stats"] { order: 2; }
  .layout-panel[data-layout-id="aptitudes"] { order: 3; }
  .layout-panel[data-layout-id="chasses"] { order: 4; }

  /* Build management: the desktop floating sidebar (display:none below
     1300px already) becomes a left-edge slide-in drawer instead, reachable
     through #mobile-build-btn. */
  /* !important on display/width: planner.js sets both as plain inline
     styles on this same element for desktop purposes (updateBuildVisibility
     hides it while scrolled with the picker open, applyLayoutWidths sizes
     it to the user's chosen preset) — a stylesheet !important still beats a
     plain inline style, which is the only way this drawer treatment can win
     without touching that JS at all. */
  .builder-float-sidebar {
    display: block !important; position: fixed; top: 0; left: 0; bottom: 0; margin: 0;
    /* top/bottom alone should auto-size height to the viewport, but with
       `display:flex` + `overflow-y:auto` in the mix that never actually
       kicked in here — the drawer just grew to its full content height
       (unreachably, since a `position:fixed` box isn't part of page
       scroll), so it's pinned explicitly instead. */
    height: 100vh;
    width: min(300px, 85vw) !important; max-height: none; z-index: 235; padding: 16px; box-sizing: border-box;
    overflow-y: auto; background: var(--bg-panel); border-right: 1px solid var(--border);
    box-shadow: 12px 0 32px rgba(0,0,0,0.5);
    transform: translateX(-100%); transition: transform 0.22s ease;
  }
  .builder-float-sidebar.mobile-open { transform: translateX(0); }

  /* Item search: same treatment for its filters column — a drawer instead
     of stacking above the results (the results grid gets the full width
     back once the filters are taken out of flow). */
  .picker-inner .filters {
    position: fixed; top: 0; left: 0; bottom: 0; margin: 0;
    height: 100vh;
    width: min(300px, 85vw); max-height: none; z-index: 235; padding: 16px; box-sizing: border-box;
    overflow-y: auto; background: var(--bg-panel); border-right: 1px solid var(--border);
    box-shadow: 12px 0 32px rgba(0,0,0,0.5);
    transform: translateX(-100%); transition: transform 0.22s ease;
  }
  .picker-inner .filters.mobile-open { transform: translateX(0); }

  /* Build switcher popover: its own JS (positionSwitcherPopover, planner.js)
     now caps the popover's width to the viewport on a narrow screen instead
     of the desktop-only 520px it used to force regardless — but the item
     strip inside each row was still `flex-wrap: nowrap`, sized for that
     wider box, so it just overflowed sideways instead of shrinking to fit.
     Wraps onto a 2nd line here instead. */
  .build-switcher-row-items { flex-wrap: wrap; }

  /* Combo Maker's slot strip (.combo-slots) is a single nowrap flex row on
     desktop — each of the up-to-12 .combo-slot cells is `flex: 1 1 0`, so on
     a 375px screen they got squeezed down to a sliver each (icon, cost
     badges and the › separator all crammed into a few px), reported by the
     user as "trop aplati". Wraps into a 4-per-row grid here instead, with
     smaller cells so the icon/cost text stay legible — the desktop-only ›
     separator (.combo-slot::after) is suppressed since "next in the grid"
     no longer reads left-to-right once a row wraps (it would visually point
     at nothing, or at the row below, once a slot is last-in-row). */
  .combo-slots { display: grid; grid-template-columns: repeat(4, 1fr); flex-wrap: unset; gap: 8px 6px; overflow-x: visible; }
  .combo-slot { flex: unset; width: auto; min-width: 0; min-height: 88px; padding: 4px 2px 3px; }
  .combo-slot:not(:last-child)::after { display: none; }
  .combo-slot-icon-wrap { width: 24px; height: 24px; }
  .combo-slot-cost { gap: 3px; font-size: 0.6rem; }
  .combo-slot-value { font-size: 0.62rem; gap: 2px; }
  .combo-slot-index { font-size: 0.5rem; }

  /* Stats panel: "Combat" and "Secondaire" (planner.js renderStats, always
     the 3rd/4th .stats-group under #build-stats — Principal and Maîtrises
     et Résistances come first) side by side, user-requested (2026-09-01),
     matching how Aptitudes already pairs Intelligence/Force this way. Each
     one's own .stat-row-grid already collapses to a single column below —
     that @container(max-width:340px) rule (style.css ~line 1748) reads
     .layout-panel's own full width, not these boxes' halved width, so it's
     unaffected by making them inline-block here. */
  #build-stats > .stats-group:nth-of-type(3), #build-stats > .stats-group:nth-of-type(4) {
    display: inline-block; vertical-align: top; box-sizing: border-box; width: calc(50% - 4px);
  }
  #build-stats > .stats-group:nth-of-type(3) { margin-right: 8px; }

  /* Secondaire's 11 rows read (in DOM order) Maîtrise Critique, Résistance
     Critique, Maîtrise Dos, Résistance Dos, Maîtrise Mêlée, Armure donnée,
     Maîtrise Distance, Armure reçue, Maîtrise Soin, Dommages indirects,
     Maîtrise Berserk — desktop's 2-col grid fills row-major, which happens
     to put every Maîtrise in the left column and everything else in the
     right column, but the single-column mobile view above flattens that
     into one interleaved list. User-requested (2026-09-01): all 6 masteries
     consecutive, in this order: Critique, Mêlée, Distance, Soin, Dos,
     Berserk. Reordered with `order` only (DOM/desktop untouched) — the
     grid is still 1 column here so `order` alone fully controls the
     visual sequence, the rest (résistances/armures/dommages indirects)
     just keep their original relative order after the masteries. */
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(1) { order: 1; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(3) { order: 2; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(4) { order: 3; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(5) { order: 4; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(2) { order: 5; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(6) { order: 6; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(7) { order: 7; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(8) { order: 8; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(9) { order: 9; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(10) { order: 10; }
  #build-stats > .stats-group:nth-of-type(4) .stat-row:nth-child(11) { order: 11; }
}

/* ---------- Coût du build (planner.js buildCostGroupHtml) ---------- */

.build-cost-total { display: flex; align-items: center; gap: 8px; }
.build-cost-total img { width: 22px; height: 22px; object-fit: contain; }
.build-cost-total-value { font-size: 1.25rem; font-weight: 700; color: #c98a3e; font-variant-numeric: tabular-nums; }
.build-cost-total-count { margin-left: auto; font-size: 0.78rem; color: var(--text-dim); }
.build-cost-sub { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 4px; font-size: 0.78rem; color: var(--text-dim); }
.build-cost-sub b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.build-cost-note { margin-top: 6px; font-size: 0.7rem; color: var(--text-dim); font-style: italic; }
.build-cost-details { margin-top: 8px; }
.build-cost-details summary { cursor: pointer; font-size: 0.78rem; color: var(--accent); user-select: none; }
.build-cost-head, .build-cost-row {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) 62px 62px; gap: 6px; align-items: center;
}
.build-cost-head { margin-top: 6px; padding: 0 4px; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-dim); }
.build-cost-head span:nth-child(n+3) { text-align: right; }
.build-cost-list { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.build-cost-row { padding: 2px 4px; border-radius: 5px; font-size: 0.76rem; }
.build-cost-row .recipe-ingredient-icon { width: 22px; height: 22px; }
.build-cost-row:hover { background: var(--bg-elevated); }
.build-cost-row.copied { background: color-mix(in srgb, var(--accent) 25%, transparent); }
.build-cost-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.build-cost-val { text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.build-cost-val.best { color: #c98a3e; font-weight: 700; }

/* ---------- Comparaison de builds (planner.js openBuildCompare) ---------- */

/* Each side carries its own class color (--a-rgb / --b-rgb, set inline on
   .bc-wrap by renderBuildCompare), reused by the header cards, the winning
   values, the diverging bars and the score strip. */
.modal.build-compare-modal { max-width: 860px; }
.bc-wrap { --a-rgb: 148, 130, 201; --b-rgb: 79, 195, 224; }
.bc-title { margin: 0 0 14px; font-size: 1.15rem; }
.bc-hint { font-size: 0.75rem; color: var(--text-dim); font-style: italic; margin: 8px 0 0; }

/* Header: two build cards facing each other, VS/swap in the middle. */
.bc-pickers { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 12px; align-items: stretch; }
.bc-side {
  --side-rgb: var(--a-rgb);
  position: relative; min-width: 0; display: flex; flex-direction: column; gap: 10px;
  padding: 12px; border-radius: 10px;
  border: 1px solid rgba(var(--side-rgb), 0.45);
  background: linear-gradient(160deg, rgba(var(--side-rgb), 0.22), rgba(var(--side-rgb), 0.04) 70%), var(--bg-elevated);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}
.bc-side-b { --side-rgb: var(--b-rgb); }
.bc-side-corner {
  position: absolute; top: -16px; right: -14px; width: 44px; height: 44px; z-index: 2;
  pointer-events: none; object-fit: contain; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6));
}
.bc-side-corner[data-class="18"] { top: -17px; } /* Élio: 1px higher, as on the Build panel */
.bc-side-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
.bc-side-letter {
  flex-shrink: 0; width: 22px; height: 22px; border-radius: 6px; display: grid; place-items: center;
  font-size: 0.78rem; font-weight: 800; color: #fff;
  background: rgb(var(--side-rgb)); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25) inset;
}
.bc-side-portrait.build-row-portrait { width: 46px; height: 46px; flex-shrink: 0; border-radius: 9px; box-shadow: 0 0 0 2px rgba(var(--side-rgb), 0.6); }
.bc-side-id { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.bc-side-name { font-weight: 800; font-size: 0.98rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bc-side-meta { display: flex; align-items: center; gap: 6px; font-size: 0.78rem; }
.bc-side-class { font-weight: 700; color: color-mix(in srgb, rgb(var(--side-rgb)) 55%, var(--text)); }
.bc-side-level {
  font-size: 0.7rem; font-weight: 800; padding: 1px 7px; border-radius: 999px;
  background: rgba(var(--side-rgb), 0.22); border: 1px solid rgba(var(--side-rgb), 0.45);
}
.bc-side select { width: 100%; min-width: 0; font-size: 0.8rem; padding: 5px 8px; background: var(--bg-panel); }
.bc-swap {
  align-self: center; display: flex; flex-direction: column; align-items: center; gap: 2px;
  width: 46px; padding: 8px 0; border-radius: 999px; cursor: pointer;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim);
  font-weight: 800; font-size: 0.78rem; letter-spacing: 0.06em;
  transition: border-color 0.15s, color 0.15s, transform 0.2s;
}
.bc-swap .ui-icon { width: 16px; height: 16px; }
.bc-swap:hover { border-color: var(--accent); color: var(--text); }
.bc-swap:hover .ui-icon { transform: rotate(180deg); transition: transform 0.25s; }

/* Score strip: how many rows each side wins. */
.bc-score { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px; margin: 16px 0 0; }
.bc-score-n { font-size: 1.1rem; font-weight: 800; font-variant-numeric: tabular-nums; min-width: 24px; text-align: center; }
.bc-score-a { color: color-mix(in srgb, rgb(var(--a-rgb)) 65%, var(--text)); }
.bc-score-b { color: color-mix(in srgb, rgb(var(--b-rgb)) 65%, var(--text)); }
.bc-score-bar { display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--bg-elevated); gap: 2px; }
.bc-score-bar i { display: block; height: 100%; transition: width 0.3s; }
.bc-score-fill-a { background: linear-gradient(90deg, rgba(var(--a-rgb), 0.6), rgb(var(--a-rgb))); border-radius: 999px 0 0 999px; }
.bc-score-fill-b { background: linear-gradient(90deg, rgb(var(--b-rgb)), rgba(var(--b-rgb), 0.6)); border-radius: 0 999px 999px 0; }
.bc-score-label { text-align: center; font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); margin-top: 4px; }

/* Stat table: label | A | diverging bar | B. */
.bc-table { margin-top: 6px; }
.bc-group {
  display: flex; align-items: center; gap: 8px; margin: 14px 0 4px; padding: 0 6px;
  font-size: 0.7rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent);
}
.bc-group::after { content: ""; flex: 1; height: 1px; background: linear-gradient(90deg, var(--border), transparent); order: 1; }
.bc-group small { order: 2; font-size: 0.66rem; font-weight: 600; letter-spacing: 0.02em; text-transform: none; color: var(--text-dim); }
.bc-row {
  display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.9fr) minmax(120px, 1.1fr) minmax(0, 0.9fr);
  gap: 10px; align-items: center; padding: 4px 6px; border-radius: 6px; font-size: 0.82rem;
}
.bc-row:nth-child(even of .bc-row) { background: color-mix(in srgb, var(--bg-elevated) 45%, transparent); }
.bc-row:hover { background: var(--bg-elevated); }
.bc-label { display: flex; align-items: center; gap: 7px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bc-label img { width: 18px; height: 18px; object-fit: contain; flex-shrink: 0; }
.bc-val { font-variant-numeric: tabular-nums; color: var(--text-dim); white-space: nowrap; }
.bc-val-a { text-align: right; }
.bc-val-b { text-align: left; }
.bc-val small { color: var(--text-dim); font-size: 0.7rem; font-weight: 400; }
.bc-val.win { font-weight: 800; }
.bc-val-a.win { color: color-mix(in srgb, rgb(var(--a-rgb)) 60%, var(--text)); }
.bc-val-b.win { color: color-mix(in srgb, rgb(var(--b-rgb)) 60%, var(--text)); }
.bc-bar { position: relative; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.bc-bar-track { position: relative; display: flex; width: 100%; height: 6px; border-radius: 999px; background: var(--bg-elevated); }
.bc-bar-track::after { content: ""; position: absolute; left: 50%; top: -2px; bottom: -2px; width: 1px; background: var(--border); }
.bc-bar-half { flex: 1; display: flex; }
.bc-bar-a { justify-content: flex-end; }
.bc-bar-half i { display: block; height: 100%; transition: width 0.3s; }
.bc-bar-a i { background: linear-gradient(270deg, rgba(var(--a-rgb), 0.5), rgb(var(--a-rgb))); border-radius: 999px 0 0 999px; }
.bc-bar-b i { background: linear-gradient(90deg, rgba(var(--b-rgb), 0.5), rgb(var(--b-rgb))); border-radius: 0 999px 999px 0; }
.bc-bar-delta { font-size: 0.66rem; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text-dim); line-height: 1; }
.bc-bar.to-a .bc-bar-delta { color: color-mix(in srgb, rgb(var(--a-rgb)) 60%, var(--text)); }
.bc-bar.to-b .bc-bar-delta { color: color-mix(in srgb, rgb(var(--b-rgb)) 60%, var(--text)); }

/* Equipment: A item | slot | B item, same items faded. */
.bc-items { display: flex; flex-direction: column; gap: 3px; }
.bc-item-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 96px minmax(0, 1fr); gap: 10px; align-items: center;
  padding: 4px 6px; border-radius: 6px; font-size: 0.8rem;
  background: color-mix(in srgb, var(--bg-elevated) 70%, transparent);
}
.bc-item-row.same { opacity: 0.45; background: transparent; }
.bc-slot { text-align: center; color: var(--text-dim); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; }
.bc-item { display: flex; align-items: center; gap: 7px; min-width: 0; }
.bc-item-a { flex-direction: row-reverse; text-align: right; }
.bc-item.empty { color: var(--text-dim); }
.bc-item-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bc-item-row:not(.same) .bc-item-a:not(.empty) { box-shadow: inset -2px 0 0 rgba(var(--a-rgb), 0.8); padding-right: 6px; }
.bc-item-row:not(.same) .bc-item-b:not(.empty) { box-shadow: inset 2px 0 0 rgba(var(--b-rgb), 0.8); padding-left: 6px; }

@media (max-width: 560px) {
  .bc-pickers { grid-template-columns: minmax(0, 1fr); }
  .bc-swap { flex-direction: row; width: auto; padding: 4px 14px; justify-self: center; }
  .bc-row { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.8fr) 64px minmax(0, 0.8fr); gap: 6px; font-size: 0.76rem; }
  .bc-label img { display: none; }
  .bc-item-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .bc-slot { display: none; }
}

/* ================================================================
   Refonte visuelle (2026-09-23, user-requested: "revoir un peu
   visuellement le builder" + "une version mobile adaptée"). Kept as ONE
   block at the very end so it wins by source order without touching the
   older rules above, and can be removed wholesale to revert (full backup
   of the previous state: Desktop/wakfu-builder-saves/save-2026-09-23-
   avant-refonte-visuelle). Every color goes through the theme tokens, so
   all 5 themes follow.
   ================================================================ */

:root {
  --radius-lg: 12px;
  --panel-shadow: inset 0 1px 0 color-mix(in srgb, #fff 5%, transparent), 0 12px 28px -20px rgba(0, 0, 0, 0.7);
}
:root[data-theme="blanc"] {
  --panel-shadow: 0 1px 2px rgba(20, 24, 40, 0.06), 0 12px 26px -18px rgba(20, 24, 40, 0.28);
}

/* Panels: a touch more depth — soft lift + a faint top-lit gradient
   instead of a flat fill, larger radius. */
.panel {
  border-radius: var(--radius-lg);
  box-shadow: var(--panel-shadow);
  background: linear-gradient(180deg, color-mix(in srgb, var(--bg-elevated) 40%, var(--bg-panel)) 0, var(--bg-panel) 110px);
}
h2.section-title { font-size: 1.25rem; font-weight: 650; letter-spacing: 0.01em; }

/* Stat group headings get a hairline running to the panel edge, so the
   long Statistiques column reads as clear sections. */
.stats-group h4 { display: flex; align-items: center; gap: 8px; letter-spacing: 0.08em; }
.stats-group h4::after { content: ""; flex: 1; height: 1px; background: var(--border); opacity: 0.7; }
.stat-row { border-bottom-color: color-mix(in srgb, var(--border) 45%, transparent); }

/* Principal: PV/PA/PM/PW always on one line (auto-fit left PW alone on a
   2nd row in most panel widths). */
#build-stats .stat-tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
#build-stats .stat-tile { border-radius: 8px; padding: 8px 6px; }
#build-stats .stat-tile .t-value { font-variant-numeric: tabular-nums; }

/* Maîtrises/Résistances tiles: plain 1fr tracks let "448 (63%)" push the
   4th resistance tile out of the panel (it overflowed into Chasses).
   minmax(0,1fr) + wrapping keeps all 4 inside; 2 columns when narrow. */
.elemental-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.elemental-tile { min-width: 0; flex-wrap: wrap; row-gap: 0; border-radius: 8px; padding: 6px 4px; }
.elemental-tile .v { font-variant-numeric: tabular-nums; }
.elemental-tile .v-pct { margin-left: 0; font-size: 0.78rem; font-weight: 600; opacity: 0.75; }
@container (max-width: 380px) {
  .elemental-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Topbar (desktop): the quick-nav keeps its full width and the shared-
   build badge is what gives way (ellipsis) when the row runs out of room
   — it used to be the other way round, clipping "Aptitudes" mid-word. */
nav.quick-nav { flex: 0 1 auto; }
.topbar-center-slot { flex: 1 1 0; }
.build-shared-status { display: block; flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.qn-short { display: none; }
header.topbar { transition: transform 0.22s ease; }
/* Mid-width desktops (<=1500px): short nav labels ("Stats", "Sorts") and
   tighter spacing so the whole bar fits one row. */
@media (min-width: 768px) and (max-width: 1500px) {
  header.topbar { gap: 12px; }
  .topbar-right { gap: 6px; }
  .quick-nav-btn { padding: 5px 9px; }
  .qn-long { display: none; }
  .qn-short { display: inline; }
}
/* The shared-build badge's own slot is a size container: when it gets too
   narrow for the full sentence, the pill swaps to a short label (full text
   stays in its title tooltip) instead of truncating to "Buil…". */
.topbar-center-slot { container-type: inline-size; }
.topbar-center-slot:has(.build-shared-status:not([style*="none"])) { min-width: 116px; }
/* Tablets: not enough width for nav + buttons + icons on one line — the
   quick-nav takes its own full-width second row instead of being squeezed
   (it was hiding "Recherche" off its scroll edge). */
@media (min-width: 768px) and (max-width: 1150px) {
  header.topbar { flex-wrap: wrap; row-gap: 0; }
  nav.quick-nav {
    order: 5; flex: 1 1 100%; margin: 4px -18px 0; padding: 4px 16px 5px;
    border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  }
}
.build-shared-status { display: flex; align-items: center; gap: 6px; }
.build-shared-status .ui-icon { width: 14px; height: 14px; flex-shrink: 0; }
.build-shared-status .bss-long { overflow: hidden; text-overflow: ellipsis; }
.build-shared-status .bss-short { display: none; }
@container (max-width: 230px) {
  .build-shared-status { padding: 5px 11px; }
  .build-shared-status .bss-long { display: none; }
  .build-shared-status .bss-short { display: inline; }
}
/* Cart count badge: its own display:flex outranked the [hidden] attribute
   the JS sets for an empty list, so "0" always showed. */
.shoppinglist-cart-badge[hidden] { display: none; }
/* Perf pass (2026-09-23): the film-grain overlay (body::after, fixed,
   full-screen) used mix-blend-mode:overlay, which makes the browser
   re-blend the whole viewport on every scroll frame. Its specks are pure
   black, and "overlay" of black over a dark background is the same
   darkening as plain alpha — so normal blending looks identical on the
   dark themes and composites for free. Promoted to its own layer too. */
body::after { mix-blend-mode: normal; opacity: 0.14; will-change: transform; transform: translateZ(0); }
.mobile-fab-bar { display: none; }

/* "Installer l'application" entry at the end of the Autres vues menu —
   styled like its sibling menu items, set apart by a divider. */
.view-dropdown-menu .install-app-btn {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  margin-top: 4px; border-top: 1px solid var(--border); border-radius: 0 0 6px 6px;
  color: var(--accent); font-weight: 600;
}
.view-dropdown-menu .install-app-btn[hidden] { display: none; }
.view-dropdown-menu .install-app-btn svg { width: 16px; height: 16px; flex-shrink: 0; }

/* Annuler / rétablir (planner.js stepHistory) — two small icon buttons in
   the Build panel header, plus a short status toast. */
.history-btns { display: flex; gap: 4px; margin-left: auto; flex-shrink: 0; }
button.btn.history-btn { width: 30px; height: 28px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
button.btn.history-btn svg { width: 16px; height: 16px; }
button.btn.history-btn:disabled { opacity: 0.35; cursor: default; }
button.btn.history-btn:disabled:hover { border-color: var(--border); color: var(--text); }
.history-toast {
  position: fixed; left: 50%; bottom: 24px; z-index: 700; pointer-events: none;
  transform: translate(-50%, 8px); opacity: 0;
  background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border);
  border-radius: 999px; padding: 7px 16px; font-size: 0.82rem; font-weight: 600;
  box-shadow: 0 8px 22px -8px rgba(0, 0, 0, 0.55);
  transition: opacity 0.16s ease, transform 0.16s ease;
}
.history-toast.show { opacity: 1; transform: translate(-50%, 0); }
@media (max-width: 767px) {
  .history-toast { bottom: calc(70px + env(safe-area-inset-bottom)); }
  button.btn.history-btn { width: 36px; height: 34px; padding: 0; }
}

/* Slot remove buttons (user-requested 2026-09-23 via bug report: "revoit
   le design des petits boutons de suppression"). Was a solid red circle
   with a text "×" glyph (font-dependent, never quite centered). Now a
   small dark rounded chip with a geometric cross drawn by two bars —
   discreet on top of the item art, red only on hover. The "×" text stays
   in the markup (font-size 0) so nothing in planner.js changes. Show/hide
   behavior (hover on desktop, always on mobile) is unchanged. */
.slot .remove-x {
  top: 2px; right: 2px; width: 14px; height: 14px; border-radius: 4px;
  font-size: 0; color: color-mix(in srgb, var(--danger) 70%, var(--text));
  background: color-mix(in srgb, var(--bg-panel) 85%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--border));
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
  transition: opacity 0.12s ease, transform 0.12s ease, background-color 0.12s, color 0.12s, border-color 0.12s;
}
.slot .remove-x::before, .slot .remove-x::after {
  content: ""; position: absolute; top: 50%; left: 50%; width: 7px; height: 1.5px;
  border-radius: 1px; background: currentColor;
}
.slot .remove-x::before { transform: translate(-50%, -50%) rotate(45deg); }
.slot .remove-x::after { transform: translate(-50%, -50%) rotate(-45deg); }
.slot.filled:hover .remove-x:hover, .slot.filled .remove-x:hover {
  background: var(--danger); border-color: var(--danger); color: #fff; transform: scale(1.12);
}

/* Line-icon pictos (WUI.ICONS, user-requested 2026-09-23) replacing the
   🗑 / 🔀 / ⓘ emoji: they inherit each button's own themed color, so hover
   states (danger red on "vider", accent on the rest) tint the icon too. */
.ui-icon { width: 16px; height: 16px; flex-shrink: 0; display: block; }
.chasse-clear-btn { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 28px; padding: 0; }
.chasse-clear-btn .ui-icon { width: 15px; height: 15px; }
#chasse-optimize-btn { display: inline-flex; align-items: center; gap: 7px; }
#chasse-optimize-btn .ui-icon { width: 16px; height: 16px; color: var(--accent); }
#chasse-optimize-btn:hover:not(:disabled) .ui-icon { color: inherit; }
#pinned-clear-btn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 26px; padding: 0; }
#pinned-clear-btn:hover { color: var(--danger); border-color: var(--danger); }
#build-del { display: inline-flex; align-items: center; justify-content: center; }
#build-del .ui-icon { width: 17px; height: 17px; }
/* ⓘ info buttons (5 places across planner/calculator/index markup): the
   glyph is hidden and an info-circle icon drawn in its place via mask,
   same technique the Discord icon already uses — no markup changes. The
   nested .info-tip is display:none (a data source for ui.js's portal), so
   zeroing the font size doesn't affect the tooltip text itself. */
.info-btn { font-size: 0; }
.info-btn::before {
  content: ""; width: 16px; height: 16px; background-color: currentColor;
  -webkit-mask: var(--info-icon) center / contain no-repeat; mask: var(--info-icon) center / contain no-repeat;
}
.info-btn-sm::before { width: 12px; height: 12px; }
:root {
  --info-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 11v5.5'/%3E%3Ccircle cx='12' cy='7.6' r='0.6' fill='black'/%3E%3C/svg%3E");
}
/* Closed drawers (damage calculator, class picker) sit just off-screen but
   their shadow still bled onto the page edge — shadow only while open. */
.calc-drawer:not(.open) { box-shadow: none; }


@media (max-width: 767px) {
  /* overflow-x:hidden (older mobile block) turns html/body into scroll
     containers, which silently disabled the sticky topbar on phones — it
     just scrolled away. clip blocks sideways scroll the same way without
     creating a scroll container, so sticky works again. */
  html, body { overflow-x: clip; }
  main { padding: 10px 8px calc(70px + env(safe-area-inset-bottom)); }
  .builder-layout { gap: 12px; }
  .panel { border-radius: 10px; }
  /* The older mobile block orders équipement/stats/aptitudes/chasses 1-4
     but never gave the spell deck one, so it defaulted to 0 and landed
     FIRST, above the equipment. */
  .layout-panel[data-layout-id="spelldeck"] { order: 5; }

  /* ---- Topbar: 3 tidy rows instead of a wrapping pile whose icon
     buttons (panier, paramètres…) ended up off-screen to the right,
     unreachable. .topbar-right dissolves (display:contents) so its
     children can be ordered independently:
       row 1: logo ............ icon buttons
       row 2: Connexion · Builds publics · Autres vues
       row 3: quick-nav (one row, short labels)
     The badge, when shown, gets its own slim line above the quick-nav. */
  header.topbar { padding: 6px 10px 0; gap: 6px; flex-wrap: wrap; }
  header.topbar .logo { order: 0; margin-right: auto; }
  .topbar-right { display: contents; }
  .topbar-right > .topbar-icon-btn { order: 1; width: 36px; height: 36px; padding: 0; }
  header.topbar::before { content: ""; order: 2; flex-basis: 100%; height: 0; }
  #account-login-btn, #account-dropdown, .topbar-right > .tab-btn:not(.topbar-icon-btn), #view-dropdown { order: 3; flex: 1 1 0; min-width: 0; }
  #account-login-btn, .topbar-right > .tab-btn:not(.topbar-icon-btn), #view-dropdown .view-dropdown-trigger, #account-dropdown-trigger {
    width: 100%; justify-content: center; padding: 7px 6px; font-size: 0.8rem; white-space: nowrap;
  }
  .topbar-center-slot { order: 4; flex: 1 1 100%; }
  .topbar-center-slot:not(:has(.build-shared-status:not([style*="none"]))) { display: none; }
  .build-shared-status { margin: 0 auto; font-size: 0.78rem; padding: 3px 10px; }
  nav.quick-nav {
    order: 5; flex: 1 1 100%; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    margin: 0 -10px; padding: 5px 8px 6px; gap: 4px;
    border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  }
  nav.quick-nav::-webkit-scrollbar { display: none; }
  .quick-nav-btn { flex: 1 0 auto; text-align: center; padding: 6px 8px; font-size: 0.78rem; border-radius: 999px; }
  .qn-long { display: none; }
  .qn-short { display: inline; }
  /* Remove chip: smaller on the ~45px mobile doll slots. */
  .slot .remove-x { width: 13px; height: 13px; top: 1px; right: 1px; border-radius: 4px; }
  .slot .remove-x::before, .slot .remove-x::after { width: 6px; }
  /* Dropdown menus hang off row 2's buttons: keep them on-screen. The
     account menu's button is the LEFTMOST one on mobile (rightmost on
     desktop), so right-aligning it like "Autres vues" pushed it off the
     left edge (user-reported) — it opens left-aligned here instead. */
  .view-dropdown-menu { right: 0; max-width: calc(100vw - 20px); }
  #account-dropdown .view-dropdown-menu { left: 0; right: auto; }

  /* ---- Stats: tighter tiles so 4 fit comfortably at phone width. */
  #build-stats .stat-tile { padding: 7px 4px; gap: 4px; }
  #build-stats .stat-tile .t-value { font-size: 0.86rem; }

  /* ---- Item search columns (user-confirmed 2026-09-23): normal mode = 1
     full-width card per row, compact (▦) = 2 per row. Either way the grid
     is pinned to the panel's width (minmax(0,…) tracks, cards allowed to
     shrink), so switching modes never changes the page width. */
  #build-picker-grid .item-grid,
  #build-picker-grid .item-grid.has-stats-cards {
    grid-template-columns: minmax(0, 1fr) !important; gap: 8px;
  }
  body.compact-cards #build-picker-grid .item-grid,
  body.compact-cards #build-picker-grid .item-grid.has-stats-cards {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap: 6px;
  }
  #build-picker-grid .item-card { min-width: 0; max-width: 100%; }
  /* The real cause of "la largeur du container change": .picker-wrap is a
     column flexbox here but kept the desktop align-items:flex-start, so
     the panel shrink-wrapped its content — full width with 2 cards per
     row, narrower with 1. Always full width now, whatever the mode. */
  .picker-wrap { align-items: stretch; }
  .picker-wrap .picker-inner { align-self: stretch; width: 100%; }
  #build-picker-grid .card-effect-list li { min-width: 0; overflow-wrap: anywhere; }

  /* ---- Stats: Combat/Secondaire sit side by side at half width (older
     mobile rule), but their own inner 2-column grids only collapsed below
     a 340px PANEL width — on a ~410px phone they stayed 2 columns inside a
     half-width box and the labels overlapped. Always one column here. */
  #build-stats .stat-row-grid { grid-template-columns: minmax(0, 1fr); }
  #build-stats .stat-row-grid-cols { grid-auto-flow: row; grid-template-rows: none; }
  #build-stats .stat-row .label-group { min-width: 0; }
  #build-stats .stat-row .label-group span:last-child { overflow-wrap: anywhere; }

  /* ---- Closed slide-in drawers sit just off-screen, but their shadows
     still spilled onto the page's left/right edges (the "ombre" on the
     sides). Shadow only while actually open. */
  .builder-float-sidebar:not(.mobile-open), .picker-inner .filters:not(.mobile-open) { box-shadow: none; }

  /* Build drawer flashing on its own on load/scroll (user-reported
     2026-09-23): the DESKTOP scroll-fade (planner.js updateBuildVisibility
     toggles .build-hidden past a scroll threshold) set transform:
     translateY(-10px), which replaced the drawer's off-screen
     translateX(-100%) — the drawer jumped on screen, then faded out (and
     slid back out on scroll-up). That fade has no meaning for a drawer
     that's only ever opened by its button, so it's cancelled here. */
  .builder-float-sidebar, .builder-float-sidebar.build-hidden {
    opacity: 1; visibility: visible; pointer-events: auto;
    transform: translateX(-100%); transition: transform 0.22s ease;
  }
  .builder-float-sidebar.mobile-open, .builder-float-sidebar.build-hidden.mobile-open { transform: translateX(0); }

  /* ---- Build / Filtres floating buttons: one bottom-right bar of labeled
     pills (themed SVG icons) instead of two stacked emoji circles over
     the content's left edge. */
  .mobile-fab-bar {
    display: flex; position: fixed; right: 12px; bottom: calc(14px + env(safe-area-inset-bottom));
    z-index: 230; gap: 8px; align-items: center;
  }
  .mobile-fab-bar .mobile-drawer-btn {
    position: static; width: auto; height: 42px; padding: 0 15px 0 12px; gap: 7px;
    border-radius: 999px; font: 600 0.84rem var(--font); letter-spacing: 0.01em;
    color: var(--text); border: 1px solid var(--border);
    background: color-mix(in srgb, var(--bg-elevated) 85%, transparent);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    box-shadow: 0 8px 20px -6px rgba(0, 0, 0, 0.55);
  }
  .mobile-fab-bar .mobile-drawer-btn.primary {
    background: var(--accent); border-color: var(--accent); color: var(--accent-text);
  }
  .mobile-fab-bar .mobile-drawer-btn svg { width: 18px; height: 18px; flex-shrink: 0; }
  .mobile-fab-bar .mobile-drawer-btn:active { transform: scale(0.96); }
  .mobile-fab-bar #mobile-build-btn { display: inline-flex; }
  /* Was padded to dodge the old bottom-LEFT build button, which moved. */
  .fightlab-class-resources { padding-left: 0; }

  /* ---- Item search, compact 2-per-row cards: names were cut to 4
     letters ("Gran…") because icon + name + the stacked action pills all
     shared one line. Icon + pills now share row 1, the name gets the full
     card width underneath (2 lines max), and the price/total badges wrap
     instead of spilling past the card's right edge. */
  body.compact-cards #build-picker-grid .card-header { flex-wrap: wrap; align-items: center; }
  body.compact-cards #build-picker-grid .card-header .card-actions { flex-direction: row; margin-left: auto; align-self: flex-start; }
  body.compact-cards #build-picker-grid .card-header .info { order: 3; flex: 1 1 100%; }
  body.compact-cards #build-picker-grid .card-action-pill.pin:not(.active) { display: none; }
  body.compact-cards #build-picker-grid .item-card .name {
    white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  }
  body.compact-cards #build-picker-grid .card-stat-row { flex-wrap: wrap; margin: 2px 0 0; }
  #build-picker-grid .item-card-badges { flex-wrap: wrap; min-width: 0; }
  body.compact-cards #build-picker-grid .card-stat-row .card-action-pill.equip { margin-left: 0; width: 100%; justify-content: center; }
  /* That pill is visibility:hidden until the item is actually equipped —
     at 100% width it still reserved a blank line on every other card. */
  body.compact-cards #build-picker-grid .card-stat-row .card-action-pill.equip:not(.active) { display: none; }

  /* ---- Enchantements: item names hidden on mobile (user-requested
     2026-09-23 via bug report) — the icon alone identifies the slot, the
     full name stays in the label's own title tooltip. The label shrinks
     to the icon so the 4 shards sit right next to it. */
  .chasse-item-name { display: none; }
  .chasse-item-label { flex: 0 0 auto; }
  /* Tier slider + éclat cost: the slider is forced full-width on mobile
     (older block) but the panel stayed nowrap, so the cost text was pushed
     off-screen. The cost wraps to its own line under the slider. */
  .chasse-budget-panel { flex-wrap: wrap; row-gap: 6px; max-width: 100%; box-sizing: border-box; }
  .chasse-budget-sep { display: none; }
  .chasse-budget-detail { flex: 1 1 100%; min-width: 0; }

  /* Aptitudes compact 2-column grid: plain 1fr tracks couldn't go below
     their content's min width (~162px each), overflowing narrow phones. */
  .apt-compact .apt-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .apt-compact .apt-category { min-width: 0; padding-left: 8px; padding-right: 8px; }
  .apt-compact .apt-stat-controls { gap: 3px; }
  .apt-compact .apt-stat-controls input { width: 38px; }
  .apt-compact .apt-category-head { flex-wrap: wrap; gap: 0 6px; }
  .apt-compact .apt-remaining { white-space: nowrap; font-size: 0.7rem; }

  /* Narrow phones (<=360px): 5 icons + logo overflowed row 1, dropping
     the gear icon onto a line of its own. */
  @media (max-width: 360px) {
    .topbar-right > .topbar-icon-btn { width: 32px; height: 32px; }
    .topbar-icon-btn svg, .discord-icon-btn::before { width: 19px; height: 19px; }
    header.topbar { column-gap: 4px; }
    header.topbar .logo-img { height: 19px; }
  }

  /* ---- Item search panel, mobile only (user-requested 2026-09-23 via bug
     report: "améliore cette partie en mode mobile et compacte"). The
     sticky header was ~160px (2 rows of 42px slots + a separate row for
     the ★/▦ buttons) and stuck at the desktop 65px offset. Now:
       - slots shrink to fit 7 per row next to a slim button column;
       - ★ / ▦ stack vertically in that column instead of taking a row;
       - the alt-loadout "1" badges are hidden here (still on the main
         doll) and × only shows on the active slot, as on desktop;
       - it sticks right under whatever part of the topbar is visible
         (--topbar-visible-h, set by index.html's auto-hide script). */
  .picker-sticky-header {
    top: var(--topbar-visible-h, 44px); transition: top 0.22s ease;
    display: grid; grid-template-columns: minmax(0, 1fr) 34px; grid-template-rows: auto auto;
    align-items: center; gap: 4px 6px;
    margin: -10px -10px 8px; padding: 6px 10px;
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 8px 14px -12px rgba(0, 0, 0, 0.6);
  }
  .picker-sticky-header .doll-compact { grid-column: 1; grid-row: 1 / span 2; flex-basis: auto; gap: 4px; }
  .picker-sticky-header .doll-compact .slot { flex: 0 0 calc((100% - 6 * 4px) / 7); width: auto; height: auto; border-radius: 6px; }
  .picker-sticky-header .doll-compact .slot img.icon { inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); }
  .picker-sticky-header .altslot-index, .picker-sticky-header .altslot-hint-dot { display: none; }
  #build-picker-doll .slot.filled .remove-x { opacity: 0; pointer-events: none; }
  #build-picker-doll .slot.filled.active .remove-x { opacity: 1; pointer-events: auto; width: 14px; height: 14px; top: 2px; right: 2px; }
  .picker-sticky-header > .btn-icon { grid-column: 2; width: 34px; height: 34px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
  #build-picker-favorites-btn { grid-row: 1; }
  #compact-cards-toggle-btn { grid-row: 2; }
  #build-picker-panel { scroll-margin-top: 50px; }

  /* Compact cards — "lisible et compacte" (user, 2026-09-23): the older
     compact mode shrank stat lines to 0.6rem (~9.6px), too small to read
     on a phone. Text goes back up to legible sizes (11-13px) and the space
     is recovered from padding/gaps instead: one-line sub-info, tighter
     rows, no per-line pill background. */
  body.compact-cards #build-picker-grid .item-card { padding: 7px 7px 5px; border-radius: 9px; }
  body.compact-cards #build-picker-grid .card-header { gap: 6px; }
  body.compact-cards #build-picker-grid .card-header .item-icon-frame { width: 40px; height: 40px; }
  body.compact-cards #build-picker-grid .item-card .name { font-size: 0.86rem; line-height: 1.2; margin-top: 2px; }
  body.compact-cards #build-picker-grid .item-card .sub { font-size: 0.78rem; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  body.compact-cards #build-picker-grid .item-card-badge { font-size: 0.7rem; padding: 1px 5px; }
  body.compact-cards #build-picker-grid .card-stat-row { margin: 4px 0 0; }
  body.compact-cards #build-picker-grid .card-effect-list { margin-top: 5px; gap: 0; }
  body.compact-cards #build-picker-grid .card-effect-list li {
    font-size: 0.78rem; line-height: 1.3; padding: 2px 2px; gap: 5px;
    background: none; border-radius: 0;
    border-bottom: 1px solid color-mix(in srgb, var(--border) 45%, transparent);
  }
  body.compact-cards #build-picker-grid .card-effect-list li:last-child { border-bottom: none; }
  body.compact-cards #build-picker-grid .card-effect-list .stat-icon { width: 15px; height: 15px; }

  /* The results list scrolls inside its own box (older desktop rule, sized
     for a desktop header) — sized here to the real mobile space left under
     the visible topbar + the compact sticky header. */
  .picker-inner #build-picker-grid { max-height: calc(100dvh - var(--topbar-visible-h, 44px) - 128px); }

}

/* ================================================================
   Micro-animations "juicy" — desktop only (user-requested 2026-09-23:
   "rajoute quelques petites animations... tout en gardant la
   réactivité"). Everything here animates only transform/opacity (GPU-
   composited, never triggers layout) plus a brief text color flash, stays
   under ~400ms, and is scoped to real mouse/trackpad devices with motion
   allowed — phones and prefers-reduced-motion users get none of it.
   The two JS hooks (planner.js animateStatChanges / equipItem's
   .slot-pop) only add classes, so outside this query they're inert.
   ================================================================ */
@media (min-width: 768px) and (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {

  /* Stat value changed → pop + green/red flash. */
  @keyframes stat-bump-pop { 0% { transform: scale(1); } 30% { transform: scale(1.22); } 100% { transform: scale(1); } }
  @keyframes stat-flash-up { 0%, 40% { color: #6fce7a; } 100% { color: inherit; } }
  @keyframes stat-flash-down { 0%, 40% { color: var(--danger); } 100% { color: inherit; } }
  .stat-bump { display: inline-block; transform-origin: center; }
  .stat-bump.up { animation: stat-bump-pop 0.38s cubic-bezier(0.3, 1.6, 0.5, 1), stat-flash-up 0.9s ease-out; }
  .stat-bump.down { animation: stat-bump-pop 0.38s cubic-bezier(0.3, 1.6, 0.5, 1), stat-flash-down 0.9s ease-out; }

  /* Freshly equipped slot: icon drops in with a small overshoot, plus a
     one-shot ring of light expanding out of the slot. */
  @keyframes slot-icon-pop { 0% { transform: scale(0.55) rotate(-8deg); opacity: 0.2; } 60% { transform: scale(1.1) rotate(2deg); opacity: 1; } 100% { transform: scale(1) rotate(0); } }
  /* Ring = the slot's own outer box-shadow expanding and fading (a
     pseudo-element ring would be clipped by .slot's overflow:hidden). */
  @keyframes slot-ring {
    0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 80%, transparent); }
    100% { box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 0%, transparent); }
  }
  .slot.slot-pop { animation: slot-ring 0.5s ease-out; }
  .slot.slot-pop img.icon { animation: slot-icon-pop 0.34s cubic-bezier(0.25, 1.4, 0.5, 1); }

  /* Search results: short staggered fade-up for the first cards of each
     new result list (later batches loaded on scroll are off-screen). */
  @keyframes card-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
  .item-grid > .item-card { animation: card-in 0.2s ease-out backwards; }
  .item-grid > .item-card:nth-child(2) { animation-delay: 15ms; }
  .item-grid > .item-card:nth-child(3) { animation-delay: 30ms; }
  .item-grid > .item-card:nth-child(4) { animation-delay: 45ms; }
  .item-grid > .item-card:nth-child(5) { animation-delay: 60ms; }
  .item-grid > .item-card:nth-child(6) { animation-delay: 75ms; }
  .item-grid > .item-card:nth-child(7) { animation-delay: 90ms; }
  .item-grid > .item-card:nth-child(8) { animation-delay: 105ms; }
  .item-grid > .item-card:nth-child(n+9) { animation-delay: 120ms; }
  /* Hover: the item icon leans in a little on top of the card's own lift. */
  .item-card .item-icon-frame img.icon { transition: transform 0.18s cubic-bezier(0.3, 1.5, 0.5, 1); }
  .item-card:hover .item-icon-frame img.icon { transform: scale(1.1) rotate(-4deg); }
  .item-card { transition: transform 0.14s ease, border-color 0.14s, box-shadow 0.14s; }

  /* Doll slots: filled icons lift slightly on hover. */
  .slot img.icon { transition: transform 0.16s cubic-bezier(0.3, 1.5, 0.5, 1); }
  .slot.filled:hover img.icon { transform: scale(1.08); }

  /* Tooltips / popovers / menus / modals: fade + slight zoom in instead
     of popping in flat. */
  .item-tooltip { transform: translateY(4px) scale(0.98); transition: opacity 0.12s ease, transform 0.14s ease; }
  .item-tooltip.active { transform: none; }
  @keyframes pop-in { from { opacity: 0; transform: translateY(-4px) scale(0.98); } to { opacity: 1; transform: none; } }
  .view-dropdown.open .view-dropdown-menu { animation: pop-in 0.14s ease-out; transform-origin: top right; }
  .build-switcher-popover { animation: pop-in 0.16s ease-out; transform-origin: top left; }
  @keyframes modal-in { from { opacity: 0; transform: translateY(10px) scale(0.97); } to { opacity: 1; transform: none; } }
  @keyframes overlay-in { from { opacity: 0; } to { opacity: 1; } }
  .modal-overlay.active { animation: overlay-in 0.16s ease-out; }
  .modal-overlay.active .modal { animation: modal-in 0.22s cubic-bezier(0.2, 1.2, 0.4, 1); }

  /* Topbar icons: a small, specific reaction on hover. */
  .topbar-icon-btn svg, .discord-icon-btn::before { transition: transform 0.22s cubic-bezier(0.3, 1.6, 0.5, 1); }
  .topbar-icon-btn:hover svg, .discord-icon-btn:hover::before { transform: translateY(-1px) scale(1.1); }
  #settings-btn:hover svg { transform: rotate(60deg); }
  #shoppinglist-topbar-btn:hover svg { transform: translateX(2px) rotate(-6deg); }

  /* Primary buttons lift a hair on hover (press still squashes to .96). */
  button.btn.primary:hover:not(:disabled) { transform: translateY(-1px); }
  button.btn.primary:active:not(:disabled) { transform: scale(0.96); }
}

/* ================================================================
   Profile page rework (2026-09-23, "améliore grandement la page de
   profil") — hero banner + toolbar + full-width grid. See js/profile.js.
   ================================================================ */
.profile-page { max-width: 1320px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }

/* Hero: favourite class art fading in from the right, avatar + identity +
   actions on one row, stat tiles underneath. */
.profile-hero-inner {
  --hero-accent: var(--accent);
  position: relative; overflow: hidden; isolation: isolate;
  border: 1px solid var(--border); border-radius: var(--radius-lg, 12px);
  background:
    radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--hero-accent) 22%, transparent), transparent 55%),
    var(--bg-panel);
  box-shadow: var(--panel-shadow, none);
  padding: 22px 24px 18px;
  display: flex; flex-direction: column; gap: 18px;
}
.profile-hero-art {
  position: absolute; inset: 0 0 0 auto; width: min(52%, 620px); z-index: -1; pointer-events: none;
  background-position: right 20%; background-size: cover; background-repeat: no-repeat;
  opacity: 0.32;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 55%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 55%);
}
/* Height frozen while a picker is open (profile.js wireChoicePicker): pinned
   to the top, with a soft bottom fade instead of a hard edge. */
.profile-hero-art.locked {
  bottom: auto;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 55%), linear-gradient(180deg, #000 65%, transparent);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(90deg, transparent 0%, #000 55%), linear-gradient(180deg, #000 65%, transparent);
  mask-composite: intersect;
}
.profile-hero-message { min-height: 90px; justify-content: center; color: var(--text-dim); }
.profile-hero-message p { margin: 0; }
.profile-hero-main { display: flex; align-items: flex-start; gap: 18px; }
.profile-avatar {
  width: 88px; height: 88px; flex-shrink: 0; border-radius: 18px;
  background-color: var(--bg-elevated); background-repeat: no-repeat;
  border: 2px solid color-mix(in srgb, var(--hero-accent) 70%, var(--border));
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--hero-accent) 18%, transparent), 0 10px 24px -12px rgba(0, 0, 0, 0.6);
  display: flex; align-items: center; justify-content: center;
  font-size: 2.2rem; font-weight: 800; color: var(--accent);
}
.profile-identity { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.profile-name-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.profile-name { margin: 0; font-size: 1.7rem; font-weight: 800; letter-spacing: -0.01em; line-height: 1.1; color: var(--text); }
.profile-you-badge {
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em;
  padding: 3px 8px; border-radius: 999px; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
}
.profile-joined { font-size: 0.8rem; color: var(--text-dim); }
.profile-bio-wrap { margin-top: 8px; }
.profile-bio { margin: 0; color: var(--text); white-space: pre-wrap; max-width: 70ch; line-height: 1.5; font-size: 0.92rem; }
.profile-bio-empty { color: var(--text-dim); font-style: italic; }
.profile-bio-edit { display: flex; flex-direction: column; gap: 8px; max-width: 70ch; }
.profile-bio-edit textarea {
  width: 100%; min-height: 84px; resize: vertical; font-family: inherit; font-size: 0.9rem; line-height: 1.5;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 9px 11px;
}
.profile-bio-edit textarea:focus { outline: 1px solid var(--accent); }
.profile-bio-edit-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.profile-bio-counter { margin-left: auto; font-size: 0.78rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.profile-bio-status { font-size: 0.78rem; color: var(--danger); }
.profile-actions { display: flex; flex-direction: column; gap: 8px; flex-shrink: 0; }
.profile-actions .btn { display: inline-flex; align-items: center; gap: 8px; font-size: 0.84rem; white-space: nowrap; }
.profile-actions .btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.profile-actions .btn.copied { border-color: #6fce7a; color: #6fce7a; }

.profile-stats { display: flex; flex-wrap: wrap; gap: 10px; }
.profile-stat {
  display: flex; align-items: center; gap: 10px; min-width: 130px;
  padding: 9px 14px; border-radius: 10px;
  background: color-mix(in srgb, var(--bg-elevated) 80%, transparent);
  border: 1px solid var(--border);
}
.profile-stat-text { display: flex; flex-direction: column; line-height: 1.15; }
.profile-stat-value { font-size: 1.15rem; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
.profile-stat-label { font-size: 0.78rem; color: var(--text-dim); }
.profile-stat-icon { font-size: 1.1rem; color: #e0577a; }
.profile-stat-portrait { width: 30px; height: 30px; border-radius: 7px; background-repeat: no-repeat; flex-shrink: 0; }

/* Content: toolbar, class chips, count header, grid. */
.profile-content { display: flex; flex-direction: column; gap: 12px; padding: 16px 18px 20px; }
.profile-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.profile-search {
  flex: 1 1 240px; display: flex; align-items: center; gap: 8px; min-width: 0;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; padding: 0 10px;
}
.profile-search:focus-within { border-color: var(--accent); }
.profile-search svg { width: 16px; height: 16px; color: var(--text-dim); flex-shrink: 0; }
.profile-search input[type="text"] { flex: 1; min-width: 0; border: none; background: none; padding: 9px 0; }
.profile-search input[type="text"]:focus { outline: none; }
.profile-seg { display: inline-flex; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; padding: 3px; gap: 2px; }
.profile-seg[hidden] { display: none; }
.profile-seg button {
  background: none; border: none; color: var(--text-dim); font: inherit; font-size: 0.82rem; font-weight: 600;
  padding: 6px 12px; border-radius: 6px; cursor: pointer; transition: background-color 0.15s, color 0.15s;
}
.profile-seg button:hover { color: var(--text); }
.profile-seg button.active { background: var(--accent); color: var(--accent-text); }
#profile-sort { padding: 8px 10px; }
.profile-level { width: 220px; }
.profile-level .level-slider-label { font-size: 0.78rem; }


.profile-class-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.profile-class-chips[hidden] { display: none; }
.profile-class-chip {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 3px 10px 3px 3px; border-radius: 999px; font: inherit; font-size: 0.8rem; font-weight: 600;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim);
  transition: border-color 0.15s, color 0.15s, background-color 0.15s;
}
.profile-class-chip:hover { border-color: var(--accent); color: var(--text); }
.profile-class-chip.active { border-color: var(--accent); color: var(--text); background: color-mix(in srgb, var(--accent) 16%, var(--bg-elevated)); }
.profile-class-chip b { font-size: 0.78rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.profile-class-chip-portrait { width: 24px; height: 24px; border-radius: 50%; background-repeat: no-repeat; flex-shrink: 0; }

.profile-builds-head { display: flex; align-items: baseline; gap: 8px; margin-top: 4px; padding-bottom: 6px; border-bottom: 1px solid var(--border); }
.profile-builds-head h3 { margin: 0; font-size: 1rem; color: var(--accent); }
.profile-count { font-size: 0.8rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
/* Two cards per row where there is room (each card stays close to the
   ~640px single column preferred before), one on narrow screens. */
#view-profile .profile-builds { grid-template-columns: repeat(auto-fill, minmax(min(100%, 560px), 1fr)); max-width: none; gap: 14px; }
.profile-empty { text-align: center; padding: 34px 16px; color: var(--text-dim); display: flex; flex-direction: column; align-items: center; gap: 12px; }
.profile-empty[hidden] { display: none; }
.profile-empty p { margin: 0; }

@media (max-width: 767px) {
  .profile-hero-inner { padding: 16px; gap: 14px; }
  .profile-hero-art { width: 100%; opacity: 0.18; }
  .profile-hero-main { flex-wrap: wrap; gap: 12px; }
  .profile-avatar { width: 64px; height: 64px; border-radius: 14px; }
  .profile-name { font-size: 1.35rem; }
  .profile-identity { flex: 1 1 calc(100% - 80px); }
  .profile-actions { flex-direction: row; flex-wrap: wrap; width: 100%; }
  .profile-actions .btn { flex: 1 1 auto; justify-content: center; }
  .profile-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .profile-stat { min-width: 0; padding: 8px 10px; }
  .profile-content { padding: 12px; }
  .profile-level { width: 100%; }
  #profile-sort { flex: 1 1 auto; }
}

/* ---------- Profile / Builds publics — follow-ups (2026-09-23) ---------- */

/* "Réinitialiser" always keeps its place in the toolbar (dimmed/disabled
   when there's nothing to reset) so switching a filter never makes the
   other controls jump (user-reported). */
.profile-toolbar .btn:disabled { opacity: 0.4; cursor: default; }
.profile-toolbar .btn:disabled:hover { border-color: var(--border); color: var(--text); }

/* Favourite class: editable avatar + tile on your own profile, and the
   inline picker (inside the hero, so never clipped by its overflow). */
button.profile-avatar { padding: 0; cursor: pointer; position: relative; font: inherit; }
.profile-avatar.editable:hover { border-color: var(--accent); }
.profile-avatar-edit {
  position: absolute; right: -6px; bottom: -6px; width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--accent-text); border: 2px solid var(--bg-panel);
  opacity: 0; transform: scale(0.8); transition: opacity 0.15s, transform 0.15s;
}
.profile-avatar-edit svg { width: 12px; height: 12px; }
.profile-avatar.editable:hover .profile-avatar-edit, .profile-avatar.editable:focus-visible .profile-avatar-edit { opacity: 1; transform: none; }
button.profile-stat-button { font: inherit; color: inherit; cursor: pointer; text-align: left; position: relative; }
button.profile-stat-button:hover { border-color: var(--accent); }
.profile-stat-edit { color: var(--text-dim); margin-left: auto; padding-left: 4px; display: flex; }
.profile-stat-edit svg { width: 13px; height: 13px; }
button.profile-stat-button:hover .profile-stat-edit { color: var(--accent); }
.profile-stat-portrait-empty { background: var(--bg-elevated); border: 1px dashed var(--border); }
.profile-fav-picker {
  border: 1px solid var(--border); border-radius: 10px; padding: 12px;
  background: color-mix(in srgb, var(--bg-panel) 92%, transparent);
  display: flex; flex-direction: column; gap: 10px;
}
.profile-fav-picker[hidden] { display: none; }
.profile-fav-picker-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.profile-fav-auto {
  font: inherit; font-size: 0.8rem; font-weight: 600; cursor: pointer; color: var(--text-dim);
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px;
}
.profile-fav-auto:hover, .profile-fav-auto.active { border-color: var(--accent); color: var(--text); }
.profile-fav-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 6px; }
.profile-fav-option {
  display: flex; align-items: center; gap: 8px; font: inherit; font-size: 0.8rem; font-weight: 600; cursor: pointer;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; padding: 4px 8px 4px 4px; color: var(--text-dim);
  transition: border-color 0.15s, color 0.15s;
}
.profile-fav-option:hover { border-color: var(--accent); color: var(--text); }
.profile-fav-option.active { border-color: var(--accent); color: var(--text); background: color-mix(in srgb, var(--accent) 16%, var(--bg-elevated)); }
.profile-fav-option:disabled, .profile-fav-auto:disabled { opacity: 0.5; cursor: default; }
.profile-fav-portrait { width: 28px; height: 28px; border-radius: 6px; background-repeat: no-repeat; flex-shrink: 0; }
.profile-fav-status { font-size: 0.78rem; color: var(--danger); }
.profile-fav-status:empty { display: none; }

/* Builds publics header: title + one-line explanation, live summary. */
.pb-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 16px 20px; }
.pb-header .section-title { margin: 0 0 4px; font-size: 1.35rem; }
.pb-subtitle { margin: 0; font-size: 0.84rem; color: var(--text-dim); max-width: 75ch; line-height: 1.45; }
.pb-subtitle b { color: var(--text); font-weight: 600; }
.pb-summary { display: flex; flex-wrap: wrap; gap: 8px; }
.pb-summary-item {
  display: inline-flex; align-items: center; gap: 6px; font-size: 0.8rem; color: var(--text-dim);
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 999px; padding: 4px 12px;
}
.pb-summary-item b { color: var(--text); font-variant-numeric: tabular-nums; }
.pb-summary-portrait { width: 20px; height: 20px; border-radius: 50%; background-repeat: no-repeat; margin-left: -6px; }

/* Sort tabs (was a <select>). */
.pb-sort-tabs { display: flex; gap: 4px; overflow-x: auto; scrollbar-width: none; border-bottom: 1px solid var(--border); }
.pb-sort-tabs::-webkit-scrollbar { display: none; }
.pb-sort-tabs button {
  flex-shrink: 0; background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px;
  font: inherit; font-size: 0.86rem; font-weight: 600; color: var(--text-dim); padding: 8px 12px; cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.pb-sort-tabs button:hover { color: var(--text); }
.pb-sort-tabs button.active { color: var(--accent); border-bottom-color: var(--accent); }
.pb-search-pseudo { flex: 0 1 220px; }

/* Placeholder cards while the list loads. */
.pb-card-skeleton { gap: 12px; pointer-events: none; }
.pb-skel-row { display: flex; gap: 12px; align-items: center; }
.pb-skel-lines { display: flex; flex-direction: column; gap: 8px; flex: 1; }
.pb-skel { display: block; border-radius: 6px; background: color-mix(in srgb, var(--text) 8%, transparent); animation: pb-skel-pulse 1.2s ease-in-out infinite; }
.pb-skel-avatar { width: 56px; height: 56px; border-radius: 10px; flex-shrink: 0; }
.pb-skel-line { height: 12px; width: 100%; }
.pb-skel-line.w80 { width: 80%; } .pb-skel-line.w60 { width: 60%; } .pb-skel-line.w40 { width: 40%; }
.pb-skel-strip { height: 34px; width: 100%; }
@keyframes pb-skel-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .pb-skel { animation: none; } }
#view-publicbuilds .profile-content { gap: 12px; }

@media (max-width: 767px) {
  .pb-header { padding: 14px; }
  .pb-search-pseudo { flex: 1 1 100%; }
  .profile-fav-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Line-icon buttons for the build bar (+ / dupliquer / lien / comparer /
   supprimer) and the copy/import code fields (deck, aptitudes, châsses) —
   2026-09-23, replacing the +, ⧉, 🔗, ⚖, 📥, 🎨/📑 glyphs. */
.build-bar-btn, #build-del { display: inline-flex; align-items: center; justify-content: center; min-height: 34px; }
/* The generic button.btn padding (14px each side) made 5 icon buttons need
   267px in a 250px row — they overflowed the Build panel (user-reported).
   Icon-only here: no side padding, free to shrink evenly. */
#build-switcher button.btn.build-bar-btn, #build-switcher button.btn#build-del { padding: 0; min-width: 0; }
.build-bar-btn .ui-icon, #build-del .ui-icon { width: 17px; height: 17px; }
.btn.code-btn { display: inline-flex; align-items: center; justify-content: center; }
.btn.code-btn .ui-icon { width: 16px; height: 16px; }

/* ================================================================
   2026-09-24 batch: icon buttons (visibility / like / shared / copy),
   rarity tooltip header, Build panel class identity, profile nation.
   ================================================================ */

/* Buttons that are "icon + label": consistent alignment everywhere. */
.btn-with-icon, .build-visibility-btn, .pb-visibility-toggle, .pb-card-private-badge {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
}
.btn-with-icon .ui-icon, .build-visibility-btn .ui-icon, .pb-visibility-toggle .ui-icon, .pb-card-private-badge .ui-icon { width: 15px; height: 15px; }
.build-visibility-btn.public .ui-icon, .pb-visibility-toggle.public .ui-icon { color: var(--accent); }
.pb-card-actions .pb-like-btn { gap: 6px; }
.pb-card-actions .pb-like-btn .ui-icon { width: 16px; height: 16px; }
.pb-card-actions .pb-like-btn.liked .ui-icon { color: #e0507a; }
.pb-card-actions .pb-like-btn:not(.liked):hover .ui-icon { color: #e0507a; }
/* Small public/private dot on build portraits (switcher list + trigger). */
.build-row-visibility-badge { font-size: 0; }
.build-row-visibility-badge .ui-icon { width: 8px; height: 8px; stroke-width: 2.6; }
.profile-stat-icon { display: flex; }
.profile-stat-icon .ui-icon { width: 20px; height: 20px; color: #e0577a; }

/* Item tooltip: rarity-tinted header, bigger name. */
.item-tooltip .tt-head.tt-head-item {
  margin: -10px -10px 10px; padding: 10px 12px 10px 10px;
  border-left-width: 4px; border-radius: 8px 8px 0 0;
  background: linear-gradient(100deg, color-mix(in srgb, var(--rarity-color, var(--accent)) 30%, transparent), color-mix(in srgb, var(--rarity-color, var(--accent)) 6%, transparent) 70%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--rarity-color, var(--accent)) 35%, transparent);
}
.item-tooltip .tt-head.tt-head-item .tt-name { font-size: 1rem; font-weight: 800; line-height: 1.2; }
.item-tooltip .tt-head.tt-head-item .tt-sub { font-size: 0.78rem; font-weight: 600; }

/* Build panel class identity: faded illustration banner + corner ornament. */
/* Class tint: the class accent color (--build-accent-rgb = "r,g,b", set
   by planner.js; site accent when no class) BLENDED INTO the theme's own
   panel color with color-mix, not layered on top — so each theme keeps
   its own look (a pastel hint on the light themes, a faint glow on the
   dark ones) instead of the same vivid overlay everywhere. Kept subtle on
   purpose (user feedback: "trop prononcé", then "réduit encore"). */
.build-panel {
  --build-tint: 10%;
  position: relative; isolation: isolate;
  /* Just a gradient: class-tinted at the top, fading into the theme's
     plain panel color by mid-height; the border stays neutral. */
  background: linear-gradient(180deg,
    color-mix(in srgb, rgb(var(--build-accent-rgb, 148, 130, 201)) var(--build-tint), var(--bg-panel)) 0%,
    var(--bg-panel) 55%);
}
:root[data-theme="blanc"] .build-panel { --build-tint: 6%; }
/* Game-style popup ornament: overhangs the panel's top-right corner, drawn
   above everything around it, without taking any layout space (user-
   requested 2026-09-24, from in-game screenshots). */
.build-panel-corner {
  position: absolute; top: -19px; right: -15px; width: 50px; height: 50px; z-index: 40;
  pointer-events: none; object-fit: contain;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6));
}
.build-panel-corner[hidden] { display: none; }
/* Élio sits 1px higher than the others (user-tuned). */
.build-panel-corner[data-class="18"] { top: -20px; }
/* The Build column used to clip anything overflowing it (overflow-y:auto
   also clips horizontally) — visible on desktop so the ornament can
   overhang; the mobile drawer keeps its own scrolling (its 16px padding
   already leaves room for the ornament). */
@media (min-width: 768px) {
  .builder-float-sidebar { overflow: visible; }
}

/* Profile nation: stamp next to the pseudo, tile, picker. */
.profile-nation-badge { width: 30px; height: 30px; object-fit: contain; filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5)); }
.profile-stat-stamp { width: 30px; height: 30px; object-fit: contain; flex-shrink: 0; }
.profile-nation-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
.profile-nation-option {
  display: flex; flex-direction: column; align-items: center; gap: 6px; cursor: pointer;
  font: inherit; font-size: 0.82rem; font-weight: 700; color: var(--text-dim);
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 10px; padding: 10px 6px 8px;
  transition: border-color 0.15s, color 0.15s, transform 0.12s;
}
.profile-nation-option:hover { border-color: var(--accent); color: var(--text); }
.profile-nation-option.active { border-color: var(--accent); color: var(--text); background: color-mix(in srgb, var(--accent) 16%, var(--bg-elevated)); }
.profile-nation-option:disabled { opacity: 0.5; cursor: default; }
.profile-nation-stamp { width: 56px; height: 56px; object-fit: contain; }
@media (max-width: 767px) {
  .profile-nation-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .profile-nation-badge { width: 24px; height: 24px; }
}

/* Item tooltip header: bigger item icon (user-requested 2026-09-24). */
.item-tooltip .tt-head.tt-head-item .item-icon-frame.sm { width: 52px; height: 52px; }
.item-tooltip .tt-head.tt-head-item .item-icon-frame.sm img.icon { inset: 5px; width: calc(100% - 10px); height: calc(100% - 10px); }

/* ================================================================
   Filters as a band on top (2026-09-24: Base de données, Rentabilité
   craft, Statistiques, Liste de courses) — same layout language as the
   Profil / Builds publics pages, instead of a side column. Scoped by the
   .filters-band class, so the builder's own item search (same filter
   component) keeps its side column.
   ================================================================ */
.filters.filters-band {
  position: static; max-height: none; overflow: visible; z-index: auto;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 12px 18px; align-items: start;
  padding: 14px 16px;
}
.filters.filters-band > .filters-divider { grid-column: 1 / -1; }
.filters.filters-band > .btn { align-self: end; justify-self: start; }
.filters.filters-band .field-inline-checkbox { align-self: end; }
/* Base de données / Liste de courses: only the 5 main filters (Recherche,
   Type, Niveau, Trier par, Rareté) until "Plus de filtres" is opened. */
.filters-band-collapsible.collapsed > .field:nth-of-type(n+6),
.filters-band-collapsible.collapsed > .filters-divider { display: none; }
.filters-band-collapsible > .field:nth-of-type(2) { grid-column: span 2; }
.filters-more-row { display: flex; justify-content: center; margin: -6px 0 2px; }
.filters-more-btn {
  display: inline-flex; align-items: center; gap: 6px; font-size: 0.82rem; font-weight: 600;
  border-radius: 999px; padding: 5px 14px; color: var(--text-dim);
}
.filters-more-btn .ui-icon { width: 15px; height: 15px; transition: transform 0.18s ease; }
.filters-more-btn.open .ui-icon { transform: rotate(180deg); }

/* Page layouts: one column, filters band above the results. */
.db-layout.filters-top-layout { grid-template-columns: minmax(0, 1fr); gap: 12px; }
.craftprofit-layout.filters-top-layout { grid-template-columns: minmax(0, 1fr); }
.craftprofit-layout.filters-top-layout .craftprofit-main > .filters-band { margin: 4px 0 14px; }

/* Liste de courses: the search band spans the full width, the three
   working columns (résultats / recettes / courses) sit underneath. */
.shoppinglist-layout:has(.filters-band) { grid-template-columns: 1.3fr 1fr 1fr; }
.shoppinglist-layout .shoppinglist-filters-col:has(.filters-band) { grid-column: 1 / -1; min-height: 0; }
.shoppinglist-filters-col .filters-band { border: none; box-shadow: none; background: none; padding: 0; }
@media (max-width: 1100px) {
  .shoppinglist-layout:has(.filters-band) { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 700px) {
  .shoppinglist-layout:has(.filters-band) { grid-template-columns: 1fr; }
  .filters-band-collapsible > .field:nth-of-type(2) { grid-column: auto; }
}

/* Class picker: each slot in its class color (user-requested 2026-09-24).
   --cls-rgb = "r,g,b" sampled from the class portrait (js/ui.js). */
.class-nav-portraits .class-btn.portrait {
  --cls-rgb: 148, 130, 201;
  background: linear-gradient(180deg, rgba(var(--cls-rgb), 0.2), rgba(var(--cls-rgb), 0.06));
  border: 1px solid rgba(var(--cls-rgb), 0.4);
  color: color-mix(in srgb, rgb(var(--cls-rgb)) 45%, var(--text));
  font-weight: 600;
  transition: transform 0.14s ease, border-color 0.14s, background-color 0.14s, box-shadow 0.14s;
}
.class-nav-portraits .class-btn.portrait .portrait-sprite { box-shadow: 0 0 0 1px rgba(var(--cls-rgb), 0.35); }
.class-nav-portraits .class-btn.portrait:hover {
  border-color: rgb(var(--cls-rgb));
  background: linear-gradient(180deg, rgba(var(--cls-rgb), 0.32), rgba(var(--cls-rgb), 0.1));
  color: var(--text);
  transform: translateY(-2px);
  box-shadow: 0 6px 16px -8px rgba(var(--cls-rgb), 0.8);
}
.class-nav-portraits .class-btn.portrait.active {
  border: 2px solid rgb(var(--cls-rgb));
  background: linear-gradient(180deg, rgba(var(--cls-rgb), 0.4), rgba(var(--cls-rgb), 0.14));
  color: var(--text);
  box-shadow: 0 0 0 3px rgba(var(--cls-rgb), 0.25), 0 6px 18px -8px rgba(var(--cls-rgb), 0.9);
}
@media (prefers-reduced-motion: reduce) { .class-nav-portraits .class-btn.portrait:hover { transform: none; } }

/* Build panel header: class + level only (user-requested 2026-09-24). The
   class name takes the class color (--build-accent-rgb, blended with the
   text color so it stays readable on every theme); the level is a badge. */
.build-info-summary {
  margin: 0; flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  font-size: 1.15rem; line-height: 1.2;
}
.build-info-summary .bis-class {
  font-weight: 800; letter-spacing: 0.01em; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: color-mix(in srgb, rgb(var(--build-accent-rgb, 148, 130, 201)) 55%, var(--text));
}
.build-info-summary .bis-level {
  flex-shrink: 0; font-size: 0.8rem; font-weight: 800; font-variant-numeric: tabular-nums;
  padding: 2px 9px; border-radius: 999px; color: var(--text);
  background: color-mix(in srgb, rgb(var(--build-accent-rgb, 148, 130, 201)) 22%, var(--bg-elevated));
  border: 1px solid color-mix(in srgb, rgb(var(--build-accent-rgb, 148, 130, 201)) 45%, var(--border));
}

/* Épique / Relique: taller sub rows (+50% height, user-requested
   2026-09-24): bigger icon and name. */
.dedicated-sub-slot.chasse-sub-slot-attached { padding: 6px 8px; gap: 8px; min-height: 42px; }
.dedicated-sub-slot .chasse-sub-icon { width: 30px; height: 30px; }
.dedicated-sub-slot.chasse-sub-slot-attached .spell-name { font-size: 0.92rem; font-weight: 600; }
.dedicated-sub-slot-empty { min-height: 42px; }

/* "Code enchantement" in the châsses palette: small caps label above one
   joined input group (field + copy/import buttons sharing one border). */
.apt-code-row.chasse-code-row-inline {
  display: flex; flex-direction: column; align-items: stretch; flex-wrap: nowrap;
  gap: 5px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border);
}
.chasse-code-row-inline label {
  font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--text-dim);
}
.code-input-group {
  display: flex; align-items: stretch; min-width: 0;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 6px; overflow: hidden;
  transition: border-color 0.15s;
}
.code-input-group:focus-within { border-color: var(--accent); }
.code-input-group input[type="text"] {
  flex: 1 1 auto; width: auto; min-width: 0; margin: 0;
  border: 0; border-radius: 0; background: transparent; outline: none;
  padding: 6px 8px; font-family: monospace; font-size: 0.78rem; text-overflow: ellipsis;
}
.code-input-group input[type="text"]:focus { outline: none; }
.code-input-group .btn.code-btn {
  flex: 0 0 auto; width: 32px; margin: 0; padding: 0;
  border: 0; border-left: 1px solid var(--border); border-radius: 0;
  background: transparent; color: var(--text-dim);
}
.code-input-group .btn.code-btn:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--text); }

/* ---------- Deck de sorts: cleaner render, same layout (2026-09-24) ----------
   Scoped to #spelldeck-panel so FightLab/Combo Maker keep their own look.
   Headings with a hairline, recessed game-like slots instead of dashed
   boxes, library icons without a second frame around their own, and the
   deck code as one joined input group like the châsses one. */
#spelldeck-panel .spell-deck-heading {
  display: flex; align-items: center; gap: 8px;
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em;
}
#spelldeck-panel .spell-deck-heading::after {
  content: ""; flex: 1; height: 1px; background: linear-gradient(90deg, var(--border), transparent);
}
#spelldeck-panel .deck-slot {
  border: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
  background: linear-gradient(180deg, color-mix(in srgb, var(--bg-elevated) 65%, #000), var(--bg-elevated));
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.35), inset 0 0 0 1px rgba(255, 255, 255, 0.03);
  transition: border-color 0.12s, box-shadow 0.12s, transform 0.1s;
}
#spelldeck-panel .deck-slot:hover {
  border-color: var(--accent);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.35), 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}
#spelldeck-panel .deck-slot.filled {
  border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
  background: var(--bg-elevated);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
#spelldeck-panel .deck-slot.filled:hover { border-color: var(--accent); }
/* Icon fills the whole slot (user-requested). */
#spelldeck-panel .deck-slot img { width: 100%; height: 100%; border-radius: 7px; }
#spelldeck-panel .deck-slot-empty { font-size: 1.1rem; opacity: 0.45; transition: opacity 0.12s, color 0.12s; }
#spelldeck-panel .deck-slot:hover .deck-slot-empty { opacity: 1; color: var(--accent); }
#spelldeck-panel .deck-slot.drag-over { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent) inset; }

#spelldeck-panel .lib-spell-tile {
  border: 1px solid transparent; background: transparent; border-radius: 8px;
  transition: box-shadow 0.12s, transform 0.1s, filter 0.12s;
}
#spelldeck-panel .lib-spell-tile:hover {
  border-color: transparent; filter: brightness(1.12);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent);
}
#spelldeck-panel .lib-spell-tile.selected {
  border-color: transparent;
  box-shadow: 0 0 0 2px var(--accent), 0 0 10px color-mix(in srgb, var(--accent) 45%, transparent);
}
#spelldeck-panel .lib-spell-check { box-shadow: 0 0 0 2px var(--bg-panel); }

.deck-code-row { flex-wrap: nowrap; gap: 10px; }
.deck-code-row label {
  font-size: 0.66rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim);
}
.deck-code-row .code-input-group { flex: 1 1 auto; }

/* ---------- Profile hover card (js/profilecard.js, 2026-09-24) ---------- */
.profile-hovercard {
  position: fixed; z-index: 3000; width: 300px; pointer-events: none;
  opacity: 0; transform: translateY(4px); transition: opacity 0.15s, transform 0.15s;
}
.profile-hovercard.visible { opacity: 1; transform: none; }
.profile-hovercard[hidden] { display: none; }
.phc-inner {
  --phc-rgb: 148, 130, 201;
  position: relative; overflow: hidden; isolation: isolate;
  display: flex; flex-direction: column; gap: 10px; padding: 12px 14px;
  border-radius: 12px; border: 1px solid rgba(var(--phc-rgb), 0.45);
  background: radial-gradient(120% 120% at 0% 0%, rgba(var(--phc-rgb), 0.24), transparent 60%), var(--bg-panel);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}
.phc-loading { flex-direction: row; align-items: baseline; gap: 8px; }
.phc-art {
  position: absolute; inset: 0 0 auto auto; width: 70%; height: 90px; z-index: -1;
  background-size: cover; background-position: right center; opacity: 0.22;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 60%), linear-gradient(180deg, #000 40%, transparent);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(90deg, transparent, #000 60%), linear-gradient(180deg, #000 40%, transparent);
  mask-composite: intersect;
}
.phc-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
.phc-portrait.build-row-portrait { width: 42px; height: 42px; flex-shrink: 0; border-radius: 9px; box-shadow: 0 0 0 2px rgba(var(--phc-rgb), 0.6); }
.phc-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.phc-pseudo { font-weight: 800; font-size: 1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.phc-sub { font-size: 0.78rem; color: color-mix(in srgb, rgb(var(--phc-rgb)) 50%, var(--text-dim)); }
.phc-nation { width: 34px; height: 34px; object-fit: contain; flex-shrink: 0; filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5)); }
.phc-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.phc-stat {
  display: flex; flex-direction: column; align-items: center; gap: 1px; padding: 6px 4px; border-radius: 8px;
  background: color-mix(in srgb, var(--bg-elevated) 80%, transparent); border: 1px solid var(--border);
}
.phc-stat b { font-size: 1rem; font-variant-numeric: tabular-nums; }
.phc-stat span { font-size: 0.64rem; color: var(--text-dim); text-align: center; }
.phc-classes { display: flex; gap: 8px; flex-wrap: wrap; }
.phc-class { display: inline-flex; align-items: center; gap: 4px; font-size: 0.78rem; }
.phc-class .build-row-portrait { width: 22px; height: 22px; border-radius: 5px; }
.phc-bio {
  margin: 0; font-size: 0.76rem; line-height: 1.35; color: var(--text-dim);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.phc-foot { font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); opacity: 0.8; }

/* Filters that change how many cards are shown (profile Tous/Publics/
   Privés, Builds publics…) made the page's scrollbar appear/disappear,
   shifting everything sideways — its space is now always reserved. The
   profile's build area also keeps a minimum height so a short result list
   doesn't collapse the page under the cursor. (2026-09-24) */
html { scrollbar-gutter: stable; }
.profile-content { min-height: 70vh; }

/* Aptitudes at mid widths (2026-09-24): the 5 categories need ~1240px of
   panel width side by side; below that the fixed 5-column grid overflowed
   the panel (and the page). Tracks can now shrink, and a panel narrower than
   that switches to 3 columns (Intelligence/Force/Agilité, then Chance/Majeur)
   before the existing 2-column step at 860px. */
.apt-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.apt-category { min-width: 0; }
@container (min-width: 861px) and (max-width: 1259px) {
  .apt-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Equipment row at mid widths (2026-09-24): wraps onto a second row instead
   of a horizontal scrollbar when the panel is too narrow for all slots;
   at full width everything still fits on one row as before. */
#build-doll { flex-wrap: wrap; overflow-x: visible; row-gap: 10px; }

/* Public build card: "Comparer" icon button (2026-09-24), fixed size like the like button. */
.pb-card-actions .pb-compare-btn { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; padding: 8px 11px; }
.pb-card-actions .pb-compare-btn .ui-icon { width: 17px; height: 17px; }

/* Share button confirmation (planner.js shareLink): check mark, green, for a moment. */
.build-bar-btn.copied { color: #6fce7a; border-color: #6fce7a; background: color-mix(in srgb, #6fce7a 14%, var(--bg-elevated)); }

/* Public/private badge on build portraits, more legible (2026-09-24):
   bigger, a ring separating it from the portrait art, public = green
   with a globe, private = dark with a light lock. Whole-pixel sizes only
   (1px border, badge and icon of the same parity) so the icon stays exactly
   centered at any screen zoom — a 1.5px border rounded unevenly at 125%
   and pushed the icon ~1px off-center. */
.build-row-visibility-badge {
  box-sizing: border-box; width: 18px; height: 18px; bottom: -4px; left: -4px; z-index: 1;
  display: grid; place-items: center; padding: 0;
  background: #1b1824; border: 1px solid #cfc8e6; color: #f1eefa;
  box-shadow: 0 0 0 2px var(--bg-panel), 0 1px 3px rgba(0, 0, 0, 0.5);
}
.build-row-visibility-badge .ui-icon { width: 10px; height: 10px; stroke-width: 2.6; margin: 0; }
.build-row-visibility-badge.public { background: #2f9e4f; border-color: #8ee6a4; color: #fff; }
.build-row-portrait-big .build-row-visibility-badge { width: 24px; height: 24px; bottom: -5px; left: -5px; border-width: 1px; }
.build-row-portrait-big .build-row-visibility-badge .ui-icon { width: 14px; height: 14px; }

/* FightLab — Xélor hour tiles around the Cadran (2026-09-24, redrawn after
   user feedback: the dotted outline looked cheap). A glowing white
   cogwheel lying flat on the tile (SVG squashed to the iso perspective),
   with the Roman numeral standing on it; the heure courante glows teal. */
.fightlab-xelor-hour {
  position: absolute; bottom: 0; left: 50%; margin-left: -24px; width: 48px; height: 24px; z-index: 1;
  pointer-events: none; color: rgba(236, 242, 255, 0.92);
  transform: scale(0.85); transform-origin: 50% 50%; /* -15 %, user-requested */
}
.fightlab-xelor-hour-gear {
  position: absolute; left: 0; top: -12px; width: 48px; height: 48px;
  transform: scaleY(0.5); transform-origin: 50% 50%;
  filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.55));
}
.fightlab-xelor-hour-gear .disc { fill: rgba(20, 24, 38, 0.55); stroke: none; }
.fightlab-xelor-hour-num {
  position: absolute; inset: 0; display: grid; place-items: center;
  font: 700 10px/1 Georgia, "Times New Roman", serif; color: #f4f6ff;
  letter-spacing: 0.03em; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
}
.fightlab-xelor-hour.current { color: #8ff5e6; }
.fightlab-xelor-hour.current .fightlab-xelor-hour-gear {
  filter: drop-shadow(0 0 3px rgba(143, 245, 230, 0.95)) drop-shadow(0 0 7px rgba(143, 245, 230, 0.6));
}
.fightlab-xelor-hour.current .fightlab-xelor-hour-gear .disc { fill: rgba(143, 245, 230, 0.28); }
.fightlab-xelor-hour.current .fightlab-xelor-hour-num { color: #eafffb; text-shadow: 0 0 4px rgba(143, 245, 230, 0.9), 0 1px 2px rgba(0, 0, 0, 0.8); }

/* Xélor Cadran: the source image is a 200px canvas with a thin needle in
   it — drawn bigger (from its base) so the needle actually reads on the board. */
.fightlab-entity-sprite.fightlab-xelor-cadran-sprite { transform: scale(2.2); transform-origin: 50% 100%; bottom: 4px; }
.fightlab-board.sprites-transparent .fightlab-xelor-cadran-sprite { opacity: 0.3; }

/* ---------- FightLab: turn history + button icons (2026-09-24) ---------- */
.fightlab-log { gap: 6px; padding-right: 2px; }
.fightlab-log-turn {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; padding: 0; overflow: hidden;
  /* overflow:hidden drops the flex min-height to 0, so the turns shrank to
     fit the log instead of making it scroll — keep their natural height. */
  flex-shrink: 0;
}
.fightlab-log-turn.live { border-color: color-mix(in srgb, var(--accent) 70%, var(--border)); background: color-mix(in srgb, var(--accent) 8%, var(--bg-elevated)); }
.fightlab-log-turn > summary { list-style: none; cursor: pointer; }
.fightlab-log-turn > summary::-webkit-details-marker { display: none; }
.fightlab-log-turn-head {
  display: flex; align-items: center; gap: 8px; margin: 0; padding: 7px 10px; font-weight: 400; font-size: 0.8rem;
  border-bottom: 1px solid transparent;
}
.fightlab-log-turn[open] .fightlab-log-turn-head, .fightlab-log-turn.live .fightlab-log-turn-head { border-bottom-color: var(--border); }
.fightlab-log-turn > summary .fightlab-log-turn-head::before {
  content: ""; width: 6px; height: 6px; border-right: 1.5px solid var(--text-dim); border-bottom: 1.5px solid var(--text-dim);
  transform: rotate(-45deg); transition: transform 0.15s; flex-shrink: 0; margin-right: -2px;
}
.fightlab-log-turn[open] > summary .fightlab-log-turn-head::before { transform: rotate(45deg); }
.fightlab-log-turn-badge {
  display: inline-flex; align-items: center; gap: 6px; font-weight: 800; font-size: 0.78rem;
  padding: 2px 8px; border-radius: 999px; background: var(--bg-panel); border: 1px solid var(--border); color: var(--text);
}
.fightlab-log-turn.live .fightlab-log-turn-badge { border-color: var(--accent); }
.fightlab-log-turn-state { font-size: 0.7rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.06em; }
.fightlab-log-chips { margin-left: auto; display: flex; gap: 4px; }
.fightlab-log-chip { font-weight: 800; font-size: 0.78rem; padding: 1px 7px; border-radius: 999px; font-variant-numeric: tabular-nums; }
.fightlab-log-chip.dmg { color: #ff8a80; background: color-mix(in srgb, var(--danger) 16%, transparent); }
.fightlab-log-chip.heal { color: #5fe0a0; background: rgba(24, 195, 114, 0.15); }
.fightlab-log-chip.armor { color: var(--wk-armor); background: color-mix(in srgb, var(--wk-armor) 15%, transparent); }
.fightlab-log-rows { gap: 1px; padding: 4px; }
.fightlab-log-row {
  grid-template-columns: 26px minmax(0, 1fr) auto; gap: 9px; padding: 4px 6px; border-radius: 6px; font-size: 0.78rem;
}
.fightlab-log-row:nth-child(odd) { background: transparent; }
.fightlab-log-row:hover { background: color-mix(in srgb, var(--bg-panel) 70%, transparent); }
.fightlab-log-row.muted { opacity: 0.6; }
.fightlab-log-spell-icon { width: 26px; height: 26px; border-radius: 6px; box-shadow: 0 0 0 1px var(--border); }
.fightlab-log-spell-icon.placeholder { display: block; background: var(--bg-panel); }
.fightlab-log-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.fightlab-log-spell-name { font-size: 0.8rem; }
.fightlab-log-sub { font-size: 0.7rem; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fightlab-log-position { font-size: 0.66rem; font-weight: 700; }
.fightlab-log-amounts { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; font-variant-numeric: tabular-nums; }
.fightlab-log-damage { font-size: 0.86rem; font-weight: 800; }
.fightlab-log-damage.crit { color: #ffcc33; }
.fightlab-log-crit-tag { font-size: 0.56rem; margin-left: 4px; padding: 0 4px; border-radius: 3px; background: rgba(255, 204, 51, 0.18); letter-spacing: 0.05em; vertical-align: 1px; }
.fightlab-log-heal, .fightlab-log-armor { font-size: 0.78rem; }
.fightlab-log-none { color: var(--text-dim); }
.fightlab-log-empty { margin: 0; padding: 8px 10px; font-size: 0.76rem; color: var(--text-dim); font-style: italic; }
/* Button icons (replacing 📍 👻 🎯 ✓ ✗). */
.fightlab-target-select { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 6px; }
.fightlab-target-select .ui-icon { width: 16px; height: 16px; }
.fightlab-target-select.shown { color: var(--accent); }
.fightlab-target-select:hover { background: var(--bg-elevated); color: var(--text); }
.fightlab-target-parade-mark { display: inline-flex; }
.fightlab-target-parade-mark .ui-icon { width: 12px; height: 12px; stroke-width: 3; }

/* ---------- FightLab board hover card (2026-09-24 redesign) ---------- */
.item-tooltip.fightlab-tooltip { width: 236px; padding: 0; background: color-mix(in srgb, var(--bg-panel) 94%, transparent); overflow: hidden; }
.fightlab-tooltip { align-items: stretch; text-align: left; gap: 0; }
.fightlab-tooltip-card { display: flex; flex-direction: column; gap: 7px; padding: 10px; border-top: 3px solid var(--accent); }
.fightlab-tooltip-card.enemy { border-top-color: var(--danger); }
.fightlab-tooltip-card.ally { border-top-color: #4caf50; }
.fightlab-tooltip-head { justify-content: flex-start; gap: 9px; }
.fightlab-tooltip-portrait { width: 38px; height: 38px; border-radius: 9px; padding: 0; flex-shrink: 0; background-color: var(--bg-elevated); }
.fightlab-tooltip-portrait.class { display: block; background-repeat: no-repeat; border: 1px solid var(--border); }
.fightlab-tooltip-portrait.empty { display: block; border: 1px dashed var(--border); }
.fightlab-tooltip-head-text { gap: 3px; min-width: 0; }
.fightlab-tooltip-name { font-size: 0.9rem; font-weight: 800; }
.fightlab-tooltip-meta { display: flex; align-items: center; gap: 6px; }
.fightlab-tooltip-role { font-size: 0.6rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em; padding: 1px 6px; border-radius: 999px; }
.fightlab-tooltip-role.player { background: color-mix(in srgb, var(--accent) 25%, transparent); color: var(--text); }
.fightlab-tooltip-role.enemy { background: color-mix(in srgb, var(--danger) 22%, transparent); color: #ffb3ad; }
.fightlab-tooltip-role.ally { background: rgba(76, 175, 80, 0.2); color: #9fe3a2; }
.fightlab-tooltip-level { font-size: 0.7rem; }
.fightlab-tooltip .fightlab-hpbar-row { width: 100%; }
.fightlab-tooltip .fightlab-hpbar { flex: 1; }
.fightlab-tooltip-res { display: flex; gap: 6px; }
.fightlab-tooltip-res-item {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 4px; padding: 3px 0;
  border-radius: 6px; background: var(--bg-elevated); border: 1px solid var(--border); font-size: 0.78rem;
}
.fightlab-tooltip-res-item .stat-icon { width: 14px; height: 14px; }
.fightlab-tooltip-section { font-size: 0.6rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); margin-bottom: -3px; }
.fightlab-tooltip-states-list { display: flex; flex-direction: column; gap: 3px; }
.fightlab-tooltip-state {
  display: flex; align-items: center; gap: 6px; padding: 3px 6px; border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 10%, var(--bg-elevated)); font-size: 0.78rem;
}
.fightlab-tooltip-state img, .fightlab-tooltip-state-letter { width: 16px; height: 16px; border-radius: 4px; flex-shrink: 0; object-fit: cover; }
.fightlab-tooltip-state-letter { display: grid; place-items: center; background: var(--bg-panel); font-size: 0.62rem; font-weight: 800; }
.fightlab-tooltip-state-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fightlab-tooltip-state-lvl { margin-left: auto; font-size: 0.7rem; font-weight: 700; color: var(--accent); white-space: nowrap; }
.fightlab-tooltip .fightlab-tooltip-resist { max-width: none; grid-template-columns: repeat(4, 1fr); gap: 4px; padding: 5px 6px; }

/* FightLab states panel — in-game buff bar look (2026-09-25): square
   icons, level/stacks bottom-right, remaining turns top-left. */
.fightlab-states-panel { width: auto; max-width: 260px; align-items: flex-end; }
.fightlab-states-panel-cards { flex-direction: row; flex-wrap: wrap; justify-content: flex-end; gap: 5px; overflow: visible; }
.fightlab-state-tile {
  position: relative; width: 36px; height: 36px; border-radius: 4px; overflow: visible; flex-shrink: 0;
  background: #14121c; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18), 0 0 0 2px rgba(0, 0, 0, 0.6), 0 2px 4px rgba(0, 0, 0, 0.5);
}
.fightlab-state-tile img { width: 100%; height: 100%; object-fit: cover; border-radius: 4px; display: block; }
.fightlab-state-tile-letter {
  width: 100%; height: 100%; display: grid; place-items: center; border-radius: 4px;
  background: color-mix(in srgb, var(--accent) 45%, #14121c); color: #fff; font-weight: 800; font-size: 0.9rem;
}
.fightlab-state-tile-num, .fightlab-state-tile-turns {
  position: absolute; font-weight: 800; line-height: 1; color: #fff; pointer-events: none;
  text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000, 0 0 3px #000;
}
.fightlab-state-tile-num { right: 1px; bottom: 1px; font-size: 0.8rem; }
.fightlab-state-tile-turns { left: 1px; top: 1px; font-size: 0.66rem; }

/* HDV price history chart in item tooltips (2026-09-25) */
.price-history { margin: 6px 0 4px; padding: 6px 8px 4px; border: 1px solid var(--border); border-radius: 6px; background: color-mix(in srgb, var(--bg) 55%, transparent); }
.price-history-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-dim); }
.price-history-trend { font-weight: 700; font-variant-numeric: tabular-nums; text-transform: none; letter-spacing: 0; }
.price-history-trend.up { color: #4fb477; }
.price-history-trend.down { color: #d9675f; }
.price-history-trend.flat { color: var(--text-dim); }
.price-history-chart { display: block; width: 100%; height: auto; margin-top: 2px; overflow: visible; }
.price-history-base { stroke: var(--border); stroke-width: 1; }
.price-history-area { fill: #d4af37; fill-opacity: 0.14; stroke: none; }
.price-history-line { fill: none; stroke: #d4af37; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; }
.price-history-dots circle { fill: #d4af37; stroke: var(--bg-panel); stroke-width: 1; }
.price-history-labels text { fill: var(--text-dim); font-size: 9px; font-family: var(--font); }
.price-history-range { display: flex; justify-content: space-between; font-size: 0.7rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.price-history-tip-head { display: flex; align-items: center; gap: 5px; font-size: 0.8rem; color: var(--text-dim); }
.price-history-tip-head img { width: 14px; height: 14px; }
.price-history-tip-head b { color: #d4af37; font-variant-numeric: tabular-nums; }
.build-cost-val.build-cost-missing { color: var(--text-dim); cursor: help; text-decoration: underline dotted; }

/* Shopping list: ⇄ glyph centered in its button; expanded components stay
   listed (dimmed, crossed qty) so the toggle can be clicked back (2026-09-25) */
.shoppinglist-expand-toggle { display: inline-flex; align-items: center; justify-content: center; padding: 0; }
.shoppinglist-course-row.expanded .shoppinglist-search-name,
.shoppinglist-course-row.expanded .shoppinglist-course-qty { opacity: 0.55; }
.shoppinglist-course-row.expanded .shoppinglist-course-qty { text-decoration: line-through; }
/* Shopping list tree: expanded component's ingredients listed beneath it */
.shoppinglist-course-row.sub { margin-left: calc(var(--depth) * 18px); position: relative; }
.shoppinglist-course-row.sub::before { content: ""; position: absolute; left: -10px; top: -4px; bottom: 50%; width: 8px; border-left: 1px solid var(--border); border-bottom: 1px solid var(--border); border-bottom-left-radius: 4px; }
.shoppinglist-course-row.expanded { border-left: 3px solid #c98a3e; padding-left: 4px; }
.shoppinglist-tree-caret { width: 20px; flex-shrink: 0; text-align: center; color: #c98a3e; font-size: 0.8rem; }
.shoppinglist-course-row.expanded .shoppinglist-course-qty { text-decoration: none; }

/* Filters band layout (2026-09-25, user: "revois la disposition des
   filtres" on Liste de courses): the tall Type grid used to push Rareté
   alone onto a second row with dead space around it. Wide screens now get
   a fixed 5-column map — Recherche over Rareté on the left, Type spanning
   both rows in the middle, Niveau + Trier par on the right with the reset
   button under them. Extra filters ("Plus de filtres") flow below. */
@media (min-width: 1100px) {
  .filters.filters-band.filters-band-collapsible { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .filters-band-collapsible > .field:nth-of-type(1) { grid-column: 1; grid-row: 1; }
  .filters-band-collapsible > .field:nth-of-type(2) { grid-column: 2 / span 2; grid-row: 1 / span 2; }
  .filters-band-collapsible > .field:nth-of-type(3) { grid-column: 4; grid-row: 1; }
  .filters-band-collapsible > .field:nth-of-type(4) { grid-column: 5; grid-row: 1; }
  .filters-band-collapsible > .field:nth-of-type(5) { grid-column: 1; grid-row: 2; align-self: end; }
  .filters.filters-band.filters-band-collapsible > .btn { grid-column: 4 / span 2; grid-row: 2; justify-self: end; align-self: end; }
}
/* Liste de courses: same panel look as the other pages' filter bands. */
.shoppinglist-filters-col .filters-band { background: var(--bg-panel); border: 1px solid var(--border); padding: 14px 16px; border-radius: var(--radius); }
.shoppinglist-filters-col > h3 { display: none; }
/* Coût du build: sublimations (2026-09-27) — 3 columns, price right. */
.build-cost-subs { margin-top: 4px; }
.build-cost-subs .build-cost-subrow { grid-template-columns: 22px minmax(0, 1fr) 80px; }
.build-cost-subs-total { color: #d4af37; font-weight: 700; font-variant-numeric: tabular-nums; }
.build-cost-subs summary small { color: var(--text-dim); }
/* Coût du build: zebra rows (items + sublimations) for readability (2026-09-27) */
.build-cost-list .build-cost-row:nth-child(even) { background: color-mix(in srgb, var(--text) 5%, transparent); }
.build-cost-list .build-cost-row:hover { background: var(--bg-elevated); }
.build-cost-list .build-cost-row.copied { background: color-mix(in srgb, var(--accent) 25%, transparent); }

/* Price history chart v2 (2026-09-27) */
.price-history-count { text-transform: none; letter-spacing: 0; font-variant-numeric: tabular-nums; }
.price-history-guide { stroke: var(--border); stroke-width: 1; stroke-dasharray: 3 3; }
.price-history-val { fill: var(--text); font-size: 9px; font-weight: 700; font-family: var(--font); font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--bg-panel); stroke-width: 3px; }
.price-history-stats { display: flex; justify-content: space-between; gap: 8px; font-size: 0.78rem; color: var(--text-dim); margin-top: 2px; }
.price-history-stats b { font-variant-numeric: tabular-nums; }
.price-history-range b { color: var(--text); font-weight: 600; }
/* Compact price history strip in item tooltips (2026-09-27) */
.price-history-compact { padding: 5px 8px; margin: 6px 0 4px; }
.price-history-compact .phc-line { display: flex; justify-content: space-between; gap: 8px; font-size: 0.7rem; color: var(--text-dim); white-space: nowrap; }
.price-history-compact .phc-line b { font-variant-numeric: tabular-nums; }
.price-history-compact .phc-chart { display: flex; align-items: center; gap: 6px; margin: 3px 0; }
.price-history-compact .phc-chart svg { flex: 1; min-width: 0; height: 28px; overflow: visible; }
.price-history-compact .phc-end { font-size: 0.7rem; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Full chart (price-badge hover): never wrap its stat lines either */
.price-history-stats, .price-history-range { white-space: nowrap; }

/* ================================================================
   Equipment row rework (2026-09-27, maquette 4 validée): same pieces as
   before — rarity frames, alt-slot n° top-left, per-item element /
   resistance toggles, mastery/resistance order — dressed up: items sit on
   a shared "shelf", filled slots get a rarity-tinted glow backdrop, the
   toggles live in a small tray under the item (4 masteries on one line, 4
   resistances on the next, chosen ones lit, others dimmed), and the order
   buttons (original design) sit in a matching card on the right.
   Scoped to #build-doll / #elem-priority-popover only.
   ================================================================ */
#build-doll {
  gap: 8px; padding: 10px 10px 8px; border-radius: 12px;
  background: linear-gradient(180deg, rgba(255,255,255,.025), rgba(0,0,0,.18));
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
#build-doll .doll-slot-col { gap: 6px; }
#build-doll .slot { width: 60px; height: 60px; border-radius: 10px; overflow: visible; transition: transform .15s ease, box-shadow .15s ease; }
#build-doll .slot.filled {
  background: radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--r) 34%, var(--bg)), var(--bg) 74%);
  box-shadow: 0 0 0 1px rgba(0,0,0,.45), 0 6px 14px -6px color-mix(in srgb, var(--r) 85%, transparent), inset 0 0 12px -4px color-mix(in srgb, var(--r) 60%, transparent);
}
#build-doll .slot.filled img.icon { inset: 7px; width: calc(100% - 14px); height: calc(100% - 14px); filter: drop-shadow(0 3px 3px rgba(0,0,0,.6)); }
#build-doll .slot.filled img.frame { border-radius: 10px; }
#build-doll .slot.filled:hover { transform: translateY(-3px); box-shadow: 0 0 0 1px color-mix(in srgb, var(--r) 70%, transparent), 0 10px 18px -6px color-mix(in srgb, var(--r) 90%, transparent); }
#build-doll .slot:not(.filled):not(.slot-search-btn) {
  background: repeating-linear-gradient(135deg, rgba(255,255,255,.025) 0 6px, transparent 6px 12px), var(--bg);
  border: 1.5px dashed color-mix(in srgb, var(--text-dim) 35%, var(--border));
}
#build-doll .slot:not(.filled):not(.slot-search-btn):hover { border-color: var(--accent); }
#build-doll .altslot-index { top: 3px; left: 3px; border-radius: 4px; z-index: 3; }
/* Toggle tray */
#build-doll .doll-slot-toggles {
  gap: 2px; padding: 3px; border-radius: 8px; width: 60px; box-sizing: border-box; align-items: center;
  background: var(--bg); border: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
}
#build-doll .elem-choice-mini { grid-template-columns: repeat(4, 1fr); gap: 1px; }
#build-doll .elem-choice-mini-btn { width: 13px; height: 13px; padding: 0; border: 0; border-radius: 3px; background: transparent; opacity: .22; filter: grayscale(1); }
#build-doll .elem-choice-mini-btn:hover { opacity: .7; filter: none; }
#build-doll .elem-choice-mini-btn.active { opacity: 1; filter: drop-shadow(0 1px 1px rgba(0,0,0,.6)); background: rgba(255,255,255,.06); box-shadow: none; }
/* Order card (buttons keep their original look) */
#elem-priority-popover.elem-priority-inline {
  width: auto; padding: 12px 14px 10px 16px; border-radius: 12px;
  background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(0,0,0,.2));
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  display: flex; flex-direction: column; gap: 12px;
}
#elem-priority-popover .elem-priority-group { margin: 0; }
#elem-priority-popover .elem-priority-group h5 { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; letter-spacing: .08em; }
#elem-priority-popover .elem-priority-group h5::after { content: ""; flex: 1; height: 1px; background: linear-gradient(90deg, var(--border), transparent); }
#elem-priority-popover .elem-priority-row { gap: 8px; }
@media (max-width: 767px) {
  #build-doll .slot { width: 100%; height: auto; }
  #build-doll .slot.filled img.icon { inset: 4px; width: calc(100% - 8px); height: calc(100% - 8px); }
  #build-doll .doll-slot-toggles { display: none; }
}
/* Two-handed weapon echoed, faded, in the empty second-hand slot (like in game) */
.slot.slot-2h .slot-2h-ghost { position: absolute; inset: 0; opacity: .38; filter: grayscale(.35); pointer-events: none; }
.slot.slot-2h .slot-2h-ghost img.icon { position: absolute; inset: 7px; width: calc(100% - 14px); height: calc(100% - 14px); object-fit: contain; }
.slot.slot-2h .slot-2h-ghost img.frame { position: absolute; inset: 0; width: 100%; height: 100%; }
#build-doll .slot.slot-2h:not(.filled), #build-picker-doll .slot.slot-2h:not(.filled) { border-style: solid; border-color: transparent; background: var(--bg); }
/* Same hover as an equipped slot (2026-10-03, user): no accent border, the
   echoed weapon art grows inside its frame. */
#build-doll .slot.slot-2h:not(.filled):hover, #build-picker-doll .slot.slot-2h:not(.filled):hover { border-color: transparent; }
#build-doll .slot.slot-2h .slot-2h-ghost img.icon { transition: transform .15s ease; }
#build-doll .slot.slot-2h:not(.filled):hover .slot-2h-ghost img.icon { transform: scale(1.14); }
/* 2026-09-27 follow-up: flat backgrounds (no gradient) on the equipment
   shelf and the order card; order card made shorter — titles hidden
   (kept as tooltips on the rows), the two rows stacked tight. */
#build-doll { background: rgba(0,0,0,.14); }
#elem-priority-popover.elem-priority-inline { background: rgba(0,0,0,.14); padding: 12px 12px 8px 14px; gap: 12px; }
#elem-priority-popover .elem-priority-group h5 { display: none; }
/* Rarity frame textures have square, ornamented corners — don't round-crop
   them, and match the filled slot's glow backdrop to that square shape. */
#build-doll .slot.filled img.frame { border-radius: 0; }
#build-doll .slot.filled { border-radius: 4px; }
.chasse-header > .chasse-title { margin: 0; align-self: flex-start; padding-top: 6px; }
/* Hover: the item art grows inside its frame instead of the whole slot lifting. */
#build-doll .slot.filled:hover { transform: none; }
#build-doll .slot.filled img.icon { transition: transform .15s ease; }
#build-doll .slot.filled:hover img.icon { transform: scale(1.14); }
/* No icon zoom/tilt when hovering an item card (2026-09-27, user). */
.item-card:hover .item-icon-frame img.icon { transform: none; }

/* Objets épinglés — visual rework (2026-09-27). Rarity-tinted cards like
   the equipment row, larger icon with glow backdrop, stats as pills,
   equip/remove as small icon buttons revealed on hover, count badge in the
   title, dashed empty state with a pin icon. */
#build-pinned-side .mini-stats-title-row { padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid var(--border); }
#build-pinned-side .pinned-count:not(:empty) {
  display: inline-block; min-width: 16px; height: 16px; line-height: 16px; padding: 0 4px; margin-left: 4px; border-radius: 8px;
  background: var(--accent); color: var(--accent-text); font-size: 0.62rem; text-align: center; letter-spacing: 0; vertical-align: 1px;
}
#build-pinned-side .pinned-list { gap: 6px; }
#build-pinned-side .pinned-card {
  gap: 9px; padding: 6px 8px 6px 6px; border-radius: 8px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--rarity-color) 14%, var(--bg-elevated)), var(--bg-elevated) 55%);
  border: 1px solid color-mix(in srgb, var(--rarity-color) 30%, var(--border));
  border-left: 3px solid var(--rarity-color);
  transition: border-color .15s ease, background .15s ease;
}
#build-pinned-side .pinned-card:hover { border-color: color-mix(in srgb, var(--rarity-color) 70%, var(--border)); }
#build-pinned-side .pinned-card-icon {
  width: 40px; height: 40px; border-radius: 4px;
  background: radial-gradient(circle at 50% 40%, color-mix(in srgb, var(--rarity-color) 30%, var(--bg)), var(--bg) 75%);
}
#build-pinned-side .pinned-card-icon img.icon { inset: 5px; width: calc(100% - 10px); height: calc(100% - 10px); filter: drop-shadow(0 2px 2px rgba(0,0,0,.55)); }
#build-pinned-side .pinned-card-name { font-size: 0.78rem; font-weight: 600; color: var(--text); }
#build-pinned-side .pinned-card-level { font-size: 0.66rem; margin-top: 1px; }
#build-pinned-side .pinned-card-stats { gap: 4px; margin-top: 4px; }
#build-pinned-side .pinned-card-stat {
  gap: 3px; padding: 1px 5px 1px 3px; border-radius: 999px; font-size: 0.66rem; font-weight: 600; font-variant-numeric: tabular-nums;
  background: var(--bg); border: 1px solid var(--border);
}
#build-pinned-side .pinned-card-stat img { width: 12px; height: 12px; }
#build-pinned-side .pinned-card-actions {
  position: absolute; top: 50%; right: 5px; transform: translateY(-50%); display: flex; flex-direction: column; gap: 3px;
  opacity: 0; transition: opacity .15s ease;
}
#build-pinned-side .pinned-card:hover .pinned-card-actions, #build-pinned-side .pinned-card:focus-within .pinned-card-actions { opacity: 1; }
#build-pinned-side .pinned-card-equip, #build-pinned-side .pinned-card-remove {
  position: static; width: 20px; height: 20px; display: grid; place-items: center; padding: 0; border-radius: 5px;
  background: var(--bg-panel); border: 1px solid var(--border); color: var(--text-dim);
}
#build-pinned-side .pinned-card-equip .ui-icon, #build-pinned-side .pinned-card-remove .ui-icon { width: 12px; height: 12px; }
#build-pinned-side .pinned-card-equip { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); }
#build-pinned-side .pinned-card-equip:hover { background: var(--accent); color: var(--accent-text); }
#build-pinned-side .pinned-card-remove:hover { color: var(--danger); border-color: var(--danger); background: var(--bg-panel); }
#build-pinned-side .pinned-empty {
  display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;
  padding: 14px 10px; border: 1.5px dashed var(--border); border-radius: 8px;
  font-size: 0.78rem; color: var(--text-dim); line-height: 1.35;
}
#build-pinned-side .pinned-empty-icon { width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; background: var(--bg-elevated); color: var(--accent); }
#build-pinned-side .pinned-empty-icon svg { width: 16px; height: 16px; }
#build-pinned-side .pinned-card { padding-right: 30px; }
#build-pinned-side #pinned-clear-btn { padding: 5px; }
#build-pinned-side #pinned-clear-btn .ui-icon { width: 15px; height: 15px; }
#build-pinned-side .mini-stats-wrap:not(.has-pinned) #pinned-clear-btn { visibility: hidden; }

/* 768-860px: the builder column layout left every panel at its 260px
   min-width (flex column + align-items:flex-start, and container-type
   panels have no intrinsic width) — stretch them to the full width. */
@media (max-width: 860px) {
  .builder-layout { align-items: stretch; }
}
@media (max-width: 767px) {
  #build-doll .doll-slot-toggles { display: flex; }
}
/* The later base `.apt-grid { repeat(5, minmax(0,1fr)) }` rule above was
   overriding the earlier ≤860px/≤480px container steps (same specificity,
   later wins) — 5 columns in a 780px panel overflowed the page. */
@container (min-width: 481px) and (max-width: 860px) { .apt-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container (max-width: 480px) { .apt-grid { grid-template-columns: minmax(0, 1fr); } }

/* Mobile only (2026-09-27, user: "3 de large 5 hauteur, seulement sur
   mobile"): equipment as a 3-column grid — 14 slots + search = 5 rows —
   with each slot's element tray kept under it. */
@media (max-width: 767px) {
  #build-doll { display: grid; grid-template-columns: repeat(3, 72px); justify-content: center; column-gap: 14px; row-gap: 12px; flex: 1 1 100% !important; min-width: 0 !important; box-sizing: border-box; }
  #build-doll .doll-slot-col { width: 72px; flex: none; }
  #build-doll .slot { width: 64px; height: 64px; aspect-ratio: auto; }
  #build-doll .slot.filled img.icon { inset: 7px; width: calc(100% - 14px); height: calc(100% - 14px); }
  #build-doll .doll-slot-toggles { display: flex; width: 64px; }
}

/* Picker icon-only mode (2026-09-27; mobile too since the same day): results as a grid of framed
   icons with a rarity glow; the full item tooltip shows on hover
   (ui.js, [data-tooltip-icon]). Click still equips, right-click still pins. */
.icon-cards-toggle-btn .ui-icon { width: 16px; height: 16px; }
@media all {
  body.icon-cards #build-picker-grid .item-grid { grid-template-columns: repeat(auto-fill, 64px); gap: 8px; justify-content: start; }
  body.icon-cards #build-picker-grid .item-card {
    width: 64px; height: 64px; padding: 4px; box-sizing: border-box; display: grid; place-items: center;
    border: 1px solid color-mix(in srgb, var(--rarity-color) 45%, var(--border)); border-radius: 8px;
    background: radial-gradient(circle at 50% 40%, color-mix(in srgb, var(--rarity-color) 26%, var(--bg)), var(--bg-panel) 75%);
    transform: none;
  }
  body.icon-cards #build-picker-grid .item-card:hover { border-color: var(--rarity-color); transform: none; box-shadow: 0 4px 12px -4px color-mix(in srgb, var(--rarity-color) 80%, transparent); }
  body.icon-cards #build-picker-grid .item-card > *:not(.card-header):not(.item-icon-frame) { display: none; }
  body.icon-cards #build-picker-grid .item-card .card-header { display: contents; }
  body.icon-cards #build-picker-grid .item-card .card-header > *:not(.item-icon-frame) { display: none; }
  body.icon-cards #build-picker-grid .item-card .item-icon-frame { width: 54px; height: 54px; }
  body.icon-cards #build-picker-grid .item-card.pinned { box-shadow: inset 0 0 0 2px var(--danger); }
}
/* Icon mode: chosen value (mastery / resist / weight) under each icon, and
   the 3-way picker next to the mode button (only shown while it's on). */
.icon-metric-picker { display: none; gap: 2px; padding: 2px; border-radius: 8px; background: var(--bg-elevated); border: 1px solid var(--border); align-self: center; }
.icon-metric-picker button { width: 26px; height: 26px; padding: 3px; border: 0; border-radius: 6px; background: transparent; cursor: pointer; opacity: .45; }
.icon-metric-picker button img { width: 100%; height: 100%; object-fit: contain; }
.icon-metric-picker button:hover { opacity: .8; }
.icon-metric-picker button.active { opacity: 1; background: color-mix(in srgb, var(--accent) 25%, transparent); box-shadow: inset 0 0 0 1px var(--accent); }
@media all {
  body.icon-cards .icon-metric-picker { display: inline-flex; }
  body.icon-cards #build-picker-grid .item-card { height: 80px; grid-template-rows: 56px 16px; row-gap: 2px; padding: 4px 3px 3px; }
  body.icon-cards #build-picker-grid .item-card > .card-stat-row.card-stat-row.card-stat-row { display: flex; justify-content: center; min-width: 0; }
  body.icon-cards #build-picker-grid .item-card > .card-stat-row > :not(.item-card-badges) { display: none; }
  body.icon-cards #build-picker-grid .item-card .item-card-badges { display: flex; gap: 0; margin: 0; }
  body.icon-cards #build-picker-grid .item-card .item-card-badge { display: none; }
  body.icon-cards.icon-metric-mastery #build-picker-grid .item-card .item-card-badge.mastery,
  body.icon-cards.icon-metric-resist #build-picker-grid .item-card .item-card-badge.resist,
  body.icon-cards.icon-metric-weight #build-picker-grid .item-card .item-card-badge.weight {
    display: inline-flex; align-items: center; gap: 2px; font-size: 0.66rem; padding: 0 4px; height: 15px; line-height: 15px; border-radius: 5px; white-space: nowrap;
  }
  body.icon-cards #build-picker-grid .item-card .item-card-badge img { width: 10px; height: 10px; }
}
/* Icon mode: same gap under the value as around the icon (2026-09-27). */
@media all {
  body.icon-cards #build-picker-grid .item-card { height: 85px; padding: 4px 3px 5px; align-content: start; }
}
/* Mini stats: Résistances / Poids / Maîtrise cumulée as 3 big tiles (2026-09-27) */
.mini-key-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; padding: 4px 0 6px; border-bottom: 1px solid #ffffff0a; }
.mini-key { display: flex; flex-direction: column; align-items: center; gap: 1px; min-width: 0; padding: 3px 2px 4px; border-radius: 6px; background: #ffffff06; }
.mini-key-label { display: flex; align-items: center; gap: 3px; font-size: 0.64rem; font-weight: 600; color: var(--text-dim); white-space: nowrap; max-width: 100%; overflow: hidden; }
.mini-key-label img { width: 13px; height: 13px; object-fit: contain; flex-shrink: 0; }
.mini-key-value { font-size: 0.92rem; font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Pinned item: a pin badge on the card's top-right corner instead of the
   colored outline (2026-09-27, user). */
.item-card.pinned { outline: none; position: relative; overflow: visible; }
body.icon-cards #build-picker-grid .item-card.pinned { box-shadow: none; }
.item-card.pinned::after {
  content: ""; position: absolute; top: -7px; right: -7px; z-index: 3; width: 20px; height: 20px; border-radius: 50%;
  background: var(--accent-2) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23111'%3E%3Cpath d='M16 3v6.7l2 2V14h-6.1v7l-.9 1-.9-1v-7H4v-2.3l2-2V3h10z'/%3E%3C/svg%3E") center / 12px no-repeat;
  box-shadow: 0 0 0 2px var(--bg-panel), 0 2px 6px rgba(0,0,0,.4); pointer-events: none;
}
.mini-key .mini-diff.invisible, .mini-elem .mini-diff.invisible { visibility: hidden; }
/* Key tiles: no reserved empty line for the hover diff — it floats as a
   small badge on the tile's top edge instead (tile height never changes). */
.mini-key-row { padding-top: 8px; }
.mini-key { position: relative; padding-top: 4px; }
.mini-key .mini-diff { position: absolute; top: -7px; left: 50%; transform: translateX(-50%); font-size: 0.62rem; line-height: 13px; padding: 0 4px; border-radius: 4px; background: var(--bg-panel); white-space: nowrap; }
.mini-key .mini-diff.invisible { display: none; }
/* Picker's compact doll (above the search results): same square rarity
   frame fix as #build-doll — the 8px radius + overflow:hidden was cutting
   the frame's ornamented corners (2026-09-27, user). */
#build-picker-doll .slot.filled { border-radius: 4px; overflow: visible; }
#build-picker-doll .slot.filled img.frame,
#build-picker-doll .slot.filled img.icon { border-radius: 0; }
/* Empty doll slots drawn like the equipped ones (2026-09-27, user): same
   square 4px corners, and the dashed outline sits where the rarity frame's
   edge does (inside the 2px transparent border), not on the outer edge. */
:is(#build-doll, #build-picker-doll) .slot:not(.filled):not(.slot-search-btn):not(.slot-2h) {
  --empty-edge: color-mix(in srgb, var(--text-dim) 40%, var(--border));
  border: 2px solid transparent; border-radius: 4px; background-clip: padding-box;
  outline: 1.5px dashed var(--empty-edge); outline-offset: -3.5px;
}
:is(#build-doll, #build-picker-doll) .slot:not(.filled):not(.slot-search-btn):not(.slot-2h):hover { border-color: transparent; outline-color: var(--accent); }
#build-picker-doll .slot.active:not(.filled) { border-color: transparent; outline: 2px solid var(--accent); outline-offset: -4px; }
/* Instant tooltip for filter icon toggles (js/ui.js quickTip). */
.quick-tip {
  position: fixed; z-index: 10000; pointer-events: none; display: none;
  padding: 4px 8px; border-radius: 6px; font-size: 12px; font-weight: 600; white-space: nowrap;
  background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border);
  box-shadow: 0 4px 12px rgba(0,0,0,.35);
}
.quick-tip.active { display: block; }
/* Accent color picker (settings, 2026-09-27) */
.accent-picker { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 4px; }
.accent-swatch {
  width: 26px; height: 26px; border-radius: 50%; padding: 0; cursor: pointer;
  background: var(--sw); border: 2px solid var(--bg-panel); box-shadow: 0 0 0 1px var(--border);
  transition: transform .12s ease, box-shadow .12s ease;
}
.accent-swatch:hover:not(:disabled) { transform: scale(1.1); }
.accent-swatch.active { box-shadow: 0 0 0 2px var(--text); }
.accent-custom { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px 3px 4px; border: 1px solid var(--border); border-radius: 999px; cursor: pointer; font-size: 12px; color: var(--text-dim); }
.accent-custom input[type="color"] { width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%; background: none; cursor: pointer; }
.accent-custom input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.accent-custom input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 50%; }
.accent-picker.disabled .accent-swatch, .accent-picker.disabled .accent-custom { opacity: .35; cursor: not-allowed; }
.accent-hint { flex-basis: 100%; font-size: 12px; color: var(--text-dim); }
/* Search button drawn like the doll slots (2026-09-27, user): same square
   4px corners, outline inset where the rarity frame / empty dashes sit —
   solid instead of dashed since it's a button, not an empty slot. */
:is(#build-doll, #build-picker-doll) .slot.slot-search-btn {
  border: 2px solid transparent; border-radius: 4px; background-clip: padding-box;
  outline: 1.5px solid color-mix(in srgb, var(--accent) 55%, var(--border)); outline-offset: -3.5px;
}
:is(#build-doll, #build-picker-doll) .slot.slot-search-btn:hover { border-color: transparent; outline-color: var(--accent); }
/* ---- Sublimations catalogue (reworked 2026-09-27, js/sublimations.js
   initSublimationsView): toolbar, grouped sections, compact cards. ---- */
.subs-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 14px; margin-bottom: 12px; }
.subs-head .section-title { margin: 0; }
.subs-sub { margin: 0; font-size: 0.8rem; color: var(--text-dim); }
.subs-toolbar {
  display: flex; flex-direction: column; gap: 10px; padding: 12px; margin-bottom: 16px;
  background: color-mix(in srgb, var(--bg-elevated) 55%, transparent); border: 1px solid var(--border); border-radius: 10px;
}
.subs-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; }
.subs-search { position: relative; flex: 1 1 260px; max-width: 420px; }
.subs-search svg { position: absolute; left: 10px; top: 50%; width: 16px; height: 16px; transform: translateY(-50%); color: var(--text-dim); pointer-events: none; }
.subs-search input { width: 100%; padding-left: 32px; }
.subs-seg-label { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-dim); margin-right: 4px; }
.subs-seg { display: inline-flex; align-items: center; gap: 2px; }
.subs-seg button {
  padding: 5px 11px; font: inherit; font-size: 0.8rem; font-weight: 600; cursor: pointer;
  color: var(--text-dim); background: var(--bg-panel); border: 1px solid var(--border);
}
.subs-seg button:first-of-type { border-radius: 7px 0 0 7px; }
.subs-seg button:last-of-type { border-radius: 0 7px 7px 0; }
.subs-seg button + button { border-left-width: 0; }
.subs-seg button:hover { color: var(--text); }
.subs-seg button.active { color: var(--accent-text); background: var(--accent); border-color: var(--accent); }
.subs-sort { display: inline-flex; align-items: center; gap: 4px; }
.subs-sort select { padding: 4px 8px; }
.subs-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.subs-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px 4px 7px; cursor: pointer;
  font: inherit; font-size: 0.78rem; font-weight: 600; color: var(--c);
  background: var(--bg-panel); border: 1px solid color-mix(in srgb, var(--c) 35%, var(--border)); border-radius: 999px;
}
.subs-chip img { width: 16px; height: 16px; }
.subs-chip-n { font-size: 0.7rem; font-weight: 700; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.subs-chip:hover { border-color: var(--c); }
.subs-chip.active { background: color-mix(in srgb, var(--c) 18%, var(--bg-panel)); border-color: var(--c); box-shadow: 0 0 0 1px var(--c) inset; }
.subs-fav { padding-left: 10px; }
.subs-colors { display: inline-flex; align-items: center; gap: 4px; }
.subs-shard { width: 30px; height: 30px; padding: 3px; cursor: pointer; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 7px; opacity: 0.55; }
.subs-shard img { width: 100%; height: 100%; object-fit: contain; }
.subs-shard:hover { opacity: 0.85; }
.subs-shard.active { opacity: 1; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.subs-reset { margin-left: auto; padding: 4px 10px; font: inherit; font-size: 0.78rem; cursor: pointer; color: var(--text-dim); background: none; border: 1px dashed var(--border); border-radius: 7px; }
.subs-reset:hover { color: var(--text); border-color: var(--accent); }

.subs-count { font-size: 0.78rem; color: var(--text-dim); margin-bottom: 8px; }
.subs-empty { color: var(--text-dim); padding: 30px 0; text-align: center; }
.subs-section + .subs-section { margin-top: 22px; }
.subs-section-title {
  display: flex; align-items: center; gap: 8px; margin: 0 0 10px; font-size: 0.95rem; color: var(--c);
}
.subs-section-title span { font-size: 0.78rem; font-weight: 700; padding: 1px 7px; border-radius: 999px; color: var(--text-dim); background: var(--bg-elevated); }
.subs-section-title::after { content: ""; flex: 1; height: 1px; background: linear-gradient(90deg, color-mix(in srgb, var(--c) 50%, var(--border)), transparent); }
.subs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px; }
.subs-card {
  display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; min-width: 0;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 9px;
  transition: border-color 0.12s ease;
}
.subs-card:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.subs-card.epic, .subs-card.relic { border-left: 3px solid var(--k); }
.subs-card-head { display: flex; align-items: flex-start; gap: 10px; }
.subs-card .sub-icon-wrap { display: inline-flex; }
.subs-icon { width: 38px; height: 38px; border-radius: 7px; background: var(--bg-elevated); border: 1px solid var(--border); }
.subs-card-title { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.subs-name { display: flex; align-items: center; gap: 6px; font-weight: 700; font-size: 0.9rem; line-height: 1.2; }
.subs-lvl { font-size: 0.7rem; font-weight: 700; padding: 0 6px; border-radius: 5px; color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); }
.subs-lvl.clamped { color: var(--text-dim); border-style: dashed; border-color: var(--border); }
.subs-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.subs-meta .sub-pattern-swatch { display: inline-flex; gap: 1px; }
.subs-meta .pattern-shard-icon { width: 16px; height: 16px; }
.subs-kind { font-size: 0.7rem; font-weight: 700; color: var(--k); }
.subs-tag { font-size: 0.7rem; font-weight: 600; color: var(--c); padding: 0 7px; border-radius: 999px; background: color-mix(in srgb, var(--c) 12%, transparent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; max-width: 100%; }
.subs-meta { flex-wrap: nowrap; min-width: 0; }
.subs-card .fav-star-btn { position: static; flex-shrink: 0; }
.subs-effect { font-size: 0.8rem; line-height: 1.45; color: color-mix(in srgb, var(--text) 85%, var(--text-dim)); }
.subs-effect img { width: 14px; height: 14px; vertical-align: -2px; }
.sub-num { color: var(--text); font-weight: 700; }
.subs-foot { display: flex; align-items: center; gap: 5px; margin-top: auto; padding-top: 6px; border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent); font-size: 0.76rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.subs-foot img { width: 14px; height: 14px; }
.subs-foot span { font-weight: 500; color: var(--text-dim); font-size: 0.7rem; }
@media (max-width: 767px) {
  .subs-toolbar { padding: 10px; }
  .subs-search { max-width: none; flex-basis: 100%; }
  .subs-grid { grid-template-columns: 1fr; }
  .subs-reset { margin-left: 0; }
}
/* Liste de courses filters, take 3 (2026-09-27, user: "la c'est le
   purgatoire"): two tidy rows instead of the 5-column map — a compact
   toolbar (Recherche · Niveau · Trier par · Réinitialiser), then Type as
   one horizontal strip of icons next to Rareté. "Plus de filtres" still
   flows below in the same 4 columns. */
@media (min-width: 1100px) {
  #shoppinglist-filters.filters-band-collapsible {
    grid-template-columns: minmax(0, 1.3fr) repeat(3, minmax(0, 1fr));
    grid-template-areas: "search level sort reset" "type type type rarity";
    gap: 12px 20px; align-items: end;
  }
  #shoppinglist-filters.filters-band-collapsible > .field:nth-of-type(1) { grid-area: search; grid-column: auto; grid-row: auto; }
  #shoppinglist-filters.filters-band-collapsible > .field:nth-of-type(2) { grid-area: type; grid-column: 1 / 4; grid-row: 2; }
  #shoppinglist-filters.filters-band-collapsible > .field:nth-of-type(3) { grid-area: level; grid-column: auto; grid-row: auto; }
  #shoppinglist-filters.filters-band-collapsible > .field:nth-of-type(4) { grid-area: sort; grid-column: auto; grid-row: auto; }
  #shoppinglist-filters.filters-band-collapsible > .field:nth-of-type(5) { grid-area: rarity; grid-column: 4; grid-row: 2; align-self: end; }
  #shoppinglist-filters.filters-band-collapsible > .btn { grid-area: reset; grid-column: 4; grid-row: 1; justify-self: end; align-self: end; }
  #shoppinglist-filters .type-icon-grid { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-start; }
  /* "Plus de filtres": Bonus / Malus (15 icons each) get 2 columns, the
     yes/no toggles sit on one line each. */
  #shoppinglist-filters.filters-band-collapsible > .field:nth-of-type(9),
  #shoppinglist-filters.filters-band-collapsible > .field:nth-of-type(10) { grid-column: span 2; }
  #shoppinglist-filters.filters-band-collapsible > .field:nth-of-type(n+6) { align-self: start; }
}
#shoppinglist-filters > .field > label { margin-bottom: 6px; }
/* Sublimations: 4-slot châsse picker, condition tags, favourite pill
   (2026-09-27). */
.subs-shards { display: inline-flex; gap: 3px; padding: 3px; border-radius: 9px; background: var(--bg-panel); border: 1px solid var(--border); }
.subs-shards .subs-shard { width: 30px; height: 30px; padding: 2px; opacity: 1; border: 1px solid transparent; border-radius: 6px; background: var(--bg-elevated); transition: transform .12s ease, border-color .12s ease; }
.subs-shards .subs-shard:hover { border-color: var(--accent); transform: translateY(-1px); }
.subs-shards .subs-shard img.empty { opacity: .35; }
.subs-shards .subs-shard.set { background: color-mix(in srgb, var(--bg-elevated) 70%, #000); }
.subs-shards-clear { width: 24px; height: 24px; padding: 0; margin-left: 2px; font-size: 1rem; line-height: 1; cursor: pointer; color: var(--text-dim); background: none; border: 1px solid var(--border); border-radius: 6px; }
.subs-shards-clear:hover { color: var(--danger); border-color: var(--danger); }
.subs-mod { font-size: 0.64rem; font-weight: 600; padding: 0 6px; border-radius: 999px; color: var(--text-dim); border: 1px dashed var(--border); white-space: nowrap; }
.subs-meta { flex-wrap: wrap; }
.subs-section-title em { font-style: normal; font-weight: 500; font-size: 0.78rem; color: var(--text-dim); }
.subs-card .card-action-pill.fav { flex-shrink: 0; align-self: flex-start; }
/* Sublimations: floating châsse color menu */
.subs-shard-menu {
  position: fixed; z-index: 1000; display: flex; flex-direction: column; gap: 2px; padding: 5px; min-width: 150px;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 9px; box-shadow: 0 10px 26px rgba(0,0,0,.45);
}
.subs-shard-menu[hidden] { display: none; }
.subs-shard-menu button {
  display: flex; align-items: center; gap: 8px; padding: 5px 8px; font: inherit; font-size: 0.8rem; text-align: left; cursor: pointer;
  color: var(--text); background: none; border: 0; border-radius: 6px;
}
.subs-shard-menu button img { width: 20px; height: 20px; object-fit: contain; }
.subs-shard-menu button img.empty { opacity: .35; }
.subs-shard-menu button:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.subs-shard-menu button.active { background: color-mix(in srgb, var(--accent) 22%, transparent); font-weight: 700; }
.subs-shard-menu button[data-pick="0"] { margin-top: 3px; border-top: 1px solid var(--border); border-radius: 0 0 6px 6px; color: var(--text-dim); }
/* Châsse clear button: always shown right of the 4 slots, dimmed when empty. */
.subs-shards-clear { width: 28px; height: 28px; margin-left: 4px; font-size: 1.15rem; font-weight: 700; }
.subs-shards-clear:disabled { opacity: .35; cursor: default; }
.subs-shards-clear:disabled:hover { color: var(--text-dim); border-color: var(--border); }
/* Rentabilité craft: profit trend vs the previous price survey (2026-09-27) */
.craftprofit-trend { display: inline-block; margin-right: 5px; font-size: 0.78rem; line-height: 1; cursor: help; }
.craftprofit-trend.up { color: #6fce7a; }
.craftprofit-trend.down { color: #e0716f; }
/* Trend arrow in its own reserved slot at the left of the profit cell
   (2026-09-27, user: "ne pas que ça bouge le texte") — every profit value
   starts at the same x whether or not it has an arrow. */
.craftprofit-profit-value { position: relative; padding-left: 16px; }
.craftprofit-profit-value .craftprofit-trend { position: absolute; left: 0; top: 50%; transform: translateY(-50%); margin: 0; }
/* Crafts à perte toggle: vertically centered in its row (2026-09-27). */
#craftprofit-filters > .craftprofit-losses-field { align-self: center; align-items: center; }
/* Compact icon mode: the icon grid fills the whole container width on
   desktop (2026-09-27, user) — columns stretch instead of leaving a gap. */
@media all {
  body.icon-cards #build-picker-grid .item-grid { grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); justify-content: stretch; }
  body.icon-cards #build-picker-grid .item-card { width: auto; justify-items: center; }
}
/* Icon mode on mobile: beat the compact 2-column phone grid (!important
   there) so icons get as many columns as fit, like on desktop. */
@media (max-width: 767px) {
  body.icon-cards.icon-cards #build-picker-grid .item-grid,
  body.icon-cards.icon-cards #build-picker-grid .item-grid.has-stats-cards { grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)) !important; gap: 6px; }
  body.icon-cards #build-picker-grid .item-card { min-width: 0; }
}
/* Mastery/resist order card centered under the doll on mobile (2026-09-27). */
@media (max-width: 767px) { #elem-priority-popover.elem-priority-inline { margin-inline: auto; } }
/* Recipe tooltip: craft cost block (2026-09-27) */
.recipe-cost { margin-top: 8px; padding-top: 7px; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 3px; font-size: 0.76rem; }
.recipe-cost-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.recipe-cost-row span { display: inline-flex; align-items: center; gap: 5px; color: var(--text-dim); }
.recipe-cost-row span img { width: 13px; height: 13px; }
.recipe-cost-row b { font-variant-numeric: tabular-nums; color: #e0c34f; white-space: nowrap; }
.recipe-cost-row b.dim { color: var(--text-dim); font-weight: 600; }
.recipe-cost-row b.pos { color: #6fce7a; }
.recipe-cost-row b.neg { color: #e0716f; }
.recipe-cost-note { font-size: 0.7rem; color: var(--text-dim); font-weight: 500; }
/* Item tooltip: "Obtenu sur" monster drop sources (2026-09-27) */
.drop-sources { margin-top: 8px; padding-top: 7px; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 3px; font-size: 0.78rem; }
.drop-sources-title { font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); }
.drop-sources-title span { text-transform: none; letter-spacing: 0; font-weight: 500; opacity: .8; }
.drop-source-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.drop-source-name { min-width: 0; display: flex; flex-direction: column; line-height: 1.2; }
.drop-source-name small { font-size: 0.66rem; color: var(--text-dim); }
.drop-source-row b { color: #7ee08a; font-variant-numeric: tabular-nums; white-space: nowrap; }
.drop-sources-more { font-size: 0.7rem; color: var(--text-dim); }
.recipe-ingredient-drop { display: block; font-size: 0.64rem; color: var(--text-dim); font-weight: 500; }
/* Settings: label + Oui/Non on one row (tooltip options, 2026-09-27) */
.settings-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 3px 0; font-size: 0.84rem; }
/* Item tooltip may use the whole viewport height (it cannot be scrolled:
   pointer-events none), so optional sections like drops are not cut off. */
.item-tooltip:not(.fightlab-tooltip) { max-height: calc(100vh - 16px); }
.calc-field label.settings-slide { display: flex; align-items: center; gap: 8px; padding: 4px 0; margin: 0; font-size: 0.84rem; }
.calc-field label.settings-slide .slide-toggle-track { display: inline-block; }
/* Liste de courses: best drop source under each resource (2026-09-27) */
.shoppinglist-drop { display: block; font-size: 0.7rem; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: help; }
.shoppinglist-drop em { font-style: normal; color: var(--accent); font-weight: 600; }
.shoppinglist-drop-badge { display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0; padding: 1px 6px; border-radius: 999px; font-size: 0.7rem; font-weight: 700; color: #7ee08a; background: color-mix(in srgb, #7ee08a 12%, transparent); border: 1px solid color-mix(in srgb, #7ee08a 35%, transparent); cursor: help; }
.shoppinglist-drop-badge:hover { background: color-mix(in srgb, #7ee08a 22%, transparent); }
.drop-source-row { align-items: center; }
.drop-source-img { width: 30px; height: 30px; flex-shrink: 0; object-fit: contain; border-radius: 6px; background: var(--bg-elevated); }
.drop-source-name { flex: 1; }
#fightlab-toggle-crit.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }
#fightlab-toggle-crit img.ui-icon { width: 16px; height: 16px; object-fit: contain; }
/* Mastery/resist order card vertically centered next to the doll on desktop (2026-09-27). */
@media (min-width: 768px) { #elem-priority-popover.elem-priority-inline { align-self: center; } }
/* Rentabilité craft: "Contient" ingredient picker (2026-09-27) */
.craftprofit-contains-field { grid-column: span 2; }
.craftprofit-contains { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 5px; padding: 4px 6px; min-height: 34px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 7px; }
.craftprofit-contains:focus-within { border-color: var(--accent); }
.craftprofit-contains input { flex: 1; min-width: 140px; border: 0; background: transparent; padding: 3px 2px; box-shadow: none; }
.craftprofit-contains input:focus { outline: none; }
.craftprofit-contains-chips { display: contents; }
.craftprofit-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 3px; border-radius: 999px; font-size: 0.76rem; font-weight: 600; background: color-mix(in srgb, var(--accent) 18%, var(--bg-panel)); border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); }
.craftprofit-chip img { width: 18px; height: 18px; object-fit: contain; }
.craftprofit-chip button { border: 0; background: none; color: var(--text-dim); cursor: pointer; font-size: 0.95rem; line-height: 1; padding: 0 2px; }
.craftprofit-chip button:hover { color: var(--danger); }
.craftprofit-contains-list { position: fixed; z-index: 1000; max-height: 300px; overflow-y: auto; padding: 4px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 10px 24px rgba(0,0,0,.4); }
.craftprofit-contains-list[hidden] { display: none; }
.craftprofit-contains-list button { display: flex; align-items: center; gap: 8px; width: 100%; padding: 5px 6px; border: 0; border-radius: 6px; background: none; color: var(--text); font: inherit; font-size: 0.82rem; text-align: left; cursor: pointer; }
.craftprofit-contains-list button:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.craftprofit-contains-list button img { width: 22px; height: 22px; object-fit: contain; }
.craftprofit-contains-list button span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.craftprofit-contains-list button small { color: var(--text-dim); font-size: 0.7rem; }
.craftprofit-contains-empty { padding: 6px; color: var(--text-dim); font-size: 0.8rem; }
/* Spell effect section titles ("Effets normaux", "Effets si Berserk"), 2026-09-28 */
.spell-effect-heading { display: block; text-align: center; font-weight: 700; margin: 4px 0 2px; }
.spell-effect-heading:first-child { margin-top: 0; }
/* Spell tooltip, in-game layout (2026-09-28): icon + name, Coût / Portée
   boxes, "Effets" / "Conditions" bands, applied states in gold. */
.stt { display: flex; flex-direction: column; gap: 6px; }
.stt-head { display: flex; align-items: center; gap: 10px; }
.stt-icon { width: 52px; height: 52px; flex-shrink: 0; border-radius: 10px; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); background: var(--bg-elevated); }
.stt-title { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.stt-name { font-size: 1.05rem; font-weight: 700; color: var(--text); line-height: 1.15; }
.stt-boxes { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.stt-box { display: inline-flex; align-items: center; gap: 4px; padding: 2px 7px; border-radius: 6px; font-size: 0.76rem; color: var(--text-dim); background: color-mix(in srgb, var(--bg-elevated) 80%, #000); border: 1px solid var(--border); }
.stt-box b { color: var(--text); font-size: 0.82rem; }
.stt-picto { width: 16px; height: 16px; object-fit: contain; }
.stt-band { text-align: center; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-dim); padding: 2px 0; margin: 0 -10px; background: color-mix(in srgb, var(--bg-elevated) 70%, #000); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.stt-effect { margin: 0; font-size: 0.8rem; line-height: 1.45; }
.stt-state { color: #e2a43b; font-weight: 700; }
.stt-conditions { font-size: 0.78rem; color: var(--text); }
/* A second section title ("Effets si Berserk") gets clear separation from
   the section above it, like the in-game tooltip. */
.spell-effect-heading ~ .spell-effect-heading { margin-top: 10px; padding-top: 8px; border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); }
header.topbar .logo { cursor: pointer; }
/* Outils menu: an icon per page (2026-09-28) */
.view-dropdown-menu .tab-btn { display: flex; align-items: center; gap: 8px; }
.view-menu-icon { width: 16px; height: 16px; flex-shrink: 0; opacity: .85; }
/* Outside the builder (Base de données, Sublimations…): the build section
   tabs give way to a single "← Retour au build" button (2026-09-28). */
.quick-nav-back { display: none; }
body.outside-builder #builder-quick-nav .quick-nav-btn { display: none; }
body.outside-builder #builder-quick-nav .quick-nav-back {
  display: inline-flex; align-items: center; gap: 7px; padding: 5px 12px; cursor: pointer;
  font: inherit; font-size: 0.85rem; font-weight: 600; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 999px;
}
body.outside-builder #builder-quick-nav .quick-nav-back:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.quick-nav-back svg { width: 16px; height: 16px; }
/* Open "Outils" page shown in the middle of the top bar (2026-09-28). */
.topbar-view-title { display: inline-flex; align-items: center; gap: 8px; font-size: 0.95rem; font-weight: 700; color: var(--text); letter-spacing: 0.01em; }
.topbar-view-title[hidden] { display: none; }
.topbar-view-title .view-menu-icon { width: 18px; height: 18px; color: var(--accent); opacity: 1; }
@media (max-width: 767px) { .topbar-view-title { display: none; } }

/* Alt-slot boxes dressed like the equipment row (2026-09-28, user: "modifie
   le visuel des alt slot vide et équipé pour qu'ils correspondent au visuel
   de la barre d'items") — same final rules as #build-doll .slot above. */
.altslot-float .slot.altslot-box { border-radius: 4px; overflow: visible; }
.altslot-float .slot.altslot-box.filled {
  border: 2px solid transparent;
  background: radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--r) 34%, var(--bg)), var(--bg) 74%);
  box-shadow: 0 0 0 1px rgba(0,0,0,.45), 0 6px 14px -6px color-mix(in srgb, var(--r) 85%, transparent), inset 0 0 12px -4px color-mix(in srgb, var(--r) 60%, transparent);
}
.altslot-float .slot.altslot-box.filled img.icon { inset: 7px; width: calc(100% - 14px); height: calc(100% - 14px); filter: drop-shadow(0 3px 3px rgba(0,0,0,.6)); transition: transform .15s ease; }
.altslot-float .slot.altslot-box.filled:hover img.icon { transform: scale(1.14); }
.altslot-float .slot.altslot-box.filled img.frame { border-radius: 0; }
.altslot-float .slot.altslot-box:not(.filled) {
  --empty-edge: color-mix(in srgb, var(--text-dim) 40%, var(--border));
  border: 2px solid transparent; background-clip: padding-box;
  background-image: repeating-linear-gradient(135deg, rgba(255,255,255,.025) 0 6px, transparent 6px 12px); background-color: var(--bg);
  outline: 1.5px dashed var(--empty-edge); outline-offset: -3.5px;
}
.altslot-float .slot.altslot-box:not(.filled):hover { border-color: transparent; outline-color: var(--accent); }
.altslot-float .altslot-index { top: 3px; left: 3px; border-radius: 4px; z-index: 3; }

/* Rarity frames on public/profile build cards (2026-09-28, user). */
.pb-card .build-row-item.framed { border-color: transparent; background: var(--bg); border-radius: 0; }
.pb-card .build-row-item.framed img.icon { inset: 12%; width: 76%; height: 76%; object-fit: contain; border-radius: 0; }
.pb-card .build-row-item.framed img.frame { inset: 0; width: 100%; height: 100%; border-radius: 0; pointer-events: none; }

/* Pictos on the build sort tabs + Voir/Dupliquer (2026-09-28, user). */
.pb-sort-tabs [data-sort] { display: inline-flex; align-items: center; gap: 6px; }
.pb-sort-icon { width: 14px; height: 14px; flex-shrink: 0; opacity: .85; }
.pb-sort-tabs [data-sort].active .pb-sort-icon { opacity: 1; }
.pb-card-actions .btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.pb-card-actions .btn .ui-icon { width: 15px; height: 15px; flex-shrink: 0; }

/* Pin restyled to the site theme (2026-09-28, user: "modifie le visuel du
   pin pour qu'il colle au theme"): the pill follows the accent color (like
   the other action pills, not the red "danger" fill), the overflowing pink
   corner badge is gone, and a pinned card gets a thin accent edge instead. */
.card-action-pill.pin { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); color: var(--accent); }
.card-action-pill.pin.active { background: color-mix(in srgb, var(--accent) 22%, var(--bg-elevated)); border-color: var(--accent); color: var(--accent); }
.card-action-pill.pin.active:hover { background: var(--accent); color: var(--accent-text, #fff); }
/* Corner pin badge kept (2026-09-28, user follow-up): rounded square in the
   theme's accent (::after = tile, ::before = pin glyph masked in
   --accent-text so it stays readable on any accent the user picks). */
.item-card.pinned::after {
  content: ""; position: absolute; top: -7px; right: -7px; z-index: 3; width: 20px; height: 20px; border-radius: 6px;
  background: var(--accent); box-shadow: 0 0 0 2px var(--bg-panel), 0 2px 6px rgba(0,0,0,.4); pointer-events: none;
}
.item-card.pinned::before {
  content: ""; position: absolute; top: -3px; right: -3px; z-index: 4; width: 12px; height: 12px; pointer-events: none;
  background: var(--accent-text, #0d1017);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16 3v6.7l2 2V14h-6.1v7l-.9 1-.9-1v-7H4v-2.3l2-2V3h10z'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M16 3v6.7l2 2V14h-6.1v7l-.9 1-.9-1v-7H4v-2.3l2-2V3h10z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.item-card.pinned { box-shadow: none; }
body.icon-cards #build-picker-grid .item-card.pinned { box-shadow: none; }

/* Card action buttons re-laid out (2026-09-28, user: "revoit l'agencement
   ces boutons"): one top-right row of identical 24px squares instead of a
   stacked column with an invisible gap where the pin sits when unpinned.
   Pin only takes room once pinned, and on the left so recipe/fav never
   move; the "équipé" chip is the same square, right-aligned under them. */
.item-card.with-stats .card-header .card-actions { flex-direction: row; align-self: flex-start; gap: 4px; margin-left: auto; }
.item-card.with-stats .card-actions .card-action-pill,
.item-card.with-stats .card-stat-row .card-action-pill.equip {
  width: 24px; height: 24px; padding: 0; border-radius: 6px; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
}
.item-card.with-stats .card-actions .card-action-pill svg { width: 14px; height: 14px; }
.item-card.with-stats .card-actions .card-action-pill.fav { font-size: 0.9rem; }
.item-card.with-stats .card-actions .card-action-pill.pin { order: -1; }
.item-card.with-stats .card-actions .card-action-pill.pin:not(.active) { display: none; }
.item-card.with-stats .card-stat-row .card-action-pill.equip:not(.active) { visibility: hidden; }
.item-card.with-stats .card-stat-row .card-action-pill.equip .equip-slot-chip { background: transparent; font-size: 0.78rem; }
/* Light gap under the mastery/resist/price row, like between the other
   rows of the card (2026-09-28, user) — was pulled flush by -10px. */
.item-card.with-stats .card-stat-row { margin-bottom: -5px; }
/* Favourites filter button: same SVG star as the item cards, same box as
   the compact-mode toggle next to it (2026-09-28, user). */
#build-picker-favorites-btn .ui-icon { width: 16px; height: 16px; }
/* Picker's sticky slot bar vs its panel (2026-09-28, user: backgrounds
   didn't match): the panel's top-lit gradient can't be matched by a bar
   that sticks and moves over it, so this one panel is flat, same colour. */
#build-picker-panel .picker-inner { background: var(--bg-panel); }
#build-picker-panel .picker-sticky-header { background: var(--bg-panel); }
/* The corner badge already marks a pinned card — no second pin button in
   the action row (2026-09-28, user). Unpinning stays on right-click. */
.item-card.with-stats .card-actions .card-action-pill.pin { display: none; }
/* Compact (icons-only) cards: favourite marker, since the ★ button is
   hidden there (2026-09-28, user) — a small gold star tile on the icon's
   top-left corner (the pin badge owns the card's top-right). */
body.icon-cards #build-picker-grid .item-card.is-fav .item-icon-frame::after {
  content: ""; position: absolute; top: -5px; left: -5px; z-index: 3; width: 16px; height: 16px; border-radius: 5px;
  background: #e0c34f; box-shadow: 0 0 0 2px var(--bg-panel), 0 2px 5px rgba(0,0,0,.4); pointer-events: none;
}
body.icon-cards #build-picker-grid .item-card.is-fav .item-icon-frame::before {
  content: ""; position: absolute; top: -2px; left: -2px; z-index: 4; width: 10px; height: 10px; pointer-events: none;
  background: #17130a;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.8l2.8 5.9 6.4.8-4.7 4.4 1.2 6.4L12 17.2l-5.7 3.1 1.2-6.4-4.7-4.4 6.4-.8z'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.8l2.8 5.9 6.4.8-4.7 4.4 1.2 6.4L12 17.2l-5.7 3.1 1.2-6.4-4.7-4.4 6.4-.8z'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* "Autre…" custom accent: same height as the swatches, dot + text centred
   on one line (2026-09-28, user). Beats the settings panel's generic label
   rules (block, margins). */
.accent-picker .accent-custom {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  box-sizing: border-box; height: 28px; margin: 0; padding: 0 10px 0 4px;
  line-height: 1; white-space: nowrap; font-size: 12px; font-weight: 600;
}
.accent-picker .accent-custom span { display: block; line-height: 1; margin: 0; }
.accent-picker .accent-custom input[type="color"] { flex: 0 0 auto; width: 20px; height: 20px; margin: 0; display: block; }
/* Light gap between the Deck / Combo / FightLab tab row and the section
   below it ("Sorts actifs"...) — was flush (2026-09-28, user). */
#spelldeck-panel, #combomaker-panel, #fightlab-panel { margin-top: 10px; }
/* "Retour au build": rounded square like the other topbar buttons (6px
   radius, 32px tall) instead of a pill (2026-09-28, user). */
body.outside-builder #builder-quick-nav .quick-nav-back { border-radius: 6px; height: 32px; box-sizing: border-box; padding: 0 12px; }

/* Whole-stuff variant switch, right of the equipment row (2026-09-28). */
.doll-loadout-switch { display: flex; flex-direction: column; align-items: center; gap: 5px; align-self: center; }
.doll-loadout-title { font-size: 0.66rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); }
.doll-loadout-btn {
  width: 32px; height: 32px; padding: 0; border-radius: 6px; cursor: pointer;
  font: inherit; font-size: 0.9rem; font-weight: 800; font-variant-numeric: tabular-nums;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim);
  transition: background .12s, color .12s, border-color .12s;
}
.doll-loadout-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--text); }
.doll-loadout-btn.active { background: color-mix(in srgb, var(--accent) 22%, var(--bg-elevated)); border-color: var(--accent); color: var(--accent); }
.doll-loadout-btn:disabled { opacity: .35; cursor: not-allowed; }
@media (max-width: 767px) {
  .doll-loadout-switch { flex-direction: row; width: 100%; justify-content: center; }
}
/* Variant switch, compact (2026-09-28 follow-up): no title, smaller squares;
   an empty variant stays clickable, shown dashed. */
.doll-loadout-switch { gap: 3px; }
.doll-loadout-btn { width: 24px; height: 24px; border-radius: 5px; font-size: 0.78rem; }
.doll-loadout-btn.empty:not(.active) { border-style: dashed; opacity: .6; }
.doll-loadout-btn.empty:hover { opacity: 1; }
/* Role icons on build cards / switcher rows: zoomed with the tile's white
   rim cropped off, like the Build panel's role buttons (2026-09-28, user). */
.role-icon-crop { display: inline-flex; flex-shrink: 0; overflow: hidden; }
.role-icon-crop img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.2); }
.pb-card-role-icon.role-icon-crop { width: 24px; height: 24px; border-radius: 5px; }
.build-row-role-icon.role-icon-crop { width: 15px; height: 15px; border-radius: 3px; }
/* Framed items on build cards: lighter backdrop — on the dark theme the
   plain --bg read as a heavy shadow behind the item (2026-09-28, user). */
.pb-card .build-row-item.framed { background: color-mix(in srgb, var(--bg-elevated) 75%, var(--bg-panel)); }

/* Progressive lists (WUI.progressiveRender): the sentinel spans the whole
   row of a grid/table so it never takes a card's place. */
.progressive-sentinel { grid-column: 1 / -1; height: 1px; width: 100%; }
/* Sublimations catalogue: sections off screen skip layout/paint until
   scrolled near (mobile perf, 2026-09-28). */
.subs-section { content-visibility: auto; contain-intrinsic-size: auto 600px; }
/* Spell-deck "remove" chip: same small dark rounded square + drawn cross as
   the equipment slots' remove-x (2026-09-28, user). Combo Maker too. */
.deck-slot-remove, .combo-slot .remove-x, .cm-card .remove-x {
  top: 2px; right: 2px; width: 14px; height: 14px; border-radius: 4px; z-index: 3;
  font-size: 0; line-height: 0; color: color-mix(in srgb, var(--danger) 70%, var(--text));
  background: color-mix(in srgb, var(--bg-panel) 85%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--border));
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
  transition: background-color 0.12s, color 0.12s, border-color 0.12s, transform 0.12s;
}
.deck-slot-remove::before, .deck-slot-remove::after,
.combo-slot .remove-x::before, .combo-slot .remove-x::after, .cm-card .remove-x::before, .cm-card .remove-x::after {
  content: ""; position: absolute; top: 50%; left: 50%; width: 7px; height: 1.5px;
  border-radius: 1px; background: currentColor;
}
.deck-slot-remove::before, .combo-slot .remove-x::before, .cm-card .remove-x::before { transform: translate(-50%, -50%) rotate(45deg); }
.deck-slot-remove::after, .combo-slot .remove-x::after, .cm-card .remove-x::after { transform: translate(-50%, -50%) rotate(-45deg); }
.deck-slot-remove:hover, .combo-slot .remove-x:hover, .cm-card .remove-x:hover { background: var(--danger); border-color: var(--danger); color: #fff; transform: scale(1.12); }
.fightlab-spell-grid .deck-slot-remove { left: 2px; right: auto; }
/* Osamodas creature picker, floating on <body> (see bindOsamodasLoadoutBar). */
.osamodas-picker-popover.floating { position: fixed; top: auto; transform: none; margin: 0; z-index: 3000; }
/* Osamodas FightLab: the "Invocations disponibles" grid used to stretch to
   the whole row, pushing the spell library onto its own line and making
   the bar twice as tall as for any other class (2026-09-28, user). Now it
   is exactly as wide as the spell grid above it, with compact slots. */
.fightlab-spell-bar-left:has(.osamodas-loadout-grid) { flex: 0 0 auto; width: 353px; max-width: 100%; }
.fightlab-spell-bar-left .osamodas-loadout-grid { width: 100%; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px; margin-top: 4px; }
.fightlab-spell-bar-left .osamodas-loadout-slot { min-height: 0; height: 34px; padding: 2px 4px; box-sizing: border-box; overflow: hidden; }
.fightlab-spell-bar-left .osamodas-loadout-slot .spell-name { font-size: 0.62rem; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.fightlab-spell-bar-left .osamodas-loadout-slot input.dedicated-sub-search { font-size: 0.66rem; padding: 2px 1px; height: 100%; box-sizing: border-box; }
.fightlab-spell-bar-left .osamodas-loadout-slot [data-loadout-remove] { top: 1px; right: 1px; padding: 0 3px; font-size: 0.7rem; line-height: 1.1; }
.fightlab-spell-bar-left .calc-section-label { margin-top: 6px !important; }

/* Sublimation max level (stacking), catalogue card + tooltip (2026-09-28). */
.subs-maxlvl { margin-left: 5px; padding: 0 5px; border-radius: 4px; font-size: 0.62rem; font-weight: 700; letter-spacing: .03em; color: var(--text-dim); border: 1px solid var(--border); vertical-align: middle; white-space: nowrap; }
.tt-sub-max { margin-top: 6px; font-size: 0.78rem; color: var(--text-dim); font-style: italic; }
/* Perf (2026-09-29, user: "améliore la fluidité"): a card's own layout and
   styles are isolated from the rest of the grid, so hovering/updating one
   card (equip pill, favourite, pin) doesn't re-lay out the whole list.
   No paint containment: the pin/favourite badges overflow the card edges. */
.item-card, .subs-card, .craftprofit-row, .pb-card { contain: layout style; }

/* ================================================================
   Visual pass 2026-09-29 (user-picked items 1, 7, 9, 10, 11 of the
   mockup). Stats animation, button consistency, page fade, mobile touch
   targets, light-theme contrast (+ css/light-contrast.css, generated).
   ================================================================ */
/* 1 — Stat changed: the value rolls in, keeps its green/red tint ~1.6 s,
   and a small delta (+481 / −40) shows next to it (planner.js
   animateStatChanges). All devices; off with prefers-reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes stat-roll-up { 0% { transform: translateY(45%); opacity: 0.25; } 100% { transform: none; opacity: 1; } }
  @keyframes stat-roll-down { 0% { transform: translateY(-45%); opacity: 0.25; } 100% { transform: none; opacity: 1; } }
  @keyframes stat-tint-up { 0%, 55% { color: #4fc46a; } 100% { color: inherit; } }
  @keyframes stat-tint-down { 0%, 55% { color: var(--danger); } 100% { color: inherit; } }
  .stat-bump { display: inline-block; }
  .stat-bump.up { animation: stat-roll-up 0.35s ease-out, stat-tint-up 1.6s ease-out; }
  .stat-bump.down { animation: stat-roll-down 0.35s ease-out, stat-tint-down 1.6s ease-out; }
  @keyframes stat-delta-life { 0% { opacity: 0; transform: translateY(3px); } 12%, 70% { opacity: 1; transform: none; } 100% { opacity: 0; } }
  .stat-delta { animation: stat-delta-life 1.8s ease-out forwards; }
}
.stat-delta { margin-left: 4px; font-size: 0.72em; font-weight: 700; white-space: nowrap; }
.stat-delta.up { color: #4fc46a; }
.stat-delta.down { color: var(--danger); }
:root[data-theme="blanc"] .stat-delta.up { color: #2e7d3a; }

/* 7 — One corner radius (6px) for every button, 32px for text buttons,
   24px for the small icon ones. Item slots / spell tiles keep their own
   frame shapes. */
.btn, button.btn, .quick-nav-btn, .local-tab-btn, .text-toggle, .f-dir-toggle, .custom-select-btn, .chasse-clear-btn,
.shard-btn, .pw-toggle-btn, .icon-toggle, .doll-loadout-btn, .elem-priority-btn, .apt-adj-btn, .card-action-pill,
.topbar-icon-btn, .subs-chip, .yesno-toggle button { border-radius: 6px; }
@media (min-width: 768px) {
  .quick-nav-btn, .local-tab-btn, .text-toggle, .chasse-clear-btn, .pw-toggle-btn { min-height: 32px; box-sizing: border-box; }
  .f-dir-toggle { height: 32px; }
  .elem-priority-btn { height: 24px; min-height: 24px; box-sizing: border-box; }
}

/* 9 — Tool pages (and the builder) fade in instead of switching dry.
   Opacity only: a transform would re-anchor the fixed floating pickers. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes view-fade-in { from { opacity: 0; } to { opacity: 1; } }
  .view.active { animation: view-fade-in 0.22s ease-out; }
}

/* 10 — Touch screens: bigger tap targets for the small buttons, roomier
   cards, more readable bottom quick-nav. */
@media (pointer: coarse) {
  .item-card.with-stats .card-actions .card-action-pill,
  .item-card.with-stats .card-stat-row .card-action-pill.equip { width: 34px; height: 34px; }
  .slot .remove-x, .deck-slot-remove, .combo-slot .remove-x, .cm-card .remove-x { width: 22px; height: 22px; }
  .slot .remove-x::before, .slot .remove-x::after, .deck-slot-remove::before, .deck-slot-remove::after { width: 10px; height: 2px; }
  .apt-adj-btn, .doll-loadout-btn { min-width: 32px; min-height: 32px; }
  .chasse-sub-slot [data-remove-sub-slot] { min-width: 34px; min-height: 34px; }
}
@media (max-width: 767px) {
  .item-grid { gap: 10px; }
  .item-card.with-stats { padding: 12px; }
  .quick-nav-btn { font-size: 0.84rem; min-height: 36px; padding: 7px 10px; }
}

/* 11 — Light theme: darker secondary text and borders, softer shadows
   (the dark-theme ones read as smudges on cream). Bright accent colours
   are remapped in css/light-contrast.css. */
:root[data-theme="blanc"] {
  --text-dim: #574f42;
  --border: #cdc2ad;
}
:root[data-theme="blanc"] #build-doll .slot.filled img.icon,
:root[data-theme="blanc"] .altslot-float .slot.altslot-box.filled img.icon { filter: drop-shadow(0 2px 2px rgba(60, 40, 10, 0.25)); }
:root[data-theme="blanc"] #build-doll .slot.filled { box-shadow: 0 0 0 1px rgba(60, 40, 10, 0.18), 0 4px 10px -6px color-mix(in srgb, var(--r) 70%, transparent); }
:root[data-theme="blanc"] .item-card.pinned::after { box-shadow: 0 0 0 2px var(--bg-panel), 0 2px 4px rgba(60, 40, 10, 0.2); }
/* 1 (follow-up, user): the delta floats ABOVE the value instead of sitting
   inline next to it, so the base numbers never shift. */
.stat-bump { position: relative; }
.stat-delta {
  position: absolute; bottom: 100%; left: 50%; margin: 0 0 1px; transform: translateX(-50%);
  padding: 0 4px; border-radius: 4px; line-height: 1.35; pointer-events: none; z-index: 2;
  background: var(--bg-panel); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
@media (prefers-reduced-motion: no-preference) {
  @keyframes stat-delta-float { 0% { opacity: 0; transform: translate(-50%, 4px); } 12%, 70% { opacity: 1; transform: translate(-50%, 0); } 100% { opacity: 0; transform: translate(-50%, -3px); } }
  .stat-delta { animation: stat-delta-float 1.8s ease-out forwards; }
}
/* Row stats (Maîtrise Critique, Parade…): delta on the LEFT of the value,
   like the mini-fiche; block tiles keep it above (user, 2026-09-29). */
.stat-row .stat-delta { bottom: auto; left: auto; right: 100%; top: 50%; margin: 0 5px 0 0; transform: translateY(-50%); box-shadow: none; background: none; padding: 0; }
@media (prefers-reduced-motion: no-preference) {
  @keyframes stat-delta-left { 0% { opacity: 0; transform: translate(4px, -50%); } 12%, 70% { opacity: 1; transform: translate(0, -50%); } 100% { opacity: 0; transform: translate(-3px, -50%); } }
  .stat-row .stat-delta { animation: stat-delta-left 1.8s ease-out forwards; }
}
/* Mini-fiche: equip deltas at the SAME spots as the hover preview
   (user, 2026-09-29) — rows "(+19) 2439" on the left, elements and the
   Maîtrise/Résist./Poids tiles above. */
#build-picker-stats .mini-stat-value .stat-delta {
  bottom: auto; left: auto; right: 100%; top: 50%; margin: 0 3px 0 0; transform: translateY(-50%);
  background: none; box-shadow: none; padding: 0; font-size: 1em;
}
#build-picker-stats .mini-stat-value .stat-delta::before { content: "("; }
#build-picker-stats .mini-stat-value .stat-delta::after { content: ")"; }
#build-picker-stats .mini-elem-main .stat-delta, #build-picker-stats .mini-key-value .stat-delta { font-size: 0.7rem; background: none; box-shadow: none; }
@media (prefers-reduced-motion: no-preference) {
  #build-picker-stats .mini-stat-value .stat-delta { animation: stat-delta-left 1.8s ease-out forwards; }
}
/* Key tiles (Maîtrise/Résist./Poids): the delta sits on the TILE's top edge
   like the hover diff (.mini-key .mini-diff), not over the label — the value
   node is un-positioned so the absolute delta anchors to .mini-key. */
#build-picker-stats .mini-key .mini-key-value.stat-bump { position: static; }
#build-picker-stats .mini-key > .stat-delta {
  bottom: auto; top: -10px; left: 50%; transform: translateX(-50%); margin: 0;
  font-size: 0.62rem; line-height: 13px; padding: 0 4px; border-radius: 4px; background: var(--bg-panel); box-shadow: none;
}
/* Bounce up on a gain, down on a loss (user, 2026-09-29) — replaces the roll. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes stat-bounce-up { 0% { transform: translateY(0); } 30% { transform: translateY(-7px); } 55% { transform: translateY(1px); } 75% { transform: translateY(-2px); } 100% { transform: translateY(0); } }
  @keyframes stat-bounce-down { 0% { transform: translateY(0); } 30% { transform: translateY(7px); } 55% { transform: translateY(-1px); } 75% { transform: translateY(2px); } 100% { transform: translateY(0); } }
  .stat-bump.up { animation: stat-bounce-up 0.55s ease-out, stat-tint-up 1.6s ease-out; }
  .stat-bump.down { animation: stat-bounce-down 0.55s ease-out, stat-tint-down 1.6s ease-out; }
  @keyframes stat-delta-edge { 0% { opacity: 0; } 12%, 70% { opacity: 1; } 100% { opacity: 0; } }
  #build-picker-stats .mini-key > .stat-delta { animation: stat-delta-edge 1.8s ease-out forwards; }
}
/* Lighter stat animation (user, 2026-09-29: "trop visible, que ça attire
   pas trop l'œil"): 2px bounce, softer and shorter tint, quieter delta. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes stat-bounce-up { 0% { transform: translateY(0); } 35% { transform: translateY(-2px); } 100% { transform: translateY(0); } }
  @keyframes stat-bounce-down { 0% { transform: translateY(0); } 35% { transform: translateY(2px); } 100% { transform: translateY(0); } }
  @keyframes stat-tint-up { 0%, 35% { color: color-mix(in srgb, #4fc46a 55%, currentColor); } 100% { color: inherit; } }
  @keyframes stat-tint-down { 0%, 35% { color: color-mix(in srgb, var(--danger) 55%, currentColor); } 100% { color: inherit; } }
  .stat-bump.up { animation: stat-bounce-up 0.4s ease-out, stat-tint-up 1.1s ease-out; }
  .stat-bump.down { animation: stat-bounce-down 0.4s ease-out, stat-tint-down 1.1s ease-out; }
}
.stat-delta { font-weight: 600; opacity: 0.85; }
.stat-delta:not(.stat-row .stat-delta):not(.mini-stat-value .stat-delta) { font-size: 0.66em; }
/* Mini-fiche element row (user, 2026-09-29): the animated value keeps its
   flex layout (inline-block changed the icon+value width), and the 4
   element cells are centred vertically. */
.mini-elem-main.stat-bump { display: flex; }
.mini-elem-row { align-items: center; }
.mini-elem { justify-content: center; align-self: stretch; }
/* Mini-fiche resistances: the % is the useful number (user, 2026-09-29) —
   bigger and in the normal text colour instead of small and dimmed. */
#build-picker-stats .mini-elem small:not(.invisible) { font-size: 0.8rem; font-weight: 700; color: var(--text); letter-spacing: 0.01em; }
/* Resist row now shows the % big and the raw points small (see planner.js
   elemRow) — the small line goes back to the discreet style. */
#build-picker-stats .mini-elem small:not(.invisible) { font-size: 0.7rem; font-weight: 600; color: var(--text-dim); }

/* Épique / relique shine on the equipment row (mockup F, 2026-09-29,
   user: "réduis légèrement le temps entre deux animations" → 4.5 s cycle).
   The light band is masked by the frame texture itself, so only the frame
   shines; a faint halo breathes on the same cycle. */
.rare-shine { position: absolute; inset: 0; z-index: 3; pointer-events: none; display: none; }
@media (prefers-reduced-motion: no-preference) {
  body:not(.no-rare-shine) .rare-shine {
    display: block;
    -webkit-mask: var(--mask) center / 100% 100% no-repeat; mask: var(--mask) center / 100% 100% no-repeat;
    background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, 0.6) 50%, transparent 60%) no-repeat;
    background-size: 250% 100%;
    animation: rare-shine-sweep 4.5s ease-in-out infinite;
  }
  body:not(.no-rare-shine) .slot.filled:has(> .rare-shine) img.frame { animation: rare-shine-halo 4.5s ease-in-out infinite; }
  @keyframes rare-shine-sweep { 0% { background-position: 130% 0; } 55%, 100% { background-position: -30% 0; } }
  @keyframes rare-shine-halo { 0%, 100% { filter: none; } 50% { filter: drop-shadow(0 0 3px var(--r)); } }
}
/* Shine variant A chosen instead of F (user, 2026-09-29): brighter, wider
   band on the frame only, 3.6 s cycle, no halo. */
@media (prefers-reduced-motion: no-preference) {
  body:not(.no-rare-shine) .rare-shine {
    background: linear-gradient(115deg, transparent 35%, rgba(255, 255, 255, 0.95) 50%, transparent 65%) no-repeat;
    background-size: 250% 100%;
    animation-duration: 3.6s;
  }
  body:not(.no-rare-shine) .slot.filled:has(> .rare-shine) img.frame { animation: none; }
}
/* Recipe button feedback after adding to the Liste de courses (2026-09-29). */
.card-action-pill.recipe.added, .recipe-corner-btn.added { background: #4fc46a; border-color: #4fc46a; color: #0d1a10; }

/* ===== Marché HDV — Statistiques page (2026-09-29, js/stats.js) ===== */
.mkt-page, .mkt-detail-overlay { --mkt-up: #4fb477; --mkt-down: #d9675f; --mkt-gold: #d4af37; }
.mkt-page { display: flex; flex-direction: column; gap: 14px; max-width: 1400px; margin: 0 auto; }
:root[data-theme="blanc"] .mkt-page, :root[data-theme="blanc"] .mkt-detail-overlay { --mkt-up: #2e7d3a; --mkt-down: #b83232; --mkt-gold: #8a6a08; }
.mkt-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 14px 16px; }
.mkt-header .section-title { margin: 0 0 4px; }
.mkt-subtitle { margin: 0; color: var(--text-dim); font-size: 0.85rem; max-width: 70ch; }
.mkt-ref { display: flex; flex-direction: column; gap: 4px; }
.mkt-ref label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); font-weight: 700; }
.mkt-ref select { min-width: 200px; }
.mkt-page > .filters-band { margin: 0; }

.mkt-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; }
.mkt-tile { display: flex; flex-direction: column; gap: 3px; padding: 11px 14px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius); min-width: 0; }
.mkt-tile-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); font-weight: 700; }
.mkt-tile-value { font-size: 1.3rem; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--text); white-space: nowrap; }
.mkt-tile-value .mkt-pct { font-size: inherit; }
.mkt-tile-sub { font-size: 0.78rem; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.mkt-pct { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.mkt-pct.up { color: var(--mkt-up); }
.mkt-pct.down { color: var(--mkt-down); }
.mkt-pct.flat, .mkt-pct.none { color: var(--text-dim); font-weight: 600; }
.mkt-muted { color: var(--text-dim); font-size: 0.78rem; margin: 6px 0 0; }

.mkt-charts { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 14px; }
.mkt-card { padding: 14px 16px; min-width: 0; }
.mkt-h3 { margin: 0 0 10px; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); font-weight: 800; display: flex; align-items: center; gap: 8px; }
.mkt-h3.up { color: var(--mkt-up); }
.mkt-h3.down { color: var(--mkt-down); }
.mkt-h3-hint { font-weight: 600; text-transform: none; letter-spacing: 0; padding: 1px 7px; border: 1px solid var(--border); border-radius: 999px; cursor: help; }

.mkt-index-chart, .mkt-detail-chart { width: 100%; height: auto; display: block; overflow: visible; }
.mkt-grid { stroke: var(--border); stroke-width: 1; }
.mkt-base { stroke: var(--text-dim); stroke-dasharray: 4 4; stroke-width: 1; opacity: 0.7; }
.mkt-refline { stroke: var(--accent); stroke-dasharray: 2 3; stroke-width: 1.2; }
.mkt-axis { fill: var(--text-dim); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.mkt-axis.missing { opacity: 0.45; }
.mkt-axis .mkt-dow { font-size: 8px; opacity: 0.65; font-weight: 600; }
.mkt-date-tick { stroke: var(--border); stroke-width: 1; }
.mkt-ref-label { fill: var(--accent); }
.mkt-series polyline { fill: none; stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.mkt-series circle { stroke: var(--bg-panel); stroke-width: 1.5; }
.mkt-series circle.gap { fill: var(--bg-panel) !important; }
.mkt-series.main polyline { stroke: var(--mkt-gold); stroke-width: 2.6; }
.mkt-series.main circle { fill: var(--mkt-gold); }
.mkt-series.main circle.gap { stroke: var(--mkt-gold); }
.mkt-series.equip polyline { stroke: var(--accent); stroke-dasharray: 5 3; }
.mkt-series.equip circle { fill: var(--accent); }
.mkt-series.equip circle.gap { stroke: var(--accent); }
.mkt-series.res polyline { stroke: var(--accent-2); stroke-dasharray: 2 3; }
.mkt-series.res circle { fill: var(--accent-2); }
.mkt-series.res circle.gap { stroke: var(--accent-2); }
.mkt-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 8px; font-size: 0.78rem; color: var(--text-dim); }
.mkt-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.mkt-legend-item b { color: var(--text); font-variant-numeric: tabular-nums; }
.mkt-legend-item i { width: 16px; height: 3px; border-radius: 2px; background: var(--mkt-gold); }
.mkt-legend-item.equip i { background: var(--accent); }
.mkt-legend-item.res i { background: var(--accent-2); }

.mkt-dist { display: grid; grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 4px; height: 190px; align-items: end; }
.mkt-dist-col { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; gap: 4px; min-width: 0; }
.mkt-dist-bar { width: 100%; max-width: 34px; height: var(--h); min-height: 2px; border-radius: 4px 4px 1px 1px; background: var(--text-dim); opacity: 0.55; transition: height 0.25s ease; }
.mkt-dist-col.up .mkt-dist-bar { background: var(--mkt-up); opacity: 0.85; }
.mkt-dist-col.down .mkt-dist-bar { background: var(--mkt-down); opacity: 0.85; }
.mkt-dist-count { font-size: 0.7rem; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text); }
.mkt-dist-label { font-size: 0.62rem; color: var(--text-dim); text-align: center; line-height: 1.15; min-height: 2.3em; }

.mkt-movers { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 20px; }
.mkt-movers-col { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.mkt-mover { display: grid; grid-template-columns: 32px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 5px 8px; border: 0; border-radius: 6px; background: transparent; color: var(--text); text-align: left; cursor: pointer; font: inherit; }
.mkt-mover:hover, .mkt-mover:focus-visible { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.mkt-mover-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.86rem; font-weight: 600; }
.mkt-mover-name small { display: block; font-weight: 500; font-size: 0.78rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }

.mkt-icon { position: relative; width: 32px; height: 32px; flex-shrink: 0; display: inline-block; }
.mkt-icon .icon { position: absolute; inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); object-fit: contain; border-radius: 4px; }
.mkt-icon .frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }

.mkt-tabs { display: flex; align-items: center; gap: 4px; border-bottom: 1px solid var(--border); margin: -4px 0 8px; flex-wrap: wrap; }
.mkt-tabs [data-mkt-tab] { background: none; border: 0; border-bottom: 2px solid transparent; color: var(--text-dim); font: inherit; font-weight: 700; padding: 8px 12px; cursor: pointer; margin-bottom: -1px; display: inline-flex; align-items: center; gap: 6px; }
.mkt-tabs [data-mkt-tab]:hover { color: var(--text); }
.mkt-tabs [data-mkt-tab].active { color: var(--text); border-bottom-color: var(--accent); }
.mkt-tab-count:not(:empty) { font-size: 0.7rem; padding: 0 6px; border-radius: 999px; background: var(--accent); color: var(--accent-text); }
.mkt-group-dim { display: flex; align-items: center; gap: 8px; margin: 4px 0 10px; font-size: 0.82rem; color: var(--text-dim); }

.mkt-table { display: flex; flex-direction: column; overflow-x: auto; }
.mkt-row { display: grid; grid-template-columns: 28px 32px minmax(160px, 2.2fr) 48px minmax(96px, 1fr) 92px 110px 60px; align-items: center; gap: 10px; padding: 5px 8px; border-radius: 6px; cursor: pointer; min-width: 640px; }
.mkt-row:not(.mkt-head):nth-child(even) { background: color-mix(in srgb, var(--bg-elevated) 45%, transparent); }
.mkt-row:not(.mkt-head):hover { background: color-mix(in srgb, var(--accent) 12%, var(--bg-elevated)); }
.mkt-head { cursor: default; position: sticky; top: 0; z-index: 1; background: var(--bg-panel); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); font-weight: 800; border-bottom: 1px solid var(--border); border-radius: 0; }
.mkt-head > span:nth-child(n+4) { text-align: right; }
.mkt-head > span:nth-child(7) { text-align: center; }
.mkt-name { min-width: 0; display: flex; flex-direction: column; }
.mkt-name-main { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.88rem; }
.mkt-name small { font-size: 0.78rem; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mkt-num { text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim); font-size: 0.82rem; }
.mkt-price { text-align: right; color: var(--mkt-gold); font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; display: flex; flex-direction: column; align-items: flex-end; }
.mkt-price small { color: var(--text-dim); font-weight: 500; font-size: 0.7rem; }
.mkt-var { text-align: right; font-size: 0.85rem; }
.mkt-spark-cell { display: flex; justify-content: center; }
.mkt-spark { width: 100px; height: 26px; overflow: visible; }
.mkt-spark polyline { fill: none; stroke: var(--text-dim); stroke-width: 1.6; stroke-linejoin: round; }
.mkt-spark circle { fill: var(--text-dim); }
.mkt-spark.up polyline { stroke: var(--mkt-up); }
.mkt-spark.up circle { fill: var(--mkt-up); }
.mkt-spark.down polyline { stroke: var(--mkt-down); }
.mkt-spark.down circle { fill: var(--mkt-down); }
.mkt-spark-empty { color: var(--text-dim); opacity: 0.6; }
.mkt-watch-btn { width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--text-dim); cursor: pointer; opacity: 0.55; padding: 0; }
.mkt-watch-btn:hover, .mkt-watch-btn:focus-visible { opacity: 1; border-color: var(--border); }
.mkt-watch-btn.active { color: var(--mkt-gold); opacity: 1; }
.mkt-watch-btn.big { width: auto; height: 34px; padding: 0 12px; opacity: 1; border-color: var(--border); font: inherit; font-weight: 700; color: var(--text); }
.mkt-watch-btn.big.active { color: var(--mkt-gold); border-color: var(--mkt-gold); }
.mkt-empty { padding: 28px 12px; text-align: center; color: var(--text-dim); border: 1px dashed var(--border); border-radius: var(--radius); }

.mkt-detail-modal { max-width: 760px; width: 100%; }
.mkt-detail-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; padding-right: 30px; }
.mkt-detail-head .mkt-icon { width: 48px; height: 48px; }
.mkt-detail-title { flex: 1; min-width: 0; }
.mkt-detail-title h3 { margin: 0; font-size: 1.15rem; }
.mkt-detail-title span { font-size: 0.8rem; color: var(--text-dim); }
.mkt-dstats { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px; margin-bottom: 12px; }
.mkt-dstat { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; background: var(--bg-elevated); border-radius: 6px; }
.mkt-dstat span { font-size: 0.72rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; }
.mkt-dstat b { font-size: 1rem; font-variant-numeric: tabular-nums; }
.mkt-dstat b .mkt-pct { font-size: inherit; }
.mkt-detail-chart-wrap { padding: 6px 0 4px; }
.mkt-area { fill: var(--mkt-gold); fill-opacity: 0.13; }
.mkt-line { fill: none; stroke: var(--mkt-gold); stroke-width: 2.2; stroke-linejoin: round; }
.mkt-dots circle { fill: var(--mkt-gold); stroke: var(--bg-panel); stroke-width: 1.5; }
.mkt-point-label { fill: var(--text); font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.mkt-detail-bottom { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; margin-top: 10px; align-items: start; }
.mkt-hist-table { width: 100%; border-collapse: collapse; font-size: 0.84rem; }
.mkt-hist-table th { text-align: left; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); padding: 4px 6px; border-bottom: 1px solid var(--border); }
.mkt-hist-table td { padding: 4px 6px; border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent); font-variant-numeric: tabular-nums; }
.mkt-hist-table th:not(:first-child), .mkt-td-num { text-align: right; }
.mkt-detail-actions { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.mkt-detail-actions .btn.added { background: #4fc46a; border-color: #4fc46a; color: #0d1a10; }

@media (max-width: 900px) {
  .mkt-charts { grid-template-columns: 1fr; }
  .mkt-movers { grid-template-columns: 1fr; }
}
@media (max-width: 700px) {
  .mkt-row { grid-template-columns: 26px 30px minmax(0, 1fr) 84px 70px; min-width: 0; gap: 8px; padding: 5px 4px; }
  .mkt-row > :nth-child(4), .mkt-row > :nth-child(7), .mkt-row > :nth-child(8) { display: none; }
  .mkt-tile-value { font-size: 1.1rem; }
  .mkt-detail-bottom { grid-template-columns: 1fr; }
  .mkt-dist { height: 160px; }
  .mkt-dist-label { font-size: 0.55rem; }
}
@media (prefers-reduced-motion: reduce) { .mkt-dist-bar { transition: none; } }

.mkt-movers-head { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 2px; }
.mkt-movers-head .mkt-muted { margin: 0; }
.mkt-seg { display: inline-flex; border: 1px solid var(--border); border-radius: 999px; overflow: hidden; }
.mkt-seg button { background: none; border: 0; color: var(--text-dim); font: inherit; font-size: 0.76rem; font-weight: 700; padding: 4px 12px; cursor: pointer; }
.mkt-seg button.active { background: var(--accent); color: var(--accent-text); }
.mkt-mover-val { display: flex; flex-direction: column; align-items: flex-end; }
.mkt-mover-val small { font-size: 0.7rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.mkt-legend-note { margin-left: auto; }
.mkt-dstat-sub { display: block; font-size: 0.78rem; font-weight: 600; color: var(--text-dim); }
.mkt-dstat-sub .mkt-pct { font-size: inherit; }
.mkt-atypical { color: #e0a030; margin-right: 4px; cursor: help; font-size: 0.85em; }
:root[data-theme="blanc"] .mkt-atypical { color: #91600a; }
.mkt-movers-head { flex-wrap: wrap; }
.mkt-seg { flex-shrink: 0; }
.mkt-seg button { white-space: nowrap; }
.mkt-unconfirmed { color: #e0a030 !important; font-weight: 600; }
:root[data-theme="blanc"] .mkt-unconfirmed { color: #91600a !important; }
.item-card-badge.price.unconfirmed { opacity: 0.65; border-style: dashed; }

/* ===== Marché HDV additions (2026-09-30): monsters, craft over time, supply,
   stability, price by level, public-build popularity, watch alerts ===== */
.mkt-page, .mkt-detail-overlay { --mkt-stable: #4fb477; --mkt-variable: #e0a030; --mkt-volatile: #d9675f; --mkt-craft: var(--accent-2); }
:root[data-theme="blanc"] .mkt-page, :root[data-theme="blanc"] .mkt-detail-overlay { --mkt-stable: #2e7d3a; --mkt-variable: #91600a; --mkt-volatile: #b83232; }
.mkt-row { grid-template-columns: 28px 32px minmax(160px, 2.2fr) 48px minmax(96px, 1fr) 92px 110px 56px 60px; min-width: 700px; }
.mkt-charts-even { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.mkt-h4 { margin: 0 0 4px; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 800; }
.mkt-h4.up { color: var(--mkt-up); }
.mkt-h4.down { color: var(--mkt-down); }
.mkt-price-sm { color: var(--mkt-gold); font-weight: 700; font-variant-numeric: tabular-nums; font-size: 0.85rem; }

.mkt-supply-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-bottom: 10px; }
.mkt-supply-tile { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; padding: 8px 10px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; color: var(--text-dim); font: inherit; font-size: 0.78rem; cursor: pointer; text-align: left; }
.mkt-supply-tile b { font-size: 1.2rem; color: var(--text); font-variant-numeric: tabular-nums; }
.mkt-supply-tile:hover, .mkt-supply-tile:focus-visible { border-color: var(--accent); }
.mkt-supply-tile.active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, var(--bg-elevated)); }
.mkt-supply-lists { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 14px; }

.mkt-stab-bar { display: flex; height: 14px; border-radius: 4px; overflow: hidden; gap: 2px; margin-bottom: 8px; }
.mkt-stab-bar span.stable { background: var(--mkt-stable); }
.mkt-stab-bar span.variable { background: var(--mkt-variable); }
.mkt-stab-bar span.volatile { background: var(--mkt-volatile); }
.mkt-stab-legend { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.mkt-stab-item { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid var(--border); border-radius: 999px; background: none; color: var(--text); font: inherit; font-size: 0.8rem; cursor: pointer; }
.mkt-stab-item i { width: 9px; height: 9px; border-radius: 50%; }
.mkt-stab-item.stable i { background: var(--mkt-stable); }
.mkt-stab-item.variable i { background: var(--mkt-variable); }
.mkt-stab-item.volatile i { background: var(--mkt-volatile); }
.mkt-stab-item small { color: var(--text-dim); }
.mkt-stab-item.active, .mkt-stab-item:hover { border-color: var(--accent); }
.mkt-stab-chip { display: inline-block; padding: 0 6px; border-radius: 999px; font-size: 0.66rem; font-weight: 700; border: 1px solid currentColor; line-height: 1.5; white-space: nowrap; }
.mkt-stab-chip.stable { color: var(--mkt-stable); }
.mkt-stab-chip.variable { color: var(--mkt-variable); }
.mkt-stab-chip.volatile { color: var(--mkt-volatile); }
.mkt-name small .mkt-stab-chip { margin-left: 4px; vertical-align: 1px; }

.mkt-craft-line { fill: none; stroke: var(--mkt-craft); stroke-width: 2; stroke-dasharray: 6 4; stroke-linejoin: round; }
.mkt-craft-dots circle { fill: var(--mkt-craft); }
.mkt-craft-legend .mkt-legend-item i { background: var(--mkt-gold); }
.mkt-craft-legend .mkt-legend-item.craft i { background: var(--mkt-craft); }
.mkt-craft-legend .mkt-legend-note { margin-left: 0; flex-basis: 100%; }

.mkt-mon-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin: 4px 0 10px; }
.mkt-mon-head .mkt-muted { margin: 0; }
.mkt-mon-list { display: flex; flex-direction: column; gap: 4px; }
.mkt-mon { border: 1px solid var(--border); border-radius: 8px; background: color-mix(in srgb, var(--bg-elevated) 40%, transparent); }
.mkt-mon[open] { border-color: var(--accent); }
.mkt-mon summary { display: grid; grid-template-columns: 40px minmax(140px, 1.3fr) minmax(0, 1.6fr) auto; gap: 10px; align-items: center; padding: 6px 10px; cursor: pointer; list-style: none; }
.mkt-mon summary::-webkit-details-marker { display: none; }
.mkt-mon-img { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; }
.mkt-mon-img img { max-width: 40px; max-height: 40px; object-fit: contain; }
.mkt-mon-name { font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mkt-mon-name small { display: block; font-weight: 500; font-size: 0.78rem; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; }
.mkt-mon-best { font-size: 0.78rem; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mkt-mon-ev { text-align: right; color: var(--mkt-gold); font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; }
.mkt-mon-ev small { display: block; font-weight: 500; font-size: 0.7rem; color: var(--text-dim); }
.mkt-drops { padding: 4px 10px 10px; display: flex; flex-direction: column; gap: 2px; }
.mkt-drop { display: grid; grid-template-columns: 32px minmax(0, 1fr) 64px 70px 90px; gap: 8px; align-items: center; padding: 3px 4px; border-radius: 6px; font-size: 0.82rem; font-variant-numeric: tabular-nums; }
.mkt-drop[data-mkt-open] { cursor: pointer; }
.mkt-drop[data-mkt-open]:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.mkt-drop.unpriced { opacity: 0.55; }
.mkt-drop-head { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); font-weight: 800; }
.mkt-drop-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mkt-drop-rate, .mkt-drop-price, .mkt-drop-val { text-align: right; }
.mkt-drop-head > span:nth-child(n+3) { text-align: right; }
.mkt-drop-val { color: var(--mkt-gold); font-weight: 700; }
.mkt-drop-mon { grid-template-columns: minmax(0, 1fr) 120px; }
.mkt-drop-mon small { color: var(--text-dim); }

.mkt-alert { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 14px; border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--bg-panel)); }
.mkt-alert > svg { color: var(--accent); flex-shrink: 0; }
.mkt-alert-text { font-size: 0.88rem; }
.mkt-alert-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.mkt-alert-chip { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-elevated); color: var(--text); font: inherit; font-size: 0.8rem; cursor: pointer; }
.mkt-alert-chip:hover { border-color: var(--accent); }
.mkt-alert-th { margin-left: auto; font-size: 0.75rem; color: var(--text-dim); display: inline-flex; align-items: center; gap: 6px; }
.mkt-alert-close { background: none; border: 0; color: var(--text-dim); font-size: 1.3rem; line-height: 1; cursor: pointer; padding: 0 4px; }
.mkt-alert-close:hover { color: var(--text); }

@media (max-width: 900px) {
  .mkt-charts-even { grid-template-columns: 1fr; }
}
@media (max-width: 700px) {
  .mkt-row { grid-template-columns: 26px 30px minmax(0, 1fr) 84px 70px; min-width: 0; }
  .mkt-row > :nth-child(4), .mkt-row > :nth-child(7), .mkt-row > :nth-child(8), .mkt-row > :nth-child(9) { display: none; }
  .mkt-supply-lists { grid-template-columns: 1fr; }
  .mkt-mon summary { grid-template-columns: 34px minmax(0, 1fr) auto; }
  .mkt-mon-best { display: none; }
  .mkt-drop { grid-template-columns: 28px minmax(0, 1fr) 56px 76px; }
  .mkt-drop > :nth-child(4) { display: none; }
  .mkt-alert-th { margin-left: 0; }
}
.mkt-page .craftprofit-filter-checkbox { display: inline-flex; flex-direction: row; align-items: center; gap: 8px; text-align: left; }
.mkt-page .craftprofit-filter-checkbox input { margin: 0; }

/* ===== Marché HDV: deals, rarity anomalies, daily image (2026-09-30) ===== */
.mkt-header-actions { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
.mkt-daily-btn { height: 36px; }
.mkt-opp { display: flex; flex-direction: column; gap: 2px; }
.mkt-opp-tile { margin-bottom: 6px; }
.mkt-mover-val small.mkt-pct { font-size: 0.78rem; }
.mkt-img-modal { max-width: 760px; width: 100%; }
.mkt-img-title { margin: 0 0 10px; }
.mkt-img-preview { width: 100%; height: auto; border-radius: 8px; border: 1px solid var(--border); display: block; }
.mkt-img-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.mkt-img-actions .btn { text-decoration: none; }

/* ===== Coût du build: cheaper alternatives (2026-09-30) ===== */
.build-alt-total { font-size: 0.82rem; margin: 6px 0; }
.build-alt-total b { color: #4fc46a; }
.build-alt-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.build-alt-row { border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; display: flex; flex-direction: column; gap: 3px; }
.build-alt-line { display: grid; grid-template-columns: 26px minmax(0, 1fr) auto; gap: 8px; align-items: center; font-size: 0.8rem; }
.build-alt-line .build-cost-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.build-alt-line .build-cost-name small { color: var(--text-dim); }
.build-alt-line.alt { border-top: 1px dashed var(--border); padding-top: 3px; }
.build-alt-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 0.78rem; color: var(--text-dim); }
.build-alt-meta > span:first-child { color: #4fc46a; font-weight: 700; }
.build-alt-meta .alt-up { color: #4fc46a; font-weight: 700; }
.build-alt-meta .alt-down { color: #d9675f; font-weight: 700; }
:root[data-theme="blanc"] .build-alt-meta > span:first-child, :root[data-theme="blanc"] .build-alt-meta .alt-up, :root[data-theme="blanc"] .build-alt-total b { color: #2e7d3a; }
:root[data-theme="blanc"] .build-alt-meta .alt-down { color: #b83232; }
.build-alt-equip { margin-left: auto; font: inherit; font-size: 0.78rem; font-weight: 700; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--accent); background: none; color: var(--accent); cursor: pointer; }
.build-alt-equip:hover, .build-alt-equip:focus-visible { background: var(--accent); color: var(--accent-text); }
.mkt-drop-name small { display: block; font-size: 0.7rem; color: var(--text-dim); font-weight: 500; }
.mkt-dj-monsters { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 8px; }
.mkt-dj-mon { font-size: 0.78rem; padding: 3px 10px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-elevated); }
.mkt-dj-mon small { color: var(--text-dim); }
.mkt-dj-mon b { color: var(--mkt-gold); font-variant-numeric: tabular-nums; }

/* ===== Marché HDV — real tabs (2026-09-30) ===== */
.mkt-nav { display: flex; gap: 4px; padding: 5px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px; overflow-x: auto; scrollbar-width: thin; scroll-margin-top: 70px; }
.mkt-nav [data-mkt-tab] { flex: 1 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 9px 14px; border: 0; border-radius: 8px; background: none; color: var(--text-dim); font: inherit; font-weight: 700; font-size: 0.9rem; cursor: pointer; white-space: nowrap; }
.mkt-nav [data-mkt-tab]:hover { color: var(--text); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.mkt-nav [data-mkt-tab]:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mkt-nav [data-mkt-tab].active { background: var(--accent); color: var(--accent-text); }
.mkt-nav [data-mkt-tab].active .mkt-tab-count:not(:empty) { background: var(--accent-text); color: var(--accent); }
.mkt-tab-intro { padding: 2px 4px 0; }
.mkt-tab-intro h3 { margin: 0 0 2px; font-size: 1.15rem; }
.mkt-tab-intro p { margin: 0; color: var(--text-dim); font-size: 0.86rem; max-width: 90ch; }
.mkt-panel { display: flex; flex-direction: column; gap: 14px; }
.mkt-page[data-tab="overview"] [data-mkt-panel="list"] { display: none; }
.mkt-page:not([data-tab="overview"]) [data-mkt-panel="overview"] { display: none; }
/* Monstres & donjons: only the search + level filters apply. */
.mkt-page[data-tab="monsters"] #stats-filters > .field:not(.mkt-f-core) { display: none; }

.mkt-list-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.mkt-subseg button { padding: 6px 14px; font-size: 0.82rem; display: inline-flex; align-items: center; gap: 6px; }
.mkt-seg-count { font-size: 0.78rem; padding: 0 6px; border-radius: 999px; background: color-mix(in srgb, var(--text-dim) 25%, transparent); }
.mkt-subseg button.active .mkt-seg-count { background: var(--accent-text); color: var(--accent); }
#mkt-list-note { margin: 8px 0 10px; }

.mkt-brief { display: flex; flex-direction: column; gap: 8px; }
.mkt-brief-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "title go" "l1 go" "l2 go"; gap: 2px 12px; align-items: center; text-align: left; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-elevated); color: var(--text); font: inherit; cursor: pointer; }
.mkt-brief-card:hover, .mkt-brief-card:focus-visible { border-color: var(--accent); }
.mkt-brief-title { grid-area: title; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 800; color: var(--text-dim); }
.mkt-brief-line { font-size: 0.9rem; }
.mkt-brief-line:nth-of-type(2) { grid-area: l1; }
.mkt-brief-line:nth-of-type(3) { grid-area: l2; }
.mkt-brief-line .mkt-muted { margin: 0; }
.mkt-brief-go { grid-area: go; font-size: 0.78rem; font-weight: 700; color: var(--accent); white-space: nowrap; }

.mkt-anom-list { display: flex; flex-direction: column; overflow-x: auto; }
.mkt-anom-row { display: grid; grid-template-columns: 32px minmax(160px, 2fr) minmax(110px, 1fr) minmax(110px, 1fr) 110px; gap: 10px; align-items: center; padding: 5px 8px; border-radius: 6px; cursor: pointer; }
.mkt-anom-row:not(.mkt-head):nth-child(even) { background: color-mix(in srgb, var(--bg-elevated) 45%, transparent); }
.mkt-anom-row:not(.mkt-head):hover { background: color-mix(in srgb, var(--accent) 12%, var(--bg-elevated)); }
.mkt-anom-row.mkt-head { cursor: default; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); font-weight: 800; border-bottom: 1px solid var(--border); border-radius: 0; }
.mkt-anom-row.mkt-head > span:nth-child(n+3) { text-align: right; }
.mkt-anom-price { display: flex; flex-direction: column; align-items: flex-end; font-size: 0.76rem; font-weight: 700; }
.mkt-anom-price b { color: var(--mkt-gold); font-size: 0.9rem; font-variant-numeric: tabular-nums; }
.mkt-anom-ratio { display: flex; flex-direction: column; align-items: flex-end; }
.mkt-anom-ratio b { color: var(--mkt-up); }
.mkt-anom-ratio small { color: var(--text-dim); font-size: 0.7rem; }

@media (max-width: 700px) {
  .mkt-nav [data-mkt-tab] { flex: 0 0 auto; padding: 8px 12px; font-size: 0.84rem; }
  .mkt-brief-card { grid-template-columns: 1fr; grid-template-areas: "title" "l1" "l2" "go"; }
  .mkt-anom-row { grid-template-columns: 30px minmax(0, 1fr) 84px 84px; }
  .mkt-anom-row > :nth-child(5) { display: none; }
}
.mkt-drop-sort { background: none; border: 0; padding: 0; font: inherit; font-size: inherit; letter-spacing: inherit; text-transform: inherit; font-weight: inherit; color: var(--text-dim); cursor: pointer; text-align: inherit; }
.mkt-drop-head > .mkt-drop-sort:nth-child(n+3) { text-align: right; }
.mkt-drop-sort:hover, .mkt-drop-sort.active { color: var(--text); }
.mkt-drop-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.mkt-h3 .mkt-see-all { margin-left: auto; text-transform: none; letter-spacing: 0; font: inherit; font-size: 0.78rem; font-weight: 700; color: var(--accent); background: none; border: 1px solid var(--border); border-radius: 999px; padding: 2px 10px; cursor: pointer; }
.mkt-h3 .mkt-see-all:hover, .mkt-h3 .mkt-see-all:focus-visible { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
/* Stasis slider + mode compétitif switch (Monstres & donjons, 2026-09-30) */
#mkt-subnav { display: flex; align-items: center; gap: 10px 18px; flex-wrap: wrap; }
.mkt-stasis { display: flex; align-items: center; gap: 10px 16px; flex-wrap: wrap; padding: 4px 12px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-elevated); font-size: 0.84rem; }
.mkt-stasis-level { display: inline-flex; align-items: center; gap: 8px; font-weight: 700; }
.mkt-stasis-level input[type="range"] { width: 150px; accent-color: var(--accent); }
.mkt-stasis-level b { min-width: 30px; color: var(--accent); font-variant-numeric: tabular-nums; }
.mkt-stasis-pct { color: var(--mkt-gold); font-weight: 700; font-variant-numeric: tabular-nums; }
.mkt-switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-weight: 700; }
.mkt-switch small { color: var(--text-dim); font-weight: 500; }
.mkt-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.mkt-switch-track { position: relative; width: 34px; height: 18px; border-radius: 999px; background: var(--border); transition: background 0.15s; flex-shrink: 0; }
.mkt-switch-track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--text); transition: transform 0.15s; }
.mkt-switch input:checked + .mkt-switch-track { background: var(--accent); }
.mkt-switch input:checked + .mkt-switch-track::after { transform: translateX(16px); background: var(--accent-text); }
.mkt-switch input:focus-visible + .mkt-switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .mkt-switch-track, .mkt-switch-track::after { transition: none; } }
/* Instant hover readout on the item price chart (2026-09-30) */
.mkt-detail-chart-wrap { position: relative; }
.mkt-detail-chart { cursor: crosshair; }
.mkt-hover-line { stroke: var(--accent); stroke-width: 1; stroke-dasharray: 3 3; pointer-events: none; }
.mkt-detail-chart circle.hover { r: 5.5px; stroke: var(--text); stroke-width: 2; }
.mkt-chart-tip { position: fixed; left: 0; top: 0; pointer-events: none; z-index: 10000; display: flex; flex-direction: column; gap: 2px; padding: 6px 10px; border: 1px solid var(--accent); border-radius: 8px; background: var(--bg-panel); color: var(--text); font-size: 0.8rem; white-space: nowrap; box-shadow: 0 6px 18px -8px rgba(0, 0, 0, 0.6); --mkt-gold: #d4af37; --mkt-craft: var(--accent-2); }
:root[data-theme="blanc"] .mkt-chart-tip { --mkt-gold: #8a6a08; }
.mkt-chart-tip[hidden] { display: none; }
.mkt-chart-tip span { display: flex; justify-content: space-between; gap: 12px; color: var(--text-dim); }
.mkt-tip-price { color: var(--mkt-gold); }
.mkt-tip-craft { color: var(--mkt-craft); }
/* ===== Empty states & loading skeletons (2026-09-30) ===== */
.empty-state { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px; padding: 34px 16px; margin: 6px 0; border: 1px dashed var(--border); border-radius: 12px; color: var(--text-dim); grid-column: 1 / -1; }
.empty-state-icon { display: inline-flex; width: 52px; height: 52px; padding: 12px; border-radius: 50%; background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); margin-bottom: 4px; }
.empty-state-icon svg { width: 100%; height: 100%; }
.empty-state-title { margin: 0; color: var(--text); font-weight: 700; font-size: 1rem; }
.empty-state-text { margin: 0; font-size: 0.86rem; max-width: 46ch; }
.empty-state-btn { margin-top: 8px; }
.empty-state.compact { flex-direction: row; flex-wrap: wrap; justify-content: center; gap: 4px 8px; padding: 14px 10px; border-radius: 8px; }
.empty-state.compact .empty-state-icon { width: 28px; height: 28px; padding: 6px; margin: 0; }
.empty-state.compact .empty-state-title { font-size: 0.86rem; }
.empty-state.compact .empty-state-text { font-size: 0.78rem; flex-basis: 100%; }

.skel { display: block; border-radius: 6px; background: linear-gradient(90deg, color-mix(in srgb, var(--text-dim) 14%, transparent) 25%, color-mix(in srgb, var(--text-dim) 26%, transparent) 50%, color-mix(in srgb, var(--text-dim) 14%, transparent) 75%); background-size: 300% 100%; animation: skel-shimmer 1.4s ease-in-out infinite; }
@keyframes skel-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
.skel-line { height: 10px; width: 60%; }
.skel-line.short { width: 32%; }
.skel-line.big { height: 20px; width: 55%; }
.skel-line.tail { width: 12%; margin-left: auto; flex-shrink: 0; }
.skel-icon { width: 40px; height: 40px; border-radius: 8px; flex-shrink: 0; }
.skel-icon.sm { width: 28px; height: 28px; }
.skel-rows { display: flex; flex-direction: column; gap: 6px; padding: 4px 0; grid-column: 1 / -1; }
.skel-row { display: flex; align-items: center; gap: 12px; padding: 6px 8px; }
.skel-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; grid-column: 1 / -1; }
.skel-card { display: flex; flex-direction: column; gap: 10px; padding: 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-panel); }
.skel-card-head { display: flex; align-items: center; gap: 10px; }
.skel-stack { display: flex; flex-direction: column; gap: 6px; flex: 1; }
.skel-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; }
.skel-tile { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-panel); }
@media (prefers-reduced-motion: reduce) { .skel { animation: none; } }
body.no-anim .skel { animation: none; }

/* ===== Sorts & classes — visual rework (2026-09-30, js/classes.js) ===== */
.sc-page { display: flex; flex-direction: column; gap: 14px; max-width: 1500px; margin: 0 auto; }
.sc-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 14px 16px; }
.sc-header .section-title { margin: 0 0 4px; }
.sc-subtitle { margin: 0; color: var(--text-dim); font-size: 0.86rem; max-width: 70ch; }
.sc-tools { display: flex; align-items: center; gap: 12px 18px; flex-wrap: wrap; }
.sc-search { min-width: 240px; }
.sc-search input { width: 100%; }
.sc-level { display: inline-flex; align-items: center; gap: 10px; padding: 6px 12px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-elevated); font-size: 0.82rem; font-weight: 700; color: var(--text-dim); }
.sc-level input[type="range"] { width: 150px; accent-color: var(--accent); }
.sc-level b { min-width: 28px; color: var(--accent); font-variant-numeric: tabular-nums; }


.sc-hero { --cls-rgb: 148, 130, 201; display: flex; align-items: center; gap: 16px; padding: 14px 18px; margin-bottom: 12px; border-radius: 14px; border: 1px solid rgba(var(--cls-rgb), 0.45); background: linear-gradient(100deg, rgba(var(--cls-rgb), 0.3), rgba(var(--cls-rgb), 0.06) 60%, transparent), var(--bg-panel); }
.sc-hero-portrait { width: 72px; height: 72px; border-radius: 12px; flex-shrink: 0; box-shadow: 0 0 0 2px rgba(var(--cls-rgb), 0.7), 0 8px 20px -10px rgba(var(--cls-rgb), 0.9); background-color: var(--bg-elevated); }
.sc-hero-text { flex: 1; min-width: 0; }
.sc-hero-text h3 { margin: 0; font-size: 1.5rem; letter-spacing: 0.01em; }
.sc-hero-text p { margin: 2px 0 0; color: var(--text-dim); font-size: 0.86rem; }
.sc-hero-elements { display: flex; gap: 6px; }
.sc-hero-elements img { width: 26px; height: 26px; padding: 3px; border-radius: 8px; background: var(--bg-elevated); border: 1px solid var(--border); }

.sc-tabs { display: flex; gap: 4px; padding: 5px; margin-bottom: 14px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px; overflow-x: auto; }
.sc-tabs button { flex: 1 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 8px 14px; border: 0; border-radius: 8px; background: none; color: var(--text-dim); font: inherit; font-weight: 700; font-size: 0.88rem; cursor: pointer; white-space: nowrap; }
.sc-tabs button:hover { color: var(--text); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.sc-tabs button.active { background: var(--accent); color: var(--accent-text); }
.sc-tab-count { font-size: 0.78rem; padding: 0 7px; border-radius: 999px; background: color-mix(in srgb, var(--text-dim) 22%, transparent); }
.sc-tabs button.active .sc-tab-count { background: var(--accent-text); color: var(--accent); }

.sc-page .spell-element-title.sc-el { --el: #9482c9; display: flex; align-items: center; gap: 8px; margin: 20px 0 10px; padding-bottom: 6px; border-bottom: 2px solid color-mix(in srgb, var(--el) 55%, transparent); color: var(--el); font-size: 1.02rem; font-weight: 800; }
.sc-page .spell-element-title.sc-el:first-child { margin-top: 0; }
.sc-el-feu { --el: #e8704f !important; }
.sc-el-eau { --el: #4fa6e8 !important; }
.sc-el-terre { --el: #7fbf4d !important; }
.sc-el-air { --el: #b995e6 !important; }
:root[data-theme="blanc"] .sc-el-feu { --el: #b8431f !important; }
:root[data-theme="blanc"] .sc-el-eau { --el: #1f6fb0 !important; }
:root[data-theme="blanc"] .sc-el-terre { --el: #4f7f24 !important; }
:root[data-theme="blanc"] .sc-el-air { --el: #7c52b8 !important; }
.sc-el-count { margin-left: auto; font-size: 0.78rem; font-weight: 600; color: var(--text-dim); }

.sc-page .spell-grid { grid-template-columns: repeat(auto-fill, minmax(262px, 1fr)); gap: 14px; }
.sc-page .spell-card { display: flex; flex-direction: column; gap: 8px; padding: 14px; border-radius: 12px; transition: border-color 0.15s, box-shadow 0.15s; }
.sc-page .spell-card:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); box-shadow: 0 8px 22px -16px rgba(0, 0, 0, 0.8); }
.sc-page .spell-card .spell-level-slider { margin-top: auto; padding-top: 4px; }
.sc-page .spell-card-innate .spell-conditions { margin-top: auto; }
.sc-page .spell-head .spell-icon-wrap > img { width: 46px; height: 46px; border-radius: 10px; }
.sc-page .spell-cost img { width: 15px; height: 15px; border: 0; background: none; border-radius: 0; }
.sc-page .spell-name { font-size: 1rem; font-weight: 700; line-height: 1.25; }

/* shared with the planner's spell-deck detail card */
.spell-head-side { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; }
.sc-unlock { font-size: 0.7rem; font-weight: 700; padding: 1px 8px; border-radius: 999px; border: 1px solid color-mix(in srgb, var(--accent-2) 55%, transparent); color: var(--accent-2); white-space: nowrap; }
.spell-desc { color: var(--text-dim); font-size: 0.84rem; line-height: 1.45; }
.spell-zone-btn { color: var(--text-dim); }
.spell-zone-btn:hover { color: var(--accent-2); }
.spell-zone-btn svg { display: block; }

@media (max-width: 700px) {
  .sc-tools { width: 100%; }
  .sc-search { min-width: 0; flex: 1 1 100%; }
  .sc-level { flex: 1 1 100%; justify-content: space-between; }
  .sc-level input[type="range"] { flex: 1; width: auto; }
  .sc-hero { padding: 12px; gap: 12px; }
  .sc-hero-portrait { width: 56px; height: 56px; }
  .sc-hero-text h3 { font-size: 1.2rem; }
  .sc-hero-elements { display: none; }
  .sc-tabs button { flex: 0 0 auto; padding: 8px 12px; }
}
@media (prefers-reduced-motion: reduce) { .sc-page .spell-card { transition: none; } }

/* Sorts & classes: same big portraits as the build class picker
   (.class-nav-portraits .class-btn.portrait), laid out in rows (2026-09-30). */
.sc-page .sc-class-nav { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); justify-content: stretch; gap: 8px; flex: none; margin: 0; }
.sc-page .sc-class-nav .class-btn.portrait { width: auto; display: flex; flex-direction: column; align-items: center; gap: 5px; border-radius: 10px; }
.sc-page .sc-class-nav .class-btn.portrait .portrait-sprite { max-width: 96px; border-radius: 8px; }
@media (max-width: 700px) {
  .sc-page .sc-class-nav { display: flex !important; flex-wrap: nowrap !important; overflow-x: auto; padding: 2px 2px 6px; scroll-snap-type: x proximity; }
  .sc-page .sc-class-nav .class-btn.portrait { flex: 0 0 84px; scroll-snap-align: start; }
}
@media (min-width: 1100px) {
  .sc-page .sc-class-nav { grid-template-columns: repeat(9, 104px); justify-content: center; }
}

/* ===== Profil: badges + activité récente (2026-10-01, compacted same day) ===== */
.profile-extras { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: 14px; align-items: start; }
.profile-extras[hidden] { display: none; }
.profile-extras > .panel { padding: 10px 14px 12px; }
.profile-extras-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.profile-extras-head h3 { margin: 0; font-size: 0.92rem; }
.pbadge-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(145px, 1fr)); gap: 6px; }
.pbadge { display: flex; align-items: center; gap: 8px; padding: 5px 10px 5px 5px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-elevated); opacity: 0.6; min-width: 0; cursor: default; }
.pbadge.earned { opacity: 1; border-color: color-mix(in srgb, #d4af37 65%, transparent); background: linear-gradient(90deg, color-mix(in srgb, #d4af37 16%, var(--bg-elevated)), var(--bg-elevated)); }
.pbadge-icon { display: inline-flex; flex-shrink: 0; width: 26px; height: 26px; padding: 5px; border-radius: 50%; background: color-mix(in srgb, var(--text-dim) 16%, transparent); color: var(--text-dim); }
.pbadge.earned .pbadge-icon { background: color-mix(in srgb, #d4af37 24%, transparent); color: #e8c34f; }
:root[data-theme="blanc"] .pbadge.earned .pbadge-icon { color: #8a6a08; }
.pbadge-icon svg { width: 100%; height: 100%; }
.pbadge-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pbadge-name { font-weight: 800; font-size: 0.78rem; line-height: 1.15; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pbadge-prog { display: block; width: 100%; height: 3px; border-radius: 2px; background: color-mix(in srgb, var(--text-dim) 20%, transparent); overflow: hidden; }
.pbadge-prog i { display: block; height: 100%; background: var(--accent); }
.pbadge-count { font-size: 0.66rem; color: var(--text-dim); font-variant-numeric: tabular-nums; flex-shrink: 0; }
.pbadge.earned .pbadge-count { color: color-mix(in srgb, #d4af37 80%, var(--text-dim)); }
.pact-list { display: flex; flex-direction: column; position: relative; }
.pact-list::before { content: ""; position: absolute; left: 7px; top: 8px; bottom: 8px; width: 2px; background: var(--border); }
.pact-row { position: relative; display: grid; grid-template-columns: 16px 38px 24px minmax(0, 1fr); align-items: center; gap: 8px; padding: 4px 6px 4px 0; border: 0; background: none; color: var(--text); font: inherit; text-align: left; cursor: pointer; border-radius: 8px; }
.pact-row:hover, .pact-row:focus-visible { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.pact-dot { width: 12px; height: 12px; border-radius: 50%; margin-left: 2px; background: var(--accent); border: 2px solid var(--bg-panel); z-index: 1; }
.pact-date { font-size: 0.78rem; font-weight: 700; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.pact-portrait { width: 24px; height: 24px; border-radius: 6px; background-repeat: no-repeat; background-color: var(--bg-elevated); }
.pact-text { min-width: 0; display: flex; align-items: baseline; gap: 8px; }
.pact-text b { font-size: 0.82rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pact-text small { font-size: 0.7rem; color: var(--text-dim); display: inline-flex; align-items: center; gap: 3px; white-space: nowrap; }
.pact-text small .ui-icon { width: 10px; height: 10px; color: #e0718f; }
@media (max-width: 900px) { .profile-extras { grid-template-columns: 1fr; } }

/* ---- Profil : bascule Builds / Builds aimés (2026-10-01, maquette 7) ---- */
.profile-list-tabs { display: flex; align-items: baseline; gap: 4px; }
.profile-list-tabs button { display: inline-flex; align-items: baseline; gap: 6px; background: none; border: 0; border-bottom: 2px solid transparent; margin-bottom: -7px; padding: 2px 8px 5px; cursor: pointer; color: var(--text-dim); font: inherit; }
.profile-list-tabs button[hidden] { display: none; }
.profile-list-tabs button:first-child { padding-left: 0; }
.profile-list-tabs button h3 { color: inherit; display: inline-flex; align-items: center; gap: 5px; }
.profile-list-tabs button h3 svg { width: 14px; height: 14px; }
.profile-list-tabs button b { font-size: 0.78rem; font-weight: 700; color: var(--text-dim); background: color-mix(in srgb, var(--text-dim) 16%, transparent); border-radius: 999px; padding: 1px 7px; }
.profile-list-tabs button b:empty, .profile-list-tabs button b[hidden] { display: none; }
.profile-list-tabs button:hover { color: var(--text); }
.profile-list-tabs button.active { color: var(--accent); border-bottom-color: var(--accent); }
.profile-list-tabs button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ---- Builds publics / Profil : aperçu mini-fiche au survol d'une carte
   (2026-10-01, maquette 1). Même mini-fiche que le planner, en flottant. ---- */
.pb-hover-fiche { position: fixed; z-index: 1200; width: 356px; max-height: calc(100vh - 16px); overflow: auto; padding: 10px 12px 8px; border-radius: 12px; background: var(--bg-panel); border: 1px solid color-mix(in srgb, rgb(var(--pb-accent, 120, 120, 140)) 45%, var(--border)); box-shadow: 0 14px 40px #0008; pointer-events: none; animation: pb-hover-in 0.12s ease-out; }
.pb-hover-fiche[hidden] { display: none; }
@keyframes pb-hover-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .pb-hover-fiche { animation: none; } }
.pb-hover-fiche-head { display: flex; align-items: center; gap: 8px; padding-bottom: 6px; margin-bottom: 6px; border-bottom: 1px solid var(--border); }
.pb-hover-fiche-portrait { width: 28px; height: 28px; border-radius: 8px; flex-shrink: 0; background-size: cover; background-position: center; }
.pb-hover-fiche-head > span:last-child { display: flex; flex-direction: column; min-width: 0; }
.pb-hover-fiche-head b { font-size: 0.86rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pb-hover-fiche-head small { font-size: 0.78rem; color: var(--text-dim); }
.pb-hover-fiche .mini-diff.invisible { display: none; }
.pb-hover-fiche .mini-key { padding-top: 3px; }
.pb-hover-fiche .mini-elem small:not(.invisible) { font-size: 0.7rem; font-weight: 600; color: var(--text-dim); }
.pb-hover-fiche .mini-stat-row.mc-inactive, .pb-hover-fiche .mini-elem.mc-inactive { opacity: 0.45; }

/* Compact mini-fiche (hover preview): labeled rows become 4/3/2-column
   grids of icon + short label + value. */
.pb-hover-fiche { padding: 8px 10px 6px; }
.pb-hover-fiche .mini-stats-title { margin: 6px 0 2px; font-size: 0.66rem; }
.mini-cgrid { display: grid; gap: 2px 6px; padding: 2px 0 3px; border-bottom: 1px solid #ffffff0a; }
.mini-cgrid-4 { grid-template-columns: 1.35fr 1fr 1fr 1fr; }
.mini-cgrid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.mini-cgrid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.mini-cgrid .mini-stat-row { border-bottom: 0; min-height: 20px; padding: 1px 0; gap: 4px; font-size: 0.78rem; min-width: 0; }
.mini-cgrid .mini-stat-row .stat-icon { width: 13px; height: 13px; }
.mini-cgrid .mini-stat-value { margin-left: auto; }
.pb-hover-fiche .mini-key-row { padding: 3px 0 4px; gap: 4px; }
.pb-hover-fiche .mini-key { padding: 2px 2px 3px; }
.pb-hover-fiche .mini-key-value { font-size: 0.95rem; }
.pb-hover-fiche .mini-elem-row { padding: 2px 0 3px; }
.pb-hover-fiche .mini-elem { font-size: 0.78rem; }

/* ================================================================
   Liste de courses, refonte 2026-10-01 ("refais cette page pour qu'elle
   rende mieux"): en-tête + tuiles de résumé, bande de filtres, puis 3
   volets collants à hauteur d'écran (résultats / ma liste / courses) qui
   défilent chacun dans leur cadre. Sous 900px : onglets, un volet à la fois.
   ================================================================ */
.shop-page { max-width: 1640px; margin: 0 auto; display: flex; flex-direction: column; gap: 12px; }
.shop-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 14px 20px; }
.shop-header .section-title { margin: 0 0 4px; font-size: 1.35rem; }
.shop-subtitle { margin: 0; font-size: 0.84rem; color: var(--text-dim); max-width: 78ch; line-height: 1.45; }
.shop-subtitle b { color: var(--text); font-weight: 600; }
.shop-summary { display: flex; flex-wrap: wrap; gap: 8px; }
.shop-tile { display: flex; flex-direction: column; min-width: 104px; padding: 7px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-elevated); }
.shop-tile b { font-size: 1.15rem; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; line-height: 1.2; }
.shop-tile span { font-size: 0.78rem; color: var(--text-dim); }
.shop-tile.gold b { color: #d4af37; }
.shop-tile.done b { color: #6fce7a; }

.shop-tabs { display: none; }
.shop-filters { padding: 2px 4px 0; }
.shop-filters .filters-band { border: none; box-shadow: none; background: none; }

.shop-layout { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.9fr) minmax(0, 1.05fr); gap: 14px; align-items: start; }
.shop-col {
  display: flex; flex-direction: column; min-width: 0; padding: 12px 12px 10px;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px;
  position: sticky; top: 58px; max-height: calc(100vh - 70px);
}
.shop-col-search { height: calc(100vh - 70px); }
.shop-col-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--border); min-height: 30px; }
.shop-col-head h3 { margin: 0; font-size: 1rem; color: var(--accent); }
.shop-col-head small { color: var(--text-dim); font-size: 0.78rem; }
.shop-count { font-size: 0.78rem; font-weight: 700; color: var(--text-dim); background: color-mix(in srgb, var(--text-dim) 16%, transparent); border-radius: 999px; padding: 1px 8px; font-variant-numeric: tabular-nums; }
.shop-count:empty { display: none; }
.shop-clear-btn { margin-left: auto; padding: 3px 12px; font-size: 0.76rem; }
.shop-clear-btn:hover { border-color: var(--danger); color: var(--danger); }
.shop-prices-date { margin-left: auto; color: #d4af37; font-size: 0.78rem; font-weight: 600; white-space: nowrap; }
.shop-results { flex: 1; min-height: 0; overflow: auto; padding-right: 4px; margin-right: -4px; }
.shop-results .result-count { font-size: 0.76rem; color: var(--text-dim); margin: 0 0 6px; }
.shop-scroll { flex: 1; min-height: 0; overflow: auto; padding-right: 4px; margin-right: -4px; }

.shop-recipe-row { padding: 6px 8px; gap: 10px; }
.shop-icon-lg { width: 38px; height: 38px; }
.shop-recipe-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.shop-recipe-text .shoppinglist-search-name { font-weight: 700; }
.shop-recipe-text small { color: var(--text-dim); font-size: 0.78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.shop-recipe-cost { color: #d4af37; font-weight: 700; }
.shop-recipe-row .shoppinglist-qty-stepper button, .shop-recipe-row .shoppinglist-remove { width: 24px; height: 24px; }

.shop-progress { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.shop-progress-bar { flex: 1; height: 6px; border-radius: 999px; background: color-mix(in srgb, var(--text-dim) 20%, transparent); overflow: hidden; }
.shop-progress-bar i { display: block; height: 100%; background: #6fce7a; border-radius: inherit; transition: width 0.25s ease; }
.shop-progress-text { font-size: 0.76rem; color: var(--text-dim); white-space: nowrap; }
.shop-progress-text b { color: var(--text); }
#shoppinglist-progress .shoppinglist-course-total { margin: 0 0 8px; padding: 4px 2px 8px; border-top: 0; border-bottom: 1px solid var(--border); }
#shoppinglist-progress .shoppinglist-course-total > span:first-child { font-weight: 700; }
#shoppinglist-progress .shoppinglist-course-total-value { font-size: 1rem; }
.shop-col .empty-state { margin: 10px 0; }

@media (max-width: 1250px) {
  .shop-layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .shop-col-search { grid-column: 1 / -1; position: static; height: 70vh; }
  .shop-col-list, .shop-col-courses { position: static; max-height: 70vh; }
}
@media (max-width: 900px) {
  .shop-tabs { display: flex; gap: 4px; position: sticky; top: 50px; z-index: 5; padding: 4px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg-panel); }
  .shop-tabs button { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 8px 6px; border: 0; border-radius: 9px; background: none; color: var(--text-dim); font: inherit; font-weight: 700; font-size: 0.86rem; cursor: pointer; }
  .shop-tabs button.active { background: var(--accent); color: var(--accent-text); }
  .shop-tabs button b { font-size: 0.78rem; padding: 0 6px; border-radius: 999px; background: color-mix(in srgb, currentColor 18%, transparent); }
  .shop-tabs button b:empty { display: none; }
  .shop-page[data-shop-active="search"] [data-shop-pane]:not([data-shop-pane="search"]),
  .shop-page[data-shop-active="list"] [data-shop-pane]:not([data-shop-pane="list"]),
  .shop-page[data-shop-active="courses"] [data-shop-pane]:not([data-shop-pane="courses"]) { display: none; }
  .shop-layout { grid-template-columns: minmax(0, 1fr); }
  .shop-col, .shop-col-search, .shop-col-list, .shop-col-courses { position: static; height: auto; max-height: none; }
  .shop-results, .shop-scroll { overflow: visible; margin-right: 0; padding-right: 0; }
  .shop-header { padding: 12px 14px; }
  .shop-summary { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; }
  .shop-tile { min-width: 0; }
}
.shop-results .item-grid.has-stats-cards { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.shop-filters .filters-band { padding: 10px 14px 12px; gap: 8px 18px; }

/* Courses: animated progress fill + copy-name button (2026-10-01). */
.shop-progress-bar { height: 8px; position: relative; }
.shop-progress-bar i { position: relative; overflow: hidden; background: linear-gradient(90deg, #4fb35c, #6fce7a); transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1); }
.shop-progress-bar i::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, transparent, #ffffff70, transparent); transform: translateX(-100%); }
.shop-progress-bar i.growing::after { animation: shop-progress-sheen 0.9s ease-out 0.1s; }
.shop-progress.complete .shop-progress-bar i { background: linear-gradient(90deg, #6fce7a, #b6f0a0); box-shadow: 0 0 10px #6fce7a88; }
.shop-progress.complete .shop-progress-text b { color: #6fce7a; }
@keyframes shop-progress-sheen { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .shop-progress-bar i { transition: none; }
  .shop-progress-bar i.growing::after { animation: none; }
}
#shoppinglist-courses .shoppinglist-course-row { cursor: copy; }
.shop-copy-btn {
  flex-shrink: 0; display: inline-flex; align-items: center; gap: 4px; height: 22px; min-width: 22px; padding: 0 4px;
  border-radius: 5px; border: 1px solid var(--border); background: var(--bg-panel); color: var(--text-dim);
  cursor: pointer; font: inherit; font-size: 0.7rem; font-weight: 700; justify-content: center;
}
.shop-copy-btn .ui-icon { width: 13px; height: 13px; }
.shop-copy-btn:hover, .shoppinglist-course-row:hover .shop-copy-btn { border-color: var(--accent); color: var(--accent); }
.shoppinglist-course-row.copied { border-color: #6fce7a; }
.shoppinglist-course-row.copied .shop-copy-btn { border-color: #6fce7a; background: #6fce7a; color: #10240f; padding: 0 6px; }

/* ---- Liste de courses : filtres en colonne latérale dans le volet
   « Ajouter » (2026-10-01, maquette B choisie). ---- */
.shop-layout { grid-template-columns: minmax(0, 1.75fr) minmax(0, 0.85fr) minmax(0, 1fr); }
.shop-search-body { flex: 1; min-height: 0; display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 14px; }
.shop-filters-side { min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; padding-right: 10px; border-right: 1px solid var(--border); }
.shop-filters-side-title { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 0; margin: 0 0 10px; border: 0; background: none; color: var(--text); font: inherit; font-size: 0.82rem; font-weight: 800; cursor: default; text-align: left; }
.shop-filters-caret { display: none; }
.filters.shop-side-filters { position: static; max-height: none; overflow: visible; z-index: auto; gap: 14px; }
.shop-side-filters > .field > label { font-size: 0.7rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); margin-bottom: 6px; }
.shop-side-filters .icon-toggle-group { flex-wrap: wrap; justify-content: flex-start; }
.shop-side-filters .type-icon-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; }
.shop-side-filters .level-slider-label { text-align: left; }
.shop-side-filters > .btn { align-self: stretch; }
.shop-side-filters .filters-divider { margin: 0; border: 0; border-top: 1px solid var(--border); }
@media (max-width: 1250px) {
  .shop-layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
@media (max-width: 900px) {
  .shop-layout { grid-template-columns: minmax(0, 1fr); }
  .shop-search-body { display: flex; flex-direction: column; gap: 10px; }
  .shop-filters-side { overflow: visible; padding: 0 0 10px; border-right: 0; border-bottom: 1px solid var(--border); }
  .shop-filters-side-title { cursor: pointer; margin: 0; padding: 8px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-elevated); }
  .shop-filters-caret { display: inline; transition: transform 0.15s; }
  .shop-filters-side.open .shop-filters-caret { transform: rotate(180deg); }
  .shop-filters-side:not(.open) .shop-side-filters { display: none; }
  .shop-filters-side.open .shop-side-filters { margin-top: 12px; }
  .shop-side-filters .type-icon-grid { grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); }
}

/* ---- Base de données : même colonne de filtres que la Liste de courses
   (2026-10-01). ---- */
.db-layout.db-side-layout { grid-template-columns: 260px minmax(0, 1fr); gap: 16px; }
.db-filters-side { position: sticky; top: 62px; max-height: calc(100vh - 74px); overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; padding: 14px 14px 16px; }
.shop-side-filters .icon-toggle-group { display: flex !important; flex-wrap: wrap !important; gap: 4px; }
.shop-side-filters .type-icon-grid { display: grid !important; }
@media (max-width: 860px) {
  .db-layout.db-side-layout { grid-template-columns: minmax(0, 1fr); }
  .db-filters-side { position: static; max-height: none; overflow: visible; padding: 10px; }
  .db-filters-side .shop-filters-side-title { cursor: pointer; margin: 0; padding: 8px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-elevated); }
  .db-filters-side .shop-filters-caret { display: inline; transition: transform 0.15s; }
  .db-filters-side.open .shop-filters-caret { transform: rotate(180deg); }
  .db-filters-side:not(.open) .shop-side-filters { display: none; }
  .db-filters-side.open .shop-side-filters { margin-top: 12px; }
  .db-filters-side .type-icon-grid { grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); }
}
.shop-side-filters .elem-count-group { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; }
.shop-side-filters .elem-count-toggle { padding: 6px 2px; justify-content: center; min-width: 0; }
.shop-side-filters .elem-count-toggle img { width: 14px; height: 14px; }

/* Price badge → Statistiques detail modal (2026-10-01). */
.item-card-badge.price { cursor: pointer; }
.item-card-badge.price:hover { border-color: #d4af37; box-shadow: 0 0 0 1px #d4af3766; }
.price-history-tip-hint { margin-top: 4px; font-size: 0.7rem; color: var(--text-dim); text-align: center; }
.sc-level-shown { color: var(--text); font-weight: 700; }
/* Element-count filter icons stacked everywhere (2026-10-01, user: 3 in a
   triangle, 4 in a square) — side filters and the build's item picker. */
.elem-count-toggle { display: inline-grid; grid-auto-flow: row; grid-template-columns: repeat(2, 14px); justify-content: center; align-content: center; gap: 1px; min-height: 38px; padding: 4px 2px; }
.elem-count-toggle img { width: 14px; height: 14px; }
.elem-count-toggle[data-value="1"] img { grid-column: 1 / -1; justify-self: center; }
.elem-count-toggle[data-value="2"] { grid-template-columns: repeat(2, 14px); }
.elem-count-toggle[data-value="3"] img:first-child { grid-column: 1 / -1; justify-self: center; }
/* Outils menu: the active entry is bold (wider) and wrapped onto 2 lines
   (2026-10-01, user-reported) — every entry stays on one line. */
.view-dropdown-menu .tab-btn, .view-dropdown-menu .install-app-btn { white-space: nowrap; }
/* Element-count buttons a bit wider / airier (2026-10-01, user), same
   triangle/square icon layout: the 4 share the row evenly. */
.elem-count-group { display: grid !important; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }
.elem-count-toggle { min-height: 42px; padding: 5px 8px; gap: 2px; grid-template-columns: repeat(2, 15px); }
.elem-count-toggle[data-value="2"] { grid-template-columns: repeat(2, 15px); }
.elem-count-toggle img { width: 15px; height: 15px; }
.shop-side-filters .elem-count-group { gap: 5px; }
.shop-side-filters .elem-count-toggle { padding: 5px 4px; }
.shop-side-filters .elem-count-toggle img { width: 15px; height: 15px; }

/* Monstres & donjons: rank column + Prospection slider (2026-10-01). */
.mkt-mon summary { grid-template-columns: 44px 40px minmax(140px, 1.3fr) minmax(0, 1.6fr) auto; }
.mkt-rank { font-weight: 800; font-size: 0.9rem; color: var(--text-dim); font-variant-numeric: tabular-nums; text-align: right; }
.mkt-mon:nth-child(-n+3) .mkt-rank { color: var(--mkt-gold, #d4af37); }
.mkt-pp .mkt-stasis-level b { min-width: 58px; }
@media (max-width: 700px) {
  .mkt-mon summary { grid-template-columns: 34px 34px minmax(0, 1fr) auto; }
}

/* ---- Optimiseur de build (js/optimizer.js, 2026-10-01) : bouton baguette
   à droite de la recherche sur la poupée + fenêtre flottante. ---- */
.slot-optim-btn { color: #e8c34f; border-color: color-mix(in srgb, #e8c34f 50%, var(--border)); }
.slot-optim-btn:hover { border-color: #e8c34f; background: color-mix(in srgb, #e8c34f 14%, var(--bg-elevated)); }
.slot-optim-btn svg { width: 60%; height: 60%; }
.opt-modal { width: min(1560px, 96vw); max-width: none; max-height: 92vh; overflow: auto; padding: 18px 20px 20px; }
.opt-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin: 0 36px 14px 0; }
.opt-head h2 { margin: 0; font-size: 1.3rem; color: var(--accent); }
.opt-head p { margin: 4px 0 0; color: var(--text-dim); font-size: 0.84rem; }
.opt-apply { padding: 10px 18px; font-weight: 800; background: #e8c34f; color: #241a03; border-color: #e8c34f; }
.opt-apply:disabled { opacity: .45; cursor: not-allowed; }
.opt-grid { display: grid; grid-template-columns: 320px minmax(0, 1fr) 300px; gap: 14px; align-items: start; }
.opt-form { display: flex; flex-direction: column; gap: 14px; padding: 14px; border-radius: 12px; background: var(--bg-elevated); border: 1px solid var(--border); }
.opt-form h3, .opt-col h3 { margin: 0; font-size: 0.7rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); }
.opt-col h3 small { text-transform: none; letter-spacing: 0; font-weight: 600; }
.opt-field { display: flex; flex-direction: column; gap: 6px; }
.opt-who { margin: 0; font-size: 0.86rem; }
.opt-who small { color: var(--text-dim); }
.opt-opt { display: flex; align-items: center; gap: 8px; font-size: 0.8rem; color: var(--text-dim); }
.opt-opt select { flex: 1; min-width: 0; }
.opt-form input[type=number]:not([style]) { width: 100%; box-sizing: border-box; }
.opt-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.opt-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-panel); color: var(--text-dim); cursor: pointer; font: inherit; font-size: 0.8rem; }
.opt-chip img { width: 16px; height: 16px; }
.opt-chip.on { border-color: var(--accent); color: var(--text); background: color-mix(in srgb, var(--accent) 18%, var(--bg-panel)); }
.opt-mins label { display: flex; flex-direction: column; gap: 3px; font-size: 0.78rem; color: var(--text-dim); align-items: center; }
.opt-mins input { width: 100%; text-align: center; padding: 4px 2px; box-sizing: border-box; }
.opt-slider { display: flex; align-items: center; gap: 8px; font-size: 0.76rem; color: var(--text-dim); }
.opt-slider input { flex: 1; accent-color: var(--accent); }
.opt-keep { align-self: flex-start; font-size: 0.78rem; padding: 4px 12px; }
.opt-go { padding: 10px; font-weight: 800; font-size: 0.95rem; background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.opt-go:disabled { opacity: .6; }
.opt-note { margin: 0; font-size: 0.78rem; color: var(--text-dim); line-height: 1.5; }
.opt-col { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.opt-status { font-size: 0.82rem; color: var(--text-dim); margin: 0; }
.opt-checks { display: flex; flex-wrap: wrap; gap: 6px; }
.opt-check { display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px; border-radius: 999px; font-size: 0.8rem; font-weight: 700; border: 1px solid var(--border); font-variant-numeric: tabular-nums; }
.opt-check img { width: 14px; height: 14px; }
.opt-check.ok { border-color: #4fc46a88; color: #4fc46a; }
.opt-check.ko { border-color: #e0574f88; color: #e0574f; }
.opt-check.gold { border-color: #d4af3788; color: #d4af37; }
.opt-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 8px; }
.opt-item { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; gap: 4px 10px; align-items: center; padding: 8px 10px; border-radius: 10px; background: var(--bg-elevated); border: 1px solid var(--border); }
.opt-item.locked { border-color: var(--accent); }
.opt-item.empty { opacity: .55; }
.opt-ico { position: relative; width: 44px; height: 44px; }
.opt-ico img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.opt-ico img.icon { inset: 4px; width: calc(100% - 8px); height: calc(100% - 8px); }
.opt-item-txt { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.opt-item-txt small { font-size: 0.7rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: .04em; }
.opt-item-txt b { font-size: 0.86rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.opt-item-txt span { font-size: 0.78rem; color: var(--text-dim); }
.opt-item-txt .price { color: #d4af37; font-weight: 700; }
.opt-runes { grid-column: 1 / -1; font-size: 0.78rem; line-height: 1.5; color: var(--text-dim); padding-top: 4px; border-top: 1px dashed var(--border); }
.opt-runes b { color: var(--text); }
.opt-apt { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 8px; margin-top: 6px; }
.opt-apt-cat { padding: 8px 10px; border-radius: 10px; background: var(--bg-elevated); border: 1px solid var(--border); font-size: 0.78rem; }
.opt-apt-cat h4 { margin: 0 0 4px; font-size: 0.78rem; color: var(--accent); display: flex; justify-content: space-between; }
.opt-apt-cat div { display: flex; justify-content: space-between; gap: 8px; color: var(--text-dim); }
.opt-apt-cat div b { color: var(--text); font-variant-numeric: tabular-nums; }
.opt-owned-btns { display: flex; flex-wrap: wrap; gap: 6px; }
.opt-excluded { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.opt-excluded button { font-size: 0.78rem; padding: 3px 8px; border-radius: 999px; border: 1px dashed var(--border); background: none; color: var(--text-dim); cursor: pointer; }
.opt-fiche { padding: 12px; border-radius: 12px; background: var(--bg-elevated); border: 1px solid var(--border); position: sticky; top: 0; }
@media (max-width: 1250px) { .opt-grid { grid-template-columns: 300px minmax(0, 1fr); } .opt-fiche { grid-column: 1 / -1; position: static; } }
@media (max-width: 800px) { .opt-grid { grid-template-columns: 1fr; } .opt-modal { width: 100vw; max-height: 100vh; border-radius: 0; padding: 14px 12px; } }
/* Optimiseur : formulaire en pictos (2026-10-01). */
.opt-who { display: flex; align-items: center; gap: 10px; }
.opt-who > span:last-child { display: flex; flex-direction: column; }
.opt-who small { font-size: 0.78rem; }
.opt-who-portrait { width: 38px; height: 38px; border-radius: 9px; flex-shrink: 0; background-size: cover; background-position: center; }
.opt-chip.icon-only { padding: 6px; width: 40px; height: 40px; justify-content: center; border-radius: 10px; }
.opt-chip.icon-only img { width: 24px; height: 24px; }
.opt-slot-chips .opt-chip.icon-only img { width: 26px; height: 26px; opacity: .55; filter: grayscale(1); }
.opt-slot-chips .opt-chip.icon-only.on img { opacity: 1; filter: none; }
.opt-chip img + img { margin-left: -4px; }
.opt-slider img { width: 20px; height: 20px; }
.opt-mins label img { width: 20px; height: 20px; }
.opt-budget { display: flex; align-items: center; gap: 8px; }
.opt-budget img { width: 20px; height: 20px; }
.opt-budget input { flex: 1; min-width: 0; }
.opt-sub { font-size: 0.78rem; color: var(--text-dim); margin-top: 2px; }
/* Optimiseur : raretés (cadre de rareté, plus de rognage), châsses dessinées, sublimations (2026-10-01). */
.opt-runes { display: flex; flex-direction: column; gap: 4px; }
.opt-subtag { margin-left: 6px; font-size: 0.78rem; font-weight: 700; color: #e8c34f; border: 1px solid #e8c34f66; border-radius: 999px; padding: 1px 8px; }
.opt-runes-txt { font-size: 0.78rem; }

/* ---- Optimiseur, 2026-10-01 (2) : gemmes de rareté, curseur par tranches,
   châsses comme dans le build, 4 actions par objet ; marques de condition ;
   séparateur sur la poupée. ---- */
.opt-mins { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.opt-rars { display: flex; gap: 14px; flex-wrap: wrap; }
.opt-rar { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.82rem; color: var(--text-dim); user-select: none; }
.opt-rar .rarity-toggle { width: 40px; height: 40px; pointer-events: none; opacity: 0.45; }
.opt-rar .rarity-toggle .rarity-toggle-base, .opt-rar .rarity-toggle .rarity-toggle-tint { width: 26px; height: 26px; }
.opt-rar .rarity-toggle.active { opacity: 1; border-color: var(--rarity-color); box-shadow: 0 0 0 1px var(--rarity-color); }
.opt-rar:has(.rarity-toggle.active) { color: var(--text); }
.opt-lvl { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px; font-size: 0.78rem; color: var(--text-dim); }
.opt-lvl-track { position: relative; display: flex; flex-direction: column; gap: 2px; padding-top: 4px; }
.opt-lvl-track input { accent-color: var(--accent); width: 100%; margin: 0; }
.opt-lvl-ticks { display: flex; justify-content: space-between; padding: 0 7px; }
.opt-lvl-ticks i { width: 2px; height: 5px; border-radius: 1px; background: var(--border); }
.opt-lvl b { color: var(--accent); font-variant-numeric: tabular-nums; white-space: nowrap; }
.opt-runes .opt-shards { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; padding: 4px 0 2px; }
.opt-shards .shard-btn { display: block; cursor: default; }
.opt-runes-txt b { color: var(--text); }
.opt-item-btns { display: grid; grid-template-columns: repeat(2, 26px); gap: 4px; align-content: start; }
.opt-item-btns button { width: 26px; height: 24px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-panel); color: var(--text-dim); cursor: pointer; }
.opt-item-btns button svg { width: 14px; height: 14px; }
.opt-item-btns button:hover { border-color: var(--accent); color: var(--text); }
.opt-item-btns button.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.opt-item-btns button.ban:hover { border-color: var(--danger); color: var(--danger); }
.opt-ico-empty { display: flex; align-items: center; justify-content: center; border: 1px dashed var(--border); border-radius: 8px; }
.opt-ico-empty img { position: static; width: 26px; height: 26px; opacity: .5; filter: grayscale(1); }
.opt-item.empty { opacity: 1; }
.opt-item.empty .opt-item-txt b { color: var(--text-dim); }
.cond-mark { display: inline-flex; align-items: center; justify-content: center; width: 15px; height: 15px; margin-right: 5px; border-radius: 50%; font-size: 0.66rem; font-weight: 900; line-height: 1; vertical-align: -2px; }
.cond-mark[hidden] { display: none; }
/* Same look as the site's other status badges (2026-10-03): tinted round
   chip, line check / cross icon in the status colour. */
.cond-mark { --cond: #4fc46a; width: 16px; height: 16px; flex-shrink: 0; border: 1px solid color-mix(in srgb, var(--cond) 55%, transparent); background: color-mix(in srgb, var(--cond) 16%, transparent); color: var(--cond); }
.cond-mark.ko { --cond: var(--danger); }
:root[data-theme="blanc"] .cond-mark.ok { --cond: #2e7d3a; }
.cond-mark .ui-icon { width: 10px; height: 10px; stroke-width: 3.2; }
.doll-sep { width: 1px; align-self: stretch; margin: 4px 4px; background: var(--border); flex-shrink: 0; }

/* Optimiseur : % CC et % Parade en fourchette min – max (2026-10-01). */
.opt-ranges { display: flex; flex-direction: column; gap: 6px; margin-top: 2px; }
.opt-range { display: grid; grid-template-columns: 20px minmax(0, 1fr) 62px auto 62px; align-items: center; gap: 6px; font-size: 0.78rem; color: var(--text-dim); }
.opt-range img { width: 20px; height: 20px; }
.opt-range input { width: 100%; text-align: center; padding: 4px 2px; box-sizing: border-box; }
.opt-range i { font-style: normal; color: var(--text-dim); }

/* Optimiseur : résistance minimum par élément (2026-10-01). */
.opt-res { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.opt-res label { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.opt-res label img { width: 20px; height: 20px; }
.opt-res input { width: 100%; text-align: center; padding: 4px 2px; box-sizing: border-box; }

/* Optimiseur : archétype de maîtrises secondaires (2026-10-01). */
#opt-secs.locked { opacity: 0.55; pointer-events: none; }

/* Monstres & donjons : composition des salles et valeur par run (2026-10-02). */
.mkt-dj-rooms { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 8px; margin: 2px 0 8px; }
.mkt-dj-room { border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; background: var(--bg-elevated); min-width: 0; }
.mkt-dj-room.optional { border-style: dashed; }
.mkt-dj-room-head { display: flex; align-items: baseline; gap: 6px; font-size: 0.8rem; margin-bottom: 5px; }
.mkt-dj-room-head span { color: var(--text-dim); font-size: 0.78rem; }
.mkt-dj-room-ev { margin-left: auto; color: var(--mkt-gold); font-variant-numeric: tabular-nums; white-space: nowrap; }
.mkt-dj-room .mkt-dj-monsters { margin: 0; gap: 4px; }
.mkt-dj-room .mkt-dj-mon { background: transparent; padding: 2px 8px; }
.mkt-dj-mon i { font-style: normal; font-weight: 700; }
.mkt-dj-variant { font-size: 0.8rem; margin: 2px 0 4px; }
.mkt-dj-variant .mkt-dj-room-ev { margin-left: 0; }
.mkt-dj-note { font-size: 0.76rem; color: var(--text-dim); margin: 0 0 6px; }
.mkt-dj-sep { margin: 12px 0 4px; padding: 8px 10px 0; font-size: 0.78rem; color: var(--text-dim); border-top: 1px solid var(--border); }

/* ---- Donjons (js/dungeons.js, 2026-10-02) : liste, fiche « Préparer » /
   « Butin », fiches de monstres. ---- */
.dj-page, .dj-modal, .gs-overlay { --dj-gold: #d4af37; --dj-ok: #4fc46a; --dj-warn: #e8964f; }
:root[data-theme="blanc"] .dj-page, :root[data-theme="blanc"] .dj-modal, :root[data-theme="blanc"] .gs-overlay { --dj-gold: #8a6a08; --dj-ok: #2e7d3a; --dj-warn: #b4540f; }
.dj-page { display: flex; flex-direction: column; gap: 12px; max-width: 1100px; margin: 0 auto; }
#dj-body { display: flex; flex-direction: column; gap: 10px; }
.dj-sub, .dj-muted { color: var(--text-dim); font-size: 0.84rem; margin: 0; }
.dj-muted b { color: var(--text); }
.dj-warn { margin: 0; padding: 8px 12px; border-radius: 8px; border: 1px solid color-mix(in srgb, #e8c34f 55%, transparent); background: color-mix(in srgb, #e8c34f 10%, transparent); font-size: 0.82rem; }
.dj-gold { color: var(--dj-gold); font-weight: 800; font-variant-numeric: tabular-nums; }
img.dj-el { width: 16px; height: 16px; vertical-align: -3px; margin-right: 3px; }
.dj-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; padding: 10px 14px; }
.dj-search { flex: 1 1 220px; }
.dj-search input { width: 100%; background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text); border-radius: 6px; padding: 7px 10px; font: inherit; font-size: 0.9rem; }
.dj-search input:focus { outline: none; border-color: var(--accent); }
.dj-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.dj-chip { padding: 5px 12px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text); font: inherit; font-size: 0.8rem; cursor: pointer; }
.dj-chip:hover { border-color: var(--accent); }
.dj-chip.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); font-weight: 700; }
/* Same switch as Statistiques (.mkt-switch) so the controls match the site theme. */
.dj-switch { position: relative; font-size: 0.84rem; white-space: nowrap; }
.dj-switch b { color: var(--accent); }
/* Options of a dungeon (intervention boss, hidden room, variant), 2026-10-03 */
.dj-opts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; margin: 10px 0 4px; padding: 8px 14px; border: 1px dashed var(--border); border-radius: 12px; background: var(--bg-elevated); }
.dj-opts-lbl { font-size: 0.78rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-dim); }
.dj-opts .dj-switch { white-space: normal; }
.mkt-drops .dj-opts { margin: 8px 0; }
/* (i) button of the Attaque / Défense headers and the lines it shows */
.dj-section:has(.dj-info-btn) { grid-template-columns: auto minmax(0, 1fr) auto; }
.dj-info-btn { width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--border); background: var(--bg-panel); color: var(--text-dim); font: italic 700 0.9rem/1 Georgia, "Times New Roman", serif; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.dj-info-btn:hover, .dj-info-btn:focus-visible { border-color: var(--accent); color: var(--accent); }
.dj-info-btn.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.dj-notes { display: grid; gap: 8px; margin-top: 8px; }
.dj-notes[hidden] { display: none; }
.dj-notes > .dj-note-row { margin-top: 0; }
.build-up-rank { color: var(--text-dim); font-weight: 700; margin-right: 2px; }
.dj-stasis-fx { margin: 6px 4px 0; font-size: 0.8rem; color: var(--text-dim); line-height: 1.45; }
.dj-stasis-fx b { color: var(--text); }
.dj-fs-big { position: relative; }
.dj-fs-plus { position: absolute; top: 3px; right: 5px; font-size: 0.66rem; font-weight: 700; color: var(--dj-ok); }
.dj-list { display: flex; flex-direction: column; gap: 4px; }
.dj-row { display: grid; grid-template-columns: 44px minmax(0, 1.5fr) minmax(0, 1fr) minmax(90px, auto); gap: 12px; align-items: center; width: 100%; text-align: left; padding: 6px 12px; border-radius: 8px; border: 1px solid var(--border); background: color-mix(in srgb, var(--bg-elevated) 40%, transparent); color: var(--text); font: inherit; cursor: pointer; }
.dj-row:hover { border-color: var(--accent); }
.dj-img { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.dj-img img { max-width: 100%; max-height: 100%; object-fit: contain; }
.dj-img.lg { width: 64px; height: 64px; }
.dj-name { min-width: 0; display: flex; flex-direction: column; }
.dj-name b, .dj-name small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dj-name small, .dj-best small, .dj-ev small { font-size: 0.78rem; color: var(--text-dim); font-weight: 500; }
.dj-best { font-size: 0.84rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dj-best b { color: var(--dj-ok); margin-right: 6px; }
.dj-ev { text-align: right; white-space: nowrap; }
.dj-ev b { display: block; color: var(--dj-gold); font-variant-numeric: tabular-nums; }
.dj-back { align-self: flex-start; }
.dj-fiche { display: flex; flex-direction: column; gap: 12px; padding: 14px; }
.dj-fiche-head { display: flex; align-items: center; gap: 12px; }
.dj-fiche-head h3 { margin: 0; font-size: 1.2rem; }
.dj-fiche-head p { margin: 2px 0 0; color: var(--text-dim); font-size: 0.86rem; }
.dj-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); }
.dj-tabs button { padding: 8px 16px; border: 0; border-bottom: 2px solid transparent; background: none; color: var(--text-dim); font: inherit; font-weight: 700; cursor: pointer; }
.dj-tabs button:hover { color: var(--text); }
.dj-tabs button.on { color: var(--accent); border-bottom-color: var(--accent); }
.dj-tabpanel { display: flex; flex-direction: column; gap: 10px; }
.dj-label { margin-top: 4px; font-size: 0.7rem; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-dim); }
.dj-label span { margin-left: 8px; font-weight: 600; letter-spacing: 0; text-transform: none; }
.dj-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.dj-tiles.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.dj-tiles.four > div { align-items: center; text-align: center; }
.dj-tiles.four img.dj-el { width: 22px; height: 22px; margin: 0 0 2px; }
.dj-tiles > div { display: flex; flex-direction: column; gap: 1px; min-width: 0; padding: 8px 10px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); }
.dj-tiles b { font-size: 1rem; font-variant-numeric: tabular-nums; }
.dj-tiles span { font-size: 0.78rem; color: var(--text-dim); }
.dj-tiles .gold b { color: var(--dj-gold); }
.dj-tiles .best { border-color: var(--dj-ok); box-shadow: 0 0 0 1px color-mix(in srgb, var(--dj-ok) 40%, transparent); }
.dj-tiles .best b { color: var(--dj-ok); }
.dj-tiles .warn { border-color: var(--dj-warn); box-shadow: 0 0 0 1px color-mix(in srgb, var(--dj-warn) 40%, transparent); }
.dj-tiles .warn b { color: var(--dj-warn); }
.dj-note-row.warn > i { background: var(--dj-warn); color: #2a1503; }
/* Two titled blocks so "what to hit with" and "what to resist" cannot be confused (user, 2026-10-02). */
.dj-section { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px; align-items: center; margin-top: 8px; padding: 8px 12px; border-radius: 10px; border: 1px solid var(--border); border-left-width: 4px; background: var(--bg-elevated); }
.dj-section b { display: block; font-size: 0.98rem; }
.dj-section span { font-size: 0.8rem; color: var(--text-dim); }
.dj-section.atk { border-left-color: var(--dj-ok); }
.dj-section.atk svg, .dj-section.atk b { color: var(--dj-ok); }
.dj-section.def { border-left-color: var(--dj-warn); }
.dj-section.def svg, .dj-section.def b { color: var(--dj-warn); }
.dj-room-inc { color: var(--text-dim); }
.dj-room-inc b { color: var(--dj-warn); }
.dj-note-row { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px; align-items: center; padding: 9px 12px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); }
.dj-note-row > i { font-style: normal; font-weight: 900; width: 22px; height: 22px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 0.78rem; background: var(--accent); color: var(--accent-text); }
.dj-note-row.ok > i { background: var(--dj-ok); color: #0c2012; }
.dj-note-row b { display: block; font-size: 0.9rem; }
.dj-note-row span { font-size: 0.8rem; color: var(--text-dim); }
.dj-rooms { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 8px; }
.dj-room { padding: 8px 10px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); min-width: 0; }
.dj-room.optional { border-style: dashed; }
.dj-room-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px; font-size: 0.86rem; }
.dj-room-head span { font-size: 0.78rem; color: var(--text-dim); }
.dj-room-head em { margin-left: auto; font-style: normal; font-weight: 700; font-size: 0.82rem; color: var(--dj-ok); white-space: nowrap; }
.dj-room-head em.dj-gold { color: var(--dj-gold); }
.dj-room p { margin: 4px 0 0; font-size: 0.8rem; color: var(--text-dim); }
.dj-mons { display: flex; flex-direction: column; gap: 4px; }
.dj-mon { border: 1px solid var(--border); border-radius: 10px; background: color-mix(in srgb, var(--bg-elevated) 45%, transparent); }
.dj-mon[open] { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 7%, transparent); }
.dj-mon.boss { border-left: 3px solid var(--dj-gold); }
.dj-mon summary { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 7px 10px; cursor: pointer; list-style: none; }
.dj-mon summary::-webkit-details-marker { display: none; }
.dj-mon-name { min-width: 0; }
.dj-mon-name small { display: block; font-size: 0.76rem; color: var(--text-dim); }
.dj-x { font-size: 0.82rem; color: var(--text-dim); font-weight: 600; }
.dj-res { display: flex; flex-wrap: wrap; gap: 2px 10px; margin-top: 2px; font-size: 0.76rem; color: var(--text-dim); }
.dj-res img.dj-el { width: 13px; height: 13px; margin-right: 2px; }
.dj-res .lo { color: var(--dj-ok); font-weight: 800; }
.dj-chev { font-size: 1.2rem; font-weight: 700; color: var(--text-dim); transition: transform 0.15s ease; }
.dj-mon[open] .dj-chev { transform: rotate(90deg); color: var(--accent); }
.dj-sheet { display: flex; flex-direction: column; gap: 8px; padding: 2px 10px 12px; }
.dj-modal .dj-sheet { padding: 0; }
.dj-type-tag { display: inline-flex; align-items: center; gap: 4px; vertical-align: bottom; }
img.dj-type { height: 16px; width: auto; flex-shrink: 0; }
.dj-chip img.dj-type { height: 15px; margin-right: 5px; vertical-align: -3px; }
.dj-fiche-head img.dj-type { height: 20px; }
/* Monster characteristics: one row of identical blocks (picto + number). */
.dj-fs { display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px; padding: 10px 14px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); }
.dj-fs-sep { width: 1px; background: var(--border); margin: 0 4px; }
.dj-fs-big { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; min-width: 50px; padding: 7px 8px; border-radius: 8px; background: var(--bg-panel); }
.dj-fs-big img { width: 20px; height: 20px; object-fit: contain; }
.dj-fs-big b { font-size: 1.25rem; line-height: 1.1; font-variant-numeric: tabular-nums; white-space: nowrap; }
.dj-fs-big.zero { opacity: 0.45; }
/* Spells side by side (2026-10-02, user) instead of one per line. */
.dj-spells { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 6px; }
.dj-spell { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: 10px; align-items: start; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-elevated); }
.dj-sp-ico { width: 32px; height: 32px; border-radius: 6px; overflow: hidden; background: var(--bg-panel); }
.dj-sp-ico img { width: 100%; height: 100%; object-fit: contain; }
.dj-sp-main { min-width: 0; display: flex; flex-direction: column; }
.dj-sp-main b { font-size: 0.86rem; }
.dj-sp-main small { font-size: 0.76rem; color: var(--text-dim); }
.dj-sp-main small.fx { color: var(--text); }
.dj-sp-dmg { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; font-size: 0.82rem; font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }
.dj-sp-dmg img.dj-el { width: 14px; height: 14px; }
.dj-sp-main small.extra { color: var(--text-dim); font-style: italic; }
.dj-sp-main small.extra img.dj-el { width: 12px; height: 12px; vertical-align: -2px; margin: 0 2px 0 0; opacity: 0.85; }
/* Top of a monster sheet: label / value rows + one cell per room. */
.dj-top { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px; }
.dj-top.single { grid-template-columns: minmax(0, 1fr); }
.dj-box { padding: 8px 12px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); min-width: 0; }
.dj-kv > div { display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; gap: 8px; align-items: center; padding: 5px 0; border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); font-size: 0.86rem; }
.dj-kv > div:last-child { border-bottom: 0; }
.dj-kv img.dj-el { width: 16px; height: 16px; margin: 0; vertical-align: 0; object-fit: contain; }
.dj-kv span { color: var(--text-dim); }
.dj-kv b { font-variant-numeric: tabular-nums; text-align: right; }
.dj-kv .best b { color: var(--dj-ok); }
.dj-kv .gold b { color: var(--dj-gold); }
.dj-box-label { font-size: 0.7rem; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 6px; }
.dj-cells { display: grid; grid-template-columns: repeat(var(--cells, 4), minmax(0, 1fr)); gap: 6px; }
.dj-cells > div { display: flex; flex-direction: column; align-items: center; padding: 6px 2px; border-radius: 8px; background: var(--bg-panel); }
.dj-cells small { font-size: 0.7rem; color: var(--text-dim); }
.dj-cells b { font-size: 1.1rem; font-variant-numeric: tabular-nums; }
.dj-cells > div.off { opacity: 0.35; }
.dj-drops { display: flex; flex-direction: column; gap: 2px; }
.dj-drop { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: 10px; align-items: center; width: 100%; text-align: left; padding: 3px 6px; border: 0; border-radius: 6px; background: none; color: var(--text); font: inherit; cursor: pointer; }
.dj-drop:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.dj-drop.unpriced { opacity: 0.6; }
.dj-ico { position: relative; width: 32px; height: 32px; flex-shrink: 0; }
.dj-ico img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.dj-ico img.a { inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); }
.dj-drop-name { min-width: 0; display: flex; flex-direction: column; }
.dj-drop-name b { font-size: 0.84rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dj-drop-name small { font-size: 0.78rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.dj-link { align-self: flex-start; border: 0; background: none; color: var(--accent); font: inherit; font-size: 0.82rem; font-weight: 700; cursor: pointer; padding: 2px 0; }
.dj-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
.dj-settings { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; padding: 6px 14px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-elevated); font-size: 0.84rem; }
.dj-settings label { display: inline-flex; align-items: center; gap: 8px; }
.dj-settings label b { min-width: 28px; color: var(--accent); font-variant-numeric: tabular-nums; }
.dj-settings label { font-weight: 700; }
.dj-settings input[type="range"] { width: 140px; accent-color: var(--accent); }
.dj-pill { color: var(--dj-gold); font-weight: 700; font-variant-numeric: tabular-nums; }
.dj-source { font-size: 0.76rem; }
.dj-modal .modal { max-width: 560px; display: block; }
.dj-modal .modal-body { display: flex; flex-direction: column; gap: 10px; }
.dj-where { display: flex; flex-direction: column; gap: 4px; }
.dj-go { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0 10px; align-items: center; width: 100%; text-align: left; padding: 7px 10px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text); font: inherit; cursor: pointer; }
.dj-go:hover { border-color: var(--accent); }
.dj-go small { grid-column: 1; font-size: 0.76rem; color: var(--text-dim); }
.dj-go > span { grid-column: 2; grid-row: 1 / span 2; font-size: 1.2rem; color: var(--text-dim); }
.mkt-dj-prep { align-self: flex-start; margin: 2px 0 8px; font-size: 0.82rem; }
.fightlab-monster-picker-info { flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text-dim); font: inherit; font-size: 0.78rem; font-weight: 800; font-style: italic; line-height: 1; cursor: pointer; }
.fightlab-monster-picker-info:hover { border-color: var(--accent); color: var(--accent); }
@media (max-width: 700px) {
  .dj-row { grid-template-columns: 38px minmax(0, 1fr) auto; gap: 8px; padding: 6px 8px; }
  .dj-row .dj-best { display: none; }
  .dj-tiles.four { gap: 5px; }
  .dj-tiles.four > div { padding: 7px 4px; }
  .dj-settings { border-radius: 12px; padding: 8px 12px; }
  .dj-top { grid-template-columns: minmax(0, 1fr); }
  .dj-fs { padding: 10px; }
  .dj-fs-sep { display: none; }
  .dj-settings input[type="range"] { width: 110px; }
}

/* ---- Recherche globale (js/search.js, 2026-10-02) : fenêtre façon
   Spotlight, ouverte par la loupe de la barre du haut ou Ctrl+K. ---- */
/* One more icon in the phone top bar: slightly smaller buttons so the row
   of six still fits beside the logo instead of wrapping the last one. */
@media (max-width: 430px) {
  header.topbar { gap: 5px; }
  .topbar-right > .topbar-icon-btn { width: 32px; height: 32px; }
  .topbar-right > .topbar-icon-btn svg { width: 20px; height: 20px; }
}
body.gs-open { overflow: hidden; }
.gs-overlay { position: fixed; inset: 0; z-index: 300; display: flex; align-items: flex-start; justify-content: center; padding: 9vh 12px 12px; background: rgba(5, 6, 10, 0.62); backdrop-filter: blur(3px); }
.gs-overlay[hidden] { display: none; }
.gs-box { width: min(880px, 100%); max-height: 78vh; display: flex; flex-direction: column; border-radius: 14px; border: 1px solid var(--accent); background: var(--bg-panel); box-shadow: 0 24px 70px -20px rgba(0, 0, 0, 0.8); overflow: hidden; }
.gs-input { flex-shrink: 0; display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--border); color: var(--text-dim); }
.gs-input svg { width: 20px; height: 20px; flex-shrink: 0; }
.gs-input input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--text); font: inherit; font-size: 1.1rem; padding: 0; }
.gs-input input::-webkit-search-cancel-button { display: none; }
.gs-close, .gs-foot kbd { font: inherit; font-size: 0.7rem; color: var(--text-dim); border: 1px solid var(--border); border-radius: 5px; padding: 2px 7px; background: var(--bg-elevated); }
.gs-close { cursor: pointer; }
.gs-close:hover { color: var(--text); border-color: var(--accent); }
.gs-chips { flex-shrink: 0; display: flex; align-items: center; gap: 6px; overflow-x: auto; padding: 10px 14px 6px; scrollbar-width: none; }
.gs-chips:empty { display: none; }
.gs-chip { flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 30px; padding: 0 14px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text); font: inherit; font-size: 0.84rem; line-height: 1; white-space: nowrap; cursor: pointer; }
.gs-chip b { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.gs-chip:hover { border-color: var(--accent); }
.gs-chip.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); }
.gs-chip.on b { color: var(--accent); }
.gs-body { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); min-height: 0; flex: 1 1 auto; overflow: hidden; }
.gs-list { overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 2px; min-height: 120px; }
.gs-list > * { flex-shrink: 0; }
.gs-sec { display: flex; align-items: baseline; gap: 8px; padding: 8px 6px 3px; font-size: 0.7rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
.gs-sec button { margin-left: auto; border: 0; background: none; color: var(--accent); font: inherit; font-size: 0.76rem; font-weight: 700; letter-spacing: 0; text-transform: none; cursor: pointer; }
.gs-recent { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 6px 4px; }
.gs-res { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: 10px; align-items: center; width: 100%; text-align: left; padding: 5px 8px; border: 1px solid transparent; border-radius: 8px; background: none; color: var(--text); font: inherit; cursor: pointer; }
.gs-res.sel { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.gs-ico { position: relative; width: 34px; height: 34px; border-radius: 7px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.gs-ico img { max-width: 100%; max-height: 100%; object-fit: contain; }
.gs-ico.item img { position: absolute; inset: 0; width: 100%; height: 100%; }
.gs-ico.item img.a { inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); }
.gs-ico.sq img { border-radius: 6px; }
.gs-ico.pg { border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text-dim); }
.gs-t { min-width: 0; display: flex; flex-direction: column; }
.gs-t b { font-size: 0.88rem; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-t small { font-size: 0.78rem; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gs-v { font-size: 0.8rem; font-weight: 700; color: var(--dj-gold); font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
.gs-v small { display: block; font-weight: 500; font-size: 0.66rem; color: var(--text-dim); }
.gs-overlay mark { background: color-mix(in srgb, var(--dj-gold) 38%, transparent); color: inherit; border-radius: 3px; padding: 0 1px; }
.gs-more { align-self: flex-start; margin: 6px; padding: 5px 12px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--accent); font: inherit; font-size: 0.8rem; font-weight: 700; cursor: pointer; }
.gs-prev { display: none; }
.gs-foot { flex-shrink: 0; display: flex; align-items: center; gap: 14px; padding: 7px 14px; border-top: 1px solid var(--border); font-size: 0.78rem; color: var(--text-dim); }
.gs-foot kbd { margin-right: 3px; }
.gs-count { margin-left: auto; font-variant-numeric: tabular-nums; }
@media (min-width: 820px) {
  .gs-body { grid-template-columns: minmax(0, 1fr) 320px; }
  .gs-prev { display: flex; flex-direction: column; gap: 9px; padding: 14px; border-left: 1px solid var(--border); overflow-y: auto; background: color-mix(in srgb, var(--bg-elevated) 45%, transparent); }
  .gs-prev h3 { margin: 0; font-size: 1.05rem; line-height: 1.2; overflow-wrap: anywhere; }
  .gs-prev p { margin: 0; font-size: 0.82rem; }
  .gs-kind, .gs-lbl { font-size: 0.66rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent); }
  .gs-lbl { color: var(--text-dim); }
  .gs-sub { color: var(--text-dim); }
  .gs-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
  .gs-tiles.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .gs-tiles > div { display: flex; flex-direction: column; min-width: 0; padding: 6px 8px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-panel); }
  .gs-tiles b { font-size: 0.88rem; font-variant-numeric: tabular-nums; }
  .gs-tiles span { font-size: 0.66rem; color: var(--text-dim); }
  .gs-tiles .gold b { color: var(--dj-gold); }
  .gs-tiles .best { border-color: var(--dj-ok); }
  .gs-tiles .best b { color: var(--dj-ok); }
  .gs-head { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 10px; align-items: center; }
  .gs-head > .gs-ico { width: 48px; height: 48px; }
  .gs-head > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .gs-fx { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 2px; font-size: 0.8rem; }
  .gs-fx li { display: flex; align-items: center; gap: 6px; }
  .gs-fx .stat-icon, .gs-fx .stat-icon-spacer { width: 16px; height: 16px; flex-shrink: 0; object-fit: contain; }
  .gs-fx .neg { color: #e0574f; }
  .gs-fx .more { color: var(--text-dim); }
  .gs-kv { display: flex; flex-direction: column; gap: 3px; }
  .gs-kv > div { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; font-size: 0.8rem; padding: 4px 8px; border-radius: 6px; background: var(--bg-panel); }
  .gs-kv b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
  .gs-kv > div.has-ico { grid-template-columns: 26px minmax(0, 1fr) auto; align-items: center; padding: 3px 8px 3px 4px; }
  .gs-ico.sm { width: 26px; height: 26px; border-radius: 5px; }
  .gs-tiles.res > div { align-items: center; text-align: center; padding: 6px 2px; }
  img.gs-el { width: 16px; height: 16px; object-fit: contain; }
  .gs-tiles b img.gs-el { vertical-align: -3px; margin-right: 4px; }
  .gs-kv span { color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
  .gs-acts { margin-top: auto; padding-top: 4px; }
  .gs-hint { margin: auto; text-align: center; color: var(--text-dim); display: flex; flex-direction: column; align-items: center; gap: 8px; }
  .gs-hint svg { width: 30px; height: 30px; opacity: 0.6; }
}
@media (max-width: 819px) {
  .gs-overlay { padding: 8px; }
  .gs-box { max-height: calc(100dvh - 16px); }
  .gs-foot span:not(.gs-count) { display: none; }
}

/* Pictos de métier (textures/jobs/, 2026-10-02) à côté du nom du métier. */
.job-icon { width: 20px; height: 20px; object-fit: contain; vertical-align: -5px; margin-right: 5px; flex-shrink: 0; }
.stats-group-label .job-icon { width: 24px; height: 24px; vertical-align: -6px; margin-right: 7px; }
.gs-tiles .job-icon { width: 18px; height: 18px; vertical-align: -4px; margin-right: 4px; }
/* "Métier" dropdowns with a picto per option (WUI.iconSelect). */
.icon-select .job-icon { width: 22px; height: 22px; margin: 0; vertical-align: 0; }
.icon-select .custom-select-btn { padding: 5px 10px; min-height: 38px; }
.icon-select .custom-select-list { z-index: 150; }
.icon-select .custom-select-option { padding: 5px 8px; gap: 8px; }

/* Statistiques : 8 raretés ne tiennent pas toujours dans leur colonne ; les
   boutons rétrécissent pour rester sur une ligne au lieu de recouvrir
   « Type d'objet » (2026-10-02). */
#stats-rarity-group { flex-wrap: nowrap; gap: 3px; min-width: 0; }
#stats-rarity-group .rarity-toggle { flex: 1 1 0; width: auto; min-width: 0; max-width: 32px; height: auto; aspect-ratio: 1; }
#stats-filters > .field { min-width: 0; }


/* ---- Build folders (2026-10-03): switcher groups + "Mon profil" ---- */
.bsw-ico { width: 15px; height: 15px; flex-shrink: 0; }
.bsw-top { display: flex; gap: 6px; padding: 2px 0 6px; position: sticky; top: -6px; z-index: 3; background: var(--bg-panel); box-shadow: 0 6px 8px -6px rgba(0, 0, 0, 0.45); }
.bsw-search { flex: 1; min-width: 0; height: 32px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg); color: var(--text); padding: 0 10px; font: inherit; font-size: 0.85rem; }
.bsw-search:focus { outline: none; border-color: var(--accent); }
.bsw-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 32px; padding: 0 12px; font-size: 0.82rem; white-space: nowrap; }
.bsw-btn .ui-icon { width: 15px; height: 15px; }
.bsw-list { display: flex; flex-direction: column; gap: 2px; }
.bsw-folder { display: flex; align-items: center; gap: 7px; padding: 8px 6px 5px; margin-top: 4px; border-top: 1px solid var(--border); font-size: 0.82rem; font-weight: 700; cursor: pointer; user-select: none; }
.bsw-list > .bsw-folder:first-child { border-top: 0; margin-top: 0; }
.bsw-folder .bsw-ico { color: var(--c); }
.bsw-chev { width: 12px; color: var(--text-dim); font-size: 0.75rem; }
.bsw-fname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bsw-cnt { font-size: 0.78rem; font-weight: 700; color: var(--text-dim); background: color-mix(in srgb, var(--text-dim) 16%, transparent); border-radius: 999px; padding: 1px 7px; }
.bsw-fmore { margin-left: auto; border: 0; background: none; color: var(--text-dim); font: inherit; font-weight: 800; letter-spacing: 1px; padding: 0 6px; border-radius: 6px; cursor: pointer; }
.bsw-fmore:hover, .bsw-fmore:focus-visible { color: var(--text); background: var(--bg-elevated); }
.bsw-folder:hover .bsw-fname { color: var(--text); }
.bsw-folder-body { display: flex; flex-direction: column; gap: 2px; margin-left: 4px; padding-left: 6px; border-left: 2px solid color-mix(in srgb, var(--c) 55%, transparent); }
.bsw-folder-body[hidden], .bsw-folder[hidden], .bsw-rowwrap[hidden] { display: none; }
.bsw-empty { margin: 4px 2px 6px; font-size: 0.78rem; color: var(--text-dim); display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.bsw-empty .bsw-ico { width: 13px; height: 13px; }
.bsw-rowwrap { position: relative; }
.bsw-move { position: absolute; top: 6px; right: 6px; z-index: 2; width: 26px; height: 24px; display: inline-flex; align-items: center; justify-content: center; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text-dim); cursor: pointer; opacity: 0; transition: opacity 0.12s; }
.bsw-rowwrap:hover .bsw-move, .bsw-move:focus-visible { opacity: 1; }
.bsw-move:hover { color: var(--accent); border-color: var(--accent); }
@media (hover: none) { .bsw-move { opacity: 0.85; } }
.bsw-foot { display: flex; gap: 8px; margin-top: 6px; padding: 8px 0 2px; border-top: 1px solid var(--border); position: sticky; bottom: -6px; background: var(--bg-panel); }
.bsw-foot .bsw-btn { flex: 1; }
.bsw-panel { position: absolute; z-index: 5; width: 250px; max-width: calc(100% - 28px); padding: 10px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); box-shadow: 0 14px 30px -8px rgba(0, 0, 0, 0.65); }
.bsw-menu b { display: block; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); padding: 2px 6px 6px; }
.bsw-menu-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 8px; border: 0; border-radius: 7px; background: none; color: var(--text); font: inherit; font-size: 0.84rem; text-align: left; cursor: pointer; }
.bsw-menu-item .bsw-ico { color: var(--c); }
.bsw-menu-item span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bsw-menu-item .ui-icon { width: 14px; height: 14px; color: var(--accent); }
.bsw-menu-item:hover, .bsw-menu-item:focus-visible { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.bsw-menu-item.on { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.bsw-menu-item.new { color: var(--accent); border-top: 1px solid var(--border); border-radius: 0 0 7px 7px; margin-top: 4px; }
.bsw-menu-item.new .ui-icon { color: var(--accent); }
.bsw-edit { display: flex; flex-direction: column; gap: 8px; }
.bsw-edit label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); }
.bsw-fname-input { height: 32px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg); color: var(--text); padding: 0 10px; font: inherit; font-size: 0.86rem; }
.bsw-fname-input:focus { outline: none; border-color: var(--accent); }
.bsw-swatches, .pf-swatches { display: grid; grid-template-columns: repeat(8, 22px); gap: 6px; }
.bsw-sw, .pf-sw { width: 22px; height: 22px; border-radius: 6px; border: 0; background: var(--c); cursor: pointer; padding: 0; }
.bsw-sw.on, .pf-sw.on { outline: 2px solid var(--text); outline-offset: 2px; }
.bsw-edit-acts { display: flex; gap: 8px; }
.bsw-edit-acts .btn { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 30px; font-size: 0.8rem; }
.bsw-edit-acts .btn .ui-icon { width: 14px; height: 14px; }
.bsw-edit-acts .btn.danger, .pf-bulk .btn.danger, .pf-edit .btn.danger { color: var(--danger); }
.bsw-hint { margin: 0; font-size: 0.78rem; color: var(--text-dim); }

/* "Mon profil": folder chips, folder tag on each card, manage mode */
.pf-folders { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 10px; position: relative; }
.pf-folders[hidden] { display: none; }
.pf-folders .profile-class-chip { padding: 4px 10px 4px 8px; }
.pf-folders .profile-class-chip .bsw-ico { width: 16px; height: 16px; color: var(--c, var(--text-dim)); }
.pf-fmore { margin-left: 2px; padding: 0 3px; border-radius: 4px; color: var(--text-dim); font-weight: 800; letter-spacing: 1px; }
.pf-fmore:hover { color: var(--text); background: var(--bg); }
.pf-fnew { border-style: dashed !important; }
.pf-fnew .ui-icon { width: 14px; height: 14px; color: var(--accent); }
.pf-edit { position: absolute; z-index: 30; width: 270px; padding: 12px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); box-shadow: 0 16px 34px -10px rgba(0, 0, 0, 0.65); display: flex; flex-direction: column; gap: 10px; }
.pf-edit[hidden] { display: none; }
.pf-edit label { font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); }
.pf-edit input { height: 34px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg); color: var(--text); padding: 0 10px; font: inherit; font-size: 0.88rem; }
.pf-edit input:focus { outline: none; border-color: var(--accent); }
.pf-edit .acts { display: flex; gap: 8px; }
.pf-edit .acts .btn { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.pf-edit .acts .btn .ui-icon { width: 15px; height: 15px; }
.pf-edit p { margin: 0; font-size: 0.76rem; color: var(--text-dim); }
.pb-date { display: inline-flex; align-items: center; gap: 3px; vertical-align: bottom; }
.pb-date .ui-icon { width: 12px; height: 12px; stroke-width: 2.4; opacity: 0.85; }
.pb-own-likes { display: inline-flex; align-items: center; justify-content: center; gap: 5px; flex: 0 0 auto; min-width: 54px; padding: 0 10px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg); color: var(--text-dim); font-size: 0.85rem; }
.pb-own-likes .ui-icon { width: 14px; height: 14px; color: #e0577a; }
.pb-own-likes b { color: var(--text); font-variant-numeric: tabular-nums; }
.pf-ftag { display: inline-flex; align-items: center; gap: 4px; color: var(--c); font-weight: 600; }
.pf-ftag .bsw-ico { width: 13px; height: 13px; }
.pf-manage-btn { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; align-self: center; }
.pf-manage-btn .ui-icon { width: 15px; height: 15px; }
.pf-manage-btn.on { border-color: var(--accent); color: var(--text); background: color-mix(in srgb, var(--accent) 16%, var(--bg-elevated)); }
#view-profile .profile-builds-head { align-items: center; }
#view-profile .pb-card { position: relative; }
.pf-ck { display: none; position: absolute; top: 39px; left: 10px; z-index: 2; width: 22px; height: 22px; border-radius: 6px; border: 1.5px solid var(--text-dim); background: var(--bg); align-items: center; justify-content: center; color: var(--accent-text, #fff); }
.pf-ck .ui-icon { width: 13px; height: 13px; stroke-width: 3.2; display: none; }
.pf-manage .pf-ck { display: inline-flex; }
.pf-manage .pb-card-top { padding-left: 26px; }
.pf-manage .pb-card { cursor: pointer; }
.pf-manage .pb-card.pf-sel { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.pf-manage .pb-card.pf-sel .pf-ck { background: var(--accent); border-color: var(--accent); }
.pf-manage .pb-card.pf-sel .pf-ck .ui-icon { display: block; }
.pf-bulk { display: none; position: sticky; top: 8px; z-index: 20; align-items: center; flex-wrap: wrap; gap: 8px; margin: 10px 0 2px; padding: 8px 10px; border-radius: 10px; border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--border)); background: color-mix(in srgb, var(--accent) 12%, var(--bg-elevated)); }
.pf-manage .pf-bulk { display: flex; }
.pf-bulk b { font-size: 0.9rem; margin-right: 4px; }
.pf-bulk .btn { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.pf-bulk .btn svg { width: 15px; height: 15px; flex-shrink: 0; }
.pf-bulk .btn:disabled { opacity: 0.5; cursor: default; }
.pf-bulk .pf-end { margin-left: auto; }
.pf-ddwrap { position: relative; }
.pf-dd { position: absolute; top: calc(100% + 6px); left: 0; z-index: 31; width: 230px; padding: 6px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); box-shadow: 0 16px 34px -10px rgba(0, 0, 0, 0.65); }
.pf-dd[hidden] { display: none; }
@media (max-width: 700px) {
  .pf-folders { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 4px; }
  .pf-folders .profile-class-chip { flex-shrink: 0; }
  .pf-edit { position: fixed; left: 12px !important; right: 12px; width: auto; top: auto !important; bottom: 12px; }
  .pf-bulk .btn span { display: none; }
  .pf-bulk .pf-end span { display: inline; }
}

/* Liste de courses (2026-10-06): recipe tree under each entry of "Ma liste",
   flat summed list in Courses. */
.shop-tree-fold { flex-shrink: 0; width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--border); border-radius: 6px; background: transparent; color: var(--text-dim); font-size: 0.8rem; cursor: pointer; transition: transform 0.15s, color 0.15s, border-color 0.15s; }
.shop-tree-fold:hover { color: var(--accent); border-color: var(--accent); }
.shop-tree-fold.open { transform: rotate(90deg); }
.shop-recipe-tree { margin: 4px 0 10px; }
/* Siblings of one craft hang on one continuous vertical line: each node
   draws it down to the next sibling, the last one stops at its own elbow
   (the row's ::before). */
.shop-tree-level { display: flex; flex-direction: column; gap: 4px; padding-left: 18px; }
.shop-tree-node { position: relative; display: flex; flex-direction: column; gap: 4px; }
.shop-tree-node:not(:last-child)::before { content: ""; position: absolute; left: -10px; top: -4px; bottom: 0; border-left: 1px solid var(--tree-line); }
.shop-tree-level { --tree-line: color-mix(in srgb, var(--text-dim) 45%, transparent); }
/* The elbow is positioned from inside the row's border (1px, 3px when the
   component is crafted): shift it by that width so it sits exactly on the
   node's vertical line. */
.shop-tree-level .shop-tree-row.sub::before { border-color: var(--tree-line); left: -11px; top: -5px; width: 9px; }
.shop-tree-level .shop-tree-row.sub.expanded::before { left: -13px; width: 9px; }
/* The line colour is translucent: two strokes drawn on top of each other
   look thicker and brighter. So a node that has a following sibling only
   draws the horizontal stub (the vertical part is the node's own line), and
   only the last one draws the full elbow. */
.shop-tree-node:not(:last-child) > .shop-tree-row.sub::before { border-left: 0; border-bottom-left-radius: 0; left: -10px; width: 8px; }
.shop-tree-node:not(:last-child) > .shop-tree-row.sub.expanded::before { left: -12px; width: 8px; }
/* A validated row is dimmed through its content, not the row itself, so its
   stub keeps the colour of the line it joins. */
.shop-recipe-tree .shop-tree-row.checked { opacity: 1; }
.shop-recipe-tree .shop-tree-row.checked > * { opacity: 0.5; }
.shop-recipe-tree .shop-tree-row { cursor: default; padding-top: 3px; padding-bottom: 3px; }
.shop-recipe-tree .shop-tree-row .shoppinglist-search-name { flex: 1; min-width: 0; }
@media (prefers-reduced-motion: reduce) { .shop-tree-fold { transition: none; } }

/* Quick-nav: one pill slides between the sections instead of each button
   lighting up on its own (2026-10-06). The pill carries the active look;
   the button keeps only its text colour. Springy easing = slight overshoot. */
nav.quick-nav.has-pill { position: relative; }
.quick-nav-pill {
  position: absolute; left: 0; top: 0; box-sizing: border-box; pointer-events: none; opacity: 0;
  /* Coloured outline in the theme accent, light tint inside (user: "juste le
     contour de couleur, plutôt que couleur pleine"; the former pink
     --accent-2 read as off-theme). */
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 70%, transparent);
  border-radius: var(--radius);
  transition: transform 0.45s cubic-bezier(0.34, 1.3, 0.64, 1), width 0.45s cubic-bezier(0.34, 1.3, 0.64, 1), height 0.2s, opacity 0.2s;
}
.quick-nav-pill.on { opacity: 1; }
.quick-nav-pill-track { position: absolute; left: 0; top: 0; height: 100%; overflow: hidden; pointer-events: none; }
/* Other views: the row only shows "Retour au build" — no pill, and no track
   left behind to widen the row (it brought a scrollbar back). */
body.outside-builder #builder-quick-nav .quick-nav-pill-track { display: none; }
nav.quick-nav.has-pill .quick-nav-btn { position: relative; z-index: 1; }
nav.quick-nav.has-pill .quick-nav-btn.active,
nav.quick-nav.has-pill .quick-nav-btn.active:hover { background: transparent; border-color: transparent; color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .quick-nav-pill { transition: opacity 0.2s; } }
/* Same pill on the Statistiques section tabs, in their own solid accent. */
.mkt-nav.has-pill { position: relative; }
.mkt-nav.has-pill .quick-nav-pill { background: var(--accent); border: 0; }
.mkt-nav.has-pill [data-mkt-tab] { position: relative; z-index: 1; transition: color 0.2s; }
.mkt-nav.has-pill [data-mkt-tab].active,
.mkt-nav.has-pill [data-mkt-tab].active:hover { background: none; }

/* Right-click menu on items (Statistiques, Rentabilité craft) — js/itemmenu.js */
.ictx-menu { position: fixed; z-index: 700; min-width: 230px; max-width: min(320px, calc(100vw - 12px)); padding: 6px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); animation: ictx-in 0.12s ease-out; }
@keyframes ictx-in { from { opacity: 0; transform: translateY(-4px); } }
.ictx-head, .ictx-recipe-title { display: flex; align-items: center; gap: 8px; padding: 4px 6px 8px; margin-bottom: 4px; border-bottom: 1px solid var(--border); }
.ictx-icon { position: relative; width: 32px; height: 32px; flex-shrink: 0; }
.ictx-icon img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.ictx-icon img.icon { inset: 3px; width: calc(100% - 6px); height: calc(100% - 6px); }
.ictx-name { font-weight: 700; font-size: 0.9rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ictx-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; border: 0; border-radius: 6px; background: none; color: var(--text); font: inherit; font-size: 0.86rem; text-align: left; cursor: pointer; }
.ictx-item svg { flex-shrink: 0; color: var(--text-dim); }
.ictx-item:hover, .ictx-item:focus-visible { background: color-mix(in srgb, var(--accent) 16%, transparent); outline: none; }
.ictx-item:hover svg, .ictx-item:focus-visible svg, .ictx-item.active svg { color: var(--accent); }
.ictx-sep { height: 1px; margin: 4px 6px; background: var(--border); }
.ictx-recipe-modal { max-width: 340px; }
.ictx-recipe-title { padding: 0 28px 10px 0; margin-bottom: 8px; }
.item-tooltip.ictx-recipe-body { position: static; z-index: auto; pointer-events: auto; width: auto; max-height: none; padding: 0; border: 0; box-shadow: none; background: none; overflow: visible; }
.ictx-sub-modal { max-width: 400px; }
/* Rows of the recipe / drops windows are links (click, right click). */
.ictx-recipe-body [data-ing-item], .ictx-recipe-body [data-drop-monster] { cursor: pointer; border-radius: 6px; }
.ictx-recipe-body [data-ing-item]:hover, .ictx-recipe-body [data-drop-monster]:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
/* Sublimation effect, one sentence per line, effects indented under their
   condition (WUI.subHtml structured). */
.spell-effect.sub-fx, .sub-fx-lines { display: flex; flex-direction: column; gap: 4px; white-space: normal; }
.sub-fx-cond { font-weight: 600; color: var(--accent); }
.sub-fx-line.in { margin-left: 4px; padding-left: 12px; border-left: 2px solid color-mix(in srgb, var(--accent) 40%, transparent); }
.ictx-sub-levels { display: flex; gap: 6px; margin-bottom: 10px; }
.ictx-sub-levels:empty { display: none; }
.ictx-sub-level { flex: 1; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; background: none; color: var(--text-dim); font: inherit; font-size: 0.8rem; font-weight: 600; cursor: pointer; }
.ictx-sub-level:hover { color: var(--text); border-color: var(--accent); }
.ictx-sub-level.active { color: var(--accent); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.ictx-sub-cats { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
.ictx-sub-cats:empty { display: none; }
.ictx-sub-cats-label { flex-basis: 100%; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-dim); }
.ictx-sub-cat { padding: 3px 9px; border: 1px solid var(--border); border-radius: 999px; font-size: 0.78rem; color: var(--text-dim); }
.ictx-sub-cat b { color: var(--text); font-weight: 600; }
@media (prefers-reduced-motion: reduce) { .ictx-menu { animation: none; } }

/* The tabs row is display:flex, which beats the `hidden` attribute. */
.account-modal-tabs[hidden] { display: none; }
/* Account window: confirmation line of the change-password form. */
.account-modal-ok { color: #6fce7a; border: 1px solid color-mix(in srgb, #6fce7a 45%, transparent); border-radius: 6px; padding: 8px 10px; }

/* "Bon moment pour acheter ?" (2026-10-08), discreet: a hairline with a dot
   in the "Plus bas / plus haut" tile of the price sheet, and a dot before the
   HDV price in "Coût du build". Zones: WDB.pricePosition. */
.price-pos { position: relative; display: block; height: 3px; border-radius: 2px; background: var(--border); margin-top: 7px; cursor: help; }
.price-pos i { position: absolute; top: -3px; width: 9px; height: 9px; margin-left: -4px; border-radius: 50%; border: 2px solid var(--bg-elevated); background: currentColor; }
.price-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: currentColor; margin-right: 4px; vertical-align: 1px; }
/* the HDV column holds the dot plus a 7-digit price on one line */
.build-cost-head, .build-cost-row { grid-template-columns: 22px minmax(0, 1fr) 74px 62px; }
.build-cost-val { white-space: nowrap; }
.price-pos.low, .price-dot.low { color: #6fce7a; }
.price-pos.mid, .price-dot.mid { color: #d9b45a; }
.price-pos.high, .price-dot.high { color: #e0706f; }
.mkt-dstat span.price-pos { font-size: 0; }

/* "Rentable à recycler" (2026-10-08): same list as the rarity anomalies;
   on a phone the shard-value column gives way to the gain. */
.mkt-recy-row .mkt-anom-price small { color: var(--text-dim); font-size: 0.7rem; font-weight: 400; }
@media (max-width: 700px) {
  .mkt-anom-row.mkt-recy-row > :nth-child(4) { display: none; }
  .mkt-anom-row.mkt-recy-row > :nth-child(5) { display: flex; }
  .mkt-anom-row.mkt-recy-row.mkt-head > :nth-child(5) { display: block; }
}

/* Options of "Rentable à recycler": identified switch + shard / powder price. */
.mkt-recy-opts { flex-wrap: wrap; gap: 8px 18px; }
.mkt-recy-price { display: inline-flex; align-items: center; gap: 6px; font-size: 0.84rem; color: var(--text-dim); }
.mkt-recy-price input { width: 72px; padding: 4px 6px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text); font: inherit; text-align: right; font-variant-numeric: tabular-nums; }
.mkt-recy-price input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* "drop" in the Craft column of "Coût du build": no recipe, monster loot (2026-10-08). */
.build-cost-val.build-cost-drop { font-size: 0.78rem; font-style: italic; cursor: help; }

/* State named in a spell text (2026-10-10): link colour of the game's states, and its tooltip. */
.effect-ref.effect-ref-state { color: #e2a43b; }
.stt-state-desc { padding: 6px 10px 8px; color: var(--text-dim); font-size: 0.82rem; line-height: 1.4; max-width: 46ch; }

/* FightLab, game-client engine (beta): zones and summoned fighters created by the engine. */
.fightlab-engine-summon-letter { position: absolute; left: 50%; top: 38%; transform: translate(-50%, -50%); font-weight: 800; font-size: 1rem; color: var(--text); text-shadow: 0 1px 2px #000; }

/* FightLab log, engine (beta): detail rows (states gained / lost, AP / MP / WP, armor) under a cast. */
.fightlab-log-row.detail { padding-top: 1px; padding-bottom: 1px; padding-left: 22px; opacity: 0.85; }
.fightlab-log-row.detail .fightlab-log-spell-icon { width: 18px; height: 18px; }
.fightlab-log-row.detail .fightlab-log-spell-name { font-size: 0.8rem; font-weight: 600; }
.fightlab-log-row.detail .fightlab-log-sub { font-size: 0.7rem; }
.fightlab-log-detail { font-size: 0.8rem; font-weight: 700; color: var(--text-dim); font-variant-numeric: tabular-nums; }

/* Sizes of the sprites of what the engine summons ("Moteur du jeu" mode): the same calibrations the hand-written
   mechanics already carry (user feedback of 2026-09-07 for the Chacha Noir, the Xélor Cadran, the Étendard), keyed by the
   client's monster id; every other monster keeps the 44px box of the other entities (balises, bombs, dolls…). */
.fightlab-tile.fightlab-entity[data-monster="1482"] .fightlab-entity-sprite { transform: scale(0.5); transform-origin: 50% 100%; }
.fightlab-tile.fightlab-entity[data-monster="5535"] .fightlab-entity-sprite { transform: scale(0.7); transform-origin: 50% 100%; }
.fightlab-tile.fightlab-entity[data-monster="3791"] .fightlab-entity-sprite { transform: scale(2.2); transform-origin: 50% 100%; bottom: 4px; }
.fightlab-tile.fightlab-entity[data-monster="3793"] .fightlab-entity-sprite { bottom: 8px; }
/* the client's area pictogram on the origin cell of an area: a small flat marker, not a character-sized sprite */
.fightlab-engine-area-icon .fightlab-entity-sprite { width: 28px; height: 28px; margin-left: -14px; bottom: 6px; opacity: 0.9; }

/* Drawn pictos that replaced the last emoji (2026-10-10) */
.zone-editor-tile .ui-icon { width: 14px; height: 14px; color: #fff; margin: auto; }
.zone-editor-zoom-icon .ui-icon { width: 15px; height: 15px; }
.random-dice .ui-icon { width: 14px; height: 14px; }
.item-card-pin-badge .ui-icon { width: 12px; height: 12px; display: inline-block; vertical-align: -2px; }
.mkt-atypical .ui-icon { width: 1.05em; height: 1.05em; display: inline-block; vertical-align: -0.15em; }
.zone-editor-hint .ui-icon { width: 14px; height: 14px; display: inline-block; vertical-align: -3px; }
.fav-star-btn .ui-icon, .subs-fav .ui-icon { width: 14px; height: 14px; display: inline-block; vertical-align: -2px; }
.dj-note-row > i .ui-icon { width: 12px; height: 12px; }
.deck-slot-status .ui-icon { width: 10px; height: 10px; }
.shoppinglist-expand-toggle .ui-icon, .shoppinglist-remove .ui-icon { width: 14px; height: 14px; }
#opt-excluded button .ui-icon, .opt-apt-cat b .ui-icon { width: 12px; height: 12px; display: inline-block; vertical-align: -2px; }
[data-add-to-list] .ui-icon { width: 14px; height: 14px; }
.compact-cards-toggle-btn .ui-icon { width: 16px; height: 16px; }

/* The player standing on a summon (Écaflip on his Arbre à chat): sprite and bars raised, the floor ring stays on the ground */
.fightlab-tile.fightlab-entity.player.fightlab-perched { margin-top: -26px; }
.fightlab-tile.fightlab-entity.player.fightlab-perched .fightlab-orientation-ring { transform: translateY(26px); }

/* hover card of a state in the FightLab states panel: bonus lines and the note on conditional effects */
.stt-state-lines div { padding: 1px 0; }
.stt-state-note { margin-top: 6px; font-size: 0.78rem; color: var(--muted, #8a8f98); }

/* Ground effect of the zones the engine posed (glyphs, auras, traps, bomb zones): a tinted, softly pulsing diamond, never a spell picture */
.fightlab-engine-area { pointer-events: none; --zone: 90, 160, 255; background: radial-gradient(ellipse at 50% 50%, rgba(var(--zone), 0.55) 0%, rgba(var(--zone), 0.28) 55%, rgba(var(--zone), 0.12) 100%); border-color: rgba(var(--zone), 0.75); box-shadow: inset 0 0 8px rgba(var(--zone), 0.6); animation: fightlab-zone-pulse 2.4s ease-in-out infinite; }
.fightlab-engine-area.side-foe { --zone: 235, 90, 80; }
.fightlab-engine-area.kind-aura, .fightlab-engine-area.kind-simple { --zone: 240, 200, 90; }
.fightlab-engine-area.kind-aura.side-foe, .fightlab-engine-area.kind-simple.side-foe { --zone: 235, 120, 80; }
.fightlab-engine-area.kind-glyph { --zone: 150, 110, 255; }
.fightlab-engine-area.kind-trap { --zone: 235, 140, 60; }
.fightlab-engine-area.kind-bomb, .fightlab-engine-area.kind-bomb-combo { --zone: 255, 110, 50; }
.fightlab-engine-area.kind-beacon, .fightlab-engine-area.kind-hour { --zone: 90, 200, 230; }
.fightlab-engine-area.kind-wall, .fightlab-engine-area.kind-fake-fighter { --zone: 190, 170, 140; animation: none; }
@keyframes fightlab-zone-pulse { 0%, 100% { opacity: 0.8; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .fightlab-engine-area { animation: none; } }

/* ======================= FightLab: targets panel and turn log (redesign 2026-10-11) ======================= */
.fightlab-targets-head { display: flex; align-items: center; justify-content: space-between; margin: 0 0 6px; }
.fightlab-targets-title { font-size: 0.72rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
.fightlab-targets-count { min-width: 20px; padding: 0 6px; border-radius: 999px; background: var(--bg-elevated); border: 1px solid var(--border); font-size: 0.7rem; font-weight: 800; text-align: center; color: var(--text); }
.fightlab-add-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; margin: 0 0 10px; }
.fightlab-add-row .fightlab-monster-picker-wrap { display: flex; }
.fightlab-add-row .fightlab-monster-picker-wrap > .fightlab-add-btn { flex: 1; }
.fightlab-add-btn {
  --tone: var(--accent);
  display: flex; align-items: center; gap: 6px; min-width: 0; padding: 7px 9px; border-radius: 8px; cursor: pointer;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text); font: inherit; font-size: 0.78rem; font-weight: 700;
  transition: border-color 0.12s, background 0.12s, transform 0.12s;
}
.fightlab-add-btn > span:nth-child(2) { flex: 1; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fightlab-add-btn .ui-icon { width: 13px; height: 13px; color: var(--text-dim); flex-shrink: 0; }
.fightlab-add-btn:hover { border-color: var(--tone); background: color-mix(in srgb, var(--tone) 14%, var(--bg-elevated)); }
.fightlab-add-btn:hover .ui-icon { color: var(--tone); }
.fightlab-add-btn:active { transform: translateY(1px); }
.fightlab-add-btn.foe { --tone: var(--danger); }
.fightlab-add-btn.ally { --tone: #4caf50; }
.fightlab-add-btn.monster { --tone: #a98bff; }
.fightlab-add-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--tone); box-shadow: 0 0 0 3px color-mix(in srgb, var(--tone) 22%, transparent); flex-shrink: 0; }

.fightlab-targets-list { gap: 8px; }
.fightlab-target-card {
  --tone: var(--danger);
  position: relative; display: flex; flex-direction: column; gap: 8px; padding: 9px 10px 9px 13px; border-radius: 10px;
  background: var(--bg-elevated); border: 1px solid var(--border); overflow: hidden;
}
.fightlab-target-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--tone); }
.fightlab-target-card.team-ally { --tone: #4caf50; }
.fightlab-target-card.active { border-color: color-mix(in srgb, var(--tone) 55%, var(--border)); }
.fightlab-target-card.hidden { opacity: 0.5; }
.fightlab-target-card.hidden::before { background: var(--text-dim); }
.fightlab-target-card-top { display: flex; align-items: center; gap: 7px; }
.fightlab-target-sprite { width: 34px; height: 34px; border-radius: 8px; background: var(--bg-panel); object-fit: contain; flex-shrink: 0; box-shadow: 0 0 0 1px var(--border); }
.fightlab-target-name { flex: 1; min-width: 0; padding: 4px 6px; border: 1px solid transparent; background: transparent; font-size: 0.84rem; font-weight: 700; border-radius: 6px; }
.fightlab-target-name:hover, .fightlab-target-name:focus { border-color: var(--border); background: var(--bg-panel); }
.fightlab-target-role { flex-shrink: 0; padding: 1px 7px; border-radius: 999px; font-size: 0.6rem; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; background: color-mix(in srgb, var(--danger) 20%, transparent); color: #ffb3ad; }
.fightlab-target-role.ally { background: rgba(76, 175, 80, 0.2); color: #9fe3a2; }
.fightlab-target-role.monster { background: rgba(169, 139, 255, 0.2); color: #cbb9ff; }
.fightlab-target-id-btn, .fightlab-target-select, .fightlab-target-remove {
  display: inline-flex; align-items: center; justify-content: center; height: 24px; min-width: 24px; padding: 0 5px; border-radius: 6px;
  border: 1px solid transparent; background: transparent; color: var(--text-dim); cursor: pointer; font-size: 0.68rem; font-weight: 800;
}
.fightlab-target-id-btn:hover, .fightlab-target-select:hover, .fightlab-target-remove:hover { background: var(--bg-panel); border-color: var(--border); color: var(--text); }
.fightlab-target-remove { font-size: 1.1rem; line-height: 1; }
.fightlab-target-remove:hover { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 50%, var(--border)); }
.fightlab-target-card-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; }
.fightlab-target-card-field { display: flex; flex-direction: column; gap: 2px; font-size: 0.6rem; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-dim); }
.fightlab-target-card-field input, .fightlab-target-card-field select { height: 26px; padding: 0 7px; border-radius: 6px; font-size: 0.78rem; font-weight: 600; text-transform: none; letter-spacing: 0; color: var(--text); background: var(--bg-panel); border: 1px solid var(--border); }
.fightlab-target-card-field input[data-field="maxHp"], .fightlab-target-card-field input[data-ally-poutch-maxhp] { width: 92px; }
.fightlab-target-card-field input[data-field="pm"] { width: 48px; }
.fightlab-target-parade, .fightlab-target-facing-btn, .fightlab-target-card-row > .btn { height: 26px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-panel); display: inline-flex; align-items: center; justify-content: center; gap: 4px; padding: 0 7px; cursor: pointer; font-size: 0.72rem; font-weight: 700; color: var(--text); }
.fightlab-target-parade:hover, .fightlab-target-facing-btn:hover, .fightlab-target-card-row > .btn:hover { border-color: var(--accent); }
.fightlab-target-card-row > .fightlab-target-parade, .fightlab-target-card-row > .fightlab-target-facing-btn { margin-left: auto; }
.fightlab-target-card-row > .fightlab-target-parade + .fightlab-target-facing-btn { margin-left: 0; }
.fightlab-target-card-resist { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 5px; padding-top: 8px; border-top: 1px solid var(--border); }
.fightlab-target-res-field { display: flex; align-items: center; gap: 4px; padding: 0 5px; height: 26px; border-radius: 6px; background: var(--bg-panel); border: 1px solid var(--border); }
.fightlab-target-res-field:focus-within { border-color: var(--accent); }
.fightlab-target-res-field img.stat-icon, .fightlab-target-res-field .stat-icon { width: 15px; height: 15px; flex-shrink: 0; }
.fightlab-target-res-field input { flex: 1; min-width: 0; width: 100%; height: 100%; padding: 0; border: 0; background: transparent; font-size: 0.76rem; font-weight: 700; text-align: right; color: var(--text); }

/* turn log: one block per action, the states and resources it changed as chips under it */
.fightlab-log-cast { display: flex; flex-direction: column; gap: 3px; padding: 5px 4px 6px; border-radius: 8px; }
.fightlab-log-cast + .fightlab-log-cast { border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
.fightlab-log-cast .fightlab-log-row { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; align-items: center; gap: 9px; padding: 0 2px; background: transparent; }
.fightlab-log-cast .fightlab-log-spell-icon { width: 28px; height: 28px; }
.fightlab-log-cast .fightlab-log-row.muted { opacity: 0.85; }
.fightlab-log-cast .fightlab-log-row.muted .fightlab-log-spell-name { color: var(--text-dim); }
.fightlab-log-fxs { display: flex; flex-wrap: wrap; gap: 4px; margin-left: 39px; }
.fightlab-log-fx {
  display: inline-flex; align-items: center; gap: 4px; max-width: 100%; padding: 1px 7px 1px 3px; border-radius: 999px; font-size: 0.68rem;
  background: var(--bg-panel); border: 1px solid var(--border); color: var(--text-dim);
}
.fightlab-log-fx-icon { width: 15px; height: 15px; border-radius: 50%; object-fit: contain; flex-shrink: 0; }
.fightlab-log-fx-icon.placeholder { display: inline-block; background: var(--border); }
.fightlab-log-fx-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; color: var(--text); }
.fightlab-log-fx b { font-weight: 800; font-variant-numeric: tabular-nums; }
.fightlab-log-fx.gain { border-color: rgba(24, 195, 114, 0.4); background: rgba(24, 195, 114, 0.1); }
.fightlab-log-fx.gain b { color: #5fe0a0; }
.fightlab-log-fx.lost { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); background: color-mix(in srgb, var(--danger) 10%, var(--bg-panel)); }
.fightlab-log-fx.lost b { color: #ff8a80; }
.fightlab-log-fx[data-target-id]:hover, .fightlab-log-row[data-target-id]:hover { border-color: var(--accent); }
.fightlab-log-start-label { margin-left: 2px; font-size: 0.62rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
.fightlab-log-start .fightlab-log-fxs { margin-left: 2px; }
.fightlab-log { scrollbar-width: thin; }

/* FightLab spell bar: the spells outside the build live in a floating card, opened and closed from a button */
#fightlab-spellbar { position: relative; }
.fightlab-spell-bar-row { flex-wrap: nowrap; align-items: flex-start; gap: 10px; }
.fightlab-spell-bar-side { margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; }
.fightlab-library-toggle {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 9px; border-radius: 8px; cursor: pointer;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim); font: inherit; font-size: 0.72rem; font-weight: 700;
  transition: border-color 0.12s, color 0.12s, background 0.12s;
}
.fightlab-library-toggle .ui-icon { width: 14px; height: 14px; }
.fightlab-library-toggle:hover, .fightlab-library-toggle.active { color: var(--text); border-color: var(--accent); }
.fightlab-library-toggle.active { background: color-mix(in srgb, var(--accent) 16%, var(--bg-elevated)); }
.fightlab-spell-library {
  display: none; position: absolute; right: 0; bottom: calc(100% + 8px); z-index: 60; width: 232px;
  flex-direction: column; align-items: stretch; justify-content: flex-start; padding: 0 0 10px;
  background: color-mix(in srgb, var(--bg-panel) 97%, transparent); border: 1px solid var(--border); border-left: 1px solid var(--border);
  border-radius: 12px; box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
}
.fightlab-spell-library.open { display: flex; }
.fightlab-spell-library-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-bottom: 1px solid var(--border); font-size: 0.74rem; font-weight: 800; letter-spacing: 0.04em; }
.fightlab-spell-library-count { padding: 0 7px; border-radius: 999px; background: var(--bg-elevated); border: 1px solid var(--border); font-size: 0.66rem; color: var(--text-dim); }
.fightlab-spell-library-close { margin-left: auto; width: 22px; height: 22px; border-radius: 6px; border: 1px solid transparent; background: transparent; color: var(--text-dim); font-size: 1.1rem; line-height: 1; cursor: pointer; }
.fightlab-spell-library-close:hover { background: var(--bg-elevated); border-color: var(--border); color: var(--text); }
.fightlab-spell-library-results { padding: 10px 10px 0; }

/* FightLab global toggles: a compact two-column toolbar, the reset spanning the full width */
.fightlab-toolbar { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; margin-top: 10px; }
.fightlab-toolbar .btn { display: flex; align-items: center; justify-content: flex-start; gap: 7px; min-width: 0; height: 30px; padding: 0 9px; border-radius: 8px; font-size: 0.74rem; font-weight: 700; background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim); }
.fightlab-toolbar .btn > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fightlab-toolbar .btn .ui-icon, .fightlab-toolbar .btn img.ui-icon { width: 15px; height: 15px; flex-shrink: 0; }
.fightlab-toolbar .btn:hover { color: var(--text); border-color: var(--accent); }
.fightlab-toolbar .btn.active { color: var(--text); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, var(--bg-elevated)); }
.fightlab-toolbar .btn.danger { grid-column: 1 / -1; justify-content: center; color: #ff9d96; border-color: color-mix(in srgb, var(--danger) 40%, var(--border)); background: color-mix(in srgb, var(--danger) 8%, var(--bg-elevated)); }
.fightlab-toolbar .btn.danger:hover { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 18%, var(--bg-elevated)); color: #fff; }

/* FightLab bottom area: the turn log under the targets column, and under the board, always on the right, the turn info then the spell bars */
.fightlab-bottom { display: grid; grid-template-columns: 380px minmax(0, 1fr); grid-template-areas: "log info" "log spells"; gap: 10px 14px; align-items: start; }
.fightlab-bottom-log { grid-area: log; width: auto; min-width: 0; }
.fightlab-bottom-info { grid-area: info; max-width: none; min-width: 0; }
.fightlab-bottom-spells { grid-area: spells; max-width: none; min-width: 0; justify-self: stretch; }
@media (max-width: 900px) {
  .fightlab-bottom { grid-template-columns: minmax(0, 1fr); grid-template-areas: "info" "spells" "log"; }
}

/* order under the board, on the right: turn / resources, the spell bars, then class gauges and the effective stats */
.fightlab-bottom { grid-template-areas: "log info" "log spells" "log res" "log stats"; grid-template-rows: auto auto auto 1fr; }
.fightlab-bottom-info { display: contents; }
#fightlab-turn-info { grid-area: info; min-width: 0; }
#fightlab-class-resources { grid-area: res; min-width: 0; }
#fightlab-mini-stats { grid-area: stats; min-width: 0; }
@media (max-width: 900px) {
  .fightlab-bottom { grid-template-areas: "info" "spells" "res" "stats" "log"; grid-template-rows: none; }
}

/* FightLab: dungeon room picker and the cells of a loaded room (data/dungeon-maps.json, from the client's map topology) */
.fightlab-toolbar .fightlab-room-btn { grid-column: 1 / -1; justify-content: center; }
.fightlab-tile.room-wall { background: rgba(255, 90, 90, 0.2); border-color: rgba(255, 120, 120, 0.55); cursor: not-allowed; }
.fightlab-room-modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; background: rgba(5, 4, 12, 0.66); }
.fightlab-room-card { width: min(920px, 94vw); max-height: 86vh; display: flex; flex-direction: column; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6); overflow: hidden; }
.fightlab-room-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); font-weight: 800; letter-spacing: 0.04em; }
.fightlab-room-close { width: 28px; height: 28px; border-radius: 8px; border: 1px solid transparent; background: transparent; color: var(--text-dim); font-size: 1.3rem; line-height: 1; cursor: pointer; }
.fightlab-room-close:hover { background: var(--bg-elevated); border-color: var(--border); color: var(--text); }
.fightlab-room-controls { display: flex; gap: 8px; padding: 10px 16px 6px; }
.fightlab-room-controls input { flex: 1; height: 32px; padding: 0 10px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text); font: inherit; }
.fightlab-room-body { display: grid; grid-template-columns: 230px minmax(0, 1fr); min-height: 0; flex: 1; border-top: 1px solid var(--border); }
.fightlab-room-list { overflow-y: auto; max-height: 62vh; padding: 8px; border-right: 1px solid var(--border); display: flex; flex-direction: column; gap: 3px; }
.fightlab-room-dg { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 9px; border-radius: 7px; border: 1px solid transparent; background: transparent; color: var(--text); font: inherit; font-size: 0.8rem; text-align: left; cursor: pointer; }
.fightlab-room-dg small { color: var(--text-dim); font-size: 0.68rem; }
.fightlab-room-dg:hover { background: var(--bg-elevated); }
.fightlab-room-dg.active { background: color-mix(in srgb, var(--accent) 20%, var(--bg-elevated)); border-color: var(--accent); }
.fightlab-room-screens { overflow-y: auto; max-height: 62vh; padding: 12px 14px; }
.fightlab-room-dgname { font-weight: 800; margin-bottom: 10px; }
.fightlab-room-dgname small { color: var(--text-dim); font-weight: 600; margin-left: 6px; }
.fightlab-room-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 10px; }
.fightlab-room-screen { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 8px 6px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text); font: inherit; font-size: 0.74rem; font-weight: 700; cursor: pointer; }
.fightlab-room-screen img { width: 100%; height: 104px; object-fit: contain; border-radius: 4px; background: #0d0b16; }
.fightlab-room-screen small { color: var(--text-dim); font-weight: 600; font-size: 0.66rem; }
.fightlab-room-screen:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--bg-elevated)); }
.fightlab-room-hint { margin: 6px; color: var(--text-dim); font-size: 0.8rem; }
@media (max-width: 700px) { .fightlab-room-body { grid-template-columns: minmax(0, 1fr); } .fightlab-room-list { max-height: 26vh; border-right: 0; border-bottom: 1px solid var(--border); } }

/* Test mock-up: the player drawn from the client animation files (js/fightlab-anim.js), feet on the tile centre. */
.fightlab-anim-sprite { width: 100px; height: 100px; margin-left: -50px; bottom: -5px; object-fit: fill; }
/* Osamodas: the summon whose turn it is (it walks and casts on its own turn, see js/fightlab.js beginSummonTurn) */
.fightlab-engine-summon.fightlab-turn-active .fightlab-mechanism-ring { box-shadow: 0 0 0 2px #ffd36b, 0 0 10px 2px rgba(255, 211, 107, 0.7); }
.fightlab-engine-summon.fightlab-playable-summon:not(.fightlab-turn-active) .fightlab-mechanism-ring { box-shadow: 0 0 0 1px rgba(255, 211, 107, 0.45); }
.fightlab-actor-head { font-size: 0.75rem; font-weight: 600; color: #ffd36b; margin: 0 0 4px 2px; }
.mkt-range-row { display: flex; justify-content: flex-end; margin: 0 0 6px; }
.mkt-range button { padding: 3px 10px; }
.mkt-hist-scroll { max-height: min(340px, 42vh); overflow-y: auto; overscroll-behavior: contain; }
.mkt-hist-scroll .mkt-hist-table thead th { position: sticky; top: 0; background: var(--bg-panel, var(--bg-elevated)); z-index: 1; }
.mkt-downame { color: var(--text-dim); font-size: 0.72rem; margin-left: 4px; }

/* ===== Page d'accueil (2026-10-21, maquette « mix A + B », _maquettes/accueil-cartes.html) ===== */
body.home-open main > .view { display: none !important; }
body.home-open main { padding-top: 0 !important; }      /* the landing gradient starts right under the top bar (2026-10-22) */
body.home-open .builder-float-sidebar, body.home-open #mobile-float-bar { display: none !important; }
.home-landing { max-width: 1240px; margin: 0 auto; padding: 40px 16px 56px; background: radial-gradient(ellipse at top, color-mix(in srgb, var(--accent) 16%, transparent) 0%, transparent 60%); }
.home-landing[hidden] { display: none; }
.home-head { text-align: center; margin-bottom: 28px; }
.home-head h1 { margin: 0 0 6px; font-size: 2rem; color: var(--text); text-wrap: balance; }
.home-head p { margin: 0; color: var(--text-dim); }
.home-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
.home-card { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 16px; overflow: hidden; cursor: pointer; transition: transform .2s, border-color .2s, box-shadow .2s; }
.home-card:hover, .home-card:focus-visible { transform: translateY(-4px); border-color: var(--accent); box-shadow: 0 16px 40px rgba(0, 0, 0, .45); outline: none; }
.home-pv { height: 300px; overflow: hidden; position: relative; border-bottom: 1px solid var(--border); }
.home-pv img { position: absolute; left: 0; top: 0; width: 100%; height: auto; transition: transform 4s ease; }
.home-card:hover .home-pv img { transform: translateY(-24%); }
.home-pv::after { content: ""; position: absolute; inset: auto 0 0 0; height: 90px; background: linear-gradient(transparent, var(--bg-panel)); }
.home-kicker { position: absolute; z-index: 1; left: 18px; top: 16px; font-size: 0.7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--text); background: color-mix(in srgb, var(--bg) 80%, transparent); border: 1px solid var(--border); padding: 3px 10px; border-radius: 99px; }
.home-in { padding: 8px 22px 22px; display: grid; gap: 10px; }
.home-in h2 { margin: 0; font-size: 1.35rem; color: var(--text); }
.home-in p { margin: 0; color: var(--text-dim); font-size: 0.9rem; }
.home-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.home-chips span { font-size: 0.75rem; padding: 2px 9px; border-radius: 99px; background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-dim); }
.home-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 4px; }
.home-btn { padding: 10px 20px; font-weight: 600; pointer-events: none; background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text); border-radius: 10px; }
.home-btn.primary { background: var(--accent); color: var(--bg); border-color: var(--accent); }
.home-stat { font-size: 0.82rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.home-stat b { color: #d4af37; }
.home-stat a { color: #d4af37; font-weight: 600; text-decoration: none; }
.home-stat a:hover { text-decoration: underline; }
.home-skip { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 24px; color: var(--text-dim); font-size: 0.82rem; cursor: pointer; }
@media (prefers-reduced-motion: reduce) { .home-card, .home-pv img { transition: none; } .home-card:hover .home-pv img { transform: none; } }
@media (max-width: 860px) { .home-grid { grid-template-columns: 1fr; } .home-pv { height: 200px; } .home-head h1 { font-size: 1.6rem; } .home-landing { padding-top: 24px; } }

/* ===== Profil : suppression du compte (2026-10-21) ===== */
.profile-danger { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-top: 18px; padding: 14px 18px; border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--border)); border-radius: 12px; background: color-mix(in srgb, var(--danger) 5%, var(--bg-panel)); }
.profile-danger[hidden] { display: none; }
.profile-danger b { color: var(--text); }
.profile-danger > div:first-child { flex: 1 1 280px; }
.profile-danger p { margin: 2px 0 0; color: var(--text-dim); font-size: 0.85rem; }
.profile-danger .btn.danger { color: #ff9d96; }
.profile-delete-dlg { max-width: 440px; width: calc(100% - 32px); background: var(--bg-panel); color: var(--text); border: 1px solid var(--border); border-radius: 14px; padding: 20px 22px; }
.profile-delete-dlg::backdrop { background: rgba(0, 0, 0, .6); }
.profile-delete-dlg h3 { margin: 0 0 8px; }
.profile-delete-dlg p { margin: 0 0 12px; color: var(--text-dim); font-size: 0.9rem; }
.profile-delete-dlg label { display: grid; gap: 6px; font-size: 0.85rem; color: var(--text-dim); }
.profile-delete-dlg input { background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 9px 12px; font: inherit; }
.profile-delete-err { color: #ff9d96 !important; margin-top: 8px !important; }
.profile-delete-err[hidden] { display: none; }
.profile-delete-acts { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.profile-delete-acts .btn.danger.solid { background: var(--danger); border-color: var(--danger); color: #fff; font-weight: 600; }
.profile-danger-acts { display: flex; gap: 8px; flex-wrap: wrap; }

/* ===== Mot de passe oublié (2026-10-22) ===== */
.account-forgot-link { background: none; border: none; padding: 2px 0; color: var(--text-dim); font: inherit; font-size: 0.85rem; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; justify-self: center; align-self: center; }
.account-forgot-link:hover { color: var(--text); }
.account-forgot-link[hidden] { display: none; }

/* ===== Fenêtres du site (2026-10-22, remplacent confirm/alert/prompt du navigateur, js/dialog.js) ===== */
.sb-dialog { border: 1px solid var(--border); border-radius: 14px; padding: 0; background: var(--bg-panel); color: var(--text); width: min(440px, calc(100% - 32px)); box-shadow: 0 20px 60px rgba(0, 0, 0, .55); }
.sb-dialog::backdrop { background: rgba(0, 0, 0, .55); }
.sb-dialog-box { padding: 20px 22px 18px; display: grid; gap: 10px; margin: 0; }
.sb-dialog h3 { margin: 0; font-size: 1.1rem; color: var(--text); text-wrap: balance; }
.sb-dialog p { margin: 0; color: var(--text-dim); font-size: 0.92rem; white-space: pre-line; line-height: 1.5; }
.sb-dialog-acts { display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.sb-dialog-acts .btn { padding: 8px 16px; }
.sb-dialog-acts .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--bg); font-weight: 600; }
.sb-dialog-acts .btn.danger.solid { background: var(--danger); border-color: var(--danger); color: #fff; font-weight: 600; }
.sb-dialog-acts .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sb-dialog-copy { width: 100%; resize: vertical; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; font: 0.85rem/1.4 Consolas, monospace; }
.sb-toast { position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 20px); opacity: 0; pointer-events: none; z-index: 2000; background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border); border-left-width: 1px; border-radius: 10px; padding: 10px 16px; font-size: 0.9rem; box-shadow: 0 10px 30px rgba(0, 0, 0, .45); transition: opacity .2s, transform .2s; max-width: calc(100% - 32px); }
.sb-toast.show { opacity: 1; transform: translate(-50%, 0); }
.sb-toast[data-kind="ok"] { border-color: color-mix(in srgb, #6fce7a 55%, var(--border)); }
.sb-toast[data-kind="error"] { border-color: color-mix(in srgb, var(--danger) 60%, var(--border)); }
@media (prefers-reduced-motion: reduce) { .sb-toast { transition: none; } }

/* ===== Fenêtre « Nouveau build » (2026-10-22, maquette F de _maquettes/creer-build.html) =====
   --nb-acc is a registered <color>, so a class change fades every accent (slider, outline, role ring) from the old colour to the new one.
   --cls-rgb = accent of the chosen class (WUI.classAccentColor), falls back to the site accent. */
@property --nb-acc { syntax: "<color>"; inherits: true; initial-value: #9482c9; }
.sb-newbuild { width: min(600px, calc(100% - 24px)); padding: 0; overflow: hidden; --nb-acc: rgb(var(--cls-rgb, 148, 130, 201)); transition: --nb-acc .45s ease; }
.sb-newbuild .nb-form { margin: 0; }
.sb-newbuild .nb-head { position: relative; height: 120px; background: var(--bg-elevated); display: flex; align-items: flex-end; padding: 0 20px 6px; overflow: hidden; }
.sb-newbuild .nb-head::after { content: ""; position: absolute; inset: 0; background: linear-gradient(transparent 35%, var(--bg-panel)); pointer-events: none; }
.sb-newbuild .nb-art { position: absolute; inset: 0; background: center 28% / cover no-repeat; opacity: 0; transition: opacity .45s ease; }
.sb-newbuild .nb-art.on { opacity: 1; }
.sb-newbuild .nb-head b, .sb-newbuild .nb-x { z-index: 1; }
.sb-newbuild .nb-head b { font-size: 1.35rem; color: #fff; text-shadow: 0 2px 8px #000; }
.sb-newbuild .nb-x { position: absolute; right: 10px; top: 6px; background: none; border: none; color: #fff; font-size: 1.5rem; line-height: 1; cursor: pointer; text-shadow: 0 0 6px #000; padding: 4px 8px; }
.sb-newbuild .nb-body { padding: 6px 20px 20px; display: grid; gap: 14px; }
.sb-newbuild .nb-l { display: block; font-size: 0.72rem; color: var(--text-dim); margin: 0 0 6px; text-transform: uppercase; letter-spacing: .05em; }
.sb-newbuild .nb-clsname { color: var(--nb-acc); font-weight: 600; }
.sb-newbuild .nb-classes { display: grid; grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 6px; }
.sb-newbuild .nb-classes button { aspect-ratio: 1; border-radius: 6px; border: 1px solid var(--border); padding: 0; overflow: hidden; background: var(--bg-elevated); cursor: pointer; }
.sb-newbuild .nb-classes button span { display: block; width: 100%; height: 100%; }
.sb-newbuild .nb-classes button:not(.on) span { filter: saturate(.55) brightness(.8); }
.sb-newbuild .nb-classes button:hover span { filter: none; }
.sb-newbuild .nb-classes button.on { outline: 2px solid var(--nb-acc); outline-offset: 1px; }
.sb-newbuild input[type=text] { width: 100%; box-sizing: border-box; }
.sb-newbuild .nb-lv { display: flex; align-items: baseline; justify-content: space-between; }
.sb-newbuild .nb-lvval { font-size: 1.2rem; color: var(--text); }
/* Drawn by hand (not accent-color): the browser picks a light or dark empty track from the accent's brightness, so the
   empty part changed with the class (user, 2026-10-22). Empty part = site colour, filled part (--pct, set by planner.js) = class colour. */
.sb-newbuild input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 18px; background: transparent; margin: 0; cursor: pointer; }
.sb-newbuild input[type=range]::-webkit-slider-runnable-track { height: 6px; border-radius: 99px; background: linear-gradient(90deg, var(--nb-acc) var(--pct, 50%), var(--bg-elevated) var(--pct, 50%)); border: 1px solid var(--border); }
.sb-newbuild input[type=range]::-moz-range-track { height: 6px; border-radius: 99px; background: var(--bg-elevated); border: 1px solid var(--border); }
.sb-newbuild input[type=range]::-moz-range-progress { height: 6px; border-radius: 99px; background: var(--nb-acc); }
.sb-newbuild input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; margin-top: -6px; border-radius: 50%; background: var(--nb-acc); border: 2px solid var(--bg-panel); box-shadow: 0 1px 3px rgba(0, 0, 0, .4); }
.sb-newbuild input[type=range]::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: var(--nb-acc); border: 2px solid var(--bg-panel); }
.sb-newbuild input[type=range]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 99px; }
.sb-newbuild .nb-tags { display: flex; gap: 24px; flex-wrap: wrap; align-items: flex-end; }
.sb-newbuild .build-role-btn { width: 40px; height: 40px; border-radius: 8px; }
.sb-newbuild .build-role-btn { transition: filter .15s, opacity .15s, box-shadow .15s; }
.sb-newbuild .build-role-btn.active { box-shadow: 0 0 0 2px var(--nb-acc), 0 0 10px color-mix(in srgb, var(--nb-acc) 60%, transparent); }
.sb-newbuild .nb-budget { display: flex; gap: 6px; }
.sb-newbuild .nb-budget button { display: flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 99px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text); font: inherit; font-size: 0.85rem; cursor: pointer; }
.sb-newbuild .nb-budget button i { width: 9px; height: 9px; border-radius: 50%; }
.sb-newbuild .nb-budget button.on { border-color: var(--text); }
.sb-newbuild .nb-opt { display: flex; align-items: center; gap: 8px; color: var(--text-dim); font-size: 0.85rem; cursor: pointer; }
.sb-newbuild .nb-acts { display: flex; justify-content: flex-end; gap: 8px; }
@media (prefers-reduced-motion: reduce) { .sb-newbuild, .sb-newbuild .nb-art { transition: none; } }
@media (max-width: 560px) { .sb-newbuild .nb-classes { grid-template-columns: repeat(6, minmax(0, 1fr)); } }

/* ===== Optimiseur, disposition V1 (2026-10-23, maquette V1 de _maquettes/optimiseur-v4.html) =====
   Filtres en 4 cartes sur 2 colonnes | objets en 2 colonnes + Optimiser dessous | fiche + aptitudes.
   Tout tient sans défiler sur un écran 1080p ; plus petit, c'est la fenêtre entière qui défile (jamais de bas rogné). */
.opt-v1.opt-modal { width: min(1860px, calc(100vw - 32px)); max-height: calc(100vh - 32px); padding: 0; overflow: auto; display: flex; flex-direction: column; }
.opt-v1 > .close-btn { top: 12px; right: 14px; }
.opt-v1 .opt-head { flex-wrap: wrap; justify-content: flex-start; gap: 10px 20px; margin: 0; padding: 12px 56px 12px 18px; border-bottom: 1px solid var(--border); }
.opt-v1 .opt-head h2 { font-size: 1.2rem; color: var(--text); white-space: nowrap; }
.opt-v1 .opt-head .opt-who { margin: 0; }
.opt-v1 .opt-head .opt-lvl { width: 340px; max-width: 100%; }
.opt-v1 .ov-info { margin-left: auto; width: 22px; height: 22px; border-radius: 50%; border: 1px solid var(--border); display: inline-grid; place-items: center; font-size: .75rem; color: var(--text-dim); cursor: help; flex: none; }
.opt-v1 .ov-info:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.opt-v1 .opt-grid { grid-template-columns: 620px minmax(0, 1fr) 380px; gap: 16px; padding: 14px 16px 16px; align-items: start; }

/* filtres */
.opt-v1 .opt-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; padding: 0; background: none; border: none; align-items: start; }
.opt-v1 .ov-colf { display: grid; gap: 12px; align-content: start; min-width: 0; }
.opt-v1 .ov-card { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px 14px; display: grid; gap: 12px; min-width: 0; }
.opt-v1 .ov-card > h4 { margin: 0; font-size: .86rem; color: var(--text); display: flex; align-items: center; gap: 8px; }
.opt-v1 .ov-card > h4::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.opt-v1 .ov-row { display: grid; gap: 6px; min-width: 0; }
.opt-v1 .ov-lab { font-size: .7rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--text-dim); }
.opt-v1 .ov-lab small { font-size: inherit; font-weight: 600; letter-spacing: 0; text-transform: none; }
.opt-v1 .opt-chips { gap: 5px; }
.opt-v1 .opt-chip { padding: 4px 10px; font-size: .8rem; }
.opt-v1 .opt-chip.icon-only { width: 38px; height: 38px; padding: 5px; }
.opt-v1 .opt-slot-chips { gap: 4px; }
.opt-v1 .opt-slot-chips .opt-chip.icon-only { width: 34px; height: 34px; padding: 4px; border-radius: 8px; }
.opt-v1 .opt-slot-chips .opt-chip.icon-only svg, .opt-v1 .opt-slot-chips .opt-chip.icon-only img { width: 20px; height: 20px; }
/* choix unique Libre / Sans secondaire / Élém + Critique : une barre de boutons collés */
.opt-v1 .ov-seg { display: flex; flex-wrap: nowrap; gap: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: var(--bg-panel); }
.opt-v1 .ov-seg .opt-chip { flex: 1; justify-content: center; border: none; border-radius: 0; background: transparent; padding: 6px; white-space: nowrap; }
.opt-v1 .ov-seg .opt-chip + .opt-chip { border-left: 1px solid var(--border); }
.opt-v1 .ov-seg .opt-chip.on { background: color-mix(in srgb, var(--accent) 24%, var(--bg-panel)); color: var(--text); }
.opt-v1 .ov-pills .opt-chip { white-space: nowrap; }
.opt-v1 .opt-slider { gap: 8px; }
.opt-v1 .ov-ends { display: flex; justify-content: space-between; font-size: .7rem; color: var(--text-dim); }
/* champs chiffrés : icône + nom + valeur dans un seul cadre */
.opt-v1 .ov-nf { display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-panel); box-sizing: border-box; min-width: 0; cursor: text; }
.opt-v1 .ov-nf:focus-within { border-color: var(--accent); }
.opt-v1 .ov-nf img { width: 18px; height: 18px; flex: none; }
.opt-v1 .ov-nf em { font-style: normal; font-size: .74rem; color: var(--text-dim); white-space: nowrap; }
.opt-v1 .ov-nf input { flex: 1; min-width: 0; width: 100%; border: none; background: none; box-shadow: none; padding: 0; height: auto; text-align: right; font: inherit; font-size: .9rem; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; outline: none; -moz-appearance: textfield; }
.opt-v1 .ov-nf input::-webkit-outer-spin-button, .opt-v1 .ov-nf input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.opt-v1 .ov-nf input::placeholder { color: var(--text-dim); font-weight: 400; }
.opt-v1 .ov-quad { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
.opt-v1 .ov-pair { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 6px; align-items: center; }
.opt-v1 .ov-pair i { font-style: normal; color: var(--text-dim); }
.opt-v1 .opt-opt { font-size: .8rem; color: var(--text); }
.opt-v1 .opt-keep { justify-self: start; }
.opt-v1 .opt-owned-btns { display: flex; gap: 6px; flex-wrap: wrap; }
.opt-v1 .opt-owned-btns .btn, .opt-v1 .opt-keep { padding: 5px 10px; font-size: .76rem; }
.opt-v1 #opt-owned:empty { display: none; }
/* raretés : pastilles comme les autres choix */
.opt-v1 .opt-rars { gap: 6px; }
.opt-v1 .opt-rar { border: 1px solid var(--border); border-radius: 999px; padding: 2px 12px 2px 3px; gap: 6px; background: var(--bg-panel); font-size: .8rem; }
.opt-v1 .opt-rar .rarity-toggle { width: 26px; height: 26px; border-radius: 50%; border: none; box-shadow: none; background: none; }
.opt-v1 .opt-rar .rarity-toggle .rarity-toggle-base, .opt-v1 .opt-rar .rarity-toggle .rarity-toggle-tint { width: 20px; height: 20px; }
.opt-v1 .opt-rar .rarity-toggle.active { border: none; box-shadow: none; }
.opt-v1 .opt-rar:has(.rarity-toggle.active) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--bg-panel)); }
.opt-v1 #opt-subs .opt-chip { padding: 3px 8px; font-size: .76rem; }
.opt-v1 #opt-subs .opt-chip img { width: 14px; height: 14px; }

/* objets : 2 colonnes, boutons toujours visibles au-dessus, châsses en bas */
.opt-v1 .opt-col { gap: 0; }
.opt-v1 .opt-checks { margin-bottom: 8px; }
.opt-v1 .opt-items { grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 5px; align-content: start; }
.opt-v1 .opt-empty { grid-column: 1 / -1; margin: 0; padding: 18px; border: 1px dashed var(--border); border-radius: 10px; color: var(--text-dim); font-size: .85rem; }
.opt-v1 .opt-item { grid-template-columns: 42px minmax(0, 1fr) auto; grid-template-areas: "ico name btns" "ico meta meta" "runes runes runes"; gap: 2px 10px; padding: 5px 10px; align-items: center; }
.opt-v1 .opt-ico { grid-area: ico; width: 42px; height: 42px; align-self: start; }
.opt-v1 .opt-item-txt { display: contents; }
.opt-v1 .opt-item-txt small { display: none; }
.opt-v1 .opt-item-txt b { grid-area: name; min-width: 0; font-size: .86rem; }
.opt-v1 .opt-item-txt > span { grid-area: meta; min-width: 0; font-size: .74rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.opt-v1 .opt-slotp { font-style: normal; }
.opt-v1 .opt-noprice { color: var(--text-dim); font-weight: 400; }
.opt-v1 .opt-item-btns { grid-area: btns; display: flex; gap: 3px; justify-self: end; }
.opt-v1 .opt-item-btns button { width: 24px; height: 24px; }
.opt-v1 .opt-runes { grid-area: runes; display: flex; flex-direction: row; align-items: center; gap: 10px; min-width: 0; padding-top: 5px; margin-top: 3px; border-top: 1px dashed var(--border); }
.opt-v1 .opt-runes.none { font-size: .7rem; color: var(--text-dim); min-height: 24px; }
.opt-v1 .opt-runes .opt-shards { padding: 0; gap: 6px; flex-wrap: nowrap; }
.opt-v1 .opt-shards .shard-btn, .opt-v1 .opt-shards .shard-wrap { width: 24px; height: 24px; }
.opt-v1 .opt-shards .shard-btn { cursor: help; }
.opt-v1 .opt-shards .shard-btn .shard-stat-icon { width: 12px; height: 12px; bottom: -1px; left: -1px; }
.opt-v1 .opt-shards .shard-x2-badge { transform: scale(.85); transform-origin: top right; }
.opt-v1 .opt-runes-txt { flex: 1; min-width: 0; font-size: .68rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.opt-v1 .ov-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
/* thinner, and clearly the two actions of the window (2026-10-23, user): accent fill + gold fill */
.opt-v1 .ov-actions .btn.opt-go { width: auto; margin: 0; padding: 6px 24px; font-size: .92rem; font-weight: 800; line-height: 1.3; background: var(--accent); color: var(--accent-text); border: 1px solid var(--accent); box-shadow: 0 2px 10px color-mix(in srgb, var(--accent) 35%, transparent); }
.opt-v1 .ov-actions .btn.opt-go:hover:not(:disabled) { background: color-mix(in srgb, var(--accent) 85%, #fff); }
.opt-v1 .ov-actions .btn.opt-apply { margin: 0; padding: 6px 16px; font-size: .86rem; font-weight: 800; line-height: 1.3; background: #e8c34f; color: #241a03; border: 1px solid #e8c34f; }
.opt-v1 .ov-actions .btn.opt-apply:hover:not(:disabled) { background: #f0d06a; }
.opt-v1 .ov-actions .btn.opt-apply:disabled { background: transparent; color: var(--text-dim); border-color: var(--border); opacity: 1; }
.opt-v1 .ov-actions .opt-status { margin: 0 0 0 auto; font-size: .8rem; color: var(--text-dim); text-align: right; }
.opt-v1 #opt-excl-wrap { margin-top: 12px; display: grid; gap: 6px; }
.opt-v1 #opt-excl-wrap[hidden] { display: none; }

/* fiche + aptitudes, sans défilement propre */
.opt-v1 .ov-right { display: grid; gap: 12px; align-content: start; min-width: 0; }
.opt-v1 .opt-fiche { position: static; }
.opt-v1 #opt-apt-wrap { display: grid; gap: 6px; }
.opt-v1 .ov-right h3, .opt-v1 #opt-excl-wrap h3 { margin: 0; font-size: 0.7rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); }
.opt-v1 #opt-apt-wrap[hidden] { display: none; }
.opt-v1 .opt-apt { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; margin: 0; }
.opt-v1 .opt-apt-cat { padding: 6px 8px; font-size: .7rem; line-height: 1.35; }
.opt-v1 .opt-apt-cat h4 { font-size: .72rem; margin-bottom: 2px; }
.opt-v1 .opt-apt-cat div span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.opt-v1 .opt-apt-cat:last-child:nth-child(odd) { grid-column: 1 / -1; }

/* écrans plus étroits : la fiche passe sous les objets, puis tout en une colonne */
@media (max-width: 1640px) {
  .opt-v1 .opt-grid { grid-template-columns: 580px minmax(0, 1fr); }
  .opt-v1 .ov-right { grid-column: 1 / -1; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
}
@media (max-width: 1150px) {
  .opt-v1 .opt-grid { grid-template-columns: minmax(0, 1fr); }
  .opt-v1 .ov-right { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 700px) {
  .opt-v1.opt-modal { width: 100vw; max-height: 100vh; border-radius: 0; }
  .opt-v1 .opt-form { grid-template-columns: minmax(0, 1fr); }
  .opt-v1 .opt-head .opt-lvl { width: 100%; }
  .opt-v1 .opt-items { grid-template-columns: minmax(0, 1fr); }
  .opt-v1 .ov-actions .opt-status { margin-left: 0; text-align: left; }
}

/* ===== Recherche d'objet depuis l'optimiseur (2026-10-23) : même disposition que la recherche du builder ===== */
.opt-pick-overlay { z-index: 320; }
.opt-pick.modal { width: min(1500px, calc(100vw - 32px)); max-width: none; height: calc(100vh - 48px); max-height: none; padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.opt-pick-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 14px 18px 12px; border-bottom: 1px solid var(--border); flex: none; }
.opt-pick-head h2 { margin: 0; font-size: 1.15rem; color: var(--text); }
.opt-pick-head p { margin: 3px 0 0; font-size: .82rem; color: var(--text-dim); }
.opt-pick-head .close-btn { position: static; flex: none; }
.opt-pick-body { flex: 1; min-height: 0; padding: 14px 16px 16px; grid-template-rows: minmax(0, 1fr); align-items: stretch; }
/* a bit wider than the DB view's 260px so the 6 secondary-mastery toggles fit on one line */
.opt-pick-body.db-layout.db-side-layout { grid-template-columns: 310px minmax(0, 1fr); }
.opt-pick-filters { position: static; max-height: none; height: 100%; overflow-y: auto; }
.opt-pick-filters .filters { position: static; max-height: none; overflow: visible; }
.opt-pick-grid { min-height: 0; height: 100%; overflow-y: auto; overscroll-behavior: contain; padding-right: 4px; }
@media (max-width: 860px) {
  .opt-pick.modal { width: 100vw; height: 100vh; border-radius: 0; }
  .opt-pick-body { grid-template-rows: auto minmax(0, 1fr); overflow-y: auto; }
  .opt-pick-filters { height: auto; }
  .opt-pick-grid { height: auto; overflow: visible; }
}

/* ===== Deck de sorts (2026-10-23, user) : cases vides « silhouette » (maquette E3), sans pastilles ===== */
/* No FightLab status dots on the deck (they stay in FightLab itself). */
#spelldeck-panel .spell-deck-left .deck-slot-status { display: none; }
/* Empty slot: a faint picto of what goes there (spell / passive) instead of "+"; it lights up on hover. */
#spelldeck-panel .spell-deck-grid.actifs, #spelldeck-panel .spell-deck-grid.passifs { --deck-empty-ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M13 2 4 14h7l-1 8 9-12h-7z'/%3E%3C/svg%3E"); }
#spelldeck-panel .spell-deck-grid.passifs { --deck-empty-ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l2.9 6.3 6.9.7-5.2 4.6 1.5 6.8L12 17l-6.1 3.4 1.5-6.8L2.2 9l6.9-.7z'/%3E%3C/svg%3E"); }
#spelldeck-panel .spell-deck-grid.actifs .deck-slot:not(.filled), #spelldeck-panel .spell-deck-grid.passifs .deck-slot:not(.filled) {
  border: 1px dashed color-mix(in srgb, var(--border) 90%, var(--text-dim)); background: color-mix(in srgb, var(--bg) 55%, var(--bg-elevated));
}
#spelldeck-panel .spell-deck-grid .deck-slot:not(.filled) .deck-slot-empty { display: none; }
#spelldeck-panel .spell-deck-grid.actifs .deck-slot:not(.filled)::after, #spelldeck-panel .spell-deck-grid.passifs .deck-slot:not(.filled)::after {
  content: ""; width: 46%; height: 46%; opacity: .22; background: var(--text-dim); transition: opacity .12s, background-color .12s;
  -webkit-mask: var(--deck-empty-ico) center / contain no-repeat; mask: var(--deck-empty-ico) center / contain no-repeat;
}
#spelldeck-panel .spell-deck-grid .deck-slot:not(.filled):hover { border-style: solid; border-color: var(--accent); }
#spelldeck-panel .spell-deck-grid .deck-slot:not(.filled):hover::after { opacity: .7; background: var(--accent); }
#spelldeck-panel .spell-deck-grid .deck-slot.drag-over::after { opacity: .7; background: var(--accent); }
/* Library: room around the tiles so the ring of a picked / hovered spell is not cut by the scroll box. */
#spelldeck-panel .spell-deck-library { padding: 4px 6px 4px 4px; }
/* Tooltips never scroll (pointer-events: none): long ones widen instead (js/ui.js fitTooltip). */
.item-tooltip:not(.fightlab-tooltip) { overflow-y: hidden; }
.item-tooltip.tt-wide { width: 380px; }
.item-tooltip.tt-wider { width: 500px; }
.item-tooltip.tt-cols { width: min(860px, calc(100vw - 24px)); }
.item-tooltip.tt-cols > .stt { display: block; column-count: 2; column-gap: 20px; }
.item-tooltip.tt-cols > .stt > * + * { margin-top: 6px; }
.item-tooltip.tt-cols > .stt > .stt-head { column-span: all; }
.item-tooltip.tt-cols .stt-state-block { break-inside: avoid; }
.item-tooltip.tt-small { font-size: .9em; }

/* ===== Outils › Stratégies (2026-10-23, js/strategies.js) ===== */
.st-page { min-height: 60vh; }
.st-empty { padding: 40px 0; text-align: center; }
.st-muted { color: var(--text-dim); font-size: .8rem; }
.st-top { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
.st-top .st-q { flex: 1; max-width: 420px; background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 7px 11px; font: inherit; }
.st-top .st-q:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.st-top .st-src { margin-left: auto; color: var(--text-dim); font-size: .74rem; }
.st-menu { display: none; }
.st-wrap { display: grid; grid-template-columns: 290px minmax(0, 1fr); gap: 18px; align-items: start; }
.st-nav { position: sticky; top: 70px; max-height: calc(100vh - 84px); overflow-y: auto; overscroll-behavior: contain; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px; padding: 6px 0 12px; }
.st-nav details { border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
.st-nav summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: 8px; padding: 8px 14px; font-weight: 700; font-size: .8rem; letter-spacing: .03em; color: var(--text-dim); }
.st-nav summary::-webkit-details-marker { display: none; }
.st-nav summary::before { content: "▸"; font-size: .7rem; transition: transform .15s; }
.st-nav details[open] summary::before { transform: rotate(90deg); }
.st-nav summary .n { margin-left: auto; font-weight: 600; font-size: .72rem; background: var(--bg-elevated); border-radius: 99px; padding: 0 7px; }
.st-nav button.st-d { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none; border: none; color: var(--text); font: inherit; font-size: .84rem; padding: 5px 14px 5px 22px; cursor: pointer; }
.st-nav button.st-d img { width: 26px; height: 26px; object-fit: contain; flex: none; }
.st-nav button.st-d small { display: block; color: var(--text-dim); font-size: .72rem; line-height: 1.2; }
.st-nav button.st-d:hover { background: var(--bg-elevated); }
.st-nav button.st-d.on { background: color-mix(in srgb, var(--accent) 18%, transparent); box-shadow: inset 3px 0 0 var(--accent); }
.st-guide { margin: 10px 8px 0; }
.st-main { min-width: 0; }
.st-head { display: flex; align-items: flex-start; gap: 18px; flex-wrap: wrap; }
.st-bossimg { width: 92px; height: 92px; border-radius: 14px; background: var(--bg-elevated) center / contain no-repeat; border: 1px solid var(--border); flex: none; }
.st-head h2 { margin: 0; font-size: 1.55rem; line-height: 1.2; text-wrap: balance; }
.st-sub { color: var(--text-dim); margin-top: 4px; }
.st-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.st-chip { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 99px; padding: 2px 10px; font-size: .78rem; }
.st-chip.lv { background: color-mix(in srgb, var(--accent) 22%, var(--bg-elevated)); border-color: var(--accent); font-weight: 700; }
.st-video { display: inline-flex; align-items: center; gap: 6px; background: #c4302b; color: #fff; text-decoration: none; border-radius: 8px; padding: 6px 12px; font-weight: 700; font-size: .82rem; margin-left: auto; align-self: center; }
.st-video:hover { filter: brightness(1.1); }
.st-h3 { font-size: .74rem; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); margin: 24px 0 10px; }
.st-grid { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 16px; align-items: start; }
.st-side { display: grid; gap: 14px; }
.st-card { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; }
.st-rich { font-size: .9rem; line-height: 1.55; }
.st-rich > div + div { margin-top: 10px; }
.st-rich img { display: inline-block; vertical-align: middle; max-height: 260px; max-width: 100%; border-radius: 6px; }
.st-mref { white-space: nowrap; }
.st-mref img { width: 22px; height: 22px; object-fit: contain; vertical-align: -6px; margin-right: 3px; border-radius: 4px; background: color-mix(in srgb, var(--bg-elevated) 70%, transparent); }
.st-mons { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 8px; }
.st-mon { display: grid; grid-template-columns: 48px minmax(0, 1fr); gap: 2px 10px; align-items: center; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px; cursor: pointer; text-align: left; color: inherit; font: inherit; }
.st-mon:hover, .st-mon.on { border-color: var(--accent); }
.st-mon.on { background: color-mix(in srgb, var(--accent) 12%, var(--bg-panel)); }
.st-mon.boss { border-color: color-mix(in srgb, #d4af37 70%, var(--border)); }
.st-mon > img { grid-row: 1 / span 2; width: 48px; height: 48px; object-fit: contain; }
.st-mon b { font-size: .86rem; line-height: 1.2; }
.st-mon small { color: var(--text-dim); font-size: .74rem; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.st-mon small img { width: 14px; height: 14px; }
.st-tag { font-size: .66rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: #d4af37; }
.st-sheet { margin-top: 10px; }
.st-stats { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: .82rem; margin-bottom: 10px; color: var(--text-dim); }
.st-stats b { color: var(--text); }
.st-stats img { width: 16px; height: 16px; vertical-align: -3px; margin-right: 3px; }
.st-spell { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 7px 0; border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
.st-spell:first-of-type { border-top: none; }
.st-ic { width: 34px; height: 34px; border-radius: 6px; background: var(--bg-elevated); overflow: hidden; }
.st-ic img { width: 100%; height: 100%; }
.st-spell b { font-size: .86rem; }
.st-spell small { display: block; color: var(--text-dim); font-size: .74rem; }
.st-spell small.fx { color: color-mix(in srgb, var(--text) 75%, var(--text-dim)); }
.st-dmg { display: grid; gap: 2px; justify-items: end; font-size: .82rem; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.st-dmg img { width: 15px; height: 15px; vertical-align: -3px; margin-right: 3px; }
.st-vtabs { display: flex; gap: 6px; margin: 0 0 10px; }
.st-vtabs button { background: var(--bg-elevated); color: var(--text-dim); border: 1px solid var(--border); border-radius: 7px; padding: 4px 12px; font: inherit; font-size: .8rem; cursor: pointer; }
.st-vtabs button.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 700; }
.st-guide-row { display: grid; grid-template-columns: 160px minmax(0, 1fr); gap: 14px; padding: 10px 0; border-top: 1px solid var(--border); }
.st-guide-row:first-child { border-top: none; }
.st-guide-row > div:first-child { font-weight: 700; }
@media (max-width: 1150px) { .st-grid { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 800px) {
  .st-wrap { grid-template-columns: minmax(0, 1fr); }
  .st-menu { display: inline-flex; }
  .st-nav { display: none; position: static; max-height: 60vh; }
  .st-nav.open { display: block; }
  .st-top .st-src { display: none; }
  .st-guide-row { grid-template-columns: minmax(0, 1fr); }
}
@media (prefers-reduced-motion: reduce) { .st-nav summary::before { transition: none; } }

/* Private tools: the menu entry stays hidden for the other accounts (the menu buttons are display:flex, which beat [hidden]). */
[data-private-tool][hidden] { display: none !important; }

/* ===== Prix et kamas : outil privé (2026-10-23). Sans l'accès, la table de prix est vide (js/app.js) et tout ce qui
   parle de kamas est masqué. ===== */
body.no-prices .item-card-badge.price,
body.no-prices .build-cost-group,
body.no-prices .recipe-cost,
body.no-prices .subs-foot,
body.no-prices .shop-recipe-cost,
body.no-prices .shoppinglist-course-price,
body.no-prices .shoppinglist-course-total,
body.no-prices .shop-tile.gold,
body.no-prices #shoppinglist-prices-date,
body.no-prices .gs-tiles .gold,
body.no-prices .opt-check.gold,
body.no-prices .opt-item-txt .price,
body.no-prices .opt-v1 .ov-row:has(.ov-budget),
body.no-prices .opt-v1 .ov-row:has(#opt-own-add),
body.no-prices .opt-item-btns [data-own] { display: none !important; }

/* Accueil : « Reprendre <build> » en vrai bouton (2026-10-23) */
.home-stat:has(.home-resume-btn) { display: contents; }
.home-resume-btn { display: inline-flex; align-items: center; gap: 10px; max-width: 100%; padding: 5px 14px 5px 5px; border-radius: 10px; border: 1px solid color-mix(in srgb, #d4af37 55%, var(--border)); background: color-mix(in srgb, #d4af37 10%, var(--bg-elevated)); color: var(--text); font: inherit; cursor: pointer; text-align: left; transition: border-color .15s, background-color .15s; }
.home-resume-btn:hover { border-color: #d4af37; background: color-mix(in srgb, #d4af37 18%, var(--bg-elevated)); }
.home-resume-btn:focus-visible { outline: 2px solid #d4af37; outline-offset: 2px; }
.home-resume-pt { width: 34px; height: 34px; border-radius: 7px; flex: none; background-color: var(--bg-panel); background-repeat: no-repeat; }
.home-resume-txt { display: grid; min-width: 0; line-height: 1.2; }
.home-resume-txt small { font-size: .7rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: .05em; }
.home-stat .home-resume-txt b, .home-resume-txt b { color: var(--text); font-size: .95rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px; }
.home-resume-lv { font-size: .78rem; font-weight: 700; color: #d4af37; white-space: nowrap; }

.settings-slide[hidden] { display: none !important; }

/* Nouveau build : styles de jeu (maquette B, 2026-10-23) */
.sb-newbuild { width: min(720px, calc(100% - 24px)); }
.sb-newbuild .nb-style { display: grid; gap: 6px; }
.sb-newbuild .nb-style-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.sb-newbuild .nb-style-head .nb-l { margin: 0; }
.sb-newbuild .nb-style-head .nb-opt { font-size: .8rem; }
.sb-newbuild .nb-styles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.sb-newbuild .nb-style-card { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 2px 8px; align-items: center; padding: 8px 10px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text); font: inherit; text-align: left; cursor: pointer; }
.sb-newbuild .nb-style-card img { grid-row: span 2; width: 26px; height: 26px; }
.sb-newbuild .nb-style-card b { font-size: .86rem; }
.sb-newbuild .nb-style-card small { font-size: .72rem; color: var(--text-dim); }
.sb-newbuild .nb-style-card:hover { border-color: color-mix(in srgb, var(--nb-acc) 60%, var(--border)); }
.sb-newbuild .nb-style-card.on { border-color: var(--nb-acc); box-shadow: 0 0 0 1px var(--nb-acc); }
.sb-newbuild .nb-style-card.none img { opacity: .5; }
.sb-newbuild .nb-role-row[hidden] { display: none; }
@media (max-width: 600px) { .sb-newbuild .nb-styles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Deck de sorts : pictos aux coins arrondis, comme leur case (2026-10-23, user) */
/* no overflow:hidden: the × and ✓ badges sit over the corners */
#spelldeck-panel .deck-slot.filled, #spelldeck-panel .lib-spell-tile { border-radius: 10px; }
/* The icon files have their own light rim with tight corners: zoomed a little and clipped, so the visible edge
   is OUR rounded corner, the same 10px as the empty slots (clip-path scales with the transform: 10 / 1.12). */
#spelldeck-panel .deck-slot.filled img, #spelldeck-panel .lib-spell-tile img { border-radius: 0; transform: scale(1.12); clip-path: inset(5% round 8.9px); }
/* the picto IS the button: no frame of the slot around it, its own corners give the shape */
#spelldeck-panel .deck-slot.filled { border-color: transparent; background: transparent; }
#spelldeck-panel .deck-slot.filled:hover { border-color: transparent; }
#spelldeck-panel .deck-slot.filled:hover { box-shadow: 0 0 0 2px var(--accent); }
#spelldeck-panel .deck-slot:not(.filled) { border-radius: 10px; }
/* thin light frame over the picto, rounded inside and out like the game's own icon rim (2026-10-23, user) */
#spelldeck-panel .deck-slot.filled::after, #spelldeck-panel .lib-spell-tile::after { content: ""; position: absolute; inset: 0; border-radius: 10px; border: 1.5px solid rgba(255, 255, 255, .78); pointer-events: none; z-index: 1; }
#spelldeck-panel .lib-spell-tile { position: relative; }
#spelldeck-panel .lib-spell-tile.selected::after { border-color: rgba(255, 255, 255, .35); }
#spelldeck-panel .deck-slot-remove, #spelldeck-panel .lib-spell-check { z-index: 2; }
/* same window height whatever the class (up to 9 styles = 3 rows), so nothing moves when the class changes */
.sb-newbuild .nb-styles { grid-auto-rows: 52px; align-content: start; min-height: calc(3 * 52px + 2 * 8px); }
.sb-newbuild .nb-style-card { height: 52px; box-sizing: border-box; overflow: hidden; }
.sb-newbuild .nb-style-card small { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 600px) { .sb-newbuild .nb-styles { min-height: calc(4 * 52px + 3 * 8px); } }

/* No accidental image drag (2026-10-23, user): pictures are never dragged on their own; a drag still starts from a
   draggable element around them (deck slots, spell library). */
img { -webkit-user-drag: none; user-drag: none; }
/* "Remplir avec l'optimiseur": a pill switch in the class colour, lit when a style of play is picked */
.sb-newbuild .nb-opt-toggle { position: relative; padding: 4px 12px 4px 6px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-elevated); transition: border-color .15s, background-color .15s; }
.sb-newbuild .nb-opt-toggle .slide-toggle-label { display: inline-flex; align-items: center; gap: 6px; }
.sb-newbuild .nb-opt-toggle:has(input:checked) { border-color: var(--nb-acc); background: color-mix(in srgb, var(--nb-acc) 16%, var(--bg-elevated)); }
.sb-newbuild .nb-opt-toggle input:checked + .slide-toggle-track { background: color-mix(in srgb, var(--nb-acc) 30%, var(--bg-elevated)); border-color: var(--nb-acc); }
.sb-newbuild .nb-opt-toggle input:checked + .slide-toggle-track .slide-toggle-thumb { background: var(--nb-acc); }
.sb-newbuild .nb-opt-toggle input:checked ~ .slide-toggle-label svg { color: var(--nb-acc); }

/* Optimiseur : picto (barres + étincelle au-dessus de la 3e barre) animé au survol (2026-10-23, user) — les barres
   montent l'une après l'autre et l'étincelle scintille en même temps. Bouton de la poupée et interrupteur de la
   fenêtre « Nouveau build » (là, seulement quand on l'allume). */
.opt-ico { overflow: visible; }
.opt-ico .opt-ico-bar { transform-box: fill-box; transform-origin: 50% 100%; }
.opt-ico .opt-ico-star { transform-box: fill-box; transform-origin: 50% 50%; }
.slot-optim-btn:hover .opt-ico-bar, .nb-opt-toggle input:checked ~ .slide-toggle-label .opt-ico-bar { animation: opt-bar .45s cubic-bezier(.2, .8, .3, 1) both; }
.opt-ico .opt-ico-bar.b2 { animation-delay: .05s !important; }
.opt-ico .opt-ico-bar.b3 { animation-delay: .1s !important; }
.opt-ico .opt-ico-bar.b4 { animation-delay: .15s !important; }
.slot-optim-btn:hover .opt-ico-star, .nb-opt-toggle input:checked ~ .slide-toggle-label .opt-ico-star { animation: opt-twinkle .45s cubic-bezier(.2, .8, .3, 1) both; }
@keyframes opt-bar { 0% { transform: scaleY(.35); } 60% { transform: scaleY(1.15); } 100% { transform: scaleY(1); } }
@keyframes opt-twinkle { 0% { transform: scale(1) rotate(0); } 40% { transform: scale(1.5) rotate(45deg); } 100% { transform: scale(1) rotate(90deg); } }
@media (prefers-reduced-motion: reduce) { .opt-ico .opt-ico-bar, .opt-ico .opt-ico-star { animation: none !important; } }
.sb-newbuild .nb-opt-toggle .opt-ico { width: 13px; height: 13px; flex: none; }
/* same weight on and off, so the switch keeps its width when clicked (2026-10-23, user) */
.sb-newbuild .nb-opt-toggle .slide-toggle-label, .sb-newbuild .nb-opt-toggle input:checked ~ .slide-toggle-label { font-weight: 600; }

/* optimiseur : élément sans sort pour la classe (2026-10-23) */
.opt-chip.na { opacity: .3; cursor: not-allowed; filter: grayscale(1); }
/* taille de l'interface (2026-10-24) */
.ui-scale-row { display: flex; align-items: center; gap: 10px; }
.ui-scale-row input[type=range] { flex: 1; accent-color: var(--accent); }
.calc-field label b { color: var(--accent); margin-left: 6px; }
/* Provenance filter: one line, same 32px buttons as the rarity row (2026-10-25, user) */
.icon-toggle-group.source-group, .shop-side-filters .icon-toggle-group.source-group { flex-wrap: nowrap !important; gap: 4px; }
.source-toggle { flex: 1 1 0; width: auto; min-width: 0; max-width: 32px; height: auto; aspect-ratio: 1; }
.source-toggle img { width: 76%; height: 76%; }
.source-toggle svg { width: 64%; height: 64%; }
.source-toggle svg { color: var(--text-dim); }
.source-toggle.active svg { color: var(--accent); }

/* ---- Combo Maker, layout "Timeline" (2026-10-25, user: maquette 1, site theme) ---- */
#combomaker-panel { --cm-feu: #f07a3a; --cm-eau: #3fa9e8; --cm-terre: #8fbf4a; --cm-air: #b48ae6; }
.cm-layout { display: grid; grid-template-columns: minmax(0, 360px) minmax(0, 1fr); gap: 16px; align-items: start; margin-top: 4px; }
.cm-lib-col, .cm-main { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 10px; padding: 12px; min-width: 0; }
.cm-search { width: 100%; box-sizing: border-box; margin-bottom: 8px; }
.cm-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; }
.cm-tabs button, .cm-seg button, .cm-chip { background: var(--bg-panel); color: var(--text-dim); border: 1px solid var(--border); border-radius: 7px; padding: 4px 10px; font-size: 0.78rem; font-weight: 600; cursor: pointer; }
.cm-tabs button:hover, .cm-seg button:hover, .cm-chip:hover { color: var(--text); border-color: var(--accent); }
.cm-tabs button.on, .cm-seg button.on, .cm-chip.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.cm-tabs button.el-feu:not(.on) { color: var(--cm-feu); } .cm-tabs button.el-eau:not(.on) { color: var(--cm-eau); }
.cm-tabs button.el-terre:not(.on) { color: var(--cm-terre); } .cm-tabs button.el-air:not(.on) { color: var(--cm-air); }
.cm-lib { max-width: none; }
.cm-lib .deck-slot { position: relative; }
.cm-cost { position: absolute; right: -4px; bottom: -4px; min-width: 15px; padding: 0 3px; border-radius: 5px; background: var(--bg-panel); border: 1px solid var(--border); color: var(--text); font-size: 0.62rem; font-weight: 700; line-height: 14px; text-align: center; pointer-events: none; }
.cm-topbar { display: flex; flex-wrap: wrap; gap: 12px 18px; align-items: flex-start; justify-content: space-between; }
.cm-gauges { display: flex; flex-wrap: wrap; gap: 10px 18px; }
.cm-gauge-head { display: flex; align-items: center; gap: 4px; font-size: 0.78rem; color: var(--text-dim); margin-bottom: 4px; }
.cm-gauge-head b { color: var(--text); font-size: 0.86rem; } .cm-gauge-head b.neg { color: var(--danger); }
.cm-squares { display: flex; flex-wrap: wrap; gap: 2px; max-width: 230px; }
.cm-squares i { width: 11px; height: 11px; border-radius: 3px; background: var(--bg-panel); border: 1px solid var(--border); box-sizing: border-box; }
.cm-gauge.pa i.f { background: #4aa3ff; border-color: #4aa3ff; } .cm-gauge.pm i.f { background: #6cc24a; border-color: #6cc24a; } .cm-gauge.pw i.f { background: #2fbfa5; border-color: #2fbfa5; }
.cm-squares i.over { background: var(--danger); border-color: var(--danger); }
.cm-settings { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: flex-end; }
.cm-seg { display: inline-flex; gap: 2px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 8px; padding: 2px; }
.cm-seg button { border: none; background: none; }
.cm-more { position: relative; } .cm-more > summary { list-style: none; } .cm-more > summary::-webkit-details-marker { display: none; }
.cm-more[open] > summary { color: var(--text); border-color: var(--accent); }
.cm-more-body { position: absolute; right: 0; top: calc(100% + 6px); z-index: 20; width: 240px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 10px; padding: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); }
.cm-timeline { display: flex; gap: 22px; overflow-x: auto; padding: 14px 2px 8px; margin-top: 4px; }
.cm-card { position: relative; flex: 0 0 132px; min-height: 176px; box-sizing: border-box; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 10px; padding: 10px 8px 8px; display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: grab; }
.cm-card:not(:last-child)::after { content: "›"; position: absolute; right: -16px; top: 50%; transform: translateY(-50%); color: var(--text-dim); font-size: 1.1rem; }
.cm-card.el-feu { border-top: 3px solid var(--cm-feu); } .cm-card.el-eau { border-top: 3px solid var(--cm-eau); }
.cm-card.el-terre { border-top: 3px solid var(--cm-terre); } .cm-card.el-air { border-top: 3px solid var(--cm-air); }
.cm-card.over-cap { border-color: var(--danger); }
.cm-card.dragging { opacity: 0.45; } .cm-card.drop { box-shadow: 0 0 0 2px var(--accent); }
.cm-card-index { position: absolute; top: 4px; left: 7px; font-size: 0.62rem; color: var(--text-dim); }
.cm-card .remove-x { position: absolute; top: 4px; right: 4px; padding: 0; cursor: pointer; }
.cm-card-icon { width: 44px; height: 44px; border-radius: 9px; overflow: hidden; background: var(--bg-elevated); flex-shrink: 0; }
.cm-card-icon img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.12); }
.cm-card-name { font-size: 0.74rem; font-weight: 700; text-align: center; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cm-card-dmg { font-size: 1.25rem; font-weight: 800; color: var(--accent-2); line-height: 1.1; }
.cm-card.el-feu .cm-card-dmg { color: var(--cm-feu); } .cm-card.el-eau .cm-card-dmg { color: var(--cm-eau); }
.cm-card.el-terre .cm-card-dmg { color: var(--cm-terre); } .cm-card.el-air .cm-card-dmg { color: var(--cm-air); }
.cm-card-parts { font-size: 0.66rem; color: var(--text-dim); }
.cm-share { width: 100%; height: 5px; border-radius: 3px; background: var(--bg-elevated); overflow: hidden; margin-top: auto; }
.cm-share b { display: block; height: 100%; background: var(--accent); }
.cm-card.el-feu .cm-share b { background: var(--cm-feu); } .cm-card.el-eau .cm-share b { background: var(--cm-eau); }
.cm-card.el-terre .cm-share b { background: var(--cm-terre); } .cm-card.el-air .cm-share b { background: var(--cm-air); }
.cm-procs { display: flex; flex-wrap: wrap; gap: 3px; justify-content: center; }
.cm-add { border-style: dashed; justify-content: center; color: var(--text-dim); font-size: 1.6rem; cursor: default; }
.cm-add small { font-size: 0.66rem; text-align: center; }
.cm-bottom { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-top: 8px; flex-wrap: wrap; }
.cm-split { flex: 1 1 280px; max-width: 520px; }
.cm-stack { display: flex; height: 10px; border-radius: 6px; overflow: hidden; background: var(--bg-panel); }
.cm-stack b.el-feu { background: var(--cm-feu); } .cm-stack b.el-eau { background: var(--cm-eau); } .cm-stack b.el-terre { background: var(--cm-terre); } .cm-stack b.el-air { background: var(--cm-air); }
.cm-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; font-size: 0.76rem; }
.cm-legend span { display: inline-flex; align-items: center; gap: 4px; }
.cm-legend .el-feu { color: var(--cm-feu); } .cm-legend .el-eau { color: var(--cm-eau); } .cm-legend .el-terre { color: var(--cm-terre); } .cm-legend .el-air { color: var(--cm-air); }
.cm-total { text-align: right; } .cm-total .num { font-size: 2.2rem; font-weight: 800; color: var(--accent-2); line-height: 1; } .cm-total .lbl { font-size: 0.76rem; color: var(--text-dim); }
@media (max-width: 1100px) { .cm-layout { grid-template-columns: minmax(0, 1fr); } }
