/* BLACKGLASS game screen. The story is the main column; the side panel holds the map, pack and
   journal and can be hidden. Phones get one view at a time with a bottom tab bar.
   Map styles live in map.css. Reading settings set --story-font, --story-size and --story-leading. */

:root {
  --story-font: var(--font-serif);
  --story-size: 19px;
  --story-leading: 1.62;
}

body.game { overflow: hidden; height: 100vh; height: 100dvh; padding: 0; }

/* Safe areas (notched phones, mostly when installed as an app): the shell keeps the top bar, the
   story, the side panel and the travel veil clear of the notch and rounded corners. At the foot,
   the phone tab bar pads itself (so its background runs under the home indicator); wider screens
   pad the shell instead. */
.game-shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  height: 100vh; height: 100dvh;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

/* ---- top bar ------------------------------------------------------------------------------ */
.game-bar {
  display: flex; align-items: center; gap: 0.6rem; min-width: 0;
  padding: 0.4rem 0.9rem; border-bottom: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.08em; color: var(--muted);
  background: var(--bg); min-height: 2.9rem;
}
.game-bar .wordmark { font-size: 0.75rem; text-decoration: none; }
.game-bar .fragment-name { text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--faint); }
.game-bar .spacer { flex: 1; }
.icon-btn {
  background: none; border: 1px solid transparent; color: var(--muted); border-radius: var(--radius);
  font-family: var(--font-mono); font-size: 0.9rem; min-width: 2.3rem; height: 2.3rem; cursor: pointer;
}
.icon-btn:hover { border-color: var(--line-strong); color: var(--text-strong); }
/* Music on/off (game-view.js showMusic): lit when on; dimmed and struck through when off. Opens
   the volume popover below (.music-pop) — it does not itself toggle the setting any more. */
.music-btn { position: relative; font-size: 1.05rem; font-family: var(--font-sans); }
.music-btn[aria-pressed="true"] { color: var(--accent); }
.music-btn[aria-pressed="false"] { color: var(--faint); }
.music-btn[aria-pressed="false"]::after {
  content: ''; position: absolute; left: 30%; right: 30%; top: 50%; height: 1.5px;
  background: currentColor; transform: rotate(-45deg); pointer-events: none;
}
/* The level indicator: a hairline under the note that fills with the current volume (width set
   from game-view.js showMusic(), via the CSSOM — inline style="" attributes are blocked by CSP). */
.music-level {
  position: absolute; left: 22%; right: 22%; bottom: 5px; height: 2px; border-radius: 1px;
  background: var(--line-strong); overflow: hidden; pointer-events: none;
}
.music-level > span { display: block; height: 100%; width: 0; background: currentColor; }

/* ---- music volume popover ------------------------------------------------------------------- */
/* position: fixed (not absolute), positioned from the ♪ button's rect in game-view.js
   positionMusicPopover() — the same pattern as the map's Key/Places popovers (map.css). */
.music-pop {
  position: fixed; z-index: 50; width: min(17rem, calc(100vw - 1.5rem));
  background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 0.7rem 0.9rem; box-shadow: 0 8px 22px rgba(0, 0, 0, 0.5);
  font-family: var(--font-sans); text-transform: none; letter-spacing: normal;
}
.music-pop[hidden] { display: none; }
.music-pop-now { margin: 0 0 0.5rem; font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.06em; color: var(--accent); }
.music-pop-now[hidden] { display: none; }
.music-pop-label {
  display: block; margin-bottom: 0.4rem; font-family: var(--font-mono); font-size: 0.6875rem;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted);
}
.music-pop-row { display: flex; align-items: center; gap: 0.7rem; }
/* A generous track and thumb: an easy target from 360px phones up. */
.music-pop-row input[type="range"] {
  flex: 1; accent-color: var(--accent); height: 2rem; cursor: pointer; touch-action: none;
}
.music-pop-out { font-family: var(--font-mono); font-size: 0.8125rem; color: var(--text); min-width: 2.6em; text-align: right; }
.music-pop-hint { margin: 0.5rem 0 0; font-size: 0.75rem; color: var(--faint); }
.save-status { white-space: nowrap; display: inline-flex; align-items: center; gap: 0.4rem; }
.save-status .short { display: none; }
.save-status::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--faint); }
.save-status[data-state="idle"]::before { display: none; }
.save-status[data-state="saved"]::before { background: var(--ok); }
.save-status[data-state="saving"]::before { background: var(--warn); animation: pulse 1s infinite alternate; }
.save-status[data-state="error"]::before, .save-status[data-state="conflict"]::before, .save-status[data-state="stale"]::before { background: var(--danger); }
.save-status[data-state="local"]::before { background: var(--accent); }
.save-status[data-state="error"], .save-status[data-state="conflict"], .save-status[data-state="stale"] { color: var(--danger); }
@keyframes pulse { from { opacity: 0.3; } to { opacity: 1; } }

/* ---- main layout --------------------------------------------------------------------------- */
/* The side panel takes a share of the width (--side-frac, set by game-view.js once the player drags
   the divider; 1/3 by default, so the story has 2/3), never narrower than 18rem nor leaving the
   story less than 22.5rem. Declared here, not on :root, so 100% and --side-frac resolve against the
   game area (the travel expansion below uses the same value). */
.game-main {
  --side-width: clamp(18rem, calc((100% - 6px) * var(--side-frac, 0.33333)), calc(100% - 6px - 22.5rem));
  display: grid; min-height: 0;
  grid-template-columns: minmax(0, 1fr) 6px var(--side-width);
}
.game-main[data-panel="closed"] { grid-template-columns: minmax(0, 1fr) 2.6rem; }
.game-main[data-panel="closed"] .pane-splitter { display: none; }
.pane-splitter {
  cursor: col-resize; background: var(--line); position: relative; touch-action: none; z-index: 5;
  transition: background 0.15s;
}
.pane-splitter::after { content: ''; position: absolute; inset: 0 -5px; }
.pane-splitter:hover, .pane-splitter.dragging, .pane-splitter:focus-visible { background: var(--accent); outline: none; }
body.resizing { cursor: col-resize; user-select: none; }
body.resizing canvas, body.resizing .pane { pointer-events: none; }
.game-main[data-panel="closed"] .pane-side { display: none; }
.panel-rail { display: none; }
.game-main[data-panel="closed"] .panel-rail {
  display: flex; flex-direction: column; border-left: 1px solid var(--line); background: var(--bg-raised);
}
.panel-rail button {
  writing-mode: vertical-rl; background: none; border: 0; border-bottom: 1px solid var(--line); color: var(--muted); cursor: pointer;
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.2em; text-transform: uppercase; padding: 1rem 0;
}
.panel-rail button:hover { color: var(--text-strong); background: var(--panel); }

.pane { min-height: 0; min-width: 0; overflow: hidden; display: flex; flex-direction: column; position: relative; }
.pane-side { background: var(--bg-raised); }

/* ---- story ------------------------------------------------------------------------------------ */
.story-head {
  display: flex; justify-content: space-between; gap: 1rem; align-items: baseline;
  padding: 0.55rem clamp(1rem, 4vw, 3rem); border-bottom: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--muted);
}
.story-head .here-name { color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.story-head .here-time { white-space: nowrap; color: var(--faint); }

.story-scroll {
  position: relative; flex: 1; overflow-y: auto; overscroll-behavior: contain;
  padding: 0 clamp(1rem, 5vw, 4rem);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 2.2rem, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 2.2rem, #000 100%);
}
/* A scene's picture kept above the story while the scene lasts (BG-213; game-view.js syncPin()): a compact
   strip outside the scrolling pane, so the page view fits its pages below it and the choices keep their room. */
.story-pin { flex: none; padding: 0.5rem clamp(1rem, 5vw, 4rem) 0.4rem; border-bottom: 1px solid var(--line); background: var(--bg); }
.story-pin[hidden], [data-place-art="0"] .story-pin { display: none; }
.story-pin .place-art { margin: 0; }
.story-pin .place-art.epic[data-frame="wide"] img { max-width: 100%; }
.pa-pinned { display: none !important; }
.story-inner { max-width: var(--gui-story-width, 42em); margin: 0 auto; font-family: var(--story-font); font-size: var(--story-size); line-height: var(--story-leading); }
.story-tail { height: 45vh; }
/* The page view (BG-202; game-view.js pageOn(), fitPage()): the tail is a whole pane, so a page can always be
   scrolled to the top of the pane whatever follows it; the choices stay pinned at the pane's foot when they
   would fall below it, unless they are more than seven tenths of the pane: then they are not pinned but follow
   the words (.choices-flow, set by the view) and the pane scrolls as one, with no box inside a box (BG-202,
   Jake 2026-10-07); the pane's height is --page-h, set by the view. A page's words are fitted above the Continue button or the choices. */
[data-page-view="1"] .story-tail { height: var(--page-h, 45vh); }
[data-page-view="1"] .choices {
  position: sticky; bottom: 0; z-index: 2; background: var(--bg); padding-bottom: 0.7rem;
}
[data-page-view="1"] .choices.choices-flow { position: static; }
/* (nothing is read below tall choices that follow the words: no blank page after them) */
[data-page-view="1"] .choices.choices-flow:not(.waiting) ~ .story-tail { height: 0; }

.log p { margin: 0 0 0.95em; }
.passage { padding-top: 1.6rem; transition: opacity 0.6s ease; }
/* Once a new passage begins, the earlier ones decay slowly until they are hardly there. Scrolling
   back up to them (game-view.js adds .reading-back) brings them back quickly. */
[data-dim-past="1"] .passage.past { opacity: 0.1; transition: opacity 5s ease-in; }
[data-dim-past="1"] .story-scroll.reading-back .passage.past { opacity: 1; transition: opacity 0.4s ease-out; }
[data-dim-past="1"] .passage.past:hover { opacity: 0.8; transition: opacity 0.4s ease-out; }
.passage .entry { animation: rise 0.35s ease-out both; }
.passage.past .entry, .passage .entry.pending { animation: none; }
.entry.pending { display: none; }
/* Reading a paragraph at a time (game-view.js paceSegments()): what is still held back is not shown
   (nor read by screen readers) until it is reached; each later paragraph rises in as it comes. The
   animation is cut by reduced motion (base.css) and ?anim=0 (html.no-anim). */
.passage .held, .passage p.pending { display: none; }
/* A passage's picture anchored at a later paragraph (placeart-view.js anchorPassageArt) is not shown
   before that paragraph: while it is held back or not yet typed. */
.passage figure.passage-art.pa-wait, .passage figure.passage-art:has(+ p.held), .passage figure.passage-art:has(+ p.pending),
.passage figure.passage-art:has(+ figure.passage-art + p.held), .passage figure.passage-art:has(+ figure.passage-art + p.pending) { display: none; }
.passage p.paced { animation: rise 0.45s ease-out both; }
.passage.past p.paced, .passage p.paced.pending { animation: none; }
.story-continue {
  display: inline-flex; align-items: center; gap: 0.5rem; margin: 0.1rem 0 0; padding: 0.45rem 0.8rem 0.45rem 0.1rem;
  background: none; border: 0; border-radius: var(--radius); color: var(--faint); cursor: pointer;
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.18em; text-transform: uppercase;
}
.story-continue:hover { color: var(--accent); }
.story-continue:focus-visible { color: var(--accent); outline: 1px solid var(--line-strong); outline-offset: 2px; }
.story-continue[hidden] { display: none; }
.story-continue-mark { font-size: 0.95rem; letter-spacing: 0; animation: nudge 2.4s ease-in-out infinite; }
@keyframes nudge { 0%, 100% { transform: translateY(0); opacity: 0.7; } 50% { transform: translateY(2px); opacity: 1; } }
@keyframes rise { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }

.caret { display: inline-block; width: 0.5em; height: 1em; margin-left: 1px; vertical-align: -0.12em; background: var(--accent); opacity: 0.7; animation: blink 1s steps(1) infinite; }
@keyframes blink { 50% { opacity: 0; } }

.log-place {
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 400; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--muted); margin: 0.6rem 0 1rem; display: flex; align-items: center; gap: 0.75rem;
}
.log-place::after { content: ''; flex: 1; height: 1px; background: var(--line); }
/* Place art (js/ui/placeart-view.js, docs/PLACEART.md): the picture of a place under its name, drawn
   at a whole number of device pixels per picture pixel (sized by the script), hidden by the setting. */
