@import url("https://fonts.googleapis.com/css2?family=Unbounded:wght@500;700&family=IBM+Plex+Sans:wght@400;600&display=swap");

:root {
  /* Relative OKLCH preserves the exact approved sRGB palette. */
  --bg: oklch(from #080C12 l c h);
  --bg2: oklch(from #111925 l c h);
  --bg3: oklch(from var(--bg2) calc(l + 0.04) c h);
  --ink: oklch(from #F0F2F5 l c h);
  --muted: oklch(from #B4BECC l c h);
  --line: oklch(from #354358 l c h);
  --acc: oklch(from #87D3F4 l c h);
  --focus: oklch(from #FFE2A3 l c h);
  --acc-ink: var(--bg);
  --acc-soft: oklch(from var(--acc) 0.28 calc(c * 0.7) h);
  --warn: oklch(from var(--focus) 0.76 c h);
  --warn-soft: oklch(from var(--focus) 0.28 calc(c * 0.5) h);
  --shadow: 0 12px 32px -16px oklch(from var(--bg) l c h / 0.6);
  --display: "Unbounded", system-ui, sans-serif;
  --body: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --mono: ui-monospace, Menlo, monospace;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;
  --space-24: 96px;
  --space-32: 128px;
  --space-48: 192px;
  --gutter: 20px;
  --max-width: 1280px;
  --radius-control: 8px;
  --radius-panel: 12px;
  --radius-round: 50%;
  --text-meta: clamp(0.8125rem, 0.8rem + 0.1vw, 0.875rem);
  --text-body: clamp(1rem, 0.9rem + 0.2vw, 1.125rem);
  --text-lead: clamp(1.125rem, 1rem + 0.25vw, 1.25rem);
  --text-section: clamp(1.25rem, 1rem + 0.75vw, 1.563rem);
  --text-hero: clamp(1.953rem, 1.1rem + 2.4vw, 3.815rem);
  --duration-press: 140ms;
  --duration-select: 180ms;
  --duration-enter: 240ms;
  --header-height: 116px;
  --anchor-offset: calc(var(--header-height) + var(--space-6));

  /* Considered easing, not the default `ease` — see emil-design-eng: an
     entrance/hover should feel like a decision, not a browser default. */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
}

/* The Keywork commits to one visual world — dark space, one blue constant —
   per the design spec (§8, §12: no light/dark toggle beyond the Real layer).
   The single exception is Good Apollo's Willing Well: entering the "Real"
   layer locally repaints that one section as typed paper. See #ga1[data-layer]
   below; nothing about that is a document-wide theme. */

* { box-sizing: border-box; }
html { color-scheme: dark; scrollbar-color: var(--line) var(--bg); }
[hidden] { display: none !important; }
::selection { background: var(--acc); color: var(--acc-ink); }
::marker { color: var(--acc); }
input { caret-color: var(--acc); }
h1, h2, h3 { text-wrap: balance; }
p { text-wrap: pretty; }
:where(section[id], #chapters, #main-content) { scroll-margin-top: var(--anchor-offset); }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--text-body);
  line-height: 1.65;
  overflow-wrap: anywhere;
  margin: 0;
  padding-inline: var(--gutter);
}

a { color: var(--acc); }
a:focus-visible, button:focus-visible, [tabindex]:focus-visible, input:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 4px;
}

/* Every pressable element gets the same instant, subtle feedback — Emil
   Kowalski's "buttons must feel responsive" rule, applied once so it can
   never be missed on a new control later. .tile carries its own transition
   (border-color + transform together, see Rail below) so it's excluded
   here rather than fought over with a specificity war. */
button, .chip {
  transition: transform var(--duration-press) var(--ease-out);
}
button:active, .chip:active {
  transform: scale(0.97);
}
@media (prefers-reduced-motion: reduce) {
  button, .chip { transition: none; }
  button:active, .chip:active { transform: none; }
}

.wrap { max-width: var(--max-width); margin: 0 auto; }
.reading { max-width: 68ch; }

.skip-link { position: fixed; top: var(--space-2); left: var(--gutter); padding: var(--space-3) var(--space-4); background: var(--acc); color: var(--acc-ink); z-index: 60; transform: translateY(-200%); }
.skip-link:focus { transform: none; }

@media (min-width: 768px) {
  :root { --gutter: 32px; }
}
@media (min-width: 1024px) {
  :root { --gutter: 48px; }
}

/* ---------- Header ---------- */
header.site {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-6);
  padding-block: var(--space-4);
  max-width: var(--max-width);
  margin-inline: auto;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 20;
}
.wordmark {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--text-lead);
  letter-spacing: -0.01em;
  margin-right: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--ink);
  text-decoration: none;
  min-height: var(--space-12);
}
.wordmark svg { width: 20px; height: 20px; flex: none; }

.primary-nav { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.primary-nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--space-12);
  font-size: var(--text-meta);
  text-decoration: none;
  color: var(--muted);
}
.primary-nav a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: var(--space-2); }

.reading-controls { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-6) var(--space-12); }
#order-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
#order-fieldset legend { color: var(--muted); font-size: var(--text-meta); padding: 0; margin-bottom: var(--space-2); }
.order-options { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.order-options label {
  display: inline-flex;
  gap: var(--space-2);
  align-items: center;
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--bg2);
  color: var(--muted);
  font-size: var(--text-meta);
  cursor: pointer;
}
.order-options label:has(input:checked) { color: var(--ink); border-color: var(--acc); }
.order-options input { margin: 0; accent-color: var(--acc); }

