/* css/app.css — Silksong Companion: layout and components.
   The base, labels, four kinds of button, shared controls, header, screen bar, atmosphere,
   notices and responsive rules. Each screen has its own block below.
   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); }
/* Leaving or landing on a game reloads the page: it fades to black before, and the new one 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. */
[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 (tokens.css, decision 1). */
.hero-num, .metric .val, .stat-value, .nav-num, .toast-num, .chip, .delta {
  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. */
.u { font-size: 0.62em; font-weight: 400; 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. */
.lbl, .block-head, .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); }

/* The frame's corners (App.brackets): the curl drawn once for the top left, mirrored for the
   other three, reaching a little past the plate's edge. */
.bk { position: absolute; width: var(--frame-corner); height: var(--frame-corner); color: var(--bracket); pointer-events: none; }
.bk-art { display: block; width: 100%; height: 100%; overflow: visible; }
.bk.tl { top: calc(var(--sp-1) * -1); left: calc(var(--sp-1) * -1); }
.bk.tr { top: calc(var(--sp-1) * -1); right: calc(var(--sp-1) * -1); scale: -1 1; }
.bk.bl { bottom: calc(var(--sp-1) * -1); left: calc(var(--sp-1) * -1); scale: 1 -1; }
.bk.br { bottom: calc(var(--sp-1) * -1); right: calc(var(--sp-1) * -1); scale: -1 -1; }
/* The thin filigree line inset all round, between the corners, and the plate's black inside it
   only (the plate itself is clear: nothing black shows past the line). The top line stops either
   side of the title's plaque (--title-half, js/app.js sizeTitles). */
.frame-line {
  position: absolute; inset: var(--frame-inset); z-index: -1; pointer-events: none; background: var(--inv-bg);
  border: 1px solid color-mix(in oklab, var(--lamp) var(--lamp-frame), transparent);
}
:is(.screen, .saves-body):has(.screen-head) .frame-line { border-top-color: transparent; }
:is(.screen, .saves-body):has(.screen-head) .frame-line::before,
:is(.screen, .saves-body):has(.screen-head) .frame-line::after {
  content: ''; position: absolute; top: -1px; width: calc(50% - var(--title-half, 6rem) + 1px); height: 1px;
  background: color-mix(in oklab, var(--lamp) var(--lamp-frame), transparent);
}
:is(.screen, .saves-body):has(.screen-head) .frame-line::before { left: -1px; }
:is(.screen, .saves-body):has(.screen-head) .frame-line::after { right: -1px; }
/* Each tab's light (tokens.css, --tab-*): the frame, the corners, the plaque, and a light from the top inside. */
.screen[data-view="home"], .screen[data-view="saves"] { --tab: var(--tab-home); }
.screen[data-view="game"] { --tab: var(--tab-game); }
.screen[data-view="progress"] { --tab: var(--tab-progress); }
.screen[data-view="map"] { --tab: var(--tab-map); }
.screen[data-view="journal"] { --tab: var(--tab-journal); }
.screen[data-view="tools"] { --tab: var(--tab-tools); }
.screen[data-view="fight"] { --tab: var(--tab-fight); }
.screen .frame-line {
  background: radial-gradient(ellipse 80% 320px at 50% 0%, color-mix(in oklab, var(--tab) var(--tab-light), transparent), transparent), var(--inv-bg);
  border-color: color-mix(in oklab, var(--tab) var(--tab-frame), transparent);
  box-shadow: 0 0 40px -10px color-mix(in oklab, var(--tab) 50%, transparent);
}
.screen:has(.screen-head) .frame-line::before, .screen:has(.screen-head) .frame-line::after { background: color-mix(in oklab, var(--tab) var(--tab-frame), transparent); }
.screen .bk { color: color-mix(in oklab, var(--tab) 80%, transparent); }
.screen .screen-plaque { color: var(--tab); }
/* Your game's place is where Hornet rests (design/03-redesign.md, step 3, one light, A, the review's
   choice, 14 Oct 2026): with a save, its frame, corners, plaque and light take that area's colour
   (--area-line and --area-fill, set on .hm by js/app-home.js), mixed as every tab's (--tab-area),
   and that one light reaches down to her bench; the scene has no glow of its own. Free mode keeps
   the red, with the same light. */
.screen .hm:has(.hm-scene) { --tab: color-mix(in oklab, var(--area-line) var(--tab-area), var(--area-fill)); }
.screen .hm .frame-line { background: radial-gradient(ellipse 90% 78% at 50% 0%, color-mix(in oklab, var(--tab) var(--tab-light), transparent), transparent), var(--inv-bg); }

/* ── The screens' frame ─────────────────────────────────────────────────
   Each screen is a pause-menu screen, framed and titled in the game's white filigree, the same
   for all (the corner brackets, the title and the rule under it, the line under the screen bar),
   with the warm bone of the Crest screen's spotlight as its light. The game titles its panes and
   selects in white, never with a hue (design/02-silksong.md §3 and §5): the sections are told
   apart by their names and the screen bar, not by a colour. Since step 1's third round
   (design/24-shell-round2-variants.html, E) each screen's frame is lit in its tab's colour
   (--tab-*): the frame, its corners and plaque, and a light from its top; the black and the
   white filigree of what's inside stay. */
.page, .screen {
  --lamp: var(--filigree);
  --lamp-light: var(--spotlight);
  --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);
}
.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(--filigree);
}

/* The interface icons (tokens.css): the width sets the size, the height follows the viewBox. */
.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); }

/* ── A screen ──────────────────────────────────────────────────────────
   Every screen is a page of the game's inventory: the black field with the corner brackets in
   the filigree, and its title centred in Cinzel over the rule with the diamond (App.screenHead). */
.screen { position: relative; isolation: isolate; padding: var(--sp-8); --inv-pad: var(--sp-8); }
/* The title sits on the frame's line, centred on it, the black knocked out behind it and a
   hollow diamond either side; it reaches a little above the plate, onto the page. What goes
   with it (.screen-lead) opens the screen, centred. */
.screen-head {
  position: absolute; z-index: 1; top: var(--frame-inset); left: 50%; transform: translate(-50%, -50%);
  display: flex; align-items: center; max-width: calc(100% - var(--frame-corner) * 2);
  padding: var(--sp-2) var(--sp-8); white-space: nowrap;
}
/* The plaque: a thin double outline with pointed ends, stretched to the title. */
.screen-plaque { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; color: color-mix(in oklab, var(--filigree) 80%, transparent); pointer-events: none; }
.screen-plaque polygon:not(.is-inner) { fill: var(--inv-bg); } /* solid: the plaque straddles the page and the plate, so it can't be clear */
.screen-plaque .is-inner { opacity: 0.4; }
.screen-head .sec-title { position: relative; overflow: hidden; text-overflow: ellipsis; }
.screen-lead { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-6); text-align: center; }

/* ── Controls ────────────────────────────────────────────────────────── */
/* Four kinds of button, by job (the reference implementation's design/11-components.md, round 2). 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. The needle's frame (design/03-redesign.md,
     step 2): a rule above and below, closed by a hollow diamond at each end in the accent's
     line; the primary one (.btn-primary) with its diamonds filled and a veil of the accent, as
     any under the finger; 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.
   · Disclosure (.disc-btn): opens something below. The label on the left, the chevron in the ring. */
.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-6); border: 1px solid var(--line-ctl); border-inline-color: transparent; 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);
}
.btn::before, .btn::after {
  content: ''; position: absolute; top: 50%; left: 0; width: calc(var(--diamond) + var(--sp-half)); height: calc(var(--diamond) + var(--sp-half));
  border: 1px solid var(--bk); background: var(--inv-bg); transform: translate(-50%, -50%) rotate(45deg); pointer-events: none;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.btn::after { left: 100%; }
.btn:is(:hover, :focus-visible):not(:disabled)::before, .btn:is(:hover, :focus-visible):not(:disabled)::after,
.btn-primary::before, .btn-primary::after { background: var(--bk); }
.btn:is(:hover, :focus-visible):not(:disabled) { color: var(--ink-strong); border-block-color: var(--accent-line); background-color: var(--accent-soft); }
.btn-primary { color: var(--ink-strong); border-block-color: var(--accent-line); background-color: color-mix(in oklab, var(--accent) 8%, transparent); --bk: var(--accent); }
.btn-primary:is(:hover, :focus-visible):not(:disabled) { background-color: color-mix(in oklab, var(--accent) 18%, transparent); }
.btn.is-danger { color: var(--bad); --bk: var(--bad); }
.btn.is-danger:is(:hover, :focus-visible):not(:disabled) { color: var(--bad); border-block-color: var(--bad); background-color: var(--bad-soft); }
.btn:disabled { color: var(--muted); background-color: transparent; --bk: var(--line-ctl); cursor: default; }
.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; }
.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); }
.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): the game's diamond on the accent's line; on, filled with the accent and lit
   (design/03-redesign.md, step 2). No tick: a row of them over the map stays quiet. */
.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(--sp-3); height: var(--sp-3); margin: 0 var(--sp-half);
  border: 1px solid color-mix(in oklab, var(--accent-line) 80%, transparent); border-radius: 0; background: none; transform: rotate(45deg);
  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);
}
.check: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); box-shadow: var(--glow); }
.check input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.check-box > svg { display: none; }
/* Search (.search): a sunken well with its rule and the lens in front. */
.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; }
/* Choose one (.seg): a group where one is always chosen. The upgrade pips (design/03-redesign.md,
   step 2): a hollow diamond under each word, on the accent's line; the chosen one in bone, its
   pip filled and lit. */
.seg { display: inline-flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.seg button {
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-1);
  padding: var(--sp-1) 0; min-height: var(--ctl); min-width: var(--ctl-sm);
  font-size: var(--fs-sm); color: var(--ink-2); text-align: center;
  transition: color var(--dur) var(--ease);
}
.seg button::after {
  content: ''; flex: none; width: var(--diamond); height: var(--diamond); transform: rotate(45deg);
  border: 1px solid color-mix(in oklab, var(--accent-line) 80%, transparent);
  transition: background-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); }
.seg button:is(:hover, :focus-visible):not(:disabled)::after { border-color: var(--accent); }
.seg button[aria-pressed="true"] { color: var(--ink-strong); }
.seg button[aria-pressed="true"]::after { background: var(--accent); border-color: var(--accent); box-shadow: var(--glow); }
.seg button:disabled { color: var(--muted); cursor: default; }
.seg button:disabled::after { border-color: var(--line-ctl); }
.seg.sm { gap: var(--sp-3); }
.seg.sm button { min-height: var(--ctl-sm); font-size: var(--fs-xs); }

/* ── Showing ───────────────────────────────────────────────────────────
   A frame says "press me", so what only informs carries none. */
.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. Three levels:
   danger (the default), a condition (.is-run) and information (.is-hint). */
.banner {
  --lv: var(--bad); --lv-ink: var(--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); }
.banner.is-hint { --lv: var(--ink-strong); }
#banner:not(:empty) { display: flex; flex-direction: column; gap: var(--sp-2); }
/* 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. */
.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; }

/* ── Header ──────────────────────────────────────────────────────────────
   Two rows: the filigree crown above, and the title below with the language and Share on its left,
   which belong to the site, and the save selector on its right, which is yours (the brand spans both
   rows and shares them, subgrid, as the reference implementation's). The two side columns grow equally, so the
   title lands dead centre. Behind it, like behind the whole page, the main menu's embers. */
.masthead {
  padding: var(--sp-4) 0 0; display: grid; grid-template-columns: 1fr auto 1fr; grid-template-rows: auto auto;
  align-items: center; gap: 0 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 crown lights up fully. */
.mh-crown { display: block; width: calc(var(--sp-16) * 3); height: auto; color: color-mix(in oklab, var(--filigree) var(--lamp-line), transparent); transition: color var(--dur) var(--ease); }
.brand:hover .mh-crown { color: var(--filigree); }
/* The companion's original needle-and-thread emblem shares the cream selection ink. */
.mh-emblem { display: block; width: var(--sp-12); height: var(--sp-12); object-fit: contain; transition: filter var(--dur) var(--ease); }
.brand:hover .mh-emblem { filter: drop-shadow(0 0 var(--sp-1) var(--accent-line)); }
.title {
  font-family: var(--font-display); font-weight: 600; 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. */
.mh-tools { --tools-gap: var(--sp-6); display: flex; align-items: center; gap: var(--tools-gap); flex-wrap: wrap; }
/* The language (the reference implementation's design/29-language-selector.html): what's chosen and a chevron (its
   name on a wide screen, its code on a phone) and, under it, a short list: each language by its
   own name, the chosen one in bone behind the diamond. */
.langsel { position: relative; display: flex; align-items: center; }
.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);
}
.lang-btn, .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); white-space: nowrap;
}
.lang-btn { gap: var(--sp-2); }
.lang-btn[aria-expanded="true"] { color: var(--ink-strong); text-decoration-color: var(--accent); }
.lang-code { display: none; }
.lang-menu {
  position: absolute; z-index: 2; top: calc(100% + var(--sp-2)); left: calc(var(--sp-4) * -1); min-width: max-content;
  max-height: calc(100dvh - var(--sp-16) * 2); overflow-y: auto; margin: 0; padding: var(--sp-2) 0; list-style: none;
  background: var(--card); border: 1px solid var(--line-panel); box-shadow: var(--shadow-md);
}
.lang-menu a {
  display: flex; align-items: center; gap: var(--sp-3); min-height: var(--ctl-sm); padding: 0 var(--sp-8) 0 var(--sp-4);
  font-size: var(--fs-sm); color: var(--ink-2); text-decoration: none; white-space: nowrap;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.lang-menu a::before { content: ''; flex: none; width: var(--diamond); height: var(--diamond); transform: rotate(45deg); }
.lang-menu a[aria-current="true"] { color: var(--ink-strong); }
.lang-menu a[aria-current="true"]::before { background: var(--accent); }
.lang-menu a:is(:hover, :focus-visible) { color: var(--accent-ink); background: var(--accent-soft); }
.lang-menu a:focus-visible { outline-offset: calc(var(--sp-half) * -1); }   /* inside the list, which clips */
/* Open, the list falls over the screen bar, which is stuck above the header's own layer. */
.masthead.is-lang-open { position: relative; z-index: 40; }

/* ── The screen bar ──────────────────────────────────────────────────────
   The pages of the game's pause menu: their names in the serif, in lowercase. Stuck at the top
   and across the full window width. */
.page { overflow-x: clip; }
.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. */
.screens > .is-entering { animation: title-in var(--dur-slow) var(--ease-fade); }
@keyframes title-in { from { opacity: 0; } to { opacity: 1; } }
@media (max-width: 899px) { .screens { gap: var(--sp-6); } }
/* The footer: the fan-project notice and the sources, small and muted under a hairline. */
.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); }
.colophon .gh { display: inline-flex; vertical-align: -3px; margin-left: var(--sp-1); text-decoration: none; }
/* The privacy notice, folded: its summary responds to the finger, so it takes the links' accent; open,
   a short left-aligned list in the footer's muted ink, no wider than a comfortable line. */
.colophon-privacy summary {
  display: inline-flex; align-items: center; gap: var(--sp-1); cursor: pointer; list-style: none;
  color: var(--accent); transition: color var(--dur) var(--ease);
}
.colophon-privacy summary:hover { color: var(--accent-ink); }
.colophon-privacy summary::-webkit-details-marker { display: none; }
.colophon-privacy summary::after { content: '▾'; transition: transform var(--dur) var(--ease); }
.colophon-privacy[open] summary::after { transform: rotate(180deg); }
.colophon-privacy ul {
  max-width: 60ch; margin: var(--sp-2) auto 0; padding-left: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-1); text-align: left; text-wrap: pretty;
}
/* The About block: the page's text for search engines and for whoever lands from one, as a guide
   page on the main menu's red with no box (tools/pages.js; design/23-static-pages-round2.html, D). */
.about {
  display: flex; flex-direction: column; gap: var(--sp-8);
  padding-top: var(--sp-12); border-top: 1px solid var(--line);
  font-size: var(--fs-md); line-height: 1.65; color: var(--ink-2);
}
.about p { margin: 0; }
.about h2 { margin: 0 0 var(--sp-3); font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.about-crown { display: block; margin: 0 auto; color: var(--filigree); opacity: 0.75; }
.about-hero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: var(--sp-8); align-items: center; }
.about-art { position: relative; display: grid; place-items: center; height: var(--about-art); }
.about-art::before { content: ''; position: absolute; inset: -8% 6%; background: var(--journal-light); pointer-events: none; }
.about-art img { position: relative; max-width: 100%; max-height: 100%; width: auto; min-height: 45%; object-fit: contain; filter: drop-shadow(0 12px 30px rgba(0, 0, 0, 0.6)); }
.about-head { display: grid; gap: var(--sp-4); justify-items: start; }
.about h1 { margin: 0; display: grid; gap: var(--sp-2); font-family: var(--font-display); line-height: 1; color: var(--ink-strong); text-wrap: balance; }
.about-name { font-weight: 700; font-size: clamp(2.2rem, 1.4rem + 3vw, 4.5rem); text-shadow: 0 0 40px color-mix(in oklab, var(--spotlight) 25%, transparent); }
.about-sub { font-weight: 400; font-size: var(--fs-lg); line-height: 1.3; letter-spacing: var(--track-title); color: var(--ink-2); }
.about-name.is-long { font-size: clamp(1.8rem, 1.2rem + 2vw, 3rem); }
.about-colon { display: none; }
.about-quote { font-family: var(--font-hand); font-size: var(--fs-lg); color: var(--ink); }
.about-cta {
  display: inline-flex; align-items: center; gap: var(--sp-2); padding: var(--sp-3) var(--sp-6); border: 1px solid var(--filigree);
  font-family: var(--font-display); font-size: var(--fs-sm); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-strong); text-decoration: none;
  box-shadow: var(--glow); transition: background-color var(--dur) var(--ease);
}
.about-cta:is(:hover, :focus-visible) { background: var(--veil); color: var(--ink-strong); }
/* A boss's figures in a row between filigree rules. */
.about-figs { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); margin: 0; border-top: 1px solid color-mix(in oklab, var(--filigree) 25%, transparent); border-bottom: 1px solid color-mix(in oklab, var(--filigree) 25%, transparent); }
.about-figs div { display: flex; flex-direction: column-reverse; align-items: center; gap: var(--sp-1); padding: var(--sp-4) var(--sp-2); text-align: center; }
.about-figs div + div { border-left: 1px solid color-mix(in oklab, var(--filigree) 18%, transparent); }
.about-figs dd { margin: 0; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xl); line-height: 1.1; color: var(--ink-strong); }
.about-figs dt { font-size: var(--fs-xs); color: var(--muted); }
.about-two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-12); }
.about-attacks { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: var(--sp-6); }
.about-attacks li { break-inside: avoid; display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2); padding: var(--sp-2) 0; border-bottom: 1px solid color-mix(in oklab, var(--filigree) 12%, transparent); font-size: var(--fs-sm); }
.about-masks { display: inline-flex; gap: 2px; flex: none; }
.about-masks img { height: var(--sp-4); width: auto; }
.about-fight { font-size: var(--fs-xs); color: var(--muted); }
.about-bar { position: relative; height: 8px; margin: var(--sp-2) 0 var(--sp-8); background: color-mix(in oklab, var(--slot-red) 70%, transparent); }
.about-bar i { position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; background: var(--ink-strong); }
.about-bar i span { position: absolute; top: calc(100% + var(--sp-1)); left: 50%; transform: translateX(-50%); white-space: nowrap; font-style: normal; font-size: var(--fs-2xs); color: var(--muted); }
.about-reward { color: var(--ink); }
.about-text { display: grid; gap: var(--sp-3); max-width: 70ch; margin: 0 auto; }
/* Folded: what's said in words that the figures above already show. */
.about-details > summary, .about-faq summary { list-style: none; cursor: pointer; }
.about-details > summary::-webkit-details-marker, .about-faq summary::-webkit-details-marker { display: none; }
.about-details > summary { display: inline-flex; gap: var(--sp-2); font-size: var(--fs-sm); color: var(--ink-2); border-bottom: 1px solid var(--accent-line); }
.about-details > summary::after { content: '⌄'; }
.about-details[open] > summary { margin-bottom: var(--sp-3); }
.about-details p { max-width: 70ch; margin-bottom: var(--sp-3); font-size: var(--fs-sm); }
.about-faq details { border-bottom: 1px solid color-mix(in oklab, var(--filigree) 18%, transparent); }
.about-faq summary { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3); padding: var(--sp-3) 0; }
.about-faq summary::after { content: ''; flex: none; width: var(--diamond); height: var(--diamond); border: 1px solid var(--filigree); transform: rotate(45deg); }
.about-faq details[open] summary::after { background: var(--filigree); }
.about-faq h3 { margin: 0; font-family: var(--font-body); font-size: var(--fs-md); font-weight: 400; color: var(--ink); }
.about-faq details p { margin: 0 0 var(--sp-3); font-size: var(--fs-sm); }
/* Every boss as a portrait; the other pages with their icon. */
.about-bosses ul { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: var(--sp-4) var(--sp-2); }
.about-bosses a { display: grid; justify-items: center; gap: var(--sp-1); text-decoration: none; font-size: var(--fs-2xs); line-height: 1.25; text-align: center; color: var(--ink-2); }
.about-bosses .hj-ring { width: 52px; }
.about-bosses a:is(:hover, :focus-visible) { color: var(--accent); }
.about-bosses a:is(:hover, :focus-visible) .hj-ring { transform: scale(1.08); }
.about-more ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-4) var(--sp-6); }
.about-more a { display: grid; justify-items: center; gap: var(--sp-1); width: 120px; text-decoration: none; text-align: center; font-size: var(--fs-xs); color: var(--ink-2); }
.about-more img { width: var(--sp-12); height: var(--sp-12); object-fit: contain; filter: drop-shadow(0 0 8px color-mix(in oklab, var(--spotlight) 30%, transparent)); }
.about-more a:is(:hover, :focus-visible) { color: var(--accent); }
.about-more h2, .about-bosses h2, .about-faq h2 { text-align: center; }
/* Folded with a save loaded (js/app.js, renderAbout): only its disclosure shows. */
.about.is-folded > :not(.about-fold) { display: none; }
.about.is-folded { padding-top: 0; border-top: 0; }
.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-text a, .about-details a, .about-faq a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--accent-line); }
@media (max-width: 899px) {
  .about-hero, .about-two { grid-template-columns: minmax(0, 1fr); }
  .about-hero { text-align: center; }
  .about-head { justify-items: center; }
  .about { --about-art: 220px; }
  .about-figs div:nth-child(odd) { border-left: 0; }
  .about-attacks { columns: 1; }
}
.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);
}
.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); }
/* The current tab: an accent rule on its bottom edge, while Hornet isn't on the bar to mark it
   (a phone's width, or before the script runs). */
