/* Opal Terminal — the shelf's own thin layer over the copied design tokens.
   Rules of the house (docs/presentation/design-language): Inter for prose,
   JetBrains Mono + tabular-nums for EVERY value, vermilion as signal only,
   dark instrument surface, quiet motion (a closed duty list — see the
   "motion" section below and design-language.md "Quiet motion"). */

@layer terminal {
  * { box-sizing: border-box; }

  html { height: 100%; }
  body {
    margin: 0;
    min-height: 100%;
    /* The canvas color comes from html (tokens layer); body stays transparent
       so the ambient binary gutters (body::before, z-index -1) can paint
       between the canvas and the content. An opaque body would cover them. */
    background: transparent;
    color: var(--text);
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
  }

  .mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
  }

  a { color: var(--link); text-decoration: none; }
  a:hover { text-decoration: underline; }

  /* Selection is attention — the same signal duty as focus rings. */
  ::selection {
    background: color-mix(in srgb, var(--accent) 30%, transparent);
    color: var(--text);
  }
  a:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 2px;
  }

  /* ---- chrome ---- */
  .topbar {
    display: block;
    padding: 0;
    /* Installed-PWA safe areas (viewport-fit=cover): the sticky bar owns the
       notch inset; browsers without env() drop the declaration harmlessly. */
    padding-top: env(safe-area-inset-top);
    border-bottom: 1px solid var(--border);
    /* Chrome floats: translucent over what scrolls beneath (the blur is the
       depth cue); browsers without backdrop-filter get the solid fallback. */
    background: var(--bg);
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    position: sticky;
    top: 0;
    z-index: 10;
  }
  .topbar-inner {
    width: 100%;
    max-width: 1200px;
    min-height: 44px;
    margin: 0 auto;
    padding: 0 var(--space-5);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
  }
  .wordmark {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 44px;
    color: var(--text);
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.14em;
  }
  .wordmark:hover { text-decoration: none; }
  .wordmark-tick { color: var(--accent); }
  /* The orbit mark rides at cap height beside the wordmark — never larger
     than the type it accompanies (brand seasoning, not chrome). */
  .brand-mark {
    width: 20px;
    height: 20px;
    display: block;
    flex: none;
  }
  .topbar-meta { color: var(--text-faint); font-size: var(--text-xs); }
  .topbar-actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
  }

  /* ---- controls (terminal chrome: mono, quiet, honest about state) ---- */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 34px;
    padding: 0 var(--space-3);
    background: transparent;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    color: var(--text-dim);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    white-space: nowrap;
    cursor: pointer;
    transition: border-color var(--motion-base), color var(--motion-base);
  }
  a.btn:hover { text-decoration: none; }
  .btn:hover:not([disabled]) { border-color: var(--text-faint); color: var(--text); }
  .btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }
  .btn[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
  }
  .btn-tag {
    font-size: var(--text-2xs);
    color: var(--text-faint);
    border-left: 1px solid var(--border);
    padding-left: var(--space-2);
    text-transform: none;
    letter-spacing: 0.02em;
  }

  /* ---- shelf head: scope (doors) + summary (figures), one tight group ---- */
  .shelf-head {
    display: grid;
    gap: var(--space-4);
  }
  .shelf-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
  }
  .door-tabs {
    display: flex;
    gap: var(--space-2);
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .door-tab {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 34px;
    padding: 0 var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.08em;
  }
  .door-tab.is-active {
    border-color: var(--border-strong);
    background: var(--bg-elev);
    color: var(--text);
  }
  .door-tab-note {
    font-size: var(--text-2xs);
    color: var(--text-faint);
  }

  .stale-banner {
    background: color-mix(in srgb, var(--status-warn) 12%, transparent);
    border-bottom: 1px solid color-mix(in srgb, var(--status-warn) 40%, transparent);
    color: var(--status-warn);
    font-size: var(--text-sm);
  }
  /* Always in the DOM (the SSE `meta` toggle needs a target); hidden when
     the projection is fresh. */
  .stale-banner.is-hidden {
    display: none;
  }
  .stale-banner-inner {
    max-width: 1200px;
    margin: 0 auto;
    padding: var(--space-2) var(--space-5);
  }

  /* Rhythm: 32px between sections, tighter inside them — the gap tells the
     reader where one zone ends and the next begins. */
  .main {
    max-width: 1200px;
    margin: 0 auto;
    padding: var(--space-6) var(--space-5) var(--space-8);
    display: grid;
    gap: var(--space-8);
  }

  /* The shelf root is the reconnect swap unit. It must own the same section
     rhythm as .main because it wraps the head, cards and activity. */
  #shelf-root {
    display: grid;
    gap: var(--space-8);
    min-width: 0;
  }

  .section-title {
    margin: 0 0 var(--space-3);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.08em;
  }

  /* ---- aggregate strip: flat figures on the body ----
     Depth ladder: context sits on the body, only products get an elevated
     surface. Hairline separators do the structural work boxes used to. */
  .aggregate {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-6);
  }
  .agg-cell {
    display: grid;
    gap: var(--space-1);
    min-width: 0;
  }
  .agg-cell + .agg-cell {
    border-left: 1px solid var(--border);
    padding-left: var(--space-6);
  }
  .agg-label {
    font-size: var(--text-2xs);
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.08em;
  }
  .agg-value {
    font-size: var(--text-lg);
    line-height: 1.2;
    white-space: nowrap;
  }

  /* ---- cards grid ---- */
  .cards-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr));
    gap: var(--space-4);
  }
  .card {
    /* The status rail (main-app item idiom): a 2px inset edge tinted by the
       card's own status class — a fact restated quietly, never decoration. */
    --card-rail: var(--border-strong);
    display: grid;
    gap: var(--space-3);
    align-content: start;
    padding: var(--space-5);
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    /* Elevation cues, near-black grammar: a hairline light catch on the top
       edge plus a soft drop into the body — shadow ink is darkness at low
       alpha, not a color duty. background-color stays solid (the swap flash
       animates it). */
    box-shadow:
      inset 2px 0 0 var(--card-rail),
      inset 0 1px 0 color-mix(in srgb, var(--text) 5%, transparent),
      0 16px 32px -24px rgb(0 0 0 / 0.7);
    transition: border-color var(--motion-base);
  }
  .card:hover { border-color: var(--border-strong); }
  .card.status-live { --card-rail: color-mix(in srgb, var(--status-success) 60%, transparent); }
  .card.status-paused { --card-rail: color-mix(in srgb, var(--status-warn) 55%, transparent); }
  .card.status-closed { --card-rail: color-mix(in srgb, var(--text-faint) 40%, transparent); }
  .card.is-stale { opacity: 0.75; }

  /* ---- motion: the closed duty list (design-language "Quiet motion") ----
     Every animation on this surface is named here and wrapped in the motion
     guard; a new one is a design-language change first. The list:
     swap flash · entrance rise · cursor blink · live-dot breath · fold ease
     · data stream · nav crossfade (htmx view transitions, enabled in app.js
     only when supported and motion is allowed). */
  .card { --flash-base: var(--bg-elev); }
  @media (prefers-reduced-motion: no-preference) {
    /* Fold ease needs keyword interpolation (block-size: 0 → auto). */
    html { interpolate-size: allow-keywords; }

    /* Swap flash: the SSE feedback — each surface settles back to its own
       resting background (cards to the elevated plane, strips to the body). */
    .flash { animation: swap-flash var(--motion-feedback) ease-out; }

    /* Entrance rise: one beat per section on a fresh render, staggered down
       the page. Scoped OFF once the client marks .main swap-quiet (after the
       first SSE swap) so live updates never re-run the entrance — flash is
       the only swap feedback. Cards ride their section, never solo (a
       swapped card must not rise). */
    .main:not(.swap-quiet) > :not(#shelf-root),
    .main:not(.swap-quiet) > #shelf-root > * {
      animation: rise-in 0.45s var(--motion-ease-enter) backwards;
    }
    .main:not(.swap-quiet) > :nth-child(2),
    .main:not(.swap-quiet) > #shelf-root > :nth-child(2) { animation-delay: 80ms; }
    .main:not(.swap-quiet) > :nth-child(3),
    .main:not(.swap-quiet) > #shelf-root > :nth-child(3) { animation-delay: 160ms; }
    .main:not(.swap-quiet) > :nth-child(n + 4),
    .main:not(.swap-quiet) > #shelf-root > :nth-child(n + 4) { animation-delay: 240ms; }
    .gate-panel { animation: rise-in 0.45s var(--motion-ease-enter) backwards; }

    /* Hover lift: merchandise answers the pointer (transform stays in the
       motion guard; reduced-motion pointers get the border cue alone). */
    .card {
      transition:
        border-color var(--motion-base),
        transform var(--motion-base),
        box-shadow var(--motion-base);
    }
    .card:hover {
      transform: translateY(-2px);
      box-shadow:
        inset 2px 0 0 var(--card-rail),
        inset 0 1px 0 color-mix(in srgb, var(--text) 5%, transparent),
        0 20px 40px -22px rgb(0 0 0 / 0.8);
    }

    /* Cursor blink: the wordmark tick is a live prompt (soft, never to 0). */
    .wordmark-tick { animation: tick-blink 1.6s linear infinite; }

    /* Live-dot breath: a slow ripple only while the feed is up. */
    .live-indicator-dot { animation: live-breath 2.6s ease-in-out infinite; }
    .live-indicator.is-off .live-indicator-dot { animation: none; }

    /* Fold ease: the activity ledger opens instead of popping (browsers
       without ::details-content interpolation fall back to instant). */
    .activity::details-content {
      block-size: 0;
      overflow: clip;
      transition:
        block-size var(--motion-feedback) var(--motion-ease-standard),
        content-visibility var(--motion-feedback) allow-discrete;
    }
    .activity[open]::details-content { block-size: auto; }

    /* Data stream: the binary field drifts upward — data flowing, one tile
       (72px) per cycle so the loop is seamless. */
    .data-texture::before { animation: data-stream 16s linear infinite; }

    /* Nav crossfade: htmx boosted navigations wrap in a view transition
       (app.js enables it); keep the default crossfade brisk. */
    ::view-transition-old(root),
    ::view-transition-new(root) { animation-duration: 180ms; }
  }
  @keyframes swap-flash {
    from { background-color: color-mix(in srgb, var(--accent) 14%, var(--flash-base, transparent)); }
    to { background-color: var(--flash-base, transparent); }
  }
  @keyframes rise-in {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: none; }
  }
  @keyframes tick-blink {
    0%, 45%, 100% { opacity: 1; }
    55%, 90% { opacity: 0.25; }
  }
  @keyframes live-breath {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--status-success) 40%, transparent); }
    60% { box-shadow: 0 0 0 5px transparent; }
  }
  @keyframes data-stream {
    to { background-position: 0 -72px; }
  }

  .card-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-2);
  }
  .card-id { color: var(--text-faint); font-size: var(--text-xs); }
  .card-name { margin: 0; font-size: var(--text-xl); line-height: 1.3; }
  .card-name a {
    display: flex;
    align-items: center;
    min-height: 44px;
    color: var(--text);
  }
  .card-strategy {
    margin: 0;
    color: var(--text-dim);
    font-size: var(--text-sm);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .badge {
    padding: 2px var(--space-2);
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-strong);
    color: var(--text-dim);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    white-space: nowrap;
  }
  .badge-live {
    color: var(--status-success);
    border-color: color-mix(in srgb, var(--status-success) 50%, transparent);
    background: color-mix(in srgb, var(--status-success) 10%, transparent);
  }
  .badge-paused {
    color: var(--status-warn);
    border-color: color-mix(in srgb, var(--status-warn) 50%, transparent);
  }
  .badge-closed { color: var(--text-faint); }

  /* ---- term grids ----
     One hero term spans the row above a hairline; the quiet terms below share
     a single value size per band, so baselines align column to column. */
  .card-metrics {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
    margin: 0;
  }
  .detail-terms {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: var(--space-4);
    margin: 0;
  }
  .metric {
    display: grid;
    gap: var(--space-1);
    align-content: start;
    min-width: 0;
  }
  .metric dt {
    font-size: var(--text-2xs);
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.08em;
  }
  .metric dd {
    margin: 0;
    font-size: var(--text-base);
    line-height: 1.3;
    overflow-wrap: anywhere;
  }
  .metric-hero {
    grid-column: 1 / -1;
    border-bottom: 1px solid var(--border);
    padding-bottom: var(--space-3);
  }
  .metric-hero dd { font-size: var(--text-3xl); line-height: 1.1; }

  .chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    list-style: none;
    margin: 0;
    padding: 0;
  }
  /* Risk tags are facts, not warnings: tinted, borderless, receding. */
  .chip {
    padding: 2px var(--space-2);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--text-faint) 12%, transparent);
    color: var(--text-dim);
    font-size: var(--text-2xs);
  }

  .card-actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
  }

  .card-foot {
    display: flex;
    gap: var(--space-1);
    align-items: baseline;
    color: var(--text-faint);
    font-size: var(--text-2xs);
    border-top: 1px solid var(--border);
    padding-top: var(--space-2);
  }
  .stale-dot { color: var(--status-warn); margin-left: auto; }

  .shelf-empty {
    padding: var(--space-12) var(--space-4);
    text-align: center;
    border: 1px dashed var(--border);
    border-radius: var(--radius-lg);
    color: var(--text-dim);
  }
  .shelf-empty-hint { color: var(--text-faint); font-size: var(--text-sm); }
  .shelf-empty-hint a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* ---- activity: a fold over flat ledger rows ----
     The record rests folded — the summary line carries the honest row count;
     opening tends the ledger. Fixed time/key columns keep a vertical grid
     across rows; hairlines between rows, no box — it is the record, not the
     product. */
  .activity-summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 44px;
    cursor: pointer;
    list-style: none;
  }
  .activity-summary::-webkit-details-marker { display: none; }
  .activity-summary .section-title { margin: 0; }
  .activity-summary:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 2px;
  }
  .activity-count {
    padding: 1px var(--space-2);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--text-faint) 12%, transparent);
    color: var(--text-dim);
    font-size: var(--text-2xs);
  }
  /* Terminal-voice disclosure marks: mono +/− at the far edge of the rail. */
  .activity-summary::after {
    content: '+';
    margin-left: auto;
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
  }
  .activity[open] > .activity-summary::after { content: '−'; }
  .activity[open] > .activity-summary { margin-bottom: var(--space-2); }
  .activity-list {
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .activity-row {
    display: grid;
    grid-template-columns: 130px 74px 1fr auto;
    gap: var(--space-4);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border);
    font-size: var(--text-sm);
    align-items: baseline;
  }
  .activity-at, .activity-prov { color: var(--text-faint); font-size: var(--text-xs); }
  .activity-key { color: var(--text-dim); }
  .activity-title { min-width: 0; overflow-wrap: anywhere; }
  .activity-prov { justify-self: end; }
  .activity-empty { padding: var(--space-2) 0; color: var(--text-faint); font-size: var(--text-sm); }

  /* ---- detail: a page, not a card ----
     The product IS the page, so it sits flat on the body; hairlines and
     section titles do the zoning that the old wrapper box used to. */
  .crumbs { font-size: var(--text-sm); }
  .crumbs a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  .detail {
    display: grid;
    gap: var(--space-5);
  }
  .detail-head {
    display: flex;
    justify-content: space-between;
    align-items: start;
    gap: var(--space-3);
  }
  .detail-head > div { min-width: 0; }
  .detail-name { margin: var(--space-1) 0 0; font-size: var(--text-3xl); line-height: 1.15; }
  .detail-name { overflow-wrap: anywhere; }
  .detail-strategy { margin: 0; color: var(--text-dim); max-width: 70ch; }
  .detail-actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    border-top: 1px solid var(--border);
    padding-top: var(--space-4);
  }
  .doc-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-2);
    /* A document row is an object, not a banner — don't stretch one row
       across the whole rail with its action flung to the far edge. */
    max-width: 640px;
  }
  .doc-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    color: var(--text-dim);
    font-size: var(--text-sm);
  }
  .doc-id { color: var(--text-faint); font-size: var(--text-xs); }
  .detail-foot {
    color: var(--text-faint);
    font-size: var(--text-xs);
    border-top: 1px solid var(--border);
    padding-top: var(--space-3);
  }

  /* ---- footer ---- */
  .footer {
    border-top: 1px solid var(--border);
    margin-top: var(--space-8);
    /* Home-indicator inset in installed PWAs; inert elsewhere. */
    padding-bottom: env(safe-area-inset-bottom);
  }
  .footer-inner {
    max-width: 1200px;
    margin: 0 auto;
    padding: var(--space-4) var(--space-5) var(--space-6);
    display: grid;
    gap: var(--space-3);
  }
  .footer-status {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex-wrap: wrap;
    color: var(--text-faint);
    font-size: var(--text-xs);
  }
  /* Swap-unit wrapper for the per-door counts: its children sit in the
     footer-status row exactly as loose spans would (same gap, same wrap). */
  .door-status {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    flex-wrap: wrap;
  }
  /* The door strings carry interpuncts inside ("BR door · 2 live"), so the
     group boundary needs a stronger mark than a gap — the aggregate's
     hairline idiom, not another interpunct. */
  .door-status span + span {
    border-left: 1px solid var(--border);
    padding-left: var(--space-4);
  }
  .live-indicator {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
  }
  .live-indicator-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--status-success);
  }
  .live-indicator.is-off .live-indicator-dot { background: var(--status-warn); }
  .live-indicator .live-label-off { display: none; }
  .live-indicator.is-off .live-label-on { display: none; }
  .live-indicator.is-off .live-label-off { display: inline; }
  /* The house sign-off, vermilion full stops — the deck's one typographic
     flourish, kept to the footer. */
  .tagline { color: var(--text-dim); margin-left: auto; }
  .tagline-stop { color: var(--accent); }
  .footer-legal {
    margin: 0;
    color: var(--text-faint);
    font-size: var(--text-xs);
    line-height: 1.6;
    max-width: 90ch;
  }

  /* ---- brand data-texture (the deck's binary field) ----
     Only on content-free surfaces — the gate backdrop and the empty shelf.
     Never behind cards, values, or prose; one textured surface per screen. */
  .data-texture { position: relative; }
  .data-texture::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='252' height='72'%3E%3Cg font-family='monospace' font-size='11' letter-spacing='4' fill='%239a9aa1'%3E%3Ctext x='0' y='12'%3E010010110100011001010%3C/text%3E%3Ctext x='-8' y='30'%3E110100100101010100011%3C/text%3E%3Ctext x='-3' y='48'%3E001101001010010101110%3C/text%3E%3Ctext x='-11' y='66'%3E100100101010100011010%3C/text%3E%3C/g%3E%3C/svg%3E");
    /* Digits land ~#1c1c20 on the black body: present like a hairline border,
       never competing with text. The mask keeps the field off the copy. */
    opacity: 0.18;
    mask-image: radial-gradient(ellipse 65% 80% at 50% 50%, transparent 55%, black 92%);
    pointer-events: none;
  }
  .data-texture > * { position: relative; }

  /* Ambient gutters: on wide viewports the binary field runs down the dead
     margins OUTSIDE the 1200px rail — chrome-level texture on content-free
     canvas (the rail itself stays clean by mask, with a 20px buffer past the
     rail edge). Fixed, behind everything, never on phones/tablets. */
  @media (min-width: 1280px) {
    body::before {
      content: '';
      position: fixed;
      inset: 0;
      z-index: -1;
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='252' height='72'%3E%3Cg font-family='monospace' font-size='11' letter-spacing='4' fill='%239a9aa1'%3E%3Ctext x='0' y='12'%3E010010110100011001010%3C/text%3E%3Ctext x='-8' y='30'%3E110100100101010100011%3C/text%3E%3Ctext x='-3' y='48'%3E001101001010010101110%3C/text%3E%3Ctext x='-11' y='66'%3E100100101010100011010%3C/text%3E%3C/g%3E%3C/svg%3E");
      opacity: 0.12;
      mask-image: linear-gradient(
        to right,
        black,
        transparent calc(50% - 620px),
        transparent calc(50% + 620px),
        black
      );
      pointer-events: none;
    }
    /* The gutters stream too (background moves inside the element, so the
       rail mask stays put) — slower than the on-surface field. */
    @media (prefers-reduced-motion: no-preference) {
      body::before { animation: data-stream 24s linear infinite; }
    }
    /* The gate hosts a full-viewport field of its own — ambient gutters on
       top would double the texture (one textured surface per screen). */
    body:has(.gate)::before { content: none; }
  }

  /* ---- access gate (shared-key unlock; investor login is post-MVP) ---- */
  .gate {
    min-height: calc(100vh - 44px);
    display: grid;
    place-items: center;
    padding: var(--space-5);
  }
  .gate-panel {
    width: 100%;
    max-width: 360px;
    display: grid;
    gap: var(--space-4);
    padding: var(--space-6) var(--space-5);
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    /* Same elevation grammar as the cards: light catch on top, soft drop. */
    box-shadow:
      inset 0 1px 0 color-mix(in srgb, var(--text) 5%, transparent),
      0 24px 48px -32px rgb(0 0 0 / 0.8);
  }
  .gate-title { margin: 0; font-size: var(--text-md); letter-spacing: 0.14em; }
  .gate-hint { margin: 0; color: var(--text-faint); font-size: var(--text-sm); }
  .gate-error { margin: 0; color: var(--status-warn); font-size: var(--text-sm); }
  .gate-form { display: grid; gap: var(--space-2); }
  .gate-input {
    min-height: 44px;
    padding: 0 var(--space-3);
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    color: var(--text);
    font-family: var(--font-mono);
    font-size: var(--text-base);
  }
  .gate-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .gate-submit {
    min-height: 44px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--text);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    cursor: pointer;
  }
  .gate-submit:hover { border-color: var(--text-faint); }
  .gate-submit:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

  /* ---- 390px ---- */
  @media (max-width: 480px) {
    .topbar-meta { display: none; }
    .footer-inner { padding-inline: var(--space-3); }
    .topbar-inner { padding-inline: var(--space-3); }
    .stale-banner-inner { padding-inline: var(--space-3); }
    .main { padding: var(--space-4) var(--space-3) var(--space-6); gap: var(--space-6); }
    #shelf-root { gap: var(--space-6); }
    .cards-grid { grid-template-columns: 1fr; }
    /* Figures fold to a 2-col grid; the grid gap replaces the separators. */
    .aggregate {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: var(--space-3) var(--space-4);
    }
    .agg-cell + .agg-cell {
      border-left: 0;
      padding-left: 0;
    }
    /* Odd counts (5 cells once a second currency lands): the last figure
       spans the row instead of orphaning in the left column. */
    .agg-cell:last-child:nth-child(odd) {
      grid-column: 1 / -1;
    }
    .detail-terms { grid-template-columns: repeat(2, 1fr); }
    .agg-label,
    .badge,
    .chip,
    .card-foot { font-size: var(--text-xs); }
    .activity-row {
      grid-template-columns: minmax(0, 1fr) auto;
      column-gap: var(--space-2);
      row-gap: var(--space-1);
    }
    .activity-at { white-space: nowrap; }
    .activity-title { grid-column: 1; }
    .activity-prov {
      display: block;
      grid-column: 2;
      max-width: 8ch;
      overflow: hidden;
      white-space: nowrap;
      justify-self: end;
    }
  }
}
