/* ==========================================================================
   CueSeal family — shared stylesheet (edition 2026.5)
   Pages: cueseal-website, genlock-website, muxnode-website, cuebus-website,
          docs-website, 404

   The pages present as specification documents rather than product pages.
   Dark is the default and reads as a bench under a lamp; light follows the
   reader's system setting. Body and mono are system stacks, but the display
   face is not: one subsetted variable woff2, self-hosted and preloaded once
   per page, which is why font-src is 'self' rather than 'none'. Section 0
   below carries the @font-face and the reasoning.

   Tiering: scheme layer -> brand layer -> components. One accent mechanism:
   a `.brand-*` class swaps the four brand tokens, so a single class re-skins
   a page (or one card). Derived tints are computed at the use site with
   color-mix() so they follow whichever accent is in scope.

   Contrast is measured, never asserted here. `node scripts/check-contrast.mjs`
   reads this file and gates every pair in both schemes; the numbers live in
   cueseal-ui/ACCESSIBILITY_AUDIT.md. Do not transcribe ratios into comments —
   that is how an audit and a stylesheet drift apart.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Scheme layer — presentation, not identity. Nothing here is per-site.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   0. The display face

   Saira Variable, subsetted, self-hosted. ONE file carries the whole 100 to 900
   range, which is why this is 19 KB rather than the roughly 250 KB of nine
   static faces that were dropped from this project once before. font-src moved
   from 'none' to 'self' to admit it: same origin only, no CDN, no third party.

   Licence: SIL Open Font License 1.1, Copyright 2020 The Saira Project Authors.
   The licence text ships beside the file as assets/saira-OFL.15698e92b2.txt. Keep it.

   font-display: swap is deliberate. The fallback stack below is metrically
   close enough that the swap is a weight change rather than a reflow, and a
   heading that is invisible for 3 seconds is worse than one that changes face.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Saira Variable";
  src: url("./assets/saira-display.5e3a94b283.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: dark light;

  --bg:            #101215;
  --bg-sunken:     #16191d;
  --surface:       #1a1e23;
  --ink:           #e9ebee;
  --ink-muted:     #aab1ba;
  --ink-faint:     #8b929b;

  /* Hairlines. Decorative separators, so WCAG 1.4.11 does not apply and the
     gate reports them without failing on them. Anything that bounds a control
     uses --border-ui instead, which is held to 3:1. */
  --rule:          #2b3037;
  --rule-strong:   #444b54;
  --border-ui:     #737c89;

  /* Scheme-level, deliberately NOT per-site. A 3px ring in Cuebus red reads
     as an error state; one fixed blue reads as focus on all four sites. */
  --focus:         #9cc4ff;
  --perf:      rgba(233, 235, 238, 0.07);
  --tape:      #2b2822;
  --tape-edge: #45402f;

  /* Artwork behind a custom property, for the reason --mark was in v14: the
     standalone build overrides this one declaration instead of inlining the
     image at each use site, and a custom property is the only dynamic styling
     the CSP allows. build-standalone.mjs asserts this token still exists. */
  --glyph:         url("./assets/cueseal-rosette.67f1a1e65e.png");

  /* The four plate ramps, straight off the supplied artwork. Global rather
     than per-brand because a tile for a SIBLING site has to wear that site's
     plate on a page branded for another one. The per-brand --plate-lo and
     --plate-hi below alias these, so there is one place to change a value. */
  --plate-cueseal-lo: #f19a5c; --plate-cueseal-hi: #f9db6f;
  --plate-genlock-lo: #f093ff; --plate-genlock-hi: #fdf1ff;
  --plate-muxnode-lo: #1b9986; --plate-muxnode-hi: #c4fff0;
  --plate-cuebus-lo:  #e52b2b; --plate-cuebus-hi:  #731919;

  /* The parent mark's own ramp, filled straight through the aperture rather
     than sitting on a plate. Dark keeps the supplied artwork untouched, so it
     aliases the plate ramp rather than restating it; light drops one OKLCh
     lightness step so it still holds at 44px against --surface. */
  --seal-lo: var(--plate-cueseal-lo); --seal-hi: var(--plate-cueseal-hi);

  /* --- Type ------------------------------------------------------------- */
  --font-display: "Saira Variable", ui-sans-serif, system-ui, -apple-system,
                  "Segoe UI Variable Display", "Segoe UI", Roboto, "Helvetica Neue",
                  Arial, sans-serif;
  --font-body:    system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono:    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* Fluid scale, clamped so it never collapses on small screens
     and never overruns the measure on large ones. */
  --step--1: clamp(0.813rem, 0.79rem + 0.11vw, 0.875rem);
  --step-0:  clamp(1rem,     0.97rem + 0.15vw, 1.063rem);
  --step-1:  clamp(1.125rem, 1.07rem + 0.28vw, 1.313rem);
  --step-2:  clamp(1.375rem, 1.28rem + 0.47vw, 1.688rem);
  --step-3:  clamp(1.75rem,  1.58rem + 0.85vw, 2.313rem);
  --step-4:  clamp(2.125rem, 1.79rem + 1.68vw, 3.25rem);

  --leading-tight: 1.18;
  --leading-body:  1.65;
  /* `ch` is the advance width of "0", which is wider than the average glyph in
     a proportional face, so a `Nch` limit renders roughly 1.33N characters.
     These two were calibrated by counting real rendered line breaks, not by
     arithmetic: 54ch lands at 72 characters and 44ch at 59, both inside the
     45 to 75 band. The old 68ch was rendering 91. */
  --measure:       54ch;
  --measure-tight: 44ch;

  /* --- Space (4px base) -------------------------------------------------- */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4.5rem;
  --sp-9: 6.5rem;

  --radius:   4px;
  --radius-lg: 8px;

  --shell:      74rem;
  --shell-pad:  clamp(1.25rem, 5vw, 3rem);

  --dur: 160ms;
  --ease: cubic-bezier(0.2, 0, 0.2, 1);
}

@media (prefers-color-scheme: light) {
  :root {
    --bg:          #faf9f6;
    --bg-sunken:   #f2f0ea;
    --surface:     #ffffff;
    --ink:         #16181c;
    --ink-muted:   #4c525b;
    --ink-faint:   #666c75;
    --rule:        #ddd9cf;
    --rule-strong: #b9b4a8;
    --border-ui:   #8c8779;
    --focus:       #0b57d0;
    --perf:      rgba(22, 24, 28, 0.085);
    --tape:      #f5f1e4;
    --tape-edge: #ddd3b4;
    --seal-lo:   #cf7b3c;
    --seal-hi:   #d8ba4d;
  }
}

/* --------------------------------------------------------------------------
   1. Brand layer — one class re-skins a page (or a card). Four values, two
   schemes, four sites. This is the ONLY per-site difference in the file, and
   the reason it is a class rather than a per-site :root is that one stylesheet
   is served to four origins: a :root override would paint all four the same.

   TWO ROLES, NOT INTERCHANGEABLE:

     --accent         Text-safe. Links, primary-button fill, kickers, card
                      titles on hover. Held to 4.5:1 (WCAG 1.4.3) against
                      --bg, --bg-sunken, --surface and --accent-soft.
     --accent-strong  The hover value. Same bar, same four surfaces.
     --accent-soft    Callout field and ::selection. Must carry --ink and
                      --ink-muted at 4.5:1, and --focus at 3:1.
     --brand          The logo hue. NON-TEXT ONLY: the 3px masthead rule, the
                      section-number rule, the callout edge, the card hover
                      border. Held to 3:1 (WCAG 1.4.11).

   Do not merge --accent and --brand. A hue dark enough to clear 4.5:1 as body
   text is no longer the logo colour: CueSeal's amber becomes olive, Genlock's
   magenta becomes plum. Merging them either breaks contrast or drains the
   brand, so they are two tokens and the gate holds each to its own threshold.

   The margins are thin, and --surface is where they are thinnest. Moving a
   surface value is exactly what broke AA here once before. Re-run
   `node scripts/check-contrast.mjs` after touching any of it.

   There is no --accent-ink: .btn--primary takes `color: var(--bg)`, which is
   scheme-derived and clears 4.5:1 for every brand in both schemes. One fewer
   token per site to get wrong.
   -------------------------------------------------------------------------- */