.nav-tab[aria-current="page"] { color: var(--ink-strong); box-shadow: inset 0 -2px 0 var(--accent); }
.nav-tabs.has-hn > .nav-tab[aria-current="page"] { box-shadow: none; }
.nav-num { font-size: var(--fs-xs); font-weight: 600; color: var(--ink-2); white-space: nowrap; }
.nav-num:empty { display: none; }
/* The two groups: your game, then the tools after a thin rule. */
.nav-sep { width: 1px; height: 1.4em; align-self: center; background: var(--line-ctl); }
/* The phone's fold (js/app.js, renderNav): the Build tab and the second rows only show there. */
.nav-tools, .nav-sub { display: none; }

/* ── The Hornet who walks the page (js/app-hornet.js) ─────────────────
   One sprite, drawn from strips of 120 × 120 cells (assets/hornet/, tools/extract-hornet.py):
   standing, running (ten frames, stepped at --dur-step) or sitting; her feet at 116 of the cell
   and her middle at 62. She faces left, as the game draws her, and turns with a mirror. On the
   screen bar she's --hn-bar tall, her feet on its bottom edge; on the Map (.mp-hornet) she's
   --hn-map tall, her feet on her bench. Reduced motion leaves her on her first frame and, from
   the JS, only standing or sitting. Decorative: the button only exists for the click (a Bind). */
.hn {
  --n: 1; --hn-h: var(--hn-bar);
  position: absolute; z-index: 1; width: var(--hn-h); height: var(--hn-h); padding: 0; border: 0; margin: 0;
  background: url(../assets/hornet/stand.png) 0 0 / auto 100% no-repeat; cursor: default;
  opacity: 1; transition: opacity var(--dur-slow) var(--ease-fade);
}
.hn:focus { outline: none; }
.hn.is-run { background-image: url(../assets/hornet/run.png); --n: 10; }
.hn.is-sit { background-image: url(../assets/hornet/sit.png); }
.hn.is-right { --flip: -1; scale: -1 1; }
.hn.is-out { opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
  .hn.is-run { animation: hn-cycle calc(var(--dur-step) * var(--n)) steps(var(--n)) infinite; }
  /* A Bind (a click): standing still, a white light swells around her and fades. */
  .hn.is-bind { animation: hn-bind var(--dur-flash) var(--ease-fade); }
}
@keyframes hn-cycle { to { background-position-x: calc(-1 * var(--hn-h) * var(--n)); } }
@keyframes hn-bind { 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 > .hn { bottom: 0; left: 0; }

/* ── Atmosphere ──────────────────────────────────────────────────────────
   The main menu behind the whole page (tokens.css): fixed to the window and behind everything.
   Below, the red light, the vignette and the embers (.atmos, js/app.js); above, the grain. */
.page { position: relative; isolation: isolate; }
.atmos { position: fixed; inset: 0; z-index: -2; overflow: hidden; pointer-events: none; background: var(--vignette), var(--ember-glow); }
.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: calc(var(--s) * 3); border-radius: 50%;
  background: var(--mote); box-shadow: 0 0 6px var(--mote); opacity: 0; transform: rotate(var(--tilt));
}
@media (prefers-reduced-motion: no-preference) {
  .atmos i { animation: mote var(--t) linear infinite; animation-delay: var(--d); }
}
@keyframes mote {
  0% { opacity: 0; transform: translate(0, 0) rotate(var(--tilt)); }
  15%, 80% { opacity: var(--o); }
  100% { opacity: 0; transform: translate(var(--dx), -100vh) rotate(var(--tilt)); }
}

/* ── Floating notice ─────────────────────────────────────────────────── */
/* The notices (toast in js/app.js), like the game's on-screen messages, just under the screen bar:
   the text in the game's face between two short rules with their diamond, over a soft dark veil. */
.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; }
.toast-num { font-weight: 600; }
.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: 899px) {
  :root { --page-pad: var(--sp-4); --sticky-h: 60px; --pg-ring: 180px; --pg-rg-ring: 104px; --pg-md: 52px; --pg-tab-art: 48px; }
  .shell { gap: var(--sp-6); }
  .screen { padding: var(--sp-6); --inv-pad: var(--sp-6); }
  .nav { flex-direction: column; align-items: stretch; gap: 0; }
  .nav-tabs { gap: var(--sp-1) var(--sp-4); }
  /* No room beside a tab for Hornet here (js/app-hornet.js): the rule marks the tab. */
  .nav-tabs > .hn { display: none; }
  /* Seven tabs don't fit: the tools fold into one, Build, and the Inventory under Your game;
     while you're in either pair a second row switches between its two. */
  .nav-tool, .nav-sep, .nav-inv { display: none; }
  .nav-tools { display: inline-flex; }
  .nav-tabs { flex-wrap: nowrap; justify-content: space-between; }
  .nav-home.is-parent { color: var(--ink-strong); box-shadow: inset 0 -2px 0 var(--accent); }
  .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. */
  :root:has(.nav-sub:not([hidden])) { --sticky-h: 104px; }
}
@media (max-width: 599px) {
  .screen { padding: var(--sp-4); --inv-pad: var(--sp-4); }
  /* The header in a single row: the title on the left, smaller, and the language on the right. */
  .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; flex: none; flex-wrap: nowrap; --tools-gap: var(--sp-3); }
  .lang-name { display: none; }
  .lang-code { display: inline; }
  /* At the right edge, the list opens leftwards. */
  .lang-menu { left: auto; right: calc(var(--sp-4) * -1); }
  .title { font-size: var(--fs-lg); text-align: left; letter-spacing: var(--track-title); }
  /* The bar, on a phone: the five tabs spread out in one row. */
  .nav-tabs { justify-content: space-between; gap: 0 var(--sp-3); }
  .mh-crown { display: none; }
  .brand, .masthead .brand { flex-direction: row; align-items: center; gap: var(--sp-2); }
  .mh-emblem { width: var(--sp-8); height: var(--sp-8); flex: none; }
  /* The title rides the frame's line: the plate opens lower so the text clears it. */
  .screen { padding-top: var(--sp-8); }
  .nav-tab { flex-direction: column; justify-content: center; gap: 0; min-height: 44px; font-size: var(--fs-sm); letter-spacing: 0; text-align: center; }
  .nav-sep { display: none; }
}
@media (min-width: 900px) {
  .nav-tab { white-space: nowrap; }
}

/* ── Hover, across the site ───────────────────────────────────────────── */
@media (hover: hover) {
  .nav-tab:hover:not([aria-current="page"]) { box-shadow: inset 0 -2px 0 color-mix(in oklab, var(--accent) 35%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* ── Saves: the game's profile screen ────────────────────────────────────
   The four slots stacked like the game's bars: the number, the masks with the
   HUD's sprites, the Needle and what the game carries (the Act, the completion, the Journal). No
   boxes: rules in the spotlight's bone between slots, and the whole bar responds to the finger. The
   slot you're playing carries the accent's line on its edge and a light from
   that edge. Free mode, which is nobody's game, is the one sheet with a frame. */
/* Saves draws its own plate (.saves-body, with the frame): the screen around it adds none. */
.screen[data-view="saves"] { padding: 0; }
.saves-body {
  --inv-pad: var(--sp-8);
  --inv-line: color-mix(in oklab, var(--spotlight) 13%, transparent);
  position: relative; isolation: isolate; 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 { display: flex; flex-wrap: wrap; gap: var(--sp-half); }
.save-masks img { height: var(--sp-6); 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: calc(var(--sp-16) * 1.5); 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(--spotlight) 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
   Hornet standing 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(--spotlight) 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(--spotlight) 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), 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; }
.save-where { flex-basis: 100%; font-family: var(--font-display); font-size: var(--fs-sm); letter-spacing: var(--track-title); color: var(--ink); }
.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-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: Hornet 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 Hornet 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(--spotlight) 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(--spotlight) 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(--spotlight) 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-fallen { height: calc(var(--sp-16) * 1.75); }
.imp-drop.is-drag .imp-figure { filter: drop-shadow(0 0 18px color-mix(in oklab, var(--spotlight) 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: "fname fname" "nail hud" "nail meta" "facts facts" "warn warn" "sync sync" "actions actions";
  gap: var(--sp-3) var(--sp-6); text-align: center;
}
.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-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; }
.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-direction: column; align-items: center; gap: var(--sp-2); max-width: 44ch; margin-top: var(--sp-3); }
.imp-sync-k { font-size: var(--fs-md); color: var(--ink-strong); }
.imp-sync-v { font-size: var(--fs-md); }
.imp-sync-v[aria-checked="true"] { color: var(--ink-strong); }
.imp-sync-note { margin: 0; font-size: var(--fs-sm); line-height: 1.45; color: var(--muted); text-wrap: pretty; }
.imp-actions { grid-area: actions; display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); }

@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-fallen { 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-warn, .imp-actions { animation: title-in 600ms var(--ease-fade) 600ms both; }
}
@keyframes imp-breathe { from { opacity: 0.5; } to { opacity: 0.85; } }
@keyframes imp-float { 50% { transform: translateY(-6px); } }
/* In the header, Hornet and the slot you're playing; on mobile, only the number beside her. */
/* The save selector (saveLink): Hornet 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): Hornet 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 Hornet 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(--spotlight) 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 colour. 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: "fname fname" "nail hud" "meta meta" "facts facts" "warn warn" "sync sync" "actions actions"; }
  .imp-meta { justify-content: center; }
  .imp-bar { margin-bottom: var(--sp-2); }
  /* The import view's title («Importar a la Partida 2») is longer than a phone: it wraps onto two
     lines inside its plaque instead of being cut (the review's choice, 14 Oct 2026), and the way back clears it. */
  .imp .screen-head { width: max-content; white-space: normal; }
  .imp .screen-head .sec-title { text-align: center; text-wrap: balance; }
  .imp .imp-bar { margin-top: var(--sp-6); }
  /* 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); }
  .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-where { grid-column: 1 / -1; }
  .save-fact { display: contents; }
  .save-fact b { text-align: right; }
  /* Its unit right after the figure, also in the import view's preview, which centres its text. */
  .save-fact .u { text-align: left; }
  .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-nail img { height: var(--sp-12); }
  .save-nail span { display: none; }
  /* On a phone Hornet alone didn't say "your saves": larger, with a short word under her
     (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); }
}


@keyframes lit-in {
  from { filter: brightness(0.2) saturate(0); }
  40% { filter: brightness(1.8) drop-shadow(0 0 8px var(--focus-glow)); }
}
@keyframes light-up { 30% { filter: brightness(1.9) drop-shadow(0 0 10px var(--focus-glow)); opacity: 1; } }
@keyframes mask-mend { from { filter: brightness(2.4); opacity: 0; transform: scale(0.5); } }
@keyframes notch-pending { to { opacity: 0.55; } }
.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;
}

/* ── Your game, the start screen (js/app-home.js) ─────────────────────────
   In free mode, the ghost bench: the card a save shows, unlit, and the file dropped here goes
   into the first empty save. In a save: the scene where she rests and what changed since the
   previous save (design/03-redesign.md, step 3, the calm round). Figures in --font-num, as everywhere. */
/* Hornet on the bench she rests at (js/benches.js, from the game's files): both of the wiki's
   sprites at one scale (--hn-rest), her hem on the bench's seat; the box is theirs together
   (--w, its width in the sprites' pixels, and its aspect ratio, set inline with the places). */
.hm-rest { position: relative; display: block; width: calc(var(--w) * var(--hn-rest) * 1px); max-width: 100%; margin-block: var(--sp-2); filter: drop-shadow(0 0 18px color-mix(in oklab, var(--spotlight) 18%, transparent)); }
.hm-rest img { position: absolute; max-width: none; height: auto; }
.hm-invite-acts { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--sp-6); margin-top: var(--sp-1); }
/* Free mode (the ghost bench): the card unlit, the kingdom's name in faint ink, Hornet a
   silhouette with a faint rim and the figures waiting. Dropping a file on it lights it. */
.hm-card.is-ghost { border: 1px dashed transparent; transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease); }
.is-ghost .hm-sup { color: var(--muted); }
.is-ghost .hm-area { color: color-mix(in oklab, var(--ink) 30%, transparent); text-shadow: none; }
/* The bench and Hornet one silhouette. */
.is-ghost .hm-rest { filter: brightness(0) drop-shadow(0 0 1px color-mix(in oklab, var(--ink) 45%, transparent)); opacity: 0.85; }
.is-ghost .hm-fig b { color: var(--line-ctl); }
.hm-ghost { margin: var(--sp-2) 0 0; font-family: var(--font-display); font-size: var(--fs-lg); letter-spacing: var(--track-title); color: var(--ink-strong); }
.hm-invite.is-drag { border-color: var(--accent-line); background-color: var(--accent-soft); }
/* The line when the game's own 100% differs from the site's (Progress, js/app-progress.js). */
.hm-check { margin: var(--sp-1) 0 0; font-size: var(--fs-xs); color: var(--muted); text-align: center; }
.hm-check.is-off { color: var(--cond); }
/* The bench card: the game's area title (a small «Resting at» over the area's name in its capitals),
   in the area's own colours from the map screen (--area-line, --area-fill: set inline on .hm from
   the area's tokens, js/app-home.js). The name is the area's line; the screen's frame and its
   light from the top are the two mixed (above, .hm:has(.hm-scene)). */
.hm { --area-line: var(--spotlight); --area-fill: var(--scene-glow); }
.hm-card {
  position: relative; isolation: isolate; display: grid; justify-items: center; gap: var(--sp-2);
  padding: var(--sp-6) var(--sp-4) var(--sp-4); margin: 0 0 var(--sp-6); text-align: center;
}
.hm-sup { margin: 0; font-family: var(--font-display); font-size: var(--fs-sm); letter-spacing: var(--track-wide); text-transform: uppercase; color: color-mix(in oklab, var(--area-line) 70%, var(--ink-2)); }
.hm-area {
  margin: 0; font-family: var(--font-display); font-weight: 400; font-size: var(--fs-hero); line-height: 1; letter-spacing: var(--track-title);
  text-transform: uppercase; color: var(--area-line); text-wrap: balance;
  text-shadow: 0 0 24px color-mix(in oklab, var(--area-line) 35%, transparent);
}
.hm-sub { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }
.hm-figs { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-4) var(--sp-12); }
.hm-fig { display: grid; justify-items: center; gap: var(--sp-1); }
.hm-fig-k { font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--label); }
.hm-fig b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); font-weight: 600; line-height: 1; color: var(--ink-strong); }
.hm-fig:first-child b { font-size: var(--fs-2xl); }
.hm-fig .u { font-size: var(--fs-xs); color: var(--muted); }
/* The scene (design/03-redesign.md, step 3, the calm round, A): the bench card, taller, with the
   map of the area she rests in behind it (js/app-map.js, App.mapAreaHtml: the Map's own drawing
   on a canvas; a picture with no marks and no Hornet, not a link) and the 100% as one figure.
   The map fills the card around her bench, its room right under her (the map round, B and C,
   the review's choice, 14 Oct 2026: it had faded to nothing), in the Map's own tone at
   --hm-map-opacity, strongest round her and fading out to the sides, the foot and under the title. */
.hm-scene { min-height: calc(var(--hm-map-max) * 1.4); align-content: center; padding-block: var(--sp-8) var(--sp-4); margin-bottom: var(--sp-8); }
.hm-scene .hm-rest { margin-block: var(--sp-4) var(--sp-2); }
.hmB-map { position: absolute; inset: 0; z-index: -2; margin: 0; overflow: hidden; opacity: var(--hm-map-opacity); pointer-events: none; }
.hmB-canvas { position: absolute; inset: 0; width: 100%; height: 100%; filter: var(--map-tone);
  -webkit-mask: radial-gradient(ellipse 50% 46% at 50% 56%, #000 50%, transparent 100%); mask: radial-gradient(ellipse 50% 46% at 50% 56%, #000 50%, transparent 100%); }
/* Hornet between what she is and what she wears (design/03-redesign.md, step 3, the summary round,
   C, A, C, A): on her left the game's act card and the HUD (her masks in a row, the spool with its
   silk), leaning towards her; on her right the Crest she wears, the Crest screen's figure at
   --hm-crest-k, and the way into it. On a phone they go under her, the Crest last. */
.hm-flank { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: var(--sp-12); width: 100%; max-width: var(--column); }
.hm-self { display: grid; justify-items: end; justify-self: end; gap: var(--sp-3); }
.hm-act { display: grid; justify-items: inherit; gap: var(--sp-1); margin: 0; font-family: var(--font-display); text-transform: uppercase; }
.hm-act-sup { font-size: var(--fs-xs); letter-spacing: var(--track-wide); color: color-mix(in oklab, var(--area-line) 60%, var(--ink-2)); }
.hm-act-name { font-size: var(--fs-lg); line-height: 1; letter-spacing: var(--track-title); color: var(--ink-strong); }
.hm-hud { display: grid; justify-items: inherit; gap: var(--sp-2); }
.hm-masks { display: flex; flex-wrap: wrap; justify-content: inherit; gap: var(--sp-1); }
.hm-masks img { height: var(--hm-mask); width: auto; }
.hm-spool { display: flex; align-items: center; gap: var(--sp-2); }
.hm-spool img { height: calc(var(--hm-mask) * 0.62); width: auto; }
.hm-spool b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-lg); line-height: 1; color: var(--ink-strong); }
.hm-worn { justify-self: start; color: var(--ink); text-decoration: none; }
.hm .hm-worn .ct-crest { --k: var(--hm-crest-k); gap: var(--sp-1); }
.hm-worn .ct-crest-name h3 { margin: 0; font-size: var(--fs-sm); transition: color var(--dur) var(--ease); }
.hm-worn .ct-curse { display: none; }
.hm-worn:is(:hover, :focus-visible) .ct-crest-name h3 { color: var(--ink-strong); }
.hm-worn:focus-visible { outline: 1px solid var(--accent); outline-offset: var(--sp-2); }

/* The 100% as one figure, the way into Progress: its label, the figure, and what Progress holds
   under it with the accent's arrow, stepping forward under the finger (as the ways on did). */
.hm-pct { display: inline-grid; justify-items: center; gap: var(--sp-1); padding: var(--sp-2) var(--sp-4); text-decoration: none; color: var(--ink); }
.hm-pct b { 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); }
.hm-pct .u { font-size: var(--fs-xs); }
.hm-pct-go { font-size: var(--fs-xs); color: var(--muted); transition: color var(--dur) var(--ease); }
.hm-pct-go::after { content: ' →'; display: inline-block; color: var(--accent); transition: transform var(--dur) var(--ease); }
.hm-pct:is(:hover, :focus-visible) .hm-pct-go { color: var(--ink-strong); }
.hm-pct:is(:hover, :focus-visible) .hm-pct-go::after { transform: translateX(var(--sp-1)); }
/* Since the previous save, under the scene: the one list, at a list's measure. */
.hm-since { display: grid; gap: var(--sp-3); align-content: start; max-width: var(--column); margin-inline: auto; }
.hm-since > .text-btn { justify-self: start; font-size: var(--fs-sm); }
.hm-since > .text-btn[aria-expanded] { margin-top: var(--sp-1); }
/* The lists' rows (design/03-redesign.md, step 3, the lists round, A, Progress's clean rows): the
   thing's picture and its name in the game's face with where under it. The picture: plain, a
   Tool on its slot's diamond, or the map's own mark (a piece's disc, a gauntlet's champion). */
.hm-rows { list-style: none; margin: 0; padding: 0; }
.hm-row { display: grid; grid-template-columns: var(--hm-art) minmax(0, 1fr); align-items: center; gap: var(--sp-3); padding: var(--sp-2) var(--sp-1); border-bottom: 1px solid var(--line-row); }
.hm-row:last-child { border-bottom: 0; }
.hm-row-txt { display: grid; min-width: 0; }
.hm-row-name { font-family: var(--font-display); font-size: var(--fs-sm); color: var(--ink-strong); overflow-wrap: anywhere; }
.hm-row-sub { font-size: var(--fs-xs); color: var(--muted); }
.hm-art { position: relative; display: grid; place-items: center; width: var(--hm-art); height: var(--hm-art); }
.hm-art img { position: relative; width: calc(var(--hm-art) - var(--sp-1)); height: calc(var(--hm-art) - var(--sp-1)); object-fit: contain; }
.hm-art.is-slot::before { content: ''; position: absolute; inset: 22%; transform: rotate(45deg); border: 1.5px solid var(--c); background: color-mix(in oklab, var(--c) 14%, var(--scene-bg)); }
.hm-art.is-slot img { width: 72%; height: 72%; }
.hm-art.is-mark :is(.mp-dot, .mp-pin) { position: static; margin: 0; --mp-s: calc(var(--hm-art) - var(--sp-1)); }
.hm-art.is-glyph::before { content: ''; width: var(--sp-3); height: var(--sp-3); transform: rotate(45deg); background: var(--accent); box-shadow: var(--glow); }
/* A group's head inside a list: small capitals and its count. */
.hm-kind { display: flex; align-items: baseline; gap: var(--sp-2); margin: var(--sp-2) 0 0; font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.hm-kind-n { font-weight: 400; letter-spacing: 0; color: var(--muted); }
/* The Journal's entries, their portraits. */
.hm-faces { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin: 0; padding: var(--sp-1) 0; }
.hm-faces img { width: var(--sp-8); height: var(--sp-8); object-fit: contain; border-radius: 50%; border: 1px solid var(--line-ctl); background: var(--scene-bg); }
/* A gauge: what there was, and what was added lit. */
.hm-bar { position: relative; display: block; height: 3px; background: var(--ring-track); }
.hm-bar i { position: absolute; inset-block: 0; left: 0; background: color-mix(in oklab, var(--accent) 45%, transparent); }
.hm-bar i + i { background: var(--accent); box-shadow: var(--glow); }
/* Arriving at the screen, the rows fade in one by one (js/app-home.js, .is-enter). */
@media (prefers-reduced-motion: no-preference) {
  .hm.is-enter :is(.hm-row, .hm-kind, .hm-faces) { animation: hm-row-in var(--dur-slow) calc(var(--i, 0) * var(--dur-step)) var(--ease-fade) both; }
}
@keyframes hm-row-in { from { opacity: 0; transform: translateY(var(--sp-1)); } }
@media (max-width: 599px) {
  .hm-area { font-size: var(--fs-2xl); }
  /* Free mode's four figures two by two: four in a row don't fit, and three left one alone (the review's choice, 14 Oct 2026). */
  .hm-figs { display: grid; grid-template-columns: repeat(2, auto); justify-content: center; gap: var(--sp-4) var(--sp-12); }
  .hm-scene { min-height: 0; }
  .hm { --hm-crest-k: var(--hm-crest-k-sm); --hm-mask: var(--hm-mask-sm); }
  .hm-flank { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: var(--sp-4); }
  .hm-flank > .hm-rest { grid-row: 1; }
  .hm-self { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; justify-self: center; gap: var(--sp-3) var(--sp-6); }
  .hm-self > * { justify-items: start; }
  .hm-worn { justify-self: center; }
}

/* ── Progress (js/app-progress.js): the ledger ────────────────────────────
   The reference implementation's (design/03-redesign.md, step 5): the 100% large, the road to the next Act at a
   glance, then each category as a row: its name, its things as the game's pictures or as pips,
   its count and the disclosure's ring; open, its things one per line: a tick when you have it,
   a Tool's slot colour (data, the slot tokens), the name, and its Act and area on the right,
   quiet. What belongs to a later Act than yours dims. */
.pg-total { display: grid; justify-items: center; gap: var(--sp-1); margin: var(--sp-6) 0 var(--sp-4); text-align: center; }
.pg-total .lbl { margin: 0; }
/* The figure in a ring that fills with it (design/20-progress-total-variants.html, A): the
   Journal's Memento made larger, a thin filigree circle inside, the figure in the game's face. */
.pg-ring { position: relative; display: grid; place-content: center; justify-items: center; gap: var(--sp-1); width: var(--pg-ring); aspect-ratio: 1; border-radius: 50%; }
.pg-ring::before {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(var(--accent) calc(var(--p) * 3.6deg), var(--ring-track) 0);
  -webkit-mask: radial-gradient(closest-side, transparent calc(100% - var(--ring) * 2), var(--ring-mask) calc(100% - var(--ring) * 2 + 1px));
  mask: radial-gradient(closest-side, transparent calc(100% - var(--ring) * 2), var(--ring-mask) calc(100% - var(--ring) * 2 + 1px));
  filter: drop-shadow(0 0 8px color-mix(in oklab, var(--spotlight) 40%, transparent));
}
.pg-ring::after { content: ''; position: absolute; inset: var(--sp-4); border-radius: 50%; border: 1px solid color-mix(in oklab, var(--filigree) 25%, transparent); }
.pg-total-n { margin: 0; font-family: var(--font-display); line-height: 1; color: var(--ink-strong); text-shadow: 0 0 24px color-mix(in oklab, var(--spotlight) 35%, transparent); }
.pg-total-n b { font-size: calc(var(--pg-ring) / 3); font-weight: 400; }
.pg-total-n .u { font-size: calc(var(--pg-ring) / 8); color: var(--ink-2); margin-left: 0.08em; }
.pg-ring-k { margin: 0; font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
/* The journey: the road's steps as diamonds on one silk thread, lit up to where you are. */
.pg-journey { display: grid; justify-items: center; gap: var(--sp-3); max-width: var(--column); margin: var(--sp-6) auto var(--sp-2); text-align: center; }
.pg-journey-steps { position: relative; display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); width: 100%; margin: 0; padding: 0; list-style: none; }
.pg-journey-steps::before {
  content: ''; position: absolute; top: calc(var(--sp-3) - 0.5px); left: calc(50% / var(--n)); right: calc(50% / var(--n)); height: 1px;
  background: linear-gradient(90deg, var(--silk) var(--f), color-mix(in oklab, var(--silk-rim) 35%, transparent) var(--f));
}
.pg-journey-steps li { position: relative; display: grid; justify-items: center; align-content: start; gap: var(--sp-2); }
.pg-journey-steps i { display: grid; place-items: center; width: var(--sp-6); height: var(--sp-6); }
.pg-journey-steps i::before { content: ''; width: var(--sp-3); height: var(--sp-3); transform: rotate(45deg); border: 1px solid var(--silk-rim); background: var(--scene-bg); }
.pg-journey-steps li.is-done i::before { background: var(--silk); border-color: var(--silk); box-shadow: 0 0 8px color-mix(in oklab, var(--silk) 60%, transparent); }
.pg-journey-steps li.is-cur i::before { width: calc(var(--sp-4) + var(--sp-half)); height: calc(var(--sp-4) + var(--sp-half)); border-color: var(--accent); box-shadow: var(--glow); }
.pg-journey-steps span { font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--muted); line-height: 1.3; }
.pg-journey-steps li.is-cur span { color: var(--ink-strong); }
/* Act 3: the endings, their diamonds set apart by a small ring; not a sequence, so the thread stays unlit. */
.pg-journey.is-game .pg-journey-steps::before { background: color-mix(in oklab, var(--silk-rim) 35%, transparent); }
/* Where you are, over the tabs (design/40, 1 A): the three Acts on the journey's thread, each with
   the game's name for it, yours marked; one sentence under them. */
