/* ==========================================================================
   NEXUS — the dark ground, shared

   DESIGN.md §0.4. Black ground, six colour roles, weight-300 display type,
   mono section labels with a blue dot, separation by surface value rather than
   by borders, and exactly two interruptions in the black.

   This file was the first 361 lines of projects.css. It moved here unchanged
   when the Products, About, Contact, Updates and Privacy routes were converted:
   the same shell, restyled once, is what those pages needed, and copying it a
   second time would have been the start of two systems drifting apart
   (CLAUDE.md §3).

   Load order matters and is fixed in the page front matter:
       pageStyles: ["dark", "projects"]     project routes
       pageStyles: ["dark", "products"]     product routes
       pageStyles: ["dark"]                 About, Contact, Updates, Privacy
   The page sheet loads after this one so it can refine without fighting it.

   Scoped to `.page--dark`, which each page sets itself through `bodyClass`.
   The frozen homepage sets the same class but loads home.css, never this file,
   so nothing here can reach it.
   ========================================================================== */

/* --- Ground, type and shell: identical to the homepage ------------------- */

.page--dark {
  background: var(--c-ground);
  color: var(--c-invert);
  --c-muted: var(--c-muted-on-ground);
}
.page--dark .band--invert { --c-muted: var(--c-muted-on-invert); }
.page--dark .band--accent { --c-muted: var(--c-ink); }

.page--dark h1, .page--dark h2, .page--dark h3 {
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-dark-display);
  line-height: var(--lh-dark-display);
  color: inherit;
}
.page--dark p { color: inherit; }
.page--dark .t-muted { color: var(--c-muted); }
.page--dark .t-lead { font-size: var(--fs-lead); line-height: var(--lh-lead); color: var(--c-muted); }
.page--dark .t-body-sm { font-size: var(--fs-body-sm); line-height: 1.6; }
.page--dark .measure { max-width: 58ch; }

.page--dark .eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-regular);
  line-height: 1.5;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
  margin: 0;
}
.page--dark .eyebrow::before {
  content: "";
  flex: 0 0 auto;
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: var(--c-accent-on-dark);
}
.page--dark .band--invert .eyebrow::before { background: var(--c-blue); }
.page--dark .band--accent .eyebrow::before { background: var(--c-ink); }
/* The dot marks a section. Inside a row, a caption or a pager it means nothing
   and only speckles the page — the same rule the homepage applies. */
.page--dark .pfact .eyebrow::before,
.page--dark .pager .eyebrow::before,
.page--dark .lead__caption .eyebrow::before,
.page--dark .ev .eyebrow::before { display: none; }

/* One media ratio for every cropped image on the section. Cards, the evidence
   grid and the equipment card all used to differ — 3/2 here, 16/10 there — for
   no reason a reader could see. 16/10 is the shorter of the two, which is also
   how several hundred pixels leave each page. */
.page--dark {
  /* lead media and the page heading: a dominant column and a supporting one */
  --grid-asym: minmax(0, 0.62fr) minmax(0, 0.38fr);
  /* parallel content of equal weight: story blocks, facts, evidence */
  --grid-even: repeat(2, minmax(0, 1fr));
  /* the index only. Six cards read as a 3 x 2 composition; at two columns each
     card was 632px — a card is a tertiary object and must not approach the
     width of a lead. Three columns inside the 1280px container gives 416px. */
  --grid-cards: repeat(3, minmax(0, 1fr));
  --gap-text: var(--s-8);   /* 56px — between columns of words */
  --gap-media: var(--s-4);  /* 16px — between tiles of image, as the homepage */

  /* ── The four media frames ────────────────────────────────────────────
     The layout defines the frame. The frame defines the image size. A source
     file never defines a page dimension, and a higher-resolution file is not
     a reason to show it larger.

     There is deliberately NO single site-wide ratio. Consistency is
     component-based: every image at the same LEVEL matches across all six
     project pages, and the four levels are visibly different in weight.

       1 lead     — the primary visual, up to 800 x 500
       2 evidence — supporting, 3:2 tiles at roughly 600 x 400
       3 card     — the index grid, one fixed 4:3 stage for all six
       4 product  — tertiary, a 190px thumbnail in a row
  */
  --lead-w:        800px;   /* landscape lead, maximum */
  --lead-h:        500px;
  --lead-w-port:   420px;   /* portrait lead — narrower, taller */
  --lead-h-port:   660px;
  --lead-w-tech:   560px;   /* video frame — subordinate, never enlarged */
  --lead-h-tech:   340px;
  --ratio-evidence: 3 / 2;
  --ratio-card:     4 / 3;
  --ratio-product:  4 / 3;
  --product-w:     190px;
}

