/* Kinetic Melbourne OCC — developer handbook.
 *
 * Copied tokens, not imported ones. `frontend/src/style.css` is bundled by Vite
 * into the application's hashed CSS; the handbook is 136 static pages generated
 * outside that build and served straight from `dist/handbook/`, so it cannot
 * reference the app's stylesheet by a name that changes every build. The values
 * below are lifted from `style.css`'s `:root` and MUST be kept in step with it —
 * the point of copying them is that the handbook reads as part of the product
 * rather than as a bolted-on site, and a drifted `--accent` is exactly what
 * breaks that impression.
 *
 * The app itself is light-only: `style.css` has no `prefers-color-scheme` block
 * and no theme attribute (checked 2026-08-11). The handbook adds a dark palette
 * of its own because it is a long-form reading surface rather than a map, and
 * the reader's OS setting is the only signal available to a static page. The
 * light half stays byte-identical to the app's tokens, so nothing here becomes a
 * second opinion about what `--accent` means. */

:root {
  --bg: #ffffff;
  --panel: #ffffff;
  --panel-2: #f6f7f9;
  --border: #e3e6ea;
  --border-strong: #7f8894;
  --text: #1a1f26;
  --muted: #5f6773;
  --accent: #1565c0;
  --accent-soft: #e8f0fe;
  --brand: #0075C9;
  --radius: 12px;
  --topbar: 48px;

  --code-bg: #f2f4f7;
  --code-fg: #1a1f26;
  --nav-w: 264px;
  --onpage-w: 216px;
  --shadow: 0 6px 24px rgba(20, 30, 45, 0.14);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #12161c;
    --panel: #171c24;
    --panel-2: #1e242e;
    --border: #2b3340;
    --border-strong: #6b7686;
    --text: #e6eaf0;
    --muted: #9aa4b2;
    /* Lifted from #1565c0, which is 2.9:1 on this background. The relationship
       the app's token encodes — "accent is the interactive blue" — survives; the
       exact hex cannot, because it was measured against white. */
    --accent: #6aa9f0;
    --accent-soft: #1c2b3f;
    --brand: #4aa8e8;
    --code-bg: #10151b;
    --code-fg: #dfe5ec;
    --shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 15px;
  line-height: 1.6;
  /* The three columns each scroll on their own; the PAGE never scrolls
     sideways. Every wide thing inside the document — tables, fences, mermaid —
     is wrapped in its own scroller for the same reason. */
  overflow-x: hidden;
}

.hb-sr {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.hb-skip {
  position: absolute; left: -9999px; top: 0; z-index: 20;
  background: var(--panel); color: var(--accent);
  padding: 10px 14px; border: 1px solid var(--border-strong); border-radius: 0 0 var(--radius) 0;
}
.hb-skip:focus { left: 0; }

/* ---- masthead ---- */
.hb-top {
  position: sticky; top: 0; z-index: 10;
  height: var(--topbar);
  display: flex; align-items: center; gap: 14px;
  padding: 0 16px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}
.hb-brand {
  display: flex; align-items: center; gap: 8px;
  color: var(--text); text-decoration: none;
  font-weight: 700; letter-spacing: -0.01em; white-space: nowrap;
}
/* `mark.png`, not `favicon.png`: the favicon's slash occupies 17x27 of a 32x32
   padded canvas, so it draws as a stray artifact inside a 16px box. The 11x18
   keeps the mark's own 17:27 aspect — setting both to one number squashes it. */
.hb-brand img { display: block; width: 11px; height: 18px; }
.hb-masthead {
  color: var(--muted); text-decoration: none; font-size: 13px; white-space: nowrap;
  padding-left: 14px; border-left: 1px solid var(--border);
}
.hb-masthead:hover { color: var(--accent); }
.hb-search { margin-left: auto; }
.hb-search input {
  font-family: inherit; font-size: 13px;
  width: 240px; max-width: 42vw; padding: 6px 10px;
  color: var(--text); background: var(--bg);
  /* `--border-strong`, not `--border`: this is the outline of a CONTROL, which
     WCAG 1.4.11 asks 3:1 of. The app makes the same distinction. */
  border: 1px solid var(--border-strong); border-radius: 8px;
}
.hb-search input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- three columns ---- */
.hb-shell {
  display: grid;
  grid-template-columns: var(--nav-w) minmax(0, 1fr) var(--onpage-w);
  align-items: start;
  gap: 0;
}
.hb-nav, .hb-onpage {
  position: sticky; top: var(--topbar);
  max-height: calc(100vh - var(--topbar));
  overflow-y: auto;
  padding: 18px 14px 40px;
  font-size: 13px;
}
.hb-nav { border-right: 1px solid var(--border); }
.hb-onpage { border-left: 1px solid var(--border); }

.hb-nav-section > summary {
  cursor: pointer; list-style: none;
  padding: 5px 6px; border-radius: 6px;
  font-weight: 700; color: var(--text);
}
.hb-nav-section > summary::-webkit-details-marker { display: none; }
.hb-nav-section > summary::before {
  content: "\25B8"; display: inline-block; width: 1em;
  color: var(--muted); transition: transform .12s;
}
.hb-nav-section[open] > summary::before { transform: rotate(90deg); }
.hb-nav-section > summary:hover { background: var(--panel-2); }
.hb-nav-section .hb-nav-section { margin-left: 12px; }
.hb-nav-list { list-style: none; margin: 2px 0 8px; padding: 0 0 0 18px; }
.hb-nav-link {
  display: block; padding: 4px 6px; border-radius: 6px;
  color: var(--muted); text-decoration: none;
}
.hb-nav-link:hover { background: var(--panel-2); color: var(--text); }
.hb-nav-link[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent); font-weight: 600;
}
.hb-nav-index { margin-bottom: 8px; font-weight: 700; color: var(--text); }
.hb-nav-overview { font-style: italic; }