.pg-acts { max-width: var(--column); margin: var(--sp-2) auto var(--sp-6); }
.pg-acts .pg-journey { margin: 0 auto var(--sp-3); }
.pg-acts .pg-journey-steps span { display: grid; gap: var(--sp-half); }
.pg-acts .pg-journey-steps small { font-family: var(--font-display); font-size: var(--fs-xs); font-weight: 400; letter-spacing: var(--track-label); color: var(--muted); }
.pg-acts .pg-journey-steps li.is-cur small { color: var(--ink-2); }
.pg-acts .pg-journey-steps em { justify-self: center; padding: 0 var(--sp-2); border: 1px solid var(--accent-line); font-style: normal; font-family: var(--font-num); font-size: var(--fs-2xs); letter-spacing: 0; text-transform: none; color: var(--ink-strong); }
.pg-here { margin: 0 auto; text-align: center; font-size: var(--fs-md); color: var(--ink-2); text-wrap: balance; }
.pg-here strong { font-weight: 600; color: var(--ink-strong); }
.pg-journey-steps li.is-ending i::before { border-radius: 50%; transform: none; }
.pg-journey-steps li.is-ending.is-done span { color: var(--ink-strong); }
/* An Act 2 ending closed in an Act 3 game (design/41, 1 A): a hollow dashed ring, crossed, its name
   struck through and «closed» under it (an inline block, so the line doesn't reach it). */
.pg-journey-steps li.is-shut i::before { border-style: dashed; border-color: var(--muted); background: none; }
.pg-journey-steps li.is-shut i::after { content: ''; position: absolute; width: var(--sp-4); height: 1px; background: var(--muted); transform: rotate(-45deg); }
.pg-journey-steps li.is-shut span { color: var(--muted); text-decoration: line-through; text-decoration-color: color-mix(in oklab, var(--muted) 60%, transparent); }
.pg-journey-steps li.is-shut small { display: inline-block; width: 100%; font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted); }
.pg-seg { justify-content: center; }
/* The tabs (design/27b-progress-variants.html, 1 D; the third, Tasks, design/75, 3 A): cards, each
   with its own picture of how far you are. The game selects with value, not hue: the open one white
   on the picked grey. On a phone a card keeps its figure, not the words after it (.pg-tab-x). */
.pg-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-3); max-width: calc(var(--column) + var(--sp-16) * 2); margin: var(--sp-2) auto var(--sp-4); }
.pg-tab {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--sp-1) var(--sp-4);
  padding: var(--sp-3) var(--sp-4); border: 1px solid var(--line); color: var(--muted); text-align: left;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.pg-tab-art { grid-row: span 2; display: grid; place-items: center; width: var(--pg-tab-art); height: var(--pg-tab-art); }
.pg-tab-art img { width: var(--pg-tab-art); height: var(--pg-tab-art); object-fit: contain; filter: var(--missing-soft); }
.pg-tab-art .pg-ring { --pg-ring: var(--pg-tab-art); }
.pg-tab-art .pg-ring::after { inset: var(--sp-2); }
.pg-tab-art .pg-total-n b { font-size: var(--fs-md); }
.pg-tab-art .pg-total-n .u { font-size: var(--fs-2xs); }
.pg-tab-l { font-family: var(--font-display); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; font-size: var(--fs-md); text-wrap: balance; }
.pg-tab-s { font-size: var(--fs-xs); }
.pg-tab-s b { font-family: var(--font-num); font-size: var(--fs-lg); font-weight: 600; }
.pg-tab:is(:hover, :focus-visible) { border-color: var(--accent); color: var(--ink-2); }
.pg-tab[aria-selected="true"] { color: var(--ink-strong); border-color: var(--accent-line); background: var(--picked); box-shadow: 0 0 18px color-mix(in oklab, var(--spotlight) 12%, transparent); }
.pg-tab[aria-selected="true"] .pg-tab-art img { filter: none; }
.pg-tab[aria-selected="true"] .pg-tab-s { color: var(--ink-2); }
.pg-tab:not([aria-selected="true"]) .pg-ring::before { opacity: 0.6; }
/* What's left for the next Act (2 C): the step you're on with its picture, then one ring per
   part of it, the game's picture inside and the figure under; under each only what's missing. */
/* The step in the game's own words (design/28, 1 A): what the pane «Tareas» (or the Journal) says
   of it, tagged with where, and its way onto the Map; under the step's heading, in the whole road
   under each step's, and opened from the thread (2 A). */
.pg-says { position: relative; max-width: var(--column); margin: calc(-1 * var(--sp-2)) auto var(--sp-6); padding: var(--sp-3) var(--sp-4); text-align: center;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.pg-says-k { display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline; gap: var(--sp-1) var(--sp-2); margin: 0 0 var(--sp-1);
  font-family: var(--font-num); font-size: var(--fs-2xs); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.pg-says-tag { padding: 0 var(--sp-1); border: 1px solid var(--line-ctl); font-family: var(--font-num); font-size: var(--fs-2xs); font-style: normal; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--muted); }
.pg-says-name { color: var(--ink); }
.pg-says-loc::before { content: '·'; margin-right: var(--sp-2); }
.pg-says-d { margin: 0; font-style: italic; font-size: var(--fs-md); line-height: 1.5; color: var(--ink); text-wrap: pretty; white-space: pre-line; }
/* A task's picture beside its words (design/79, 1 A to 5 A): bare, as the pane «Tareas» draws it. */
.pg-says-task { display: flex; align-items: center; justify-content: center; gap: var(--sp-4); text-align: left; }
.pg-says-task > div { min-width: 0; }
.pg-says-task .pg-says-k { justify-content: flex-start; }
.pg-says-art { flex: none; width: var(--sp-12); height: var(--sp-12); }
.pg-says-art img { width: 100%; height: 100%; object-fit: contain; }
.pg-says-art.is-dim img { filter: var(--missing-soft); opacity: var(--missing-soft-opacity); }
.pg-says-map { margin-top: var(--sp-2); font-size: var(--fs-sm); }
/* A row's own words (4 A), folded. */
.pg-says-row { display: block; }
.pg-says-btn { min-height: 0; font-size: var(--fs-xs); }
.pg-says-q { display: block; margin-top: var(--sp-1); padding-left: var(--sp-3); border-left: 1px solid var(--line-ctl); font-style: italic; font-size: var(--fs-sm); line-height: 1.45; color: var(--ink); white-space: pre-line; text-wrap: pretty; }
.pg-says-q[hidden] { display: none; }
@media (max-width: 640px) {
  /* The objective's line under its name, not after a dot that a wrap leaves alone. */
  .pg-says-loc { flex-basis: 100%; }
  .pg-says-loc::before { content: none; }
}
.pg-rings { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--sp-8) var(--sp-6); max-width: calc(var(--column) + var(--sp-16)); margin: var(--sp-6) auto 0; }
/* The road's step as a summary (design/03-redesign.md, step 5, E): its parts' rings small, side by
   side, over their lists (.pg-groups); and, when there are several, the line that all are needed. */
.pg-lead { max-width: var(--column); margin: 0 auto var(--sp-4); text-align: center; font-size: var(--fs-sm); color: var(--ink-2); text-wrap: balance; }
.pg-rg { display: grid; justify-items: center; align-content: start; gap: var(--sp-2); text-align: center; }
.pg-rg-ring { --pg-ring: var(--pg-rg-ring); }
.pg-rg-ring::after { inset: var(--sp-3); }
.pg-rg-ring img { position: relative; width: calc(var(--pg-rg-ring) * 0.46); height: calc(var(--pg-rg-ring) * 0.46); object-fit: contain; }
.pg-rg.is-done .pg-rg-ring::before { filter: drop-shadow(0 0 12px color-mix(in oklab, var(--silk) 70%, transparent)); }
.pg-rg-n { margin: 0; font-family: var(--font-display); font-size: var(--fs-2xl); line-height: 1; color: var(--ink-strong); }
.pg-rg-n .u { font-family: var(--font-num); font-size: var(--fs-md); color: var(--muted); }
.pg-rg.is-done .pg-rg-n b { color: var(--good); }
.pg-rg-k { margin: 0; font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.pg-rg-after { margin: 0; font-size: var(--fs-xs); color: var(--muted); text-wrap: balance; }
/* A medallion: a missing thing's picture in a lit circle, its name under it, a pin to the Map. */
.pg-mds { display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-start; gap: var(--sp-3) var(--sp-2); margin: var(--sp-2) 0 0; padding: 0; list-style: none; }
.pg-md { position: relative; display: grid; justify-items: center; align-content: start; gap: var(--sp-1); width: calc(var(--pg-md) + var(--sp-6)); text-align: center; }
.pg-md-art {
  position: relative; display: grid; place-items: center; width: var(--pg-md); height: var(--pg-md); border-radius: 50%; border: 1px solid var(--accent-line);
  background: radial-gradient(closest-side, color-mix(in oklab, var(--spotlight) 14%, transparent), transparent);
  box-shadow: 0 0 14px color-mix(in oklab, var(--spotlight) 18%, transparent);
}
.pg-md-art img { width: calc(var(--pg-md) * 0.72); height: calc(var(--pg-md) * 0.72); object-fit: contain; }
.pg-md-badge { position: absolute; top: calc(var(--sp-1) * -1); right: calc(var(--sp-1) * -1); min-width: var(--sp-4); padding: 0 var(--sp-1); border-radius: var(--sp-3); background: var(--accent); color: var(--scene-bg); font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 700; line-height: 1.6; }
.pg-md-pin { position: absolute; top: calc(var(--pg-md) - var(--sp-6)); right: 0; display: grid; place-items: center; width: var(--sp-6); height: var(--sp-6); border-radius: 50%; border: 1px solid var(--line-ctl); background: var(--scene-bg); color: var(--ink-2); }
.pg-md-pin .ic { width: var(--sp-3); }
.pg-md-pin:is(:hover, :focus-visible) { border-color: var(--accent); color: var(--accent); }
.pg-md-name { font-size: var(--fs-2xs); line-height: 1.25; color: var(--ink); text-wrap: balance; }
.pg-md-sub { font-size: var(--fs-2xs); line-height: 1.25; color: var(--muted); text-wrap: balance; }
.pg-md-more { align-self: center; font-family: var(--font-num); font-size: var(--fs-sm); color: var(--ink-2); }
.pg-controls { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-2) var(--sp-12); margin-bottom: var(--sp-2); }
.pg-part {
  display: flex; align-items: baseline; justify-content: center; gap: var(--sp-3); margin: var(--sp-8) 0 var(--sp-2);
  font-family: var(--font-display); font-size: var(--fs-xl); font-weight: 600; letter-spacing: var(--track-title); color: var(--filigree);
}
.pg-part b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-lg); color: var(--ink-2); }
.pg > .pg-note, .pg-panel > .pg-note { text-align: center; }
/* What's next (design/40, 2 A): the 100% tab's rows, a numbered diamond in place of the tick, the
   name free to wrap, and the wishes that would give a point as small chips. */
.pg-todo { max-width: calc(var(--column) + var(--sp-16)); margin: 0 auto; }
.pg-todo .pg-part { margin-top: var(--sp-6); }
.pg-todo .pg-item { grid-template-columns: var(--sp-6) 40px minmax(0, 1fr) auto 28px; }
.pg-todo .pg-name { white-space: normal; text-wrap: balance; }
.pg-num { display: grid; place-items: center; width: var(--sp-6); height: var(--sp-6); border: 1px solid var(--accent-line); transform: rotate(45deg); }
.pg-num b { transform: rotate(-45deg); font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 700; color: var(--ink-strong); }
.pg-opts { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-2); margin: var(--sp-1) 0 0; padding: 0; list-style: none; font-size: var(--fs-xs); color: var(--ink-2); }
.pg-opts li { display: inline-flex; align-items: center; gap: var(--sp-1); padding: 0 var(--sp-2); border: 1px solid var(--line-ctl); }
.pg-opts img { width: var(--sp-4); height: var(--sp-4); object-fit: contain; }
.pg-opts li.is-more { border: 0; color: var(--muted); }
.pg-opts li.is-more .text-btn { font-size: inherit; min-height: 0; }
.pg-opts:not(.is-open) li.is-extra { display: none; }
.pg-opts li.is-got { border-style: dashed; color: var(--muted); }
.pg-opts li.is-got .ic { width: 10px; height: 10px; color: var(--ink-2); }
/* The one list's toggle under its title, and a done row: the number's diamond, dimmed, with the tick
   (design/82, 1 A). */
.pg-todo .pg-seg { display: flex; margin-bottom: var(--sp-3); }
.pg-num.is-done { border-color: var(--line-ctl); color: var(--ink-2); }
.pg-num.is-done .ic { transform: rotate(-45deg); width: 10px; height: 10px; }
/* How the 100% splits (design/75-progress-100-variants.html, 1 A; js/app-progress.js splitBar):
   one line of a hundred in a thin filigree frame with a diamond at each end, a hairline notch where
   each category ends, a taller one with its diamonds where the Tools' half ends. Self-contained:
   it may be swapped for another design. */
.pg-split-box { max-width: calc(var(--column) + var(--sp-16)); margin: 0 auto var(--sp-6); }
.pg-split { position: relative; margin: var(--sp-2) var(--sp-3) 0; padding: 3px; border: 1px solid color-mix(in oklab, var(--filigree) 45%, transparent); }
.pg-split::before, .pg-split::after {
  content: ''; position: absolute; top: 50%; width: var(--sp-2); height: var(--sp-2); background: var(--scene-bg);
  border: 1px solid color-mix(in oklab, var(--filigree) 70%, transparent); transform: translate(-50%, -50%) rotate(45deg);
}
.pg-split::before { left: calc(-1 * var(--sp-1)); }
.pg-split::after { left: calc(100% + var(--sp-1)); }
.pg-split-track { position: relative; display: flex; height: var(--sp-2); background: var(--ring-track); }
.pg-split-seg { position: relative; min-width: 2px; }
.pg-split-seg + .pg-split-seg { box-shadow: inset 1px 0 0 var(--scene-bg); }
.pg-split-seg i { position: absolute; inset: 0 auto 0 0; background: var(--accent); box-shadow: 0 0 6px color-mix(in oklab, var(--spotlight) 45%, transparent); }
.pg-split-seg + .pg-split-seg i { left: 1px; }
.pg-split-half { position: absolute; top: calc(-1 * var(--sp-2)); bottom: calc(-1 * var(--sp-2)); width: 1px; background: color-mix(in oklab, var(--filigree) 70%, transparent); }
.pg-split-half::before, .pg-split-half::after {
  content: ''; position: absolute; left: 50%; width: var(--diamond); height: var(--diamond); background: var(--filigree); transform: translate(-50%, -50%) rotate(45deg);
}
.pg-split-half::before { top: 0; }
.pg-split-half::after { top: 100%; }
.pg-legend { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-1) var(--sp-4); margin: var(--sp-4) 0 0; padding: 0; list-style: none; font-size: var(--fs-xs); color: var(--muted); }
.pg-legend b { font-family: var(--font-num); font-weight: 600; color: var(--ink-strong); }
/* A part's title with the masthead's filigree under it (design/75, 6 B). */
.pg-part.is-crowned { flex-direction: column; align-items: center; gap: var(--sp-2); }
.pg-crown { display: block; width: calc(var(--sp-16) * 2.5); height: auto; color: color-mix(in oklab, var(--filigree) 55%, transparent); }
.pg-colhead { max-width: calc(var(--column) + var(--sp-16) * 2); margin: var(--sp-4) auto calc(var(--sp-2) * -1); padding-right: calc(28px + var(--sp-4)); text-align: right; font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--label); }
/* No save (design/40, 5 A): the invitation over either tab. */
.pg-invite { max-width: calc(var(--column) + var(--sp-16)); margin: 0 auto var(--sp-6); }
.pg-groups { display: grid; max-width: calc(var(--column) + var(--sp-16) * 2); margin: var(--sp-4) auto 0; border-top: 1px solid var(--line-row); }
.pg-group { min-width: 0; border-bottom: 1px solid var(--line-row); }
.pg-head { margin: 0; font-size: var(--fs-md); font-weight: 400; }
.pg-open {
  display: grid; grid-template-columns: minmax(0, 13rem) minmax(0, 1fr) auto auto; align-items: center; gap: var(--sp-4);
  width: 100%; min-height: var(--sp-12); padding: var(--sp-2) 0; text-align: left; color: var(--ink);
  transition: color var(--dur) var(--ease);
}
.pg-open:is(:hover, :focus-visible) { color: var(--ink-strong); }
.pg-open:is(:hover, :focus-visible) .disc-ring, .pg-group.is-open .disc-ring { color: var(--ink-strong); border-color: var(--accent); background-color: var(--accent-soft); }
.pg-title { font-family: var(--font-body); font-size: var(--fs-md); }
.pg-group.is-done .pg-title { color: var(--ink-2); }
.pg-group.is-done .pg-count b { color: var(--good); }
.pg-strip { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-half); min-width: 0; }
.pg-strip img { width: var(--sp-6); height: var(--sp-6); object-fit: contain; }
.pg-strip img.is-missing { filter: var(--missing-soft); opacity: var(--missing-soft-opacity); }
.pg-pips { gap: 3px; }
.pg-pips i { width: var(--diamond); height: var(--diamond); transform: rotate(45deg); border: 1px solid var(--accent-line); }
.pg-pips i.is-on { background: var(--accent); border-color: var(--accent); }
.pg-pips i.is-promised { border-color: var(--accent); background: color-mix(in oklab, var(--accent) 35%, transparent); }
.pg-group.is-open > :not(.pg-head) { margin-left: calc(13rem + var(--sp-4)); }
.pg-group.is-open > :last-child { margin-bottom: var(--sp-4); }
/* A Tasks group's emblem by its title, the Wishwalls' (design/75, 7 B); an empty one keeps its place. */
.pg-title:has(.pg-emb) { display: inline-flex; align-items: center; gap: var(--sp-2); }
.pg-emb { flex: none; display: grid; place-items: center; width: var(--sp-8); height: var(--sp-8); }
.pg-emb img { max-width: 100%; max-height: 100%; object-fit: contain; }
/* Tasks: the pane's own filigree between the main objectives and the wishes (design/80, E), the
   height of a group's row with its picture centred in it. */
.pg-spacer { display: grid; place-items: center; padding: var(--sp-4) 0; }
.pg-spacer img { width: min(100%, calc(var(--sp-16) * 6)); height: auto; opacity: var(--filigree-art); }
/* The 100% and Tasks (design/75, 2 B): an open group's rows take the whole width, and on a wide
   screen each thing is one line: its picture, its name, how to get it, where, the pin. */
