/* css/app.css — Hollow: layout and components.
   It follows the mockup's language: a 1216 px column, labels in Cinzel with generous
   letter-spacing, text in Spectral, 1 px borders with no radii and corner brackets on
   the panels. All colour comes from css/tokens.css. */

/* ── Base ────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-md);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
.page { min-height: 100vh; background: var(--page-bg); padding-bottom: var(--sp-16); transition: opacity var(--dur-slow) var(--ease-fade); }
/* Clearing the save you're in reloads the page (js/app-saves.js): it fades to black before, and the
   new one starts black and comes in once it's in place, as the game's loading screens do. */
html.is-leaving .page, html.is-landing .page { opacity: 0; }
html.is-leaving .page { transition-duration: var(--dur); }
.shell {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 var(--page-pad);
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
}
h1, h2, h3, h4, p, ol, ul { margin: 0; }
ol, ul { padding: 0; list-style: none; }
img { display: block; max-width: 100%; }
/* Hidden things stay hidden even if their class sets display (the screens, the notice, the mini-bar). */
[hidden] { display: none !important; }
button { font-family: inherit; color: inherit; cursor: pointer; border: 0; background: none; }
button:disabled { cursor: default; opacity: 0.45; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A screen's title takes the focus on opening (for screen readers, tabindex="-1"): it isn't a
   control, so it shows no ring. */
:is(h1, h2, h3)[tabindex="-1"]:focus-visible { outline: none; }
/* The screen bar, stuck at the top, mustn't cover what has just been focused. */
html { scroll-padding-block-start: calc(var(--sticky-h) + var(--sp-3)); }

/* ── Figures ───────────────────────────────────────────────────────────
   Every number goes in the sans with tabular figures. Two reasons: the sheet's
   columns can be scanned top to bottom without the digits dancing, and the
   units are lowercase again —Cinzel has no real lowercase, it replaces it with
   small caps, which is why "0.891 s" read as "0.891 ꜱ"—. */
.hero-num, .metric .val, .plate-val, .rd .v, .stat-value, .nailpick-num, .gplate-val, .step-num,
.minihud b, .minihud em, .delta, .chip,
.stat-detail .v, .insp-list .vals, .notch-free {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  letter-spacing: 0;
}
/* The unit goes with the number; it doesn't weigh the same. They're in <i>, so they
   have to be set upright again. */
.u { font-size: 0.62em; font-weight: 400; font-style: normal; color: var(--muted); }
/* Next to a 34 or 88 px figure, a unit at 0.62em is one more number; at 0.4em and in dim
   grey, the healing's "s" couldn't even be seen. At half height and in the secondary ink it
   reads at first glance without competing with the figure. */
.hero-num .u, .metric .val .u, .rd .v .u, .step-num .u { font-size: 0.5em; font-weight: 500; color: var(--ink-2); margin-left: 0.08em; }
.plate-val .u { font-size: 0.6em; font-weight: 500; color: var(--ink-2); margin-left: 0.08em; }
/* The "×" of a multiplier (reach, "×1"): it goes in front and is part of the value, not a unit
   that can be read in passing. Larger and in the figure's ink, slightly lighter, so that
   "×1.15" reads as "1.15 times" at first sight. */
.u.u-mult, .hero-num .u.u-mult, .metric .val .u.u-mult, .rd .v .u.u-mult { font-size: 0.7em; font-weight: 600; color: var(--ink-2); margin-right: 0.04em; }
/* A value that doesn't apply yet is a dash, not a huge figure. */
.na { font-size: 0.7em; font-style: normal; color: var(--muted); }

/* ── Labels: two levels and no more ────────────────────────────────────
   Level 1, Cinzel, for section headers. Level 2, the sans, for field labels:
   below 12 px the wide spacing of a Roman capital stops reading as a word and
   starts reading as separate letters. */
.sheet-title h2 {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--ink-2);
  font-weight: 600;
}
.lbl, .block-head, .group h3,
.stat-detail h4, .charm-detail h3, .banner-tag {
  font-family: var(--font-num);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  font-size: var(--fs-2xs);
  font-weight: 600;
  color: var(--label);
}
.block-head { margin: 0 0 var(--sp-3); }

/* Corner brackets. */
.bk { position: absolute; width: 16px; height: 16px; pointer-events: none; }
.bk.tl { top: -1px; left: -1px; border-top: 2px solid var(--bracket); border-left: 2px solid var(--bracket); }
.bk.tr { top: -1px; right: -1px; border-top: 2px solid var(--bracket); border-right: 2px solid var(--bracket); }
.bk.bl { bottom: -1px; left: -1px; border-bottom: 2px solid var(--bracket); border-left: 2px solid var(--bracket); }
.bk.br { bottom: -1px; right: -1px; border-bottom: 2px solid var(--bracket); border-right: 2px solid var(--bracket); }

/* ── Tinted sections ─────────────────────────────────────────────────────
   Each section carries the pale tint of its area on the game's map (tokens.css): the sheet
   and Your game, City of Tears'; combat, Crystal Peak's; the Hall and the
   Pantheons, Godhome's; the Journal, Fungal Wastes'. The title goes in the tint; the corner
   brackets, the rule under the title and the lights (the spotlight, the plates' halo) in its lamp,
   below. The figures and what responds to the finger don't change. */
.panel, .gear { --tint: var(--tint-sheet); }
.fight { --tint: var(--tint-combat); }
.gear.pg, .gear.home { --tint: var(--tint-progress); }
.fight.is-godhome { --tint: var(--tint-godhome); }
/* The page's chrome (the screen bar's rule, what sits outside a section) takes the tint of the
   screen in view. */
.page { --tint: var(--tint-sheet); }
.page:has(.fight:not([hidden])) { --tint: var(--tint-combat); }
.page:has(.fight.is-godhome:not([hidden])) { --tint: var(--tint-godhome); }
.page:has(.hj-sec:not([hidden])) { --tint: var(--tint-journal); }
.page:has(.pg:not([hidden])), .page:has(.home:not([hidden])) { --tint: var(--tint-progress); }
/* The pale tints are all close to bone: on the lights they're carried by the section's lamp, the
   tint's hue with a little more chroma (tokens.css). Declared again on every element that sets
   --tint, because a custom property is resolved where it's declared: the lamp, the brackets, the
   spotlight and the plates' halo follow that element's tint. Without relative colour, the lamp
   is the tint itself. */
.page, .panel, .gear, .hj-sec, .saves {
  --lamp: var(--tint);
  --lamp-light: var(--tint);
  --bracket: color-mix(in oklab, var(--lamp) var(--lamp-line), transparent);
  --scene-glow: color-mix(in oklab, var(--lamp-light) var(--lamp-glow), transparent);
  --scene-glow-low: color-mix(in oklab, var(--lamp-light) var(--lamp-glow-low), transparent);
  --halo-plate: radial-gradient(closest-side, color-mix(in oklab, var(--lamp-light) var(--lamp-plate), transparent),
    color-mix(in oklab, var(--lamp-light) var(--lamp-plate-edge), transparent) 58%, transparent);
}
@supports (color: oklch(from red l c h)) {
  .page, .panel, .gear, .hj-sec, .saves {
    --lamp: oklch(from var(--tint) var(--lamp-l) var(--lamp-c) h);
    --lamp-light: oklch(from var(--tint) var(--lamp-l) var(--lamp-light-c) h);
  }
}
.sec-title {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: var(--track-caps);
  font-weight: 600; font-size: var(--fs-lg); line-height: 1.2; color: var(--tint);
}

/* The interface icons (tokens.css): the width sets the size, the height follows the viewBox, and
   the stroke is measured on the screen, not in the drawing's units. */
.ic { flex: none; width: var(--icon-sm); height: auto; stroke-width: var(--icon-stroke); }
.ic * { vector-effect: non-scaling-stroke; }
.ic.is-lg { width: var(--icon); }
.chev { transition: transform var(--dur) var(--ease); }
.chev.up { transform: rotate(180deg); }

/* ── Controls ────────────────────────────────────────────────────────── */
/* Four kinds of button, by job (design/11-components.md, round 2, chosen on 27-sep-2026). The
   first rule for all four: it has to read as a button at rest, with no mouse over it, on a phone
   and the first time. Accent, because it's what responds to the finger.
   · Menu (.btn): a screen's or a dialog's actions. A thin frame with the panels' corner brackets
     in the accent; the primary one (.btn-primary, what the screen leads to) with longer brackets
     and a veil of the accent; one that throws something away (.is-danger), in red.
   · Text (.text-btn): an action inside a line or a list. Underlined with the accent's line.
   · Icon (.icon-btn): a glyph alone (close, help, − +), in a thin ring like the notches' and the
     medals' wells.
   · Disclosure (.disc-btn): opens something below. The label on the left and the chevron in the
     same ring on the right.
   The screens copied from the game (the Hall's tablet, the save slots, the header's save
   selector) keep the game's own unboxed controls in their ink: there a box looked pasted on. */
.btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--ctl); padding: var(--sp-1) var(--sp-4); border: 1px solid var(--line-ctl); background: none;
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-xs); letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--ink);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
  --bk: var(--accent-line); --bk-len: var(--sp-2);
}
/* The corner brackets, drawn over the frame's four corners. */
.btn::before {
  content: ''; position: absolute; inset: -1px; pointer-events: none;
  --bk-h: linear-gradient(var(--bk), var(--bk));
  background:
    var(--bk-h) top left / var(--bk-len) 1px no-repeat, var(--bk-h) top left / 1px var(--bk-len) no-repeat,
    var(--bk-h) top right / var(--bk-len) 1px no-repeat, var(--bk-h) top right / 1px var(--bk-len) no-repeat,
    var(--bk-h) bottom left / var(--bk-len) 1px no-repeat, var(--bk-h) bottom left / 1px var(--bk-len) no-repeat,
    var(--bk-h) bottom right / var(--bk-len) 1px no-repeat, var(--bk-h) bottom right / 1px var(--bk-len) no-repeat;
}
.btn:is(:hover, :focus-visible):not(:disabled) { color: var(--ink-strong); border-color: var(--accent-line); background-color: var(--accent-soft); }
.btn-primary { color: var(--ink-strong); border-color: var(--accent-line); background-color: color-mix(in oklab, var(--accent) 10%, transparent); --bk: var(--accent); --bk-len: var(--sp-3); }
.btn-primary:is(:hover, :focus-visible):not(:disabled) { background-color: color-mix(in oklab, var(--accent) 18%, transparent); }
/* Large: the one call-to-action a flow ends in (the import preview's Import). */
.btn-lg { min-height: calc(var(--ctl) + var(--sp-2)); padding-inline: var(--sp-8); font-size: var(--fs-sm); --bk-len: var(--sp-4); }
.btn.is-danger { color: var(--bad); --bk: var(--bad); }
.btn.is-danger:is(:hover, :focus-visible):not(:disabled) { color: var(--bad); border-color: var(--bad); background-color: var(--bad-soft); }
.btn:disabled { color: var(--muted); background-color: transparent; --bk: var(--line-ctl); cursor: default; }
/* Text: underlined with the accent's line at rest, whole accent under the pointer. The underline
   is the text's own decoration, so an icon in front (✕, ↶, ←) isn't underlined. */
.text-btn {
  display: inline-flex; align-items: center; gap: var(--sp-1); min-height: var(--ctl-sm); padding: 0;
  font-family: var(--font-body); font-size: var(--fs-md); color: var(--ink-2);
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px;
  text-decoration-color: color-mix(in oklab, var(--accent-line) 75%, transparent);
  transition: color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease);
}
.text-btn:is(:hover, :focus-visible):not(:disabled) { color: var(--accent); text-decoration-color: var(--accent); }
.text-btn:disabled { color: var(--muted); text-decoration-color: transparent; cursor: default; }
/* "Mark in bulk", in the Hall and in the Journal: a list's secondary tool, so a text button, with
   the chevron that says it opens below; open, in bone on the accent's whole line. */
.bulk-toggle { gap: var(--sp-2); font-size: var(--fs-sm); }
.bulk-toggle[aria-expanded="true"] { color: var(--ink-strong); text-decoration-color: var(--accent); }
/* Icon: the button takes the control's height, so it's easy to hit; the ring inside it is what
   shows, as the notches' and the medals' wells. Under the pointer it fills with the accent. */
.icon-btn {
  position: relative; display: inline-grid; place-items: center; flex: none; width: var(--ctl-sm); height: var(--ctl-sm); padding: 0;
  font-family: var(--font-num); font-size: var(--fs-md); line-height: 1; color: var(--ink-2);
  transition: color var(--dur) var(--ease);
}
.icon-btn::before {
  content: ''; position: absolute; top: 50%; left: 50%; width: var(--ring); height: var(--ring); transform: translate(-50%, -50%);
  border: 1px solid color-mix(in oklab, var(--accent-line) 70%, transparent); border-radius: 50%; pointer-events: none;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
  --ring: calc(var(--sp-6) + var(--sp-half));
}
.icon-btn:is(:hover, :focus-visible):not(:disabled) { color: var(--ink-strong); }
.icon-btn:is(:hover, :focus-visible):not(:disabled)::before { border-color: var(--accent); background-color: var(--accent-soft); }
.icon-btn:disabled { color: var(--line-ctl); cursor: default; }
.icon-btn:disabled::before { border-color: var(--line-ctl); }
/* Disclosure: a row between two rules, the label on the left and the chevron in the icons' ring on
   the right. Open, the ring stays lit. .is-pick is the picker's form: what's chosen, in the body
   face, and what the button does next to the ring. */
.disc-btn {
  display: flex; align-items: center; gap: var(--sp-3); width: 100%; min-height: var(--ctl); padding: var(--sp-1) 0; text-align: left;
  border-top: 1px solid var(--line-ctl); border-bottom: 1px solid var(--line-ctl);
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-xs); letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--ink); transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.disc-ring {
  display: inline-grid; place-items: center; flex: none; margin-left: auto;
  width: calc(var(--sp-6) + var(--sp-half)); height: calc(var(--sp-6) + var(--sp-half));
  border: 1px solid var(--accent-line); border-radius: 50%; color: var(--accent-ink);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.disc-btn:is(:hover, :focus-visible) { color: var(--ink-strong); }
.disc-btn:is(:hover, :focus-visible, [aria-expanded="true"]) .disc-ring { color: var(--ink-strong); border-color: var(--accent); background-color: var(--accent-soft); }
.disc-btn.is-pick { border-top: 0; font-family: var(--font-body); font-weight: 400; font-size: var(--fs-md); letter-spacing: 0; text-transform: none; }
.disc-btn.is-pick .disc-ring { margin-left: 0; }
/* On/off (.check): a square box on the accent's line; on, filled with the accent and a dark tick.
   Two forms, one look: a <label class="check"> round a native checkbox (the tick is its
   background), or a button with role="checkbox"/"switch" carrying a .check-box (the tick is the
   .ic inside; the Journal's row picks are this, with no label). */
.check {
  display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--ctl-sm); padding: 0;
  font-family: var(--font-body); font-size: var(--fs-sm); color: var(--ink-2); cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.check:hover { color: var(--ink-strong); }
.check-box, .check input[type="checkbox"] {
  flex: none; display: grid; place-items: center; width: var(--icon); height: var(--icon); margin: 0;
  border: 1px solid color-mix(in oklab, var(--accent-line) 80%, transparent); border-radius: 0; background: var(--card) center / 10px no-repeat;
  color: var(--scene-bg); -webkit-appearance: none; appearance: none; cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
:is(.check, .hj-pick):hover :is(.check-box, input[type="checkbox"]) { border-color: var(--accent); }
:is([aria-checked="true"], [aria-pressed="true"]) > .check-box, .check input:checked { background-color: var(--accent); border-color: var(--accent); }
.check input:checked { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 10' fill='none' stroke='%2304060b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1.5 5.2 L4.6 8.2 L10.5 1.8'/%3E%3C/svg%3E"); }
.check input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.check-box > svg { opacity: 0; }
:is([aria-checked="true"], [aria-pressed="true"]) > .check-box > svg { opacity: 1; }
/* Search (.search): a sunken well with its rule and the lens in front, the well of the steppers'
   number, so it's plain that it takes typing. The rule takes the accent under the pointer and,
   doubled, while typing (the focus mark: the input itself draws no ring). */
.search {
  display: flex; align-items: center; gap: var(--sp-2); min-height: var(--ctl); padding: 0 var(--sp-3);
  background: var(--card); border-bottom: 1px solid var(--line-ctl); color: var(--muted); cursor: text;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.search:hover { border-bottom-color: var(--accent-line); color: var(--accent-ink); }
.search:focus-within { border-bottom-color: var(--accent); color: var(--accent-ink); box-shadow: inset 0 -1px 0 var(--accent); }
.search input {
  flex: 1 1 auto; min-width: 0; min-height: var(--ctl); padding: 0; margin: 0;
  font-family: var(--font-body); font-size: var(--fs-md); color: var(--ink);
  background: none; border: 0; border-radius: 0; -webkit-appearance: none; appearance: none;
}
.search input::placeholder { color: var(--muted); }
.search input:focus-visible { outline: none; }

/* ── Showing (round 4, 27-sep) ─────────────────────────────────────────────
   Since round 2 a frame says "press me", so what only informs carries none. */
/* Tag (.tag): small capitals in the secondary ink after the game's hollow diamond. The full
   sheet's chips (.chip) are the same without the capitals: they carry charm names and values. */
.tag {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; line-height: 1.4;
  color: var(--ink-2);
}
.tag::before, .chip::before {
  content: ''; flex: none; width: var(--diamond); height: var(--diamond); border: 1px solid currentColor; transform: rotate(45deg); opacity: 0.8;
}
/* Notice (.banner): the level's rule on the left and a breath of its colour behind; the tag and
   the text in the level's inks. Three levels: danger (overcharm, the game's magenta, the
   default), a condition (.is-run: in a pantheon, the conditions' ochre) and information
   (.is-hint: your real game, bone). */
.banner {
  --lv: var(--over-ink); --lv-ink: var(--over-ink-2);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-4); padding: var(--sp-3) var(--sp-4);
  border-left: 2px solid var(--lv); background: color-mix(in oklab, var(--lv) 7%, transparent);
}
.banner-tag { flex-shrink: 0; color: var(--lv); }
.banner-text { flex: 1 1 24ch; font-size: var(--fs-sm); color: var(--lv-ink); }
.banner .btn { flex-shrink: 0; }
.banner.is-run { --lv: var(--cond); --lv-ink: var(--ink-2); }
.banner.is-hint { --lv: var(--ink-strong); --lv-ink: var(--ink-2); }
#banner:not(:empty) { display: flex; flex-direction: column; gap: var(--sp-2); }

/* ── Patterns (round 5, 27-sep) ────────────────────────────────────────────
   The row you're reading, in a list (.jr-row, .hj-row, .pg-row's head): the accent's bar on the
   left over the chosen veil, the one gesture for "this is what's open". The chosen enemy's card
   and row carry the same bar without the veil. */
/* Empty (App.emptyHtml): centred under a short rule with its diamond, in the secondary ink, with
   the action that solves it when there is one. .is-inline, for a slot that has to keep its
   height (the enemy's attacks) or a dropdown: the text and the action on one line, no rule. */
.empty {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); margin: 0; padding: var(--sp-4) var(--sp-3);
  text-align: center; font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-2); text-wrap: balance; list-style: none;
}
.empty > .rule { width: calc(var(--sp-16) * 2); height: auto; color: var(--bracket); }
.empty .text-btn { font-size: var(--fs-sm); }
.empty.is-inline { flex-direction: row; flex-wrap: wrap; justify-content: flex-start; gap: var(--sp-1) var(--sp-3); padding: 0 var(--sp-3); text-align: left; }
.empty.is-inline > .rule { display: none; }

/* Choose one (.seg, round 3, 27-sep): a group where one is always chosen — compare with, the
   import's systems, the Journal's filters and an entry's state, the language, a spell's side.
   Words in a row, each on a faint line of the accent so it reads as something to pick; the
   chosen one in bone on the accent's whole line, doubled (a shadow, so nothing moves). */
.seg { display: inline-flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.seg button {
  padding: var(--sp-1) 0; min-height: var(--ctl);
  font-size: var(--fs-sm); color: var(--ink-2); text-align: center;
  border-bottom: 1px solid color-mix(in oklab, var(--accent-line) 40%, transparent);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.seg button:is(:hover, :focus-visible):not(:disabled):not([aria-pressed="true"]) { color: var(--accent); border-bottom-color: var(--accent-line); }
.seg button[aria-pressed="true"] { color: var(--ink-strong); border-bottom-color: var(--accent); box-shadow: 0 1px 0 var(--accent); }
.seg button:disabled { color: var(--muted); border-bottom-color: transparent; cursor: default; }
.seg.sm { gap: var(--sp-3); }
.seg.sm button { min-height: var(--ctl-sm); font-size: var(--fs-xs); }

/* ── Header ──────────────────────────────────────────────────────────────
   One row: the title, centred, under the filigree from the Hall's screen (tablet-hdr, the game's
   white stroke), small; on its left the language and Share, which belong to the site, and on
   its right the save selector, which is yours. Behind it, like behind the whole page, the dust motes from the main menu (.atmos). */
/* Three columns: the title in the centre of the page and one side on each flank. The two side
   columns grow equally, so the title lands dead centre; if a side doesn't fit in its half, its
   column widens and the title gives way a little rather than overlap. */
/* Two rows: the filigree above, and the title below with the two sides beside it, centred on its
   line (the brand spans both rows and shares them, subgrid). */
.masthead {
  padding: var(--sp-6) 0 0; display: grid; grid-template-columns: 1fr auto 1fr; grid-template-rows: auto auto;
  align-items: center; gap: var(--sp-4) var(--sp-6);
}
.masthead .brand { grid-column: 2; grid-row: 1 / 3; display: grid; grid-template-rows: subgrid; row-gap: var(--sp-1); justify-items: center; }
.masthead .mh-tools { grid-column: 1; grid-row: 2; justify-self: start; flex-wrap: nowrap; }
.masthead .mh-save { grid-column: 3; grid-row: 2; justify-self: end; }
.brand { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); min-width: 0; color: inherit; text-decoration: none; }
/* The title leads to the start screen: on hover, the filigree lights up fully. */
.brand .mh-hdr { transition: opacity var(--dur) var(--ease); }
.brand:hover .mh-hdr { opacity: 1; }
.mh-hdr { width: 200px; height: auto; opacity: 0.88; }
.title {
  font-family: var(--font-display); font-weight: 900; font-size: var(--fs-brand);
  line-height: 1.05; letter-spacing: var(--track-title); color: var(--ink-strong); text-align: center;
  text-wrap: balance;
}
/* The language and Share, as loose text like a game menu: small, unboxed, so the title
   rules. The chosen language in bone; what can be touched lights up in the accent. */
.mh-tools { --tools-gap: var(--sp-6); display: flex; align-items: center; gap: var(--tools-gap); flex-wrap: wrap; }
.langsel { position: relative; display: flex; align-items: center; gap: var(--sp-2); }
/* Between the language and Share, a thin upright rule centred in the gap, like Combat's tabs' (.tab-sep). */
.mh-tools > .langsel:not(:last-child)::after {
  content: ''; position: absolute; top: 50%; right: calc(var(--tools-gap) / -2 - 0.5px);
  width: 1px; height: var(--sp-4); transform: translateY(-50%); background: var(--line-ctl);
}
.langsel button, .mh-link {
  display: inline-flex; align-items: center; min-height: var(--ctl-sm); padding: 0;
  font-size: var(--fs-sm); letter-spacing: var(--track-label);
}


/* ── The screen bar ──────────────────────────────────────────────────────
   The pages of the game's pause menu: their names in the serif, in lowercase, and an accent
   diamond in front of the one you're viewing (design/02-hollow-knight.md, the charm screen).
   Stuck at the top and across the full window width; on the right, the mini-bar with what you
   look at while touching charms. The page clips what sticks out (overflow-x: clip, which
   doesn't break sticky the way hidden would). */
.page { overflow-x: clip; }
/* The notices and the screen, with the column's breathing room; what's empty takes no space. */
.screens { display: flex; flex-direction: column; gap: var(--sp-8); }
.screens > :empty { display: none; }
/* Switching screens (js/app.js, go): the one you arrive at fades in, as the game fades between areas. */
.screens > .is-entering { animation: title-in var(--dur-slow) var(--ease-fade); }
@media (max-width: 899px) { .screens { gap: var(--sp-6); } }
/* The footer: the fan-project notice and the sources, small and muted under a hairline. The
   figures don't live here, so no accent but on the links, which are what responds to the finger. */
.colophon {
  display: flex; flex-direction: column; gap: var(--sp-1);
  padding-top: var(--sp-4); border-top: 1px solid var(--line);
  font-size: var(--fs-xs); line-height: 1.5; color: var(--muted); text-align: center; text-wrap: balance;
}
.colophon a {
  color: var(--accent); text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--accent-line); transition: color var(--dur) var(--ease);
}
.colophon a:hover { color: var(--accent-ink); }
/* The GitHub mark after the contact line: just the icon, so no underline. */
.colophon .gh { display: inline-flex; vertical-align: -3px; margin-left: var(--sp-1); text-decoration: none; }
/* The About block (tools/pages.js): the page's text for search engines and for whoever lands
   from one, under the screens and above the footer. As wide as the screens above it, in the
   secondary ink, so it's there to read without competing with them; only its links take the
   accent, as in the footer. */
.about {
  display: flex; flex-direction: column; gap: var(--sp-3);
  padding-top: var(--sp-8); border-top: 1px solid var(--line);
  font-size: var(--fs-sm); line-height: 1.6; color: var(--ink-2);
}
.about h1 { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-xl); line-height: 1.2; letter-spacing: var(--track-title); color: var(--ink-strong); text-wrap: balance; }
.about h2 { margin-top: var(--sp-4); font-family: var(--font-display); font-size: var(--fs-2xs); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--muted); }
.about h3 { margin-top: var(--sp-2); font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.about-faq { display: flex; flex-direction: column; gap: var(--sp-1); }
.about code { font-family: var(--font-num); font-size: var(--fs-xs); color: var(--ink); overflow-wrap: anywhere; }
.about strong { color: var(--ink); font-weight: 600; }
.about-more ul { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-6); margin-top: var(--sp-2); padding: 0; list-style: none; }
.about a {
  color: var(--accent); text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--accent-line); transition: color var(--dur) var(--ease);
}
.about a:hover { color: var(--accent-ink); }
/* One row: the tabs and, at the right, the mini-bar. The tabs never shrink (they'd overlap the
   figures); if the figures ever don't fit beside them, they wrap under, as a net: the widths
   below are measured so that they do fit from 1240 px in both languages. */
.nav {
  position: sticky; top: 0; z-index: 30; isolation: isolate;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-2) var(--sp-4);
  min-height: var(--sticky-h);
}
.nav::before {
  content: ''; position: absolute; z-index: -1; top: 0; bottom: 0; left: calc(50% - 50vw); right: calc(50% - 50vw);
  background: var(--bg); border-bottom: 1px solid color-mix(in oklab, var(--lamp) 22%, transparent);
}
/* The tabs take the bar's full height, so the mark of the current one lies on the bar's bottom
   edge, like a page tab: on a computer the Knight, sitting under its title (js/app-knight.js),
   and no rule; on a phone, where he isn't, an accent rule (the same mark as Combat's tabs). */
.nav-tabs { position: relative; display: flex; flex-shrink: 0; align-self: stretch; align-items: stretch; gap: var(--sp-1) var(--sp-4); }
.nav-tab {
  display: inline-flex; align-items: center; gap: var(--sp-2); min-height: 44px; padding: 0;
  font-family: var(--font-body); font-size: var(--fs-lg); line-height: 1.2; color: var(--muted); text-decoration: none;
  transition: color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.nav-tab:hover { color: var(--accent); }
.nav-tab[aria-current="page"] { color: var(--ink-strong); }
/* The Journal carries its completed entries over the total alongside. */
.nav-num { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); font-weight: 600; color: var(--ink-2); }
.nav-hj:hover .nav-num { color: var(--accent); }
/* The two groups: your game, then the tools after a thin rule (design/10-restructure.md). On a
   computer the tools go by name; the Tools tab and its second row are the phone's. */
.nav-sep { width: 1px; height: 1.4em; align-self: center; background: var(--line-ctl); }
.nav-tools, .nav-sub, .nav-short { display: none; }
/* ── The Knight who walks the page (js/app-knight.js) ──────────────────
   One sprite, drawn from a strip of 104 × 140 cells (assets/knight/, tools/fetch-knight.js):
   standing, running (six frames, stepped at --dur-step) or sitting; he faces right and turns
   with a mirror. He sits on the screen bar, --kn-bar tall (--kn-h), his feet on its bottom
   edge. He moves by translate, linear, like the motes, and reduced
   motion leaves him on his first frame and, from the JS, only standing or sitting. Decorative:
   the button only exists for the click (he focuses soul). */
.kn {
  --n: 1; --kn-h: var(--kn-bar); --cell: calc(var(--kn-h) * 104 / 140);
  position: absolute; z-index: 1; width: var(--cell); height: var(--kn-h); padding: 0; border: 0; margin: 0;
  background: url(../assets/knight/idle.png) 0 0 / auto 100% no-repeat; cursor: default;
  opacity: 1; transition: opacity var(--dur-slow) var(--ease-fade);
}
.kn:focus { outline: none; }
.kn.is-run { background-image: url(../assets/knight/run.png); --n: 6; }
.kn.is-sit { background-image: url(../assets/knight/sit.png); }
.kn.is-left { scale: -1 1; }
.kn.is-out { opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
  .kn.is-run { animation: kn-cycle calc(var(--dur-step) * var(--n)) steps(var(--n)) infinite; }
  /* Focusing soul (a click): standing still, the Focus's white light swells around him and fades. */
  .kn.is-focus { animation: kn-focus var(--dur-flash) var(--ease-fade); }
}
@keyframes kn-cycle { to { background-position-x: calc(-1 * var(--cell) * var(--n)); } }
@keyframes kn-focus { 40% { filter: drop-shadow(0 0 3px var(--focus-glow)) drop-shadow(0 0 14px var(--focus-glow)); } }
/* On the screen bar: sitting at its bottom edge, under the current tab's title. */
.nav-tabs > .kn { bottom: 0; left: 0; }

/* The link to the game's file, on Your game's link line: the diamond lit while it follows the game. */
.nav-live { display: inline-block; width: var(--sp-2); height: var(--sp-2); transform: rotate(45deg); border: 1px solid currentColor; }
.nav-live.is-live { background: var(--ink-strong); border-color: var(--ink-strong); box-shadow: 0 0 8px color-mix(in oklab, var(--ink-strong) 55%, transparent); }
.nav-live.is-paused, .nav-live.is-lost { color: var(--cond); }
@media (prefers-reduced-motion: no-preference) {
  .nav-live.is-live { animation: live-breathe 2.4s var(--ease-fade) infinite alternate; }
}
.minihud[hidden] { display: none; }

/* The atmosphere: the main menu behind the whole page (tokens.css, "Atmosphere"). Fixed to the
   window and behind everything: .page isolates, so z-index −2 and −1 paint over its background
   and under its content, and the screens' black covers them; they show in the header, the
   margins and between blocks. Below, the vignette with the motes (.atmos, js/app.js); above, the
   grain, which blends (overlay) with what's under it. Still, the motes can't be seen: they only
   show with motion. */
.page { position: relative; isolation: isolate; }
.atmos { position: fixed; inset: 0; z-index: -2; overflow: hidden; pointer-events: none; background: var(--vignette); }
.page::before {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: var(--grain); opacity: var(--grain-opacity); mix-blend-mode: overlay;
}
.atmos i {
  position: absolute; left: var(--x); bottom: 0; width: var(--s); height: var(--s); border-radius: 50%;
  background: var(--mote); box-shadow: 0 0 6px var(--mote); opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .atmos i { animation: mote var(--t) linear infinite; animation-delay: var(--d); }
}
/* From the bottom edge of the window to the top one, faded in and out, with no easing to jump. */
@keyframes mote {
  0% { opacity: 0; transform: translate(0, 0); }
  15%, 80% { opacity: var(--o); }
  100% { opacity: 0; transform: translate(var(--dx), -100vh); }
}

/* ── The sheet: the game's Inventory screen ──────────────────────────────
   Almost pure black, the corner brackets in the tint and no box: the title with its diamond, the
   HUD, the large nail with the arena's cold spotlight behind it, the damage and the six figures,
   the plates and the charm band. It's a container (inv): its pieces arrange themselves by the
   sheet's width, not the window's. */
.panel {
  container: inv / inline-size;
  --inv-pad: var(--sp-8);
  --inv-line: color-mix(in oklab, var(--tint) 13%, transparent);
  position: relative; background: var(--inv-bg); padding: var(--inv-pad) var(--inv-pad) 0;
}
.inv-head { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); text-align: center; }
.inv-head .sec-title { font-size: var(--fs-xl); }
.inv-head .rule { color: var(--bracket); }
/* The rule between blocks: on the sheet, the full sheet; elsewhere, the charms in the pantheon's
   rest room (.sep-wrap). */
.sep-wrap, .detail { border-top: 1px solid var(--line); margin-top: var(--sp-8); padding-top: var(--sp-6); }

/* The status block, in one row so it fits with the grid on an 800 px tall screen: the HUD with
   health and soul as figures below it; the nail damage with the nail and its cold spotlight;
   and after a rule, the six figures in two rows of three. The HUD is the arena's, larger: its
   measurements come from --orb, --pip-h and --hud-row (see .kscene). */
.inv-status {
  --nail-h: 130px;
  display: grid; grid-template-columns: minmax(0, 340px) minmax(0, 220px) minmax(0, 1fr); grid-template-areas: "hud lead stats";
  gap: var(--sp-6); align-items: center;
}
.inv-hud { grid-area: hud; display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.inv-lead { grid-area: lead; display: flex; align-items: center; gap: var(--sp-4); min-width: 0; }
.panel .hud { --orb: 84px; --pip-h: 30px; --pip-w: calc(var(--pip-h) * 51 / 66); --hud-row: 244px; column-gap: var(--sp-3); }
.panel .hud-side { padding-top: var(--sp-2); }
.panel .hud-masks { gap: var(--sp-1); }
.panel .hud-vessels { gap: var(--sp-2); padding-top: var(--sp-half); }
/* Health and soul, as figures only: the HUD already draws the rest. With health lost, the
   maximum goes after it, in grey. */
.inv-read { display: flex; gap: var(--sp-8); }
.rd { display: flex; flex-direction: column; gap: var(--sp-half); min-width: 0; }
.rd .v { font-size: var(--fs-xl); font-weight: 700; line-height: 1.05; color: var(--ink-strong); display: flex; align-items: baseline; gap: var(--sp-2); }
.rd .of { font-style: normal; font-weight: 400; font-size: 0.7em; color: var(--muted); }

/* The masks, aligned by their body and not by their canvas. Measured on the sprites
   themselves (pixels with alpha ≥ 128):
     mask.png     canvas 60×86, body 51×66 from (4, 10)
     mask-lb.png  canvas 87×96, body 51×65 from (18, 17) — the rest is halo
   The two bodies are almost the same size, but the canvases aren't, so with object-fit the
   white ones came out narrow (16.7 px of useful width out of the box's 20) and the blue ones
   lower. Here the slot IS the body (51 × 66) and each sprite is scaled and shifted as needed
   to fit it, with lifeblood's halo spilling out, just like the soul orb's.
   On the sheet each mask is a button (.pip-mask): the one you're about to remove lightens. */
.pip-mask img, .hud-mask img { position: absolute; max-width: none; height: auto; }
.pip-mask img, .hud-mask img { width: 117.647%; left: -7.843%; top: -15.152%; }
.pip-mask.lb img, .hud-mask.lb img { width: 173.222%; left: -35.837%; top: -26.154%; }
/* With Hiveblood, the honey hexagon in the mask's slot (the same fit as the honey growing in
   the one that regenerates, .hud-honey); the blue one, in Joni's Blessing's. */
.pip-mask img.is-hive, .hud-mask img.is-hive { width: 94.9%; left: 1.7%; top: -1.333%; }
.pip-mask.lb img.is-hive.is-joni, .hud-mask.lb img.is-hive.is-joni { width: 93.66%; left: 4.88%; top: 0; }
button.hud-mask { padding: 0; border: 0; background: none; cursor: pointer; }
.pip-mask:hover { filter: brightness(1.25); }
/* With a mouse, the HUD previews what the click would do, like Your game's pieces: the empty masks
   it would fill (up to the one under the pointer) come up to half light, the lit ones after it go
   dim; the last one brings back full health, so it only fills. The soul is only drawn: spending it
   by hand changed no figure, so it went (27-sep); it's spent in Combat. */
@media (hover: hover) {
  .pip-mask.is-empty:is(:hover, :has(~ .pip-mask:hover)) img { filter: brightness(0.7) saturate(0.4); opacity: 1; }
  .pip-mask:hover ~ .pip-mask:not(.is-empty) img { opacity: 0.35; }
}
/* The empty container of the orb and the vessels, under the soul. */
.orb-well {
  position: absolute; inset: 0; border-radius: 50%;
  background: var(--soul-well); box-shadow: inset 0 0 0 1px var(--soul-rim);
}

/* The nail, vertical as in the game's Inventory and with the arena's spotlight behind it, next to its
   damage; and the six figures, unboxed. */
.inv-nail { position: relative; isolation: isolate; flex: none; width: 40px; height: var(--nail-h); display: flex; justify-content: center; }
.inv-nail::before {
  content: ''; position: absolute; z-index: -1; left: 50%; top: 50%; width: 180px; height: 250px; transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, var(--scene-glow), transparent);
}
.hero-nail { height: var(--nail-h); width: auto; max-width: none; }
.hero { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; padding: 0; text-align: left; }
.hero > .lbl, .hero-note { display: block; }
.hero-row { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.hero-num { font-size: var(--fs-hero); font-weight: 700; line-height: 0.9; color: var(--ink-strong); }
.hero-note { font-size: var(--fs-md); line-height: 1.45; color: var(--ink-2); }
.inv-stats { grid-area: stats; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-6) var(--sp-4); padding-left: var(--sp-6); border-left: 1px solid var(--inv-line); }