.check-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--space-12);
  font-size: var(--text-meta);
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}
.check-toggle input { accent-color: var(--acc); margin: 0; width: var(--space-4); height: var(--space-4); }

.ghost-btn {
  border: 1px solid var(--line);
  background: var(--bg2);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 12px;
  padding: 10px 16px;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 160ms var(--ease-out), color 160ms var(--ease-out);
}
.ghost-btn:hover { border-color: var(--acc); color: var(--acc); }
@media (prefers-reduced-motion: reduce) { .ghost-btn { transition: none; } }

/* ---------- Entrance / navigation / universe ---------- */
.hero {
  display: grid;
  gap: var(--space-8);
  padding-block: var(--space-12);
  align-items: center;
}
.hero-copy { min-width: 0; }
.hero .eyebrow {
  font-size: var(--text-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 var(--space-6);
}
.hero h1 {
  font-family: var(--display);
  font-weight: 500;
  font-size: var(--text-hero);
  line-height: 1.17;
  letter-spacing: -0.045em;
  margin: 0 0 var(--space-6);
  text-wrap: balance;
}
.hero p.lede {
  max-width: 44ch;
  color: var(--muted);
  font-size: var(--text-lead);
  margin: 0 0 var(--space-8);
}
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.hero-action {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  min-height: var(--space-12);
  max-width: 100%;
  font-size: var(--text-meta);
  font-weight: 600;
  text-decoration: none;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  color: var(--ink);
  transition: transform var(--duration-press) var(--ease-out);
}
.hero-action.primary { background: var(--acc); color: var(--acc-ink); border-color: var(--acc); }
.hero-action:hover { border-color: var(--acc); }
.hero-action:active { transform: scale(0.98); }
.hero-action.explore { border-color: transparent; color: var(--muted); padding-inline: 0; flex-basis: 100%; justify-content: flex-start; }
.hero-action.explore:hover { color: var(--ink); }
.hero-media {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 4 / 5;
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  background:
    radial-gradient(circle at 24% 24%, var(--muted) 0 1px, transparent 2px),
    radial-gradient(circle at 79% 18%, var(--muted) 0 1px, transparent 2px),
    radial-gradient(circle at 86% 68%, var(--muted) 0 1px, transparent 2px),
    radial-gradient(ellipse at 90% 0%, var(--bg3), transparent 60%),
    var(--bg);
}
.hero-media::before {
  content: "";
  position: absolute;
  width: 76%;
  aspect-ratio: 1;
  left: 32%;
  top: 14%;
  border-radius: var(--radius-round);
  background: radial-gradient(circle at 16% 28%, var(--bg3), var(--bg) 65%);
  box-shadow: -1px -1px 0 color-mix(in oklch, var(--acc) 72%, transparent), -12px -8px 48px -32px var(--acc);
}
.hero-media::after {
  content: "";
  position: absolute;
  width: 13%;
  aspect-ratio: 1;
  left: 18%;
  top: 62%;
  border-radius: var(--radius-round);
  background: radial-gradient(circle at 70% 25%, var(--line), var(--bg) 65%);
  box-shadow: 1px -1px 0 color-mix(in oklch, var(--focus) 55%, transparent);
}
.hero-orbit {
  position: absolute;
  width: 125%;
  height: 23%;
  left: -6%;
  top: 40%;
  border: 1px solid color-mix(in oklch, var(--acc) 24%, transparent);
  border-radius: var(--radius-round);
  transform: rotate(-32deg);
}
.hero-media-caption { position: absolute; left: var(--space-6); bottom: var(--space-6); right: var(--space-6); color: var(--muted); font-family: var(--mono); font-size: var(--text-meta); }
/* keep in sync with <source media> in index.html */
.hero-picture { position: absolute; inset: 0; }
.hero-picture img { width: 100%; height: 100%; object-fit: cover; object-position: 54% 31%; display: block; }
@media (min-width: 1024px) {
  .hero-picture img { object-position: 76% 45%; }
}
/* While the real hero art is present, the CSS-only orbit fallback stays in
   the DOM but hidden — a failed image (see the error handler in app.js)
   removes .has-art and the image, letting this same fallback reappear with
   no broken-image icon and no extra markup swap. */
.hero-media.has-art::before,
.hero-media.has-art::after,
.hero-media.has-art .hero-orbit {
  display: none;
}
.chapter-navigator { padding-block: var(--space-8) var(--space-12); border-top: 1px solid var(--line); }
.orientation { margin: 0 0 var(--space-12); color: var(--muted); font-size: var(--text-lead); max-width: 42ch; }
.section-heading { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-3) var(--space-6); margin-bottom: var(--space-6); }
.section-heading h2 { font-family: var(--display); font-weight: 500; font-size: var(--text-section); letter-spacing: -0.025em; margin: 0; }
.section-index { color: var(--muted); font-family: var(--mono); font-size: var(--text-meta); }
.section-intro { color: var(--muted); max-width: 52ch; margin: 0 0 var(--space-6); }
.universe { padding-block: var(--space-12); border-top: 1px solid var(--line); }

