/* ─────────────────────────────────────────────────────────────────────────
   LIVING CORE — Island era stylesheet, part 3 (SPEC3).
   Hand-written, no framework. Warm paper-and-ink by day, warm dark by night
   (prefers-color-scheme — no blue tint in either). Every text/background
   pair here is checked to >= 4.5:1. Works at 360px with no horizontal
   scroll; every tap target is >= 44px; [hidden] really hides (no author
   `display` ever overrides it — see the [hidden] rule near the bottom).

   NO SaaS chrome anywhere any more (SPEC3: "go away from old SaaS website
   style") — there is no `.site-header`, no `.site-footer`, no `.disclaimer`
   bar in this file on purpose; BaseLayout.tsx no longer renders them. Two
   bodies instead:
     `body.v-map`   — the home page. IS the map: fixed, full-viewport,
                      no scroll. Everything else floats (`.hud`, `.icon-btn`,
                      `<dialog>` cards).
     `body.v-sheet` — every other page. A blurred, dimmed island behind a
                      floating "paper sheet" holding the real content, plus
                      the shared back/menu icons (src/views/chrome.tsx).
   ───────────────────────────────────────────────────────────────────────── */

:root {
  --paper: #f6f1e4;
  --paper-2: #eee5d0;
  --card: #fffdf6;
  --card-2: #faf4e6;
  --border: #ddd0ae;
  --ink: #241f14;
  --ink-dim: #5a5240;
  --muted: #7a7159;
  --kevin: #1f8f87;
  --kevin-bg: #1f8f8718;
  --jenny: #c2185b;
  --jenny-bg: #c2185b16;
  --gold: #9a6a12;
  --gold-bg: #9a6a1218;
  --good: #2e7d32;
  --bad: #b3261e;
  --sea-1: #bfe0e6;
  --sea-2: #7fb6c4;
  --sand: #e6d3a1;
  --grass: #93a35e;

  --sky-dawn-1: #ffd9ad;
  --sky-dawn-2: #f7a978;
  --sky-morning-1: #bfe4f2;
  --sky-morning-2: #eef7ea;
  --sky-midday-1: #8fd0ee;
  --sky-midday-2: #cdeefb;
  --sky-afternoon-1: #a9d7ea;
  --sky-afternoon-2: #f3e3ae;
  --sky-evening-1: #6a4a86;
  --sky-evening-2: #e8895f;
  --sky-night-1: #10142b;
  --sky-night-2: #262c52;

  --radius: 14px;
  --radius-sm: 9px;
  --shadow: 0 1px 2px rgba(36, 31, 20, 0.06), 0 6px 20px rgba(36, 31, 20, 0.06);
  --font-ui: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-serif: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --font-hand: 'Caveat', 'Segoe Script', cursive;
  --max: 880px;
  --max-wide: 1180px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #17140f;
    --paper-2: #1d1912;
    --card: #211c14;
    --card-2: #251f16;
    --border: #3c3322;
    --ink: #efe6d3;
    --ink-dim: #c9bea3;
    --muted: #a3987e;
    --kevin: #56d9cf;
    --kevin-bg: #56d9cf1c;
    --jenny: #ff85ad;
    --jenny-bg: #ff85ad1c;
    --gold: #e2b714;
    --gold-bg: #e2b71420;
    --good: #6fcf74;
    --bad: #ff7a70;
    --sea-1: #123b46;
    --sea-2: #0b2530;
    --sand: #4a3f26;
    --grass: #45542c;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
  }
}
:root[data-theme="dark"] {
  --paper: #17140f;
  --paper-2: #1d1912;
  --card: #211c14;
  --card-2: #251f16;
  --border: #3c3322;
  --ink: #efe6d3;
  --ink-dim: #c9bea3;
  --muted: #a3987e;
  --kevin: #56d9cf;
  --kevin-bg: #56d9cf1c;
  --jenny: #ff85ad;
  --jenny-bg: #ff85ad1c;
  --gold: #e2b714;
  --gold-bg: #e2b71420;
  --good: #6fcf74;
  --bad: #ff7a70;
  --sea-1: #123b46;
  --sea-2: #0b2530;
  --sand: #4a3f26;
  --grass: #45542c;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
a { color: var(--kevin); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3, h4 { font-family: var(--font-ui); font-weight: 700; line-height: 1.25; margin: 0 0 .5em; color: var(--ink); }
h1 { font-size: 1.7rem; }
h2 { font-size: 1.25rem; }
h3 { font-size: 1.02rem; }
p { margin: 0 0 .8em; }
img, svg { max-width: 100%; display: block; }
button, input, textarea, select { font-family: inherit; font-size: inherit; color: inherit; }
ul { padding-left: 1.2em; }

.prose {
  font-family: var(--font-serif);
  font-size: 1.05rem;
  line-height: 1.75;
  color: var(--ink);
}
.prose p { margin: 0 0 1.1em; }

.wrap { max-width: var(--max); margin: 0 auto; padding: 0 16px; }
.wrap.wide { max-width: var(--max-wide); }

/* ═══════════════════════════════════════════════════════════════════════
   HOME (body.v-map) — the island IS the page.
   ═══════════════════════════════════════════════════════════════════════ */
body.v-map {
  position: fixed;
  inset: 0;
  overflow: hidden;
  overscroll-behavior: none;
}
/* Layout fix #2, v2 (owner, live: "remove background around the island,
   it's 2 different color, and add something like water waves around the
   island"). The first cut matched every layer's flat background-color to
   the SVG's own sea gradient — correct, but still N layers that all have to
   agree. This version removes the possibility of disagreement instead: the
   ocean is ONE fixed, full-viewport layer (`.ocean-layer`, below) painted
   BEHIND the entire pan/zoom stage, and `.island-root`/`.island-viewport`/
   `.map-wrap` are all transparent, so that one layer shows through
   everywhere nothing else paints — at any zoom, at any pan position,
   without a second colour ever being in the running. island-map.tsx's SVG
   no longer paints its own opaque "deep sea" rect at all (see its header
   comment by the sky rect). */
.island-root { position: fixed; inset: 0; overflow: hidden; background: transparent; }

.empty-island { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: var(--paper); padding: 16px; }
.empty-island-card { max-width: 420px; text-align: center; }

/* The ocean itself: a radial gradient from `--sea-1` (lighter, toward the
   island) to `--sea-2` (deeper, toward the edges) — the SAME two custom
   properties the map's own shallows/reef/surf paint with (below), so
   there's one colour system, not two. Sits BEHIND `.island-viewport`
   (explicit z-index; both are `position:fixed` siblings, so paint order
   would otherwise just be DOM order — this makes it not depend on that).
   OceanShimmer (island-map.tsx) draws the drifting wave strokes on top of
   it, inside its own SVG. */
.ocean-layer {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: radial-gradient(ellipse 78% 68% at 50% 36%, var(--sea-1) 0%, var(--sea-2) 100%);
}
.ocean-shimmer { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
.ocean-shimmer-stroke {
  fill: none; stroke: #fffdf6; stroke-opacity: .28; stroke-width: 2.2; stroke-linecap: round;
  animation: oceanShimmerDrift var(--dur, 9s) ease-in-out var(--delay, 0s) infinite;
}
@keyframes oceanShimmerDrift {
  0%, 100% { transform: translateX(0); opacity: .16; }
  50% { transform: translateX(22px); opacity: .34; }
}
/* Weather + time of day (owner: "weather affects the sea … calmer at
   night, bigger/faster/more whitecaps when windy or storm, glassy in
   heat") — scoped to `.island-root`, the shared ancestor of `.ocean-layer`
   (a sibling of `.map-wrap`, not a descendant of it, so `.map-wrap`'s own
   `[data-weather]` can't reach it — see script.js's updateWeather()). */
.island-root[data-weather="storm"] .ocean-shimmer-stroke,
.island-root[data-weather="windy"] .ocean-shimmer-stroke { animation-duration: 2.8s; stroke-opacity: .4; stroke-width: 2.8; }
.island-root[data-slot="night"] .ocean-shimmer-stroke { animation-duration: 12s; stroke-opacity: .12; }
.island-root[data-weather="heat"] .ocean-shimmer-stroke { animation-duration: 15s; stroke-opacity: .2; }

/* the pan/zoom viewport + stage (public/script.js's PanZoom module) */
.island-viewport {
  position: fixed;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  touch-action: none; /* we handle pan/pinch ourselves — the browser must not also scroll/zoom the page */
  background: transparent;
}
.map-stage {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  will-change: transform;
}
.map-stage.animated { transition: transform .5s cubic-bezier(.2,.7,.2,1); }
@media (prefers-reduced-motion: reduce) { .map-stage.animated { transition: none; } }

.map-wrap { position: absolute; inset: 0; background: transparent; }
/* overflow: visible — an <svg> clips to its own box by default, so the map's
   full-bleed tints (sky wash, storm flash, heat haze; drawn 9000 units wide in
   island-map.tsx) still stopped at the map's edge and drew a lighter RECTANGLE
   over the endless ocean layer ("2 different colors" around the island). */
.map-wrap svg { width: 100%; height: 100%; display: block; overflow: visible; }

.map-wrap[data-slot="dawn"] { --sky-1: var(--sky-dawn-1); --sky-2: var(--sky-dawn-2); }
.map-wrap[data-slot="morning"] { --sky-1: var(--sky-morning-1); --sky-2: var(--sky-morning-2); }
.map-wrap[data-slot="midday"] { --sky-1: var(--sky-midday-1); --sky-2: var(--sky-midday-2); }
.map-wrap[data-slot="afternoon"] { --sky-1: var(--sky-afternoon-1); --sky-2: var(--sky-afternoon-2); }
.map-wrap[data-slot="evening"] { --sky-1: var(--sky-evening-1); --sky-2: var(--sky-evening-2); }
.map-wrap[data-slot="night"] { --sky-1: var(--sky-night-1); --sky-2: var(--sky-night-2); }

/* The sea itself, tinted by time of day — same idea as the sky block above,
   but scoped to `.island-root[data-slot]` (the OUTERMOST element) so the
   override cascades DOWN into `.ocean-layer`'s gradient AND the SVG's
   shallows/reef/surf, never just one of them. `--sea-1`/`--sea-2` still
   fall back to the plain light/dark-theme values (`:root`, near the top of
   this file) outside the map, and here they override cleanly (same
   specificity as the `prefers-color-scheme` block, later in source order →
   wins) whenever a slot is known. Deliberately theme-agnostic, same as the
   sky block — the world's own day/night cycle drives this, independent of
   the visitor's light/dark reading preference. */
.island-root[data-slot="dawn"] { --sea-1: #cfe3d8; --sea-2: #6f8f95; }
.island-root[data-slot="morning"] { --sea-1: #c3e7ec; --sea-2: #7cb2bf; }
.island-root[data-slot="midday"] { --sea-1: #bfe0e6; --sea-2: #7fb6c4; }
.island-root[data-slot="afternoon"] { --sea-1: #cbe2d9; --sea-2: #6f9ca0; }
.island-root[data-slot="evening"] { --sea-1: #7d6f96; --sea-2: #3c4a6b; }
.island-root[data-slot="night"] { --sea-1: #1c3b4a; --sea-2: #0c1f2c; }
.map-wrap .stars { opacity: 0; transition: opacity .8s; }
.map-wrap[data-slot="night"] .stars, .map-wrap[data-slot="dawn"] .stars, .map-wrap[data-slot="evening"] .stars { opacity: 0.9; }
.map-wrap[data-slot="midday"] .stars, .map-wrap[data-slot="morning"] .stars, .map-wrap[data-slot="afternoon"] .stars { opacity: 0; }

.hand-label { font-family: var(--font-hand); font-weight: 600; letter-spacing: .01em; }
.place-label { pointer-events: none; }
.place-label text { fill: #241f14; }

/* Walking is a hand-tweened multi-waypoint move (public/script.js writes the
   `transform` attribute every animation frame) — a CSS transition here would
   fight that frame-by-frame write and produce a stutter, so position changes
   are instant at the CSS layer; the tween itself supplies the motion. Pan/
   zoom lives on `.map-stage`, an ANCESTOR of `.pin` — completely separate
   transform, never fought over. */
.pin { transition: none; }
.pin-bob { animation: pinBob 3.6s ease-in-out infinite; transform-origin: center; }
@keyframes pinBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }
/* Layout fix #3 (lead review: Kevin & Jenny are tiny at the fit zoom).
   public/script.js writes `style.transform: scale(...)` here every time the
   map's pan/zoom scale changes, counter-scaling the figure (+ its name
   label, drawn inside it) back up to a readable minimum when zoomed out —
   see updateFigureScale() there. `transform-box: view-box` + an explicit
   `0 0` origin anchors the scale at the figure's own local (0,0), which
   Figure()'s header comment fixes at its FEET, so it grows in place instead
   of drifting off its spot. A short transition smooths a zoom step; pan/
   drag frames don't touch this element at all, so it never fights a
   frame-by-frame write the way `.pin`'s own transform would. */
.pin-scale { transform-box: view-box; transform-origin: 0px 0px; transition: transform .18s ease; }
@media (prefers-reduced-motion: reduce) { .pin-scale { transition: none; } }
.figure-link { cursor: pointer; }

.wave-drift { animation: waveDrift 5s ease-in-out infinite; transform-origin: center; }
@keyframes waveDrift { 0%, 100% { transform: translateX(0); opacity: .22; } 50% { transform: translateX(6px); opacity: .4; } }

/* surf — 3 rings tracing the coastline (island-map.tsx's Surf()/SURF_RINGS),
   each swelling outward and fading, staggered by animation-delay so they
   read as waves breaking on the sand rather than one static outline.
   `transform-box: fill-box` + `transform-origin: center` anchors the SCALE
   at each ring's own geometric centre (same convention as `.ripple`/
   `.rod-float` below) rather than the SVG's origin corner. */
.surf-ring {
  fill: none; stroke: #fffdf6; stroke-linecap: round;
  transform-box: fill-box; transform-origin: center;
  animation: surfSwell 4.6s ease-out infinite;
}
.surf-ring-0 { stroke-width: 2.4; animation-delay: 0s; }
.surf-ring-1 { stroke-width: 1.8; animation-delay: 1.55s; }
.surf-ring-2 { stroke-width: 1.3; animation-delay: 3.05s; }
@keyframes surfSwell {
  0% { transform: scale(.988); opacity: 0; }
  18% { opacity: .55; }
  65% { opacity: .22; }
  100% { transform: scale(1.03); opacity: 0; }
}
/* Weather + time of day — `.surf-ring` lives inside `.map-wrap`, which
   already carries `data-weather`/`data-slot` itself (script.js's
   updateWeather()), so no extra mirroring is needed here. */
.map-wrap[data-weather="storm"] .surf-ring,
.map-wrap[data-weather="windy"] .surf-ring { animation-duration: 2.4s; }
.map-wrap[data-slot="night"] .surf-ring { animation-duration: 7.5s; }
.map-wrap[data-weather="heat"] .surf-ring { animation-duration: 8s; }

@media (prefers-reduced-motion: reduce) {
  .surf-ring { animation: none; opacity: .3; }
  .ocean-shimmer-stroke { animation: none; opacity: .18; }
}

.chimney-smoke { animation: smokeRise 6s ease-in-out infinite; transform-origin: bottom center; }
@keyframes smokeRise { 0%, 100% { transform: translateY(0); opacity: .75; } 50% { transform: translateY(-4px); opacity: .95; } }

.lamp-glow { animation: lampPulse 3.2s ease-in-out infinite; transform-origin: center; }
@keyframes lampPulse { 0%, 100% { opacity: .75; } 50% { opacity: 1; } }

.fog-blob { animation: fogDrift 22s ease-in-out infinite; }
@keyframes fogDrift { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(18px); } }

/* pointer-events:none is load-bearing, not decoration: each of these is a
   full-canvas <rect>/<g> (0,0 1000×640) painted AFTER the landmarks in SVG
   source order, so on any day its weather ISN'T active it still sat there
   at opacity 0 — invisible, but still hit-tested, silently eating every
   click on the lighthouse/cottage/cairn/etc. underneath it. Found live
   while wiring SPEC3's landmark cards: a real click missed every time, a
   script-dispatched one (which skips hit-testing) "worked" — the gap
   between those two is exactly this bug. Pre-existing, not new to pan/zoom. */
.rain-layer { opacity: 0; transition: opacity .6s; pointer-events: none; }
.fog-layer { opacity: 0; transition: opacity .6s; pointer-events: none; }
.storm-flash { opacity: 0; pointer-events: none; }
.heat-layer { opacity: 0; transition: opacity .6s; pointer-events: none; }
.wave-layer, .stars { pointer-events: none; }
[data-weather="rain"] .rain-layer,
[data-weather="storm"] .rain-layer { opacity: 1; }
[data-weather="fog"] .fog-layer { opacity: .85; }
[data-weather="storm"] .storm-flash { animation: stormFlash 7s ease-in-out infinite; }
[data-weather="heat"] .heat-layer { opacity: .5; }
@keyframes stormFlash {
  0%, 92%, 100% { opacity: 0; }
  93% { opacity: .5; }
  94% { opacity: 0; }
  95% { opacity: .35; }
  96% { opacity: 0; }
}
.rain-drop { animation: rainFall 0.9s linear infinite; }
@keyframes rainFall { 0% { transform: translateY(-12px); opacity: 0; } 15% { opacity: .7; } 100% { transform: translateY(24px); opacity: 0; } }

.fog-unknown { opacity: .85; }
.fog-unknown text { font-family: var(--font-ui); font-weight: 700; }

/* ── landmarks — doors AND plain spots (island-map.tsx's Landmark()/PlainSpot()) ── */
.landmark { cursor: pointer; outline: none; }
.plain-landmark { -webkit-tap-highlight-color: transparent; }
.landmark-hit { pointer-events: all; }
.landmark-glow { transition: opacity .25s ease; pointer-events: none; }
.landmark:hover .landmark-glow,
.landmark:focus-visible .landmark-glow,
.landmark.tapped .landmark-glow { opacity: .55; }
.landmark-label { opacity: 0; transition: opacity .18s ease; pointer-events: none; }
.landmark:hover .landmark-label,
.landmark:focus-visible .landmark-label,
.landmark.tapped .landmark-label { opacity: 1; }
.landmark:focus-visible .landmark-hit { outline: 2px solid #fffdf6; outline-offset: 2px; }
.bottle-landmark .bottle-bob { animation: bottleBob 4.2s ease-in-out infinite; transform-origin: center; }
@keyframes bottleBob {
  0%, 100% { transform: translate(var(--bx, 0px), var(--by, 0px)) rotate(-4deg) translateY(0); }
  50% { transform: translate(var(--bx, 0px), var(--by, 0px)) rotate(4deg) translateY(-3px); }
}

/* ── figures: Kevin & Jenny (island-map.tsx's Figure()) ──
   The SAME markup exists for every pose — only `data-activity` on the outer
   `.pin` changes, and CSS alone decides what shows and how it moves. That is
   what lets public/script.js update a pose on every poll (every few seconds)
   without touching the DOM tree at all, just one attribute. */
.fig-body { transform-origin: 0px -6px; }
.fig-leg, .fig-arm { transform-box: fill-box; }
.fig-leg-l { transform-origin: -1.8px -12px; }
.fig-leg-r { transform-origin: 1.8px -12px; }
.fig-arm-l { transform-origin: -3.6px -20.5px; }
.fig-arm-r { transform-origin: 3.6px -20.5px; }
.fig-torso { transform-origin: center; transform-box: fill-box; }
.fig-nameplate { pointer-events: none; }
.fig-zzz { opacity: 0; }
.fig-shadow { transition: opacity .3s; }

/* props — every one exists in the markup; only the matching activity shows it */
.prop { display: none; }
.pin[data-activity="fishing"] .prop-rod,
.pin[data-activity="chopping"] .prop-axe,
.pin[data-activity="gardening"] .prop-hoe,
.pin[data-activity="sketching"] .prop-book,
.pin[data-activity="writing"] .prop-book,
.pin[data-activity="repairing"] .prop-hammer,
.pin[data-activity="building"] .prop-hammer,
.pin[data-activity="exploring"] .prop-spyglass,
.pin[data-activity="radio"] .prop-radio,
.pin[data-activity="swimming"] .prop-ripple { display: block; }

/* sleeping — the figure itself disappears (they're inside, in bed); only a
   drifting "Zzz" near the roofline says so. */
.pin[data-activity="sleeping"] .fig-body,
.pin[data-activity="sleeping"] .fig-shadow { opacity: 0; }
.pin[data-activity="sleeping"] .fig-zzz { opacity: 1; }
.pin[data-activity="sleeping"] .fig-nameplate { opacity: .55; }

/* idle — a slow breath, nothing else */
.pin[data-activity="idle"] .fig-torso,
.pin[data-activity="eating"] .fig-torso,
.pin[data-activity="cooking"] .fig-torso { animation: figBreathe 3.4s ease-in-out infinite; }
@keyframes figBreathe { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(1.05); } }

/* resting — sit a little lower, one arm relaxed */
.pin[data-activity="resting"] .fig-body,
.pin[data-activity="eating"] .fig-body { transform: translateY(2.5px); }

/* talking — a small, slow gesture; never the big working swings below */
.pin[data-activity="talking"] .fig-arm-r { animation: figTalk 2.6s ease-in-out infinite; }
@keyframes figTalk { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(-9deg); } }

/* walking — leg scissor + counter-swinging arms, shown only mid-tween
   (script.js sets data-activity="walking" for the duration of the move) */
.pin[data-activity="walking"] .fig-leg-l { animation: figLegA .62s ease-in-out infinite; }
.pin[data-activity="walking"] .fig-leg-r { animation: figLegB .62s ease-in-out infinite; }
.pin[data-activity="walking"] .fig-arm-l { animation: figLegB .62s ease-in-out infinite; }
.pin[data-activity="walking"] .fig-arm-r { animation: figLegA .62s ease-in-out infinite; }
@keyframes figLegA { 0%, 100% { transform: rotate(16deg); } 50% { transform: rotate(-16deg); } }
@keyframes figLegB { 0%, 100% { transform: rotate(-16deg); } 50% { transform: rotate(16deg); } }

/* fishing — rod arm dips slowly; the float bobs on the water */
.pin[data-activity="fishing"] .fig-arm-r { animation: figFishArm 3.2s ease-in-out infinite; }
@keyframes figFishArm { 0%, 100% { transform: rotate(-4deg); } 50% { transform: rotate(6deg); } }
.rod-float { animation: figFloatBob 1.8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
@keyframes figFloatBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(2px); } }

/* chopping — a real swing, with chips flicking off on the downstroke */
.pin[data-activity="chopping"] .fig-arm-r { animation: figChop 1.05s ease-in infinite; transform-box: fill-box; }
@keyframes figChop { 0%, 55% { transform: rotate(-46deg); } 62% { transform: rotate(14deg); } 100% { transform: rotate(-46deg); } }
.chip { opacity: 0; transform-box: fill-box; }
.pin[data-activity="chopping"] .chip-1 { animation: figChip 1.05s ease-out infinite; }
.pin[data-activity="chopping"] .chip-2 { animation: figChip 1.05s ease-out .08s infinite; }
@keyframes figChip { 0%, 58% { opacity: 0; transform: translate(0, 0); } 64% { opacity: 1; transform: translate(2px, -1px); } 82% { opacity: 0; transform: translate(6px, -6px); } 100% { opacity: 0; } }

/* gardening — a small dig/hoe motion */
.pin[data-activity="gardening"] .fig-arm-r { animation: figDig 1.7s ease-in-out infinite; }
@keyframes figDig { 0%, 100% { transform: rotate(-10deg); } 50% { transform: rotate(20deg); } }

/* sketching — the pencil hand flicks, the book hand stays still */
.pin[data-activity="sketching"] .fig-arm-r { animation: figSketch 1.1s ease-in-out infinite; }
@keyframes figSketch { 0%, 100% { transform: rotate(-6deg); } 50% { transform: rotate(4deg); } }

/* repairing / building — a hammer tap, with a spark on the strike */
.pin[data-activity="repairing"] .fig-arm-r,
.pin[data-activity="building"] .fig-arm-r { animation: figHammer .82s ease-in infinite; transform-box: fill-box; }
@keyframes figHammer { 0%, 45% { transform: rotate(-30deg); } 55% { transform: rotate(10deg); } 100% { transform: rotate(-30deg); } }
.spark { opacity: 0; }
.pin[data-activity="repairing"] .spark,
.pin[data-activity="building"] .spark { animation: figSpark .82s ease-out infinite; }
@keyframes figSpark { 0%, 50% { opacity: 0; } 56% { opacity: 1; } 70% { opacity: 0; } 100% { opacity: 0; } }

/* foraging — a bend at the waist */
.pin[data-activity="foraging"] .fig-body { animation: figBend 1.6s ease-in-out infinite; transform-origin: 0px -6px; }
@keyframes figBend { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(8deg); } }

/* swimming — half-submerged, ripples spreading */
.pin[data-activity="swimming"] .fig-leg { opacity: .35; }
.pin[data-activity="swimming"] .fig-body { transform: translateY(6px); }
.ripple { opacity: 0; transform-box: fill-box; transform-origin: center; }
.pin[data-activity="swimming"] .ripple-1 { animation: figRipple 2.4s ease-out infinite; }
.pin[data-activity="swimming"] .ripple-2 { animation: figRipple 2.4s ease-out 1.2s infinite; }
@keyframes figRipple { 0% { opacity: .5; transform: scale(.4); } 100% { opacity: 0; transform: scale(1.6); } }

/* radio — wave arcs pulse outward */
.radio-wave { opacity: 0; }
.pin[data-activity="radio"] .radio-wave.w1 { animation: figWave 1.6s ease-out infinite; }
.pin[data-activity="radio"] .radio-wave.w2 { animation: figWave 1.6s ease-out .4s infinite; }
@keyframes figWave { 0% { opacity: .8; } 100% { opacity: 0; } }

/* exploring — a slow scanning tilt */
.pin[data-activity="exploring"] .fig-arm-r { animation: figScan 2.2s ease-in-out infinite; }
@keyframes figScan { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(-30deg); } }

@media (prefers-reduced-motion: reduce) {
  .pin-bob, .rain-drop, .fog-blob, .wave-drift, .chimney-smoke, .lamp-glow,
  [data-weather="storm"] .storm-flash, .bottle-bob,
  .fig-torso, .fig-leg-l, .fig-leg-r, .fig-arm-l, .fig-arm-r, .fig-body,
  .chip-1, .chip-2, .rod-float, .ripple-1, .ripple-2, .radio-wave.w1, .radio-wave.w2,
  .spark { animation: none !important; }
}

/* ── HUD — every floating icon on the home page (SPEC3) ── */
.hud { position: fixed; z-index: 20; display: flex; gap: 8px; color: #fffdf6; }
.hud-tl { top: 14px; left: 14px; flex-direction: column; align-items: flex-start; max-width: min(76vw, 420px); }
.hud-tr { top: 14px; right: 14px; flex-wrap: wrap; justify-content: flex-end; max-width: min(70vw, 420px); }
/* Below ~680px the wordmark+clock chip and the 5-icon cluster no longer fit
   side by side (measured: ~180px + ~250px > a 390px phone) — found live at
   360-414px, where "Living Core" ran straight under the icon row. The icon
   cluster drops BELOW the wordmark+clock stack instead of shrinking icons
   past a 44px tap target (RULE 6/tap-target floor). 96px clears two stacked
   chips (wordmark ~40px + clock chip ~32-40px + the gap between them) with
   a small margin on every phone width tested (360-430px). */
@media (max-width: 680px) {
  .hud-tl, .hud-tr { max-width: calc(100vw - 28px); }
  .hud-tr { top: 96px; }
}
.hud-br { bottom: 14px; right: 14px; flex-direction: column; }
.hud-bl { bottom: 14px; left: 14px; }
/* Both bottom clusters clear the phone live-panel sheet — the zoom buttons sat
   ON the sheet at 390px (seen live 2026-09-25), only the bottle had been moved. */
@media (max-width: 1023px) { .hud-bl, .hud-br { bottom: 108px; } }
/* Layout fix #1: the live panel moved from bottom-centre to a right-hand
   dock (see `.island-panel`'s ≥1024px rules below) — the zoom cluster,
   still "bottom-right" per SPEC3, shifts left to clear it (360px card +
   16px right offset + 16px gap). `.hud-bl` (bottom-left, the bottle icon)
   never shared that corner, so it no longer needs its own ≥1024px
   clearance rule — removed rather than left stale. */
@media (min-width: 1024px) { .hud-br { right: 392px; } }

.hud-chip {
  display: inline-flex; align-items: center; min-height: 40px; padding: 6px 13px;
  background: rgba(10, 8, 5, 0.5); color: #fffdf6; border: 1px solid rgba(255,253,246,.28);
  border-radius: 999px; backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.wordmark-chip { padding: 6px 14px; }
.wordmark-chip h1 { margin: 0; font-size: 1.1rem; font-weight: 800; color: #fffdf6; }
.wordmark-sub { font-weight: 600; font-size: .78em; opacity: .82; }
.hud-clock { font-size: 12.5px; font-weight: 700; margin-top: 4px; cursor: pointer; }
/* One line on phones: the clock chip wrapped to two lines at 390px and ran
   under the icon row that sits at top:96px (the full line is in the weather
   card it opens). */
@media (max-width: 680px) {
  .hud-clock { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: calc(100vw - 28px); display: block; }
}
.hud-clock:hover { background: rgba(10, 8, 5, 0.68); text-decoration: none; }

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  min-width: 44px; min-height: 44px; padding: 0 12px;
  background: rgba(10, 8, 5, 0.5); color: #fffdf6;
  border: 1px solid rgba(255,253,246,.28); border-radius: 999px;
  font-size: 15px; font-weight: 700; cursor: pointer;
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.icon-btn:hover { background: rgba(10, 8, 5, 0.68); text-decoration: none; }
.icon-btn[aria-pressed="false"] { opacity: .55; }
.icon-btn[aria-expanded="true"] { background: rgba(10, 8, 5, 0.75); }
.menu-btn-label { display: none; }
@media (min-width: 560px) { .menu-btn-label { display: inline; } }
.zoom-cluster .icon-btn { min-width: 40px; min-height: 40px; padding: 0; font-size: 18px; }

.quiet-chip {
  position: fixed; z-index: 15; top: 66px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px;
  background: rgba(154, 106, 18, 0.85); color: #fffdf6; font-size: 12.5px; font-weight: 700;
  padding: 7px 14px; border-radius: 999px; backdrop-filter: blur(3px);
}
.quiet-chip .dot { width: 8px; height: 8px; border-radius: 50%; background: #fffdf6; animation: presencePulse 1.4s ease-in-out infinite; }

/* ── the live scene: a slim card docked to the RIGHT on desktop, a bottom
   sheet on phones — layout fix #1 (lead review at 1440x900: SPEC3's
   original "bottom-centre" card sat over the middle of the island and
   covered Kevin + Jenny whenever they were anywhere near the cottage/
   garden/dock cluster, i.e. most of the time). It is NOT a dialog: always
   visible, SSR'd, never gated behind a click. ── */
.island-panel {
  position: fixed;
  z-index: 12;
  left: 0; right: 0; bottom: 0;
  border-radius: 16px 16px 0 0;
  max-height: 72svh;
  max-height: 72vh;
  background: rgba(20, 16, 10, 0.88);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--ink);
  box-shadow: 0 -6px 24px rgba(0,0,0,.3);
}
.island-panel .scene-card { background: transparent; border: none; box-shadow: none; margin: 0; color: #fffdf6; }
.island-panel .scene-kicker,
.island-panel .presence-line { color: rgba(255,253,246,.65); }
.island-panel .scene-setup { color: rgba(255,253,246,.85); }
.island-panel h2 { color: #fffdf6; }
.island-panel .turn { border-top-color: rgba(255,253,246,.16); }
.island-panel .turn-time { color: rgba(255,253,246,.5); }
.island-panel .turn-do { color: rgba(255,253,246,.7); }
.island-panel .thought-peek summary { color: rgba(255,253,246,.6); }
.island-panel .thought-peek .thought-body { background: rgba(255,253,246,.08); color: rgba(255,253,246,.85); }
.island-panel .thoughts-toggle { color: rgba(255,253,246,.7); border-color: rgba(255,253,246,.28); }

.panel-handle {
  display: none;
  width: 100%;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: none;
  border: none;
  color: #fffdf6;
  font-size: 12.5px;
  font-weight: 700;
  min-height: 34px;
  padding: 8px 0 4px;
  cursor: pointer;
}
.handle-bar { width: 34px; height: 4px; border-radius: 2px; background: rgba(255,253,246,.4); }

.apart-tracks { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.apart-track-head { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 4px; padding-bottom: 4px; border-bottom: 1px solid rgba(255,253,246,.18); }
.apart-track-head.kevin { color: var(--kevin); }
.apart-track-head.jenny { color: var(--jenny); }
@media (max-width: 640px) { .apart-tracks { grid-template-columns: 1fr; gap: 16px; } }

/* Phones: bottom sheet, COLLAPSED BY DEFAULT to one line + the last turn
   (`.panel-peek`, server-rendered in HomePage.tsx from the same data the
   full card uses — never JS-only, so it's there with no script too) — tap
   the handle (or drag) to swap to the full scrollable scene card. */
.panel-handle { display: flex; }
.panel-peek { display: block; padding: 0 14px 12px; }
.panel-peek-line { margin: 0 0 4px; font-size: 12.5px; font-weight: 700; color: rgba(255,253,246,.75); }
.panel-peek-turn {
  margin: 0; font-size: 13.5px; color: #fffdf6;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.panel-body { display: none; padding: 0 14px 14px; }
.island-panel[data-expanded="true"] .panel-peek { display: none; }
.island-panel[data-expanded="true"] .panel-body { display: block; max-height: 58svh; max-height: 58vh; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.island-panel[data-expanded="true"] .panel-handle .handle-label::after { content: ' — tap to collapse'; }

/* Desktop-only collapse button — base state hidden, and this rule MUST come
   BEFORE the ≥1024px override below: two rules of equal specificity resolve
   by source order regardless of which media query matches, so a later
   unconditional `display:none` would have beaten the media query's
   `display:block` at every width (found live — the button was unreachable
   at 1440px until this moved). */
.panel-collapse-btn { display: none; }

@media (min-width: 1024px) {
  /* Layout fix #1: a slim card DOCKED TO THE RIGHT, not the old
     bottom-centre card — `top` clears the HUD's top-right icon row
     (~58px tall + a margin), `bottom` matches every other floating HUD
     element's edge margin. Fixed at ~360px wide so it never eats more of
     the island than it has to; `fitView()`'s panelReserve() in script.js
     re-centres the initial "fit" into the uncovered left/centre area to
     match (SPEC3: "the initial map fit must account for the card"). */
  .island-panel {
    left: auto; right: 16px; bottom: 24px; top: 80px;
    width: 360px;
    max-height: none;
    border-radius: var(--radius);
    display: flex;
    flex-direction: column;
  }
  .panel-peek { display: none; }
  .panel-handle { display: none; }
  .panel-body { display: block; flex: 1 1 auto; min-height: 0; max-height: none; overflow-y: auto; padding: 16px; }
  .island-panel[data-collapsed="true"] {
    width: 44px; height: 44px; bottom: auto; overflow: hidden;
  }
  .island-panel[data-collapsed="true"] .panel-body { display: none; }
  .panel-collapse-btn {
    display: block; position: absolute; top: 8px; right: 8px; z-index: 1;
    width: 26px; height: 26px; border-radius: 8px; border: 1px solid rgba(255,253,246,.28);
    background: rgba(255,253,246,.08); color: #fffdf6; cursor: pointer; font-size: 13px; line-height: 1;
  }
  .panel-collapse-btn:hover { background: rgba(255,253,246,.18); }
  .island-panel[data-collapsed="true"] .panel-collapse-btn { position: static; margin: 10px auto; }
}

/* ═══════════════════════════════════════════════════════════════════════
   CARDS — every floating panel (menu, weather, today, projects, character,
   info, landmark, bottle) is a real <dialog>, so ONE set of rules covers
   them all. `dialog:target` is the no-JS fallback (a fragment link like
   `#weather-dialog` lands here even with no script — see chrome.tsx's
   header comment); public/script.js upgrades the SAME click into
   `.showModal()`, which the UA already shows via `::backdrop` + top layer.
   ═══════════════════════════════════════════════════════════════════════ */
dialog.card-dialog {
  border: none;
  border-radius: var(--radius);
  padding: 18px;
  width: min(92vw, 440px);
  max-height: 80dvh;
  max-height: 80vh;
  overflow-y: auto;
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--shadow);
}
dialog.card-dialog::backdrop { background: rgba(10, 8, 5, 0.55); }
/* The no-JS fallback: a plain <dialog> with no `open` attribute is
   `display:none` in every UA stylesheet — `:target` overrides that so a
   fragment link alone reveals it, centred like an open dialog would be. */
dialog.card-dialog:target {
  display: block !important;
  position: fixed; z-index: 50; top: 50%; left: 50%; transform: translate(-50%, -50%);
}
.dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.dialog-head h2 { margin: 0; font-size: 1.05rem; }
.dialog-close {
  background: none; border: none; color: var(--muted); font-size: 15px;
  min-width: 34px; min-height: 34px; border-radius: 8px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.dialog-close:hover { background: var(--paper-2); color: var(--ink); text-decoration: none; }

.menu-dialog .menu-nav { display: flex; flex-direction: column; gap: 2px; margin-bottom: 10px; }
.menu-nav-item a {
  color: var(--ink); font-size: 14px; font-weight: 700; padding: 10px 4px;
  border-radius: 8px; min-height: 44px; display: flex; align-items: center;
}
.menu-nav-item a:hover { background: var(--paper-2); text-decoration: none; }
.menu-nav-preview { font-size: 11.5px; color: var(--muted); margin: -4px 0 6px 4px; }
.menu-datasets { margin-top: 8px; border-top: 1px solid var(--border); padding-top: 8px; }
.menu-datasets summary { cursor: pointer; font-size: 12.5px; font-weight: 700; color: var(--muted); min-height: 30px; display: flex; align-items: center; }
.menu-dataset-links { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
.menu-dataset-links a { font-size: 12px; color: var(--kevin); padding: 5px 4px; min-height: 30px; display: flex; align-items: center; }
.credit-line { font-size: 11px; color: var(--muted); margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--border); line-height: 1.5; }

.landmark-card-dialog #landmark-dialog-body { color: var(--ink-dim); font-size: 13.5px; }
.landmark-dialog-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

.char-dialog .char-head { display: flex; align-items: center; gap: 10px; }
.char-dialog.kevin .char-name { color: var(--kevin); }
.char-dialog.jenny .char-name { color: var(--jenny); }
.offline-note { font-size: 12.5px; color: var(--gold); background: var(--gold-bg); border-radius: var(--radius-sm); padding: 8px 10px; }

.gauges-card { display: flex; gap: 14px; flex-wrap: wrap; margin: 10px 0; }

/* ── bottle dialog ── */
#bottle-dialog .bottle-form textarea { width: 100%; min-height: 84px; resize: vertical; background: var(--paper-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; color: var(--ink); outline: none; }
#bottle-dialog .bottle-form textarea:focus { border-color: var(--kevin); }

/* ═══════════════════════════════════════════════════════════════════════
   SHEET PAGES (body.v-sheet) — every page that isn't the home map.
   ═══════════════════════════════════════════════════════════════════════ */
body.v-sheet { min-height: 100vh; min-height: 100dvh; }
.sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  filter: blur(18px) saturate(0.9) brightness(0.72);
  transform: scale(1.06); /* hides the blur's own soft edge */
}
.sheet-backdrop svg { width: 100%; height: 100%; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sheet-backdrop { filter: blur(18px) saturate(0.8) brightness(0.4); }
}
:root[data-theme="dark"] .sheet-backdrop { filter: blur(18px) saturate(0.8) brightness(0.4); }

.sheet-main { display: flex; justify-content: center; padding: 76px 16px 48px; }
.paper-sheet {
  width: 100%;
  max-width: 760px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 28px clamp(16px, 5vw, 40px) 32px;
}
/* the Lab's charts want more room */
.paper-sheet:has(.chart-block) { max-width: 900px; }

.floating-icons { position: fixed; z-index: 20; top: 14px; display: flex; gap: 8px; }
.floating-icons-tl { left: 14px; }
.floating-icons-tr { right: 14px; }

@media (max-width: 480px) {
  .sheet-main { padding: 68px 10px 40px; }
  .paper-sheet { padding: 20px 16px 24px; }
}

/* ── status strip (used inside the weather dialog's body) ── */
.gauges { display: flex; gap: 12px; flex-wrap: wrap; }
.gauge { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--muted); }
.gauge .bar { width: 46px; height: 6px; border-radius: 3px; background: var(--paper-2); overflow: hidden; border: 1px solid var(--border); }
.gauge .fill { height: 100%; border-radius: 3px; }
.supply-count { font-variant-numeric: tabular-nums; }

/* ── bubble layer — HTML, positioned by script.js from the map's own
   coordinate transform (viewport-absolute pixels, unaffected by pan/zoom
   maths living elsewhere). ── */
.bubble-layer { position: fixed; inset: 0; z-index: 8; pointer-events: none; overflow: hidden; }
.map-bubble {
  position: absolute;
  max-width: min(70vw, 280px);
  min-width: 60px;
  font-size: 14px;
  line-height: 1.4;
  padding: 8px 11px;
  border-radius: 12px;
  background: #fffdf6;
  color: #241f14;
  box-shadow: 0 4px 16px rgba(0,0,0,.28);
  transform: translate(-50%, -100%);
  opacity: 0;
  transition: opacity .35s ease;
}
.map-bubble.visible { opacity: 1; }
/* Phones: at 390px one long thought covered the entire island. Smaller type and
   width here; script.js also clips the text at a word boundary (the full turn
   is always in the live panel). */
@media (max-width: 680px) {
  .map-bubble { max-width: 58vw; font-size: 12.5px; line-height: 1.35; padding: 6px 9px; border-radius: 10px; }
}
.map-bubble::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -7px;
  transform: translateX(-50%);
  border: 7px solid transparent;
  border-top-color: #fffdf6;
}
.map-bubble.flip-left { transform: translate(-88%, -100%); }
.map-bubble.flip-left::after { left: 88%; }
.map-bubble.flip-right { transform: translate(-12%, -100%); }
.map-bubble.flip-right::after { left: 12%; }
.map-bubble.thought {
  background: rgba(255,253,246,.94);
  border: 1px dashed var(--border);
  font-style: italic;
  color: var(--ink-dim);
}
.map-bubble.thought::after { border-top-color: rgba(255,253,246,.94); }
.map-bubble.do-caption {
  background: rgba(23, 18, 8, 0.72);
  color: #fffdf6;
  font-size: 12px;
  font-style: italic;
  font-weight: 600;
  padding: 5px 10px;
  box-shadow: none;
  transform: translate(-50%, 6px);
}
.map-bubble.do-caption::after { display: none; }
.island-root.thoughts-off .map-bubble.thought { display: none; }

/* ── scene card ── */
.scene-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin: 14px 0;
  box-shadow: var(--shadow);
}
.scene-card .scene-kicker { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 700; margin-bottom: 4px; }
.scene-card h2 { margin-bottom: 6px; }
.scene-setup { font-family: var(--font-serif); font-size: 1rem; color: var(--ink-dim); line-height: 1.65; margin-bottom: 14px; }

.turn { padding: 10px 0; border-top: 1px solid var(--border); }
.turn:first-child { border-top: none; }
.turn-head { display: flex; align-items: center; gap: 8px; margin-bottom: 3px; }
.turn-name { font-weight: 700; font-size: 13px; }
.turn.kevin .turn-name { color: var(--kevin); }
.turn.jenny .turn-name { color: var(--jenny); }
.turn-time { font-size: 11px; color: var(--muted); }
.turn-say { font-size: 14.5px; line-height: 1.6; }
.turn-do { font-size: 12.5px; color: var(--muted); font-style: italic; margin-top: 3px; }
.turn-do-name { font-style: normal; font-weight: 700; color: var(--ink-dim); }
.turn-do-quiet { opacity: .8; }
.turn-retry { font-size: 10.5px; color: var(--gold); margin-top: 2px; }

.thought-peek {
  margin-top: 6px;
}
.thought-peek summary {
  cursor: pointer;
  font-size: 11px;
  color: var(--muted);
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 30px;
  padding: 2px 4px;
  border-radius: 6px;
}
.thought-peek summary::-webkit-details-marker { display: none; }
.thought-peek summary:hover { color: var(--ink-dim); background: var(--paper-2); }
.thought-peek .thought-body {
  margin-top: 4px;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--paper-2);
  font-size: 12.5px;
  color: var(--ink-dim);
  filter: blur(4px);
  transition: filter .2s;
}
.thought-peek[open] .thought-body,
.thoughts-shown .thought-peek .thought-body { filter: blur(0); }

.presence-line { font-size: 12px; color: var(--muted); padding: 8px 2px 2px; display: flex; align-items: center; gap: 6px; }
.presence-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); animation: presencePulse 1.4s ease-in-out infinite; }
@keyframes presencePulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }

.typewriter-caret { display: inline-block; width: 2px; height: 1em; background: currentColor; vertical-align: -0.15em; animation: caretBlink .9s step-end infinite; }
@keyframes caretBlink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }

.thoughts-toggle { font-size: 11.5px; color: var(--muted); background: none; border: 1px solid var(--border); border-radius: 999px; padding: 6px 12px; min-height: 30px; cursor: pointer; }
.thoughts-toggle:hover { color: var(--ink); border-color: var(--muted); }

/* ── character cards (used both in the home dialog and nowhere else now) ── */
.char-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.char-name { font-weight: 800; font-size: 1rem; }
.char-loc { font-size: 11.5px; color: var(--muted); }
.bars { display: grid; gap: 9px; margin: 10px 0; }
.bar-row { display: grid; gap: 3px; font-size: 11px; color: var(--muted); }
.bar-row .bar-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.bar-row .bar-top .bar-label { text-transform: uppercase; font-size: 10px; letter-spacing: .05em; font-weight: 700; color: var(--muted); }
.bar-row .bar-top .bar-word { font-size: 12px; font-weight: 700; color: var(--ink-dim); text-align: right; }
.bar-row .track { height: 6px; border-radius: 3px; background: var(--paper-2); border: 1px solid var(--border); overflow: hidden; }
.bar-row .track > span { display: block; height: 100%; border-radius: 3px; transition: width .6s ease; }
.skills-line { font-size: 11.5px; color: var(--ink-dim); margin-top: 6px; }
.char-plan { font-size: 11.5px; color: var(--muted); margin-top: 6px; }
.char-made { font-size: 11.5px; color: var(--muted); margin-top: 4px; }

/* ── timeline ── */
.timeline { margin: 14px 0; }
.tl-item { display: flex; gap: 10px; padding: 9px 0; border-top: 1px solid var(--border); }
.tl-item:first-child { border-top: none; }
.tl-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); margin-top: 6px; flex-shrink: 0; }
.tl-item.scene .tl-dot { background: var(--kevin); }
.tl-item.event .tl-dot { background: var(--gold); }
.tl-body { flex: 1; min-width: 0; }
.tl-body a { color: var(--ink); font-weight: 600; font-size: 13.5px; }
.tl-meta { font-size: 11px; color: var(--muted); }
.tl-summary { font-size: 12.5px; color: var(--ink-dim); margin-top: 2px; }
.tl-repeat { font-size: 10px; font-weight: 700; color: var(--muted); background: var(--paper-2); border: 1px solid var(--border); border-radius: 999px; padding: 1px 7px; align-self: flex-start; margin-top: 9px; flex-shrink: 0; }

/* ── projects board ── */
.projects { display: grid; gap: 10px; margin: 6px 0; }
.project-row { background: var(--paper-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; }
.project-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.project-name { font-weight: 700; font-size: 13.5px; }
.project-status { font-size: 10.5px; padding: 3px 8px; border-radius: 999px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
.project-status.idea { background: var(--card); color: var(--muted); }
.project-status.active { background: var(--kevin-bg); color: var(--kevin); }
.project-status.done { background: var(--good); color: #fff; }
.project-status.stalled, .project-status.abandoned { background: var(--jenny-bg); color: var(--jenny); }
.project-track { height: 7px; border-radius: 4px; background: var(--card); border: 1px solid var(--border); overflow: hidden; }
.project-track > span { display: block; height: 100%; background: linear-gradient(90deg, var(--kevin), var(--jenny)); }
.project-note { font-size: 11.5px; color: var(--muted); margin-top: 6px; }

/* ── chapter excerpt / artifacts / notebook cards (still used on /days,
   /day/:n, /workshop, /notebook) ── */
.section { margin: 26px 0; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.section-head h2 { margin: 0; }
.section-head .more { font-size: 12px; color: var(--muted); }

.chapter-excerpt { background: var(--card-2); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; }
.chapter-excerpt .pull-quote,
.pull-quote { font-family: var(--font-serif); font-style: italic; font-size: 1.1rem; color: var(--ink); border-left: 3px solid var(--gold); padding-left: 12px; margin: 10px 0; }

.artifact-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.artifact-card { background: var(--card-2); border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; display: flex; flex-direction: column; min-height: 44px; }
.artifact-card img { width: 100%; aspect-ratio: 4/3; object-fit: contain; background: var(--paper-2); }
.artifact-card .paper { padding: 12px; font-family: var(--font-serif); font-size: 12.5px; color: var(--ink-dim); min-height: 92px; }
.artifact-meta { padding: 8px 10px; font-size: 10.5px; color: var(--muted); border-top: 1px solid var(--border); }

.notebook-list { display: grid; gap: 8px; }
.notebook-item { background: var(--card-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; font-size: 13px; }
.notebook-item .nb-meta { font-size: 10.5px; color: var(--muted); margin-top: 3px; }
.kind-pill { font-size: 10px; text-transform: uppercase; letter-spacing: .03em; font-weight: 700; padding: 2px 7px; border-radius: 999px; background: var(--gold-bg); color: var(--gold); margin-right: 6px; }

/* ── bottle form (below-fold form is gone — this now only styles the
   dialog's inner form, but the class names are shared so nothing drifts) ── */
.bottle-block { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; }
.bottle-form textarea,
.bottle-block textarea { width: 100%; min-height: 84px; resize: vertical; background: var(--paper-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; color: var(--ink); outline: none; }
.bottle-form textarea:focus,
.bottle-block textarea:focus { border-color: var(--kevin); }
.bottle-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 10px; }
.bottle-row input[type=text] { flex: 1 1 160px; background: var(--paper-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; min-height: 44px; }
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--jenny); color: #fff; border: none; border-radius: 999px;
  font-weight: 700; font-size: 13.5px; padding: 11px 20px; min-height: 44px; cursor: pointer;
}
.btn:hover { filter: brightness(1.08); text-decoration: none; color: #fff; }
.btn:disabled { opacity: .5; cursor: default; }
.btn.secondary { background: var(--paper-2); color: var(--ink); border: 1px solid var(--border); }
.form-note { font-size: 11px; color: var(--muted); margin-top: 6px; }
.bottle-feedback { font-size: 12.5px; margin-top: 8px; color: var(--good); }
.bottle-feedback.err { color: var(--bad); }
.answered-bottles { margin-top: 18px; display: grid; gap: 10px; }
.answered-bottle { background: var(--paper-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; font-size: 12.5px; }
.answered-bottle .visitor { color: var(--muted); font-style: italic; }
.answered-bottle .reply { margin-top: 6px; font-family: var(--font-serif); color: var(--ink); }

/* ── lab strip ── */
.lab-strip { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; background: var(--card-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px 14px; margin: 14px 0; }
.lab-num { font-size: 1.3rem; font-weight: 800; color: var(--ink); }
.lab-num small { font-size: .6em; font-weight: 600; color: var(--muted); }
.lab-cmp { font-size: 10.5px; color: var(--good); }
.lab-cmp.bad { color: var(--bad); }

/* ── lab / almanac page bits ── */
.era-scroll { display: flex; gap: 12px; overflow-x: auto; padding: 4px 2px 14px; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; }
.era-card { flex: 0 0 240px; scroll-snap-align: start; background: var(--card-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px; }
.era-card h3 { font-size: .92rem; margin-bottom: 4px; }
.era-card .era-dates { font-size: 10.5px; color: var(--muted); margin-bottom: 6px; }
.era-card p { font-size: 12px; color: var(--ink-dim); margin: 0; }
.era-card .era-models { font-size: 10.5px; color: var(--muted); margin-top: 8px; border-top: 1px solid var(--border); padding-top: 6px; }

.chart-block { background: var(--card-2); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; margin: 14px 0; overflow-x: auto; }
.chart-block h3 { font-size: .95rem; }
.chart-note { font-size: 11px; color: var(--muted); margin-top: 6px; }

.def-list dt { font-weight: 700; font-size: 12.5px; margin-top: 10px; color: var(--ink); }
.def-list dd { margin: 2px 0 0; font-size: 12px; color: var(--ink-dim); }

.code-block { background: var(--paper-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 11.5px; overflow-x: auto; white-space: pre; color: var(--ink-dim); }

.chain-list { display: grid; gap: 8px; margin: 10px 0; }
.chain-row { font-size: 12px; color: var(--ink-dim); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.chain-row b { color: var(--ink); }
.model-chip { font-size: 10.5px; padding: 2px 8px; border-radius: 999px; background: var(--paper-2); border: 1px solid var(--border); color: var(--ink-dim); }
.model-chip.gone { text-decoration: line-through; color: var(--muted); opacity: .6; }

/* ── pagination ── */
.pagination { display: flex; justify-content: space-between; align-items: center; margin: 18px 0; font-size: 13px; }
.pagination a, .pagination span { padding: 8px 14px; border-radius: 999px; min-height: 44px; display: inline-flex; align-items: center; }
.pagination a { border: 1px solid var(--border); }
.pagination a:hover { background: var(--paper-2); text-decoration: none; }

/* ── filter chips (workshop, notebook) ── */
.chip-row { display: flex; gap: 6px; flex-wrap: wrap; margin: 10px 0 16px; }
.chip { font-size: 12px; padding: 7px 13px; min-height: 34px; display: inline-flex; align-items: center; border-radius: 999px; border: 1px solid var(--border); color: var(--ink-dim); }
.chip[aria-current="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ── general utility ── */
.muted { color: var(--muted); }
.mono { font-family: 'JetBrains Mono', ui-monospace, monospace; }
.center { text-align: center; }
.empty-note { text-align: center; padding: 40px 16px; color: var(--muted); font-size: 13.5px; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
[hidden] { display: none !important; }

@media (max-width: 480px) {
  .wrap { padding: 0 14px; }
  h1 { font-size: 1.4rem; }
}