:root, .brand-cueseal { --accent: #b28734; --accent-strong: #c7a057; --accent-soft: #2a2110; --brand: #a56f09; --brand-lo: #a56f09; --brand-hi: #f2a30e; --plate-lo: var(--plate-cueseal-lo); --plate-hi: var(--plate-cueseal-hi); }
.brand-genlock        { --accent: #c559cf; --accent-strong: #d188d7; --accent-soft: #28102a; --brand: #b70ac7; --brand-lo: #b70ac7; --brand-hi: #e42ef5; --plate-lo: var(--plate-genlock-lo); --plate-hi: var(--plate-genlock-hi); }
.brand-muxnode        { --accent: #34b295; --accent-strong: #57c7ad; --accent-soft: #102a24; --brand: #189579; --brand-lo: #189579; --brand-hi: #23dbb2; --plate-lo: var(--plate-muxnode-lo); --plate-hi: var(--plate-muxnode-hi); }
.brand-cuebus         { --accent: #d26565; --accent-strong: #db9494; --accent-soft: #2a1010; --brand: #cc1e1e; --brand-lo: #cc1e1e; --brand-hi: #e65656; --plate-lo: var(--plate-cuebus-lo); --plate-hi: var(--plate-cuebus-hi); }

@media (prefers-color-scheme: light) {
  :root, .brand-cueseal { --accent: #7b5814; --accent-strong: #563c0b; --accent-soft: #f7f3eb; --brand: #b87c0a; --brand-lo: #6b4806; --brand-hi: #b87c0a; --plate-lo: var(--plate-cueseal-lo); --plate-hi: var(--plate-cueseal-hi); }
  .brand-genlock        { --accent: #9f1cab; --accent-strong: #7e1188; --accent-soft: #f6ebf7; --brand: #e32ff4; --brand-lo: #b70bc7; --brand-hi: #e32ff4; --plate-lo: var(--plate-genlock-lo); --plate-hi: var(--plate-genlock-hi); }
  .brand-muxnode        { --accent: #116955; --accent-strong: #084437; --accent-soft: #ebf7f4; --brand: #199a7c; --brand-lo: #0e5443; --brand-hi: #199a7c; --plate-lo: var(--plate-muxnode-lo); --plate-hi: var(--plate-muxnode-hi); }
  .brand-cuebus         { --accent: #b81e1e; --accent-strong: #961313; --accent-soft: #f7ebeb; --brand: #e65b5b; --brand-lo: #d02020; --brand-hi: #e65b5b; --plate-lo: var(--plate-cuebus-lo); --plate-hi: var(--plate-cuebus-hi); }
}

/* ==========================================================================
   Reset and base
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: var(--leading-body);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* The design-canvas runtime re-renders the body content into its own wrapper
   after load, so the surface has to be painted on .page as well as on body.
   Without this, a page opened straight from cueseal-ui/ flashes an unstyled
   band while the runtime settles, and stays that way if it never arrives. */
.page {
  background: var(--bg);
  color: var(--ink);
}

/* The masthead is sticky above 48rem, so without this an anchor jump lands
   with the heading it targeted hidden underneath it. Ten in-page links on the
   CueSeal page alone depend on it. Below 48rem the masthead scrolls away and
   the offset would just push the target down the viewport for no reason. */
[id] { scroll-margin-block-start: 5.5rem; }
@media (max-width: 48rem) {
  [id] { scroll-margin-block-start: 0; }
}

:where(h1, h2, h3, h4, p, ul, ol, dl, figure, table, pre) { margin: 0; }

img, svg { max-inline-size: 100%; block-size: auto; }

/* Visible focus everywhere, on every interactive element, always. */
:where(a, button, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  border-radius: 2px;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
a:hover { color: var(--accent-strong); text-decoration-thickness: 2px; }

::selection { background: var(--accent-soft); color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* ==========================================================================
   Layout primitives
   ========================================================================== */

.shell {
  inline-size: min(100% - (var(--shell-pad) * 2), var(--shell));
  margin-inline: auto;
}

/* Extra separation where a block follows prose it is not part of. A class
   rather than a style= attribute: the CSP has no unsafe-inline, so an inline
   style is dead markup on the deployed page while looking right locally. */
.flow-lg { margin-block-start: var(--sp-6); }

.skip {
  position: absolute;
  inset-block-start: -100%;
  inset-inline-start: var(--sp-4);
  z-index: 20;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border-ui);
  border-radius: var(--radius);
  font-weight: 600;
  text-decoration: none;
}
.skip:focus { inset-block-start: var(--sp-4); }

.visually-hidden {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap;
}

/* ==========================================================================
   Masthead
   ========================================================================== */

.masthead {
  /* The solid brand rule is the fallback and it must be a real colour, not
     transparent: border-image is dropped entirely in forced-colors mode, and a
     transparent fallback takes the whole masthead rule with it. Declared first
     so border-image overrides it wherever border-image is honoured. */
  border-block-start: 3px solid var(--brand);
  border-image: linear-gradient(90deg, var(--brand-lo), var(--brand-hi)) 1;
  border-block-end: 1px solid var(--rule);
  background: var(--bg);
  position: sticky;
  inset-block-start: 0;
  z-index: 10;
}

.masthead__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4) var(--sp-6);
  padding-block: var(--sp-4);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  min-block-size: 44px;
}
/* align-content: center was declared here and did nothing. This is a
   single-line flex container: there is no flex-wrap, so there is never more
   than one line for align-content to distribute, and align-items above is what
   actually centres the row. Removed rather than left to read as load-bearing.
   If .wordmark ever gains flex-wrap, it comes back with it. */
.wordmark:hover { color: var(--ink); }
/* Column gap is wider than the gap the artwork puts between the letters and
   the mark (36 of 1269 units, about 7px as drawn). That ordering is the point:
   the lockup has to read as one object with a tagline beside it, not as three
   evenly spaced things. At the old --sp-3 the two gaps measured 18px and 16px
   and the tagline looked like part of the mark.

   Row gap only applies once this wraps, which it does below roughly 30rem. It
   is not zero because .wordmark__sub is shifted up, and on a wrapped line that
   shift eats into the space above it. */
.wordmark__text {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
}

/* The colour tile carries its own background, so it needs no scheme handling.
   The hairline keeps a pale tile from bleeding into a pale page. */
.mark {
  flex: none;
  inline-size: 2rem;
  block-size: 2rem;
  border-radius: 6px;
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.16);
}
.mark--sm {
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border-radius: 4px;
}

/* The CueSeal rosette as an alpha mask over currentColor, so one asset serves
   both colour schemes. A flat black PNG would vanish on the dark scheme. */
.glyph {
  display: inline-block;
  inline-size: 1.15em;
  block-size: 1.15em;
  vertical-align: -0.22em;
  background-color: currentColor;
  -webkit-mask: var(--glyph) center / contain no-repeat;
          mask: var(--glyph) center / contain no-repeat;
}
/* The lift is not a nudge to taste, it is the artwork's own dead space.
   An SVG set to display:block has no text baseline, so `align-items: baseline`
   on the parent falls back to its bottom margin edge. The wordmark's glyphs
   stop 11% above that edge (they span 23.9 to 181.5 of a 204 box), so the
   tagline was landing 11% of the lockup height too low: 5px at 1.85em, plainly
   visible once the lockup grew. 0.25rem puts it back on the wordmark's
   baseline. The value is flat rather than em-based because --step-1 clamps
   between 1.125rem and 1.313rem, over which the true offset moves 3.7px to
   4.3px; one figure covers the range to within a third of a pixel, and an em
   here would be relative to --step--1, the wrong font size to key it to.
   Revisit if .wordmark__svg block-size changes. */
.wordmark__sub {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--ink-faint);
  translate: 0 -0.25rem;
}

.nav { margin-inline-start: auto; }
.nav ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-5);
  list-style: none;
  padding: 0;
}
.nav a {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  font-size: var(--step--1);
  letter-spacing: 0.01em;
  color: var(--ink-muted);
  text-decoration: none;
}
.nav a:hover,
.nav a[aria-current="page"] { color: var(--ink); text-decoration: underline; }

/* Below the point where the nav wraps to a second row, a sticky masthead
   costs more viewport than it returns. Let it scroll away. */
@media (max-width: 48rem) {
  .masthead { position: static; }
}

/* ==========================================================================
   Document strip: the spec-sheet header that gives every page a record
   ========================================================================== */

.docstrip {
  border-block-end: 1px solid var(--rule);
  background: var(--bg-sunken);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--ink-muted);
}
.docstrip dl {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-6);
  padding-block: var(--sp-3);
}
.docstrip div { display: flex; gap: var(--sp-2); }
.docstrip dt { color: var(--ink-faint); }
.docstrip dt::after { content: ":"; }
.docstrip dd { margin: 0; color: var(--ink); }

/* ==========================================================================
   Hero
   ========================================================================== */

.hero { padding-block: var(--sp-8) var(--sp-7); }

.hero__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-block-end: var(--sp-4);
}