.metric { display: flex; flex-direction: column; gap: var(--sp-1); text-align: left; padding: 0; min-width: 0; }
.metric .lbl { font-size: var(--fs-2xs); }
.metric .val {
  font-size: var(--fs-2xl); font-weight: 700; line-height: 1;
  color: var(--ink-strong); display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap;
}
.metric .note { font-size: var(--fs-xs); color: var(--muted); line-height: calc(var(--fs-xs) * 1.3); display: grid; min-height: 1.3em; }
/* Its line height is fixed in length, so the preview (".pv", a step larger) fits the same line box
   as the note it covers: hovering a charm doesn't make the figures grow and push the band. */
.metric .note > * { grid-area: 1 / 1; }

/* The preview of the charm under the mouse (paintPreview): "→ what would remain" next to each figure
   that would change with a click. On the six it goes on top of its note, which moves aside without
   leaving a gap: it neither changes the height nor pushes the band. No green, since a hypothesis
   isn't an advantage; what gets worse, in red. While it's there, the last change's chip steps aside. */
.pv { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); white-space: nowrap; }
.pv.bad { color: var(--bad); }
.pv:empty { display: none; }
.hero-row .pv { font-size: var(--fs-lg); }
.metric .note:has(.pv:not(:empty)) .note-t { visibility: hidden; }
:is(.rd .v, .hero-row):has(.pv:not(:empty)) .delta { display: none; }
.metric { transition: color var(--dur) var(--ease); }
/* The six and the nail damage open the full sheet at their row. On hover the label takes the accent
   and the figure a soft white glow, Focus's, standing still. */
:is(.metric, .hero) .lbl { transition: color var(--dur) var(--ease); }
:is(.metric, .hero):is(:hover, :focus-visible) .lbl { color: var(--accent); }
.metric .val > span:first-child, .hero-num { transition: text-shadow var(--dur) var(--ease); }
.metric:is(:hover, :focus-visible) .val > span:first-child, .hero:is(:hover, :focus-visible) .hero-num {
  text-shadow: 0 0 14px color-mix(in oklab, var(--focus-glow) 55%, transparent);
}

/* On the sheet the deltas are text: the colour already says whether it's better or worse. */
.panel .delta, .panel .delta.good, .panel .delta.bad { border: 0; background: none; padding: 0; font-size: var(--fs-sm); font-weight: 600; }
.panel .hero-row .delta { font-size: var(--fs-lg); }

/* The numbers that move: the figure that just changed blooms in white and fades —Focus's
   glow, not the accent—. Only on the repaint that follows the change (flashIds, in js/app.js).
   Next to it appears what your last action moved (changeChip: "+6", not the distance to the
   base Knight, which is read on the full sheet): it fades in, stays for a moment and goes,
   because it's a notice and not data. It's hidden with visibility, so it keeps its space (the figure doesn't jump) and leaves the accessibility tree. With
   reduced motion there's no animation: it stays until the next repaint. */
@keyframes num-flash { from { text-shadow: 0 0 18px var(--focus-glow), 0 0 4px var(--focus-glow); } }
@keyframes delta-last { 0% { opacity: 0; } 10%, 80% { opacity: 1; } 100% { opacity: 0; visibility: hidden; } }
.is-flash .hero-num, .metric.is-flash .val > span:first-child, .plate.is-flash .plate-val,
.rd.is-flash .v > span:first-child, .minihud .is-flash b { animation: num-flash var(--dur-flash) var(--ease); }
.delta.is-last { animation: delta-last var(--dur-delta) var(--ease) forwards; }

/* ── Equipped and notches, as on the game's screen ─────────────────────
   The notch's dot uses the HUD sprite (assets/hud/notch-ui.png) as a mask: that way it
   keeps its exact silhouette and halo, but can be tinted white or magenta, which is what
   the game does when you overcharm.
   The sprite is embedded in base64 on purpose: Chrome treats each local file as a
   different origin and blocks external CSS masks, so over `file://` a
   url(../assets/…) leaves the used notch invisible. To regenerate it:
   base64 -w0 assets/hud/notch-ui.png */
:root { --notch-sprite: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAqCAYAAADFw8lbAAABL2lDQ1BJQ0MgcHJvZmlsZQAAeNqtkbFKw1AUhr8bRcWhVgji4HAnUVBs1cGMSVuKIFirQ5KtSUOV0iTc3Kp9CEe3Di7uPoGTo+Cg+AS+geLUwSFIcBLBb/rOz+Fw4AejYtedhlGGQaxVu+lI1/Pl7BMzTAFAJ8xSu9U6AIiTOOInAj5fEQDPm3bdafA35sNUaWACbHejLARRAfoXOtUgxoAZ9FMN4g4w1Um7BuIBKPVyfwFKQe5vQEm5ng/iAzB7rueDMQeYQe4rgKmjSw1QS9KROuudalm1LEva3SSI5PEo09Egk/txmKg0UR0ddYH8PwAW88V205FrVcvaW+efcT1f5vZ+hADE0mORFYRDdf7dhbHz+1zcGC/D4S1MT4ps9wpuNmDhushWq1DegvvxF8KzT/7obdArAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACKNJREFUeNq8WVtvG8cVnp2dJbm8SSRFRqQk23AcKUBkN0DSpkUM5CFF89AkAgKkL33qryuKFnmIi+QlDRCjSRvLKRwXRQrHcOxAoiiLpHgn995zRrPOyWApMZZRAUezu9zd+eY791nj5t//yZ7Bn7HAPdF5JhDPAJSxANjovKDFORmko5Hwe6REP//JgMVTsMjJyAlIrt0XaeACkJC8LyJj9CyAUtY4GU0C1tSAcjV5qCRQ98SAg5/KrlgAJGUsFouMJhkZGSMCEkdPHXsELGX5VMBiARYpSKFAoaS00SLMUqAxMFcdO+rYJxKcxapYkM0YII5pJTZIRgFNqd84eWesdl8BRHAz9R6HAObqd/80ZsUZAA2i2hQBl1NgswRsfA8nQGMmY5kqsBPNEanDJTJ7FqNCTU5B5ZXgefbhdw+ujUej7SAInheWqJsGLxmGwfwwOM6kMi249iBXKPynUlm5q94zVYuPRQ9tgRYREoHqzhMDjVlEKSLQb+/de3U0Hr+RyWReXSqV7VqtxlKpFLNtm3EA6jhO7bjXqw0Gg2vH3eO3ut3uV5Vy6Wa5Uv0XMRVG2PfVGCWFLHFKGLI0NhHkMoLc3d19D1j7zebmZq2+usosAFgoFCRQ0zRZFEUs8H1WWVlho+GQAWC73W5fHw2GW7PZ7JPG2saNBJWHmvzIVucxahLbjNlEdee+un3793Yms3P58mVRbzTY0tKSBGUJIUGGYchQ9el0WgIvFousXKnIsdt+XG21mu8397/PNtYu/EmB0MNVpIE9E6gg3i0ZvfnZZ+9lbHtn++pV0ajXmeu6DOyTjScTyaI0MAAtLIvlczmWy+eZgAXgIpD5fB7MgnNxdHT02/bj1mSltvqBcrI4vsbCtfQbiTm2aWqOZH/z329+DgDe2traEpVymXkAKAD2+v2+ZFEyCoJsygXACGpmdjZ7wi6AL+SX2ErVZTPHE8e97q9T/d6j4tLyFyoSzNR8DjGFJw7FExiNg3uKMto6PHhjfW2tBsI4qHgETLqOIx0nDAJ0Hik+HCOraJvINNgm6/d6EnRocFYsVVi1WgXwdhXuuU5CXpoQRBOHwbQTmqcFCfKpu/++sw0T/QLULtnxPI95wBoyhzaKwMFhWLfTYT0YIRpIwLgANAkEPATguBCwb2mv+J7xePTKoNfbViAtklS4VjsYPKEy0tVvHbUfXysUC3YBbA5VPgEg+LdcKslzZBdtsQQmUQVPh5AlQQ3hOh7jnwuLm4wnsIAfEpAlLHsw6L9EIozQChzjtPDENRFhFF0pLZekF6PzoC3mwPbQHiFOyocuXbrESsvL8h4EiSpvHhxIJvEasjsF9WcmU8kmhrN+t4OPPk/qhCQ2EwM+1+pNuUJgsIHqRhUiOBuACvOkSOodH0ubQ5Do5SbnMq7iYnARaJtyIpUIDG6AqXD5PEQAWNSsTlg05/hNxOekT5OqYObMyrGToM0hWASCNoqejzaH3o1A8ByjQBxH0TTwXKYfFcLweRQE7LneskaQkQD2zHr0JDTAf67ASQEmkA0EHqs6DD0WYAQAUPibDxpA0LgwZE/mf7BP3w+YACh43Q/DhXufJEbp07JWNAzR9Rzwct9jUwQVGSxr51ghV2QWgOiBrR3s77HAc0ANEUZ91mrug9rRVEADHIWzKIxktIBYIMUPcTKjR+ImzUynMhoSgHGREJom359Op6tj8Np0ygYcMBG8MpfNMXSydqfFms0mm46mkEYF0g/X2swDwJmMLa9FmFr5ieo974TZKAqZmbKapB6N5lX8IqE7pH0OimNZ6Xv9fu+VCXh8PptnU3CqCYgo5lmtXmPpjMkODw/ZYDwCxk5KSijzwEwsDOzAKjgYZCYT1G2ZkLmmU4bv8l0H0mr+vtam0ErqR4zSdjbUWghZ8IJX37l//94O2GIWVTcaD8GjbbBPE4BYrNFYZ1lYwGzqsuNBX9oj2iU6oFCFCjoSOhjauINZCzOb606z2dxdNY9DugFKWERzPUvoc+KqfPbaa7/8+tGjh/9otfbfxABuCg5gRwAgkJVRFgqQUqUqY+VyuSSdCB0sUKkVowMCRkGA7c4R5PsJLC53q1qtfa0qfkczubk2mqT6eKWTtbX1vz18+N1LKSuzepJVhHQM9F4EgqlRpIW0RwdUiyDxOkYA6fUwgp2zFpjI3t4ems7hxsaFT+FVY1WQuKTpo2wy3UYN0grEDVfcjKVef/36l91258ZB8+APkNtFXGvC/MzxHalWfAxtcTadybAFDg/nAu6xAPSMDXt91nl8yMaDoQ/p9uPnnlvdJUBnihTq/acyyoj6TQLWfGdn588f3fgof9g8eB+8WFy4ePEJW/IFwK4XpCGGujKYW1xACj0xESjrWHPve1yEX1xa+uDK5tZf4JGhUvtEzeNrVf4TTAbZzdNzvUn6JZs0dcW/fnjjd1Alvb3WaKxiMbJ2YQPiqs1yxcIPHVoQgvgy17uuh9U9m01GrZVq7ePtqy8jSCwSRkrGpH2moSpKqvBp5xcXrb5aKS0Fw7fffeePX3z++QMISW8Oev1f9UfDrGzschlpm5jrMUYiQAxlyFg+m/1yfePip1de2Nol4MaqK6Ugg6QGz0jYH9U3vVJz+nrZLt++devlznHnZ1AtbQKD62EYldFOQbrQke6DHX9br9fvbG6+eFeBisGNFLApaUf8pH5pHtCkHbu4kI4r8Rh03AHQLR19R88luyQzcuxonh4s2i6zBDOgKTUix3ETZilHEGSXhGvZjjZtdP9J33sK5+X5s4DqSSDSWlmXtCu06DW050KSHn2tNdZj5lNtktEEwAmbIYm1Pglj1K6jhK3HQAOuq/mpgepVlW57nExqnFI2BtqOyDxw59rIZQkbVrTf1kEaCdvdYUKFFs3Z0z/3Hn40B7z+YWHRryELsXjeryLRGYwv4pz/l+9M55nwqT+K/U+AAQBUkJHWB0VxwwAAAABJRU5ErkJggg==); }
.loadout { display: flex; flex-direction: column; gap: var(--sp-6); }
.loadout-block .block-head { display: flex; align-items: baseline; gap: var(--sp-3); }
.notch-free { font-family: var(--font-body); font-size: var(--fs-sm); letter-spacing: 0; text-transform: none; color: var(--ink-2); }
.notch-free.is-over { color: var(--over-ink); }

/* With no charms, the row is just the empty slot, as in the game (the text stays for screen
   readers); it's the same size as with one equipped, so the grid doesn't jump when equipping the first. */
.eq-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; min-height: 54px; }
.eq {
  width: 54px; height: 54px; padding: 0; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.eq img { width: 100%; height: 100%; object-fit: contain; }
.eq:hover img, .eq:focus-visible img { filter: drop-shadow(0 0 8px var(--accent)); }
/* The charm just equipped arrives in Equipped as a light that settles into the artwork (App.was). */
.eq.is-new img { animation: charm-arrive var(--dur-slow) var(--ease) both; }
@keyframes charm-arrive { from { opacity: 0; filter: brightness(2.4) drop-shadow(0 0 12px var(--focus-glow)); } }
/* "Clear": they leave one after another, from the last, fading as dust (save-fade, the slots'). */
.eq-row.is-clearing .eq img { animation: save-fade 320ms var(--ease-fade) both; animation-delay: calc(var(--i) * 50ms); }
/* The next slot takes a charm's whole cell (54 × 54) and draws its dot in the centre:
   that way it lands right where the one you equip will go. */
.eq-slot { width: 54px; height: 54px; flex-shrink: 0; display: grid; place-items: center; }
.eq-slot::before {
  content: ''; width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--notch-free); opacity: 0.7;
}

/* All notches take the same 23px cell: the row is the same length with an empty game as
   with everything equipped, so filling one doesn't shift the others. */
.notches { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; min-height: 24px; }
.notch {
  width: 23px; height: 23px; flex-shrink: 0; display: block;
  --dot: var(--notch-used);
  -webkit-mask: var(--notch-sprite) center / contain no-repeat;
  mask: var(--notch-sprite) center / contain no-repeat;
  /* The game's disc has a dark ring and a light dot in the centre. */
  background: radial-gradient(circle, var(--dot) 0 7%, var(--notch-core) 11% 18%, var(--dot) 23% 100%);
  filter: drop-shadow(0 0 3px var(--notch-used-glow)) drop-shadow(0 0 9px var(--notch-used-glow));
}
.notch.is-over {
  --dot: var(--notch-over);
  filter: drop-shadow(0 0 4px var(--notch-over-glow)) drop-shadow(0 0 12px var(--notch-over-glow));
}
/* The free notch isn't lit: in the game it's a dark ring. It keeps the cell and draws the
   ring inside, the same diameter as the lit dot. */
.notch.is-free {
  -webkit-mask: none; mask: none; filter: none; background: transparent;
  display: grid; place-items: center;
}
.notch.is-free::before {
  content: ''; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--notch-free);
}
/* The grid's preview (paintNotchPreview, in js/app.js), with the mouse over a charm.
   The one you'd take: the free one's ring, same diameter, but lit —the ring in white, or in
   magenta if it would overcharm you, with a short halo and the centre at half tone—, and
   breathing: you don't have it yet. The one you'd release: the dot you already have, with no halo and almost off. */
.notch.is-pending {
  --fill: var(--notch-pending-fill);
  -webkit-mask: none; mask: none; background: transparent;
  display: grid; place-items: center;
  filter: drop-shadow(0 0 3px var(--notch-used-glow));
  animation: notch-pending var(--dur-pending) var(--ease-fade) infinite alternate;
}
.notch.is-pending::before {
  content: ''; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--dot); background: var(--fill);
}
.notch.is-pending.is-over { --fill: var(--notch-over-pending-fill); filter: drop-shadow(0 0 4px var(--notch-over-glow)); }
@keyframes notch-pending { to { opacity: 0.55; } }
.notch.is-leaving { filter: none; opacity: 0.38; }
/* Right after equipping or removing (notchDots, App.was): the notches the charm takes light up one
   by one from the left, from the free ring to the full dot with a burst of its glow; the ones it
   gives back go out from the right, their dot draining into the ring. */
.notch.is-filling { animation: notch-fill var(--dur-slow) var(--ease) both; animation-delay: calc(var(--i) * 70ms); }
.notch.is-free.is-draining::before { animation: notch-drain var(--dur-slow) var(--ease) both; animation-delay: calc(var(--i) * 70ms); }
@keyframes notch-fill {
  from { opacity: 0.3; filter: brightness(2) drop-shadow(0 0 6px var(--notch-used-glow)) drop-shadow(0 0 16px var(--notch-used-glow)); }
}
@keyframes notch-drain {
  from { background: var(--notch-used); border-color: var(--notch-used); box-shadow: 0 0 8px var(--notch-used-glow); }
}
.notch-free .nf-to { color: var(--ink-2); }
.notch-free .nf-to.is-over { color: var(--over-ink); }
/* In one column the row sits above the grid: the preview stays at the height it had
   (--row-h, noted by paintNotchPreview) and what doesn't fit is clipped, with a margin for the halo. */
@container inv (max-width: 899px) {
  .notches.is-preview { height: var(--row-h); overflow: clip; overflow-clip-margin: var(--sp-2); }
}

/* ── Quick access to the charms ────────────────────────────────────────
   The grid from the game's charm screen, to the right of the notches: four rows of ten
   slots with the even ones offset by half a slot. The proportions come from measuring an
   official screenshot: slot 0.806 of the pitch, rows 0.968 of the pitch and offset 0.554
   of the pitch; hence the factors below.
   A row measures 12.85 slots (ten, nine gaps of 0.24 and the 0.69 offset), so the slot is
   the grid's width over 12.85, up to 68 px: 62 px on the sheet's band at full width, 40 in
   Your game's column. */
.loadout-wrap { display: grid; grid-template-columns: minmax(0, 392px) minmax(0, 1fr); gap: var(--sp-6); align-items: start; }
/* On the sheet, the charms go on a full-bleed black band, between two rules, laid out like the
   game's charm screen: Equipped with the grid below, and to its right Notches with the charm's
   detail. They're two independent columns: what grows in one (the notches when you overcharm)
   doesn't push anything apart in the other. */
.inv-band {
  position: relative; isolation: isolate; overflow: clip;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 336px);
  gap: var(--sp-6); align-items: start;
  margin: var(--sp-8) calc(var(--inv-pad) * -1) 0; padding: var(--sp-8) var(--inv-pad);
  background: var(--well); border-block: 1px solid var(--inv-line);
}
.band-col { display: flex; flex-direction: column; gap: var(--sp-6); min-width: 0; }
/* The notches in their column: 11 fit in one row. */
.inv-band .notches { gap: var(--sp-2); }
/* "Clear", in Equipped's header: text in the screen's ink, unboxed. */
.eq-clear { margin-left: auto; font-size: var(--fs-sm); font-weight: 400; letter-spacing: 0; text-transform: none; }
.eq-clear:disabled { opacity: 0; pointer-events: none; }
/* The overcharm notice, inside the band and below the notches: unboxed, in the game's magenta. */
.inv-band .banner.is-band { background: none; border: 0; padding: 0; margin-top: var(--sp-3); gap: var(--sp-1); flex-direction: column; }
.inv-band .banner.is-band .banner-text { font-size: var(--fs-sm); line-height: 1.45; }
.quick { min-width: 0; container-type: inline-size; }
.quick .block-head { display: flex; align-items: baseline; gap: var(--sp-3); }
.quick-hint { font-family: var(--font-body); font-size: var(--fs-sm); letter-spacing: 0; text-transform: none; color: var(--muted); }
.quick-grid {
  --qc: min(68px, 100cqw / 12.85);
  display: flex; flex-direction: column; gap: calc(var(--qc) * 0.2);
}
.qrow { display: flex; gap: calc(var(--qc) * 0.24); }
.qrow:nth-child(even) { margin-left: calc(var(--qc) * 0.69); }
.qc {
  position: relative; isolation: isolate; width: var(--qc); height: var(--qc); padding: 0; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
/* The equipped one, with a soft light behind it, as in the game (--halo-charm), and not a glow
   hugging its outline. Also the one the full sheet's chip is pointing at (is-hot). */
.qc::before {
  content: ''; position: absolute; z-index: -1; inset: -22%; pointer-events: none;
  background: var(--halo-charm); opacity: 0;
}
.qc:is(.is-on, .is-hot)::before { opacity: 1; }
.qc-half::before { inset: -22% -94%; }
.qc.is-on:is(:hover, :focus-visible, .is-sel)::before, .qc.is-hot::before { background: var(--halo-charm-hot); }
@media (prefers-reduced-motion: no-preference) {
  .qc::before { transition: opacity var(--dur) var(--ease); }
}
/* The double slots with no version chosen, and always on Charms found, split into two halves. */
.qslot-pair { display: flex; gap: var(--sp-half); width: var(--qc); height: var(--qc); flex-shrink: 0; }
.qslot-pair .qc { width: calc((var(--qc) - 2px) / 2); }
/* With a finger, each slot takes half of the gap that separates it from its neighbour. */
@media (pointer: coarse) {
  .qc:not(.qc-half)::after { content: ''; position: absolute; inset: calc(var(--qc) * -0.12); }
}
.qc img {
  width: 100%; height: 100%; object-fit: contain;
  opacity: 0.7; filter: grayscale(0.3);
  transition: opacity var(--dur-fast) var(--ease), filter var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.qc:hover img, .qc:focus-visible img { opacity: 0.95; filter: none; transform: scale(1.09); }
.qc.is-on img { opacity: 1; filter: none; }
/* The detail's charm looks like the one under the mouse: lit and a little larger, with no ring
   or ornament. That way you know which one the detail describes when the pointer is no longer on it. */
.qc.is-sel img { opacity: 0.95; filter: none; transform: scale(1.09); }
.qc.is-on.is-sel img { opacity: 1; }
/* Just equipped or just found: it lights up once in Focus's white and settles into its state. */
.qc.is-new img { animation: light-up var(--dur-flash) var(--ease); }
@keyframes light-up { 30% { filter: brightness(1.9) drop-shadow(0 0 10px var(--focus-glow)); opacity: 1; } }
/* The one that would overcharm you announces itself in magenta, like the notches. */
.qc.is-over:hover img, .qc.is-over:focus-visible img { filter: drop-shadow(0 0 8px var(--over-line)); }
.qc.is-locked { cursor: not-allowed; }
/* The one moved by the full-sheet chip you're looking at: lit, with its halo (above). */
.qc.is-hot img { opacity: 1; filter: none; }
.qc.is-locked img { opacity: 0.25; filter: grayscale(0.75); }
.qc.is-locked:hover img { opacity: 0.45; filter: grayscale(0.75); transform: none; }
/* The one you haven't found in your game stays shadowed in every state —under the mouse, in
   the detail (is-sel), highlighted by a chip (is-hot) or warning of overcharm—: otherwise
   those rules gave it its colour back and it looked found. That's why it comes after all of
   them. It only lightens a little, and the one that would overcharm you keeps its magenta halo.
   Equipped (is-on) looks like any equipped one: you're wearing it. */
.qc.is-missing:not(.is-on) img { filter: var(--missing-art); }
.qc.is-missing:not(.is-on):not(.is-locked) img { opacity: 1; }
.qc.is-missing:not(.is-on):is(:hover, :focus-visible, .is-sel, .is-hot) img { filter: var(--missing-art) brightness(1.6); }
.qc.is-missing.is-over:not(.is-on):is(:hover, :focus-visible) img { filter: var(--missing-art) brightness(1.6) drop-shadow(0 0 8px var(--over-line)); }
/* The locked grid —halfway through a pantheon outside its benches, or with the Charms
   binding—: all grey and with no hover reaction, so it doesn't look touchable. The click
   does get through, and brings up the notice saying why not. */
.quick.is-locked .quick-grid, :is(.loadout-wrap, .inv-band):has(.quick.is-locked) .eq-row {
  filter: grayscale(0.8); opacity: 0.55;
}
.quick.is-locked .qc, :is(.loadout-wrap, .inv-band):has(.quick.is-locked) .eq { cursor: not-allowed; }
.quick.is-locked .qc:is(:hover, .is-sel) img { transform: none; }
:is(.loadout-wrap, .inv-band):has(.quick.is-locked) .eq:hover img { filter: none; }
.quick.is-locked .quick-hint { color: var(--cond); font-style: italic; }

/* ── The save lock (App.saveLock, js/app.js): a save is read, not changed ──
   Its controls stay as they are, at full light —they say what you have: the pieces, the
   plates, the Hall's rings, the door's bindings—, but disabled: the pointer goes through them
   (nothing reacts, no cursor) and a click on one lands behind and brings the notice (heldClick).
   The charm grid is the exception: it keeps its hover, which drives the detail, so only the
   cursor and its hint say it's locked. The Clear button keeps its own disabled look (hidden). */
.is-held { cursor: default; }
.is-held :disabled:not(.eq-clear) { opacity: 1; pointer-events: none; cursor: default; }
.quick.is-held .qc, :is(.loadout-wrap, .inv-band):has(.quick.is-held) .eq { cursor: default; }
.quick.is-held .quick-hint { color: var(--cond); font-style: italic; }
.pgm-card-state { font-size: var(--fs-xs); color: var(--muted); }

/* Spells and arts, as plates: the artwork on black, frameless, with its light behind it;
   white if you've learnt it and off if not, which leaves the artwork as a silhouette. */
.inv-plates { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-6) var(--sp-12); margin-top: var(--sp-8); padding-top: var(--sp-6); border-top: 1px solid var(--inv-line); }
/* After the charm band, its rule already separates: without the plates' one, which doubled it. */
.inv-band + .inv-plates { border-top: 0; padding-top: 0; }
.plates { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); }
.plate { display: flex; flex-direction: column; align-items: center; gap: var(--sp-half); text-align: center; padding: 0 0 var(--sp-2); min-width: 0; }
.plate-art { position: relative; isolation: isolate; width: 100%; height: 92px; display: grid; place-items: center; margin-bottom: var(--sp-2); }
/* The light behind a plate goes only where you look (round 6, 27-sep), as the game's Inventory
   puts it behind the item under its cursor: under the pointer or the focus, and a moment on what
   just changed. At rest, what you have shows in colour and what you don't in --missing-art. */
.plate-art::before { content: ''; position: absolute; z-index: -1; inset: -8% 2%; background: var(--halo-plate); opacity: 0; }
/* Never stretching the sprite: the 192 px ones are scaled down and the fluke, at 88, stays at its size. */
/* The width is capped too, not only the height: Shade Soul's artwork is three times as wide as it
   is tall (192 × 60) and, fitted by height alone, it came out twice the size of Descending Dark's. */
.plate-art img { width: auto; height: auto; max-width: min(120px, 90%); max-height: 76px; object-fit: contain; }
.plate:not(.is-on) .plate-art::before { background: var(--halo-plate-off); }
.plate:not(.is-on) .plate-art img { filter: var(--missing-art); }
.plate-name { font-size: var(--fs-sm); line-height: 1.25; color: var(--ink); }
.plate-val { font-size: var(--fs-xl); font-weight: 700; line-height: 1.1; color: var(--ink-strong); }
.plate-note { font-size: var(--fs-xs); line-height: 1.3; color: var(--muted); }
.plate:not(.is-on) .plate-name { color: var(--muted); }
button.plate:hover .plate-name, button.plate:focus-visible .plate-name { color: var(--accent); }
/* And the artwork takes the light, while its halo swells a little: a lamp brightening. */
.plate-art::before { transition: opacity var(--dur) var(--ease), filter var(--dur) var(--ease), transform var(--dur) var(--ease); }
.plate-art img { transition: filter var(--dur) var(--ease); }
button.plate:is(:hover, :focus-visible) .plate-art::before { opacity: 1; filter: brightness(1.5); transform: scale(1.08); }
button.plate.is-on:is(:hover, :focus-visible) .plate-art > img { filter: brightness(1.15) drop-shadow(0 0 8px color-mix(in oklab, var(--focus-glow) 45%, transparent)); }

/* Effects: what charms do that doesn't show in the figures, below the spell and art plates. The
   same plates as the spells, with the effect's artwork (or the charm's) and, in the corner, the
   medal of the charm that gives it; if the figure comes from a synergy, both, overlapping. Below,
   when it triggers, in a label. The drawbacks (Joni's Blessing, the fragile ones), in --bad. */
.inv-effects { margin-top: var(--sp-8); }
.inv-effects .block-head { display: flex; align-items: baseline; gap: var(--sp-1) var(--sp-3); flex-wrap: wrap; }
.eff-plates { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: var(--sp-6) var(--sp-4); }
.eff-plates .plate-art { height: 96px; }
.eff-plates .plate-art > img { max-width: min(128px, 86%); max-height: 84px; }
.eff-plates .plate-art .plate-src { position: absolute; right: calc(50% - 62px); bottom: -2px; display: inline-flex; }
.plate-art .plate-src img { width: 28px; height: 28px; max-width: none; max-height: none; object-fit: contain; }
.plate-art .plate-src img + img { margin-left: -10px; }
.plate-when {
  margin-top: var(--sp-1); font-family: var(--font-num); text-transform: uppercase; letter-spacing: var(--track-label);
  font-size: var(--fs-2xs); font-weight: 600; line-height: 1.3; color: var(--label);
}
.plate-note.is-na { font-style: italic; }
.plate-note b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ink-strong); }
.plate.is-bad :is(.plate-name, .plate-note) { color: var(--bad); }

/* Frame for the game's icons. Each family of artwork has its shape:
   charms are square and go in a circle; spells, arts and abilities are landscape (up to
   3.2:1) and call for a rectangle; nails are vertical (1:4.5) and are laid down so their
   edge can be seen. */
.medal {
  width: 46px; height: 46px; flex-shrink: 0; border-radius: 50%; padding: var(--sp-1);
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--medal-off-line); background: var(--medal-off);
  overflow: hidden;
}
.medal img { width: auto; height: auto; max-width: 100%; max-height: 100%; object-fit: contain; }
.medal.sm { width: 34px; height: 34px; padding: var(--sp-1); }


.medal.nail { width: 68px; height: 36px; border-radius: 0; padding: 0; }
.medal.nail img { width: auto; height: 62px; max-width: none; max-height: none; transform: rotate(-90deg); }
.medal.nail.sm { width: 60px; height: 32px; }
.medal.nail.sm img { height: 54px; }

.medal.off img { opacity: 0.4; filter: grayscale(0.8); }

.good { color: var(--good); }
.bad { color: var(--bad); }

/* The "See every stat" button: it stays in place and the full sheet opens below it; when open,
   an identical one closes it from the end. Right under the top one, the sheet doesn't repeat the rule. */
.toggle-all + .detail { border-top: 0; margin-top: 0; padding-top: var(--sp-2); }
.toggle-all { margin-top: var(--sp-6); }

/* ── Full sheet ──────────────────────────────────────────────────────── */
.sheet-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-6); flex-wrap: wrap; margin-bottom: var(--sp-6); }
.sheet-title { display: flex; align-items: baseline; gap: var(--sp-4); flex-wrap: wrap; }
.sheet-title h2 { font-size: var(--fs-xs); color: var(--ink-2); }
.hint { font-size: var(--fs-xs); color: var(--muted); font-style: italic; }
.compare { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

/* Two flowing columns: with grid, a long group left gaps next to a short one. */
.groups { columns: 2; column-gap: var(--sp-8); }
.group { break-inside: avoid; margin-bottom: var(--sp-6); }
.group h3 { padding-bottom: var(--sp-2); border-bottom: 1px solid var(--line); margin-bottom: var(--sp-half); }

.stat { border-bottom: 1px solid var(--line-row); padding: var(--sp-2) var(--sp-half); cursor: pointer; }
.stat:hover { background: var(--accent-soft); }
.stat-row { display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: var(--sp-2) var(--sp-4); }
.stat-label { font-size: var(--fs-sm); line-height: 1.3; color: var(--ink-2); }
.stat-value { font-size: var(--fs-md); font-weight: 600; color: var(--ink-strong); display: inline-flex; align-items: baseline; justify-content: flex-end; gap: var(--sp-2); }
.stat-reason { grid-column: 1 / -1; font-size: var(--fs-xs); color: var(--muted); font-style: italic; }
.stat.is-na .stat-label, .stat.is-na .stat-value { color: var(--muted); }
.stat.is-hit { background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); }
.stat.is-flash { animation: flash 1.1s ease; }
@keyframes flash { 0%, 40% { background: var(--accent-soft); } 100% { background: transparent; } }

.delta { font-size: var(--fs-xs); font-weight: 500; padding: 1px var(--sp-2); border: 1px solid var(--line); color: var(--muted); }
.delta.good { color: var(--good); border-color: var(--good); background: var(--good-soft); }
.delta.bad { color: var(--bad); border-color: var(--bad); background: var(--bad-soft); }

.chips { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); margin-top: var(--sp-half); }
.chip { display: inline-flex; align-items: center; gap: var(--sp-1); font-size: var(--fs-2xs); color: var(--muted); }
.chip b { color: var(--ink-2); font-weight: 600; }
/* The charm chip is data: in ink, like the rest. Only on hover (it lights up on the grid the
   charm that moves it) does it take the accent. */
.chip.chip-charm { color: var(--ink-2); }
.chip.chip-charm b { color: var(--ink-strong); }
.chip.chip-charm:hover { color: var(--accent); }
.chip.syn { color: var(--cond); }
.chip.off { opacity: 0.55; font-style: italic; }
.chip.more { font-style: italic; }
.chip.more::before { content: none; }
.chip i { font-style: italic; }

.stat-detail { display: none; padding: var(--sp-3) 0 var(--sp-1); }
.stat.is-open .stat-detail { display: block; }
.stat-detail h4 { margin: var(--sp-2) 0 var(--sp-1); }
.stat-detail ul { border-top: 1px solid var(--line-row); }
.stat-detail li { display: flex; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-1) 0; border-bottom: 1px solid var(--line-row); font-size: var(--fs-xs); }
.stat-detail li.off { opacity: 0.6; font-style: italic; }
.stat-detail .k { color: var(--muted); }
.stat-detail .v { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.stat-detail .note { font-size: var(--fs-xs); color: var(--muted); margin-top: var(--sp-2); font-style: italic; }

/* What your charms give you */
.fx-wrap { border-top: 1px solid var(--line); padding-top: var(--sp-6); }
.fx { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-2); }
.fx-item { display: flex; align-items: center; gap: var(--sp-3); background: var(--card); border: 1px solid var(--line-card); padding: var(--sp-3) var(--sp-3); min-width: 0; }
.fx-item > span:last-child { display: flex; flex-direction: column; gap: var(--sp-half); min-width: 0; }
.fx-name { font-size: var(--fs-sm); line-height: 1.25; color: var(--ink); }
.fx-note { font-size: var(--fs-xs); line-height: 1.3; color: var(--muted); }



/* When you overcharm, the screen is tinted purple at the edges and it goes: it jumps in with
   the flash and fades, instead of staying the whole time, which was tiring. That you're still
   overcharmed is already said by the notice, the magenta notches and the HUD's aura. It sits on
   top of the content but transparent in the centre, so it doesn't touch the numbers; without
   motion it doesn't show. */
.page::after {
  content: ''; position: fixed; inset: 0; z-index: 40; pointer-events: none;
  background: radial-gradient(ellipse 92% 74% at 50% 50%, transparent 38%, var(--over-ambient-soft) 70%, var(--over-ambient) 100%);
  opacity: 0;
}
/* It comes in fast with the flash (~200 ms), holds for half a second and fades out slowly
   (~2 s, soft fade on both sides), like the game's fades. */
:root.is-overcharming .page::after { animation: overcharm-ambient 2600ms linear forwards; }
@keyframes overcharm-ambient {
  0%   { opacity: 0; animation-timing-function: var(--ease); }
  8%   { opacity: 1; }
  25%  { opacity: 1; animation-timing-function: var(--ease-fade); }
  100% { opacity: 0; }
}

/* ── Overcharm: the game's flash and shake ───────────────────────────────
   In Hollow Knight, going over your notches lights the screen up in white and violet and
   shakes it. It fires only once, when crossing the threshold (js/app.js → overcharmFx). */
