/* ============================================================================
   BACKBONE - the house design system.

   Promoted from public/design-lab/v7 ("Backbone": hexagonal infrastructure
   blue) to the whole platform. Loaded LAST on every surface, so its :root
   block re-values the light palettes that styles.css, home.css, my.css,
   admin.css, platform.css, auth.css and b.css each declare for themselves.

   What this file owns
   -------------------
   1. TOKENS for both themes. Dark is the default and lives in `:root`; the
      derived light variant lives in `:root[data-theme="light"]` plus an
      OS-preference fallback. Every legacy token name is re-valued here, so a
      component that says `background: var(--surface)` inverts for free.
   2. GEOMETRY. Radius is 0 everywhere. The only curve on the platform is a
      hexagon.
   3. The `bb-` PRIMITIVE LAYER. One component vocabulary shared by marketing,
      auth, account, admin and chat.

   Two worlds, one system. `<html data-chrome="operate">` switches the page
   from the brand treatment (full document gradient, grain, translucent slabs)
   to the product treatment (fixed short gradient, no grain, opaque panels).
   Marketing/auth surfaces omit the attribute; admin/chat/platform set it.

   Self-contained: no imports, no CDN, no external assets. This file still ships
   system fonts only, and every page that links it and nothing else still renders
   in the OS face. The operate surfaces (the 15 console pages and the 3 account
   pages) additionally link css/type.css AFTER this file, which re-values --sans
   and --mono to two self-hosted OFL families; the CDN and third-party bans are
   unaffected, because that file serves both faces from this origin. See its
   header for why the exception is scoped to those pages and nowhere else.
   Every texture is generated (gradient stacks, clip-path polygons, an
   SVG-turbulence grain).
   ========================================================================= */

/* ══ TOKENS - DARK (default) ═══════════════════════════════════════════════ */
:root {
  color-scheme: dark;

  /* -- the gradient stack, dark -> mid teal -> dark ------------------------ */
  --deep-0: #03101a;
  --deep-1: #061f2b;
  --deep-2: #0a3244;
  --deep-3: #0e4257;
  --mid-1:  #145c76;
  --mid-2:  #17647f;          /* brightest band on a brand page */

  /* -- Backbone-native names ---------------------------------------------- */
  --sig:      #5ce1e6;        /* the one accent */
  --sig-ink:  #04202a;        /* text ON the accent fill - 10.4:1 */
  --sig-lift: #90f1f4;        /* accent hover */
  --sig-text: #8ceef1;        /* accent used AS text on a dark band */

  --line:      rgba(216, 236, 244, 0.28);
  --line-hi:   rgba(216, 236, 244, 0.55);
  --slab:      rgba(3, 16, 22, 0.62);
  --slab-hi:   rgba(3, 16, 22, 0.82);
  --grain-opacity: 0.26;

  /* -- THE DIRECTIONAL EDGE ------------------------------------------------
     Declared house-wide so there is one definition and one place to reason
     about it, but CONSUMED only by admin.css and my.css. Marketing, chat and
     /browse render byte-identical; a token nothing reads costs nothing.

     THE PROBLEM THESE SOLVE. The three opaque planes of the operate surfaces
     sit 1.04:1 (rail vs canvas), 1.14:1 (rail vs surface) and 1.18:1 (canvas
     vs surface) apart. That is not a mistake — the light-theme derivation below
     fixes every adjacent step at 1.11:1 on purpose, and Radix's dark scales run
     1.07-1.14:1 per step for the same reason. Steps that small are meant to be
     read as a family, not individually, which is correct for a ramp and useless
     as elevation. Raising them is not available either: the --deep-/--mid-
     ramps are baked into the backdrop asset.

     So the separation comes from the EDGE instead of from the fill. A 1px
     border whose four sides are not the same weight reads as an object with a
     light source; a uniform 1px outline reads as a diagram. Top catches the
     light, bottom falls into shadow, left and right stay at --border.

     Deliberately NOT a box-shadow, inset or otherwise. Every reference that
     ships this effect (Linear, Raycast, Dimension) draws it with an inset
     shadow, but this system's identity is "radius 0 + borders + no shadow", and
     a border drawn with the border property cannot be mistaken for a retreat
     from that. It also means `border-radius: 0` needs no special case and the
     high-contrast path inherits for free.

     Equally deliberately NOT a gradient inside the plane. A panel that is
     lighter at the top than at the bottom is the neumorphic move, and no
     high-craft dark console ships it — Linear, Raycast, Dimension and
     Superhuman all fill panels flat and put every depth cue in the edge.

     The direction FLIPS with the theme, because "lit from above" does. On a
     dark ground the top edge is the bright one and the bottom edge sinks into
     the canvas. On a light ground the top edge is the one that dissolves into
     the light and the bottom edge is the one that reads as cast shadow. Same
     physics, opposite hexes — the same flip --adm-rail already performs.
       dark   top .52 / sides .28 / bottom .10                                */
  --edge-top:    rgba(216, 236, 244, 0.52);
  --edge-bottom: rgba(216, 236, 244, 0.10);

  /* -- legacy vocabulary A: chat / admin / platform / my ------------------- */
  --bg:        #061f2b;
  --surface:   #08232f;
  --surface2:  #0c2c3b;
  --surface-2: #0c2c3b;                        /* auth.css spells it this way */
  --border:    rgba(216, 236, 244, 0.28);
  /* 4.96:1 on --surface, 4.52:1 composited over --slab on the brightest band - 1.4.11. Every
     consumer (.bb-input/.bb-select/.bb-textarea/.bb-check/.bb-switch/.bb-upload) carries a --slab
     fill, so the bare-on-band case (2.79:1) is not reachable. Do not use this token on a
     transparent control; --line-hi is the decorative hairline. */
  --control-border: rgba(216, 236, 244, 0.55);
  --text:      #ffffff;
  --muted:     #d8ecf4;                        /* >= 4.5:1 on every band */
  --text-muted: #d8ecf4;
  --faint:     rgba(216, 236, 244, 0.66);

  /* --accent is set INLINE on <html> at runtime from a bot's config, and an
     inline style beats this file. On surfaces that do that (chat, /b) the bot
     keeps its own colour and --sig carries the Backbone chrome. Everywhere
     else --accent resolves to the signal cyan below. */
  --accent:      #5ce1e6;
  --accent-fg:   #04202a;
  --accent-ink:  #04202a;
  --accent-text: #8ceef1;
  --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
  --accent-ring: color-mix(in srgb, var(--accent) 45%, transparent);

  --ok:         #5ce1e6;      /* grounded / cited / answered */
  --warn:       #f6bf6a;      /* semantic only: a refusal, never decoration */
  --err:        #ff9d9d;      /* 8.9:1 on --bg */
  --danger:     #ff9d9d;      /* my.css + auth.css spell --err this way */
  --ungrounded: #f6bf6a;

  /* -- PRODUCT TYPE SCALE -------------------------------------------------
     Fixed and in rem, ratio ~1.14 to 1.17: 11 · 12 · 13 · 14 · 16 · 18 · 21px
     at a 16px root. Product UI, so FIXED — clamp()-sized headings serve a
     landing page and nothing else.

     THE FLOOR IS THE SCALE, and each tier has ONE job. 12px is a DATA size, not
     a reading size: mono values, counts, badges and inline code keep it, while
     every run of words a person actually has to READ starts at 13px. Nothing in
     a product surface is set below 11px.

     It lives HERE, in the house system, rather than in one console's own
     stylesheet, because two consoles need it: admin.css wrote this scale first
     and /platform/ had none at all — 83 hard-coded sizes across seventeen
     values from 9px to 25px, 23 of them at 10.5px. A scale that exists in one
     app's stylesheet is not a scale, it is that app's preference. */
  --fs-2xs:  0.6875rem;   /* 11px — eyebrows, badges, mono labels            */
  --fs-xs:   0.75rem;     /* 12px — help text, table meta                    */
  --fs-sm:   0.8125rem;   /* 13px — dense body, table cells                  */
  --fs-base: 0.875rem;    /* 14px — body                                     */
  --fs-md:   1rem;        /* 16px — group headings                           */
  --fs-lg:   1.125rem;    /* 18px — section headings                         */
  --fs-xl:   1.3125rem;   /* 21px — page title                               */

  /* -- THE DISPLAY TIER --------------------------------------------------
     Two sizes ABOVE the page title, and they are not part of the ~1.15 ratio
     the seven tiers above run on. They are deliberately a JUMP.

     WHY. The console's whole scale ran 11px to 21px, and its hierarchy rode
     no contrast axis hard: weight stayed between 400 and 600, colour stayed
     inside one family, and the largest thing on an analytics page was 34px of
     hard-coded mono. Seven tiers all within 10px of each other is a flat scale
     with extra tokens. The house system's own rule is that hierarchy rides ONE
     axis pushed past comfort — so this is that axis, and scale is the axis.

     --fs-3xl is THE NUMBER: the one figure per area that answers "is everything
     okay". 44px against the 21px page title is 2.1x, which is the ratio that
     makes a page have a subject rather than a list of equal facts.

     RATION IT. One --fs-3xl per area, never two. A second one in the same view
     means neither is the answer, exactly as two filled buttons means neither is
     primary. Secondary metrics stay on --fs-xl and read as secondary BECAUSE
     something above them is loud. --fs-2xl is the in-between for a lead figure
     that shares its view with a chart, not a licence to size things up.

     Fixed rem like the rest of the scale, not clamp(): a product surface sizes
     from its own scale, and clamp()-sized headings serve a landing page. The
     one concession to narrow screens is a single step-down in admin.css's
     RESPONSIVE block, where 44px stops earning its width. */
  --fs-2xl:  1.75rem;     /* 28px — lead figure sharing a view               */
  --fs-3xl:  2.75rem;     /* 44px — THE metric. One per area.                */

  /* -- legacy vocabulary B: marketing / directory / bot profile ------------ */
  --ink:            #ffffff;
  --ink-soft:       #d8ecf4;
  --paper:          #061f2b;
  --paper-2:        #0a3244;
  --line-strong:    rgba(216, 236, 244, 0.55);
  --muted-2:        rgba(216, 236, 244, 0.66);
  --verified:       #5ce1e6;
  --verified-soft:  rgba(92, 225, 230, 0.10);
  --boundary:       #f6bf6a;
  --boundary-soft:  rgba(246, 191, 106, 0.10);

  /* -- chat bubbles -------------------------------------------------------- */
  --user-bubble: rgba(216, 236, 244, 0.09);
  --bot-bubble:  rgba(92, 225, 230, 0.07);

  /* -- syntax highlighting (chat code blocks), retuned to the teal world --- */
  --hl-keyword:  #7fd8ff;
  --hl-string:   #8ee6b8;
  --hl-number:   #f6bf6a;
  --hl-comment:  rgba(216, 236, 244, 0.55);
  --hl-function: #5ce1e6;
  --hl-tag:      #ff9d9d;
  --hl-attr:     #f0d79a;
  --hl-punct:    rgba(216, 236, 244, 0.72);

  /* -- type ---------------------------------------------------------------- */
  --sans: "Segoe UI", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
  --font: var(--sans);
  --mono: ui-monospace, "Cascadia Mono", "SF Mono", "Segoe UI Mono", Menlo, Consolas, monospace;
  /* Backbone has no serif voice. The old display serif is retired rather than
     left dangling: anything still asking for --serif gets the sans. */
  --serif: var(--sans);

  /* -- geometry ------------------------------------------------------------ */
  --radius: 0px;
  --shell: 1240px;
  --maxw: 1240px;
  --gut: clamp(18px, 4vw, 44px);
  --gap: clamp(18px, 4vw, 44px);

  --shadow-card: 0 30px 80px rgba(2, 10, 16, 0.55);
  /* A tooltip's own lift. --shadow-card is cast for a 400px card; under a 28px
     chip the same 30px/80px reads as a smudge rather than as height. */
  --tip-shadow: 0 8px 24px rgba(2, 10, 16, 0.45);
  --backdrop: rgba(3, 16, 22, 0.74);

  /* Honeycomb geometry. A pointy-top hexagon of width w is w * 2/root3 tall.
     Three across plus the half-cell interlock offset needs 3.5w + 2.5gap. */
  --hex-gap: 16px;
  --hex-w: min(300px, calc((100vw - 2 * var(--gut) - 2.5 * var(--hex-gap) - 6px) / 3.5));
  --hex-step: calc(var(--hex-w) + var(--hex-gap));
  --hex-clip: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);

  /* -- motion -------------------------------------------------------------- */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --t-in: 320ms;
  --t-out: 180ms;
  --t-ui: 160ms;

  /* Tooltip motion is its own pair, not --t-in/--t-out. Those are PANEL timings:
     320ms is right for a drawer and far too slow for a remark about the control
     under the pointer, and an exit at --t-out (180ms) would be SLOWER than the
     entrance, inverting the one motion rule this file states. A tooltip is the
     fastest feedback class on the site. The 400ms hover delay before any of this
     lives in js/ui/tooltip.js, where the "warm window" that skips it also does. */
  --tip-in: var(--t-ui);
  --tip-out: 110ms;
}