/* ── The frame primitive ─────────────────────────────────────────────────
   Contain is the default, because a crop that removes engineering detail is a
   worse failure than a letterbox. Cover is opted into per component, and each
   use is checked against the actual photograph. */
.media-frame {
  position: relative;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: var(--radius-dark-card);
}
/* The element fills the stage and the PICTURE is fitted inside it.
   The intent is `inline-size: auto; block-size: auto; max-*: 100%`, but a
   percentage max-block-size is not honoured on a centred replaced element in a
   grid — measured: a 1600x1200 source rendered 759x569 inside a 759x500 stage
   and was clipped. Filling the stage and letting object-fit do the fitting
   gives the same picture and is deterministic. */
.media-frame img {
  /* Absolute inside the stage. The intent is auto sizing with max-* caps, but
     the width/height attributes give the element an intrinsic aspect-ratio that
     wins over a percentage block-size — measured: a 1600x1200 source computed
     569px tall inside a 500px stage. Pinning to the stage and letting
     object-fit place the picture gives the intended result deterministically. */
  position: absolute;
  inset: 0;
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: contain;
  border-radius: 0;
}
/* Cover is a per-component decision, never a global rule, and every use is
   checked against the actual photograph. */
.media-frame--cover img { object-fit: cover; }

.page--dark .t-statement {
  font-size: var(--fs-statement);
  max-width: 20ch;
  margin: var(--s-4) 0 0;
}
/* A supporting section must not announce itself at the same scale as the story
   it supports. At 1440px --fs-statement is 64px on a 20ch measure, so every
   section heading ran to three lines and cost ~210px — five times a page. The
   selection band, the evidence set, the confirmed facts and the related product
   are subordinate to the story and the closing call, and step down to a second
   display scale on a wider measure. Nothing is deleted; the words are the same.
   Specificity is deliberately 0,3,0 so the mobile override further down this
   sheet does not take the step-down away again. */
.page--dark .selection .t-statement,
.page--dark .evidence-set .t-statement,
.page--dark .facts-band .t-statement,
.page--dark .equipment .t-statement {
  font-size: clamp(1.5rem, 1.05rem + 1.6vw, 2rem);
  line-height: 1.14;
  max-width: 34ch;
  margin-top: var(--s-3);
}

/* The global token is clamp(56px, 4.6vw, 80px) — 66px each side at 1440px, and
   a project page stacks eight sections, so section padding alone was buying
   1056px of every detail page. Redefined only for this sheet, which only the
   Projects routes load; the frozen homepage reads the global token from
   home.css and is untouched. */
.page--dark { --section-dark: clamp(48px, 3.8vw, 68px); }
.page--dark .section--major { padding-block: var(--section-dark); }
.page--dark .section--band  { padding-block: var(--section-dark); }
.page--dark .section-foot { margin-top: var(--s-6); }
.page--dark .equipment .section-foot { margin-top: var(--s-5); }

.page--dark .band--invert {
  background: var(--c-invert);
  color: var(--c-ink);
  border-radius: var(--radius-dark-section);
}
.page--dark .band--invert h2 { color: var(--c-ink); }
.page--dark .band--accent {
  background: var(--c-blue);
  color: var(--c-ink);
  border-radius: var(--radius-dark-section);
}
.page--dark .band--accent h2,
.page--dark .band--accent p,
.page--dark .band--accent .eyebrow { color: var(--c-ink); }

/* --- The shared shell, restyled exactly as the homepage does it ---------- */
/* The header and footer markup is the site's own and is not duplicated. Only
   its surface changes. The frozen homepage is untouched by any of this: these
   rules only apply where `.page--dark` is set, and each page sets it itself. */

.page--dark .site-header { background: var(--c-ground); border-bottom: 1px solid var(--c-line-dark); }
.page--dark .site-logo { background: none; border: 0; border-radius: 0; padding: 0; box-shadow: none; }
.page--dark .site-logo img { width: auto; filter: none; }
.page--dark .site-logo__lockup { height: 74px; }
.page--dark .site-logo__mark { height: 40px; }

.page--dark .site-nav__link {
  font-family: var(--font-sans);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-regular);
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--c-invert);
  opacity: 0.82;
  transition: opacity var(--dur-base) var(--ease-expo-out);
}
.page--dark .site-nav__link:hover,
.page--dark .site-nav__link:focus-visible { opacity: 1; }
.page--dark .site-nav__link[aria-current="page"] { opacity: 1; border-bottom-color: var(--c-accent-on-dark); }

