/* devdash — tokens taken from the validated reference palette.
   Dark steps are their own selected values, not an automatic flip, and are
   declared under BOTH the OS media query and the explicit theme stamp so a
   manual choice wins either way. */
:root {
  color-scheme: light;
  --plane:        #f9f9f7;
  --surface:      #fcfcfb;
  --ink:          #0b0b0b;
  --ink-2:        #52514e;
  --ink-muted:    #898781;
  --grid:         #e1e0d9;
  --axis:         #c3c2b7;
  --hairline:     rgba(11,11,11,0.10);
  --series-1:     #2a78d6;
  --series-2:     #eb6834;
  --series-3:     #1baf7a;
  --good:         #0ca30c;
  --warning:      #fab219;
  --serious:      #ec835a;
  --critical:     #d03b3b;
  --success-text: #006300;
}
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    color-scheme: dark;
    --plane: #0d0d0d; --surface: #1a1a19; --ink: #fff; --ink-2: #c3c2b7;
    --ink-muted: #898781; --grid: #2c2c2a; --axis: #383835;
    --hairline: rgba(255,255,255,0.10);
    --series-1: #3987e5; --series-2: #d95926; --series-3: #199e70;
    --success-text: #0ca30c;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --plane: #0d0d0d; --surface: #1a1a19; --ink: #fff; --ink-2: #c3c2b7;
  --ink-muted: #898781; --grid: #2c2c2a; --axis: #383835;
  --hairline: rgba(255,255,255,0.10);
  --series-1: #3987e5; --series-2: #d95926; --series-3: #199e70;
  --success-text: #0ca30c;
}

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--plane); color: var(--ink);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}
body.centred { display: grid; place-items: center; min-height: 100vh; }
.card {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: 10px; padding: 28px 32px; max-width: 34rem;
}
h1 { font-size: 1.35rem; margin: 0 0 .5rem; letter-spacing: -0.01em; }
p { color: var(--ink-2); margin: .4rem 0; }
.muted { color: var(--ink-muted); font-size: .85rem; }
a.btn {
  display: inline-block; margin-top: 1rem; padding: .6rem 1.1rem;
  background: var(--series-1); color: #fff; text-decoration: none;
  border-radius: 7px; font-weight: 600;
}

/* --- chrome ---
   The header and the navigation stick TOGETHER, as one block. The navigation
   used to stick on its own at a hard-coded 49px, under a header measured at
   60px tall on a desktop and 101px on a phone, so scrolling slid its top under
   the header. That cost only padding while it was one row of tabs; it would
   cut the group headings in half.
   Nothing sticks on a narrow or short screen: header plus two rows is about a
   fifth of a phone, held there permanently. */
.chrome { position: sticky; top: 0; z-index: 5; }
@media (max-width: 700px), (max-height: 500px) { .chrome { position: static; } }
header.bar {
  display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap;
  padding: 14px 22px; border-bottom: 1px solid var(--hairline);
  background: var(--surface);
}
header.bar h1 { margin: 0; font-size: 1.05rem; }
header.bar .spacer { flex: 1; }
header.bar .meta { color: var(--ink-muted); font-size: .82rem; }
header.bar button, header.bar a.plain {
  background: none; border: 1px solid var(--hairline); color: var(--ink-2);
  border-radius: 6px; padding: .3rem .7rem; font: inherit; font-size: .82rem;
  cursor: pointer; text-decoration: none;
}

main { padding: 22px; display: grid; gap: 18px;
       grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
section.panel {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: 10px; padding: 18px 20px; min-width: 0;
}
section.panel.wide { grid-column: 1 / -1; }
section.panel > h2 {
  margin: 0 0 .9rem; font-size: .78rem; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-muted);
}