.place-art { margin: -0.2rem 0 1.2rem; display: flex; justify-content: center; }
.place-art img { display: block; max-width: 100%; height: auto; image-rendering: crisp-edges; image-rendering: pixelated; }
[data-place-art="0"] .place-art { display: none; }
/* An epic vista (BG-140): its wide framing may be wider than the text column, centred over it, up to the
   story panel's width (the script sizes it); its phone framing, in a portrait window, is as wide as the
   column, above the words. */
.place-art.epic[data-frame="wide"] img { max-width: none; }
/* An animated picture (BG-143, js/ui/placeart-player.js): a canvas laid exactly over the picture plays its moving
   parts; the image stays under it for its alt text. */
.place-art { position: relative; }
.place-art-motion { position: absolute; display: block; image-rendering: crisp-edges; image-rendering: pixelated; pointer-events: none; }
.place-art.pa-moving > img { opacity: 0; }
.log-you { font-family: var(--font-mono); font-size: 0.78em; color: var(--faint); margin: 0 0 0.9rem !important; letter-spacing: 0.02em; }
.log-you::before { content: '› '; color: var(--accent); }
.log-verse { font-style: italic; padding-left: 1.4em; border-left: 1px solid var(--line-strong); color: var(--text-strong); }
.log-insight { margin: 1.3em 0; padding: 0.15em 0 0.15em 1em; border-left: 2px solid var(--accent); color: var(--text-strong); }
.log-insight p { margin: 0 !important; font-style: italic; }
.kicker { display: block; font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--accent); margin-bottom: 0.3rem; font-style: normal; }
.log-reinterpret { font-family: var(--font-mono); font-size: 0.8em; margin: 0.8em 0 !important; color: var(--accent-strong); }
.log-reinterpret s { color: var(--faint); text-decoration-thickness: 1px; }
.log-reinterpret .arrow { color: var(--faint); margin: 0 0.5rem; }
.log-memory { font-family: var(--font-mono); font-size: 0.78em; color: var(--warn); }
.log-memory::before { content: '✦ '; }
.log-time { font-style: italic; color: var(--muted); }
.log-notice { font-family: var(--font-mono); font-size: 0.78em; color: var(--muted); border: 1px dashed var(--line-strong); padding: 0.6rem 0.8rem; }