.page--dark .site-header .btn--secondary {
  background: var(--c-blue);
  border-color: var(--c-blue);
  color: var(--c-ink);
  border-radius: var(--radius-dark-ui);
  font-family: var(--font-sans);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-regular);
  letter-spacing: -0.01em;
  text-transform: none;
}
.page--dark .nav-toggle { color: var(--c-invert); border-color: rgba(238, 238, 238, 0.42); }
.page--dark .mobile-nav { background: var(--c-ground); }
.page--dark .mobile-nav__logo { filter: none; background: none; padding: 0; height: 40px; width: auto; }
.page--dark .mobile-nav__list li + li { border-top-color: var(--c-line-dark); }
.page--dark .mobile-nav__link { color: var(--c-invert); }

.page--dark .title-block { background: var(--c-ground); border-top: 1px solid var(--c-line-dark); color: var(--c-muted); }
.page--dark .sheet { display: none; }

.page--dark .site-footer { background: var(--c-ground); border-top: 1px solid var(--c-line-dark); }
.page--dark .site-footer__logo { background: none; border: 0; padding: 0; box-shadow: none; inline-size: fit-content; color: var(--c-invert); }
.page--dark .site-footer__logo img { height: 44px; width: auto; filter: none; }
.page--dark .site-footer__wordmark { color: var(--c-invert); }
.page--dark .site-footer__heading {
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
}
.page--dark .site-footer__list a,
.page--dark .site-footer__contact,
.page--dark .site-footer__contact a { color: var(--c-invert); }
.page--dark .site-footer__descriptor { color: var(--c-muted); }
.page--dark .site-footer__bottom { border-top-color: var(--c-line-dark); }
.page--dark .site-footer__bottom,
.page--dark .site-footer__bottom a {
  font-family: var(--font-label);
  font-size: 0.6875rem;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
}

/* --- Controls, identical to the homepage -------------------------------- */

.page--dark .btn {
  border-radius: var(--radius-dark-ui);
  font-family: var(--font-sans);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-regular);
  letter-spacing: -0.01em;
  text-transform: none;
  min-width: 0;
  padding: 13px 20px;
  transition: opacity var(--dur-base) var(--ease-expo-out);
}
.page--dark .btn--accent { background: var(--c-blue); border-color: var(--c-blue); color: var(--c-ink); }
.page--dark .btn--ink { background: var(--c-ink); border-color: var(--c-ink); color: var(--c-invert); }
.page--dark .btn:hover { opacity: 0.9; }

.page--dark .link-arrow {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-2);
  color: var(--c-invert);
  text-decoration: none;
  font-size: var(--fs-body-sm);
}
.page--dark .link-arrow__mark { color: var(--c-accent-on-dark); transition: transform var(--dur-base) var(--ease-expo-out); }
.page--dark .link-arrow:hover .link-arrow__mark { transform: translateX(4px); }

/* --- No motion on this page -------------------------------------------------
   The shared page header still carries `data-split="lines"`, and components.css
   hides every split part at opacity 0 until an observer reveals it. The owner
   deferred all motion until the static visual is approved, and the homepage has
   no split headings at all — so on the dark pages the reveal states are simply
   neutralised. Without this the H1 is invisible: a heading that depends on an
   animation to exist is a heading that can fail to exist.
   The unconverted light pages keep their reveal behaviour untouched. */
.page--dark .split__part,
.page--dark .js .is-shown .split__part {
  opacity: 1;
  transform: none;
  transition: none;
}
.page--dark .fade-in,
.js .page--dark .fade-in { opacity: 1; transition: none; }
.page--dark .rule--draw,
.page--dark .sheet__edge { transform: none; transition: none; }

/* --- Breadcrumbs and the shared page header ----------------------------- */

.page--dark .breadcrumbs { padding-block: var(--s-6) 0; }
.page--dark .breadcrumbs ol { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-3); list-style: none; margin: 0; padding: 0; }
.page--dark .breadcrumbs li + li::before { content: "/"; margin-inline-end: var(--s-3); color: var(--c-line-dark); }
.page--dark .breadcrumbs a,
.page--dark .breadcrumbs span {
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
  text-decoration: none;
}
.page--dark .breadcrumbs a:hover { color: var(--c-invert); }