/* --- stat tiles: label, value, then a recessive sub-line --- */
.tiles { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
.tile .label { font-size: .75rem; color: var(--ink-muted); }
.tile .value { font-size: 1.7rem; font-weight: 600; letter-spacing: -0.02em; line-height: 1.15; }
.tile .sub   { font-size: .78rem; color: var(--ink-2); }
/* A meter is a magnitude, so it gets one hue and a track — never a rainbow. */
.meter { height: 5px; border-radius: 3px; background: var(--grid); margin-top: 6px; overflow: hidden; }
.meter > i { display: block; height: 100%; background: var(--series-1); border-radius: 3px; }

/* --- status: colour NEVER carries the meaning alone; the dot ships with text --- */
.state { display: inline-flex; align-items: center; gap: .4rem; font-size: .85rem; }
.state::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%;
  background: var(--ink-muted); flex: none;
}
.state.good::before     { background: var(--good); }
.state.warning::before  { background: var(--warning); }
.state.serious::before  { background: var(--serious); }
.state.critical::before { background: var(--critical); }
.state.good     { color: var(--success-text); }
.state.critical { color: var(--critical); }
/* "I could not read this" is its own outcome and must not look like either
   healthy or broken. A hollow ring rather than a filled dot: the reading is
   carried by the shape and the word beside it, never by colour alone. */
.state.unknown::before {
  background: transparent;
  box-shadow: inset 0 0 0 2px var(--ink-muted);
}
.state.unknown { color: var(--ink-muted); }