@media (min-width: 768px) {
  .hero { grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr); padding-block: var(--space-16); }
  .hero-media { aspect-ratio: 4 / 5; }
}
@media (min-width: 1024px) {
  .hero { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-12); padding-block: var(--space-24); }
  .hero-media { aspect-ratio: 6 / 5; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-action { transition: none; }
  .hero-action:active { transform: none; }
}

.map-frame {
  position: relative;
  overflow: hidden;
  background: radial-gradient(ellipse at 50% 20%, var(--bg3), var(--bg2) 70%);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: clamp(10px, 2vw, 22px);
  box-shadow: var(--shadow);
}
/* Decorative fence backdrop (Phase 3). A CSS background on a ::before layer,
   not an <img>: it depicts no navigational information, so it stays out of
   the accessibility tree entirely rather than needing an empty alt. z-index
   -1 keeps it beneath .map-frame's own in-flow content (the #fence SVG —
   every planet dot, ring, label and the Great Crash banner) while still
   painting above .map-frame's own background, per the CSS stacking order
   for negative-z-index children. Fixed opacity only: no gradient/scrim
   layered with the url() here, so a failed image load leaves nothing
   behind — .map-frame's own radial-gradient shows through exactly as
   before, with no gap, no broken-image icon, no layout shift, since this
   is a background of an absolutely-positioned, non-flow element. Opacity
   0.15 was chosen by measuring: the single brightest pixel in the source
   image (~white) composited over the lightest point of the existing
   gradient at this opacity still leaves the .label text color at ~5.3:1
   contrast, comfortably above the 4.5:1 floor — see phase3-report.md. */
.map-frame::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: url("assets/art/map/keywork-map-backdrop.webp") center / cover no-repeat;
  opacity: 0.15;
  pointer-events: none;
}
#fence { width: 100%; height: auto; display: block; }
#fence .lattice { fill: var(--muted); opacity: 0.45; }
#fence .beam { stroke: var(--acc); stroke-opacity: 0.22; stroke-width: 1; fill: none; }
#fence .beam.crashed { stroke-opacity: 0.06; transition: stroke-opacity 1.1s ease; }
#fence .star { fill: var(--acc); }
#fence .planet {
  fill: var(--muted);
  stroke: var(--bg);
  stroke-width: 1;
  transition: transform 0.4s cubic-bezier(.2,.8,.2,1), fill 0.3s, opacity 0.9s;
  transform-box: fill-box;
  transform-origin: center;
}
#fence .planet.lit { fill: var(--acc); r: 5; }
#fence .planet.crashed {
  transform: translate(calc(var(--dx, 0) * 1px), calc(var(--dy, 0) * 1px));
  opacity: 0.35;
}
#fence text.label {
  font-family: var(--mono);
  font-size: 9px;
  fill: var(--muted);
  pointer-events: none;
}
#fence text.label.lit { fill: var(--acc); }
#fence .ring {
  fill: none;
  stroke: var(--acc);
  stroke-width: 1.5;
  opacity: 0;
  transition: opacity 0.3s;
}
#fence .ring.lit { opacity: 1; }
.crash-banner {
  font-family: var(--mono);
  font-size: 11px;
  fill: var(--warn);
  opacity: 0;
  transition: opacity 0.6s;
}
.crash-banner.show { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  #fence .planet, #fence .beam, .crash-banner { transition: opacity 0.2s linear !important; transform: none !important; }
}