.pg-groups.is-wide .pg-group.is-open > :not(.pg-head) { margin-left: 0; }
@media (min-width: 700px) {
  .pg-groups.is-wide .pg-item, .pg-groups.is-wide .pg-item.is-plain {
    grid-template-columns: var(--sp-4) 40px minmax(12rem, 18rem) minmax(0, 1fr) minmax(7rem, auto) 28px;
    grid-template-areas: "mark art name how place pin";
  }
  .pg-groups.is-wide .pg-main { display: contents; }
  .pg-groups.is-wide .pg-mark { grid-area: mark; }
  .pg-groups.is-wide .pg-art { grid-area: art; }
  .pg-groups.is-wide .pg-name { grid-area: name; white-space: normal; text-wrap: balance; }
  .pg-groups.is-wide .pg-how { grid-area: how; }
  .pg-groups.is-wide .pg-place { grid-area: place; }
  .pg-groups.is-wide .pg-onmap { grid-area: pin; }
  .pg-groups.is-wide .pg-says-row { grid-column: 3 / 5; grid-row: 2; }
  .pg-groups.is-wide .pg-item:has(.pg-says-row) :is(.pg-mark, .pg-art, .pg-place, .pg-onmap) { grid-row: 1 / span 2; }
}
/* Free mode: a missing row's box marks it had (the Inventory's marks). */
.pg-mark.is-own { min-height: 0; }
@media (max-width: 599px) {
  .pg-open { grid-template-columns: minmax(0, 1fr) auto auto; }
  .pg-strip { display: none; }
  .pg-tab-x { display: none; }
  .pg-group.is-open > :not(.pg-head) { margin-left: 0; }
  /* Every step keeps its name (round 1, 7 A), smaller; Act 3's seven in small letters. */
  .pg-journey-steps span { font-size: calc(var(--fs-2xs) * 0.9); letter-spacing: 0; text-wrap: balance; }
  .pg-journey.is-game .pg-journey-steps span { text-transform: none; font-weight: 400; }
  .pg-tabs { gap: var(--sp-2); }
  .pg-tab { grid-template-columns: minmax(0, 1fr); justify-items: center; text-align: center; padding: var(--sp-2); }
  .pg-tab-art { grid-row: auto; }
  .pg-tab-l { font-size: var(--fs-xs); letter-spacing: var(--track-label); }
  .pg-rings { grid-template-columns: 1fr 1fr; gap: var(--sp-6) var(--sp-3); }
  .pg-md { width: calc(var(--pg-md) + var(--sp-3)); }
}
.pg-count { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); color: var(--ink-strong); white-space: nowrap; }
.pg-count .u { font-size: var(--fs-xs); font-style: normal; color: var(--muted); }
.pg-note { margin: 0 0 var(--sp-2); font-size: var(--fs-xs); color: var(--muted); }
/* An open category's rows (design/25-progress-lists-variants.html, B): the picture, the name in
   the game's face and how to get it under it, then how far and where, and a pin to the Map. */
.pg-list { list-style: none; margin: 0; padding: 0; }
.pg-act { margin: var(--sp-4) 0 var(--sp-1); font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.pg-act:first-child { margin-top: var(--sp-2); }
.pg-item {
  display: grid; grid-template-columns: var(--sp-4) 40px minmax(0, 1fr) auto 28px; align-items: center; gap: 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-1); border-bottom: 1px solid color-mix(in oklab, var(--filigree) 10%, transparent); color: var(--ink);
  transition: background-color var(--dur-fast) var(--ease);
}
.pg-item:hover { background: var(--veil); }
.pg-item.is-plain { grid-template-columns: var(--sp-4) minmax(0, 1fr) auto 28px; }
.pg-mark { color: var(--ink-strong); }
.pg-mark .ic { width: var(--icon-sm); height: var(--icon-sm); }
.pg-art { display: grid; place-items: center; width: 40px; height: 40px; }
.pg-art img { max-width: 36px; max-height: 36px; object-fit: contain; }
.pg-item.is-got .pg-art img { opacity: 0.55; }
.pg-main { display: grid; gap: var(--sp-half); min-width: 0; }
.pg-name { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; font-family: var(--font-display); font-size: var(--fs-md); color: var(--ink-strong); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pg-slot { flex: none; width: 7px; height: 7px; transform: rotate(45deg); }
.pg-slot.is-red { background: var(--slot-red); }
.pg-slot.is-blue { background: var(--slot-blue); }
.pg-slot.is-yellow { background: var(--slot-yellow); }
.pg-how { font-size: var(--fs-xs); color: var(--ink-2); }
/* A wish promised (design/81-task-states-variants.html, all A): its mark the game's hollow diamond
   lit, filled once it's ready to hand in; its line what it asks with how much is carried, what's in
   hand dimmed, or the game's words for handing it in. */
.pg-promise { display: block; width: calc(var(--diamond) + var(--sp-half)); height: calc(var(--diamond) + var(--sp-half)); margin: auto; transform: rotate(45deg); border: 1px solid var(--accent); }
.pg-promise.is-ready { background: var(--accent); }
.pg-wish { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-half) var(--sp-3); color: var(--ink); }
.pg-wish-n { white-space: nowrap; color: var(--ink-2); }
.pg-wish-n b { font-family: var(--font-num); font-weight: 600; font-variant-numeric: tabular-nums; color: var(--ink-strong); }
.pg-wish-n i { font-style: normal; font-family: var(--font-num); color: var(--muted); }
.pg-wish-n.is-full, .pg-wish-n.is-full b { color: var(--muted); }
/* What a how line waits for follows its lead; on a phone it folds behind «+n more» (design/41, 2 B). */
.pg-how-btn { display: none; margin-left: var(--sp-1); font-size: var(--fs-xs); }
@media (max-width: 640px) {
  .pg-how-btn { display: inline; }
  .pg-how:not(.is-open) .pg-how-more { display: none; }
  .pg-how.is-open .pg-how-more > span { display: block; color: var(--muted); }
}
.pg-place { display: grid; justify-items: end; gap: var(--sp-half); text-align: right; }
.pg-where { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0 var(--sp-2); font-size: var(--fs-xs); color: var(--muted); }
.pg-where span + span::before { content: '·'; margin-right: var(--sp-2); }
:is(.pg-item, .mp-near-list, .hm-row, .hj-sheet) .pg-onmap { display: grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 1px solid var(--line-ctl); border-radius: 50%; background: none; color: var(--ink-2); cursor: pointer; transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.pg-item .pg-onmap.is-none { border: 0; cursor: default; }
:is(.pg-item, .mp-near-list, .hm-row, .hj-sheet) .pg-onmap .ic { width: 10px; }
:is(.pg-item, .mp-near-list, .hm-row, .hj-sheet) .pg-onmap:is(:hover, :focus-visible) { color: var(--accent); border-color: var(--accent); outline: none; }
.pg > .pg-buy { text-align: center; }
.pg-item.is-got .pg-name { color: var(--ink-2); }
.pg-item.is-later { opacity: 0.55; }
.pg-done { display: flex; align-items: center; gap: var(--sp-2); margin: var(--sp-2) 0 0; font-size: var(--fs-sm); color: var(--ink-2); }
/* The road to Act 2 as the whole route (design/83): the journey's thread down the page, a station
   per step with the journey's diamonds (done lit, yours larger), the thread lit behind the steps
   done; each station a row that opens, the one you're on with its name larger. */
.pg-rt { position: relative; max-width: calc(var(--column) + var(--sp-16)); margin: var(--sp-8) auto 0; padding: 0; list-style: none; }
.pg-rt-st, .pg-rt-end { position: relative; padding-left: calc(var(--sp-8) + var(--sp-4)); }
.pg-rt-st::before {
  content: ''; position: absolute; left: calc(var(--sp-4) - 0.5px); top: calc(var(--sp-6) + var(--sp-3)); bottom: calc(-1 * var(--sp-6));
  width: 1px; background: color-mix(in oklab, var(--silk-rim) 45%, transparent);
}
.pg-rt-st.is-done::before { background: var(--silk); box-shadow: 0 0 6px color-mix(in oklab, var(--silk) 50%, transparent); }
.pg-rt-dia { position: absolute; left: 0; top: var(--sp-3); display: grid; place-items: center; width: var(--sp-8); height: var(--sp-8); }
.pg-rt-dia::before { content: ''; width: var(--sp-3); height: var(--sp-3); transform: rotate(45deg); border: 1px solid var(--silk-rim); background: var(--scene-bg); }
.pg-rt-st.is-done .pg-rt-dia::before { background: var(--silk); border-color: var(--silk); box-shadow: 0 0 8px color-mix(in oklab, var(--silk) 60%, transparent); }
.pg-rt-st.is-cur .pg-rt-dia::before { width: calc(var(--sp-4) + var(--sp-half)); height: calc(var(--sp-4) + var(--sp-half)); border-color: var(--accent); box-shadow: var(--glow); }
.pg-rt-end .pg-rt-dia::before { width: var(--sp-6); height: var(--sp-6); }
.pg-rt-h { margin: 0; font-size: var(--fs-md); font-weight: 400; }
.pg-rt-open {
  display: grid; grid-template-columns: var(--sp-12) minmax(0, 1fr) auto auto; align-items: center; gap: var(--sp-3);
  width: 100%; min-height: calc(var(--sp-12) + var(--sp-4)); padding: var(--sp-2) 0; text-align: left; color: var(--ink);
  border-bottom: 1px solid var(--line-row); transition: color var(--dur) var(--ease);
}
.pg-rt-open:is(:hover, :focus-visible) { color: var(--ink-strong); }
.pg-rt-open:is(:hover, :focus-visible, [aria-expanded="true"]) .disc-ring { color: var(--ink-strong); border-color: var(--accent); background-color: var(--accent-soft); }
.pg-rt-art { width: var(--sp-12); height: var(--sp-12); object-fit: contain; }
.pg-rt-st.is-later .pg-rt-art { opacity: 0.55; filter: grayscale(0.6); }
.pg-rt-t { display: grid; gap: var(--sp-half); min-width: 0; }
.pg-rt-k { font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--label); }
.pg-rt-k em { margin-left: var(--sp-2); padding: 0 var(--sp-2); border: 1px solid var(--accent-line); font-style: normal; letter-spacing: 0; text-transform: none; color: var(--ink-strong); }
.pg-rt-name { font-family: var(--font-display); font-size: var(--fs-md); color: var(--ink-2); text-wrap: balance; }
.pg-rt-st.is-cur .pg-rt-name { font-size: var(--fs-xl); color: var(--ink-strong); }
.pg-rt-sub { font-size: var(--fs-xs); font-style: italic; color: var(--muted); }
.pg-rt-n { font-family: var(--font-num); white-space: nowrap; }
.pg-rt-n .pg-mark { color: var(--good); }
.pg-rt-body { padding: var(--sp-2) 0 var(--sp-6); }
.pg-rt-body .pg-todo { margin-top: var(--sp-4); }
.pg-rt-body .pg-lead { margin: var(--sp-4) 0 var(--sp-2); }
/* A step of several parts (design/84): each part on one line, its picture, its name and its count. */
.pg-rt-parts { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-2) var(--sp-6); margin: 0; padding: 0; list-style: none; }
.pg-rt-parts li { display: flex; align-items: center; gap: var(--sp-2); }
.pg-rt-parts img { width: var(--sp-8); height: var(--sp-8); object-fit: contain; }
.pg-rt-pk { font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--label); }
.pg-rt-parts .is-done .pg-count b { color: var(--good); }
.pg-rt-parts .pg-mark { color: var(--good); }
.pg-rt-end .pg-rt-h { padding: var(--sp-2) 0 var(--sp-2) calc(var(--sp-12) + var(--sp-3)); min-height: calc(var(--sp-12) + var(--sp-4)); display: grid; align-content: center; }
.pg-rt-end .pg-rt-name { font-size: var(--fs-lg); }
@media (max-width: 640px) {
  .pg-rt-st, .pg-rt-end { padding-left: calc(var(--sp-6) + var(--sp-2)); }
  .pg-rt-dia { width: var(--sp-6); height: var(--sp-6); top: var(--sp-4); }
  .pg-rt-st::before { left: calc(var(--sp-3) - 0.5px); }
  .pg-rt-open { grid-template-columns: var(--sp-8) minmax(0, 1fr) auto auto; gap: var(--sp-2); }
  .pg-rt-art { width: var(--sp-8); height: var(--sp-8); }
  .pg-rt-st.is-cur .pg-rt-name { font-size: var(--fs-lg); }
  .pg-rt-end .pg-rt-h { padding-left: calc(var(--sp-8) + var(--sp-2)); }
}
/* The other way in beside the road (design/90c, 1 A): a wider gutter holds two threads, the road's
   (--rt-x1) and the other way's (--rt-x2), which parts at the road's start (.pg-rt-start, its curve
   .pg-rt-fork), runs beside the steps it skips (.is-beside), reaches its framed station (.is-alt,
   set in by --rt-in, an «or» between the threads) and joins the road again (.pg-rt-join). Lit once
   taken, dashed while it's open, dotted while it isn't; the steps it skipped go dim on a dotted
   thread. */
.pg-rt.has-alt { --rt-g: calc(var(--sp-16) + var(--sp-8)); --rt-x1: var(--sp-4); --rt-x2: calc(var(--sp-16) + var(--sp-3)); --rt-in: var(--sp-4);
  --alt-c: color-mix(in oklab, var(--silk-rim) 70%, transparent); --alt-s: dashed; --alt-dash: 4 4; }
.pg-rt.is-alt-lit { --alt-c: var(--silk); --alt-s: solid; --alt-dash: none; }
.pg-rt.is-alt-shut { --alt-c: color-mix(in oklab, var(--silk-rim) 35%, transparent); --alt-s: dotted; --alt-dash: 1 4; }
/* In the guide both roads take the wider gutter, so their steps line up one under the other. */
.pg-road .pg-rt { --rt-g: calc(var(--sp-16) + var(--sp-8)); }
.pg-rt.has-alt > li, .pg-road .pg-rt > li { padding-left: var(--rt-g); }
.pg-rt-start { position: relative; padding-bottom: var(--sp-6); }
.pg-rt-start .pg-rt-h { min-height: var(--sp-12); padding-left: calc(var(--sp-12) + var(--sp-3)); display: grid; align-content: center; }
.pg-rt-start .pg-rt-name { color: var(--muted); }
.pg-rt-start::before { content: ''; position: absolute; left: calc(var(--rt-x1) - 0.5px); top: calc(var(--sp-6) + var(--sp-3)); bottom: calc(-1 * var(--sp-6)); width: 1px; background: color-mix(in oklab, var(--silk-rim) 45%, transparent); }
.pg-rt-start.is-done::before { background: var(--silk); box-shadow: 0 0 6px color-mix(in oklab, var(--silk) 50%, transparent); }
.pg-rt-start.is-done .pg-rt-dia::before { width: var(--sp-2); height: var(--sp-2); background: var(--silk); border-color: var(--silk); }
.pg-rt-start .pg-rt-dia::before { width: var(--sp-2); height: var(--sp-2); }
.pg-rt-fork, .pg-rt-join { position: absolute; left: var(--rt-x1); width: calc(var(--rt-x2) - var(--rt-x1)); pointer-events: none; }
.pg-rt-fork { top: calc(var(--sp-3) + var(--sp-4)); bottom: 0; }
.pg-rt-fork svg, .pg-rt-join svg { display: block; width: 100%; height: 100%; overflow: visible; }
.pg-rt-fork path, .pg-rt-join path { fill: none; stroke: var(--alt-c); stroke-width: 1; stroke-dasharray: var(--alt-dash); }
.pg-rt.is-alt-lit :is(.pg-rt-fork, .pg-rt-join) path { filter: drop-shadow(0 0 3px color-mix(in oklab, var(--silk) 55%, transparent)); }
.pg-rt-st.is-beside::after { content: ''; position: absolute; top: 0; bottom: 0; left: calc(var(--rt-x2) - 0.5px); border-left: 1px var(--alt-s) var(--alt-c); }
/* The other way's station: framed, set in, its diamond on its own thread and the «or» between the two. */
.pg-rt-st.is-alt { padding: var(--sp-3) 0 var(--sp-6) calc(var(--rt-g) + var(--rt-in)); }
.pg-rt-st.is-alt::after { content: ''; position: absolute; top: 0; bottom: var(--sp-4); left: calc(var(--rt-x2) - 0.5px); border-left: 1px var(--alt-s) var(--alt-c); }
.pg-rt-st.is-alt::before { top: 0; }
.pg-rt-st.is-alt .pg-rt-h { position: relative; }
.pg-rt-st.is-alt .pg-rt-open { padding: var(--sp-2) var(--sp-3); border: 1px dashed var(--line-ctl); }
.pg-rt-st.is-alt.is-done .pg-rt-open { border-style: solid; border-color: var(--line); }
.pg-rt-st.is-alt .pg-rt-k { color: var(--ink-strong); }
.pg-rt-st.is-alt .pg-rt-dia { z-index: 1; top: 50%; left: calc(var(--rt-x2) - var(--rt-g) - var(--rt-in) - var(--sp-4)); transform: translateY(-50%); }
.pg-rt-st.is-alt:not(.is-done) .pg-rt-dia::before { border-style: var(--alt-s); border-color: var(--alt-c); }
.pg-rt-or { position: absolute; z-index: 1; top: 50%; left: calc((var(--rt-x1) + var(--rt-x2)) / 2 - var(--rt-g) - var(--rt-in)); transform: translate(-50%, -50%);
  padding: 0 var(--sp-1); border: 1px solid var(--line-ctl); background: var(--scene-bg); font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600;
  letter-spacing: var(--track-label); text-transform: uppercase; line-height: 1.6; color: var(--ink-strong); }
.pg-rt-join { top: calc(100% - var(--sp-4)); height: calc(var(--sp-4) + var(--sp-3) + var(--sp-4)); }
.pg-rt-needs { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-4); margin-top: var(--sp-1); font-size: var(--fs-xs); color: var(--muted); }
.pg-rt-needs > span { display: inline-flex; align-items: center; gap: var(--sp-1); }
.pg-rt-needs img { width: var(--sp-6); height: var(--sp-6); object-fit: contain; }
.pg-rt-needs .is-on { color: var(--ink); }
.pg-rt-needs .is-off img { filter: var(--missing-soft); opacity: var(--missing-soft-opacity); }
.pg-rt-needs .pg-mark { color: var(--good); }
/* A step the other way skipped: dim and crossed out, on a dotted thread. */
.pg-rt-st.is-skipped .pg-rt-name { color: var(--muted); text-decoration: line-through; text-decoration-color: color-mix(in oklab, var(--muted) 60%, transparent); }
.pg-rt-st.is-skipped .pg-rt-art { opacity: 0.4; filter: grayscale(1); }
.pg-rt-st.is-skipped .pg-rt-dia::before { border-style: dotted; border-color: color-mix(in oklab, var(--silk-rim) 40%, transparent); }
.pg-rt.is-alt-lit :is(.pg-rt-start, .pg-rt-st.is-skipped, .pg-rt-st.is-skipped + .is-alt)::before {
  background: repeating-linear-gradient(to bottom, color-mix(in oklab, var(--silk-rim) 30%, transparent) 0 2px, transparent 2px 7px); box-shadow: none; }
.pg-rt-st.is-done + .pg-rt-st.is-alt::before { background: var(--silk); box-shadow: 0 0 6px color-mix(in oklab, var(--silk) 50%, transparent); }
@media (max-width: 640px) {
  .pg-road .pg-rt { --rt-g: calc(var(--sp-16) + var(--sp-3)); }
  .pg-rt.has-alt { --rt-g: calc(var(--sp-16) + var(--sp-3)); --rt-x1: var(--sp-3); --rt-x2: calc(var(--sp-12) + var(--sp-2)); --rt-in: var(--sp-2); }
  .pg-rt-st.is-alt .pg-rt-open { padding: var(--sp-2); }
  .pg-rt-start .pg-rt-h { padding-left: calc(var(--sp-8) + var(--sp-2)); }
}
/* The guide (no save): each road under its title and what it is, as the route. */
.pg-road + .pg-road { margin-top: var(--sp-16); }
.pg-road > .pg-note { max-width: var(--column); margin-inline: auto; text-align: center; }
@media (max-width: 640px) {
  /* The place goes under the name; the pin stays on the right. */
  .pg-item { grid-template-columns: var(--sp-4) 36px minmax(0, 1fr) 28px; }
  .pg-item.is-plain { grid-template-columns: var(--sp-4) minmax(0, 1fr) 28px; }
  .pg-place { grid-column: 3; grid-row: 2; justify-items: start; text-align: left; display: flex; flex-wrap: wrap; gap: 0 var(--sp-2); }
  .pg-item.is-plain .pg-place { grid-column: 2; }
  .pg-todo .pg-item { grid-template-columns: var(--sp-6) 36px minmax(0, 1fr) 28px; }
  .pg-colhead { padding-right: calc(28px + var(--sp-2)); }
  .pg-here { font-size: var(--fs-sm); }
  .pg-where { justify-content: flex-start; }
  .pg-item .pg-onmap { grid-row: 1 / span 2; grid-column: -2; }
  .pg-name { white-space: normal; }
}

/* Since the previous save (js/app-home.js, js/changes.js): opened by what the completion went up
   by, from and to on one gauge, and when the save before was made. */
.hm-gain { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: var(--sp-1) var(--sp-3); padding: var(--sp-1) 0 var(--sp-3); border-bottom: 1px solid var(--line-panel); }
.hm-gain-k { font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--label); }
.hm-gain-v { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); font-weight: 600; color: var(--ink-strong); white-space: nowrap; }
.hm-gain .hm-bar { grid-column: 1 / -1; height: 4px; }
.hm-gain-at { grid-column: 1 / -1; font-size: var(--fs-xs); color: var(--muted); }

/* ── The Inventory (js/app-game.js): the reference implementation's sheet ─────────────────
   Two columns (design/03-redesign.md, step 4): what Hornet is on the left, her equipment on the
   right, and what she carries in a band under both (design/43). The Needle is the focal point; the
   body, rows of the game's own pieces; the Tools sit on their slots; the things are plates. In a
   save the controls are still but at full light (read, not greyed). */
.inv-presets { display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline; gap: var(--sp-1) var(--sp-3); margin: 0; }
.inv-presets .lbl { color: var(--muted); }
/* Free mode (design/42): what the screen is, over «Start from»; on a phone «Start from» takes its
   own line, so its two choices stay together. The older visit's notice, over the body. */
.inv-lead { max-width: 60ch; margin: 0; font-size: var(--fs-sm); color: var(--ink-2); text-wrap: balance; }
@media (max-width: 480px) { .inv-presets > .lbl:first-child { flex-basis: 100%; text-align: center; } }
.inv-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: var(--sp-12); align-items: start; }
.inv-col { display: grid; gap: var(--sp-8); min-width: 0; }
.inv-sec { display: grid; gap: var(--sp-2); align-content: start; min-width: 0; }
.inv-n { margin-left: var(--sp-1); font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); letter-spacing: 0; color: var(--ink-2); }
.inv-n i { font-style: normal; font-weight: 400; color: var(--muted); }
/* A shelf's head (design/43, 2 A): its rule a gauge, lit in bone up to the count; complete, the
   whole rule glows and the head's diamond fills, as the pip of a thing you have. */
