/* ==========================================================================
   nanocosmos Product Theme
   ==========================================================================

   A dark, technical UI theme extracted from player-diagnostics.html
   (nanoStudio Player Diagnostics), re-based 2026-09-03 on captions-workflow's
   in-production redesign (see the :root block below) — panels now carry a
   real shadow rather than being fully flat; the rest of the visual language
   (orange for primary/committing actions, blue for secondary actions and
   status, pill-shaped buttons and badges) is unchanged.

   USAGE
   --------------------------------------------------------------------------
   1. Load fonts in <head> before this stylesheet. Self-hosted is the target
      (see captions-workflow's own styles/fonts.css for a working example —
      GDPR: a Google Fonts request sends the visitor's IP to Google before
      consent is possible) — vendor the same families/weights from Google
      Fonts (OFL-licensed) into your own @font-face rules pointed at
      nanocosmos.net. Until that's done for a given page, the Google Fonts
      <link> below is an acceptable stopgap — this is a known open item, see
      the font-hosting note in SKILL.md:

      <link rel="preconnect" href="https://fonts.googleapis.com">
      <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
      <link href="https://fonts.googleapis.com/css2?family=Lexend+Deca:wght@400;700&family=Source+Sans+3:wght@400&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
      <link rel="stylesheet" href="/assets/product-theme.css">

   2. Fonts:
      - Lexend Deca (700 Bold) — page/panel titles, buttons.
      - Lexend Deca (400 Regular) — header eyebrow labels only.
      - Source Sans 3 (400 Regular) — all other body text (labels,
        helper text, values, status text). This is the default body font.
        (Was Source Sans Pro — renamed upstream; captions-workflow's
        2026-09 redesign moved product pages onto the current name, so
        product pages no longer diverge from marketing's font-family choice
        here, only in hosting/weights.)
      - JetBrains Mono (400 Regular) — monospace content only (event log,
        raw JSON payload dumps). Was Roboto Mono — swapped 2026-09-03 to
        match captions-workflow, which already self-hosts this family.

   3. Components documented section by section below. Each section can be
      lifted independently — they only depend on the :root tokens.

   3a. Permission / consent gate (.engage-*, near the bottom, before the
      footer section): optional — only include it if the page gates a
      sensitive browser permission request (camera, mic, location, etc.)
      behind an explicit user action. Skip it entirely otherwise.

   3b. Header product identity: use the product line's canonical logo image
      (.logo-lockup) — never spell the product name out as plain eyebrow
      text (e.g. don't type "nanoStream"). See the comment above
      .logo-lockup for the canonical asset URL per product line.

   4. This file is shared by both player-diagnostics.html (the tool itself)
      and diagnostics-theme-moodboard.html (the visual reference page) —
      both link this file directly (`assets/product-theme.css` in that
      repo) rather than embedding a copy; player-diagnostics.html moved
      off its old inline self-contained copy 2026-09-04, once the file
      that would previously have needed manual re-syncing could just be
      linked instead. The final section, "Moodboard / documentation page
      styles", is only used by the moodboard.

   5. No infinite/looping animations (2026-09-01, direct feedback on two
      separate product-mode pages). Status/loading dots, spinners, "breathe"
      or "ping" effects — all static, never `animation: ... infinite`. State
      is communicated by color and label already; motion on an element the
      visitor sits looking at for a while reads as distracting, not
      informative. One-shot ENTRANCE animations (a card fading/sliding in,
      a toast sliding in) are unaffected by this rule — the objection is to
      motion that never stops, not to all motion.

   6. No green/red/yellow status colors, on ANY product/demo page (2026-09-01,
      direct instruction — not brand colors, must not appear even for
      success/error/warning/live semantics). This file was already
      status-token-free for exactly this reason ("Status" section deliberately
      doesn't exist here — a consuming page must not invent green/red/amber
      tokens of its own either, which is what captions-workflow's own
      base.css originally did before this was caught and corrected). If a
      page needs to signal success/error/warning/live states, use ONLY
      --accent-orange (attention: error, warning, live — bucketed together,
      there is no third hue) and --accent-blue (positive: success, done).
      Icons and text labels carry the specific meaning; color only signals
      attention-level. Don't reach for a semantic-but-off-brand hue just
      because it's the conventional choice elsewhere.

   7. Panels carry a real shadow (2026-09-03, re-based on captions-workflow's
      in-production redesign — see the :root block below). This SUPERSEDES
      the theme's original "flat, no drop-shadow, border + fill only"
      philosophy for the base .panel surface: captions-workflow's own
      design language leans on shadow-based depth as a core mechanism (a
      "One Lift Rule" — one shadow-level surface per screen — documented in
      that repo), and stripping shadows back out would fight the real page
      this theme is meant to track. The .modal-panel exception noted
      elsewhere in this file is no longer the ONLY shadowed surface, just
      the one with the strongest (--shadow-lg) elevation.

   8. Five more optional, page-pattern sections were added 2026-09-04, all
      extracted from captions-workflow's in-production implementation (the
      reusable structure/behavior only, never that page's own content) —
      each is documented with its own "why" at its section header, use only
      what a given page actually needs:
        - .drawer-overlay / .drawer-panel — a side-sliding panel for
          content too large for a centered modal.
        - .toast-stack / .toast — transient corner notifications.
        - .journey / .station — a guided, one-step-at-a-time setup flow
          (distinct from the AV workbench pattern above, which is for a
          live preview/monitor page instead).
        - .snippet / .lang-switch — a copyable, multi-language code sample
          block, including hand-rolled syntax coloring built from this
          file's own two accents plus its text-color scale (no new hue).
        - .detail-rail — a sticky, wide-viewport-only companion panel for
          reference material that would crowd a narrow page.

   9. Header/footer auto-widen for AV-workbench pages (2026-09-07, fixed
      2026-09-08). The default .header-inner/.footer-inner column-align to
      .container (1040px) below — correct for most product pages, but a
      page built on the AV workbench pattern (item 1 above) uses the wider
      .workbench column instead, and a plain 1040px header on top of that
      body reads as visibly misaligned. Rather than make every workbench
      page hand-write that override in its own page-specific CSS (a
      chromakey-tuner-demo redesign did exactly that before this was
      folded back in here), the header/footer auto-detect a .workbench
      anywhere on the page — see the body:has(.workbench) rule right
      after .workbench-layout below. Pages built on .container are
      unaffected.

      That rule's max-width is 1616px, NOT .workbench's own 1520px —
      this looks wrong but isn't. .workbench has zero side padding (its
      width IS its visible edge-to-edge box), while .header-inner/
      .footer-inner carry 48px of padding on top of their box (so their
      logo/text starts inset from the box edge, not flush with it). Give
      both the same 1520px max-width and the header/footer content ends
      up 48px further right than the workbench's edge at any viewport
      above the 1616px breakpoint — confirmed empirically at 1920px on
      live-plus-replay and chromakey-tuner-demo (content at x=248 vs.
      workbench at x=200, screenshot-measured). Padding is content
      *inside* a border-box width, so to get a 1520px-wide content area
      that starts flush with .workbench's edge, the box itself has to be
      1520 + (48 * 2) = 1616px. Do not "simplify" this back to 1520 to
      match .workbench's own value — that reintroduces the 48px gap.

   ========================================================================== */