/* ---------- Worlds gallery ---------- */
.worlds-gallery { margin-top: var(--space-8); }
.worlds-empty { color: var(--muted); font-style: italic; max-width: 52ch; }
.world-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--space-4);
}
.world-card {
  background: var(--bg2);
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  overflow: hidden;
  box-shadow: var(--shadow);
}
.world-card-media { aspect-ratio: 3 / 2; background: var(--bg3); }
.world-card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.world-card-body { padding: var(--space-3) var(--space-4) var(--space-4); }
.world-card-body h3 { font-family: var(--body); font-weight: 600; font-size: 15px; margin: 0 0 2px; }
.world-card-body .sub { font-family: var(--mono); font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; margin: 0 0 8px; }
.world-card-body p:last-child { margin: 0; font-size: 13px; color: var(--ink); }
/* A card whose image 404s loses its media block entirely (see
   buildWorldCard's error listener) — no broken-image icon, no gap. */
.world-card.no-media .world-card-body { padding-top: var(--space-4); }

/* ---------- Rail ---------- */
#rail {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding: 18px 2px 6px;
  scroll-snap-type: x proximity;
}
@keyframes rail-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}
.tile {
  flex: 0 0 148px;
  scroll-snap-align: start;
  border: 1px solid var(--line);
  border-top: 3px solid var(--tint, var(--line));
  background: var(--bg2);
  border-radius: 8px;
  /* The 3px tint accent needs a square top edge — a thick top border
     fighting a rounded corner creates a visible seam at 8px radius. */
  border-top-left-radius: 2px;
  border-top-right-radius: 2px;
  padding: 10px 12px 12px;
  cursor: pointer;
  text-align: left;
  font-family: var(--body);
  color: var(--ink);
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* One declaration for hover, focus AND press — a second rule fighting
     this one over `transition` would silently drop whichever loses. */
  transition: border-color 180ms var(--ease-out), transform 180ms var(--ease-out);
}
/* The stagger-in only plays once, on the very first paint (body.first-paint,
   removed by app.js right after) — the rail re-renders on every order
   toggle and every "Enter chapter" click, and replaying a fade on a
   tens-of-times-per-session action would be exactly the animation
   frequency mistake emil-design-eng warns against. */
body.first-paint .tile {
  animation: rail-in 360ms var(--ease-out) backwards;
  animation-delay: var(--rail-delay, 0ms);
}
.tile:hover, .tile:focus-visible { border-color: var(--acc); transform: translateY(-2px); }
.tile:active { transform: scale(0.97); }
@media (prefers-reduced-motion: reduce) {
  body.first-paint .tile { animation: none; opacity: 1; }
  .tile { transition: border-color 120ms linear; }
  .tile:hover, .tile:focus-visible, .tile:active { transform: none; }
}
.tile .num { font-family: var(--mono); font-size: 10px; color: var(--muted); }
.tile .ttl { font-family: var(--display); font-weight: 600; font-size: 13px; line-height: 1.25; text-wrap: balance; }
.tile .flags { display: flex; gap: 6px; margin-top: auto; }
.tile.extra { opacity: 0.7; cursor: default; }
.tile.extra:hover, .tile.extra:active { transform: none; border-top-color: var(--line); }

.badge {
  font-family: var(--mono);
  font-size: 10px;
  padding: 2px 6px;
  border-radius: 3px;
  background: var(--acc-soft);
  color: var(--acc);
  white-space: nowrap;
}
.badge.partial { background: var(--warn-soft); color: var(--warn); }
.badge.none { background: var(--bg3); color: var(--muted); }

/* ---------- Chapter navigator (Task 4) ---------- */
/* Coverage and reading progress always carry an icon + word, never color
   alone — colorblind-safe by construction, not by contrast tuning. */
