:root {
  /* The Lantern (2026-10-15): the map edge to edge, the light as status (cobalt while the world
     speaks, ochre while it listens), the reader (the caption paged with the arrows) and the slab
     at the foot. One family, Archivo; controls have a 4px corner. */
  color-scheme: dark;
  --w-lit: #2a4d9b; --w-ink: #f5f2ea; --w-soft: #c3cfea; --w-you: #f2c35a;
  --w-unlit: #0f1626; --w-unlit-ink: #c9d5f0; --w-unlit-soft: #6e81ab; --w-unlit-you: #e4ab2c;
  --side: #17244a; --side-ink: #f5f2ea; --side-soft: #a9b8dc; --side-line: #ffffff24;
  --y-lit: #e4ab2c; --y-ink: #1a1405; --y-soft: #6b4f12;
  --y-unlit: #15120c; --y-unlit-ink: #d3a43c; --y-unlit-soft: #8a6c2a;
  --r: 4px;
  --font: 'Archivo', 'Arial', sans-serif;
  /* The Lantern's two curves (2026-10-15): a spring for what travels (the camera, a path, the
     name), 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(--w-unlit); }
body { margin: 0; min-height: 100svh; color: var(--w-unlit-ink); font: 400 17px/1.5 var(--font); -webkit-font-smoothing: antialiased; --soft: var(--w-unlit-soft); }
button, input { font: inherit; color: inherit; }
[hidden] { display: none !important; }
/* Controls: flat, a 4px corner; an outline in the ink around them 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 1.5px currentColor; cursor: pointer;
  white-space: nowrap; font-weight: 600; font-stretch: 112%; font-size: 15px; letter-spacing: .02em; transition: transform .08s;
}
button:hover:not(:disabled) { background: color-mix(in srgb, currentColor 12%, transparent); }
button:active:not(:disabled) { transform: translateY(1px); }
button:disabled { cursor: default; color: var(--soft); box-shadow: inset 0 0 0 1.5px 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 is the one ochre thing on the screen. While it waits it is an outline in the
   soft ink, the same size, so nothing moves when it fills in. */
.primary { background: var(--y-lit); color: var(--y-ink); box-shadow: none; }
.primary:hover:not(:disabled) { background: var(--w-you); }
.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; }
.link { min-height: 0; padding: 4px 0; box-shadow: none; font-weight: 500; font-stretch: 100%; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }
.link:hover:not(:disabled) { background: none; }
/* 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 Lantern (2026-10-15) ---------- */
/* The map fills the screen edge to edge; the place's name and End Crossing sit over its top, the
   reader (the caption between ‹ and ›) and the slab (the player's controls) over its foot. 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
   and the slab lit ochre, `thinking` leaves the field dark and the ring breathing. Every value is
   the approved mockup's (docs/superpowers/specs/2026-10-15-player-map-mock/). */
