/* Slag and brass: the game's own substance ladder, not a default palette. */
:root {
  --ground: #e9eae4;
  --surface: #f4f5ef;
  /* One step off `surface`, for a row under the pointer. Used since M5 and
     never defined, so every `.wares:hover` resolved to nothing and the tree
     had no hover state at all. */
  --surface-2: #e2e4db;
  --line: #c9ccc2;
  --ink: #171a19;
  --ink-2: #464c46;
  --ink-3: #6e736c;
  --brass: #8a5c1c;
  --rust: #8b4225;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}
@media (prefers-color-scheme: dark) {
  :root {
    --ground: #131614; --surface: #1b1f1c; --surface-2: #262b27; --line: #333a35;
    --ink: #dfe1da; --ink-2: #acb1a9; --ink-3: #858b83;
    --brass: #d3a05a; --rust: #d28563;
  }
}
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--ground); color: var(--ink);
  font: 16px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
}
main { max-width: 720px; margin: 0 auto; padding: 56px 24px 72px; }
.eyebrow {
  font-family: var(--mono); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--brass); margin: 0 0 10px;
}
h1 { font-size: 40px; line-height: 1.05; letter-spacing: -.02em; margin: 0 0 12px; }
h2 { font-size: 20px; margin: 40px 0 6px; }
.sub, .note { color: var(--ink-2); margin: 0; max-width: 60ch; }
.note { font-size: 14px; margin-bottom: 16px; }
.counts { display: flex; gap: 1px; background: var(--line);
  border: 1px solid var(--line); margin: 32px 0 8px; }
.counts div { background: var(--surface); padding: 20px 22px; flex: 1; }
.counts b { display: block; font-family: var(--mono); font-size: 30px;
  font-variant-numeric: tabular-nums; color: var(--brass); }
.counts span { font-size: 13px; color: var(--ink-3); }
table { width: 100%; border-collapse: collapse; background: var(--surface);
  border: 1px solid var(--line); font-size: 14px; }
td { padding: 9px 14px; border-bottom: 1px solid var(--line); }
tr:last-child td { border-bottom: none; }
.num { font-family: var(--mono); font-variant-numeric: tabular-nums;
  text-align: right; width: 5ch; color: var(--ink-2); }
.rarity { font-family: var(--mono); font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3); }
.r-rare, .r-epic, .r-legendary { color: var(--brass); }
footer { display: flex; justify-content: space-between; gap: 16px;
  margin-top: 40px; padding-top: 16px; border-top: 1px solid var(--line);
  font-family: var(--mono); font-size: 12px; color: var(--ink-3); }
#status.bad { color: var(--rust); }

/* --- M1: the save controls ------------------------------------------- */
.state { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1px; background: var(--line); border: 1px solid var(--line); margin: 32px 0 8px; }
.state > div { background: var(--surface); padding: 20px 22px; }
.state b { display: block; font-family: var(--mono); font-size: 30px;
  font-variant-numeric: tabular-nums; color: var(--brass); line-height: 1.1; }
.state b.small { font-size: 15px; word-break: break-all; }
.state span { display: block; font-size: 13px; color: var(--ink-3); margin-top: 4px; }
.row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
button, .btn { font: inherit; font-size: 14px; padding: 7px 14px; cursor: pointer;
  background: var(--ground); color: var(--ink); border: 1px solid var(--line);
  border-radius: 2px; }
button:hover, .btn:hover { border-color: var(--brass); color: var(--brass); }
button.primary { background: var(--brass); border-color: var(--brass); color: var(--surface); }
button.primary:hover { filter: brightness(1.1); color: var(--surface); }
:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
.save .row { margin-top: 16px; }
/* `.says` is still the in-screen slot for a town, a bench, the tree and the
   board — a sentence about the screen you are on, printed on it. What left is
   the one that hung below the save panel and belonged to nothing. */
.says { margin: 16px 0 0; font-family: var(--mono); font-size: 13px; color: var(--ink-2); }
.says.bad { color: var(--rust); }

/* --- M11.0: the log ----------------------------------------------------- */
/* The strip: the last few things the game said, always up. The overlay wears
   the same class with `.whole` on it, so a line reads the same in both — one
   look, because they are one transcript at two lengths. */