.hero h1 {
  font-family: var(--font-display);
  font-size: var(--step-4);
  line-height: var(--leading-tight);
  letter-spacing: -0.032em;
  font-weight: 800;
  max-inline-size: 24ch;
  text-wrap: balance;
}

.hero__lede {
  margin-block-start: var(--sp-5);
  font-size: var(--step-1);
  color: var(--ink-muted);
  max-inline-size: 58ch;
  text-wrap: pretty;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-block-start: var(--sp-6);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 44px;
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  border: 1px solid transparent;
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.btn--primary {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}
.btn--primary:hover { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.btn--quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--border-ui);
}
.btn--quiet:hover { background: var(--bg-sunken); color: var(--ink); border-color: var(--ink-faint); }

/* ==========================================================================
   Sections
   ========================================================================== */

.section {
  padding-block: var(--sp-7);
  border-block-start: 1px solid var(--rule);
}

.section__head {
  display: grid;
  gap: var(--sp-2);
  margin-block-end: var(--sp-6);
}

.section__num {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  color: var(--ink-faint);
}

.section h2 {
  font-family: var(--font-display);
  font-size: var(--step-3);
  line-height: 1.22;
  letter-spacing: -0.025em;
  font-weight: 800;
  max-inline-size: 30ch;
  text-wrap: balance;
}

.section__intro {
  margin-block-start: var(--sp-3);
  color: var(--ink-muted);
  max-inline-size: var(--measure);
  text-wrap: pretty;
}

/* Wide viewports get the specification-sheet rail: the section number sits
   in a left margin column and all section content aligns under the heading.
   Below this width everything stacks in source order, unchanged. */
@media (min-width: 62rem) {
  :root { --rail: 7rem; --rail-gap: var(--sp-7); }

  .section__head {
    grid-template-columns: var(--rail) minmax(0, 1fr);
    column-gap: var(--rail-gap);
    align-items: start;
  }
  .section__num {
    grid-column: 1; grid-row: 1;
    padding-block-start: 0.55em;
    border-block-start: 2px solid var(--brand);
    text-align: start;
  }
  .section h2      { grid-column: 2; grid-row: 1; }
  .section__intro  { grid-column: 2; grid-row: 2; margin-block-start: var(--sp-4); }

  .section > :not(.section__head) {
    margin-inline-start: calc(var(--rail) + var(--rail-gap));
  }
}

.prose > * + * { margin-block-start: var(--sp-4); }
.prose { max-inline-size: var(--measure); }
.prose h3 {
  font-size: var(--step-1);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-block-start: var(--sp-6);
}
.prose ul, .prose ol { padding-inline-start: var(--sp-5); }
.prose li + li { margin-block-start: var(--sp-2); }

/* ==========================================================================
   Card grid
   ========================================================================== */

.grid {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}

.grid--quad {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13.5rem), 1fr));
}

/* Stranded last card.
   `auto-fit` picks a column count from the available width and knows nothing
   about how many cards there are, so it happily lands on 3+1 and 2+1. What
   makes the band wide here is the section rail: at 62rem the rail and its gap
   take 10rem out of the content column, so that column gets NARROWER as the
   viewport grows past 62rem, and a four-card grid falls back to three columns
   from roughly 62rem to 73rem. Pinning the count by child count fixes both the
   arithmetic and the rail step in one place.

   Three cards: three across, then one. Three across still measures ~213px at
   48rem, which holds.
   Four cards: four across only above the rail step, then 2x2, then one. */
.grid:has(> :nth-child(3):last-child) {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid:has(> :nth-child(4):last-child),
.grid--quad:has(> :nth-child(4):last-child) {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 73rem) {
  .grid:has(> :nth-child(4):last-child),
  .grid--quad:has(> :nth-child(4):last-child) {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
@media (max-width: 47.99rem) {
  .grid:has(> :nth-child(3):last-child) { grid-template-columns: 1fr; }
}
@media (max-width: 33.99rem) {
  .grid:has(> :nth-child(4):last-child),
  .grid--quad:has(> :nth-child(4):last-child) { grid-template-columns: 1fr; }
}

.card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
}
.card--link { transition: border-color var(--dur) var(--ease); }
.card--link:hover { border-color: var(--brand); }

.card__kicker {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.card h3 {
  font-size: var(--step-1);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.card h3 a { text-decoration: none; color: var(--ink); }
.card h3 a::after { content: ""; position: absolute; inset: 0; } /* whole-card target */
.card h3 a:hover { color: var(--accent); }
.card--link { position: relative; }
.card p { color: var(--ink-muted); }
.card__meta {
  margin-block-start: auto;
  padding-block-start: var(--sp-3);
  border-block-start: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--ink-faint);
}

/* ==========================================================================
   Definition rows and specification tables
   ========================================================================== */

.deflist {
  display: grid;
  gap: 0;
  border-block-start: 1px solid var(--rule);
}
.deflist > div {
  display: grid;
  grid-template-columns: minmax(9rem, 15rem) 1fr;
  gap: var(--sp-3) var(--sp-6);
  padding-block: var(--sp-4);
  border-block-end: 1px solid var(--rule);
}
@media (max-width: 44rem) {
  .deflist > div { grid-template-columns: 1fr; gap: var(--sp-2); }
}
.deflist dt {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.02em;
  color: var(--ink);
  padding-block-start: 0.2em;
}
.deflist dd { margin: 0; color: var(--ink-muted); max-inline-size: 58ch; }

.table-wrap { overflow-x: auto; }
table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--step--1);
}
caption {
  text-align: start;
  color: var(--ink-faint);
  font-family: var(--font-mono);
  padding-block-end: var(--sp-3);
}
th, td {
  text-align: start;
  vertical-align: top;
  padding: var(--sp-3) var(--sp-4);
  border-block-end: 1px solid var(--rule);
}
thead th {
  color: var(--ink-faint);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.8em;
  border-block-end: 1px solid var(--rule-strong);
  white-space: nowrap;
}
tbody th { font-weight: 600; color: var(--ink); }
td { color: var(--ink-muted); }

code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  background: var(--bg-sunken);
  padding: 0.1em 0.35em;
  border-radius: 3px;
  color: var(--ink);
}

/* ==========================================================================
   Callout: one restrained emphasis block, no pull-quote styling
   ========================================================================== */

.callout {
  padding: var(--sp-5) var(--sp-6);
  background: var(--accent-soft);
  border: 1px solid var(--rule);
  border-inline-start: 3px solid var(--brand);
  border-radius: var(--radius);
  max-inline-size: var(--measure);
}
.callout h3 {
  font-size: var(--step-0);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink);
  margin-block-end: var(--sp-2);
}
.callout p { color: var(--ink-muted); }

/* ==========================================================================
   Ledger sample (the audit-record figure)
   ========================================================================== */

.ledger {
  background: var(--bg-sunken);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--sp-5);
  overflow-x: auto;
}
.ledger pre {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  line-height: 1.7;
  color: var(--ink-muted);
  white-space: pre;
}
.ledger figcaption {
  margin-block-start: var(--sp-4);
  padding-block-start: var(--sp-3);
  border-block-start: 1px solid var(--rule);
  font-size: var(--step--1);
  color: var(--ink-faint);
}