.glass-screen {
  margin: 1.4em 0; padding: 1.4rem 1rem; background: #030404; border: 1px solid #1b2320;
  font-family: var(--font-mono); font-size: clamp(0.8rem, 2.2vw, 1.05rem); letter-spacing: 0.12em;
  color: #1c2622; text-align: center; line-height: 1.9; white-space: pre; overflow: hidden;
}
.glass-screen.live { animation: glass-wake 3.4s steps(1, end) both; }
/* A fully fed glass (the room of glass) comes up and stays lit: "sharp and steady, every mark whole". */
.glass-screen.steady { color: #cfeee0; text-shadow: 0 0 6px rgba(160, 255, 210, 0.45), 0 0 18px rgba(120, 220, 180, 0.2); }
.glass-screen.steady.live { animation: glass-wake-steady 1.6s steps(1, end) both; }
@keyframes glass-wake-steady { 0% { opacity: 0; } 6% { opacity: 1; } 9% { opacity: 0.4; } 14% { opacity: 1; } 100% { opacity: 1; } }
@keyframes glass-wake {
  0% { opacity: 0; } 4% { opacity: 1; color: #cfeee0; text-shadow: 0 0 6px rgba(160, 255, 210, 0.55), 0 0 18px rgba(120, 220, 180, 0.25); }
  6% { opacity: 0.2; } 9% { opacity: 1; } 40% { opacity: 0.85; } 42% { opacity: 1; }
  70% { opacity: 1; color: #cfeee0; } 78% { opacity: 0.6; } 84% { opacity: 0.25; } 92% { opacity: 0.08; }
  100% { opacity: 1; color: #1c2622; text-shadow: none; }
}

/* What screen readers hear of each paragraph (game-view.js announce()): never part of a copy of the
   story, where it read as the passage again run together (BG-200). */
.story-announce { user-select: none; -webkit-user-select: none; }

.skip-hint {
  position: absolute; right: 1rem; bottom: 0.8rem; background: var(--panel); border: 1px solid var(--line); color: var(--faint);
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.08em; padding: 0.35rem 0.6rem; border-radius: var(--radius); cursor: pointer;
}
.skip-hint[hidden] { display: none; }

/* ---- choices ------------------------------------------------------------------------------------ */
.choices { margin-top: 1.4rem; padding-top: 1rem; border-top: 1px solid var(--line); transition: opacity 0.4s ease; }
.choices:empty { display: none; }
.choices.waiting { visibility: hidden; opacity: 0; }
.choice-group + .choice-group { margin-top: 0.9rem; }
.choice-group h3 { margin: 0 0 0.3rem; font-size: 0.625rem; letter-spacing: 0.22em; color: var(--faint); }
.choice {
  display: flex; width: 100%; text-align: left; gap: 0.8rem; align-items: baseline;
  background: none; border: 0; border-left: 2px solid transparent; color: var(--text);
  font: inherit; font-size: 0.94em; line-height: 1.4; padding: 0.45rem 0.7rem; cursor: pointer; border-radius: 0 var(--radius) var(--radius) 0;
}
.choice:hover:not(:disabled), .choice:focus-visible { background: var(--panel); border-left-color: var(--accent); color: var(--text-strong); outline: none; }
.choice .key { font-family: var(--font-mono); font-size: 0.72rem; color: var(--faint); min-width: 1rem; }
.choice .choice-label { flex: 1; }
.choice .meta { font-family: var(--font-mono); font-size: 0.72rem; color: var(--faint); white-space: nowrap; padding-left: 0.75rem; }
.choice-go .choice-label { color: var(--muted); }
.choice-go:hover .choice-label { color: var(--text-strong); }
.choice:disabled { color: var(--faint); cursor: not-allowed; }
.choice .why { display: block; font-size: 0.85em; color: var(--faint); font-style: italic; }
/* The way forward in a scene of waiting (content `onward: true`): a quiet verdigris edge and key. */
.choice.is-onward { border-left-color: rgba(159, 183, 170, 0.45); }
.choice.is-onward .key { color: var(--accent); }
.choice.is-onward .choice-label { color: var(--text-strong); }

/* ---- side panel ---------------------------------------------------------------------------------- */
.side-tabs { display: flex; border-bottom: 1px solid var(--line); }
.side-tabs button {
  flex: 1; background: none; border: 0; border-bottom: 2px solid transparent; color: var(--muted);
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase;
  padding: 0.75rem 0.5rem; cursor: pointer;
}
.side-tabs button[aria-selected="true"] { color: var(--text-strong); border-bottom-color: var(--accent); }
.side-tabs .panel-toggle { flex: 0 0 2.6rem; font-size: 1rem; letter-spacing: 0; border-left: 1px solid var(--line); }
/* The views' tabs (BG-252): each word fits its tab at the usual widths (the type closes up with the panel's width: 1280
   to 1920, stacked or not), and the unread dot is never clipped: it follows the word as a flex item of its own, and
   only the word gives way if a panel is dragged narrower still. */
.side-tabs { container-type: inline-size; }
.side-tabs button[data-tab] {
  min-width: 0; overflow: hidden; white-space: nowrap; display: inline-flex; align-items: center; justify-content: center;
  flex: 1 1 auto; padding-left: 0.35rem; padding-right: 0.35rem;
  font-size: clamp(0.58rem, 2.45cqi, 0.72rem); letter-spacing: clamp(0.02em, calc(0.4cqi - 0.04em), 0.16em);
}
.side-tabs button[data-tab] .tab-label { min-width: 0; overflow: hidden; text-overflow: clip; }
.side-tabs button[data-tab].has-new::after { flex: none; }
.side-tabs button:hover, .side-tabs button:focus-visible { color: var(--text-strong); outline: none; }
.side-tabs button:focus-visible { box-shadow: inset 0 0 0 1px var(--accent); }
.side-tabs .side-tool { flex: 0 0 2.2rem; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-left: 1px solid var(--line); }
.side-tabs .side-tool[hidden] { display: none; }
.side-tabs .side-tool[aria-pressed="true"] { color: var(--accent); }
.side-tool svg, .slot-btn svg { fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round; }
.side-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.side-page { display: none; padding: 1rem; overflow: auto; flex: 1; min-height: 0; font-size: calc(var(--story-size) * 0.84); }
.side-page.active { display: flex; flex-direction: column; }
.side-page[data-page="journal"].active, .side-page[data-page="pack"].active { display: block; }
.side-page h2 { margin: 1.4rem 0 0.6rem; font-size: 0.72rem; letter-spacing: 0.18em; color: var(--muted); }
.side-page h2:first-child { margin-top: 0; }

/* ---- the stacked side panel (wide screens only) ---------------------------------------------------
   Two views at once, map over pack by default, each under a slim header with Collapse and Expand,
   split by a draggable divider (game-view.js applyLayout(): data-slot on the pages, data-collapsed
   and data-expanded on the panel, --stack-rows for the grid). The pages never move in the DOM; the
   grid places them. Phones keep one view at a time, so none of this applies below 62rem. */
.slot-head, .slot-splitter { display: none; }
@media (min-width: 62.01rem) {
  .pane-side[data-layout="stack"] .side-body {
    display: grid; grid-template-columns: minmax(0, 1fr); align-content: start;
    grid-template-rows: var(--stack-rows, auto minmax(0, 1fr) 6px auto minmax(0, 1fr));
  }
  .pane-side[data-layout="stack"] .slot-head {
    display: flex; align-items: center; gap: 0.15rem; min-width: 0;
    padding: 0 0.35rem 0 1rem; height: 1.9rem; border-bottom: 1px solid var(--line); background: var(--bg-raised);
    font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--muted);
  }
  .pane-side[data-layout="stack"] [data-slot-head="a"] { grid-row: 1; }
  .pane-side[data-layout="stack"] [data-slot-head="b"] { grid-row: 4; }
  /* Map or Recollections chosen (BG-157): one view fills the panel, without a header (the tab names it). */
  .pane-side[data-layout="stack"][data-solo] .slot-head { display: none; }
  .slot-head .slot-name { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; }
  .slot-btn {
    flex: 0 0 auto; width: 1.6rem; height: 1.6rem; padding: 0; display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0; border-radius: 2px; color: var(--faint); cursor: pointer;
  }
  .slot-btn:hover, .slot-btn:focus-visible { color: var(--text-strong); background: var(--panel); outline: none; }
  .slot-btn[aria-pressed="true"] { color: var(--accent); }
  .slot-collapse svg { transition: transform 0.15s; }
  .slot-collapse[aria-expanded="false"] svg { transform: rotate(-90deg); }
  .pane-side[data-layout="stack"] .slot-splitter {
    display: block; grid-row: 3; cursor: row-resize; background: var(--line); position: relative; touch-action: none; z-index: 5;
    transition: background 0.15s;
  }
  .slot-splitter::after { content: ''; position: absolute; inset: -5px 0; }
  .slot-splitter:hover, .slot-splitter.dragging, .slot-splitter:focus-visible { background: var(--accent); outline: none; }
  .pane-side[data-layout="stack"] .side-page { display: none; padding: 0.75rem 1rem; }
  .pane-side[data-layout="stack"] .side-page[data-slot="a"] { display: block; grid-row: 2; }
  .pane-side[data-layout="stack"] .side-page[data-slot="b"] { display: block; grid-row: 5; }
  .pane-side[data-layout="stack"] .side-page[data-page="map"][data-slot] { display: flex; flex-direction: column; }
  /* The map fits whatever height its slot has (it asks for most of the screen on its own). */
  .pane-side[data-layout="stack"] .map-view { min-height: 0; }
  .pane-side[data-layout="stack"] .map-frame { min-height: 5rem; }
  .pane-side[data-layout="stack"][data-collapsed~="a"] .side-page[data-slot="a"],
  .pane-side[data-layout="stack"][data-collapsed~="b"] .side-page[data-slot="b"],
  .pane-side[data-layout="stack"][data-collapsed~="a"] .slot-splitter,
  .pane-side[data-layout="stack"][data-collapsed~="b"] .slot-splitter,
  .pane-side[data-layout="stack"][data-expanded="a"] [data-slot-head="b"],
  .pane-side[data-layout="stack"][data-expanded="a"] .side-page[data-slot="b"],
  .pane-side[data-layout="stack"][data-expanded="b"] [data-slot-head="a"],
  .pane-side[data-layout="stack"][data-expanded="b"] .side-page[data-slot="a"],
  .pane-side[data-layout="stack"][data-expanded="a"] .slot-splitter,
  .pane-side[data-layout="stack"][data-expanded="b"] .slot-splitter { display: none; }
  body.resizing-rows { cursor: row-resize; user-select: none; }
  body.resizing-rows canvas, body.resizing-rows .pane-story, body.resizing-rows .side-page { pointer-events: none; }
}

/* ---- journal ------------------------------------------------------------------------------------ */
.journal-tools { position: sticky; top: -1rem; background: var(--bg-raised); padding: 0 0 0.8rem; margin: 0 0 0.4rem; z-index: 2; display: flex; align-items: center; gap: 0.5rem; }
.journal-tools .journal-search { flex: 1 1 auto; min-width: 0; }
/* Collapse all (Jake, 2026-10-23): folds every open entry; no "expand all". Touch-sized. */
.journal-collapse, .discovery-collapse { flex: none; min-height: 44px; min-width: 44px; white-space: nowrap; }
.journal-search { font-size: 0.9rem !important; }
.entries { list-style: none; padding: 0; margin: 0; }
.entries li { padding: 0.55rem 0; border-bottom: 1px solid var(--line); }
.entries .title { font-family: var(--font-mono); font-size: 0.8rem; letter-spacing: 0.06em; color: var(--text-strong); }
.entries .former { font-family: var(--font-mono); font-size: 0.72rem; color: var(--faint); }
.entries .former s { margin-right: 0.4rem; }
.entries p { margin: 0.25rem 0 0; }
.entries .standing { color: var(--accent); font-style: italic; }
.entries mark { background: rgba(199, 154, 91, 0.3); color: var(--text-strong); border-radius: 2px; }

/* Topics and entries collapsed until opened (BG-153): each is a button (keyboard, aria-expanded) with a
   small chevron; an open topic shows its entries, an open entry its words. */
.jsec { border-bottom: 1px solid var(--line); }
.side-page .jsec h2 { margin: 0; }
.jsec-toggle, .jentry-toggle {
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit; background: none; border: 0;
  margin: 0; width: 100%; display: flex; align-items: center; gap: 0.1rem; text-align: left; cursor: pointer; border-radius: 2px;
}
.jsec-toggle { padding: 0.75rem 0; }
.jentry-toggle { padding: 0; }
.jsec-toggle::before, .jentry-toggle::after {
  content: ''; flex: none; width: 0.38rem; height: 0.38rem; border: solid currentColor; border-width: 0 1.5px 1.5px 0;
  transform: rotate(-45deg); opacity: 0.7; transition: transform 160ms ease;
}
.jsec-toggle::before { margin-right: 0.6rem; }
.jentry-toggle::after { margin-left: auto; margin-right: 0.3rem; width: 0.5rem; height: 0.5rem; border-width: 0 2px 2px 0; color: var(--muted); opacity: 0.95; }
.jentry-toggle { min-height: 1.9rem; gap: 0.5rem; }
.jsec-toggle[aria-expanded="true"]::before, .jentry-toggle[aria-expanded="true"]::after { transform: rotate(45deg); }
.jsec-toggle:hover, .jentry-toggle:hover .title { color: var(--text-strong); }
.jsec-toggle:focus-visible, .jentry-toggle:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
.jsec > .entries { padding: 0 0 0.6rem 0.98rem; }
.jsec > .entries > li:last-child { border-bottom: 0; }
.jentry-toggle .title { flex: 1 1 auto; min-width: 0; }
.jentry-body[hidden] { display: none; }
.jentry-map { margin-top: 0.35rem; }
.jentry-where { color: var(--faint); font-style: italic; font-size: 0.9em; }

/* New journal entries: a small glowing dot until they have been seen. It sits on the Recollections
   (journal) buttons (tab, rail, phone tab), on each section with something new and on each new entry.
   Once the entry is on screen, .is-fading shrinks and fades the dot over 600 ms (DOT_FADE_MS in
   journal-view.js), then the class goes. Reduced motion: no pulse and a plain fade. ?anim=0
   (html.no-anim) cuts both. Warm amber-gold (Jake, 2026-10-01, BG-145): it reads on the dark panels
   and is not the red of a warning. */
:root { --new-dot: #e8b65a; --new-dot-glow: rgba(232, 182, 90, 0.75); }
.new-dot,
.has-new::after,
.entries li.is-new::before {
  content: ''; display: inline-block; width: 0.5rem; height: 0.5rem; border-radius: 50%;
  background: var(--new-dot); box-shadow: 0 0 0.45rem 0.05rem var(--new-dot-glow);
  animation: new-dot-pulse 2.8s ease-in-out infinite;
  transition: opacity 600ms ease, transform 600ms ease;
}
.new-dot { margin-left: 0.55em; vertical-align: 0.1em; }
.has-new::after { margin-left: 0.45em; vertical-align: 0.15em; }
.entries li.is-new { position: relative; }
.entries li.is-new::before { position: absolute; left: -0.8rem; top: 0.95em; }
.is-fading > .new-dot, h2.is-fading .new-dot, .has-new.is-fading::after, .entries li.is-fading::before {
  opacity: 0; transform: scale(0.2); animation: none;
}
@keyframes new-dot-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0.3rem 0 var(--new-dot-glow); }
  50% { opacity: 0.6; box-shadow: 0 0 0.6rem 0.1rem var(--new-dot-glow); }
}
@media (max-width: 420px) {
  .mobile-tabs button { letter-spacing: 0.06em; padding-inline: 0.15rem; }
}
@media (prefers-reduced-motion: reduce) {
  .new-dot, .has-new::after, .entries li.is-new::before { animation: none; transition: opacity 600ms ease; }
  .is-fading > .new-dot, h2.is-fading .new-dot, .has-new.is-fading::after, .entries li.is-fading::before { transform: none; }
}
.note-form { display: flex; flex-direction: column; gap: 0.4rem; margin-bottom: 0.4rem; }
.note-form textarea, .note-edit textarea { min-height: 3.4rem; font-family: var(--story-font); font-size: 0.95em; }
.notes .note-text { white-space: pre-wrap; }
.note-meta { gap: 0.3rem; margin-top: 0.3rem; }
.row .spacer, .note-meta .spacer { flex: 1; }
.empty { color: var(--faint); font-style: italic; }

/* ---- pack ---------------------------------------------------------------------------------------- */
/* Two columns when there is room and something for the second (Jake, 2026-09-28; BG-157, 2026-10-02):
   the inventory grid (and the weight bar above it) on the left, the lists (on your back, on the ground,
   a pack animal's load) on the right, once the pack pane itself - not the window - is at least 36rem
   wide and pack-view.js has marked .pack-cols.has-lists. A container query on the pane's own scroll
   container means it reflows live as the side panel's dividers move. With nothing to list, one column,
   so the grid has the whole width: on a wide panel layoutGrid() then scales the cells up with it (Jake:
   "on a full-screen browser ... the inventory looks tiny"). With lists, the grid's column takes two
   shares to the lists' one, never below 20rem, the lists never below 12rem so a long name still reads.
   DOM order never changes (How you are, then the grid, then the lists), only the CSS. */
.side-page[data-page="pack"] { container-type: inline-size; container-name: pack-pane; }
.pack-cols { display: grid; grid-template-columns: minmax(0, 1fr); column-gap: 1.75rem; align-items: start; }
.pack-col-main, .pack-col-lists { min-width: 0; }
@container pack-pane (min-width: 36rem) {
  .pack-cols.has-lists { grid-template-columns: minmax(20rem, 2fr) minmax(12rem, 1fr); }
}
.pack-head { margin-bottom: 0.9rem; }
.pack-weight-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 0.35rem; font-size: 0.85rem; }
.meter { position: relative; height: 6px; background: var(--panel-2); border: 1px solid var(--line); border-radius: 3px; }
.meter-fill { position: absolute; inset: 0 auto 0 0; background: var(--accent); border-radius: 3px; transition: width 0.4s ease; }
.is-heavy .meter-fill { background: var(--warn); }
.meter-mark { position: absolute; top: -3px; bottom: -3px; width: 1px; background: var(--warn); opacity: 0.8; }
.hint.warn { color: var(--warn); margin: 0.4rem 0 0; }

.pack-grid {
  position: relative; display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); grid-template-rows: repeat(var(--rows), auto);
  gap: 3px; padding: 3px; background: #07090a; border: 1px solid var(--line-strong); border-radius: var(--radius);
  touch-action: none; user-select: none; outline: none;
}
.pack-grid:focus-visible { border-color: var(--accent); }
/* Once pack-view.js layoutGrid() has fixed the column width (a whole number of device pixels per sprite
   pixel), the grid shrinks to its columns and sits centred. */