.chapter-tile { flex-basis: 168px; }
.tile .coverage, .tile .progress {
  font-family: var(--mono);
  font-size: 10px;
  display: flex;
  align-items: center;
  gap: 4px;
}
.tile .coverage { color: var(--muted); }
.tile .progress { margin-top: auto; }
.coverage-complete, .progress-completed { color: var(--acc); }
.coverage-partial, .progress-in-progress { color: var(--warn); }
.coverage-outside, .coverage-unreleased, .progress-not-started { color: var(--muted); }
.tile[aria-current="true"] { border-color: var(--acc); box-shadow: inset 0 0 0 1px var(--acc); }

.nav-controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin-top: var(--space-4); }
.nav-btn {
  font-family: var(--mono);
  font-size: 12px;
  border: 1px solid var(--line);
  background: var(--bg3);
  color: var(--ink);
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
}
.nav-btn:hover:not(:disabled) { border-color: var(--acc); }
.nav-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.chapter-position { font-family: var(--mono); font-size: 12px; color: var(--muted); margin: 0; flex: 1 1 160px; text-align: center; min-width: 0; }
.all-chapters-btn { margin-left: auto; }

.mark-complete-btn {
  display: block;
  margin: 20px 0 0;
  font-family: var(--mono);
  font-size: 12px;
  border: 1px solid var(--line);
  background: var(--bg3);
  color: var(--ink);
  padding: 10px 16px;
  border-radius: var(--radius-control);
  cursor: pointer;
}
.mark-complete-btn:hover:not([aria-disabled="true"]) { border-color: var(--acc); }
.mark-complete-btn[aria-disabled="true"] { color: var(--acc); border-color: var(--acc); cursor: default; }

/* ---------- Dialogs (all-chapters, reset-confirm) ---------- */
dialog#all-chapters, dialog#reset-confirm {
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  background: var(--bg2);
  color: var(--ink);
  padding: 0;
  width: min(640px, calc(100% - var(--gutter) * 2));
  max-height: min(80vh, 720px);
  flex-direction: column;
  box-shadow: var(--shadow);
}
dialog#reset-confirm { width: min(420px, calc(100% - var(--gutter) * 2)); }
dialog#all-chapters[open], dialog#reset-confirm[open] { display: flex; }
dialog#all-chapters::backdrop, dialog#reset-confirm::backdrop { background: rgba(3, 6, 12, 0.6); }
/* Fallback path (no native showModal support): position and dim manually,
   reusing the same scrim the Codex drawer uses. */
dialog#all-chapters.open, dialog#reset-confirm.open { position: fixed; inset: 0; margin: auto; z-index: 45; }
.dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--line); flex: none; }
.dialog-head h2 { margin: 0; font-family: var(--display); font-weight: 500; font-size: 18px; }
.all-chapters-list { overflow-y: auto; padding: 4px 16px 16px; }
.dialog-body { padding: 16px; }
.dialog-body p { color: var(--muted); margin: 0 0 18px; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 10px; }
.danger-btn {
  border: 1px solid var(--warn);
  background: var(--warn-soft);
  color: var(--warn);
  font-family: var(--mono);
  font-size: 12px;
  padding: 10px 16px;
  border-radius: 999px;
  cursor: pointer;
}
.danger-btn:hover { filter: brightness(1.1); }
.chapter-row {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 4px;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  font-family: var(--body);
}
.chapter-row:last-child { border-bottom: 0; }
.chapter-row:hover, .chapter-row:focus-visible { color: var(--acc); }
.chapter-row[aria-current="true"] .ttl { color: var(--acc); }
.chapter-row .row-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.chapter-row .ttl { font-weight: 600; font-size: 14px; }
.chapter-row .row-meta { display: flex; flex-wrap: wrap; gap: 10px; font-family: var(--mono); font-size: 10px; color: var(--muted); }

/* ---------- Chapter sections ---------- */
#chapters { padding-block: var(--space-2) var(--space-16); }
.chapter { padding-block: 56px; border-top: 1px solid var(--line); }
.chapter:first-child { border-top: none; }

/* The opening composition: reserved-ratio art, title, intro, coverage,
   source summary, and the Start/Continue action — always above the beats,
   whether or not spoiler protection is on. */
.chapter-opening { display: flex; gap: 24px; flex-wrap: wrap; align-items: flex-start; margin-bottom: 28px; }
.chapter-opening .opening-body { flex: 1 1 320px; min-width: 0; }
.chapter-art {
  flex: 1 1 240px;
  max-width: 320px;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-panel);
  overflow: hidden;
  background: linear-gradient(155deg, color-mix(in oklch, var(--tint) 35%, var(--bg2)), var(--bg2));
  position: relative;
}
.chapter-art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focal, 50% 50%);
  display: block;
}
/* Generated/missing art never leaves a broken-image icon on screen — the
   gradient above is always present underneath, so hiding a failed <img>
   (see the error listener in buildChapterArt) is enough on its own. */