/* ══ TOKENS - LIGHT (derived) ══════════════════════════════════════════════
   Not a neutral paper theme: the same teal gradient read from the other end.
   Only luminance flips.

   THE RULE: a surface step is a CONTRAST RATIO, never a hex delta — deltas do
   not survive the flip (at L≈.015 a 1.11:1 step costs ΔL .007; at L≈.87, .095).
   Every adjacent opaque pair is 1.11:1, dark's strongest step:
       --bg #d6e8ef → --surface2 #e8f2f6  1.109:1   --surface2 is the ANCHOR;
       --surface2   → --surface  #fafdfe  1.113:1   --bg moved, not it.
   The ramp inverts (--surface2 under --surface): the card is already near paper.
   AA holds everywhere; --faint on --bg is tightest at 4.66:1. Bands untouched
   for lack of headroom; editing the --deep- or --mid- ramps invalidates the
   baked backdrop.
   (Written without a star-slash pair on purpose. `--deep-*` followed directly by
   `/--mid-*` spelled `*` `/`, which CLOSED THIS COMMENT four lines early. The
   parser then read the rest of the prose as declarations, failed, and resynced by
   discarding the `:root[data-theme="light"]` rule below — silently deleting the
   entire light palette. Measured 2026-08-20: the browser parsed 356 rules from
   this file and not one of them set `--deep-1` for an explicit light choice, so
   picking Light while the OS was Dark left every page dark on /home/, /browse/,
   /pricing/, /admin/* and /my/. Only /platform/, which carries its own palette,
   was unaffected. Never write a bare `*` immediately before a `/` in here.)

   FULL DERIVATION: design-reference/light-theme-derivation.md — there, not here,
   because this file ships to every visitor on all 34 pages.
   ------------------------------------------------------------------------- */
:root[data-theme="light"] {
  color-scheme: light;

  --deep-0: #eef5f8;
  --deep-1: #e3eff4;
  --deep-2: #d3e6ee;
  --deep-3: #c2dce7;
  --mid-1:  #a8cddc;
  --mid-2:  #99c5d7;

  --sig:      #075260;
  --sig-ink:  #ffffff;        /* white on #075260 - 8.7:1 */
  --sig-lift: #043d48;
  --sig-text: #05454f;

  /* Decorative, and stays faint: the ramp separates, the hairline only marks. */
  --line:      rgba(6, 36, 48, 0.20);
  /* NOT decorative — 63 control boundaries, so 1.4.11 wants 3:1. .42 failed everywhere. */
  --line-hi:   rgba(6, 36, 48, 0.55);
  /* .62 -> .80. Over --deep-0 a slab IS its --line-hi edge. */
  --slab:      rgba(255, 255, 255, 0.80);
  --slab-hi:   rgba(255, 255, 255, 0.92);
  --grain-opacity: 0.14;

  /* The direction flip — see the dark block. On paper the TOP edge is the one
     that dissolves into the light and the BOTTOM edge is the cast shadow, so
     the two values swap roles rather than being inverted in place.
       light  top .10 / sides .20 / bottom .38                                */
  --edge-top:    rgba(6, 36, 48, 0.10);
  --edge-bottom: rgba(6, 36, 48, 0.38);

  /* The ramp. Steps 1.109:1 and 1.113:1 — see the header. */
  --bg:        #d6e8ef;                         /* the WELL: inputs, code boxes, scrims */
  --surface:   #fafdfe;                         /* the card plane. Off-white, never #fff */
  --surface2:  #e8f2f6;                         /* the anchor - unchanged, both neighbours moved */
  --surface-2: #e8f2f6;
  --border:    rgba(6, 36, 48, 0.20);
  --control-border: #456470;                    /* 6.21:1 on --surface, 5.03:1 on --bg, 3.42:1 on the deepest band - 1.4.11 */
  /* Pins the page stylesheets' color-mix workaround, LIGHT ONLY: 2.94:1 -> 5.03:1. */
  --control-edge: #456470;
  --text:      #062430;
  --muted:     #1d4655;
  --text-muted: #1d4655;
  --faint:     rgba(29, 70, 85, 0.78);

  --accent:      #075260;
  --accent-fg:   #ffffff;
  --accent-ink:  #ffffff;
  --accent-text: #05454f;
  /* 10% of a dark accent on a pale card is 1.18:1; 18% gives 1.35:1. */
  --accent-soft: color-mix(in srgb, var(--accent) 18%, transparent);
  --accent-ring: color-mix(in srgb, var(--accent) 40%, transparent);

  --ok:         #075260;
  --warn:       #7a4e07;      /* 6.4:1 on --surface */
  --err:        #9c2b36;      /* 7.2:1 on --surface */
  --danger:     #9c2b36;
  --ungrounded: #7a4e07;

  --ink:            #062430;
  --ink-soft:       #1d4655;
  --paper:          #e3eff4;
  --paper-2:        #d3e6ee;
  --line-strong:    rgba(6, 36, 48, 0.55);      /* the marketing spelling of --line-hi; tracks it */
  --muted-2:        rgba(29, 70, 85, 0.78);
  --verified:       #075260;
  --verified-soft:  rgba(7, 82, 96, 0.14);      /* 1.18:1 -> 1.26:1, matching dark's 1.26:1 */
  --boundary:       #7a4e07;
  --boundary-soft:  rgba(122, 78, 7, 0.14);

  /* Had merged to 1.024:1; the pair now splits around the band for 1.45:1. */
  --user-bubble: rgba(6, 36, 48, 0.12);
  --bot-bubble:  rgba(255, 255, 255, 0.86);

  --hl-keyword:  #0b4a8c;
  --hl-string:   #10603f;
  --hl-number:   #7a4e07;
  --hl-comment:  rgba(29, 70, 85, 0.72);
  --hl-function: #075260;
  --hl-tag:      #9c2b36;
  --hl-attr:     #6b4a12;
  --hl-punct:    rgba(29, 70, 85, 0.85);

  --shadow-card: 0 24px 60px rgba(6, 36, 48, 0.16);
  --tip-shadow: 0 8px 20px rgba(6, 36, 48, 0.14);
  --backdrop: rgba(6, 36, 48, 0.42);
}

/* Same values for the no-JS / pre-boot path. Kept as a literal duplicate
   rather than an @import (a second blocking request) or a class (needs JS,
   which is exactly what this branch survives without). `:not([data-theme="dark"])`
   is what makes an explicit DARK choice beat an OS light preference. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;

    --deep-0: #eef5f8;
    --deep-1: #e3eff4;
    --deep-2: #d3e6ee;
    --deep-3: #c2dce7;
    --mid-1:  #a8cddc;
    --mid-2:  #99c5d7;

    --sig:      #075260;
    --sig-ink:  #ffffff;
    --sig-lift: #043d48;
    --sig-text: #05454f;

    --line:      rgba(6, 36, 48, 0.20);
    --line-hi:   rgba(6, 36, 48, 0.55);
    --slab:      rgba(255, 255, 255, 0.80);
    --slab-hi:   rgba(255, 255, 255, 0.92);
    --grain-opacity: 0.14;

    --edge-top:    rgba(6, 36, 48, 0.10);
    --edge-bottom: rgba(6, 36, 48, 0.38);

    --bg:        #d6e8ef;
    --surface:   #fafdfe;
    --surface2:  #e8f2f6;
    --surface-2: #e8f2f6;
    --border:    rgba(6, 36, 48, 0.20);
    --control-border: #456470;
    --control-edge: #456470;
    --text:      #062430;
    --muted:     #1d4655;
    --text-muted: #1d4655;
    --faint:     rgba(29, 70, 85, 0.78);

    --accent:      #075260;
    --accent-fg:   #ffffff;
    --accent-ink:  #ffffff;
    --accent-text: #05454f;
    --accent-soft: color-mix(in srgb, var(--accent) 18%, transparent);
    --accent-ring: color-mix(in srgb, var(--accent) 40%, transparent);

    --ok:         #075260;
    --warn:       #7a4e07;
    --err:        #9c2b36;
    --danger:     #9c2b36;
    --ungrounded: #7a4e07;

    --ink:            #062430;
    --ink-soft:       #1d4655;
    --paper:          #e3eff4;
    --paper-2:        #d3e6ee;
    --line-strong:    rgba(6, 36, 48, 0.55);
    --muted-2:        rgba(29, 70, 85, 0.78);
    --verified:       #075260;
    --verified-soft:  rgba(7, 82, 96, 0.14);
    --boundary:       #7a4e07;
    --boundary-soft:  rgba(122, 78, 7, 0.14);

    --user-bubble: rgba(6, 36, 48, 0.12);
    --bot-bubble:  rgba(255, 255, 255, 0.86);

    --hl-keyword:  #0b4a8c;
    --hl-string:   #10603f;
    --hl-number:   #7a4e07;
    --hl-comment:  rgba(29, 70, 85, 0.72);
    --hl-function: #075260;
    --hl-tag:      #9c2b36;
    --hl-attr:     #6b4a12;
    --hl-punct:    rgba(29, 70, 85, 0.85);

    --shadow-card: 0 24px 60px rgba(6, 36, 48, 0.16);
  --tip-shadow: 0 8px 20px rgba(6, 36, 48, 0.14);
    --backdrop: rgba(6, 36, 48, 0.42);
  }
}

/* ══ BAKED BACKDROP ════════════════════════════════════════════════════════
   One `--bb-backdrop` per palette above, consumed by `body` further down. The
   selectors mirror the token cascade exactly — including the two high-contrast
   variants that theme.css re-values — so the backdrop always tracks whichever
   `--deep-*`/`--mid-*` block won.

   EVERYTHING BETWEEN THE SENTINELS IS GENERATED. Run `pnpm bake:backdrop`
   after any palette change; see scripts/bake-backdrop.mjs for why these are
   bitmaps and not `linear-gradient()`. */