.inv-h.is-gauge::after { height: 2px; background: linear-gradient(90deg, var(--accent-line) var(--f), color-mix(in oklab, var(--filigree) 14%, transparent) var(--f)); }
.inv-h.is-full::after { background: var(--accent); box-shadow: var(--glow); }
.inv-h.is-full::before { background: var(--filigree); }
.inv :is(button:disabled) { opacity: 1; cursor: default; }
/* The Needle: five upright, the one carried taller, lit by the spotlight, its damage larger. */
.inv-needles { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); align-items: end; gap: var(--sp-1); }
.inv-np { position: relative; isolation: isolate; display: grid; justify-items: center; gap: var(--sp-2); padding: var(--sp-2) 0 var(--sp-1); }
.inv-np::before { content: ''; position: absolute; z-index: -1; inset: 0 8%; background: var(--halo-plate); opacity: 0; transition: opacity var(--dur) var(--ease); }
.inv-np.is-on::before { opacity: 1; }
.inv-np img { height: calc(var(--sp-16) * 1.5); width: auto; opacity: 0.38; filter: grayscale(0.5); transition: opacity var(--dur) var(--ease), filter var(--dur) var(--ease), height var(--dur) var(--ease); }
.inv-np.is-on img { height: calc(var(--sp-16) * 2); opacity: 1; filter: none; }
.inv-np:is(:hover, :focus-visible):not(:disabled) img { opacity: 0.85; filter: none; }
.inv-np b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-lg); color: var(--muted); padding-bottom: var(--sp-1); border-bottom: 2px solid transparent; }
.inv-np.is-on b { font-size: var(--fs-xl); color: var(--ink-strong); border-bottom-color: var(--accent-line); }
.inv-caption { margin: var(--sp-1) 0 0; text-align: center; font-family: var(--font-display); font-size: var(--fs-md); letter-spacing: var(--track-title); color: var(--ink-strong); }
/* The body: a row per ladder, the figure on the right, the pieces under it. */
.inv-field { display: grid; gap: var(--sp-2); padding: var(--sp-3) 0; }
.inv-field + .inv-field { border-top: 1px solid var(--inv-line); }
.inv-frow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); }
.inv-fname { font-size: var(--fs-md); color: var(--ink); }
.inv-fnote { display: block; font-size: var(--fs-xs); color: var(--muted); }
.inv-fnum { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); font-weight: 600; color: var(--ink-strong); }
.inv-fnum i { font-style: normal; font-size: var(--fs-xs); font-weight: 400; color: var(--muted); }
.inv-pieces { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.inv-pc { display: grid; place-items: center; min-width: var(--ctl-sm); min-height: var(--ctl-sm); padding: 0; transition: transform var(--dur) var(--ease); }
.inv-pc img { height: calc(var(--sp-6) + var(--sp-half)); width: auto; filter: var(--missing-art); transition: filter var(--dur) var(--ease); }
.inv-pc.is-on img { filter: none; }
.inv-pc:is(:hover, :focus-visible):not(:disabled) { transform: translateY(calc(var(--sp-half) * -1)); }
/* The masks: together from the left, as the HUD draws them (design/03-redesign.md, Inventory masks). */
.inv-pieces.is-masks { flex-wrap: nowrap; }
.inv-pieces.is-masks .inv-pc { min-width: 0; flex: none; }
.inv-pieces.is-masks .inv-pc img { height: var(--sp-8); }
/* The silk: a bead per silk strung tight from the left like the masks (the game's diamond), the
   thread drawn bead by bead and lit up to yours (design/03-redesign.md, Inventory silk). */
.inv-pieces.is-spools { flex-wrap: nowrap; gap: 0; }
.inv-pieces.is-spools .inv-pc { position: relative; isolation: isolate; min-width: 0; width: calc(var(--sp-4) + var(--sp-half)); flex: none; }
.inv-pieces.is-spools .inv-pc::before { content: ''; position: absolute; z-index: -1; left: 0; right: 0; top: 50%; height: 1px; background: color-mix(in oklab, var(--silk-rim) 35%, transparent); }
.inv-pieces.is-spools .inv-pc.is-on::before { background: var(--silk); }
.inv-bead { width: 10px; height: 10px; transform: rotate(45deg); border: 1px solid var(--silk-rim); background: var(--scene-bg); transition: background-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.inv-pc.is-on .inv-bead { background: var(--silk); border-color: var(--silk); box-shadow: 0 0 8px color-mix(in oklab, var(--silk) 60%, transparent); }
.inv-pc.is-base .inv-bead { width: 8px; height: 8px; }
@keyframes inv-lit { 0% { filter: brightness(2.2) drop-shadow(0 0 10px var(--focus-glow)); } 100% { filter: none; } }
@media (prefers-reduced-motion: no-preference) {
  .inv .is-lit :is(img, .inv-bead) { animation: inv-lit var(--dur-flash) var(--ease-fade); }
}
/* What's loose of the next mask or spool, under its row (design/43, 1 A): the game's piece, lit
   up to what you hold, the count and how many make a whole one. */
.inv-loose { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-3); font-size: var(--fs-xs); color: var(--muted); }
.inv-loose-art { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--ctl-sm); padding: 0; color: var(--ink-2); }
.inv-loose-art:is(:hover, :focus-visible) .inv-loose-name { color: var(--accent); }
.inv-loose-pcs { display: inline-flex; gap: var(--sp-1); }
.inv-loose-pcs img { height: var(--sp-6); width: auto; filter: drop-shadow(0 0 6px color-mix(in oklab, var(--spotlight) 35%, transparent)); }
.inv-loose-pcs img.is-off { filter: var(--missing-art); }
.inv-loose-art b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink-strong); }
.inv-loose-art b i { font-style: normal; font-weight: 400; color: var(--muted); }
.inv-loose.is-centre { justify-content: center; }
/* A material beside what it feeds (design/43, 1 A), drawn as the Kit's and the Pouch's rows
   (design/49): its picture, its name and under it the count or − N +; the Memory Lockets apart
   from the Crests by a rule. */
.inv-mat { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--sp-1) var(--sp-3); }
.inv-mat.is-rule { margin-top: var(--sp-1); padding-top: var(--sp-3); border-top: 1px solid var(--inv-line); }
.inv-mat .inv-fname { align-self: end; }
.inv-mat > :last-child { align-self: start; justify-self: start; }
.inv-mat-art { grid-row: span 2; display: grid; place-items: center; padding: 0; }
.inv-mat-art img { width: var(--sp-12); height: var(--sp-12); object-fit: contain; filter: drop-shadow(0 0 6px color-mix(in oklab, var(--spotlight) 35%, transparent)); transition: filter var(--dur) var(--ease); }
.inv-mat-art:is(:hover, :focus-visible) img { filter: drop-shadow(0 0 8px color-mix(in oklab, var(--spotlight) 55%, transparent)); }
.inv-mat.is-missing .inv-mat-art img { filter: var(--missing-soft); opacity: var(--missing-soft-opacity); }
.inv-mat-n { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-strong); }
.inv-mat-n i { font-style: normal; font-size: var(--fs-xs); font-weight: 400; color: var(--muted); }
.inv-mat.is-missing .inv-mat-n { color: var(--muted); }
.inv-levels { display: grid; gap: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--inv-line); }
.is-tools .inv-levels { padding-top: var(--sp-1); border-top: 0; }
.inv-level { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--sp-1) var(--sp-3); }
.inv-level img { grid-row: span 2; width: var(--sp-12); height: var(--sp-12); object-fit: contain; }
.inv-level img.is-missing { filter: var(--missing-soft); opacity: var(--missing-soft-opacity); }
.inv-level .seg { gap: var(--sp-1); flex-wrap: nowrap; }
/* Plates: the art, the name, what it gives. */
.inv-plates { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--sp-3) var(--sp-2); }
.inv-plate { display: grid; justify-items: center; align-content: start; gap: var(--sp-1); width: 100%; padding: 0; text-align: center; }
.inv-plate-art { position: relative; isolation: isolate; display: grid; place-items: center; width: 100%; height: calc(var(--sp-12) + var(--sp-2)); }
.inv-plate-art::before { content: ''; position: absolute; z-index: -1; inset: -8% 12%; background: var(--halo-plate); opacity: 0; transition: opacity var(--dur) var(--ease); }
.inv-plate:is(:hover, :focus-visible, .is-picked) .inv-plate-art::before { opacity: 1; }
.inv-plate img { max-height: calc(var(--sp-12) - var(--sp-1)); max-width: calc(var(--sp-16) + var(--sp-2)); object-fit: contain; }
.inv-plate.is-missing img { filter: var(--missing-soft); opacity: var(--missing-soft-opacity); }
/* Had or missing, said twice (design/08-missing-variants.html, A + C): what you have stands in the
   spotlight's glow, and under every picture the game's diamond, lit had, hollow missing. */
.inv-plate-art { order: 0; }
.inv-plate::after {
  content: ''; order: 1; width: var(--diamond); height: var(--diamond); transform: rotate(45deg);
  border: 1px solid var(--accent-line); transition: background-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.inv-plate:not(.is-missing)::after { background: var(--accent); border-color: var(--accent); box-shadow: var(--glow); }
.inv-plate-name { order: 2; }
.inv-plate-v { order: 3; }
.inv-plate:not(.is-missing) .inv-plate-art::before { opacity: 0.8; }
.inv-plate:not(.is-missing) img { filter: drop-shadow(0 0 6px color-mix(in oklab, var(--spotlight) 35%, transparent)); }
.inv-plate-name { font-size: var(--fs-xs); line-height: 1.25; color: var(--ink-2); }
.inv-plate:not(.is-missing) .inv-plate-name { color: var(--ink); }
.inv-plate-v { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); font-weight: 700; color: var(--ink-strong); }
.inv-plate-v.is-none { font-family: var(--font-body); font-size: var(--fs-xs); font-weight: 400; color: var(--muted); }
/* The things' plates (design/43, 5 A): the same plate, a step narrower on the purse's, the Relics'
   and the Mementos' shelves; in Free mode what comes in numbers keeps its − N + under the name,
   so only the picture is a button. */
.inv-plates { list-style: none; margin: 0; padding: 0; }
.inv-plates.is-sm { grid-template-columns: repeat(auto-fill, minmax(calc(var(--sp-16) + var(--sp-6)), 1fr)); }
button.inv-plate-art { padding: 0; }
.inv-plate-v .inv-step { justify-content: center; }
/* The grids: one fixed square per cell, so every row lines up. */
.inv-sub { display: flex; align-items: center; gap: var(--sp-2); margin: var(--sp-2) 0 0; font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--label); }
.inv-sub::before { content: ''; width: var(--sp-2); height: var(--sp-2); transform: rotate(45deg); background: var(--c); }
.inv-sub b { color: var(--ink-2); }
.inv-sub i { font-style: normal; font-weight: 400; color: var(--muted); }
/* The two Tools a wish lends (design/51, A): among the reds, their slot dashed, and a line under
   the row with a dashed diamond of the colour saying why they're out of the count. */
.inv-lent { display: flex; align-items: center; gap: var(--sp-2); margin: calc(var(--sp-2) * -1) 0 0; font-size: var(--fs-xs); color: var(--muted); }
.inv-lent::before { content: ''; flex: none; width: var(--sp-3); height: var(--sp-3); transform: rotate(45deg); border: 1px dashed var(--c); }
.inv-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, calc(var(--sp-12) + var(--sp-2))); gap: var(--sp-2); }
.inv-grid > li { display: grid; justify-items: center; gap: var(--sp-half); }
.inv-cell {
  position: relative; display: grid; place-items: center; width: calc(var(--sp-12) + var(--sp-2)); height: calc(var(--sp-12) + var(--sp-2)); padding: 0;
  border: 1px solid transparent; background: none; cursor: pointer; transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.inv-cell img { position: relative; z-index: 1; width: var(--sp-8); height: var(--sp-8); object-fit: contain; transition: filter var(--dur) var(--ease); }
.inv-cell.is-missing img { filter: var(--missing-soft); opacity: var(--missing-soft-opacity); }
.inv-cell:not(.is-missing) img { filter: drop-shadow(0 0 6px color-mix(in oklab, var(--spotlight) 40%, transparent)); }
.inv-cell::after {
  content: ''; position: absolute; left: 50%; bottom: calc(var(--sp-2) * -1); width: var(--diamond); height: var(--diamond);
  transform: translateX(-50%) rotate(45deg); border: 1px solid var(--accent-line);
}
.inv-cell:not(.is-missing)::after { background: var(--accent); border-color: var(--accent); box-shadow: var(--glow); }
.inv-grid > li { padding-bottom: var(--sp-3); row-gap: var(--sp-4); }
/* Under the pointer only the picture lights up: no square behind it (the reviewer). The keyboard keeps its focus ring. */
.inv-cell:is(:hover, :focus-visible) img { filter: drop-shadow(0 0 6px color-mix(in oklab, var(--spotlight) 45%, transparent)); }
.inv-cell.is-missing:is(:hover, :focus-visible) img { filter: grayscale(1) brightness(0.8); }
/* The one whose text is open needs no frame: the text under its shelf says it (the reviewer: the white square was too much). */
/* A Tool on its slot: the Crest screen's diamond in the slot's colour. Lacking it, the slot hollow
   in its own colour and the picture a faint grey (design/43, 3 A; it was the game's grey locked
   slot, design/09, B, which on a New Game made the three colours one grey wall). */
.inv-cell.is-slot::before {
  content: ''; position: absolute; inset: calc(var(--sp-2) + var(--sp-half)); transform: rotate(45deg);
  border: 1.5px solid var(--c); background: color-mix(in oklab, var(--c) 8%, var(--scene-bg));
}
.inv-cell.is-slot.is-missing::before { border: 1px solid color-mix(in oklab, var(--c) 55%, transparent); background: var(--scene-bg); }
.inv-cell.is-slot.is-missing img { filter: grayscale(1) brightness(0.85); opacity: 0.42; }
.is-lent > .inv-cell.is-slot::before { border-style: dashed; }
/* A Tool and its upgrade on one slot (design/43, 4 A): two pips under it, had and upgraded. */
.inv-grid > li.is-twin { position: relative; }
.is-twin .inv-cell::after { content: none; }
.inv-pips { position: absolute; left: 50%; top: calc(var(--sp-12) + var(--sp-2) + var(--sp-1)); transform: translateX(-50%); display: flex; gap: var(--sp-1); }
.inv-pips i { width: var(--diamond); height: var(--diamond); transform: rotate(45deg); border: 1px solid var(--accent-line); }
.inv-pips i.is-on { background: var(--accent); border-color: var(--accent); box-shadow: var(--glow); }
/* Free mode's marks: All · None on a shelf's head, and − N + under a counted item. */
.inv-all { display: inline-flex; align-items: baseline; gap: var(--sp-2); margin-left: auto; padding-left: var(--sp-3); order: 3; font-family: var(--font-body); font-size: var(--fs-xs); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted); }
.inv-all .text-btn { font-size: var(--fs-xs); min-height: 0; }
.inv-h:has(.inv-all)::after { order: 2; }
.inv-step { display: inline-flex; align-items: center; gap: var(--sp-half); }
.inv-step b { min-width: var(--sp-6); text-align: center; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-sm); color: var(--ink-strong); }
.inv-step b i { font-style: normal; font-size: var(--fs-xs); font-weight: 400; color: var(--muted); }
.inv-step .icon-btn { width: var(--sp-8); height: var(--sp-8); }
/* What Hornet carries, under both columns and across the width (design/43, 1 A): the purse and
   the items, then the Relics and the Mementos. */
.inv-bag { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-8) var(--sp-12); align-items: start; margin-top: var(--sp-12); padding-top: var(--sp-8); border-top: 1px solid var(--inv-line); }
/* The purse (design/29-inventory-variants.html, A): each currency a figure with its picture, a bar
   for the Shell Shards' cap, then what breaks into it and the sum. */
.inv-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-6); }
.inv-cur { display: grid; gap: var(--sp-3); align-content: start; min-width: 0; }
.inv-fig { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 0 var(--sp-3); }
.inv-fig-art { grid-row: span 3; display: grid; place-items: center; padding: 0; }
.inv-fig-art img { width: calc(var(--sp-12) + var(--sp-2)); height: calc(var(--sp-12) + var(--sp-2)); object-fit: contain; filter: drop-shadow(0 0 6px color-mix(in oklab, var(--spotlight) 35%, transparent)); transition: filter var(--dur) var(--ease); }
.inv-fig-art:is(:hover, :focus-visible, .is-picked) img { filter: drop-shadow(0 0 8px color-mix(in oklab, var(--spotlight) 55%, transparent)); }
.inv-fig-name { font-size: var(--fs-xs); color: var(--ink-2); }
.inv-fig-num { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xl); font-weight: 600; line-height: 1.1; color: var(--ink-strong); }
.inv-fig-num i { font-style: normal; font-size: var(--fs-sm); font-weight: 400; color: var(--muted); }
.inv-bar { height: 3px; margin: var(--sp-1) 0; background: var(--inv-line); }
.inv-bar::after { content: ''; display: block; height: 100%; width: var(--f); background: var(--accent-line); }
.inv-in { width: 9ch; padding: var(--sp-1) var(--sp-2); background: transparent; border: 1px solid var(--line-ctl); color: var(--ink-strong); font: inherit; font-family: var(--font-num); font-size: var(--fs-xl); font-variant-numeric: tabular-nums; }
.inv-in:focus-visible { outline: 1px solid var(--accent-line); border-color: var(--accent-line); }
.inv-broken { margin: 0; font-size: var(--fs-xs); color: var(--muted); }
/* What's open under a shelf: the accent's bar on the left over the chosen veil (the open row). */
.inv-open { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-1) var(--sp-6); align-items: center; margin-top: var(--sp-2); padding: var(--sp-4) var(--sp-6); border-left: 2px solid var(--accent); background: var(--picked); }
.inv-open img { grid-row: 1 / span 3; width: calc(var(--sp-16) + var(--sp-2)); height: calc(var(--sp-16) + var(--sp-2)); object-fit: contain; }
.inv-open img.is-missing { filter: var(--missing-soft); opacity: var(--missing-soft-opacity); }
.inv-name { margin: 0; font-family: var(--font-display); font-size: var(--fs-lg); font-weight: 600; letter-spacing: var(--track-title); color: var(--ink-strong); text-wrap: balance; }
.inv-not { margin: 0; font-family: var(--font-num); font-size: var(--fs-2xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--muted); }
.inv-desc { margin: 0; font-size: var(--fs-sm); line-height: 1.55; color: var(--ink-2); text-wrap: pretty; }
@media (max-width: 899px) {
  .inv-body, .inv-bag { grid-template-columns: minmax(0, 1fr); gap: var(--sp-8); }
  .inv-bag { margin-top: var(--sp-8); }
}
@media (max-width: 599px) {
  .inv-two { grid-template-columns: minmax(0, 1fr); }
  .inv-open { grid-template-columns: minmax(0, 1fr); padding: var(--sp-3) var(--sp-4); }
  .inv-open img { grid-row: auto; width: var(--sp-12); height: var(--sp-12); }
  .inv-pieces.is-spools .inv-bead { width: 8px; height: 8px; }
}

/* ── The Crest screen (js/app-tools.js) ─────────────────────────────────────
   design/18-crest-variants.html, A (the game's pane): the figures as big numbers, the levels as
   − N + steppers; then the Crest drawn as the game draws it, its slots where the game puts them
   (js/crest-slots.js: units from its centre, --k px to the unit), each slot the game's frame
   (inline in that file, --g, as a mask won't take a file over file://) tinted by its colour; beside it the Tool list by colour and the one pointed at,
   described. Figures in --font-num. */
/* The figures as the game's HUD (design/19-crest-figures-variants.html, A): the Needle at its
   level with the slash large beside it; damage a second and the Needle Strike in the middle;
   Hornet as the HUD shows her on the right, her masks drawn and the spool with its silk. */
.ct-band { display: flex; justify-content: center; align-items: flex-end; margin-top: var(--sp-4); }
/* One centred block, its three parts on one baseline, a thin filigree rule between them. */
.ct-band > * { padding: 0 var(--sp-8); }
.ct-band > * + * { border-left: 1px solid color-mix(in oklab, var(--filigree) 22%, transparent); }
.ct-slash { display: flex; align-items: flex-end; gap: var(--sp-3); }
.ct-slash img { height: calc(var(--sp-16) + var(--sp-8)); width: auto; filter: drop-shadow(0 0 10px color-mix(in oklab, var(--spotlight) 30%, transparent)); }
.ct-slash b { display: block; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: calc(var(--fs-2xl) * 1.7); line-height: 1; color: var(--ink-strong); }
.ct-fig2 span, .ct-slash span { display: block; margin-top: var(--sp-1); font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.ct-mid { display: flex; align-items: flex-end; gap: var(--sp-8); }
.ct-fig2 b { display: block; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xl); line-height: 1.1; color: var(--ink-strong); }
/* Hornet as the HUD draws her: the masks in a row, the spool under them with its silk. */
.ct-hud { display: grid; justify-items: start; gap: var(--sp-2); }
.ct-masks { display: flex; gap: var(--sp-1); }
.ct-masks img { height: calc(var(--sp-6) + var(--sp-1)); width: auto; }
.ct-spool { display: flex; align-items: center; gap: var(--sp-2); }
.ct-spool img { height: calc(var(--sp-4) + var(--sp-1)); width: auto; }
.ct-spool b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-lg); line-height: 1; color: var(--ink-strong); }
.ct-heal { font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
/* The levels as the game's upgrade pips, on one centred row with the moment's switches. */
.ct-levels { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--sp-3) var(--sp-6); margin-top: var(--sp-6); padding: var(--sp-4) 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.ct-lv { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-xs); color: var(--ink-2); }
.ct-lv img { width: var(--sp-6); height: var(--sp-6); object-fit: contain; }
.ct-lv .icon-btn { width: var(--ctl-sm); height: var(--ctl-sm); }
.ct-lv .icon-btn:disabled { visibility: hidden; }
.ct-pips { display: inline-flex; align-items: center; gap: var(--sp-1); }
.ct-pip { display: block; width: calc(var(--diamond) + 3px); height: calc(var(--diamond) + 3px); padding: 0; transform: rotate(45deg); border: 1px solid color-mix(in oklab, var(--accent-line) 80%, transparent); background: none; }
button.ct-pip { cursor: pointer; }
button.ct-pip:is(:hover, :focus-visible) { border-color: var(--accent); outline: none; }
.ct-pip.is-on { background: var(--accent); border-color: var(--accent); box-shadow: var(--glow); }
.ct-checks { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }
.ct-body { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-8); align-items: start; margin-top: var(--sp-8); }
.ct-side { display: grid; justify-items: center; gap: var(--sp-3); }
/* The Crest (design/43, every piece the game's sprite at its own size, js/crest-slots.js): the
   Vesticrest's column beside it, its name under it alone. */
.ct-crest { --k: var(--crest-k); display: grid; justify-items: center; align-items: center; gap: var(--sp-3) var(--sp-2); }
.ct-crest.has-float { grid-template-columns: auto auto; }
.ct-crest.has-float > .ct-float { grid-column: 1; grid-row: 1; }
.ct-crest.has-float > :not(.ct-float) { grid-column: 2; }
/* The box is centred on the Crest's centre, where the slots are measured from, and reaches every
   Crest's art and its slots full, a ring 1.43 units across (±3.4 units across, ±3.55 up and down).
   The art's own centre sits off it by (--ax, --ay): the game trims it in its atlas. It's drawn
   half grey with no glow, as the pane tints it while you browse it (--crest-tint). */