.chapter-art.art-fallback::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 30% 30%, color-mix(in oklch, var(--tint) 45%, transparent), transparent 60%),
    radial-gradient(circle at 75% 70%, color-mix(in oklch, var(--acc) 20%, transparent), transparent 55%);
}
.opening-body .chapter-intro { color: var(--muted); max-width: 62ch; margin: 0 0 14px; }
.opening-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-bottom: 16px; font-family: var(--mono); font-size: 11px; color: var(--muted); }
.opening-meta .source-summary { text-transform: uppercase; letter-spacing: 0.04em; }

.chapter-head { display: flex; gap: 28px; flex-wrap: wrap; align-items: flex-start; }
.chapter-head .col-main { flex: 1 1 420px; min-width: 0; }
.chapter-meta {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.chapter-meta .tint-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 2px;
  background: var(--tint); vertical-align: -1px; margin-right: 5px;
}
.chapter h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(24px, 3.4vw, 34px);
  line-height: 1.1;
  margin: 0 0 12px;
  text-wrap: balance;
}
.chapter .premise { color: var(--muted); max-width: 62ch; margin: 0 0 16px; }
.canon-note {
  font-size: 13px;
  color: var(--warn);
  background: var(--warn-soft);
  border-radius: 6px;
  padding: 8px 12px;
  max-width: 62ch;
  margin: 0 0 16px;
}

.sources { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.source-chip {
  border: 1px solid var(--line);
  background: var(--bg2);
  border-radius: 6px;
  padding: 6px 10px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink);
  text-decoration: none;
  display: inline-flex;
  gap: 6px;
  align-items: baseline;
}
.source-chip .kind { color: var(--acc); text-transform: uppercase; }
.source-chip:hover { border-color: var(--acc); }

.opening-action {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.03em;
  background: var(--acc);
  color: var(--acc-ink);
  border: 0;
  border-radius: 999px;
  padding: 13px 22px;
  cursor: pointer;
}
.opening-action[hidden] { display: none; }

/* mini-map column */
.mini-map {
  flex: 0 0 200px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg2);
  padding: 12px;
}
.mini-map .cap { font-family: var(--mono); font-size: 10px; color: var(--muted); margin: 0 0 8px; text-transform: uppercase; letter-spacing: 0.06em; }
.mini-map svg { width: 100%; height: auto; }
.mini-map .p { fill: var(--muted); }
.mini-map .p.pulse { fill: var(--acc); }
@keyframes pulse { 0%,100% { r: 4; opacity: 1; } 50% { r: 7; opacity: 0.5; } }
.mini-map .p.pulse { animation: pulse 1.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .mini-map .p.pulse { animation: none; r: 5; } }

/* Willing Well */
.well-switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 6px 6px 14px;
  margin: 4px 0 18px;
  background: var(--bg2);
  cursor: pointer;
}
.well-switch .track { width: 34px; height: 18px; border-radius: 999px; background: var(--bg3); position: relative; }
.well-switch .knob {
  position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;
  background: var(--muted); transition: transform 220ms var(--ease-out), background-color 220ms var(--ease-out);
}
.well-switch[aria-pressed="true"] .knob { transform: translateX(16px); background: var(--acc); }
@media (prefers-reduced-motion: reduce) { .well-switch .knob { transition: background-color 120ms linear; } }

/* The Real: the one place the page turns to typed paper — this is the
   site's signature moment, so it earns real craft rather than a class
   toggle. background-color/color crossfade on their own; every CHILD
   color (chips, mini-map, beat borders) would still snap instantly since
   they're separate declarations, so a brief blur pulse (.well-flipping,
   applied by app.js for one frame either side of the swap) bridges the
   whole section through the cut — Emil Kowalski's "blur masks an
   imperfect transition" applied to a full theme flip, not just one button.
   These are literal paper colors, not theme tokens: a deliberate one-
   section inversion, not a site theme. */