/* BAKED-BACKDROP:start — generated by scripts/bake-backdrop.mjs, do not edit by hand */
:root { --bb-backdrop: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAQACAIAAABEUx9rAAABsElEQVR42s1XW47DMAjE6QX63fvf06uy20imBAYM0eajVVN7GIaHMT2er8fzdRA/59cYNIZ8Kb4G0Tj4k5CVDOmACRIrtLYkvdJYEl05+HEkKJUVEKtCVhmxGlkBCXIJFlfiz03+M+LDzQTjIsNaf3zfJIjXX2cyBAmevpdo3UCwKhmU2F74vtdYdhUMV1WWvF8BUZSIINF9tepmG0S4n9WHKtJYSrT2i9LsIfEmV1oP7Vr7vQ6SJ3l23Ndt0GTncWl0TmTxlIInzu3xvHSQh0+1BdpsVrshLjkbDf8E+YYb2P+4KjRehfZWivJtvzeWHhNQ7+k4JsKjrU8+ediUHuw4CkK+f5Kbk+ZMb5+8/6BJNFNDxnvjFyUcTNsOo3zIm/JYKIZ1BAXeroEh5Ivi/stT+KC8lMmw+Bd4qTC7yLOVRM7e5AeTJ2dPhEqFTtqT0KiewEsIesueVN7Is3C+IEFF8zqdL0l7Zh0F8gWuYiHdG9zprYZ1veVZfV41dO3KRWJmSVh90NdTc4V/0L6hNj0lwds0Q7efOViTYEnNTD2NXoArES/YnBJQTbveLoH7Aa3momh0RGesAAAAAElFTkSuQmCC"); }
:root[data-theme="light"] { --bb-backdrop: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAQACAIAAABEUx9rAAABq0lEQVR42t1Xyw6DMAxr9//fyg2JWydaMY0SjPPaYbtUGzRxHOexsqzbsm6v0j/T0Upplx+Poz+8edYf4uvgmO5BRzRcCeB0vZXW/HD5e9CfANDs9sSgFe58XQIIUhXlVqnBWO+0FDWx32faqkhz0FAoj4rUe0/RC+g9Ka3EqheQd6v34S+41EDepfhSCh3mT+rlPhA59fdcQAl8amhNUGRsG90TYgcRUh2Klp43DZ2LUuxs9BUJBvGjJQMTaSOLFztcqXwShrT61sIUzrRagi3BGN/N6rC78sqN3wtSOWM3eDdn93Mlp47Y+WDnTOpLMZzJs5+OiDHGIxtuQzotwTUTJt35+P8dhFtibdLTGlJxtZT69WP/enml1lI1xiQrsj9obLiNMdatUf6QsYGFcAtfOaxoafVBgmGeSFbEd9zbT/meyPwEAsQw63O/id+cQhGiRWLQI4uhAL7pouAHySEo8CXnY7N7IDT/9/qkC5Zo8Iie4D4/8qdtzXwflPXCG5PnH8HLiUHAi73FAgYJgPoxITGoHRP5UuQVSaHO0yeTDq1afTOcqRW2Ht6rsFnEUn3MggAAAABJRU5ErkJggg=="); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) { --bb-backdrop: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAQACAIAAABEUx9rAAABq0lEQVR42t1Xyw6DMAxr9//fyg2JWydaMY0SjPPaYbtUGzRxHOexsqzbsm6v0j/T0Upplx+Poz+8edYf4uvgmO5BRzRcCeB0vZXW/HD5e9CfANDs9sSgFe58XQIIUhXlVqnBWO+0FDWx32faqkhz0FAoj4rUe0/RC+g9Ka3EqheQd6v34S+41EDepfhSCh3mT+rlPhA59fdcQAl8amhNUGRsG90TYgcRUh2Klp43DZ2LUuxs9BUJBvGjJQMTaSOLFztcqXwShrT61sIUzrRagi3BGN/N6rC78sqN3wtSOWM3eDdn93Mlp47Y+WDnTOpLMZzJs5+OiDHGIxtuQzotwTUTJt35+P8dhFtibdLTGlJxtZT69WP/enml1lI1xiQrsj9obLiNMdatUf6QsYGFcAtfOaxoafVBgmGeSFbEd9zbT/meyPwEAsQw63O/id+cQhGiRWLQI4uhAL7pouAHySEo8CXnY7N7IDT/9/qkC5Zo8Iie4D4/8qdtzXwflPXCG5PnH8HLiUHAi73FAgYJgPoxITGoHRP5UuQVSaHO0yeTDq1afTOcqRW2Ht6rsFnEUn3MggAAAABJRU5ErkJggg=="); }
}
:root[data-a11y-contrast="high"]:not([data-theme="light"]) { --bb-backdrop: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAQACAIAAABEUx9rAAABDUlEQVR42u1W0Q6EMAhj/v8/cwlm5iSVAZvxvLAHmygdUJCNSNZWUPBr0IjaJs+BCfymeNeWYuj0Z2zm9qd5jVqLCIL9ud12nlgeuWf9+evgFqTqHvHnr/u3v+m6B/us5llBQcGb7xqA0Mdo9uyYHLHjSWvO+fSEzollZavjfEQsC5qshEqe21P20AcEJeQzYll3m4uODIt1Q4fsvEAr1oWnoKDgjcBEvMnTZSmGNCDse55fKgIywQRg2YNQBCbmZUkPcziCMFJZmTQEluXKFocULhywVOW4M+nrwumeuDtpSGBpQlcPeroVZmuFa/wr2J+lEnRkRZ0LN1K4NSFZjl6i4GmzaQWDQ6fg/+ADCGbJ/76bnfwAAAAASUVORK5CYII="); }
:root[data-theme="light"][data-a11y-contrast="high"] { --bb-backdrop: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAQACAIAAABEUx9rAAAAsElEQVR42u2UwQ7AIAhDcf//zy5jpyUaUSnDpF7eYcJKbZCq5xI9BHECqkidq9OK/dbP10DV5lGcWgNVN1vjdk+4IeaAQVX382lpvTgD0OtMm8FnCZxi5Ko90BlgzgemJ5F4/AOkSg9BEARBEAQxjSJSjAV6dXDzuROvzBWWMRECg60zj/lX3Fw9G08bFrfNx+mUD52Yn+8VsenLannRE//bbx1kFzSeql2eKLTEKbgBhw1N+UkogikAAAAASUVORK5CYII="); }
@media (prefers-contrast: more) {
  :root:not([data-theme="light"]):not([data-a11y-contrast]) { --bb-backdrop: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAQACAIAAABEUx9rAAABDUlEQVR42u1W0Q6EMAhj/v8/cwlm5iSVAZvxvLAHmygdUJCNSNZWUPBr0IjaJs+BCfymeNeWYuj0Z2zm9qd5jVqLCIL9ud12nlgeuWf9+evgFqTqHvHnr/u3v+m6B/us5llBQcGb7xqA0Mdo9uyYHLHjSWvO+fSEzollZavjfEQsC5qshEqe21P20AcEJeQzYll3m4uODIt1Q4fsvEAr1oWnoKDgjcBEvMnTZSmGNCDse55fKgIywQRg2YNQBCbmZUkPcziCMFJZmTQEluXKFocULhywVOW4M+nrwumeuDtpSGBpQlcPeroVZmuFa/wr2J+lEnRkRZ0LN1K4NSFZjl6i4GmzaQWDQ6fg/+ADCGbJ/76bnfwAAAAASUVORK5CYII="); }
  :root[data-theme="light"]:not([data-a11y-contrast]) { --bb-backdrop: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAQACAIAAABEUx9rAAAAsElEQVR42u2UwQ7AIAhDcf//zy5jpyUaUSnDpF7eYcJKbZCq5xI9BHECqkidq9OK/dbP10DV5lGcWgNVN1vjdk+4IeaAQVX382lpvTgD0OtMm8FnCZxi5Ko90BlgzgemJ5F4/AOkSg9BEARBEAQxjSJSjAV6dXDzuROvzBWWMRECg60zj/lX3Fw9G08bFrfNx+mUD52Yn+8VsenLannRE//bbx1kFzSeql2eKLTEKbgBhw1N+UkogikAAAAASUVORK5CYII="); }
}
:root { --bb-circuit-mask: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAQACAYAAADLMYg8AAAAT0lEQVR42u3YwQYAIRiF0av3f+cS7SJtop8zDMc1vsVsoiTp821ZDwAAAAAAAAAAAEBJ7Fedh8vPm48FBasE/QRBQUFBQcGnB6vFYrF8vwy2a5Z+9GIHAQAAAABJRU5ErkJggg=="); }
/* BAKED-BACKDROP:end */

/* ══ GEOMETRY RESET ════════════════════════════════════════════════════════
   Radius is a hard identity switch, never mixed. Backbone is square. The one
   opt-out is `.bb-round`, for genuinely circular objects (avatars, dots). */
*, *::before, *::after { border-radius: 0; }
.bb-round, .bb-round::before, .bb-round::after { border-radius: 999px; }

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

/* The single continuous gradient, painted on body so it spans the whole
   document rather than repeating per section.

   It is a BAKED 2x1024 PNG, not a live `linear-gradient()`, and that is a
   performance decision with a measurement behind it. Firefox falls back to
   software WebRender (SWGL) on a large share of machines; SWGL rasterizes a
   >2-stop gradient with a per-pixel search over the stop list, and this one
   covers the entire document. In a 2026-07-28 Firefox profile of /home/ that
   single declaration was 7.8s of the GPU process's 11.2s of render CPU (~70%)
   and the page ran at ~5fps. Chrome and Edge, compositing on the GPU, showed
   nothing. Stretching a bitmap is a texture sample instead: same pixels, ~100x
   cheaper to rasterize, and identical everywhere that was already fine.

   `--bb-backdrop` is generated per palette by `pnpm bake:backdrop` into the
   BAKED-BACKDROP block below. Change any `--deep-*` or `--mid-*` and you must
   re-run it; nothing detects the drift. The flat `background-color` underneath
   is what shows if the data URI is ever stripped by a proxy or CSP.

   `100% 100%` reproduces the old geometry exactly: a document-height gradient
   box, no repeat, no tiling seam, and no dependence on viewport size — so it
   is shape-independent from a 320px phone to a 4K desktop. */
/* THE CANVAS IS PAINTED ON `html`, NOT ONLY ON `body`. This is a white-flash fix, and the browser
   it fixes is Firefox.
   ---------------------------------------------------------------------------------------------
   Firefox does not implement CROSS-DOCUMENT view transitions, so `@view-transition` in
   css/page-fade.css is inert there and every public navigation is an ordinary one. Chrome holds the
   previous page on screen until the next one paints; Firefox commits the new document sooner and
   paints its canvas before the stylesheets have applied. At that moment `body` does not exist yet,
   so the `background-color` below cannot propagate to the canvas — and with `html` transparent the
   canvas falls back to the UA default, which is WHITE. On a dark-first product that is a hard white
   blink between every pair of pages. Reported from Firefox against `pnpm dev`; Chrome never showed
   it, which is exactly the asymmetry this explains.
   `styles.css` has always done this for the chat (`html { background: var(--bg) }`) — the chat is
   the one page that never flashed. This is that rule, for everything else.

   `:not([data-chrome="operate"])` keeps it off the chat, whose own `html` background is declared in
   styles.css. backbone.css loads AFTER styles.css on index.html, so an unscoped rule here would
   silently take that page's canvas over.

   `min-height: 100dvh` on both is not decoration; it is what keeps this from changing anything.
   `body`'s backdrop is sized `100% 100%` OF BODY'S BOX, and until now body had no background of its
   own on the canvas — the propagation meant it was painted across the whole viewport however short
   the document was. Giving `html` a background stops that propagation, so without a floor on body's
   height the texture on a short page (a thin legal page, the 404) would suddenly scale to a short
   box instead of the viewport. `my/my.css` already carries the same pair for the same reason. */
html:not([data-chrome="operate"]) {
  background-color: var(--deep-1);
  min-height: 100dvh;
}

body {
  color: var(--text);
  font-family: var(--sans);
  background-color: var(--deep-1);
  background-image: var(--bb-backdrop);
  background-repeat: no-repeat;
  background-size: 100% 100%;
}
html:not([data-chrome="operate"]) body {
  min-height: 100dvh;
}

/* Product surfaces scroll inside their own panes, so a document-height
   gradient would drift out from under the shell. Pin it to the viewport and
   flatten the swing: the identity still reads, the data stays scannable. */
html[data-chrome="operate"] body {
  background:
    linear-gradient(180deg,
      var(--deep-0)  0%,
      var(--deep-1) 26%,
      var(--deep-2) 62%,
      var(--deep-1) 100%);
  background-attachment: fixed;
}

/* Static SVG grain over the whole viewport. Rasterized once, fixed and
   pointer-events-none, so it costs no repaint work while scrolling. Brand
   surfaces only: on a dense table it is noise, not texture. */
html:not([data-chrome="operate"]) body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.42'/%3E%3C/svg%3E");
}

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

:focus-visible { outline: 2px solid var(--sig); outline-offset: 3px; }

* { scrollbar-width: thin; scrollbar-color: var(--line-hi) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--line-hi); }
*::-webkit-scrollbar-thumb:hover { background: var(--sig); }

/* ══ LAYOUT ════════════════════════════════════════════════════════════════ */
.bb-shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gut);
  position: relative;
  z-index: 2;
}
/* NOTE: on /home/ this is overridden by `#main .bb-band { padding-block: var(--band-y) }`
   in home.css, which out-specifies it. Change --band-y there, not this. */
.bb-band { padding-block: clamp(72px, 7.4vw, 112px); }
.bb-band[id] { scroll-margin-top: 84px; }

.bb-head { text-align: center; max-width: 800px; margin: 0 auto clamp(48px, 5.4vw, 74px); }
.bb-head .bb-lede { margin-inline: auto; text-align: center; text-wrap: pretty; }

/* ══ TYPE ══════════════════════════════════════════════════════════════════ */
.bb-kicker {
  font: 600 11px/1.4 var(--mono);
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--sig);
  margin-bottom: 22px;
}
/* The accent loses ground against the brightest band, so lift it there. */
.bb-peak .bb-kicker { color: #c2f6f8; }
:root[data-theme="light"] .bb-peak .bb-kicker { color: #04353e; }

.bb-display {
  display: flex;
  flex-direction: column;
  text-transform: uppercase;
  line-height: 0.94;
  font-weight: 700;
  margin: 0 0 26px;
}
.bb-display .bb-thin  { font-weight: 200; font-size: clamp(28px, 5.9vw, 70px); letter-spacing: 0.02em; }
.bb-display .bb-heavy { font-weight: 800; font-size: clamp(37px, 8.2vw, 98px); letter-spacing: -0.018em; }

.bb-h2 {
  font-size: clamp(25px, 3.5vw, 44px);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -0.004em;
  line-height: 1.07;
  margin: 0 0 18px;
  text-wrap: balance;
}
.bb-h3 {
  font: 700 13px/1.35 var(--mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  margin: 0 0 12px;
}
.bb-lede {
  color: var(--muted);
  font-size: clamp(15px, 1.15vw, 17px);
  line-height: 1.62;
  max-width: 62ch;
}
.bb-prose { max-width: 68ch; line-height: 1.7; color: var(--muted); }
.bb-prose > * + * { margin-top: 1em; }
.bb-prose strong { color: var(--text); font-weight: 700; }
.bb-prose a { color: var(--sig-text); text-underline-offset: 4px; }

/* Ordinal / measurement numerals. Mono is reserved for data and labels here,
   never worn as a costume for "technical". */
.bb-num { font: 300 clamp(28px, 4vw, 52px)/1 var(--mono); letter-spacing: -0.04em; }

.bb-sr {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.bb-skip {
  position: absolute; left: 12px; top: -70px; z-index: 60;
  padding: 11px 18px;
  background: var(--sig); color: var(--sig-ink);
  font: 700 12px/1 var(--mono); letter-spacing: 0.18em; text-transform: uppercase;
  text-decoration: none;
  transition: top var(--t-ui) ease;
}
.bb-skip:focus { top: 12px; }

/* ══ BUTTONS ═══════════════════════════════════════════════════════════════
   One shape, four weights. The fill enters from the cursor's side on hover
   (a directional wipe, not a glow) so the affordance reads as mechanical. */
.bb-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  white-space: nowrap;
  min-height: 46px;
  padding: 0 26px;
  border: 1px solid var(--line-hi);
  background: transparent;
  color: var(--text);
  font: 600 11.5px/1 var(--mono);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  overflow: hidden;
  transition: border-color var(--t-ui) ease, color var(--t-ui) ease, background-color var(--t-ui) ease, translate 100ms ease;
}
.bb-btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: color-mix(in srgb, var(--sig) 12%, transparent);
  scale: 0 1;
  transform-origin: left center;
  transition: scale 220ms var(--ease);
}
.bb-btn:hover { border-color: var(--sig); color: var(--sig-text); }
.bb-btn:hover::before { scale: 1 1; }
.bb-btn:active { translate: 0 1px; }
.bb-btn[disabled], .bb-btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

.bb-btn-solid {
  background: var(--sig);
  border-color: var(--sig);
  color: var(--sig-ink);
}
.bb-btn-solid::before { background: var(--sig-lift); }
.bb-btn-solid:hover { background: var(--sig-lift); border-color: var(--sig-lift); color: var(--sig-ink); }

.bb-btn-ghost { border-color: transparent; }
.bb-btn-ghost:hover { border-color: var(--line-hi); }

.bb-btn-danger { border-color: color-mix(in srgb, var(--err) 60%, transparent); color: var(--err); }
.bb-btn-danger::before { background: color-mix(in srgb, var(--err) 14%, transparent); }
.bb-btn-danger:hover { border-color: var(--err); color: var(--err); }

.bb-btn-lg { min-height: 54px; padding: 0 34px; }
.bb-btn-sm { min-height: 34px; padding: 0 14px; font-size: 10px; letter-spacing: 0.18em; }
.bb-btn-block { width: 100%; }
.bb-btn-icon { padding: 0; width: 46px; min-height: 46px; }
.bb-btn-icon.bb-btn-sm { width: 34px; }
.bb-btn svg { width: 16px; height: 16px; flex: none; }

/* A button that is really a link. Keeps the underline, drops the box. */
.bb-linkbtn {
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: inherit; font-size: 14px; color: var(--muted);
  text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px;
  transition: color var(--t-ui) ease;
}
.bb-linkbtn:hover { color: var(--sig-text); }

/* ══ CHIPS, BADGES, PILLS ══════════════════════════════════════════════════ */
.bb-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 36px;
  padding: 0 14px;
  border: 1px solid var(--line-hi);
  background: transparent;
  color: var(--text);
  font: 600 10.5px/1 var(--mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color var(--t-ui) ease, color var(--t-ui) ease, background-color var(--t-ui) ease;
}
.bb-chip:hover { border-color: var(--sig); color: var(--sig-text); }
.bb-chip[aria-pressed="true"], .bb-chip.is-on {
  background: var(--sig); border-color: var(--sig); color: var(--sig-ink);
}
.bb-chip-solid { background: var(--sig); border-color: var(--sig); color: var(--sig-ink); }
.bb-chip-solid:hover { background: var(--sig-lift); border-color: var(--sig-lift); color: var(--sig-ink); }
.bb-chip svg { width: 14px; height: 14px; flex: none; }

/* Softer, sentence-case chip for example questions and filter sets. */
.bb-chip-q {
  height: auto;
  padding: 10px 14px;
  border-color: var(--line-hi);
  background: var(--slab);
  font: 500 11px/1 var(--mono);
  letter-spacing: 0.1em;
  text-transform: none;
}
.bb-chip-q:hover { background: var(--slab-hi); border-color: var(--text); color: var(--text); }

.bb-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border: 1px solid var(--line-hi);
  font: 600 10px/1.3 var(--mono);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}
.bb-badge svg { width: 12px; height: 12px; flex: none; }
.bb-badge-ok   { color: var(--ok);   border-color: color-mix(in srgb, var(--ok) 50%, transparent);   background: color-mix(in srgb, var(--ok) 8%, transparent); }
.bb-badge-warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 50%, transparent); background: color-mix(in srgb, var(--warn) 8%, transparent); }
.bb-badge-err  { color: var(--err);  border-color: color-mix(in srgb, var(--err) 50%, transparent);  background: color-mix(in srgb, var(--err) 8%, transparent); }