.pack-grid.is-laid { width: max-content; max-width: 100%; margin-inline: auto; }
.pack-cell { aspect-ratio: 1; background: var(--panel); border: 1px solid var(--line); border-radius: 2px; }
.pack-cell.drop-target { background: rgba(159, 183, 170, 0.18); border-color: var(--accent); }
.pack-item {
  position: relative; z-index: 1; margin: 0; min-width: 0; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.15rem;
  background: linear-gradient(160deg, #22292b, #181d1f); border: 1px solid #3c4649; border-radius: 3px;
  color: var(--text-strong); cursor: grab; padding: 1px; font: inherit;
}
.pack-item:hover { border-color: var(--accent); }
.pack-item.is-selected { border-color: var(--accent-strong); box-shadow: 0 0 0 1px var(--accent-strong), 0 0 12px rgba(159, 183, 170, 0.25); }
.pack-item.dragging { z-index: 5; cursor: grabbing; opacity: 0.85; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.6); }
.pack-item.perishable { background: linear-gradient(160deg, #2b2720, #1d1a16); border-color: #4a4234; }
/* Pixel-art item icons (public/js/ui/sprites.js): a shared base look plus an 'icon-fill' variant
   that fills its container (the pack-grid cell footprint) instead of using its own width/height).
   Grid items carry no caption text (name/weight stay in title/aria-label, and in the detail panel
   below when selected) so a rotated footprint's icon (pre-rotated by sprites.js from it.rot) has
   the whole cell, with only the button's 1px padding round it. pack-view.js layoutGrid() then gives
   every icon in the grid the same whole number of device pixels per sprite pixel (sprites.js
   snapIcons()); the 100% size below only holds until it has run. */
.item-icon {
  image-rendering: pixelated; -ms-interpolation-mode: nearest-neighbor; display: inline-block; vertical-align: middle; flex: 0 0 auto;
  /* Decorative only (alt="", aria-hidden, draggable="false" already set): never starts a native
     image drag or eats the pointer gesture the pack grid's own drag-to-rearrange relies on. */
  -webkit-user-drag: none; user-select: none; -webkit-user-select: none; pointer-events: none;
}
.item-icon.icon-fill { width: 100%; height: 100%; object-fit: contain; }
.pack-icon { flex: 1 1 0; min-height: 0; width: 100%; display: flex; align-items: center; justify-content: center; }
/* The detail of the thing chosen, as a modal over the Belongings pane (Jake, 2026-10-01): a backdrop over
   the whole pane and the card held in view while the pane scrolls, so choosing a thing never resizes the
   grids. ×, Esc or a click on the backdrop closes it (pack-view.js closeDetail()). */
[data-page="pack"] { position: relative; }
.pack-modal { position: absolute; inset: 0; z-index: 20; background: rgba(4, 6, 7, 0.55); display: flex; justify-content: center; align-items: flex-start; padding: 0 1rem; }
.pack-modal[hidden] { display: none; }
.pack-modal > .pack-card {
  position: sticky; top: 12vh; margin-top: 12vh; width: min(26rem, 100%); max-height: 70vh; overflow: auto;
  padding: 0.9rem 1rem 0.8rem; background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
}
.pack-card .pack-modal-close { float: right; margin: -0.35rem -0.4rem 0 0.5rem; background: none; border: 0; color: var(--muted); font-size: 1.25rem; line-height: 1; cursor: pointer; padding: 0.2rem 0.35rem; }
.pack-card .pack-modal-close:hover, .pack-card .pack-modal-close:focus-visible { color: var(--text-strong); }
.pack-detail .row { flex-wrap: wrap; gap: 0.4rem; }
.pack-detail .title { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-strong); letter-spacing: 0.06em; }
.pack-detail p { margin: 0.25rem 0 0.5rem; }
.pack-counters { list-style: none; padding: 0; margin: 0; }
.pack-counters li { display: flex; align-items: center; gap: 0.6rem; padding: 0.4rem 0; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.pack-counters .count { color: var(--accent-strong); min-width: 2rem; }
.pack-counters .cname { flex: 1; min-width: 8rem; }
/* A pack animal's load (pack-view.js animalHTML): a name opens its description, as selecting a pack item does. */
.pack-animal { margin-top: 1.2rem; }
.pack-load .linklike { background: none; border: 0; padding: 0; font: inherit; color: inherit; text-align: left; cursor: pointer; text-decoration: underline dotted; text-underline-offset: 0.2em; }
.pack-load .load-desc { flex-basis: 100%; margin: 0.2rem 0 0; color: var(--muted); }
/* How the Belongings tab works (Jake, 2026-10-01): a small ? beside the Pockets heading opens it as a
   popover (the HTML popover attribute: Esc or a click outside closes it), instead of a paragraph under
   the lists that crowded the layout. */
.pack-help-btn {
  flex: none; width: 1.15rem; height: 1.15rem; margin-block: -0.3rem; padding: 0; border-radius: 50%; border: 1px solid var(--line-strong);
  background: none; color: var(--muted); font: 600 0.7rem/1 var(--font-sans); cursor: pointer;
}
.pack-help-btn:hover, .pack-help-btn:focus-visible { color: var(--text-strong); border-color: var(--accent); }
.pack-help-pop {
  max-width: min(22rem, calc(100vw - 2rem)); margin: auto; padding: 0.8rem 1rem; border: 1px solid var(--line-strong);
  border-radius: var(--radius); background: var(--panel); color: var(--text); font-size: 0.875rem; line-height: 1.45;
}
.pack-help-pop p { margin: 0 0 0.5rem; }
.pack-help-pop p:last-child { margin-bottom: 0; }
.pack-help-pop::backdrop { background: rgba(0, 0, 0, 0.35); }

/* The Belongings pane never scrolls (BG-162, Jake 2026-10-02: "I do not want any scrolling in the inventory so
   all contents must always fit inside that pane"). The pane's content is one column (.pack-fit) as tall as the
   pane: "How you are" at its natural height, then the grid and the lists, which take the rest. pack-view.js
   layoutGrid() sizes the cells so the grids fit what is left across and down; the lists (on your back, on the
   ground, a pack animal) scroll in their own area when they are longer than the room left for them: under the
   grids in one column, or beside them in two. In a short pane layoutGrid() makes room in steps (.pack-compact:
   without "How you are" and the weight's small print, which the story's header also shows; .pack-tight:
   without the weight; .pack-tiny: without the tabs), then squeezes the cells (.is-squeezed). A pane as tall as
   its content (no height of its own) is unchanged: .pack-fit's 100% is then its content. */
.side-page[data-page="pack"] { overflow: hidden; }
.pack-fit { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.pack-fit > .pack-state { flex: none; }
.pack-fit > .pack-cols { flex: 1 1 auto; min-height: 0; grid-template-rows: auto minmax(0, 1fr); }
.pack-fit .pack-col-main { align-self: start; }
.pack-fit .pack-col-lists { align-self: stretch; min-height: 0; overflow: auto; overscroll-behavior: contain; }
@container pack-pane (min-width: 36rem) {
  .pack-fit > .pack-cols.has-lists { grid-template-rows: minmax(0, 1fr); }
}
.pack-compact .pack-state, .pack-compact .pack-weight-split, .pack-compact .pack-head .hint { display: none; }
.pack-tight .pack-head { display: none; }
.pack-tiny .pack-tabs-row { display: none; }
/* Cells squeezed below a sprite's size: the icons shrink to their cells (no whole-pixel snap). */
.is-squeezed .item-icon.icon-fill { width: 100%; height: 100%; }
/* The hover card (BG-162, Jake 2026-10-02: "a hover on the inventory items that looks just like the interaction
   modal"): the menu's card - its panel, edge, shadow and words - without the buttons, beside the thing under the
   mouse or the keyboard's focus, kept inside the pane by pack-view.js placeHover(). It never takes the pointer. */
.pack-hovercard {
  position: absolute; z-index: 15; width: max-content; max-width: 18rem; overflow: hidden; pointer-events: none;
  padding: 0.7rem 0.85rem 0.55rem; background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
}
.pack-hovercard[hidden] { display: none; }
.pack-hovercard .pack-detail > :last-child { margin-bottom: 0; }

/* Satchel and pockets (BG-145, Jake 2026-10-01; each under its own tab since BG-157, 2026-10-02): the
   pockets are a 2 x 5 grid laid 5 across, with the satchel's cell size (pack-view.js layoutGrid()), one
   kind of thing per cell with its count on the sprite. The cell's tint is the thing's
   state (engine.pocketsView()): red when some has gone bad, amber going off, a fainter amber with a day
   or two left, a cool grey for a part-burnt candle or torch; a dashed amber edge for more than the
   pockets now hold (an older save). The same state is in each cell's label and in its menu. */
.pack-weight-split { margin: 0.3rem 0 0; font-size: 0.72rem; letter-spacing: 0.02em; }
.pack-grids { display: flex; justify-content: center; align-items: flex-start; min-width: 0; }
.pack-satchel, .pack-pockets { min-width: 0; }
.pack-satchel[hidden], .pack-pockets[hidden] { display: none; }
/* Satchel | Pockets: two small tabs over the grid, the ? beside them; a warm dot on a tab while
   something in it is new (BG-157). */
.pack-tabs-row { display: flex; align-items: center; gap: 0.6rem; margin: 0 0 0.6rem; border-bottom: 1px solid var(--line); }
.pack-tabs { display: flex; gap: 0.2rem; }
.pack-tab {
  font: inherit; font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--muted); background: none; border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px;
  padding: 0.45rem 0.55rem 0.4rem; cursor: pointer; white-space: nowrap;
}
.pack-tab[aria-selected="true"] { color: var(--text-strong); border-bottom-color: var(--accent); }
.pack-tab:hover, .pack-tab:focus-visible { color: var(--text-strong); outline: none; }
.pack-tab:focus-visible { box-shadow: inset 0 0 0 1px var(--accent); }
/* A thing new to the player: a warm dot in its cell's corner until it is touched (BG-157), or rested on with
   the mouse or the keyboard (BG-162). */
.pack-item.is-new::after, .pocket-item.is-new::after {
  content: ''; position: absolute; top: 3px; right: 3px; z-index: 2; width: 0.45rem; height: 0.45rem; border-radius: 50%;
  background: var(--gui-pack-dot, var(--new-dot)); box-shadow: 0 0 0.4rem 0.05rem var(--gui-pack-dot-glow, var(--new-dot-glow)); pointer-events: none;
}
/* A thing a choice on the screen could use (BG-251, "You could use this here"): a small verdigris mark in the
   cell's lower corner, the onward colour, still; its card says it in words. */
.pack-item.is-useful::before, .pocket-item.is-useful::before {
  content: ''; position: absolute; left: 3px; bottom: 3px; z-index: 2; width: 0.5rem; height: 0.5rem; border-radius: 1px;
  background: var(--accent); opacity: 0.85; pointer-events: none;
}
.pack-useful { color: var(--accent); }
.pack-history { font-style: italic; opacity: 0.8; }
/* Where you are (BG-251): the quiet passage at the top of a save reopened after a while, and the Recollections' head. */
.log-recap { border-left: 2px solid rgba(159, 183, 170, 0.45); padding: 0.1rem 0 0.1rem 0.8rem; margin: 0 0 1rem; }
.log-recap .kicker { display: block; font-size: 0.75em; letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.7; margin-bottom: 0.25rem; }
.log-recap ul { list-style: none; margin: 0; padding: 0; }
.log-recap li { margin: 0.2rem 0; }
.jwhere li.jwhere-line { padding: 0.2rem 0; }
.jwhere li.is-question { font-style: italic; }
.log-recap .is-question { font-style: italic; }
/* Every unread dot of the belongings pulses slowly (BG-162, Jake 2026-10-02: "all yellow inventory dots should
   pulse slowly now"): on a thing's cell, on the Satchel and Pockets tabs, and on the Belongings tab, rail and
   phone tab. A slow ease in and out of the dot's brightness and glow, deeper than the Recollections' pulse so
   it is seen; Admin -> GUI settings' animation speed sets its pace (js/shell/gui-motion.js) and reduced motion
   (the system's, or the owner's for everyone) holds it steady. */
@keyframes pack-dot-pulse {
  0%, 100% { opacity: 1; transform: scale(1); box-shadow: 0 0 0.5rem 0.1rem var(--gui-pack-dot-glow, var(--new-dot-glow)); }
  50% { opacity: 0.35; transform: scale(0.78); box-shadow: 0 0 0.15rem 0 var(--gui-pack-dot-glow, var(--new-dot-glow)); }
}
.pack-item.is-new::after, .pocket-item.is-new::after, .pack-tab.has-new::after,
[data-tab="pack"].has-new::after, [data-rail="pack"].has-new::after, .mobile-tabs [data-view="pack"].has-new::after {
  animation: pack-dot-pulse 2.6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .pack-item.is-new::after, .pocket-item.is-new::after, .pack-tab.has-new::after,
  [data-tab="pack"].has-new::after, [data-rail="pack"].has-new::after, .mobile-tabs [data-view="pack"].has-new::after { animation: none; }
}
html.gui-reduce-motion .pack-item.is-new::after, html.gui-reduce-motion .pocket-item.is-new::after, html.gui-reduce-motion .pack-tab.has-new::after,
html.gui-reduce-motion [data-tab="pack"].has-new::after, html.gui-reduce-motion [data-rail="pack"].has-new::after,
html.gui-reduce-motion .mobile-tabs [data-view="pack"].has-new::after { animation: none; }
.pockets-grid {
  display: grid; grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr)); grid-auto-rows: auto;
  gap: 3px; padding: 3px; background: #07090a; border: 1px solid var(--line-strong); border-radius: var(--radius);
  user-select: none;
}
.pockets-grid.is-laid { width: max-content; max-width: 100%; }
.pocket-cell { aspect-ratio: 1; background: var(--panel); border: 1px solid var(--line); border-radius: 2px; }
.pocket-cell.is-overflow { border-style: dashed; }
.pocket-item {
  position: relative; aspect-ratio: 1; margin: 0; min-width: 0; min-height: 0; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, #22292b, #181d1f); border: 1px solid #3c4649; border-radius: 3px;
  color: var(--text-strong); cursor: pointer; padding: 1px; font: inherit;
}
.pocket-item:hover { border-color: var(--accent); }
.pocket-item:focus-visible { outline: 1px solid var(--accent-strong); outline-offset: 1px; }
.pocket-item.is-selected { border-color: var(--accent-strong); box-shadow: 0 0 0 1px var(--accent-strong), 0 0 12px rgba(159, 183, 170, 0.25); }
.pocket-item.is-ageing { background: var(--gui-tint-ageing-bg, linear-gradient(160deg, rgba(199, 154, 91, 0.16), rgba(199, 154, 91, 0.07))), #1b1f20; border-color: var(--gui-tint-ageing-border, rgba(199, 154, 91, 0.35)); }
.pocket-item.is-stale { background: var(--gui-tint-stale-bg, linear-gradient(160deg, rgba(199, 154, 91, 0.36), rgba(199, 154, 91, 0.18))), #1b1f20; border-color: var(--gui-tint-stale-border, rgba(199, 154, 91, 0.7)); }
.pocket-item.is-rotten { background: var(--gui-tint-rotten-bg, linear-gradient(160deg, rgba(192, 100, 90, 0.5), rgba(150, 60, 52, 0.32))), #1b1f20; border-color: var(--gui-tint-rotten-border, rgba(192, 100, 90, 0.85)); }
.pocket-item.is-used { background: var(--gui-tint-used-bg, linear-gradient(160deg, rgba(150, 165, 180, 0.2), rgba(150, 165, 180, 0.08))), #1b1f20; border-color: var(--gui-tint-used-border, rgba(150, 165, 180, 0.45)); }
/* Special things, in the satchel or the pockets (pack-view.js isSpecial(): anything of blackglass, or
   marked `special`, like the black bone): a violet tint. A pocket's food tints above win over it.
   Every tint here is tunable in Admin → GUI settings (BG-159, app/src/GuiTheme.php): each reads its
   --gui-tint-* property from /gui-settings.css, which defines it only once changed, so today's values
   (the fallbacks) stand until then. */
.pack-item.is-special, .pocket-item.is-special { background: var(--gui-tint-special-bg, linear-gradient(160deg, rgba(150, 125, 200, 0.3), rgba(150, 125, 200, 0.14))), #1b1f20; border-color: var(--gui-tint-special-border, rgba(160, 140, 210, 0.62)); }
.pack-item.is-special:hover, .pocket-item.is-special:hover { background: var(--gui-tint-special-hover-bg, linear-gradient(160deg, rgba(150, 125, 200, 0.36), rgba(150, 125, 200, 0.18))), #1b1f20; border-color: var(--gui-tint-special-hover-border, rgba(170, 150, 220, 0.85)); }
.pack-item.is-special.is-selected, .pocket-item.is-special.is-selected { border-color: var(--accent-strong); }
.pocket-item.is-over { border-style: dashed; border-color: var(--warn); }
.pocket-count {
  position: absolute; right: 2px; bottom: 1px; z-index: 1; pointer-events: none;
  font-size: 0.68rem; line-height: 1; color: var(--text-strong);
  text-shadow: 0 0 2px #000, 0 0 2px #000, 1px 1px 0 #000;
}
.pockets-over { margin: 0.5rem 0 0; }
.pocket-tags { display: flex; flex-wrap: wrap; gap: 0.3rem; }

/* Flash on change (Jake, 2026-09-28, pack-view.js diffFlashes/applyFlashes): a gained item's cells and
   its box brighten in the pack's own green; a lost item's now-empty cells pulse a muted red instead;
   a counter whose count moved (an oatcake eaten) gets a lighter pulse than a whole new item. Moving an
   item by drag never flashes (see diffFlashes). Purely decorative (no layout shift: box-shadow and
   background only), so it plays once on the freshly built element with no JS cleanup needed - the
   next render replaces the DOM outright. The -static classes (prefers-reduced-motion, applyFlashes())
   hold the peak look without animating and are cleared by a timer instead. */
@keyframes pack-item-flash-gain {
  0%, 100% { box-shadow: none; }
  25%, 65% { box-shadow: 0 0 0 2px rgba(159, 183, 170, 0.7), 0 0 16px rgba(159, 183, 170, 0.5); }
  45% { box-shadow: 0 0 0 1px rgba(159, 183, 170, 0.35); }
}
@keyframes pack-cell-flash-gain {
  0%, 100% { background: var(--panel); box-shadow: none; }
  25%, 65% { background: rgba(159, 183, 170, 0.28); box-shadow: inset 0 0 0 2px rgba(159, 183, 170, 0.6); }
  45% { background: rgba(159, 183, 170, 0.12); box-shadow: inset 0 0 0 1px rgba(159, 183, 170, 0.3); }
}
@keyframes pack-cell-flash-loss {
  0%, 100% { background: var(--panel); box-shadow: none; }
  25%, 65% { background: rgba(192, 100, 90, 0.24); box-shadow: inset 0 0 0 2px rgba(192, 100, 90, 0.5); }
  45% { background: rgba(192, 100, 90, 0.1); box-shadow: inset 0 0 0 1px rgba(192, 100, 90, 0.25); }
}
@keyframes pack-counter-flash-pulse {
  0%, 100% { background: transparent; }
  40% { background: rgba(159, 183, 170, 0.22); }
}
.pack-item.pack-flash-gain { animation: pack-item-flash-gain 1.2s ease-in-out; }
.pack-cell.pack-flash-gain { animation: pack-cell-flash-gain 1.2s ease-in-out; }
.pack-cell.pack-flash-loss { animation: pack-cell-flash-loss 1.2s ease-in-out; }
.pack-counters li.pack-flash-pulse { animation: pack-counter-flash-pulse 0.7s ease-in-out; }
.pack-item.pack-flash-gain-static { box-shadow: 0 0 0 2px rgba(159, 183, 170, 0.7), 0 0 14px rgba(159, 183, 170, 0.45); }
.pack-cell.pack-flash-gain-static { background: rgba(159, 183, 170, 0.24); box-shadow: inset 0 0 0 2px rgba(159, 183, 170, 0.55); }
.pack-cell.pack-flash-loss-static { background: rgba(192, 100, 90, 0.2); box-shadow: inset 0 0 0 2px rgba(192, 100, 90, 0.5); }
.pack-counters li.pack-flash-pulse-static { background: rgba(159, 183, 170, 0.2); }
/* A pocket cell whose count moved: the same lighter pulse, as a ring so the state tint stays. */
@keyframes pocket-flash-pulse {
  0%, 100% { box-shadow: none; }
  40% { box-shadow: inset 0 0 0 2px rgba(159, 183, 170, 0.55), 0 0 10px rgba(159, 183, 170, 0.35); }
}
.pocket-item.pack-flash-pulse { animation: pocket-flash-pulse 0.7s ease-in-out; }
.pocket-item.pack-flash-pulse-static { box-shadow: inset 0 0 0 2px rgba(159, 183, 170, 0.55); }

/* ---- overlays and dialogs --------------------------------------------------------------------------- */
.overlay {
  position: fixed; inset: 0; z-index: 50; background: rgba(5, 6, 6, 0.975); backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center; overflow-y: auto;
  padding: max(1.5rem, env(safe-area-inset-top)) max(1.5rem, env(safe-area-inset-right)) max(1.5rem, env(safe-area-inset-bottom)) max(1.5rem, env(safe-area-inset-left));
}
.overlay[hidden] { display: none; }
/* A card taller than the screen (the completion card with its trophy, on a phone) starts at the top and scrolls:
   centred by the container's alignment alone, its top would lie above the overlay's scroll range. */
.overlay > .card { margin-block: auto; }
.card { max-width: 34rem; width: 100%; text-align: center; animation: rise 0.8s ease-out both; }
.card .series { font-family: var(--font-mono); letter-spacing: 0.5em; text-transform: uppercase; color: var(--muted); font-size: 0.75rem; margin-bottom: 2rem; }
.card .ch-num { font-family: var(--font-mono); letter-spacing: 0.3em; color: var(--muted); font-size: 0.8125rem; text-transform: uppercase; }
.card .ch-title { font-family: var(--font-mono); font-size: clamp(1.4rem, 5vw, 2.2rem); letter-spacing: 0.22em; text-transform: uppercase; color: var(--text-strong); margin: 0.4rem 0 2rem; font-weight: 400; }
.card .complete { font-family: var(--font-mono); letter-spacing: 0.4em; color: var(--accent-strong); margin: -1rem 0 2rem; }
.card form, .card .actions { display: flex; flex-direction: column; gap: 0.75rem; align-items: stretch; max-width: 22rem; margin: 1.5rem auto 0; text-align: left; }
.card .actions .btn { width: 100%; }
.card .note { font-size: 0.875rem; color: var(--muted); margin-top: 1.25rem; }
/* The completion card's trophy (BG-251): what is carried, what will be remembered, what is still open. */
.card .card-trophy { text-align: left; max-width: 26rem; margin: -0.5rem auto 1.25rem; font-size: 0.95rem; }
.card .card-trophy h2 { font-family: var(--font-mono); font-weight: 400; font-size: 0.72rem; letter-spacing: 0.3em; text-transform: uppercase; color: var(--muted); margin: 1rem 0 0.35rem; }
.card .card-trophy ul { list-style: none; margin: 0; padding: 0; }
.card .card-trophy li { padding: 0.15rem 0; border-left: 2px solid rgba(159, 183, 170, 0.35); padding-left: 0.7rem; margin: 0.2rem 0; }
.card .card-trophy li.is-question { font-style: italic; }
/* the muted ink, not the faint one dimmed again: these are lines to read (4.5 : 1 on the card) */
.card .card-trophy .faint { color: var(--muted); }
/* The guest stop (start of Act III). */
.card .lead { font-size: 1.125rem; color: var(--text-strong); margin: 0 0 0.9rem; }
.card.guest-stop p { max-width: 30rem; margin-left: auto; margin-right: auto; }
.card .note a { color: var(--accent); }

/* "Choose your Journey" (js/ui/choose-journey.js): a quiet list of doorways, one column. */
.overlay:has(.card.choose) { align-items: flex-start; }
.card.choose { margin: auto 0; max-width: 36rem; }
.card.choose .ch-title { margin-bottom: 1.6rem; outline: none; font-size: clamp(1.15rem, 5vw, 2.2rem); }
.journeys { list-style: none; margin: 0; padding: 0; text-align: left; border-top: 1px solid var(--line); }
.journey { border-bottom: 1px solid var(--line); }
.journey-door {
  display: flex; flex-direction: column; gap: 0.35rem; width: 100%; text-align: left;
  background: none; border: 0; border-left: 2px solid transparent; color: var(--text);
  font: inherit; padding: 1rem 0.9rem; cursor: pointer;
}
button.journey-door:hover, button.journey-door:focus-visible { background: var(--panel); border-left-color: var(--accent); outline: none; }
.journey.is-open button.journey-door { border-left-color: var(--accent); }
.journey-who { font-family: var(--font-mono); font-size: 0.8125rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--text-strong); }
.journey-role { color: var(--muted); }
.journey-unreleased { color: var(--warn); letter-spacing: 0.08em; text-transform: none; } /* development only */
.journey-line { font-family: var(--story-font); font-style: italic; font-size: 1rem; line-height: 1.45; color: var(--text); }
.journey-more { padding: 0 0.9rem 1.1rem calc(0.9rem + 2px); }
.journey-more p { margin: 0 0 0.6rem; line-height: 1.5; }
.journey-fullname { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); }
.journey-home { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.06em; color: var(--muted); }
.journey-intro { font-family: var(--story-font); font-size: 1rem; color: var(--text); }
.journey-begin { margin-top: 0.4rem; min-width: 9rem; }
.journey.is-unknown .journey-door { cursor: default; }
.journey.is-unknown .journey-who, .journey.is-unknown .journey-line { color: var(--faint); }
.journey-tick { color: var(--accent); font-weight: 700; }
.journey-completed { display: block; margin-top: 0.1rem; }
.journey-completed-note { display: block; font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.04em; color: var(--muted); }
.journey-completed-summary { display: block; font-family: var(--story-font); font-style: italic; font-size: 0.9rem; line-height: 1.4; color: var(--text); margin-top: 0.15rem; }
.journey-completed-summary strong { font-style: normal; color: var(--text-strong); }
.journey-completed-more { font-style: normal; color: var(--faint); }
.card.choose .note { text-align: center; }

/* A gated protagonist unlocked and not yet begun (registry `requires`, e.g. Nia; Jake, 2026-09-28,
   restyled 2026-09-30): a gold border with a gentle glow that breathes every 1.8 s, drawing the eye
   without moving anything (js/ui/choose-journey.js, continue-journey.js). It ends when a Journey with
   her exists, and she is then an ordinary card. The landing page's twin is .fragment-journey.is-unlocked
   (landing.css). Reduced motion: a steady gold border, no animation. */
.journey.journey-card--new { border: 1px solid #d4af37; box-shadow: 0 0 6px rgba(212, 175, 55, 0.25); animation: journey-card-new-pulse 1.8s ease-in-out infinite; }
.journey-newly { color: #d4af37; font-size: 0.6875rem; letter-spacing: 0.1em; text-transform: uppercase; }
@keyframes journey-card-new-pulse {
  0%, 100% { border-color: #d4af37; box-shadow: 0 0 4px rgba(212, 175, 55, 0.2); }
  50% { border-color: #f0d271; box-shadow: 0 0 16px rgba(212, 175, 55, 0.55); }
}
@media (prefers-reduced-motion: reduce) {
  .journey.journey-card--new { animation: none; box-shadow: none; }
}
@media (max-width: 30rem) {
  .journey-door { padding: 0.9rem 0.6rem; }
  .journey-more { padding: 0 0.6rem 1rem calc(0.6rem + 2px); }
  .journey-begin { width: 100%; }
}

.menu-list { display: flex; flex-direction: column; gap: 0.5rem; }
.menu-list .btn { justify-content: flex-start; width: 100%; }
.menu-list hr { border: 0; border-top: 1px solid var(--line); margin: 0.5rem 0; width: 100%; }
.menu-list .version-line { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.25rem; user-select: text; }
.menu-list .version-line .btn { width: auto; }
.panel.slot { padding: 0.75rem; }
.slot-actions { margin-top: 0.5rem; }

.settings-dialog { width: min(32rem, calc(100vw - 2rem)); }
.settings-grid { display: flex; flex-direction: column; gap: 1.1rem; }
.settings-grid fieldset { border: 0; padding: 0; margin: 0; }
.settings-grid legend { margin-bottom: 0.4rem; }
.settings-grid .settings-heading {
  margin: 0.4rem 0 -0.3rem; padding-top: 0.9rem; border-top: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 0.75rem; font-weight: 400; letter-spacing: 0.22em; text-transform: uppercase; color: var(--text-strong);
}
.settings-grid .settings-heading:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
/* "Play music" reads like the other checkboxes, not like a field's small caps label. */
.settings-grid .field label.check { font-family: inherit; font-size: 0.9375rem; letter-spacing: normal; text-transform: none; color: var(--text); }
.font-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 0.4rem; }
.font-choice { display: flex; gap: 0.5rem; align-items: center; border: 1px solid var(--line-strong); border-radius: var(--radius); padding: 0.5rem 0.6rem; cursor: pointer; }
.font-choice:has(input:checked) { border-color: var(--accent); background: rgba(159, 183, 170, 0.07); }
.font-sample { font-size: 1rem; }
.seg-inline { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; }
.seg-inline label { display: inline-flex; gap: 0.35rem; align-items: center; cursor: pointer; }
.settings-grid input[type="range"] { width: 100%; accent-color: var(--accent); }
.settings-preview { font-family: var(--story-font); font-size: var(--story-size); line-height: var(--story-leading); color: var(--text); border-left: 2px solid var(--line-strong); padding-left: 0.8rem; }

/* ---- phones and narrow windows ------------------------------------------------------------------------ */
.mobile-tabs { display: none; }

@media (max-width: 62rem) {
  .game-shell { grid-template-rows: auto minmax(0, 1fr) auto; padding-bottom: 0; }
  /* The analytics bar (js/shell/analytics.js) floats over the foot of the page: while it is open the shell stops above it, so no choice sits under it (BG-253). */
  html.has-consent-bar .game-shell { height: calc(100vh - var(--consent-h, 0px)); height: calc(100dvh - var(--consent-h, 0px)); }
  .game-main, .game-main[data-panel="closed"] { grid-template-columns: minmax(0, 1fr); }
  .game-main[data-view="story"] .pane-side { display: none; }
  .game-main:not([data-view="story"]) .pane-story { display: none; }
  .game-main:not([data-view="story"]) .pane-side { display: flex; border-left: 0; }
  .game-main .panel-rail, .game-main[data-panel="closed"] .panel-rail { display: none; }
  .pane-splitter { display: none; }
  .side-tabs { display: none; }
  .mobile-tabs { display: flex; border-top: 1px solid var(--line); background: var(--bg); padding-bottom: env(safe-area-inset-bottom); }
  .update-notice { bottom: calc(3.9rem + env(safe-area-inset-bottom)); } /* above the tab bar (js/shell/pwa.js) */
  .mobile-tabs button {
    flex: 1; background: none; border: 0; color: var(--muted); padding: 0.85rem 0.25rem;
    font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.14em; text-transform: uppercase; cursor: pointer;
  }
  .mobile-tabs button[aria-selected="true"] { color: var(--text-strong); box-shadow: inset 0 2px 0 var(--accent); }
  /* Belongings and Recollections (BG-145) are longer words than Pack and Journal: each tab takes the room
     its word needs, on one line with its dot, and the spacing closes up on the narrowest phones. */
  .mobile-tabs button { flex: 1 1 auto; min-width: 0; white-space: nowrap; }
  .game-bar .fragment-name { display: none; }
  .story-tail { height: 30vh; }

  /* Travel on phones/narrow windows: the Story view (and its travel veil) stays on screen, and the
     live map is shown full-bleed behind it instead of switching to the Map tab — see game-view.js
     openMapForTravel(). The map (.pane-side) is laid over the whole game-main area, one step below
     the story pane, which is given a transparent background so the map shows through; the veil's
     own background is lightened so it reads as translucent rather than opaque. */
  .game-main.travel-peek { position: relative; }
  .game-main.travel-peek[data-view="story"] .pane-side { display: flex; position: absolute; inset: 0; z-index: 1; border-left: 0; }
  .game-main.travel-peek .pane-story { position: relative; z-index: 2; background: transparent; }
  .game-main.travel-peek .pane-story.travelling .story-scroll,
  .game-main.travel-peek .pane-story.travelling .story-head { visibility: hidden; }
  /* The card sits at the foot so the middle of the map, where the player is centred, stays clear. */
  .game-main.travel-peek .travel-veil {
    align-items: flex-end; padding-bottom: 3.5rem;
    background: linear-gradient(to top, rgba(9, 10, 11, 0.92) 0%, rgba(9, 10, 11, 0.6) 28%, rgba(9, 10, 11, 0.05) 55%, transparent 100%);
  }
  .game-main.travel-peek .travel-card { padding: 0.75rem 1rem; }
}
@media (max-width: 40rem) {
  .game-bar { gap: 0.35rem; padding: 0.35rem 0.5rem; }
  .game-bar .wordmark { letter-spacing: 0.2em; }
  .save-status .long { display: none; }
  .save-status .short { display: inline; }
  .story-scroll { padding: 0 1rem; }
  .story-head { padding: 0.5rem 1rem; }
}
/* The narrowest phones (360–415 px): the save state is shown by its dot alone (its words stay in the
   live region for screen readers), so Save, ♪, Aa and the menu still fit beside the wordmark. */
@media (max-width: 26rem) {
  .game-bar { gap: 0.25rem; }
  .game-bar .icon-btn { min-width: 2.1rem; }
  .save-status .short { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
}

/* Development screenshots (?anim=0): headless capture does not advance CSS animations. */
html.no-anim *, html.no-anim *::before, html.no-anim *::after { animation: none !important; transition: none !important; }

.settings-grid .font-choice, .settings-grid .seg-inline label { text-transform: none; letter-spacing: normal; font-family: var(--font-sans); font-size: 0.9rem; color: var(--text); }
.settings-grid .font-sample { font-size: 1.05rem; }

/* Reading font everywhere text is read, not only in the story. Small uppercase labels, tabs and
   the map itself keep the monospaced face. */
.side-page, .dialog, .card, .map-card, .map-info { font-family: var(--story-font); }
.entries .title, .entries .former, .pack-detail .title, .pack-counters, .journal-search {
  font-family: var(--story-font); letter-spacing: 0;
}
.entries .title { font-size: 1em; font-weight: 600; }

/* Interactive terms in the story: things with a journal entry. */
.term {
  font: inherit; font-weight: 700; color: var(--text-strong); background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer; text-decoration: underline dotted rgba(159, 183, 170, 0.55); text-underline-offset: 0.2em;
  text-decoration-thickness: 1px; border-radius: 2px;
}
.term:hover, .term:focus-visible { color: var(--accent-strong); text-decoration-color: var(--accent); outline: none; }
.passage.past .term { font-weight: 600; }
.term-card {
  position: absolute; z-index: 20; background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55); padding: 0.75rem 0.9rem; font-family: var(--story-font);
  font-size: calc(var(--story-size) * 0.82); line-height: 1.5; color: var(--text);
}
.term-card[hidden] { display: none; }
.term-card-head { display: flex; align-items: baseline; gap: 0.5rem; }
.term-card .title { flex: 1; font-weight: 700; color: var(--text-strong); }
.term-card .former { font-size: 0.85em; color: var(--faint); margin-top: 0.15rem; }
.term-card .former s { margin-right: 0.4rem; }
.term-card p { margin: 0.4rem 0 0; }
.term-card .standing { color: var(--accent); font-style: italic; }
.term-card .btn { margin-top: 0.6rem; }
.term-close { background: none; border: 0; color: var(--muted); font-size: 1.1rem; cursor: pointer; line-height: 1; }
.term-close:hover { color: var(--text-strong); }

/* Questions already asked in a conversation; a talk asked out, or an action with nothing new to
   show (disabled), in a place's list. */
.choice.is-asked .choice-label { color: var(--gui-asked-ink, var(--faint)); } /* dimming: Admin → GUI settings (BG-159) */
.choice.is-asked:disabled { cursor: default; }
.choice.is-asked:hover:not(:disabled) .choice-label { color: var(--muted); }
.asked-note {
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--faint); border: 1px solid var(--line); border-radius: 2px; padding: 0 0.35rem; white-space: nowrap; align-self: center;
}

/* Locations with nothing new for now. */
.tick { color: var(--ok); font-family: var(--font-mono); font-weight: 700; }

/* Travel: the story fades out while the protagonist is on the move. */
.story-scroll, .story-head { transition: opacity 0.45s ease, filter 0.45s ease; }
.pane-story.travelling .story-scroll, .pane-story.travelling .story-head { opacity: 0.06; filter: blur(2px); }
.travel-veil {
  position: absolute; inset: 0; z-index: 15; display: flex; align-items: center; justify-content: center;
  cursor: pointer; animation: veil-in 0.45s ease both;
  background: radial-gradient(ellipse at center, rgba(10, 11, 12, 0.92) 0%, rgba(10, 11, 12, 0.55) 60%, rgba(10, 11, 12, 0.2) 100%);
}
.travel-veil[hidden] { display: none; }
.travel-veil.leaving { animation: veil-out 0.45s ease both; }
@keyframes veil-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes veil-out { from { opacity: 1; } to { opacity: 0; } }
.travel-card { text-align: center; padding: 1.5rem; max-width: 30rem; }
.travel-kicker { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.3em; text-transform: uppercase; color: var(--muted); }
.travel-dest { font-family: var(--story-font); font-size: calc(var(--story-size) * 1.35); color: var(--text-strong); margin: 0.6rem 0 0.9rem; }
.travel-via { font-style: italic; font-size: 0.8em; color: var(--text); }
.travel-time { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.12em; color: var(--faint); margin-top: 0.8rem; }
.travel-dots { display: inline-flex; gap: 0.5rem; }
.travel-dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); opacity: 0.25; animation: travel-dot 1.2s ease-in-out infinite; }
.travel-dots span:nth-child(2) { animation-delay: 0.2s; }
.travel-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes travel-dot { 0%, 100% { opacity: 0.2; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-3px); } }
.travel-note { font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); margin-top: 0.5rem; }
.travel-remain { font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.05em; color: var(--faint); margin-top: 0.9rem; }
.travel-progress { width: 11rem; max-width: 60vw; height: 2px; background: var(--line); border-radius: 2px; margin: 0.5rem auto 0; overflow: hidden; }
.travel-progress-bar { height: 100%; width: 0; background: var(--accent); }
.travel-skip {
  font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint); margin-top: 1.1rem; opacity: 0.7;
}

/* ---- travel: desktop full-screen map -------------------------------------------------------
   On wide layouts (the .mobile-tabs breakpoint above never applies .travel-expand — game-view.js
   only adds it when the mobile tab bar is hidden), travel expands the map pane over the whole
   game area — story pane, splitter and all — the same full-bleed composition as the phone's
   .travel-peek above, just reached from a grid layout instead of a single column. Both panes are
   pinned to the game-main box instead of their grid column/track, so the map fills the whole area
   regardless of the side panel's own width, and the story pane's (now-transparent, emptied-out)
   box no longer clips the veil to its own column. See game-view.js openMapForTravel()/
   restoreAfterTravel(). */
.game-main.travel-expand { position: relative; }
.game-main.travel-expand .pane-story { position: absolute; inset: 0; z-index: 2; background: transparent; }
.game-main.travel-expand .pane-story.travelling .story-scroll,
.game-main.travel-expand .pane-story.travelling .story-head { visibility: hidden; }
.game-main.travel-expand .pane-splitter,
.game-main.travel-expand .panel-rail { display: none; }
/* The map pane starts docked at its normal side-panel rect (so adding .travel-expand alone causes
   no visible jump) and, once .travel-expanded also lands a frame later, animates its left edge
   out to the full width over ~0.35s — an actual, not merely faded, expansion. Collapsing back is
   instant (restoreAfterTravel() drops both classes together): the travel veil is still fading out
   on top of it at that point, which masks the snap the same way it does on the phone. Reduced
   motion (base.css) already collapses this transition's duration to near-zero. */
.game-main.travel-expand .pane-side {
  position: absolute; top: 0; right: 0; bottom: 0; z-index: 1; display: flex; border-left: 0;
  left: calc(100% - var(--side-width) - 6px);
  transition: left 0.35s ease;
}
.game-main.travel-expand.travel-expanded .pane-side { left: 0; }
/* Card at the foot, gradient behind it, so the middle of the map — where the player is centred —
   stays clear. */
.game-main.travel-expand .travel-veil {
  align-items: flex-end; padding-bottom: 3.5rem;
  background: linear-gradient(to top, rgba(9, 10, 11, 0.92) 0%, rgba(9, 10, 11, 0.6) 28%, rgba(9, 10, 11, 0.05) 55%, transparent 100%);
}
.game-main.travel-expand .travel-card { padding: 0.75rem 1rem; }
/* Over the map (desktop and phone), the card sits on its own dark panel so map labels under it
   never compete with its text. */
.game-main.travel-expand .travel-card,
.game-main.travel-peek .travel-card {
  background: rgba(9, 10, 11, 0.86); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.9rem 1.4rem; box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
}

/* The journey's overlay (BG-208): a light layer over the whole map (the map and its layers stay clearly
   visible, a little dimmed and vignetted), the player's marker held at the middle of the map (the point
   game-view.js sets as --travel-cx/--travel-cy), "On your way to ..." above it and the time and progress
   below. Each block sits on its own dark, translucent panel so parchment labels never fight the text. */
.game-main .travel-veil.travel-over {
  display: block; cursor: default;
  background: radial-gradient(ellipse at var(--travel-cx, 50%) var(--travel-cy, 50%), rgba(9, 10, 11, 0.1) 0%, rgba(9, 10, 11, 0.2) 45%, rgba(9, 10, 11, 0.5) 100%);
}
.game-main .travel-veil.travel-over.can-skip { cursor: pointer; }
.travel-over .travel-label, .travel-over .travel-details {
  position: absolute; left: var(--travel-cx, 50%); transform: translateX(-50%);
  width: max-content; max-width: min(30rem, calc(100% - 2rem)); text-align: center;
  background: rgba(9, 10, 11, 0.42); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: var(--radius);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9), 0 0 8px rgba(0, 0, 0, 0.7);
}
/* Out of the way (BG-208): the destination near the top of the map, the progress near the bottom, so the
   map round the marker stays clear. */