#ga1 {
  transition: background-color 420ms var(--ease-in-out), color 420ms var(--ease-in-out),
    box-shadow 420ms var(--ease-in-out), filter 260ms var(--ease-out);
}
#ga1.well-flipping { filter: blur(7px); }
@media (prefers-reduced-motion: reduce) {
  #ga1 { transition: background-color 200ms linear, color 200ms linear, box-shadow 200ms linear; }
  #ga1.well-flipping { filter: none; }
}
#ga1[data-layer="real"] {
  background: #f3efe6;
  color: #1c1a16;
  border-radius: 10px;
  padding: 28px clamp(14px, 2vw, 30px);
  box-shadow: var(--shadow);
}
#ga1[data-layer="real"] .premise,
#ga1[data-layer="real"] .chapter-meta,
#ga1[data-layer="real"] .btext { color: #5c564a; }
#ga1[data-layer="real"] .beat { border-top-color: #d8d0bd; }
#ga1[data-layer="real"] .chip, #ga1[data-layer="real"] .source-chip {
  background: #ece6d6; border-color: #d8d0bd; color: #1c1a16;
}
#ga1[data-layer="real"] .mini-map { background: #ece6d6; border-color: #d8d0bd; }
#ga1[data-layer="real"] .mini-map .p { fill: #8a8168; }
#ga1[data-layer="real"] .mini-map .p.pulse { fill: #1a6fd6; }
#ga1[data-layer="real"] .embed .fallback { border-color: #d8d0bd; color: #5c564a; }

/* ---------- Beats ---------- */
.beats { margin-top: 8px; }
.beats[hidden] { display: none; }
.beat {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
  padding-block: 22px;
  border-top: 1px dashed var(--line);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out);
}
.beat.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .beat { transition: none; } }
.beat:first-child { border-top: none; padding-top: 6px; }
.beat[data-layer]:not([data-visible]) { display: none; }
.beat .btitle { font-family: var(--body); font-weight: 600; font-size: 15px; margin: 0; }
.beat .btext { margin: 6px 0 12px; max-width: 60ch; }
.beat .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.chip {
  border: 1px solid var(--line);
  background: var(--bg2);
  border-radius: 999px;
  padding: 3px 10px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink);
  cursor: pointer;
}
.chip:hover { border-color: var(--acc); color: var(--acc); }
.chip.planet-chip { color: var(--muted); cursor: default; }
.chip.planet-chip::before { content: "◉ "; }

.embed { max-width: 420px; }
.embed .row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; font-family: var(--mono); font-size: 11px; color: var(--muted); margin-bottom: 6px; }
.embed .row a { color: var(--muted); }
.embed .row a:hover { color: var(--acc); }
.embed .confidence { color: var(--warn); }
.embed iframe { border-radius: 8px; border: 1px solid var(--line); display: block; }
.embed .fallback {
  border: 1px dashed var(--line);
  border-radius: 8px;
  padding: 14px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
}

.next-link {
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 13px;
}
.next-link a { text-decoration: none; }
.next-link a:hover { text-decoration: underline; }

/* ---------- Drawer ---------- */
#drawer {
  position: fixed;
  inset: 0 0 0 auto;
  width: min(420px, 100%);
  background: var(--bg2);
  border-left: 1px solid var(--line);
  box-shadow: var(--shadow);
  transform: translateX(100%);
  /* Off-screen is not out of reach: without this the closed drawer keeps its
     tabs, close button and every Codex entry in the tab order, so Tab from
     the header walks into a panel nobody can see (and aria-hidden over
     focusable content is a violation in its own right). Transitioned, so the
     panel stays visible for the whole slide-out and only then goes away. */
  visibility: hidden;
  /* The iOS-style drawer curve, not a flat ease — a panel this size reads
     as a real object sliding in, so it gets the sheet-specific easing.
     Visibility is stepped, not interpolated: it must flip to visible the
     instant the panel opens (an element that is still visibility:hidden
     cannot take focus, so an interpolated flip loses the focus move), and
     wait out the slide on the way back. */
  transition: transform 320ms var(--ease-drawer), visibility 0s linear 320ms;
  z-index: 40;
  display: flex;
  flex-direction: column;
}
#drawer.open { transform: translateX(0); visibility: visible; transition: transform 320ms var(--ease-drawer), visibility 0s; }
@media (prefers-reduced-motion: reduce) { #drawer { transition: none; } }
#drawer .drawer-head { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--line); }
#drawer .drawer-tabs { display: inline-flex; gap: 6px; margin-right: auto; }
#drawer .drawer-tabs button {
  font-family: var(--mono); font-size: 12px; border: 1px solid var(--line); background: var(--bg3);
  color: var(--muted); padding: 8px 14px; border-radius: 999px; cursor: pointer;
  transition: background-color 160ms var(--ease-out), color 160ms var(--ease-out);
}
#drawer .drawer-tabs button[aria-selected="true"] { background: var(--acc); color: var(--acc-ink); border-color: var(--acc); }
/* One rule for all three close controls (drawer, all-chapters, reset). The
   dialog ones had no styling at all: bare UA buttons, 24x22 — under the 24x24
   minimum target, and nothing like the drawer's. */