.taperow { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.taperow h3 { margin: 0; }
button.small { font-size: 12px; padding: 4px 10px; letter-spacing: .08em; }
.tape { list-style: none; margin: 12px 0 0; padding: 0;
  font-family: var(--mono); font-size: 13px; line-height: 1.5; }
.tape li { color: var(--ink-2); padding: 3px 0 3px 12px; border-left: 2px solid var(--line);
  overflow-wrap: anywhere; }
.tape li.bad { color: var(--rust); border-left-color: var(--rust); }
/* The newest line is the one being read; the ones above it are context. */
.tape:not(.whole) li:last-child { color: var(--ink); border-left-color: var(--brass); }
.tape:empty::after { content: 'Nothing yet.'; color: var(--ink-3); font-family: var(--mono);
  font-size: 13px; }
.tape.whole { max-height: none; margin-top: 20px; }
.tape.whole li { padding: 5px 0 5px 14px; }
code { font-family: var(--mono); font-size: .9em; }

/* --- M2: the map ------------------------------------------------------- */
main.wide { max-width: 1120px; }
.board { display: grid; grid-template-columns: 640px minmax(240px, 1fr);
  gap: 28px; align-items: start; margin: 28px 0 8px; }
.mapwrap { border: 1px solid var(--line); line-height: 0; }
#map { display: block; width: 640px; height: 640px; image-rendering: pixelated; }
#map:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
aside { display: flex; flex-direction: column; gap: 20px; }
.panel { background: var(--surface); border: 1px solid var(--line); padding: 18px 20px; }
.panel h3 { font-size: 17px; margin: 0 0 12px; }
.panel dl { margin: 0; display: grid; gap: 6px; }
.panel dl > div { display: flex; justify-content: space-between; gap: 12px;
  font-size: 13.5px; border-bottom: 1px dotted var(--line); padding-bottom: 5px; }
.panel dl > div:last-child { border-bottom: none; }
/* **Trap 6, fourth coat.** Anything that sets `display` on an element it also
   hides needs its own `[hidden]` rule, because the two have equal specificity
   and the later one wins. The survey row is hidden on every map but one and was
   showing "surveying with —" on all of them; the playtest driver found it
   before the playtest did. `.card`, `.screen.framed` and the z-index stack were
   the first three. */
.panel dl > div[hidden] { display: none; }
.panel dt { color: var(--ink-3); }
.panel dd { margin: 0; font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--ink); }
kbd { font-family: var(--mono); font-size: .85em; border: 1px solid var(--line);
  border-bottom-width: 2px; border-radius: 3px; padding: 1px 5px; background: var(--surface); }

/* --- the card ---------------------------------------------------------- */
.card { position: fixed; inset: 0; display: grid; place-items: center;
  background: rgba(19,22,20,.55); padding: 24px; z-index: 10; }
/* `display: grid` above outranks the browser's `[hidden] { display: none }`,
   so a hidden card stays laid out and keeps swallowing clicks. Anything that
   sets `display` on an element it also hides needs this line. */
.card[hidden] { display: none; }
.cardbox { background: var(--surface); border: 1px solid var(--line);
  max-width: 620px; width: 100%; padding: 28px 30px 26px; max-height: 86vh; overflow-y: auto; }
.cardbox h2 { font-family: var(--mono); font-size: 15px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--brass); margin: 0 0 16px; }
#card-prose p { margin: 0 0 12px; max-width: 60ch; }
.choices { display: grid; gap: 8px; margin-top: 20px; }
.choice { text-align: left; padding: 12px 14px; display: grid; gap: 3px; }
.choice b { font-weight: 600; }
.choice span { font-size: 13px; color: var(--ink-3); }
.choice:disabled { opacity: .55; cursor: not-allowed; }
.choice:disabled span { color: var(--rust); }
.receipt { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line);
  font-family: var(--mono); font-size: 13px; }
.receipt p { margin: 0 0 4px; }

@media (max-width: 1040px) {
  .board { grid-template-columns: 1fr; }
  .mapwrap { max-width: 640px; }
  #map { width: 100%; height: auto; aspect-ratio: 1; }
}

/* --- M3: the fight screen ---------------------------------------------- */
.screen { position: fixed; inset: 0; background: var(--ground); z-index: 20;
  overflow-y: auto; padding: 28px 24px 40px; }