/* The "ways in" strip, drawn between the index link and the contents tree by
   `renderEntryPoints` in docsite.mjs. Only the operator manual declares entry
   points today; the handbook emits none of this markup, so these rules simply
   never match there.

   It is separated from the tree by a rule rather than by whitespace on purpose.
   The tree below it is the corpus in reading order; this is a short list of
   places to START, and a reader skimming a 264px column has to be able to see
   at a glance that the two are different kinds of thing rather than the first
   few items of one list. */
.hb-nav-ways {
  margin: 0 0 12px; padding: 0 0 10px;
  border-bottom: 1px solid var(--border);
}
.hb-nav-ways-label {
  margin: 0; padding: 0 6px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
/* No left padding on this list: the tree indents its links under a disclosure
   triangle, and these have no parent to sit under. */
.hb-nav-ways .hb-nav-list { margin: 4px 0 0; padding: 0; }
.hb-nav-way { color: var(--text); }

.hb-results { margin-top: 4px; }
.hb-result {
  display: block; padding: 8px 8px; border-radius: 8px;
  color: var(--text); text-decoration: none;
}
.hb-result:hover, .hb-result:focus-visible { background: var(--panel-2); }
.hb-result b { display: block; font-weight: 600; }
.hb-result span { display: block; color: var(--muted); font-size: 11px; }
.hb-results-empty { padding: 8px; color: var(--muted); }

/* ---- the document ---- */
.hb-main { min-width: 0; padding: 28px 40px 96px; }
.hb-doc { max-width: 80ch; }
.hb-crumbs { margin: 0 0 6px; color: var(--muted); font-size: 12px; }
.hb-crumbs span { opacity: .5; }
.hb-doc h1 { margin: 0 0 14px; font-size: 30px; line-height: 1.25; letter-spacing: -0.02em; }
.hb-doc h2 {
  margin: 40px 0 12px; font-size: 22px; letter-spacing: -0.01em;
  padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
.hb-doc h3 { margin: 28px 0 8px; font-size: 17px; }
.hb-doc h4, .hb-doc h5, .hb-doc h6 { margin: 20px 0 6px; font-size: 15px; }
.hb-doc p, .hb-doc li { overflow-wrap: break-word; }
.hb-doc a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
.hb-doc ul, .hb-doc ol { padding-left: 24px; }
.hb-doc li { margin: 4px 0; }
.hb-doc hr { border: 0; border-top: 1px solid var(--border); margin: 32px 0; }
.hb-doc blockquote {
  margin: 16px 0; padding: 2px 16px;
  border-left: 3px solid var(--border-strong); color: var(--muted);
}

/* ---- figures ----
 *
 * Only one of the two sites this stylesheet serves has any pictures: the
 * operator manual is illustrated with screenshots and the developer handbook
 * has none at all. The rule lives here anyway, because the two share one
 * stylesheet on purpose — a second copy for the manual is how a CSP fix or a
 * search fix ends up landing on one site and not the other.
 *
 * Two things about it are load-bearing.
 *
 * `max-width: 100%` is not tidying. Without an image rule of any kind — which
 * is what this file had until the manual grew figures — a 1440px screenshot
 * sits at its natural size inside a column capped at 80ch, and pushes the
 * whole page sideways: the nav, the prose and the on-page contents all scroll
 * horizontally on a laptop. It renders perfectly in a Markdown preview and
 * breaks on the published site, which is this repository's least favourite
 * shape of bug.
 *
 * The width cap on a wide window is the other half. A figure is not prose, and
 * 80ch is a measure chosen for reading sentences: an interface screenshot
 * scaled down to it is one whose labels nobody can read, which makes the
 * figure worse than no figure. So on a window with room, a figure is allowed
 * to run past the prose column and use the width the main pane actually has —
 * capped so it can never reach the nav on one side or the on-page contents on
 * the other, and never enlarged past the size it was composed at.
 *
 * "The size it was composed at" is not the same as "its own pixels" any more.
 * Since 2026-08-20 the manual's figures are captured at device scale 2, so a
 * figure stores twice the pixels in each direction that it is drawn in, and
 * `docsite.mjs` writes the drawn size onto every one of them as a `width` and
 * `height` pair (see FIGURE_SCALE there). Those attributes are what set the
 * size; the rules below only cap it. Nothing here changed when the scale did,
 * and that is the point — the figures are sharper and the layout is identical. */
.hb-doc img {
  display: block;
  height: auto;
  max-width: 100%;
  margin: 18px 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  /* The capture harness takes figures on a white page in the light theme. On
     the dark theme they sit on a dark ground, and an unbounded white rectangle
     glares; the border above and this ground behind a figure with any
     transparency in it keep the edge legible either way. */
  background: #ffffff;
}

@media (min-width: 1180px) {
  /* `max()` of the two, and the order matters. The first term is the prose
     column, so a figure is never made SMALLER than it would have been; the
     second is the width the main pane actually has, worked out from the two
     fixed side columns and the pane's own padding, so the figure can never
     reach either of them. The 1440px ceiling is the widest VIEWPORT the capture
     harness composes a figure in — its stored width is twice that at device
     scale 2 — so past 1440 a figure would be drawn larger than the app it is a
     picture of, which is a bigger picture of the same screen rather than a
     more useful one. */
  .hb-doc img {
    max-width: max(
      100%,
      min(1440px, calc(100vw - var(--nav-w) - var(--onpage-w) - 96px))
    );
  }
}

/* An inert repository path: a citation of a file that is NOT published, so it
   deliberately does not look clickable. See the link-rewriting note in
   build-handbook.mjs — the alternative is a live-looking link that resolves to
   the application's index page with a 200. */
.hb-path { color: var(--muted); }
.hb-path code { color: inherit; }

.hb-doc code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.88em;
  background: var(--code-bg); color: var(--code-fg);
  padding: 1px 5px; border-radius: 5px;
}
.hb-pre-scroll { overflow-x: auto; margin: 16px 0; border-radius: var(--radius); }
.hb-pre-scroll pre {
  margin: 0; padding: 14px 16px;
  background: var(--code-bg); color: var(--code-fg);
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: 12.5px; line-height: 1.55;
  /* No wrapping: these are commands and code, and a wrapped shell line reads as
     two commands. It scrolls in the box above instead. */
  white-space: pre;
}
.hb-pre-scroll pre code { background: none; padding: 0; font-size: inherit; }

.hb-table-scroll { overflow-x: auto; margin: 16px 0; }
.hb-table-scroll table {
  border-collapse: collapse; width: 100%;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
  font-size: 13px;
}
.hb-table-scroll th {
  text-align: left; padding: 9px 12px; background: var(--panel-2);
  color: var(--muted); font-size: 11px; text-transform: uppercase;
  letter-spacing: .03em; font-weight: 700; white-space: nowrap;
}
.hb-table-scroll td { padding: 8px 12px; border-top: 1px solid var(--border); vertical-align: top; }

/* ---- front-matter strip ---- */
/* Rendered as its own strip rather than as the blockquote it is in source: a
   quotation reads as something the page is citing, when it is in fact the
   page's own provenance — and "Last verified against" is the first thing a
   reader needs in a tree where staleness is the main failure mode. */
.hb-front {
  margin: 0 0 28px; padding: 12px 16px;
  background: var(--panel-2); border: 1px solid var(--border);
  border-left: 3px solid var(--brand); border-radius: var(--radius);
  font-size: 12.5px; line-height: 1.5;
}
.hb-front-note { margin: 0 0 8px; font-weight: 600; }
.hb-front-fields { display: grid; gap: 6px 20px; margin: 0; }
@media (min-width: 900px) { .hb-front-fields { grid-template-columns: 1fr 1fr; } }
.hb-front-fields > div { display: grid; grid-template-columns: 9.5em 1fr; gap: 8px; }
/* "Source of truth" is last on every page and is a comma-separated list of up
   to a dozen file paths; in half the width it wraps into a column of broken
   filenames. It takes the full row instead. */
.hb-front-fields > div:last-child { grid-column: 1 / -1; }
.hb-front-fields dt {
  color: var(--muted); text-transform: uppercase;
  font-size: 10.5px; letter-spacing: .04em; font-weight: 700; padding-top: 2px;
}
/* `break-word`, not `anywhere`: `anywhere` splits inside a filename, and
   `backend/diversions/disruptions.p / y` across two lines is unreadable. */
.hb-front-fields dd { margin: 0; overflow-wrap: break-word; }
.hb-front code { background: var(--code-bg); padding: 1px 5px; border-radius: 5px; font-size: 0.92em; }

/* ---- on-page contents ---- */
.hb-onpage h2 {
  margin: 0 0 8px; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted);
}
.hb-onpage ul { list-style: none; margin: 0; padding: 0; }
.hb-onpage a {
  display: block; padding: 3px 8px; border-left: 2px solid var(--border);
  color: var(--muted); text-decoration: none;
}
.hb-onpage a:hover { color: var(--text); border-left-color: var(--border-strong); }
.hb-onpage .lvl3 a { padding-left: 20px; font-size: 12px; }