.ct-crest-art { position: relative; width: calc(var(--k) * 6.8); height: calc(var(--k) * 7.1); }
.ct-crest-art > img {
  position: absolute; left: calc(50% + var(--ax) * var(--k)); top: calc(50% - var(--ay) * var(--k));
  width: calc(var(--k) * var(--w)); height: calc(var(--k) * var(--h)); transform: translate(-50%, -50%);
  filter: var(--crest-tint);
}
/* A slot: a ring's room (1.43 units) centred on its place; inside, the game's sprites (.ct-g, each at
   its size and centre, white tinted through a mask) and a worn one's picture at its own size and
   centre (--iw, --ih, --ix, --iy: SS.crestIcons), 1.04 to 1.58 units, about the ring's, as in the game. */
.ct-slot {
  --c: var(--slot-red);
  position: absolute; left: calc(50% + var(--x) * var(--k)); top: calc(50% - var(--y) * var(--k)); transform: translate(-50%, -50%);
  width: calc(var(--k) * 1.43); height: calc(var(--k) * 1.43); padding: 0; border: 0; background: none;
}
.ct-slot.is-blue { --c: var(--slot-blue); }
.ct-slot.is-yellow { --c: var(--slot-yellow); }
.ct-slot.is-skill { --c: var(--slot-skill); }
.ct-g {
  position: absolute; left: calc(50% + var(--gx) * var(--k)); top: calc(50% - var(--gy) * var(--k));
  width: calc(var(--gw) * var(--k)); height: calc(var(--gh) * var(--k)); transform: translate(-50%, -50%); pointer-events: none;
  background: var(--c); -webkit-mask: var(--g) center / contain no-repeat; mask: var(--g) center / contain no-repeat;
}
/* Locked: the lock ring in the slot colour's grey, the slot at half opacity (InventoryToolCrestSlot). */
.ct-slot.is-locked .ct-g { background: var(--slot-lock); opacity: var(--slot-lock-opacity); }
/* A worn Tool sits clean in its ring, with no glow behind it, as the game's pane draws it. */
.ct-slot.is-full { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.ct-slot img {
  position: absolute; left: calc(50% + var(--ix) * var(--k)); top: calc(50% - var(--iy) * var(--k));
  width: calc(var(--k) * var(--iw)); height: calc(var(--k) * var(--ih)); max-width: none; object-fit: contain;
  transform: translate(-50%, -50%); transition: transform var(--dur) var(--ease);
}
.ct-slot.is-full:is(:hover, :focus-visible) { outline: none; }
.ct-slot.is-full:is(:hover, :focus-visible) img { transform: translate(-50%, -50%) scale(1.1); }
/* The Vesticrest's column (SS.crestFloat, the game's Floating Slots): its slots and white brackets
   at the prefab's heights, in the Crest's units. */
.ct-float { position: relative; width: calc(var(--k) * 1.85); height: calc(var(--k) * var(--fh)); }
.ct-float > .ct-slot, .ct-bracket { left: 50%; top: calc((var(--top) - var(--y)) * var(--k)); }
.ct-bracket { position: absolute; width: 0; height: 0; }
.ct-bracket .ct-g { background: var(--filigree); }
.ct-curse { max-width: calc(var(--k) * 6.8); margin: 0; text-align: center; font-size: var(--fs-sm); color: var(--muted); text-wrap: pretty; }
.ct-crest-name { display: flex; align-items: center; gap: var(--sp-4); text-align: center; }
.ct-crest-name h3 { margin: 0; font-family: var(--font-display); font-size: var(--fs-xl); font-weight: 400; letter-spacing: var(--track-title); color: var(--ink-strong); }
.ct-stage { font-size: var(--fs-xs); color: var(--muted); }
.ct-arrow { font-size: var(--fs-lg); }
/* The Tool list: a row per colour and the Silk Skills, each on its slot as the Inventory draws it
   (.inv-cell.is-slot: the diamond in the slot's colour), only the ones you have, as the game
   lists them. Only the diamond here, no pip under it (the reviewer): a worn one's diamond is filled
   with its colour and lit. */
.ct-pick { display: grid; gap: var(--sp-4); min-width: 0; }
.ct-shelf { display: grid; gap: var(--sp-2); }
.ct-tools > li { padding-bottom: 0; }
.ct-tools .inv-cell::after { content: none; }
.ct-tools .inv-cell.is-on::before { border-width: 2px; background: color-mix(in oklab, var(--c) 32%, var(--scene-bg)); box-shadow: 0 0 12px color-mix(in oklab, var(--c) 45%, transparent); }
.ct-tools .inv-cell:focus { outline: none; }
.ct-tools .inv-cell:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
/* The one pointed at: name in its colour's rule, the game's words, its numbers. */
.ct-desc { display: grid; gap: var(--sp-2); min-height: calc(var(--sp-16) * 1.5); padding-top: var(--sp-4); border-top: 1px solid var(--line); }
.ct-desc-name { margin: 0; font-family: var(--font-display); font-size: var(--fs-lg); font-weight: 400; color: var(--ink-strong); }
.ct-desc-text { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); text-wrap: pretty; }
.ct-desc-nums { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-1) var(--sp-3); margin: 0; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--muted); }
.ct-desc-nums b { font-size: var(--fs-xl); color: var(--ink-strong); }
.ct-desc-none { margin: 0; font-size: var(--fs-sm); color: var(--muted); }
.ct-figs { display: grid; gap: var(--sp-6); position: sticky; top: calc(var(--sticky-h) + var(--sp-4)); }   /* Combat's figures (js/app-fight.js) */
.ct-block { display: grid; gap: var(--sp-2); }
/* A block's title (design/03-redesign.md, step 2): the screen title's mark one level down, a
   hollow diamond in front and a rule that fades out to the right. */
.ct-h {
  display: flex; align-items: center; gap: var(--sp-2); margin: 0;
  font-family: var(--font-display); font-size: var(--fs-sm); font-weight: 600; letter-spacing: var(--track-title); color: var(--filigree);
}
.ct-h::before { content: ''; flex: none; width: var(--diamond); height: var(--diamond); border: 1px solid var(--filigree); transform: rotate(45deg); }
.ct-h::after { content: ''; flex: 1 1 var(--sp-8); height: 1px; background: linear-gradient(90deg, color-mix(in oklab, var(--filigree) 40%, transparent), transparent); }
.ct-fig { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; column-gap: var(--sp-3); }
.ct-fig-k { font-size: var(--fs-sm); color: var(--ink); }
.ct-fig-v b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); color: var(--ink-strong); }
.ct-fig-sub { grid-column: 1 / -1; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--muted); }
.ct-mods { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); font-size: var(--fs-xs); color: var(--ink-2); }
.ct-mods li::before { content: '+ '; color: var(--muted); }
.ct-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
.ct-list li { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; grid-template-areas: "i n v" "i s v"; align-items: center; column-gap: var(--sp-2); color: var(--ink-2); }
.ct-list li.is-on { color: var(--ink-strong); }
.ct-list img { grid-area: i; width: 28px; height: 28px; object-fit: contain; }
.ct-list-name { grid-area: n; font-size: var(--fs-sm); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ct-list-sub { grid-area: s; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xs); color: var(--muted); }
.ct-list b { grid-area: v; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); color: var(--ink-strong); }
.ct-list li:not(.is-on) b { color: var(--ink-2); }
@media (max-width: 1100px) {
  .ct-body { grid-template-columns: minmax(0, 1fr); justify-items: center; }
  .ct-pick { width: 100%; }
  .ct-figs { grid-column: 1 / -1; position: static; grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr)); }
}
/* Between the phone and a wide window (design/52, A) the band's one row doesn't fit: the HUD goes
   under the Needle and the figures, centred after a rule; narrower still, the figures too. The
   Crest takes its small size there, so the Vesticrest's column beside it stays in the frame. */
@media (min-width: 600px) and (max-width: 1059px) {
  .ct-band { flex-wrap: wrap; row-gap: var(--sp-4); }
  .ct-hud { flex-basis: 100%; justify-items: center; padding-top: var(--sp-4); border-left: 0; border-top: 1px solid color-mix(in oklab, var(--filigree) 22%, transparent); }
}
@media (min-width: 600px) and (max-width: 719px) {
  .ct-band > * { padding-inline: var(--sp-4); }
  .ct-mid { flex-basis: 100%; justify-content: center; gap: var(--sp-6); padding-top: var(--sp-4); border-left: 0; border-top: 1px solid color-mix(in oklab, var(--filigree) 22%, transparent); }
  .ct-crest.has-float { --k: var(--crest-k-sm); }
}
@media (max-width: 599px) {
  .ct-crest { --k: var(--crest-k-sm); }
  .ct-crest.has-float { --k: var(--crest-k-xs); }
  .ct-band { flex-direction: column; align-items: center; gap: var(--sp-4); text-align: center; }
  .ct-band > * { padding: 0; }
  .ct-band > * + * { border-left: 0; padding-top: var(--sp-4); border-top: 1px solid color-mix(in oklab, var(--filigree) 22%, transparent); }
  .ct-mid { justify-content: center; gap: var(--sp-6); }
  .ct-hud { justify-items: center; }
  .ct-masks { justify-content: center; }
  .ct-lv { width: 100%; }
  .ct-lv-k { margin-right: auto; }
}
/* The figures in one strip on the window's bottom edge, only where the full list has gone below
   the controls: the Inventory's detail bar, the same frame. */
.ct-sum { display: none; }
@media (max-width: 1100px) {
  .ct-sum {
    position: sticky; bottom: 0; z-index: 2; display: grid; grid-auto-flow: column; grid-auto-columns: auto; justify-content: space-between; gap: var(--sp-2);
    margin-top: var(--sp-6); padding: var(--sp-2) var(--sp-3); border-top: 1px solid var(--filigree);
    background: var(--scene-bg); box-shadow: 0 -12px 24px var(--scene-bg);
  }
  .ct-sum-cell { display: grid; justify-items: center; align-content: start; gap: var(--sp-half); min-width: 0; text-align: center; }
  .ct-sum-cell img { width: 20px; height: 20px; object-fit: contain; }
  .ct-sum-k { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-xs); color: var(--label); }
  .ct-sum-cell b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-lg); color: var(--ink-strong); }
  .ct-sum-cell img + .ct-sum-k { display: none; }
}
/* Free mode's «Try with» (design/42, 2 A), and «What I have» with no Tool marked. */
.ct-try { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center; gap: var(--sp-1) var(--sp-3); }
.ct-try .lbl { color: var(--muted); }
.ct-none { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }

/* ── The Hunter's Journal (js/app-journal.js) ─────────────────────────────
   design/16-journal-top-variants.html, B (next hunts): on top, the Memento as a large ring (the
   complete in bone, the seen and not complete dimmer) beside the four closest missing as cards;
   under it, two small toggles and the grid full width, by area under the Crest screen's heads
   (.ct-h) or in the Journal's own order. Each portrait sits in a ring that fills with its kills
   (design/15-journal-variants.html, A), lit bone once complete; one not seen is in shadow in a
   dashed ring. A tap opens the entry as the game's page (.hj-sheet, below), with the Hunter's note in the hand the site keeps
   for it (--font-hand). An optional entry carries a hollow diamond. */
.hj-hero { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-8); align-items: center; margin-top: var(--sp-4); padding-bottom: var(--sp-6); border-bottom: 1px solid var(--line); }
.hj-memento { position: relative; display: grid; place-items: center; width: 150px; aspect-ratio: 1; border-radius: 50%; }
.hj-memento::before {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(var(--accent) calc(var(--d) * 360deg), color-mix(in oklab, var(--silk-rim) 45%, transparent) 0 calc(var(--s) * 360deg), var(--ring-track) 0);
  -webkit-mask: radial-gradient(closest-side, transparent calc(100% - var(--ring) * 2), var(--ring-mask) calc(100% - var(--ring) * 2 + 1px));
  mask: radial-gradient(closest-side, transparent calc(100% - var(--ring) * 2), var(--ring-mask) calc(100% - var(--ring) * 2 + 1px));
}
.hj-memento div { display: grid; justify-items: center; line-height: 1.15; font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.hj-memento b { font-size: var(--fs-2xl); color: var(--ink-strong); }
.hj-memento .u { font-style: normal; font-size: var(--fs-sm); color: var(--muted); }
.hj-memento span { margin-top: var(--sp-1); font-size: var(--fs-2xs); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.hj-hero:not(:has(.hj-hunts)) { grid-template-columns: minmax(0, 1fr); justify-items: center; }
.hj-hunts .ct-h { margin-top: 0; }
.hj-hunt-row { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-3); }
.hj-hunt {
  display: grid; justify-items: center; align-content: start; gap: var(--sp-1); width: 100%; height: 100%; padding: var(--sp-3) var(--sp-2);
  border: 1px solid var(--line); text-align: center; cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.hj-hunt .hj-ring { width: 72px; margin-bottom: var(--sp-1); }
.hj-hunt b { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-sm); color: var(--ink-strong); }
.hj-hunt-left { font-size: var(--fs-xs); color: var(--muted); }
.hj-hunt:focus { outline: none; }
.hj-hunt:is(:hover, :focus-visible), .hj-hunt.is-picked { border-color: var(--accent-line); background: var(--veil); }
.hj-tools { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--sp-3) var(--sp-8); margin: var(--sp-4) 0 var(--sp-2); }
.hj-find { flex: 1 1 16rem; max-width: 24rem; margin-right: auto; }
.hj-find input { text-overflow: ellipsis; }   /* the Spanish placeholder is longer than a phone's field: ends in an ellipsis, not cut mid-word */
/* What it shows and in what order: each choose-one in words under its label (design/03-redesign.md,
   step 7, fourth round), as the Crest screen's «Try with». */
.hj-ctl { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2) var(--sp-8); }
.hj-pair { display: flex; align-items: baseline; gap: var(--sp-3); }
.hj-pair .lbl { color: var(--muted); }
.hj-shelf { display: grid; gap: var(--sp-6); min-width: 0; }
.hj-area-group { display: grid; gap: var(--sp-3); }
.hj-area-n { margin-left: auto; order: 3; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); letter-spacing: 0; color: var(--muted); }
.hj-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: var(--sp-2); }
.hj-cell {
  position: relative; display: grid; place-items: center; width: 100%; padding: var(--sp-1);
  border: 0; background: none; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.hj-cell:focus { outline: none; }
.hj-ring { position: relative; display: grid; place-items: center; width: 60px; aspect-ratio: 1; border-radius: 50%; transition: transform var(--dur) var(--ease); }
.hj-ring::before {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(var(--silk) calc(var(--f) * 360deg), var(--ring-track) 0);
  -webkit-mask: radial-gradient(closest-side, transparent calc(100% - var(--ring)), var(--ring-mask) calc(100% - var(--ring) + 1px));
  mask: radial-gradient(closest-side, transparent calc(100% - var(--ring)), var(--ring-mask) calc(100% - var(--ring) + 1px));
}
.hj-ring img { width: 80%; height: 80%; object-fit: contain; transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease); }
.hj-ring.is-done::before { background: var(--accent); box-shadow: var(--glow); }
.hj-ring.is-seen img { opacity: 0.7; filter: grayscale(0.5); }
.hj-ring.is-unseen::before { background: none; border: 1px dashed var(--line-ctl); -webkit-mask: none; mask: none; }
.hj-ring.is-unseen img { filter: var(--unseen-art); opacity: 0.9; }
@media (hover: hover) {
  .hj-cell:hover .hj-ring { transform: scale(1.06); }
  .hj-cell:hover .hj-ring:not(.is-unseen) img { opacity: 1; filter: none; }
}
.hj-cell:focus-visible .hj-ring, .hj-cell.is-picked .hj-ring { outline: 1px solid var(--accent); outline-offset: var(--sp-1); }
.hj-opt {
  position: absolute; top: var(--sp-1); right: var(--sp-1); width: var(--diamond); height: var(--diamond);
  border: 1px solid var(--ink-2); transform: rotate(45deg);
}
.hj-entry { display: grid; justify-items: center; gap: var(--sp-3); text-align: center; }
.hj-area { margin: 0; font-size: var(--fs-xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--muted); }
.hj-kills { margin: 0; color: var(--ink-2); font-size: var(--fs-sm); }
.hj-kills b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-lg); color: var(--ink-strong); }
.hj-kills .u { font-family: var(--font-num); font-style: normal; color: var(--muted); }
.hj-note {
  margin: var(--sp-2) 0 0; padding-top: var(--sp-3); border-top: 1px solid var(--line);
  font-family: var(--font-hand); font-size: var(--fs-md); line-height: 1.45; color: var(--ink); text-wrap: pretty;
}
.hj-by { margin: 0; font-size: var(--fs-xs); color: var(--muted); }
/* The entry, on a tap, as the game's page (design/17-journal-entry-variants.html, A): a modal
   <dialog> (in the top layer, over the header), wide and centred, the whole drawing on the pane's
   light at the size the game draws it (its pixels as they are, only capped by the box), beside it
   the name under a filigree rule, the kills, the description and the notes under the Hunter's
   symbol. On a phone, from the foot, the drawing above the text. */
.hj-sheet {
  position: fixed; inset: 0; margin: auto; max-width: none; max-height: calc(100vh - var(--sp-8));
  width: min(900px, calc(100vw - var(--sp-8))); height: fit-content; overflow: hidden auto; scrollbar-width: thin;
  display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: var(--sp-8); align-items: center;
  padding: var(--sp-12) var(--sp-8); color: var(--ink); background: var(--scene-floor); border: 1px solid var(--line-ctl); box-shadow: var(--shadow-md);
  animation: hj-sheet-in var(--dur) var(--ease) both;
}
.hj-sheet:not([open]) { display: none; }
.hj-sheet::backdrop { background: color-mix(in oklab, var(--scene-floor) 60%, transparent); }
.hj-sheet:focus { outline: none; }
.hj-sheet-x { position: absolute; top: var(--sp-2); right: var(--sp-2); }
.hj-art { position: relative; display: grid; place-items: center; min-height: 380px; }
.hj-art::before { content: ''; position: absolute; inset: -6%; background: var(--journal-light); pointer-events: none; }
.hj-art img { position: relative; max-width: 100%; max-height: 440px; }
.hj-art.is-unseen img { filter: var(--unseen-art); }
.hj-name { margin: 0; font-family: var(--font-display); font-weight: 400; font-size: var(--fs-xl); letter-spacing: var(--track-title); color: var(--ink-strong); text-wrap: balance; }
.hj-name::after { content: ''; display: block; width: 60%; height: 1px; margin: var(--sp-3) auto 0; background: linear-gradient(90deg, transparent, color-mix(in oklab, var(--filigree) 60%, transparent), transparent); }
.hj-sym { display: block; width: 150px; margin: var(--sp-2) auto 0; opacity: 0.8; }
.hj-sym.is-locked { opacity: 0.25; }
.hj-sheet .hj-note { margin: 0; padding: 0; border: 0; }
.hj-later { margin: 0; font-size: var(--fs-xs); font-style: italic; color: var(--muted); }
@keyframes hj-sheet-in { from { opacity: 0; transform: translateY(var(--sp-4)); } }
@media (max-width: 899px) {
  .hj-hero { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: var(--sp-4); }
  .hj-memento { width: 120px; }
  .hj-hunts { width: 100%; }
  .hj-hunt-row { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-1); }
  .hj-hunt { padding: var(--sp-2) 0; border-color: transparent; }
  .hj-hunt .hj-ring { width: 56px; }
  .hj-hunt b { font-size: var(--fs-2xs); letter-spacing: 0; max-width: 100%; overflow-wrap: anywhere; }
  .hj-hunt-left { display: none; }
  .hj-tools { justify-content: center; gap: var(--sp-3) var(--sp-6); }
  .hj-find { flex-basis: 100%; max-width: none; margin-right: 0; }
  .hj-ctl { justify-content: center; gap: var(--sp-2) var(--sp-6); }
  .hj-sheet { inset: auto 0 0 0; margin: 0; width: auto; max-height: 90vh; grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); padding: var(--sp-8) var(--sp-4) var(--sp-6); border-width: 1px 0 0; animation-name: hj-sheet-up; }
  .hj-art { min-height: 200px; }
  .hj-art img { max-height: 260px; }
  @keyframes hj-sheet-up { from { opacity: 0; transform: translateY(var(--sp-8)); } }
}
@media (max-width: 480px) {
  .hj-grid { grid-template-columns: repeat(auto-fill, minmax(52px, 1fr)); gap: var(--sp-1); }
  .hj-ring { width: 48px; }
}
}

/* ── Combat (js/app-fight.js): the duel ────────────────────────────────────
   design/21-combat-variants.html, A (the reference implementation's): the enemy in a dropdown; Hornet and the
   enemy face to face on the Journal's light, the slashes that win and the hits that take you down
   between them, its health as a bar; the quickest way in one line; your attacks as cards and its
   with their masks; how it's worked out, folded. */
.ft-choose { position: relative; z-index: 3; max-width: 560px; margin: var(--sp-4) auto 0; }
.ft-choose > summary {
  display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) var(--sp-3); list-style: none; cursor: pointer;
  border-bottom: 1px solid var(--line-ctl); -webkit-tap-highlight-color: transparent;
}
.ft-choose > summary::-webkit-details-marker { display: none; }
.ft-choose > summary:focus { outline: none; }
.ft-choose > summary:focus-visible { border-color: var(--accent); }
.ft-choose > summary img { width: var(--sp-8); height: var(--sp-8); object-fit: contain; }
.ft-choose-name { flex: 1; min-width: 0; font-family: var(--font-display); color: var(--ink-strong); }
.ft-choose-v { color: var(--ink-2); transition: transform var(--dur) var(--ease); }
.ft-choose[open] .ft-choose-v { transform: rotate(180deg); }
/* A <details>' content doesn't take its box as the containing block (Chrome slots it), so the
   list hangs from a zero-height relative wrapper instead. */