.travel-over .travel-label { top: calc(var(--travel-top, 0px) + 0.9rem); padding: 0.45rem 1.2rem 0.55rem; }
.travel-over .travel-details { bottom: calc(var(--travel-bottom, 0px) + 0.9rem); padding: 0.55rem 1.2rem 0.65rem; }
@media (max-height: 44rem) {
  .travel-over .travel-label { top: calc(var(--travel-top, 0px) + 0.4rem); padding-top: 0.3rem; padding-bottom: 0.35rem; }
  .travel-over .travel-details { bottom: calc(var(--travel-bottom, 0px) + 0.4rem); padding-top: 0.35rem; padding-bottom: 0.4rem; }
  .travel-over .travel-time { margin-top: 0.3rem; }
  .travel-over .travel-remain { margin-top: 0.3rem; }
}
.travel-over .travel-dest { margin: 0.3rem 0 0; }
.travel-over .travel-time { margin-top: 0.55rem; color: var(--text); }
.travel-over .travel-remain { margin-top: 0.5rem; color: var(--muted); }
.travel-over .travel-skip { margin-top: 0.8rem; }
@media (max-width: 40rem) {
  .travel-over .travel-dest { font-size: calc(var(--story-size) * 1.15); }
  .travel-over .travel-label, .travel-over .travel-details { padding-left: 0.9rem; padding-right: 0.9rem; }
}