table { width: 100%; border-collapse: collapse; font-size: .85rem; }
th {
  text-align: left; font-weight: 600; color: var(--ink-muted);
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  padding: 0 .6rem .4rem 0; border-bottom: 1px solid var(--grid);
}
td { padding: .42rem .6rem .42rem 0; border-bottom: 1px solid var(--grid); vertical-align: top; }
tr:last-child td { border-bottom: 0; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.name { font-weight: 500; }

/* --- horizontal bars: one hue, every bar directly labelled, so identity is
       never colour-alone and the sub-3:1 contrast relief is satisfied --- */
.bars { display: grid; gap: 7px; }
/* The value column is a FIXED width, not `auto`. With `auto` the column took
   its width from the widest number in each ROW, so `263` left a wider track
   than `98,641` and the grey ends stepped in and out down the chart. A bar
   chart whose baseline-opposite end wanders is one you cannot read lengths
   off at a glance, which is the entire job of the form.
   5rem holds `1,234,567` in tabular figures with room to spare. */
.bar-row { display: grid; grid-template-columns: 8.5rem 1fr 5rem; gap: .7rem; align-items: center; }
.bar-row .lang { font-size: .82rem; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-row .track { background: var(--grid); border-radius: 0 4px 4px 0; height: 14px; }
.bar-row .track > i {
  display: block; height: 100%; background: var(--series-1);
  border-radius: 0 4px 4px 0; min-width: 2px;
}
.bar-row .n { font-size: .8rem; color: var(--ink-2); font-variant-numeric: tabular-nums;
              text-align: right; }

.chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.chip {
  border: 1px solid var(--hairline); border-radius: 999px;
  padding: .18rem .6rem; font-size: .78rem; color: var(--ink-2);
}
.err {
  border-left: 3px solid var(--critical); background: color-mix(in srgb, var(--critical) 7%, transparent);
  padding: .5rem .7rem; border-radius: 0 6px 6px 0; font-size: .84rem; color: var(--ink-2);
}
.note {
  border-left: 3px solid var(--warning); background: color-mix(in srgb, var(--warning) 10%, transparent);
  padding: .5rem .7rem; border-radius: 0 6px 6px 0; font-size: .84rem; color: var(--ink-2);
}
/* The Builds tab's always-visible answer to "what is the difference between
   the lanes?" - prose, so it is held to a readable measure. */
.explain { max-width: 70ch; font-size: .88rem; }
.lock-line { font-size: .9rem; }
/* There is deliberately no height cap on a card. A panel renders at the
   height its content needs and the PAGE scrolls; a scrollbar inside a card
   hides content on a screen that had room to show it, and hides it in a
   place the reader has to discover. */
.stale { opacity: .45; transition: opacity .2s; }

/* Inline style attributes are blocked by the CSP (style-src self, no
   unsafe-inline), so spacing that used to ride on style="margin-top" lives
   here instead. */
section.panel h2.sep { margin-top: 1.2rem; }

/* --- tab navigation ------------------------------------------------------
   Two rows: the group headings, then the tabs of the active group. Real
   anchors with real hrefs, so middle-click and "open in new tab" behave. The
   active heading is marked by an outlined pill, the active tab by an underline,
   and both by aria-current, never by colour alone.

   Both rows WRAP rather than scroll sideways. A strip that scrolls hides its
   last tabs in a place nobody looks: at 390px wide the old single strip was
   880px long, and six of its ten tabs, Codebase onwards, were off the screen. */
nav.tabs {
  padding: 0 22px;
  background: var(--surface); border-bottom: 1px solid var(--hairline);
}
nav.tabs .groups, nav.tabs .pages { display: flex; flex-wrap: wrap; gap: .15rem; }
/* The headings take the section-heading type used on every panel (small,
   uppercase, spaced), so they read as the names of groups and the row under
   them as the pages. */
nav.tabs .groups { gap: .3rem; padding-top: .5rem; }
nav.tabs .group {
  padding: .2rem .7rem; border: 1px solid transparent; border-radius: 999px;
  font-size: .74rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-muted); text-decoration: none; white-space: nowrap;
}
nav.tabs .group:hover { color: var(--ink); }
nav.tabs .group.on {
  color: var(--ink); border-color: var(--series-1);
  background: color-mix(in srgb, var(--series-1) 10%, transparent);
}
nav.tabs a:focus-visible { outline: 2px solid var(--series-1); outline-offset: -2px; }
nav.tabs .tab {
  padding: .7rem .95rem; font-size: .88rem; color: var(--ink-2);
  text-decoration: none; white-space: nowrap;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
nav.tabs .tab:hover { color: var(--ink); background: color-mix(in srgb, var(--ink) 4%, transparent); }
nav.tabs .tab.on { color: var(--ink); border-bottom-color: var(--series-1); font-weight: 600; }
/* Measured at 390px wide: Monitor's five tabs take 410px at the padding above
   and wrapped onto a second line. At half the side padding they take 347px of
   the 362px there is. A longer row still wraps rather than overflowing. */
@media (max-width: 700px) {
  nav.tabs { padding: 0 14px; }
  nav.tabs .tab { padding: .7rem .5rem; }
  nav.tabs .group { padding: .35rem .8rem; }
}
main { padding: 22px; display: block; }
main > section.panel { max-width: 1100px; margin: 0 auto; }

/* --- dependency popovers -------------------------------------------------
   Hover reveals, click pins. Colour follows the owner's mapping (red blocked,
   yellow in-progress, grey ready) with green added for done — but every chip
   also spells the status out, so hue is never the only carrier. */
.dep-cell { white-space: nowrap; }
.deps { position: relative; display: inline-block; cursor: pointer; }
.deps:focus-visible { outline: 2px solid var(--series-1); outline-offset: 2px; border-radius: 4px; }
.deps-n {
  display: inline-block; min-width: 1.5rem; text-align: center;
  font-variant-numeric: tabular-nums; font-size: .8rem;
  border: 1px solid var(--hairline); border-radius: 999px; padding: .05rem .4rem;
  color: var(--ink-2); background: var(--plane);
}
.deps:hover .deps-n, .deps.open .deps-n { border-color: var(--series-1); color: var(--ink); }
.deps-none { color: var(--ink-muted); }
.deps .pop {
  display: none; position: absolute; z-index: 20; left: 0; top: calc(100% + 6px);
  min-width: 13rem; max-width: 22rem; padding: .6rem .7rem;
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.18);
  white-space: normal;
}
.deps:hover .pop, .deps.open .pop { display: block; }
.deps .pop b {
  display: block; font-size: .7rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-muted); margin-bottom: .4rem; font-weight: 600;
}
.dep {
  display: inline-flex; align-items: baseline; gap: .35rem;
  border: 1px solid var(--hairline); border-left-width: 3px;
  border-radius: 4px; padding: .12rem .4rem; margin: 0 .25rem .25rem 0;
  font-size: .78rem; color: var(--ink);
}
.dep > i { font-style: normal; font-size: .68rem; color: var(--ink-muted); }
.dep.good     { border-left-color: var(--good); }
.dep.warning  { border-left-color: var(--warning); }
.dep.critical { border-left-color: var(--critical); }
.dep.idle     { border-left-color: var(--axis); }
/* A dependency naming a task that has no file yet: dashed, so it does not read
   as "ready" in colour or in greyscale. */
.dep.unknown { border-left-style: dashed; border-left-color: var(--ink-muted); }

/* --- progress bars -------------------------------------------------------
   Part-to-whole, so one stacked bar rather than several. Segments are separated
   by a 2px rule in the surface colour drawn INSIDE the segment (border-box), so
   the separation never distorts the proportion it is separating. */