:root {
  /* Surfaces — 3-tier system re-based 2026-09-03 on captions-workflow's
     in-production redesign (see that repo's styles/base.css for the
     original derivation). Page background AND text/form-input boxes share
     the DARKEST tier (--bg) — a deliberate merge, not a coincidence: an
     input box reads as "a field" via its border + being darker than the
     panel it sits in, not via a separate color of its own. Panels/cards
     (--panel) are a lighter mid-tone. Nested-but-lighter chip content
     (dropdown option hover, disabled-state fills) is --panel-soft — this
     is LIGHTER than --panel, a deliberate reversal of the theme's original
     "recessed surfaces get darker" convention. --panel-inset is kept as a
     token (rather than merged into --bg outright) for legibility at call
     sites that mean "this is a field-shaped box", even though its value
     now equals --bg exactly. */
  --bg: #0d171e;               /* page background AND text/form-input boxes (darkest tier) */
  --panel: #19222b;            /* card / panel background (was #252a2f) */
  --panel-inset: #0d171e;      /* inputs, log body, code blocks — same darkest tier as --bg (was #1c2228, a separate darker tier) */
  --panel-soft: #202a32;       /* nested chip content lighter than --panel: disabled fills, hover states, dropdown options */
  --border: #2e343d;           /* 1px borders on panels, inputs, dividers */
  --border-strong: #3a4149;    /* borders that need more contrast than --border (e.g. against --panel-soft) */

  /* Accents */
  --accent-orange: #e5883c;        /* primary / committing actions, footer */
  --accent-orange-hover: #f0954a;  /* hover/active state for orange elements */
  --accent-blue: #408bc3;          /* secondary actions, checked checkboxes */
  --accent-blue-hover: #5398cf;    /* hover/active state for blue elements */

  /* Text — unified to white 2026-09-03 (was: white outside boxes, grey
     inside them). Box-ness is now carried entirely by --panel-inset being
     darker than --panel, so text no longer has to do that job too —
     --text-secondary equals --text-primary now; kept as a separate token
     only so existing call sites that reference it by name don't need a
     find/replace. --text-muted stays a real grey — reserved strictly for
     placeholder text and disabled state, which must read as inactive. */
  --text-primary: #ffffff;     /* headings, values, primary labels — was #eef1f3 */
  --text-secondary: #ffffff;   /* now equals --text-primary — was #97a1ad (grey, "inside a box") */
  --text-muted: #9aa1a8;       /* placeholders, disabled state ONLY — was #6b7480 */
  --text-on-accent: #ffffff;   /* text sitting on orange/blue fills */

  /* Fonts — Source Sans 3 / JetBrains Mono as of 2026-09-03 (were Source
     Sans Pro / Roboto Mono) — matches captions-workflow, which self-hosts
     both. See the USAGE note above for hosting. */
  --font-heading: 'Lexend Deca', sans-serif;   /* titles + buttons, weight 700 (400 for header eyebrow) */
  --font-body: 'Source Sans 3', 'Open Sans', sans-serif;  /* everything else, weight 400 */
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;  /* event log, raw payload dumps */

  /* Shadows — added 2026-09-03, re-based on captions-workflow (see note 7
     above). Neutral black, not brand-tinted — a shadow on a dark page just
     needs to read as depth, not carry color. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.45);      /* base .panel elevation */
  --shadow-lg: 0 22px 60px rgba(0, 0, 0, 0.6);   /* .modal-panel — the strongest elevation on the page */
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-weight: 400;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

main { flex: 1; }

a { color: inherit; }

button, input {
  font-family: inherit;
}

/* Native browser focus-ring reset (added 2026-09-01, caught on a real
   page — captions-workflow). Without this, a plain mouse CLICK on a
   focusable element with no outline rule of its own falls through to the
   browser's NATIVE outline on plain :focus (most browsers only paint
   :focus-visible for keyboard interaction, not pointer clicks) — and
   Safari's native ring follows the visitor's OS system accent color, NOT
   this theme's --accent-blue. Reported in the wild as "green outline on
   a blue button." This reset makes :focus-visible below the only possible
   source of a visible ring, on every element type, regardless of how
   focus was triggered. */
a, button, select, input, [tabindex] {
  outline: none;
}

/* Focus states — keep visible, this is a tool meant for keyboard use */
input:focus-visible,
button:focus-visible,
select:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent-blue);
  outline-offset: 2px;
}
.btn-primary:focus-visible {
  outline-color: var(--accent-orange-hover);
}

/* ==========================================================================
   Header — brand-blue gradient bar, full width, content column-aligned
   ========================================================================== */