.close-btn { border: 0; background: none; color: var(--muted); font-size: 22px; cursor: pointer; line-height: 1; padding: 10px; }
.close-btn:hover { color: var(--acc); }
@media (prefers-reduced-motion: reduce) { #drawer .drawer-tabs button { transition: none; } }
#drawer .drawer-body { overflow-y: auto; padding: 14px 16px 40px; }
.entry { border-bottom: 1px solid var(--line); padding: 14px 0; }
.entry:last-child { border-bottom: none; }
/* Codex portrait thumbnail: a fixed-width 3:4 box reserved up front so a
   lazy-loading image never shifts the text beside it. A gated or missing
   portrait means buildCharPortrait returns null (or its wrapper is removed
   on load error) — .entry then has no .has-portrait class and lays out
   exactly as it did before portraits existed, with no empty gap. */
.entry.has-portrait { display: flex; gap: 12px; align-items: flex-start; }
.entry-portrait {
  flex: 0 0 64px;
  width: 64px;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-panel);
  overflow: hidden;
  background: var(--bg2);
}
.entry-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.entry-body { flex: 1 1 auto; min-width: 0; }
/* Codex term art (ships/technology, Phase 2b-2): a full-width box above the
   term's name/blurb, reserved at the card's real 3:2 aspect ratio so a
   lazy-loading image never shifts the text below it. Only the 5 gated terms
   ever get this element (see buildTermArt) — a term with no art, or whose
   art is still gated, keeps the exact pre-2b-2 layout. */
.term-art { aspect-ratio: 3 / 2; border-radius: var(--radius-panel); overflow: hidden; background: var(--bg2); margin: 0 0 10px; }
.term-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.entry h3 { font-family: var(--body); font-weight: 600; font-size: 14px; margin: 0 0 4px; }
.entry .sub { font-family: var(--mono); font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 6px; }
.entry p { margin: 0 0 8px; font-size: 13px; color: var(--ink); }
.entry .appears { display: flex; flex-wrap: wrap; gap: 6px; }
.entry .spoiler-note { font-family: var(--mono); font-size: 11px; color: var(--warn); }
.entry .reveal-btn {
  display: block;
  margin-top: 8px;
  border: 1px solid var(--warn);
  background: none;
  color: var(--warn);
  font-family: var(--mono);
  font-size: 11px;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
}
.entry .reveal-btn:hover { background: var(--warn-soft); }

.scrim {
  position: fixed; inset: 0; background: rgba(3, 6, 12, 0.5); z-index: 35;
  opacity: 0; pointer-events: none; transition: opacity 0.2s;
}
.scrim.open { opacity: 1; pointer-events: auto; }

@media (max-width: 640px) {
  #drawer { width: 100%; inset: auto 0 0 0; height: 78vh; transform: translateY(100%); border-left: none; border-top: 1px solid var(--line); border-radius: 14px 14px 0 0; }
  #drawer.open { transform: translateY(0); }
}

/* ---------- About ---------- */
#about { border-top: 1px solid var(--line); padding-block: 48px 80px; }
#about h2 { font-family: var(--display); font-size: 22px; margin: 0 0 14px; }
#about p { color: var(--muted); max-width: 62ch; }
#about ul { color: var(--muted); max-width: 62ch; }
#about .start-over { font-family: var(--mono); font-size: 12px; }
/* Empty is the resting state, so the live region can stay in the document
   permanently (see index.html) without leaving a box on the page. */
.deep-link-notice:empty { display: none; }
.deep-link-notice {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block: var(--space-4) 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--warn);
  border-radius: var(--radius-control);
  background: var(--warn-soft);
  font-family: var(--mono);
  font-size: var(--text-meta);
  color: var(--ink);
}

/* Empty is the resting state, so the live region can stay in the document
   permanently (see index.html) without leaving a box on the page. */
.reset-undo:empty { display: none; }
.reset-undo {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--bg2);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  max-width: fit-content;
}
.reset-undo button {
  border: 1px solid var(--acc);
  background: none;
  color: var(--acc);
  font-family: var(--mono);
  font-size: 12px;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
}
.reset-undo button:hover { background: var(--acc-soft); }

footer.site { font-family: var(--mono); font-size: 11px; color: var(--muted); text-align: center; padding-block: 28px 40px; }