.overcharm-fx {
  position: fixed; inset: 0; z-index: 90; pointer-events: none; opacity: 0;
  background:
    radial-gradient(circle at 50% 44%, var(--overcharm-core) 0%, var(--overcharm-glow) 16%, var(--overcharm-edge) 36%, transparent 66%),
    radial-gradient(circle at 50% 44%, transparent 52%, var(--overcharm-edge) 100%);
}
.overcharm-fx.is-on { animation: overcharm-flash 780ms cubic-bezier(0.16, 0.8, 0.3, 1) forwards; }
/* All four bindings at once: the same flash and the same shake, in gold (bindAllFx). */
.overcharm-fx.is-gold {
  background:
    radial-gradient(circle at 50% 44%, var(--bindall-core) 0%, var(--bindall-glow) 16%, var(--bindall-edge) 36%, transparent 66%),
    radial-gradient(circle at 50% 44%, transparent 52%, var(--bindall-edge) 100%);
}
:root.is-binding-all .binds.is-all .bind-art { animation: bindall-pip 420ms ease-out 3 alternate; }
@keyframes bindall-pip {
  from { filter: sepia(0.35) saturate(0.9) brightness(1.9) drop-shadow(0 0 6px var(--godhome-glow)) drop-shadow(0 0 14px var(--godhome-glow)); }
  to   { filter: sepia(0.35) saturate(0.9) brightness(2.3) drop-shadow(0 0 10px var(--godhome-glow)) drop-shadow(0 0 26px var(--godhome-glow)); }
}
@keyframes overcharm-flash {
  0%   { opacity: 0;    transform: scale(0.55); }
  8%   { opacity: 1;    transform: scale(1); }
  30%  { opacity: 0.45; transform: scale(1.1); }
  100% { opacity: 0;    transform: scale(1.32); }
}

/* The shake goes on .page: the floating notice lives outside it and doesn't move. */
.page.is-quaking { animation: overcharm-quake 480ms ease-in-out; }
@keyframes overcharm-quake {
  0%, 100% { transform: translate3d(0, 0, 0); }
  9%  { transform: translate3d(-6px, 4px, 0); }
  21% { transform: translate3d(6px, -5px, 0); }
  33% { transform: translate3d(-5px, -3px, 0); }
  45% { transform: translate3d(5px, 3px, 0); }
  57% { transform: translate3d(-3px, 2px, 0); }
  69% { transform: translate3d(3px, -2px, 0); }
  85% { transform: translate3d(-2px, 1px, 0); }
}

/* While the hit lasts: the notice comes in and the notches you went over pulse. */
:root.is-overcharming .banner:not(.is-run) { animation: overcharm-banner 520ms ease-out; }
@keyframes overcharm-banner {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}
:root.is-overcharming .notch.is-over { animation: overcharm-pip 420ms ease-out 3 alternate; }
@keyframes overcharm-pip {
  from { filter: drop-shadow(0 0 4px var(--notch-over-glow)) drop-shadow(0 0 12px var(--notch-over-glow)); }
  to   { filter: drop-shadow(0 0 7px var(--notch-over-glow)) drop-shadow(0 0 22px var(--notch-over-glow)); }
}

/* ── The mini-bar, in the screen bar ───────────────────────────────────
   With the game's sprites instead of labels: the nail, the mask, soul and a notch.
   Its figures flash like the sheet's. In combat, those of the build you're fighting with.
   At the bar's right, in the tabs' row: the eight tabs take up to 806 px in Spanish and this
   about 300 (the page's column is 1152), so it fits from 1240 px; below that, the second row. */
.minihud { display: flex; gap: var(--sp-3); align-items: center; }
.minihud span { display: flex; align-items: center; gap: calc(var(--sp-1) + var(--sp-half)); min-width: 0; }
.minihud b { font-size: var(--fs-lg); font-weight: 600; color: var(--ink-strong); }
.minihud em { font-style: normal; font-size: var(--fs-xs); color: var(--lifeblood-line); }
.minihud .is-over b { color: var(--over-ink); }
.mini-nail { height: 28px; width: auto; }
.mini-mask { height: 22px; width: auto; }
.mini-soul { width: 20px; height: 20px; }
.mini-lbl { font-family: var(--font-num); font-style: normal; font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-label); color: var(--label); }
.minihud .notch { width: 16px; height: 16px; }
/* The fight's scoreboard (.is-fight): your masks and your soul, and the name, bar and health of
   whoever you're hitting. The bar is the one from their card (.fbar); here it stretches over what
   the name leaves. It stays in the tabs' row: it takes what the tabs leave (the name shortens,
   the bar gives way down to its minimum), so the bar keeps one height on Combat as on Charms. */
.mini-of { font-family: var(--font-num); font-style: normal; font-size: var(--fs-xs); color: var(--muted); margin-left: calc(var(--sp-1) * -1); }
.minihud.is-fight { flex: 1 1 200px; min-width: 0; justify-content: flex-end; }
.minihud .mini-me { flex: none; }
.minihud .mini-foe { flex: 1 1 320px; min-width: 0; gap: var(--sp-2); }
.minihud .mini-foe-name { display: block; min-width: 0; max-width: 18ch; font-size: var(--fs-sm); color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.minihud .mini-foe .fbar { display: block; flex: 1 1 64px; min-width: 40px; }
.minihud .mini-foe .fbar span { display: block; }

/* ── Your game ───────────────────────────────────────────────────────────
   Another page of the Inventory: the sheet's black with its corner brackets in the tint, and
   no boxes inside. What can be touched is marked by the accent; what's read goes in bone. */
.gear { display: flex; flex-direction: column; }
/* The two starting points, under the title: text in the screen's ink, unboxed. */
.presets { display: flex; justify-content: center; align-items: baseline; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); margin-top: var(--sp-2); }
.presets .lbl { color: var(--muted); }
.presets-sep { color: var(--muted); }
.gear-body > .inv-head { grid-column: 1 / -1; margin-bottom: 0; }
.gear-body {
  --inv-pad: var(--sp-8);
  --inv-line: color-mix(in oklab, var(--tint) 13%, transparent);
  position: relative; background: var(--inv-bg); padding: var(--inv-pad);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-8) var(--sp-12); align-items: start;
}
/* Two independent columns (js/app-game.js, renderGear), each block under the previous one with a
   rule between them; in one column, the page's order: nail, body, arts, spells, abilities, charms. */
.block { min-width: 0; }
.gear-col { display: flex; flex-direction: column; gap: var(--sp-6); min-width: 0; }
.gear-col > section.block + section.block { border-top: 1px solid var(--inv-line); padding-top: var(--sp-6); }
.gear-body .block-head { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.block-note { font-family: var(--font-body); font-size: var(--fs-sm); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted); }

/* The nail, the screen's focal point, at the head of the left column (js/app-game.js,
   renderNailBlock): the picker itself. The one you carry stands taller, with the spotlight and its
   damage larger; the others sit on the same baseline in half-light. Under the row, its name. */
.gear-hero .nailpicks { align-items: end; }
.gear-hero .nailpick-art { align-items: flex-end; }
.gear-hero .nailpick.is-on .nailpick-art { height: 140px; }
.gear-hero .nailpick.is-on .nailpick-num { font-size: var(--fs-xl); line-height: 1; }
.gh-caption { margin: var(--sp-2) 0 0; text-align: center; color: var(--ink); }
/* The nail: all five upright, with their damage below. The one you carry, with the arena's cold
   spotlight behind it and the damage in bone; the others, in half-light. */
.nailpicks { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--sp-1); }
.nailpick { position: relative; isolation: isolate; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); padding: var(--sp-2) 0 var(--sp-1); min-width: 0; }
.nailpick::before {
  content: ''; position: absolute; z-index: -1; inset: -6% -14% 18%;
  background: radial-gradient(closest-side, var(--scene-glow), transparent);
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.nailpick.is-on::before { opacity: 1; }
.nailpick-art { height: 104px; display: flex; justify-content: center; }
.nailpick-art img { height: 100%; width: auto; max-width: none; opacity: 0.38; filter: grayscale(0.5); transition: opacity var(--dur) var(--ease), filter var(--dur) var(--ease); }
.nailpick.is-on .nailpick-art img { opacity: 1; filter: none; }
.nailpick:hover .nailpick-art img, .nailpick:focus-visible .nailpick-art img { opacity: 0.85; filter: none; }
.nailpick-num { font-size: var(--fs-lg); font-weight: 700; line-height: 1.1; color: var(--muted); padding-bottom: var(--sp-1); border-bottom: 2px solid transparent; }
.nailpick.is-on .nailpick-num { color: var(--ink-strong); border-bottom-color: var(--accent-line); }
.nailpick:hover .nailpick-num, .nailpick:focus-visible .nailpick-num { color: var(--accent); }

/* Arts and spells: small plates, with the white light if you've learnt it and as a silhouette
   if not. The three in a block share rows (subgrid): a name that breaks onto two lines doesn't
   throw off the neighbours' figure or selector. */
.gplates { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-1) var(--sp-3); }
.gplate { display: grid; grid-row: span 3; grid-template-rows: subgrid; row-gap: var(--sp-1); justify-items: center; align-items: start; text-align: center; min-width: 0; padding: 0; }
.gplate:has(.lvlpick) { grid-row: span 4; }
.gplate-art { position: relative; isolation: isolate; width: 100%; height: 60px; display: grid; place-items: center; }
.gplate-art::before { content: ''; position: absolute; z-index: -1; inset: -8% 2%; background: var(--halo-plate); opacity: 0; transition: opacity var(--dur) var(--ease), filter var(--dur) var(--ease); }
.gplate-art img { width: auto; height: auto; max-width: min(100px, 92%); max-height: 50px; object-fit: contain; }
.gplate:not(.is-on) .gplate-art::before { background: var(--halo-plate-off); }
.gplate:not(.is-on) .gplate-art img { filter: var(--missing-art); }
.gplate-name { font-size: var(--fs-xs); line-height: 1.25; color: var(--ink-2); }
.gplate.is-on .gplate-name { color: var(--ink); }
.gplate-val { font-size: var(--fs-md); font-weight: 700; line-height: 1.25; color: var(--ink-strong); }
.gplate-val.is-none { font-family: var(--font-body); font-size: var(--fs-xs); font-weight: 400; color: var(--muted); }
button.gplate:hover .gplate-name, button.gplate:focus-visible .gplate-name { color: var(--accent); }
button.gplate:is(:hover, :focus-visible) .gplate-art::before, .gplate:has(.lvlpick button:is(:hover, :focus-visible)) .gplate-art::before { opacity: 1; }
/* The level under a plate: each level's own artwork, small, and the dimmed "not learnt" first.
   The chosen one with the accent's veil and rule, like the nail picks; the rest in half-light. */
.lvlpick { display: flex; justify-content: center; gap: var(--sp-1); width: 100%; min-width: 0; margin-top: var(--sp-1); }
.lvl {
  flex: 0 1 56px; min-width: 0;
  /* A row of definite height, so an upright picture (the Awoken Dream Nail) is held by max-height. */
  display: grid; grid-template-rows: minmax(0, 1fr); place-items: center; height: 40px; padding: var(--sp-1);
  border-bottom: 1px solid color-mix(in oklab, var(--accent-line) 40%, transparent); color: var(--muted);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.lvl img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; filter: grayscale(1) brightness(0.55); transition: filter var(--dur) var(--ease); }
.lvl:hover img, .lvl:focus-visible img { filter: grayscale(0.4) brightness(0.9); }
/* A level is a choose-one (.seg, round 6): each on its faint line, the chosen one on the accent's line, doubled. */
.lvl:hover, .lvl:focus-visible { color: var(--accent); border-bottom-color: var(--accent-line); }
.lvl.is-on { border-bottom-color: var(--accent); box-shadow: 0 1px 0 var(--accent); color: var(--ink-strong); }
.lvl.is-on img { filter: none; }
.lvl.is-none { flex: 0 0 24px; font-size: var(--fs-md); }
.lvlpick.is-dots { gap: 0; }
.lvlpick.is-dots .sd-dot small { color: var(--ink-2); text-decoration: none; }
.lvlpick.is-dots .sd-dot:disabled { opacity: 0.4; }
/* Charms found: the Charms grid (.quick-grid), but what you have shows whole, undimmed, and
   what you don't, shadowed (.qc.is-missing, further up). */
.own-all { margin-left: auto; display: flex; align-items: baseline; gap: var(--sp-2); }
.own-all .text-btn { font-size: var(--fs-sm); }
.quick-owned .qc:not(.is-missing) img { opacity: 1; filter: none; }
/* The double slots: one whole slot that switches version on each tap, and two dots under it,
   the marked one filled in bone. With none, two empty rings. */
.qc-pips {
  position: absolute; left: 50%; bottom: calc(var(--qc) * -0.1); transform: translateX(-50%);
  display: flex; gap: var(--sp-1); pointer-events: none;
}
.qc-pip { width: 5px; height: 5px; border-radius: 50%; border: 1px solid var(--muted); }
.qc-pip.is-on { background: var(--ink); border-color: var(--ink); }

/* Body: its name and range with the figure opposite, and below it the game's pieces to tap —the
   masks, the vessels, the notches—, lit up to what you have and shadowed after, like the HUD's
   lost mask. The base ones can't be removed. */
.field { display: flex; flex-direction: column; gap: var(--sp-1); }
.field + .field { margin-top: var(--sp-4); }
.field-row { display: flex; align-items: baseline; gap: var(--sp-3); }
.field-num { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); font-weight: 700; line-height: 1; color: var(--ink-strong); }
.field-num .u { font-size: var(--fs-xs); font-style: normal; font-weight: 400; color: var(--muted); }
.pieces { display: grid; grid-template-columns: repeat(var(--n), minmax(0, 38px)); align-items: center; }
.piece {
  display: grid; place-items: center; width: 100%; height: 38px; padding: var(--sp-1);
  border-bottom: 2px solid transparent; transition: border-color var(--dur) var(--ease);
}
.piece img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease); }
.piece:not(.is-on) img { filter: brightness(0.2) saturate(0); opacity: 0.85; }
.piece .notch { width: 20px; height: 20px; }
/* The keyboard's focus keeps its rule; the mouse gets no line: it gets a preview up to the piece
   under the pointer, like the notches on Charms. What a click would add comes up to half light
   (the free notch, as a pending ring); what it would take away —a lit one under the pointer and
   every lit one after it— goes dim. */
.piece:focus-visible { border-bottom-color: var(--accent-line); }
.piece .notch { transition: opacity var(--dur) var(--ease); }
@media (hover: hover) {
  .piece:not(.is-on):is(:hover, :has(~ .piece:hover)):not(:disabled) img { filter: brightness(0.7) saturate(0.4); opacity: 1; }
  .piece:not(.is-on):is(:hover, :has(~ .piece:hover)):not(:disabled) .notch.is-free::before { border-color: var(--notch-used); background: var(--notch-pending-fill); }
  .piece.is-on:hover:not(:disabled) > *, .piece.is-on:hover:not(:disabled) ~ .piece.is-on > * { opacity: 0.35; }
}
.piece.is-base { cursor: default; }
.piece.is-base:disabled { opacity: 1; }
.pieces.is-vessels { grid-template-columns: repeat(var(--n), minmax(0, 46px)); }
.pieces.is-vessels .piece { height: 44px; }
.field-text { display: flex; flex-direction: column; flex-grow: 1; min-width: 0; }
.field-name { font-size: var(--fs-sm); line-height: 1.3; color: var(--ink); }
.field-note { font-size: var(--fs-xs); line-height: 1.3; color: var(--muted); }
/* The loose mask shards and vessel fragments, under their row: the same pieces, smaller. */
.field.is-shards { margin-top: var(--sp-2); }
.pieces.is-shards, .pieces.is-fragments { grid-template-columns: repeat(var(--n), minmax(0, 30px)); }
.piece.is-shard { height: 32px; }
.field.is-shards .field-num { font-size: var(--fs-md); }

/* Your game's items (js/app-game.js): the key items as plates, and under them what you carry,
   each with its [− N +]. The geo plate carries Millibelle's bank in a note. */
.gplates + .gplates { margin-top: var(--sp-6); }
.gcount { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); }
.gcount-note { font-size: var(--fs-xs); line-height: 1.25; color: var(--muted); }
.step-count.is-wide { width: 8ch; }
/* On a phone, two to a row: the [− N +] with touch-sized buttons doesn't fit in a third. */
@media (max-width: 599px) { .gplates.is-counts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.gplate.is-count.is-lit .step-count { animation: num-flash var(--dur-flash) var(--ease); }
@media (pointer: coarse) { .piece { min-height: 44px; } }

/* ── Your game, right after a change (js/app-game.js, App.was) ─────────────
   What you just got lights up from the dark with a burst of Focus's white and settles; what you
   just lost goes out from its light. Several pieces at once go one after another (--i), like the
   masks on the HUD. The nail you pick takes the spotlight with a fade, and the one you leave lets it go. */
@keyframes lit-in {
  from { filter: brightness(0.2) saturate(0); }
  40% { filter: brightness(1.8) drop-shadow(0 0 8px var(--focus-glow)); }
}
@keyframes lit-out { from { filter: brightness(1.4) drop-shadow(0 0 6px var(--focus-glow)); opacity: 1; } }
@keyframes glow-out { from { opacity: 1; } }
.piece.is-lit > * { animation: lit-in var(--dur-flash) var(--ease) both; animation-delay: calc(var(--i) * 70ms); }
.piece.is-out > * { animation: lit-out var(--dur-slow) var(--ease) both; animation-delay: calc(var(--i) * 70ms); }
.nailpick.is-lit::before { animation: title-in var(--dur-slow) var(--ease-fade); }
.nailpick.is-lit .nailpick-art img { animation: lit-in var(--dur-flash) var(--ease); }
.nailpick.is-out::before { animation: glow-out var(--dur-slow) var(--ease-fade); }
.nailpick.is-out .nailpick-art img { animation: lit-out var(--dur-slow) var(--ease); }
:is(.gplate.is-lit .gplate-art, .plate.is-flash .plate-art)::before { animation: halo-flash var(--dur-flash) var(--ease-fade); }
@keyframes halo-flash { 0% { opacity: 0; } 25% { opacity: 1; } 100% { opacity: 0; } }
.gplate.is-lit .gplate-art img { animation: lit-in var(--dur-flash) var(--ease); }
.gplate.is-out .gplate-art img { animation: lit-out var(--dur-slow) var(--ease); }
.sd-dot.is-lit .notch { animation: notch-fill var(--dur-slow) var(--ease) both; animation-delay: calc(var(--i) * 70ms); }
.preview { display: block; font-size: var(--fs-xs); color: var(--muted); min-height: 1.2em; text-wrap: pretty; }
/* The condition, in front and in grey; what would change, in normal ink. Green is kept for
   what you've already gained: a hypothesis isn't an advantage. */
.preview i { font-style: normal; color: var(--muted); }
.preview b { font-weight: 600; color: var(--ink-2); }
.preview b.bad { color: var(--bad); }

/* ── The charm's detail, next to the grid ─────────────────────────────────
   What the floating inspector did, now neither floating nor boxed: the name with its medal, its
   cost in notch points and the button on the same row; the game's line, a status line and what
   changes, before → after. It's always the same size —also with no charm chosen— so the band
   doesn't grow or shrink on hover: each piece has its line cap and the list its own
   (DETAIL_ROWS in js/app.js). */
.charm-detail { min-width: 0; height: 340px; overflow: hidden; padding-top: var(--sp-4); border-top: 1px solid var(--inv-line); }
/* Another charm in the detail (paintDetail): it fades into it, short, so crossing the grid doesn't lag. */
.charm-detail.is-swap > * { animation: detail-in var(--dur) var(--ease); }
@keyframes detail-in { from { opacity: 0.35; } }
.detail-hint:not(.empty) { font-size: var(--fs-sm); line-height: 1.5; color: var(--muted); font-style: italic; max-width: 40ch; }
.detail-sum + .detail-hint { margin-top: var(--sp-3); font-size: var(--fs-xs); }
.insp-head { display: flex; align-items: center; gap: var(--sp-3); }
.insp-id { flex: 1 1 auto; min-width: 0; }
.insp-name { font-size: var(--fs-lg); line-height: 1.25; color: var(--ink-strong); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.insp-notch { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-half); font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; overflow: hidden; }
.insp-en { font-style: italic; overflow: hidden; text-overflow: ellipsis; }
.detail-cost { display: inline-flex; flex: none; gap: var(--sp-1); }
.detail-cost .notch { width: 16px; height: 16px; }
.insp-blurb {
  font-size: var(--fs-sm); line-height: 1.45; color: var(--ink-2); margin: var(--sp-3) 0 var(--sp-2);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.insp-state { font-size: var(--fs-xs); line-height: 1.4; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.insp-state.bad { color: var(--bad); }
/* Not found and chosen: the button that marks it found, under the state line. */
.insp-own { margin-top: var(--sp-2); }
.insp-state.over { color: var(--over-ink); }
.insp-state.cond { color: var(--cond); }
.charm-detail h3 { margin: var(--sp-3) 0 var(--sp-1); }
.insp-list li { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-1) 0; border-bottom: 1px solid var(--line-row); font-size: var(--fs-xs); line-height: 1.35; }
.insp-list .lbl { min-width: 0; font-family: var(--font-body); font-size: var(--fs-xs); letter-spacing: 0; text-transform: none; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.insp-list .with { font-style: italic; color: var(--muted); }
.insp-list .vals { flex: none; display: inline-flex; align-items: baseline; gap: var(--sp-1); font-variant-numeric: tabular-nums; }
.insp-list .from { color: var(--muted); }
.insp-list .arrow { color: var(--muted); }
.insp-list .to { color: var(--ink); font-weight: 600; }
.insp-more button { padding: 0; font: inherit; font-style: italic; color: var(--accent); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--accent-line); }
.insp-more button:hover { color: var(--ink-strong); }

/* ── Floating notice ─────────────────────────────────────────────────── */
/* The notices (toast in js/app.js), like the game's on-screen messages, just under the screen bar
   (where the Journal's notice goes; js/app.js sets its top): no card, the text in the
   game's face between two short rules with their diamond, over a soft dark veil so it reads on
   anything; it fades in, holds and fades out, with no slide (design/02 §5). */
.toast {
  position: fixed; left: 50%; top: calc(var(--sticky-h) + var(--sp-3)); transform: translateX(-50%); z-index: 80; isolation: isolate;
  display: flex; align-items: center; justify-content: center; gap: var(--sp-3);
  width: max-content; max-width: min(640px, calc(100vw - var(--sp-8))); padding: var(--sp-3) var(--sp-6);
  font-family: var(--font-display); font-size: var(--fs-lg); font-weight: 600; letter-spacing: var(--track-title); line-height: 1.3;
  color: var(--ink-strong); text-align: center; pointer-events: none;
  opacity: 0; transition: opacity var(--dur-slow) var(--ease-fade);
}
.toast.is-on { opacity: 1; }
.toast::before {
  content: ''; position: absolute; z-index: -1; inset: -35% -12% -70%;
  background: radial-gradient(closest-side, color-mix(in oklab, var(--scene-bg) 96%, transparent) 64%, transparent);
}
.toast-rule { flex: none; color: var(--ink-2); }
.toast-t { min-width: 0; text-wrap: balance; }
/* The figures, outside Cinzel (tokens.css, decision 3). */
.toast-num { font-family: var(--font-num); font-weight: 600; letter-spacing: 0; font-variant-numeric: tabular-nums; }
/* A Journal entry's notice carries its medallion, as the game's does. */
.toast-art { flex: none; width: 42px; height: 48px; object-fit: contain; }
@media (max-width: 599px) {
  .toast { gap: var(--sp-2); padding-inline: var(--sp-2); font-size: var(--fs-md); }
  .toast-rule { width: var(--sp-6); }
  .toast-art { width: 35px; height: 40px; }
}

/* ── Responsive ──────────────────────────────────────────────────────── */
@media (max-width: 1099px) {
  /* No room for the two columns: the grid moves below and gets its size back. */
  .loadout-wrap { grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); }
    .groups { columns: 1; }
  .fx { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 899px) {
  /* The title, the lead paragraph and the large figures already scale by themselves with clamp():
     three sets of sizes, one per breakpoint, aren't needed. */
  :root { --page-pad: var(--sp-4); --sticky-h: 100px; }
  .shell { gap: var(--sp-6); }
  /* The bar in two rows: the tabs and, below, the mini-bar across the full width. */
  .nav { flex-direction: column; align-items: stretch; gap: 0; }
  .minihud { justify-content: space-between; gap: var(--sp-3); padding: var(--sp-2) 0; border-top: 1px solid var(--line-row); }
  .minihud.is-fight { flex: none; justify-content: space-between; }
  .minihud .mini-foe { flex: 1 1 auto; }
  .panel { --inv-pad: var(--sp-6); }
  /* Your game in one column, with the rule between each block. */
  .gear-body { --inv-pad: var(--sp-6); grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); }
  .gear-col { display: contents; }
  .gear-body section.block { border-top: 1px solid var(--inv-line); padding-top: var(--sp-6); }
  .gear-body section.block.is-nail { order: 1; border-top: 0; padding-top: 0; padding-bottom: 0; border-bottom: 0; }
  .gear-body section.block.is-body { order: 2; }
  .gear-body section.block.is-arts { order: 3; }
  .gear-body section.block.is-spells { order: 4; }
  .gear-body section.block.is-abilities { order: 5; }
  .gear-body section.block.is-equipment { order: 6; }
  .gear-body section.block.is-owned { order: 7; }
  .gear-body section.block.is-items { order: 8; }
}
@media (max-width: 599px) {
  .panel, .gear-body { --inv-pad: var(--sp-4); }
  .fx { grid-template-columns: minmax(0, 1fr); }
  /* The header in a single row, so what matters starts sooner on every screen: the title on the
     left, smaller and without its filigree, and on the right the Knight, the language and Share
     (the title goes first and the site's two after the selector: order). */
  .masthead { display: flex; flex-wrap: nowrap; justify-content: space-between; align-items: center; gap: var(--sp-2); padding-top: var(--sp-3); }
  .brand, .masthead .brand { display: flex; flex: 1 1 auto; align-items: flex-start; order: -1; }
  .masthead .mh-tools { order: 1; }
  .brand .mh-hdr { display: none; }
  .title { font-size: var(--fs-lg); text-align: left; letter-spacing: var(--track-title); }
  .masthead .mh-tools { flex: none; flex-wrap: nowrap; --tools-gap: var(--sp-3); }
  /* The two starting points: the label above, and both on one line. */
  .presets .lbl { flex: 1 1 100%; text-align: center; }
  /* The bar, on mobile: the four tabs spread out (the active one underlined: the diamond doesn't
     fit) and without the Journal's book, which leaves room for its name with your completed entries. */
  :root { --sticky-h: 100px; }
  /* Each tab as wide as its word (a long one, «Inventario», doesn't break), the free space between them. */
  .nav-tabs { display: grid; grid-template-columns: repeat(6, auto); justify-content: space-between; gap: 0 var(--sp-2); }
  .nav-tab { flex-direction: column; justify-content: center; gap: 0; min-height: 48px; font-size: var(--fs-md); text-align: center; }
  /* No gap beside a tab here: no Knight (js/app-knight.js); the rule marks the tab. */
  .nav-tabs > .kn { display: none; }
}
/* The eight tabs and the mini-bar (the build's figures or the fight's scoreboard) don't fit side
   by side below 1240 px: the mini-bar goes to a second row, across the full width, as on a phone. */
.nav-num { white-space: nowrap; }
@media (min-width: 900px) and (max-width: 1239px) {
  .nav:has(#minihud:not([hidden])) { flex-direction: column; align-items: stretch; gap: 0; }
  .nav:has(#minihud:not([hidden])) .minihud { flex: none; justify-content: flex-end; padding: var(--sp-2) 0; border-top: 1px solid var(--line-row); }
  :root:has(#minihud:not([hidden])) { --sticky-h: 110px; }
}
@media (min-width: 900px) {
  .nav-tab { white-space: nowrap; }
}
/* In the tabs' row the mini-bar keeps to the right, also if it ever wraps under them (the net above). */
@media (min-width: 1240px) {
  .minihud { margin-left: auto; }
}
/* Seven tabs don't fit below 900 px: the tools fold into one, Tools, and while you're in one a
   second row switches between Charms and Combat. */
@media (max-width: 899px) {
  .nav-tabs { gap: var(--sp-1) var(--sp-4); }
  .nav-tool, .nav-sep, .nav-inv { display: none; }
  /* The current tab's rule, on the bar's bottom edge: the Knight isn't on this bar to mark it. */
  .nav-tab[aria-current="page"], .nav-home.is-parent { color: var(--ink-strong); box-shadow: inset 0 -2px 0 var(--accent); }
  .nav-tools { display: inline-flex; }
  .nav-sub:not([hidden]) { display: flex; justify-content: center; gap: var(--sp-8); border-top: 1px solid var(--line-row); }
  .nav-sub .nav-tab { min-height: var(--ctl); font-size: var(--fs-md); }
  /* The bar's height follows its rows: the tabs alone on your game's screens, and the tools'
     second row and the mini-bar with the tools. */
  :root:has(#minihud[hidden]) { --sticky-h: 60px; }
  :root:has(#nav-sub:not([hidden])) { --sticky-h: 140px; }
  :root:has(#nav-sub-game:not([hidden])) { --sticky-h: 100px; }
}
@media (max-width: 599px) {
  /* Six tabs in 358 px: one step smaller. */
  .nav-tabs { gap: 0 var(--sp-1); }
  .nav-tabs > .nav-tab { font-size: var(--fs-sm); letter-spacing: 0; }
  .nav-long { display: none; }
  .nav-short { display: inline; }
}
/* The sheet arranges itself by its own width (container inv), not the window's. */
/* No room for the row: the HUD and the damage on top, and the six below, after a rule. */
@container inv (max-width: 1039px) {
  .inv-status { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); grid-template-areas: "hud lead" "stats stats"; }
  .inv-stats { grid-template-columns: repeat(6, minmax(0, 1fr)); padding: var(--sp-6) 0 0; border-left: 0; border-top: 1px solid var(--inv-line); }
}
@container inv (max-width: 899px) {
  /* The status in three rows: the HUD, the nail damage and the six in two rows of three. */
  .inv-status { grid-template-columns: minmax(0, 1fr); grid-template-areas: "hud" "lead" "stats"; }
  .inv-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-6); }
  /* The band in one column: Equipped, Notches, the grid and the detail BELOW it, so as not to
     push it under the finger. The columns dissolve and each piece takes its place. */
  .inv-band { display: flex; flex-direction: column; align-items: stretch; }
  .band-col { display: contents; }
  .inv-band .is-eq { order: 1; }
  .inv-band .is-notches { order: 2; }
  .inv-band .quick { order: 3; }
  .inv-band .charm-detail { order: 4; }
}
@container inv (max-width: 559px) {
  /* Nine 24.8 px masks fit in a row next to the 64 px orb: not a single slot below finger
     size. */
  .panel .hud { --orb: 64px; --pip-h: 32px; --hud-row: 248px; column-gap: var(--sp-2); }
  .panel .hud-masks { gap: var(--sp-1) var(--sp-half); }
  .inv-status { --nail-h: 120px; }
  .inv-lead { gap: var(--sp-4); }
  .inv-nail { width: 40px; }
  .inv-nail::before { width: 140px; height: 200px; }
  .inv-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-6) var(--sp-4); }
  .inv-plates { grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); }
  .plates { gap: var(--sp-2); }
  .plate-art { height: 64px; }
  .plate-art img { max-height: 54px; max-width: 94%; }
  .plate-name { font-size: var(--fs-xs); }
  .plate-val { font-size: var(--fs-lg); }
  .eff-plates { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-6) var(--sp-2); }
  .eff-plates .plate-art { height: 76px; }
  .eff-plates .plate-art > img { max-height: 64px; }
  .inv-head .sec-title { font-size: var(--fs-lg); }
}
/* On mobile the sheet reads in the order it's used: the HUD and the nail, and right after them
   the charms (Equipped, Notches, the grid and its detail); the six figures, afterwards. While you
   scroll down to touch charms, the fixed mini-bar already shows the ones that change most. It's
   the same trick as the band (display: contents + order), one level up: the panel becomes a flex column. */
@media (max-width: 599px) {
  .panel { display: flex; flex-direction: column; }
  .panel > .inv-status { display: contents; }
  .inv-hud { order: 1; }
  .inv-lead { order: 2; margin-top: var(--sp-6); }
  .panel > .inv-band { order: 3; }
  .panel > .inv-status .inv-stats { order: 4; margin-top: var(--sp-8); padding-top: 0; border-top: 0; }
  .panel > .inv-plates, .panel > .inv-effects, .panel > .toggle-all, .panel > .detail { order: 5; }
  .panel > .inv-band + .inv-plates { border-top: 1px solid var(--inv-line); padding-top: var(--sp-6); }
  /* The grid, seven per row and without the staggered layout: each slot ~45 px, finger size.
     At ten per row they measured 27 px. */
  .quick-grid {
    --qc: min(48px, calc((100cqw - 6 * var(--sp-half)) / 7));
    display: grid; grid-template-columns: repeat(7, var(--qc)); justify-content: space-between; gap: var(--sp-2) 0;
  }
  .quick-grid .qrow { display: contents; }
  .quick-grid .qrow:nth-child(even) { margin-left: 0; }
  /* Without a mouse the detail doesn't jump from slot to slot: it's as tall as what it says, without the fixed space. */
  .charm-detail { height: auto; }
  /* The band is a long column here: the eleven notches keep to one row. */
  .inv-band .notches { gap: var(--sp-1); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* ── Combat simulator ──────────────────────────────────────────────────
   Two cards facing each other and two columns of buttons. Each button carries its number in
   front: what you see is your build's arithmetic against the enemy's health, not an
   animation. Its header is its three tabs (fightHead).
   It's another screen of the game, like the Inventory: the same black, with the corner
   brackets in the section's tint (Crystal Peak's; in the Hall and the Pantheons, Godhome's),
   and no slate boxes inside. What can be touched goes without a background —with a thin rule
   if it has no artwork to give it shape—, veils on hover, and the chosen one carries the accent. */
.fight-body {
  --inv-pad: var(--sp-8);
  --inv-line: color-mix(in oklab, var(--tint) 13%, transparent);
  position: relative; background: var(--inv-bg); padding: var(--inv-pad);
  display: flex; flex-direction: column; gap: var(--sp-6);
}
/* Block labels loose on the screen (Pantheon, Bindings) carry their rule behind them,
   like the Your game blocks. */
.fight-body > .block-head { display: flex; align-items: center; gap: var(--sp-3); margin: 0; }
.fight-body > .block-head::after { content: ''; flex: 1; border-top: 1px solid var(--inv-line); }
/* Here and not with the other breakpoints, which come earlier in the file and would lose. */
@media (max-width: 899px) { .fight-body { --inv-pad: var(--sp-6); } }
@media (max-width: 599px) { .fight-body { --inv-pad: var(--sp-4); } }
.fight-tools { display: flex; align-items: flex-end; gap: var(--sp-4) var(--sp-6); flex-wrap: wrap; }
.fight-reset { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--ctl); font-size: var(--fs-sm); }
.fight-reset svg { flex: none; transition: transform var(--dur) var(--ease); }
.fight-reset:hover svg { transform: rotate(-60deg); }
.fight-pick { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; flex: 1 1 300px; }
/* ── The Hunter's Journal: the enemy picker ──────────────────────────────
   Traced from the game's Journal screen (design/02-hollow-knight.md): on the left the list
   with the medallions, on the right the page, on black, with the frameless artwork, the name
   in small caps and the corner brackets.
   It isn't a dropdown but a panel of the combat screen itself: it pushes the arena down
   and closes with its button, with Esc or when picking. */
.jr-toggle:hover, .jr-toggle[aria-expanded="true"] { border-bottom-color: var(--accent-line); }
.jr-val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-lg); color: var(--ink-strong); }
.jr-val.is-empty { color: var(--muted); }
.jr-toggle-act { font-size: var(--fs-sm); color: var(--ink-2); white-space: nowrap; }
.jr-toggle:hover .jr-toggle-act { color: var(--ink-strong); }

/* The Journal medallion (84 × 96: the ring and its two finials). Those without an entry carry
   their portrait in a round well of the same size. */
.jr-medal { width: 35px; height: 40px; flex: none; object-fit: contain; }
.jr-medal.is-art {
  display: grid; place-items: center; overflow: hidden;
  height: 35px; margin: var(--sp-half) 0 3px;
  border-radius: 50%; background: var(--medal); box-shadow: inset 0 0 0 2px var(--medal-line);
}
.jr-medal.is-art img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 20%; }

.journal {
  display: grid; grid-template-columns: minmax(250px, 320px) minmax(0, 1fr);
  height: clamp(540px, 74vh, 660px);
  border: 1px solid var(--inv-line);
}

.jr-index { display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--inv-line); }
.jr-index > .search { border-bottom-color: var(--inv-line); }
.jr-index > .search:hover { border-bottom-color: var(--accent-line); }
.jr-index > .search:focus-within { border-bottom-color: var(--accent); }
.jr-kinds { margin: var(--sp-2); }
/* Each filter's count, inside its own button. */
.jr-kinds i {
  font-family: var(--font-num); font-style: normal; font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xs); color: var(--muted); margin-left: var(--sp-half);
}
.jr-kinds button[aria-pressed="true"] i { color: var(--ink-2); }
.jr-head {
  display: flex; justify-content: space-between; padding: 0 var(--sp-4) var(--sp-1);
  font-family: var(--font-num); text-transform: uppercase; letter-spacing: var(--track-label);
  font-size: var(--fs-2xs); font-weight: 600; color: var(--label);
}

