:root {
  /* The literary page (2026-10-26): the twilight is the paper, the text warm pale ink; one family,
     Alegreya; controls keep a 4px corner. No small capitals labels, no pills, no textures. */
  color-scheme: dark;
  --sky: #161f3f; --ink: #ece4d1; --soft: #9fa7c4; --faint: #ffffff1c; --you: #e9bb5f; --here: #e4ab2c;
  --r: 4px;
  --font: 'Alegreya', Georgia, serif;
  /* The Lantern's tokens (2026-10-15) still read, as aliases onto the new ones: the map's paint
     (`--w-ink`), the debug sheet, the monitor and the overlays' ochre bands. Those with no new
     counterpart keep their value: the side panel's ground and the dark ink on ochre. */
  --w-ink: var(--ink); --w-unlit: var(--sky); --w-unlit-ink: var(--ink); --w-unlit-soft: var(--soft);
  --side: #17244a; --side-ink: var(--ink); --side-soft: var(--soft); --side-line: var(--faint);
  --y-lit: var(--here); --y-ink: #1a1405;
  /* The Lantern's two curves (2026-10-15): a spring for what travels (the camera, a path), a
     settle for what fades. */
  --spring: cubic-bezier(.32, .72, 0, 1);
  --settle: cubic-bezier(.22, .61, .36, 1);
}
* { box-sizing: border-box; }
html { background: var(--sky); }
body { margin: 0; min-height: 100svh; background: var(--sky); color: var(--ink); font: 400 20px/1.55 var(--font); -webkit-font-smoothing: antialiased; }
button, input { font: inherit; color: inherit; }
[hidden] { display: none !important; }
/* A station's pages (role.ts, 2026-10-23). The sound page draws only its monitor, for the staff:
   the station, what it does now, red while it is stalled. The screen has no mic button: the
   headset listens. */