/* ==========================================================================
   Footer
   ========================================================================== */

.site-footer {
  margin-block-start: var(--sp-8);
  border-block-start: 1px solid var(--rule);
  background: var(--bg-sunken);
  padding-block: var(--sp-7) var(--sp-6);
  font-size: var(--step--1);
}
.footer-grid {
  display: grid;
  gap: var(--sp-6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
}

/* The footer sits outside the rail, so its step is simply where four columns
   stop clearing the 13rem track. */
.footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 64rem) {
  .footer-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 33.99rem) {
  .footer-grid { grid-template-columns: 1fr; }
}
.footer-grid h2 {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 400;
  margin-block-end: var(--sp-3);
}
.footer-grid ul { list-style: none; padding: 0; }
.footer-grid li + li { margin-block-start: var(--sp-2); }
/* gap rather than a margin on the mark, matching .card__kicker: a text-only
   link is a single anonymous flex item, so the gap costs it nothing, and the
   cross-links get their separation without a style= attribute the CSP drops. */
.footer-grid a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: 32px;
  color: var(--ink-muted);
  text-decoration: none;
}
.footer-grid a:hover { color: var(--ink); text-decoration: underline; }
.footer-grid p { color: var(--ink-muted); }

.colophon {
  align-items: center;
  margin-block-start: var(--sp-6);
  padding-block-start: var(--sp-4);
  border-block-start: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  color: var(--ink-faint);
  font-family: var(--font-mono);
}