.screen[hidden] { display: none; }
/* **Screens stack in the order they open, and the order is written down.**
   Every `.screen` sat at 20, so which one covered which was decided by the
   order of the file — and the town comes after the fork. A level lands when
   you bank, banking happens with the town up, and the fork opened *underneath*
   it: four class cards on screen, none of them clickable, and the game
   unfinishable from level five. Taking a class then opens the tree, which was
   under the town for the same reason.

   Three tiers, and each is a sentence about what a screen is:

   - **where you are** — the fight, the town, the map's card. One at a time.
   - **what you opened from there** — the tree, the log, the ending.
   - **the one that does not come off** — the fork.

   The third collision of this kind: `.card` made every item card a
   full-viewport overlay, `.screen.framed[hidden]` kept a hidden fight screen
   swallowing clicks, and this. All three were found by `elementFromPoint` and
   none of them by reading. `check_the_fork_is_on_top` is what stops a fourth. */
#tree, #log, #ending, #history { z-index: 30; }
/* The van is *where you are*, like a town and the fight — tier 20. Putting it
   above the town would have been a screen you could open on top of one you
   cannot walk out of. See the stack in CLAUDE.md. */
#fork { z-index: 40; }
/* The board is the game's main screen and was being squeezed into the same
   column as a paragraph of prose. The original ran at 1600x980 with 36px
   cells; this gets as close as the viewport allows. */
.screenbox { max-width: min(1560px, 97vw); margin: 0 auto; }
/* **A framed screen: header, a scrolling middle, and one action row.**
   The fight screen's three stages are 15, 19 and 3 lines tall, so a primary
   button at the bottom of whichever one was showing sat at three different
   heights — Fight, Skip to the end and Walk on were three targets. The bar is
   pinned and the stages scroll under it, so the advance gesture is one place. */
.screen.framed { display: flex; flex-direction: column; overflow: hidden;
  padding-bottom: 0; }
/* **The `.card[hidden]` trap again, and it bit again.** `.screen[hidden]` and
   `.screen.framed` have the same specificity, so the later rule wins and a
   hidden fight screen stayed laid out over the whole page — the town's Spend
   it button resolved, was visible and enabled, and `#run` from the hidden
   fight screen swallowed every click on it. Anything that sets `display` on an
   element it also hides needs this line. */
.screen.framed[hidden] { display: none; }
.screen.framed > .screenbox { display: flex; flex-direction: column;
  width: 100%; flex: 1; min-height: 0; }
.stages { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-bottom: 10px; }
.actionbar { flex: none; display: flex; flex-wrap: wrap; gap: 8px;
  border-top: 2px solid var(--ink); padding: 12px 0 16px; background: var(--ground); }
.actionbar[hidden] { display: none; }
/* One width for the three, so the box a player is aiming at is identical on
   every stage and not merely in the same corner. */
.actionbar .advance { min-width: 190px; text-align: center; justify-content: center; }
/* The event card is a dialog rather than a screen: its box scrolls, so the bar
   sticks to the bottom of the box. */
.actionbar.sticky { position: sticky; bottom: -26px; margin-top: 20px;
  background: var(--surface); }

.fighthead { display: flex; flex-wrap: wrap; justify-content: space-between;
  gap: 20px; align-items: flex-start; border-bottom: 2px solid var(--ink);
  padding-bottom: 18px; margin-bottom: 20px; }
.fighthead h2 { font-size: 30px; margin: 0 0 6px; letter-spacing: -.01em; }
.fighthead .note { margin: 0; max-width: 46ch; }
.figures { display: flex; gap: 26px; margin: 0; }
.figures dt { font-family: var(--mono); font-size: 10.5px; letter-spacing: .11em;
  text-transform: uppercase; color: var(--ink-3); }
.figures dd { margin: 2px 0 0; font-family: var(--mono); font-size: 20px;
  font-variant-numeric: tabular-nums; color: var(--brass); }
/* The board sizes its own backing store to its box — see Board#fit — so it must
   not also be scaled by CSS. Width fills the column; height follows content. */
#board { display: block; width: 100%; background: #101018;
  border: 1px solid var(--line); margin: 0; }
#replay { display: block; width: 100%; height: auto; margin: 0;
  background: #101018; border: 1px solid var(--line); }

/* Board on the left, what it made on the right. The item list was 11px canvas
   text crammed under each grid, where two items overlapped and a third was cut
   off; it is HTML now, so it can wrap, scroll and be read. */
.packing { display: grid; grid-template-columns: minmax(0, 1fr) 330px;
  gap: 20px; align-items: start; margin: 14px 0 6px; }
.made { background: var(--surface); border: 1px solid var(--line); padding: 16px 18px;
  max-height: 78vh; overflow-y: auto; }
.made h4 { font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3);
  margin: 0 0 10px; }