/* Semantic state dot. Used only where the state is real (a live bot, a failed
   run), never as list-item decoration. */
.bb-dot { width: 7px; height: 7px; flex: none; background: currentColor; }
.bb-dot-live { color: var(--ok); animation: bb-breathe 2.4s ease-in-out infinite; }
@keyframes bb-breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.42; } }

/* ══ FORMS ═════════════════════════════════════════════════════════════════
   Label above, helper in markup always, error below. No placeholder-as-label. */
.bb-field { display: flex; flex-direction: column; gap: 8px; }
.bb-field + .bb-field { margin-top: 20px; }
.bb-label {
  font: 600 10px/1 var(--mono);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
}
.bb-label .bb-req { color: var(--sig); margin-left: 4px; }

.bb-input, .bb-select, .bb-textarea {
  width: 100%;
  min-height: 46px;
  padding: 12px 14px;
  border: 1px solid var(--control-border);
  background: var(--slab);
  color: var(--text);
  font: 400 14.5px/1.5 var(--sans);
  transition: border-color var(--t-ui) ease, background-color var(--t-ui) ease;
}
.bb-textarea { min-height: 120px; resize: vertical; line-height: 1.6; }
.bb-input::placeholder, .bb-textarea::placeholder { color: var(--faint); }
.bb-input:hover, .bb-select:hover, .bb-textarea:hover { border-color: var(--text); }
.bb-input:focus-visible, .bb-select:focus-visible, .bb-textarea:focus-visible {
  outline: 2px solid var(--sig); outline-offset: 1px; border-color: var(--sig);
}
.bb-input[aria-invalid="true"], .bb-textarea[aria-invalid="true"] { border-color: var(--err); }
.bb-input[disabled], .bb-select[disabled], .bb-textarea[disabled] { opacity: 0.5; cursor: not-allowed; }
.bb-select {
  appearance: none;
  padding-right: 40px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 20px) 21px, calc(100% - 14px) 21px;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}

.bb-help { font-size: 12.5px; line-height: 1.5; color: var(--faint); }
.bb-error {
  display: flex; align-items: flex-start; gap: 7px;
  font-size: 12.5px; line-height: 1.5; color: var(--err);
}
.bb-error svg { width: 14px; height: 14px; flex: none; margin-top: 2px; }

/* Checkbox / radio: a real square with a drawn mark, not a recoloured native
   control. Consent copy sits beside it and stays selectable. */
.bb-check { display: flex; align-items: flex-start; gap: 11px; cursor: pointer; }
.bb-check input {
  appearance: none;
  flex: none;
  width: 18px; height: 18px;
  margin-top: 2px;
  border: 1px solid var(--control-border);
  background: var(--slab);
  cursor: pointer;
  transition: background-color var(--t-ui) ease, border-color var(--t-ui) ease;
}
.bb-check input[type="radio"] { border-radius: 999px; }
.bb-check input:checked { background: var(--sig); border-color: var(--sig); }
.bb-check input:checked::after {
  content: "";
  display: block;
  width: 100%; height: 100%;
  background: var(--sig-ink);
  clip-path: polygon(19% 47%, 8% 58%, 40% 88%, 92% 27%, 81% 18%, 39% 66%);
}
.bb-check input[type="radio"]:checked::after { clip-path: circle(28% at 50% 50%); }
.bb-check input:focus-visible { outline: 2px solid var(--sig); outline-offset: 2px; }
.bb-check span { font-size: 14px; line-height: 1.55; color: var(--muted); }
.bb-check a { color: var(--sig-text); text-underline-offset: 3px; }

/* Switch. Square, because Backbone is square. */
.bb-switch { display: inline-flex; align-items: center; gap: 11px; cursor: pointer; }
.bb-switch input { appearance: none; width: 44px; height: 24px; border: 1px solid var(--control-border); background: var(--slab); position: relative; cursor: pointer; transition: background-color var(--t-ui) ease, border-color var(--t-ui) ease; }
.bb-switch input::after { content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; background: var(--muted); transition: translate 180ms var(--ease), background-color var(--t-ui) ease; }
.bb-switch input:checked { background: color-mix(in srgb, var(--sig) 22%, transparent); border-color: var(--sig); }
.bb-switch input:checked::after { translate: 20px 0; background: var(--sig); }
.bb-switch input:focus-visible { outline: 2px solid var(--sig); outline-offset: 2px; }

/* ══ PANELS ════════════════════════════════════════════════════════════════
   The container of last resort. Group with a rule or with space first; reach
   for a panel only when elevation communicates real hierarchy. */
.bb-panel {
  border: 1px solid var(--border);
  background: var(--surface);
}
.bb-panel-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--surface2);
  font: 600 10.5px/1.3 var(--mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}