/* Owner preview (registry status `preview`; docs/fragment-2/BUILD.md §1.4): a quiet chip in the game
   bar, only ever shown to owners. Stays visible on phones, where the fragment name is hidden. */
.game-bar .preview-chip {
  flex: 0 0 auto; margin-left: 0.4rem; padding: 0.05rem 0.4rem; white-space: nowrap;
  font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--warn); border: 1px solid var(--warn); border-radius: 2px;
}

/* "Which Journey goes on?" (js/ui/continue-journey.js): the finished Journey's ending title. */
.continue-journey .journey-ending { font-style: italic; }
/* A Journey whose way on is not open yet, and a protagonist who has not finished the earlier
   fragment (Jake, 2026-09-28): shown, quieter, with no action. */
.continue-journey .journey-not-yet { color: var(--muted); letter-spacing: 0.08em; }
.continue-journey .journey.is-unfinished .journey-door { cursor: default; }
.continue-journey .journey.is-unfinished .journey-who,
.continue-journey .journey.is-unfinished .journey-line { color: var(--muted); }

/* The owner's draft preview (BG-181.1; js/ui/draft-ribbon.js, ?draft=1): the game framed in the warning colour
   and a ribbon in its bar, so a draft run is never mistaken for the released story. */
html.is-draft-run .game-shell { box-shadow: inset 0 0 0 3px var(--warn); }
.draft-ribbon {
  flex: 0 0 auto; margin-left: 0.5rem; padding: 0.15rem 0.55rem; white-space: nowrap; text-decoration: none;
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: #1a140c; background: var(--warn); border-radius: 2px;
}
.draft-ribbon:hover, .draft-ribbon:focus-visible { color: #1a140c; text-decoration: underline; }
@media (max-width: 560px) { .draft-ribbon-more { display: none; } }
/* The draft run's trace (BG-187.2; js/ui/draft-preview.js): why each choice is open or shut, and each effect as it
   applies. Beside the story on a wide window, over it on a narrow one; the draft run only. */
.draft-trace-toggle {
  flex: 0 0 auto; margin-left: 0.35rem; padding: 0.1rem 0.5rem; font-family: var(--font-mono); font-size: 0.6875rem;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--warn); background: transparent; border: 1px solid var(--warn); border-radius: 2px; cursor: pointer;
}
.draft-trace-toggle[aria-expanded="true"] { color: #1a140c; background: var(--warn); }
.draft-trace {
  position: fixed; z-index: 60; top: 3.2rem; right: 0.5rem; bottom: 0.5rem; width: min(26rem, calc(100vw - 1rem)); overflow: auto;
  padding: 0.5rem 0.65rem; font-size: 0.8rem; color: var(--text); background: var(--panel); border: 1px solid var(--warn); border-radius: var(--radius);
}
.draft-trace code { font-family: var(--font-mono); font-size: 0.75rem; overflow-wrap: anywhere; }
.draft-trace .dt-tabs { display: flex; gap: 0.3rem; align-items: center; margin-bottom: 0.4rem; position: sticky; top: -0.5rem; background: var(--panel); padding: 0.25rem 0; }
.draft-trace .dt-tabs button { font: inherit; padding: 0.1rem 0.5rem; color: var(--muted); background: transparent; border: 1px solid var(--line); border-radius: 2px; cursor: pointer; }
.draft-trace .dt-tabs button[aria-selected="true"] { color: var(--text-strong); border-color: var(--warn); }
.draft-trace .dt-close { margin-left: auto; }
.draft-trace ol, .draft-trace ul { margin: 0; padding: 0; list-style: none; }
.draft-trace .dt-row { padding: 0.35rem 0; border-bottom: 1px solid var(--line); }
.draft-trace .dt-row.is-shut .dt-head strong { color: var(--muted); }
.draft-trace .dt-kind, .draft-trace .dt-state, .draft-trace .dt-to, .draft-trace .dt-muted, .draft-trace .dt-def { color: var(--muted); font-size: 0.72rem; }
.draft-trace .dt-row.is-open .dt-state { color: var(--accent); }
.draft-trace .dt-terms li { padding-left: 0.6rem; }
.draft-trace .is-yes .dt-mark { color: var(--accent); }
.draft-trace .is-no .dt-mark, .draft-trace .dt-bad, .draft-trace .is-skipped .dt-mark { color: var(--warn); }
.draft-trace .dt-mark { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; }
.draft-trace .dt-step { margin-top: 0.4rem; color: var(--text-strong); font-weight: 600; }
.draft-trace .dt-effect { padding: 0.15rem 0 0.15rem 0.5rem; }
.draft-trace .dt-why { color: var(--muted); font-style: italic; }

/* Pictures open large when clicked (js/ui/picture-zoom.js, BG-239). */
.place-art { cursor: zoom-in; }
.place-art:focus-visible { outline: 2px solid var(--accent, currentColor); outline-offset: 3px; }
/* With a mouse (not on a phone or tablet), a picture grows a touch under the pointer: a hint that it opens. */
@media (hover: hover) and (pointer: fine) {
  .place-art { transition: transform 0.2s ease-out; }
  .place-art:hover { transform: scale(1.025); }
}
@media (prefers-reduced-motion: reduce) { .place-art, .place-art:hover { transition: none; transform: none; } }
html.pz-locked, html.pz-locked body { overflow: hidden; }
.pz-overlay {
  position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.88); cursor: zoom-out; opacity: 0; overscroll-behavior: contain; touch-action: none;
}
.pz-overlay.pz-in { opacity: 1; transition: opacity 0.18s ease-out; }
.pz-overlay[data-instant] { opacity: 1; transition: none; }
.pz-fig { position: relative; line-height: 0; }
.pz-fig > img { display: block; max-width: none; image-rendering: crisp-edges; image-rendering: pixelated; }
.pz-fig.pa-moving > img { opacity: 0; }
.pz-close {
  position: absolute; top: 0.6rem; right: 0.6rem; width: 2.5rem; height: 2.5rem; border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.35); background: rgba(0, 0, 0, 0.6); color: #fff;
  font: 1.6rem/1 var(--font-sans, sans-serif); cursor: pointer; padding: 0;
}
.pz-close:hover, .pz-close:focus-visible { border-color: #fff; background: rgba(0, 0, 0, 0.85); }

/* Discovery (BG-252, js/ui/discovery.js): a thing newly come by shows at a readable size, holds, then shrinks and
   travels into the Belongings (or the Recollections) control on screen, which glows once; the amber dot stays the
   persistent mark. The card stands in the story's own flow, in the band between the passage and the choices
   (.discovery-slot), so it never lies over the prose or a choice; it is lifted out (fixed, over the same place) to
   travel, and the band closes after it. With the story off screen (a phone's other views) it stands fixed over the
   window's foot. The card is the Belongings hover card's (panel, edge, shadow), solid while it holds; the kicker the
   recap's. A Recollection's card has Read and Keep for later; the starting kit is one card with every thing in it.
   Nothing bounces: one ease, in and out. Reduced motion (the system's or the owner's) and ?anim=0 fade the card where
   it is instead. */
.discovery-layer { position: fixed; inset: 0; z-index: 40; pointer-events: none; }
.discovery-slot { display: none; }
.discovery-slot.is-open { display: flex; justify-content: center; padding: 0.35rem 0 0.9rem; box-sizing: border-box; }
.discovery-slot.is-closing { overflow: hidden; padding-top: 0; padding-bottom: 0; } /* (its height eased by discovery.js closeSlot()) */
.discovery-card {
  position: fixed; display: flex; align-items: center; gap: 0.85rem; box-sizing: border-box;
  width: max-content; max-width: min(22rem, calc(100vw - 2rem)); padding: 0.8rem 1rem 0.75rem;
  font-family: var(--story-font, var(--font-serif)); font-size: 1rem; line-height: 1.4; text-align: left;
  background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55); pointer-events: auto;
  opacity: 0; transform: scale(0.96); transform-origin: 50% 50%;
  transition: opacity 220ms ease, transform 220ms ease;
}
.discovery-card.in-flow { position: relative; max-width: min(22rem, 100%); box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45); }
.discovery-kit.in-flow { width: min(36rem, 100%); max-width: min(36rem, 100%); }
.discovery-card.is-in { opacity: 1; transform: none; }
.discovery-item, .discovery-kit { cursor: pointer; }
.discovery-card.is-travelling { transition-property: transform, opacity; transition-timing-function: cubic-bezier(0.55, 0, 0.3, 1); opacity: 0.2; }
.discovery-card.is-fading { transition-property: opacity; opacity: 0; }
.discovery-sprite {
  flex: none; display: flex; align-items: center; justify-content: center; width: 4.75rem; height: 4.75rem;
  background: #07090a; border: 1px solid var(--line-strong); border-radius: var(--radius);
}
.discovery-words { min-width: 0; }
.discovery-card .kicker { display: block; font-size: 0.75em; letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.8; margin-bottom: 0.25rem; }
.discovery-card .title { font-family: var(--story-font); font-size: 1.05rem; line-height: 1.3; color: var(--text-strong); overflow-wrap: anywhere; }
.discovery-line { margin: 0.3rem 0 0; font-size: 0.85rem; line-height: 1.4; color: var(--muted); }
.discovery-recollection { flex-direction: column; align-items: stretch; gap: 0.7rem; min-width: min(17rem, calc(100vw - 2rem)); }
.discovery-list { list-style: none; margin: 0; padding: 0; }
.discovery-list li + li { margin-top: 0.25rem; }
.discovery-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem; }
.discovery-actions .btn { min-height: 44px; min-width: 44px; }
/* A card the story cannot be scrolled to show whole above the choices pinned at the pane's foot (page view, a short
   phone pane): the choices are unpinned while it stands and follow it, so a card that waits to be put away or read is
   never under them (discovery.js unpinIfCovered(), 2026-10-23). */
