/* The free checker, on the Casient design system.
 *
 * ONE PLATE, DECIDED 2026-09-22. This file used to argue for a split: the site
 * was a document on a drawing print, this was an instrument on warm near-black,
 * and only the type was shared. Ted's call reverses it. check.casientpro.com is
 * not a second product a visitor discovers, it is the thing the landing page's
 * one button sends them to — and a person who crosses that link in half a
 * second reads a change of ground as a change of company, whatever the type is
 * doing. The handoff the old note wanted to feel like "entering the machine"
 * mostly felt like leaving the site.
 *
 * So the tokens below are the site's own, value for value.
 *
 * RE-PINNED 2026-09-23, and the reason is worth recording because it will
 * happen again. This file was aligned to casient-web/src/styles/global.css --
 * paper #E2E4DF, sheet #F5F6F3, ink #15181A, redline #A82A1E -- and that was
 * correct on the day. The site then moved: casientpro.com now drives every
 * page from src/styles/tokens.css and its ground is #FFFFFF with Archivo, not
 * the diazo print with osifont. Nothing here drifted. The target moved, and
 * this file was left as the last surface on a palette the brand had left.
 *
 * The lesson is that "value for value" is a claim with a date on it. The
 * contract test in public-check/src/ui-contract.test.ts is what makes it
 * checkable, and it is the thing that has to be updated deliberately whenever
 * the site moves again -- which is exactly what it is for.
 *
 * Current source of truth: casient-web/src/styles/tokens.css, [data-theme=paper].
 *   paper #FFFFFF  sheet #F3F5F3  band #E8EBE8
 *   ink   #14181A  grey  #596164  accent #B0281A  slate #23566B
 *
 * WHAT STAYS SPLIT, because it is about reading rather than about brand:
 *
 *   REDLINE, everywhere a person is being spoken to — a heading, a button, the
 *     accent on a finding.
 *   INSTRUMENT (drafting blue #2C5871 on the band) for readings. The site
 *     reserves the mono face and the band ground for its own evidence tables,
 *     and a report about someone's project IS an evidence table. Keeping the
 *     two apart is what stops every number on the page shouting at the same
 *     volume as the headline.
 *
 * The faces are self-hosted. The Casient site's own note says a
 * privacy-positioned brand should not phone Google on every page load, and of
 * every page we serve, this is the one that promises to delete your file.
 *
 * There is no prefers-color-scheme block. The drawing print is the art
 * direction, not a preference, exactly as near-black was before it.
 */

/* THE TWO FACES, AND WHAT EACH ONE IS NOW FOR.
 *
 * The split changed on 2026-09-23. It used to be osifont for display and
 * Literata for prose, which is what the site set at the time. The site now
 * sets Archivo for both, so keeping Literata here put a serif on the one page
 * the landing page's only button leads to -- a change of voice across a single
 * click, which is the same failure as a change of ground.
 *
 * ARCHIVO carries the SHELL: headings, navigation, buttons, prose, labels a
 * person reads as a sentence. Identical to casientpro.com, because the shell
 * is the part a visitor compares.
 *
 * OSIFONT carries the INSTRUMENT: group addresses, hold times, counts, table
 * values, anything the tool has measured. It is the open implementation of
 * ISO 3098, "Technical product documentation -- Lettering" -- the standard an
 * engineering drawing is required to use -- and at 12.5 KB subset it is the
 * most distinctive asset this product has. The site reserves IBM Plex Mono for
 * that role; here osifont does it, deliberately, and that is the one place the
 * two surfaces are ALLOWED to differ. It is what makes the checker read as an
 * instrument rather than as a second brochure.
 *
 * osifont is a LETTERING STANDARD, not a text face: monolinear, one weight. So
 * within the instrument, hierarchy comes from size, spacing and rules, never
 * from weight, which would be synthesised and would look it. Archivo is a
 * variable face and carries 100-900, so the shell takes its hierarchy from
 * weight in the normal way -- 700 for headings, matching the site.
 *
 * Literata is gone, and so is its 52 KB. Net payload change is -17.6 KB.
 */
@font-face {
  font-family: "osifont";
  font-style: normal; font-weight: 400; font-display: swap;
  src: url("/fonts/osifont.woff2") format("woff2");
}
@font-face {
  font-family: "Archivo Variable";
  font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("/fonts/archivo.woff2") format("woff2");
}

:root {
  color-scheme: light;

  /* ROLE-NAMED TOKENS. These hold the values; every historical name further
   * down (--ink, --bone, --brass ...) is an ALIAS to one of these, so a page or
   * a JS-built diagram that still says var(--brass) keeps working. New code
   * names the role. Value for value the site's paper theme (see the note above):
   *   ground #FFFFFF  sheet #F3F5F3  band #E8EBE8  text #14181A  accent #B0281A
   * FAULT IS NOT BRAND RED. The brand accent #B0281A is for actions and accents
   * ONLY (buttons, links, the current page, headings). A fault is #7A1810, a
   * deeper, browner red, and always carries a glyph and a word, never colour
   * alone:  ■ fault   ▲ review   ● note   ✓ ok. Fault items also get a 3px
   * left border. A status tag never uses the brand red.
   * Contrast (WCAG, computed 2026-10-06):
   *   fault  #7A1810  on ground #FFFFFF 10.68:1, sheet #F3F5F3 9.75:1,
   *                   band #E8EBE8 8.89:1, accent-bg #FDF1EF 9.67:1   (AAA)
   *   accent #B0281A  on ground 6.64:1, sheet 6.06:1, band 5.52:1    (AA)
   *   fault and accent are only 1.6:1 apart in luminance, so the split rests on
   *   the glyph, the word and the border, never on hue alone. */
  --c-ground:    #FFFFFF;
  --c-sheet:     #F3F5F3;
  --c-band:      #E8EBE8;
  --c-text:      #14181A;
  --c-text-2:    #2B3134;
  --c-text-3:    #464C50;
  --c-text-4:    #596164;   /* 6.3:1 on paper, 5.3:1 on band: AA for body copy */
  --c-rule:      #DDE1DE;
  --c-rule-firm: #B8BFBA;
  --c-accent:    #B0281A;
  --c-accent-ink:#8E2318;   /* hover / pressed accent */
  --c-accent-bg: #FDF1EF;
  --c-on-accent: #FFFFFF;
  --c-info:      #23566B;
  --c-ok:        #1F6F5C;
  --c-warn:      #7A5D1E;
  --c-fault:     #7A1810;

  /* Spacing, radius, motion. */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px;  --s-4: 16px;  --s-5: 24px;  --s-6: 40px;
  --r-0: 0;
  --dur-1: 120ms;
  --ease: cubic-bezier(.2, 0, 0, 1);

  /* Tap targets: 32px minimum for a pointer, 44px for a finger. Nothing below 24. */
  --hit: 32px;

  /* Ground. The site's drawing print, value for value.
   *
   * THE NAMES ARE HISTORICAL AND ARE KEPT ON PURPOSE. `--ink` meant the darkest
   * ground when the ground was near-black; it now holds the paper. Every page,
   * every inline <style> and every JS-built diagram in this tool names these
   * tokens, and renaming forty call sites to make one file read better is how
   * a diagram ends up stroking `var(--undefined)` — which computes to `none`
   * and disappears in silence. Read them as ROLES: `--ink` is the page,
   * `--raised` and `--card` are surfaces on it, `--bone*` is type. */
  --ink:        var(--c-ground);
  --raised:     var(--c-sheet);
  --card:       var(--c-band);
  --line:       var(--c-rule);
  --line-2:     var(--c-rule-firm);

  /* Type on ground.
   *
   * THE ONLY PLACE THIS DELIBERATELY LEAVES THE SITE'S VALUES, and the reason
   * is contrast rather than taste. The site's graphite pair is #565C60 and
   * #7D8489, and that second value is 3.5:1 on the sheet — under AA for normal
   * text. It is fine THERE, because the site spends it on decoration: an
   * instrument label, a footer sub-line, a few words of small print under 17px
   * body copy set in full ink.
   *
   * This tool spends `--bone-4` on CONTENT — the retention note, the state
   * line, a table's secondary column, the count beside every rail item — at
   * 10.5 to 13px. Copying a decorative grey into that role is the one way an
   * exact match makes the product worse, so the bottom two steps move down
   * until every one of them clears 4.5:1 on paper, sheet and band alike. The
   * hue is untouched; only the lightness moves, and the ladder is opened at
   * --bone-3 as well so the two steps stay tellable apart.
   *
   *   bone    13.93 : 1   on paper
   *   bone-2  11.05
   *   bone-3   6.80        (site: 5.30)
   *   bone-4   4.51        (site: 2.96 — this is the one that was failing) */
  --bone:       var(--c-text);
  --bone-2:     var(--c-text-2);
  --bone-3:     var(--c-text-3);
  --bone-4:     var(--c-text-4);

  /* Type placed ON the accent. It was `--ink` while the ground was dark and a
     dark letterform sat correctly on brass; on paper that same token would put
     #E2E4DF on #A82A1E, which is a 3:1 smudge. The site's own .btn-solid says
     white, so this is white. */
  --on-accent:  var(--c-on-accent);

  /* A field's own ground. On the dark plate an input was the DEEPEST colour —
     a well cut into the panel — and `var(--ink)` said that correctly. Inverted
     onto paper the same token gives a field the same value as the sheet it
     sits on, leaving a 1px border to do all of the work; on the registration
     panel that read as ruled lines rather than as boxes you type in. White is
     the one value lighter than the sheet, and it is what a form on a drawing
     is: the part left blank to be filled in. */
  --well:       var(--c-ground);

  /* Redline. The one accent.
     `--brass-lit` is the HOVER, and on paper hover goes DARKER — lifting a
     colour on a light ground spends contrast rather than attention.
     `--brass-deep` is the quiet tint; on near-black it was a darker red, and
     on paper there is nothing quieter than the accent itself, so it is the
     same value. The .doc plate settled all three of these first. */
  --brass:      var(--c-accent);
  --brass-lit:  var(--c-accent-ink);
  --brass-deep: var(--c-accent);
  --redline-bg: var(--c-accent-bg);

  /* Instrument. Readings only.
     Drafting blue straight from the site. `--hold` is the same amber family as
     its #A8823F, taken down until it can carry type: there the value is a
     decorative stroke (the mark's outer ring, at half opacity), here it colours
     a warning count. Every value below clears 4.5:1 on BOTH surfaces a reading
     lands on — the sheet and, when a rail item is selected, the band. */
  --signal:     var(--c-info);
  --hold:       var(--c-warn);
  --alarm:      var(--c-fault);
  --instr-bg:   #F3F5F3;
  --instr-bg-2: #E8EBE8;
  --instr-line: #DDE1DE;
  --instr-text: #2B3134;
  --instr-dim:  #596164;

  --display: "Archivo Variable", system-ui, -apple-system, "Segoe UI", sans-serif;
  --sans: "Archivo Variable", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "osifont", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* DRAWING TOKENS. The diagrams are built in JS and can only name tokens, so
   * every token they name has to exist in both plates or the SVG silently
   * renders black-on-black — `stroke: var(--undefined)` computes to `none` and
   * `fill:` to inherited black, which is exactly what used to happen here.
   * These four are the diagrams' whole vocabulary; `.doc` redefines all of
   * them for paper. */
  --accent:  var(--brass);
  --rule:    var(--line-2);
  --surface: var(--raised);
  --track:   var(--card);

  /* Severity, likewise named by markup that cannot see this file. */
  --chip: var(--line);
  --warn: var(--hold);
  --bad:  var(--alarm);

  /* The head is sticky, so anything else that pins itself to the top of the
     viewport has to know how tall it is. The rail on the workbench is the one
     that does; it lived at top:0 and slid under the bar the moment the bar
     started floating. */
  --head-h: 56px;

  /* Height of a sticky bottom bar, if the page has one. The workbench sets this
     on :root when its bar exists; the toast region sits above it. */
  --bottom-bar-h: 0px;
}