.bb-panel-head .bb-panel-end { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.bb-panel-body { padding: 18px 16px; }
.bb-panel-foot { padding: 14px 16px; border-top: 1px solid var(--border); display: flex; gap: 10px; justify-content: flex-end; }

/* Spotlight border: the edge illuminates from the cursor. JS sets --mx/--my
   as percentages on pointermove; without JS the panel is simply outlined. */
.bb-glow { position: relative; isolation: isolate; }
.bb-glow::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(220px circle at var(--mx, 50%) var(--my, 50%), color-mix(in srgb, var(--sig) 55%, transparent), transparent 70%);
  transition: opacity 260ms var(--ease);
}
.bb-glow:hover::after, .bb-glow:focus-within::after { opacity: 1; }

/* ══ HONEYCOMB ═════════════════════════════════════════════════════════════
   The one curve on the platform. Six interlocking cells, each with a built
   subject field: nothing here is a fetched image. */
.bb-comb {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: var(--hex-gap);
  width: min(100%, calc(3 * var(--hex-w) + 2 * var(--hex-gap)));
  margin-inline: auto;
}
.bb-hex {
  --dx: 0px;
  --dy: 0px;
  position: relative;
  width: var(--hex-w);
  max-width: 100%;
  aspect-ratio: 1 / 1.1547;          /* a true hexagon at every size */
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  translate: var(--dx) var(--dy);
  transition: translate 620ms var(--ease), opacity 620ms var(--ease);
}
/* Interlock: the two rows nudge half a cell in opposite directions, which
   nests them AND keeps the six-tile block centred on the page axis. */
.bb-hex:nth-child(-n+3) { --dx: calc(var(--hex-step) * 0.25); }
.bb-hex:nth-child(n+4)  { --dx: calc(var(--hex-step) * -0.25); margin-top: calc(var(--hex-w) * -0.2887); }

.bb-hex-shell {
  position: absolute; inset: 0;
  clip-path: var(--hex-clip);
  background: var(--line-hi);        /* reads as the 1.5px hex outline */
  transition: background-color 200ms ease;
}
.bb-hex-face {
  position: absolute; inset: 1.5px;
  clip-path: var(--hex-clip);
  background: var(--fill, linear-gradient(158deg, var(--deep-3), var(--deep-1)));
  transition: filter 250ms ease;
}
.bb-hex-scrim {
  position: absolute; inset: 1.5px;
  clip-path: var(--hex-clip);
  background:
    radial-gradient(64% 40% at 50% 62%, rgba(3, 15, 22, 0.62), transparent 78%),
    linear-gradient(180deg, rgba(3, 15, 22, 0.06) 0%, rgba(3, 15, 22, 0.50) 42%, rgba(3, 15, 22, 0.82) 100%);
}
.bb-hex:hover .bb-hex-shell { background: var(--sig); }
.bb-hex:hover .bb-hex-face { filter: saturate(1.35) brightness(1.3); }
/* The clip-path would eat a ring drawn on the shell, so the ring lives on the
   unclipped anchor itself. */
.bb-hex:focus-visible { outline: 2px solid var(--sig); outline-offset: 4px; }
.bb-hex:focus-visible .bb-hex-shell { background: var(--sig); }

.bb-hex-body {
  position: relative;
  width: 70%;
  display: flex; flex-direction: column; align-items: center;
  gap: 9px;
  text-align: center;
  padding-top: 13%;
}
.bb-hex-label {
  font: 700 12.5px/1 var(--mono);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #ffffff;
  text-shadow: 0 1px 8px rgba(3, 15, 22, 0.95);
}
.bb-hex-desc {
  font-size: 12.5px; line-height: 1.5;
  color: #d8ecf4;
  text-shadow: 0 1px 8px rgba(3, 15, 22, 0.95);
}
.bb-hex-go {
  margin-top: 3px;
  padding: 7px 12px;
  border: 1px solid rgba(216, 236, 244, 0.55);
  font: 600 9.5px/1 var(--mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #ffffff;
  transition: border-color 200ms ease, color 200ms ease;
}
.bb-hex:hover .bb-hex-go { border-color: var(--sig); color: var(--sig); }

/* Six built subject fields: a survey mesh, a radar, strata, two lenses, a
   signal quadrant and a spectrum, drawn entirely with gradient stacks. The
   cells stay dark in both themes, so these literals are deliberate. */
.bb-f1 .bb-hex-face { --fill:
  repeating-linear-gradient(90deg, rgba(92,225,230,.34) 0 1px, transparent 1px 20px),
  repeating-linear-gradient(0deg,  rgba(92,225,230,.26) 0 1px, transparent 1px 20px),
  radial-gradient(58% 46% at 50% 34%, rgba(92,225,230,.46), transparent 72%),
  linear-gradient(158deg, #11536c, #061f2b); }
.bb-f2 .bb-hex-face { --fill:
  repeating-radial-gradient(circle at 50% 48%, rgba(92,225,230,.34) 0 1px, transparent 1px 15px),
  radial-gradient(44% 38% at 50% 48%, rgba(92,225,230,.48), transparent 76%),
  linear-gradient(200deg, #135469, #05202c); }
.bb-f3 .bb-hex-face { --fill:
  repeating-linear-gradient(0deg, rgba(216,236,244,.24) 0 2px, transparent 2px 11px),
  linear-gradient(180deg, rgba(92,225,230,.42), transparent 66%),
  linear-gradient(140deg, #0f4a5f, #06212c); }
.bb-f4 .bb-hex-face { --fill:
  radial-gradient(34% 28% at 34% 38%, rgba(92,225,230,.56), transparent 72%),
  radial-gradient(34% 28% at 64% 54%, rgba(128,192,255,.48), transparent 72%),
  repeating-linear-gradient(45deg, rgba(216,236,244,.10) 0 1px, transparent 1px 9px),
  linear-gradient(160deg, #114f66, #06222d); }
.bb-f5 .bb-hex-face { --fill:
  conic-gradient(from 45deg at 50% 46%, rgba(92,225,230,.42), transparent 24%, rgba(92,225,230,.42) 50%, transparent 74%, rgba(92,225,230,.42)),
  repeating-linear-gradient(90deg, transparent 0 15px, rgba(216,236,244,.14) 15px 16px),
  linear-gradient(190deg, #0f4a60, #06202b); }
.bb-f6 .bb-hex-face { --fill:
  repeating-linear-gradient(90deg, rgba(92,225,230,.38) 0 7px, transparent 7px 19px),
  linear-gradient(0deg, rgba(92,225,230,.44), transparent 62%),
  linear-gradient(150deg, #104c62, #06202b); }

/* ══ MEASUREMENT STRIP ═════════════════════════════════════════════════════ */
.bb-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border: 1px solid var(--line);
  background: var(--slab);
}
.bb-strip-cell {
  padding: 20px 16px;
  border-right: 1px solid var(--line);
  font: 500 11px/1.55 var(--mono);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
}
.bb-strip-cell:last-child { border-right: 0; }
.bb-strip-cell b { color: var(--text); font-weight: 700; }
.bb-strip-3 { grid-template-columns: repeat(3, 1fr); }
.bb-strip-2 { grid-template-columns: repeat(2, 1fr); }

/* ══ RAIL (sequence without cards) ═════════════════════════════════════════ */
.bb-rail {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(20px, 3vw, 40px);
  position: relative;
  padding-top: 34px;
  margin: 0; list-style: none;
}
.bb-rail::before {
  content: "";
  position: absolute;
  top: 5px; left: 6%; right: 6%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-hi) 10%, var(--line-hi) 90%, transparent);
}
.bb-stop { position: relative; }
.bb-stop-node {
  position: absolute;
  top: -34px; left: 0;
  width: 11px; height: 11px;
  background: var(--deep-3);
  border: 1px solid var(--sig);
  rotate: 45deg;
}
.bb-stop p { color: var(--muted); font-size: 15px; max-width: 34ch; }
.bb-rail-4 { grid-template-columns: repeat(4, 1fr); }

/* ══ CONSOLE (product preview slab) ════════════════════════════════════════ */
.bb-console {
  width: 100%;
  border: 1px solid var(--line-hi);
  background: var(--slab-hi);
  text-align: left;
  box-shadow: var(--shadow-card);
}
.bb-console-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--text) 5%, transparent);
}
.bb-console-marks { display: inline-flex; gap: 5px; flex: none; }
.bb-console-marks i { width: 7px; height: 7px; background: var(--faint); }
.bb-console-title {
  font: 500 10.5px/1.3 var(--mono);
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--faint);
}
.bb-console-state {
  margin-left: auto; flex: none;
  font: 600 10px/1 var(--mono);
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--sig);
  border: 1px solid color-mix(in srgb, var(--sig) 50%, transparent);
  padding: 4px 8px;
}
.bb-console-body { padding: 18px 16px; display: flex; flex-direction: column; gap: 12px; font-size: 14px; }

.bb-msg { max-width: 84%; padding: 10px 13px; border: 1px solid var(--line); line-height: 1.5; flex: none; }
.bb-msg-user { align-self: flex-end; background: var(--user-bubble); color: var(--text); }
.bb-msg-bot  { align-self: flex-start; background: var(--bot-bubble); border-color: color-mix(in srgb, var(--sig) 34%, transparent); color: var(--muted); }
.bb-msg-refusal { background: color-mix(in srgb, var(--warn) 8%, transparent); border-color: color-mix(in srgb, var(--warn) 46%, transparent); }
.bb-msg-tag {
  display: block; margin-bottom: 7px;
  font: 700 9.5px/1 var(--mono);
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--sig);
}
.bb-msg-refusal .bb-msg-tag { color: var(--warn); }
.bb-sources {
  display: block; margin-top: 8px; padding-top: 7px;
  border-top: 1px solid color-mix(in srgb, var(--sig) 28%, transparent);
  font: 500 10px/1.6 var(--mono);
  letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--sig-text);
}
.bb-caret {
  display: inline-block; width: 7px; height: 14px; margin-left: 2px;
  background: var(--sig); vertical-align: -2px;
  animation: bb-blink 0.9s steps(2, start) infinite;
}
@keyframes bb-blink { 50% { opacity: 0; } }

/* ══ TABLE ═════════════════════════════════════════════════════════════════
   Density is a permission here, not a failure. Rules separate rows; there is
   no card around each record. */
.bb-table-wrap { overflow-x: auto; border: 1px solid var(--border); }
.bb-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.bb-table th {
  position: sticky; top: 0; z-index: 1;
  padding: 11px 14px;
  text-align: left;
  background: var(--surface2);
  border-bottom: 1px solid var(--border);
  font: 600 10px/1.3 var(--mono);
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.bb-table th[aria-sort] { cursor: pointer; }
.bb-table th[aria-sort="ascending"], .bb-table th[aria-sort="descending"] { color: var(--sig); }
.bb-table td { padding: 12px 14px; border-bottom: 1px solid var(--border); color: var(--text); vertical-align: top; }
.bb-table tbody tr:last-child td { border-bottom: 0; }
.bb-table tbody tr:hover td { background: color-mix(in srgb, var(--sig) 6%, transparent); }
.bb-table tbody tr[aria-selected="true"] td { background: color-mix(in srgb, var(--sig) 12%, transparent); }
.bb-table .bb-td-num { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; }
.bb-table .bb-td-dim { color: var(--muted); }

.bb-pagination { display: flex; align-items: center; gap: 6px; padding: 12px 14px; border-top: 1px solid var(--border); }
.bb-pagination .bb-page-info { margin-right: auto; font: 500 11px/1 var(--mono); letter-spacing: 0.14em; color: var(--faint); }
.bb-page {
  min-width: 34px; height: 34px; padding: 0 9px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: transparent; color: var(--muted);
  font: 600 11px/1 var(--mono); cursor: pointer;
  transition: border-color var(--t-ui) ease, color var(--t-ui) ease;
}
.bb-page:hover { border-color: var(--sig); color: var(--sig-text); }
.bb-page[aria-current="page"] { background: var(--sig); border-color: var(--sig); color: var(--sig-ink); }

/* ══ TABS ══════════════════════════════════════════════════════════════════ */
.bb-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); overflow-x: auto; }
.bb-tab {
  flex: none;
  padding: 12px 18px;
  border: 0; border-bottom: 2px solid transparent;
  background: transparent; color: var(--muted);
  font: 600 11px/1 var(--mono);
  letter-spacing: 0.2em; text-transform: uppercase;
  cursor: pointer; white-space: nowrap;
  transition: color var(--t-ui) ease, border-color var(--t-ui) ease;
}
.bb-tab:hover { color: var(--text); }
.bb-tab[aria-selected="true"] { color: var(--sig); border-bottom-color: var(--sig); }

/* ══ ACCORDION ═════════════════════════════════════════════════════════════ */
.bb-accordion { border-top: 1px solid var(--border); }
.bb-acc-item { border-bottom: 1px solid var(--border); }
.bb-acc-trigger {
  width: 100%;
  display: flex; align-items: center; gap: 16px;
  padding: 18px 0;
  border: 0; background: transparent; color: var(--text);
  font: 600 15px/1.4 var(--sans);
  text-align: left; cursor: pointer;
}
.bb-acc-trigger::after {
  content: "";
  margin-left: auto; flex: none;
  width: 11px; height: 11px;
  border-right: 1px solid currentColor; border-bottom: 1px solid currentColor;
  rotate: 45deg;
  transition: rotate 220ms var(--ease);
}
.bb-acc-trigger[aria-expanded="true"] { color: var(--sig); }
.bb-acc-trigger[aria-expanded="true"]::after { rotate: 225deg; }
.bb-acc-panel { padding: 0 0 20px; color: var(--muted); line-height: 1.7; max-width: 68ch; }

/* ══ DIALOG ════════════════════════════════════════════════════════════════
   A real <dialog>, so it escapes every overflow container and the backdrop
   and focus trap come from the platform. */
.bb-dialog {
  width: min(520px, calc(100vw - 32px));
  padding: 0;
  border: 1px solid var(--line-hi);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-card);
}
.bb-dialog::backdrop { background: var(--backdrop); backdrop-filter: blur(3px); }
.bb-dialog-head {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 20px 20px 14px;
  border-bottom: 1px solid var(--border);
}
.bb-dialog-title { font: 700 16px/1.3 var(--sans); margin: 0; }
.bb-dialog-sub { margin-top: 5px; font-size: 13.5px; color: var(--muted); }
.bb-dialog-close { margin-left: auto; }
.bb-dialog-body { padding: 20px; }
.bb-dialog-foot { padding: 16px 20px; border-top: 1px solid var(--border); display: flex; gap: 10px; justify-content: flex-end; }
.bb-dialog-lg { width: min(760px, calc(100vw - 32px)); }

/* One-time-code entry: separate cells, mono, tabular. */
.bb-otp { display: flex; gap: 8px; }
.bb-otp input {
  width: 46px; height: 56px;
  text-align: center;
  border: 1px solid var(--control-border);
  background: var(--slab);
  color: var(--text);
  font: 600 20px/1 var(--mono);
}
.bb-otp input:focus-visible { outline: 2px solid var(--sig); outline-offset: 1px; border-color: var(--sig); }

/* ══ POPOVER / MENU ════════════════════════════════════════════════════════
   Positioned by js/ui/dropdown.js, which writes `position: fixed` plus top/right on open. Fixed,
   not absolute: a menu positioned against an ancestor is at the mercy of whatever `overflow` that
   ancestor sets, which is how the admin console's menu once ended up clipped inside #content.

   THE Z-INDEX IS PART OF THE PRIMITIVE, not of each surface. Every hand-rolled version of this menu
   carried its own `z-index: 1001` (the chat header's, the admin console's), and the value only
   looked arbitrary until they were consolidated: a popover has to clear the tallest thing any
   surface stacks over the page, and the chat's mobile overflow panel is 300 over a 299 backdrop.
   Leaving it unset made the chat's menu render on top of that backdrop and take none of its
   clicks — visible, correct, and completely dead. It belongs here so no future surface has to
   rediscover it. */
.bb-pop {
  min-width: 220px;
  z-index: 1001;
  padding: 6px;
  border: 1px solid var(--line-hi);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}
.bb-pop-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 9px 11px;
  border: 0; background: transparent;
  color: var(--text); text-align: left; text-decoration: none;
  cursor: pointer;
  transition: background-color var(--t-ui) ease, color var(--t-ui) ease;
  font: 400 14px/1.35 var(--sans);
  letter-spacing: normal;
  text-transform: none;
}
/* THE WHOLE FONT, STATED, AT ELEMENT+CLASS SPECIFICITY. A popover is opened FROM somewhere, and its
   rows are still DOM descendants of that somewhere even though js/ui/dropdown.js positions them
   fixed. The marketing and dashboard headers set `.nav-links a:not(.btn)` in tracked uppercase mono
   — specificity (0,2,1) — so an account menu mounted there came out reading "MY BOTS / SETTINGS"
   beside a plain "Sign out": the two <a> rows inherited the nav treatment and the <button> row did
   not. Matching (0,2,1) here and relying on backbone.css being the LAST sheet on every page is what
   settles it, for any host rule of that weight rather than only for this one. A menu row is body
   text wherever it is opened. */