.jr-list { position: relative; flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 0 var(--sp-2) var(--sp-2); }
.jr-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--sp-3);
  width: 100%; text-align: left; padding: var(--sp-half) var(--sp-2);
  font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.25;
  border: 1px solid transparent;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.jr-row:hover { background: var(--veil); color: var(--ink); }
.jr-row.is-cur { background: var(--picked); color: var(--ink-strong); box-shadow: inset 2px 0 0 var(--accent); }
/* Who you're fighting, even while reading another entry: the chosen card's rule. */
.jr-row.is-on { box-shadow: inset 2px 0 0 var(--accent); }
.jr-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jr-hits { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--muted); }
.jr-row.is-cur .jr-hits { color: var(--ink-2); }
/* "No enemy" weighs little: removing the enemy is an option, not the way forward. */
.jr-none {
  margin: 0 var(--sp-2) var(--sp-2); padding: var(--sp-2) var(--sp-3); text-align: left;
  font-size: var(--fs-xs); font-style: italic; color: var(--muted); border-top: 1px solid var(--inv-line);
}
.jr-none:hover { color: var(--ink-2); }

.jr-page {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  min-height: 0; margin: var(--sp-4); padding: var(--sp-4) var(--sp-6);
  text-align: center;
}
.jr-num {
  position: absolute; top: var(--sp-2); right: var(--sp-3);
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xs); letter-spacing: var(--track-label); color: var(--muted);
}
.jr-art { flex: none; height: 170px; width: 100%; display: flex; align-items: flex-end; justify-content: center; }
.jr-art img { max-height: 100%; max-width: 100%; object-fit: contain; }
.jr-art img.is-missing { visibility: hidden; }
.jr-title { font-family: var(--font-display); font-size: var(--fs-xl); font-weight: 600; letter-spacing: var(--track-title); color: var(--ink-strong); margin-top: var(--sp-2); }
.jr-page .rule { flex: none; color: var(--bracket); }
.jr-kind { font-size: var(--fs-xs); color: var(--muted); }
.jr-text {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: var(--sp-3);
  width: 100%; max-width: var(--measure); margin-top: var(--sp-2); padding-right: var(--sp-2);
  text-align: left; line-height: 1.55;
}
.jr-desc { font-size: var(--fs-md); color: var(--ink); }
.jr-notes { font-size: var(--fs-md); color: var(--ink-2); font-style: italic; white-space: pre-line; }
.jr-by { font-size: var(--fs-sm); color: var(--muted); text-align: right; }
.jr-aside { font-size: var(--fs-sm); color: var(--muted); font-style: italic; }
.jr-blank { margin: auto; font-size: var(--fs-sm); color: var(--muted); font-style: italic; }

/* What the calculator adds: always in view, at the foot of the page. */
.jr-foot {
  flex: none; width: 100%; display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--sp-4); flex-wrap: wrap; padding-top: var(--sp-3); border-top: 1px solid var(--inv-line); text-align: left;
}
.jr-facts { display: flex; gap: var(--sp-6); flex-wrap: wrap; }
.jr-fact { display: flex; flex-direction: column; gap: var(--sp-half); }
.jr-fact dt {
  font-family: var(--font-num); text-transform: uppercase; letter-spacing: var(--track-label);
  font-size: var(--fs-2xs); font-weight: 600; color: var(--label);
}
.jr-fact dd { margin: 0; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); font-weight: 600; color: var(--ink-strong); line-height: 1.1; }
.jr-fact small { font-size: var(--fs-xs); color: var(--muted); }

/* On mobile the two columns don't fit, and without a mouse there's no "hover" to read the
   page: only the list remains, and tapping a row picks it. The Journal page does show on the
   Hall's plaque (.hall-plaque.jr-page), which grows with its text instead of scrolling. */
@media (max-width: 699px) {
  .journal { grid-template-columns: minmax(0, 1fr); height: auto; }
  .jr-index { border-right: 0; }
  .jr-list { flex: none; max-height: 62vh; }
  .journal .jr-page { display: none; }
  .jr-page { margin: var(--sp-3); padding: var(--sp-4) var(--sp-3); }
  .jr-art { height: 140px; }
  .jr-text { overflow: visible; flex: none; }
  .jr-foot { flex-direction: column; align-items: stretch; }
  .jr-facts { gap: var(--sp-4); justify-content: space-between; }
}

/* ── Your side of the arena: a scene from the game ─────────────────────
   The HUD at the top left and the Knight standing on the black foreground. Nothing in the
   HUD is a rectangle (design/02-hollow-knight.md, §5): no box and no border. The scene is
   the field; what's read goes in the header above, just like the enemy's.
   The HUD is painted by hudHtml() in js/app.js, the same as the sheet's, and shares its fit (.pip-mask, .orb-well). */
.kscene {
  --orb: 64px;
  --pip-h: 24px; --pip-w: calc(var(--pip-h) * 51 / 66);
  --hud-row: 224px;     /* what the masks take up before wrapping: the tail, the vessels and the aura are measured from here and from --orb */
  /* The HUD is in the flow and the Knight stays anchored at the bottom: with two rows of masks
     (9 and 6 of lifeblood) the scene grows instead of pushing the vessels into his horns.
     --stage is his place: what shows of the artwork plus the ground. */
  --stage: 164px;
  position: relative; min-height: 248px; overflow: hidden; isolation: isolate;
  padding: var(--sp-3) var(--sp-3) var(--stage);
  background: var(--scene-bg);
}
/* The spotlight behind the Knight: without it, his cloak and his Shade, which are black, can't be seen. */
.kscene::before {
  content: ''; position: absolute; z-index: -1; left: 50%; bottom: -24px;
  width: 340px; height: 260px; transform: translateX(-50%);
  background: radial-gradient(closest-side, var(--scene-glow), transparent);
}
.kscene.is-down::before { background: radial-gradient(closest-side, var(--scene-glow-low), transparent); }
.kside-read b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink-strong); }
.kside-lb, .kside-lb b { color: var(--lifeblood-line); }

/* The HUD: the orb on the left; to its right, the masks on top of the frame's tail and the
   reserve vessels below. It's the same size at any width, as in the game. */
.hud {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: var(--orb) auto; column-gap: var(--sp-2); align-items: start;
}
.hud-orb { position: relative; display: block; width: var(--orb); height: var(--orb); }
.hud-soul {
  position: absolute; width: 120.37%; max-width: none; height: auto; left: -9.03%; top: -7.18%;
  clip-path: inset(var(--cut) 0 0 0); transition: clip-path var(--dur) var(--ease);
}
.hud-ring {
  position: absolute; left: -10%; top: -10%; width: 120%; height: 120%;
  fill: var(--hud-frame); overflow: visible;
}
.hud-side { position: relative; display: flex; flex-direction: column; align-items: flex-start; padding-top: var(--sp-1); }
.hud-masks { position: relative; display: flex; flex-wrap: wrap; gap: var(--sp-1); max-width: var(--hud-row); }
.hud-mask { position: relative; display: block; width: var(--pip-w); height: var(--pip-h); }
/* The lost mask is the same one shadowed, like the HUD's dark gap. */
.hud-mask.is-empty img { filter: brightness(0.2) saturate(0); opacity: 0.85; }
.hud-tail { display: block; width: calc(var(--hud-row) + 6px); height: auto; aspect-ratio: 230 / 16; margin-left: calc(var(--sp-1) * -1); fill: var(--hud-frame); }
.hud-curl { fill: none; stroke: var(--hud-frame); stroke-width: 1.3; stroke-linecap: round; }
.hud-vessels { display: flex; gap: var(--sp-1); padding-left: var(--sp-1); }
.hud-vessel {
  position: relative; width: calc(var(--orb) / 4); height: calc(var(--orb) / 4); border-radius: 50%;
  background: var(--soul-well); box-shadow: inset 0 0 0 1px var(--soul-rim);
}
.hud-vessel img {
  position: absolute; width: 121.6%; max-width: none; height: auto; left: -10.8%; top: -10.8%;
  clip-path: inset(var(--cut) 0 0 0); transition: clip-path var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
/* Overcharmed: the game's purple aura, behind the masks. */
.hud-over {
  position: absolute; z-index: -1; left: calc(var(--hud-row) * -0.125); top: calc(var(--orb) * -0.25);
  width: calc(var(--hud-row) * 1.2143); max-width: none; height: auto;
  pointer-events: none;
}

/* The Knight, standing on the ground. The artwork has 5% of air under the feet, so the
   feet fall inside the ground's edge and don't float above it. */
.kstage { position: absolute; left: 50%; bottom: 14px; width: 150px; height: 160px; transform: translateX(-50%); }
.kstage-art { position: absolute; left: 50%; bottom: 0; height: 100%; width: auto; max-width: none; transform: translateX(-50%); }
/* The Shade doesn't stand: it floats. */
.kscene.is-down .kstage-art { height: 116px; bottom: 22px; animation: shade-float 3.2s ease-in-out infinite; }
.kscene.is-dying .kstage-art { animation: shade-in var(--dur-slow) var(--ease), shade-float 3.2s ease-in-out infinite; }
.kfloor { position: absolute; left: 0; bottom: 0; width: 100%; height: 44px; fill: var(--scene-floor); }
.krock { position: absolute; bottom: 0; fill: var(--scene-floor); }
.krock.is-left { left: 0; width: 100px; height: 110px; }
.krock.is-right { right: 0; width: 120px; height: 130px; }

/* What just happened. The hit: the flash, the mask that breaks and the game's invulnerability
   flicker, in jumps and not faded. */
.kscene.is-hit::after, .kscene.is-dying::after {
  content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: var(--scene-flash); opacity: 0; animation: scene-flash var(--dur) var(--ease);
}
.kscene.is-hit .kstage-art { animation: kn-blink 900ms steps(1, end); }
.hud-mask.is-breaking img { animation: mask-break var(--dur-slow) var(--ease); }
.hud-mask.is-mending img { animation: mask-mend var(--dur-slow) var(--ease); }
.kscene.is-focus .kstage-art { animation: kn-focus 700ms var(--ease); }
.hud-orb.is-moving .hud-soul, .hud-vessel.is-moving img { animation: soul-level var(--dur-slow) var(--ease); }

/* A single mask: the black smoke the Knight gives off. Without motion it isn't shown. */
.wisp {
  position: absolute; left: calc(50% + var(--x)); bottom: 84px; width: var(--s); height: var(--s);
  border-radius: 50%; background: var(--void-wisp); opacity: 0;
  animation: wisp 2.4s ease-out infinite; animation-delay: var(--d);
}
.wisp:nth-of-type(1) { --x: -26px; --s: 8px; --d: 0s; --dx: -6px; }
.wisp:nth-of-type(2) { --x: 16px; --s: 6px; --d: -0.4s; --dx: 5px; }
.wisp:nth-of-type(3) { --x: -8px; --s: 10px; --d: -0.8s; --dx: -3px; }
.wisp:nth-of-type(4) { --x: 24px; --s: 7px; --d: -1.2s; --dx: 8px; }
.wisp:nth-of-type(5) { --x: -18px; --s: 5px; --d: -1.6s; --dx: -9px; }
.wisp:nth-of-type(6) { --x: 4px; --s: 8px; --d: -2s; --dx: 2px; }

@keyframes scene-flash { from { opacity: 1; } }
@keyframes kn-blink { 0%, 22%, 44%, 66% { opacity: 0.25; } 11%, 33%, 55%, 77%, 100% { opacity: 1; } }
@keyframes kn-focus { 45% { filter: drop-shadow(0 0 14px var(--focus-glow)) brightness(1.12); } }
@keyframes mask-break { from { filter: brightness(2); opacity: 1; transform: scale(1.2); } }
@keyframes mask-mend { from { filter: brightness(2.4); opacity: 0; transform: scale(0.5); } }
@keyframes soul-level { from { clip-path: inset(var(--cut-was) 0 0 0); } }
@keyframes shade-in { from { opacity: 0; } }
@keyframes shade-float { 50% { transform: translate(-50%, -5px); } }
@keyframes wisp {
  0% { opacity: 0; transform: translate(0, 0) scale(0.6); }
  20% { opacity: 0.9; }
  100% { opacity: 0; transform: translate(var(--dx), -72px) scale(1.2); }
}

/* ── The arena ────────────────────────────────────────────────────────
   Three bands. The stage: you and whoever is in front, face to face on their two spotlights,
   with the scoreboard between. The commands: your attacks and theirs, side by side, so
   answering a hit doesn't mean scrolling. And the log. Opposite, one card per entity: the boss
   and each of its parts carry a normal card; the minions, a smaller one. Each card brings ITS
   OWN buttons, because an Infected Balloon attacks on its own, and the header is a button
   because you have to be able to choose who you hit. */
.arena { display: flex; flex-direction: column; gap: var(--sp-8); }
.stage {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(136px, auto) minmax(0, 1fr);
  gap: var(--sp-4); align-items: end;
}
.commands { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 4fr); gap: var(--sp-8); align-items: start; }
.side-knight, .side-foes { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }

/* The arena's two headers, yours and the enemy's: name and figures on one line. */
.foes-head, .kside-head { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.foes-head h3, .kside-head h3 { font-size: var(--fs-md); color: var(--ink-strong); }
.foes-total, .kside-read { font-size: var(--fs-xs); color: var(--muted); }
/* On the stage, the Knight's header answers the enemy's title card: his name in the same
   capitals, centred, and his figures below. */
.stage .kside-head { flex-direction: column; align-items: center; gap: var(--sp-2); text-align: center; }
.stage .kside-head h3 { font-family: var(--font-display); font-size: var(--fs-2xl); font-weight: 600; letter-spacing: var(--track-title); line-height: 1.1; }

.foecard { border-top: 1px solid var(--inv-line); min-width: 0;
  transition: box-shadow var(--dur) var(--ease), background-color var(--dur-fast) var(--ease); }
/* Who you're hitting: the accent rule on its left, like the Journal row. */
.foecard.is-on { box-shadow: inset 2px 0 0 var(--accent); }
.foecard.is-down { opacity: 0.45; }
.foecard.is-empty { border: 1px dashed var(--inv-line); }

.foecard-head {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  gap: var(--sp-3); width: 100%; text-align: left; padding: var(--sp-3);
}
/* The whole card picks who you hit (data-act="target"), the space next to the attacks too:
   it's veiled whole wherever you come in, and if you aren't over an attack, the name takes
   the accent. With the veil only on the header, the attacks looked like another card's. */
.foecard[data-act] { cursor: pointer; }
.foecard[data-act]:hover { background: var(--veil); }
.foecard[data-act]:hover:not(:has(.move:hover)) .foecard-name { color: var(--accent); }
/* The portrait, with the same cold spotlight the Knight has behind him opposite: without it,
   the dark enemies (False Knight, Nosk) get lost in the black. An <img>'s background is
   painted behind the artwork. */
.foecard-art { width: auto; height: 72px; max-width: 84px; padding: var(--sp-1); object-fit: contain;
  background: radial-gradient(closest-side, var(--scene-glow), transparent); }
.foecard-art.is-missing { display: none; }
.foecard-body { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.foecard-name { font-size: var(--fs-sm); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.foecard-num {
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: var(--fs-md); font-weight: 600; color: var(--ink-strong);
}
.foecard-note { font-size: var(--fs-xs); color: var(--muted); }
.foecard-moves {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(156px, 1fr));
  gap: var(--sp-2); padding: 0 var(--sp-3) var(--sp-3);
}
/* A minion is a guest: a shorter card, a small portrait and the name in italics. */
.foecard.is-minion { margin-left: var(--sp-6); }
.foecard.is-minion .foecard-art { height: 44px; max-width: 48px; }
.foecard.is-minion .foecard-head { padding: var(--sp-2) var(--sp-3); }
.foecard.is-minion .foecard-name { font-style: italic; color: var(--muted); font-size: var(--fs-xs); }
.foecard.is-minion .foecard-num { font-size: var(--fs-sm); }
.foecard.is-minion .fbar { height: 5px; }
.move-summon .move-num { color: var(--ink-2); }

.fbar { height: 8px; background: var(--well); box-shadow: inset 0 0 0 1px var(--well-line); }
/* Bone while it has health left; red is kept for when there really is little left,
   otherwise a full red bar warns of a danger that doesn't exist yet. */
.fbar span { display: block; height: 100%; background: var(--ink-2); transition: width var(--dur) var(--ease); }
.fbar.is-low span { background: var(--bad); }

/* The end of a fight, titled the way the game titles an area on entry: spaced-out small
   caps, centred, and the flourish from the page title below. No box and no traffic-light
   colour, which made it look like a status tag: the scene already says who has fallen,
   and falling is written only one step dimmer than winning. It comes in like the game's
   titles, fading, an instant after the hit that causes it. */
.fight-end {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  text-align: center; padding: var(--sp-2) 0;
}
.fight-end-title {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-xl);
  text-transform: uppercase; letter-spacing: var(--track-caps); line-height: 1.2;
  padding-left: 0.14em;              /* the letter-spacing hangs after the last letter: it's offset to centre */
  color: var(--ink-strong); text-wrap: balance;
}
.fight-end-rule { display: flex; align-items: center; gap: var(--sp-3); color: var(--bracket); line-height: 0; }
.fight-end-rule i { width: 72px; height: 1px; background: linear-gradient(to left, currentColor, transparent); }
.fight-end-rule i:last-child { background: linear-gradient(to right, currentColor, transparent); }
.fight-end-rule svg { fill: none; stroke: currentColor; stroke-width: 1; }
.fight-end-rule .hall-badge { width: 28px; height: 28px; }
.fight-end-note { font-size: var(--fs-sm); font-style: italic; color: var(--ink-2); text-wrap: balance; }
.fight-end-acts { display: flex; justify-content: center; gap: var(--sp-2); flex-wrap: wrap; margin-top: var(--sp-2); }
.fight-end.is-dead .fight-end-title { color: var(--ink-2); }
.fight-end.is-dead .fight-end-rule { color: var(--line-ctl); }
.fight-end.is-dead .fight-end-note { color: var(--muted); }
.fight-end.is-fresh { animation: fight-end-in var(--dur-slow) var(--ease) var(--dur) both; }
@keyframes fight-end-in { from { opacity: 0; transform: translateY(6px); } }

/* The two movesets. */
.moveset { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.move {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  gap: var(--sp-half) var(--sp-3); text-align: left; width: 100%;
  background: none; border: 1px solid var(--inv-line);
  padding: var(--sp-2) var(--sp-3); min-height: 44px;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.move:hover:not(:disabled) { border-color: var(--accent-line); background: var(--veil); }
/* Yours carry their artwork from the game, which already gives them shape: no rule, like the charms. */
.moveset .move, .moveset .spell-card { border-color: transparent; }
.move-foe:hover:not(:disabled) { border-color: var(--over-line); }
.move-name { font-size: var(--fs-sm); color: var(--ink); min-width: 0; }
.move-num {
  font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: var(--fs-md); font-weight: 600; color: var(--ink-strong);
}
.move-foe .move-num { color: var(--over-ink); }
.move-cost { grid-column: 1 / -1; font-size: var(--fs-2xs); color: var(--muted); }
/* Your attacks carry their artwork from the game, in the same landscape slot for all:
   spells and arts are wide, and the nail, vertical, is laid down. */
.move.has-art { grid-template-columns: 56px minmax(0, 1fr) auto; align-items: center; }
.move-art { grid-row: 1 / span 2; display: grid; place-items: center; width: 56px; height: 30px; }
/* The row, the slot's height, and the artwork centred even if it overflows (unsafe). Without
   fixing it, the row grew to the artwork and left it stuck at the top: the nail (56 px before
   rotating it) sat 13 px below its row's centre and Great Slash, 7. Each artwork's height is
   still given by its width (max-height didn't resolve against an auto row; now, without it). */
.move-art { grid-template-rows: 30px; }
.move-art img { width: auto; height: auto; max-width: 100%; max-height: none; object-fit: contain; place-self: unsafe center; }
.move-art.is-nail img { height: 56px; max-height: none; max-width: none; transform: rotate(-90deg); }
/* The charm icons (Shadow Dash, Weaverlings, Dreamshield) are square: with the grid's auto row,
   max-height: 100% has nothing to measure against and only the width ruled, so they came out
   at 56 × 56 and spilled out of the button. The row fixed at the slot's height leaves them at
   30 × 30, centred. */
.move-art.is-charm { grid-template-rows: 100%; }
.move-art.is-charm img { height: 100%; width: auto; max-width: none; }
.move.has-art .move-cost { grid-column: 2 / -1; }
.move:disabled .move-art img { filter: grayscale(0.8); }   /* the button already dims as a whole */
/* Why hitting there is no use: immunity or damage cap. It's flagged, not simulated. */
.move-cost.is-warn { color: var(--cond); font-style: italic; }
.summons { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-3); }
.summons .block-head { margin: 0; }
.move-summon .move-num { color: var(--ink-2); }

/* The log: the latest at the top, and only the latest in live ink. */
/* It takes the place of the buttons that aren't there yet, so the column doesn't shrink: it
   measures exactly one .move, so choosing an enemy with a single attack doesn't move
   anything below. */
.moveset-empty.is-inline { align-items: center; align-content: center; min-height: var(--ctl); }

.fight-log { border-top: 1px solid var(--inv-line); padding-top: var(--sp-4); }
.side-foes > .fight-log { margin-top: var(--sp-4); }
.fight-log ol { display: flex; flex-direction: column; gap: var(--sp-1); }
.fight-log li { font-size: var(--fs-xs); color: var(--muted); font-variant-numeric: tabular-nums; }
.fight-log li.is-last { color: var(--ink-2); }

/* Charms in the arena (design/04-charms-in-combat.md §3.3). Carefree Melody splits the enemy
   attack into two buttons —the hit and "negated", with its probability— under a shared
   frame, like the Hall's double pedestals. */
.move-pair { display: grid; grid-template-columns: minmax(0, 1fr); grid-auto-flow: column; grid-auto-columns: auto; }
/* The pair takes two cells of the attack grid: in just one, the attack's name gets squashed
   against its figure. In one column (mobile), the whole row. */
.foecard-moves .move-pair { grid-column: span 2; }
/* A split attack is a single card: the border belongs to the whole and lights up whole; inside,
   only the piece you're about to press is tinted, because each one is a different action (it hits
   you, you stop it, you negate it). With a border per piece, on hover they looked like loose parts. */
.move-pair { border: 1px solid var(--inv-line); transition: border-color var(--dur) var(--ease); }
.move-pair .move { border: 0; background: transparent; }
.move-pair .move + .move { border-left: 1px dashed var(--inv-line); }
.move-pair .move:hover:not(:disabled) { border-color: var(--inv-line); background: var(--veil); }
.move-pair:has(.move:hover:not(:disabled)) { border-color: var(--over-line); }
.move-negate, .move-block { grid-template-columns: auto; text-align: center; }
.move-negate .move-name, .move-block .move-name { font-size: var(--fs-2xs); color: var(--muted); }
.move-negate .move-num, .move-block .move-num { font-size: var(--fs-sm); color: var(--ink-2); }
/* A spell's impacts, with the game's notches (design/05-spell-variants.html, B): the button
   and its row form a single card. Lit lands; a ring, not; the figure, below. */
/* The border and the background belong to the card, not to its two parts: otherwise, on hover
   only the button lit up and they looked like two cards. It lights up whole, wherever you come
   in, while the spell can be cast; disabled, its button dims and the notches stay at hand. */
.spell-card {
  display: flex; flex-direction: column;
  border: 1px solid transparent;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.spell-card .move, .spell-card .move:hover:not(:disabled) { border: 0; background: transparent; }
.spell-card:has(.move:not(:disabled)):hover { border-color: var(--accent-line); background: var(--veil); }
.spell-dots {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-1) var(--sp-2);
  padding: 0 var(--sp-3) var(--sp-2) calc(56px + var(--sp-3) * 2);
}
.sd-dot { display: grid; justify-items: center; gap: var(--sp-half); min-width: 28px; min-height: 28px; padding: var(--sp-half); }
.sd-dot .notch { width: 17px; height: 17px; }
.sd-dot .notch.is-free::before { width: 10px; height: 10px; }
.sd-dot small { font-family: var(--font-num); font-size: var(--fs-2xs); color: var(--ink-2); font-variant-numeric: tabular-nums; }
.sd-dot.is-off small { color: var(--muted); text-decoration: line-through; }
.sd-dot:hover .notch.is-free::before, .sd-dot:focus-visible .notch.is-free::before { border-color: var(--accent-line); }
/* The sixteen flukes: smaller dots, no figure, and the count at the end. */
.spell-dots.is-small { gap: 0; }
.spell-dots.is-small .sd-dot { min-width: 22px; min-height: 22px; place-content: center; }
.spell-dots.is-small .notch { width: 13px; height: 13px; }
.spell-dots.is-small .notch.is-free::before { width: 8px; height: 8px; }
.sd-count, .sd-side { font-size: var(--fs-2xs); color: var(--muted); align-self: center; margin-left: var(--sp-1); font-variant-numeric: tabular-nums; }
.sd-count { color: var(--ink-2); }
.sd-side i { font-style: normal; }
.sd-side { gap: var(--sp-2); }
.sd-side button { font-size: var(--fs-2xs); }
/* "land", in front of the notches: the legend that says what lit means without opening the "?". It
   sits in the space below the artwork, next to the first notch, so as not to move the notches or
   push "left · right" onto another line; in that text's ink, and at the notch's height (17 px
   plus its 2 px margin top and bottom), not its figure's. */
.spell-dots { position: relative; }
.sd-lbl {
  position: absolute; left: 0; top: 0; width: calc(56px + var(--sp-3) * 2 - var(--sp-2));
  text-align: right; font-size: var(--fs-2xs); line-height: 21px; color: var(--muted);
}

/* The arena's explanations (js/app.js, "The arena's explanations"). The "?" goes at the end of
   the row it explains, in a column of its own; attacks that don't carry one keep that space, so
   their figures stay in column with their neighbours'. What it opens goes below and pushes what's
   underneath: nothing floats on top, which on mobile would cover the buttons. The card lights up
   whole, like the spell's (this rule comes from it), while the attack can be cast. */
.moveset { --help-w: 32px; }
.move-card {
  display: grid; grid-template-columns: minmax(0, 1fr) var(--help-w);
  border: 1px solid transparent;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.move-card > .move { grid-column: 1; grid-row: 1; }
.move-card > .help-q { grid-column: 2; grid-row: 1; }
.move-card > .spell-dots, .move-card > .arena-help { grid-column: 1 / -1; }
.move-card .move, .move-card .move:hover:not(:disabled) { border: 0; background: transparent; }
.move-card:has(.move:not(:disabled)):hover { border-color: var(--accent-line); background: var(--veil); }
/* The "?": a small grey ring, in the body serif. It responds to the finger, so on hover,
   focused or open it takes the accent (design/00 §3); open, also the chosen veil. The area
   you can tap is the row's full height, not just the ring. */
.help-q { display: grid; place-items: center; align-self: stretch; width: var(--help-w, 32px); min-height: var(--ctl-sm); padding: 0; color: var(--muted); }
.help-q span {
  display: grid; place-items: center; width: 18px; height: 18px; border-radius: 50%;
  border: 1px solid color-mix(in oklab, var(--accent-line) 70%, transparent); color: var(--ink-2);
  font-family: var(--font-body); font-size: var(--fs-2xs); font-weight: 600; line-height: 1;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.help-q:hover span, .help-q:focus-visible span, .help-q[aria-expanded="true"] span { color: var(--ink-strong); border-color: var(--accent); background: var(--accent-soft); }
/* What it opens: short prose in the serif, in secondary ink; in bone, the figures and the term
   as it appears on screen, so the sum reads at a glance. Under an attack it takes its card's
   width (aligned with the name, in the column's 330 px, it was 37 letters per line and
   Descending Dark needed nine); on the enemy's side, under its header and at a reading measure. */
.arena-help { display: flex; flex-direction: column; gap: var(--sp-1); font-size: var(--fs-xs); line-height: 1.5; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.arena-help b { font-weight: 600; color: var(--ink-strong); }
.move-card > .arena-help { padding: 0 var(--sp-3) var(--sp-3); }
.arena-help.is-foe { max-width: var(--measure); }
/* The enemy's "?", at the top right of its header: on mobile, when the line broke, it was left
   alone on a row. */
.foes-head:has(> .help-q) { position: relative; padding-right: calc(var(--help-w, 32px) + var(--sp-2)); }
.foes-head > .help-q { position: absolute; top: 0; right: 0; min-height: 0; height: 1.6em; }
/* Stagger: the counter under the bar and, when staggered, the card dimmed without quite falling. */
.foecard-stagger { font-size: var(--fs-2xs); color: var(--muted); font-variant-numeric: tabular-nums; }
.foecard-stagger.is-down { color: var(--cond); font-style: italic; }
.foecard.is-staggered .foecard-art { filter: grayscale(0.6); transform: rotate(-8deg); }
/* Hiveblood: the returning mask fills with honey from the bottom up, like the orb's soul.
   The hexagon is narrower than the mask: it's fitted by its body's height and centred
   (measured on the sprites, alpha ≥ 128: 52 × 75 in 55 × 77, and Joni's 53 × 76 in 55 × 78). */
.hud-mask .hud-honey { width: 94.9%; left: 1.7%; top: -1.333%; clip-path: inset(calc((1 - var(--regen, 0)) * 100%) 0 0 0); }
.hud-mask .hud-honey.is-joni { width: 93.66%; left: 4.88%; top: 0; }
.hud-mask.is-empty .hud-honey { filter: none; opacity: 1; }
/* Baldur Shell under the orb, with the hits it has left. Broken, like an empty mask. */
.hud-shell {
  grid-column: 1; justify-self: center; display: flex; align-items: center; gap: var(--sp-1);
  margin-top: var(--sp-1); font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xs); color: var(--ink-2);
}
.hud-shell img { height: var(--pip-h); width: auto; }
.hud-shell.is-broken img { filter: brightness(0.2) saturate(0); opacity: 0.85; }
.hud-shell.is-broken b { color: var(--muted); }
/* Dreamshield: the attack says whether it blocks it or passes through (the wiki's two lists). */
.move-cost.is-proj { color: var(--ink-2); font-style: italic; }
/* Focusing: the window in which a hit takes your heal away (what Baldur Shell covers). */
.kside-focus {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  margin-top: var(--sp-2); font-size: var(--fs-xs); color: var(--cond); font-style: italic;
}
/* The positional ones, which aren't simulated here: they're named, without inventing figures. */
.moveset-notes {
  list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-half);
  font-size: var(--fs-2xs); color: var(--muted);
}
.moveset-notes b { color: var(--ink-2); font-weight: 500; }
/* The clock counts your actions, not the fight: it's said at the foot of the log. */
.fight-clock-note { margin-top: var(--sp-2); font-size: var(--fs-2xs); color: var(--muted); font-style: italic; }

/* ── The stage: the enemy opposite the Knight ───────────────────────────
   The same scene as yours, mirrored: its cold spotlight, the black foreground with the rocks
   swapped round, and above it what the game shows when a boss fight starts, its title card,
   with the bar under it. The title is Cinzel (design/00 §4: labels, never figures), the figures
   go in --font-num. What happens to it is animated from the last thing painted (js/app-arena.js,
   foePrev): the white flash of the hit and a small recoil, the figure rising and fading, the
   bar's pale trail, the new phase announced like an area, and the dust it leaves on falling.
   Slow fades and no bounce (design/02 §5); prefers-reduced-motion leaves only the result. */
.fstage { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; text-align: center; }
.ftitle {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-half);
  font-family: var(--font-display); color: var(--ink-strong); line-height: 1.1; text-wrap: balance;
}
.ftitle-main { font-size: var(--fs-2xl); font-weight: 600; letter-spacing: var(--track-title); }
.ftitle-sup, .ftitle-sub { font-size: var(--fs-lg); letter-spacing: var(--track-caps); color: var(--ink-2); }
.fstage.is-fresh .ftitle { animation: title-in 1.6s var(--ease-fade) both; }
.fstage .foes-head { justify-content: center; }
.fstage .foecard-note { text-align: center; }
.fhero-bar { display: flex; flex-direction: column; gap: var(--sp-1); text-align: left; }
.fhero-line { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-3); }
.fhero-name { font-size: var(--fs-sm); color: var(--ink-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fhero-num { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); font-weight: 600; color: var(--ink-strong); line-height: 1; }
.fhero-num .u { font-size: var(--fs-xs); color: var(--muted); font-style: normal; font-weight: 400; }
.fbar.is-hero { position: relative; height: 10px; }
.fbar.is-hero .fbar-fill { position: relative; z-index: 1; }
/* What the hit took, pale for a moment before it goes. */
.fbar.is-hero .fbar-trail {
  position: absolute; left: 0; top: 0; bottom: 0; background: var(--ink-strong); opacity: 0.55;
  animation: bar-trail var(--dur-flash) var(--ease) var(--dur-slow) forwards;
}
.fbar.is-hero.is-low .fbar-trail { background: var(--bad); }

.fscene {
  position: relative; min-height: 248px; overflow: hidden; isolation: isolate;
  background: var(--scene-bg);
}
.fscene::before {
  content: ''; position: absolute; z-index: -1; left: 50%; bottom: -24px;
  width: 340px; height: 260px; transform: translateX(-50%);
  background: radial-gradient(closest-side, var(--scene-glow), transparent);
}
/* The area's ambience (css/tokens.css, --area-*; js/app-arena.js, areaOf): the scene behind the
   enemy is its area's value ramp, as the game paints each area in one hue family darkening towards
   the viewer: the upper mid-tone at the top, the lower one in the middle and black at the foot,
   where the black rocks stand and catch a hairline of the area's accent on their edge. It fades
   to black at the sides and the top, so it reads as a lit place and not as a coloured box; the
   two bright ones (Godhome, the Radiance) go at a little over half. A pale,
   neutral light behind the enemy lifts its silhouette. Only the scene: the title card, the bar
   and the figures above it stay in bone, so their contrast doesn't change. With no area, the
   plain black and the cold spotlight. */
[data-area="dirtmouth"]  { --area-mid: var(--area-dirtmouth-mid); --area-deep: var(--area-dirtmouth-deep); --area-light: var(--area-dirtmouth-light); }
[data-area="crossroads"] { --area-mid: var(--area-crossroads-mid); --area-deep: var(--area-crossroads-deep); --area-light: var(--area-crossroads-light); }
[data-area="greenpath"]  { --area-mid: var(--area-greenpath-mid); --area-deep: var(--area-greenpath-deep); --area-light: var(--area-greenpath-light); }
[data-area="fungal"]     { --area-mid: var(--area-fungal-mid); --area-deep: var(--area-fungal-deep); --area-light: var(--area-fungal-light); }
[data-area="city"]       { --area-mid: var(--area-city-mid); --area-deep: var(--area-city-deep); --area-light: var(--area-city-light); }
[data-area="crystal"]    { --area-mid: var(--area-crystal-mid); --area-deep: var(--area-crystal-deep); --area-light: var(--area-crystal-light); }
[data-area="deepnest"]   { --area-mid: var(--area-deepnest-mid); --area-deep: var(--area-deepnest-deep); --area-light: var(--area-deepnest-light); }
[data-area="resting"]    { --area-mid: var(--area-resting-mid); --area-deep: var(--area-resting-deep); --area-light: var(--area-resting-light); }
[data-area="basin"]      { --area-mid: var(--area-basin-mid); --area-deep: var(--area-basin-deep); --area-light: var(--area-basin-light); }
[data-area="edge"]       { --area-mid: var(--area-edge-mid); --area-deep: var(--area-edge-deep); --area-light: var(--area-edge-light); }
[data-area="gardens"]    { --area-mid: var(--area-gardens-mid); --area-deep: var(--area-gardens-deep); --area-light: var(--area-gardens-light); }
[data-area="hive"]       { --area-mid: var(--area-hive-mid); --area-deep: var(--area-hive-deep); --area-light: var(--area-hive-light); }
[data-area="colosseum"]  { --area-mid: var(--area-colosseum-mid); --area-deep: var(--area-colosseum-deep); --area-light: var(--area-colosseum-light); }
[data-area="palace"]     { --area-mid: var(--area-palace-mid); --area-deep: var(--area-palace-deep); --area-light: var(--area-palace-light); }
[data-area="abyss"]      { --area-mid: var(--area-abyss-mid); --area-deep: var(--area-abyss-deep); --area-light: var(--area-abyss-light); }
[data-area="grimm"]      { --area-mid: var(--area-grimm-mid); --area-deep: var(--area-grimm-deep); --area-light: var(--area-grimm-light); }
[data-area="radiance"]   { --area-mid: var(--area-radiance-mid); --area-deep: var(--area-radiance-deep); --area-light: var(--area-radiance-light); }
[data-area="godhome"]    { --area-mid: var(--area-godhome-mid); --area-deep: var(--area-godhome-deep); --area-light: var(--area-godhome-light); }
.fstage:not([data-area]) .fscene::before { background: radial-gradient(closest-side, var(--scene-cold), transparent); }
.fstage:not([data-area]).is-over .fscene::before { background: radial-gradient(closest-side, var(--scene-cold-off), transparent); }
.fstage[data-area] { --area-k: 78%; }
.fstage:is([data-area="godhome"], [data-area="radiance"]) { --area-k: 55%; }
.fstage[data-area] .fscene {
  background:
    radial-gradient(62% 78% at 50% 62%, transparent 30%, var(--scene-bg) 100%),
    linear-gradient(to bottom,
      color-mix(in oklab, var(--area-mid) var(--area-k), var(--scene-bg)) 0%,
      color-mix(in oklab, var(--area-deep) var(--area-k), var(--scene-bg)) 55%,
      var(--scene-bg) 100%);
  transition: background var(--dur-slow) var(--ease-fade);
}
.fstage[data-area] .fscene::before { background: radial-gradient(closest-side, var(--area-spot), transparent); }
.fstage[data-area] .fscene :is(.kfloor, .krock) {
  filter: drop-shadow(0 -1px 0 color-mix(in oklab, var(--area-light) 55%, transparent));
}
/* Beaten, its place goes dark with it, as the Knight's light does when he falls. */
.fstage[data-area].is-over { --area-k: 28%; }
.fstage[data-area].is-over .fscene::before { background: radial-gradient(closest-side, var(--area-spot-off), transparent); }
.fscene-art {
  position: absolute; left: 50%; bottom: 26px; height: 188px; width: auto; max-width: 86%;
  object-fit: contain; object-position: bottom; transform: translateX(-50%); transform-origin: 50% 100%;
  transition: opacity var(--dur-slow) var(--ease), filter var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
}
.fscene-art.is-missing { visibility: hidden; }
/* A common enemy or a minion keeps its artwork's own size: stretched to a boss's height, a
   Crawlid stood taller than the Knight, and blurred. */
.fstage.is-common .fscene-art { height: auto; max-height: 188px; }
/* The foreground, mirrored: the tall rock on the left, facing the Knight's. */
.fscene .krock.is-left { left: auto; right: 0; transform: scaleX(-1); }
.fscene .krock.is-right { right: auto; left: 0; transform: scaleX(-1); }
.fscene-q { position: absolute; inset: 0; display: grid; place-items: center; font-family: var(--font-display); font-size: var(--fs-hero); color: var(--line-ctl); }

.fstage.is-hit .fscene-art { animation: foe-hit 520ms var(--ease); }
.fscene-dmg {
  position: absolute; left: 50%; top: 32%; z-index: 2; pointer-events: none;
  font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xl); font-weight: 600;
  color: var(--ink-strong); text-shadow: 0 0 var(--sp-3) var(--scene-bg);
  opacity: 0; animation: dmg-rise 1100ms var(--ease) forwards;
}
.fscene-phase {
  position: absolute; left: 0; right: 0; top: 38%; z-index: 2; pointer-events: none;
  font-family: var(--font-body); font-variant: small-caps; font-size: var(--fs-xl); letter-spacing: var(--track-label);
  color: var(--ink-strong); text-shadow: 0 0 var(--sp-4) var(--scene-bg);
  opacity: 0; animation: phase-title 2.4s var(--ease-fade) forwards;
}
/* Staggered: dimmed and bowed, as its card was. */
.fstage.is-staggered .fscene-art { filter: grayscale(0.6) brightness(0.75); transform: translateX(-50%) rotate(-8deg); }
/* Beaten: it stays as a shadow; if it has just happened, it dissolves into the menu's dust. */
.fstage.is-over .fscene-art { opacity: 0.18; filter: grayscale(1) blur(1px); }
.fstage.is-dying .fscene-art { animation: foe-fade 1.8s var(--ease-fade) both; }
.fdust {
  position: absolute; z-index: 2; bottom: 40%; left: calc(32% + var(--i) * 4%);
  width: 3px; height: 3px; border-radius: 50%; background: var(--mote); pointer-events: none;
  opacity: 0; animation: dust-rise 2.2s var(--ease) calc(var(--i) * 80ms) forwards;
  --dx: calc((var(--i) - 4.5) * 5px);
}
.fdust:nth-child(3n) { width: 2px; height: 2px; bottom: 52%; }
.fdust:nth-child(4n) { width: 4px; height: 4px; bottom: 30%; }

@keyframes title-in { from { opacity: 0; } }
@keyframes imp-call { 50% { inset: calc(var(--sp-1) * -1 - 1px); } }
@keyframes bar-trail { to { opacity: 0; } }
@keyframes foe-hit {
  0% { filter: brightness(0) invert(1); transform: translateX(-50%) translateX(10px); }
  35% { filter: brightness(0) invert(0.85); }
  100% { filter: none; transform: translateX(-50%); }
}
@keyframes dmg-rise {
  0% { opacity: 0; transform: translate(-50%, 6px); }
  15% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -40px); }
}
@keyframes phase-title { 18% { opacity: 1; } 72% { opacity: 1; } 100% { opacity: 0; } }
@keyframes foe-fade { from { opacity: 1; filter: none; } }
@keyframes dust-rise {
  0% { opacity: 0; transform: translate(0, 0); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), -110px); }
}