.page--dark .page-head { padding-block: var(--s-8) var(--section-dark); }
.page--dark .page-head .section-mark { display: none; }
.page--dark .page-head .split { display: grid; gap: var(--s-7); }
.page--dark .page-head .split > * { min-width: 0; }
@media (min-width: 1024px) {
  .page--dark .page-head .split { grid-template-columns: var(--grid-asym); column-gap: var(--gap-text); align-items: end; }
}
.page--dark .page-head .t-label {
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
  margin: 0 0 var(--s-4);
}
.page--dark .page-head h1 {
  font-size: clamp(2.125rem, 1.15rem + 4.3vw, 3.5rem);
  letter-spacing: var(--ls-dark-display);
  line-height: 1.08;
  margin: 0;
  max-width: 22ch;
}
.page--dark .page-head .t-lead { margin: 0; }

/* ==========================================================================
   Shared components on the dark ground
   ==========================================================================

   Everything below is a RESTYLE of a component the light pages already use —
   the same markup, the same class names, the same templates. Converting the
   remaining routes was a matter of giving these components a dark treatment
   once, not of rebuilding eleven pages (CLAUDE.md §3).

   The rule that shapes all of it (DESIGN.md §0.4): separation comes from
   surface value, not from borders, and the black is interrupted at most twice
   per page — one accent band, and one inverted band where a page genuinely has
   reference material to set apart.
   ========================================================================== */

/* --- Visited links ------------------------------------------------------
   A link that has been followed must still be readable.

   There was no `:visited` rule anywhere in this project, so Chromium fell back
   to its own visited colour on every anchor whose colour came from `inherit`:
   the card titles on the Products index, the Spiral family grid, the "Other
   products" rows and the About project cards. Measured on the painted pixels
   after visiting each link, they rendered at **1.13:1** on the black ground —
   present in the DOM and invisible on screen.

   `color: inherit` is not enough on its own. The state has to be named, so
   both are named here and the anchor keeps whatever colour the component it
   sits in gives it. That is why this is safe for the two places the palette
   deliberately inverts: black text on the cyan closing band and dark text in
   the white specification panel both inherit their own ink, unchanged.

   Components that set their own link colour restate it for :visited below. */
.page--dark :where(a:link, a:visited) { color: inherit; }

/* --- Section rhythm ------------------------------------------------------ */

.page--dark .section { padding-block: var(--section-dark); }
.page--dark .section-head { margin-bottom: var(--s-7); }
.page--dark .section-foot { margin-top: var(--s-6); }

/* The light pages alternate white and mist to separate sections. On the black
   ground that alternation becomes a raised surface: same job, no stripe, and
   it does not spend one of the two permitted interruptions. */
.page--dark .band--mist {
  background: var(--c-raised);
  border-radius: var(--radius-dark-section);
}

/* The section number. Quiet, monospaced, aligned to the left baseline the
   whole system hangs from. */
.page--dark .section-mark { color: var(--c-line-dark); }

/* --- Headings and labels ------------------------------------------------- */

/* `t-label` is the light pages' section label. On dark it is the same object
   as `eyebrow` — mono, uppercase, muted, with the accent dot. */
.page--dark .t-label {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
  margin: 0 0 var(--s-4);
}
.page--dark .t-label::before {
  content: "";
  flex: 0 0 auto;
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: var(--c-accent-on-dark);
}
.page--dark .band--invert .t-label::before { background: var(--c-blue); }
.page--dark .breadcrumbs .t-label,
.page--dark .page-head .t-label { display: block; margin-bottom: var(--s-4); }
.page--dark .breadcrumbs .t-label { display: inline; margin: 0; }
.page--dark .breadcrumbs .t-label::before,
.page--dark .page-head .t-label::before { content: none; }
.page--dark .closing-cta .t-label::before { background: var(--c-ink); }

/* Two display scales, as the Projects pass established (DESIGN.md §0.7.3): a
   page-section scale for the body of the page, and the full statement scale
   reserved for the closing call. A 64px heading over a specification table is
   not a hierarchy. */
.page--dark .heading-block h2,
.page--dark .section-head h2 {
  font-size: clamp(1.75rem, 1.2rem + 2.2vw, 2.75rem);
  line-height: 1.1;
  letter-spacing: var(--ls-dark-display);
  margin: 0;
  max-width: 18ch;
}
.page--dark .closing-cta h2 {
  font-size: var(--fs-statement);
  line-height: var(--lh-dark-display);
  letter-spacing: var(--ls-dark-display);
  margin: 0;
  max-width: 16ch;
}
.page--dark .application__heading,
.page--dark .update-row__title { letter-spacing: -0.02em; }

.page--dark .t-caption { display: block; margin-top: var(--s-2); color: var(--c-muted); font-size: var(--fs-body-sm); }
.page--dark .t-optional { color: var(--c-muted); }

/* --- Specification lists: the component every page here leans on --------- */