.made .grid-of { font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--brass); margin: 14px 0 6px; }
.made .grid-of:first-of-type { margin-top: 0; }
/* One item's card. Named `made-item`, not `card`: `.card` is the event
   dialog — position: fixed, inset: 0, z-index 10 — and reusing the name turned
   every item into a full-viewport overlay pinned over the whole game.
   In the shape upstream's tooltip used: what it is, what it
   is worth, what it does standing still, and what it does every time it comes
   round. The sparse version showed a name and a number, which is the two least
   interesting facts about an item. */
.made .made-item { padding: 12px 0 14px; border-bottom: 1px solid var(--line); }
.made .made-item:last-child { border-bottom: none; }
.made .made-item > b { display: block; font-weight: 600; font-size: 15px;
  color: var(--brass); line-height: 1.3; }
.made .made-item .built { display: block; font-family: var(--mono); font-size: 11px;
  color: var(--ink-3); margin-top: 2px; }
.made .made-item .rank { display: block; font-family: var(--mono); font-size: 11px;
  color: var(--ink-2); margin-top: 3px; }
.made .made-item .pips { letter-spacing: .08em; color: var(--brass); }
.made .made-item .next { display: block; color: var(--ink-3); margin-top: 1px; }
.made .made-item .head { display: block; font-family: var(--mono); font-size: 10px;
  letter-spacing: .13em; text-transform: uppercase; color: var(--ink-3);
  margin: 10px 0 4px; padding-top: 6px; border-top: 1px dotted var(--line); }
.made .stats { margin: 0; padding: 0; list-style: none; }
.made .stats li { font-size: 13px; color: var(--ink); padding: 1px 0 1px 10px;
  position: relative; }
.made .stats li::before { content: "·"; position: absolute; left: 0; color: var(--ink-3); }
.made .stats li b { font-weight: 600; font-variant-numeric: tabular-nums; }
.made .stats li.none { color: var(--ink-3); font-style: italic; }
.made .made-item.short > b { color: var(--ink-3); font-weight: 400; }
/* A set is a name somebody wrote rather than one the generator made, so it gets
   the brass the page already uses for something earned — the same ink the sheet
   prints a rule in, because they are the same fact seen from two screens. */
.made .made-item.is-set > b { color: var(--brass); }
.made .stats.set-rules li { color: var(--brass); cursor: help; }
.made .made-item .why { display: block; font-size: 12.5px; color: var(--rust); margin-top: 3px; }
.made .empty { font-size: 13px; color: var(--ink-3); }
.holding { font-family: var(--mono); font-size: 13px; color: var(--brass);
  margin: 8px 0 0; min-height: 1.4em; }

@media (max-width: 1080px) { .packing { grid-template-columns: 1fr; } }
#board { cursor: pointer; }
#stage-result h3 { font-family: var(--mono); font-size: 15px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--brass); margin: 8px 0 14px; }

/* --- M3: the town ------------------------------------------------------ */
.sub-h { font-family: var(--mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); margin: 22px 0 10px; }
.shelf { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 1px; background: var(--line); border: 1px solid var(--line); }
.wares { background: var(--surface); padding: 14px 16px; text-align: left;
  display: grid; gap: 3px; border: none; border-radius: 0; }
.wares:hover:not(:disabled) { background: var(--surface-2); border: none; color: var(--ink); }
.wares b { font-size: 14.5px; font-weight: 600; }
.wares .meta { font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }
.wares .cost { font-family: var(--mono); font-size: 13px; color: var(--brass); }
.wares:disabled { opacity: .5; cursor: not-allowed; }
.wares:disabled .cost { color: var(--rust); }
.wares.pinned { outline: 2px solid var(--brass); outline-offset: -2px; }
/* --- M12.5: what a choice pays ------------------------------------------ */
/*
 * The engine talking, on a choice. Same register as the skill node's line and
 * the recipe box: mono, quiet, and set apart from the blurb above it, which is
 * the author's.
 */
.choice .outcome { margin: 5px 0 0; padding: 0 0 0 2px; list-style: none;
  border-top: 1px solid var(--line); padding-top: 5px; }
.choice .outcome li { font-family: var(--mono); font-size: 11.5px; color: var(--ink-2);
  line-height: 1.55; }
.choice .outcome li.wants { color: var(--rust); }

/* --- M12.B: what a grid takes ------------------------------------------- */
/*
 * A spec, not prose, so it reads like the skill tree's line rather than like
 * an item's flavour: mono, quiet, and on the panel's own recessed ground.
 */