* { box-sizing: border-box; }

html { background: var(--ink); scroll-padding-top: calc(var(--head-h) + 16px); }

body {
  margin: 0;
  padding: 0;
  background: var(--ink);
  color: var(--bone);
  font-family: var(--sans);
  font-size: 15.5px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;

  /* THE HAIRLINE GROUND. A 1-in-4 rule at 2% ink, which is the site's own
     background-image and the single cheapest thing that makes a page read as
     drawing stock rather than as a white box. It is below the threshold where
     it can be seen as stripes and above the one where it does nothing. */
  background-image: repeating-linear-gradient(
    0deg,
    rgb(21 24 26 / 0.02) 0, rgb(21 24 26 / 0.02) 1px,
    transparent 1px, transparent 4px
  );
}

::selection { background: var(--brass); color: var(--on-accent); }

/* Square, like everything else on a drawing. The old 1px radius was a
   leftover from the rounded plate. */
:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; border-radius: 0; }

/* `hidden` is only a default display:none, so any rule that sets display beats
   it. Every page here toggles visibility with the attribute, so it has to win. */
[hidden] { display: none !important; }

/* The content column, and the gutter the head shares with it. 860px is the
   measure this tool has always read at; 1180px is the site's `.shell`, and it
   is what the head and the footer span so the two products line their chrome
   up at the same edges. */
.wrap { max-width: 860px; margin: 0 auto; padding: 40px 20px 64px; }
.wrap.wide { max-width: 1000px; }
/* .shell is the header mountShell() injects (session.js): <header class="head shell">
   holding the .nav strip. It takes the sticky/blur/hairline from .head below. */
.shell { min-height: var(--head-h); }

/* ── the sheet head ────────────────────────────────────────────────────────
 *
 * The site's own header, rebuilt: 68px, sticky, the band at 85% behind a blur,
 * one hairline under it. The mark is the same two circles at the same radii —
 * an amber ring at half opacity around a redline dot — because a wordmark that
 * is merely SIMILAR across two domains is worse than one that is absent.
 *
 * A wordmark and five places. The tool is small enough that hiding any of them
 * behind a menu would be affectation, so on a narrow screen the links wrap to
 * a second line instead of collapsing into a button.
 */

/* THE SITE'S BAR (casient-web .bar, 2026-10-06): opaque paper, one hairline, no
   blur. Wordmark "Casient Pro" in Archivo 700, links in Archivo 16px ink, the
   current page in the accent, and the filled .bar-cta reserved for ONE primary
   action (mountShell({ cta })). */
.head {
  position: sticky; top: 0; z-index: 40;
  background: var(--c-ground);
  border-bottom: 1px solid var(--c-rule);
}

.nav {
  display: flex; align-items: center; gap: 10px 28px; flex-wrap: wrap;
  min-height: var(--head-h); padding-block: 10px;
  max-width: 1180px; margin-inline: auto; padding-inline: 20px;
}
@media (min-width: 768px) { .nav { padding-inline: 40px; } }

/* The mark. The svg is inline in every page's markup; this only sets the
   lockup. Literata semibold at 0.3em, exactly as the site sets it. */
.nav .mark {
  display: inline-flex; align-items: center;
  font-family: var(--display); font-weight: 700; font-size: 1.1875rem;
  letter-spacing: -0.02em; text-transform: none; color: var(--c-text); text-decoration: none;
  margin-right: auto; white-space: nowrap; min-height: var(--hit);
}
.nav .mark span { color: inherit; font-weight: inherit; }
.nav .mark:hover { color: var(--c-accent); }

.nav a {
  font-family: var(--sans); font-size: 1rem; font-weight: 400;
  letter-spacing: 0; text-transform: none;
  color: var(--c-text); text-decoration: none;
  display: inline-flex; align-items: center; min-height: var(--hit);
}
.nav a:hover { color: var(--c-accent); }
.nav a.here { color: var(--c-accent); text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 6px; }
.nav a.right { margin-left: auto; }
/* The one filled action in the bar. At most one per page. */
.nav a.bar-cta {
  font-family: var(--mono); font-size: 0.8125rem; letter-spacing: 0.1em; text-transform: uppercase;
  background: var(--c-accent); color: var(--c-on-accent); border: 2px solid var(--c-accent);
  padding: 0.6875rem 1.125rem; flex: none; white-space: nowrap; text-decoration: none;
}
.nav a.bar-cta:hover { background: var(--c-text); border-color: var(--c-text); color: var(--c-on-accent); }

/* ON A PHONE THE STRIP SCROLLS, it does not wrap.
 *
 * Six items and a session slot wrapped to three lines at 375px, and because
 * the head is sticky that cost a third of the screen on every scroll of every
 * page — permanently, not once. The site solves it with a hamburger; this
 * solves it the way the workbench rail already does on the same breakpoint,
 * which keeps one pattern in the tool instead of two. Nothing is hidden: the
 * strip scrolls sideways and the current page is the one with the rule under
 * it, so you can always see where you are without scrolling at all. */