/* ── The scoreboard, between the two ─────────────────────────────────────
   The screen's answer, so its one focal point: your nail hits to win at the hero's size, in
   bone and --font-num (a figure, never the accent and never Cinzel); how many of its strongest
   attack you can still take, smaller below the diamond from the ending's flourish. */
.vs { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-6); align-self: center; }
.vs-score { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); margin: 0; }
.vs-score > div { display: flex; flex-direction: column-reverse; align-items: center; gap: var(--sp-1); text-align: center; }
.vs-score dd {
  margin: 0; font-family: var(--font-num); font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xl); font-weight: 600; line-height: 1; color: var(--ink-strong);
}
.vs-win dd { font-size: var(--fs-hero); font-weight: 700; letter-spacing: -0.02em; line-height: 0.9; }
.vs-score dt { font-size: var(--fs-2xs); line-height: 1.3; color: var(--muted); max-width: 13ch; text-wrap: balance; }
.vs-win dt { font-size: var(--fs-xs); color: var(--ink-2); }
.vs-fall[title] { cursor: help; }
.vs-mark { display: flex; align-items: center; gap: var(--sp-2); color: var(--bracket); line-height: 0; }
.vs-mark i { width: var(--sp-8); height: 1px; background: linear-gradient(to left, currentColor, transparent); }
.vs-mark i:last-child { background: linear-gradient(to right, currentColor, transparent); }
.vs-mark svg { fill: none; stroke: currentColor; stroke-width: 1; }
.fight-undo { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); }
.fight-undo:disabled { opacity: 0.4; cursor: default; }

/* ── Your attacks, in their four groups ──────────────────────────────────
   Each one is a plate, like the spells and arts on the Charms screen: the game's artwork on its
   soft white halo (soul is white), the name and the figure in bone, and below it the soul it
   costs or gives with the game's own soul. No box: on hover, the veil of ink and the name in the
   accent, and the halo brightens. The enemy's are rows (below). */
.moveset { gap: var(--sp-4); }
.moveset > .block-head, .side-foes > .block-head { margin: 0; }
.mgroup { display: flex; flex-direction: column; gap: var(--sp-2); }
.mgroup-head {
  display: flex; align-items: center; gap: var(--sp-3);
  font-family: var(--font-body); font-size: var(--fs-xs); font-style: italic; font-weight: 400; color: var(--muted);
}
.mgroup-head::after { content: ''; flex: 1; height: 1px; background: var(--inv-line); }
.mgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: var(--sp-2); }
.mgroup.is-spell .mgrid { grid-template-columns: repeat(auto-fill, minmax(184px, 1fr)); }

.moveset .move-card {
  position: relative; display: flex; flex-direction: column;
  border: 0;
  transition: background-color var(--dur) var(--ease);
}
.moveset .move-card:has(.move:not(:disabled)):hover { background: var(--veil); }
.moveset .move-card:has(.move:not(:disabled)):hover .move-name, .moveset .move:focus-visible .move-name { color: var(--accent); }
.moveset .move-card .move, .moveset .move-card .move:hover:not(:disabled) { border: 0; background: transparent; }
.moveset .move.has-art {
  grid-template-columns: minmax(0, 1fr); grid-template-rows: 64px auto auto;
  justify-items: center; text-align: center;
  gap: var(--sp-half); padding: var(--sp-3) var(--sp-2); flex: 1; align-content: start;
}
.moveset .move-art {
  position: relative; isolation: isolate;
  grid-row: 1; grid-column: 1; justify-self: center; width: 96px; height: 64px; grid-template-rows: 64px;
  margin-bottom: var(--sp-1);
}
.moveset .move-art::before { content: ''; position: absolute; z-index: -1; inset: -8% 2%; background: var(--halo-plate); opacity: 0; transition: opacity var(--dur) var(--ease), filter var(--dur) var(--ease); }
.moveset .move-card:has(.move:not(:disabled):is(:hover, :focus-visible)) .move-art::before { opacity: 1; filter: brightness(1.5); }
.moveset .move-art.is-nail img { height: 72px; }
.moveset .move-art.is-charm img { height: 48px; }
.moveset .move-name { grid-row: 2; grid-column: 1; font-size: var(--fs-sm); line-height: 1.25; transition: color var(--dur-fast) var(--ease); }
.moveset .move-num {
  grid-row: 3; grid-column: 1; display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-xl); font-weight: 700; line-height: 1.1;
}
.moveset .move-num .move-mask { height: 22px; }
.moveset .move.has-art .move-cost { grid-row: 4; grid-column: 1; justify-content: center; }
.moveset .move.has-art .move-why { grid-column: 1; }
.move-cost { display: flex; align-items: center; gap: var(--sp-1); }
.mc-soul { width: 14px; height: 14px; }
.move-why { grid-column: 1 / -1; font-size: var(--fs-2xs); font-style: italic; color: var(--cond); }
/* The "?" in the plate's top corner, clear of the artwork, the name and the figure. */
.moveset .move-card > .help-q { position: absolute; top: var(--sp-1); right: var(--sp-1); min-height: var(--ctl-sm); height: 32px; }
.moveset .move-card > .arena-help { padding: 0 var(--sp-3) var(--sp-3); }
/* The spell's notches, under the whole plate and centred, with "land" in front as a word. */
.moveset .spell-dots { gap: var(--sp-1); justify-content: center; padding: 0 var(--sp-3) var(--sp-3); }
.moveset .sd-lbl { position: static; flex-basis: 100%; width: auto; text-align: center; line-height: 1; }

/* The enemy's attacks: the figure in bone with the game's mask, which already says what it takes. */
.move-foe .move-num { display: inline-flex; align-items: center; gap: var(--sp-1); color: var(--ink-strong); }
.move-mask { height: 18px; width: auto; }
/* Summoning: the minion's portrait on its small spotlight, on the left, so you see what comes out. */
.move-summon { grid-template-columns: 44px minmax(0, 1fr) auto; align-items: center; }
.summon-art {
  grid-row: 1 / span 2; display: grid; place-items: center; width: 44px; height: 44px;
  background: radial-gradient(closest-side, var(--scene-glow), transparent);
}
.summon-art img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.summon-art.is-missing img { visibility: hidden; }
.move-summon .move-cost { grid-column: 2 / -1; }
.move-summon:disabled .summon-art img { filter: grayscale(0.8); }
/* The enemy's attacks have no artwork, so they aren't plates: they're the rows of a list, like
   the Hunter's Journal's, with no box. A thin rule in the section's tint under each one (the
   inv-line), the name on the left and what it takes on the right, the figure in bone with the
   game's mask. Under the finger, the veil of ink, the name in the accent and its rule in the
   accent's line; the split ones (Dreamshield, Carefree Melody) keep their dashed divider. */
.foecard-moves { gap: 0 var(--sp-6); }
.foecard-moves > .move, .foecard-moves > .move-pair {
  border: 0; border-bottom: 1px solid var(--inv-line);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.foecard-moves .move { align-items: center; padding: var(--sp-2) var(--sp-2); }
.foecard-moves .move-num { font-size: var(--fs-lg); }
.foecard-moves .move-name { transition: color var(--dur-fast) var(--ease); }
.foecard-moves > .move:hover:not(:disabled), .foecard-moves > .move-pair:has(.move:hover:not(:disabled)) { border-color: var(--accent-line); }
.foecard-moves .move:hover:not(:disabled) { background: var(--veil); }
.foecard-moves .move:is(:hover, :focus-visible):not(:disabled) .move-name { color: var(--accent); }
.foecard-moves .move-pair .move { border-bottom: 0; }
.foecard.is-empty { border: 0; border-top: 1px dashed var(--inv-line); }
/* With a single entity in front, its card doesn't repeat the stage: only its attacks. */
.foecard.is-solo { border-top: 0; box-shadow: none; }
.foecard.is-solo .foecard-head { display: none; }
.foecard.is-solo .foecard-moves { padding: 0; }
.foecard.is-solo[data-act]:hover { background: none; }

/* ── The log, in two voices and a narrator ───────────────────────────────
   Yours with a small nail stroke, theirs with the mask they took, and the narrator (phases,
   stagger, who comes in) centred between two rules, like a chapter. */
.fight-log ol { max-width: var(--measure); }
.fight-log li { display: flex; align-items: center; gap: var(--sp-2); }
.fight-log li.is-you::before { content: ''; flex: none; width: 12px; height: 1px; background: currentColor; opacity: 0.7; }
.log-mask { flex: none; height: 12px; width: auto; opacity: 0.8; }
.fight-log li.is-sys { justify-content: center; font-style: italic; text-align: center; }
.fight-log li.is-sys::before, .fight-log li.is-sys::after { content: ''; flex: 0 1 var(--sp-12); height: 1px; background: var(--inv-line); }

/* The fight in figures, under its ending. */
.fight-sum { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-3) var(--sp-8); margin: var(--sp-2) 0 0; }
.fight-sum > div { display: flex; flex-direction: column-reverse; align-items: center; gap: var(--sp-1); }
.fight-sum dd { margin: 0; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); font-weight: 600; color: var(--ink-strong); line-height: 1; }
.fight-sum dt { font-size: var(--fs-2xs); color: var(--muted); }

/* ── The band: the stage, small and stuck under the bar ───────────────────
   A zero-height anchor at the top of the arena sticks under the bar (--band-top, measured in
   js/app-arena.js: the bar's height; on mobile, where the minihud has its own row, over that row),
   and the band hangs from it. It fades in when the stage leaves the screen and out when it comes
   back. The same face-off in small: the Knight with his HUD (the arena's, at a smaller scale), the
   scoreboard and Undo, and whoever you're hitting with its bar. Its reactions are the stage's. */
.fband-anchor { position: sticky; top: var(--band-top, var(--sticky-h)); z-index: 31; height: 0; }
.fband {
  position: absolute; left: 0; right: 0; top: 0;
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: var(--sp-4);
  padding: var(--sp-2) var(--sp-4); min-height: 88px;
  background: var(--inv-bg); border-bottom: 1px solid var(--inv-line); box-shadow: var(--shadow-md);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--dur) var(--ease), visibility 0s linear var(--dur);
}
.fband-anchor.is-on .fband { opacity: 1; visibility: visible; pointer-events: auto; transition: opacity var(--dur) var(--ease); }
.minihud.is-fight.is-banded { visibility: hidden; }

.fband-me { --orb: 44px; --pip-h: 17px; --pip-w: calc(var(--pip-h) * 51 / 66); --hud-row: 170px;
  display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.fband-me .hud { flex: none; }
.fband-knight {
  flex: none; height: 64px; width: auto; padding: var(--sp-1);
  background: radial-gradient(closest-side, var(--scene-glow), transparent);
}
.fband-me.is-hit .fband-knight { animation: kn-blink 900ms steps(1, end); }
.fband-me.is-focus .fband-knight { animation: kn-focus 700ms var(--ease); }

.fband-vs { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); }
.fband-score { display: flex; align-items: center; gap: var(--sp-2); color: var(--bracket); line-height: 1; }
.fband-score b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); font-weight: 600; color: var(--ink-strong); }
.fband-score svg { fill: none; stroke: currentColor; stroke-width: 1; }
.fband .fight-undo { font-size: var(--fs-xs); padding: 0; }

.fband-foe { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.fband-art {
  position: relative; flex: none; display: block; width: 72px; height: 64px;
  background: radial-gradient(closest-side, var(--scene-glow), transparent);
}
.fband-art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
.fband-art img.is-missing { visibility: hidden; }
.fband-art .fscene-dmg { top: 0; font-size: var(--fs-lg); }
.fband-foe.is-hit .fband-art img { animation: foe-hit-mini 520ms var(--ease); }
.fband-foe.is-over .fband-art img { opacity: 0.25; filter: grayscale(1); }
.fband-foe.is-staggered .fband-art img { filter: grayscale(0.6) brightness(0.75); transform: rotate(-8deg); }
.fband-body { flex: 1; display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.fband-line { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-2); }
.fband-name { min-width: 0; font-size: var(--fs-sm); color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fband-num { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-lg); font-weight: 600; color: var(--ink-strong); }
.fband-num .u { font-size: var(--fs-2xs); color: var(--muted); font-style: normal; font-weight: 400; }
@keyframes foe-hit-mini {
  0% { filter: brightness(0) invert(1); transform: translateX(6px); }
  35% { filter: brightness(0) invert(0.85); }
  100% { filter: none; transform: none; }
}


/* Screen readers only: the timeline is seen through pictures and heard through words. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── Tabs: ARENA | HALL OF GODS | PANTHEONS ────────────────────────────
   They are the Combat screen's title, centred over their rule like the other screens': Cinzel
   in small caps, the active one in its section's tint and the others dimmed. What responds to
   the finger still belongs to the accent: the underline and the hover. */
.tabs { display: flex; justify-content: center; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.tab {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: var(--track-caps);
  font-weight: 600; font-size: var(--fs-lg); color: var(--muted); padding: var(--sp-1) 0;
  border-bottom: 1px solid transparent;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.tab:hover { color: var(--accent); }
.tab.is-on { color: var(--tint); border-bottom-color: var(--accent-line); }
.tab-sep { width: 1px; height: 16px; background: var(--line-ctl); flex: none; }
/* Godhome's two, with the rule between them right over the diamond: each takes an equal column
   (the widest rules), one flush right and the other flush left. Otherwise "Hall of Gods" and
   "Pantheons", of different lengths, left the rule to one side of the diamond even though the
   whole group was centred. */
.fight-head .tabs { display: grid; grid-template-columns: 1fr auto 1fr; column-gap: var(--sp-4); }
.fight-head .tabs > :first-child { justify-self: end; }
.fight-head .tabs > :last-child { justify-self: start; }
/* Letter-spacing leaves air after the last letter: the same in front, and the label stays
   centred in its box (and the underline, under it). */
.tab { padding-inline-start: 0.14em; }
/* On mobile the two don't fit on one line in equal columns (in Spanish, «Salón de los Dioses»
   alone already measures 200 px): the long one switches to its short label («Salón», "Hall")
   rather than break or push the other. */
.tab-short { display: none; }
@media (max-width: 599px) {
  /* Equal columns need twice the widest («Panteones»): with less air between letters and
     between tabs, they fit at 390 px in both languages. */
  .fight-head .tabs { align-self: stretch; column-gap: var(--sp-1); }
  .tab { min-width: 0; letter-spacing: var(--track-title); padding-inline-start: 0.06em; text-align: center; font-size: var(--fs-md); }
  .tab-long { display: none; }
  .tab-short { display: inline; }
}
/* Narrower still, twice «Panteones» no longer fits: they share the row as before. */
@media (max-width: 379px) {
  .fight-head .tabs { display: flex; justify-content: space-between; }
}

/* ── Choosing a pantheon ────────────────────────────────────────────── */
/* Each pantheon is its final boss in a doorway of Godhome's light: in the game a pantheon is
   entered through a door, so its light fills an arch with a thin gold frame, rising from the
   threshold (not the Hall's niche, whose halo sits behind a statue on its pedestal). The light
   switches on on hover and on the chosen one, which also carries the accent. */
/* auto-fit: the five fill the row on desktop and go two by two on mobile. */
.pcards { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: var(--sp-3); }
.pcard {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-2);
  text-align: center; padding: 0 0 var(--sp-3); border: 1px solid transparent;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.pcard:hover { background: var(--veil); }
/* The chosen pantheon, like the Hall's statue: its door lit and its threshold in the accent, no frame. */
.pcard.is-on { background: var(--picked); }
.pcard.is-on .pcard-art { border-bottom-color: var(--accent); }
.pcard-art {
  display: block; width: 76%; height: 124px; margin: var(--sp-3) auto 0; padding: var(--sp-6) var(--sp-3) var(--sp-1);
  object-fit: contain; object-position: 50% 100%;
  background: var(--pantheon-door-rest); border: 1px solid var(--godhome-line); border-bottom-width: 2px;
  border-radius: 999px 999px 0 0;
}
.pcard:hover .pcard-art, .pcard.is-on .pcard-art { background-image: var(--pantheon-door); }
.pcard-body { display: flex; flex-direction: column; gap: var(--sp-half); min-width: 0; padding: 0 var(--sp-3); }
.pcard-name { font-size: var(--fs-md); color: var(--ink); }
.pcard.is-on .pcard-name { color: var(--ink-strong); }
.pcard-motto { font-size: var(--fs-xs); color: var(--ink-2); font-style: italic; }
.pcard-meta { font-size: var(--fs-2xs); color: var(--muted); }

/* The bindings: switches with their game icon. Off, the icon in grey. */
.binds { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: var(--sp-2); }
.bind {
  display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto auto;
  column-gap: var(--sp-3); align-items: center; text-align: left;
  padding: var(--sp-2) var(--sp-3); border: 1px solid transparent;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.bind:hover { background: var(--veil); border-color: var(--line); }
.bind.is-on { background: var(--picked); border-color: var(--accent-line); }
.bind-art { grid-row: 1 / 3; width: 40px; height: 40px; filter: grayscale(1) brightness(0.7);
  transition: filter var(--dur) var(--ease); }
.bind.is-on .bind-art { filter: drop-shadow(0 0 6px var(--notch-used-glow)); }
.bind-name { font-size: var(--fs-sm); color: var(--ink); }
/* All four at once: in the game the icons light up in Godhome's gold, with their halo.
   It's the game's signal, not the accent: that's why the rule and the name go in that tint too. */
.binds.is-all .bind.is-on { border-color: var(--godhome-line); }
.binds.is-all .bind-art, .run-binds.is-all img {
  filter: sepia(0.35) saturate(0.9) brightness(1.9) drop-shadow(0 0 6px var(--godhome-glow)) drop-shadow(0 0 14px var(--godhome-glow));
}
.binds.is-all .bind-name { color: var(--godhome); }
.bind-note { font-size: var(--fs-2xs); color: var(--muted); }
/* Under each pantheon, the bindings you've finished it with in your game (the lifeblood door's
   notches): off in grey, done in their bronze, and with "×4" (all four at once) in
   Godhome's gold, like the marks on the pantheon's door in the game. */
.pcard-wrap { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.pcard-wrap > .pcard { flex: 1 1 auto; }
.pdone { display: flex; justify-content: center; align-items: center; gap: var(--sp-half); }
.pdone-bind, .pdone-all { width: 32px; height: 32px; display: grid; place-items: center; }
.pdone-bind img { width: 22px; height: 22px; filter: grayscale(1) brightness(0.3); opacity: 0.8; transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease); }
.pdone-bind:hover img { filter: grayscale(1) brightness(0.8); }
.pdone-bind.is-on img { filter: drop-shadow(0 0 4px var(--notch-used-glow)); opacity: 1; }
.pdone-all { font-family: var(--font-num); font-size: var(--fs-xs); font-weight: 600; color: var(--muted); transition: color var(--dur) var(--ease); }
.pdone-all:hover { color: var(--accent); }
.pdone-all.is-on { color: var(--godhome); }
.pdone.is-all .pdone-bind img { filter: sepia(0.35) saturate(0.9) brightness(1.9) drop-shadow(0 0 4px var(--godhome-glow)); }
/* Right after marking (doorFx): the binding lights up from the dark; completing all four, they
   light up one after another in Godhome's gold, and the final boss's light swells once. */
.pdone-bind.is-lit img { animation: lit-in var(--dur-flash) var(--ease); }
.pdone.is-sealing .pdone-bind img { animation: seal-pip var(--dur-slow) var(--ease) both; animation-delay: calc(var(--i) * 110ms); }
.pdone.is-sealing .pdone-all { animation: title-in var(--dur-slow) var(--ease-fade) 440ms both; }
.pcard-wrap.is-sealed .pcard-art { animation: seal-glow 1.4s var(--ease-fade) 300ms; }
@keyframes seal-pip {
  from { filter: grayscale(1) brightness(0.3); }
  50% { filter: sepia(0.35) saturate(0.9) brightness(2.4) drop-shadow(0 0 8px var(--godhome-glow)) drop-shadow(0 0 18px var(--godhome-glow)); }
}
@keyframes seal-glow { 30% { filter: brightness(1.3) drop-shadow(0 0 12px var(--godhome-glow)); } }
@media (pointer: coarse) { .pdone-bind, .pdone-all { width: 34px; height: 44px; } }

/* The lifeblood door: its 20 notches in a row, lit in lifeblood blue, with the 8, 12 and 16
   ones (those that open it and raise the germs) larger. Below, the cocoon. */
.door { display: flex; flex-direction: column; gap: var(--sp-3); max-width: var(--measure); }
.door-notches { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.dn { width: 10px; height: 10px; border-radius: 50%; border: 2px solid var(--notch-free); }
.dn.is-major { width: 16px; height: 16px; }
@media (max-width: 599px) { .door-notches { gap: var(--sp-1); } }
.dn.is-lit { border-color: var(--lifeblood-line); background: var(--lifeblood-line); box-shadow: 0 0 8px var(--lifeblood-line); }
.door-state { font-size: var(--fs-sm); color: var(--ink); }
.door-cocoon { display: flex; align-items: center; gap: var(--sp-4); }
.door-cocoon-art { width: 128px; height: auto; flex: none; }
.door-cocoon-body { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; font-size: var(--fs-sm); color: var(--ink); }
.door-seeds { display: flex; gap: var(--sp-1); }
.door-seeds img { height: 26px; width: auto; }
.run-go {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6);
  margin: var(--sp-2) 0 var(--sp-4); padding: var(--sp-4) 0; border-top: 1px solid var(--inv-line); border-bottom: 1px solid var(--inv-line);
}
.run-sum { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); font-size: var(--fs-sm); color: var(--muted); }
.run-sum b { font-family: var(--font-display); font-size: var(--fs-lg); font-weight: 600; color: var(--ink-strong); }
.run-sum span + span::before { content: '·'; margin-right: var(--sp-3); color: var(--line-ctl); }

/* ── Inside the pantheon ────────────────────────────────────────────── */
.run-head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.run-head h3 { font-size: var(--fs-md); color: var(--ink-strong); }
.run-head .btn { margin-left: auto; }
/* Room and bosses left: two readings, apart, with a rule between them. */
.run-stats { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-3); color: var(--ink-2); font-variant-numeric: tabular-nums; }
.run-stat + .run-stat { border-left: 1px solid var(--line-ctl); padding-left: var(--sp-3); }
.run-binds { display: inline-flex; gap: var(--sp-1); }

/* The timeline: the rooms as a path through Godhome (pathHtml). Round medallions threaded on a
   thin gold line; the benches and the Godseeker's rooms, larger marks on it that cut it into
   stretches; the final boss last and largest, in its doorway of light (--pantheon-door). Each room
   says its state: to go, the boss in view and a little quieter; cleared, lit with a gold ring and a
   small tick; the current one, larger, on Godhome's light with the accent's ring; the one you fell
   in, in red; skipped, grey and struck through. The thread lights up as far as you've walked. On
   the picker (is-preview) every room is lit: it's who you'll face. The number goes under each one. */
.timeline {
  --tl: 40px;
  display: flex; flex-wrap: wrap; align-items: center; row-gap: var(--sp-12); padding: var(--sp-8) 0 var(--sp-4); margin: 0; list-style: none;
}
.timeline .tl { position: relative; display: flex; align-items: center; }
/* The four pantheons of 12 rooms fill a row with larger medallions; Hallownest's 53 wrap at 40 px. */
@media (min-width: 900px) {
  .timeline:not(:has(> .tl:nth-child(20))) { --tl: 50px; }
  .timeline:not(:has(> .tl:nth-child(20))) .tl-thread { width: var(--sp-4); }
  .timeline:not(:has(> .tl:nth-child(20))) .tl:is(.tl-rest, .tl-godseeker) { --tl: 58px; }
  .timeline:not(:has(> .tl:nth-child(20))) .tl.is-current { --tl: 64px; }
  .timeline:not(:has(> .tl:nth-child(20))) .tl.is-final { --tl: 76px; }
}
.tl-thread { flex: none; width: var(--sp-3); height: 1px; background: var(--godhome-line); opacity: 0.45; transition: opacity var(--dur) var(--ease); }
.timeline .tl:first-child .tl-thread { display: none; }
.timeline .tl:is(.is-done, .is-current, .is-fail) .tl-thread { background: var(--godhome); opacity: 0.85; }
.tl-go { position: relative; display: grid; place-items: center; padding: 0; border-radius: 50%; }
.tl-medal {
  position: relative; width: var(--tl); height: var(--tl); border-radius: 50%; overflow: hidden;
  display: grid; place-items: center; background: var(--well); box-shadow: inset 0 0 0 1px var(--godhome-line);
  transition: box-shadow var(--dur) var(--ease);
}
.tl-art { width: 82%; height: 82%; object-fit: contain; transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease); }
/* The bench's banner is landscape: it's framed by its knob, the recognisable part. */
.tl-rest .tl-art { width: 100%; height: 100%; object-fit: cover; object-position: 0 70%; }
.timeline .tl:is(.tl-rest, .tl-godseeker) { --tl: 48px; }
.timeline .tl:is(.tl-rest, .tl-godseeker) .tl-medal { background: var(--godhome-light-rest); }
.timeline .tl:is(.tl-rest, .tl-godseeker) .tl-thread { width: var(--sp-6); }
.tl-n {
  position: absolute; top: calc(100% + 2px); left: 50%; transform: translateX(-50%);
  font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xs); color: var(--muted);
}
.tl-count {
  position: absolute; top: -2px; right: -8px; z-index: 1;
  font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; color: var(--ink-2); text-shadow: 0 0 3px var(--bg);
}
.tl-tick { position: absolute; right: 8%; bottom: 8%; width: 34%; height: auto; color: var(--godhome); filter: drop-shadow(0 0 2px var(--scene-bg)); }
/* Still to go: the boss in view, only a little quieter than the ones already behind you. */
.timeline .tl.is-todo .tl-art { opacity: 0.75; filter: saturate(0.7); }
.timeline .tl.is-done .tl-medal { box-shadow: inset 0 0 0 1px var(--godhome); }
.timeline .tl.is-current { --tl: 54px; }
.timeline .tl.is-current .tl-medal { background: var(--godhome-light-rest); box-shadow: inset 0 0 0 2px var(--accent), var(--glow); }
.timeline .tl.is-current .tl-n { color: var(--accent-ink); font-weight: 600; }
/* Where you are, at a glance: the Knight over the current room, like his pin on the game's map. */
.tl-you {
  position: absolute; bottom: calc(100% + var(--sp-1)); left: 50%; transform: translateX(-50%);
  height: var(--sp-8); width: auto; filter: drop-shadow(0 0 6px var(--mote)); pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .tl-you { animation: tl-you-float 2.8s ease-in-out infinite; }
  .timeline .tl.is-current .tl-medal { animation: tl-here 2.4s var(--ease-fade) infinite alternate; }
}
@keyframes tl-you-float { 50% { transform: translate(-50%, -3px); } }
@keyframes tl-here { to { box-shadow: inset 0 0 0 2px var(--accent), 0 0 18px 2px var(--accent-line); } }
.timeline .tl.is-fail .tl-medal { box-shadow: inset 0 0 0 2px var(--bad); }
/* Skipped: neither cleared nor pending. Grey and struck through. */
.timeline .tl.is-skipped .tl-art { opacity: 0.3; filter: grayscale(1); }
.timeline .tl.is-skipped .tl-medal::after {
  content: ''; position: absolute; left: 12%; right: 12%; top: 50%; height: 1px; background: var(--muted); transform: rotate(-35deg);
}
/* The final boss: last and largest, in its doorway of light. */
.timeline .tl.is-final { --tl: 62px; }
.timeline .tl.is-final .tl-medal { background: var(--pantheon-door-rest); }
.timeline .tl.is-final:is(.is-current, .is-done, .is-preview) .tl-medal { background: var(--pantheon-door); }
.timeline .tl.is-final .tl-thread { width: var(--sp-6); }
/* A room that can be tapped lights up under the pointer. */
@media (hover: hover) {
  button.tl-go:hover .tl-medal { box-shadow: inset 0 0 0 1px var(--accent-line), 0 0 10px var(--accent-soft); }
  button.tl-go:hover .tl-art { opacity: 1; filter: brightness(1.15); }
}
button.tl-go:focus-visible { outline-offset: 3px; }
/* On the picker, the chosen pantheon's way through, under Enter. */
.run-path { display: flex; flex-direction: column; gap: var(--sp-3); }
.run-path .block-head { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }

/* The rest: one card per station, and the whole card is the button, because you decide the
   order. On top, its corner of the Godhome room —the only area of the game with golden
   light—, which fades into the card at the bottom; below, what it would give you now with
   the HUD icons. The figures in bone, like all of them; only what you lose goes in red.
   Three in a row if they fit; if not, one below the other with the artwork on the left. */
.rest-room { container-type: inline-size; }
.rest-stations { --rs-ico: 18px; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-3); }
.rest-station {
  display: flex; flex-direction: column; min-width: 0; padding: 0; text-align: left;
  border: 1px solid var(--inv-line);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.rest-station:hover:not(:disabled) { border-color: var(--accent-line); background: var(--veil); }
.rs-art { position: relative; display: block; flex: none; aspect-ratio: 16 / 9; overflow: hidden; background: var(--scene-bg); }
.rs-art img { display: block; width: 100%; height: 100%; object-fit: cover;
  filter: brightness(0.8); transition: filter var(--dur) var(--ease); }
.rest-station:hover:not(:disabled) .rs-art img { filter: none; }
.rs-art::after { content: ''; position: absolute; inset: auto 0 0; height: 35%;
  background: linear-gradient(transparent, var(--inv-bg)); }
.rs-body { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3) var(--sp-3); min-width: 0; }
.rs-name { font-size: var(--fs-sm); color: var(--ink); }
.rs-fxs { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-3); }
.rs-fx { display: inline-flex; align-items: center; gap: var(--sp-1); font-size: var(--fs-xs); color: var(--muted); }
.rs-fx b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); font-weight: 600; color: var(--ink-strong); }
.rs-fx.is-loss b { color: var(--bad); }
.rs-fx.is-note { font-style: italic; }
.rs-rule { font-size: var(--fs-2xs); color: var(--muted); }
/* The icons, fitted by their body like the panel's masks (.pip-mask): the slot is the body
   and the sprite is scaled and shifted. Soul: soul.png, a 37 px disc in 45. */
.rs-ico { position: relative; display: inline-block; flex: none; height: var(--rs-ico); width: calc(var(--rs-ico) * 51 / 66); }
.rs-ico img { position: absolute; max-width: none; height: auto; width: 117.647%; left: -7.843%; top: -15.152%; }
.rs-ico.is-lb img { width: 173.222%; left: -35.837%; top: -26.154%; }
.rs-ico.is-soul { width: var(--rs-ico); }
.rs-ico.is-soul img { width: 121.622%; left: -10.811%; top: -10.811%; }
/* No cocoon, or already broken: the station dims, but the reason can still be read. */
.rest-station:disabled { opacity: 1; }
.rest-station:disabled .rs-art img { filter: grayscale(1) brightness(0.4); }
.rest-station:disabled .rs-name { color: var(--muted); }

@container (max-width: 559px) {
  .rest-stations { grid-template-columns: minmax(0, 1fr); gap: var(--sp-2); }
  .rest-station { flex-direction: row; }
  .rs-art { width: 132px; aspect-ratio: auto; min-height: 88px; }
  .rs-art::after { inset: 0 0 0 auto; width: 35%; height: auto;
    background: linear-gradient(90deg, transparent, var(--inv-bg)); }
  .rs-body { flex: 1; justify-content: center; padding: var(--sp-2) var(--sp-3); }
}