.recipe { margin: 2px 0 8px; padding: 6px 9px; background: var(--surface-2);
  border-left: 2px solid var(--line); }
.recipe-h { font-family: var(--mono); font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3); }
.recipe ul { margin: 3px 0 0; padding: 0; list-style: none; }
.recipe li { font-family: var(--mono); font-size: 11.5px; color: var(--ink-2);
  line-height: 1.5; }
.recipe li b { color: var(--ink); font-weight: 600; }
/* And where it is asked, which moved. The `?` stands beside the frame's name
   on the board rather than five boxes down the panel beside it — the question
   belongs at the empty greaves grid, not above the cards for the items the
   weapon grid has already made. The layer is inert and the buttons are not, so
   the canvas keeps every pixel it hit-tests. */
.boardwrap { position: relative; }
#frame-help { position: absolute; inset: 0; pointer-events: none; }
.frame-help { position: absolute; pointer-events: auto; padding: 0;
  display: grid; place-items: center; border-radius: 50%;
  background: #1b1b26; border: 1px solid var(--line); color: var(--ink-3);
  font-family: var(--mono); font-size: 10px; line-height: 1; cursor: help; }
.frame-help:hover, .frame-help:focus-visible { color: var(--brass); border-color: var(--brass); }

/* --- M12.6: turning a list over ---------------------------------------- */
.rollbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 8px}
.rollbar[hidden]{display:none!important}
.rollbtn{display:flex;align-items:baseline;gap:9px;padding:6px 11px;background:var(--surface);
  border:1px solid var(--line);cursor:pointer;color:var(--ink)}
.rollbtn:hover:not(:disabled){background:var(--surface-2)}
.rollbtn .cost{font-family:var(--mono);font-size:12px;color:var(--brass)}
.rollnote{font-family:var(--mono);font-size:11px;color:var(--ink-3)}

/* --- M12.1: the barrel -------------------------------------------------- */
/*
 * **The shelf is the designed curve; the barrel is the floor under it.** Told
 * apart by ground and by weight rather than by a colour, for the same reason
 * assembled-versus-not is brightness and not gold-against-red: the two shelves
 * sit one above the other and the difference has to survive being read by
 * somebody who cannot tell the hues apart.
 */
.shelf.barrel { background: var(--surface-2); border-color: var(--surface-2); }
.shelf.barrel .wares { background: var(--surface-2); }
.shelf.barrel .wares:hover:not(:disabled) { background: var(--surface); }
.shelf.barrel .wares b { font-weight: 500; }
.shelf.barrel .wares .cost { color: var(--ink-3); }

/* --- M6: the figures ---------------------------------------------------- */
/* TikZ, compiled to SVG by `make art`. Flat fills and heavy outlines, so a
   figure reads at 64px on the map and again at 4x in a panel. */
.portrait {
  width: 120px; height: 120px; object-fit: contain;
  background: #101018; border: 1px solid var(--line); padding: 8px;
  order: 2; flex: none;
}
.portrait[hidden] { display: none; }
@media (max-width: 620px) { .portrait { width: 84px; height: 84px; } }

/* Pointing at an item on the board lights its card, and the board draws a ring
   round the same item — so the two always agree about what is being read. */
.made .made-item { transition: background .12s, box-shadow .12s; margin: 0 -8px;
  padding-left: 8px; padding-right: 8px; }