.ft-choose-body { position: relative; z-index: 5; height: 0; }
.ft-choose-body > * { position: absolute; left: 0; right: 0; top: 0; padding: var(--sp-3); background: var(--scene-floor); border: 1px solid var(--line-ctl); box-shadow: var(--shadow-md); }
.ft-choose-body .ft-foes { max-height: 50vh; }
.ft-pick { display: grid; gap: var(--sp-3); }
.ft-foes { list-style: none; margin: 0; padding: 0; max-height: 70vh; overflow: auto; display: grid; gap: var(--sp-half); }
.ft-foe {
  display: grid; grid-template-columns: 32px minmax(0, 1fr) auto; align-items: center; gap: var(--sp-2); width: 100%;
  padding: var(--sp-1) var(--sp-2); border: 1px solid transparent; background: none; color: var(--ink-2); text-align: left; font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.ft-foe img { width: 32px; height: 36px; object-fit: contain; }
.ft-foe:is(:hover, :focus-visible) { border-color: color-mix(in oklab, var(--filigree) 40%, transparent); background: var(--veil); color: var(--ink); outline: none; }
.ft-foe.is-on { border-color: var(--filigree); background: var(--veil); color: var(--ink-strong); }
.ft-boss { font-style: normal; font-size: var(--fs-2xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--muted); }
/* The duel: Hornet · the figures · the enemy, on one baseline. */
.ft-duel { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: var(--sp-6); align-items: end; margin-top: var(--sp-8); }
.ft-fighter { display: grid; justify-items: center; gap: var(--sp-2); text-align: center; }
.ft-fighter h3 { margin: 0; font-family: var(--font-display); font-size: var(--fs-xl); font-weight: 400; letter-spacing: var(--track-title); color: var(--ink-strong); }
.ft-duel { --ft-art: calc(var(--sp-16) * 3.5); }
.ft-art { position: relative; display: grid; place-items: center; width: 100%; height: var(--ft-art); }
.ft-art::before { content: ''; position: absolute; inset: -6% 10%; background: var(--journal-light); pointer-events: none; }
.ft-art img { position: relative; max-width: 100%; max-height: var(--ft-art); object-fit: contain; }
.ft-masks { display: flex; flex-wrap: wrap; justify-content: center; gap: 3px; }
.ft-masks img { height: var(--sp-6); width: auto; }
.ft-hpbar { position: relative; display: block; width: min(100%, 320px); height: 6px; background: linear-gradient(90deg, color-mix(in oklab, var(--slot-red) 75%, transparent) var(--h, 100%), var(--ring-track) 0); box-shadow: 0 0 10px color-mix(in oklab, var(--slot-red) 35%, transparent); transition: background var(--dur) var(--ease); }
.ft-masks img.is-off { filter: brightness(0.25) grayscale(1); }
/* Her silk: the HUD's spool, lit as far as it's full. */
.ft-silk { display: flex; align-items: center; gap: var(--sp-2); }
.ft-silk > span { position: relative; display: block; width: calc(var(--sp-16) * 1.6); height: var(--sp-4); }
.ft-silk img { position: absolute; inset: 0; width: 100%; height: 100%; }
.ft-silk .is-dim { filter: brightness(0.25) grayscale(1); }
.ft-silk .is-lit { clip-path: inset(0 calc(100% - var(--s)) 0 0); }
.ft-silk b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-md); color: var(--ink-strong); }
.ft-bind { margin-top: var(--sp-1); }
.ft-fighter.is-down .ft-art img { filter: grayscale(1) brightness(0.45); transform: rotate(-8deg); transition: filter var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease); }
.ft-hpbar i { position: absolute; top: -3px; bottom: -3px; width: 2px; margin-left: -1px; background: var(--ink-strong); }
.ft-hpn { font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.ft-mid { display: grid; justify-items: center; gap: var(--sp-6); padding-bottom: var(--sp-12); text-align: center; }
.ft-mid p { margin: 0; display: grid; justify-items: center; gap: var(--sp-1); }
.ft-mid b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: calc(var(--fs-2xl) * 2); line-height: 1; color: var(--ink-strong); }
.ft-mid .is-sub b { font-size: var(--fs-2xl); }
.ft-mid span { font-size: var(--fs-xs); color: var(--muted); }
.ft-plan { max-width: 640px; margin: var(--sp-4) auto 0; text-align: center; font-size: var(--fs-sm); color: var(--ink-2); text-wrap: balance; }
.ft-plan b { font-family: var(--font-display); font-weight: 400; color: var(--ink-strong); }
/* Your attacks as cards, the one that needs fewest framed; its attacks with their masks. */
.ft-two { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: var(--sp-8); align-items: start; margin-top: var(--sp-8); }
.ft-side { display: grid; gap: var(--sp-4); }
.ft-cards { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: var(--sp-3); }
.ft-cardx {
  display: grid; justify-items: center; align-content: start; gap: var(--sp-1); width: 100%; height: 100%; padding: var(--sp-3) var(--sp-2);
  border: 1px solid transparent; background: none; text-align: center; cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.ft-cardx:is(:hover, :focus-visible):not(:disabled) { background: var(--veil); outline: none; }
.ft-cardx:active:not(:disabled) { transform: scale(0.96); }
.ft-cardx:disabled { cursor: default; opacity: 0.4; }
.ft-cardx small { font-family: var(--font-num); font-size: var(--fs-2xs); color: var(--label); }
.ft-cardx img { width: var(--sp-12); height: var(--sp-12); object-fit: contain; }
.ft-cardx img.is-needle { transform: rotate(35deg); }
.ft-cardx span { font-size: var(--fs-sm); color: var(--ink-2); }
.ft-cardx b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xl); color: var(--ink-strong); }
.ft-cardx em { font-style: normal; font-size: var(--fs-xs); color: var(--muted); }
.ft-cardx.is-best { border-color: var(--accent-line); background: var(--picked); }
.ft-hits { list-style: none; margin: 0; padding: 0; display: grid; }
.ft-hits li { border-bottom: 1px solid var(--line); }
.ft-hit {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "n m" "e e"; gap: 0 var(--sp-3); width: 100%; padding: var(--sp-2) var(--sp-1);
  border: 0; background: none; text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background-color var(--dur-fast) var(--ease);
}
.ft-hit:is(:hover, :focus-visible):not(:disabled) { background: color-mix(in oklab, var(--slot-red) 10%, transparent); outline: none; }
.ft-hit:disabled { cursor: default; opacity: 0.4; }
.ft-hit-name { grid-area: n; font-size: var(--fs-sm); color: var(--ink); }
.ft-hit-masks { grid-area: m; display: inline-flex; align-items: center; gap: 2px; }
.ft-hit-masks img { height: var(--sp-4); width: auto; }
.ft-hit-masks b { font-family: var(--font-num); font-size: var(--fs-xs); color: var(--ink-2); }
.ft-hits em { grid-area: e; font-style: normal; font-size: var(--fs-xs); color: var(--muted); }
/* Enemy gauntlets: the arena's summary centred, then its waves. */
.ft-summary { display: grid; justify-items: center; gap: var(--sp-2); margin-top: var(--sp-6); text-align: center; }
.ft-summary .inv-name { margin: 0; }
.ft-hp { margin: 0; color: var(--ink-2); }
.ft-hp b { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-2xl); color: var(--ink-strong); }
.ft .ct-figs.ft-waves { position: static; margin-top: var(--sp-6); }
/* The fight's log, newest first, with undo and start over. */
/* The log, in three voices (the reference implementation's): what you did on the left with a slash of the Needle,
   what it did to you on the right with a mask, the fight's own lines centred between two rules. */
.ft-log { max-width: 720px; margin: var(--sp-8) auto 0; }
.ft-log-lines { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); font-family: var(--font-num); font-size: var(--fs-xs); color: var(--muted); }
.ft-log-lines li { display: flex; align-items: center; gap: var(--sp-2); }
.ft-log-lines li.is-foe { justify-content: flex-end; text-align: right; }
.ft-log-lines li.is-sys { justify-content: center; font-family: var(--font-display); letter-spacing: var(--track-caps); color: var(--label); }
.ft-log-lines li.is-sys::before, .ft-log-lines li.is-sys::after { content: ''; flex: 0 1 var(--sp-12); border-top: 1px solid var(--line); }
.ft-log-lines li.is-last { font-size: var(--fs-sm); color: var(--ink-strong); animation: ft-in var(--dur) var(--ease); }
.ft-log-mask { height: var(--sp-3); width: auto; }
.ft-log-tools { display: flex; justify-content: center; gap: var(--sp-4); }
.ft-log-tools .ic, .ft-undo .ic { margin-right: var(--sp-1); vertical-align: -1px; }
@keyframes ft-in { from { opacity: 0; transform: translateY(-4px); } }
/* How it's worked out, folded. */
.ft-how { margin-top: var(--sp-8); }
.ft-how > summary { list-style: none; cursor: pointer; }
.ft-how > summary::-webkit-details-marker { display: none; }
.ft-how-body { display: grid; gap: var(--sp-3); padding-top: var(--sp-4); }
.ft-mods { display: grid; gap: var(--sp-1); width: 100%; }
.ft-mods ol { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--sp-1); }
.ft-mods li { display: grid; gap: var(--sp-half); padding: var(--sp-1) 0; border: 1px solid var(--line); font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.ft-mods li span { font-size: var(--fs-2xs); color: var(--muted); }
.ft-mods li b { font-size: var(--fs-sm); font-weight: 500; color: var(--ink-2); }
.ft-mods li.is-needle { border-color: var(--filigree); }
.ft-mods li.is-needle b, .ft-mods li.is-kit b { color: var(--ink-strong); }
.ft-mods li.is-kit:not(.is-needle) { border-style: dashed; border-color: var(--ink-2); }
.ft-stagger { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }
.ft-phases { font-size: var(--fs-sm); color: var(--ink-2); }
.ft-phases ul { margin: var(--sp-1) 0 0; padding: 0; list-style: none; }
.ft-phases li { font-variant-numeric: tabular-nums; }
.ft-list li { grid-template-columns: 28px minmax(0, 1fr) auto auto; grid-template-areas: "i n v u" "i s v u"; column-gap: var(--sp-3); }
.ft-list.is-theirs li { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "n v u" "s v u"; }
.ft-uses { grid-area: u; min-width: 8ch; text-align: right; font-size: var(--fs-xs); color: var(--ink-2); }
/* The fight played out (js/fight.js): Hornet's HUD, its bar, the clock, the end. What changes
   flashes once when the action causes it (the reference implementation's): a hit whitens the one hit, its figure
   rises and fades, the bar leaves a pale trail, a mask breaks, a Bind glows; nothing bounces. */
.ft-masks img.is-plasm { filter: sepia(1) hue-rotate(170deg) saturate(3) brightness(0.8); }
.ft-masks img.is-fractured { outline: 1px dashed var(--mask-line); outline-offset: 1px; }
.ft-masks img.is-broken { animation: ft-break var(--dur-slow) var(--ease); }
@keyframes ft-break { from { filter: none; transform: scale(1.2); } }
.ft-silk.is-changed b { animation: ft-glow var(--dur-flash) var(--ease); }
@keyframes ft-glow { from { text-shadow: 0 0 8px var(--focus-glow); } }
.ft-chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-1); font-size: var(--fs-2xs); letter-spacing: var(--track-caps); color: var(--label); }
.ft-chips li { padding: var(--sp-half) var(--sp-2); border: 1px solid var(--line); }
.ft-binding { display: grid; justify-items: center; gap: var(--sp-1); max-width: 260px; font-size: var(--fs-xs); color: var(--ink); }
/* Hornet's sprite faces left: mirrored, she faces the enemy (scale, not transform, so the hit's shake still runs). */
.ft-hornet .ft-art img { scale: -1 1; }
.ft-hornet.is-hit .ft-art img { animation: ft-hit var(--dur-slow) var(--ease); }
.ft-hornet.is-bind .ft-art img, .ft-hornet.is-binding .ft-art img { filter: drop-shadow(0 0 12px var(--focus-glow)); transition: filter var(--dur) var(--ease); }
.ft-enemy.is-hit .ft-art img { animation: ft-hit var(--dur-slow) var(--ease); }
@keyframes ft-hit { 0% { filter: brightness(3); transform: translateX(4px); } 100% { filter: none; transform: none; } }
.ft-enemy.is-staggered .ft-art img { transform: rotate(6deg) translateY(4%); filter: brightness(0.8); transition: transform var(--dur) var(--ease), filter var(--dur) var(--ease); }
.ft-dmg { position: absolute; top: 20%; left: 50%; font-family: var(--font-num); font-size: var(--fs-xl); font-weight: 600; color: var(--ink-strong); pointer-events: none; animation: ft-rise var(--dur-flash) var(--ease) forwards; }
@keyframes ft-rise { from { opacity: 1; transform: translate(-50%, 0); } to { opacity: 0; transform: translate(-50%, -40px); } }
.ft-dust { position: absolute; left: calc(20% + var(--i) * 6%); bottom: 30%; width: 4px; height: 4px; border-radius: 50%; background: var(--ink); opacity: 0; animation: ft-dust calc(var(--dur-flash) * 1.4) var(--ease) calc(var(--i) * 40ms) forwards; }
@keyframes ft-dust { from { opacity: 0.9; transform: none; } to { opacity: 0; transform: translateY(-60px); } }
.ft-enemy.is-dying .ft-art img { animation: ft-fade calc(var(--dur-flash) * 1.4) var(--ease) forwards; }
@keyframes ft-fade { to { opacity: 0.35; filter: grayscale(1); } }
.ft-hpbar { background: var(--ring-track); box-shadow: 0 0 10px color-mix(in oklab, var(--slot-red) 35%, transparent); }
.ft-hpbar > .ft-fill, .ft-hpbar > .ft-trail { position: absolute; top: 0; bottom: 0; left: 0; transition: width var(--dur) var(--ease); }
.ft-hpbar > .ft-fill { background: color-mix(in oklab, var(--slot-red) 75%, transparent); }
.ft-hpbar > .ft-trail { background: color-mix(in oklab, var(--ink) 55%, transparent); animation: ft-trail var(--dur-flash) var(--ease) forwards; }
@keyframes ft-trail { to { opacity: 0; } }
.ft-hpbar.is-low > .ft-fill { background: var(--slot-red); }
.ft-phase-n, .ft-part-name { font-size: var(--fs-xs); letter-spacing: var(--track-caps); color: var(--label); }
.ft-stag { font-family: var(--font-num); font-size: var(--fs-2xs); color: var(--muted); }
.ft-stag.is-down { color: var(--ink-strong); }
.ft-clock { font-family: var(--font-num); font-size: var(--fs-xs); color: var(--muted); }
/* Your moves in groups; a card that can't be played says why. The critical sits beside the slash. */
.ft-group { display: grid; gap: var(--sp-2); }
.ft-group-h { margin: 0; font-size: var(--fs-xs); font-style: italic; font-weight: 400; color: var(--muted); border-bottom: 1px solid var(--line-row); }
.ft-cards li.has-crit { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 0.8fr); grid-column: span 2; }
.ft-cardx.is-crit { border-left: 1px dashed var(--line); }
.ft-card-glyph { display: grid; place-items: center; width: var(--sp-12); height: var(--sp-12); font-size: var(--fs-xl); color: var(--ink-2); }
.ft-cardx .ft-why { color: var(--slot-red); }
.ft-targets { display: grid; gap: var(--sp-1); }
.ft-target { display: grid; grid-template-columns: minmax(0, 1fr) 30% auto; align-items: center; gap: var(--sp-2); padding: var(--sp-1) var(--sp-2); border: 1px solid transparent; background: none; color: var(--ink-2); font: inherit; font-size: var(--fs-sm); text-align: left; cursor: pointer; }
.ft-target.is-on { border-color: var(--filigree); color: var(--ink-strong); background: var(--veil); }
.ft-target:disabled { opacity: 0.4; cursor: default; }
.ft-target b { font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.ft-target .ft-hpbar { width: 100%; height: 4px; }
/* The end, titled as the game titles an area, with the fight in figures. */
.ft-end { display: grid; justify-items: center; gap: var(--sp-2); margin: var(--sp-8) auto 0; text-align: center; }
.ft-end.is-fresh { animation: ft-in var(--dur-slow) var(--ease-fade); }
.ft-end-title { margin: 0; font-family: var(--font-display); font-size: var(--fs-2xl); letter-spacing: var(--track-title); color: var(--ink-strong); }
.ft-end-rule { display: flex; align-items: center; gap: var(--sp-2); width: 220px; color: var(--filigree); }
.ft-end-rule i { flex: 1; border-top: 1px solid currentColor; }
.ft-end-rule svg { fill: none; stroke: currentColor; }
.ft-end-note { margin: 0; color: var(--ink-2); }
.ft-sum { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-2) var(--sp-6); margin: var(--sp-2) 0; }
.ft-sum div { display: grid; justify-items: center; }
.ft-sum dt { order: 2; font-size: var(--fs-2xs); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.ft-sum dd { margin: 0; font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-lg); color: var(--ink-strong); }
/* The band: the duel in small, stuck under the bar when the duel scrolls away. */
.ft-band { position: sticky; top: var(--ft-band-top, 0); z-index: 6; height: 0; }
.ft-band-in { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); padding: var(--sp-2) var(--sp-3); background: var(--scene-bg); border-bottom: 1px solid var(--line); box-shadow: var(--shadow-md);
  opacity: 0; visibility: hidden; transform: translateY(-8px); transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility 0s linear var(--dur); }
.ft-band.is-on .ft-band-in { opacity: 1; visibility: visible; transform: none; transition-delay: 0s; }
.ft-band-me { display: flex; align-items: center; gap: var(--sp-2); }
.ft-band-me .ft-masks img { height: var(--sp-3); }
.ft-band-me b, .ft-band-vs b { font-family: var(--font-num); color: var(--ink-strong); }
.ft-band-vs { font-family: var(--font-num); font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; }
.ft-band-foe { display: grid; justify-items: end; gap: var(--sp-1); min-width: 0; font-size: var(--fs-xs); color: var(--ink); }
.ft-band-foe .ft-hpbar { width: 140px; height: 4px; }
@media (prefers-reduced-motion: reduce) {
  .ft-dmg, .ft-dust, .ft-hpbar > .ft-trail { display: none; }
  .ft [class*="is-"] img, .ft-end.is-fresh, .ft-log-lines li.is-last { animation: none !important; }
}
@media (max-width: 899px) {
  .ft-duel { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-4); }
  .ft-mid { grid-column: 1 / -1; grid-row: 2; grid-template-columns: repeat(2, auto); justify-content: center; align-items: end; gap: var(--sp-2) var(--sp-8); padding: 0; }
  .ft-mid > .ft-clock, .ft-mid > .ft-log-tools, .ft-mid > .ft-clock:first-child { grid-column: 1 / -1; }
  .ft-duel { --ft-art: calc(var(--sp-16) * 2.2); }
  .ft-two { grid-template-columns: minmax(0, 1fr); }
  .ft-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ft-masks img { height: var(--sp-4); }
}

/* ── The Map (js/app-map.js) ──────────────────────────────────────────────
   The game's map on the pause menu's black, which scrolls when zoomed; on it, Hornet at your bench
   and a dot per piece missing, each kind in its own colour (the map's own tints are the areas';
   the dots take the HUD's: bone for shards, silk white for fragments, the slots' for the rest). */
/* The map first (design/03-redesign.md, step 6): the view fills the plate to the frame's inner
   edge; on it, the search top left, the zoom top right, the legend bottom left. */
/* It has no ground of its own (design/50): the screen's black and its light from the top show
   behind the map, and closer up its edges fade into the frame instead of a cut. The large map and
   full screen, which leave the frame, keep the black. */
.mp-stage { position: relative; margin: 0 calc(var(--inv-pad) * -1 + var(--frame-inset) + 1px); }
/* The view: a fixed window the map moves and scales in (js/app-map.js): drag, wheel, pinch. */
.mp-view { position: relative; overflow: hidden; height: min(72vh, 720px, 62vw + 120px);
  touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none; }