@media print {
  .masthead { position: static; }
  .actions, .skip { display: none; }
  body { background: #fff; color: #000; }
}


/* ==========================================================================
   Bench layer
   The chassis stays a specification document. These are the places it is
   allowed to look like it was built on a bench rather than printed by an
   institute. Each one is budgeted: the perfboard field appears once per page,
   the plate once, the bench note at most once.
   ========================================================================== */

/* .wordmark__svg was declared here. It moved to "2. The masthead lockup"
   below, which restates every one of its declarations, so the copy here was
   contributing nothing to computed style and has been removed rather than
   left to look load-bearing. Not the same case as .faceplate, which is
   re-opened later to override two properties and genuinely needs both halves. */

/* Perfboard. A dot pitch, faded out downward so the hero sits on it rather
   than inside a box. Decoration: nothing reads against it. */
.hero {
  position: relative;
  isolation: isolate;
}
.hero::before {
  content: "";
  position: absolute;
  /* Top inset was -32px, which painted the dot field over the tape strip
     above it: .hero is its own stacking context but the strip is an earlier
     sibling, so a negative z-index inside the hero still covers it. */
  inset: 0 calc(var(--shell-pad) * -1) 0;
  z-index: -1;
  background-image: radial-gradient(circle at 1px 1px, var(--perf) 1px, transparent 0);
  background-size: 14px 14px;
  -webkit-mask-image: linear-gradient(to bottom, #000 42%, transparent 96%);
          mask-image: linear-gradient(to bottom, #000 42%, transparent 96%);
  pointer-events: none;
}


/* Label tape. Used for the document strip, so the page's own metadata reads
   like something applied to the equipment rather than typeset with it. */
.docstrip {
  background: var(--tape);
  border-block-end: 1px solid var(--tape-edge);
}
.docstrip dd {
  border: 1px solid var(--tape-edge);
  border-radius: 3px;
  padding: 0.1em 0.45em;
  background: var(--surface);
}

/* Silkscreen reference designator. The section number is set the way a board
   labels a component: big, flat, low-contrast, sitting in the margin. */
@media (min-width: 62rem) {
  .section__num {
    font-size: var(--step-2);
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--ink-faint);
    padding-block-start: 0.25em;
    border-block-start-width: 3px;
  }
}

/* Bench note. One per page. The tilt is a static half-degree: enough to read
   as applied by hand, small enough that no line looks broken. */
.note-bench {
  position: relative;
  max-inline-size: 46ch;
  margin-block: var(--sp-6);
  padding: var(--sp-4) var(--sp-5);
  background: var(--tape);
  border: 1px solid var(--tape-edge);
  border-radius: 2px;
  rotate: -0.5deg;
  box-shadow: 0 1px 0 0 var(--rule);
}
.note-bench h3 {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-block-end: var(--sp-2);
}
.note-bench p { color: var(--ink-muted); }

/* Corner tick on a linked card: a registration mark, not a shadow. */
.card--link::after {
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline-end: -1px;
  inline-size: 12px;
  block-size: 12px;
  border-block-start: 2px solid var(--brand);
  border-inline-end: 2px solid var(--brand);
  border-start-end-radius: var(--radius-lg);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.card--link:hover::after,
.card--link:focus-within::after { opacity: 1; }

/* Device label on a record figure. */
.ledger__tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.05em 0.4em;
  margin-block-end: var(--sp-3);
}


/* ==========================================================================
   Typographic corrections, edition 2026.3
   Each of these came out of measuring the rendered page rather than reading
   the stylesheet. The numbers in the comments are what was measured.
   ========================================================================== */

/* Secondary blocks read at a shorter measure than running prose: a standfirst,
   a definition body and a callout are all read in one pass, not scanned. */
.hero__lede,
.deflist dd,
.callout { max-inline-size: var(--measure-tight); }

/* Headings inherit the 1.65 body line-height, which is a body rhythm on a
   heading. h1 and h2 already set their own; h3 never did, so a two-line card
   title opened to 34.7px of leading on 21px of type. */
h3 { line-height: 1.28; }

/* Three different h3 sizes were in play with no system behind them. Two of
   them are real headings and should match; the other two are labels and are
   left alone, because uppercase mono at label size is doing a different job. */
.prose h3 { font-size: var(--step-1); }

/* Table headers were 0.8em of --step--1, which computes to 11.2px: the
   smallest text on the site, and it sits on the row you scan first. */
thead th { font-size: 0.9em; }

/* A four-up card grid gives a 194px column, which rendered 19 characters per
   line and split "SCTE-104" across a line break. Cards in that grid step down
   a size and give back some padding, which takes the column to 30 characters:
   short enough to stay a scannable blurb, wide enough to stop breaking terms. */
@media (min-width: 73rem) {
  .grid--quad .card { padding: var(--sp-4); }
  .grid--quad .card > p:not([class]) { font-size: var(--step--1); }
}

/* Numbers in a specification document line up in columns. Free, and it is the
   difference between a table you can scan down and one you cannot. */
.ledger pre,
.docstrip,
table,
.tok { font-variant-numeric: tabular-nums; }

/* A standards identifier is one token. "SCTE-104" broken across two lines
   reads as "SCTE-" then "104", which in a document whose whole argument rests
   on precision is a correctness problem, not a typographic one. */
.tok {
  white-space: nowrap;
  hyphens: none;
}

/* Last lines of a paragraph do not get left as one word. */
.prose p,
.section__intro,
.card p,
.callout p,
.note-bench p,
.deflist dd { text-wrap: pretty; }

/* An in-page jump should land somewhere visible. The masthead scroll-margin
   already puts the heading clear; this says which one you asked for, then
   gets out of the way. No JavaScript, so it persists rather than fading:
   that is the honest trade for a CSS-only affordance. */
.section:target .section__num { color: var(--accent); }
.section:target > .section__head > h2 { text-decoration: underline; }
.section:target > .section__head > h2 { text-decoration-color: var(--brand); }
.section:target > .section__head > h2 { text-underline-offset: 0.22em; }
.section:target > .section__head > h2 { text-decoration-thickness: 3px; }

/* Forced-colors mode strips backgrounds, gradients and border-image and
   substitutes the user's own palette. Most of the bench layer is decoration and
   is meant to go. Two things are not: the plate is a filled tile whose fill
   disappears, so it gets an outline to stay a shape, and the card keeps its
   edge for the same reason. The wordmark needs nothing, because it is
   currentColor and inherits the forced text colour, which is the whole reason
   it is drawn rather than shipped as an image. */
@media (forced-colors: active) {
  .card  { border-color: CanvasText; }
}

/* ==========================================================================
   Bar layer — the reference bar

   A colour bar in this domain is not decoration, it is a reference signal: a
   pattern you measure a system against. That is the same argument the rest of
   the site makes, so the bars are built as a reference and used sparingly,
   rather than sprayed on as a motif.

   THREE ROLES NOW, NOT TWO. --accent is text-safe at 4.5:1, --brand is the
   logo hue at 3:1 non-text, and --bar-* is a third role: the same hue as the
   brand, rendered at its LEVEL IN THE BAR rather than at its logo value. A bar
   segment is a signal level. Forcing it to equal --brand flattens the ladder,
   which is the one thing a bar cannot lose.

   THE LADDER IS THE POINT. Segment luminances keep the order and spacing of a
   true 75% bar, compressed into the range the page can carry. That ordering is
   why the pattern still reads in greyscale, on a monochrome display, and to a
   viewer with any of the three common colour vision deficiencies. The derivation
   and its arithmetic are in cueseal-system/01_COLOUR.md; `python checks/derive.py`
   regenerates the ladder from the rule and asserts it still descends. Re-run it
   if a brand hue moves.

   WHERE A BAR MAY GO. Anywhere, but only ever as decoration: never the sole
   boundary of a control, and never the only thing separating one state from
   another. Nothing places one that way today -- every bar-coded element sits
   beside text that already names the same thing, so the colour is never
   carrying the fact by itself.

   NO RATIOS ARE QUOTED IN THIS PARAGRAPH, ON PURPOSE. It used to claim every
   segment cleared 3:1 against --bg and --surface, and that was false in both
   schemes: two segments do not. It went unnoticed for exactly the reason this
   file's header gives for banning the practice -- a number in a comment is
   asserted, not measured, so nothing tells you when it stops being true. The
   numbers live in cueseal-ui/ACCESSIBILITY_AUDIT.md, and `check-contrast.mjs`
   now measures every segment against every surface in both schemes and prints
   them beside the hairlines: reported, never gated, because a bar is decoration.
   It reads the values this file ships rather than the ones a derivation would
   produce. They are not the same in light.

   NO TEXT EVER SITS ON A BAR. The lightest and darkest segments sit close enough
   together that no ink token can clear 4.5:1 against both ends at once; the
   ladder would have to span several times further than a bar's does. Labels sit
   beside or below, never on.
   -------------------------------------------------------------------------- */

:root {
  --bar-neutral: #e5e5e5;
  --bar-cueseal: #ffd89e;
  --bar-muxnode: #77e3c4;
  --bar-green:   #90da8a;
  --bar-genlock: #e049f0;
  --bar-cuebus:  #eb423a;
  --bar-blue:    #385db0;

  /* Video black is black in both schemes. A pluge step that follows the UI
     scheme is not a pluge step, it is a rectangle. */
  --bar-black:   #0d0d0d;

  /* Seven equal segments, hard stops. The hard stop is what separates a
     reference bar from a gradient: a blend reads as brand decoration, a step
     reads as a signal. 100/7 = 14.2857. */
  --bars: linear-gradient(90deg,
    var(--bar-neutral) 0         14.2857%,
    var(--bar-cueseal) 14.2857%  28.5714%,
    var(--bar-muxnode) 28.5714%  42.8571%,
    var(--bar-green)   42.8571%  57.1429%,
    var(--bar-genlock) 57.1429%  71.4286%,
    var(--bar-cuebus)  71.4286%  85.7143%,
    var(--bar-blue)    85.7143%  100%);
}

@media (prefers-color-scheme: light) {
  :root {
    --bar-neutral: #919191;
    --bar-cueseal: #bd8115;
    --bar-muxnode: #0e9578;
    --bar-green:   #488e43;
    --bar-genlock: #a100af;
    --bar-cuebus:  #a61927;
    --bar-blue:    #1c3d8d;
  }
}

/* --- 1. The masthead rule ------------------------------------------------
   Was a two-stop ramp in the site's own brand hue, which made the top of each
   site look like a different product. The bar is identical on all four, so the
   family reads before the name does. Site identity moves down one level, to
   the wordmark and to --accent, which still carries every link on the page.

   The solid fallback stays and stays a real colour: border-image is dropped
   entirely in forced-colors, and a transparent fallback takes the whole rule
   with it. That was learned the hard way once already.

   border-image-width confines the image to the top edge. Without it the
   shorthand paints every border that has width, and .masthead has two: the 3px
   rule at the top and the 1px `border-block-end: 1px solid var(--rule)` the
   base block declares. The bottom hairline was therefore rendering the full
   seven segment bar at 1px, measured at 1400px as seven segments of
   199/201/200/200/200/201/199 on row y=81, 78px below the real rule. That is a
   second full bar, which 00_SYSTEM.md 0.2 gives as the reason the doc strip
   edge takes a slice at all: "a second full bar 40px down reads as a mistake."
   The masthead was making that mistake against itself.

   Zeroing the other three edges stops the duplicate. It does not hand the
   bottom edge back to its declared colour: with a border-image-source set the
   border-style goes unpainted on every edge, width 0 included, so
   `border-block-end: 1px solid var(--rule)` measures as --bg, not --rule.
   That is left alone rather than redrawn some other way. The declared border
   is still doing the job 0.4's fourth test asks of it, because border-image is
   dropped in forced colours and the solid 1px comes back as a real boundary
   there. In normal rendering the masthead background simply meets the doc
   strip's tape, which is a tonal step already, and the strip carries its own
   slice one row further down.

   Net effect: one full bar, at the top, where the placement map puts it. No
   token, colour or bar definition changes; a second render of the bar stops. */
.masthead {
  border-image: var(--bars) 1;
  border-image-width: 3px 0 0 0;
}

/* --- 3. The 404 ----------------------------------------------------------
   The one place the "signal lost" reading of a colour bar is the correct one,
   and the only place the pattern is allowed at size. Everywhere else the bars
   are a reference; here they are the thing a viewer sees when the reference is
   all that is left. */
.holdover {
  margin-block-end: var(--sp-6);
}

.holdover__bars {
  block-size: clamp(64px, 14vh, 132px);
  background: var(--bars);
  border-radius: var(--radius) var(--radius) 0 0;
}

/* The lower band of a real bar pattern: the sub-black and pluge steps under
   the colour. Four unequal steps, which is what stops the whole thing reading
   as a stripe of colour with a grey rectangle attached. */
.holdover__pluge {
  block-size: clamp(20px, 4.5vh, 38px);
  border-radius: 0 0 var(--radius) var(--radius);
  /* The lower band of a real pattern: three colour cells separated by black,
     then the pluge trio of sub-black, black and above-black. The alternating
     cells were --bg in the first pass, which on a light page made them
     invisible and turned the band into a row of holes. */
  background: linear-gradient(90deg,
    var(--bar-blue)    0        14.2857%,
    var(--bar-black)   14.2857% 28.5714%,
    var(--bar-genlock) 28.5714% 42.8571%,
    var(--bar-black)   42.8571% 57.1429%,
    var(--bar-muxnode) 57.1429% 71.4286%,
    var(--bar-black)   71.4286% 78.5714%,
    #060606            78.5714% 82.1428%,
    var(--bar-black)   82.1428% 85.7143%,
    #1c1c1c            85.7143% 89.2857%,
    var(--bar-black)   89.2857% 100%);
}

/* --- 4. Forced colours ---------------------------------------------------
   Backgrounds, gradients and border-image are all dropped in forced-colors, so
   every bar in this file disappears. That is the correct outcome for
   decoration, but a bar that disappears leaves an unexplained gap where a
   block used to be, so the two that carry structure collapse to a rule the
   mode will honour. */
@media (forced-colors: active) {
  .holdover__bars { block-size: 0; border-block-start: 3px solid CanvasText; }
  .holdover__pluge { block-size: 0; border-block-start: 1px solid CanvasText; }
}

/* ==========================================================================
   Amp layer — the drawn marks and the front panel

   Two things live here. First, the four brand marks as drawn vector, used at a
   size where they read. Second, a front-panel register: the gear this project
   studies is rack-mounted, so screw corners, ventilation slots, silkscreen
   designators and indicator LEDs are the vocabulary of the subject rather than
   decoration borrowed from somewhere else.

   THE BUDGET IS THE DESIGN. A panel per page, at most two. An LED only where a
   real state is being reported. A designator only on a figure that is genuinely
   a test point. The moment every block is a panel, none of them is.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The mark tile

   The supplied artwork is a black mark on a gradient plate, and the plate is
   the reason the marks read at all at 36px. The mark itself is an alpha MASK
   over a painted box rather than inline SVG or an <img>: one cached request per
   mark, zero markup weight at 23 use sites across six pages, and the plate
   colours travel with the modifier so a Cuebus tile on the CueSeal page still
   wears Cuebus colours.

   The plate values are the artwork's own, declared once in :root as
   --plate-<slug>-lo and -hi so the per-brand aliases and these modifiers cannot
   drift apart. WCAG exempts logotypes from contrast, which is what lets the
   Cuebus mark sit on its own dark red the way the supplied icon does.
   -------------------------------------------------------------------------- */
.tile {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border-radius: 8px;
  background: linear-gradient(145deg, var(--tile-lo), var(--tile-hi));
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.18);
}

.tile::before {
  content: "";
  inline-size: 64%;
  block-size: 64%;
  background-color: #17140e;
  -webkit-mask: var(--tile-mark) center / contain no-repeat;
          mask: var(--tile-mark) center / contain no-repeat;
}

.tile--cueseal { --tile-mark: url("./assets/mark-cueseal.1ab74d5036.svg"); --tile-lo: var(--plate-cueseal-lo); --tile-hi: var(--plate-cueseal-hi); }

/* CueSeal is the parent, not a fourth programme, so its mark does not wear the
   plate the three programme tiles share. The gradient fills the aperture
   itself instead of sitting behind a black silhouette. Small cross-reference
   icons on the other three sites (.tile--sm) keep the plate: the ramp has no
   contrast to spare at that size. */
.tile--cueseal:not(.tile--sm) {
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: 0;
  background: linear-gradient(90deg, var(--seal-lo), var(--seal-hi));
  box-shadow: none;
  -webkit-mask: var(--tile-mark) center / contain no-repeat;
          mask: var(--tile-mark) center / contain no-repeat;
}

.tile--cueseal:not(.tile--sm)::before { content: none; }

/* The module tile is not sized down to line up with the three programme
   tiles, because the parent is not one of the set. The two .faceplate
   .tile--lg rules below structurally exclude cueseal, so this is the only
   place its module size lives, and border-radius falls through to the flat
   fill above rather than needing to be restated here. */
.tile--cueseal.tile--lg { inline-size: 5rem; block-size: 5rem; }

/* A mask over a background is stripped twice over in forced colours: the
   gradient goes and the box goes with it. A system colour survives, and the
   mask still cuts the shape. */
@media (forced-colors: active) {
  .tile--cueseal:not(.tile--sm) { display: block; background: CanvasText; }
}

.tile--genlock { --tile-mark: url("./assets/mark-genlock.93b8644dbb.svg"); --tile-lo: var(--plate-genlock-lo); --tile-hi: var(--plate-genlock-hi); }
.tile--muxnode { --tile-mark: url("./assets/mark-muxnode.a030666711.svg"); --tile-lo: var(--plate-muxnode-lo); --tile-hi: var(--plate-muxnode-hi); }
.tile--cuebus  { --tile-mark: url("./assets/mark-cuebus.611981834b.svg");  --tile-lo: var(--plate-cuebus-lo);  --tile-hi: var(--plate-cuebus-hi); }

.tile--sm {
  inline-size: 1.5rem;
  block-size: 1.5rem;
  border-radius: 5px;
}

.tile--lg {
  inline-size: 5rem;
  block-size: 5rem;
  border-radius: 16px;
}

/* Forced colours strips the plate and the masked box together, which would
   leave an empty square. Every tile on every page sits beside text that names
   the same thing, so removing it loses nothing and leaves no hole. */
@media (forced-colors: active) {
  .tile { display: none; }
}

/* --------------------------------------------------------------------------
   2. The masthead lockup

   Drawn rather than set, because the face it is drawn in is not a system font
   and font-src is 'none'. Inline SVG is markup, so it is not an img-src fetch
   either, and it takes currentColor and custom properties in both schemes,
   which a linked file could not: an <img src> is a separate document and the
   page's tokens never reach it.

   The wordmark and the mark are one drawing now (viewBox 1269x204, mark at
   x=1065), so this one length sets both: the mark is the full height of the
   box, the letters are whatever share of it the artwork gives them. That
   couples them, and the mark is the half with a legibility floor. It carries
   the aperture cuts and the counter of the S, and below roughly 25px those
   close up and it reads as a filled blob.

   1.85em puts the mark near 35px. The previous masthead sat it in a .tile at a
   flat 2.75rem, and --seal-lo/--seal-hi in the light scheme are still tuned
   against that 44px (see the ramp note in the token block); 35px is inside
   what that tuning holds for, 20px was not. Raising this raises the wordmark
   with it, which is the trade the lockup imposes: there is no way to grow one
   without the other short of redrawing the artwork.
   -------------------------------------------------------------------------- */
.wordmark__svg {
  display: block;
  block-size: 1.85em;
  inline-size: auto;
  color: var(--ink);
}

/* The three lens sites have no drawn wordmark, so their names are set in the
   display face. That works now in a way it did not before: the face was chosen
   against the CueSeal lockup, so a set name and a drawn one sit in the same
   register instead of looking like two different projects. */
.wordmark__name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--step-1);
  letter-spacing: -0.015em;
  line-height: 1.05;
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   3. Indicator LEDs

   Only where a real state is reported. The site already labels its own status
   honestly, and an LED beside a status label is the same claim in the
   vocabulary of the rack. Never the only carrier of meaning: the label is
   always present and always says the same thing, so this passes 1.4.1.
   -------------------------------------------------------------------------- */