.made .made-item.pointed { background: color-mix(in srgb, #69cdeb 12%, transparent);
  box-shadow: inset 3px 0 0 #69cdeb; }
.made .stats li .dim { color: var(--ink-3); }
.made .stats li.dim { color: var(--ink-3); }
@media (prefers-reduced-motion: reduce) { .made .made-item { transition: none; } }

/* --- what a skill actually does ----------------------------------------- */
/* The button carries two registers and they must not be confused for one
   another: `spec` is the engine's, unthemed and with a number in it, and
   `flavour` is the world's. Monospace and full-strength ink for the first,
   italic and dimmed for the second — so the half you compare two nodes on is
   the half that reads first. */
.wares .spec {
  font-family: var(--mono); font-size: 12.5px; color: var(--ink);
  letter-spacing: -.01em;
}
.wares .flavour { font-size: 12.5px; color: var(--ink-3); font-style: italic; }
.wares:disabled .spec { color: var(--ink-2); }

/* The hover card. Pinned to the viewport by script, so nothing here positions
   it — this only says what it looks like once it is somewhere. */
#node-detail {
  position: fixed; z-index: 20; max-width: 34ch;
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 6px 24px rgb(0 0 0 / .28);
  padding: 12px 14px; display: grid; gap: 7px;
  font-size: 13px; line-height: 1.45; color: var(--ink-2);
  pointer-events: none;   /* it must never eat the pointer that summoned it */
}
#node-detail[hidden] { display: none; }
#node-detail b { font-size: 14px; color: var(--ink); }
#node-detail p { margin: 0; }
#node-detail .spec { font-family: var(--mono); font-size: 12.5px; color: var(--ink); }
#node-detail .flavour { font-style: italic; color: var(--ink-3); }
#node-detail .cost { font-family: var(--mono); font-size: 12px; color: var(--brass); }

/* --- the other side's board --------------------------------------------- */
/* Two tabs over one panel: your gear and its gear, in the same cards, because
   the question this screen asks is which of the two boards is better packed. */
.made .tabs { display: flex; gap: 2px; margin-bottom: 12px; }
.made .tabs button {
  flex: 1; font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  padding: 7px 8px; border: 1px solid var(--line); background: transparent;
  color: var(--ink-3); border-radius: 0; text-align: center;
}
.made .tabs button.on {
  background: var(--surface-2); color: var(--ink);
  border-color: var(--brass); box-shadow: inset 0 -2px 0 var(--brass);
}
.made .tabs button:hover:not(.on) { color: var(--ink-2); }

#panel-theirs > h4 { margin: 0 0 8px; font-size: 15px; color: var(--ink);
  font-family: inherit; letter-spacing: 0; text-transform: none; }
#panel-theirs .portrait.wide {
  width: 100%; height: 132px; margin-bottom: 10px; order: 0;
}
#panel-theirs > .stats { list-style: none; margin: 0 0 12px; padding: 0;
  font-size: 13px; line-height: 1.55; }
#panel-theirs > .stats li { position: relative; padding-left: 11px; }
#panel-theirs > .stats li::before {
  content: "·"; position: absolute; left: 0; color: var(--ink-3);
}
#panel-theirs .dim { color: var(--ink-3); }
.theirs-grids { overflow-x: auto; margin: 0 0 14px; background: #101018;
  border: 1px solid var(--line); padding: 10px; }
.theirs-grids canvas { display: block; }

/* You, in the standing panel. Wider than tall because the panel is a column
   and a square portrait would push the numbers off the fold. */
.portrait.mine { width: 100%; height: 96px; margin-bottom: 10px; order: 0; }
.theirs-grids[hidden] { display: none; }

/* The class fork. A sentence rather than a spec, so it is set in the reading
   face — and it is the last thing on this screen that comes off, so it is not
   behind a hover. */
.wares .promise { font-size: 13.5px; line-height: 1.5; color: var(--ink); }
/* **Five classes, in two rows.** Three were offered and `auto-fill` left the
   row padded out with empty track; `auto-fit` collapsed it and the three shared
   the width. Four still fitted a 1280x720 viewport, measured — and `PLAN.md`
   §6a row 3 wrote down that a fifth would not, and that the fix was a two-row
   grid rather than a shorter portrait. This is that: a floor of three tracks,
   so five wrap to three and two rather than shrinking to five slivers, and a
   card that is still tall enough to read the promise on.
   `check_the_fork_is_on_top` measures the result with `elementFromPoint`. */
#fork-choices {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  max-width: 1000px;
  gap: 10px;
}
/* Three across and two down at 1280x720, which is the viewport the gate
   measures. Under nine hundred the third column is more than a card can be
   read at, so it goes to two across and three down and the screen scrolls —
   which is the ordinary phone case and is not what §6a row 3 was about. */