[data-page-view="1"] .discovery-slot.is-open.is-unpin + .choices { position: static; }
/* Each title on a Recollection's card has the warm dot of an unread entry, as in the Recollections. */
.discovery-recollection .title .new-dot { margin-left: 0.5em; }
/* Read opens the card where it stands (Jake, 2026-10-23): the entries' own words, one after another, scrolling within
   the card when long (discovery.js fitRead() sets the height left on screen); each has its chevron and dot. */
.discovery-recollection.is-expanded { width: min(30rem, calc(100vw - 2rem)); max-width: min(30rem, calc(100vw - 2rem)); }
.discovery-recollection.is-expanded.in-flow { width: min(30rem, 100%); max-width: min(30rem, 100%); }
.discovery-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.discovery-head .kicker { margin-bottom: 0; }
.discovery-read { overflow-y: auto; overscroll-behavior: contain; margin: 0 -0.4rem; padding: 0 0.4rem 0 1.2rem; }
.discovery-entries > li:last-child { border-bottom: 0; }
.discovery-entries .jentry-toggle { min-height: 44px; }
.discovery-entries .jentry-toggle .title { font-family: var(--font-mono); font-size: 0.8rem; }
/* A belonging just landed while the Belongings are open: its cell lit a moment. */
.pack-item.is-landed, .pocket-item.is-landed { box-shadow: 0 0 0 2px var(--new-dot), 0 0 0.9rem var(--new-dot-glow); transition: box-shadow 900ms ease; }
/* The starting kit: one card, a small grid of every thing with its name; it wraps on a phone. */
.discovery-kit { width: min(36rem, calc(100vw - 2rem)); max-width: min(36rem, calc(100vw - 2rem)); }
.discovery-kit .discovery-words { width: 100%; }
.discovery-kit-list { list-style: none; margin: 0.35rem 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(6.4rem, 1fr)); gap: 0.6rem 0.7rem; }
.discovery-kit-list li { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; min-width: 0; text-align: center; }
.discovery-kit-sprite {
  display: flex; align-items: center; justify-content: center; width: 3.6rem; height: 3.6rem;
  background: #07090a; border: 1px solid var(--line-strong); border-radius: var(--radius);
}
.discovery-kit-list .title { font-size: 0.9rem; line-height: 1.25; }
/* The control a card went into glows once; the dot beside its name is the engine's, as ever. */
.discovery-arrive { animation: discovery-arrive 900ms ease-out; }
@keyframes discovery-arrive {
  0% { box-shadow: inset 0 0 0 1px var(--new-dot), 0 0 0.9rem 0.1rem var(--new-dot-glow); }
  100% { box-shadow: inset 0 0 0 1px transparent, 0 0 0 0 transparent; }
}
/* A new entry shown in the Recollections themselves, while they are open (or read from its card): the amber edge and
   wash of the dot's own colour, breathing twice, then held steady until the glow ends (DISCOVERY_MS.emphasis). */
.entries li.is-discovered > .jentry-toggle {
  background: rgba(232, 182, 90, 0.2); box-shadow: inset 3px 0 0 var(--new-dot), 0 0 0.8rem 0 rgba(232, 182, 90, 0.35);
  color: var(--text-strong); animation: discovery-entry 1.6s ease-in-out 2;
  transition: background 900ms ease, box-shadow 900ms ease;
}
@keyframes discovery-entry {
  0%, 100% { background: rgba(232, 182, 90, 0.2); }
  50% { background: rgba(232, 182, 90, 0.34); }
}
@media (prefers-reduced-motion: reduce) {
  .discovery-card, .discovery-card.is-in { transform: none; transition: opacity 320ms ease; }
  .discovery-arrive { animation: none; box-shadow: inset 0 0 0 1px var(--new-dot); }
  .entries li.is-discovered > .jentry-toggle { animation: none; }
}
html.gui-reduce-motion .discovery-card, html.gui-reduce-motion .discovery-card.is-in { transform: none; transition: opacity 320ms ease; }
html.gui-reduce-motion .discovery-arrive { animation: none; box-shadow: inset 0 0 0 1px var(--new-dot); }
html.gui-reduce-motion .entries li.is-discovered > .jentry-toggle { animation: none; }