body[data-role="sound"] :is(#start, #play, #resume, .toasts) { display: none !important; }
body[data-role="screen"] #mic-toggle { display: none !important; }
/* The monitor keeps its look and its own family: Alegreya is the player's page's alone. */
.monitor { max-width: 720px; margin: 48px auto; padding: 24px 28px; border-radius: var(--r); background: var(--side); color: var(--side-ink); box-shadow: inset 0 0 0 3px #3fae6a; font: 400 17px/1.5 system-ui, sans-serif; }
.monitor[data-stalled="true"] { box-shadow: inset 0 0 0 3px #d0473c; }
.monitor h1 { margin: 0 0 16px; font-size: 26px; font-stretch: 112%; }
.monitor dl { display: grid; grid-template-columns: max-content 1fr; gap: 8px 20px; margin: 0; }
.monitor dt { color: var(--side-soft); }
.monitor dd { margin: 0; overflow-wrap: anywhere; }
.monitor meter { width: 100%; }
.monitor-alarm { margin: 0 0 16px; padding: 12px 16px; border-radius: var(--r); background: #d0473c; color: #fff; font-weight: 600; }
/* Controls: flat, a 4px corner, a fine outline in their ink unless filled. */
button {
  min-height: 44px; padding: 0 18px; display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  border: 0; border-radius: var(--r); background: none; box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 55%, transparent); cursor: pointer;
  white-space: nowrap; font-weight: 500; font-size: 17px; transition: transform .08s;
}
button:hover:not(:disabled) { background: color-mix(in srgb, currentColor 10%, transparent); }
button:active:not(:disabled) { transform: translateY(1px); }
button:disabled { cursor: default; color: var(--soft); box-shadow: inset 0 0 0 1px var(--soft); }
button svg { width: 22px; height: 22px; flex-shrink: 0; }
button:focus-visible, input:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }
/* The next step: filled in the ink, the sky's colour in its words. While it waits it is an
   outline in the soft ink, the same size, so nothing moves when it fills in. */
.primary { background: var(--ink); color: var(--sky); box-shadow: none; }
.primary:hover:not(:disabled) { background: color-mix(in srgb, var(--ink) 85%, #fff); }
.primary:disabled { background: none; }
.step-go { min-width: 300px; min-height: 68px; padding: 0 26px; font-size: 20px; }
.step-go svg { width: 26px; height: 26px; }
/* A link: soft words, a faint underline. */
.link { min-height: 0; padding: 4px 0; box-shadow: none; font-weight: 400; color: var(--soft); text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--faint); text-underline-offset: 5px; }
.link:hover:not(:disabled) { background: none; color: var(--ink); text-decoration-color: currentColor; }
/* Read by a screen reader, never drawn: the wizard's "Step N of M". */
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* ---------- Play's screen: the open book (the literary page, 2026-10-26) ---------- */
/* On a wide screen a two-page spread: the text page at the left (the transcript, its newest words
   at the foot over the writing line), the map page at the right (the map filling it, End Crossing
   at its top right). A soft shadow down the middle is the gutter; nothing else marks the pages.
   The light is the status, never a word in a box: `setState` sets #play's data-state, and
   `speaking` spreads cobalt light from where the player stands, `listening` and `ready` a faint
   warm glow, `thinking` leaves the sky dark and the ring breathing. */
.play {
  position: fixed; inset: 0; overflow: hidden; background: var(--sky); color: var(--ink);
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
}
.play::after {
  content: ""; position: absolute; z-index: 1; left: 50%; top: 0; bottom: 0; width: 80px; transform: translateX(-50%); pointer-events: none;
  background: linear-gradient(90deg, transparent, #0000002e 48%, #ffffff08 50%, #0000002e 52%, transparent);
}
/* A page of the spread; its crossfade on a crossing is way.css's (`.play.crossing > .leaf`). */
.leaf { position: relative; min-width: 0; min-height: 0; }
.leaf-map { overflow: hidden; }
/* The light spreads from where the player stands: the current place's ✦ as the map's camera
   will rest (player-map.ts sets --here-x and --here-y on #play, in the map's pixels: the glow is
   the map page's, so they need no offset), gliding with it; 50%, 45% until the map is drawn. */
@property --here-x { syntax: "<length-percentage>"; inherits: true; initial-value: 50%; }
@property --here-y { syntax: "<length-percentage>"; inherits: true; initial-value: 45%; }
.play { transition: --here-x 1.8s var(--spring), --here-y 1.8s var(--spring); }
.play:has(.map.still) { transition: none; }
.glow { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity 1s var(--settle), background .8s; background: radial-gradient(circle at var(--here-x) var(--here-y), #2a4d9bee 0, #2a4d9b55 26%, transparent 58%); }
.play[data-state=speaking] .glow { opacity: 1; }
.play:is([data-state=listening], [data-state=ready]) .glow { opacity: .55; background: radial-gradient(circle at var(--here-x) var(--here-y), #e4ab2c40 0, #e4ab2c14 18%, transparent 40%); }

/* The map (player-map.ts draws it): every place a ✦, the current one ochre, the camera framing
   every listed place (the literary page, 2026-10-26). player-map.ts sets
   the camera as it will rest, in the map's viewBox (its pixels): `--zoom-x` and `--zoom-y`
   pixels a map unit across and down (each direction stretched on its own, 2026-10-20), the map
   point (`--look-x`, `--look-y`) drawn at the screen point (`--anchor-x`, `--anchor-y`), and the
   ring at the current place (`--ring-x`, `--ring-y`). Registered, they glide together over a
   crossing, and every mark is scaled by their inverses as they do, so it keeps its size and
   shape on screen throughout; a path's stroke is set from its direction (player-map.ts). */
@property --zoom-x { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --zoom-y { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --look-x { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --look-y { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --anchor-x { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --anchor-y { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --ring-x { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --ring-y { syntax: "<number>"; inherits: true; initial-value: 0; }
.map-wrap { position: absolute; inset: 0; }
/* The player drags it to look around (player-map.ts): a finger's drag never scrolls the page,
   and a drag never selects a label. */
.map {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block; overflow: visible; touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none;
  transition-property: --zoom-x, --zoom-y, --look-x, --look-y, --anchor-x, --anchor-y, --ring-x, --ring-y;
  transition-duration: 1.8s; transition-timing-function: var(--spring);
}
.map.dragging { cursor: grabbing; }
.map.farewell { cursor: auto; }
.map .cam { transform: translate(calc(var(--anchor-x) * 1px), calc(var(--anchor-y) * 1px)) scale(var(--zoom-x), var(--zoom-y)) translate(calc(var(--look-x) * -1px), calc(var(--look-y) * -1px)); }
.map.still, .map.still .label { transition: none; }
.map .here { vector-effect: non-scaling-stroke; }
/* A halo, drawn round its place's middle, keeps its size on screen. */
.map .halo { transform-box: fill-box; transform-origin: center; scale: calc(1 / var(--zoom-x)) calc(1 / var(--zoom-y)); }
/* A path appears only as the player sets out along it, drawn from where they stood: a fine line
   in the soft ink. A way to a place not reached is dotted (its dots inline, player-map.ts),
   drawn whole. */
.map .way { fill: none; stroke: var(--soft); stroke-linecap: round; opacity: .55; transition: stroke-dashoffset 1.8s var(--spring); }
.map .untaken { fill: none; stroke: var(--soft); opacity: .45; }
.map .place { animation: arrive 1.2s var(--settle) both; }
.map .star { fill: var(--soft); font-size: 26px; transition: fill .8s var(--settle), font-size .8s var(--spring); }
.map .visited .star { fill: var(--ink); }
.map .current .star { fill: var(--here); font-size: 34px; }
/* A label stays wholly inside the map (player-map.ts's `keepIn` sets its `translate`, which glides
   with the camera); one whose ✦ is out of view is hidden. */
/* Labels in Alegreya's italic: `Unmapped` soft, a reached place in the ink, the current place
   in its ochre, a little larger (its baseline under the ring, player-map.ts). */
.map .label { fill: var(--soft); font: italic 400 17px var(--font); transition: fill .8s, translate 1.8s var(--spring); }
.map .visited .label { fill: var(--ink); }
.map .current .label { fill: var(--here); font-size: 19px; }
/* The ring shows only while it carries meaning (the literary page, 2026-10-26): while the mic
   listens, following the level, and while the world thinks, breathing; else the ✦'s glow alone. */
.map .here { visibility: hidden; fill: none; stroke: var(--here); stroke-width: 2.5; transform: translate(calc(var(--ring-x) * 1px), calc(var(--ring-y) * 1px)) scale(calc(1 / var(--zoom-x)), calc(1 / var(--zoom-y))); }
.play:is([data-state=listening], [data-state=thinking]) .map .here { visibility: visible; }
.play[data-state=thinking] .map .here { animation: breathe 2.4s ease-in-out infinite; }
/* While the mic listens the ring follows the level (app.ts sets --level on #map, which only the ring reads). */
.play[data-state=listening] .map .here { stroke-width: calc(2.5px + var(--level, 0) * 10px); }
/* The glow (player-map.ts's `paint`): its fill follows each frame, so no fill transition. */
.map .halo { pointer-events: none; }
.map .star.glowing { transition: font-size .8s var(--spring); }

/* The farewell view (the visit timer, 2026-10-19; player-map.ts's `farewell`): the places reached
   ochre, the walked paths ochre and dashed (the dashes are inline, set once the camera rests),
   the dotted lines to the unreached kept. The camera and the labels glide to it on the map's own
   transitions, as over a crossing. */
.map.farewell .visited :is(.star, .label) { fill: var(--here); }
.map.farewell .way { stroke: var(--here); opacity: 1; }

/* The map waits for the opening (2026-10-10; `map-waiting`, set by app.ts's `drawWaiting`):
   through the crossing and the opening only the light and the words show; it fades in with the
   player's first turn. */
.play .map-wrap { transition: opacity .6s ease-out, visibility .6s; }
.play.map-waiting .map-wrap { opacity: 0; visibility: hidden; }

/* The map page's top: End Crossing a quiet underlined link at the right (skip mode's
   Fast-forward and Debug beside it, links too). No name over the map since 2026-10-18 (the
   developer: the map's current label says it). */
.top { position: absolute; z-index: 2; left: 32px; right: 28px; top: 16px; display: flex; justify-content: flex-end; align-items: center; gap: 16px; }
.top-actions { display: flex; flex-shrink: 0; gap: 22px; }
.top-actions button { min-height: 44px; padding: 0; box-shadow: none; font-size: 15px; font-weight: 400; color: var(--soft); text-decoration: underline; text-decoration-thickness: 1px; text-decoration-color: var(--faint); text-underline-offset: 5px; }
.top-actions button:hover:not(:disabled) { background: none; color: var(--ink); text-decoration-color: currentColor; }
.fast-forward { gap: 6px; }
.fast-forward span { font-size: 16px; line-height: 1; }
/* The developer's clock control (2026-10-20), at the left, at `?test=true` only. */
.clock { margin-right: auto; align-self: center; display: flex; align-items: center; gap: 8px; color: var(--ink); font-size: 16px; }
.clock[hidden] { display: none; }
.clock-left { min-width: 4.5ch; font-variant-numeric: tabular-nums; }
.clock input { width: 6ch; min-height: 36px; padding: 0 6px; border: 1px solid var(--soft); border-radius: var(--r); background: transparent; }
.clock button { min-height: 36px; padding: 0 10px; font-size: 15px; }

/* The text page: the transcript scrolls above the writing line, its newest words at the foot, its
   top fading. Back to the newest sits over the writing line's rule. */
.leaf-text { display: flex; flex-direction: column; padding: 44px 64px calc(30px + env(safe-area-inset-bottom, 0px)) 72px; }
.scroll {
  flex: 1; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none;
  -webkit-mask-image: linear-gradient(transparent, #000 70px); mask-image: linear-gradient(transparent, #000 70px);
}
/* Back to the newest: centred on the writing line's rule (the slab's 18px margin above it, 12px
   on the fold), breaking it, taking no room. */
.newest { position: relative; z-index: 1; align-self: center; min-height: 32px; margin: 2px 0 -34px; padding: 0 12px; background: var(--sky); font-size: 16px; font-style: italic; }
@media (max-width: 899px) {
  .newest { margin: -4px 0 -28px; }
}

/* The transcript (reader.ts writes it): each arrival a chapter, a small soft ✦ over the place's
   name, centred and italic; the narrator's paragraphs plain, the player's words italic in their
   gold, flush, the ending's italic; notes and the waiting line read by a screen reader, never
   drawn. */
.transcript { margin-top: auto; padding-top: 70px; text-wrap: pretty; }
.transcript .chapter { margin: 26px 0 14px; font-size: 22px; font-style: italic; font-weight: 400; line-height: 1.3; text-align: center; color: var(--ink); text-wrap: balance; }
.transcript .chapter::before { content: "✦" / ""; display: block; margin-bottom: 4px; font-size: 13px; font-style: normal; color: var(--soft); }
.transcript .entry.line .text { margin: 0; }
.transcript .paragraph-break { display: block; height: .55em; }
.transcript .entry.you { margin: 12px 0; font-style: italic; color: var(--you); }
.transcript .entry + .entry.line { margin-top: 12px; }
.transcript .entry.ending { font-style: italic; }
.transcript .entry:is(.noted, .waiting) { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* Each sentence fades in as it is said (2026-09-29), each of the player's lines and each chapter;
   a rebuilt transcript is drawn still. */
.transcript .said { animation: fade-in 1.2s var(--settle) both; }
/* More coming (2026-10-17): a ✦ after the newest narrator words while the reply is still on its
   way, joined to the word (a word joiner) and silent to a screen reader; still under reduced
   motion. */
.transcript .entry.coming .text::after { content: "\2060✦" / ""; margin-left: .35em; color: var(--soft); font-size: .7em; font-style: normal; vertical-align: .1em; animation: coming 2.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .transcript .entry.coming .text::after { opacity: .55; }
}

/* The writing line: a faint rule, under it the mic, the entry and Send; no band. The world's turn:
   its words alone, italic, soft and breathing, at the same height, with no mic and no Send, so
   nothing looks pressable and nothing moves when the controls come back. */
.slab { display: flex; gap: 14px; align-items: center; min-height: 59px; margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--faint); color: var(--soft); transition: border-color .5s var(--settle); }
.slab:focus-within { border-top-color: color-mix(in srgb, var(--soft) 55%, transparent); }
.play:is([data-state=thinking], [data-state=speaking]) .slab :is(.instrument, .session-actions, .send) { display: none; }
/* One mic, on the player's turn: the mic button where the page has one, its words beside its
   glyph; else the drawn glyph (typed, host audio). Gold while the mic listens. */
.instrument { display: grid; place-items: center; width: 22px; height: 44px; flex-shrink: 0; color: var(--soft); }
.instrument .mic-glyph { width: 18px; height: 18px; }
.session-actions { display: flex; align-items: center; flex-shrink: 0; }
.session-actions:not(:has(> #mic-toggle:not([hidden]))) { display: none; }
.slab:has(#mic-toggle:not([hidden])) .instrument { display: none; }
.mic-button { min-height: 44px; padding: 0; gap: 8px; box-shadow: none; color: var(--soft); font-size: 16px; font-weight: 400; }
.mic-button:hover:not(:disabled) { background: none; color: var(--ink); }
.mic-button svg { width: 18px; height: 18px; }
.mic-button[data-mode=on] { color: var(--ink); }
.play[data-state=listening] :is(.instrument, .mic-button[data-mode=on]) { color: var(--you); }
.mic-button .mic-off, .mic-button[data-mode=paused] .mic-on { display: none; }
.mic-button[data-mode=paused] .mic-off { display: block; }
.mic-label:empty { display: none; }
.band-slot { position: relative; flex: 1; min-width: 0; }
/* The world's words ("Thinking...", "Speaking..."), breathing, with no box around them. */
.state-copy { display: flex; align-items: center; min-height: 44px; color: var(--soft); font-size: 18px; font-style: italic; line-height: 1.3; }
.state-copy strong { font-weight: 400; animation: breathe 2.4s ease-in-out infinite; }
/* The player's turn: read, never drawn (the entry and its placeholder say it). */
.play:is([data-state=listening], [data-state=ready]) .state-copy {
  position: absolute; width: 1px; height: 1px; min-height: 0; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.input-form { position: relative; display: flex; align-items: center; height: 44px; }
.input-form input { width: 100%; min-width: 0; padding: 0; border: 0; background: none; color: var(--ink); font-size: 18px; text-overflow: ellipsis; }
.input-form input::placeholder { color: var(--soft); font-style: italic; opacity: 1; }
.input-form input:focus-visible { outline: none; }
/* Play's listening (the wizard upgrade, 2026-10-11): over the empty entry while the mic listens,
   "Listening..." in the ink, then what to do in the placeholder's italic; the words heard so far
   take its place in the player's gold italic. Drawn, never typed: a press reaches the entry. */
.listen-cue { position: absolute; inset: 0; display: flex; align-items: center; min-width: 0; overflow: hidden; white-space: nowrap; pointer-events: none; font-size: 18px; }
.listen-cue strong { margin-right: .35em; font-weight: 500; color: var(--ink); }
.listen-cue .cue-help { color: var(--soft); font-style: italic; }
.listen-cue .cue-words { overflow: hidden; text-overflow: ellipsis; font-style: italic; color: var(--you); }
.send { min-height: 44px; padding: 0 0 0 6px; box-shadow: none; color: var(--you); font-size: 16px; font-weight: 400; }
.send:hover:not(:disabled) { background: none; text-decoration: underline; text-underline-offset: 5px; }
.send:disabled { box-shadow: none; color: var(--soft); opacity: .6; }

/* The farewell, the book's closing page (the literary page, 2026-10-26; farewell.ts's
   `showFarewell`; docs/architecture/screen-close.md): the visit over, it closes the transcript
   under the ending's words, centred: a small soft ✦, the greeting its own italic line, the visited
   line soft, and Visit Again. The writing line, Back to the newest and End Crossing go; the warm
   glow is out already, `.glow` lit only while speaking, listening or ready. */
.play[data-state=farewell] :is(.top-actions, .slab, .newest) { display: none; }
.closing-page { display: flex; flex-direction: column; align-items: center; padding: 26px 0 6px; text-align: center; }
.closing-mark { margin-bottom: 14px; font-size: 13px; line-height: 1; color: var(--soft); }
.farewell-words { margin: 0 0 22px; font-size: 18px; color: var(--soft); text-wrap: pretty; }
.farewell-words strong { display: block; margin-bottom: 6px; font-size: 26px; font-style: italic; font-weight: 400; line-height: 1.3; color: var(--ink); }
.closing-page .primary { min-height: 52px; padding: 0 26px; }

@keyframes fade-in { from { opacity: 0; } }
@keyframes arrive { from { opacity: 0; } }
@keyframes breathe { 50% { opacity: .35; } }
@keyframes coming { 0%, 100% { opacity: .25; } 50% { opacity: .9; } }

/* A short spread (a landscape tablet, a short laptop): the pages' margins tighten. */
@media (min-width: 900px) and (max-height: 700px) {
  .leaf-text { padding-top: 28px; padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px)); }
}
@media (min-width: 900px) and (max-width: 1100px) {
  .leaf-text { padding-left: 48px; padding-right: 48px; }
}
/* The fold (under 900px: a phone, a tablet held upright): one page, the map a band on top with
   a faint rule under it, End Crossing at its top right; the transcript below and the writing
   line at the foot. Body text 18px. */
@media (max-width: 899px) {
  body { font-size: 18px; }
  .play { grid-template-columns: minmax(0, 1fr); grid-template-rows: min(36svh, 320px) minmax(0, 1fr); }
  .play::after { display: none; }
  .leaf-map { grid-row: 1; border-bottom: 1px solid var(--faint); }
  /* A measure of at most 680px, centred, on a tablet held upright. */
  .leaf-text { grid-row: 2; padding: 4px max(16px, calc((100% - 680px) / 2)) calc(12px + env(safe-area-inset-bottom, 0px)); }
  .top { left: 16px; right: 16px; top: 4px; }
  .top-actions { gap: 18px; }
  .scroll { -webkit-mask-image: linear-gradient(transparent, #000 40px); mask-image: linear-gradient(transparent, #000 40px); }
  .transcript { padding-top: 40px; }
  .slab { gap: 12px; margin-top: 12px; padding-top: 10px; min-height: 55px; }
  .input-form input, .listen-cue { font-size: 17px; }
  .state-copy { font-size: 17px; }
  .farewell-words strong { font-size: 24px; }
}