.header {
  /* Dark-to-light blue gradient, weighted toward the darker stop so the
     lighter tone only takes over in the bottom quarter. Adjust the two
     hex values and the 25% stop to retune the balance/smoothness. */
  background: linear-gradient(180deg, #00486c 0%, #00486c 25%, #0f8dc8 100%);
  padding: 9px 0;
  border-bottom: 1px solid #0f8dc8;
}

/* Shares max-width + horizontal padding with .container / .footer-inner so
   header/body/footer content edges all line up regardless of viewport. */
.header-inner {
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 18px;
}

/* Logo lockup image (brand mark + product name combined asset).
   Standard: identify the product line with its canonical logo image here,
   not with plain eyebrow text (e.g. don't type "nanoStream" as a label —
   use the logo asset below). Canonical URLs by product line:
     nanoStream — https://www.nanocosmos.net/wp-content/uploads/2026/06/nanoStream-logo-V1.webp
     nanoStudio — https://www.nanocosmos.net/wp-content/uploads/2026/06/nanoStudio-logo-V2.webp
   Pair it with .header-divider + .nav-label (below) for the page-specific
   name (e.g. "Instant Test", "Player Diagnostics") — see product-sample.html. */
.logo-lockup {
  display: block;
  height: 30px;
  width: auto;
}

.header-divider {
  width: 1px;
  height: 34px;
  background: rgba(255, 255, 255, 0.22);
}

.nav-label {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}

.nav-label .eyebrow {
  font-family: var(--font-heading);
  font-size: 11px;
  font-weight: 400; /* Lexend Deca Regular, not Bold — small caps eyebrow */
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #ffffff;
}

.nav-label .title {
  font-family: var(--font-heading);
  font-size: 16px;
  font-weight: 700; /* Lexend Deca Bold */
  color: #ffffff;
}

.header-right {
  display: flex;
  align-items: center;
  gap: 18px;
}

/* Solid white border (2026-09-04, matches captions-workflow/product-sample.html
   exactly) — was a translucent rgba(255,255,255,0.35) border with no hover
   state at all. This is the standard treatment for the header's own button
   pair (see .header-right usage note above .theme-pill's markup in
   product-sample.html: "Start for free" + "Contact", always exactly these
   two, see-through with this white outline — never solid-filled). */
.theme-pill {
  border: 1px solid #ffffff;
  background: transparent;
  color: #ffffff;
  border-radius: 999px;
  padding: 5px 14px;
  font-size: 12px;
  font-weight: 500;
  text-decoration: none; /* often used on <a> nav links — don't show the default link underline */
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s;
}
.theme-pill:hover { background: rgba(255, 255, 255, 0.14); }

/* Secondary brand mark image (company logo, right side of header) */
.brand-mark {
  display: block;
  height: 26px;
  width: auto;
}

/* ==========================================================================
   Page layout
   ========================================================================== */

.container {
  max-width: 1040px;
  margin: 0 auto;
  padding: 24px 16px 40px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* Base panel: the card used everywhere (form, player, stats, log). Carries
   a shadow as of 2026-09-03 (see top-of-file note 7) — was flat/border-only. */
.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 4px; /* deliberately modest — squared-off, not pill-like */
  box-shadow: var(--shadow);
}

/* ==========================================================================
   Form panel — label-beside-input layout
   ========================================================================== */

.form-panel {
  padding: 20px;
}

.field {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  margin-bottom: 16px;
}
.field:last-of-type { margin-bottom: 0; }

.field label {
  flex: 0 0 200px; /* fixed label column so inputs align vertically */
  padding-top: 10px; /* optically aligns label baseline with input text */
  font-size: 14px; /* 2026-09-04: labels 14px, field content (input/select) 16px — deliberately different sizes now, not both 16px */
  font-weight: 500;
  color: #ffffff;
}

.field-control {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

/* Plain uniform border on every side, deliberately — no colored
   left-accent "stripe" on any field, ever (confirmed 2026-09-01, direct
   request after a consuming page — captions-workflow — had grown its own
   orange-left-accent form style; removed there too once it was pointed
   out it didn't match this theme). If a page needs to flag an error
   state, use border-color on ALL sides (e.g. var(--accent-orange)), never
   a single accented edge. */
.field input[type="text"],
.field input[type="password"],
.field select {
  background: var(--panel-inset);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text-secondary); /* now white like everything else — the box reads as "a box" via --panel-inset's darker background, not text color; see note above :root */
  padding: 10px 12px;
  font-size: 16px;
  width: 100%;
}

.field select:hover { border-color: var(--accent-blue); }
.field select:disabled { color: var(--text-muted); cursor: not-allowed; }

.field input::placeholder {
  color: var(--text-muted);
}

/* Stack label above input below 640px so the fixed label column doesn't
   crowd the field on narrow viewports. */
@media (max-width: 640px) {
  .field {
    flex-direction: column;
    gap: 6px;
  }
  .field label {
    flex-basis: auto;
    padding-top: 0;
  }
}

.field .helper {
  font-size: 11.5px;
  line-height: 1.5;
  color: #ffffff;
}

.checkbox-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin: 20px 0;
  padding: 16px 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.checkbox-item {
  display: flex;
  align-items: center;
  gap: 8px;
}

.checkbox-item input[type="checkbox"] {
  width: 17px;
  height: 17px;
  accent-color: var(--accent-blue);
}

.checkbox-item label {
  font-size: 13px;
  color: var(--text-primary);
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.btn {
  font-family: var(--font-heading); /* Lexend Deca */
  border: none;
  border-radius: 999px; /* pill */
  padding: 10px 22px;
  font-size: 14px;
  font-weight: 700; /* Bold */
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.05s ease;
}
.btn:active { transform: translateY(1px); }
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

/* Use for the single primary/committing action per view (load, save, copy) */
.btn-primary {
  background: var(--accent-orange);
  color: var(--text-on-accent);
}
.btn-primary:hover:not(:disabled) { background: var(--accent-orange-hover); }

/* Use for secondary/utility actions (resume, stop, clear, mute) */
.btn-secondary {
  background: var(--accent-blue);
  color: var(--text-on-accent);
}
.btn-secondary:hover:not(:disabled) { background: var(--accent-blue-hover); }

@media (prefers-reduced-motion: reduce) {
  .btn { transition: none; }
}

/* ==========================================================================
   Monitor / player surface
   ========================================================================== */

.player-panel {
  overflow: hidden; /* clips the video element to the panel's border-radius */
  padding: 0;
}

.video-surface {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10.5;
  background: #000000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.video-el {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000000;
}

.status-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  padding: 14px 20px;
}

.status-left {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* ==========================================================================
   Badges — status pills
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 13px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
}

.badge.paused { background: var(--accent-orange); color: var(--text-on-accent); }
.badge.playing { background: var(--accent-blue); color: var(--text-on-accent); }
.badge.loading { background: var(--accent-blue); color: var(--text-on-accent); }

.badge .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
/* Static, not animated — deliberate (2026-09-01, per repeated direct
   feedback on two separate product-mode pages, instant-test/instanttest.html
   and captions-workflow/index.html): infinite looping animations on status
   dots/spinners (pulse, breathe, ping, spin) read as distracting rather than
   informative on a page a visitor sits with. State changes (paused → loading
   → playing) already carry the state via color/label; motion adds nothing
   the badge doesn't already say. Applies to every status/loading indicator
   in this theme, not just this one dot — don't reintroduce `infinite`
   animations elsewhere in this file without checking with the user first. */
.badge.loading .dot {
  opacity: 0.9;
}

.status-left .status-text,
.status-right {
  font-size: 12px;
  color: #ffffff;
}

/* ==========================================================================
   Stats / info panels — label/value row list
   ========================================================================== */

.stats-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}
@media (max-width: 640px) {
  .stats-row { grid-template-columns: 1fr; }
}

