    /* ============================================================================
       DESIGN TOKENS - THE STYLE KIT. EDIT HERE.
       This is the ONE place to change the look. Every color, font, and key size in
       the whole app is a variable below; landing.ts and tape.ts reference these names
       (never a raw hex). Change one line here and the app reflects it everywhere.
       Contrast note: text tokens clear 7:1 on the LIGHTEST ground (--bg-card), not the
       legal AA 4.5:1 - see the TEXT INK block. Type sizes come from THE TYPE SCALE below
       (--fs-*); a raw font-size literal in a page module is a bug the source test catches.
       ============================================================================ */
    :root {
      /* --- FONTS --- prose is Sans Hebrew (brand primary, Latin + Hebrew in one family);
         Mono is ONLY for data (values, keys, addresses, telemetry cells). --- */
      --font-display: 'IBM Plex Sans Hebrew', system-ui, -apple-system, 'Segoe UI', sans-serif;
      --font-mono: 'IBM Plex Mono', ui-monospace, monospace;
      --mono: var(--font-mono); /* legacy alias: existing rules read --mono */

      /* --- GROUND & SURFACES --- */
      --ground: #0c0a1d;            /* the page ground (deep space navy) */
      --bg-primary: var(--ground);
      --bg-secondary: #131029;      /* panels, cards, the tape */
      --bg-card: #1a1635;           /* raised cards (the x-ray) */
      --border: #2a2550;            /* hairlines, card borders */

      /* --- TEXT INK --- the floor here is 7:1, NOT the legal 4.5:1 (2026-08-02).
         WCAG AA 4.5:1 is the minimum a court would accept, and this codebase had
         been treating it as the target. Measured on the four busiest pages, EVERY
         text node under 7:1 was one token: --text-muted at 6.35:1 on the ground and
         5.65:1 on --bg-card. At arm's length on a phone that reads as grey mush,
         which is exactly what "too low contrast" meant. Every ink below now clears
         7:1 on the LIGHTEST ground we paint on (--bg-card #1a1635), so it clears it
         on all three. Hue and saturation are untouched - only lightness moved - so
         nothing changed color, it only stopped whispering. Re-solve with
         docs/product/ui-contrast.md if you add an ink. */
      --text-primary: #e8e6f0; --text-secondary: #c0bdd0; --text-muted: #a9a4b8;
      --silver: #b0b8c8;
      /* --faint is the ONE deliberately recessive ink: it ghosts a struck old value
         that has been REPLACED, so it must read as subordinate to the new one. It is
         a decorative/superseded state, which is the only thing allowed near the legal
         floor. 6.35:1 - still above AA. Never use it for text a reader must read. */
      --faint: #9590a8;

      /* --- LEGACY TOKEN ALIASES (design-panel P0 root cause #1, fixed 2026-07-16) ---
         Page styles across the reports + gated pages reference these short names about
         150 times (var(--muted) etc., mostly WITHOUT fallbacks). They were never defined, so the
         browser treated the property as invalid-at-computed-value and the pages fell
         back to inherited/initial colors - the "off-brand greys". Every alias maps onto
         the governed palette above; restyle THERE, never here. All text hues clear
         7:1 on the lightest ground, not just AA 4.5:1 (ui-contrast.md). */
      --bg: var(--bg-primary); --panel: var(--bg-secondary); --panel-2: var(--bg-card);
      --bg3: var(--bg-card); --bg-input: var(--bg-secondary); --line: var(--border);
      --text: var(--text-primary); --muted: var(--text-muted); --dim: var(--text-muted);
      --teal: #2dd4bf; --cyan: #45c0e0; --purple: #b198fb; --orange: #fb923c;
      --amber: var(--amber-text); --red-dim: rgba(220,38,38,0.14);

      /* --- DOMAINDRIFT BLUE --- the primary accent (brand, links, hero accent, scan line).
         --blue is a dark FILL (white text on it clears AA); --blue-text is the bright
         accent used for text/links, 7.85:1 on the ground and 7.21:1 on --bg-card. See
         ui-contrast.md. --- */
      --blue: #1d4ed8; --blue-dim: rgba(59,130,246,0.15); --blue-text: #68aafa;
      --blue-glow: rgba(59,130,246,0.18);   /* the soft hero glow */
      --scan-line: rgba(96,165,250,0.9);     /* the x-ray sweep */
      --scan-dot: rgba(96,165,250,0.14);     /* the telemetry dot field (loading surfaces) */

      /* --- DRIFT MAGENTA --- the ONE thing that moved. NEVER decorative: it marks only a
         field that drifted (x-ray flip) or a tape count of infra that moved. Lifted in
         lightness only 2026-08-02 (from the old 5.45:1 magenta) to clear 7:1 on every ground; same
         hue, same meaning, it just stopped reading as mush on a card. */
      --drift-magenta: #ff7e9e;
      --mag: var(--drift-magenta); --mag-dim: rgba(255,77,121,0.14);
      --pink: #fc7db3; --pink-dim: rgba(251,61,142,0.12); /* the pricing "soon" tag (brand pink) */

      /* --- STATUS --- green = signed/up, amber = blocked, red = down. --- */
      --green: #15803d; --green-text: #4ade80;
      --amber-text: #fbbf24;
      --red: #dc2626; --red-text: #f98585;

      /* --- PROVIDER CHIP COLORS --- each ALSO USES chip gets its own brand color. Every one
         clears 7:1 as text on --bg-card (the lightest ground), not the old AA 4.5:1 - seven of
         them sat between 5.6 and 6.9 and read as mush. Lightness only; the vendor hue is intact.
         An unknown provider falls back to --blue-text. Edit a line to recolor a provider
         everywhere. --- */
      --chip-cloudflare: #f78c30; --chip-aws: #ff9900; --chip-google: #8ab4f8;
      --chip-microsoft: #56acff;  --chip-fastly: #ff8087; --chip-akamai: #45c0e0;
      --chip-github: #e9eef6;     --chip-shopify: #a6d06a; --chip-digicert: #7bc043;
      --chip-sectigo: #f08b83;    --chip-letsencrypt: #7ea8ea; --chip-godaddy: #2ed9d9;
      --chip-oracle: #f78686;     --chip-mail: #8ab4f8; --chip-neutral: #e9eef6;

      /* --- THE EYE MARK + WORDMARK --- the header/favicon logo. DomainDrift blue eye, magenta
         drifted pupil. The wordmark is clean + bold: "Domain" in ink, "Drift" in brand blue. --- */
      --eye-blue: #3B82F6;        /* the eye outline + resting pupil (DomainDrift broker blue) */
      --wm-domain: var(--text-primary); /* "Domain" - ink */
      --wm-drift: #3B82F6;              /* "Drift" - brand blue */
      /* the drifted pupil dot reuses --drift-magenta */

      /* ============================================================
         THE TYPE SCALE (2026-08-02). ONE ratio, ONE floor, no ad-hoc sizes.
         Before this block the app shipped 40+ different font sizes (10, 11, 12,
         12.5, 13, 14, 15, 16, 17, 18, 20, 23, 24, 34 ... ) and 169 of the text
         nodes on /features were under 14px, 84 of them at 11px or smaller. That
         is not a design decision, it is what happens when nobody owns the scale.

         ABOVE THE BASE the ladder is a fixed 1.2 ratio off 16px:
             16 -> 19.2 -> 23.0 -> 27.6 -> 33.2 -> 39.8 -> 47.8, rounded.
         BELOW THE BASE there are exactly TWO steps and both clear the human
         floor, because a ratio taken downward from 16 lands at 13.3 and then
         11.1 - sizes a person holding a phone at arm's length cannot read.

         THE FLOOR IS 14px. Body, supporting text, chips, captions, table cells,
         footnotes: --fs-xs or larger, always. --fs-micro (13px) is legal ONLY
         for an uppercase, letter-spaced micro-label - a kicker, a column head, a
         unit - and NEVER for a sentence. src/ui/type-scale.source.test.ts fails the
         build on any new literal below the floor, which is what stops the drift
         back; a token is only a suggestion until something enforces it.
         ============================================================ */
      --fs-micro: 13px;   /* uppercase + tracked micro-labels ONLY. Never prose. */
      --fs-xs:    14px;   /* THE FLOOR. Chips, captions, cells, footnotes, meta. */
      --fs-sm:    15px;   /* dense UI rows, secondary lines */
      --fs-base:  16px;   /* body. The honest floor for reading. */
      --fs-lede:  19px;   /* lede paragraph, large body, h5 */
      --fs-h4:    23px;
      --fs-h3:    28px;
      --fs-h2:    33px;
      --fs-h1:    40px;
      --fs-hero:  48px;   /* desktop hero only */
      /* Measure: a line of body text stays inside 65-75 characters. A wide 15px
         line is harder to read than a narrow 16px one, so the measure is part of
         the scale, not a per-page decision. */
      --measure: 68ch;
      --measure-narrow: 58ch;

      /* --- KEY SIZES --- */
      --page-max: 1280px;         /* content column width */
      --h1-size: var(--fs-h1);    /* hero headline */
      --nav-gap: 1.2rem;          /* space between nav items (tune to fit the row) */

      /* --- RADIUS SCALE (2026-07-14 observatory refresh) --- the terminal squares are gone.
         Every card/button/input/menu now carries a real, governed corner. --- */
      --r-sm: 8px; --r: 12px; --r-lg: 18px; --r-pill: 999px;
      --shadow-sm: 0 10px 30px -18px rgba(0,0,0,0.6);
    }
    * { margin: 0; padding: 0; box-sizing: border-box; }
    ::selection { background: var(--blue); color: #fff; }
    /* ── App-shell navigation (SPA) ─────────────────────────────────────────
       The router (client/spa.ts) swaps #page-content in place. A click is acknowledged by the
       thin brand bar below; the SKELETON paints only when the network genuinely makes the
       visitor wait AND the destination has an honest wireframe (four faces, client/spa-skeleton.ts).
       A skeleton in a shape the page never takes reads as a load that broke - so the pages
       without a measured face paint nothing at all. */
    #spa-progress { position: fixed; top: 0; left: 0; height: 2px; width: 0; z-index: 2000; pointer-events: none;
      background: linear-gradient(90deg, var(--blue-text), var(--drift-magenta)); opacity: 0; }
    #spa-progress.active { opacity: 1; width: 82%; transition: width 6s cubic-bezier(0.08, 0.6, 0.2, 1), opacity 0.15s; }
    #spa-progress.done { width: 100%; transition: width 0.2s ease-out; }

    /* THE TELEMETRY SKELETON - DomainDrift's own idiom, not a generic grey bar. Brand dark
       surfaces and hairlines, the scan-dot field the wordmark and the x-ray already use, and a
       slow SIGNED PULSE instead of a shimmer sweep, so it reads as "the record is loading"
       rather than "content placeholder". CSS only: one opacity keyframe on composited layers,
       staggered per block - no JS loop, nothing repaints layout. */
    .ddsk { max-width: 1120px; margin: 0 auto; padding: 4px 0 24px; }
    .ddsk-status { font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.14em; text-transform: uppercase;
      color: var(--text-muted); margin: 0.2rem 0 0.8rem; }
    .ddsk-h { font-size: var(--fs-h2); line-height: 1.12; font-weight: 700; letter-spacing: -0.01em;
      color: var(--text-primary); margin: 0 0 0.6rem; overflow-wrap: anywhere; min-height: 1.12em; }
    .ddsk-b { background-color: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--r-sm);
      background-image: radial-gradient(var(--scan-dot) 1px, transparent 1px); background-size: 9px 9px;
      animation: dd-skel-pulse 2.4s ease-in-out infinite; }
    .ddsk-i { background: rgba(255,255,255,0.05); border-radius: var(--r-sm);
      animation: dd-skel-pulse 2.4s ease-in-out infinite; }
    .ddsk-b:nth-child(2n) { animation-delay: 0.3s; }
    .ddsk-b:nth-child(3n) { animation-delay: 0.6s; }
    .ddsk-i:nth-child(2n) { animation-delay: 0.45s; }
    @keyframes dd-skel-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }
    .ddsk-lede { height: 0.95rem; width: min(280px, 48%); margin: 0 0 1.4rem; }
    .ddsk-p { height: 0.8rem; max-width: 74ch; margin: 0 0 0.75rem; }
    .ddsk-p:nth-of-type(3n) { width: 82%; }
    .ddsk-cmd { height: 74px; max-width: 680px; margin: 1rem 0 1.4rem; }
    /* doc: the real /docs grid - a 212px section rail beside the prose column. */
    .ddsk-doc { display: grid; grid-template-columns: 212px minmax(0, 1fr); gap: 2.4rem; align-items: start; }
    .ddsk-rail { border: 1px solid var(--border); background: var(--bg-secondary); border-radius: var(--r-sm); padding: 0.85rem 0.9rem; }
    .ddsk-rail-h { height: 0.6rem; width: 55%; margin: 0 0 0.55rem; border: 0; background-image: none; }
    .ddsk-rail-i { height: 0.85rem; margin: 0 0 0.5rem; border: 0; background-image: none; }
    .ddsk-col { min-width: 0; }
    @media (max-width: 860px) {
      .ddsk-doc { grid-template-columns: 1fr; gap: 1rem; }
      .ddsk-rail { display: flex; gap: 0.5rem; overflow: hidden; }
      .ddsk-rail-h, .ddsk-rail-i { flex: 1; margin: 0; width: auto; }
    }
    /* list: the head, the three rows the gate actually serves, and the unlock panel under them. */
    .ddsk-rows { display: grid; gap: 0.55rem; margin: 0 0 1.4rem; }
    .ddsk-row { height: 64px; }
    .ddsk-panel { height: 190px; }
    /* feed: head + the date/section strip + the change-card grid. */
    .ddsk-tabs { height: 40px; margin: 0 0 1.2rem; }
    .ddsk-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 14px; margin: 0 0 1.4rem; }
    .ddsk-card { height: 200px; padding: 18px; }
    .ddsk-l { height: 0.8rem; margin-top: 10px; }
    .ddsk-l.w60 { width: 60%; } .ddsk-l.w80 { width: 80%; }
    .ddsk-num { height: 2rem; width: 42%; margin-top: 12px; }
    /* record: title + lede + the action row + the stacked evidence blocks. */
    .ddsk-acts { display: flex; gap: 0.6rem; flex-wrap: wrap; margin: 0 0 1.4rem; }
    .ddsk-pill { height: 38px; width: 132px; border-radius: var(--r-pill); }
    .ddsk-band { height: 150px; margin: 0 0 1.2rem; }
    @media (prefers-reduced-motion: reduce) {
      .ddsk-b, .ddsk-i { animation: none; opacity: 0.8; }
      #spa-progress.active { transition: none; }
    }
    html, body { overflow-x: hidden; }
    body { background: var(--bg-primary); color: var(--text-primary); font-family: var(--font-display); font-size: var(--fs-base); line-height: 1.6; }
    a { color: var(--blue-text); text-decoration: none; } a:hover { color: var(--blue-text); }
    /* ── MONO INHERITS ITS SIZE (2026-08-02) ── the single biggest source of sub-floor text left
       after the scale landed, and it was never written by anyone. When an element has no explicit
       font-size and the UA gives it `font-family: monospace` (code, pre, kbd, samp, tt), browsers
       switch to a SEPARATE, smaller default monospace size instead of inheriting: measured on /api
       at 390px, `<code>` inside a 14px line rendered at 11px and inside a 15px line at 12px. This
       product puts endpoints, headers and keys in <code> everywhere, so the most technical text on
       the site was also the smallest. `1em` makes mono take exactly the size of the text around it,
       which is the only way a floor set in one place can hold. A page that wants a code BLOCK sized
       differently sets it on the block, deliberately, and the source test holds it to the floor. */
    code, pre, kbd, samp, tt { font-size: 1em; }
    /* --container-pad IS THE SIDE PADDING, declared beside each padding rule so a full-bleed
       child can cancel it exactly. #dd-campaign used to hardcode -1.25rem against a value that
       is 1.15rem below 480px, so it over-pulled and sat at left:-2px. Change one, change both. */
    .container { --container-pad: 2rem; max-width: var(--page-max); margin: 0 auto; padding: 1.5rem 2rem; }
    .header { display: flex; align-items: center; justify-content: space-between; padding-bottom: 1rem; border-bottom: 1px solid var(--border); margin-bottom: 2rem; position: relative; gap: 1rem; }
    /* The DomainDrift lockup: the Eye mark + a clean bold wordmark (Domain in ink, Drift in blue). */
    /* The DomainDrift lockup: the leading D IS the mark (a lens set in the counter), "omain" tucks
       against its bowl, and the three DRM3 scan-dots blink a-b-c under "Drift" (drive-light feel). */
    .logo { font-size: var(--fs-h4); font-weight: 700; letter-spacing: -0.01em; display: flex; align-items: center; gap: 0; text-decoration: none; white-space: nowrap; flex: none; }
    .logo-d { flex: none; width: 0.92em; height: 0.92em; display: block; color: var(--wm-domain); overflow: visible; transform: translateY(0.04em); }
    .wm { letter-spacing: -0.01em; display: inline-flex; align-items: baseline; margin-left: -0.05em; }
    .wm-domain { color: var(--wm-domain); }
    .wm-drift { color: var(--wm-drift); position: relative; }
    /* the three scan "lights" - DRM3 broker dots blinking a-b-c under "Drift" (drive-light feel).
       Parked copy: src/ui/parked/wm-scan-dots.html */
    .wm-dots { position: absolute; left: 0; right: 0; bottom: -0.20em; display: flex; justify-content: center; gap: 0.22em; }
    .wm-dots i { width: 0.17em; height: 0.17em; border-radius: 50%; background: var(--wm-drift); opacity: 0.3; animation: wmblink 1.5s infinite; }
    .wm-dots i:nth-child(2) { animation-delay: 0.25s; }
    .wm-dots i:nth-child(3) { animation-delay: 0.5s; }
    @keyframes wmblink { 0%, 70%, 100% { opacity: 0.3; } 18% { opacity: 1; } }
    @media (prefers-reduced-motion: reduce) { .wm-dots i { animation: none; opacity: 0.65; } }
    .logo:hover .wm-domain, .logo:hover .logo-d { color: #fff; }
    /* Single clean row, never wraps: nav stays on one line, each label is unbreakable. */
    .nav { display: flex; gap: var(--nav-gap); align-items: center; flex-wrap: nowrap; }
    .nav a { color: var(--text-secondary); font-size: var(--fs-sm); text-transform: none; letter-spacing: 0; white-space: nowrap; }
    .nav a.active { color: var(--blue-text); }
    .nav .logout { margin-left: auto; border: none; padding: 0.5rem; color: var(--text-muted); display: flex; align-items: center; }
    .nav .logout:hover { color: var(--red-text); }
    /* THE ONE DOOR: a single session-aware CTA - "Sign in" signed out,
       "Enter dashboard" signed in (the probe below swaps it). No secondary text link.
       White text on the #2563eb..#1e40af gradient clears WCAG AA at every stop.
       See docs/product/ui-contrast.md. */
    .nav a.cta { background: linear-gradient(135deg, #2563eb, #1d4ed8 55%, #1e40af); color: #fff; padding: 0.55rem 1.1rem; letter-spacing: 0; border: 1px solid rgba(147,197,253,0.45); font-weight: 600; white-space: nowrap; box-shadow: 0 4px 16px rgba(29,78,216,0.4); transition: box-shadow 0.2s, transform 0.2s, border-color 0.2s; }
    .nav a.cta:hover { color: #fff; border-color: rgba(191,219,254,0.75); box-shadow: 0 6px 22px rgba(29,78,216,0.55); transform: translateY(-1px); }
    .nav a.cta .cta-ar { display: inline-block; margin-left: 3px; transition: transform 0.2s; }
    .nav a.cta:hover .cta-ar { transform: translateX(3px); }
    @media (prefers-reduced-motion: reduce) { .nav a.cta, .nav a.cta .cta-ar { transition: none; } }
    /* ── Hamburger ── desktop-hidden; on a phone the nav collapses behind it so labels
       never wrap into a two-line mess. Toggles .nav.open (an absolute dropdown). */
    .nav-toggle { display: none; background: transparent; border: 1px solid var(--border); color: var(--text-primary); padding: 0.45rem 0.55rem; cursor: pointer; align-items: center; justify-content: center; min-height: 44px; min-width: 44px; }
    .nav-toggle:hover, .nav-toggle:focus-visible { border-color: var(--blue-text); color: var(--blue-text); }
    .nav-toggle svg { display: block; }
    .section-title { font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); margin-bottom: 1rem; }

    /* ── Shared stat cards ── numbers are SANS + tabular (not mono): the mono-on-big-numbers
       look was the biggest "terminal" tell. Mono stays for data cells, never for headline stats. */
    .stats-bar { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.75rem; margin-bottom: 2rem; }
    .stat-card { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--r); padding: 1.1rem 1.25rem; transition: border-color 0.2s, transform 0.2s; display: flex; flex-direction: column; }
    .stat-card:hover { border-color: rgba(59, 130, 246, 0.3); transform: translateY(-2px); }
    .stat-label { font-size: var(--fs-xs); color: var(--text-muted); text-transform: none; letter-spacing: 0; font-weight: 500; margin-bottom: 0.45rem; min-height: 1.8em; }
    .stat-value { font-size: var(--fs-h3); font-weight: 700; color: var(--text-primary); line-height: 1.05; font-family: var(--font-display); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
    .stat-value.highlight { color: var(--pink); }
    /* A counter waiting on /health holds the exact height of the number that replaces it
       (1.9rem x 1.05 line-height = 32px), so the stat row never reflows. */
    .stat-value .skel-line { height: 32px; width: 72%; }
    .stat-sub { font-size: var(--fs-xs); color: var(--text-muted); margin-top: 0.3rem; letter-spacing: 0.05em; }

    @media (max-width: 768px) {
      .stats-bar { grid-template-columns: repeat(2, 1fr); gap: 0.5rem; }
      .stat-card { padding: 0.75rem 1rem; }
      .stat-value { font-size: var(--fs-h4); }
      .stat-label { font-size: var(--fs-xs); }
    }
    @media (max-width: 480px) {
      .stats-bar { grid-template-columns: repeat(2, 1fr); gap: 0.4rem; }
      .stat-card { padding: 0.6rem 0.75rem; }
      .stat-value { font-size: var(--fs-lede); }
      .stat-label { font-size: var(--fs-xs); letter-spacing: 0.1em; }
    }

    /* ── Safe area support ── */
    .container { padding: calc(1.5rem + env(safe-area-inset-top)) calc(2rem + env(safe-area-inset-right)) 2rem calc(2rem + env(safe-area-inset-left)); }

    /* ── Skeleton loading ── the ONE skeleton system for the whole app.
       THE RULE: a skeleton reserves the exact height of the content that will land
       in its place, so nothing jumps on arrival. A skeleton whose shape does not
       match what arrives is worse than text. Build page-specific skeletons out of
       the PAGE'S OWN row/card classes and drop .skel-line inside them - that way the
       padding, border and gap geometry can never drift from the real thing.

       The keyframe is named skelShimmer, NOT shimmer, and must stay that way: the
       per-page style block is injected AFTER this one, so any page that declares a
       `shimmer` keyframe silently overrides this transform-based one. index.html did
       exactly that with an opacity-based `shimmer` (dead code - the class it drove was
       never used anywhere), which is why this system had never actually rendered on the
       busiest page in the app. That rule is deleted; keep the name distinct so a page
       cannot break the shared system by accident.

       And never write the page-styles mustache tag in prose here. A comment is not a
       safe place for it: mustache substitutes inside comments too, so the injected
       block's closing style tag terminates THIS stylesheet, every rule below it is
       dropped by the browser, and the remaining CSS prints as text at the top of the
       page. That shipped in this file and is why the focus rings below never applied. */
    .skel-shimmer { position: relative; overflow: hidden; background: var(--bg-secondary); border: 1px solid var(--border); }
    .skel-shimmer::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.03) 50%, transparent 100%); animation: skelShimmer 1.8s infinite; }
    @keyframes skelShimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }
    /* A skeleton must never out-shout real content. The sweep is a 3% white wash over
       --bg-secondary; with motion off it holds that plate at rest instead of flashing. */
    @media (prefers-reduced-motion: reduce) {
      .skel-shimmer::after { animation: none; background: rgba(255,255,255,0.02); }
    }
    .skel-title { height: 0.65rem; width: 40%; margin-bottom: 1rem; }
    /* A single line of stand-in text. Height is set by the caller to the line-height of
       the text it stands in for; width modifiers keep a block of them from reading as a
       solid rectangle. */
    .skel-line { height: 0.7rem; display: block; }
    .skel-w10 { width: 10%; } .skel-w15 { width: 15%; } .skel-w20 { width: 20%; }
    .skel-w25 { width: 25%; } .skel-w30 { width: 30%; } .skel-w40 { width: 40%; }
    .skel-w50 { width: 50%; } .skel-w60 { width: 60%; } .skel-w70 { width: 70%; }
    .skel-w85 { width: 85%; } .skel-w100 { width: 100%; }
    /* Skeletons are decorative. Screen readers get the one honest sentence on the
       wrapper (aria-busy + aria-label), never a pile of empty divs. */
    .skel-wrap { position: relative; }
    .skel-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.75rem; margin-bottom: 2rem; }
    .skel-stat { height: 5rem; }
    .skel-table-row { height: 2.2rem; margin-bottom: 0.35rem; }
    .skel-table-row:nth-child(2) { animation-delay: 0.1s; }
    .skel-table-row:nth-child(2)::after { animation-delay: 0.1s; }
    .skel-table-row:nth-child(3) { animation-delay: 0.2s; }
    .skel-table-row:nth-child(3)::after { animation-delay: 0.2s; }
    .skel-table-row:nth-child(4) { animation-delay: 0.3s; }
    .skel-table-row:nth-child(4)::after { animation-delay: 0.3s; }
    .skel-table-row:nth-child(5) { animation-delay: 0.4s; }
    .skel-table-row:nth-child(5)::after { animation-delay: 0.4s; }
    .skel-table-row:nth-child(6) { animation-delay: 0.5s; }
    .skel-table-row:nth-child(6)::after { animation-delay: 0.5s; }
    .skel-table-row:nth-child(7) { animation-delay: 0.6s; }
    .skel-table-row:nth-child(7)::after { animation-delay: 0.6s; }
    .skel-table-row:nth-child(8) { animation-delay: 0.7s; }
    .skel-table-row:nth-child(8)::after { animation-delay: 0.7s; }
    @media (max-width: 768px) {
      .skel-stats { grid-template-columns: repeat(2, 1fr); }
    }

    /* ── Honest states ── the two ways a skeleton is allowed to end.
       A fetch that returns nothing says WHY and offers a next step; a fetch that
       FAILS says so plainly and offers a retry. Neither is ever a bare "No data",
       and neither is ever left shimmering. Sentence case, never the uppercase
       tracking of .empty-state - these are sentences a human reads, not labels. */
    .state-block { text-align: center; padding: 2rem 1.25rem; color: var(--text-secondary); font-size: var(--fs-base); line-height: 1.6; text-transform: none; letter-spacing: 0; }
    .state-block .state-title { color: var(--text-primary); font-weight: 600; margin-bottom: 0.3rem; }
    .state-block .state-hint { color: var(--text-muted); font-size: var(--fs-sm); margin-top: 0.35rem; }
    .state-block a { color: var(--blue-text); text-decoration: underline; text-underline-offset: 2px; }
    .state-block.is-error .state-title { color: var(--red-text); }
    /* Retry is a real button, not a link that reloads the world. */
    .state-retry { margin-top: 0.75rem; background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-primary); font-family: inherit; font-size: var(--fs-xs); padding: 0.45rem 0.9rem; cursor: pointer; letter-spacing: 0.05em; min-height: 36px; }
    .state-retry:hover, .state-retry:focus-visible { border-color: var(--blue-text); color: var(--blue-text); }

    /* ── Keyboard focus ── ONE governed ring for the whole app. Every interactive element gets a
       visible indicator for keyboard users; :focus-visible means a mouse press never draws it, so
       there is no reason for a page to opt out with `outline: none`. --blue-text on the ground is
       7.67:1, far above the 3:1 minimum for UI controls (docs/product/ui-contrast.md). This is the
       ring landing-styles.ts already used on .lx / .xray - adopted fleet-wide, not invented here.
       Page styles load AFTER this block, so the few rules that set their own focus outline are
       matched to these values rather than left to fight the cascade. */
    :focus-visible { outline: 2px solid var(--blue-text); outline-offset: 2px; }
    a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible,
    select:focus-visible, summary:focus-visible, [tabindex]:focus-visible,
    [role="option"]:focus-visible, [role="button"]:focus-visible {
      outline: 2px solid var(--blue-text); outline-offset: 2px;
    }
    /* The search inputs sit inside a bordered wrapper, so the ring goes on the WRAPPER (an
       outline on the borderless input would draw inside the box and read as a glitch). */
    .np-search:focus-within { outline: 2px solid var(--blue-text); outline-offset: 2px; }
    .np-search input:focus-visible { outline: none; }

    /* ── MEASURE ── line length is half of legibility and it was ungoverned. A 15px line
       running the full 1280px column is harder to read than a narrower 16px one, because the
       eye loses its place on the return sweep. Body prose gets --measure (68ch, inside the
       65-75 character band); a lede runs a touch narrower. Scoped to plain <p> inside the page
       body so it can never squeeze a grid cell, a table, or a card that happens to hold text -
       those set their own width and are not prose. A page that needs a wider paragraph says so
       on its own class; the DEFAULT is readable. */
    #page-content > p, #page-content > * > p { max-width: var(--measure); }

    /* ── Smooth transitions ── */
    a, .nav a, input { transition: all 0.2s ease; }

    /* ── Touch targets ── */
    .nav a { min-height: 44px; display: inline-flex; align-items: center; }

    /* ── No horizontal scroll ── */
    .container { box-sizing: border-box; overflow-wrap: break-word; }

    @media (max-width: 980px) {
      body { font-size: var(--fs-base); }
      /* A REAL gutter: content never touches the screen edge. 20px each side (was 12px),
         plus the safe-area inset on notched phones. This is the single biggest mobile fix. */
      .container { --container-pad: calc(1.25rem + env(safe-area-inset-left)); padding: calc(1.25rem + env(safe-area-inset-top)) calc(1.25rem + env(safe-area-inset-right)) 1.75rem calc(1.25rem + env(safe-area-inset-left)); max-width: 100%; }
      .header { padding-bottom: 0.85rem; margin-bottom: 1.5rem; }
      /* Clean single row: logo left, hamburger right. The nav becomes a floating dropdown so no
         label ever wraps to a second line. A small gap + soft shadow makes it read as a raised
         panel; hairline dividers give it structure instead of a bland stack. */
      .header { flex-direction: row; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
      /* The hamburger must sit ABOVE the open menu's scrim so it stays tappable to close. */
      .nav-toggle { display: inline-flex; position: relative; z-index: 1001; }
      /* THE OPEN MENU IS A SOLID, OPAQUE PANEL ON TOP OF EVERYTHING (2026-07-25).
         It was z-index:60, which TIES the landing hero's #search-wrap (also 60, and later in the
         DOM so it won its tie) and LOSES to #lesson-pop (100) - so the hero search box and the
         x-ray card bled straight through the "transparent" menu. --bg-card (#1a1635) was opaque
         all along; the bug was purely stacking. z-index:1000 clears every in-page layer
         (search-wrap 60, guide 50, lesson-pop 100), and a fixed scrim dims the rest so nothing
         reads through. Verify on a ~390px viewport with the menu OPEN. */
      .nav { position: absolute; top: calc(100% + 8px); left: 0; right: 0; flex-direction: column; align-items: stretch; gap: 0; background: var(--bg-card); border: 1px solid var(--border); box-shadow: 0 22px 54px rgba(0,0,0,0.6), 0 0 0 1px rgba(96,165,250,0.08); padding: 0.4rem 0.55rem 0.6rem; display: none; z-index: 1000; }
      .nav.open { display: flex; }
      /* A full-viewport scrim behind the panel. position:fixed takes it OUT of the flex flow
         (absolutely/fixed children of a flex container are not flex items), so it never pushes the
         menu rows. It paints behind the panel's own rows (z-index:-1 within the panel's stacking
         context) but above all page content, because the whole .nav context sits at z-index:1000. */
      .nav.open::before { content: ''; position: fixed; inset: 0; z-index: -1; background: rgba(6,4,16,0.72); }
      .nav a { font-size: var(--fs-xs); min-height: 50px; display: flex; align-items: center; width: 100%; padding: 0.8rem 0.9rem; letter-spacing: 0.1em; border-bottom: 1px solid rgba(42,37,80,0.5); }
      .nav a:last-child { border-bottom: 0; }
      .nav a:hover, .nav a:focus-visible { background: var(--bg-secondary); color: var(--blue-text); }
      .nav .logout { margin-left: 0; width: 100%; justify-content: flex-start; gap: 0.5rem; border-bottom: 0; }
      .nav a.cta { justify-content: center; text-align: center; margin-top: 0.6rem; border-bottom: 0; box-shadow: 0 8px 22px rgba(29,78,216,0.35); }
      .logo { font-size: var(--fs-lede); }
    }
    @media (max-width: 480px) {
      /* Keep the full gutter on the smallest phones - 18px, never the old 8px. */
      .container { --container-pad: calc(1.15rem + env(safe-area-inset-left)); padding: calc(1rem + env(safe-area-inset-top)) calc(1.15rem + env(safe-area-inset-right)) 1.5rem calc(1.15rem + env(safe-area-inset-left)); }
    }

    /* ── The DomainDrift tape ── 24h rollup stats rolling full-bleed above the app
       chrome (the MorScan pattern). Seamless loop = duplicated half + -50%
       translate. Pauses on hover/focus; reduced-motion freezes to the first
       half. Injected via the DD_TAPE slot by utils/tape.ts. */
    /* Edge fade-masks so a tick never HARD-CLIPS mid-word at either viewport edge (a "looks
       broken" first-paint tell). The gradient fades the marquee in/out over 28px each side. */
    .tape { background: var(--bg-primary, #0a0e1a); border-bottom: 1px solid var(--border); overflow: hidden; white-space: nowrap; -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%); mask-image: linear-gradient(90deg, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%); }
    .tape-track { display: inline-flex; align-items: center; animation: tapeScroll 86s linear infinite; will-change: transform; }
    .tape:hover .tape-track, .tape:focus-within .tape-track { animation-play-state: paused; }
    @keyframes tapeScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
    .tape-half { display: inline-flex; align-items: baseline; gap: 1.6rem; padding: 0.42rem 1.6rem 0.42rem 0.9rem; }
    .tape-brand { font-size: var(--fs-micro); font-weight: 800; letter-spacing: 0.24em; color: var(--text-muted); font-family: var(--mono); }
    .tape-item { display: inline-flex; align-items: baseline; gap: 0.42rem; font-size: var(--fs-xs); line-height: 1.4; }
    .tape-l { font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.16em; color: var(--text-muted); font-family: var(--mono); }
    .tape-v { color: var(--blue-text, #68aafa); font-weight: 700; text-decoration: none; font-family: var(--mono); }
    .tape-v:hover { text-decoration: underline; }
    /* Tape tones: blue = baseline / reachability (informational), VIOLET = drift (something
       moved), green = signed/up, amber = blocked/errors, red = down ONLY. Red is earned by real
       failure, never by a routine change.

       THE LAW: PINK READS AS RED, AND RED MEANS WARN OR ERROR - so pink and magenta are reserved
       for warning states and nothing else. Drift used to be var(--pink) #FC7DB3, on the argument
       that it was "distinctly NOT red so it never reads as an incident." Read live, it read as an
       incident. Worse, it was the WRONG pink - #FC7DB3 is the pricing "soon" tag; the drift var is
       --drift-magenta #FF7E9E, which is REDDER still, so "use the right one" would have made it
       worse. And `drift` is the tone on most of the legs, so a color meant to mark "this moved"
       was marking almost everything, i.e. shouting continuously.

       Violet is cool, carries no warn/error meaning, is hue-distinct from the blue baseline, and
       measures 7.17:1 on --ground #0c0a1d (AA needs 4.5:1). Do NOT move any routine count back
       onto pink/magenta/red - those are reserved for something being WRONG. Pinned by tape tests. */
    .tape-v.tape-drift { color: var(--purple); }
    .tape-v.tape-ok { color: var(--green-text); }
    .tape-v.tape-warn { color: var(--amber-text); }
    .tape-v.tape-crit { color: var(--red-text); }
    .tape-s { color: var(--text-muted); font-size: var(--fs-xs); }
    @media (max-width: 500px) { .tape-item { font-size: var(--fs-xs); } .tape-half { gap: 1.05rem; } }
    @media (prefers-reduced-motion: reduce) { .tape-track { animation: none; } }

    /* ── Mobile overflow safety net ── nothing in the page body may force the
       viewport wider than the screen (the "wobble"). Wide tables/code/media
       scroll inside their own box instead of dragging the whole page. */
    #page-content { max-width: 100%; }
    #page-content img, #page-content svg, #page-content video { max-width: 100%; height: auto; }
    @media (max-width: 768px) {
      #page-content table { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
      #page-content pre, #page-content code { max-width: 100%; overflow-x: auto; }
      #page-content { overflow-wrap: anywhere; }
    }

    /* ===== Consolidated menu: 4-parent spine + dropdowns + conversion cluster (2026-07-14).
       The spine is identical signed out/in; only the cluster swaps. Dropdown items deliberately
       RESET the uppercase/letter-spaced `.nav a` base so they read as sentence-case menu rows. ===== */
    /* Native <details>/<summary> disclosure: clicking the summary toggles open/closed with zero
       JS, so a dropdown can NEVER get stuck open. JS below only closes siblings + on outside/Esc. */
    .np { position: relative; display: flex; align-items: center; }
    .np-top { list-style: none; background: none; border: 0; cursor: pointer; color: var(--text-secondary); font-family: var(--font-display); font-size: var(--fs-sm); font-weight: 500; letter-spacing: 0; text-transform: none; padding: 0.5rem 0.6rem; border-radius: var(--r-sm); display: inline-flex; align-items: center; gap: 0.32rem; white-space: nowrap; min-height: 44px; }
    .np-top:hover { background: var(--blue-dim); }
    .np-top::-webkit-details-marker { display: none; }
    .np-top::marker { content: ""; }
    .np-top:hover, details.np[open] > .np-top { color: var(--blue-text); }
    .np-car { width: 8px; height: 8px; flex: none; transition: transform 0.18s ease; }
    details.np[open] > .np-top .np-car { transform: rotate(180deg); }
    .np-flat { display: inline-flex; align-items: center; }
    .np-menu { position: absolute; top: calc(100% + 8px); left: 0; z-index: 70; background: var(--bg-card); border: 1px solid var(--border); box-shadow: 0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(96,165,250,0.06); padding: 0.5rem; min-width: 252px; display: none; }
    details.np[open] > .np-menu { display: block; }
    /* The mega keeps its two-column grid when open (the plain block rule above would flatten it). */
    details.np[open] > .np-menu.np-mega { display: grid; }
    .np-menu-right { left: auto; right: 0; }
    .np-mega { display: grid; grid-template-columns: 1fr 1fr; gap: 0.2rem 1.3rem; min-width: 520px; padding: 0.85rem 1rem; }
    /* BOTH COLUMN HEADERS RESERVE TWO LINES, so the rule under them and every item below it land on
       the same baseline in both columns. One header carries an injected catalog count and wraps at
       two lines while the other does not, so without this the right column started ~14px lower than
       the left and nothing in the menu lined up. Reserving the space is what makes the alignment
       independent of how long the text happens to be today. */
    .np-col-h { font-family: var(--mono); font-size: var(--fs-micro); line-height: 1.45; letter-spacing: 0.2em; text-transform: uppercase; color: var(--text-muted); padding: 0.15rem 0.5rem 0.45rem; border-bottom: 1px solid var(--border); margin-bottom: 0.3rem; min-height: calc(2.9em + 0.6rem); box-sizing: border-box; display: flex; align-items: flex-end; }
    .nav .np-menu a.mi { display: flex; flex-direction: column; gap: 0.08rem; align-items: flex-start; padding: 0.5rem 0.55rem; color: var(--text-primary); font-size: var(--fs-sm); font-weight: 600; text-transform: none; letter-spacing: normal; white-space: normal; min-height: 0; border-bottom: 0; line-height: 1.25; }
    /* The hover fill must be visibly LIGHTER than the menu card: --bg-secondary (#131029) is
       darker than --bg-card (#1a1635), so the old hover was invisible. The brighter blue fill
       drops --text-muted below AA 4.5:1, so the description steps up to --text-secondary. */
    .nav .np-menu a.mi:hover { background: rgba(59,130,246,0.22); color: var(--blue-text); }
    .nav .np-menu a.mi:hover .mi-d { color: var(--text-secondary); }
    .mi-d { font-size: var(--fs-xs); font-weight: 400; color: var(--text-muted); text-transform: none; letter-spacing: normal; }
    .mi-sep { border-top: 1px solid var(--border); margin-top: 0.35rem; }
    /* Explore mega-menu: icon-led rows + a section kicker (with a count) + a utility footer row. */
    .nav .np-menu.np-mega a.mi { flex-direction: row; align-items: flex-start; gap: 0.6rem; }
    .np-mega .mi-ic { flex: none; width: 20px; height: 20px; color: var(--blue-text); margin-top: 1px; }
    .np-mega .mi-ic svg { width: 20px; height: 20px; display: block; }
    .np-mega .mi-tx { display: flex; flex-direction: column; gap: 0.06rem; min-width: 0; }
    .np-mega .mi-t { font-size: var(--fs-sm); font-weight: 600; color: var(--text-primary); line-height: 1.25; }
    .np-mega-foot { grid-column: 1 / -1; display: flex; align-items: center; gap: 1.1rem; border-top: 1px solid var(--border); margin-top: 0.4rem; padding: 0.7rem 0.55rem 0.1rem; }
    .np-mega-foot a { font-size: var(--fs-xs); font-weight: 600; color: var(--text-secondary); white-space: nowrap; }
    .np-mega-foot a:hover { color: var(--blue-text); }
    .np-mega-foot .np-mega-more { margin-left: auto; color: var(--blue-text); }
    .pro { display: inline-block; font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.12em; text-transform: uppercase; color: var(--pink); background: var(--pink-dim); border: 1px solid rgba(251,61,142,0.42); padding: 0.02rem 0.32rem; margin-left: 0.4rem; vertical-align: middle; }
    .np-search { display: flex; align-items: center; gap: 0.45rem; background: var(--bg-secondary); border: 1px solid var(--border); padding: 0.35rem 0.55rem; margin-left: auto; flex: 0 1 210px; min-width: 130px; }
    .np-search:focus-within { border-color: var(--blue-text); }
    .np-search svg { width: 14px; height: 14px; color: var(--text-muted); flex: none; }
    .np-search input { flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var(--text-primary); font-family: var(--font-display); font-size: var(--fs-xs); }
    .np-search input::placeholder { color: var(--text-muted); }
    /* ── PHONE SEARCH: AN ICON, NOT A ROW (2026-07-29) ── the old phone-header search form was
       a full-width header row on every page under 981px, spending the first ~60px of every phone
       viewport on an empty input. The phone gets a 44px magnifier button in the bar instead;
       tapping it opens a fixed overlay with the SAME .np-search form (so the shared typeahead
       binds to it) and focuses the input. Desktop keeps the inline nav-row search and never
       shows the icon. */
    .search-toggle { display: none; background: transparent; border: 1px solid var(--border); color: var(--text-primary); padding: 0.45rem 0.55rem; cursor: pointer; align-items: center; justify-content: center; min-height: 44px; min-width: 44px; border-radius: var(--r-sm); }
    .search-toggle:hover, .search-toggle:focus-visible { border-color: var(--blue-text); color: var(--blue-text); }
    .search-toggle svg { display: block; }
    #dd-search-overlay { position: fixed; inset: 0; z-index: 1200; background: rgba(6,4,16,0.78); padding: calc(0.9rem + env(safe-area-inset-top)) calc(0.9rem + env(safe-area-inset-right)) 0 calc(0.9rem + env(safe-area-inset-left)); }
    #dd-search-overlay[hidden] { display: none; }
    #dd-search-overlay .np-search { display: flex; margin: 0; flex: none; width: 100%; max-width: 560px; margin: 0 auto; border: 1px solid var(--blue-text); background: var(--bg-card); box-shadow: 0 18px 50px rgba(0,0,0,0.55); }
    /* 16px kills iOS's focus auto-zoom; 44px is the touch target. */
    #dd-search-overlay .np-search input { font-size: var(--fs-base); min-height: 44px; }
    #dd-search-overlay .nps-panel { left: 0; right: 0; width: auto; }
    .nps-close { flex: none; background: none; border: 0; color: var(--text-muted); font-size: var(--fs-h4); line-height: 1; padding: 0.1rem 0.3rem; cursor: pointer; font-family: inherit; }
    .nps-close:hover, .nps-close:focus-visible { color: var(--text-primary); }

    /* ── THE HEADER TYPEAHEAD (client/nav-search.ts) ── the form is the anchor, so it needs a
       stacking context of its own or the panel paints under the nav row beneath it. The RESULTS are
       drawn by the shared renderer (ui/search-render.ts) as the SAME grouped .sr-* dropdown the
       landing hero uses; the rules below restyle that markup for the compact header panel. The
       landing's own .sr-* rules live in landing-styles.ts and load ONLY on the landing, so these
       scope every property under .nps-panel and never rely on them. */
    .np-search { position: relative; }
    .np-search:focus-within { z-index: 70; }
    .nps-panel { position: absolute; top: calc(100% + 6px); right: 0; left: auto; width: min(420px, calc(100vw - 2rem));
                 background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--r);
                 box-shadow: 0 18px 50px rgba(0,0,0,0.55); z-index: 70; overflow: hidden; max-height: min(60vh, 420px); overflow-y: auto; overscroll-behavior: contain; }
    .nps-panel .sr-group { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.8rem 0.25rem; }
    .nps-panel .sr-group:not(:first-child) { border-top: 1px solid var(--border); margin-top: 0.2rem; }
    .nps-panel .sr-kind { font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.14em; color: var(--blue-text); background: var(--blue-dim); padding: 1px 7px; border-radius: var(--r-pill); }
    .nps-panel .sr-count { font-size: var(--fs-xs); color: var(--text-muted); }
    .nps-panel .sr-row { display: flex; align-items: center; gap: 0.6rem; padding: 0.45rem 0.8rem; color: var(--text-primary); border-left: 2px solid transparent; text-decoration: none; }
    .nps-panel .sr-row:hover, .nps-panel .sr-row.sel { background: var(--bg-secondary); border-left-color: var(--blue-text); color: var(--text-primary); }
    .nps-panel .sr-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
    .nps-panel .sr-dot.p-ok { background: var(--green-text); }
    .nps-panel .sr-dot.p-warn { background: var(--amber-text); }
    .nps-panel .sr-dot.p-err { background: var(--red-text); }
    .nps-panel .sr-dot.p-none { background: var(--border); }
    .nps-panel .sr-domain { font-weight: 600; word-break: break-all; font-size: var(--fs-xs); }
    .nps-panel .sr-match { margin-left: auto; color: var(--text-secondary); font-size: var(--fs-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 42%; text-align: right; }
    .nps-panel .sr-verdict { flex: none; font-size: var(--fs-xs); color: var(--text-muted); letter-spacing: 0.04em; }
    .nps-panel mark { background: rgba(96,165,250,0.3); color: #ffffff; padding: 0 1px; }
    .nps-panel .sr-empty { padding: 0.85rem 0.9rem 0.95rem; }
    .nps-panel .sr-empty-title { color: var(--text-primary); font-size: var(--fs-xs); }
    .nps-panel .sr-scope { color: var(--text-muted); font-size: var(--fs-xs); margin-top: 0.3rem; line-height: 1.5; }
    /* The host hint (asn_org) - the compact "who runs this" next to a row, on BOTH dropdowns, so it
       is defined in the shared chrome (not scoped). asn_org is the NETWORK operator (who hosts it),
       never "the owner"; the row markup carries that in the title attribute. */
    .sr-org { flex: none; color: var(--text-muted); font-size: var(--fs-xs); white-space: nowrap; }
    .sr-org::before { content: '\00B7'; margin-right: 0.3rem; }
    .np-watch { display: inline-flex; align-items: center; gap: 0.3rem; border: 1px solid var(--border); padding: 0.45rem 0.85rem; text-transform: none; letter-spacing: 0; font-size: var(--fs-xs); font-weight: 600; border-radius: var(--r-pill); }
    .np-watch:hover { border-color: var(--blue-text); color: var(--blue-text); }
    .acct-btn { border: 1px solid var(--border); padding: 0.25rem 0.5rem 0.25rem 0.35rem; letter-spacing: 0; border-radius: var(--r-pill); }
    .acct-btn:hover, details.np-acct[open] > .acct-btn { border-color: var(--blue-text); }
    .acct-ava { width: 24px; height: 24px; background: var(--blue-dim); border: 1px solid rgba(96,165,250,0.4); color: var(--blue-text); font-weight: 700; font-size: var(--fs-xs); display: inline-flex; align-items: center; justify-content: center; }
    .acct-label { display: none; }

    @media (max-width: 980px) {
      /* Flatten: the collapsed hamburger panel shows every parent as a section with its rows
         inline (no hover popouts, no absolute positioning that would overflow the panel). */
      .np { flex-direction: column; align-items: stretch; width: 100%; }
      .np-top { width: 100%; justify-content: space-between; min-height: 50px; padding: 0.8rem 0.9rem; letter-spacing: 0.1em; border-bottom: 1px solid rgba(42,37,80,0.5); }
      .np-car { display: none; }
      details.np[open] > .np-menu { position: static; display: block; box-shadow: none; border: 0; min-width: 0; padding: 0; background: transparent; }
      .np-mega { grid-template-columns: 1fr; min-width: 0; padding: 0; gap: 0; }
      .np-col-h { padding: 0.6rem 0.9rem 0.35rem; }
      .nav .np-menu a.mi { padding: 0.7rem 0.9rem 0.7rem 1.4rem; border-bottom: 1px solid rgba(42,37,80,0.35); }
      /* The mega footer wraps on a phone so "Browse all views" can't get shoved off the right edge. */
      .np-mega-foot { flex-wrap: wrap; gap: 0.6rem 1.1rem; padding: 0.7rem 0.9rem 0.4rem; }
      .np-mega-foot .np-mega-more { margin-left: 0; }
      /* Search comes OUT of the hamburger panel; the phone gets the bar ICON + overlay instead
         (see .search-toggle above) so the field never costs a header row. */
      .nav .np-search { display: none; }
      /* THE TWO BAR BUTTONS SIT TOGETHER. .header is space-between with three children (logo,
         search, menu), which strands the search icon in the MIDDLE of the bar - far from the
         hamburger it belongs beside. margin-left:auto absorbs the free space before the pair, so
         the logo stays left and both controls group on the right where a thumb is. */
      .search-toggle { display: inline-flex; position: relative; z-index: 1001; margin-left: auto; }
      .nav a.cta, .np-watch { width: 100%; justify-content: center; text-align: center; margin-top: 0.4rem; display: inline-flex; align-items: center; }
      .acct-label { display: inline; margin-left: 0.4rem; }
      .acct-btn { width: 100%; justify-content: flex-start; min-height: 50px; }
    }

    /* ===== OBSERVATORY REFRESH (2026-07-14) — governed radii on the shared chrome. Replaces the
       old blanket `border-radius:0 !important`. Palette + contrast unchanged; this only softens the
       corners of buttons, inputs, menus, and the account chip so nothing reads as a terminal box. ===== */
    .np-menu { border-radius: var(--r); }
    .np-search { border-radius: var(--r-pill); padding: 0.4rem 0.75rem; }
    .acct-ava { border-radius: var(--r-pill); }
    .nav-toggle { border-radius: var(--r-sm); }
    input, textarea, button { border-radius: var(--r-sm); }
    .cta { box-shadow: var(--shadow-sm); }

    /* ===== OBSERVATORY REFRESH — PAGE-BODY RADII (2026-07-14). The chrome rounds above; this
       governs every PAGE body from one place so no card/panel/chip/button ships square next to the
       rounded chrome. Scoped to #page-content so it never touches nav/tape/footer. The namespaced
       specificity (0,1,1) beats a bare page `.class` (0,1,0), so it also normalizes the few
       off-scale literals (e.g. .chg-badge 3px). Joined panels (the gate) round in their own CSS. ===== */
    /* PILL — status chips, badges, tags, count pills, thin bar tracks */
    #page-content .tag, #page-content .pchip, #page-content .badge, #page-content .badge-local,
    #page-content .rcnt, #page-content .sub-tag, #page-content .svc-cat, #page-content .im-host,
    #page-content .im-domain, #page-content .chg-badge, #page-content .ep-method,
    #page-content .col-category, #page-content .change-count, #page-content .filter-badge,
    #page-content .adv-pill, #page-content .progress-bar-wrap, #page-content .cat-count,
    #page-content .svc-bar, #page-content .issuer-bar, #page-content .q-badge,
    #page-content .q-bar-track, #page-content .pill, #page-content .xray-status,
    #page-content .xray-v .chip, #page-content .cmp .chip, #page-content .drift-chip,
    #page-content .cr-event, #page-content .sr-kind, #page-content .mtag, #page-content .al-tag,
    #page-content .bl-badge, #page-content .bl-ok, #page-content .bl-drift, #page-content .bl-wait,
    #page-content .gr-badge, #page-content .gr-monitored, #page-content .gr-queued { border-radius: var(--r-pill); }
    /* SM — buttons, small controls, small clickable list rows */
    #page-content .btn, #page-content .btn-primary, #page-content .btn-ghost, #page-content .ex,
    #page-content .prov-toggle, #page-content .verify-btn, #page-content .dd-btn,
    #page-content .pager a, #page-content .pager span, #page-content .api-tab,
    #page-content .btn-run, #page-content .key-mask, #page-content .key-btn, #page-content .tab,
    #page-content .toggle-btn, #page-content .page-btn, #page-content .adv-toggle,
    #page-content .domain-row, #page-content .change-item, #page-content .svc-row,
    #page-content .issuer-row, #page-content .q-refresh, #page-content .sr-row,
    #page-content .sr-try, #page-content .search-kbd, #page-content .ep-btn, #page-content .pg-run,
    #page-content .pg-cta a, #page-content .al-btn, #page-content .al-del, #page-content .gr-btn,
    #page-content .gr-del, #page-content .v-instructions code { border-radius: var(--r-sm); }
    /* R — cards, panels, containers, dropdowns, code & diff blocks */
    #page-content .scan-card, #page-content .probe-card, #page-content .card,
    #page-content .cert-card, #page-content .cert-warning, #page-content .cert-error-banner,
    #page-content .prov, #page-content .svc-item, #page-content .json-view, #page-content .diff-block,
    #page-content .im-wrap, #page-content .im-lanes, #page-content .dd-list,
    #page-content .expiry-section, #page-content .chg, #page-content .v-pane, #page-content .v-stat,
    #page-content .v-toast, #page-content .key-bar, #page-content .ep-item, #page-content .pnl,
    #page-content .code, #page-content .term-box, #page-content .adv-panel,
    #page-content .domain-list, #page-content .q-stat, #page-content .xray, #page-content .feature,
    #page-content .watch-pitch, #page-content .search-box, #page-content .change-row,
    #page-content .pg-req, #page-content .pg-out, #page-content .pg-x402, #page-content .pg-cta,
    #page-content .cmp .box, #page-content .al-card, #page-content .al-flash, #page-content .gr-card,
    #page-content .gr-flash { border-radius: var(--r); }
    /* LG — large hero/feature/pricing panels */
    #page-content .price-card { border-radius: var(--r-lg); }

    /* ── Info circles (dd-tip) ── the ONE tooltip primitive (2026-07-30). An "i" chip next to a
       heavy term; the sentence that remains must stand alone, the popover carries the depth.
       Honest-claims caveats NEVER move into a popover - they stay in the visible sentence.
       Markup + behavior: src/handlers/info-tip.ts (server) + src/client/info-tip.ts (shell bundle,
       event-delegated so it survives SPA swaps). AA: popover text is --text-primary on --bg-card
       with a border; the chip is --text-muted at rest (a control, 3:1 floor) and --blue-text on
       hover/focus/open. */
    .dd-tip { position: relative; display: inline-flex; align-items: center; vertical-align: baseline; }
    .dd-tip-b { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;
      margin: 0 0.05rem; padding: 0; background: none; border: 1px solid var(--border); border-radius: var(--r-pill);
      color: var(--text-muted); cursor: pointer; flex: none; vertical-align: -3px; }
    .dd-tip-b svg { width: 11px; height: 11px; display: block; }
    .dd-tip-b:hover, .dd-tip-b:focus-visible, .dd-tip[data-open] .dd-tip-b {
      color: var(--blue-text); border-color: var(--blue-text); background: var(--blue-dim); }
    .dd-tip-p { display: none; position: absolute; bottom: calc(100% + 9px); left: 50%; transform: translateX(-50%);
      z-index: 900; width: max-content; max-width: min(320px, calc(100vw - 2.5rem));
      background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--r);
      box-shadow: 0 18px 50px rgba(0,0,0,0.55), 0 0 0 1px rgba(96,165,250,0.07);
      padding: 0.6rem 0.75rem; font-size: var(--fs-sm); font-weight: 400; line-height: 1.55;
      color: var(--text-primary); text-align: left; text-transform: none; letter-spacing: 0; white-space: normal; }
    .dd-tip-p a { color: var(--blue-text); text-decoration: underline; text-underline-offset: 2px; }
    .dd-tip[data-open] .dd-tip-p { display: block; }
    .dd-tip[data-flip] .dd-tip-p { bottom: auto; top: calc(100% + 9px); }
    .dd-tip-term { text-decoration: underline dotted; text-underline-offset: 3px; text-decoration-color: var(--text-muted); }