@media (max-width: 899px) {
  /* In one column: your scene, the scoreboard in a row, the enemy on its stage; then their
     attacks before yours, so you tap one of theirs and see who it hits without scrolling the
     whole list; and, at the end, the log. */
  .arena { gap: var(--sp-6); }
  .stage { grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
  .vs { flex-direction: row; flex-wrap: wrap; }
  .vs-score { flex-direction: row; gap: var(--sp-4); }
  .vs-mark i { width: var(--sp-4); }
  .fscene { min-height: 196px; }
  .fscene-art { height: 132px; }
  .stage .kside-head h3, .ftitle-main { font-size: var(--fs-xl); }
  .mgroup.is-spell .mgrid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  /* The band, on mobile: the Knight without his figure (the HUD says it), the scoreboard and the enemy. */
  .fband {
    grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "me vs" "foe foe";
    gap: var(--sp-1) var(--sp-3); padding: var(--sp-2) var(--sp-3); min-height: 0;
  }
  .fband-me { grid-area: me; --orb: 36px; --pip-h: 13px; --hud-row: 130px; }
  .fband-vs { grid-area: vs; flex-direction: row; gap: var(--sp-3); }
  .fband-foe { grid-area: foe; gap: var(--sp-2); }
  .fband-knight, .fband .fight-undo span { display: none; }
  .fband .fight-undo { min-width: 32px; min-height: var(--ctl-sm); justify-content: center; text-decoration: none; }
  .fband-art { width: 40px; height: 36px; }
  .fband-score b { font-size: var(--fs-lg); }
  .fband-num { font-size: var(--fs-md); }
  .commands { grid-template-columns: minmax(0, 1fr); gap: var(--sp-8); }
  .commands > .side-foes { order: -1; }
  .foecard.is-minion { margin-left: var(--sp-4); }
  .timeline { --tl: 34px; }
  .timeline .tl:is(.tl-rest, .tl-godseeker) { --tl: 40px; }
  .timeline .tl.is-current { --tl: 44px; }
  .timeline .tl.is-final { --tl: 50px; }
  .tl-thread, .timeline .tl:is(.tl-rest, .tl-godseeker, .is-final) .tl-thread { width: var(--sp-2); }
}

/* ── Hall of Gods ──────────────────────────────────────────────────────
   The game's statues are almost black sculptures: each carries a light behind it, not a
   box. It's Godhome's radial halo (--godhome-light*, tokens.css) faded into the surface
   underneath, with a stone rule under the feet as a pedestal: lit silhouettes in a dark
   room, which is how they look there. The light is the background, not an accent: the
   chosen one is marked, as everywhere else, with --accent. At rest the light is bronze, so
   35 pedestals don't dazzle; the chosen one and the one under your finger light up, and
   the ones the filter dims stay in half-light. */
/* The Idol is the room's header, in one row, so the statues are the first thing on the
   screen: its figure small, its name and when it appears, the three counts and, on the right,
   the two rare actions (Mark in bulk, the tablet). */
.hall-idol {
  display: flex; align-items: center; gap: var(--sp-3) var(--sp-6); flex-wrap: wrap;
  padding-bottom: var(--sp-4); border-bottom: 1px solid var(--inv-line);
}
.hall-idol-art {
  flex: none; width: 40px; height: 52px; padding: var(--sp-half);
  display: flex; align-items: flex-end; justify-content: center;
  background: var(--godhome-light); border-bottom: 3px solid var(--godhome-line);
}
.hall-idol-art img { max-width: 100%; max-height: 100%; object-fit: contain; }
/* Not won: the light at rest and the silhouette at half opacity, like an unbeaten statue. */
.hall-idol.is-off .hall-idol-art { background-image: var(--godhome-light-rest); }
.hall-idol.is-off .hall-idol-art img { opacity: 0.6; }
/* The figure, its name and when it appears. */
.hall-idol-id { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; max-width: 20rem; }
.hall-idol-id > div { display: flex; flex-direction: column; gap: var(--sp-half); min-width: 0; }
.hall-idol-id h3 { font-family: var(--font-display); font-size: var(--fs-lg); font-weight: 600; letter-spacing: var(--track-title); color: var(--ink-strong); line-height: 1.2; }
.hall-idol-note { font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.4; }
.hall-counts { display: flex; gap: var(--sp-2) var(--sp-6); flex-wrap: wrap; }
/* On a wide screen the counts never break: they're one row with the name and the actions. */
@media (min-width: 900px) { .hall-counts { flex-wrap: nowrap; } }
/* Each count is a filter: active, it carries the accent frame and dims on the grid the ones
   already beaten at that difficulty (.ped-btn.is-dim). The negative padding leaves the text
   where it was and gives the button its tap area. The figure, above its label: they're the
   header's hero, in bone and the sans. */
.hall-count-btn {
  display: grid; grid-template-columns: auto auto; grid-template-rows: auto auto; align-items: center;
  column-gap: var(--sp-2); text-align: left;
  min-height: var(--ctl); padding: var(--sp-1) var(--sp-2); margin: 0 calc(-1 * var(--sp-2));
  border: 1px solid transparent;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.hall-count-btn .hall-badge { grid-row: 1 / 3; width: 24px; height: 24px; }
.hall-count-btn:hover { border-color: var(--line-ctl); }
.hall-count-btn:hover .hall-count-lbl, .hall-count-btn.is-on .hall-count-lbl { color: var(--accent); }
.hall-count-btn.is-on { border-color: var(--accent-line); background: var(--picked); }
/* Right under the row, closer than the screen's gap: it says what the pressed count does. */
.hall-filter-note { margin-top: calc(-1 * var(--sp-3)); font-size: var(--fs-xs); color: var(--ink-2); }
.hall-count-num { line-height: 1; }
.hall-count-num b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); font-weight: 600; color: var(--ink-strong); }
.hall-count-num .u { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--muted); }
.hall-count-lbl {
  font-family: var(--font-num); text-transform: uppercase; letter-spacing: var(--track-label);
  font-size: var(--fs-2xs); font-weight: 600; color: var(--label); line-height: 1.3;
}
.hall-idol-acts { margin-left: auto; display: flex; align-items: center; gap: var(--sp-2) var(--sp-4); flex-wrap: wrap; }
/* The rules, read once, at the foot of the grid. */
.hall-rules { max-width: var(--measure); margin-top: var(--sp-4); font-size: var(--fs-xs); color: var(--muted); font-style: italic; }
/* Mark in bulk: its button in the Idol's row; open, the panel goes under the row, on the right,
   below its button: two rows of three badges (label, then the badges), and after an action the
   line that undoes it. */
.hall-undo { min-height: var(--ctl); padding: var(--sp-1) var(--sp-4); font-size: var(--fs-2xs); }
.hall-bulk { align-self: flex-end; display: grid; grid-template-columns: auto repeat(3, 40px); align-items: center; justify-content: start; gap: 0 var(--sp-2); }
.hall-bulk-row { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; align-items: center; }
.hall-bulk-row .lbl { padding-right: var(--sp-2); white-space: nowrap; }
.hall-bulk-btn { min-height: var(--ctl); padding: 0; }
.hall-bulk-done { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--sp-2) var(--sp-3); flex-wrap: wrap; font-size: var(--fs-xs); color: var(--ink-2); }

/* The bronze, silver and radiant symbols are the game's badges; not won, in grey. */
.hall-badge { flex: none; width: 20px; height: 20px; object-fit: contain; filter: var(--missing-art); }
.hall-badge.is-on { filter: none; opacity: 1; }
/* On the tile, at 12 px: the same language as the Idol and the plaque. */
.ped-marks .hall-badge { width: 12px; height: 12px; }

/* Right after marking on the plaque (js/app-hall.js, markFx): the symbol inks in from a blur with
   a burst of light, on the plaque and on the tile; its count flashes; and the statue's light
   swells once, on the plaque and in its niche. Removed, the symbol lets its light go. The Idol
   changing tier lights up like a plate on Your game. */
.hall-badge.is-inking { animation: badge-ink var(--dur-flash) var(--ease-fade); }
.hall-badge.is-out { animation: lit-out var(--dur-slow) var(--ease); }
@keyframes badge-ink {
  from { opacity: 0; filter: blur(3px) brightness(2); }
  45% { opacity: 1; filter: brightness(1.8) drop-shadow(0 0 6px var(--godhome-glow)); }
}
.hall-count.is-flash b { animation: num-flash var(--dur-flash) var(--ease); }
.hall-art.is-marked, .ped-btn.is-marked .ped-niche::after { animation: niche-swell 1.2s var(--ease-fade); }
@keyframes niche-swell { 30% { filter: brightness(1.35); } }
.hall-idol.is-lit .hall-idol-art img { animation: lit-in 1.2s var(--ease); }
.hall-idol.is-out .hall-idol-art img { animation: lit-out var(--dur-slow) var(--ease); }
/* A count's filter switching: the ones that dim let their light go and the ones that wake take it back,
   instead of snapping (filterWas). */
.ped-btn.is-dimming .ped-niche::before { animation: niche-dim var(--dur-slow) var(--ease-fade); }
.ped-btn.is-dimming .ped-niche > img:first-child { animation: glow-out var(--dur-slow) var(--ease-fade); }
.ped-btn.is-waking .ped-niche::before { animation: niche-wake var(--dur-slow) var(--ease-fade); }
.ped-btn.is-waking .ped-niche > img:first-child { animation: niche-wake-art var(--dur-slow) var(--ease-fade); }
@keyframes niche-dim { from { filter: brightness(1.8); } }
@keyframes niche-wake { from { filter: brightness(0.4); } }
@keyframes niche-wake-art { from { opacity: 0.55; } }

/* The room: the statues and, next to them, the chosen one's plaque, which is the focal point. */
.hall-room { display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, min(420px, 40%)); gap: var(--sp-6); align-items: start; }
.hall-floor { min-width: 0; }

/* The pedestals: one per boss, and the double ones (lever, dreamcatcher) take two cells with
   a shared frame, so that each half has its statue at full size. */
/* "dense" fills the gap a double pedestal leaves when it doesn't fit at the end of a row:
   the single statue that came after moves up to fill it. */
.hall-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); grid-auto-flow: row dense; gap: var(--sp-2); }
.ped { display: grid; }
/* A double pedestal is one pedestal: the same frame as the others and the stone band
   continuous under both halves; the second carries its glyph (Dream Nail or lever). */
.ped-pair { grid-column: span 2; grid-template-columns: 1fr 1fr; }
.ped-btn {
  position: relative; display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-1);
  min-width: 0; padding: 0 0 var(--sp-2); text-align: center;
  transition: background-color var(--dur-fast) var(--ease);
}
/* The niche: the card's surface, the pedestal as the bottom edge (outside the lights'
   inset:0) and two overlapping lights, the resting one and the lit one, which fades in on
   hover. The statue goes on top of both. */
.ped-niche {
  position: relative; display: block; aspect-ratio: 1;
  border-bottom: 5px solid var(--godhome-line);
}
.ped-niche::before, .ped-niche::after {
  content: ''; position: absolute; inset: 0; background: var(--godhome-light-rest);
}
.ped-niche::after { background: var(--godhome-light); opacity: 0; transition: opacity var(--dur) var(--ease); }
.ped-niche > img:first-child {
  position: absolute; z-index: 1; left: 7%; right: 7%; top: 8%; bottom: 3%; width: 86%; height: 89%;
  object-fit: contain; object-position: 50% 100%;
}
.ped-btn:hover .ped-niche::after, .ped-btn:focus-visible .ped-niche::after, .ped-btn.is-on .ped-niche::after { opacity: 1; }
.ped-btn:hover { background: var(--veil); }
/* The chosen statue (round 6): no frame (a frame says "press me"), its light on and its pedestal
   in the accent, like a statue lit in the game. */
.ped-btn.is-on { background: var(--picked); z-index: 1; }
.ped-btn.is-on .ped-niche { border-bottom-color: var(--accent); }
/* The dream version of a dreamcatcher pedestal: the same statue, with the Dream Nail. */
.ped-dream { position: absolute; z-index: 1; top: var(--sp-1); right: var(--sp-1); width: 18px; height: 18px; object-fit: contain; filter: drop-shadow(0 0 2px var(--godhome-ink)); }
/* The second fight of a lever pedestal carries the lever's glyph, in the same place. */
.ped-lever { position: absolute; z-index: 1; top: var(--sp-1); right: var(--sp-1); color: var(--godhome-ink); }
/* The name, in the dense cells' sans: a serif at 11 px on dark loses its strokes. */
.ped-name {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  min-height: 2.6em; padding: 0 var(--sp-1);
  font-family: var(--font-num); font-size: var(--fs-2xs); line-height: 1.3; color: var(--ink-2);
}
.ped-btn.is-on .ped-name { color: var(--ink-strong); }
.ped-marks { display: flex; justify-content: center; gap: var(--sp-1); }
/* With a count's filter, the ones already beaten at that difficulty have their light switched
   off and the silhouette drops to half opacity: the ones still to go stay lit. The chosen one
   keeps its accent rule. */
.ped-btn.is-dim .ped-niche::before { background: var(--godhome-light-off); }
.ped-btn.is-dim .ped-niche > img:first-child { opacity: 0.55; }
/* Locked in your game (the boss not beaten in the kingdom yet): the statue in shadow, as the
   game draws a pedestal still covered, with no light in its niche. */
.ped-btn.is-locked .ped-niche::before { background: var(--godhome-light-off); }
.ped-btn.is-locked .ped-niche > img:first-child { filter: brightness(0.25) saturate(0); opacity: 0.8; }
.ped-btn.is-locked .ped-name { color: var(--muted); }
.ped-btn.is-dim .ped-name { color: var(--muted); }
.ped-btn.is-dim .ped-marks { opacity: 0.5; }

/* The plaque: the Journal page, with the statue in its niche and one row per difficulty. It's
   the room's focal point: next to the grid, stuck under the bar while the grid scrolls, with
   the statue large and lit. */
.hall-plaque.jr-page {
  position: sticky; top: calc(var(--sticky-h) + var(--sp-4)); margin: 0; padding: var(--sp-4) var(--sp-4) var(--sp-6);
}
/* On a wide screen the grid is always next to it: "‹ Hall" is only for mobile, where the
   plaque replaces the grid. */
@media (min-width: 700px) { .hall-plaque .jr-back { display: none; } }
/* The chosen statue, with its light on, directly on the plaque's well: like the creature on
   the black on the Journal page. */
.hall-art {
  flex: none; width: 100%; height: clamp(170px, 24vh, 240px); padding: var(--sp-4) var(--sp-4) var(--sp-2);
  display: flex; align-items: flex-end; justify-content: center;
  background: var(--godhome-light); border-bottom: 6px solid var(--godhome-line);
}
.hall-art img { max-width: 100%; max-height: 100%; object-fit: contain; }
.hall-plaque .jr-title { font-size: var(--fs-2xl); line-height: 1.15; margin-top: var(--sp-3); }
.hall-title { font-size: var(--fs-md); color: var(--ink-2); font-style: italic; }
.hall-other { font-size: var(--fs-xs); min-height: var(--ctl); }
/* The table of the three difficulties: one row each, with its button to fight. */
.hall-table { width: 100%; border-collapse: collapse; text-align: left; table-layout: auto; }
.hall-table th, .hall-table td { padding: var(--sp-2) var(--sp-1); border-top: 1px solid var(--inv-line); vertical-align: middle; }
.hall-table thead th {
  border-top: 0; padding-top: 0;
  font-family: var(--font-num); text-transform: uppercase; letter-spacing: var(--track-label);
  font-size: var(--fs-2xs); font-weight: 600; color: var(--label); line-height: 1.2;
}
.hall-table td { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); font-weight: 600; color: var(--ink-strong); }
/* The figures, on the right and with air: they're compared in a column, from one difficulty to another. */
.hall-table td, .hall-table thead th:not(:first-child) { text-align: right; padding-left: var(--sp-3); }
.hall-table td:last-child, .hall-table thead th:last-child { padding-right: 0; }
.hall-table th:first-child { padding-left: 0; }
/* Each difficulty is a button that puts your real game's symbol in its socket, so it reads as
   something to fill and not as a label. Empty, the socket is the game's ring (as on the
   tablet) and the name waits in --label; beaten, the badge sits in it, lit, and the name goes
   to bone. Under the pointer it previews the click, like the notches with a charm: the badge
   you'd put breathes in over the ring, the one you'd remove fades. What responds to the finger
   lights up in --accent. */
.hall-mark {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: var(--ctl); padding: 0;
  font-family: var(--font-display); font-weight: 600; letter-spacing: var(--track-title); font-size: var(--fs-xs); color: var(--label);
  transition: color var(--dur-fast) var(--ease);
}
.hall-mark[aria-pressed="true"] { color: var(--ink-strong); }
.hall-socket {
  /* The ring takes a little of the accent: on a phone, with no hover, it's what says it's pressed. */
  --socket-ring: color-mix(in oklab, var(--accent-line) 45%, var(--tablet-empty-ring));
  flex: none; position: relative; width: 28px; height: 28px; display: grid; place-items: center; }
.hall-socket::before {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(circle closest-side,
      var(--tablet-empty-dot) 0 19%, transparent 25% 43%,
      var(--tablet-empty-ring-in) 52%, var(--socket-ring) 83% 90%, transparent 100%)
    center / 82% 82% no-repeat;
  transition: opacity var(--dur) var(--ease);
}
.hall-socket .hall-badge { position: relative; width: 28px; height: 28px; filter: none; opacity: 0; transition: opacity var(--dur) var(--ease); }
.hall-mark[aria-pressed="true"] .hall-socket::before { opacity: 0; }
.hall-mark[aria-pressed="true"] .hall-badge { opacity: 1; }
.hall-mark[aria-pressed="true"] .hall-badge.m-radiant { filter: var(--radiant-glow); }
@media (hover: hover) {
  .hall-mark:hover { color: var(--accent); }
  .hall-mark:hover .hall-socket { --socket-ring: var(--accent-line); }
  .hall-mark:not([aria-pressed="true"]):hover .hall-badge { animation: notch-pending var(--dur-pending) var(--ease-fade) infinite alternate; opacity: 0.55; }
  .hall-mark[aria-pressed="true"]:hover .hall-badge { opacity: 0.38; }
}
.hall-mark-hint { width: 100%; text-align: left; font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; }
/* Fight: one button per difficulty, below the table that compares them. */
.hall-go-row { width: 100%; display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.hall-go-row .lbl { margin-right: auto; text-align: left; }
/* The three difficulties to fight at: the label above and the three sharing the row below. */
.hall-go-row:has(.hall-go) .lbl { flex: 1 1 100%; }
.hall-go-row .hall-go { flex: 1 1 0; min-width: 0; padding-inline: var(--sp-2); }
@media (max-width: 599px) { .hall-go-row .hall-go { font-size: var(--fs-2xs); letter-spacing: var(--track-title); } }
/* Three primaries in a row can't all glow at once: the halo, only under the finger. */
/* Three equals in a row: bone, without the primary's light, which three times over was a smudge. */
.hall-go { min-height: var(--ctl); padding: var(--sp-1) var(--sp-3); font-size: var(--fs-xs); letter-spacing: var(--track-title); }
.hall-notes { width: 100%; text-align: left; font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; }
.hall-arena { width: 100%; text-align: left; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.5; padding-top: var(--sp-3); border-top: 1px solid var(--inv-line); }
.hall-arena b {
  font-family: var(--font-num); text-transform: uppercase; letter-spacing: var(--track-label);
  font-size: var(--fs-2xs); font-weight: 600; color: var(--label); margin-right: var(--sp-1);
}

/* In front of the statue. */
.hall-diff {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-family: var(--font-num); text-transform: uppercase; letter-spacing: var(--track-label);
  font-size: var(--fs-2xs); font-weight: 600; color: var(--ink-2);
}
.hall-head-acts { margin-left: auto; display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.run-head .hall-head-acts .btn { margin-left: 0; }

/* "Read the tablet" (tabletMini), at the end of the Idol's row: the tablet's two ornaments around
   its label, small, as the game's screen in a word. Until 25 September it carried the 44 in
   miniature, which pushed the statues below the fold. Under the pointer, and while the tablet is
   open, it lights up. */
.hall-mini {
  flex: none; display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-3); border-radius: var(--sp-1); opacity: 0.9;
  transition: opacity var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.mini-orn { width: 132px; height: auto; }
.mini-orn.is-ftr { width: 72px; }
.mini-lbl {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-2xs); letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--label); transition: color var(--dur) var(--ease);
}
.hall-mini:is(:hover, :focus-visible, [aria-pressed="true"]) { opacity: 1; background: var(--veil); }
.hall-mini:is(:hover, :focus-visible, [aria-pressed="true"]) .mini-lbl { color: var(--ink-strong); }

/* ── The Hall's tablet ─────────────────────────────────────────────────
   Traced from its screen in the game ("Screenshot HK Hall of Gods 03", 1920 × 1080). The
   unit is --tu, the names' body size (26 px in the screenshot), and every measurement from
   the screenshot is in multiples of it, like --qc on the charm grid:
     columns    four of eleven rows, every 16.33 (424.5 px): the list measures 65.3
     rows       every 2.36 (61.4 px); the symbol, 2 tall with its halo, and the name at 0.38
     title      1.7, in regular Trajan; the names, in capitals and semibold: Cinzel 600
                without spacing gives 338 px for "MASSIVE MOSS CHARGER", and the game 342
     ornaments  the top one measures 28.8 wide (749 px) and the bottom one 17.3 (450 px)
     air        0.4 from the ornament to the title, 1.6 from the title to the list, 1.7 from the list to the foot
     "Exit"     at 5.5 from the bottom ornament's edge, which is a controller spot: here you go back at the top
   Cinzel stands in for Trajan: its lowercase letters are small caps, so "Hall of Gods" comes
   out as in the game without forcing anything. It doesn't cover the site: it's read inside the
   Hall, in place of the grid and the plaque, and unboxed, because the frame's black is already
   its screen's. That's why the unit comes from the frame's width and not the window's: the
   tablet is a container, and with four columns the list fills it as in the game (--fs-tablet). */
.tablet { container: tablet / inline-size; color: var(--ink-strong); }
.tablet-bar, .tablet-page { --tu: var(--fs-tablet); }
.tablet-page {
  width: min(100%, calc(var(--tu) * 65.3)); margin: 0 auto;
  display: flex; flex-direction: column; align-items: center;
}
.tablet-hdr { width: min(100%, calc(var(--tu) * 28.8)); height: auto; }
.tablet-title {
  margin-top: calc(var(--tu) * 0.4); font-family: var(--font-display); font-size: calc(var(--tu) * 1.7);
  font-weight: 400; letter-spacing: var(--track-title); line-height: 1.1; color: var(--ink-strong); text-align: center;
}
/* Eleven rows, filled by columns, as in the game: top to bottom and then to the right. */
.tablet-list {
  width: 100%; margin: calc(var(--tu) * 1.6) 0 0; padding: 0; list-style: none;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: repeat(11, auto);
  grid-auto-flow: column; row-gap: calc(var(--tu) * 0.36); column-gap: 0;
}
.tablet-row { display: flex; align-items: center; gap: calc(var(--tu) * 0.38); min-width: 0; min-height: calc(var(--tu) * 2); }
.tablet-mark { flex: none; width: calc(var(--tu) * 2); height: calc(var(--tu) * 2); object-fit: contain; }
.tablet-mark.m-radiant { filter: var(--radiant-glow); }
/* Opening, the tablet fades in and its names appear down the columns, as if read; closing, the
   statues fade back in (tabletFx). */
.tablet.is-fresh .tablet-hdr, .tablet.is-fresh .tablet-title, .tablet.is-fresh .tablet-ftr { animation: title-in var(--dur-slow) var(--ease-fade) both; }
.tablet.is-fresh .tablet-row { animation: title-in var(--dur-slow) var(--ease-fade) both; animation-delay: calc(120ms + var(--i) * 14ms); }
.hall-room.is-fresh { animation: title-in var(--dur-slow) var(--ease-fade); }
/* With no symbol, the game doesn't leave the spot blank: it puts an empty ring, centred where
   the symbol would go. At 1920 px it measures 20.6 across (0.79 of the body): a dot of radius
   2.3, a gap up to 5 and a ring up to 10.3. Here, in a box of 0.82 so the soft edge falls at 0.79. */
.tablet-mark.is-empty {
  background: radial-gradient(circle closest-side,
      var(--tablet-empty-dot) 0 19%, transparent 25% 43%,
      var(--tablet-empty-ring-in) 52%, var(--tablet-empty-ring) 83% 90%, transparent 100%)
    center / calc(var(--tu) * 0.82) calc(var(--tu) * 0.82) no-repeat;
}
.tablet-name {
  min-width: 0; padding-right: var(--sp-1); font-family: var(--font-display); font-size: var(--tu); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0; line-height: 1.15; color: var(--ink-strong);
}
.tablet-ftr { width: calc(var(--tu) * 17.3); max-width: 60%; height: auto; margin-top: calc(var(--tu) * 1.7); }
/* "‹ Statues", at the top left, where you go back on the web (like "‹ Hall" on the mobile
   plaque). The game's "Exit" sits by the bottom ornament because there you leave with the
   controller; here you leave with a finger or the mouse. Unboxed, in the screen's ink and
   typeface. With four columns, next to the ornament: it takes no room (the bottom margin gives
   its height back) and doesn't catch clicks outside the button, and the tablet fits on one screen. */
.tablet-bar { display: flex; height: 44px; margin-bottom: -44px; pointer-events: none; }
.tablet-back {
  display: flex; align-items: center; gap: calc(var(--tu) * 0.4);
  padding: 0 var(--sp-2); margin-left: calc(-1 * var(--sp-2)); pointer-events: auto;
  font-family: var(--font-body); font-size: calc(var(--tu) * 1.17); color: var(--ink-strong);
  transition: color var(--dur-fast) var(--ease);
}
.tablet-back svg { width: calc(var(--tu) * 0.8); height: calc(var(--tu) * 0.8); }
.tablet-back:hover { color: var(--accent); }
/* On opening, focus goes to the title (tabletOpen), to read from the top; it isn't a control:
   no ring. With Shift+Tab you reach "‹ Statues", which does have one. */
.tablet-title:focus-visible { outline: none; }

/* Where the four columns don't fit without breaking the names, the list splits into two
   (22 rows) and, on mobile, into one; the order is still the tablet's. Then it's taller than
   the screen, so "‹ Statues" goes on its own line, above the ornament, and stays stuck under
   the bar while scrolling down the list: a strip of the frame's black across its full width,
   faded at the bottom, like a screen edge and not a box. */
@container tablet (max-width: 999px) {
  .tablet-bar, .tablet-page { --tu: var(--fs-lg); }
  .tablet-page { width: min(100%, calc(var(--tu) * 40)); }
  .tablet-list { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(22, auto); }
  .tablet-bar {
    position: sticky; top: var(--sticky-h); z-index: 1;
    margin: 0 calc(-1 * var(--inv-pad)) var(--sp-2); padding: 0 var(--inv-pad);
    background: var(--inv-bg);
  }
  /* The fade, downwards only: a shadow would also spill out of the frame's sides. */
  .tablet-bar::after {
    content: ''; position: absolute; inset: 100% 0 auto; height: var(--sp-3);
    background: linear-gradient(var(--inv-bg), transparent); pointer-events: none;
  }
}
@container tablet (max-width: 519px) {
  .tablet-title { font-size: var(--fs-xl); }
  .tablet-list { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; grid-auto-flow: row; }
  .tablet-ftr { margin-top: var(--sp-4); }
}

/* On mobile the grid and the plaque take turns, like the Journal's list and page. */
@media (max-width: 699px) {
  .hall-room { grid-template-columns: minmax(0, 1fr); }
  .hall-room.is-reading .hall-floor { display: none; }
  .hall-room:not(.is-reading) .hall-plaque { display: none; }
  .hall-plaque.jr-page { position: relative; top: auto; padding: var(--sp-3); }
  .hall-art { height: 200px; }
  .hall-plaque .jr-title { font-size: var(--fs-xl); }
  .hall-go-row .lbl { flex: 1 1 100%; }
  .hall-go { flex: 1 1 0; justify-content: center; }
  .hall-grid { grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); gap: var(--sp-1); }
  /* The Idol's row stacks in three short lines: the name, the three counts across the width
     and the two actions side by side; Mark in bulk opens under them, on the left. */
  .hall-idol { gap: var(--sp-3); }
  .hall-idol-id { max-width: none; flex: 1 1 100%; }
  .hall-counts { flex: 1 1 100%; justify-content: space-between; gap: var(--sp-2); }
  .hall-idol-acts { flex: 1 1 100%; margin-left: 0; flex-wrap: nowrap; justify-content: space-between; }
  .hall-bulk { align-self: flex-start; }
  .hall-bulk-toggle { white-space: nowrap; }
}

/* ── The Hunter's Journal: your game ───────────────────────────────────
   The Journal's quest, apart from combat: it's its own screen, the fourth in the bar (until
   22-Sep, a full-screen <dialog>). The Hunter and the counts at the top, and below them the
   list and the page. Everything with the hj- prefix, so as not to clash with the
   combat Journal (.journal, .jr-*), which has its own handlers. */

/* The screen, like the others: the black with its corner brackets in its tint (Fungal Wastes',
   which the map study set aside for the enemies) and the title centred. */
.hj-sec { --tint: var(--tint-journal); }
.hj-body {
  --inv-pad: var(--sp-8);
  --inv-line: color-mix(in oklab, var(--tint) 13%, transparent);
  position: relative; background: var(--inv-bg); padding: var(--inv-pad);
  display: flex; flex-direction: column; gap: var(--sp-4);
}
.hj-head .inv-head { margin-bottom: 0; }

/* The Hunter: his artwork, his line and the counts, unboxed, like the Charms sheet: the
   artwork loose with its light behind it, and the figures on the other side of a rule in the tint. */
.hj-top { display: flex; flex-direction: column; gap: var(--sp-2); }
.hj-hunter {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--sp-4) var(--sp-8);
}
.hj-hunter-fig { position: relative; isolation: isolate; display: flex; }
.hj-hunter-fig::before {
  content: ''; position: absolute; z-index: -1; left: 50%; top: 55%; width: 200px; height: 160px; transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, var(--scene-glow), transparent);
}
.hj-hunter-art { width: 110px; height: auto; align-self: end; }
.hj-hunter-body { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
/* The game's title when talking to him: a small "The" above "Hunter". */
.hj-hunter-name { display: flex; flex-direction: column; font-family: var(--font-display); font-size: var(--fs-lg); font-weight: 600; letter-spacing: var(--track-caps); color: var(--ink-strong); line-height: 1.1; }
.hj-hunter-name small { font-size: var(--fs-2xs); letter-spacing: var(--track-wide); color: var(--ink-2); }
.hj-says { margin: 0; display: flex; flex-direction: column; gap: var(--sp-1); max-width: 60ch; font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-2); }
.hj-hunter-side {
  display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start;
  padding-left: var(--sp-6); border-left: 1px solid var(--inv-line);
}
.hj-counts, .hj-feats { display: flex; gap: var(--sp-2) var(--sp-6); flex-wrap: wrap; }
/* The counts are read-only: filtering is the list tabs' job. */
.hj-tally { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-half); }
.hj-count-num b, .hj-feat-num b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink-strong); }
.hj-count-num b { font-size: var(--fs-xl); line-height: 1; }
.hj-count-num .u, .hj-feat-num .u { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-style: normal; font-size: var(--fs-xs); color: var(--muted); }
.hj-count-lbl, .hj-feat-lbl {
  font-family: var(--font-num); text-transform: uppercase; letter-spacing: var(--track-label);
  font-size: var(--fs-2xs); font-weight: 600; color: var(--label);
}
.hj-feat { display: flex; align-items: baseline; gap: var(--sp-2); }
.hj-feat-num b { font-size: var(--fs-sm); }
.hj-feat.is-done .hj-feat-lbl { color: var(--ink-strong); }
.hj-total-note { max-width: 40ch; font-size: var(--fs-xs); line-height: 1.5; color: var(--muted); }
.hj-howto { max-width: 40ch; font-size: var(--fs-sm); line-height: 1.5; color: var(--ink); }
/* Mark in bulk, at the foot of the list. Collapsed, only its text button. Open, unboxed and on
   three short lines, so the list stays in view while ticking boxes: at the bottom, the
   button and All · None (it doesn't move on opening: the panel grows upwards); above, which
   ones it acts on, and above that the three actions as text with how many would change, and "Undo". */
.hj-bulkbar {
  flex: none; display: flex; flex-direction: column-reverse; gap: var(--sp-2);
  margin-left: var(--sp-3); padding: var(--sp-2) var(--sp-4) 0 0; border-top: 1px solid var(--inv-line);
}
.hj-bulk-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.hj-bulk-toggle { min-height: var(--ctl); }
.hj-bulk-picks { display: inline-flex; align-items: center; gap: var(--sp-2); }
.hj-bulk-sep { color: var(--line-ctl); }
.hj-bulk { display: flex; flex-direction: column; gap: var(--sp-1); }
.hj-bulk-scope { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--sp-2); }
.hj-bulk-rule { font-size: var(--fs-xs); color: var(--muted); }
.hj-bulk-acts { display: flex; align-items: center; gap: 0 var(--sp-4); flex-wrap: wrap; }
/* The actions and All · None, as text like the entry's state; dimmed if they wouldn't
   change anything. */
.hj-bulk-btn { font-size: var(--fs-sm); }
.hj-pick-btn { font-size: var(--fs-xs); }
.hj-bulk-btn:disabled, .hj-pick-btn:disabled { opacity: 0.55; }
.hj-bulk-btn i { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-style: normal; font-size: var(--fs-xs); color: var(--muted); }
.hj-bulk-btn:hover:not(:disabled) i { color: var(--ink-2); }
.hj-bulk-done { display: flex; align-items: baseline; gap: var(--sp-1) var(--sp-3); flex-wrap: wrap; font-size: var(--fs-xs); color: var(--ink-2); }
.hj-undo { font-size: var(--fs-xs); }
/* Each row's checkbox, in front of the medallion. Picked, it fills with the accent: it's a state
   of what responds to the finger. The ones that never change keep the space, so the column
   doesn't go out of line. With Shift a range is picked: with no text selection in between. */
.hj-list li.is-picking { display: flex; align-items: stretch; -webkit-user-select: none; user-select: none; }
.hj-list li.is-picking .hj-row { flex: 1 1 auto; min-width: 0; }
.hj-pick { flex: none; width: 32px; display: grid; place-items: center; }
.hj-row.is-picked:not(.is-cur) { background: var(--accent-soft); }
@media (pointer: coarse) { .hj-pick { width: 44px; } }

/* The open book: the index on the left and the entry on the right, unboxed, on the screen's
   black. What makes it a book is the spine —a rule in the tint that fades at the top and the
   bottom, with the light of both pages rising towards it. No corner brackets of its own: the
   screen's already frame it, and two frames read as a picture inside another. The entry's
   number goes tiny in the portrait's corner. They measure what's left of the window under the
   bar (never less than 460 px) and each page scrolls inside. */
.hj-pane {
  position: relative; flex: none; height: var(--hj-pane-h, max(460px, calc(100dvh - var(--sticky-h) - var(--sp-8) * 2)));
  display: grid; grid-template-columns: minmax(260px, 340px) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
  margin-bottom: var(--sp-2);
  container-type: size;   /* the page's portrait is sized in proportion to the book */
}
.hj-index > .search { flex: none; margin: 0 var(--sp-4) 0 var(--sp-3); }
.hj-index {
  position: relative; display: flex; flex-direction: column; min-height: 0; padding: var(--sp-4) 0 0 var(--sp-1);
}
/* The fold's light: an ellipse centred on the spine that fades towards all four sides, so it
   doesn't read as a box's edge. */
.hj-index::before {
  content: ''; position: absolute; top: 0; bottom: 0; right: -160px; width: 320px; pointer-events: none;
  background: radial-gradient(closest-side, var(--hj-leaf), transparent);
}
.hj-index::after {
  content: ''; position: absolute; top: 0; bottom: 0; right: 0; width: 1px;
  background: linear-gradient(to bottom, transparent, var(--hj-spine) 14%, var(--hj-spine) 86%, transparent);
}
/* As on the Arena's page: at the top right, level with the portrait. */
.hj-folio {
  position: absolute; top: var(--sp-2); right: 0;
  font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xs); letter-spacing: var(--track-label); color: var(--muted);
}
/* The state tabs, under the search box: text with its count below and the chosen one underlined,
   like the Combat tabs. Four in the column: the label goes small and on one line, and the
   spare space is shared out. */
.hj-show { flex: none; padding: var(--sp-3) var(--sp-4) var(--sp-2) var(--sp-3); }
.hj-tabs { display: flex; justify-content: space-between; gap: var(--sp-2); width: 100%; }
.hj-tabs button {
  display: flex; flex-direction: column; align-items: center; gap: 1px; padding: var(--sp-1) 0;
  font-family: var(--font-body); font-size: var(--fs-xs); line-height: 1.2; white-space: nowrap;
}
.hj-tabs i { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-style: normal; font-size: var(--fs-2xs); color: var(--muted); }
.hj-tabs button[aria-pressed="true"] i { color: var(--ink-2); }
.hj-list { position: relative; flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-3); }
/* The rows, like the Arena picker's: on hover, a veil; the one you're reading, in its rectangle
   (--picked with the accent's rule). */
.hj-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--sp-3);
  width: 100%; text-align: left; padding: 3px var(--sp-2);
  font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.25;
  border: 1px solid transparent;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.hj-row:hover { background: var(--veil); color: var(--ink); }
.hj-row.is-cur { background: var(--picked); color: var(--ink-strong); box-shadow: inset 2px 0 0 var(--accent); }
.hj-row.is-unseen { color: var(--muted); }
.hj-row.is-unseen.is-cur { color: var(--ink-2); }
.hj-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hj-left { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--muted); }
.hj-row.is-cur .hj-left { color: var(--ink-2); }