.play {
  --field: var(--w-unlit); --ink: var(--w-unlit-ink); --soft: var(--w-unlit-soft); --you: var(--w-unlit-you);
  position: fixed; inset: 0; overflow: hidden; background: var(--field); color: var(--ink);
}
.play[data-state=speaking] { --soft: var(--w-soft); --ink: var(--w-ink); }
.glow { position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity 1s var(--settle), background .8s; background: radial-gradient(circle at 50% 45%, #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 50% 45%, #e4ab2c40 0, #e4ab2c14 18%, transparent 40%); }

/* The map (player-map.ts draws it): every place a ✦, the current one ochre in its ring, the
   camera keeping it at the centre, 45% down. Marks and labels keep their size on screen at any
   scale (player-map.ts scales them by the view's units per pixel). */
.map-wrap { position: absolute; inset: 0; }
.map { position: absolute; inset: 0; width: 100%; height: 100%; display: block; overflow: visible; }
.map .cam { transition: transform 1.8s var(--spring); }
.map.still :is(.cam, .here) { transition: none; }
.map :is(.ripple, .here) { vector-effect: non-scaling-stroke; }
/* A path appears only as the player sets out along it, drawn from where they stood. */
.map .way { fill: none; stroke: var(--ink); stroke-linecap: round; opacity: .7; transition: stroke-dashoffset 1.8s var(--spring); }
.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(--you); font-size: 34px; }
.map .label { fill: var(--soft); font: 500 15px var(--font); letter-spacing: .01em; transition: fill .8s; }
.map .unidentified .label { font-style: italic; }
.map .visited .label { fill: var(--ink); }
.map .current .label { transform: translateY(14px); fill: var(--you); font-weight: 700; font-stretch: 125%; font-size: 17px; text-transform: uppercase; letter-spacing: .03em; }
.map .here { fill: none; stroke: var(--you); stroke-width: 2.5; transition: transform 1.8s var(--spring); }
.play[data-state=thinking] .map .here { animation: breathe 2.4s ease-in-out infinite; }
.play[data-state=speaking] .map .here { stroke: var(--w-ink); }
/* 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); }
.map .ripple { fill: none; stroke: var(--ink); stroke-width: 1.5; transform-box: fill-box; transform-origin: center; animation: ripple 2.8s var(--settle) forwards; }

/* The name and the map wait 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; both
   fade in with the player's first turn. */
.play :is(.map-wrap, .name) { transition: opacity .6s ease-out, visibility .6s; }
.play.map-waiting :is(.map-wrap, .name) { opacity: 0; visibility: hidden; }

/* The top: the place's name, uppercase, wide and heavy, entering again on each arrival; End
   Crossing (and skip mode's Fast-forward) at the right, outlined in the field's ink. The name
   takes the row's free width and one line's height even empty, so End Crossing stays right. */
.top { position: absolute; left: clamp(18px, 4vw, 64px); right: clamp(18px, 4vw, 64px); top: clamp(18px, 3.4vw, 44px); display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.name { flex: 1 1 auto; align-self: stretch; min-width: 0; min-height: .95em; margin: 0; font-weight: 800; font-stretch: 125%; text-transform: uppercase; font-size: clamp(30px, 4vw, 58px); line-height: .95; color: var(--ink); overflow-wrap: break-word; text-wrap: balance; }
.name.arrive { animation: name-in 1.6s var(--spring) both; }
.top-actions { display: flex; flex-shrink: 0; gap: 10px; }
.top-actions button { color: var(--ink); }
.fast-forward { gap: 8px; }
.fast-forward span { font-size: 20px; line-height: 1; }

/* The foot: the reader over the slab, centred, min(900px, 100% − 32px) wide. */
.lower { position: absolute; z-index: 3; left: 50%; bottom: 0; transform: translateX(-50%); width: min(900px, calc(100% - 32px)); display: flex; flex-direction: column; align-items: stretch; gap: 22px; padding-bottom: calc(28px + env(safe-area-inset-bottom, 0px)); }

/* The reader: the caption between back and forward, two big outlined squares. Paging back dims
   the words and fills forward in the player's ochre; anything new from the world returns it to
   now. A long paragraph scrolls inside the caption, its top fading once scrolled. */
.reader { display: grid; grid-template-columns: 64px minmax(0, 1fr) 64px; align-items: center; gap: 16px; }
.caption { margin: 0; max-height: 40svh; overflow-y: auto; scrollbar-width: none; text-align: left; text-wrap: pretty; font-size: clamp(22px, 2.2vw, 30px); line-height: 1.34; color: var(--ink); min-height: 2.7em; transition: color .4s var(--settle); }
.caption.faded { -webkit-mask-image: linear-gradient(transparent, #000 2.4em); mask-image: linear-gradient(transparent, #000 2.4em); }
/* Each sentence fades in as it is said (2026-09-29), and each of the player's lines. */
.caption .said { animation: fade-in 1.2s var(--settle) both; }
.caption.you { color: var(--you); font-style: italic; }
.caption.asked { color: var(--soft); font-style: italic; font-size: clamp(18px, 1.6vw, 22px); min-height: calc(2.7 * clamp(22px, 2.2vw, 30px)); } /* the row holds its height: the arrows never move under a finger */
.play.paging .caption { color: var(--soft); }
.play.paging .caption.you { color: color-mix(in srgb, var(--you) 65%, transparent); }
.arrow { width: 64px; height: 64px; min-height: 64px; padding: 0; color: var(--ink); box-shadow: inset 0 0 0 2px currentColor; }
.arrow svg { width: 34px; height: 34px; }
.arrow:hover:not(:disabled) { background: color-mix(in srgb, var(--ink) 16%, transparent); }
.arrow:disabled { opacity: .22; color: var(--ink); box-shadow: inset 0 0 0 2px currentColor; }
.play.paging #page-fwd { background: var(--y-lit); color: var(--y-ink); box-shadow: none; }

/* The slab: the player's controls, lit ochre on their turn. The world's turn: its words alone,
   no box and no button, so nothing looks pressable; the slab keeps its height (80px), so nothing
   moves when the controls come back. */
.slab { display: flex; gap: 10px; align-items: center; min-height: 80px; padding: 12px; border-radius: var(--r); background: var(--y-unlit); color: var(--y-unlit-ink); --soft: var(--y-unlit-soft); transition: background-color .5s var(--settle), color .5s var(--settle); }
.play:is([data-state=listening], [data-state=ready]) .slab { background: var(--y-lit); color: var(--y-ink); --soft: var(--y-soft); }
.play:is([data-state=thinking], [data-state=speaking]) .slab :is(.instrument, .session-actions, .send, #hint) { display: none; }
/* One mic square, on the player's turn: the mic button where the page has one, filled while the
   mic is on; else the drawn square (typed, host audio). */
.instrument { display: grid; place-items: center; width: 56px; height: 56px; flex-shrink: 0; border-radius: var(--r); color: var(--soft); box-shadow: inset 0 0 0 1.5px var(--soft); }
.instrument .mic-glyph { width: 22px; height: 22px; }
.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; }
.play:is([data-state=listening], [data-state=ready]) :is(.instrument, .mic-button[data-mode=on]) { background: var(--y-ink); color: var(--y-lit); box-shadow: none; }
/* The mic button: "Enable mic" (or "Retry mic") an outline with its label; on and paused, a
   square icon button. */
.mic-button { min-height: 56px; }
.mic-button:is([data-mode=on], [data-mode=paused]) { width: 56px; padding: 0; }
.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: 56px; padding: 0 8px; color: var(--soft); font-size: 19px; 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: 56px; padding: 0 18px; border-radius: var(--r); box-shadow: inset 0 0 0 1.5px currentColor; }
.input-form:focus-within { box-shadow: inset 0 0 0 2.5px currentColor; }
.input-form input { width: 100%; min-width: 0; padding: 0; border: 0; background: none; font-size: 19px; }
.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 box while the mic listens,
   "Listening..." in bold, then what to do in the placeholder's italic; the words heard so far
   take its place in the player's italic. Drawn, never typed: a press reaches the box under it. */
.listen-cue { position: absolute; inset: 0 18px; display: flex; align-items: center; min-width: 0; overflow: hidden; white-space: nowrap; pointer-events: none; font-size: 19px; }
.listen-cue strong { margin-right: .35em; font-weight: 700; }
.listen-cue .cue-help { color: var(--soft); font-style: italic; }
.listen-cue .cue-words { overflow: hidden; text-overflow: ellipsis; font-style: italic; }
.send { min-height: 56px; padding: 0 22px; background: var(--y-ink); color: var(--y-lit); box-shadow: none; }
.send:hover:not(:disabled) { background: #000; }
.send:disabled { background: none; }
/* ? Hint (2026-10-15): a bold ?, then "Hint", outlined in the slab's ink, last in the row. */
.slab #hint { min-height: 56px; gap: 8px; }
.q { display: grid; place-items: center; width: 24px; height: 24px; font-size: 22px; font-weight: 800; font-stretch: 100%; line-height: 1; }

@keyframes fade-in { from { opacity: 0; } }
@keyframes arrive { from { opacity: 0; } }
@keyframes breathe { 50% { opacity: .35; } }
@keyframes name-in { from { opacity: 0; letter-spacing: .08em; } }
@keyframes ripple { from { transform: scale(.4); opacity: .9; } to { transform: scale(4.2); opacity: 0; } }

/* A phone (≤ 680px): End Crossing (and Fast-forward) under the name, 36px tall; the reader's
   squares narrower; the slab two rows, the mic and Send and Hint, at the right, then the entry (or the
   world's words in its place), 140px whoever has the turn. */
/* A short laptop or tablet: a long paragraph's caption stays under the current star (45% down). */
@media (max-height: 820px) and (min-width: 681px) { .caption { max-height: 30svh; } }
@media (max-width: 680px) {
  .top { flex-direction: column; gap: 10px; }
  .name { font-size: 26px; max-width: 80vw; }
  .top-actions button { min-height: 36px; padding: 0 10px; font-size: 13px; }
  .fast-forward span { font-size: 16px; }
  .lower { gap: 12px; padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px)); width: calc(100% - 20px); }
  .reader { grid-template-columns: 48px minmax(0, 1fr) 48px; gap: 8px; }
  .arrow { width: 48px; height: 56px; min-height: 56px; }
  .arrow svg { width: 28px; height: 28px; }
  .caption { font-size: 18px; max-height: 24svh; }
  .slab { flex-wrap: wrap; min-height: 140px; }
  .band-slot { order: 3; flex-basis: 100%; }
  .state-copy, .input-form { height: 52px; min-height: 52px; font-size: 17px; }
  .input-form input, .listen-cue { font-size: 17px; }
  .instrument { width: 52px; height: 52px; }
  .mic-button, .send { min-height: 52px; padding: 0 14px; }
  .mic-button:is([data-mode=on], [data-mode=paused]) { width: 52px; padding: 0; }
  .slab #hint { margin-left: auto; min-height: 52px; padding: 0 14px; }
}