.page--dark .spec-list { display: grid; gap: 0; margin: 0; }
.page--dark .spec-row {
  display: grid;
  gap: var(--s-2);
  padding-block: var(--s-4);
  border-top: 1px solid var(--c-line-dark);
}
.page--dark .spec-row:last-child { border-bottom: 1px solid var(--c-line-dark); }
@media (min-width: 720px) {
  .page--dark .spec-row { grid-template-columns: minmax(0, 0.34fr) minmax(0, 0.66fr); gap: var(--s-5); }
}
.page--dark .t-spec-key {
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
}
.page--dark .spec-row dd { margin: 0; font-size: var(--fs-body); line-height: 1.55; color: var(--c-invert); }
.page--dark .band--invert .spec-row { border-top-color: #D6D6D6; }
.page--dark .band--invert .spec-row:last-child { border-bottom-color: #D6D6D6; }
.page--dark .band--invert .spec-row dd { color: var(--c-ink); }

/* --- At a glance --------------------------------------------------------- */

.page--dark .glance-list { list-style: none; margin: 0; padding: 0; display: grid; }
.page--dark .glance-list li {
  border-top: 1px solid var(--c-line-dark);
  padding-block: var(--s-4);
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--c-invert);
}
.page--dark .glance-list li:last-child { border-bottom: 1px solid var(--c-line-dark); }

/* --- Photograph grids: applications, engineering details ---------------- */

.page--dark .application-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--s-7) var(--gap-media);
  grid-template-columns: minmax(0, 1fr);
}
.page--dark .application-grid > * { min-width: 0; }
@media (min-width: 820px) {
  .page--dark .application-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .page--dark .application-grid--two { grid-template-columns: var(--grid-even); }
}
/* One frame level for supporting photography, DESIGN.md §0.6 level 2. The
   frame sets the size; the source file never does. */
.page--dark .application img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: var(--ratio-evidence);
  object-fit: cover;
  background: var(--c-raised);
  border-radius: var(--radius-dark-card);
}
.page--dark .application__heading { margin: var(--s-4) 0 var(--s-2); font-size: var(--fs-h3); font-weight: var(--fw-light); }
.page--dark .application p { color: var(--c-muted); line-height: 1.55; margin: 0; }

/* --- Row of linked cards: variants, siblings, projects in service ------- */

.page--dark .variant-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--gap-media); grid-template-columns: minmax(0, 1fr); }
.page--dark .variant-list > * { min-width: 0; }
@media (min-width: 720px) { .page--dark .variant-list { grid-template-columns: var(--grid-even); } }
.page--dark .variant__link {
  display: grid;
  grid-template-columns: 110px minmax(0, 1fr);
  block-size: 100%;
  background: var(--c-raised);
  border-radius: var(--radius-dark-card);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: background-color var(--dur-reveal) var(--ease-expo-out);
}
.page--dark .variant__link:hover { background: var(--c-raised2); }
@media (min-width: 560px) {
  .page--dark .variant__link { grid-template-columns: 190px minmax(0, 1fr); align-items: start; }
}
.page--dark .variant__frame {
  inline-size: 110px;
  block-size: calc(110px * 3 / 4);
  background: #101010;
  overflow: hidden;
}
@media (min-width: 560px) { .page--dark .variant__frame { inline-size: 190px; block-size: calc(190px * 3 / 4); } }
.page--dark .variant__frame img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.page--dark .variant__text { padding: var(--s-5) var(--s-6); display: grid; gap: var(--s-2); align-content: center; }
.page--dark .variant__name { font-size: var(--fs-h3); font-weight: var(--fw-light); letter-spacing: -0.02em; }
.page--dark .variant__line { color: var(--c-muted); line-height: 1.55; }
.page--dark .variant__link--plain { background: none; }
.page--dark .variant__link--plain:hover { background: var(--c-raised); }

/* --- The closing call: the accent band every page ends on ---------------- */

.page--dark .closing-cta {
  background: var(--c-blue);
  color: var(--c-ink);
  border-radius: var(--radius-dark-section);
  padding-block: var(--section-dark);
}
.page--dark .closing-cta h2,
.page--dark .closing-cta p,
.page--dark .closing-cta .t-label { color: var(--c-ink); }
.page--dark .closing-cta .t-lead { margin-top: var(--s-6); max-width: 56ch; }
.page--dark .closing-cta__inner > p:last-child { margin-top: var(--s-6); }
.page--dark .closing-cta .btn--accent,
.page--dark .closing-cta .btn--secondary { background: var(--c-ink); border-color: var(--c-ink); color: var(--c-invert); }
.page--dark .closing-cta .section-mark { color: rgba(0, 0, 0, 0.42); }