/* The Journal medallion (84 × 96: the ring and its two finials). Complete, it carries the game's
   frame on top (Bestiary frame upgrade.png, 102 × 105), centred on the ring. Not encountered,
   shadowed. */
.hj-medal { position: relative; flex: none; width: 42px; height: 48px; }
.hj-medal img { width: 100%; height: 100%; object-fit: contain; }
.hj-medal .hj-frame { position: absolute; left: -10.71%; top: -4.69%; width: 121.43%; height: 109.375%; }
/* With the frame, the medallion's white ring would peek out between its points: it's clipped inside
   the ring (radius 38 of 84, measured on the sprites), and the frame acts as the border. */
.hj-row.is-done .hj-medal img:first-child { clip-path: circle(42% at 50% 50%); }
.hj-row.is-unseen .hj-medal img { filter: var(--missing-art); }
/* Right after marking (hjWas): the entry just encountered lights its medallion up from the shadow;
   the one just completed, its frame. The number of defeats and the counts that moved flash, and a
   feat reached lights up. Changing the list's tabs, the list fades in. */
.hj-row.is-new-seen .hj-medal img { animation: lit-in var(--dur-flash) var(--ease); }
.hj-row.is-new-done .hj-frame { animation: lit-in var(--dur-flash) var(--ease); }
.hj-count.is-flash, .hj-tally.is-flash b { animation: num-flash var(--dur-flash) var(--ease); }
.hj-feat.is-lit { animation: feat-lit 1.2s var(--ease-fade); }
@keyframes feat-lit { 25% { text-shadow: 0 0 12px var(--tint-journal), 0 0 3px var(--tint-journal); } }
.hj-list.is-fresh { animation: detail-in var(--dur) var(--ease); }
/* Receiving the Hunter's Mark: his light swells in the Journal's tint and dust rises from him, the
   main menu's motes (save-dust, the slots'). */
.hj-hunter.is-marked .hj-hunter-fig::before { animation: hunter-swell 1.8s var(--ease-fade); }
.hj-hunter.is-marked .hj-hunter-art { animation: lit-in 1.4s var(--ease); }
@keyframes hunter-swell {
  30% { filter: brightness(1.6) drop-shadow(0 0 24px var(--tint-journal)); transform: translate(-50%, -50%) scale(1.25); }
}
.hj-dust { position: absolute; inset: -20% -30% 0; pointer-events: none; }
.hj-dust i {
  position: absolute; left: var(--x); top: var(--y); width: var(--s); height: var(--s); border-radius: 50%;
  background: var(--tint-journal); box-shadow: 0 0 6px var(--tint-journal); opacity: 0;
  animation: hunter-dust 1.6s var(--ease-fade) both; animation-delay: var(--d);
}
@keyframes hunter-dust {
  0% { opacity: 0; transform: translate(0, 0); }
  25% { opacity: var(--o); }
  100% { opacity: 0; transform: translate(var(--dx), -90px); }
}

.hj-page {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding: var(--sp-4) var(--sp-8) var(--sp-4); text-align: center;
}
.hj-back { display: none; }
/* The portrait rules, as in the game: large and with the arena's light behind it; not
   encountered, a silhouette with the light off. */
.hj-art { position: relative; isolation: isolate; flex: none; height: clamp(96px, 26cqh, 280px); width: 100%; display: flex; align-items: flex-end; justify-content: center; }
.hj-art::before {
  content: ''; position: absolute; z-index: -1; left: 50%; top: 55%; width: min(440px, 100%); height: calc(100% + 40px); transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, var(--scene-glow), transparent);
}
.hj-art.is-shadow::before { background: radial-gradient(closest-side, var(--scene-glow-low), transparent); }
/* A creature's portrait stands in its area's light, the arena's (js/app-arena.js, areaOf): the
   area's value ramp, the upper mid-tone above and the lower one below, faded to black all round so
   it reads as light and not as a box, with the same pale neutral light behind it. Not yet
   encountered, only a trace of it. */
.hj-art[data-area] { --area-k: 78%; }
.hj-art:is([data-area="godhome"], [data-area="radiance"]) { --area-k: 55%; }
.hj-art[data-area]::before {
  background:
    radial-gradient(closest-side, var(--area-spot), transparent 70%),
    linear-gradient(to bottom,
      color-mix(in oklab, var(--area-mid) var(--area-k), var(--scene-bg)),
      color-mix(in oklab, var(--area-deep) var(--area-k), var(--scene-bg)) 60%,
      var(--scene-bg));
  -webkit-mask: radial-gradient(closest-side, #000 40%, transparent); mask: radial-gradient(closest-side, #000 40%, transparent);
}
.hj-art.is-shadow[data-area] { --area-k: 22%; }
/* With no area, the arena's cold spotlight, as on its stage. */
.hj-art:not([data-area])::before { background: radial-gradient(closest-side, var(--scene-cold), transparent); }
.hj-art.is-shadow:not([data-area])::before { background: radial-gradient(closest-side, var(--scene-cold-off), transparent); }
.hj-art img { max-height: 100%; max-width: 100%; object-fit: contain; }
.hj-art.is-medal img { height: 168px; }
.hj-art.is-shadow img { filter: var(--hj-unseen-art); }
.hj-art img.is-missing { visibility: hidden; }
.hj-page-name { font-family: var(--font-display); font-size: var(--fs-2xl); font-weight: 600; letter-spacing: var(--track-title); line-height: 1.15; text-wrap: balance; color: var(--ink-strong); margin-top: var(--sp-3); }
/* Where it's found, under the name: a quiet line in the body serif, as the game sets a place. */
.hj-zone { margin: 0; font-style: italic; font-size: var(--fs-sm); color: var(--muted); }
/* The Journal's flourish: the screen's only ornament. */
.hj-fleur { flex: none; width: min(237px, 70%); height: auto; }
.hj-text {
  display: flex; flex-direction: column; gap: var(--sp-3);
  width: 100%; max-width: var(--measure); margin-top: var(--sp-2);
  text-align: left; line-height: 1.55;
}
.hj-desc { font-size: var(--fs-md); color: var(--ink); white-space: pre-line; }
/* The book's two voices: the description in the serif, and the Hunter's notes handwritten, in
   capitals. Undeciphered, their ink is there but blurred; on completing the entry it settles. */
.hj-notes, .hj-sealed {
  font-family: var(--font-hand); font-size: var(--fs-lg); text-transform: uppercase; letter-spacing: var(--track-title);
  line-height: 1.7; color: var(--ink-2); white-space: pre-line;
}
.hj-sealed { filter: var(--hj-sealed); opacity: 0.8; -webkit-user-select: none; user-select: none; pointer-events: none; }
@media (prefers-reduced-motion: no-preference) {
  .hj-notes.is-inking { animation: hj-ink var(--dur-flash) var(--ease-fade); }
}
@keyframes hj-ink { from { filter: var(--hj-sealed); opacity: 0.8; } }
.hj-by { font-size: var(--fs-sm); color: var(--muted); text-align: right; }
.hj-aside { font-size: var(--fs-sm); color: var(--muted); font-style: italic; }
.hj-inspect { font-size: var(--fs-sm); color: var(--ink-2); padding-left: var(--sp-3); border-left: 2px solid var(--line-ctl); }
/* "Defeat [−] N [+] more to decipher the Hunter's notes.": the game's sentence, and the
   number is the control. The number goes in --font-num, like every figure. */
.hj-kills { font-size: var(--fs-md); color: var(--ink-2); line-height: 2.2; }
/* The − and + are icon buttons (their own ring), so the group carries no box: the number sits
   between them in a sunken well with its rule, like the other fields. */
.hj-stepper, .stepper { display: inline-flex; align-items: center; gap: var(--sp-half); vertical-align: middle; margin: 0 var(--sp-1); line-height: 1; }
.hj-step, .step { width: var(--ctl-sm); min-height: var(--ctl-sm); font-family: var(--font-num); font-size: var(--fs-md); color: var(--ink-2); }
.pgm-zoom .step:hover:not(:disabled) { color: var(--accent); background: var(--card-idle); }
.hj-step:disabled, .step:disabled { color: var(--line-ctl); }
.hj-count, .step-count {
  width: 4.5ch; min-height: var(--ctl-sm); padding: 0 var(--sp-1); text-align: center;
  font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); font-weight: 600; color: var(--ink-strong);
  background: var(--card); border: 0; border-bottom: 1px solid var(--line-ctl); border-radius: 0;
  -moz-appearance: textfield; appearance: textfield;
}
.hj-count::-webkit-outer-spin-button, .hj-count::-webkit-inner-spin-button,
.step-count::-webkit-outer-spin-button, .step-count::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* The entry's state, at the top of the page and in view on entering: the states as text,
   like the tabs, with the chosen one underlined. "In your game" is left only for the screen
   reader (the group's aria-label). */
.hj-mark {
  flex: none; width: 100%; display: flex; align-items: baseline; justify-content: center; gap: var(--sp-2) var(--sp-4); flex-wrap: wrap;
  padding-bottom: var(--sp-3); border-bottom: 1px solid var(--inv-line);
}
.hj-state { display: inline-flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; justify-content: center; }
.hj-state button { font-family: var(--font-body); font-size: var(--fs-md); }
.hj-state-lbl { font-size: var(--fs-xs); color: var(--muted); }
.hj-state-lbl::after { content: ':'; }


/* On mobile the list and the page take turns, as in the combat Journal; the Hunter only
   shows with the list. */
@media (max-width: 699px) {
  .hj-body { --inv-pad: var(--sp-4); gap: var(--sp-3); }
  .hj-hunter { grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-3) var(--sp-4); }
  .hj-hunter-art { width: 96px; }
  .hj-hunter-fig::before { width: 160px; height: 140px; }
  .hj-says { font-size: var(--fs-sm); }
  .hj-hunter-side { grid-column: 1 / -1; padding: var(--sp-3) 0 0; border-left: 0; border-top: 1px solid var(--inv-line); }
  .hj-top.is-reading { display: none; }
  .hj-pane { height: auto; grid-template-columns: minmax(0, 1fr); grid-template-rows: none; container-type: normal; }
  .hj-index { padding: var(--sp-3) 0; }
  /* The label on its own line and the three states in one row below, instead of breaking the row in two. */
  .hj-state { gap: var(--sp-1) var(--sp-2); }
  .hj-state-lbl { flex: 1 1 100%; text-align: center; }
  .hj-state-lbl::after { content: none; }
  /* The pantheon's summary: the bindings on a line of their own, without the separator in front. */
  .run-sum span:last-child { flex-basis: 100%; }
  .run-sum span:last-child::before { content: none; }
  .hj-state button { font-size: var(--fs-sm); }
  .hj-index::before, .hj-index::after { display: none; }
  .hj-list { flex: none; max-height: 64vh; }
  .hj-pane.is-reading .hj-index { display: none; }
  .hj-pane:not(.is-reading) .hj-page { display: none; }
  .hj-page { padding: var(--sp-4) var(--sp-3); overflow: visible; background: none; }
  .hj-back { display: inline-flex; align-self: flex-start; }
  .hj-art { height: 200px; }
  .hj-art::before { height: 240px; }
  .hj-art.is-medal img { height: 112px; }
  .hj-page-name { font-size: var(--fs-xl); }
  /* Between the title and "Close" it doesn't fit: the notice becomes a full-width strip, on one line. */
}

/* ── Saves: the game's profile screen ────────────────────────────────────
   The four slots stacked like the game's bars: the number, the masks and soul vessels with the
   HUD's sprites, the nail and what the game carries (charms found, Journal, Hall). No boxes:
   rules in the tint between slots, and the whole bar responds to the finger. The slot you're
   playing carries the accent's line on its edge, as the equipped charm does, and a light from
   that edge. Free mode, which is nobody's game, is the one sheet with a frame. */
.saves { --tint: var(--tint-sheet); }
.saves-body {
  --inv-pad: var(--sp-8);
  --inv-line: color-mix(in oklab, var(--tint) 13%, transparent);
  position: relative; background: var(--inv-bg); padding: var(--inv-pad);
}
.saves-note { max-width: var(--measure); margin: 0; font-size: var(--fs-sm); color: var(--muted); text-wrap: pretty; }
/* Free mode and the four share their columns (subgrid): the slot, then its buttons, stacked as a
   menu in a column of their own behind a rule (free mode's note, there), so the numbers, the masks,
   the nails and the buttons line up from one slot to the next whatever each one holds. */
.saves-lists { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
.saves-list {
  grid-column: 1 / -1; list-style: none; margin: 0; padding: 0; border-bottom: 1px solid var(--inv-line);
  display: grid; grid-template-columns: subgrid;
}
.save {
  grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; align-items: center;
  border-top: 1px solid var(--inv-line); transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.save-main {
  width: 100%; display: grid; grid-template-columns: var(--sp-12) minmax(0, 1fr) calc(var(--sp-16) * 2);
  grid-template-areas: "n hud nail" "n facts nail"; align-items: center; gap: var(--sp-3) var(--sp-6);
  padding: var(--sp-4); text-align: left; font-size: var(--fs-md); color: var(--ink);
}
/* The slot is one piece, its buttons included: the hover, the light of the one you're in and the
   focus ring go on the whole row, not only on the part that loads it. The hover is a light coming
   in from the left, the side the number is on; the nail catches it. */
.save:has(> .save-main:hover) { background: linear-gradient(90deg, var(--accent-soft), transparent 75%); }
.save.is-active {
  box-shadow: inset var(--sp-half) 0 0 var(--accent-line);
  background: linear-gradient(90deg, color-mix(in oklab, var(--accent-line) 16%, transparent), transparent 60%);
}
.save.is-active:has(> .save-main:hover) { background: linear-gradient(90deg, color-mix(in oklab, var(--accent-line) 24%, transparent), transparent 75%); }
.save-main:focus-visible { outline: none; }
.save:has(> .save-main:focus-visible) { outline: 2px solid var(--accent); outline-offset: -2px; }
.save-n {
  grid-area: n; justify-self: center; text-align: center;
  font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xl); line-height: 1; color: var(--ink-strong);
}
.save-hud { grid-area: hud; display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }
.save-masks, .save-vessels { display: flex; flex-wrap: wrap; gap: var(--sp-half); }
.save-masks img { height: var(--sp-6); width: auto; }
.save-vessels img { height: var(--sp-4); width: auto; }
/* You're here: the rule's diamond, lit, and the words, at the head of the card. */
.save.is-active:not(.is-free) .save-main { grid-template-areas: "n tag nail" "n hud nail" "n facts nail"; }
.save-tag {
  grid-area: tag; justify-self: start; display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-2xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-2);
}
.save-tag::before {
  content: ''; width: var(--sp-2); height: var(--sp-2); transform: rotate(45deg);
  background: var(--ink-strong); box-shadow: 0 0 8px color-mix(in oklab, var(--ink-strong) 55%, transparent);
}
.save-nail { grid-area: nail; display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); font-size: var(--fs-xs); color: var(--ink-2); text-align: center; text-wrap: balance; }
.save-nail img { height: var(--sp-16); width: auto; transition: filter var(--dur-slow) var(--ease); }
.save:has(> .save-main:hover) .save-nail img { filter: drop-shadow(0 0 10px color-mix(in oklab, var(--tint) 45%, transparent)); }
.save-facts { grid-area: facts; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-6); }
.save-fact { display: inline-flex; align-items: baseline; gap: var(--sp-2); }
.save-k { font-size: var(--fs-2xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--label); }
.save-fact b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); color: var(--ink-strong); }
.save-fact .u { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); }
/* The slot's buttons: a menu in the second column, behind a rule, one under the other. */
.save-foot {
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: var(--sp-1);
  align-self: stretch; margin-block: var(--sp-4); padding-inline: var(--sp-4) var(--sp-6);
  border-left: 1px solid var(--inv-line);
}
/* The file the slot follows, above its buttons; on the one you're in, how the link is. */
.save-link { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-1); padding: 0 var(--sp-4) var(--sp-2); font-size: var(--fs-xs); color: var(--ink-2); }
.save-link-t { font-family: var(--font-num); overflow-wrap: anywhere; }
.save-link b { font-family: var(--font-body); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; font-size: var(--fs-2xs); color: var(--ink-strong); }
.save-link.is-live b { color: var(--good); }
.save-link.is-paused b, .save-link.is-lost b { color: var(--cond); }
.save-link .text-btn { font-size: var(--fs-xs); }
.save-ask { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-1); }
.save-ask-q { padding-inline: var(--sp-4); font-size: var(--fs-sm); color: var(--ink-strong); }
/* Under the question: only the site's copy goes, the game's file isn't touched. */
.save-ask-note { max-width: 26ch; padding-inline: var(--sp-4); font-size: var(--fs-xs); line-height: 1.4; color: var(--muted); text-wrap: pretty; }
.save-ask-yn { display: flex; gap: var(--sp-2); }
/* The slot's buttons, as the game's menu items (js/app-saves.js, FLEURS): capitals in the menu's
   face, no box, and the menu's two pointers fading in on either side of the one you're on.
   Quieter than the slot itself: they're what you do to it, not what it holds. */
.save-act {
  position: relative; display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: var(--ctl); padding: var(--sp-1) var(--sp-4);
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-xs); letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--label); transition: color var(--dur) var(--ease);
}
.save-act:hover, .save-act:focus-visible { color: var(--ink-strong); }
.save-act.is-clear:hover, .save-act.is-clear:focus-visible { color: var(--bad); }
/* The pointers are the focus mark, as in the game's menus: no ring on top of them. */
.save-act:focus-visible { outline: none; }
.save-act-short { display: none; }
.save-fleur {
  position: absolute; top: 50%; width: var(--sp-3); height: calc(var(--sp-4) + var(--sp-1)); display: flex; color: var(--ink-strong);
  opacity: 0; transform: translateY(-50%) scaleX(0.3); pointer-events: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.save-fleur svg { width: 100%; height: 100%; }
.save-fleur.is-l { left: calc(var(--sp-1) * -1); }
.save-fleur.is-r { right: calc(var(--sp-1) * -1); }
.save-fleur.is-r svg { transform: scaleX(-1); }
.save-act:hover .save-fleur, .save-act:focus-visible .save-fleur,
.save-main:hover .save-new .save-fleur, .save-main:focus-visible .save-new .save-fleur { opacity: 1; transform: translateY(-50%) scaleX(1); }
/* The icons, in the text's ink. Import's arrow dips into the tray; Clear's bin lifts its lid. */
.save-ico { width: var(--sp-4); height: var(--sp-4); flex-shrink: 0; overflow: visible; }
.save-ico-arrow, .save-ico-lid { transition: transform var(--dur) var(--ease); transform-box: fill-box; }
.save-ico-lid { transform-origin: 100% 100%; }
.save-act.is-import:hover .save-ico-arrow, .save-act.is-import:focus-visible .save-ico-arrow { transform: translateY(1.5px); }
.save-act.is-clear:hover .save-ico-lid, .save-act.is-clear:focus-visible .save-ico-lid { transform: translate(0.5px, -1.5px) rotate(14deg); }
.save-ico-turn { transform-origin: 50% 50%; transition: transform var(--dur-slow) var(--ease); transform-box: fill-box; }
.save-act.is-follow:hover .save-ico-turn, .save-act.is-follow:focus-visible .save-ico-turn { transform: rotate(180deg); }
/* Free mode, above the four and apart from them: nobody's game, so a sheet of its own, with the
   Knight under a bench's lamp instead of a number and its name on its own row. */
.saves-list.is-free {
  margin-bottom: var(--sp-8); border: 1px solid var(--inv-line);
  background: linear-gradient(180deg, color-mix(in oklab, var(--tint) 5%, transparent), transparent 70%);
}
.saves-list.is-free .save { border-top: 0; }
.save-note { max-width: calc(var(--sp-16) * 3); padding-inline: var(--sp-6); font-size: var(--fs-sm); line-height: 1.5; color: var(--muted); text-wrap: pretty; }
.save.is-free .save-main { grid-template-areas: "n name nail" "n hud nail" "n facts nail"; padding-block: var(--sp-6); }
.save.is-free .save-n {
  position: relative; isolation: isolate; display: grid; place-items: center;
}
.save.is-free .save-n::before {
  content: ''; position: absolute; z-index: -1; width: calc(var(--sp-16) * 1.5); aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in oklab, var(--tint) 20%, transparent), transparent);
}
.save.is-free .save-n img { height: var(--sp-12); width: auto; }
.save-name { grid-area: name; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-4); }
.save-name b { font-family: var(--font-display); font-weight: 600; text-transform: uppercase; letter-spacing: var(--track-caps); font-size: var(--fs-md); color: var(--ink-strong); }
/* An empty slot is its one way in, where the game's says New Game: the number unlit and «Import
   from the game», with its tray, between the menu's pointers (a save only comes from the file). */
.save.is-empty .save-main { grid-template-areas: "n new new"; min-height: calc(var(--sp-16) + var(--sp-6)); }
.save.is-empty .save-n { color: var(--muted); transition: color var(--dur) var(--ease); }
.save-new {
  grid-area: new; justify-self: start; position: relative; display: inline-flex; align-items: center; gap: var(--sp-2);
  margin-inline-start: calc(var(--sp-4) * -1); padding-inline: var(--sp-4);
  font-family: var(--font-display); font-weight: 600; text-transform: uppercase; letter-spacing: var(--track-caps); font-size: var(--fs-md);
  color: var(--ink-2); transition: color var(--dur) var(--ease);
}
.save.is-empty .save-main:hover .save-new, .save.is-empty .save-main:focus-visible .save-new,
.save.is-empty .save-main:hover .save-n { color: var(--ink-strong); }
.save.is-empty .save-main:hover .save-ico-arrow, .save.is-empty .save-main:focus-visible .save-ico-arrow { transform: translateY(1.5px); }
/* Going into a game (js/app-saves.js, leave): the slot's nail catches the light and its masks
   glow before the page fades. */
.save.is-loading { pointer-events: none; }
.save.is-loading .save-nail img { animation: lit-in 600ms var(--ease) both; }
.save.is-loading .save-masks img { animation: light-up 600ms var(--ease) both; }

/* Clear Save, seen (js/app-saves.js, shatter): the masks break from the right one after another
   (a flash and gone, as on the HUD), the vessels drain after them, and the rest of the slot fades
   while dust rises out of it. Then the slot is cleared and its Import fades in. */
.save { position: relative; }
/* Where you'd wake up (js/app-saves.js): the area of your last bench, as the game's profile
   screen says it, and its light (--where, the area's deep tone) rising from the right, behind
   the nail. Your shade, if it waits, closes the line. */
.save.is-where { isolation: isolate; }
.save.is-where::after {
  content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(270deg, color-mix(in oklab, var(--where) 70%, transparent), transparent 65%);
}
.save-where { flex-basis: 100%; font-family: var(--font-display); font-size: var(--fs-sm); letter-spacing: var(--track-title); color: var(--ink); }
.save-shade { flex-basis: 100%; font-size: var(--fs-xs); color: var(--muted); }
.save.is-clearing { pointer-events: none; }
.save.is-clearing .save-foot { opacity: 0; transition: opacity var(--dur) var(--ease); }
.save.is-clearing .save-masks img { animation: save-break 420ms var(--ease) both; animation-delay: calc(var(--r) * 70ms); }
.save.is-clearing .save-vessels img { animation: save-drain 420ms var(--ease) both; animation-delay: calc(var(--masks) * 70ms + var(--r) * 60ms); }
.save.is-clearing .save-main { animation: save-fade 700ms var(--ease-fade) calc(var(--masks) * 70ms + 150ms) both; }
.save.is-clearing { background: transparent; }
.save-dust { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.save-dust i {
  position: absolute; left: var(--x); top: var(--y); width: var(--s); height: var(--s); border-radius: 50%;
  background: var(--mote); box-shadow: 0 0 6px var(--mote); opacity: 0;
  animation: save-dust 900ms var(--ease-fade) both; animation-delay: calc(var(--masks) * 70ms + var(--d) * 0.6);
}
.save.is-cleared .save-new, .save.is-cleared .save-n, .save.is-cleared .save-foot { animation: title-in 700ms var(--ease-fade) both; }
@keyframes save-break {
  30% { opacity: 1; filter: brightness(2.6); transform: scale(1.12); }
  100% { opacity: 0; filter: brightness(2.6); transform: scale(0.7); }
}
@keyframes save-drain { to { opacity: 0; filter: brightness(0.4); } }
@keyframes save-fade { to { opacity: 0; filter: blur(3px); } }
@keyframes save-dust {
  0% { opacity: 0; transform: translate(0, 0); }
  25% { opacity: var(--o); }
  100% { opacity: 0; transform: translate(var(--dx), -48px); }
}
/* ── The import view ────────────────────────────────────────────────────
   It takes the list's place on the same sheet. Left, the three steps to find the game's file,
   each on a medallion (the game's achievement icons: a clean circle with a white line) joined by
   a thread; right, the zone to drop it on: the Knight under a light that breathes, as a bench's
   lamp does, and brightens when a file comes over it. Once read, the zone shows the save as the
   game's profile screen would (masks appearing one by one, the nail, the time and the %) before
   anything is written. Only fades, and the Knight floats (design/02 §5: no slides, no bounce). */
.imp-bar { display: flex; margin-bottom: calc(var(--sp-4) * -1); }
.imp-back { gap: var(--sp-2); min-height: var(--ctl); font-size: var(--fs-sm); }
.imp .saves-note { margin-inline: auto; }
.imp-mobile {
  max-width: var(--measure); margin: var(--sp-6) auto 0; padding: var(--sp-3) var(--sp-4);
  border-left: 2px solid var(--bracket); background: var(--card); font-size: var(--fs-sm); color: var(--ink-2);
}
.imp-grid {
  display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: var(--sp-8);
  align-items: stretch; margin-top: var(--sp-8);
}

.imp-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.imp-step { position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-4); padding-bottom: var(--sp-8); }
.imp-step:last-child { padding-bottom: 0; }
/* The thread from one medallion to the next. */
.imp-step:not(:last-child)::before {
  content: ''; position: absolute; left: calc(var(--sp-12) / 2); top: var(--sp-12); bottom: 0; width: 1px;
  background: linear-gradient(var(--bracket), transparent);
}
.imp-num {
  display: grid; place-items: center; width: var(--sp-12); height: var(--sp-12); border-radius: 50%;
  border: 1px solid var(--bracket); background: var(--card);
  box-shadow: inset 0 0 0 3px var(--scene-bg), inset 0 0 0 4px color-mix(in oklab, var(--tint) 18%, transparent);
  font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-lg); color: var(--ink-strong);
}
.imp-step-body { min-width: 0; padding-top: var(--sp-2); display: flex; flex-direction: column; gap: var(--sp-3); }
.imp-step-title { margin: 0; font-family: var(--font-display); font-weight: 600; font-size: var(--fs-lg); letter-spacing: var(--track-title); line-height: 1.25; color: var(--ink-strong); }
.imp-text { margin: 0; max-width: var(--measure); font-size: var(--fs-sm); line-height: 1.6; color: var(--ink-2); text-wrap: pretty; }
.imp-os { align-self: flex-start; }
.imp-path {
  display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--line-ctl); background: var(--card);
}
.imp-path code { flex: 1; min-width: 0; font-family: var(--font-num); font-size: var(--fs-sm); color: var(--ink); overflow-wrap: anywhere; user-select: all; }
.imp-copy { flex-shrink: 0; font-size: var(--fs-sm); }
.imp-copy.is-done { color: var(--ink-strong); text-decoration-color: transparent; }
.imp-copy.is-done::before { content: '✓ '; }
kbd {
  display: inline-block; padding: 0 var(--sp-1); border: 1px solid var(--line-ctl); border-bottom-width: 2px;
  background: var(--card); font-family: var(--font-num); font-size: var(--fs-xs); line-height: 1.5; color: var(--ink-strong); white-space: nowrap;
}
.imp-files { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.imp-files li {
  display: inline-flex; align-items: center; gap: var(--sp-2); padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--line); font-family: var(--font-num); font-size: var(--fs-xs); color: var(--ink);
}
.imp-files li.is-bak { color: var(--muted); border-style: dashed; text-decoration: line-through; text-decoration-color: var(--muted); }
.imp-ficon { flex-shrink: 0; color: var(--label); }

.imp-drop {
  position: relative; isolation: isolate; overflow: hidden;
  display: flex; flex-direction: column; min-height: calc(var(--sp-16) * 7);
  border: 1px dashed color-mix(in oklab, var(--tint) 32%, transparent); background: var(--scene-bg);
  transition: border-color var(--dur) var(--ease);
}
.imp-light {
  position: absolute; inset: 0; z-index: -1; pointer-events: none; opacity: 0.7;
  background: radial-gradient(55% 48% at 50% 40%, color-mix(in oklab, var(--tint) 17%, transparent), transparent 72%);
  transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
}
.imp-drop.is-drag { border-style: solid; border-color: var(--accent-line); }
.imp-drop.is-drag .imp-light { opacity: 1; transform: scale(1.15); }
.imp-drop-in { flex: 1; display: flex; align-items: center; justify-content: center; padding: var(--sp-8) var(--sp-6) var(--sp-4); }
.imp-still { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); text-align: center; }
.imp-figure { height: calc(var(--sp-16) * 2.25); width: auto; margin-bottom: var(--sp-3); transition: opacity var(--dur-slow) var(--ease), filter var(--dur-slow) var(--ease); }
.imp-figure.is-shade { height: calc(var(--sp-16) * 1.75); }
.imp-drop.is-drag .imp-figure { filter: drop-shadow(0 0 18px color-mix(in oklab, var(--tint) 45%, transparent)); }
.imp-drop[data-state="reading"] .imp-figure { opacity: 0.45; }
.imp-drop-title, .imp-drop-drag { margin: 0; font-family: var(--font-display); font-weight: 600; font-size: var(--fs-xl); letter-spacing: var(--track-title); color: var(--ink-strong); }
.imp-drop-drag, .imp-drop.is-drag .imp-drop-title { display: none; }
.imp-drop.is-drag .imp-drop-drag { display: block; }
.imp-drop.is-drag .imp-or, .imp-drop.is-drag .imp-still .btn { visibility: hidden; }
.imp-or { margin: 0; font-size: var(--fs-sm); color: var(--muted); }
.imp-err { margin: 0; max-width: 30ch; font-size: var(--fs-md); color: var(--ink); text-wrap: balance; }
.imp-private { margin: 0; padding: 0 var(--sp-6) var(--sp-4); font-size: var(--fs-xs); color: var(--muted); text-align: center; text-wrap: balance; }

.imp-found {
  display: grid; grid-template-columns: auto auto; justify-content: center; align-items: center;
  grid-template-areas: "lead lead" "fname fname" "nail hud" "nail meta" "facts facts" "pans pans" "warn warn" "actions actions" "sync sync" "other other";
  gap: var(--sp-3) var(--sp-6); text-align: center;
}
.imp-found-lead { grid-area: lead; justify-self: center; max-width: 40ch; margin: 0 0 var(--sp-2); font-size: var(--fs-md); line-height: 1.45; color: var(--ink-strong); text-wrap: balance; }
.imp-fname { grid-area: fname; margin: 0 0 var(--sp-2); display: inline-flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: var(--sp-2); font-family: var(--font-num); font-size: var(--fs-sm); color: var(--ink-2); }
.imp-steel { margin-left: var(--sp-2); color: var(--ink-strong); }
.imp-hud { grid-area: hud; display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); justify-content: flex-start; }
.imp-hud .save-masks img { height: var(--sp-8); }
.imp-hud .save-vessels img { height: var(--sp-6); }
.imp-nail { grid-area: nail; display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); font-size: var(--fs-xs); color: var(--ink-2); }
.imp-nail img { height: calc(var(--sp-16) + var(--sp-8)); width: auto; }
.imp-meta { grid-area: meta; margin: 0; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-4); align-items: baseline; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-sm); color: var(--ink); }
.imp-meta b { font-weight: 600; color: var(--ink-strong); }
.imp-ver { font-family: var(--font-body); font-size: var(--fs-xs); color: var(--muted); }
.imp-meta .save-k { margin-right: var(--sp-1); }
.imp-facts { grid-area: facts; justify-content: center; margin: var(--sp-2) 0 0; }
/* The pantheons completed: five diamonds like the rule's, outlined, and filled in bone when done. */
.imp-pans { grid-area: pans; display: flex; align-items: center; justify-content: center; gap: var(--sp-3); }
.imp-pans b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); color: var(--ink-strong); }
.imp-pans .u { margin-left: calc(var(--sp-3) * -1 + var(--sp-half)); font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); }
.imp-pan-row { list-style: none; margin: 0; padding: 0; display: flex; gap: var(--sp-2); }
.imp-pan { width: var(--sp-3); height: var(--sp-3); transform: rotate(45deg); border: 1px solid color-mix(in oklab, var(--tint) 45%, transparent); }
.imp-pan.is-done { background: var(--ink-strong); border-color: var(--ink-strong); box-shadow: 0 0 8px color-mix(in oklab, var(--ink-strong) 45%, transparent); }
.imp-warn { grid-area: warn; margin: 0; font-size: var(--fs-sm); color: var(--bad); }
/* Following the game (js/live.js): a row of the game's options menu. What it is in the text's
   ink, and its value on a button (.btn, its frame says it can be pressed) with the rule's
   diamond: lit in bone when it's on, hollow and dimmer when it's off. */
.imp-sync { grid-area: sync; justify-self: center; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: var(--sp-1) var(--sp-3); max-width: 44ch; }
.imp-sync-k { font-size: var(--fs-sm); color: var(--ink); }
.imp-sync-v { font-size: var(--fs-sm); }
.imp-sync-v[aria-checked="true"] { color: var(--ink-strong); }
.imp-sync-note { flex: 1 0 100%; margin: 0; font-size: var(--fs-xs); line-height: 1.45; color: var(--muted); text-wrap: pretty; }
.imp-actions { grid-area: actions; display: flex; justify-content: center; margin-top: var(--sp-3); }
.imp-other { grid-area: other; justify-self: center; }

@media (prefers-reduced-motion: no-preference) {
  .imp.is-fresh .imp-step { animation: title-in var(--dur-slow) var(--ease-fade) both; animation-delay: calc(var(--i) * 140ms + 100ms); }
  .imp.is-fresh .imp-drop { animation: title-in 700ms var(--ease-fade) 300ms both; }
  .imp-light { animation: imp-breathe 5s var(--ease-fade) infinite alternate; }
  .imp-drop.is-drag .imp-light { animation: none; }
  .imp-figure { animation: imp-float 4.2s ease-in-out infinite; }
  .imp-figure.is-shade { animation: title-in var(--dur-slow) var(--ease-fade) both, imp-float 3.2s ease-in-out infinite; }
  .imp-drop[data-state="reading"] .imp-drop-title { animation: notch-pending 700ms var(--ease-fade) infinite alternate; }
  .imp-drop-in > * { animation: title-in var(--dur-slow) var(--ease-fade) both; }
  .imp-hud img { animation: mask-mend 500ms var(--ease) both; animation-delay: calc(var(--i) * 70ms + 150ms); }
  .imp-meta, .imp-facts, .imp-pans, .imp-warn, .imp-actions, .imp-sync, .imp-other { animation: title-in 600ms var(--ease-fade) 600ms both; }
  /* Once it's in, the Import's brackets reach out and back twice: the preview isn't the end. */
  .imp-actions .btn-lg::before { animation: imp-call 800ms var(--ease) 1300ms 2; }
  .imp-pan.is-done { animation: title-in 500ms var(--ease-fade) both; animation-delay: calc(700ms + var(--p) * 120ms); }
}
@keyframes imp-breathe { from { opacity: 0.5; } to { opacity: 0.85; } }
@keyframes imp-float { 50% { transform: translateY(-6px); } }
/* In the header, the Knight and the slot you're playing; on mobile, only the number beside him. */
/* The save selector (saveLink): the Knight at a bench, under a lamp's light that breathes, and
   the label in the game's menu capitals, like the slots' buttons (.save-act). It's the way into
   your saves, so it reads as pressable at rest (27-sep): the Knight at full light and the menu's
   pointers already either side of the label, small; on hover or focus they open whole, the lamp
   swells and the Knight glows (the focus mark). */
.mh-save {
  display: inline-flex; align-items: center; gap: 0; min-height: var(--ctl); white-space: nowrap; text-decoration: none;
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-xs); letter-spacing: var(--track-caps); text-transform: uppercase;
  color: var(--ink); transition: color var(--dur) var(--ease);
}
.mh-save:focus-visible { outline: none; }
.mh-save-fig { position: relative; isolation: isolate; display: grid; place-items: center; width: var(--sp-12); height: var(--sp-12); }
.mh-save-fig img { height: var(--sp-12); width: auto; transition: filter var(--dur) var(--ease); }
.mh-save-light {
  position: absolute; inset: -25%; z-index: -1; pointer-events: none; opacity: 0.7;
  background: radial-gradient(closest-side, color-mix(in oklab, var(--tint-sheet) 22%, transparent), transparent);
  transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
}
.mh-save-lbl { position: relative; padding-inline: var(--sp-4); }
.mh-save .save-fleur { opacity: 0.5; transform: translateY(-50%) scaleX(0.6); }
.mh-save:is(:hover, :focus-visible, [aria-current="page"]) { color: var(--ink-strong); }
.mh-save:is(:hover, :focus-visible, [aria-current="page"]) .mh-save-fig img { filter: drop-shadow(0 0 6px var(--mote)); }
.mh-save:is(:hover, :focus-visible) .mh-save-light { opacity: 1; transform: scale(1.15); }
.mh-save:is(:hover, :focus-visible) .save-fleur { opacity: 1; transform: translateY(-50%) scaleX(1); }
@media (prefers-reduced-motion: no-preference) {
  .mh-save-light { animation: imp-breathe 5s var(--ease-fade) infinite alternate; }
  .mh-save:is(:hover, :focus-visible) .mh-save-light { animation: none; }
}
.mh-save-short { display: none; }
/* Following the game (js/live.js): under the label, the rule's diamond and the state in small
   capitals. Live, the diamond lit in bone, breathing like the lamp; paused or with the file
   missing, hollow, in the notice's tint. On mobile, only the diamond, over the save's number. */