.led {
  flex: none;
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-radius: 50%;
  background: var(--ink-faint);
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.35);
}
/* vertical-align: 0.02em was declared here and did nothing. Every .led on
   every page sits in one of two parents, `.docstrip dd` or `.faceplate__row`,
   and both are display:flex, where vertical-align does not apply to children.
   What actually centres the dot is `align-items: center` on those parents:
   measured at 1400px, the dot centre and the adjacent text ink centre both
   land on y=107.5, a 0.0px offset. Removed so the centring has one owner. */

/* Active work. The glow is a second box-shadow rather than a filter, because a
   filter on a 8px dot costs a compositing layer for nothing. */
.led--live {
  background: var(--bar-muxnode);
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.3), 0 0 6px 1px var(--bar-muxnode);
}

/* Draft or pre-validation. Amber, and amber means exactly what it means on a
   rack: powered, not yet trusted. */
.led--hold {
  background: var(--bar-cueseal);
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.3), 0 0 6px 1px var(--bar-cueseal);
}

/* Tally. Reserved for the one thing on a site that is genuinely on air. Do not
   spend it on anything else, because a tally that is always lit is furniture. */
.led--tally {
  background: var(--bar-cuebus);
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.3), 0 0 7px 1px var(--bar-cuebus);
}

@media (forced-colors: active) {
  .led { border: 1px solid CanvasText; box-shadow: none; }
  .led--live, .led--hold, .led--tally { background: Highlight; }
}

/* --------------------------------------------------------------------------
   4. The front panel

   A rack panel: four screw corners, a ventilation field, and a silkscreen
   designator in the corner. Everything is drawn with gradients and shadows, so
   it adds no request and no image.
   -------------------------------------------------------------------------- */
.panel {
  position: relative;
  isolation: isolate;
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
  border: 1px solid var(--rule-strong);
  border-radius: 6px;
  background:
    linear-gradient(180deg, var(--surface), var(--bg-sunken));
  margin-block: var(--sp-6);
}

/* Screws. One element, four positions, via two repeating backgrounds on a
   pseudo element rather than four absolutely positioned dots. */