@media (max-width: 720px) {
  :root { --head-h: 52px; }
  .nav {
    flex-wrap: nowrap; overflow-x: auto; gap: 20px;
    padding-block: 0; scrollbar-width: none; -ms-overflow-style: none;
  }
  .nav::-webkit-scrollbar { display: none; }
  /* Without this every item is a flex item free to shrink, and six of them at
     375px compress into an unreadable column of one word each. */
  .nav > * { flex: none; }
  /* Overflow cue: a fade on whichever edge has more to scroll to. The class is
     set by mountShell() from the scroll position; without script there is no
     fade and the strip still scrolls. */
  .nav.more-right { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent); }
  .nav.more-left { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 36px), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 36px), transparent); }
  .nav.more-left.more-right { -webkit-mask-image: linear-gradient(to right, transparent, #000 36px, #000 calc(100% - 36px), transparent); mask-image: linear-gradient(to right, transparent, #000 36px, #000 calc(100% - 36px), transparent); }
  .nav .mark { margin-right: 0; }
  /* `auto` would push the session past the scrollable width and make it
     unreachable rather than merely off-screen. */
  .nav .session { margin-left: 4px; }
}

/* Who is signed in, and the way out.
 *
 * In the navigation on every page rather than only on the account card,
 * because "how do I sign out" is a question people ask from wherever they
 * happen to be - and until this existed the only answer was to delete the
 * whole account. */
.nav .session {
  margin-left: auto; display: flex; align-items: center; gap: 14px;
  font-family: var(--sans); font-size: 0.9375rem;
}
.nav .session:empty { display: none; }
.nav .session .who { color: var(--bone-4); }

/* The line above a page's controls saying whether they will accept anything.
   Lived inline in index.html until the compare page needed the same sentence;
   two pages carrying one rule is what this sheet is for. */
.who { font-size: 13.5px; color: var(--bone-3); margin-bottom: 20px; }
.nav .session .who b { color: var(--bone-2); font-weight: 400; }
.nav .session a, .nav .session button { color: var(--c-text-2); }
.nav .session button {
  font: inherit; font-family: var(--sans); font-size: 0.9375rem;
  background: none; border: none; padding: 0 2px; min-height: var(--hit); cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.nav .session button:hover { color: var(--brass); }
.nav a.right + .session { margin-left: 0; }

/* ── page heads ────────────────────────────────────────────────────────────
   The display face carries the question; the sans carries the answer. */

h1 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(30px, 5vw, 42px); line-height: 1.06; letter-spacing: -0.032em;
  letter-spacing: -0.005em; text-wrap: balance;
  margin: 0 0 12px;
}
h1 em { color: var(--brass); font-style: normal; }
.lede { color: var(--bone-2); font-size: 17px; max-width: 60ch; margin: 0 0 8px; }
.lede em { color: var(--brass); font-style: normal; }
.fine { color: var(--bone-4); font-size: 13px; max-width: 66ch; margin: 10px 0 0; }

/* Section marks. The lettering face rather than the prose one, which is what
   turns them into the drawing's own annotation — the site sets its §-heads
   exactly this way and the rule beneath is the same hairline. */
h2 {
  font-family: var(--mono); font-weight: 400;
  font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--brass);
  margin: 40px 0 14px; padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
}
h3 { font-size: 14.5px; font-weight: 600; margin: 24px 0 6px; }
.sub { color: var(--bone-4); font-size: 13px; margin: -6px 0 14px; max-width: 68ch; }

a { color: var(--brass); }
a:hover { color: var(--brass-lit); }

/* ── surfaces ──────────────────────────────────────────────────────────────
   Border, fill and rule are spent by role rather than stamped on every block:
   .panel is a thing you act on, .plate is a thing you read. */

/* SQUARE, ALL OF IT. Every radius in this file went to 0 with the plate: a
   drawing has no rounded corners, and 3px was the one detail that kept reading
   as "web app" once the ground stopped being near-black. */
.panel {
  background: var(--raised); border: 1px solid var(--line-2);
  border-radius: 0; padding: 24px;
}
.plate {
  background: var(--instr-bg); border: 1px solid var(--instr-line);
  border-radius: 0; padding: 20px 22px;
  font-variant-numeric: tabular-nums;
}

/* ── controls ──────────────────────────────────────────────────────────── */

label.field {
  display: flex; flex-direction: column; gap: 5px; margin-bottom: 15px;
  font-family: var(--mono); font-size: 12px; font-weight: 400;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--bone-3);
}
input[type=text], input[type=email], input[type=password], input[type=date], textarea, select {
  font: inherit; font-size: 14.5px; font-weight: 400;
  color: var(--bone); background: var(--well);
  border: 1px solid var(--line-2); border-radius: 0; padding: 10px 11px;
  text-transform: none; letter-spacing: 0;
}
input:hover, textarea:hover, select:hover { border-color: var(--bone); }
textarea { min-height: 70px; resize: vertical; }

/* THE BUTTON IS THE SITE'S. Lettering face, uppercase, 0.1em, a 2px rule and
   no radius — and the hover inverts to ink rather than shifting the red,
   which is what the landing page's own .btn-solid does. Two products whose
   primary buttons behave differently under the cursor do not feel like one
   product however well the colours match. */
.go {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.6rem;
  font: inherit; font-family: var(--mono); font-weight: 400; font-size: 12.5px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--on-accent); background: var(--brass);
  border: 2px solid var(--brass); border-radius: 0;
  padding: 11px 22px; cursor: pointer; min-height: 44px;
  transition: background var(--dur-1) linear, color var(--dur-1) linear, border-color var(--dur-1) linear;
}
.go:hover { color: var(--on-accent); background: var(--bone); border-color: var(--bone); }
.go:disabled { opacity: 0.4; cursor: default; }
.go:disabled:hover { background: var(--brass); border-color: var(--brass); }
.go.quiet { color: var(--bone); background: transparent; border-color: var(--bone); }
.go.quiet:hover { color: var(--raised); background: var(--bone); }
.go.block { width: 100%; }

.link {
  font: inherit; font-size: 13px; color: var(--brass);
  background: none; border: none; padding: 0 2px; min-height: var(--hit); cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.del {
  font: inherit; font-family: var(--mono); font-size: 11.5px; font-weight: 400;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--alarm); background: none;
  border: 1px solid var(--alarm);
  border-radius: 0; padding: 8px 14px; min-height: var(--hit); cursor: pointer;
}
.del:hover { color: var(--on-accent); background: var(--alarm); border-color: var(--alarm); }

/* ── messages ──────────────────────────────────────────────────────────── */

.err { color: var(--alarm); font-size: 13.5px; margin-top: 12px; }
.ok  { color: var(--c-ok); font-size: 13.5px; margin-top: 12px; }
.state { font-size: 13.5px; color: var(--bone-3); margin: 14px 0; min-height: 20px; }
.state.err { color: var(--c-fault); }
.state.err::before { content: "\25A0\00a0"; }

/* ── readings ──────────────────────────────────────────────────────────────
   Instrument plate. Mono digits, teal, on the colder ground — so a number is
   visibly a measurement and not a headline. */

.cells { display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 1px; background: var(--instr-line); border: 1px solid var(--instr-line); border-radius: 0; overflow: hidden; }
.cell { background: var(--instr-bg); padding: 14px 15px; }
.cell b {
  display: block; font-family: var(--mono); font-weight: 400;
  font-size: 23px; line-height: 1.1; color: var(--instr-text);
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
}
.cell span {
  display: block; margin-top: 5px;
  font-family: var(--mono);
  font-size: 12px; font-weight: 400; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--instr-dim);
}
.cell.lit b { color: var(--signal); }
.cell.warn b { color: var(--hold); }

/* ── tables ────────────────────────────────────────────────────────────── */

.scroll { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th {
  text-align: left; font-family: var(--mono); font-size: 12px; font-weight: 400;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--bone-3);
  padding: 8px 10px; border-bottom: 1px solid var(--line-2); white-space: nowrap;
}
td { padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
tr:last-child td { border-bottom: none; }
td.mono, .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
td.mono { color: var(--brass); white-space: nowrap; }
td.num { font-family: var(--mono); text-align: right; font-variant-numeric: tabular-nums; }

/* ── findings ──────────────────────────────────────────────────────────────
 *
 * Severity is a left rule, not a coloured panel: a page of red blocks reads as
 * alarm, and most of these are ordinary work an integrator can quote for.
 *
 * UNSCOPED, AND IT HAS TO BE. These started inside report.html under `.doc`,
 * which was fine for as long as the handover document was the only thing that
 * showed a finding. The free check now shows the same list, and that page is on
 * the dark plate — so the rules live here and both plates get them, exactly as
 * tables and figures already do. `.doc` redefines the tokens; nothing below
 * needs a second implementation.
 */
.rec { border-left: 3px solid var(--chip); padding: 7px 0 7px 12px; margin: 9px 0; }
.rec-high { border-left-color: var(--c-fault); border-left-width: 3px; }
.rec-medium { border-left-color: var(--warn); }
.rec-low { border-left-color: var(--chip); }
.rec-head { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.rec-head b { font-size: 1.04em; }
.rec-ev { margin-top: 3px; color: var(--bone-2); }
.rec-cost { margin-top: 3px; color: var(--bone-3); }
/* The remedy, where the finding arrived carrying one. Marked with the accent
   rather than another grey line, because it is the only part of a finding that
   tells you what to actually do. */
.rec-fix { margin-top: 3px; color: var(--bone-2); }
.rec-fix em { font-style: normal; color: var(--brass); }
/* The addresses a finding is about. Spaced apart, because run together at this
   size they read as one long address rather than as eight. */
.rec-sub { margin-top: 4px; font-size: 12px; color: var(--bone-4); }
.rec-sub code { font-family: var(--mono); margin-right: 10px; white-space: nowrap; }
/* Square, not a pill. A drawing annotates with a box. */
.tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--mono); font-size: 12px; font-weight: 400; line-height: 1.3;
  text-transform: uppercase; letter-spacing: 0.08em;
  border: 1px solid var(--c-rule-firm); border-radius: var(--r-0);
  padding: 2px 7px; color: var(--bone-3); white-space: nowrap;
}
/* The glyph is part of the tag, so severity never rests on colour alone. */
.tag-high, .tag-fault { border-color: var(--c-fault); color: var(--c-fault); }
.tag-high::before, .tag-fault::before { content: "\25A0"; }
.tag-medium, .tag-review { border-color: var(--c-warn); color: var(--c-warn); }
.tag-medium::before, .tag-review::before { content: "\25B2"; }
.tag-low, .tag-note { border-color: var(--c-rule-firm); color: var(--bone-3); }
.tag-low::before, .tag-note::before { content: "\25CF"; }
.tag-ok { border-color: var(--c-ok); color: var(--c-ok); }
.tag-ok::before { content: "\2713"; }
.tag-do { border-color: var(--signal); color: var(--signal); }
/* Who has to act. Fix = ours to do; decide = needs a decision first; info = read only. */
.tag-who-fix { border-color: var(--c-text); color: var(--c-text); }
.tag-who-decide { border-color: var(--c-warn); color: var(--c-warn); }
.tag-who-info { border-color: var(--c-rule-firm); color: var(--bone-3); }
.warn-line { border-left: 2.5px solid var(--warn); padding-left: 11px; }