.bb-pop a.bb-pop-item,
.bb-pop button.bb-pop-item {
  font: 400 14px/1.35 var(--sans);
  letter-spacing: normal;
  text-transform: none;
  color: var(--text);
  padding: 9px 11px;
  border-bottom: 0;
}
.bb-pop-item:hover { background: color-mix(in srgb, var(--sig) 12%, transparent); color: var(--sig-text); }
.bb-pop-item svg { width: 15px; height: 15px; flex: none; }
.bb-pop-sep { height: 1px; margin: 6px 0; background: var(--border); }
.bb-pop-label { padding: 8px 11px 6px; font: 600 9.5px/1 var(--mono); letter-spacing: 0.24em; text-transform: uppercase; color: var(--faint); }
.bb-pop-danger:hover { background: color-mix(in srgb, var(--err) 14%, transparent); color: var(--err); }

/* The identity header at the top of an account menu: who you are, above what you can do. */
.bb-pop-id {
  padding: 9px 11px 11px;
  margin-bottom: 5px;
  border-bottom: 1px solid var(--border);
}
.bb-pop-id .n { font-weight: 600; font-size: 14px; color: var(--text); }
.bb-pop-id .e {
  margin-top: 2px;
  font-size: 12.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The Appearance radiogroup (js/ui/theme-picker.js). Three equal cells, so the group reads as one
   control rather than as three more menu rows — which is the point: it is one tab stop. */
.bb-pop-theme { margin-top: 6px; padding-top: 7px; border-top: 1px solid var(--border); }
.bb-pop-theme-opts { display: flex; gap: 4px; padding: 0 6px 2px; }
.bb-pop-theme-opt {
  flex: 1 1 0; min-width: 0;
  padding: 7px 4px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  font: 600 10px/1.2 var(--mono);
  letter-spacing: 0.1em; text-transform: uppercase;
  cursor: pointer;
  transition: border-color var(--t-ui) ease, background-color var(--t-ui) ease, color var(--t-ui) ease;
}
.bb-pop-theme-opt:hover { border-color: var(--line-hi); color: var(--text); }
.bb-pop-theme-opt[aria-checked="true"] { background: var(--sig); border-color: var(--sig); color: var(--sig-ink); }
.bb-pop-theme-opt:focus-visible { outline: 2px solid var(--sig); outline-offset: 1px; }

/* ══ IDENTITY BUTTON ═══════════════════════════════════════════════════════
   The profile control — avatar, name, caret — that opens a `.bb-pop`. ONE button for the
   marketing header, the chat header, the admin console and the operator console
   (js/ui/account-menu.js). It is deliberately NOT `.bb-btn`: that primitive is a 46px uppercase
   mono call-to-action, and a person's name set in 24em-tracked capitals is not a name. */
.bb-idmenu { position: relative; display: inline-flex; }
.bb-idbtn {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 36px;
  padding: 3px 9px 3px 3px;
  border: 1px solid var(--border);
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: border-color var(--t-ui) ease, background-color var(--t-ui) ease;
}
.bb-idbtn:hover { border-color: var(--sig); background: color-mix(in srgb, var(--sig) 8%, transparent); }
.bb-idbtn[aria-expanded="true"] { border-color: var(--sig); background: color-mix(in srgb, var(--sig) 12%, transparent); }
.bb-idbtn:focus-visible { outline: 2px solid var(--sig); outline-offset: 2px; }
.bb-idbtn .bb-avatar { border: 0; }
.bb-idbtn .bb-avatar svg { width: 16px; height: 16px; }
/* Same reason as `.bb-pop-item`: a person's name is body text, whatever the header it sits in sets
   its own links in. */
.bb-idbtn-name {
  max-width: 12ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 400 14px/1.2 var(--sans);
  letter-spacing: normal;
  text-transform: none;
}
.bb-pop-id .n, .bb-pop-id .e { font-family: var(--sans); letter-spacing: normal; text-transform: none; }
.bb-idbtn-chev { font-size: 10px; color: var(--faint); }
/* Signed out: an icon + "Sign in", which needs its own left padding now that no avatar tile is
   filling that side. */
.bb-idbtn-out { padding: 3px 12px; }
.bb-idbtn-out svg { width: 16px; height: 16px; }

/* Below 640px the name is dropped and the pill closes up symmetrically around the 26px avatar
   (3px all round + 1px border = 34px), instead of stranding the desktop's 9px right inset. */
@media (max-width: 640px) {
  .bb-idbtn-name { display: none; }
  .bb-idbtn { padding: 3px; }
  .bb-idbtn-out { padding: 3px 10px; }
}
/* A touch target is 44px, and the pill above is 36px. Raised only where the pointer is coarse, so
   a mouse keeps the tighter header row. */
@media (pointer: coarse) {
  .bb-idbtn { min-height: 44px; min-width: 44px; justify-content: center; }
  .bb-pop-item { padding: 12px 11px; }
  .bb-pop-theme-opt { padding: 11px 4px; }
}

/* ══ TOOLTIP ═══════════════════════════════════════════════════════════════
   The hover detail. ONE element per document, positioned and filled by
   js/ui/tooltip.js; every control that wants one carries `data-tip` (plus an
   optional `data-tip-title` for the two-line form). See that module's header for
   the behaviour and the opt-in rule.

   IT IS NOT A ROUNDED BLACK RECTANGLE, and that is the whole point: `--radius`
   is 0 here, so the tip is the same square, hairline-bordered, lifted plane as
   `.bb-pop` and `.bb-toast` — read at a smaller scale. The shadow is its OWN
   token rather than `--shadow-card`, which is a 30px/80px cast sized for a
   400px card and reads as a smudge under a 28px chip.

   z-index 1002 sits exactly one above `.bb-pop`'s 1001 (see the note there for
   why 1001 is the site's real ceiling). A tip raised FROM a control inside an
   open menu has to clear that menu; nothing else on the site stacks higher.

   `pointer-events: none` is what makes the WCAG 1.4.13 "not obscuring" clause
   free: the tip can never intercept a click meant for the control it describes,
   and combined with the 8px gap it never covers its own trigger either. */
.bb-tip {
  position: fixed;
  top: 0; left: 0;
  z-index: 1002;
  /* `width: max-content` capped by `max-width`, NOT plain shrink-to-fit. A fixed element with
     `width: auto` is sized against the space remaining beside it, and with `text-wrap: pretty` in
     play Chrome settled on an intrinsic width ~1px under its own single line — so "More options"
     came out as a two-line box 94px wide. max-content asks the question directly: one line unless
     the cap forces a wrap. */
  width: max-content;
  max-width: min(280px, calc(100vw - 32px));
  padding: 6px 9px;
  border: 1px solid var(--line-hi);
  background: var(--surface);
  box-shadow: var(--tip-shadow);
  color: var(--text);
  font: 500 12.5px/1.4 var(--sans);
  letter-spacing: normal;
  text-transform: none;
  text-wrap: pretty;
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
  /* Leaving is faster than arriving — the house rule, stated in the motion block
     at the top of this file. `visibility` is stepped at the END of the exit so a
     faded-out tip stops being hit-tested and stops answering find-in-page. */
  transition:
    opacity var(--tip-out) var(--ease),
    translate var(--tip-out) var(--ease),
    visibility 0s linear var(--tip-out);
}
/* The from-state, per side: the tip rises INTO place from the direction of the
   control it belongs to, which is the whole job the arrow this component does
   not have would otherwise be doing. */
.bb-tip[data-place="top"]    { translate: 0 4px; }
.bb-tip[data-place="bottom"] { translate: 0 -4px; }
.bb-tip[data-place="left"]   { translate: 4px 0; }
.bb-tip[data-place="right"]  { translate: -4px 0; }

.bb-tip.is-on {
  visibility: visible;
  opacity: 1;
  translate: 0 0;
  transition:
    opacity var(--tip-in) var(--ease),
    translate var(--tip-in) var(--ease),
    visibility 0s;
}

/* The two-line form. Its eyebrow is `.bb-pop-label`'s type, exactly — the same
   mono micro-label that heads every menu group on the site — so a rich tip reads
   as part of the same system rather than as a hover card someone bolted on. */
.bb-tip-rich { padding: 9px 11px; }
.bb-tip-title {
  display: block;
  margin-bottom: 4px;
  font: 600 9.5px/1 var(--mono);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--faint);
}
.bb-tip-rich .bb-tip-body { display: block; font-size: 13px; line-height: 1.5; color: var(--muted); }
/* The shortcut that fires the control, as a keycap rather than a parenthetical. It is the same mono
   micro-type as every other chrome label on the site, boxed in the `--slab` fill the form controls
   use, so it reads as a KEY and not as a second sentence. Sits inline after a label tip; a rich tip
   pushes it onto its own line, because a two-line explanation ending in a floating keycap is a
   worse sentence than one that doesn't. */