.panel::before {
  content: "";
  position: absolute;
  inset: 7px;
  border-radius: 3px;
  pointer-events: none;
  z-index: -1;
  background-repeat: no-repeat;
  background-size: 7px 7px;
  background-position: left top, right top, left bottom, right bottom;
  background-image:
    radial-gradient(circle at 50% 50%, var(--rule-strong) 0 34%, transparent 36%),
    radial-gradient(circle at 50% 50%, var(--rule-strong) 0 34%, transparent 36%),
    radial-gradient(circle at 50% 50%, var(--rule-strong) 0 34%, transparent 36%),
    radial-gradient(circle at 50% 50%, var(--rule-strong) 0 34%, transparent 36%);
}

/* The ventilation field, top right, kept well clear of the text column. A
   repeating gradient of slots rather than an image. */
.panel::after {
  content: "";
  position: absolute;
  inset-block-start: 14px;
  inset-inline-end: 18px;
  inline-size: 76px;
  block-size: 14px;
  border-radius: 2px;
  pointer-events: none;
  z-index: -1;
  opacity: 0.55;
  background: repeating-linear-gradient(90deg,
    var(--rule-strong) 0 2px, transparent 2px 7px);
}

.panel__label {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-block-end: var(--sp-3);
}

.panel > :last-child { margin-block-end: 0; }

@media (max-width: 34rem) {
  /* At this width the vent field would sit under the label. Drop it rather
     than let two decorations fight over the same 76px. */
  .panel::after { display: none; }
  .panel { padding-inline: var(--sp-4); }
}

@media (forced-colors: active) {
  .panel { background: Canvas; border-color: CanvasText; }
  .panel::before, .panel::after { display: none; }
}

/* --------------------------------------------------------------------------
   5. Silkscreen designators

   The reference marker a board carries beside a test point. Used only where
   the page is pointing at a specific artefact, which is what a designator is
   for. Presentational, so it is aria-hidden in the markup and the surrounding
   text always names the thing itself.
   -------------------------------------------------------------------------- */
.silk {
  font-family: var(--font-mono);
  font-size: 0.72em;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 0.05em 0.4em;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   6. Display face tuning

   Saira is wider and more open than the system stack it replaces, so the
   tracking that suited the fallback is too loose here and the weight can come
   down one step without losing presence. Measured against rendered line counts,
   not guessed: at --step-4 the old -0.032em left the first line of the CueSeal
   hero one character short of wrapping the same way.
   -------------------------------------------------------------------------- */
.hero h1 {
  font-weight: 800;
  letter-spacing: -0.028em;
}

.section h2 {
  font-weight: 800;
  letter-spacing: -0.022em;
}

/* h3 was on --font-body and stays there. A third level in the display face
   makes every card title shout, and the cards are where the reading happens. */

/* The section number. Set in the display face at a weight that lets it hold
   its own against the heading beside it, which the mono at 13px never did. */
.section__num {
  font-family: var(--font-display);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

/* --------------------------------------------------------------------------
   7. The status row

   The doc strip already reports status honestly in words. The LED is the same
   claim in the vocabulary of the rack, and the words stay exactly as they were.
   -------------------------------------------------------------------------- */
.docstrip dd {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* --------------------------------------------------------------------------
   8. The hero faceplate

   The hero on the three lens pages is a single text column with an empty right
   half at any width above about 62rem, which is the largest unused area on the
   whole site. It gets a rack module faceplate: screw corners, a ventilation
   field, the drawn mark at a size where it reads, and a silkscreen designator.

   It carries NO information the page does not already state. The one lit
   indicator repeats the status from the doc strip and nothing else, because a
   panel that invents a state it cannot back is exactly the kind of decoration
   this project should not ship. The block is aria-hidden for that reason: every
   word in it appears elsewhere on the page, so announcing it twice is noise.
   -------------------------------------------------------------------------- */
.faceplate {
  position: relative;
  isolation: isolate;
  display: grid;
  justify-items: center;
  gap: var(--sp-4);
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
  background: linear-gradient(170deg, var(--surface), var(--bg-sunken) 70%);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.5);
  margin-block-start: var(--sp-7);
}

/* Screw corners. Four radial gradients on one inset pseudo element. */
.faceplate::before {
  content: "";
  position: absolute;
  inset: 8px;
  z-index: -1;
  pointer-events: none;
  background-repeat: no-repeat;
  background-size: 8px 8px;
  background-position: left top, right top, left bottom, right bottom;
  background-image:
    radial-gradient(circle at 50% 45%, var(--rule-strong) 0 32%, var(--rule) 34% 46%, transparent 48%),
    radial-gradient(circle at 50% 45%, var(--rule-strong) 0 32%, var(--rule) 34% 46%, transparent 48%),
    radial-gradient(circle at 50% 45%, var(--rule-strong) 0 32%, var(--rule) 34% 46%, transparent 48%),
    radial-gradient(circle at 50% 45%, var(--rule-strong) 0 32%, var(--rule) 34% 46%, transparent 48%);
}

.faceplate__vent {
  inline-size: 62%;
  block-size: 16px;
  border-radius: 2px;
  opacity: 0.5;
  background: repeating-linear-gradient(90deg,
    var(--rule-strong) 0 2px, transparent 2px 7px);
}

/* :not(.tile--cueseal) because the flat-filled seal explicitly drops the
   shadow (box-shadow: none, in the tile mark section above); without the
   exclusion this rule and that one tie on specificity and this one, being
   later in the file, would silently win. */
.faceplate .tile--lg:not(.tile--cueseal) { box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.22), 0 2px 10px rgb(0 0 0 / 0.14); }

.faceplate__row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 0;
}

.faceplate__silk {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* Above this width the hero has room for a second column. Below it the
   faceplate follows the actions in document order, which is where it already
   is in the markup, so nothing has to move. */
@media (min-width: 62rem) {
  .hero--rack {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 17rem;
    column-gap: var(--sp-8);
    align-items: start;
  }
  .hero--rack > * { grid-column: 1; }
  .hero--rack > .faceplate {
    grid-column: 2;
    grid-row: 1 / span 20;
    margin-block-start: var(--sp-2);
    align-self: start;
  }
}

@media (max-width: 61.999rem) {
  .faceplate { max-inline-size: 15rem; }
}

@media (forced-colors: active) {
  .faceplate { background: Canvas; border-color: CanvasText; }
  .faceplate::before { display: none; }
  .faceplate__vent { display: none; }
}

/* --------------------------------------------------------------------------
   9. The record figures as boards

   The ledger figures are the most instrument-like thing on any page: a real
   record, in mono, in a box. They get screw corners and a silkscreen reference
   designator, which is the marker a board carries beside a test point.

   A designator makes no claim. That is why it is the right ornament here and an
   LED is not: these are recorded samples, and an indicator implying a live feed
   would be the site lying about its own status in a way the copy never does.
   -------------------------------------------------------------------------- */
.ledger {
  position: relative;
}

.ledger::before {
  content: "";
  position: absolute;
  inset: 6px;
  z-index: 0;
  pointer-events: none;
  background-repeat: no-repeat;
  background-size: 7px 7px;
  background-position: left top, right top, left bottom, right bottom;
  background-image:
    radial-gradient(circle at 50% 45%, var(--rule-strong) 0 30%, transparent 34%),
    radial-gradient(circle at 50% 45%, var(--rule-strong) 0 30%, transparent 34%),
    radial-gradient(circle at 50% 45%, var(--rule-strong) 0 30%, transparent 34%),
    radial-gradient(circle at 50% 45%, var(--rule-strong) 0 30%, transparent 34%);
}

/* The figure scrolls horizontally, so its contents have to sit above the
   decoration or the screws scroll away from their corners. */
.ledger > * { position: relative; z-index: 1; }

.ledger__head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-block-end: var(--sp-3);
}

.ledger__head .ledger__tag { margin-block-end: 0; }

.ledger__head .silk { margin-inline-start: auto; }

@media (forced-colors: active) {
  .ledger::before { display: none; }
}