.stats-panel {
  padding: 20px;
}

.stats-panel h2 {
  font-family: var(--font-heading);
  margin: 0 0 12px;
  font-size: 15px;
  font-weight: 700;
  color: var(--text-primary);
}

.info-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.info-row:last-child { border-bottom: none; }

/* Use for long values (URLs, IDs) that need to wrap onto their own line
   instead of squeezing next to the label */
.info-row.wrap {
  flex-direction: column;
  gap: 6px;
  align-items: stretch;
  padding: 10px 0;
}

.info-row .label {
  color: #ffffff;
  flex-shrink: 0;
}

.info-row .value {
  color: var(--text-primary);
  font-weight: 500;
  text-align: right;
}

/* Renders as a field-shaped box (background/border, --panel-inset) —
   matching selects/inputs above, which is what reads as "a box" now, not
   the text color (--text-secondary is white like everything else). */
.info-row.wrap .value {
  text-align: left;
  word-break: break-all;
  font-weight: 400;
  font-family: var(--font-mono);
  font-size: 13.5px;
  color: var(--text-secondary);
  background: var(--panel-inset);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
}

/* Expandable "raw payload" disclosure row */
.raw-toggle {
  width: 100%;
  background: none;
  border: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 7px 0 0;
  font-size: 13px;
  color: #ffffff;
  cursor: pointer;
  font-family: inherit;
}
.raw-toggle:hover { color: var(--accent-blue-hover); }

.raw-toggle .chevron {
  transition: transform 0.15s ease;
  display: inline-block;
}
.raw-toggle[aria-expanded="true"] .chevron {
  transform: rotate(90deg);
}
@media (prefers-reduced-motion: reduce) {
  .raw-toggle .chevron { transition: none; }
}

.raw-payload {
  display: none;
  margin-top: 10px;
  background: var(--panel-inset);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: #ffffff;
  overflow-x: auto;
  white-space: pre;
}
.raw-payload.open { display: block; }

/* ==========================================================================
   Event log
   ========================================================================== */

.log-panel {
  padding: 20px;
}

.log-panel h2 {
  font-family: var(--font-heading);
  margin: 0 0 12px;
  font-size: 15px;
  font-weight: 700;
  color: var(--text-primary);
}

.log-box {
  background: var(--panel-inset);
  border: 1px solid var(--border);
  border-radius: 8px;
  height: 210px;
  overflow-y: auto;
  padding: 12px 14px;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.7;
}

.log-line {
  white-space: pre-wrap;
  word-break: break-word;
}
.log-line .ts { color: #ffffff; margin-right: 10px; }
.log-line .evt { color: var(--text-primary); }

.log-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 16px;
}

.copy-confirm {
  font-size: 12px;
  color: var(--accent-blue-hover);
  opacity: 0;
  transition: opacity 0.15s ease;
}
.copy-confirm.show { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .copy-confirm { transition: none; }
}

/* ==========================================================================
   Permission / consent gate — full-screen overlay with a single primary
   action, shown before the page requests a sensitive browser permission
   (camera, mic, location, notifications, etc.) so the browser's own
   prompt doesn't come out of nowhere. Extracted from instanttest.html's
   camera/mic gate (2026-08) — optional section, only needed by pages that
   gate a permission request behind an explicit user action.

   Usage: swap .engage-title / .engage-hint copy for the page's own —
   title states the payoff, hint states what's about to be asked for and
   why (plus a privacy reassurance, e.g. "nothing is recorded or stored").

   Deliberately no pulsing/blinking animation on .engage-btn: a blinking
   CTA reads as attention-grabbing at first glance but becomes visually
   noisy on a static page and can be uncomfortable for some users. A
   static shadow still stands out against the dimmed backdrop without
   relying on motion — see .btn-primary/#startButton-style CTAs elsewhere
   on the page for the same rule if they need extra emphasis.
   ========================================================================== */

.engage-overlay {
  position: fixed;
  inset: 0;
  background: rgba(13, 23, 30, 0.88);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 15vh;
  z-index: 10002;
  transition: opacity 0.6s ease;
}
.engage-overlay.fadeout { opacity: 0; pointer-events: none; }

.engage-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

.engage-title {
  font-family: var(--font-heading);
  font-size: 1.3em;
  font-weight: 700;
  color: #ffffff;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: -4px;
}

.engage-hint {
  color: #ffffff;
  font-size: 14px;
  text-align: center;
  max-width: 380px;
  line-height: 1.5;
  opacity: 0.85;
}

.engage-btn {
  padding: 18px 42px;
  background: var(--accent-orange);
  color: var(--text-on-accent);
  border: none;
  border-radius: 999px;
  font-family: var(--font-heading);
  font-size: 1.15em;
  font-weight: 700;
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5);
  transition: background-color 0.15s ease, transform 0.15s ease;
  opacity: 1;
}
.engage-btn.fadeout { opacity: 0; }
.engage-btn:hover { background: var(--accent-orange-hover); transform: translateY(-2px); }