.mp:not(.is-big, .is-full) .mp-canvas {
  --fade: linear-gradient(to bottom, transparent, #000 var(--sp-12), #000 calc(100% - var(--sp-16)), transparent), linear-gradient(to right, transparent, #000 var(--sp-12), #000 calc(100% - var(--sp-12)), transparent);
  -webkit-mask-image: var(--fade); -webkit-mask-composite: source-in; mask-image: var(--fade); mask-composite: intersect; }
.mp:is(.is-big, .is-full) :is(.mp-stage, .mp-view) { background: var(--scene-bg); }
.mp-view.is-grabbing { cursor: grabbing; }
/* No will-change: it would keep the layer rasterised at the fitted size, blurred close up. */
.mp-view img { -webkit-user-drag: none; pointer-events: none; }
/* The marks and names keep their size on screen whatever the zoom (--k, the scale's inverse). */
/* The large map, as the reference implementation's (the zoom's third button): the window's whole width, centred
   over the page's column, and the window's height under the bar. */
.mp-big[aria-pressed="true"] { color: var(--ink-strong); background: var(--picked); }
@media (min-width: 900px) {
  .mp.is-big .mp-stage { --map-w: calc(var(--page-w, 100vw) - 2 * var(--page-pad)); width: var(--map-w); margin-inline: calc((100% - var(--map-w)) / 2); }
  .mp.is-big .mp-view { height: max(480px, calc(100vh - var(--sticky-h) - var(--sp-6))); }
}
@media (max-width: 899px) { .mp-big { display: none; } }
/* Full screen (the zoom's last button; js/app-map.js, setFull): the stage over the whole window,
   with the search and the zoom on it; the page doesn't scroll behind. On a phone too: there it's
   what makes the map big. After the large map's rules, which it overrides: both can be on. */
.mp-full[aria-pressed="true"] { color: var(--ink-strong); background: var(--picked); }
/* The screen is a layer of its own (isolation), under the bar: over it. */
.screen:has(.mp.is-full) { z-index: 50; }
.mp.is-full .mp-stage { position: fixed; z-index: 4; inset: 0; width: auto; margin: 0; display: block; }
.mp.is-full .mp-view { height: 100%; }
.mp.is-full .mp-big { display: none; }
.mp.is-full .mp-find { position: absolute; order: 0; top: var(--sp-3); left: var(--sp-3); margin: 0; padding: 0; width: auto; right: calc(var(--ctl-sm) * 3 + var(--sp-3) * 2 + var(--sp-2)); max-width: 26rem; }
.mp.is-full .mp-tools { top: var(--sp-3); }
html:has(.mp.is-full) { overflow: hidden; }
.mp-float { background: color-mix(in oklab, var(--scene-bg) 88%, transparent); border: 1px solid var(--line-ctl); }
/* Wide enough for its placeholder, which ends in an ellipsis where it can't be (a phone has a shorter one). */
.mp-find { position: absolute; z-index: 4; top: var(--sp-3); left: var(--sp-3); width: min(26rem, calc(100% - var(--sp-16) * 2)); }
.mp-find input { text-overflow: ellipsis; }
.mp-find .search { background: color-mix(in oklab, var(--scene-bg) 88%, transparent); border: 1px solid var(--line-ctl); }
.mp-found { list-style: none; margin: var(--sp-1) 0 0; padding: var(--sp-1) 0; background: color-mix(in oklab, var(--scene-bg) 94%, transparent); border: 1px solid var(--line-ctl); }
.mp-found button { width: 100%; padding: var(--sp-1) var(--sp-3); min-height: var(--ctl-sm); text-align: left; font-size: var(--fs-sm); color: var(--ink); }
.mp-found button:is(:hover, :focus-visible) { background: var(--veil); color: var(--ink-strong); }
.mp-found button { display: flex; flex-direction: column; align-items: flex-start; gap: 0; }
.mp-found small { font-size: var(--fs-2xs); color: var(--muted); }
.mp-found-none { padding: var(--sp-1) var(--sp-3); font-size: var(--fs-sm); color: var(--muted); }
/* The focus (App.mapFocus): in the search's place, what the marks are, ‹ i of n › and its ×. */
.mp-find.mp-focus { width: min(26rem, calc(100% - var(--sp-16) * 2)); }
.mp-focus { display: flex; align-items: center; gap: var(--sp-1); min-height: var(--ctl-sm); padding: 0 var(--sp-1) 0 var(--sp-3);
  background: color-mix(in oklab, var(--scene-bg) 88%, transparent); border: 1px solid var(--line-ctl); font-size: var(--fs-sm); color: var(--ink); }
.mp-focus-t { display: flex; align-items: center; gap: var(--sp-2); flex: 1; min-width: 0; }
.mp-focus-t > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mp-focus-t .ic { flex: none; color: var(--ink-2); }
.mp-focus-n { flex: none; font-family: var(--font-num); font-size: var(--fs-xs); color: var(--ink-2); white-space: nowrap; }
.mp-focus :is(.mp-focus-go, .mp-focus-x) { flex: none; display: grid; place-items: center; width: var(--ctl-sm); height: var(--ctl-sm); color: var(--ink-2); font-size: var(--fs-md); }
.mp-focus :is(.mp-focus-go, .mp-focus-x):is(:hover, :focus-visible) { color: var(--ink-strong); background: var(--veil); }
.mp-tools { position: absolute; z-index: 4; top: var(--sp-3); right: var(--sp-3); display: flex; border: 1px solid var(--line-ctl); background: color-mix(in oklab, var(--scene-bg) 88%, transparent); }
.mp-tools button { width: var(--ctl-sm); height: var(--ctl-sm); font-family: var(--font-num); font-size: var(--fs-md); color: var(--ink-2); }
.mp-tools button + button { border-left: 1px solid var(--line-ctl); }
.mp-tools button:is(:hover, :focus-visible):not(:disabled) { color: var(--ink-strong); background: var(--veil); }
/* The selector under the map (design/03-redesign.md, step 6, the selector; the reference implementation's filter):
   its head (title, show all · hide all, the Acts and the three choices), then the groups, each a
   row that folds over its layers, its title its switch. */
.mp-legend { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4) 0 0; }
.mp-legend-h { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--sp-1) var(--sp-6); }
.mp-legend-t { margin: 0; font-family: var(--font-display); font-size: var(--fs-sm); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-strong); }
.mp-all { display: inline-flex; gap: var(--sp-4); }
.mp-all .text-btn { font-size: var(--fs-sm); }
.mp-opts { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-8); }
.mp-checks { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-6); }
.mp-sec { border-top: 1px solid var(--inv-line); }
.mp-sec:last-child { border-bottom: 1px solid var(--inv-line); }
.mp-sec-h { display: flex; align-items: center; gap: var(--sp-3); }
.mp-fold { display: grid; place-items: center; flex: none; width: var(--ctl-sm); min-height: var(--ctl); padding: 0; }
/* A caret in plain ink: the frame's light is the tab's colour, never a control's. */
.mp-caret { flex: none; width: 10px; height: 10px; color: var(--ink-2); transition: rotate var(--dur) var(--ease); }
.mp-sec.is-open .mp-fold .mp-caret, .mp-more[aria-expanded="true"] .mp-caret { rotate: 90deg; }
/* The group's title is its switch: all on in ink, some in ink-2, none muted. */
.mp-sec-sw { display: flex; flex: 1; align-items: center; gap: var(--sp-3); min-height: var(--ctl); padding: 0; text-align: left; color: var(--ink-strong); }
.mp-sec-sw.is-some { color: var(--ink-2); }
.mp-sec-sw.is-none { color: var(--muted); }
.mp-sec-sw:is(:hover, :focus-visible) .mp-sec-t, .mp-fold:is(:hover, :focus-visible) + .mp-sec-sw .mp-sec-t { color: var(--accent); }
.mp-sec-t { font-family: var(--font-display); font-size: var(--fs-sm); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; transition: color var(--dur) var(--ease); }
.mp-kinds { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-6); padding: 0 0 var(--sp-3) var(--sp-6); }
/* A layer: the game's picture, bare, is the switch; off, in grey (the Inventory's missing look). */
.mp-kind { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--ctl-sm); padding: 0; font-size: var(--fs-sm); color: var(--ink); text-align: left; }
.mp-kind-t { transition: color var(--dur) var(--ease); }
.mp-kind:is(:hover, :focus-visible) .mp-kind-t { color: var(--accent); }
.mp-kind.is-off .mp-kind-t, .mp-kind.is-off .mp-n b { color: var(--muted); }
.mp-art { --a: 28px; flex: none; display: inline-grid; place-items: center; width: var(--a); height: var(--a); color: var(--ink-2); }
.mp-art > img { display: block; width: var(--a); height: var(--a); max-width: none; object-fit: contain; transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease); }
.mp-art > svg { width: 70%; height: 70%; }
/* The Needle, a long thin line, laid across as on the map. */
.mp-art.is-needle > img { transform: rotate(35deg) scale(1.15); }
.mp-kind.is-off .mp-art > img { filter: var(--missing-art); }
.mp-kind.is-off .mp-art > svg { opacity: 0.35; }
/* A set with some of its layers off: its picture half lit. */
.mp-kind.is-some .mp-art > img { filter: grayscale(0.5) brightness(0.75); }
/* How many: had of total in the figures' face; complete, in --good. */
.mp-n { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; }
.mp-n b { font-weight: 700; color: var(--ink-strong); }
.mp-n i { font-style: normal; }
.mp-n.is-full b { color: var(--good); }
.mp-sec-sw .mp-n { font-size: var(--fs-2xs); }
/* A set (the relics) and its caret, which opens its layers in a row of their own. */
.mp-set { display: inline-flex; align-items: center; gap: var(--sp-2); }
.mp-more { display: inline-grid; place-items: center; flex: none; width: 24px; height: 24px; padding: 0; border: 1px solid var(--line-ctl); border-radius: 50%; color: var(--ink-2); }
.mp-more .mp-caret { width: 9px; height: 9px; color: inherit; }
.mp-more:is(:hover, :focus-visible), .mp-more[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
@media (pointer: coarse) { .mp-more { width: 32px; height: 32px; } }
.mp-inside { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-4); margin: 0 0 var(--sp-2) var(--sp-8); padding: var(--sp-1) var(--sp-4); border-left: 1px solid var(--line); }
.mp-inside .mp-kind { font-size: var(--fs-xs); }
.mp-inside .mp-art { --a: 22px; }
/* The area names, on the map in the game's capitals, lit only enough to read. */
/* A mark is tappable: a finger's cursor, and the one whose card is open ringed in the accent. */
/* The card over the map, the reference implementation's: above its mark (below when there's no room), inside the box. */
.mp-card-wrap { position: absolute; z-index: 5; transform: translate(-50%, calc(-100% - var(--sp-3))); }
.mp-card-wrap.is-below { transform: translate(-50%, calc(var(--sp-8) + var(--sp-1))); }
.mp-card {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--sp-1) var(--sp-3);
  width: max-content; max-width: min(24rem, 80vw); padding: var(--sp-3) var(--sp-2) var(--sp-3) var(--sp-4);
  background: color-mix(in oklab, var(--scene-bg) 94%, transparent); border: 1px solid var(--line-ctl); box-shadow: var(--shadow-md);
}
.mp-card-art { grid-row: span 2; display: grid; place-items: center; }
.mp-card-art :is(.mp-dot, .mp-pin) { position: static; margin: 0; --mp-s: 36px; }
.mp-card-t { display: flex; flex-direction: column; gap: var(--sp-half); min-width: 0; font-size: var(--fs-xs); color: var(--muted); }
.mp-card-t b { font-size: var(--fs-md); font-weight: 600; color: var(--ink-strong); }
.mp-card-note { color: var(--ink-2); text-wrap: pretty; }
.mp-card > .text-btn, .mp-card-state { grid-column: 2; justify-self: start; font-size: var(--fs-sm); }
.mp-card-state { color: var(--muted); }
.mp-card-x { grid-column: 3; grid-row: 1; align-self: start; }
.mp-card-acts { grid-column: 2; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-4); font-size: var(--fs-sm); }
.mp-card-acts .mp-card-state { grid-column: auto; }
/* Another screen's way onto the Map. */
.pg-onmap { justify-self: start; font-size: var(--fs-xs); }

/* Where a search result is: a ring that swells and fades. */
.mp-ping { position: absolute; z-index: 3; width: var(--sp-12); height: var(--sp-12); margin: calc(var(--sp-6) * -1) 0 0 calc(var(--sp-6) * -1); border: 2px solid var(--accent); border-radius: 50%; opacity: 0; pointer-events: none; }
@keyframes mp-ping { 0% { opacity: 1; transform: scale(0.4); } 100% { opacity: 0; transform: scale(1.6); } }
.mp-ping.is-on { animation: mp-ping 1.2s var(--ease-fade) 3; }
@media (prefers-reduced-motion: reduce) { .mp-ping.is-on { opacity: 1; } }
@media (max-width: 899px) {
  .mp-find { width: auto; right: calc(var(--ctl-sm) * 3 + var(--sp-3) * 2); }   /* the zoom's three buttons (no large map here) */
}
/* Whole on a phone the names would cover the map: they come with the zoom. */
@media (max-width: 599px) {
  .mp-view:not(.is-zoomed) .mp-name { display: none; }
  /* Whole, the map is too short to float the search on: it goes above it. */
  .mp-stage { display: flex; flex-direction: column; }
  .mp-find { position: static; order: -1; width: auto; margin: 0 var(--sp-3) var(--sp-2); padding-right: calc(var(--ctl-sm) * 3 + var(--sp-3)); }
  /* The focus's line keeps the room the search leaves for the zoom outside its frame, its name over ‹ i of n ›. */
  .mp-find.mp-focus { flex-wrap: wrap; width: auto; margin-right: calc(var(--ctl-sm) * 4 + var(--sp-3) * 2); padding: var(--sp-1) var(--sp-1) var(--sp-1) var(--sp-3); }
  .mp-focus-t { flex-basis: 100%; }
  /* Whole, the map is small: smaller marks, so they don't cover it. */
  .mp-view:not(.is-zoomed) { --mp-s: 16px; }
  .mp-tools { top: 0; }
  .mp-legend-h { grid-template-columns: minmax(0, 1fr); }
  .mp-opts { gap: var(--sp-1) var(--sp-6); }
  .mp-checks { flex-direction: column; align-items: flex-start; }
  .mp-kinds { padding-left: 0; gap: var(--sp-1) var(--sp-4); }
  .mp-inside { margin-left: var(--sp-2); }
}
/* A piece: the game's round badge, a dark disc with a rim in the kind's colour, the picture inside. */
.mp-dot {
  --c: var(--ink); position: absolute; z-index: 2; display: grid; place-items: center; width: var(--mp-s, 22px); height: var(--mp-s, 22px);
  margin: calc(var(--mp-s, 22px) / -2) 0 0 calc(var(--mp-s, 22px) / -2); border-radius: 50%;
  background: var(--scene-bg); border: 1px solid var(--line-ctl);
  /* One row of the disc's own height: a tall picture (the Needle's 256 px) would stretch an auto row past the disc, its 100% height resolving to its own. */
  grid-template-rows: minmax(0, 1fr);
}
.mp-dot img { width: calc(var(--mp-s, 22px) * 0.86); height: calc(var(--mp-s, 22px) * 0.86); object-fit: contain; }
/* The Needle, a long thin line, laid across the disc as the fight's card lays it (.ft-cardx). */
.mp-dot.is-needle img { transform: rotate(35deg) scale(1.2); }
/* A picture placed by its measure (js/app-map.js fitOf; design/03-redesign.md, step 6, the marks'
   size): it fills its disc, or the selector's square, moved and scaled so its opaque part is as big
   as every other's and centred. */
:is(.mp-dot, .mp-pin.is-badge) img.mp-fit { width: 100%; height: 100%; }
img.mp-fit { transform: translate(calc(var(--fx) * 100%), calc(var(--fy) * 100%)) scale(var(--fs)); }
.is-needle > img.mp-fit { transform: translate(calc(var(--fx) * 100%), calc(var(--fy) * 100%)) scale(var(--fs)) rotate(35deg); }
/* The map in SVG, as the reference implementation's: the view is its viewBox, the marks keep their size (--k). */
.mp-svg { position: relative; display: block; width: 100%; height: 100%; touch-action: none; cursor: grab; user-select: none; }
/* The map's pieces, composed on a canvas under the SVG (js/map-paint.js), in the map's tone. */
.mp-canvas { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; filter: var(--map-tone); }
.mp-view.is-grabbing .mp-svg { cursor: grabbing; }
/* The marks' own cursor (each mark's, below) set on their group too, so that the grab's, changing
   at each drag's start and end, isn't passed down to every mark (a restyle of all of them). */
.mp-marks { cursor: pointer; }
.mp-svg image { pointer-events: none; }
/* A mark: a dark disc with a thin rim and the thing's picture almost as big; the rim lit when
   pointed at or chosen. js/app-map.js writes this transform whole on each mark (placeMark), so
   that a zoom restyles each mark once; this rule only places them until it has. */
.mp-mk { transform: translate(var(--px), var(--py)) scale(var(--k, 1)) translate(var(--ox, 0px), var(--oy, 0px)); cursor: pointer; }
.mp-mk > circle:first-of-type { fill: var(--scene-bg); stroke: var(--line-ctl); stroke-width: 0.06; }
.mp-mk:is(:hover, :focus-visible, .is-sel) > circle:first-of-type { stroke: var(--accent); stroke-width: 0.1; }
.mp-mk:focus { outline: none; }
.mp-mk.is-off { opacity: 0.4; }
/* What's had, with "Show Completed" (design/77, 3 A): dimmed, a tick in --good on its upper right;
   whole again on hover or picked. */
.mp-mk.is-had > :not(.mp-tick) { opacity: var(--mp-had-dim); }
.mp-mk.is-had:is(:hover, :focus-visible, .is-sel) > * { opacity: 1; }
.mp-tick { pointer-events: none; }
.mp-tick circle { fill: var(--scene-bg); stroke: var(--good); stroke-width: 0.05; }
.mp-tick path { fill: none; stroke: var(--good); stroke-width: 0.06; stroke-linecap: round; stroke-linejoin: round; }
/* What the game took away (design/86, 1 A), shown with what's done: dimmed the same, a cross in
   --muted where the tick goes. */
.mp-mk.is-lost > :not(.mp-cross) { opacity: var(--mp-had-dim); }
.mp-mk.is-lost:is(:hover, :focus-visible, .is-sel) > * { opacity: 1; }
.mp-cross { pointer-events: none; }
.mp-cross circle { fill: var(--scene-bg); stroke: var(--muted); stroke-width: 0.05; }
.mp-cross path { fill: none; stroke: var(--muted); stroke-width: 0.06; stroke-linecap: round; }
.mp-glyph { color: var(--ink-strong); }
/* Behind the picture, the inventory's pale plate (--halo-plate), so a dark portrait or a thin
   needle reads on the black disc. */
.mp-plate { fill: url(#mp-plate); pointer-events: none; }
#mp-plate stop { stop-color: var(--map-plate); }
#mp-plate stop:nth-child(2) { stop-color: var(--map-plate-mid); }
#mp-plate stop:last-child { stop-color: transparent; }
/* The areas' names, keeping their size on screen, with the map's black round their letters. */
.mp-name { font-family: var(--font-display); font-size: calc(var(--k, 1) * 0.5px); letter-spacing: var(--track-caps); text-transform: uppercase;
  text-anchor: middle; dominant-baseline: middle; fill: color-mix(in oklab, var(--ink) 72%, transparent);
  stroke: var(--scene-bg); stroke-width: calc(var(--k, 1) * 0.12px); paint-order: stroke; cursor: pointer; }
.mp-name:focus { outline: none; }
.mp-name:is(:hover, :focus-visible, .is-sel) { fill: var(--ink-strong); }
.mp-name.is-unmapped { fill: color-mix(in oklab, var(--muted) 70%, transparent); }
/* From afar, a name that would overlap a larger area's waits for the zoom (js/app-map.js unclutter). */
.mp-name.is-crowded { display: none; }
/* The caches and walls from afar: per room, a mark per layer with how many in a corner. */
.mp-cluster { pointer-events: none; }
.mp-count circle { fill: var(--scene-bg); stroke: var(--line-ctl); stroke-width: 0.04; }
.mp-count text { font-family: var(--font-num); font-size: 0.26px; text-anchor: middle; dominant-baseline: middle; fill: var(--ink-strong); }
.mp-view.is-close .mp-cluster { display: none; }
/* The caches and walls: so many they're drawn close up only; from afar, a count per room. */
.mp-view:not(.is-close) .is-fine { display: none; }
/* Close up, a mark far out of the view (js/app-map.js cull). */
.mp-svg .is-away { display: none; }
/* An area whose map isn't bought yet. */

/* The Map's admin mode (js/app-map-admin.js, #…&admin=1; design/30-map-admin-variants.html, all A):
   a band under the map, above the legend; on the map, a moved mark's way from the data's point
   (in the accent while unsaved), a hidden one faint and struck through. */
.mp-adm { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); gap: var(--sp-3) var(--sp-8); align-items: start; padding: var(--sp-4) 0; border-bottom: 1px solid var(--inv-line); font-size: var(--fs-xs); color: var(--ink-2); }
.mp-adm-col { display: grid; gap: var(--sp-3); align-content: start; min-width: 0; }
.mp-adm-h { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-1) var(--sp-3); }
.mp-adm-h b { font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.mp-adm-h span { font-family: var(--font-num); font-variant-numeric: tabular-nums; color: var(--muted); }
.mp-adm .is-pending { color: var(--accent); }
.mp-adm-sel { display: grid; grid-template-columns: var(--sp-8) minmax(0, 1fr); gap: var(--sp-2) var(--sp-3); align-items: start; }
.mp-adm-art { display: grid; place-items: center; height: var(--sp-8); }
.mp-adm :is(.mp-dot, .mp-pin) { position: static; margin: 0; --mp-s: 24px; }
.mp-adm-name { display: block; font-family: var(--font-display); font-weight: 400; font-size: var(--fs-sm); color: var(--ink-strong); }
.mp-adm :is(code, .mp-adm-xy, .mp-adm-in) { font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.mp-adm code { font-size: var(--fs-2xs); color: var(--muted); word-break: break-all; }
.mp-adm-body { grid-column: 1 / -1; display: grid; gap: var(--sp-2); }
.mp-adm-xy { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-1) var(--sp-3); margin: 0; }
.mp-adm-xy dt { color: var(--muted); }
.mp-adm-xy dd { margin: 0; color: var(--ink); }
.mp-adm-xy small { color: var(--muted); }
.mp-adm-acts, .mp-adm-foot { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); align-items: center; }
.mp-adm :is(.mp-adm-acts, .mp-adm-foot) .text-btn { font-size: var(--fs-xs); }
.mp-adm-foot .mp-adm-hint { margin-left: auto; }
.mp-adm-state { color: var(--muted); font-style: italic; }
.mp-adm-hint { font-size: var(--fs-2xs); color: var(--muted); }
.mp-adm-in { width: 100%; min-width: 0; padding: var(--sp-1) var(--sp-2); background: var(--scene-bg); color: var(--ink); border: 1px solid var(--line-ctl); font-size: var(--fs-xs); }
.mp-adm-in:focus { outline: none; border-color: var(--accent-line); }
.mp-adm-text { min-height: calc(var(--sp-16) * 2); resize: vertical; }
.mp-adm-list details { border-top: 1px solid color-mix(in oklab, var(--filigree) 10%, transparent); padding-top: var(--sp-1); }
.mp-adm-list summary { cursor: pointer; font-family: var(--font-num); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--label); }
.mp-adm-list summary span { color: var(--muted); font-weight: 400; letter-spacing: 0; }
.mp-adm-list ul { list-style: none; margin: var(--sp-1) 0 0; padding: 0; display: grid; gap: var(--sp-1); }
.mp-adm-list li button { display: grid; grid-template-columns: var(--sp-6) minmax(0, 1fr) auto; gap: var(--sp-2); align-items: center; width: 100%; padding: var(--sp-1); text-align: left; color: var(--ink-2); }
.mp-adm-list li button:hover { background: var(--veil); color: var(--ink-strong); }
.mp-adm-list li :is(.mp-dot, .mp-pin) { --mp-s: 20px; }
.mp-adm-ways { pointer-events: none; }
.mp-adm-ways line { stroke: var(--accent); stroke-width: 1.5; stroke-dasharray: 4 3; vector-effect: non-scaling-stroke; }
.mp-adm-ways circle { fill: none; stroke: var(--accent); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.mp-mk.is-hidden { opacity: 0.35; }
.mp-adm-strike { stroke: var(--ink-strong); stroke-width: 0.09; stroke-linecap: round; }
/* What's missing closest to your bench: a short list under the map. */
.mp-near-list { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: grid; gap: var(--sp-1); }
.mp-near-list li { display: grid; grid-template-columns: var(--sp-6) minmax(0, 1fr) auto auto; align-items: center; gap: var(--sp-3); font-size: var(--fs-sm); color: var(--ink); }
.mp-near-list .mp-dot, .mp-near-list .mp-pin { position: static; margin: 0; --mp-s: 20px; }
.mp-near-list li > img { width: 18px; height: 20px; object-fit: contain; }
.mp-near-list .text-btn { justify-self: start; text-align: left; font-size: var(--fs-sm); }
.hj-where { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }
/* The entry's way onto the Map (design/69): the lists' round pin at the end of the line that says
   where it is, both centred; alone in that place when the entry has no area. */
.hj-entry .has-pin { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--sp-1) var(--sp-3); }
.hj-entry > .pg-onmap { justify-self: center; }
.mp-near-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mp-near-area { font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; }
/* A place: the game's own map pin; a gauntlet, the badge with its needles crossed, in the slots'
   red; dimmed while your game hasn't opened it. */
.mp-pin {
  position: absolute; z-index: 2; display: grid; place-items: center; width: var(--mp-s, 22px); height: var(--mp-s, 22px);
  margin: calc(var(--mp-s, 22px) / -2) 0 0 calc(var(--mp-s, 22px) / -2); object-fit: contain; filter: drop-shadow(0 1px 2px var(--badge-shadow));
}
span.mp-pin { border-radius: 50%; background: var(--scene-bg); border: 1px solid var(--line-ctl); color: var(--ink-strong); }
.mp-pin svg { width: 55%; height: 55%; }
.mp-pin.is-gauntlet img { width: 80%; height: 80%; object-fit: contain; }
.mp-pin.is-badge img { width: calc(var(--mp-s, 22px) * 0.8); height: calc(var(--mp-s, 22px) * 0.8); object-fit: contain; }
.mp-pin.is-off { filter: var(--missing-soft) drop-shadow(0 1px 2px var(--badge-shadow)); opacity: var(--missing-soft-opacity); }
/* Hornet at her bench: the .hn sprite (sitting; running while js/app-hornet.js walks her from the
   previous bench), her feet and her middle on the point. */
.mp-hornet.hn {
  --hn-h: var(--hn-map); z-index: 2; translate: calc(-1 * var(--hn-map) * 62 / 120) calc(-1 * var(--hn-map) * 116 / 120);
  filter: drop-shadow(0 0 8px color-mix(in oklab, var(--spotlight) 60%, transparent)); transition: none;
}