@media (max-width: 900px) {
  #fork-choices { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* The fork's own cards are shorter than a shelf's: there are five of them and
   they have to fit without the promise falling below the fold. */
/* The fork's own header is shorter than a screen's: five cards in two rows and
   a paragraph explaining the choice do not both fit 720px otherwise, and the
   cards are the part somebody is reading. */
#fork { padding-top: 14px; padding-bottom: 14px; }
#fork .fighthead { margin-bottom: 8px; }
#fork .fighthead .note { font-size: 13px; margin-top: 4px; }
#fork-choices .wares { padding: 9px 12px 11px; gap: 3px; }
#fork-choices .wares .promise { font-size: 12.5px; line-height: 1.42; }
#fork-choices .wares .flavour { font-size: 11.5px; line-height: 1.35; }

/* A shelf entry already bought, and an errand already run. The gap is the
   memory of what you took — a fixed shelf that silently closed up would look
   like a shelf that had never had it. */
.wares.sold { opacity: .45; }
.wares.sold .cost { color: var(--ink-3); font-style: italic; }
/* An errand is a paragraph, not a card in a rack: it takes the row. */
.wares.errand { grid-column: 1 / -1; }
.wares.errand .flavour { margin-top: 2px; }
.wares.errand.ready { outline: 2px solid var(--brass); outline-offset: -2px; }
/* The errand the map is pointing at. **Not `.pinned`**, which already means
   "taken" on a skill node and "can be handed in" a line above — two states
   wearing one mark is a list you cannot read. A bar down the edge, so a pinned
   errand that is also ready still shows both. */
.wares.errand.pin { box-shadow: inset 5px 0 0 var(--brass); }
.wares.errand.pin .cost { color: var(--brass); font-weight: 600; }
/* A road that is shut. The rust the page already uses for a refusal — the
   board's "why" line is the same fact about a different thing. */
.wares.errand .why { display: block; font-size: 12px; color: var(--rust); margin-top: 3px; }

/* The class figures. Wide and short so a row of them sits without the promise
   under each one falling below the fold — shorter again since there are two
   rows of cards to fit rather than one. */
#fork-choices .portrait.wide {
  width: 100%; height: 72px; margin-bottom: 4px; order: 0; padding: 4px;
}

/* --- the replay ---------------------------------------------------------- */
#stage-replay canvas { display: block; width: 100%; }
.ticks { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; margin: 18px 0 4px; }
.tick-list { display: grid; gap: 3px; }
/* A row is name, track, number. `minmax(0,...)` on the name so a long item
   name wraps inside its column instead of pushing the track off the end. */
.tick {
  display: grid; grid-template-columns: minmax(0, 15ch) 1fr 3.5ch;
  align-items: center; gap: 8px;
  font-family: var(--mono); font-size: 11.5px; color: var(--ink-2);
  padding: 1px 4px; border-radius: 0;
}
.tick-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tick-track { display: block; height: 10px; background: var(--surface);
  border: 1px solid var(--line); }
.tick-track i { display: block; height: 100%; width: 0; background: var(--ink-3); }
.tick.ready .tick-track i { background: var(--brass); }
.tick-hit { text-align: right; color: var(--ink-3); }
/* The number beside a row is the swing it last threw, not the one it opened
   on — fury and the spin both raise it mid-fight. Lit while it is above the
   opening figure, so a board winding up says so without anybody holding two
   numbers in their head. */
.tick-hit.risen { color: var(--brass); font-weight: 600; }
.tick.has-card { cursor: help; }
.tick.has-card:hover, .tick.has-card:focus-visible {
  background: color-mix(in srgb, #69cdeb 14%, transparent); outline: none;
}
@media (max-width: 720px) { .ticks { grid-template-columns: 1fr; } }

/* The card the hovered row opens. Same treatment as the skill tree's, and for
   the same reason: it must not eat the pointer that summoned it. */
#tick-card {
  position: fixed; right: 18px; bottom: 18px; z-index: 20; width: min(38ch, 92vw);
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 6px 24px rgb(0 0 0 / .28); padding: 12px 14px;
  pointer-events: none;
}
#tick-card[hidden] { display: none; }

/* The replay panel is dark, like the board, so give the box round it the same
   edge rather than letting a light border frame a black canvas. */
#stage-replay canvas { border: 1px solid var(--line); }

/* --- a component is a shape ---------------------------------------------- */
.shape { display: block; image-rendering: pixelated; }
.ware-top { display: flex; align-items: flex-end; min-height: 46px; margin-bottom: 6px; }

/* The two floating cards: one component, and what one frame takes. Both are
   pinned to the viewport by `pinNear`, so neither is dragged around by a list
   that scrolls under it. */
#piece-card, #frame-card {
  position: fixed; z-index: 21; width: min(34ch, 92vw);
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 6px 24px rgb(0 0 0 / .28); padding: 12px 14px;
  pointer-events: none;
}
/* `.made` is a box with a background and a border and no `display` of its
   own — but these two set one, so they carry their own `[hidden]`. The rule
   that has been learned three times here. */
#piece-card[hidden], #frame-card[hidden] { display: none; }
#piece-card > b { font-size: 14.5px; }
#frame-card { width: min(40ch, 92vw); }
#frame-card h4 { margin-bottom: 8px; }
/* On its own ground rather than the panel's recessed one: there is no panel
   behind it here, and the left rule is what still says "a spec". */