@media (max-width: 640px) {
  .engage-btn { padding: 16px 28px; font-size: 1em; max-width: 90%; text-align: center; }
}

/* ==========================================================================
   Modal / dialog panel — centered overlay with a bordered panel, for a
   sign-in form, confirmation dialog, or settings panel. Optional — only
   include if the page needs one; skip it entirely otherwise. Added
   2026-09-01, generalized from captions-workflow's sign-in popup restyle:
   same .panel radius/border as everywhere else in this theme (this is NOT
   a separate "modal" visual language, just the standard panel, centered
   over a dimmed backdrop) — reuse .field/.btn/.badge inside it rather than
   inventing modal-specific form styling.
   ========================================================================== */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(13, 23, 30, 0.72);
}

.modal-panel {
  width: min(460px, 100%);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 4px; /* same modest radius as every panel in this theme */
  /* Strongest elevation on the page — a dialog floating over the page it
     covers needs to lift off it more than a base .panel does. */
  box-shadow: var(--shadow-lg);
  padding: 28px;
}

.modal-panel__title {
  font-family: var(--font-heading);
  font-size: 19px;
  font-weight: 700;
  color: var(--text-primary);
  margin: 0 0 4px;
}

.modal-panel__sub {
  font-size: 13.5px;
  color: var(--text-secondary);
  margin: 0 0 20px;
}

/* ==========================================================================
   Drawer — a panel that slides in from the side, for content too large for
   a centered modal (a media player, a long detail view) but still meant as
   a temporary overlay, not a permanent part of the layout. Optional — only
   include if the page needs one; skip it entirely otherwise. Added
   2026-09-04, extracted from captions-workflow's caption player drawer
   (`.e2e-player-drawer`) — only the reusable overlay/slide/backdrop
   mechanism is adapted here, not that page's player-specific content.
   ========================================================================== */

.drawer-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  pointer-events: none;
}
.drawer-overlay__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(13, 23, 30, 0.72); /* same overlay tint as .modal-overlay */
  opacity: 0;
  transition: opacity 0.24s ease-out;
}
.drawer-panel {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: min(960px, calc(100% - 48px));
  background: var(--panel);
  border-radius: 4px 0 0 4px; /* same modest radius as every panel, just one-sided */
  box-shadow: var(--shadow-lg); /* strongest elevation, same as .modal-panel */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transform: translateX(102%);
  transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
.drawer-overlay.open { pointer-events: auto; }
.drawer-overlay.open .drawer-overlay__backdrop { opacity: 1; }
.drawer-overlay.open .drawer-panel { transform: none; }
/* Keep the closed drawer out of the tab order and off-screen for assistive
   tech, but delay it until the slide-out transition finishes so the panel
   doesn't just vanish mid-animation. */
.drawer-overlay[aria-hidden="true"] { visibility: hidden; transition: visibility 0s 0.3s; }
.drawer-overlay.open[aria-hidden="false"] { visibility: visible; }

.drawer-panel__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  background: linear-gradient(180deg, #00486c 0%, #00486c 25%, #0f8dc8 100%); /* same header gradient, not a separate color */
  flex-shrink: 0;
}
.drawer-panel__title {
  font-family: var(--font-heading);
  font-size: 15px;
  font-weight: 700;
  color: #ffffff;
  white-space: nowrap;
}
.drawer-panel__close {
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 999px;
  color: #ffffff;
  padding: 7px;
  cursor: pointer;
  display: inline-flex;
  flex-shrink: 0;
  transition: background 0.15s ease;
}
.drawer-panel__close:hover { background: rgba(255, 255, 255, 0.18); }

.drawer-panel__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 18px;
}

/* ==========================================================================
   Toast — a transient corner notification. Optional — only include if the
   page needs one; skip it entirely otherwise. Added 2026-09-04, extracted
   from captions-workflow's `.app-toast`. Two states only, per the no-green/
   red/yellow rule: default reads as informational/positive (blue left
   edge), .toast--attn is the attention bucket (error, warning — orange left
   edge). There is no third variant — don't add one. Entrance is a one-shot
   slide-in, never looping.
   ========================================================================== */