/* ── the sales pitch ───────────────────────────────────────────────────────
   Deliberately the only place on the page that uses the display face at body
   size. It has to read as us talking, not as another finding. */

.pitch {
  margin-top: 44px; padding: 28px 30px;
  background: var(--raised);
  border: 1px solid var(--line-2); border-left: 2px solid var(--brass);
  border-radius: 0;
}
.pitch h2 { margin-top: 0; border: none; padding: 0; color: var(--brass); }
.pitch .say {
  font-family: var(--display); font-weight: 400;
  font-size: 22px; line-height: 1.34; color: var(--bone);
  margin: 0 0 16px; max-width: 42ch; text-wrap: balance;
}
.pitch p { color: var(--bone-3); font-size: 14px; max-width: 64ch; margin: 0 0 12px; }
.pitch ul { margin: 0 0 18px; padding: 0; list-style: none; }
.pitch li {
  position: relative; padding-left: 20px; margin-bottom: 9px;
  color: var(--bone-2); font-size: 14px; max-width: 62ch;
}
.pitch li::before {
  content: ""; position: absolute; left: 2px; top: 9px;
  width: 6px; height: 6px; border: 1px solid var(--brass); border-radius: 50%;
}
.pitch li b { color: var(--bone); font-weight: 600; }
.pitch .cta { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; margin-top: 4px; }

/* A link that acts as a button. Lives here rather than on the page that first
   used it: the pitch module emits one, and a class the shared module depends on
   belongs in the shared sheet or it renders unstyled wherever it is reused. */
.doclink {
  display: inline-flex; align-items: center; gap: 0.6rem; text-decoration: none;
  font-family: var(--mono); font-weight: 400; font-size: 12.5px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--on-accent); background: var(--brass);
  border: 2px solid var(--brass); border-radius: 0; padding: 11px 20px; min-height: 44px;
  transition: background var(--dur-1) linear, border-color var(--dur-1) linear;
}
.doclink:hover { background: var(--bone); border-color: var(--bone); color: var(--on-accent); }
.pitch .aside { color: var(--bone-4); font-size: 12.5px; }

/* ── footer ────────────────────────────────────────────────────────────────
 *
 * The site's footer, rebuilt with this tool's own content. Same three-column
 * grid at the same 1.4fr/1fr/1fr, the same mono eyebrows, the same closing
 * band of uppercase small print carrying the trademark notice.
 *
 * NOT A COPY OF ITS LINKS. The site's middle column points at /knx, /guide and
 * /writing; a person standing in the checker needs the checker's own places
 * first and the reading after. Aligned means the same furniture, not the same
 * contents — and a footer offering four links that all leave the tool is a
 * footer that has forgotten where it is.
 *
 * It is FULL-BLEED, so it has to sit outside .wrap in the markup. Inside it,
 * the page's own 860px measure would leave the rule floating in the middle of
 * a 1180px page.
 */

footer.site {
  margin-top: 80px;
  border-top: 1px solid var(--line);
  font-size: 13px;
}
footer.site .cols {
  display: grid; gap: 40px;
  max-width: 1180px; margin-inline: auto;
  padding: 56px 20px;
}
@media (min-width: 768px) {
  footer.site .cols { grid-template-columns: 1.4fr 1fr 1fr; gap: 36px; padding-inline: 40px; }
}

footer.site .mark {
  display: flex; align-items: center; gap: 12px;
  font-family: var(--sans); font-weight: 600; font-size: 12.5px;
  letter-spacing: 0.3em; color: var(--bone); text-decoration: none;
}
footer.site .mark svg { display: block; flex: none; }

/* The line the site closes on. The lettering face at display size, light — it
   is the one place in the tool, other than a page title, where that face is
   allowed to be large. */
footer.site .say {
  font-family: var(--display); font-weight: 400;
  font-size: 21px; line-height: 1.375; color: var(--bone-2);
  max-width: 54ch; margin: 20px 0 0;
}
footer.site .where { color: var(--bone-3); max-width: 42ch; margin: 20px 0 0; font-size: 13px; line-height: 1.6; }

footer.site h2 {
  font-family: var(--mono); font-weight: 400; font-size: 12px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--bone-3);
  margin: 0; padding: 0; border: none;
}
footer.site ul { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
footer.site a { color: var(--bone-3); text-decoration: none; }
footer.site a:hover { color: var(--bone); }
footer.site li a { color: var(--bone-2); }
footer.site li span {
  display: block; margin-top: 2px;
  font-family: var(--mono); font-size: 12px; line-height: 1.375; color: var(--bone-4);
}
footer.site .talk { margin: 16px 0 0; }
footer.site .talk + p { color: var(--bone-3); max-width: 34ch; margin: 22px 0 0; font-size: 12.5px; line-height: 1.6; }

/* The closing band. Uppercase lettering at 10.5px — the small print on a
   drawing's title block, which is what it is. */
footer.site .band { border-top: 1px solid var(--line); }
footer.site .band > div {
  display: flex; flex-direction: column; gap: 12px;
  max-width: 1180px; margin-inline: auto; padding: 22px 20px;
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--bone-4); line-height: 1.6;
}
@media (min-width: 768px) {
  footer.site .band > div { flex-direction: row; align-items: center; justify-content: space-between; padding-inline: 40px; }
  footer.site .band .legal { max-width: 62ch; }
}