.mh-save-lbl:has(.mh-live) { display: inline-flex; flex-direction: column; align-items: flex-start; gap: var(--sp-half); }
.mh-live { display: inline-flex; align-items: center; gap: var(--sp-2); font-family: var(--font-body); font-weight: 600; font-size: var(--fs-2xs); letter-spacing: var(--track-label); color: var(--ink-2); }
.mh-live-dot { width: var(--sp-2); height: var(--sp-2); transform: rotate(45deg); border: 1px solid currentColor; }
.mh-save.is-live .mh-live-dot { background: var(--ink-strong); border-color: var(--ink-strong); box-shadow: 0 0 8px color-mix(in oklab, var(--ink-strong) 55%, transparent); }
.mh-save.is-paused .mh-live, .mh-save.is-lost .mh-live { color: var(--cond); }
@media (prefers-reduced-motion: no-preference) {
  .mh-save.is-live .mh-live-dot { animation: live-breathe 2.4s var(--ease-fade) infinite alternate; }
}
@keyframes live-breathe { from { opacity: 0.55; } to { opacity: 1; } }
@media (max-width: 899px) {
  /* A slot takes the whole row, and a full one's menu (or free mode's note) goes under it. */
  .save .save-main { grid-column: 1 / -1; }
  .save.is-free .save-note { grid-column: 1 / -1; max-width: none; margin: 0; padding: 0 var(--sp-4) var(--sp-6) calc(var(--sp-12) + var(--sp-6) + var(--sp-4)); border-left: 0; }
  .save:not(.is-free) .save-foot {
    grid-column: 1 / -1; flex-direction: row; flex-wrap: wrap; justify-content: flex-end; align-items: center;
    margin: calc(var(--sp-2) * -1) 0 0; padding: 0 var(--sp-2) var(--sp-2); border-left: 0;
  }
  .save-ask { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: flex-end; }
  .save-ask-note { order: 1; flex: 1 1 100%; max-width: none; text-align: right; }
  .save-link { flex: 1 0 100%; flex-direction: row; flex-wrap: wrap; align-items: baseline; justify-content: flex-end; gap: var(--sp-1) var(--sp-4); padding: 0 var(--sp-3); }
  .imp-grid { grid-template-columns: minmax(0, 1fr); }
  .imp-drop { min-height: calc(var(--sp-16) * 6); }
}
@media (max-width: 599px) {
  .saves-body { --inv-pad: var(--sp-4); }
  .imp-found { grid-template-areas: "lead lead" "fname fname" "nail hud" "meta meta" "facts facts" "pans pans" "warn warn" "actions actions" "sync sync" "other other"; }
  .imp-meta { justify-content: center; }
  .imp-bar { margin-bottom: var(--sp-2); }
  /* On a phone an empty slot says only «Import». */
  .save.is-empty .save-main { min-height: calc(var(--sp-16) + var(--sp-2)); }
  .save-act-long { display: none; }
  .save-act-short { display: inline; }
  .save-act { padding-inline: var(--sp-3); letter-spacing: var(--track-title); }
  .save-new { font-size: var(--fs-sm); letter-spacing: var(--track-caps); }
  .imp-hud .save-masks img { height: var(--sp-6); }
  .imp-hud .save-vessels img { height: var(--sp-4); }
  .save-main { grid-template-columns: var(--sp-8) minmax(0, 1fr) var(--sp-8); gap: var(--sp-2) var(--sp-3); padding: var(--sp-3) var(--sp-2); }
  .save.is-free .save-main { padding-block: var(--sp-4) var(--sp-2); }
  .save.is-free .save-note { padding: 0 var(--sp-2) var(--sp-4) calc(var(--sp-8) + var(--sp-3) + var(--sp-2)); font-size: var(--fs-xs); }
  .save-new { white-space: nowrap; }
  /* The facts as a little table: the label, then the figure lined up on the right. */
  .save-facts { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: baseline; gap: var(--sp-1) var(--sp-half); }
  .save-fact { display: contents; }
  .save-fact b { text-align: right; }
  .save.is-free .save-n::before { width: var(--sp-16); }
  .save.is-free .save-n img { height: var(--sp-8); }
  .save-masks img { height: var(--sp-4); }
  .save-vessels img { height: var(--sp-3); }
  .save-nail img { height: var(--sp-12); }
  .save-nail span { display: none; }
  /* On a phone the Knight alone didn't say "your saves": larger, with a short word under him
     (Saves, or Save 1 after the state's diamond when it follows the game), in a 44 px target. */
  .mh-save-lbl, .mh-save-lbl:has(.mh-live) { display: none; }
  .mh-save { flex-direction: column; justify-content: center; gap: var(--sp-half); min-width: var(--ctl); min-height: var(--ctl); }
  .mh-save-fig { width: var(--sp-8); height: var(--sp-8); }
  .mh-save-fig img { height: var(--sp-8); }
  .mh-save-short {
    display: inline-flex; align-items: center; gap: var(--sp-1);
    font-family: var(--font-body); font-size: var(--fs-2xs); letter-spacing: 0; text-transform: none; line-height: 1.2;
  }
  .mh-save:is(.is-live, .is-paused, .is-lost) .mh-save-short::before {
    content: ''; flex: none; width: var(--diamond); height: var(--diamond); transform: rotate(45deg); border: 1px solid var(--cond);
  }
  .mh-save.is-live .mh-save-short::before { border-color: var(--ink-strong); background: var(--ink-strong); box-shadow: 0 0 8px color-mix(in oklab, var(--ink-strong) 55%, transparent); }
}

/* ── Hover and ambient, across the site ─────────────────────────────────────
   With a mouse, what can be pressed lights up and, where it can, previews the click. What's only
   to look at never reacts to the pointer (it would promise a click): it moves by itself, slowly,
   as the game's lights and objects do (design/02 §5), and only without reduced motion. */
@media (hover: hover) {
  /* The screen bar, on a phone, where the current tab carries a rule: that rule, faint, under the
     one you'd go to. On a computer the Knight marks the tab, and the name alone lights up. */
  @media (max-width: 899px) { .nav-tab:hover:not([aria-current="page"]) { box-shadow: inset 0 -2px 0 color-mix(in oklab, var(--accent) 35%, transparent); } }
  /* Your game: the nail under the pointer takes a faint spotlight; the plates light their artwork
     and their halo swells, like Charms' (a spell's plate, while you point at one of its levels). */
  .nailpick:not(.is-on):hover::before { opacity: 0.45; }
  .gplate-art::before { transition: opacity var(--dur) var(--ease), filter var(--dur) var(--ease), transform var(--dur) var(--ease); }
  .gplate-art img { transition: filter var(--dur) var(--ease); }
  :is(button.gplate:hover, .gplate:has(.lvlpick button:hover)) .gplate-art::before { filter: brightness(1.5); transform: scale(1.08); }
  :is(button.gplate:hover, .gplate:has(.lvlpick button:hover)).is-on .gplate-art img { filter: brightness(1.15) drop-shadow(0 0 8px color-mix(in oklab, var(--focus-glow) 45%, transparent)); }
  /* Combat: an attack's artwork takes the light; the Journal picker's row, its portrait. */
  .move-art img, .jr-medal { transition: filter var(--dur) var(--ease); }
  .move:hover:not(:disabled) .move-art img, :is(.move-card, .spell-card):has(.move:hover:not(:disabled)) .move-art img { filter: brightness(1.25) drop-shadow(0 0 6px color-mix(in oklab, var(--focus-glow) 40%, transparent)); }
  .jr-row:hover .jr-medal { filter: brightness(1.2); }
  /* Hall of Gods: a difficulty you haven't marked shows its symbol at half, in its colour, before
     the click; the statue under the pointer brightens a touch. */
  .hall-badge, .ped-niche > img:first-child { transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease); }
  .hall-mark:hover .hall-badge:not(.is-on) { filter: grayscale(0.3) brightness(0.8); opacity: 0.9; }
  .ped-btn:hover .ped-niche > img:first-child { filter: brightness(1.15); }
  /* Pantheons: a binding not marked on the door previews its light; the switches light their icon. */
  .pdone-bind:not(.is-on):hover img { filter: drop-shadow(0 0 4px var(--notch-used-glow)); opacity: 0.6; }
  .bind:not(.is-on):hover .bind-art { filter: grayscale(0.4) brightness(0.95); }
  /* The Journal: the row's medallion lights up. */
  .hj-medal img:first-child { transition: filter var(--dur) var(--ease); }
  .hj-row:not(.is-unseen):hover .hj-medal img:first-child { filter: brightness(1.2); }
  .hj-row.is-unseen:hover .hj-medal img:first-child { filter: var(--missing-art) brightness(1.6); }
}
@media (prefers-reduced-motion: no-preference) {
  /* The Hunter's light and the Void Idol's breathe, very slowly, like a bench's lamp. */
  .hj-hunter-fig::before { animation: lamp-breathe 6s var(--ease-fade) infinite alternate; }
  .hall-idol:not(.is-off) .hall-idol-art { animation: idol-breathe 6s var(--ease-fade) infinite alternate; }
  /* The chosen pantheon's final boss, on its light, too. */
  .pcard-wrap:not(.is-sealed) .pcard.is-on .pcard-art { animation: idol-breathe 5s var(--ease-fade) infinite alternate; }
  /* The enemy on the arena's stage floats a few px, like the Shade (shade-float): the hit, the
     phase and the fall take over while they last. */
  .fstage:not(.is-dying, .is-hit, .is-over, .is-staggered) .fscene-art { animation: shade-float 4.2s ease-in-out infinite; }
  /* The diamond of each screen's rule glints now and then. */
  .inv-head .rule path:last-child { animation: rule-glint 9s var(--ease-fade) infinite; }
  /* The HUD. The soul's surface rises and falls a hair, like the game's orb liquid (each vessel out
     of step); a lifeblood mask breathes its blue; and the orb's rim glints while there's enough in
     it for a spell or a Focus. The HUD's own animations (a mask breaking, soul moving) take over. */
  .hud-soul { animation: soul-slosh 4.5s ease-in-out infinite; }
  .hud-vessel img { animation: soul-slosh 4.5s ease-in-out infinite; }
  .hud-vessel:nth-child(2) img { animation-delay: -1.5s; }
  .hud-vessel:nth-child(3) img { animation-delay: -3s; }
  .hud-mask.lb:not(.is-empty):not(.is-mending):not(.is-breaking) img { animation: lb-breathe 2.8s var(--ease-fade) infinite alternate; }
  .hud-orb.is-ready::after {
    content: ''; position: absolute; inset: 3%; border-radius: 50%; pointer-events: none;
    box-shadow: 0 0 10px 1px color-mix(in oklab, var(--focus-glow) 55%, transparent);
    opacity: 0; animation: orb-ready 3.2s var(--ease-fade) infinite alternate;
  }
}
@keyframes lamp-breathe { from { opacity: 0.7; } to { opacity: 1; } }
@keyframes idol-breathe { to { filter: brightness(1.14); } }
@keyframes rule-glint {
  0%, 84%, 100% { fill: transparent; filter: none; }
  90% { fill: currentColor; filter: drop-shadow(0 0 3px var(--focus-glow)); }
}
@keyframes soul-slosh { 50% { clip-path: inset(calc(var(--cut) + 1.6%) 0 0 0); } }
@keyframes lb-breathe { to { filter: brightness(1.18) drop-shadow(0 0 4px var(--lifeblood-line)); } }
@keyframes orb-ready { to { opacity: 1; } }

/* ── Progress (js/app-progress.js, design/09-progress.md: the tablet) ──────────
   Another page of the Inventory, in the map's green. Under the title, the figure the game's
   map shows, "Completion", large and in bone over 112; then one row per category of the
   wiki's fifteen: its name, its things as small pips (lit, half-lit for a spell half learnt,
   in shadow) and its points. A row opens to show its things as Your game's plates. The points
   are data, in bone; a full category turns to --good. The accent, only on what's touched. */
.pg-body { display: block; }
.pg-total { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); margin: var(--sp-2) 0 var(--sp-8); text-align: center; }
.pg-total-k { font-size: var(--fs-2xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--label); }
.pg-total-v { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 700; line-height: 1; color: var(--ink-strong); font-size: calc(var(--fs-xl) * 2.2); }
.pg-total-v .u { font-size: 0.38em; font-weight: 400; color: var(--muted); }
.pg-lead { max-width: 52ch; margin: var(--sp-2) 0 0; font-size: var(--fs-sm); line-height: 1.5; color: var(--muted); text-wrap: balance; }
.pg-rows { list-style: none; margin: 0 auto; padding: 0; max-width: 900px; border-top: 1px solid var(--inv-line); }
.pg-row { border-bottom: 1px solid var(--inv-line); }
.pg-head {
  width: 100%; display: grid; grid-template-columns: minmax(0, 220px) minmax(0, 1fr) auto auto; gap: var(--sp-2) var(--sp-4);
  align-items: center; padding: var(--sp-3) var(--sp-2); text-align: left; transition: background var(--dur) var(--ease);
}
.pg-head:hover { background: var(--veil); }
.pg-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pg-row.is-open > .pg-head { background: var(--picked); box-shadow: inset 2px 0 0 var(--accent); }
/* It opens below, so it ends in the disclosure's ring (round 6), lit while open. */
.pg-head .disc-ring { margin-left: 0; }
.pg-head:is(:hover, :focus-visible, [aria-expanded="true"]) .disc-ring { color: var(--ink-strong); border-color: var(--accent); background-color: var(--accent-soft); }
.pg-name { font-size: var(--fs-md); color: var(--ink); }
.pg-pts { font-family: var(--font-num); font-variant-numeric: tabular-nums; white-space: nowrap; }
.pg-pts b { font-size: var(--fs-md); font-weight: 700; color: var(--ink-strong); }
.pg-pts .u { font-size: var(--fs-xs); font-style: normal; color: var(--muted); }
.pg-row.is-full .pg-pts b { color: var(--good); }
/* The pips: each thing's picture, small; in shadow what's missing, dimmed what's half done. */
.pg-pips { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.pg-pip { width: 18px; height: 18px; display: grid; grid-template-rows: minmax(0, 1fr); place-items: center; }
.pg-pip img { max-width: 100%; max-height: 100%; object-fit: contain; filter: brightness(0.3) saturate(0); opacity: 0.6; }
.pg-pip.is-on img { filter: none; opacity: 1; }
.pg-pip.is-part img { filter: brightness(0.6) saturate(0.6); opacity: 1; }
/* Where there's no picture (the Dreamers, the trials, the pantheons): the rule's diamond. */
.pg-glyph { display: block; width: 30px; height: 30px; border: 1px solid currentColor; transform: rotate(45deg) scale(0.7); color: var(--ink-2); }
.pg-pip .pg-glyph { width: 10px; height: 10px; transform: rotate(45deg); color: var(--muted); }
.pg-pip.is-on .pg-glyph { color: var(--ink); background: var(--ink-2); }
.gplate:not(.is-on) .pg-glyph { color: var(--muted); opacity: 0.5; }
.pg-open { padding: var(--sp-2) var(--sp-2) var(--sp-6); }
.pg-plates { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: var(--sp-3) var(--sp-3); }
.pg-plates .gplate-art { height: 56px; }
.pg-plates .gplate-art img { max-height: 48px; }
.pg-plates .gplate.is-part .gplate-art img { filter: brightness(0.6) saturate(0.6); }
.pg-plates .gplate.is-part .gplate-art::before { background: var(--halo-plate-off); }
/* The build's things are marked on Your game: they say so, and take you there. */
.pg-plates .gplate.is-far .gplate-val { font-family: var(--font-body); }
/* List · Map, under the title: text tabs, the chosen one with the accent's rule, like Combat's. */
.pg-tabs { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-1) var(--sp-6); margin: 0 0 var(--sp-6); }
.pg-tab { padding: var(--sp-1) 0; font-family: var(--font-body); font-size: var(--fs-md); color: var(--muted); border-bottom: 2px solid transparent; transition: color var(--dur) var(--ease); }
.pg-tab:hover { color: var(--accent); }
.pg-tab.is-on { color: var(--ink-strong); border-bottom-color: var(--accent); }

/* ── The Map (js/app-map.js): the game's own map, black like its map screen, each area in its
   own tint (baked into the drawings). Whole what the Quill drew, rough what you only bought,
   barely there what you don't know. The pins keep their size on screen (--k). ── */
.pgm { display: flex; flex-direction: column; gap: var(--sp-3); }
.pgm-kinds .pg-area img, .pgm-ico { width: 18px; height: 18px; object-fit: contain; flex: none; }
/* The area names' layer has no picture: a letter in the titles' face stands for it. */
.pgm-ico.is-name { display: inline-grid; place-items: center; font-family: var(--font-display); font-style: normal; font-size: var(--fs-sm); line-height: 1; color: var(--ink-2); }
/* How many of each kind you have: the figures in the sans, in bone; a kind complete, in --good. */
.pgm-kind-n { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--muted); }
.pgm-kind-n b { font-weight: 700; color: var(--ink-strong); }
.pgm-kind-n .u { font-style: normal; }
.pgm-kind.is-full .pgm-kind-n b { color: var(--good); }
/* A layer is an on/off: the box in front (.check-box); off, its icon in grey. */
.pg-area.pgm-kind { border-bottom: 0; }
.pg-area.pgm-kind.is-on { color: var(--ink); background: none; }
.pg-area.pgm-kind:hover { color: var(--ink-strong); }
.pgm-kind:hover .check-box { border-color: var(--accent); }
.pgm-kind:not(.is-on) :is(img, .pgm-ico) { filter: var(--missing-art); }
/* The filter, under the map: a title with show all · hide all, the two choices, and the layers
   in four groups (collectibles, the 112%, places, yours), each a row of chips that wraps. */
.pgm-filter { display: flex; flex-direction: column; gap: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--inv-line); }
.pgm-filter-bar { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--sp-2) var(--sp-6); }
.pgm-filter-t { margin: 0; font-family: var(--font-display); font-size: var(--fs-md); font-weight: 400; color: var(--ink-strong); }
.pgm-all { display: inline-flex; gap: var(--sp-4); }
.pgm-opts { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-6); }
.pgm-group { display: flex; flex-direction: column; gap: var(--sp-1); }
.pgm-group-t { margin: 0; font-family: var(--font-body); font-size: var(--fs-xs); font-weight: 400; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--muted); }
.pgm-filter .pg-areas { max-width: none; margin: 0; flex-wrap: wrap; overflow: visible; padding-bottom: 0; }
.pgm-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-6); }
.pgm-note { flex: 1 1 32ch; margin: 0; font-size: var(--fs-sm); color: var(--muted); }
/* The zoom, over the map's top right corner, as maps have it: always in sight, the large map too. */
.pgm-zoom { position: absolute; z-index: 3; top: var(--sp-3); right: var(--sp-3); display: inline-flex; border: 1px solid var(--line-ctl); background: var(--card); box-shadow: var(--shadow-md); }
/* The search, beside the zoom: a line to type on, as the Journal's, and its results under it,
   over the map, each with its picture, its name and where it is; the one chosen by the keys lit. */
.pgm-search { position: relative; flex: 1 1 14rem; max-width: 22rem; }
.pgm-results {
  position: absolute; z-index: 3; top: calc(100% + var(--sp-1)); left: 0; right: 0; min-width: 16rem;
  list-style: none; margin: 0; padding: var(--sp-1) 0; max-height: 22rem; overflow-y: auto;
  background: var(--card); border: 1px solid var(--line-panel); box-shadow: var(--shadow-md);
}
.pgm-result { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-1) var(--sp-3); cursor: pointer; }
.pgm-result:hover, .pgm-result.is-cur { background: var(--picked); }
.pgm-result.empty { cursor: default; padding-block: var(--sp-2); }
.pgm-result.empty:hover { background: none; }
.pgm-result.is-cur .pgm-card-t b { color: var(--accent); }
.pgm-result .pgm-card-art, .pgm-result .pgm-card-art img, .pgm-result .pgm-card-art svg { width: 24px; height: 24px; }
.pgm-box { position: relative; height: min(72vh, 720px, 62vw + 120px); background: var(--bg); border: 1px solid var(--inv-line); overflow: hidden; }
/* The large map, a choice (the zoom's third button, ⤢ / ⤡): on a computer it takes the window's whole
   width (--page-w, set by js/app-map.js), centred over the page's column, and its box the
   window's height under the bar: the map is 3:2, so the height is what makes it bigger. On a
   phone it already has the whole width: no button. */
.pgm-big[aria-pressed="true"] { color: var(--ink-strong); background: var(--picked); }
@media (max-width: 899px) { .pgm-big { display: none; } }
@media (min-width: 900px) {
  .pg.is-big { --map-w: calc(var(--page-w, 100vw) - 2 * var(--page-pad)); width: var(--map-w); margin-inline: calc((100% - var(--map-w)) / 2); }
  .pg.is-big .pgm-box { height: max(480px, min(calc(100vh - var(--sticky-h) - var(--sp-6)), var(--map-w) / 1.6)); }
}
.pgm-svg { display: block; width: 100%; height: 100%; touch-action: none; cursor: grab; user-select: none; }
.pgm-svg:active { cursor: grabbing; }
.pgm-room.is-rough { opacity: 0.8; }
.pgm-room.is-ghost { opacity: 0.16; }
.pgm-pin { transform: translate(var(--px), var(--py)) scale(var(--k, 0.5)) translate(var(--ox, 0px), var(--oy, 0px)); cursor: pointer; }
.pgm-pin circle { fill: var(--bg); stroke: var(--line-ctl); stroke-width: 0.06; }
.pgm-pin:hover circle, .pgm-pin:focus-visible circle, .pgm-pin.is-sel circle { stroke: var(--accent); stroke-width: 0.1; }
.pgm-pin:focus { outline: none; }
.pgm-pin.is-on { opacity: 0.4; }
/* The lift, drawn by the site (the game has no pin for it): in bone, like the game's pins. */
.pgm-glyph { fill: var(--ink-strong); }
/* Close up (.is-near), each pin's name under it, in bone with the black of the map around its
   letters, so it reads over the rooms' lines. */
.pgm-lbl { display: none; font-family: var(--font-body); font-size: 0.46px; text-anchor: middle; fill: var(--ink-strong);
  stroke: var(--bg); stroke-width: 0.12px; paint-order: stroke; pointer-events: none; }
.pgm-svg.is-near .pgm-lbl { display: block; }
/* A name that would cover another is hidden; its pin shows it when pointed at or chosen. */
.pgm-svg.is-near .pgm-lbl.is-off { display: none; }
.pgm-svg.is-near .pgm-pin:is(:hover, :focus-visible, .is-sel) .pgm-lbl.is-off { display: block; }
.pgm-pin:is(:hover, :focus-visible, .is-sel) { isolation: isolate; }
/* Yours sit where they are, on top, with no disc; your bench carries a bone ring. */
.pgm-mark circle { fill: none; stroke: var(--ink-strong); stroke-width: 0.08; }
/* You, by your bench: the Knight (js/app-map.js, youSvg), not a pin to pick. */
.pgm-you { pointer-events: none; cursor: default; }
/* The map's own titles, as its map screen writes them: the areas' in the titles' face, from
   afar; the places' smaller, once you're closer (.is-mid), when the areas' step aside. They keep
   their size on screen, as the pins do (--k). */
.pgm-names { pointer-events: none; }
.pgm-name { text-anchor: middle; dominant-baseline: middle; fill: var(--ink-2); stroke: var(--bg); paint-order: stroke; }
.pgm-name.is-area { font-family: var(--font-display); font-size: calc(var(--k, 0.5) * 0.6px); letter-spacing: var(--track-title); stroke-width: calc(var(--k, 0.5) * 0.14px); opacity: 0.9; }
.pgm-name.is-place { display: none; font-family: var(--font-body); font-size: calc(var(--k, 0.5) * 0.46px); stroke-width: calc(var(--k, 0.5) * 0.12px); }
.pgm-svg.is-mid .pgm-name.is-place { display: block; }
.pgm-svg.is-mid .pgm-name.is-area { display: none; }
/* The card of the pin chosen, over it (below it near the top edge). */
.pgm-card-wrap { position: absolute; z-index: 2; transform: translate(-50%, calc(-100% - var(--sp-4))); pointer-events: none; }
.pgm-card-wrap.is-below { transform: translate(-50%, var(--sp-6)); }
.pgm-card {
  pointer-events: auto; display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) var(--sp-3);
  background: var(--card); border: 1px solid var(--line-panel); box-shadow: var(--shadow-md); white-space: nowrap;
}
.pgm-card-art { display: inline-grid; place-items: center; width: 32px; height: 32px; }
.pgm-card-art img, .pgm-card-art svg { width: 32px; height: 32px; object-fit: contain; }
.pgm-card-t { display: flex; flex-direction: column; font-size: var(--fs-sm); color: var(--ink-2); }
.pgm-card-t b { color: var(--ink-strong); font-weight: 600; }


/* The Map's filter by kind: chips in a row, the chosen ones with the accent's line. */
.pg-areas { max-width: 900px; margin: 0 auto var(--sp-4); display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-2); }
.pg-area {
  display: inline-flex; align-items: center; gap: var(--sp-2); padding: var(--sp-1) var(--sp-2);
  font-size: var(--fs-sm); color: var(--ink-2); border-bottom: 1px solid transparent; transition: color var(--dur) var(--ease);
}
.pg-area:hover { color: var(--accent); }
.pg-area.is-on { color: var(--ink-strong); background: var(--picked); border-bottom-color: var(--accent-line); }
@media (max-width: 599px) {
  /* The kinds in one strip that slides sideways, inside its own box: fifteen would fill the screen. */
  .pg-areas { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: var(--sp-1); }
  .pg-areas::-webkit-scrollbar { display: none; }
  .pg-area { flex: none; }
  .pg-head { grid-template-columns: minmax(0, 1fr) auto auto; }
  .pg-pips { grid-column: 1 / -1; grid-row: 2; }
  .pg-plates { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ── Your game, the start screen (js/app-home.js; design/10-restructure.md, C · the bench) ──
   The area's title card over its own light, the figures, then three columns: since last time,
   your shade and what's missing nearby. The figures in bone, as on the sheet. */
.hm { display: block; }
.hm-live { display: inline-flex; align-items: center; gap: var(--sp-2); font-weight: 600; font-size: var(--fs-2xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-2); }
.hm-live.is-paused, .hm-live.is-lost { color: var(--cond); }
.hm-link-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--sp-2) var(--sp-4); }
.hm-when { font-size: var(--fs-sm); color: var(--muted); }
.hm-figs { display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-12); }
.hm-fig { display: flex; flex-direction: column; gap: var(--sp-half); }
.hm-fig-k { font-size: var(--fs-2xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--label); }
.hm-fig-v { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 700; font-size: var(--fs-xl); line-height: 1.1; color: var(--ink-strong); }
.hm-fig-v .u { font-size: 0.55em; font-weight: 400; color: var(--muted); margin-left: 0.15em; }
.hm-fig.is-big .hm-fig-v { font-size: calc(var(--fs-xl) * 2); }
.hm-block { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-3); min-width: 0; }
.hm-block .block-head { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; margin: 0; }
.hm-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; align-self: stretch; }
.hm-item { display: grid; grid-template-columns: 36px minmax(0, 1fr) auto; gap: var(--sp-3); align-items: center; padding: var(--sp-2) 0; border-bottom: 1px solid var(--inv-line); }
.hm-item:last-child { border-bottom: 0; }
.hm-item-art { width: 36px; height: 32px; display: grid; place-items: center; }
.hm-item-art img { max-width: 100%; max-height: 100%; object-fit: contain; }
.hm-item-name { font-size: var(--fs-sm); color: var(--ink); min-width: 0; }
.hm-item-name small { display: block; font-size: var(--fs-xs); color: var(--muted); }
.hm-item-k { font-weight: 400; color: var(--muted); justify-content: flex-end; text-align: right; }
.hm-item-v { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-sm); color: var(--ink-2); white-space: nowrap; }
/* What's missing near your bench: small pictures, grey until found, each with its place. */
.hm-near { display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: var(--sp-2); align-self: stretch; }
.hm-near span { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); font-size: var(--fs-2xs); color: var(--muted); text-align: center; line-height: 1.2; }
.hm-near img { height: 28px; width: auto; max-width: 48px; object-fit: contain; filter: brightness(0.55) saturate(0.4); }
.hm-shade { display: flex; gap: var(--sp-3); align-items: center; }
.hm-shade img { width: 40px; height: auto; filter: brightness(0.25) contrast(1.4); }
.hm-shade p { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }
/* The title card: "Resting at" over the area's name, on the area's light (--area-*: tokens.css). */
.hmC-hero { position: relative; isolation: isolate; display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); text-align: center;
  padding: var(--sp-8) var(--sp-4); margin: var(--sp-4) calc(-1 * var(--inv-pad)) 0; }
.hmC-hero::before { content: ''; position: absolute; inset: 0; z-index: -1; opacity: 0.7;
  background: radial-gradient(ellipse 55% 80% at 50% 38%, var(--area-m), var(--area-d) 55%, transparent 80%); }
.hmC-sup { font-family: var(--font-display); font-size: var(--fs-sm); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--ink-2); }
.hmC-area { font-family: var(--font-display); font-size: clamp(1.8rem, 5vw, 3.2rem); font-weight: 400; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-strong); line-height: 1.1; margin: 0; }
/* The bench under the name (js/app-home.js, a silhouette in the page's black with the area's light
   as its edge), with the Knight sitting on it: a picture of him (the sit strip, --kn-bench tall,
   the bench 2.75 of that wide), feet hanging in front of the seat, which lies at half the
   picture's height, lit in the area's colour. Still: the Knight who moves, and marks the tab
   you're on, is the one on the screen bar (js/app-knight.js). */
.hmC-bench { position: relative; width: calc(var(--kn-bench) * 2.75); margin: var(--sp-3) 0 var(--sp-1); }
.hmC-seat { display: block; width: 100%; height: auto; fill: var(--bg); stroke: color-mix(in oklab, var(--area-l) 55%, transparent); stroke-width: 2; paint-order: stroke; }
.hmC-kn { position: absolute; left: 50%; bottom: 22%; width: calc(var(--kn-bench) * 104 / 140); height: var(--kn-bench); translate: -50% 0;
  background: url(../assets/knight/sit.png) 0 0 / auto 100% no-repeat; filter: drop-shadow(0 0 18px color-mix(in oklab, var(--area-l) 60%, transparent)); }
.hmC-figs { justify-content: center; margin-top: var(--sp-6); }
.hmC-figs .hm-fig { align-items: center; }
.hmC-cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-8) var(--sp-12); margin-top: var(--sp-12); padding-top: var(--sp-8); border-top: 1px solid var(--inv-line); }
/* Nobody's game: the invitation. Written phone first (the other way round from the rest of the
   file): one column, an example bench on top, dimmed and fading out under the call to connect
   yours, which rises into the fade; then what the site keeps, with the game's art, and the other
   way in. On a computer (900 px up) the call and the example go side by side instead, the call on
   the left, so that its button is on the first screen of a laptop whatever the example's height:
   the example, compact (the area's name no bigger than a large figure, its list of gains shown),
   fades out at its foot alone. The example is a picture: inert, and no pointer over it. */
.hmI { max-width: 1100px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--sp-8); }
.hmI-demo { pointer-events: none; user-select: none; opacity: 0.8;
  -webkit-mask: linear-gradient(to bottom, var(--bg) 50%, transparent 90%); mask: linear-gradient(to bottom, var(--bg) 50%, transparent 90%); }
.hmI-demo .hmC-hero { margin-top: 0; padding-bottom: var(--sp-12); }
.hmI-tag { margin-bottom: var(--sp-4); }
.hmI-since { display: none; width: min(100%, 420px); margin-top: var(--sp-6); text-align: left; flex-direction: column; gap: var(--sp-2); }
.hmI-since .hm-list { align-self: stretch; }
.hmI-cta { position: relative; margin-top: calc(-1 * var(--sp-16)); text-align: center; display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); }
.hmI-cta h3 { font-family: var(--font-display); font-size: var(--fs-xl); font-weight: 400; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-strong); margin: 0; }
.hmI-cta p { margin: 0 0 var(--sp-2); max-width: 46ch; color: var(--ink-2); font-size: var(--fs-md); line-height: 1.45; }
.hmI-cta .btn { min-height: 48px; padding-inline: var(--sp-8); font-size: var(--fs-sm); }
.hmI-drop { font-size: var(--fs-xs); color: var(--muted); }
@media (hover: none), (max-width: 899px) { .hmI-drop { display: none; } }
/* A file over the screen: the call lights up, as the import's drop zone does. */
.hmI.is-drag .hmI-cta h3 { text-shadow: 0 0 18px var(--focus-glow); }
.hmI.is-drag .hmI-drop { color: var(--accent); }
.hmI-feats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); }
.hmI-feat, .hmI-way { display: grid; justify-items: center; align-content: start; gap: var(--sp-1); text-align: center; padding: var(--sp-4) var(--sp-3);
  border-top: 1px solid var(--inv-line); transition: border-color var(--dur) var(--ease); }
.hmI-feat img { height: 48px; width: auto; max-width: 72px; object-fit: contain; margin-bottom: var(--sp-2); transition: transform var(--dur) var(--ease); }
.hmI-feat b, .hmI-way b { font-size: var(--fs-md); font-weight: 600; color: var(--ink); }
.hmI-feat span, .hmI-way span { font-size: var(--fs-sm); color: var(--muted); line-height: 1.4; }
.hmI-feat:is(:hover, :focus-visible), .hmI-way:is(:hover, :focus-visible) { border-top-color: var(--accent-line); }
.hmI-feat:is(:hover, :focus-visible) b, .hmI-way:is(:hover, :focus-visible) b { color: var(--accent); }
.hmI-feat:hover img { transform: translateY(calc(-1 * var(--sp-1))); }
/* They lead to another screen (round 6): an arrow in the accent after the title says so at rest,
   and steps forward under the pointer. */
.hmI-feat b::after, .hmI-way b::after {
  content: '→'; display: inline-block; margin-left: var(--sp-2); font-family: var(--font-num); font-weight: 400; color: var(--accent-ink);
  transition: transform var(--dur) var(--ease);
}
.hmI-feat:is(:hover, :focus-visible) b::after, .hmI-way:is(:hover, :focus-visible) b::after { transform: translateX(var(--sp-1)); }
/* The other way in, just trying builds: one row (a save isn't made by hand, so there's no second). */
.hmI-or { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
.hmI-way { grid-template-columns: 40px minmax(0, 1fr); justify-items: start; text-align: left; column-gap: var(--sp-3); }
.hmI-way img { grid-row: span 2; width: 40px; height: 40px; object-fit: contain; }
/* A computer: the call on the left with the four pieces under it, the example on the right
   beside both, and the other way in under the two columns. The card doesn't bleed past its
   column there (the fade's mask clips at the column's edge, so anything wider would be cut).
   The example is its own container: in a narrow column (a window under the page's width) its
   four figures go two by two, as on a phone, instead of wrapping three and one. */
@media (min-width: 900px) {
  .hmI { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-12); align-items: start; }
  .hmI-demo { grid-column: 2; grid-row: 1 / span 2; container-type: inline-size;
    -webkit-mask: linear-gradient(to bottom, var(--bg) 75%, transparent); mask: linear-gradient(to bottom, var(--bg) 75%, transparent); }
  .hmI-demo .hmC-hero { margin-inline: 0; padding-bottom: var(--sp-8); }
  .hmI-demo .hmC-area { font-size: clamp(var(--fs-xl), 2.5vw, var(--fs-2xl)); }
  .hmI-demo .hmC-figs { margin-top: var(--sp-4); gap: var(--sp-4) var(--sp-6); }
  .hmI-since { display: flex; }
  .hmI-cta { grid-column: 1; grid-row: 1; margin-top: 0; padding-top: var(--sp-8); text-align: left; align-items: flex-start; }
  .hmI-cta h3 { font-size: var(--fs-2xl); }
  .hmI-cta p { font-size: var(--fs-lg); }
  .hmI-feats { grid-column: 1; grid-row: 2; }
  .hmI-or { grid-column: 1 / -1; grid-row: 3; }
}
@container (max-width: 499px) { .hmI-demo .hmC-figs { display: grid; grid-template-columns: repeat(2, auto); justify-content: center; } }
@media (max-width: 899px) {
  .hmC-cols { grid-template-columns: minmax(0, 1fr); }
  .hm-figs { gap: var(--sp-3) var(--sp-6); }
}