.toast-stack {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 110;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(360px, calc(100vw - 32px));
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow);
  padding: 11px 12px 11px 16px;
  overflow: hidden;
  animation: toast-in 0.24s cubic-bezier(0.22, 1, 0.36, 1);
}
.toast::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  background: var(--accent-blue);
}
.toast--attn::before { background: var(--accent-orange); }
@keyframes toast-in {
  from { opacity: 0; transform: translateX(16px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

.toast__text { flex: 1; min-width: 0; }
.toast__title { font-size: 13.5px; font-weight: 700; color: var(--text-primary); }
.toast__body { font-size: 13px; color: var(--text-secondary); margin-top: 1px; overflow-wrap: break-word; }
.toast__dismiss {
  background: none;
  border: none;
  padding: 3px;
  margin: 0;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: 4px;
  flex-shrink: 0;
}
.toast__dismiss:hover { color: var(--text-primary); background: var(--panel-inset); }

/* ==========================================================================
   Footer — full-width orange bar, column-aligned content
   ========================================================================== */

.footer {
  background: var(--accent-orange);
  color: #ffffff;
  padding: 16px 0;
  font-size: 13px;
  margin-top: 40px; /* 2026-09-04, matches product-sample.html — was missing entirely */
}

.footer-inner {
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}

/* 2026-09-04: links no longer always-underlined — bold, white, underline
   only on hover, matching product-sample.html/captions-workflow. */
.footer a {
  color: #ffffff;
  text-decoration: none;
  font-weight: 600;
}
.footer a:hover {
  text-decoration: underline;
}

/* Page version stamp (2026-09-04 — added, was completely missing from this
   file despite being a mandatory brand requirement per SKILL.md's "Page
   Version Stamp" section: every generated page carries this, no exceptions).
   Solid #ffffff, not translucent — that's the marketing-tier convention,
   not this one. Markup:
     <div class="page-version" aria-label="Page version">
       <span>v<!-- version -->1.0.0<!-- /version --></span>
       <span aria-hidden="true">·</span>
       <span>Updated <!-- updated -->2026-09-04<!-- /updated --></span>
     </div>
   Keep the HTML comment markers intact — CI/scripts bump them without
   parsing HTML. */
.page-version {
  display: flex;
  gap: 8px;
  font-size: 12px;
  color: #ffffff;
  letter-spacing: 0.02em;
}

/* ==========================================================================
   AV workbench layout — for tool pages built around a live preview/monitor
   (chromakey setup, encoder config, stream dashboard, player diagnostics)
   ==========================================================================

   Added 2026-09-04. Adapted from the dark-neutral-studio skill's AV-workbench
   pattern — but only the STRUCTURE and the COPY RULES below are adapted.
   dark-neutral-studio's own color tokens (#ff821f orange / #07587f blue) and
   its "strong geometric sans" font suggestion are NOT used anywhere in this
   section — every color and font below is this file's own token
   (--accent-orange, --accent-blue, --panel, --border, --font-heading,
   --font-body). See SKILL.md's dark-neutral-studio priority note: for any
   nanocosmos page, this file's tokens always win, even when
   dark-neutral-studio is the skill that supplied the layout idea.

   Reading order for a page with a live preview (put elements in this order,
   top to bottom or in this visual priority):
   1. The preview/monitor/canvas itself — the thing the visitor is actually
      looking at.
   2. Its current status (connecting, live, error, etc).
   3. The controls that act on it.
   4. Setup, auth, and other one-time or secondary content.
   A setup form or instructions block must never be visually bigger or
   louder than the preview area.

   Status color rule: this file only has two status colors, per the "No
   green/red/yellow" rule earlier in this file (see :root comment) —
   --accent-orange for anything needing attention (error, warning, live)
   and --accent-blue for a positive/neutral state (connected, ready, done).
   Do not add a third hue for status. Use the icon or text label to say
   which specific thing is happening; color only signals "does this need
   attention or not."

   Copy style for this kind of page — write labels and messages like a
   control surface, not a pitch:
     Good: "Preview engine: JS canvas", "Live Apply", "Needs Apply",
           "Stream ready", "Capture blocked by browser".
     Avoid: "Experience the next generation of chromakey",
            "Unlock powerful creative possibilities",
            long explanatory tutorials inside the tool itself.
   This matches the "Copy: Functional, label-driven, brief, instructional"
   row in the Two-Tier table in SKILL.md — it is not a new rule, just a
   concrete example set for workbench-style pages.
   ========================================================================== */

.workbench {
  width: min(calc(100% - 96px), 1520px);
  margin: 18px auto 30px;
}

.workbench-layout {
  display: grid;
  grid-template-columns: minmax(720px, 1fr) minmax(390px, 440px);
  gap: 18px;
  align-items: start;
}

@media (max-width: 960px) {
  .workbench-layout {
    grid-template-columns: 1fr;
  }
}

/* Widen the header/footer content column to match .workbench's 1520px
   content width whenever a .workbench is present anywhere on the page —
   see item 9 in the top-of-file comment for why max-width here is 1616px
   (1520 + this rule's own 48px*2 padding), not 1520px: .workbench has no
   side padding of its own, so matching its 1520px figure directly here
   would leave the header/footer content sitting 48px further right than
   the workbench's actual edge (confirmed on real pages, fixed
   2026-09-08). Requires :has() (Chrome 105+, Safari 15.4+, Firefox 121+),
   an acceptable baseline for internal product/demo tooling. Pages built
   on .container only are unaffected. */
body:has(.workbench) .header-inner,
body:has(.workbench) .footer-inner {
  max-width: 1616px;
  padding: 0 48px;
}

/* The live preview surface. Stays near-black regardless of theme — this
   is the frame around video content, not a themed UI surface, so it does
   not use --panel/--bg. */
.monitor {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #050607;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
  box-shadow: var(--shadow);
}

/* Right-hand parameter panel. Use section dividers (.control-section)
   instead of nested cards for each group of controls. */
.control-rail {
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 2px solid var(--accent-blue);
  border-radius: 9px;
  padding: 18px;
}

.control-section {
  border-top: 1px solid var(--border);
  padding-top: 16px;
  margin-top: 16px;
}
.control-section:first-child {
  border-top: 0;
  margin-top: 0;
  padding-top: 0;
}

/* Compact status line for preview engine / connection / stream state.
   Default reads as informational/positive (blue). Add .status-strip--attn
   for error/warning/live states — do not invent a third color for this. */
.status-strip {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-primary);
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-blue);
  border-radius: 6px;
  padding: 7px 12px;
  font-size: 12px;
}
.status-strip--attn {
  border-left-color: var(--accent-orange);
}

/* ==========================================================================
   Guided journey / station list — for a page built around a linear,
   step-by-step setup flow (onboarding, activation, a multi-stage wizard)
   rather than a live preview/monitor (that's the AV workbench above).
   ==========================================================================

   Added 2026-09-04, extracted from captions-workflow's five-station
   onboarding journey (`.cw-journey`/`.cw-station`) — the reusable
   structure/interaction only (marker states, progress spine, spotlight,
   expand-on-current), not that page's own station content or copy.

   One station open at a time. Completed stations collapse to a one-line
   summary and stay visible (never removed from the DOM or hidden away) —
   the visitor should always be able to see how far they've come. Exactly
   one station carries the "current" spotlight + orange halo at a time; two
   competing "you are here" markers means the flow has failed.

   Status color rule (same two-hue system as everywhere else in this file):
   a done station's marker/spine is --accent-blue (positive/complete), the
   current station's marker is --accent-orange (the one thing needing
   action right now), a locked station is neutral border color. No green
   checkmark, no third hue.
   ========================================================================== */

.journey {
  position: relative;
  background: var(--panel);
  border-radius: 4px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
/* Hairline brand accent across the very top of the card — the one accent
   line in the whole component, appears exactly once. */
.journey::after {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--accent-blue);
  opacity: 0.9;
}

.station {
  position: relative;
  border-top: 1px solid var(--border);
}
.station:first-of-type { border-top: none; }

/* Connecting progress spine — a 2px line down the marker gutter, joining
   each station's marker to the next. Markers paint over it via head
   z-index (below). Position math assumes 22px card padding + a 30px
   marker (half-marker = 15px): left = 22 + 15 = 37px — re-derive both
   numbers together if either changes. */
.station::before {
  content: '';
  position: absolute;
  left: 37px;
  top: 0;
  bottom: 0;
  width: 2px;
  transform: translateX(-50%);
  background: var(--border);
  z-index: 0;
}
.station:first-of-type::before { top: 31px; }
.station:last-of-type::before  { bottom: auto; height: 31px; }
.station[data-state="done"]::before { background: var(--accent-blue); }
.station[data-state="current"]::before {
  background: linear-gradient(180deg, var(--accent-blue) 0px, var(--accent-blue) 31px, var(--border) 72px);
}

/* "You are here" spotlight on the active station — a plain translucent
   white lift, no hue, so it works regardless of accent color. */
.station[data-state="current"] { background: rgba(255, 255, 255, 0.04); }

.station__head {
  display: grid;
  grid-template-columns: 34px 1fr auto;
  align-items: center;
  column-gap: 14px;
  padding: 16px 22px;
  position: relative; /* lifts the head, including the marker, above the spine */
  z-index: 1;
}

.station__marker {
  width: 30px; height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-heading);
  font-size: 14px;
  font-weight: 700;
  flex-shrink: 0;
  border: 2px solid var(--border);
  color: var(--text-secondary);
  background: var(--panel);
  /* Ring in the panel's own color punches the marker out of the spine
     running behind it. */
  box-shadow: 0 0 0 4px var(--panel);
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}
.station[data-state="current"] .station__marker {
  background: var(--accent-orange);
  border-color: var(--accent-orange);
  color: #ffffff;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.04), 0 0 0 8px rgba(229, 136, 60, 0.18);
}
.station[data-state="done"] .station__marker {
  background: var(--accent-blue);
  border-color: var(--accent-blue);
  color: #ffffff;
  box-shadow: 0 0 0 4px var(--panel), 0 0 0 7px rgba(64, 139, 195, 0.12);
  animation: station-pop 0.24s ease-out;
}
@keyframes station-pop {
  0% { transform: scale(0.7); }
  60% { transform: scale(1.08); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .station[data-state="done"] .station__marker { animation: none; }
}

.station__title {
  font-family: var(--font-heading);
  font-size: 16.5px;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.25;
}
/* One-line recap shown once a station is done — never disappears, the
   trail behind the visitor stays visible. */
.station__summary {
  font-size: 13.5px;
  color: var(--text-secondary);
  margin-top: 1px;
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
}

/* Only the current (or explicitly reopened) station shows its body. Keep
   station bodies in the DOM even while hidden if any form fields inside
   them hold live state a script reads by id — hiding, not removing,
   preserves that state across steps. */
.station__body {
  display: none;
  padding: 0 22px 22px 70px; /* left-indents to sit under the title, not the marker */
}
.station[data-state="current"] > .station__body,
.station[data-open="1"] > .station__body {
  display: block;
  animation: station-body-in 0.22s ease-out;
}
@keyframes station-body-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .station__body { animation: none; }
}