/* --- Click-to-load video (site/_includes/video-list.njk) -----------------
   Shared by the tabbed product page and the project pages (DESIGN.md §0.11).
   16:9 stages. Two across on desktop; a list of three or more becomes a
   horizontal swipe track below 768px (§0.6.4); a single project video is one
   wide stage, never wider than the lead frame. The play mark is NEXUS blue and
   brightens on hover; the title takes the bright blue too. No YouTube badge:
   nothing is YouTube's until the click. */
.page--dark .videos { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-6) var(--gap-media); }
.page--dark .videos > * { min-width: 0; }
@media (min-width: 720px) { .page--dark .videos { grid-template-columns: var(--grid-even); } }
@media (min-width: 720px) { .page--dark .videos--single { grid-template-columns: minmax(0, 1fr); max-inline-size: 900px; } }
.page--dark .video__link { display: grid; gap: var(--s-2); text-decoration: none; }
.page--dark .video__link:link,
.page--dark .video__link:visited { color: var(--c-invert); }
.page--dark .video__frame {
  display: block;
  position: relative;
  inline-size: 100%;
  aspect-ratio: 16 / 9;
  background: #101010;
  border-radius: var(--radius-dark-card);
  overflow: hidden;
}
.page--dark .video__play {
  position: absolute;
  left: var(--s-4);
  bottom: var(--s-4);
  inline-size: 56px;
  block-size: 56px;
  border-radius: 50%;
  background: var(--c-blue);
  transition: background-color var(--dur-micro) var(--ease-state);
}
.page--dark .video__play::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 54%;
  transform: translate(-50%, -50%);
  border-style: solid;
  border-width: 9px 0 9px 15px;
  border-color: transparent transparent transparent #FFFFFF;
}
.page--dark .video__title {
  margin-top: var(--s-2);
  font-size: var(--fs-body);
  line-height: 1.4;
  color: var(--c-invert);
  transition: color var(--dur-micro) var(--ease-state);
}
.page--dark .video__meta {
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
}
@media (hover: hover) and (pointer: fine) {
  .page--dark .video__link:hover .video__play { background: var(--c-accent-on-dark); }
  .page--dark .video__link:hover .video__title { color: var(--c-accent-on-dark); }
}
.page--dark .video__player { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; border: 0; }
@media (max-width: 767px) {
  .page--dark .videos--track {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 84%;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: var(--s-3);
    gap: var(--s-4);
  }
  .page--dark .videos--track .video { scroll-snap-align: start; }
  .page--dark .video__play { inline-size: 48px; block-size: 48px; left: var(--s-3); bottom: var(--s-3); }
}

/* --- Prose (Privacy) ----------------------------------------------------- */

.page--dark .prose { color: var(--c-muted); max-width: 68ch; }
.page--dark .prose h2 { font-size: var(--fs-h3); color: var(--c-invert); margin: var(--s-8) 0 var(--s-4); max-width: none; }
.page--dark .prose h2:first-child { margin-top: 0; }
.page--dark .prose p { margin: 0 0 var(--s-4); font-size: var(--fs-body); line-height: 1.7; }
.page--dark .prose a { color: var(--c-accent-on-dark); }
.page--dark .prose ul { margin: 0 0 var(--s-4); padding-left: 1.2em; }
.page--dark .prose li { margin-bottom: var(--s-2); line-height: 1.7; }

/* --- Updates ------------------------------------------------------------- */