.progs { display: grid; gap: 14px; margin: 0 0 1.2rem; }
.prog-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.prog-label { font-size: .85rem; color: var(--ink); font-weight: 500; }
.prog-pct { font-size: .85rem; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.prog-track {
  display: flex; height: 10px; margin-top: 5px;
  background: var(--grid); border-radius: 5px; overflow: hidden;
}
.prog-track .seg { display: block; height: 100%; box-sizing: border-box; min-width: 2px; }
.prog-track .seg + .seg { border-left: 2px solid var(--surface); }
.seg.good { background: var(--good); }
.seg.warning { background: var(--warning); }
.seg.critical { background: var(--critical); }
.seg.idle { background: var(--axis); }
.prog-legend { display: flex; flex-wrap: wrap; gap: .5rem 1rem; margin-top: .45rem; }
.prog-legend .key { display: inline-flex; align-items: center; gap: .35rem; font-size: .76rem; color: var(--ink-2); }
.prog-legend .key::before { content: ""; width: 8px; height: 8px; border-radius: 2px; background: var(--axis); }
.prog-legend .key.good::before { background: var(--good); }
.prog-legend .key.warning::before { background: var(--warning); }
.prog-legend .key.critical::before { background: var(--critical); }

/* --- task links and the task page --------------------------------------- */
a.task-link, a.dep-link {
  color: var(--series-1); text-decoration: none; font-weight: 600;
}
a.task-link:hover, a.dep-link:hover { text-decoration: underline; }
a.dep-link {
  display: inline-block; border: 1px solid var(--hairline); border-radius: 999px;
  padding: .18rem .6rem; font-size: .78rem; font-weight: 500; margin: 0 .25rem .25rem 0;
}

/* Rendered task markdown. Constrained measure, because these files are long
   prose and a full-width line is unreadable. */
.md { max-width: 62rem; }
.md h2, .md h3, .md h4, .md h5, .md h6 {
  margin: 1.4rem 0 .5rem; line-height: 1.3; color: var(--ink);
  font-size: 1.05rem; text-transform: none; letter-spacing: 0;
}
.md h2 { font-size: 1.2rem; border-bottom: 1px solid var(--grid); padding-bottom: .3rem; }
.md p { margin: .6rem 0; color: var(--ink-2); }
.md ul, .md ol { margin: .6rem 0; padding-left: 1.4rem; color: var(--ink-2); }
.md li { margin: .25rem 0; }
.md code {
  background: var(--plane); border: 1px solid var(--hairline); border-radius: 4px;
  padding: .05rem .3rem; font-size: .85em;
}
.md pre {
  background: var(--plane); border: 1px solid var(--hairline); border-radius: 7px;
  padding: .8rem; overflow-x: auto;
}
.md pre code { background: none; border: 0; padding: 0; }
.md blockquote {
  margin: .7rem 0; padding: .4rem .8rem; border-left: 3px solid var(--series-1);
  background: color-mix(in srgb, var(--series-1) 6%, transparent);
  border-radius: 0 6px 6px 0; color: var(--ink-2);
}
.md table { margin: .8rem 0; }
.md hr { border: 0; border-top: 1px solid var(--grid); margin: 1.2rem 0; }
.md a { color: var(--series-1); }
/* Images in a repository document: the Workflows page is drawn mostly in them.
   Each is a link to itself, so a phone can open it full size. The link is an
   inline-block so it shrinks to the image, and the image to the panel. */
.md a.md-img-link { display: inline-block; max-width: 100%; margin: .8rem 0 .3rem; }
.md img.md-img { display: block; max-width: 100%; height: auto; }
/* A heading reached by a link must not land under the sticky header and its two
   rows of navigation. app.js measures that block into --chrome-h (0 where it
   does not stick, on a narrow or short screen); the fallback is its measured
   desktop height with room to spare. It was a fixed 4.5rem until 2026-09-23,
   which cleared the header but not the tab strip below it on a desktop. */
.md [id] { scroll-margin-top: calc(var(--chrome-h, 8.5rem) + .75rem); }
/* The Workflows page names long paths in code spans and links; let them break
   rather than push the page wider than a phone. Only those: letting every word
   break made a table's first column read "You, the own er". */
.md.wf code, .md.wf a { overflow-wrap: anywhere; }
.path a.doc-link { color: var(--series-1); }
@media (max-width: 600px) {
  /* On a phone the panel's padding costs a diagram about a tenth of its width,
     and its smallest text is about nine pixels (10.5 units shown at 0.863,
     measured at 390px wide), so a diagram may spend the padding. */
  .md.wf a.md-img-link { margin-left: -12px; margin-right: -12px; max-width: calc(100% + 24px); }
}
/* Work that is named but has no task file. Given a texture rather than another
   colour: it reads as distinct from `ready` even in greyscale, in forced-colours
   mode, and for a reader who cannot separate the two greys. */
.seg.unknown {
  background: repeating-linear-gradient(45deg,
    var(--axis), var(--axis) 3px, var(--grid) 3px, var(--grid) 6px);
}
.prog-legend .key.unknown::before {
  background: repeating-linear-gradient(45deg,
    var(--axis), var(--axis) 2px, var(--grid) 2px, var(--grid) 4px);
}
/* A phase with nothing written yet: the track is drawn empty rather than the row
   being omitted, so "no tasks defined" is visible as a fact instead of as a gap. */
.prog.empty .prog-track { opacity: .45; }
.prog.empty .prog-label { color: var(--ink-2); }
/* A compact share bar inside a table cell. */
.track.mini { display: block; width: 7rem; height: 8px; background: var(--grid); border-radius: 0 4px 4px 0; }
.track.mini > i { display: block; height: 100%; background: var(--series-1); border-radius: 0 4px 4px 0; min-width: 2px; }
.chips.sep-top { margin-top: .6rem; }

/* Completion dates on done tasks. `.approx` marks a date whose basis is
   `created-done` — the task file's first commit already said done, so the date is
   when the work was written up, not when it finished. Marking it is the whole
   point: rendering it as a plain date would invent a measurement. The marker is a
   word rather than a colour so it survives greyscale and screen readers. */
td.when { white-space: nowrap; }
td.when .approx { color: var(--muted, #8b949e); }
td.when .approx i {
  font-style: normal;
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-left: 0.4em;
  opacity: 0.8;
}

/* --- what a running build is doing, and when it might finish --- */

/* Two columns that carry sentences rather than numbers, so they need a width
   floor that stops "running determinism-analyzer" wrapping one word per line,
   and a ceiling that stops them crowding the lane and job columns out. */
td.doing { min-width: 13rem; max-width: 22rem; font-size: .82rem; line-height: 1.45; }
td.doing b { font-weight: 600; }
td.doing .muted { display: block; margin-top: .15rem; }

/* A build that has already failed a check while still grinding through twenty
   more minutes is the most useful thing this panel says. It is marked with a
   word and a dot, never with colour alone. */
.fail-note { margin-top: .3rem; }

/* THE SPLIT METER IS THE WEIGHTED SHARE, NOT done/planned. The solid part is
   work definitely behind the run; the lighter part is the check currently in
   flight, whose share is bounded but not known. Drawing them as one bar would
   claim a precision that does not exist - and inside a twenty-minute MSBuild
   check that gap is most of the bar.

   BOTH WIDTHS ARRIVE VIA data-width AND applyWidths(). The Caddy CSP is
   `style-src 'self'` with no 'unsafe-inline', so a style="" attribute is
   dropped silently and every bar renders full width beside a correct number -
   which is exactly what happened on 2026-09-20. */
.meter.split { display: flex; margin-top: .35rem; }
.meter.split > i { flex: 0 0 auto; border-radius: 3px 0 0 3px; }
.meter.split > u {
  display: block; height: 100%; flex: 0 0 auto; text-decoration: none;
  background: var(--series-1); opacity: .35; border-radius: 0 3px 3px 0;
}

/* Coverage: the uncovered-branch line numbers. These are the actionable part of
   that panel — `98 1/2` is one arm of one `if` that no test takes — so they are
   packed tight and tabular rather than given a row each. The chip styling is
   reused rather than reinvented; only the density changes. */
td.cov-lines { line-height: 1.9; }
td.cov-lines .chip {
  font-variant-numeric: tabular-nums; font-size: .72rem;
  padding: .05rem .35rem; margin: 0 .15rem .15rem 0;
}

/* --- the library ------------------------------------------------------- */

/* A FILTER OVER THREE HUNDRED ROWS, and the reason it is a search input rather
   than a plain text box: `type="search"` gives a clear button on every browser
   and announces itself correctly to a screen reader, and neither had to be
   built. Hiding is done with a class, never with an inline style attribute —
   `style-src 'self'` drops those silently, which is the bug this dashboard
   already paid for once with progress-bar widths. */
.lib-filter {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  margin: 1rem 0 .3rem;
}
.lib-filter label {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-muted); font-weight: 600;
}
.lib-filter input {
  flex: 1 1 18rem; max-width: 30rem;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--grid); border-radius: 6px;
  padding: .4rem .6rem; font: inherit; font-size: .85rem;
}
.lib-filter input:focus-visible { outline: 2px solid var(--series-1); outline-offset: 1px; }

/* Rows past the tenth are present in the markup and hidden by a class, so
   "show all" costs no request and the filter can reach them. `.hide` beats
   `.over` because a filter hit inside a collapsed group must appear. */
.lib-rows:not(.all) tr.lib-row.over { display: none; }
tr.lib-row.hide, .mock-style.hide { display: none; }
.lib-rows .matches:empty { display: none; }
.lib-rows .matches { margin: .4rem 0 0; }
td.path {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .74rem; word-break: break-all;
}
.lib-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .74rem; color: var(--ink-2);
}
.count {
  font-size: .72rem; color: var(--ink-muted); font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.warn { color: var(--critical); }

/* One button style for every control the library adds. A real <button>, not a
   styled link: these change what is on the screen and navigate nowhere, so a
   keyboard and a screen reader should be told that. */
button.more, button.chip-btn {
  background: var(--surface); color: var(--ink-2);
  border: 1px solid var(--grid); border-radius: 6px;
  padding: .28rem .7rem; font: inherit; font-size: .78rem; cursor: pointer;
}
button.more { margin-top: .5rem; }
button.more:hover, button.chip-btn:hover { border-color: var(--axis); color: var(--ink); }
button.more:focus-visible, button.chip-btn:focus-visible {
  outline: 2px solid var(--series-1); outline-offset: 1px;
}
.chips button.chip-btn { border-radius: 999px; }

/* THE CONTACT SHEET. Styles are cards in a grid rather than rows in a table,
   because the thing a reader is looking for here is a LOOK, and a filename is
   a poor substitute for one. Where a screenshot exists it leads; where none
   exists the card says so in words rather than leaving a grey hole that reads
   as a failed image. */
.mock-set { margin: 1rem 0 1.6rem; }
.mock-set h4 { margin: 0 0 .5rem; font-size: .95rem; font-weight: 600; }
.mock-set h4 .muted { font-weight: 400; }
.mock-styles {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
}
.mock-style {
  border: 1px solid var(--grid); border-radius: 8px; padding: .6rem;
  background: var(--surface); display: flex; flex-direction: column; gap: .35rem;
}
/* An empty style card sizes to its own sentence instead of stretching to the
   height of the row. Grid stretches by default, which turned "no files yet"
   into a half-metre of dashed outline beside three real cards. */
.mock-style.empty { opacity: .72; border-style: dashed; align-self: start; }
.mock-style .mock-name { font-weight: 600; font-size: .85rem; }
.mock-style p { margin: 0; font-size: .78rem; line-height: 1.45; }
img.thumb {
  width: 100%; aspect-ratio: 16 / 10; object-fit: cover; object-position: top left;
  border-radius: 5px; border: 1px solid var(--grid); background: var(--plane);
  display: block;
}
.thumb.none {
  width: 100%; aspect-ratio: 16 / 10; border-radius: 5px;
  border: 1px dashed var(--grid); background: var(--plane);
  display: flex; align-items: center; justify-content: center;
  text-align: center; padding: .5rem;
  color: var(--ink-muted); font-size: .74rem;
}
.mock-style .screens { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: auto; }
a.screen {
  border: 1px solid var(--hairline); border-radius: 999px;
  padding: .12rem .55rem; font-size: .75rem; color: var(--series-1);
  text-decoration: none;
}
a.screen:hover { border-color: var(--series-1); }
/* A file written in the last five minutes. Another agent writes into the
   mockups tree while this page reads it, and half a document looks exactly
   like a finished one until you know. */
.fresh { color: var(--series-2); font-weight: 700; }

/* THE REFERENCE GAMES. Four cards built from the specification rather than from
   a directory, because three of the four have no code yet and a directory
   listing would show one throwaway game and call it the reference suite. */
/* `align-items: start` is load-bearing, not tidiness. Grid rows stretch every
   card to the tallest in the row, so Reference C's table left an empty half
   metre under Reference A and the section looked broken. */
.ref-cards {
  display: grid; gap: 16px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
}
.ref-card {
  border: 1px solid var(--grid); border-radius: 8px; padding: .8rem 1rem;
  background: var(--surface);
}
.ref-card h4 { margin: 0 0 .4rem; font-size: .95rem; }
.ref-card h4 a { margin-left: .4rem; font-size: .78rem; font-weight: 400; }
.ref-card > p { font-size: .82rem; color: var(--ink-2); margin: .3rem 0 .6rem; }
table.props td { font-size: .78rem; line-height: 1.45; }
/* THE GAMES TAB: one card per game package, its design documents and its
   mockup sets' screenshots as a grid of thumbnails. */
.game-card { margin: 0 0 1rem; }
.game-card h3 { margin: 0 0 .4rem; font-size: 1rem; }
.game-card h3 .muted { font-weight: 400; font-size: .85rem; }
.game-shots {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
table.props td.name { width: 9rem; color: var(--ink-muted); font-weight: 600; }
.ref-work { margin-top: .7rem; border-top: 1px solid var(--grid); padding-top: .5rem; }
.ref-work p { font-size: .8rem; margin: .35rem 0; color: var(--ink-2); }

/* A document's front matter, shown as what it is — metadata — instead of being
   handed to the Markdown renderer, which would turn `---` into a rule and
   `title:` into a sentence. */
table.front { max-width: 46rem; margin-bottom: 1rem; }
table.front td.name { width: 9rem; color: var(--ink-muted); font-size: .78rem; }
table.front td { font-size: .8rem; }

.sec-nav { display: flex; flex-wrap: wrap; gap: .3rem; margin: .6rem 0 1rem; }
.sec-nav .sec {
  border: 1px solid var(--hairline); border-radius: 5px;
  padding: .15rem .5rem; font-size: .75rem; color: var(--ink-2);
  text-decoration: none;
}
.sec-nav a.sec { color: var(--series-1); }
.sec-nav a.sec.on { border-color: var(--series-1); color: var(--ink); font-weight: 600; }
.sec-nav .level2 { opacity: .8; }
details.toc summary { cursor: pointer; font-size: .8rem; color: var(--ink-2); }

.doc-toggle { display: flex; align-items: baseline; gap: .8rem; flex-wrap: wrap; margin: .8rem 0; }
a.doc-link.toggle {
  background: var(--surface); border: 1px solid var(--grid); border-radius: 6px;
  padding: .28rem .7rem; font-size: .78rem; text-decoration: none;
  color: var(--ink-2); white-space: nowrap;
}
a.doc-link.toggle:hover { border-color: var(--axis); color: var(--ink); }
.doc-toggle .muted { flex: 1 1 22rem; }
/* The source view is the honest escape hatch: the file exactly as it is on
   disk, escaped, so the reader can always check what the small renderer did
   with it. It wraps rather than scrolls sideways — a horizontal scrollbar in a
   document view hides text in a place nobody looks. */
pre.doc-src {
  background: var(--plane); border: 1px solid var(--grid); border-radius: 6px;
  padding: .8rem; overflow-x: auto; max-width: 62rem;
  font-size: .76rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
}

/* A CSV shown as a table (Library document page). Cells WRAP rather than
   scroll: the owner asked for no scroll bars inside cards, and the game
   matrix's concept column runs to two thousand characters. The first column
   stays narrow enough to read as a row label. */
.csv-wrap table { table-layout: auto; }
.csv-wrap td { white-space: normal; overflow-wrap: anywhere; font-size: .8rem; line-height: 1.45; }
.csv-wrap td:first-child { font-weight: 500; min-width: 8rem; }

/* Waiting on you: one block per item of the owner's kept list. */
.wait-item { border-top: 1px solid var(--grid); padding: .6rem 0 .2rem; }
.wait-item h3 { margin: 0 0 .3rem; font-size: .95rem; font-weight: 600; }
.wait-item .md { overflow-wrap: anywhere; }
details.wait-how { margin-top: 1.2rem; }
details.wait-how summary { cursor: pointer; font-size: .8rem; color: var(--ink-2); }