.station__lead {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
}
.station__help {
  margin: 0 0 16px;
  font-size: 14px;
  color: var(--text-secondary);
  max-width: 62ch;
}

/* ==========================================================================
   Snippet viewer — a copyable, multi-language code sample block with a
   language switcher. For any page that shows the real API call behind a
   setup step. Optional — only include if the page needs one.
   ==========================================================================

   Added 2026-09-04, extracted from captions-workflow's per-station "API
   request" disclosures (`.cw-code-block`/`.cw-langswitch`) — the reusable
   block/switcher/copy-button mechanism, not that page's own snippet
   content or language list.

   Syntax coloring is hand-rolled from this file's own tokens, not a new
   palette: keywords = blue, strings = --text-secondary, comments =
   --text-muted (italic), function/type names = --text-primary, numbers =
   orange, tags = orange-hover. That's deliberate — six visually distinct
   token roles built from exactly the two brand accents plus the existing
   text-color scale, with no new hue introduced for syntax highlighting.
   ========================================================================== */

.snippet {
  position: relative;
  margin: 14px 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--panel-inset);
}
.snippet__head {
  display: flex;
  justify-content: flex-end;
  padding: 6px 8px;
  background: var(--panel-inset);
  border-bottom: 1px solid var(--border);
}
.snippet pre {
  margin: 0;
  padding: 16px 18px;
  background: transparent;
  overflow: auto;
  /* Caps how tall one snippet gets — several open at once must stay a
     quiet side path, never a wall of gray chrome competing with the
     page's primary content. Most snippets never hit this. */
  max-height: 280px;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.snippet pre::-webkit-scrollbar { width: 8px; height: 8px; }
.snippet pre::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; }
.snippet pre code {
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-primary);
  white-space: pre;
}
.snippet__copy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-secondary);
  padding: 6px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.snippet__copy:hover { color: var(--accent-blue-hover); border-color: var(--accent-blue); }
.snippet__copy--copied { color: var(--accent-blue); border-color: var(--accent-blue); }

/* One global language preference can drive every open snippet on the page
   at once — show only the panel matching the active language. */
.snippet[data-lang-count] pre[data-lang] { display: none; }
.snippet[data-lang-count] pre[data-lang].is-shown { display: block; }