.page--dark .update-rows { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.page--dark .update-row { border-top: 1px solid var(--c-line-dark); padding-block: var(--s-5); display: grid; gap: var(--s-3); }
.page--dark .update-row:last-child { border-bottom: 1px solid var(--c-line-dark); }
.page--dark .update-row__title { font-size: var(--fs-h3); font-weight: var(--fw-light); letter-spacing: -0.02em; margin: 0; }
.page--dark .update-row__title a { color: var(--c-invert); text-decoration: none; }
.page--dark .update-row__title a:hover { text-decoration: underline; text-underline-offset: 4px; }
.page--dark .chips { display: flex; flex-wrap: wrap; gap: var(--s-2); list-style: none; margin: 0; padding: 0; }
.page--dark .chip {
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
  border: 1px solid var(--c-line-dark);
  border-radius: var(--radius-dark-ui);
  padding: 5px 10px;
}
.page--dark .empty-state {
  border: 1px solid var(--c-line-dark);
  border-radius: var(--radius-dark-card);
  padding: var(--s-7) var(--s-6);
  color: var(--c-muted);
  max-width: 62ch;
}

/* --- Forms (Contact) ----------------------------------------------------- */

.page--dark .form { display: grid; gap: var(--s-5); }
.page--dark .field { display: grid; gap: var(--s-2); }
.page--dark .field > label {
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
}
.page--dark .field input,
.page--dark .field select,
.page--dark .field textarea {
  inline-size: 100%;
  font-family: var(--font-sans);
  /* 16px minimum: anything smaller makes iOS zoom the page on focus, and the
     reading size is not what pays for density. */
  font-size: 1rem;
  line-height: 1.55;
  color: var(--c-invert);
  background: var(--c-raised);
  border: 1px solid var(--c-line-dark);
  border-radius: var(--radius-dark-ui);
  padding: 12px 14px;
  transition: border-color var(--dur-base) var(--ease-expo-out);
}
.page--dark .field textarea { min-block-size: 8.5em; resize: vertical; }
.page--dark .field input::placeholder,
.page--dark .field textarea::placeholder { color: #6E6E6E; }
.page--dark .field input:hover,
.page--dark .field select:hover,
.page--dark .field textarea:hover { border-color: #3A3A3A; }
.page--dark .field input:focus-visible,
.page--dark .field select:focus-visible,
.page--dark .field textarea:focus-visible {
  outline: 2px solid var(--c-accent-on-dark);
  outline-offset: 2px;
  border-color: var(--c-accent-on-dark);
}
/* A fieldset carrying a group of checkboxes. It is reset to look exactly like a
   .field, so the group sits in the same rhythm as the single inputs, and the
   legend takes the label's type. */
.page--dark .field--group { border: 0; margin: 0; padding: 0; min-inline-size: 0; }
.page--dark .field--group > legend {
  padding: 0;
  margin-bottom: var(--s-2);
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
}

/* Help that stays visible while the field is being filled in. */
.page--dark .field__help {
  margin: 0 0 var(--s-3);
  font-size: var(--fs-body-sm);
  line-height: 1.55;
  color: var(--c-muted);
  max-width: 54ch;
}

.page--dark .check-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
.page--dark .check-item { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start; gap: var(--s-3); }
.page--dark .check-item input { inline-size: 18px; block-size: 18px; margin-top: 3px; accent-color: var(--c-blue); }
/* A product name is read, not scanned like a field label, so it keeps sentence
   case. `.field label` in components.css uppercases every label inside a field,
   and this group sits inside one — hence the explicit reset, the same one
   .field--check label already makes. */
.page--dark .check-item label {
  font-family: var(--font-sans);
  font-size: var(--fs-body-sm);
  line-height: 1.55;
  letter-spacing: 0;
  text-transform: none;
  color: var(--c-invert);
  cursor: pointer;
}

.page--dark .field--check { grid-template-columns: auto minmax(0, 1fr); align-items: start; gap: var(--s-3); }
.page--dark .field--check input { inline-size: 18px; block-size: 18px; margin-top: 3px; accent-color: var(--c-blue); }
.page--dark .field--check label { text-transform: none; letter-spacing: 0; font-family: var(--font-sans); font-size: var(--fs-body-sm); line-height: 1.55; color: var(--c-muted); }
.page--dark .form__actions { margin-top: var(--s-3); display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: center; }
@media (min-width: 900px) { .page--dark .split--form { grid-template-columns: var(--grid-asym); column-gap: var(--gap-text); } }

/* --- Keyboard focus, everywhere on the dark ground ---------------------- */

.page--dark a:focus-visible,
.page--dark button:focus-visible,
.page--dark summary:focus-visible,
.page--dark [tabindex]:focus-visible {
  outline: 2px solid var(--c-accent-on-dark);
  outline-offset: 3px;
  border-radius: 2px;
}

/* --- Contact: the form's own states ------------------------------------- */

/* The heading column travels with the form. It held a heading and one
   paragraph beside 700px of fields, and the empty half read as a gap. */
@media (min-width: 900px) {
  .page--dark .split--form > :first-child { position: sticky; top: calc(var(--header-h) + var(--s-7)); align-self: start; }
}

/* The honeypot. Off-screen rather than display:none, because some bots skip
   what is hidden and fill what is merely positioned away. Never focusable. */
.page--dark .field--trap {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.page--dark .form__status {
  margin: 0;
  padding: var(--s-4) var(--s-5);
  border-radius: var(--radius-dark-ui);
  font-size: var(--fs-body-sm);
  line-height: 1.55;
}
.page--dark .form__status--ok { background: var(--c-raised); color: var(--c-invert); border-left: 3px solid var(--c-accent-on-dark); }
.page--dark .form__status--fail { background: var(--c-raised); color: var(--c-invert); border-left: 3px solid #E5573F; }
/* The mail-client draft. Neutral on purpose — it is neither a success nor a
   failure, and it must not be mistaken for either. */
.page--dark .form__status--pending { background: var(--c-raised); color: var(--c-invert); border-left: 3px solid var(--c-aluminium); }
.page--dark .form__status a { color: var(--c-accent-on-dark); }
.page--dark .form button[disabled] { opacity: 0.55; cursor: progress; }

/* --- Mobile density: spacing, never type size --------------------------- */

@media (max-width: 767px) {
  .page--dark .spec-row { padding-block: var(--s-3); }
  .page--dark .glance-list li { padding-block: var(--s-3); }
  .page--dark .variant__text { padding: var(--s-4); }
  .page--dark .variant__name { font-size: var(--fs-body); }
  .page--dark .application-grid { gap: var(--s-6) var(--gap-media); }
  .page--dark .section-head { margin-bottom: var(--s-6); }
}

/* --- Visited states for the components that set their own link colour ---- */

.page--dark .family__link:link,
.page--dark .family__link:visited,
.page--dark .variant__link:link,
.page--dark .variant__link:visited { color: var(--c-invert); }

.page--dark .family__variants a:link,
.page--dark .family__variants a:visited,
.page--dark .selection-row__answer:link,
.page--dark .selection-row__answer:visited,
.page--dark .update-row__title a:link,
.page--dark .update-row__title a:visited { color: var(--c-invert); }

.page--dark .prose a:link,
.page--dark .prose a:visited,
.page--dark .form__status a:link,
.page--dark .form__status a:visited { color: var(--c-accent-on-dark); }

/* --- Contrast corrections, measured -------------------------------------
   Every ratio below was computed from the rendered foreground against the
   first ancestor that actually paints a background. */

/* 1.13:1 -> 15.9:1. Card titles took the LIGHT theme's ink from sections.css
   and nothing on the dark ground overrode it. */
.page--dark .family__name,
.page--dark .variant__name { color: var(--c-invert); }

/* 1.85:1 -> 4.6:1. The section ordinal was quieter than it was legible. It is
   hidden inside the page header on the Projects routes, so those are untouched. */
.page--dark .section-mark { color: #808080; }   /* 5.3:1 on black, 4.6:1 on the raised band */
/* On the white reference panel the same grey is 3.4:1, so the ordinal there
   takes the panel s own muted token instead. */
.page--dark .band--invert .section-mark { color: var(--c-muted-on-invert); }

/* 3.43:1 -> 16.4:1. Brand blue on the white reference panel does not reach
   4.5:1 at label size. The group labels keep their mono uppercase treatment,
   which is what distinguishes them, and take the panel's own ink. */

/* 1.94:1. approach__figure paints the light mist band from sections.css, so a
   figure frame on About stayed light while the page around it went black, and
   its caption used the muted colour meant for the dark ground. The frame takes
   the raised dark surface; the caption follows it. */
.page--dark .approach__figure {
  background: var(--c-raised);
  border-radius: var(--radius-dark-card);
}
.page--dark .approach__figure .t-caption,
.page--dark .approach__figure figcaption { color: var(--c-muted-on-ground); }

/* Any caption that does sit on a light band takes the inverted muted token. */
.page--dark .band--invert .t-caption,
.page--dark .band--invert .t-muted { color: var(--c-muted-on-invert); }

/* --- The mobile panel ----------------------------------------------------
   The close button's bars, the panel's Contact us and its focus rings moved to
   components.css on 13 Sep 2026. The homepage loads home.css, not this file,
   so on the homepage the × was ink on black and Contact us was ink text on a
   transparent button — both present and invisible. One definition, in the
   sheet every page loads, fixes both pages. */

.page--dark .mobile-nav__link:link,
.page--dark .mobile-nav__link:visited { color: var(--c-invert); }
.page--dark .mobile-nav__link[aria-current="page"] { color: var(--c-accent-on-dark); }

/* --- Scrolling out from under the sticky header --------------------------
   The header is sticky, so anything the browser or a script scrolls to lands
   behind it: the first invalid field on an empty submit, and the success and
   failure messages afterwards. `scroll-margin-top` is what native validation
   and `scrollIntoView` both honour, so the field, its label and its validation
   bubble all clear the header. `scroll-padding-top` covers in-page anchors. */

html { scroll-padding-top: calc(var(--header-h) + var(--s-6)); }

.page--dark .field,
.page--dark .field input,
.page--dark .field select,
.page--dark .field textarea,
.page--dark .form__status,
.page--dark .form__actions { scroll-margin-top: calc(var(--header-h) + var(--s-7)); }