/* ==========================================================================
   Unify layer — one system, not two

   The reference bar and the front panel arrived in separate passes and behaved
   like separate vocabularies: a coloured rule at the top of the page, and a
   grey module on the right that had nothing to do with it. This layer makes
   them one thing, and the rule that does it is a hierarchy of scale:

     THE FULL SEVEN SEGMENT BAR MEANS THE FAMILY.
       The masthead rule, the hero boundary, the 404, and the screen on the
       faceplate. Identical on all four sites, on purpose.

     A THREE SEGMENT SLICE MEANS THIS SITE INSIDE THE FAMILY.
       The section ticks, the doc strip edge, and the card edges. The site's own
       segment in the middle, its two neighbours in the bar either side.

   Once that reads, every coloured element on the page is answering the same
   question, which is what "cohesive" has to mean here. It also makes the pages
   considerably louder, which was the point: the colour comes from placement and
   scale rather than from moving a single palette value, so the contrast gate
   passes unchanged.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. The slice

   Each site knows where it sits in the bar and who its neighbours are. The
   proportions are deliberately uneven: the site's own segment takes the middle
   56% so a slice reads as a position rather than as three equal stripes.
   -------------------------------------------------------------------------- */
:root, .brand-cueseal {
  --bar-self: var(--bar-cueseal);
  --bar-prev: var(--bar-neutral);
  --bar-next: var(--bar-muxnode);
  --bar-slice: linear-gradient(90deg,
    var(--bar-prev) 0   22%,
    var(--bar-self) 22% 78%,
    var(--bar-next) 78% 100%);
}
.brand-genlock {
  --bar-self: var(--bar-genlock);
  --bar-prev: var(--bar-green);
  --bar-next: var(--bar-cuebus);
  --bar-slice: linear-gradient(90deg,
    var(--bar-prev) 0   22%,
    var(--bar-self) 22% 78%,
    var(--bar-next) 78% 100%);
}
.brand-muxnode {
  --bar-self: var(--bar-muxnode);
  --bar-prev: var(--bar-cueseal);
  --bar-next: var(--bar-green);
  --bar-slice: linear-gradient(90deg,
    var(--bar-prev) 0   22%,
    var(--bar-self) 22% 78%,
    var(--bar-next) 78% 100%);
}
.brand-cuebus {
  --bar-self: var(--bar-cuebus);
  --bar-prev: var(--bar-genlock);
  --bar-next: var(--bar-blue);
  --bar-slice: linear-gradient(90deg,
    var(--bar-prev) 0   22%,
    var(--bar-self) 22% 78%,
    var(--bar-next) 78% 100%);
}

/* --bar-slice IS DECLARED IN EACH BLOCK ABOVE, NOT ONCE IN :root.
   A custom property whose value references other custom properties resolves
   those references where it is DECLARED, not where it is used. Declared once on
   :root it computed to CueSeal's slice and every site inherited that already
   resolved value, so a Genlock page drew CueSeal's tick. The computed style
   said --bar-self was #a100af and --bar-slice was amber at the same time, which
   is the tell. Four declarations is the price of the property being correct. */

/* --------------------------------------------------------------------------
   2. The section tick becomes a slice

   Was 2px of flat --brand above the section number. It is the single most
   repeated coloured element on any page, eight or nine per page, so it is where
   a change of language is felt most. The solid fallback stays a real colour:
   border-image is dropped in forced colours and a transparent fallback takes
   the whole rule with it, which this project has learned twice.
   -------------------------------------------------------------------------- */
@media (min-width: 62rem) {
  .section__num {
    border-block-start: 3px solid var(--brand);
    border-image: var(--bar-slice) 1;
  }
}

/* Below the rail breakpoint the number has no rule at all, which left the
   narrow layout with none of this. Give it the same slice as a short bar above
   the heading, so a phone reads the same system a desktop does. */
@media (max-width: 61.999rem) {
  .section__head::before {
    content: "";
    display: block;
    inline-size: 3.5rem;
    block-size: 3px;
    background: var(--bar-slice);
    margin-block-end: var(--sp-3);
  }
}

/* --------------------------------------------------------------------------
   3. The hero boundary becomes the full bar

   One per page, at the single most structural moment on it: the line where the
   hero stops and the argument starts. Every later section keeps its hairline,
   because a full bar at every boundary is wallpaper.
   -------------------------------------------------------------------------- */
.hero + .section {
  border-block-start: 3px solid var(--brand);
  border-image: var(--bars) 1;
}

/* --------------------------------------------------------------------------
   4. The doc strip edge becomes a slice

   The strip sits directly under the masthead's full bar, so it takes the slice
   rather than a second full bar. Two full bars 40px apart read as a mistake.
   -------------------------------------------------------------------------- */
.docstrip {
  border-block-end: 2px solid var(--tape-edge);
  border-image: var(--bar-slice) 1;
}

/* --------------------------------------------------------------------------
   5. Cards carry the edge of whatever they point at

   A card whose kicker holds a Genlock tile is a card about Genlock, so it takes
   Genlock's segment. :has() reads that from the markup, which means no card
   needs a second class naming a thing its own contents already name.

   This is where most of the new colour on the site actually lands: the card
   grids are the widest blocks on every page and they were entirely grey.
   -------------------------------------------------------------------------- */
.card {
  position: relative;
  overflow: hidden;
}

.card::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 3px;
  background: var(--card-edge, var(--rule));
}

.card:has(.tile--cueseal) { --card-edge: var(--bar-cueseal); }
.card:has(.tile--genlock) { --card-edge: var(--bar-genlock); }
.card:has(.tile--muxnode) { --card-edge: var(--bar-muxnode); }
.card:has(.tile--cuebus)  { --card-edge: var(--bar-cuebus); }

/* A card with no tile is about this site, so it takes this site's segment.
   Written as a separate rule rather than a fallback in the custom property,
   because a browser without :has() should still get the neutral hairline
   rather than every card in the page's own colour. */
@supports selector(:has(*)) {
  .card:not(:has(.tile)) { --card-edge: var(--bar-self); }
}

/* --------------------------------------------------------------------------
   6. The faceplate becomes a signal generator

   This is the join. A rack module that displays colour bars is a real object,
   and it is the object this whole system has been describing in two halves: the
   panel was the chassis and the bar was the signal, and they belong in one
   picture. The screen shows the family bar, the tile below it says which site
   this chassis is, and the indicator repeats the status the page already gives.
   -------------------------------------------------------------------------- */
.faceplate__screen {
  inline-size: 100%;
  block-size: 3.25rem;
  border-radius: 3px;
  background: var(--bars);
  box-shadow:
    inset 0 0 0 1px rgb(0 0 0 / 0.28),
    inset 0 0 14px rgb(0 0 0 / 0.22);
}

/* The lower band of a real pattern, at module scale. Three cells and the pluge
   trio, the same construction the 404 uses, so the two are the same object at
   two sizes rather than two drawings of a similar idea. */
.faceplate__pluge {
  inline-size: 100%;
  block-size: 0.85rem;
  margin-block-start: -0.85rem;
  border-radius: 0 0 3px 3px;
  background: linear-gradient(90deg,
    var(--bar-blue)    0        14.2857%,
    var(--bar-black)   14.2857% 28.5714%,
    var(--bar-genlock) 28.5714% 42.8571%,
    var(--bar-black)   42.8571% 57.1429%,
    var(--bar-muxnode) 57.1429% 71.4286%,
    var(--bar-black)   71.4286% 78.5714%,
    #060606            78.5714% 82.1428%,
    var(--bar-black)   82.1428% 85.7143%,
    #1c1c1c            85.7143% 89.2857%,
    var(--bar-black)   89.2857% 100%);
  position: relative;
  z-index: 1;
}

/* The screen is the widest element in the module, so the tile stops being the
   thing the eye lands on first. Drop it a size and let the bar lead. Excludes
   cueseal: the parent is not one of the three programme tiles this applies
   to, and its own size lives beside the tile mark rules above. */
.faceplate .tile--lg:not(.tile--cueseal) {
  inline-size: 3.5rem;
  block-size: 3.5rem;
  border-radius: 12px;
}

.faceplate {
  gap: var(--sp-3);
  padding-block-start: var(--sp-5);
}

/* --------------------------------------------------------------------------
   7. Forced colours

   Everything above is a background or a border-image, so all of it is dropped.
   Each element keeps a real colour underneath, and the two that carry structure
   collapse to a rule the mode honours.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .section__head::before { display: none; }
  .card::before { background: CanvasText; }
  .faceplate__screen { block-size: 0; border-block-start: 3px solid CanvasText; }
  .faceplate__pluge { display: none; }
}