@media (max-width: 560px) {
  .wrap { padding-block: 28px 48px; }
  .panel { padding: 18px; }
  h2 { margin-top: 32px; }
  footer.site .cols { padding-block: 40px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ── the paper plate ───────────────────────────────────────────────────────
 *
 * The one light surface in the tool. Two pages produce a document that gets
 * printed and handed to somebody — a handover pack and a change report — and a
 * document is a sheet of paper whether or not it is currently on a screen.
 *
 * It redefines the tokens INSIDE its own scope rather than using a separate set
 * of names, so every component above (tables, figures, rules) renders correctly
 * on paper without a second implementation.
 *
 * WHICH FACE CARRIES THE PROSE. Literata, not osifont — which is what the note
 * at the top of this file already says ("Literata carries running prose").
 * osifont is a LETTERING standard: monolinear, one weight, drawn to be
 * unambiguous on a drawing at 2.5 mm, and it is exhausting for four hundred
 * words of explanation set at document size. It keeps the work it is good at —
 * the title, the addresses, the figures, the diagram labels — where its
 * evenness reads as an instrument and its single weight is no loss.
 *
 * WHY THE BODY IS 12px. That is 9 pt, the size a customer-facing O&M manual or
 * a commissioning pack is set at. It went 15px → 13px → 12px over two passes:
 * 15px is 11.25 pt, which is large-print, and 13px still read as a web page
 * printed out rather than as a document.
 *
 * ONE MEASURE FOR EVERYTHING. The page margins set the measure, and prose runs
 * to the same edge the tables and diagrams do. A paragraph capped well short of
 * the tables beside it is the single thing that made this look assembled rather
 * than set: the eye reads the mismatch as two documents interleaved. 84ch is a
 * ceiling for unusually wide paper, not the working width — on A4 the text
 * reaches the margin.
 */

.doc {
  /* The same greys the rest of the tool now uses, so the document reads as a
     sheet laid ON the drawing stock rather than as a third palette. It was a
     cool-grey family of its own while the tool around it was near-black and
     nothing could be compared; on paper the mismatch is visible at the border. */
  --ink:      #15181A;
  --raised:   #F5F6F3;
  --card:     #FFFFFF;
  --line:     #D8DBD6;
  --line-2:   #B4B8B1;
  --bone:     #15181A;
  --bone-2:   #24282B;
  /* The same opened ladder as :root, and it matters more here: this document
     sets its body at 12px (9pt, the size a commissioning pack is set at) and
     gets printed, where a washed grey does not survive an office laser. */
  --bone-3:   #464C50;
  --bone-4:   #5F676B;
  /* Light ground, so the site's own redline rather than the lifted one used
     on the instrument. Same accent, correct contrast for white. */
  --brass:      #B0281A;
  --brass-lit:  #8E2318;
  --brass-deep: #B0281A;
  --signal:   #1F6F5C;
  --alarm:    #7A1810;

  /* The drawing vocabulary, on paper.
   *
   * --rule is a mid grey rather than the hairline used for table rules: a
   * connector in a diagram has to survive an office laser printer, and #E2E5E4
   * does not. --track is the unfilled part of a bar, and it is what lets a
   * short bar still read as a proportion rather than as a stub. */
  --accent:  #A82A1E;
  --rule:    #97A0A2;
  --surface: #FFFFFF;
  --track:   #EDEFEF;

  /* Severity, in the document's own palette rather than the stock web red and
   * orange that used to arrive through var() fallbacks. Both are dark enough
   * to set type in, which the stock pair was not. */
  --chip: #B4B8B1;
  --warn: #92600F;
  --bad:  #7A1810;

  /* A 2px ink rule, which is the site's own .sheet-frame. On the dark plate
     the document needed no border — white against near-black IS an edge. On
     paper #FFF against #E2E4DF is a 6% step, and without the frame the sheet
     did not read as a sheet. */
  background: #fff; color: var(--ink);
  border: 2px solid var(--bone);
  border-radius: 0; padding: 44px 50px; margin-top: 28px;
  font-family: var(--sans); font-weight: 400;
  font-size: 12px; line-height: 1.45;
  counter-reset: sec;
}
.doc:empty { display: none; }

.doc h1 {
  font-family: var(--display); font-weight: 700; letter-spacing: -0.024em;
  font-size: 21px; line-height: 1.18; margin: 0 0 4px; color: var(--ink);
}

/* Numbered sections. A handover pack gets referred to on the phone — "the
   table in section 4" only works if the sections are numbered, and a counter
   is the only way to number them that survives a section being absent. */
.doc h2 {
  font-family: var(--sans); font-weight: 600;
  font-size: 9.5px; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--brass); margin: 21px 0 8px;
  padding-bottom: 4px; border-bottom: 1px solid var(--line-2);
}
.doc h2::before {
  counter-increment: sec; content: counter(sec) "\00a0\00a0";
  font-family: var(--mono); color: var(--bone-4);
}
.doc h2.unnumbered::before { counter-increment: none; content: none; }
/* Subheadings carry more space above than below, so they attach to what they
   introduce rather than floating between two blocks of equal weight — which is
   most of what makes a long document feel assembled rather than set. */
.doc h3 {
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  letter-spacing: 0.02em; margin: 17px 0 4px;
}
.doc p { margin: 0 0 7px; max-width: 84ch; }
.doc .lead { font-size: 13px; color: var(--bone-2); }
.doc .sub { font-family: var(--sans); font-size: 10.5px; color: var(--bone-3); }
.doc table { font-family: var(--sans); font-size: 10.5px; }
.doc th { font-size: 10px; letter-spacing: 0.1em; padding: 5px 8px 4px; color: var(--bone-4); border-bottom: 1px solid var(--line-2); }
.doc td { padding: 3.5px 8px; }
.doc td.mono, .doc .mono { font-family: var(--mono); }
.doc td.mono { color: var(--brass); }
.doc code { font-family: var(--mono); font-size: 0.95em; color: var(--brass); }
.doc .muted { color: var(--bone-4); }
.doc .empty { color: var(--bone-3); font-style: italic; }
.doc footer {
  margin-top: 24px; padding-top: 10px; border-top: 1px solid var(--line-2);
  font-family: var(--sans); font-size: 8.5px; color: var(--bone-3);
}

/* Contents. Not a table of contents with page numbers — a browser cannot know
   them — but the list a reader needs to decide whether this document contains
   the thing they are looking for, which is most of what a contents page is
   for. Two columns because it is a list of short lines. */
.doc .toc { columns: 2; column-gap: 26px; margin: 4px 0 2px; }
.doc .toc div {
  break-inside: avoid; display: flex; align-items: baseline; gap: 9px;
  font-size: 10.5px; line-height: 1.7; color: var(--bone-2);
}
.doc .toc div i { font-family: var(--mono); font-style: normal; color: var(--bone-4); min-width: 11px; }

/* Figures on paper: outlined rather than filled, because a solid tint costs
   ink and prints as grey mud on most office printers. The track is narrow
   enough that six fit one row of A4, which is the count this document
   actually emits — at the old 118px they wrapped 5 + 1. */
.doc .figs { display: grid; grid-template-columns: repeat(auto-fit, minmax(86px, 1fr)); gap: 7px; margin: 11px 0; }
.doc .fig { border: 1px solid var(--line-2); border-radius: 2px; padding: 7px 9px; }
.doc .fig b { display: block; font-family: var(--mono); font-size: 16px; line-height: 1.15; font-variant-numeric: tabular-nums; }
.doc .fig span { font-family: var(--sans); font-size: 9px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; color: var(--bone-3); }
.doc .fig.add b { color: var(--signal); }
.doc .fig.del b { color: var(--alarm); }
.doc .fig.warn { border-color: var(--brass); }
.doc .fig.warn b { color: var(--brass); }

.doc .note { background: var(--raised); border-left: 2px solid var(--brass); padding: 9px 12px; margin: 10px 0; font-size: 11.5px; }
.doc .breaking { background: #FDF3F1; border-left: 2px solid var(--alarm); padding: 10px 13px; margin: 11px 0; }
.doc .breaking b { color: var(--alarm); }
.doc .breaking ul { margin: 6px 0 0; padding-left: 16px; }
.doc .breaking.clear { background: #F1F7F3; border-left-color: var(--signal); }
.doc .breaking.clear b { color: var(--signal); }

/* Diagrams. Labels are set in the prose face so a diagram reads as part of the
   document; addresses and figures inside them keep the lettering face, which
   is set per-class by the diagram itself. */
.doc svg { display: block; max-width: 100%; }
.doc svg text { font-family: var(--sans); fill: var(--ink); }

/* Diagram labels.
 *
 * SCOPED UNDER `.doc svg` ON PURPOSE. A bare `.t-dim { fill: … }` loses to
 * `.doc svg text { fill: var(--ink) }` on specificity, which is why every label
 * in every diagram used to print at the same full-ink weight however it was
 * classed — the hierarchy was written and then silently discarded. */
.doc svg .t-line { font-size: 11px; font-weight: 700; }
.doc svg .t-dev  { font-size: 10px; fill: var(--bone-2); }
.doc svg .t-addr { font-family: var(--mono); font-size: 9.5px; fill: var(--brass); }
.doc svg .t-dim  { font-size: 9.5px; fill: var(--bone-3); }
.doc svg .t-edge { font-size: 8.5px; font-weight: 600; letter-spacing: 0.09em;
                   text-transform: uppercase; fill: var(--bone-4); }
.doc svg .t-box  { font-size: 12.5px; font-weight: 600; }
.doc svg .t-num  { font-family: var(--mono); font-size: 11.5px; fill: var(--ink); }
.doc svg .t-plan-label { font-family: var(--mono); font-size: 10.5px; fill: var(--bone-2); }
.doc figure { margin: 10px 0 14px; }
.doc figure figcaption {
  font-family: var(--sans); font-size: 9.5px; color: var(--bone-3);
  margin-top: 6px; max-width: 92ch;
}

/* ── print ─────────────────────────────────────────────────────────────────
 * A heading stranded at the foot of a page, a table row split across two, and
 * a column of figures whose header only appears on the first page are the
 * three things that make a generated document look generated. */

/* NO RUNNING HEAD, AND WHY NOT.
 *
 * A handover pack gets split and filed a page at a time, so a band carrying the
 * client and the reference onto every sheet is worth having. There is no way to
 * produce one that can be relied on. Chrome implements no @page margin boxes,
 * which rules out both a running head and page numbers; the usual substitute is
 * a `position: fixed` element, which Chrome does repeat per page — but in
 * testing here it landed at the top of every page under one offset, at the
 * bottom under another, and on the cover page under neither. A band that may
 * appear anywhere is worse than no band, so the identifying detail stays on the
 * cover and in the closing footer, and the sections are numbered instead.
 *
 * Revisit if Chrome ships @page margin boxes.
 */

@page { size: A4; margin: 16mm 18mm 16mm; }
@media print {
  /* THE PAPER IS WHITE. ALL OF IT.
   *
   * This block was written against the dark plate, where `color-scheme: dark`
   * made the UA paint the canvas #121212 before any background of ours
   * propagated up from body — so every sheet printed near-black with the
   * document's white panel laid on top, leaving a black margin on all four
   * sides. The scheme is light now and that particular fault cannot recur, but
   * the override stays: the screen ground is #E2E4DF over a hairline rule, and
   * printing drawing stock onto real stock is a waste of toner and makes the
   * document look photocopied. Both html and body are told explicitly rather
   * than relying on propagation. */
  html, body { background: #fff !important; background-image: none !important; }

  body { color: #15181A; padding: 0; }
  .noprint, .head, .nav, .panel, footer.site, .pitch { display: none !important; }
  .wrap { max-width: none; padding: 0; }
  /* The frame is a screen affordance — it separates the sheet from the stock
     behind it. On actual paper there is nothing behind it. */
  .doc { padding: 0; margin: 0; border: 0; border-radius: 0; }

  /* A long table that breaks across pages repeats its own header, so the
     second page of a device inventory is still readable as one. */
  thead { display: table-header-group; }
  h2, h3 { break-after: avoid; }
  .doc h1, .doc .lead { break-after: avoid; }
  tr, .fig, .note, .breaking, .rec, .room, .doc figure, .doc .sign { break-inside: avoid; }
  .doc svg { break-inside: avoid; }
  p, li { orphans: 3; widows: 3; }
  .page-break { break-before: page; }
}

/* ── loader ────────────────────────────────────────────────────────────────
   A scrambled file being read and made sense of. Lines of noise flicker at random
   lengths; a scan beam sweeps down the page and each line snaps to an ordered,
   aligned row as the beam passes — then the file scrambles again and it repeats.
   Keyframes are generated: line k settles when the beam reaches it. */
.loader { display: flex; align-items: center; gap: 16px; margin: 14px 0; }
.loader-art { position: relative; flex: none; width: 56px; height: 72px; padding: 11px 8px 8px; box-sizing: border-box; background: var(--raised, #fff); border: 1.5px solid var(--bone-3, #464C50); overflow: hidden; }
.loader-art::before { content: ""; position: absolute; top: 0; right: 0; width: 12px; height: 12px; background: linear-gradient(225deg, var(--ink, #fff) 50%, var(--line-2, #ccc) 50%); }
.loader-art i { display: block; height: 4px; margin-bottom: 6px; width: 60%; background: var(--bone-4, #888); opacity: .55; }
.loader-art i:nth-child(1) { animation: ld-l0 4.2s steps(1, end) infinite; }
.loader-art i:nth-child(2) { animation: ld-l1 4.2s steps(1, end) infinite; }
.loader-art i:nth-child(3) { animation: ld-l2 4.2s steps(1, end) infinite; }
.loader-art i:nth-child(4) { animation: ld-l3 4.2s steps(1, end) infinite; }
.loader-art i:nth-child(5) { animation: ld-l4 4.2s steps(1, end) infinite; }
.loader-art i:nth-child(6) { animation: ld-l5 4.2s steps(1, end) infinite; }
.loader-art b { position: absolute; left: 0; right: 0; top: 0; height: 2px; background: var(--brass, #B0281A); box-shadow: 0 0 8px 1px var(--brass, #B0281A); animation: ld-scan 4.2s linear infinite; }
@keyframes ld-scan { 0% { transform: translateY(8px); opacity: 0; } 6% { opacity: 1; } 84% { transform: translateY(66px); opacity: 1; } 90%, 100% { transform: translateY(66px); opacity: 0; } }
@keyframes ld-l0 { 0.0% { width: 38%; background: var(--bone-4, #888); opacity: .55; } 2.0% { width: 84%; background: var(--bone-4, #888); opacity: .55; } 4.0% { width: 58%; background: var(--bone-4, #888); opacity: .55; } 6.0% { width: 90%; background: var(--bone-4, #888); opacity: .55; } 8.0% { width: 30%; background: var(--bone-4, #888); opacity: .55; } 10.0% { width: 72%; background: var(--bone-4, #888); opacity: .55; } 12.0% { width: 48%; background: var(--bone-4, #888); opacity: .55; } 14% { width: 100%; background: var(--brass, #B0281A); opacity: 1; } 88% { width: 100%; background: var(--brass, #B0281A); opacity: 1; } 100% { width: 38%; background: var(--bone-4, #888); opacity: .55; } }
@keyframes ld-l1 { 0.0% { width: 96%; background: var(--bone-4, #888); opacity: .55; } 3.7% { width: 30%; background: var(--bone-4, #888); opacity: .55; } 7.4% { width: 74%; background: var(--bone-4, #888); opacity: .55; } 11.1% { width: 46%; background: var(--bone-4, #888); opacity: .55; } 14.9% { width: 82%; background: var(--bone-4, #888); opacity: .55; } 18.6% { width: 26%; background: var(--bone-4, #888); opacity: .55; } 22.3% { width: 92%; background: var(--bone-4, #888); opacity: .55; } 26% { width: 68%; background: var(--brass, #B0281A); opacity: 1; } 88% { width: 68%; background: var(--brass, #B0281A); opacity: 1; } 100% { width: 96%; background: var(--bone-4, #888); opacity: .55; } }
@keyframes ld-l2 { 0.0% { width: 52%; background: var(--bone-4, #888); opacity: .55; } 5.4% { width: 92%; background: var(--bone-4, #888); opacity: .55; } 10.9% { width: 28%; background: var(--bone-4, #888); opacity: .55; } 16.3% { width: 78%; background: var(--bone-4, #888); opacity: .55; } 21.7% { width: 62%; background: var(--bone-4, #888); opacity: .55; } 27.1% { width: 88%; background: var(--bone-4, #888); opacity: .55; } 32.6% { width: 36%; background: var(--bone-4, #888); opacity: .55; } 38% { width: 86%; background: var(--brass, #B0281A); opacity: 1; } 88% { width: 86%; background: var(--brass, #B0281A); opacity: 1; } 100% { width: 52%; background: var(--bone-4, #888); opacity: .55; } }
@keyframes ld-l3 { 0.0% { width: 80%; background: var(--bone-4, #888); opacity: .55; } 7.1% { width: 44%; background: var(--bone-4, #888); opacity: .55; } 14.3% { width: 98%; background: var(--bone-4, #888); opacity: .55; } 21.4% { width: 36%; background: var(--bone-4, #888); opacity: .55; } 28.6% { width: 94%; background: var(--bone-4, #888); opacity: .55; } 35.7% { width: 54%; background: var(--bone-4, #888); opacity: .55; } 42.9% { width: 70%; background: var(--bone-4, #888); opacity: .55; } 50% { width: 56%; background: var(--brass, #B0281A); opacity: 1; } 88% { width: 56%; background: var(--brass, #B0281A); opacity: 1; } 100% { width: 80%; background: var(--bone-4, #888); opacity: .55; } }
@keyframes ld-l4 { 0.0% { width: 24%; background: var(--bone-4, #888); opacity: .55; } 8.9% { width: 66%; background: var(--bone-4, #888); opacity: .55; } 17.7% { width: 50%; background: var(--bone-4, #888); opacity: .55; } 26.6% { width: 88%; background: var(--bone-4, #888); opacity: .55; } 35.4% { width: 40%; background: var(--bone-4, #888); opacity: .55; } 44.3% { width: 96%; background: var(--bone-4, #888); opacity: .55; } 53.1% { width: 58%; background: var(--bone-4, #888); opacity: .55; } 62% { width: 92%; background: var(--brass, #B0281A); opacity: 1; } 88% { width: 92%; background: var(--brass, #B0281A); opacity: 1; } 100% { width: 24%; background: var(--bone-4, #888); opacity: .55; } }
@keyframes ld-l5 { 0.0% { width: 70%; background: var(--bone-4, #888); opacity: .55; } 10.6% { width: 90%; background: var(--bone-4, #888); opacity: .55; } 21.1% { width: 34%; background: var(--bone-4, #888); opacity: .55; } 31.7% { width: 60%; background: var(--bone-4, #888); opacity: .55; } 42.3% { width: 76%; background: var(--bone-4, #888); opacity: .55; } 52.9% { width: 42%; background: var(--bone-4, #888); opacity: .55; } 63.4% { width: 100%; background: var(--bone-4, #888); opacity: .55; } 74% { width: 74%; background: var(--brass, #B0281A); opacity: 1; } 88% { width: 74%; background: var(--brass, #B0281A); opacity: 1; } 100% { width: 70%; background: var(--bone-4, #888); opacity: .55; } }
.loader-text { display: grid; gap: 2px; min-width: 0; }
.loader-label { font-weight: 600; font-size: 14px; color: var(--bone, #111); font-variant-numeric: tabular-nums; }
.loader-stage { font-size: 12.5px; color: var(--bone-4, #666); }
.loader-sm { gap: 10px; margin: 6px 0; }
.loader-sm .loader-art { width: 28px; height: 36px; padding: 6px 4px 4px; border-width: 1px; }
.loader-sm .loader-art::before { width: 7px; height: 7px; }
.loader-sm .loader-art i { height: 2px; margin-bottom: 3px; }
.loader-sm .loader-art b { height: 1px; animation-name: ld-scan-sm; }
@keyframes ld-scan-sm { 0% { transform: translateY(4px); opacity: 0; } 6% { opacity: 1; } 84% { transform: translateY(33px); opacity: 1; } 90%, 100% { transform: translateY(33px); opacity: 0; } }
.loader-sm .loader-label { font-weight: 400; font-size: 13px; color: var(--bone-3, #464C50); }
@media (prefers-reduced-motion: reduce) {
  .loader-art i, .loader-art b { animation: none; }
  .loader-art i { background: var(--brass, #B0281A); opacity: 1; }
  .loader-art i:nth-child(1) { width: 100%; } .loader-art i:nth-child(2) { width: 68%; } .loader-art i:nth-child(3) { width: 86%; }
  .loader-art i:nth-child(4) { width: 56%; } .loader-art i:nth-child(5) { width: 92%; } .loader-art i:nth-child(6) { width: 74%; }
  .loader-art b { display: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
 * SHARED COMPONENTS (2026-10, the entry-flow pass)
 *
 * Used across index / account / compare / report, and by the workbench where it
 * wants them. Names the ROLE tokens; nothing here hard-codes a colour.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* Focus: 2px, visible on every interactive element. Nothing in this sheet
   removes an outline without drawing a replacement. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--c-accent); outline-offset: 2px;
}
.drop:focus-within, .pick:focus-within { outline: 2px solid var(--c-accent); outline-offset: 2px; border-color: var(--c-accent); }

/* Tap targets. A pointer gets 32px; a finger gets 44px. */
@media (pointer: coarse) {
  .nav a, .nav .mark, .nav .session button, .link, .del, .tab, .btn, .go, .doclink, .copyable,
  footer.site li a { min-height: 44px; }
  .nav a, .nav .session button { padding-inline: 4px; }
  td a, td button, .row-act { min-height: 44px; display: inline-flex; align-items: center; }
  footer.site li a { display: inline-flex; align-items: center; }
}

/* Buttons. .go stays as the original primary; .btn is the role-named family. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.6rem;
  font: inherit; font-family: var(--mono); font-size: 12.5px; letter-spacing: 0.1em; text-transform: uppercase;
  min-height: var(--hit); padding: 8px 16px; text-decoration: none; cursor: pointer;
  color: var(--c-text); background: transparent;
  border: 1px solid var(--c-text); border-radius: var(--r-0);
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.btn:hover { background: var(--c-text); color: var(--c-ground); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.45; cursor: default; pointer-events: none; }
.btn-primary { color: var(--c-on-accent); background: var(--c-accent); border-color: var(--c-accent); min-height: 44px; padding-inline: 22px; }
.btn-primary:hover { color: var(--c-on-accent); background: var(--c-text); border-color: var(--c-text); }
.btn-quiet { border-color: var(--c-rule-firm); color: var(--c-text-2); }
.btn-quiet:hover { border-color: var(--c-text); }
.btn-danger { color: var(--c-fault); border-color: var(--c-fault); }
.btn-danger:hover { color: var(--c-on-accent); background: var(--c-fault); border-color: var(--c-fault); }
.btn-sm { min-height: 28px; padding: 3px 10px; font-size: 11.5px; }

/* A tile: one measured thing and what it is. */
.tile { background: var(--c-sheet); border: 1px solid var(--c-rule); padding: var(--s-4); }
.tile h3 { margin: 0 0 var(--s-1); font-family: var(--mono); font-weight: 400; font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--c-text-3); }
.tile p { margin: 0; font-size: 13.5px; color: var(--c-text-2); }
.tile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--s-3); }

/* Empty and stateful blocks. A blank area is never the whole message. */
.empty-state, .state-block {
  border: 1px dashed var(--c-rule-firm); padding: var(--s-5); color: var(--c-text-3);
  font-size: 14px; background: var(--c-sheet);
}
.empty-state b, .state-block b { display: block; color: var(--c-text); margin-bottom: var(--s-1); }
.state-block.err { border-style: solid; border-color: var(--c-fault); color: var(--c-fault); background: var(--c-ground); }
.state-block .row { display: flex; gap: var(--s-3); align-items: center; flex-wrap: wrap; margin-top: var(--s-3); }

/* Toast. One polite live region, mounted by toast() in session.js. */
.toast-region { position: fixed; left: 50%; bottom: calc(var(--s-5) + var(--bottom-bar-h, 0px)); transform: translateX(-50%); z-index: 90; display: grid; gap: var(--s-2); justify-items: center; pointer-events: none; max-width: calc(100vw - 32px); }
.toast { background: var(--c-text); color: var(--c-ground); padding: 10px 16px; font-size: 13.5px; border-radius: var(--r-0); animation: toast-in var(--dur-1) var(--ease); }
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* A copyable address. One tap copies; the word Copied replaces it inline. */
.copyable {
  display: inline-flex; align-items: center; gap: 6px; vertical-align: baseline;
  font-family: var(--mono); font-size: 13px; color: var(--c-text);
  background: var(--c-sheet); border: 1px solid var(--c-rule-firm); border-radius: var(--r-0);
  padding: 1px 8px; min-height: var(--hit); cursor: copy; white-space: nowrap;
}
.copyable:hover { border-color: var(--c-text); }
.copyable.copied { border-color: var(--c-ok); color: var(--c-ok); }
.copyable { justify-content: center; }
.copyable .copied-word { font-family: var(--sans); font-size: 12px; letter-spacing: 0.04em; }

/* Skeleton rows: the shape of what is loading. */
.skeleton { display: block; height: 14px; margin: 10px 0; background: linear-gradient(90deg, var(--c-band) 25%, var(--c-sheet) 50%, var(--c-band) 75%); background-size: 200% 100%; animation: sk 1.4s linear infinite; }
.skeleton.w-60 { width: 60%; } .skeleton.w-80 { width: 80%; }
@keyframes sk { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton { animation: none; } }

/* Visually hidden, still announced. */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ── the first result: score, fix list, what an account adds ─────────────── */
.score-block { display: grid; grid-template-columns: auto 1fr; gap: var(--s-2) var(--s-5); align-items: start; margin: var(--s-5) 0 var(--s-3); }
.score-block .num { font-family: var(--mono); font-size: 56px; line-height: 1; color: var(--c-info); font-variant-numeric: tabular-nums; }
.score-block .num small { font-size: 18px; color: var(--c-text-4); }
.score-block .kicker { font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--c-text-3); }
.score-block h2 { margin: 2px 0 6px; padding: 0; border: 0; font-family: var(--sans); font-size: 19px; font-weight: 700; letter-spacing: -0.01em; text-transform: none; color: var(--c-text); }
.score-block p { margin: 0; color: var(--c-text-2); font-size: 14.5px; max-width: 62ch; }
.score-block.band-ready .num { color: var(--c-ok); }
.score-block.band-needs-work .num { color: var(--c-fault); }
.drivers { margin: var(--s-3) 0 0; padding: 0; list-style: none; display: grid; gap: var(--s-2); }
.drivers li { display: grid; grid-template-columns: minmax(120px, 190px) 1fr 54px; gap: var(--s-3); align-items: center; font-size: 13px; }
.drivers .track { display: block; height: 8px; background: var(--c-band); border: 1px solid var(--c-rule); }
.drivers .fill { display: block; height: 100%; background: var(--c-info); }
.drivers .pts { font-family: var(--mono); text-align: right; color: var(--c-text-3); font-size: 12px; }
@media (max-width: 560px) { .score-block { grid-template-columns: 1fr; } .drivers li { grid-template-columns: 1fr 54px; } .drivers .track { grid-column: 1 / -1; } }

.fixlist { list-style: none; margin: 0; padding: 0; counter-reset: fix; }
.fix { counter-increment: fix; position: relative; border: 1px solid var(--c-rule-firm); border-left: 3px solid var(--c-rule-firm); padding: var(--s-3) var(--s-4) var(--s-3) 44px; margin: 0 0 var(--s-2); background: var(--c-ground); }
.fix.fix-fault { border-left-color: var(--c-fault); }
.fix.fix-review { border-left-color: var(--c-warn); }
.fix::before { content: counter(fix); position: absolute; left: 14px; top: 12px; font-family: var(--mono); font-size: 15px; color: var(--c-text-3); }
.fix-head { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: baseline; }
.fix-head b { font-size: 15px; }
.fix-addrs { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-2); margin-top: var(--s-2); align-items: center; }
.fix-addrs .more { font-size: 12px; color: var(--c-text-4); }
.fix-step { margin: var(--s-2) 0 0; font-size: 13.5px; color: var(--c-text-2); }
.fix-step .lbl { font-family: var(--mono); font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--c-text-3); margin-right: 6px; }

.workbench-cta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4); border: 1px solid var(--c-text); padding: var(--s-4) var(--s-5); margin: var(--s-5) 0; background: var(--c-sheet); }
.workbench-cta p { margin: 0; flex: 1 1 280px; font-size: 14px; color: var(--c-text-2); }
.workbench-cta .doclink { font-size: 13.5px; padding: 14px 26px; }

.locked-row { margin: var(--s-6) 0 var(--s-5); border-top: 1px solid var(--c-rule-firm); padding-top: var(--s-4); }
.locked-row h2 { margin-top: 0; border: 0; padding: 0; color: var(--c-text-3); }
.locked-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 1px; background: var(--c-rule); border: 1px solid var(--c-rule); }
.locked-item { background: var(--c-ground); padding: var(--s-3) var(--s-4); display: flex; flex-direction: column; gap: var(--s-1); }
.locked-item b { font-size: 14px; }
.locked-item p { margin: 0; font-size: 13px; color: var(--c-text-3); flex: 1; }
.locked-item .preview { font-family: var(--mono); font-size: 12px; color: var(--c-text-4); border-top: 1px dashed var(--c-rule); padding-top: var(--s-1); margin-top: var(--s-1); }
.locked-item .btn { align-self: flex-start; margin-top: var(--s-2); }
.locked-note { font-size: 13px; color: var(--c-text-3); margin: 0 0 var(--s-3); max-width: 64ch; }

/* The one register sheet. Native <dialog>; Esc and the backdrop close it. */
dialog.sheet { border: 1px solid var(--c-text); border-radius: var(--r-0); padding: var(--s-5); max-width: min(460px, calc(100vw - 32px)); background: var(--c-ground); color: var(--c-text); box-shadow: 0 12px 40px rgb(20 24 26 / 0.25); }
dialog.sheet::backdrop { background: rgb(20 24 26 / 0.45); }
dialog.sheet h2 { margin: 0 0 var(--s-2); padding: 0; border: 0; font-family: var(--sans); font-size: 18px; text-transform: none; letter-spacing: -0.01em; color: var(--c-text); }
dialog.sheet p { margin: 0 0 var(--s-3); font-size: 14px; color: var(--c-text-2); }
dialog.sheet .row { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; margin-top: var(--s-4); }
dialog.sheet .fine { margin: var(--s-3) 0 0; }
dialog.sheet:not([open]) { display: none; }

.skip-link:focus { position: fixed; left: 8px; top: 8px; width: auto; height: auto; margin: 0; clip: auto; padding: 10px 14px; background: var(--c-text); color: var(--c-ground); z-index: 100; white-space: normal; }
.loader-slow { font-size: 12.5px; color: var(--c-text-3); }
.loader-live { position: absolute; }

/* ── round 2A additions ──────────────────────────────────────────────────────────── */

/* Tap targets, declared AFTER every component they apply to. The first coarse
   block sits above .btn-sm and lost the cascade to its 28px; this one is last. */
@media (pointer: coarse) {
  .btn-sm, .a-tab, #reg-in, .tab, #reg-close, .gloss-btn, .foot-tool { min-height: 44px; }
  #reg-in, .gloss-btn, .foot-tool { display: inline-flex; align-items: center; padding-inline: 6px; }
  body .a-tab { min-height: 44px; }
}

/* Safety banner: what reading a file does and does not do. */
.safety-banner {
  margin: var(--s-4) 0; padding: var(--s-3) var(--s-4);
  border: 1px solid var(--c-rule-firm); border-left: 3px solid var(--c-info);
  background: var(--c-sheet); color: var(--c-text-2); font-size: 14px; max-width: 70ch;
}
.safety-banner b { color: var(--c-text); font-weight: 600; }

/* Who-legend: what the three tags on a finding mean, inline next to them. */
.who-legend { margin: var(--s-3) 0 var(--s-4); display: grid; gap: var(--s-1); font-size: 13px; color: var(--c-text-3); max-width: 72ch; }
.who-legend div { display: flex; gap: var(--s-2); align-items: baseline; flex-wrap: wrap; }
.who-legend dt { margin: 0; flex: none; }
.who-legend dd { margin: 0; flex: 1 1 260px; }

.score-note { margin-top: var(--s-2) !important; font-size: 13px !important; color: var(--c-text-3) !important; }

/* One primary save, above the fix list. */
.save-cta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-4); margin: var(--s-4) 0 var(--s-3); }
.save-cta p { margin: 0; font-size: 13.5px; color: var(--c-text-3); flex: 1 1 260px; }

/* Glossary tooltip: a button that discloses one plain-language definition. */
.gloss { position: relative; display: inline; }
.gloss-btn {
  font: inherit; color: inherit; background: none; border: 0; padding: 0; margin: 0; cursor: help;
  text-decoration: underline dotted; text-underline-offset: 3px; text-transform: inherit; letter-spacing: inherit;
}
.gloss-btn:hover { color: var(--c-accent); }
.gloss-def {
  display: block; margin: var(--s-1) 0; padding: var(--s-2) var(--s-3);
  background: var(--c-sheet); border: 1px solid var(--c-rule-firm); color: var(--c-text-2);
  font-family: var(--sans); font-size: 13px; font-weight: 400; letter-spacing: 0; text-transform: none; line-height: 1.5;
  max-width: 52ch;
}

/* Footer tools (Clear this device). */
.foot-tool {
  font: inherit; font-family: var(--mono); font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase;
  color: inherit; background: none; border: 0; padding: 0; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px; min-height: var(--hit);
}
.foot-tool:hover { color: var(--c-text); }

/* The password field that appears in place when a project is protected. */
.pw-retry { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-3); align-items: flex-end; margin-top: var(--s-3); }
.pw-retry label { display: block; font-size: 13px; color: var(--c-text-2); flex: 1 1 100%; }
.pw-retry input { flex: 1 1 220px; max-width: 320px; }

/* A dialog with a field in it (project password). */
dialog.sheet input[type=password] { width: 100%; margin-top: var(--s-2); }

/* ── loader ────────────────────────────────────────────────────────────────
   A pointer travels a 4x3 grid in reading order and each cell it reaches lights
   and stays lit until the loop restarts: a file being unravelled, cell by cell. */
.loader { display: flex; align-items: center; gap: 16px; margin: 14px 0; }
.loader-art { position: relative; flex: none; width: 76px; height: 58px; }
.loader-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; width: 100%; height: 100%; }
.loader-grid i { background: var(--line-2, #ccc); animation: loader-cell 3.6s steps(1, end) infinite; animation-delay: calc(var(--i) * .3s); }
.loader-ptr { position: absolute; left: 0; top: 0; color: var(--bone, #111); animation: loader-ptr 3.6s linear infinite; filter: drop-shadow(0 1px 1px rgba(0,0,0,.25)); }
.loader-text { display: grid; gap: 2px; min-width: 0; }
.loader-label { font-weight: 600; font-size: 14px; color: var(--bone, #111); font-variant-numeric: tabular-nums; }
.loader-stage { font-size: 12.5px; color: var(--bone-4, #666); }
@keyframes loader-cell { 0% { background: var(--line-2, #ccc); } 8% { background: var(--brass, #B0281A); } 100% { background: var(--brass, #B0281A); } }
/* Row 1 → row 2 → row 3, left to right, 12 steps of .3s. */
@keyframes loader-ptr {
  0%      { transform: translate(4px, 4px); }
  7.5%    { transform: translate(23px, 4px); }
  15%     { transform: translate(42px, 4px); }
  22.5%   { transform: translate(61px, 4px); }
  25%     { transform: translate(4px, 22px); }
  32.5%   { transform: translate(23px, 22px); }
  40%     { transform: translate(42px, 22px); }
  47.5%   { transform: translate(61px, 22px); }
  50%     { transform: translate(4px, 40px); }
  57.5%   { transform: translate(23px, 40px); }
  65%     { transform: translate(42px, 40px); }
  72.5%   { transform: translate(61px, 40px); }
  100%    { transform: translate(61px, 40px); }
}
@media (prefers-reduced-motion: reduce) {
  .loader-grid i { animation: none; background: var(--brass, #B0281A); opacity: .5; }
  .loader-ptr { animation: none; transform: translate(61px, 40px); }
}