#frame-card .recipe { margin: 0; background: transparent; padding: 0 0 0 9px; }

/* The two boards in the replay. Scroll rather than shrink: a grid drawn small
   enough to always fit would stop reading as the board you packed. */
/* Shrink to the board it holds. Full width left a black expanse beside a
   one-item board, which reads as something failing to draw. */
.fight-grid { overflow-x: auto; margin-bottom: 10px; background: #101018;
  border: 1px solid var(--line); padding: 8px;
  width: fit-content; max-width: 100%; }
.fight-grid canvas { display: block; }
/* The shape sits beside the name in the hover card, not above it. */
#piece-card .made-item { display: grid; gap: 2px; }

/* The character sheet on the standing panel, and the pool table under it.
   **Both named, because `.stats` is scoped to `.made` and this panel is not
   one** — the same trap `.made .tabs` set for the tree's tab strip, which
   inherited nothing and stacked. A list that wears a class for its meaning
   still has to be given its look where it stands. */
#sheet, #pools-pay { list-style: none; margin: 0 0 14px; padding: 0;
  font-size: 13px; line-height: 1.55; }
#sheet li, #pools-pay li { position: relative; padding-left: 11px; }
#sheet li::before, #pools-pay li::before {
  content: "·"; position: absolute; left: 0; color: var(--ink-3); }
#sheet li.dim { color: var(--ink-3); }
#sheet li.none, #pools-pay li.none { color: var(--ink-3); font-style: italic; }
/* The pool is the world's word and what it pays is the engine's, so the rate
   is set in the face the rest of the game sets a spec in. */
#pools-pay li { color: var(--ink-2); }
#pools-pay li b { color: var(--ink); font-weight: 600; }
/* A rule is not a stat. It moves no bar, so it gets the one visual difference
   on this list: the brass the page already uses for something earned. */
#sheet li.rule { color: var(--brass); cursor: help; }

/* --- the tree, drawn as a tree ------------------------------------------- */
/* Rows are depth: nothing on the top row asks for anything first, and every
   node below sits under the deepest thing it needs. */
.skilltree { position: relative; display: grid; gap: 30px; padding-top: 4px; }
.skilltree .tier {
  display: flex; flex-wrap: wrap; gap: 14px; justify-content: center;
  position: relative; z-index: 1;
}
.skilltree .node { width: 208px; }
/* The wires sit under the buttons and take no pointer events, or the gaps
   between nodes would swallow a hover. */
.skilltree .wires {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: visible;
}
.skilltree .wires path { fill: none; stroke: var(--line); stroke-width: 2; }
.skilltree .wires path.live { stroke: var(--brass); stroke-width: 2.5; }
.skilltree .wires path.done { stroke: var(--brass); opacity: .45; }
/* Something you can spend a point on right now. The tree is mostly locked at
   any moment, so the few open doors are what wants finding. */
.skilltree .node.open { border: 1px solid var(--brass); }

/* The tab strip was styled only as `.made .tabs`, and this one is not inside
   a `.made` panel — so it inherited nothing and the buttons stacked. */
#tree-tabs { display: flex; flex-wrap: wrap; gap: 2px; margin-bottom: 22px; }
#tree-tabs button {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  border: 1px solid var(--line); background: transparent; color: var(--ink-3);
  border-radius: 0; text-align: center;
}
#tree-tabs button.on {
  background: var(--surface-2); color: var(--ink);
  border-color: var(--brass); box-shadow: inset 0 -2px 0 var(--brass);
}
#tree-tabs button:hover:not(.on) { color: var(--ink-2); }
#tree-tabs[hidden] { display: none; }
#tree-tabs button { flex: 0 1 auto; padding: 8px 16px; display: grid; gap: 1px; }
#tree-tabs button .meta { font-size: 10px; letter-spacing: .06em; opacity: .8; }
@media (max-width: 720px) { .skilltree .node { width: 100%; } }

/* Restoratives, on the standing panel where the decision is made. */
.pack { display: grid; gap: 4px; margin-bottom: 14px; }
.pack .tin {
  display: grid; gap: 1px; text-align: left; padding: 7px 10px;
  border: 1px solid var(--line); background: transparent; border-radius: 0;
}
.pack .tin:hover:not(:disabled) { background: var(--surface-2); }
.pack .tin b { font-size: 13px; }
.pack .tin .meta { font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