.bb-tip-key {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 5px;
  border: 1px solid var(--line-hi);
  background: var(--slab);
  color: var(--muted);
  font: 600 10px/1.5 var(--mono);
  letter-spacing: 0.06em;
  vertical-align: 1px;
}
.bb-tip-rich .bb-tip-key { display: block; width: max-content; margin: 6px 0 0; }

/* LAST, and it has to be: a `display` on a bare class OUTRANKS the UA sheet's `[hidden]{display:none}`,
   so the unused half of the tip kept rendering as an empty block — a label-only tip came out ~14px
   taller than its own text and, being measured that way, was placed off its trigger. */
.bb-tip [hidden] { display: none; }

/* TOUCH: no tooltips, decided rather than defaulted. A tap is an activation, so
   a tooltip that opens on tap eats the first press on every icon on the page,
   and long-press is already the OS context menu. The site's own answer is
   better and already shipped: at <=600px the chat's overflow menu turns each
   icon into a labelled row (.header-action-label). Mobile gets labels, not
   hovers. The module bails on `(pointer: coarse)` too; this is the belt. */
@media (pointer: coarse) {
  .bb-tip { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* The 400ms delay stays — a delay is not motion, and removing it would make
     these the only users getting tooltip flicker. Only the travel goes. */
  .bb-tip, .bb-tip.is-on { transition-duration: 1ms; translate: none; }
}

/* ══ TOAST ═════════════════════════════════════════════════════════════════ */
.bb-toasts {
  position: fixed;
  right: 18px; bottom: 18px;
  z-index: 200;
  display: flex; flex-direction: column; gap: 10px;
  pointer-events: none;
}
.bb-toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: 11px;
  min-width: 280px; max-width: 380px;
  padding: 13px 15px;
  border: 1px solid var(--line-hi);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  font-size: 14px;
  animation: bb-toast-in 260ms var(--ease);
}
@keyframes bb-toast-in { from { opacity: 0; translate: 0 12px; } }
/* Semantics ride the whole outline and a tinted ground, not a coloured tab down
   one edge. A 3px side bar on a callout is the most recognisable tell there is,
   and it also reads as a progress affordance it does not have. */
.bb-toast-ok  { border-color: color-mix(in srgb, var(--ok) 55%, transparent);  background: color-mix(in srgb, var(--ok) 7%, var(--surface)); }
.bb-toast-err { border-color: color-mix(in srgb, var(--err) 55%, transparent); background: color-mix(in srgb, var(--err) 7%, var(--surface)); }
.bb-toast-title { font-weight: 700; }
.bb-toast-body { color: var(--muted); font-size: 13px; margin-top: 3px; }
.bb-toast-close { margin-left: auto; }

/* ══ APP SHELL ═════════════════════════════════════════════════════════════
   The `.bb-side*` sidebar primitive that lived here has been REMOVED, not
   deprecated. The admin console is the platform's only sidebar surface and it
   ships a deliberately non-collapsible rail (see the note at admin.css:909: a
   14-area rail collapsed to ~40px needs an icon per area, and this console has
   no icon vocabulary for them). Nothing else imported it, so it was dead code
   whose only live consequence was animating a layout property on an element no
   page renders. If a collapsible shell is ever wanted, build it against the real
   surface rather than restoring this. */

.bb-topbar {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--deep-0) 80%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--text) 16%, transparent);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
}
.bb-bar { display: flex; align-items: center; gap: clamp(14px, 3vw, 44px); height: 68px; }
/* The wordmark. Heavy letterforms held inside two light cyan brackets: the
   brackets are the mark, so there is no logo file to fetch and it inherits the
   accent in both themes. Written in the markup as "Grounded Builds" and
   uppercased in CSS, so assistive tech reads a name rather than shouting.
   The brackets carry aria-hidden and no tracking of their own, which keeps them
   tight against the word instead of floating. */
.bb-mark {
  display: inline-flex; align-items: baseline; gap: 1px;
  font: 700 clamp(14px, 1.35vw, 18px)/1 var(--sans);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  white-space: nowrap;
  text-decoration: none; flex: none;
  color: var(--text);
}
.bb-mark-b { color: var(--sig); font-weight: 300; letter-spacing: 0; }
.bb-mark-sm { font-size: 13px; letter-spacing: 0.1em; }
/* Two words plus brackets is a wide lockup. Tighten before it wraps or starts
   pushing the nav around. */
@media (max-width: 560px) { .bb-mark { font-size: 13px; letter-spacing: 0.06em; } }
@media (max-width: 380px) { .bb-mark { font-size: 12px; letter-spacing: 0.03em; } }
.bb-bar-nav { display: flex; gap: clamp(16px, 2.4vw, 34px); margin-inline: auto; }
.bb-bar-nav a {
  font: 600 11px/1 var(--mono);
  letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--muted); text-decoration: none;
  padding: 7px 0;
  border-bottom: 1px solid transparent;
  transition: color var(--t-ui) ease, border-color var(--t-ui) ease;
}
.bb-bar-nav a:hover, .bb-bar-nav a[aria-current="page"] { color: var(--text); border-bottom-color: var(--sig); }
.bb-bar-end { display: flex; align-items: center; gap: 8px; margin-left: auto; }

/* ══ THEME SWITCH ══════════════════════════════════════════════════════════
   One base control, mounted by js/theme-switch.js. The sun and moon are stacked
   in the same box and cross-fade on a springy overshoot curve: the outgoing
   glyph drops and shrinks, the incoming one rises into place. Both live in the
   layer at all times, so nothing reflows and the swap is transform + opacity
   only.

   SQUARE, where the React original was `rounded-full`. Shape is a locked
   identity switch in this system, and an icon button is not one of the
   genuinely circular objects (`.bb-round`) the exception exists for. */
/* The declarative host. Without this it is a bare inline <span>, and dropping one into a flex
   toolbar (the platform topbar does exactly this) makes it a flex item that shrinks to 0x0 while
   the button inside still reports its own 36px. The switch renders, measures correctly, and is
   invisible. */
[data-theme-switch] {
  display: inline-flex;
  flex: none;
  align-items: center;
}

/* SIZE IS A TOKEN, not a fixed value, because this control's job is to sit in a row of that
   surface's own buttons and a single hard-coded size cannot match all of them: the marketing nav,
   the chat header, the admin topbar and the platform bar are 34px, /my/ is 38px, and the auth
   corner is standalone. Each stylesheet sets --bb-ts-size next to the control it must match; this
   is only the fallback. The glyph scales with it, so the proportion holds at every size. */
.bb-theme-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--bb-ts-size, 36px);
  height: var(--bb-ts-size, 36px);
  flex: none;
  padding: 0;
  border: 1px solid var(--line-hi);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  overflow: hidden;
  transition: border-color var(--t-ui) ease, color var(--t-ui) ease, background-color var(--t-ui) ease;
}
.bb-theme-switch:hover { border-color: var(--sig); color: var(--sig); background: color-mix(in srgb, var(--sig) 8%, transparent); }
.bb-theme-switch:active { translate: 0 1px; }

.bb-theme-switch > span {
  position: absolute;
  display: inline-flex;
  /* The React source shipped a spring overshoot curve. Swapped to the house
     --ease (exponential ease-out), which is the one curve every other motion on
     this platform uses, and a real object decelerates rather than springing past
     its mark. The swap still reads as a lift-and-settle because the glyphs
     travel 20px and cross-fade; it just does not overshoot. 260ms rather than
     the original 300ms, because without the overshoot tail the longer duration
     reads as lag. */
  transition:
    scale 260ms var(--ease),
    translate 260ms var(--ease),
    opacity 260ms var(--ease);
}
/* Half the control, so the glyph keeps its proportion at 30px and at 38px alike. */
.bb-ts-icon {
  display: block;
  width: calc(var(--bb-ts-size, 36px) * 0.5);
  height: calc(var(--bb-ts-size, 36px) * 0.5);
}

/* Resting state is "hidden below"; the active glyph is lifted to centre. */
.bb-theme-switch > span { scale: 0.5; translate: 0 20px; opacity: 0; }
.bb-theme-switch[data-theme="light"] .bb-ts-sun,
.bb-theme-switch[data-theme="dark"] .bb-ts-moon { scale: 1; translate: 0 0; opacity: 1; }

/* No JS, no data-theme: show one glyph rather than an empty box. */
.bb-theme-switch:not([data-theme]) .bb-ts-moon { scale: 1; translate: 0 0; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .bb-theme-switch > span { transition: none; }
}

/* ══ AVATAR / PEOPLE ═══════════════════════════════════════════════════════ */
.bb-avatar {
  position: relative;
  width: 36px; height: 36px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-hi);
  background: var(--surface2);
  color: var(--text);
  font: 700 12px/1 var(--mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  overflow: hidden;
}
.bb-avatar img { width: 100%; height: 100%; object-fit: cover; }
.bb-avatar-lg { width: 56px; height: 56px; font-size: 17px; }
.bb-avatar-sm { width: 26px; height: 26px; font-size: 9.5px; }
.bb-avatar[data-presence="online"]::after,
.bb-avatar[data-presence="away"]::after {
  content: "";
  position: absolute; right: -1px; bottom: -1px;
  width: 9px; height: 9px;
  border: 1px solid var(--surface);
}
.bb-avatar[data-presence="online"]::after { background: var(--ok); }
.bb-avatar[data-presence="away"]::after { background: var(--warn); }

.bb-people { display: flex; flex-direction: column; }
.bb-person {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.bb-person:last-child { border-bottom: 0; }
.bb-person-name { font-weight: 600; font-size: 14.5px; }
.bb-person-meta { font-size: 12.5px; color: var(--muted); }
.bb-person-end { margin-left: auto; display: flex; align-items: center; gap: 8px; }

/* ══ UPLOAD ════════════════════════════════════════════════════════════════ */
.bb-upload {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 34px 20px;
  border: 1px dashed var(--control-border);
  background: var(--slab);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--t-ui) ease, background-color var(--t-ui) ease;
}
.bb-upload:hover, .bb-upload[data-dragover="true"] {
  border-color: var(--sig);
  border-style: solid;
  background: color-mix(in srgb, var(--sig) 8%, transparent);
}
.bb-upload svg { width: 26px; height: 26px; color: var(--sig); }
.bb-upload-hint { font-size: 12.5px; color: var(--faint); }
.bb-file {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 13px;
  border: 1px solid var(--border);
  background: var(--surface2);
  font-size: 13.5px;
}
.bb-file + .bb-file { margin-top: 8px; }
.bb-file-size { margin-left: auto; font-family: var(--mono); font-size: 11.5px; color: var(--faint); }