/* ---- mermaid ---- */
/* Sized before the module loads so 25 pages do not jump when it does. The
   fence stays visible as text until then, which is the honest degradation if
   the module ever fails to load. */
pre.mermaid {
  margin: 20px 0; padding: 14px 16px; overflow-x: auto;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px; color: var(--muted);
}
pre.mermaid[data-processed] {
  padding: 16px; color: inherit; font-family: inherit; text-align: center;
}
pre.mermaid svg { max-width: 100%; height: auto; }

/* ---- narrow viewports ---- */
/* The on-page contents goes first: it is a duplicate of headings that are on
   the page anyway. The nav tree stays, above the document, because without it
   there is no way to reach another page. */
@media (max-width: 1180px) {
  .hb-shell { grid-template-columns: var(--nav-w) minmax(0, 1fr); }
  .hb-onpage { display: none; }
}
@media (max-width: 820px) {
  .hb-shell { grid-template-columns: minmax(0, 1fr); }
  .hb-nav {
    position: static; max-height: none; border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .hb-main { padding: 20px 18px 72px; }
  .hb-search input { width: 160px; }
}

/* PRINT. Controllers print things, and the user manual is the tree they print —
   a page taped beside a workstation is a real way this documentation gets used.
   The handbook inherits the same rules, which is free and does no harm.

   Three things beyond hiding the chrome, and each fixes a real printed page:

   1. THE COLOURS ARE FORCED BACK TO LIGHT. The tokens above flip on
      `prefers-color-scheme: dark`, and a print media query does NOT cancel a
      colour-scheme one — so a reader whose machine is in dark mode prints white
      text on a background their printer renders as either a solid black page or
      (with backgrounds off, the browser default) as nothing at all. Restating
      the light values here is the only thing that fixes it, and it is invisible
      on screen.
   2. NOTHING BREAKS ACROSS A PAGE THAT READS AS ONE THING. A heading stranded
      at the foot of a page, a figure split in half, or a table's header row
      alone on page 2 are the three that actually happen. `break-inside` is the
      modern property; `page-break-inside` is kept beside it because print
      support lags the screen engine.
   3. LINKS PRINT AS TEXT, NOT AS BLUE UNDERLINE. On paper a link is not
      clickable and the URL is a relative path into a site the reader is not
      looking at, so showing it would be noise. The words stay; the affordance
      goes. */
@media print {
  :root {
    --bg: #ffffff;
    --panel: #ffffff;
    --panel-2: #f6f7f9;
    --border: #e3e6ea;
    --border-strong: #7f8894;
    --text: #1a1f26;
    --muted: #5f6773;
    --accent: #1565c0;
    --accent-soft: #e8f0fe;
    --brand: #0075C9;
    --code-bg: #f2f4f7;
    --code-fg: #1a1f26;
    --shadow: none;
  }
  .hb-top, .hb-nav, .hb-onpage, .hb-skip { display: none; }
  .hb-shell { display: block; }
  .hb-main { padding: 0; }
  .hb-doc { max-width: none; }

  h1, h2, h3, h4 { break-after: avoid; page-break-after: avoid; }
  h1, h2, h3, h4, li, tr, pre, blockquote, figure, img, table {
    break-inside: avoid; page-break-inside: avoid;
  }
  thead { display: table-header-group; }
  /* Controllers print things. `.hb-doc` loses its measure above, so a figure
     would otherwise print at its own pixel width and run off the paper — and
     a figure split across a page break is one nobody can read either (the
     break-inside rule above covers that). */
  img { max-width: 100%; }
  a { color: inherit; text-decoration: none; }
}