.lang-switch {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 14px;
  padding: 8px;
  background: var(--panel-inset);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.lang-switch__btn {
  padding: 6px 14px;
  background: none;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.lang-switch__btn:hover { color: var(--text-primary); background: var(--panel); }
.lang-switch__btn.active {
  background: var(--panel);
  border-color: var(--border);
  color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}

.snippet pre code .tok-kw  { color: var(--accent-blue); }
.snippet pre code .tok-str { color: var(--text-secondary); }
.snippet pre code .tok-com { color: var(--text-muted); font-style: italic; }
.snippet pre code .tok-fn  { color: var(--text-primary); }
.snippet pre code .tok-num { color: var(--accent-orange); }
.snippet pre code .tok-tag { color: var(--accent-orange-hover); }

/* ==========================================================================
   Detail rail — a sticky companion panel that appears beside the main
   content only on wide viewports, carrying reference material (e.g. the
   snippet viewer above) that would otherwise crowd a narrow-measure page.
   Optional, opt-in, wide-screen-only — the page must still work as a
   single column with this panel simply absent below the breakpoint; never
   make it the only way to reach its content.
   ==========================================================================

   Added 2026-09-04, extracted from captions-workflow's developer API rail
   — a real, load-bearing pattern there (documented at length in that
   repo's CLAUDE.md: it replaced five separate inline disclosures with one
   synced panel on wide screens). Generalized here: one clean 1280px
   breakpoint and a fixed 420px rail width, instead of that page's two-tier
   1600/1800px thresholds tuned to its own specific 1040px content column —
   re-derive both numbers for whatever column width the new page actually
   uses, the same way that page did for its own.

   Structural rules only carry over, not the multi-step-picker/role-chip
   specifics — pick a step/tab picker (.detail-rail__tabs below) sized to
   however many items the new page's rail actually needs.
   ========================================================================== */

.detail-rail { display: none; }

@media (min-width: 1280px) {
  .detail-rail {
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 84px; /* clear a sticky header of that height — adjust to match */
    max-height: calc(100vh - 84px - 24px);
    width: 420px;
    flex-shrink: 0;
    background: var(--panel-inset);
    border: 1px solid var(--border);
    border-radius: 4px;
    overflow: hidden;
  }
}

.detail-rail__head {
  flex: none; /* fixed band above the scrolling body — never scrolls away */
  padding: 14px 18px 12px;
  border-bottom: 1px solid var(--border);
}
.detail-rail__kicker {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-primary);
}

.detail-rail__tabs { display: flex; gap: 4px; margin-top: 8px; }
.detail-rail__tab {
  width: 24px;
  height: 24px;
  padding: 0;
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
}
.detail-rail__tab:hover:not(:disabled) { color: var(--accent-blue); border-color: var(--accent-blue); }
.detail-rail__tab.active {
  background: var(--accent-blue);
  border-color: var(--accent-blue);
  color: #ffffff;
}
.detail-rail__tab:disabled {
  color: var(--text-muted);
  background: var(--panel-inset);
  cursor: not-allowed;
  opacity: 0.7;
}

.detail-rail__body {
  padding: 0 18px 18px;
  /* The one scroll container in the rail: fills whatever height the head
     leaves, and is what actually scrolls when content exceeds the rail's
     viewport cap above. min-height: 0 is load-bearing — a flex item's
     default min-height:auto refuses to shrink below its content, which
     would silently defeat the cap. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.detail-rail__body::-webkit-scrollbar { width: 8px; }
.detail-rail__body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; }
/* Self-gating "more below" fade: transparent → panel-inset over a
   panel-inset background, so it's invisible until content actually passes
   under it. */
.detail-rail__body::after {
  content: '';
  display: block;
  position: sticky;
  bottom: 0;
  height: 26px;
  margin: -26px -18px 0;
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--panel-inset));
}
/* No caption content ever needs a max-height inside the rail — the body
   above is the one scroll container; capping a nested element too would
   create a second, confusing scroller. */
.detail-rail .snippet pre { max-height: none; }

/* ==========================================================================
   Moodboard / documentation page styles
   ==========================================================================

   Presentation classes used only by diagnostics-theme-moodboard.html to
   showcase the tokens/components above (color swatches, gradient strip,
   type specimens, corner-radius comparison). Not part of the reusable
   component set — skip this section when lifting the theme into a new page.
   ========================================================================== */

.mb-container {
  max-width: 1040px;
  margin: 0 auto;
  padding: 40px 16px 60px;
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.mb-intro h1 {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 28px;
  margin: 0 0 8px;
  color: #ffffff;
}
.mb-intro p {
  max-width: 640px;
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
}

/* Caption under a section title explaining a demoed component's real
   behavior when the demo itself had to fake something (e.g. a fixed
   overlay shown static, in flow). Added 2026-09-04 for the journey/
   snippet/drawer/toast/detail-rail demos. */
.mb-pattern-note {
  max-width: 640px;
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 1.55;
  margin: -4px 0 0;
}
.mb-pattern-note code {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

.mb-section {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.mb-section-title {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-blue-hover);
  border-bottom: 1px solid var(--border);
  padding-bottom: 10px;
}

/* Color token swatches */
.mb-swatch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 16px;
}
.mb-swatch {
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
  background: var(--panel);
}
.mb-swatch-color {
  height: 64px;
}
.mb-swatch-label {
  padding: 10px 12px;
  font-size: 12px;
}
.mb-swatch-name {
  display: block;
  color: #ffffff;
  font-weight: 600;
  margin-bottom: 2px;
}
.mb-swatch-hex {
  display: block;
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: 11px;
}

/* Header gradient strip */
.mb-gradient-strip {
  height: 72px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: linear-gradient(180deg, #00486c 0%, #00486c 25%, #0f8dc8 100%);
}
.mb-gradient-caption {
  color: var(--text-secondary);
  font-size: 12px;
  font-family: var(--font-mono);
}

/* Type specimens */
.mb-type-row {
  display: flex;
  align-items: baseline;
  gap: 20px;
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}
.mb-type-row:last-child { border-bottom: none; }
.mb-type-meta {
  flex: 0 0 260px;
  color: var(--text-secondary);
  font-size: 12px;
  font-family: var(--font-mono);
  line-height: 1.6;
}
.mb-type-sample {
  color: #ffffff;
}
.mb-type-heading-bold {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 22px;
}
.mb-type-heading-regular {
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: 15px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.mb-type-body {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 15px;
}
.mb-type-mono {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 13px;
}

/* Component demo rows */
.mb-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.mb-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}
@media (max-width: 640px) {
  .mb-two-col { grid-template-columns: 1fr; }
  .mb-type-row { flex-direction: column; gap: 6px; }
  .mb-type-meta { flex-basis: auto; }
}

/* Corner-radius comparison cards */
.mb-radius-sample {
  width: 100%;
  height: 90px;
  background: var(--panel);
  border: 1px solid var(--border);
}
.mb-radius-row {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}
.mb-radius-card {
  flex: 1;
  min-width: 140px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.mb-radius-caption {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-secondary);
  text-align: center;
}