/* ══ PROGRESS / METER ══════════════════════════════════════════════════════
   No filled background track standing in for content. The track is a hairline;
   the fill is the signal.

   The fill is a full-width bar squashed with scaleX rather than a narrow bar
   grown with `width`, so a progress update is a compositor job and never a
   relayout. `--value` is therefore a UNITLESS 0 to 1 fraction, not a
   percentage: `style="--value: .62"`. */
.bb-progress { height: 3px; background: color-mix(in srgb, var(--text) 14%, transparent); overflow: hidden; }
.bb-progress > i {
  display: block;
  height: 100%; width: 100%;
  background: var(--sig);
  transform-origin: left center;
  scale: var(--value, 0) 1;
  transition: scale 320ms var(--ease);
}
/* The `translate` property resolves its percentage against the UNSCALED box,
   so the sweep is written in full-track units while the bar reads as 34%. */
.bb-progress-indeterminate > i { scale: 0.34 1; animation: bb-slide 1.2s var(--ease) infinite; }
@keyframes bb-slide { from { translate: -40% 0; } to { translate: 105% 0; } }

.bb-meter { display: flex; align-items: baseline; gap: 10px; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.bb-meter b { font-size: clamp(22px, 2.4vw, 32px); font-weight: 300; letter-spacing: -0.03em; }
.bb-meter span { font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--faint); }

/* ══ LOADING / EMPTY ═══════════════════════════════════════════════════════ */
.bb-skeleton {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--text) 6%, transparent) 0%,
    color-mix(in srgb, var(--text) 13%, transparent) 50%,
    color-mix(in srgb, var(--text) 6%, transparent) 100%);
  background-size: 220% 100%;
  animation: bb-shimmer 1.5s linear infinite;
}
@keyframes bb-shimmer { to { background-position: -220% 0; } }
.bb-skeleton-line { height: 12px; }
.bb-skeleton-line + .bb-skeleton-line { margin-top: 9px; }

/* Thinking indicator. Three squares, not a spinner. */
.bb-dots { display: inline-flex; gap: 6px; align-items: center; }
.bb-dots i { width: 7px; height: 7px; background: var(--sig); opacity: 0.35; animation: bb-hop 1.05s ease-in-out infinite; }
.bb-dots i:nth-child(2) { animation-delay: 0.16s; }
.bb-dots i:nth-child(3) { animation-delay: 0.32s; }
@keyframes bb-hop { 0%, 100% { opacity: 0.3; translate: 0 0; } 45% { opacity: 1; translate: 0 -4px; } }

/* Empty states teach the interface. They never just say "nothing here". */
.bb-empty {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: clamp(40px, 7vw, 76px) 20px;
  text-align: center;
}
.bb-empty-mark {
  width: 54px; height: 62px;
  clip-path: var(--hex-clip);
  background: linear-gradient(158deg, var(--deep-3), var(--deep-1));
  border: 0;
}
.bb-empty h3 { font: 700 15px/1.3 var(--sans); margin: 0; }
.bb-empty p { max-width: 42ch; color: var(--muted); font-size: 14px; }

/* ══ FOOTER ════════════════════════════════════════════════════════════════ */
.bb-foot { padding-block: clamp(56px, 6vw, 84px) 32px; border-top: 1px solid var(--line); }
.bb-foot-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(20px, 3vw, 44px); }
.bb-foot-col h2 {
  font: 700 11px/1 var(--mono);
  letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--text);
  margin: 0 0 18px;
}
.bb-foot-col ul { margin: 0; padding: 0; list-style: none; }
.bb-foot-col li + li { margin-top: 11px; }
.bb-foot-col a {
  font-size: 14px; color: var(--muted);
  text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px;
  transition: color var(--t-ui) ease;
}
.bb-foot-col a:hover { color: var(--sig-text); }
.bb-foot-base {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 12px;
  margin-top: clamp(40px, 5vw, 60px);
  padding-top: 22px;
  border-top: 1px solid var(--line);
}
.bb-foot-tag {
  font: 500 10.5px/1 var(--mono);
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--faint);
}

/* ══ HERO CIRCUIT ══════════════════════════════════════════════════════════ */
.bb-circuit {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(90deg, color-mix(in srgb, var(--text) 6%, transparent) 0 1px, transparent 1px 88px),
    repeating-linear-gradient(0deg,  color-mix(in srgb, var(--text) 4%, transparent) 0 1px, transparent 1px 88px);
  /* Baked for the same reason as the document backdrop, and it cost more than
     the backdrop did: a 4-stop gradient MASK over the full hero is ~1.1M px
     re-rasterized on every render, and once the backdrop was fixed this one
     stood alone at 3.6s of 13.1s of Firefox render CPU (28%). Note it is not
     `ps_quad_gradient` in a profile — a mask is not a background, so it shows
     up as bare `commitLinearGradientFromStops` under `UpdateAndRender`.
     `--bb-circuit-mask` is black with the ramp in its alpha channel, which is
     what `mask-mode: match-source` (the default) reads, so it needs no
     `mask-mode` and works through `-webkit-mask-image` too. */
  -webkit-mask-image: var(--bb-circuit-mask);
  mask-image: var(--bb-circuit-mask);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.bb-circuit svg { width: 100%; height: 100%; display: block; }
.bb-trace polyline { fill: none; stroke: var(--sig); stroke-width: 1.1; opacity: 0.22; }
.bb-node rect { fill: var(--sig); opacity: 0.55; }
.bb-skyline rect { fill: var(--deep-2); opacity: 0.55; }

/* A `will-change: transform` was tried here, to give the circuit its own
   compositing surface so the pulse's repaints would stop invalidating the
   picture-cache tiles under the hero. It was measured and removed: the
   `mask-image` above already forces a render target, so the extra surface
   isolated nothing (tiles invalidated per render 9.5 -> 8.9) while adding
   ~31% more draw calls per render. Do not re-add it without a profile. */

/* One restrained ambient signal: three traces carry a pulse, 9s apart. It says
   "live infrastructure" and nothing else, and it is killed under reduced motion. */
.bb-trace-live polyline {
  opacity: 0.85;
  stroke-width: 1.6;
  stroke-dasharray: 130 1400;
  stroke-dashoffset: 1530;
  animation: bb-pulse 9s linear infinite;
}
.bb-trace-live polyline:nth-child(2) { animation-delay: 3s; }
.bb-trace-live polyline:nth-child(3) { animation-delay: 6s; }
@keyframes bb-pulse { to { stroke-dashoffset: 0; } }

/* ══ SCROLL REVEAL ═════════════════════════════════════════════════════════
   Opt-in: script adds `.bb-reveal-on` to <html> only when IntersectionObserver
   exists and motion is allowed. Without JS the page renders fully visible. */
.bb-reveal-on [data-reveal] {
  opacity: 0;
  translate: 0 16px;
  transition: opacity 620ms var(--ease), translate 620ms var(--ease);
  transition-delay: calc(var(--i, 0) * 65ms);
}
.bb-reveal-on [data-reveal].shown { opacity: 1; translate: 0 0; }
/* Hexes already carry a layout offset in --dx, so their reveal rides on --dy. */
.bb-reveal-on .bb-hex[data-reveal] { opacity: 0; --dy: 14px; translate: var(--dx) var(--dy); }
.bb-reveal-on .bb-hex[data-reveal].shown { opacity: 1; --dy: 0px; }

/* A NON-SCROLLING RENDERER MUST NOT SEE EMPTY BANDS.
   IntersectionObserver only fires for what enters the scrollport, so print, a
   full-page CDP capture and any headless renderer that never scrolls would
   otherwise photograph ~2000px of empty gradient between the hero and the
   footer — measured 23 of 29 [data-reveal] nodes still at opacity 0 through a
   full-page screenshot at 1440. Print is fixed here declaratively; the capture
   case is fixed in home.js, which drops .bb-reveal-on entirely under
   navigator.webdriver. Both are escape hatches, not a second animation. */
@media print {
  .bb-reveal-on [data-reveal],
  .bb-reveal-on .bb-hex[data-reveal] {
    opacity: 1 !important;
    translate: var(--dx, 0) 0 !important;
    transition: none !important;
  }
}

/* ══ RESPONSIVE ════════════════════════════════════════════════════════════ */
@media (max-width: 1180px) { .bb-wide-only { display: none; } }

@media (max-width: 900px) {
  .bb-bar-nav { display: none; }
  .bb-bar { height: 62px; }

  /* Below this width the interlock has no room to breathe: keep real
     hexagons, drop to an even 2-up grid with no overlap and no offset. */
  .bb-comb { width: 100%; gap: 18px; }
  .bb-hex { width: calc((100% - 18px) / 2); }
  .bb-hex:nth-child(-n+3), .bb-hex:nth-child(n+4) { --dx: 0px; margin-top: 0; }
  .bb-hex-body { width: 72%; }

  .bb-strip, .bb-strip-3 { grid-template-columns: repeat(2, 1fr); }
  .bb-strip-cell:nth-child(2n) { border-right: 0; }
  .bb-strip-cell:nth-child(-n+2) { border-bottom: 1px solid var(--line); }

  .bb-rail, .bb-rail-4 { grid-template-columns: 1fr; gap: 34px; padding-top: 0; }
  .bb-rail::before {
    top: 6px; bottom: 10px; left: 5px; right: auto;
    width: 1px; height: auto;
    background: linear-gradient(180deg, var(--line-hi), transparent);
  }
  .bb-stop { padding-left: 34px; }
  .bb-stop-node { top: 6px; left: 0; }
  .bb-stop p { max-width: 54ch; }

  .bb-foot-cols { grid-template-columns: repeat(2, 1fr); gap: 32px; }
}

@media (max-width: 640px) {
  .bb-msg { max-width: 94%; }
  .bb-btn { padding: 0 20px; letter-spacing: 0.18em; }
  .bb-toasts { left: 12px; right: 12px; bottom: 12px; }
  .bb-toast { min-width: 0; max-width: none; }
  .bb-dialog { width: calc(100vw - 24px); }
}

@media (max-width: 560px) {
  .bb-mid-only { display: none; }
  .bb-comb { gap: 14px; }
  .bb-hex { width: 100%; max-width: 320px; }
  .bb-hex-body { width: 74%; }
  .bb-hex-desc { font-size: 13px; }
  .bb-strip, .bb-strip-3, .bb-strip-2 { grid-template-columns: 1fr; }
  .bb-strip-cell { border-right: 0; border-bottom: 1px solid var(--line); }
  .bb-strip-cell:last-child { border-bottom: 0; }
}

@media (max-width: 430px) {
  .bb-foot-cols { grid-template-columns: 1fr; gap: 28px; }
  .bb-hex { max-width: 296px; }
  .bb-otp input { width: 38px; height: 48px; font-size: 17px; }
}

/* ══ REDUCED MOTION: nothing loops, nothing waits, nothing hides ═══════════ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .bb-reveal-on [data-reveal],
  .bb-reveal-on .bb-hex[data-reveal] { opacity: 1; translate: var(--dx, 0) 0; }
  .bb-trace-live polyline { stroke-dasharray: none; stroke-dashoffset: 0; opacity: 0.34; }
  .bb-caret, .bb-dot-live { display: revert; animation: none; }
  .bb-btn::before { display: none; }
}

/* ══ REDUCED TRANSPARENCY ══════════════════════════════════════════════════ */
@media (prefers-reduced-transparency: reduce) {
  .bb-topbar { background: var(--deep-0); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .bb-dialog::backdrop { backdrop-filter: none; }
  html:not([data-chrome="operate"]) body::before { display: none; }
}

/* ══ FORCED COLORS ═════════════════════════════════════════════════════════ */
@media (forced-colors: active) {
  .bb-btn, .bb-chip, .bb-panel, .bb-input, .bb-table-wrap { border-color: CanvasText; }
  .bb-hex-shell, .bb-hex-face, .bb-hex-scrim { display: none; }
  .bb-hex { border: 1px solid CanvasText; }
  html:not([data-chrome="operate"]) body::before { display: none; }
}
