/* Signature DISPLAY face — self-hosted Bricolage Grotesque (variable, latin subset).
   Used ONLY for big editorial type (h1/h2/.p-h/.logo); body/UI copy stays on --sans.
   WHY NOT Space Grotesk (the previous face): Anthropic's own design guidance names it, by name, as
   the typeface its models converge on — which made it the loudest remaining "an AI built this" tell
   on the page. Bricolage is a grotesque with actual quirk in the letterforms, it holds the two-line
   hero without wrapping, and no framework or model reaches for it by default. Owner picked it from
   a render of all four candidates on the real headline.
   swap = no invisible-text stall; the preload in <head> lands it before first paint. */
@font-face{
  font-family:"Bricolage Grotesque";
  src:url('assets/fonts/bricolage.woff2') format('woff2');
  font-weight:200 800;font-style:normal;font-display:swap;
}
:root{
  --bg:#070a12; --text:#e8edf6; --muted:#8a93a8; --line:#1a2233;
  --violet:#8b5cf6; --gold:#ffcc6e;
  /* CYAN IS RETIRED. It was a third hue with no job — it owned the eyebrows, the links and the focus
     rings for no reason, and neon-cyan-on-dark is itself a named "AI-generated" tell. The palette now
     has exactly two accents with clear jobs:
        --accent (violet) = the BRAND and every action  — CTAs, links, focus, the one headline word
        --gold            = the LIGHT in the scene      — the hero star, the hub nodes, the eyebrows
     The token name stays so the 32 downstream call-sites don't all have to churn. */
  --cyan:#c79bff;
  --accent:#c79bff;
  --eyebrow:#d3ae72;   /* the scene's light, dimmed for small type (7.4:1 on the near-black) */
  /* ONE flat CTA colour for every primary action (was a violet→cyan gradient on each button —
     the single loudest "generated landing page" cue on the whole surface). Solid, one hue.
     PURPLE by owner's call, to match the galaxy. Deliberately NOT the stock #8b5cf6 (Tailwind's
     indigo/violet default, the most-flagged "AI-generated" hue): this is #966ef2, sampled from the
     galaxy's OWN arm violet, so the brand colour is taken from the scene instead of a framework.
     Dark ink on it clears 5.1:1 contrast (WCAG AA). */
  --cta:#966ef2;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  /* display/text pairing: Bricolage headlines, system sans body. Fallback = --sans so the
     swap holds the same box (the metrics are close enough to skip size-adjust). */
  --display:"Bricolage Grotesque",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
  background:var(--bg);color:var(--text);font-family:var(--sans);line-height:1.5;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
/* The one accent word in a headline. Solid (never a gradient) — a light violet lifted from the
   galaxy's arms, bright enough to read at display size on the near-black (8.6:1). */
.grad{color:#c79bff}
/* Per-glyph spans for the hero's gravity well (src/gravitype.js). inline-block so a transform
   applies at all; will-change keeps the 15-odd glyphs on their own layer so bending the line
   never repaints the headline underneath it. */
.gt{display:inline-block;will-change:transform}
.muted{color:var(--muted)}

/* The 3D canvas sits fixed behind the scrolling copy. */
#scene{position:fixed;inset:0;width:100%;height:100%;z-index:0;display:block}
/* "Become the light" ENTRY push-in — a whisper dolly on the RENDERED brain via a CSS scale on the
   canvas ONLY (the tuned camera rig is untouched; the #cursor-fx overlay + JS-positioned pills stay
   at true positions). The class is added/removed by main.js on a real settled hub arrival, and is
   never present when pinned/reduced-motion → the beat goldens are untouched. */
@keyframes sceneBecome{0%{transform:scale(1)}38%{transform:scale(1.016)}100%{transform:scale(1)}}
body.becoming #scene{animation:sceneBecome 1.15s cubic-bezier(.22,.61,.36,1)}

/* Violet "dark-matter" cursor — a full-viewport canvas drawing a drifting violet particle
   wisp + faint connection filaments to nearby service nodes (see main.js drawDarkMatter). */
#cursor-fx{
  /* z-index sits ABOVE the recruit modal (.rc-scrim, 300): the canvas is pointer-events:none, and
     at 60 the dark-matter wisp was painted UNDER the scrim — so the cursor visibly died the moment
     the "Work with me" card opened. It now threads over the card like everywhere else.
     1000000: the rlt-chat widget (2026-08-26) mounts at 999998/999999 and the page is cursor:none
     everywhere — under the owner's cursor law the creature must ride the widget too, or the
     visitor has no pointer over it. The canvas stays pointer-events:none, so clicks pass through. */
  position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:1000000;
  /* normal blend (not screen) so the dark-matter VOID CORE can actually darken the scene
     inside the purple ring; the canvas is otherwise transparent. */
  mix-blend-mode:normal;opacity:0;transition:opacity .5s ease;
}
body.pointer-active #cursor-fx{opacity:1}

/* T5 teleport-blink cover — the rare service→service transport: the view collapses into dark
   matter (fast fade IN), the camera cut + panel refill happen fully under it, then it lifts
   (slower fade OUT). Sits above the panel (7) and below the cursor-fx canvas (60) so the
   dark-matter blob visibly persists through its own blink. */
#blink{position:fixed;inset:0;z-index:30;pointer-events:none;opacity:0;
  background:radial-gradient(circle at 50% 50%,#0b0618 0%,#020108 72%);
  transition:opacity .3s ease}
#blink.on{opacity:1;transition:opacity .16s ease}

/* Scroll-progress hairline — a whisper-quiet "chapter mark" on the right edge. The rail is
   near-invisible; a warm-gold fill grows from the top to track journey progress (currentP,
   driven each frame in animate()). Fades out at the hub (opacity set in JS). Now a <button>:
   CLICK the rail to jump back to the top / replay. Widened hit-area (padding), 2px visual rail. */
#scrollcue{position:fixed;top:0;right:0;width:2px;height:100%;z-index:6;pointer-events:none;
  padding:0;border:0;appearance:none;background:rgba(255,255,255,.05);opacity:.6;
  transition:opacity .6s ease}
#scrollcue i{display:block;width:100%;height:0;transform-origin:top;
  background:linear-gradient(180deg,rgba(255,236,196,.55),rgba(255,204,110,.32));
  box-shadow:0 0 6px rgba(255,204,110,.35)}

/* Return / replay affordance — a whisper-quiet bottom-left control. Hidden by default and over
   the dark galaxy hero; fades in ONLY once body.scrolled lands (past the hero, set in readScroll,
   never in pinned/reduced-motion) so it can't clutter the 4 pinned beat captures. Apple-dark. */
/* R7 P4 (owner: the "Scroll ↓" cue and "↑ Top" button showed TOGETHER at journey start): #totop is
   now the ONE bottom-left slot. It carries two stacked labels — near the top it shows "Scroll ↓"
   (glides down to the hub), and once past 0.6vh it swaps to "↑ Top" (returns). One element, one state
   at a time, so they can NEVER both appear. Hidden on the pinned beat captures (no body.live) and
   while the loader is up (body.booted); the branch-open hide is unchanged. */
#totop{position:fixed;left:clamp(14px,3vw,26px);bottom:clamp(16px,3.5vh,30px);z-index:6;
  display:inline-grid;place-items:center;cursor:pointer;
  font:600 12.5px var(--sans);letter-spacing:.3px;color:#cdd6ea;
  padding:8px 13px;border-radius:100px;border:1px solid rgba(255,255,255,.10);
  background:rgba(9,13,21,.55);backdrop-filter:blur(10px) saturate(1.05);
  -webkit-backdrop-filter:blur(10px) saturate(1.05);text-shadow:0 1px 8px rgba(3,6,12,.7);
  opacity:0;transform:translateY(6px);pointer-events:none;visibility:hidden;
  transition:opacity .5s ease,transform .5s cubic-bezier(.2,.7,.2,1),border-color .25s,box-shadow .25s,visibility .5s}
/* the two labels share one grid cell → the pill sizes to the wider of them (no width jump on swap).
   The label swap is INSTANT (no crossfade) so the two texts are never superimposed — one at a time. */
#totop .tt-scroll,#totop .tt-top{grid-area:1/1;display:inline-flex;align-items:center;gap:6px;
  white-space:nowrap;pointer-events:none}
#totop .ttw{color:var(--muted)}
#totop .tt-top{opacity:0}                                  /* default = near the top: show Scroll, hide Top */
body.scrolled #totop .tt-scroll{opacity:0}
body.scrolled #totop .tt-top{opacity:1}
/* visible once the real hero has arrived (body.live, not through the loader) OR after any scroll.
   Scroll-state gets a gentle bob to invite; Top-state is a calm .72 return control. */
body.live:not(.booted) #totop{opacity:.9;transform:none;pointer-events:auto;visibility:visible}
body.live:not(.booted):not(.scrolled) #totop{animation:bobY 2.6s ease-in-out infinite}
body.scrolled #totop{opacity:.72;transform:none;animation:none}
#totop:hover{opacity:1;border-color:rgba(255,204,110,.55);box-shadow:0 0 18px rgba(255,196,100,.28);animation:none}
@keyframes bobY{0%,100%{transform:translateY(0)}50%{transform:translateY(4px)}}
/* a branch panel is open → hide the control so it never sits under the side panel / bottom-sheet
   (the panel's own "← Back to the brain" is the return path there). */
body.in-branch #totop{opacity:0;pointer-events:none;visibility:hidden}

/* remove the native mouse cursor entirely — the dark-matter ring IS the cursor */
*, *::before, *::after{cursor:none !important}
/* first-impression veil — fades up from near-black on load (real users only; see main.js) */
/* R11 beat A: 1.3s -> .85s. The veil is armed at bootPhase2 and the loader opens 900ms later, so at
   1.3s it still had ~14% of its near-black left AT the cross-dissolve — a third darkener stacked on
   the measured handoff trough. Its whole fade happens UNDER the opaque loader either way (nothing
   visible is lost); at .85s it is simply clear before the seam. */
#intro-veil{position:fixed;inset:0;z-index:80;pointer-events:none;
  background:radial-gradient(circle at 50% 46%, #06040e 0%, #000 72%);
  opacity:1;transition:opacity .85s ease}
body.intro-ready #intro-veil{opacity:0}

/* ARRIVAL hero entrance — the ONE loading experience is the rotating galaxy loader (#loader, inline in
   index.html); there is no separate wordmark card. The galaxy chapter's copy stays HELD BACK (invisible,
   offset) while the loader is up (body.booted), so it never shows THROUGH the loader as it cross-dissolves
   into the real galaxy. Once the galaxy is exposing, it ASSEMBLES with a clear, gentle stagger:
   pill → h1 → sub each rise + fade in ~120ms apart. Only the galaxy chapter staggers on the
   first arrival; later chapters keep the plain cross-fade. pinned/reduced-motion never get these classes →
   settled state instantly, so the 4 pinned beat captures are untouched. */
/* held-back state: hide INSTANTLY (no transition) so the copy never ghost-fades OUT behind the loader
   at load — it's simply absent until it assembles IN on .arrive-copy below. */
body.booted .chapter[data-stage=galaxy].is-visible > *{
  opacity:0;transform:translateY(18px);transition:none}
/* assemble IN — the rise+fade transition lives here so it only plays on the reveal, per-child staggered. */
body.arrive-copy .chapter[data-stage=galaxy].is-visible > *{opacity:1;transform:none;
  transition:opacity .7s ease,transform .7s cubic-bezier(.2,.7,.2,1)}
body.arrive-copy .chapter[data-stage=galaxy].is-visible > :nth-child(1){transition-delay:0s}
body.arrive-copy .chapter[data-stage=galaxy].is-visible > :nth-child(2){transition-delay:.12s}
body.arrive-copy .chapter[data-stage=galaxy].is-visible > :nth-child(3){transition-delay:.24s}

/* ARRIVAL, part 2 — the top bar (R11 beat A). The chapter copy had a considered entrance; the top
   bar had NONE: it is painted from frame #1 underneath the loader and was simply UNCOVERED by the
   fade, at full final strength. Measured on the throttled-load captures, that put a solid violet
   "Work with me" button as the brightest object on a near-black frame at the exact moment the
   galaxy was arriving — the eye went to a button instead of to the scene. Now the controls are held
   back with the copy and drop in AFTER the loader has fully cleared (delay .42s > the .95s fade's
   useful tail), wordmark first, then the three controls 60ms apart.
   Deliberately a keyframe with `backwards` fill, not a transition: (a) it runs on the compositor,
   and the main thread is genuinely busy in this exact window; (b) `backwards` (never `forwards`)
   means the elements return to their own untouched resting style when it ends, so every hover
   transition below is completely unaffected afterwards.
   It animates the .nav-cta GROUP, not its three children: the controls are one cluster (they should
   arrive as one), and per-child rules would out-specify `body.live #work-nav{animation:ctaPulse}`
   and `#work-nav:hover{animation:none}` and silently kill the primary CTA's idle pulse.
   pinned + reduced-motion never receive .booted/.arrive-copy → the bar is simply there, and the
   four pinned beat captures are untouched. */
/* held-back state: INSTANT (the wordmark carries its own .2s opacity transition for hover, which
   would otherwise ghost it out over 200ms) — same rule the chapter copy uses above. */
body.booted #topbar .logo,
body.booted #topbar .nav-cta{opacity:0;transition:none}
body.arrive-copy #topbar .logo{animation:tbIn .3s cubic-bezier(.2,.7,.2,1) .42s backwards}
body.arrive-copy #topbar .nav-cta{animation:tbIn .3s cubic-bezier(.2,.7,.2,1) .52s backwards}
@keyframes tbIn{from{opacity:0;transform:translateY(-9px)}to{opacity:1;transform:none}}

/* Top bar */
#topbar{
  position:fixed;top:0;left:0;right:0;z-index:6;
  display:flex;justify-content:space-between;align-items:center;
  padding:20px clamp(18px,4vw,40px);
  /* frost is OFF by default (invisible over the dark opening galaxy). It only lifts in
     once body.scrolled lands — set from the scroll handler past the hero, and never in
     pinned/reduced-motion — so the 4 pinned beat captures stay byte-identical. */
  background:transparent;border-bottom:1px solid transparent;
  transition:background .5s ease,border-color .5s ease,backdrop-filter .5s ease;
}
/* Frost-on-scroll: a subtle darker translucent bg + blur + faint bottom hairline for
   legibility once the topbar sits over the busy, bright hub. */
/* Owner 2026-07-20: the frost read as a solid black SLAB sitting on the galaxy ("that high part
   has a shape — make it see through, the background is galaxy"). The bar now stays FULLY
   transparent at every scroll depth; legibility comes from each control's own chrome (the CTAs
   and the sound toggle carry their own backgrounds) plus a stronger halo on the wordmark, so
   nothing paints a full-width plate over the scene. */
body.scrolled #topbar{
  background:transparent;
  border-bottom-color:transparent;
  backdrop-filter:none;
  -webkit-backdrop-filter:none;
}
/* the wordmark is the only bare-text element up there — carry its contrast locally instead */
body.scrolled .logo{text-shadow:0 1px 3px rgba(3,6,12,.95),0 0 14px rgba(3,6,12,.85)}
/* The wordmark is the "home" control: it returns you to the galaxy (and replays it from the top
   if you're already there). Button reset — it inherits the bar's type, not the UA's. */
/* inline-flex + the negative margin keeps the wordmark optically where it was while giving it a
   real >=24px tap target (WCAG 2.2 SC 2.5.8 — it was a 16px-tall text button on phones). */
/* gap, not a space character: inline-flex makes "RealtyLT" and <span>AI</span> separate flex items
   and collapses the whitespace text node between them ("RealtyLTAI"). */
.logo{display:inline-flex;align-items:center;gap:.3em;min-height:26px;
  font-family:var(--display);font-size:16px;font-weight:700;letter-spacing:0;
  background:none;border:0;padding:2px 4px;margin-left:-4px;color:var(--text);cursor:pointer;
  text-shadow:0 1px 12px rgba(3,6,12,.8);transition:opacity .2s ease}
.logo:hover{opacity:.82}
.logo:focus-visible{outline:2px solid rgba(199,155,255,.8);outline-offset:4px;border-radius:4px}
/* brand-mark glow: a faint violet halo so the "AI" wordmark reads as the mark (restrained). */
.logo .grad{text-shadow:0 0 14px rgba(199,155,255,.35)}
.navbtn{font-size:14px;font-weight:600;text-decoration:none;color:#06121a;
  padding:9px 16px;border-radius:8px;background:var(--cta);
  transition:transform .18s cubic-bezier(.2,.7,.2,1),box-shadow .25s ease,border-color .25s ease,background .25s ease}
.nav-cta{display:flex;gap:10px;align-items:center}
button.navbtn{cursor:pointer;border:0;font-family:inherit}
.navbtn.ghost{color:var(--text);background:none;border:1px solid var(--line)}
/* Primary conversion CTA ("Work with me", #work-nav) — THE primary action (owner 2026-07-16:
   this is the recruiting page, so the recruit CTA takes the purple fill; "See it live" goes
   ghost). A faint STANDING gold ring makes it read as dominant vs the ghost (static → shown to
   everyone, incl. captures). The subtle idle PULSE that draws the eye runs only on the real-user
   path (body.live, set in main.js on the non-pinned boot) so the pinned #p= beat captures stay
   frame-stable. Touch / reduced-motion drop the pulse below. (reconcile 2026-07-01: win-line
   ring+pulse UNIONED with the mac-line hover lift + real pressed :active micro-interactions.) */
#work-nav{box-shadow:0 0 0 1px rgba(255,204,110,.28)}
body.live #work-nav{animation:ctaPulse 3.6s ease-in-out infinite}
@keyframes ctaPulse{
  0%,100%{box-shadow:0 0 0 1px rgba(255,204,110,.28),0 0 0 rgba(255,196,100,0)}
  50%{box-shadow:0 0 0 1px rgba(255,204,110,.42),0 0 16px rgba(255,196,100,.32)}
}
#work-nav:hover{transform:translateY(-2px) scale(1.03);animation:none;
  box-shadow:0 0 0 1px rgba(255,204,110,.5),0 6px 22px rgba(255,196,100,.5),0 0 30px rgba(139,92,246,.26)}
#work-nav:active{transform:translateY(0) scale(.98);box-shadow:0 0 14px rgba(255,196,100,.42);transition-duration:.09s}
/* Ghost "See it live" — rests a touch quieter (primary dominates), wakes with a quiet gold-tinted lift. */
.navbtn.ghost{opacity:.92}
.navbtn.ghost:hover{opacity:1;transform:translateY(-1px);border-color:rgba(255,204,110,.45);background:rgba(255,204,110,.06);box-shadow:none}
.navbtn.ghost:active{opacity:1;transform:translateY(0);background:rgba(255,204,110,.10);transition-duration:.09s}
/* Ambient-sound toggle — a quiet icon-only utility, restrained vs the two CTAs. Square ghost
   chip; muted grey at rest, warms on hover. aria-pressed reflects state: on = gold sound-waves,
   off (muted) = dim + a slash. Icon-only so it never crowds/wraps the topbar. */
.navbtn.snd{width:38px;height:38px;padding:0;display:inline-flex;align-items:center;justify-content:center;
  color:var(--muted);background:none;border:1px solid var(--line);border-radius:9px;
  transition:color .2s ease,border-color .2s ease,background .2s ease,transform .18s cubic-bezier(.2,.7,.2,1)}
.navbtn.snd svg{width:19px;height:19px;overflow:visible}
.navbtn.snd:hover{color:var(--text);border-color:rgba(255,204,110,.4);background:rgba(255,204,110,.05);transform:translateY(-1px)}
.navbtn.snd:active{transform:translateY(0);background:rgba(255,204,110,.09);transition-duration:.09s}
.navbtn.snd .snd-slash{display:none}
.navbtn.snd[aria-pressed="true"]{color:var(--text)}
.navbtn.snd[aria-pressed="true"] .snd-wave{stroke:var(--gold);opacity:.92}   /* "alive" — playing */
.navbtn.snd[aria-pressed="false"] .snd-wave{display:none}                     /* muted → no waves… */
.navbtn.snd[aria-pressed="false"] .snd-slash{display:inline}                  /* …a slash instead */

/* Scroll track — pure spacer; height set in JS from stage count. */
#scroll-track{position:relative;width:1px;z-index:1;pointer-events:none}

/* Chapters: fixed, centered-ish, cross-fade in/out. */
#chapters{position:fixed;inset:0;z-index:4;pointer-events:none}
.chapter{
  /* R7 P3 (owner: "pc could use lowering text down too"): a modest ~30px drop of the desktop copy
     block — bottom clamp 14vh/140 -> 10vh/110 — so it reads a touch lower without gluing to the edge. */
  position:absolute;left:clamp(18px,5vw,72px);bottom:clamp(52px,10vh,110px);
  max-width:min(560px,86vw);
  /* filmic cross-fade: a hair longer + a soft ease-in-out curve + a faint blur-IN that resolves to
     crisp, so copy "focuses in" rather than hard-swapping. SETTLED (.is-visible) = opacity 1 /
     transform none / blur 0, so a pinned #p= capture is unchanged — only the in/out FEEL changes. */
  opacity:0;transform:translateY(18px);filter:blur(3px);
  /* R11 C-1 — the EXIT is now FASTER than the entrance (.42s vs .72s). This rule is the OUT state,
     so its timings are the ones that play when a chapter LEAVES; the .is-visible rule below carries
     the owner-tuned .72s entrance unchanged. WHY: the R9 dip gate releases the incoming chapter the
     moment the outgoing computes ≤0.22 opacity, so the outgoing's REMAINING fade is exactly the
     window in which two headlines are on screen together — and they sit at the same left/bottom
     anchor, so they interleave glyph-on-glyph. Measured on a real scroll (agent/_r11c_scroll.mjs):
     both chapters over 0.06 for ~160ms, crossing at ~0.10/0.10 — inside the R9 rule, but 10% white
     on near-black is still legible, so the crossing read as a double exposure. A faster exit reaches
     the gate sooner (so the incoming is not held as long) AND spends less time on the way down.
     Enter/exit asymmetry is the standing rule for this kind of pair — emil "exit faster than enter",
     apple §7 "mirror the path, not the duration". Blur/transform VALUES are untouched, so the
     entrance is byte-identical; a settled chapter is opacity 1 / blur 0 either way, so the four
     pinned beat captures cannot see this. */
  transition:opacity .42s ease,transform .42s cubic-bezier(.22,.61,.36,1),filter .42s ease;
  /* R18: box/scrim removed → text sits on the galaxy's black. Keep the ORIGINAL soft, wide glow
     (a tight dark shadow muddies the thin gradient-clipped "at cosmic scale." glyphs); a soft glow
     lifts legibility on black + any stray star without a container. */
  text-shadow:0 2px 26px rgba(3,6,12,.92);
}
.chapter.is-visible{opacity:1;transform:none;filter:blur(0);
  transition:opacity .72s ease,transform .72s cubic-bezier(.22,.61,.36,1),filter .72s ease}
/* Galaxy-hero legibility scrim — the OPENING headline sits over the BRIGHT warm galaxy core, where
   the gold half of the .grad line ("at cosmic scale.") muddies out. A soft, edgeless radial darkening
   sits BEHIND just the galaxy copy block (z-index:-1, kept inside the chapter's own stacking context
   via its blur() filter, so it's above the 3D canvas but under the text). It's a cinematic lower-third
   glow, not a box: darkest under the copy, fading to nothing well before any edge shows — lifting BOTH
   the white line and the gradient line to consistent contrast. Only the galaxy chapter needs it (the
   later chapters sit over the dark brain field), so it's scoped by data-stage. */
/* R18 (owner 2026-07-06: "no box behind the hero text — use the galaxy's black background"):
   the legibility scrim behind the galaxy hero copy is REMOVED. Since the zoom-out moved the
   galaxy off the left, the copy now sits over true black, so the per-letter text-shadow (.chapter)
   carries legibility with no box. */
/* Later-chapter legibility scrims REMOVED (owner 2026-07-09: the radial still read as a dark BOX
   behind "The galaxy becomes one brain." — "just plain text, the galaxy is the background").
   The per-letter .chapter text-shadow carries legibility on every chapter, same as the galaxy hero. */
/* INTERIOR narrative line (audit #6) — mirrors a .chapter's placement + filmic cross-fade, but is
   driven by the dwell-zoom (going INSIDE the hub) rather than a scroll window, so it hands off from the
   brain headline as the camera dollies in. opacity 0 at rest → the pinned #p=1.0 hub beat is untouched. */
#interiorline{
  position:absolute;left:clamp(18px,5vw,72px);bottom:clamp(64px,14vh,140px);
  max-width:min(560px,86vw);pointer-events:none;
  opacity:0;transform:translateY(18px);filter:blur(3px);
  transition:opacity .72s ease,transform .72s cubic-bezier(.22,.61,.36,1),filter .72s ease;
  text-shadow:0 2px 24px rgba(3,6,12,.9);
}
#interiorline.is-in{opacity:1;transform:none;filter:blur(0)}
/* R18: pill chip box REMOVED (owner: just text on the galaxy's black) — no background, border or
   blur; the label sits directly on the backdrop. */
.pill{display:inline-flex;align-items:center;font-size:13px;color:#cdd6ea;
  margin-bottom:22px}
.eyebrow{font-size:12px;letter-spacing:2px;text-transform:uppercase;color:var(--eyebrow);margin-bottom:14px}
/* DISPLAY headlines — Bricolage Grotesque. It sets a touch tighter than Space Grotesk did, so the
   tracking pull-in eases off (-2.4 → -1.8) or the big clamp starts colliding its own counters.
   Box footprint unchanged (same clamp sizes), weight 700 for authority. */
h1{font-family:var(--display);font-size:clamp(40px,7vw,68px);line-height:1.02;letter-spacing:-1.8px;font-weight:700;margin-bottom:20px}
h2{font-family:var(--display);font-size:clamp(32px,5.4vw,52px);line-height:1.04;letter-spacing:-1.2px;font-weight:700;margin-bottom:16px}
.sub{font-size:clamp(15px,2.2vw,19px);color:#aab4c8;max-width:30ch}
/* R7 P4: the in-copy .scroll-hint was retired — its "Scroll ↓" cue now lives in the unified #totop
   bottom-left slot (see the #totop rules above), so the hero copy ends on its .sub line. */

/* Interactive hub — each service is a glowing hub-STAR that sits on the brain surface, a thin
   connector thread, and a clean minimal NAME that splays outward from the node (placed by JS). The
   heavy floating chip is gone: node + connector + light label read as an annotated part of the
   neural network, while every name stays legible at a glance. */
#hub{position:fixed;inset:0;z-index:5;pointer-events:none;opacity:0;transition:opacity .6s ease}
#hub.is-on{opacity:1}

/* The button is a zero-size ANCHOR POINT centred on the node's 3D screen position (updateHub writes
   its transform/opacity every frame). Everything visible is an absolutely-positioned child that
   overflows this point — so the name can float clear of the node without moving the click target. */
.cluster{position:fixed;top:0;left:0;width:0;height:0;pointer-events:auto;cursor:pointer;
  border:0;background:none;padding:0;margin:0;color:#dbe3f2;
  transition:transform .06s linear,opacity .3s ease}
.cluster.hot{z-index:2}
/* the LIVE service ("Assistant · Live") is the primary demo — raise it so its name always wins
   an overlap against a neighbouring node's label (on narrow/portrait screens the 9 nodes crowd
   and the labels can collide; the hottest/live one must stay fully readable on top). */
.cluster.live{z-index:3}

/* creveal — the conflict-free reveal channel: the node+connector+name MORPH in (fade + scale up
   FROM the node) as the brain forms, staggered per node by --i. updateHub only ever writes the OUTER
   button's transform, so this child's transition is never fought. Settles to opacity 1 / no transform
   = the exact resting look, so a #p= capture taken after the settle wait is unaffected. Total ≈0.98s
   (last of 9: 8×.045 + .62) — under verify's ≥1000ms settle wait, so a settled hub shows all at rest. */
.creveal{position:absolute;left:0;top:0;width:0;height:0;transform-origin:0 0;will-change:opacity,transform}
#hub .creveal{opacity:0;transform:scale(.84)}
#hub.is-on .creveal{opacity:1;transform:none;
  transition:opacity .5s ease,transform .62s cubic-bezier(.2,.7,.2,1);
  transition-delay:calc(var(--i, 0) * .045s)}
#morehub .creveal{opacity:1;transform:none}

/* Hub-STAR — a small, bright, glowing node that sits ON the brain surface (over its 3D core).
   Restraint is the point: a warm-gold star, not a chip. */
.chub{position:absolute;left:0;top:0;width:8px;height:8px;transform:translate(-50%,-50%);border-radius:50%;
  background:radial-gradient(circle at 42% 36%,#fff7e6 0%,#ffcf85 46%,#c98a34 100%);
  box-shadow:0 0 9px 1px rgba(255,193,100,.5),0 0 3px rgba(255,246,214,.75);
  transition:box-shadow .25s ease,transform .13s cubic-bezier(.23,1,.32,1)}

/* Connector — a whisper-thin thread from the node out to its name (fades toward the label). */
.cline{position:absolute;top:50%;left:-15px;width:15px;height:1px;transform:translateY(-50%);
  background:linear-gradient(90deg,rgba(255,207,120,.6),rgba(255,204,110,.16));
  transition:background .25s ease}

/* Name group — offset OUTWARD from the node over the barest legibility veil (no bordered chip).
   .side-l mirrors everything for nodes on the brain's left so the names splay away from centre. */
.clabelwrap{position:absolute;left:0;top:0;transform:translate(15px,-50%);
  display:flex;align-items:center;white-space:nowrap}
.cluster.side-l .clabelwrap{transform:translate(calc(-100% - 15px),-50%)}
.cluster.side-l .cline{left:auto;right:-15px;
  background:linear-gradient(270deg,rgba(255,207,120,.6),rgba(255,204,110,.16))}
/* the NAME reads at a glance over the busy brain via a tight dark GLYPH-HALO (text-shadow), not a
   heavy chip — the veil stays a whisper so the label never reads as floating UI. A hairline warm
   border is the RESTING "this is a button" cue: barely there (so it never becomes a loud chip or
   buries the brain), but enough that every name reads as tappable, not a passive caption. */
.cinner{display:inline-flex;align-items:center;gap:7px;padding:3px 9px;border-radius:7px;
  font:500 13px/1.05 var(--sans);letter-spacing:.2px;color:#e0e7f3;
  /* veil lifted .32→.46 + blur 3→5 so ALL nine names read crisply even over the brightest star
     clusters (Assistant / Lead-gen / Voice agents), while staying a whisper — a legibility backing,
     not a loud UI chip that would bury the brain form. */
  background:rgba(7,9,15,.46);backdrop-filter:blur(5px) saturate(1.05);-webkit-backdrop-filter:blur(5px) saturate(1.05);
  border:1px solid rgba(255,214,150,.16);
  text-shadow:0 1px 2px rgba(0,0,0,.95),0 0 7px rgba(3,6,12,.85),0 0 14px rgba(3,6,12,.6);
  box-shadow:0 1px 10px rgba(3,6,12,.4);
  transition:color .25s ease,background .25s ease,box-shadow .25s ease,border-color .25s ease,
             transform .13s cubic-bezier(.23,1,.32,1)}

/* The primary demo (the assistant) still leads the eye — but with the SAME gold star as every other
   system. Its name simply sits a touch fuller and brighter; no status dot, no colour-coded badge. */
.cluster.live .chub{width:9px;height:9px}
.cluster.live .cinner{color:#f1f5fc;font-weight:600;background:rgba(12,13,12,.54);
  box-shadow:0 1px 12px rgba(3,6,12,.5),0 0 12px rgba(255,196,100,.12)}

/* PROXIMITY BECKON (approach) — JS sets .near on the SINGLE nearest node as the cursor comes within
   NEAR_R (a wide well, well before the tight HOVER_R contact lift). The armed node then reads as
   obviously clickable: its pill warms to a bright chip, its connector brightens, and its star "shivers"
   (a soft glow-buzz). It's the step BETWEEN rest and contact — .hot (contact) and .live (green) still
   win. Cursor-driven only (canMagnet gates it), so mobile/reduced-motion never arm it. */
@keyframes nodearm{
  0%,100%{box-shadow:0 0 11px 2px rgba(255,201,120,.55),0 0 4px #fff3d8}
  50%{box-shadow:0 0 16px 3px rgba(255,214,150,.82),0 0 6px #fff}}
.cluster.near .cinner{color:#f4f7fc;background:rgba(15,12,9,.46);border-color:rgba(255,214,150,.34);
  box-shadow:0 1px 12px rgba(3,6,12,.48),0 0 13px rgba(255,196,100,.16)}
.cluster.near:not(.hot) .chub{animation:nodearm 1.1s ease-in-out infinite}
.cluster.near .cline{background:linear-gradient(90deg,rgba(255,214,150,.72),rgba(255,204,110,.24))}
.cluster.near.side-l .cline{background:linear-gradient(270deg,rgba(255,214,150,.72),rgba(255,204,110,.24))}

/* Hover / magnetic-hot / selected — the NODE flares and the name brightens (no chip chrome). JS
   adds .hot when the cursor nears a node's on-screen anchor; the lift/nudge live in the JS transform. */
.cluster:hover .chub,.cluster.hot .chub{box-shadow:0 0 15px 3px rgba(255,201,120,.7),0 0 5px #fff3d8}
.cluster:hover .cinner,.cluster.hot .cinner{color:#fff;background:rgba(17,13,9,.5);border-color:rgba(255,214,150,.55);
  box-shadow:0 1px 14px rgba(3,6,12,.5),0 0 16px rgba(255,196,100,.22)}
.cluster:hover .cline,.cluster.hot .cline{background:linear-gradient(90deg,rgba(255,214,150,.78),rgba(255,204,110,.3))}
.cluster:hover.side-l .cline,.cluster.hot.side-l .cline{background:linear-gradient(270deg,rgba(255,214,150,.78),rgba(255,204,110,.3))}
.cluster.sel .chub{box-shadow:0 0 18px 4px rgba(255,201,120,.85),0 0 6px #fff}
.cluster.sel .cinner{color:#fff;background:rgba(19,14,9,.52);box-shadow:0 0 18px rgba(255,196,100,.3)}

/* PRESS (R11 E-2). Before this, a hub node's only press feedback was the `.chub` box-shadow below —
   the `.cinner` NAME, which is both the thing the eye is on and the real tap target, had no `:active`
   rule at all (the in-page sampler read `transform:none` right through a held press). Both skills are
   explicit: apple §1 "respond on pointer-down, not on release", emil "buttons must feel responsive to
   press… scale(0.97) on :active". The press CANNOT live on `.cluster` (updateHub writes that element's
   transform every frame) or on `.creveal` (its transform is the one-time reveal channel, and its
   transition is .62s) — so it rides the two children, which own no per-frame state. The star pushes
   in harder than the name because it is the smaller object: 8px scaled .88 moves ~1px, 13px type
   scaled .965 stays legible and never re-rasterises visibly. 130ms ease-out, per emil's 100-160ms
   press band. `.chub`'s brighter :active glow stays exactly as it was. */
.cluster:active .cinner,.cluster.pressing .cinner{transform:scale(.965)}
.cluster:active .chub,.cluster.pressing .chub{box-shadow:0 0 12px 2px rgba(255,214,150,.9);transform:translate(-50%,-50%) scale(.88)}

/* ARM (R11 E-2). The RESTING transition above is .25s `ease`, which starts slow — fine for the
   release, wrong for the arrival: a hover is the interaction a visitor triggers dozens of times per
   visit (emil's frequency table says reduce it), and the node should acknowledge the cursor at once.
   Overriding only duration + timing keeps the base property list, so this is asymmetric by
   construction: 140ms hard ease-out IN, the unchanged 250ms `ease` OUT (the cursor has left; there is
   nothing to acknowledge). */
.cluster.near .cinner,.cluster.near .cline,.cluster.near .chub,
.cluster:hover .cinner,.cluster:hover .cline,.cluster:hover .chub,
.cluster.hot .cinner,.cluster.hot .cline,.cluster.hot .chub,
.cluster.pressing .cinner,.cluster.pressing .chub{
  transition-duration:.14s;transition-timing-function:cubic-bezier(.23,1,.32,1)}

/* Clicked-node flare — a brief tactile pop on the node the moment a service opens. */
@keyframes nodeflare{
  0%{box-shadow:0 0 18px 4px rgba(255,201,120,.85),0 0 6px #fff}
  30%{box-shadow:0 0 27px 7px rgba(255,214,150,.98),0 0 10px #fff}
  100%{box-shadow:0 0 18px 4px rgba(255,201,120,.85),0 0 6px #fff}}
.cluster.sel.flare .chub{animation:nodeflare .25s ease-out}
/* "Become the light" ENTRY beckon — the node glows as the core's outward pulse reaches it. */
@keyframes nodebeckon{
  0%{box-shadow:0 0 9px 1px rgba(255,193,100,.5),0 0 3px rgba(255,246,214,.75)}
  35%{box-shadow:0 0 22px 5px rgba(255,214,150,.92),0 0 8px rgba(255,246,214,.7)}
  100%{box-shadow:0 0 9px 1px rgba(255,193,100,.5),0 0 3px rgba(255,246,214,.75)}}
.cluster.beckon .chub{animation:nodebeckon .52s ease-out}

/* In-service "jump" targets — the other systems stay reachable while a panel is open; they rest
   quieter so the open service leads, and brighten on hover. */
.cluster.jump .cinner{color:#aeb7c8;background:rgba(7,9,15,.26);box-shadow:none}
.cluster.jump .chub{box-shadow:0 0 6px rgba(255,193,100,.4)}
.cluster.jump:hover .cinner{color:#fff;background:rgba(17,13,9,.46);box-shadow:0 0 14px rgba(255,196,100,.2)}
.cluster.jump:hover .chub{box-shadow:0 0 14px 3px rgba(255,201,120,.7)}

#brainhint{position:fixed;left:0;right:0;bottom:clamp(26px,7vh,64px);text-align:center;z-index:5;
  color:var(--muted);font-size:13px;letter-spacing:.4px;opacity:0;transition:opacity .6s ease;pointer-events:none;
  /* multi-layer glyph halo (a11y #7) — a tight dark rim + soft glow keeps the hint legible over bright
     hub clusters without a scrim box. */
  text-shadow:0 1px 2px rgba(3,6,12,.95),0 0 10px rgba(3,6,12,.8),0 0 18px rgba(3,6,12,.5)}
#brainhint.is-on{opacity:1}
/* Hide the hub hint when a branch is open, OR when the deeper "+ more" ring is open: in more mode
   the JS still flags it .is-on (hubActive && !branch), but "Pick a system" is stale there AND it
   sits at the same centred-bottom spot as the "← Back to the hub" control, so the two collide.
   more-open is only ever set by a real click → the pinned beat/hub goldens are untouched. */
body.in-branch #brainhint,body.more-open #brainhint{opacity:0}

/* ── Second-tier "+ more" ring (B3.2) ─────────────────────────────────────────────
   A separate pill container so the 9-node hub selector (#hub button.cluster) is untouched.
   Hidden (display:none) until the +more node is explored, so the pinned beat/hub captures never
   render it. When body.more-open: the 9-pill hub fades out and this ring fades in, and the camera
   has dollied CLOSER to the deeper automations. Ring pills reuse the .cluster look (position:fixed,
   JS-tracked transform). */
#morehub{position:fixed;inset:0;z-index:6;pointer-events:none;display:none}
body.more-open #morehub{display:block}
body.more-open #hub{opacity:0;pointer-events:none}
.morecluster{z-index:6;visibility:hidden}   /* a11y: boots invisible — updateMoreHub flips inline visibility while the ring is live, so they never sit in the tab order unseen */
/* Back-out control — returns to the hub (glides the camera back out). Only present in more mode. */
#more-back{position:fixed;left:50%;transform:translateX(-50%);bottom:clamp(22px,6vh,54px);z-index:7;
  padding:9px 16px;border-radius:100px;font:600 13px/1 var(--sans);color:#dbe3f2;cursor:pointer;
  background:rgba(10,9,16,.72);border:1px solid rgba(255,204,110,.30);backdrop-filter:blur(8px) saturate(1.1);
  box-shadow:0 2px 18px rgba(3,6,12,.6);transition:opacity .3s ease,border-color .25s,background .25s,transform .05s linear}
#more-back:hover{border-color:rgba(255,204,110,.8);background:rgba(22,17,12,.82);box-shadow:0 0 24px rgba(255,196,100,.4)}
#more-back:active{transform:translateX(-50%) scale(.97)}   /* R11 E-2: the ring's own exit control had no press state at all */

/* MOBILE LABEL DECLUTTER (Fable mobile pass): a hub pill whose label would overlap a nearer one
   hides its TEXT — the tappable star dot stays. Class applied per-frame by updateHub (mobile only),
   so names return as the orbit separates them. */
.cluster.declutter .clabelwrap{opacity:0 !important;pointer-events:none !important}

/* In-frame proof strip — RETIRED (owner 2026-07-08: covered the brain crown; the brain speaks for
   itself). display:none hard-hides it on top of main.js never arming .is-on; markup kept for revert. */
#proofbar{position:fixed;left:50%;top:clamp(58px,8.5vh,82px);transform:translateX(-50%);z-index:5;
  display:none;justify-content:center;flex-wrap:wrap;align-items:center;max-width:92vw;
  padding:9px 20px;border-radius:999px;border:1px solid var(--line);
  background:rgba(9,13,21,.62);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  font-size:14px;letter-spacing:.3px;color:var(--muted);text-align:center;
  opacity:0;transition:opacity .6s ease;pointer-events:none;
  text-shadow:0 1px 10px rgba(3,6,12,.7)}
#proofbar.is-on{opacity:1}
body.in-branch #proofbar{opacity:0}
#proofbar b{color:var(--text);font-weight:700}
/* the surfaced conversion stat — its number glows warm-gold so the key metric leads the strip
   (the claim itself is unchanged copy from COPY.chat.why: "78% ... respond first"). */
#proofbar .pf-stat{color:#cdd6ea}
#proofbar .pf-stat b{color:var(--gold);text-shadow:0 0 12px rgba(255,204,110,.4)}
#proofbar .pf-live{display:inline-flex;align-items:center}
#proofbar span:not(:last-child)::after{content:"·";margin:0 14px;color:#4a5570}

/* Branch panel — slides in from the right with the system's real copy/specs. */
#panel{position:fixed;top:0;right:0;height:100%;width:min(430px,93vw);z-index:7;
  display:flex;flex-direction:column;justify-content:center;justify-content:safe center;gap:14px;padding:clamp(24px,5vw,56px);
  /* safe-center + scroll: the panel centres its column on a normal-height window, but in a SHORT
     window (landscape phone, or a short desktop window) the content is taller than the viewport —
     plain `center` would clip the headline top + the "← Back to the brain" link UNREACHABLY (flex
     overflow can't scroll past a centred start). `safe center` falls back to start-align exactly when
     it overflows, and overflow-y lets it scroll. Mobile's bottom-sheet overrides both below. */
  overflow-y:auto;
  /* FROSTED-GLASS pane (audit #4): solid enough across the body for legibility + to hide the topbar CTAs
     behind its header, but its LEFT ~44px fades to transparent so the blurred brain-field bleeds INTO the
     glass — the 3D dissolves into the panel instead of butting a hard opaque seam. */
  background:linear-gradient(to left,rgba(7,10,18,.92) 0,rgba(7,10,18,.92) calc(100% - 44px),rgba(7,10,18,0) 100%);
  backdrop-filter:blur(18px) saturate(1.1);-webkit-backdrop-filter:blur(18px) saturate(1.1);
  /* a 1px warm-gold pane-edge hairline (inset, left) reads the glass edge, and a soft ambient shadow
     casts onto the scene so the panel LIFTS off the 3D rather than butting it. */
  box-shadow:inset 1px 0 0 rgba(255,204,110,.22),-22px 0 55px rgba(3,6,12,.5);
  /* Entrance is a SPRING SETTLE (audit #5): the panel rests just off its docked edge + transparent, then on
     open it springs the last 28px home with a hard-decelerate curve while the opacity eases alongside — a
     refined settle, not a long flat slide. This BASE transition is the smooth (non-spring) one, so CLOSE
     glides back quietly. Mobile/reduced-motion override the transform below, inheriting this opacity fade. */
  transform:translateX(28px);opacity:0;
  /* EXIT (R11 E-1). Measured before the change: the panel took ~600 ms to arrive and ~900 ms to
     leave, and its two curves were the wrong way round — the open rode a hard ease-out while the
     close rode cubic-bezier(.45,0,.55,1), an ease-IN-out that barely moves for its first third, so
     the dismissal read as reluctant. The exit is the system responding, not the visitor deciding:
     it starts NOW and it is faster than the enter (emil "exit faster than enter" / "never ease-in on
     a UI element"; apple §7 mirror the easing on a reversible transition). The mobile sheet inherits
     this pair while travelling translateY(106%), which is where it read worst. */
  transition:transform .3s cubic-bezier(.32,.72,0,1),opacity .24s cubic-bezier(.23,1,.32,1);
  pointer-events:none}
#panel.is-open{transform:none;opacity:1;pointer-events:auto;
  transition:transform .58s cubic-bezier(.16,1,.3,1),opacity .5s ease}   /* OPEN: spring settle — curve peaks at 1 so the docked right edge never gaps */
/* Content stagger on open — each child fades + rises with an increasing delay, and the spec
   pills cascade one-by-one for a choreographed reveal. The whole sequence finishes within
   ~0.72s (p-cta: .38s delay + .34s) so it completes INSIDE the 0.9s camera/panel glide — no
   second-stage "jump" (U1). Keyed off .is-open so it resets to the hidden/offset rest state on
   close and re-plays on the next open. */
#panel .p-sys,#panel .p-h,#panel .p-body,#panel .p-why,#panel .p-cta,#panel .p-specs .spec{
  opacity:0;transform:translateY(10px);
  /* EXIT (R11 E-3). This hidden REST state had no transition, so on close every child snapped to
     opacity 0 on the very first frame while the pane was still fully opaque — you watched an EMPTY
     frosted pane fade out on its own (sampled: `.p-h` opacity 1 -> 0 in one frame at the class flip,
     with the pane still at 1). Now the content LEAVES WITH the pane, along the same 10px path it
     arrived on (apple §7, mirror a reversible transition), and finishes inside the pane's own 240ms
     so nothing is left hanging. The staggered per-child delays live on `.is-open` and so apply to the
     ENTER only — the exit is one movement, not a reverse cascade. */
  transition:opacity .2s cubic-bezier(.23,1,.32,1),transform .2s cubic-bezier(.23,1,.32,1)}
#panel.is-open .p-sys,#panel.is-open .p-h,#panel.is-open .p-body,#panel.is-open .p-why,
#panel.is-open .p-cta,#panel.is-open .p-specs .spec{
  opacity:1;transform:none;transition:opacity .34s ease,transform .34s cubic-bezier(.22,.61,.36,1)}
#panel.is-open .p-sys{transition-delay:.04s}
#panel.is-open .p-h{transition-delay:.09s}
#panel.is-open .p-body{transition-delay:.14s}
#panel.is-open .p-why{transition-delay:.19s}
#panel.is-open .p-specs .spec{transition-delay:.23s}
#panel.is-open .p-specs .spec:nth-child(2){transition-delay:.26s}
#panel.is-open .p-specs .spec:nth-child(3){transition-delay:.29s}
#panel.is-open .p-specs .spec:nth-child(4){transition-delay:.32s}
#panel.is-open .p-specs .spec:nth-child(n+5){transition-delay:.35s}
#panel.is-open .p-cta{transition-delay:.38s}
/* R13 beat E — FOUND, not designed. The rule two lines up is (1,2,0) and it OWNS `transform` on this
   element with a 340ms transition and a 380ms delay. `.p-cta:hover` and `.p-cta:active` are (0,2,0),
   so ever since the entrance stagger existed the panel's call to action has answered a hover and a
   press with nothing at all — and beat D just made it the panel's ONLY call to action. These two
   rules match the entrance's specificity and bring their own timing, so the interaction is instant
   while the entrance keeps its slow reveal. Verified by dispatching a real pointerdown: the computed
   transform was `none` before this and is the press scale after. */
@media (hover:hover) and (pointer:fine){
  #panel.is-open .p-cta:hover{transform:translateY(-2px);
    box-shadow:0 6px 22px rgba(150,110,242,.45),0 0 26px rgba(150,110,242,.24);
    transition:transform .18s cubic-bezier(.2,.7,.2,1),box-shadow .25s ease;transition-delay:0s}
}
#panel.is-open .p-cta:active,#panel.is-open .p-cta.pressing{
  transform:translateY(0) scale(.98);box-shadow:0 0 14px rgba(150,110,242,.38);
  transition:transform .09s cubic-bezier(.2,.7,.2,1),box-shadow .09s ease;transition-delay:0s}
#panel-back{align-self:flex-start;background:none;border:none;color:var(--cyan);cursor:pointer;
  font:600 13px var(--sans);padding:0;margin-bottom:4px;
  transition:color .2s ease,transform .18s cubic-bezier(.2,.7,.2,1)}
/* back affordance → the arrow nudges LEFT + brightens on hover (echoes the "← to the brain" motion). */
#panel-back:hover{color:#dcc4ff;transform:translateX(-3px)}
#panel-back:active{transform:translateX(-1px) scale(.97)}   /* R11 E-2: a press needs a press, not just a nudge */
.p-sys{font-size:12px;letter-spacing:1.5px;text-transform:uppercase;color:var(--eyebrow)}
.p-h{font-family:var(--display);font-size:clamp(24px,3.4vw,34px);line-height:1.1;letter-spacing:-1px;font-weight:700}
.p-body{color:var(--muted);font-size:16px;max-width:36ch}
.p-specs{display:flex;flex-wrap:wrap;gap:8px;margin-top:2px}
.p-specs .spec{font-size:12px;color:#cdd6ea;border:1px solid var(--line);
  background:rgba(13,18,30,.6);padding:5px 10px;border-radius:8px;
  transition:border-color .2s ease,background .2s ease,color .2s ease}
/* spec chips aren't clickable — just a faint gold border-warm on hover so the panel isn't dead. */
.p-specs .spec:hover{border-color:rgba(255,204,110,.4);background:rgba(22,17,12,.55);color:var(--text)}
/* benefit line — why the business needs it (gold accent ties to the focal/node stars) */
.p-why{color:#aeb7c8;font-size:13.5px;line-height:1.5;max-width:38ch;
  padding-left:11px;border-left:2px solid rgba(255,204,110,.55)}
/* visually-hidden but crawlable / screen-reader text (SEO mirror of the canvas copy) */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* Deep-dive guide card — the visible way into this service's full blog post. Deliberately a bordered
   BOX, not a third text line: the owner asked for something you can see and click to go read. Violet
   panel language — violet and outlined, not filled — so it reads as a place to go, not a second CTA
   competing with .p-cta (which is now the panel's only one; R13 beat D). */
.p-guide{display:flex;flex-direction:column;gap:5px;align-self:stretch;text-decoration:none;
  margin-top:6px;padding:11px 13px;border-radius:11px;
  border:1px solid rgba(150,110,242,.32);background:rgba(15,11,26,.55);
  transition:transform .18s cubic-bezier(.2,.7,.2,1),border-color .2s ease,background .2s ease,box-shadow .25s ease}
.p-guide[hidden]{display:none}
.p-guide:hover{transform:translateY(-1px);border-color:rgba(150,110,242,.62);
  background:rgba(22,16,36,.72);box-shadow:0 0 22px rgba(150,110,242,.22)}
.p-guide:focus-visible{outline:2px solid rgba(255,204,110,.75);outline-offset:2px}
.pg-eyebrow{font:600 10.5px/1 var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
.pg-title{font:600 13.5px/1.35 var(--sans);color:var(--text);max-width:38ch}
.pg-hint{font:500 11.5px/1 var(--sans);color:#8f98ab}
/* (The old quiet .p-more text link became a .p-guide card too — owner 2026-07-29: both outbound
   destinations read as boxes now, service page first, blog guide under it.) */
.p-cta{margin-top:10px;align-self:flex-start;font-size:14px;font-weight:600;text-decoration:none;
  color:#06121a;padding:10px 18px;border-radius:9px;background:var(--cta);
  transition:transform .2s cubic-bezier(.2,.7,.2,1),box-shadow .25s ease}
/* panel CTA → lift + violet/cyan glow on hover, real pressed :active (mac micro-interaction pass). */
.p-cta:hover{transform:translateY(-2px);box-shadow:0 6px 22px rgba(150,110,242,.45),0 0 26px rgba(150,110,242,.24)}
/* R13 beat E: `.pressing` beside `:active`. This is the panel's ONLY call to action now (beat D),
   and `:active` is not trustworthy on this page — an emulated or real touch never sets it (measured
   in R11 beat E), so the one button a thumb has to trust would answer nothing. main.js arms the
   class on pointerdown. */
.p-cta:active,.p-cta.pressing{transform:translateY(0) scale(.98);box-shadow:0 0 14px rgba(150,110,242,.38);transition-duration:.09s}

/* Live Website AI Assistant — the chat widget, shown only when the chat branch is open.
   Framed as a real messenger (owner 2026-07-29: the demo read as loose text): a bordered inset
   card with a header row, so it reads as A CHAT before a single word is read. */
#chatbox{display:flex;flex-direction:column;gap:10px;padding:12px;border-radius:14px;
  border:1px solid var(--line);background:rgba(7,10,18,.55);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
#chatbox[hidden]{display:none}        /* [hidden] must beat the display:flex above — chat widget shows ONLY in the chat branch */
#chathead{display:flex;align-items:center;gap:8px;padding:0 2px 9px;border-bottom:1px solid rgba(255,255,255,.07)}
.ch-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);flex:none;
  animation:chpulse 2.6s ease-in-out infinite}
@keyframes chpulse{0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(199,155,255,.45)}50%{opacity:.6;box-shadow:0 0 0 5px rgba(199,155,255,0)}}
.ch-name{font:600 12.5px var(--sans);color:var(--text)}
.ch-live{margin-left:auto;font:600 10px/1 var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
@media (prefers-reduced-motion:reduce){.ch-dot{animation:none}}
/* demo tabs: Assistant ↔ Doc Q&A (RAG showcase) — quiet segmented pills above the log */
#chattabs{display:flex;gap:6px}
.ctab{font:600 12px var(--sans);color:var(--muted);border:1px solid var(--line);background:rgba(13,18,30,.55);
  padding:6px 12px;border-radius:999px;cursor:pointer;transition:color .2s,border-color .2s,background .2s}
.ctab:hover{color:var(--text);border-color:var(--cyan)}
.ctab.is-on{color:var(--text);border-color:var(--cyan);background:rgba(199,155,255,.12)}
/* RAG source line — quiet citation under an exact-quote answer */
#chatlog .msg.bot .rag-src{display:block;margin-top:7px;font:11px/1.4 var(--sans);color:var(--muted);font-style:italic}
#chatlog{display:flex;flex-direction:column;gap:8px;max-height:34vh;overflow-y:auto;overscroll-behavior:contain;padding-right:4px}
/* Thin, theme-matched scrollbars for the two scroll surfaces (the chat log, and the panel when a
   short window / long content makes it scroll). The default Windows bar is chunky and light and
   reads as an accident against the dark glass — a hairline translucent thumb looks intentional. */
#chatlog,#panel{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.18) transparent}
#chatlog::-webkit-scrollbar,#panel::-webkit-scrollbar{width:6px;height:6px}
#chatlog::-webkit-scrollbar-track,#panel::-webkit-scrollbar-track{background:transparent}
#chatlog::-webkit-scrollbar-thumb,#panel::-webkit-scrollbar-thumb{
  background:rgba(255,255,255,.15);border-radius:6px}
#chatlog::-webkit-scrollbar-thumb:hover,#panel::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.28)}
/* ROOT scrollbar, same treatment — the page's own bar was still the stock light Windows one:
   the single brightest object on screen through the whole dark journey. Near-black track +
   translucent thumb; geometry untouched (no thin) so nothing reflows. Chromium 121+/Firefox
   take scrollbar-color; Safari takes the ::-webkit-* fallback. */
html{scrollbar-color:rgba(255,255,255,.17) #0a0c16}
html::-webkit-scrollbar{width:12px}
html::-webkit-scrollbar-track{background:#0a0c16}
html::-webkit-scrollbar-thumb{background:rgba(255,255,255,.17);border-radius:7px;border:3px solid #0a0c16}
html::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.30)}
#chatlog .msg{font-size:14px;line-height:1.4;padding:9px 12px;border-radius:12px;max-width:90%;white-space:pre-wrap;word-wrap:break-word}
#chatlog .msg.user{align-self:flex-end;color:#06121a;border-bottom-right-radius:4px;
  background:var(--cta)}
#chatlog .msg.bot{align-self:flex-start;color:var(--text);border:1px solid var(--line);
  background:rgba(13,18,30,.7);border-bottom-left-radius:4px}
#chatlog .msg.bot b{color:var(--cyan)}
#chatlog .msg.typing{display:flex;gap:4px;align-items:center}
#chatlog .msg.typing span{width:6px;height:6px;border-radius:50%;background:var(--muted);animation:chatblink 1.2s infinite}
#chatlog .msg.typing span:nth-child(2){animation-delay:.2s}
#chatlog .msg.typing span:nth-child(3){animation-delay:.4s}
@keyframes chatblink{0%,60%,100%{opacity:.25}30%{opacity:1}}
#chatchips{display:flex;flex-wrap:wrap;gap:6px}
.qchip{font:12px var(--sans);color:#cdd6ea;border:1px solid var(--line);background:rgba(13,18,30,.6);
  padding:6px 10px;border-radius:8px;cursor:pointer;
  transition:transform .18s cubic-bezier(.2,.7,.2,1),border-color .2s ease,background .2s ease,box-shadow .2s ease,color .2s ease}
/* clickable prompts → a subtle lift + warm-cyan border/glow on hover so they read as tappable. */
.qchip:hover{border-color:var(--cyan);color:var(--text);transform:translateY(-1px);
  background:rgba(16,26,38,.7);box-shadow:0 3px 14px rgba(199,155,255,.16)}
.qchip:active{transform:translateY(0);border-color:var(--cyan);background:rgba(199,155,255,.14)}   /* pressed = mac cyan tint */
#chatform{display:flex;gap:8px}
#chatinput{flex:1;background:rgba(7,10,18,.8);border:1px solid var(--line);border-radius:9px;
  color:var(--text);font:15px var(--sans);padding:11px 13px;outline:none}
#chatinput:focus{border-color:var(--cyan)}
#chatinput:focus-visible{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(199,155,255,.20);outline:none}
#chatform button{background:var(--cta);color:#06121a;
  border:none;border-radius:999px;width:42px;font-size:18px;font-weight:700;cursor:pointer;
  transition:opacity .25s}
/* while a reply is typing, further sends are ignored (chatBusy) — SHOW that instead of a
   silent drop: the send arrow dims until the assistant finishes. */
#chatform button:disabled{opacity:.38;cursor:default}
/* honesty badge — a fallback (non-live) reply is clearly marked as demo mode */
#chatlog .msg.bot .demo-tag{display:block;font:600 10px/1 var(--sans);letter-spacing:.08em;
  text-transform:uppercase;color:rgba(255,204,110,.85);margin-bottom:6px;font-style:normal}
/* 2-try lead gate card — replaces the chips+input after the free tries are spent */
#chatchips[hidden],#chatform[hidden]{display:none}   /* [hidden] must beat their display:flex (same trap as #chatbox) */
#chatbox.gated #chatlog{max-height:16vh}             /* shorter log while the gate is up so the card fits in view */
/* the "Book a real demo" card wires up the same way the recruit modal does — one pulse runs down
   the fields as it arrives, so both CTAs speak the same motion language. */
#leadgate:not([hidden]){animation:rcWire .42s ease-out backwards}
#leadgate:not([hidden]) input{animation:rcWireLine .62s ease-out backwards}
#leadgate:not([hidden]) input:nth-of-type(1){animation-delay:.14s}
#leadgate:not([hidden]) input:nth-of-type(2){animation-delay:.23s}
#leadgate:not([hidden]) input:nth-of-type(3){animation-delay:.32s}
#leadgate:not([hidden]) button{animation:rcWire .42s .40s ease-out backwards}
@media (prefers-reduced-motion:reduce){
  #leadgate:not([hidden]),#leadgate:not([hidden]) input,#leadgate:not([hidden]) button{animation:none}
}
#leadgate{display:flex;flex-direction:column;gap:8px;padding:14px;border:1px solid var(--line);
  border-radius:14px;background:rgba(13,18,30,.72)}
#leadgate[hidden]{display:none}
#leadgate .lg-h{margin:0;font:700 15px/1.3 var(--sans);color:var(--text)}
#leadgate .lg-sub{margin:0 0 2px;font:12.5px/1.45 var(--sans);color:var(--muted)}
#leadgate input{background:rgba(7,10,18,.8);border:1px solid var(--line);border-radius:9px;
  color:var(--text);font:14px var(--sans);padding:10px 12px;outline:none}
#leadgate input:focus{border-color:var(--cyan)}
#leadgate input:focus-visible{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(199,155,255,.20);outline:none}
/* design pass 2026-07-15 #3: the flagged field gets the SAME gold aria-invalid ring as the recruit
   modal (set in main.js, cleared on input) — the gate showed a violet focus ring beside a gold
   message + gold card edge: two voices for one error. One language now. After the :focus rules so
   the flag wins while the field is focused (equal specificity → source order). */
#leadgate input[aria-invalid="true"]{border-color:rgba(255,204,110,.75);box-shadow:0 0 0 3px rgba(255,204,110,.14)}
#leadgate button{background:var(--cta);color:#06121a;
  border:none;border-radius:9px;padding:11px 14px;font:700 14px var(--sans);cursor:pointer}
#leadgate button:disabled{opacity:.6;cursor:default}
#leadgate .lg-err{margin:0;font:12px/1.4 var(--sans);color:rgba(255,204,110,.85)}
/* ERROR affordance — when a submit fails and .lg-err surfaces, warm the whole card edge + a faint gold
   ring so the failure reads as one cohesive state, not a stray line under a neutral card. Pure CSS via
   :has() (no JS/class hook needed); the card only ever shows after a real submit, so the pinned #p=
   captures + goldens never render it. */
#leadgate:has(.lg-err:not([hidden])){border-color:rgba(255,204,110,.5);
  box-shadow:0 0 0 1px rgba(255,204,110,.16),0 6px 26px rgba(3,6,12,.5)}
/* R13 beat D removed the "Book a real demo" pill and the whole 'book' path with it, so the
   `#bookdemo` rules and the `#leadgate.done` booked-✓ state that lived here are gone: nothing sets
   that class any more, and a lead card has exactly one job now. */

/* Outro — pivot narrative + the single "Work with me" CTA, normal flow below the intro spacer. */
#outro{position:relative;z-index:4;min-height:100vh;display:flex;flex-direction:column;justify-content:center;
  padding:clamp(40px,9vw,120px);
  /* R7 P8 (owner: "when that text comes up its background is on half of services and looks strange"):
     the old ramp hit .82 opacity by just 16% → a hard darkening FRONT sliced across the hub as the
     outro scrolled up. A much TALLER, softer top fade (spread over the top ~30% instead of ~16%) lets
     the brain dim GRADUALLY into the outro — no slab edge — while the copy (which starts ~32% down)
     still lands on a near-solid background. The branch-fx/hubVis intrusion fade is untouched. */
  background:linear-gradient(180deg,transparent,rgba(5,7,13,.18) 14%,rgba(5,7,13,.55) 28%,rgba(5,7,13,.85) 42%,#05070d 60%)}
.sub.wide{max-width:54ch}
.cta-row{display:flex;gap:14px;margin-top:30px;flex-wrap:wrap}
.btn{text-decoration:none;font-weight:700;font-size:15px;padding:13px 22px;border-radius:10px;
  transition:transform .2s cubic-bezier(.2,.7,.2,1),box-shadow .25s ease,border-color .25s ease,background .25s ease}
.btn.primary{color:#06121a;background:var(--cta)}
.btn.primary:hover{transform:translateY(-2px);box-shadow:0 8px 26px rgba(150,110,242,.45),0 0 30px rgba(150,110,242,.24)}
.btn.primary:active,.btn.primary.pressing{transform:translateY(0) scale(.98);box-shadow:0 0 16px rgba(150,110,242,.38);transition-duration:.09s}
.btn.ghost{color:var(--text);border:1px solid var(--line);background:rgba(13,18,30,.5)}
.btn.ghost:hover{transform:translateY(-2px);border-color:rgba(255,204,110,.45);background:rgba(255,204,110,.06)}
.btn.ghost:active{transform:translateY(0);border-color:rgba(255,204,110,.6);background:rgba(255,204,110,.10);transition-duration:.09s}

.noscript{position:fixed;inset:0;display:grid;place-items:center;padding:32px;text-align:center;z-index:9}
.noscript a{color:var(--cyan)}

/* Keyboard a11y — one coherent, subtle GOLD focus ring across every interactive control (ties to
   the node/CTA-secondary gold accent, never the default browser blue). Only on keyboard focus
   (:focus-visible) so mouse/touch clicks don't ring. Offset so it hugs each pill/button cleanly.
   #chatinput keeps its cyan border-focus (form field, brand-primary) but also lands a ring.
   (reconcile: a11y pass built twice — kept the win-line superset, mac duplicate dropped.) */
.navbtn:focus-visible,.btn:focus-visible,.qchip:focus-visible,
.p-cta:focus-visible,#panel-back:focus-visible,#totop:focus-visible,#scrollcue:focus-visible,
#more-back:focus-visible,.ctab:focus-visible,
#chatform button:focus-visible,#chatinput:focus-visible,#leadgate button:focus-visible{
  outline:2px solid rgba(255,204,110,.75);outline-offset:2px;border-radius:100px}
/* the cluster button is a zero-size point → ring its visible NAME instead of the button box */
.cluster:focus-visible{outline:none}
.cluster:focus-visible .cinner{outline:2px solid rgba(255,204,110,.8);outline-offset:2px;border-radius:8px}
/* square-ish controls keep a matching but tighter-radiused ring */
.btn:focus-visible,.qchip:focus-visible,.p-cta:focus-visible,.navbtn:focus-visible,
#chatform button:focus-visible,#chatinput:focus-visible,#leadgate button:focus-visible{outline-offset:2px;border-radius:10px}

@media (max-width:720px){
  /* Touch feel: no tap-flash / accidental text-select on the interactive chrome (canvas, pills,
     topbar, panel chrome). Panel COPY stays selectable — handled below. */
  #scene,#cursor-fx,#topbar,.cluster,#hub,#proofbar,#brainhint,.qchip,#chatform button,
  #panel-back,.navbtn,.btn,#totop,#scrollcue{
    -webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;
    -webkit-user-select:none;user-select:none}

  /* Return control: nudge in a touch on phones; still comfortably tappable (≥38px). */
  #totop{left:14px;bottom:16px;padding:9px 13px;font-size:12px}

  /* R7 P2 (owner: hero copy sits too high, covers half the galaxy): bottom-anchor the copy block low —
     the P4 scroll-hint removal already shortened it ~54px, so this still reads as a tight bottom caption
     with the galaxy dominant above. The +44px leaves a clean strip at the very bottom for the control
     row (the unified #totop pill on the left + the "Swipe to look around" hint centred) so neither sits
     on the copy. max(safe-area,18px) clears the iOS home indicator / Safari-Chrome bottom bar. The mid-
     journey chapters share the anchor so the cross-fade stays put + never collides with #totop either. */
  .chapter{left:18px;right:18px;bottom:calc(max(env(safe-area-inset-bottom,0px),18px) + 66px);max-width:none}
  /* R7 PR1: only the HERO (galaxy) chapter lowers all the way — there is no hub brainhint to clear at
     the hero. The mid-journey chapters (earth/brain) stay higher (above) so the BRAIN chapter's copy
     doesn't crowd the "Pick a system" hub hint + Top pill at the settled hub (a P2/P4 side-effect). */
  .chapter[data-stage="galaxy"]{bottom:calc(max(env(safe-area-inset-bottom,0px),18px) + 44px)}
  /* Grotesk at phone sizes: ease the tracking back off the big-clamp pull (too tight small
     hurts legibility) — still snug, not cramped. Face inherits from the base rule. */
  h1{font-size:clamp(30px,10vw,44px);letter-spacing:-1.4px}   /* cap so the headline can't overflow narrow screens */
  h2{font-size:clamp(26px,8.5vw,38px);letter-spacing:-1px}
  .sub{max-width:34ch;font-size:16px}

  /* Topbar: keep logo + both CTAs on one row on small phones (no ugly wrap). Tighten the
     padding/gap and trim the button chrome; still comfortably tappable. */
  #topbar{padding:14px 16px}
  .nav-cta{gap:7px}
  .navbtn{font-size:13px;padding:9px 13px}    /* keeps ~38px tap height; both CTAs fit 360px wide */
  .navbtn.snd{width:34px;height:34px}          /* compact icon so the sound toggle never wraps the row */
  .navbtn.snd svg{width:17px;height:17px}

  /* Hub names: on a narrow portrait screen the nine nodes crowd together and their labels
     collide. Tighten the chip footprint (smaller padding + snug max-width) so each name occupies
     less width and overlaps less, while keeping a comfortable tap target (the whole chip is the
     hit area). A crisper multi-layer glyph-halo carries contrast when two chips DO overlap. */
  .cinner{padding:7px 11px;font-size:12px;max-width:62vw;
    text-shadow:0 1px 2px rgba(0,0,0,.98),0 0 6px rgba(3,6,12,.92),0 0 12px rgba(3,6,12,.7)}

  /* Proof strip on phones: the full four-stat block wraps to 6–7 lines on a 360px screen and
     buries the brain hero. Trim to the TWO strongest proofs (the 78% conversion stat + the
     "Live in production" status) and drop the model/latency detail; the card stays a tight two
     lines. A capped radius keeps the wrapped block a clean CARD (999px bulges into a lumpy oval),
     and dropping the "·" separators avoids a dangling dot at a line end. */
  #proofbar{max-width:min(340px,calc(100vw - 28px));padding:8px 15px;border-radius:15px;
    font-size:12.5px;line-height:1.42;row-gap:1px}
  #proofbar span:nth-child(3),#proofbar span:nth-child(4){display:none}
  #proofbar span:not(:last-child)::after{display:none}

  #panel{width:100%;height:auto;max-height:82vh;top:auto;bottom:0;
    /* bottom-sheet content sits at the bottom (thumb reach) — BUT the tallest panels (chat) are
       taller than 82vh, and plain flex-end overflows UPWARD, which isn't scrollable: the top of the
       sheet (headline + the "← Back to the brain" close link) then clips UNREACHABLY (scrollHeight
       stays == clientHeight, scrollTop pinned at 0). `safe flex-end` falls back to flex-start exactly
       when content overflows, so the top stays reachable and the sheet scrolls — the same fix the
       desktop panel uses with `safe center`. Plain flex-end kept first as the old-browser fallback. */
    justify-content:flex-end;justify-content:safe flex-end;overflow-y:auto;-webkit-overflow-scrolling:touch;
    overscroll-behavior:contain;   /* R7 P7: the sheet scrolls internally; its scroll never chains to the (locked) page */
    transform:translateY(106%);
    background:linear-gradient(0deg,#070a12,rgba(7,10,18,.92) 70%,transparent);
    /* bottom-sheet: recast the desktop left-edge glass for a TOP edge — a lighter frost + an UPWARD
       ambient shadow; the existing border-top serves as the pane hairline. */
    backdrop-filter:blur(14px) saturate(1.08);-webkit-backdrop-filter:blur(14px) saturate(1.08);
    box-shadow:0 -14px 44px rgba(3,6,12,.5);
    border-top:1px solid var(--line);border-radius:18px 18px 0 0}
  #panel.is-open{transform:none}
  /* keep the sheet's own copy selectable (overrides the chrome user-select:none above) */
  #panel .p-sys,#panel .p-h,#panel .p-body,#panel .p-why,#panel .p-specs{
    -webkit-user-select:text;user-select:text}
  .p-body{max-width:none}
  /* "Back to the brain" — bigger tap target on mobile (padding, not just link text). */
  #panel-back{padding:8px 2px;margin-bottom:2px;font-size:14px}

  /* Chat widget fits the bottom-sheet: log capped, input at 16px so iOS doesn't zoom-jump on focus. */
  #chatlog{max-height:40vh}
  #chatinput{font-size:16px;padding:11px 12px}   /* 16px = no focus-zoom on iOS Safari */
  #chatform button{width:46px}
  /* Same 16px rule for the two lead-capture forms (recruit modal + lead gate) so iOS Safari
     doesn't zoom-jump the fixed canvas on field focus — the conversion moment must stay clean.
     #rc-form (id) selector so it beats the later .rc-field base rule; #leadgate wins by order. */
  #leadgate input,#rc-form input,#rc-form textarea{font-size:16px}
}

/* Coarse-pointer (touch) devices that may be WIDER than 720px (tablets in landscape):
   drop the hover-only lift/glow that has no matching pointerup, and lighten the many
   simultaneous backdrop-blurs of the hub pills — a real per-frame cost on mobile GPUs.
   Desktop (fine pointer) never matches this block. */
@media (pointer:coarse){
  /* cheaper legibility veil (drop the per-name blur on mobile GPUs) + neutralize sticky :hover on touch.
     Veil OPACITY raised (.60→.76) so where two crowded labels overlap on a narrow screen, the
     backing stays solid enough that neither turns to mud — no blur to lean on here, so the flat
     fill has to carry the contrast itself. */
  .cinner{backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(7,9,15,.76)}
  .cluster.live .cinner{background:rgba(11,13,11,.78)}
  .cluster:hover .cinner,.cluster.hot .cinner{color:#d3dcec;background:rgba(7,9,15,.76);box-shadow:0 1px 10px rgba(3,6,12,.4)}
  .cluster.live:hover .cinner,.cluster.live.hot .cinner{color:#f1f5fc;background:rgba(11,13,11,.78)}
  .cluster:hover .chub,.cluster.hot .chub{box-shadow:0 0 9px 1px rgba(255,193,100,.5),0 0 3px rgba(255,246,214,.75)}
  .cluster:hover .cline,.cluster.hot .cline{background:linear-gradient(90deg,rgba(255,204,110,.42),rgba(255,204,110,.14))}
  /* touch: keep the standing gold ring (primary signal) but drop the idle pulse animation + hover pop.
     body.live prefix matches the rule that turns the pulse ON, so this reliably overrides it. */
  body.live #work-nav{animation:none;box-shadow:0 0 0 1px rgba(255,204,110,.28)}
  #work-nav:hover{transform:none;box-shadow:0 0 0 1px rgba(255,204,110,.28)}
  /* neutralize the new panel/chip hover pops on touch (no matching pointerup → they'd stick after a tap) */
  .qchip:hover{transform:none;box-shadow:none;background:rgba(13,18,30,.6);border-color:var(--line);color:#cdd6ea}
  .p-cta:hover{transform:none;box-shadow:none}
  .ctab:hover:not(.is-on){color:var(--muted);border-color:var(--line)}   /* :not(.is-on) — a tapped-active tab must keep its on-state */
  .p-specs .spec:hover{border-color:var(--line);background:rgba(13,18,30,.6);color:#cdd6ea}
  #panel-back:hover{transform:none;color:var(--cyan)}
  /* neutralize the mac-line lift hovers kept in the reconcile (ghost navbtn + outro CTAs) */
  .navbtn.ghost:hover,.btn.primary:hover,.btn.ghost:hover{transform:none;box-shadow:none}
  .navbtn.snd:hover{transform:none;background:none;border-color:var(--line)}   /* no sticky hover on touch */
}

/* Short viewports (landscape phones ≤430px tall): the proof strip sits mid-top and, with almost no
   vertical room, collides with the hub labels ("Your AI twin" / "Voice agents" / "Assistant"). It's
   decorative (aria-hidden; the crawlable copy lives in the .sr-only mirror), so drop it here to
   declutter. Desktop, portrait phones, and tablets are all taller than 430px → never matched. */
@media (max-height:430px){
  #proofbar{display:none}
  /* pull the chapter copy tight to the bottom so the headline clears the brain in the short window */
  .chapter{bottom:clamp(14px,4vh,28px)}
  /* RB 2026-07-16: the display clamps size by vw — wrong axis here. On a 844×390 landscape phone
     h1 hit 59px, the bottom-anchored hero stack grew past the topbar (eyebrow sat ON the logo) and
     the hub h2 ran deep into the bright brain tissue. Cap by HEIGHT in this window; everything
     taller than 430px never matches. */
  h1{font-size:clamp(30px,9vh,40px)}
  h2{font-size:clamp(24px,8vh,34px)}
  .sub{font-size:14px;max-width:40ch}
  .eyebrow{margin-bottom:8px}
}

/* Reduced motion: kill the bob + the chapter transforms (cross-fade only). */
@media (prefers-reduced-motion:reduce){
  .chapter{transform:none;filter:none;transition:opacity .5s ease}   /* pure cross-fade — no rise, no blur */
  /* R11 C-1 made `.chapter.is-visible` carry its own transition (to keep the .72s ENTRANCE while the
     exit got faster). That selector is (0,2,0) and this media block's `.chapter` rule is only (0,1,0),
     so without repeating it here the entrance would silently out-specify the reduced-motion override
     and run at .72s. Caught by the reduced-motion gate's computed style, not by eye. */
  .chapter.is-visible{transform:none;filter:none;transition:opacity .5s ease}
  #interiorline{transform:none;filter:none;transition:opacity .5s ease}   /* interior line still swaps in — opacity only */
  #interiorline.is-in{transform:none;filter:none}
  #cursor-fx{display:none}
  #scrollcue{display:none}                         /* no scroll-progress cue under reduced motion */
  body.live #work-nav{animation:none;box-shadow:0 0 0 1px rgba(255,204,110,.28)}   /* no idle pulse; keep the standing gold ring */
  #work-nav:hover{box-shadow:0 0 0 1px rgba(255,204,110,.5),0 0 22px rgba(255,196,100,.5)}  /* glow stays, no scale (handled below) */
  #totop{transition:opacity .3s ease;animation:none}   /* fade only, no slide-up, no bob (R7 P4) */
  /* PRE-EXISTING, found by R11 beat C's own reduced-motion gate (computed style, not by eye): the
     line above never actually killed the bob. `body.live:not(.booted):not(.scrolled) #totop` is
     (1,3,1) and out-specifies a bare `#totop` (1,0,0), so the 2.6s bobY loop kept running under
     prefers-reduced-motion — as did the look-hint's 2.8s lhSwing. Two slow looping oscillations are
     exactly what the setting asks to stop (apple-design §14). Re-stated at matching specificity. */
  body.live:not(.booted):not(.scrolled) #totop{animation:none}
  /* (the look-hint's own lhSwing loop needs no rule here — `#lookhint{display:none}` under this same
     media query already removes it; it still shows up in a computed-style animation scan, which is a
     false positive worth remembering when reading that gate.) */
  body.scrolled #totop{transform:none}
  #panel{transition:opacity .35s ease}            /* fade, no slide */
  #panel:not(.is-open){transform:none;opacity:0;pointer-events:none}
  #panel.is-open{transform:none;opacity:1}
  /* no per-child stagger — content is present immediately (fade the whole panel only) */
  #panel .p-sys,#panel .p-h,#panel .p-body,#panel .p-why,#panel .p-cta,#panel .p-specs .spec{
    opacity:1;transform:none}
  #panel.is-open .p-sys,#panel.is-open .p-h,#panel.is-open .p-body,#panel.is-open .p-why,
  #panel.is-open .p-cta,#panel.is-open .p-specs .spec{transition:none;transition-delay:0s}
  .cluster.sel.flare .chub,.cluster.beckon .chub,.cluster.near:not(.hot) .chub{animation:none}
  body.becoming #scene{animation:none}
  /* no hub-node morph-in — names are present instantly at rest */
  #hub .creveal,#hub.is-on .creveal{opacity:1;transform:none;transition:none;transition-delay:0s}
  #chatlog .msg.typing span{animation:none}
  /* no lift/scale pop under reduced motion on any button (glows stay) */
  /* `.pressing` is listed EXPLICITLY beside `:active`: both are (0,2,0), a media query adds no
     specificity, and the press rules are written later in the file — a bare `:active` here would
     silently lose to `.p-cta.pressing` (R11 beat C's lesson, and R13 beat E added the class). */
  #work-nav:hover,#work-nav:active,.navbtn.ghost:hover,.navbtn.ghost:active,
  .p-cta:hover,.p-cta:active,.p-cta.pressing,.btn.primary:hover,.btn.primary:active,
  .btn.primary.pressing,.btn.ghost:hover,.btn.ghost:active{transform:none}
  /* the hover LIFT goes (it travels); the press SCALE stays, for the reason spelled out at the foot
     of this file — it is the acknowledgement of a tap, not vestibular motion, and it is the only
     feedback a touch visitor gets. Both need the entrance rule's specificity to be reachable. */
  #panel.is-open .p-cta:hover{transform:none}
}

/* horizontal orbit at the hub: cursor affordance for "drag to look around" */
body.can-orbit #scene{cursor:grab}
body.grabbing,body.grabbing #scene{cursor:grabbing}
/* look-around affordance (subagent-G r2): a grab cursor WHEREVER the drag-orbit is armed (galaxy/dive/
   brain/hub, desktop) — the interaction was previously only signposted at the hub. Grabbing while dragging. */
body.look-armed #scene{cursor:grab}
body.looking,body.looking #scene{cursor:grabbing}

/* One-time "drag/swipe to look around" cue — a faint, self-dismissing signpost at the galaxy. JS-gated
   off the pinned + reduced-motion paths and shown at most once ever, so the golden captures never see it. */
#lookhint{position:fixed;left:50%;bottom:clamp(84px,15vh,150px);transform:translateX(-50%) translateY(8px);
  z-index:5;display:inline-flex;align-items:center;gap:9px;white-space:nowrap;pointer-events:none;
  color:var(--muted);font:500 13px/1 var(--sans);letter-spacing:.5px;opacity:0;
  transition:opacity .9s ease,transform .9s ease;
  text-shadow:0 1px 2px rgba(3,6,12,.95),0 0 12px rgba(3,6,12,.7)}
#lookhint.is-on{opacity:.72;transform:translateX(-50%) translateY(0)}
#lookhint .lh-glyph{font-size:16px;color:#cdbcff;transform-origin:50% 50%;animation:lhSwing 2.8s ease-in-out infinite}
@keyframes lhSwing{0%,100%{transform:rotate(-13deg)}50%{transform:rotate(13deg)}}
/* Mobile: the hero copy is bottom-anchored (~84px), so drop the cue INTO the clear band below it. */
@media (max-width:720px){#lookhint{bottom:30px;font-size:12px;gap:7px}#lookhint .lh-glyph{font-size:14px}}
@media (prefers-reduced-motion:reduce){#lookhint{display:none}}

/* Landscape tablets (coarse pointer, 721-1180px wide): at the desktop 68px display size the hero
   headline ran INTO the centered galaxy core (the copy column reached ~0.52 of the viewport).
   One display notch down + a firm column cap keeps every chapter line clear of the core. */
@media (pointer:coarse) and (orientation:landscape) and (min-width:721px) and (max-width:1180px){
  h1{font-size:clamp(36px,5vw,50px);letter-spacing:-1.8px}
  h2{font-size:clamp(30px,4.2vw,42px);letter-spacing:-1.2px}
  .chapter{max-width:44vw}
}

/* ── NOTCHED-DEVICE SAFE AREAS ────────────────────────────────────────────────────────────
   The viewport meta declares viewport-fit=cover so the scene runs true edge-to-edge (right
   for the art — no letterboxing on notched iPhones), but that ALSO extends the page under
   the notch / home indicator / rounded corners, and no rule compensated: the fixed topbar
   sat under the status bar and the bottom-sheet under the home indicator on real devices
   (emulated viewports have zero insets, so probes never see it). env() resolves to 0 on
   normal screens — nothing moves anywhere else. Declared LAST so it wins the cascade. */
#topbar{padding-top:calc(20px + env(safe-area-inset-top,0px));
  padding-left:calc(clamp(18px,4vw,40px) + env(safe-area-inset-left,0px));
  padding-right:calc(clamp(18px,4vw,40px) + env(safe-area-inset-right,0px))}
#totop{bottom:calc(clamp(16px,3.5vh,30px) + env(safe-area-inset-bottom,0px))}
#brainhint{bottom:calc(clamp(26px,7vh,64px) + env(safe-area-inset-bottom,0px))}
@media (max-width:720px){
  #lookhint{bottom:calc(30px + env(safe-area-inset-bottom,0px))}
  /* bottom-sheet content clears the home indicator */
  #panel{padding-bottom:calc(clamp(24px,5vw,56px) + env(safe-area-inset-bottom,0px))}
}

/* ── "WORK WITH ME" RECRUIT MODAL ─────────────────────────────────────────────────────────
   A centred glass card over the dimmed/blurred scene. Captures a real CRM lead (same pipeline
   as marketing-site leads) — replaces the old mailto dead-end. Matches the page's glass +
   gold-hairline + violet→cyan gradient system. z above panel(7)/cursor-fx(60). */
.rc-scrim{position:fixed;inset:0;z-index:300;display:flex;align-items:center;justify-content:center;
  padding:calc(20px + env(safe-area-inset-top,0px)) calc(20px + env(safe-area-inset-right,0px))
          calc(20px + env(safe-area-inset-bottom,0px)) calc(20px + env(safe-area-inset-left,0px));
  background:rgba(4,6,12,.66);backdrop-filter:blur(7px) saturate(1.05);-webkit-backdrop-filter:blur(7px) saturate(1.05);
  opacity:0;pointer-events:none;transition:opacity .34s ease}
.rc-scrim.is-open{opacity:1;pointer-events:auto}
.rc-scrim[hidden]{display:none}
html.rc-open{overflow:hidden}   /* freeze the scroll-driven scene behind the modal */

/* ── THE CARD IS BORN FROM YOUR CURSOR ───────────────────────────────────────────────────
   The page already speaks a motion language: a dark-matter wisp that threads filaments toward the
   hub nodes. A modal that cross-fades in from centre ignores all of it and reads as a stock dialog.
   So: openRecruit writes the CLICK POINT into --rc-ox/--rc-oy (card-local px), the card's
   transform-origin sits on it, and the card UNFURLS out of that exact point — it grows from where
   your pointer was. The filaments arrive as the rim flash (rcRim), then a pulse runs down the
   fields (rcWire). Reduced-motion gets a plain fade (see the media query at the end of the file). */
.rc-card{position:relative;width:min(440px,100%);max-height:calc(100dvh - 40px);overflow-y:auto;
  overscroll-behavior:contain;
  padding:28px 26px 22px;border-radius:20px;
  background:linear-gradient(180deg,rgba(16,14,26,.92),rgba(10,10,18,.94));
  border:1px solid rgba(199,155,255,.24);
  box-shadow:0 30px 90px rgba(3,4,10,.7),0 0 0 1px rgba(150,110,242,.10),inset 0 1px 0 rgba(255,255,255,.05);
  backdrop-filter:blur(20px) saturate(1.15);-webkit-backdrop-filter:blur(20px) saturate(1.15);
  transform-origin:var(--rc-ox,50%) var(--rc-oy,50%);
  transform:scale(.06);opacity:0;
  transition:transform .54s cubic-bezier(.16,1.01,.29,1),opacity .22s ease}
.rc-scrim.is-open .rc-card{transform:none;opacity:1;animation:rcRim .95s ease-out}
/* the wisp's filaments, absorbed into the card's edge */
@keyframes rcRim{
  0%{box-shadow:0 0 0 1px rgba(214,182,255,.95),0 0 64px 8px rgba(150,110,242,.55),0 30px 90px rgba(3,4,10,.7)}
  100%{box-shadow:0 30px 90px rgba(3,4,10,.7),0 0 0 1px rgba(150,110,242,.10),inset 0 1px 0 rgba(255,255,255,.05)}
}
.rc-card::before{content:"";position:absolute;inset:0;border-radius:20px;pointer-events:none;
  background:radial-gradient(120% 80% at 15% 0%,rgba(150,110,242,.16),transparent 55%),
             radial-gradient(120% 80% at 100% 0%,rgba(199,155,255,.08),transparent 55%)}
.rc-card>*{position:relative}

/* THE COLLAPSE — the wisp at the pointer implodes a beat before the card grows out of it.
   A DOM element (not the cursor canvas) so this stays fully self-contained. */
#rc-spark{position:fixed;z-index:310;width:10px;height:10px;margin:-5px 0 0 -5px;border-radius:50%;
  pointer-events:none;opacity:0;
  background:radial-gradient(circle,#fff 0%,#dcc4ff 35%,rgba(150,110,242,0) 70%)}
#rc-spark.fire{animation:rcCore .46s ease-out}
#rc-spark::before{content:"";position:absolute;left:50%;top:50%;width:130px;height:130px;
  margin:-65px 0 0 -65px;border-radius:50%;border:1px solid rgba(199,155,255,.85);opacity:0}
#rc-spark.fire::before{animation:rcRing .46s cubic-bezier(.4,0,.18,1)}
@keyframes rcCore{0%{opacity:0;transform:scale(.4)}28%{opacity:1;transform:scale(2.8)}100%{opacity:0;transform:scale(6)}}
@keyframes rcRing{0%{opacity:.9;transform:scale(1)}70%{opacity:.75}100%{opacity:0;transform:scale(.05)}}

/* THE WIRE-UP — one pulse runs down whatever the entering step is showing, igniting each row as it
   passes. Beat G moved this OFF `.rc-scrim.is-open` (which fired once at open, so by the time a
   visitor reached the contact step the ignition had long finished) and onto the pane that is
   ENTERING — so it now belongs to every step, and it is the same pulse at the card's birth
   (`--rqd` just holds it back until the card has finished unfurling). `--i` is written once per
   pane in main.js, so the stagger never depends on where a row sits among its siblings. */
@keyframes rcWire{
  0%{opacity:0;transform:translateY(7px)}
  100%{opacity:1;transform:none}
}
@keyframes rcWireLine{
  0%,100%{border-color:var(--line);box-shadow:0 0 0 0 rgba(150,110,242,0)}
  45%{border-color:rgba(199,155,255,.85);box-shadow:0 0 0 3px rgba(150,110,242,.16),0 0 20px rgba(150,110,242,.34)}
}

.rc-close{position:absolute;top:12px;right:12px;z-index:2;width:34px;height:34px;display:flex;align-items:center;justify-content:center;
  border:1px solid var(--line);border-radius:10px;background:rgba(9,13,21,.5);color:var(--muted);cursor:pointer;
  transition:color .2s ease,border-color .2s ease,background .2s ease,transform .12s ease}
.rc-close svg{width:16px;height:16px}
.rc-close:hover{color:var(--text);border-color:rgba(255,204,110,.4);background:rgba(255,204,110,.06)}
.rc-close:active,.rc-close.pressing{transform:scale(.94)}
.rc-close:focus-visible{outline:none;border-color:var(--cyan);box-shadow:0 0 0 3px rgba(199,155,255,.22)}

.rc-eyebrow{font:600 11.5px/1 var(--sans);letter-spacing:.18em;text-transform:uppercase;color:var(--eyebrow);opacity:.85;margin-bottom:10px}
.rc-title{font-family:var(--display);font-size:30px;font-weight:600;letter-spacing:-.6px;line-height:1.05;color:var(--text)}
.rc-title::after{content:"";display:block;width:46px;height:3px;margin-top:12px;border-radius:2px;
  background:#c79bff}
/* max-width was 34ch, which inside a 386px card broke a 93-character sentence over THREE lines with
   a bad rag ("running in / your business?") and cost 22px of a card that reaches the viewport cap on
   the contact step. The card is its own measure. */
.rc-sub{margin:14px 0 18px;font:14.5px/1.5 var(--sans);color:var(--muted)}

#rc-form{display:flex;flex-direction:column;gap:13px}
.rc-field{display:flex;flex-direction:column;gap:6px}
.rc-field>span{font:600 12px/1 var(--sans);color:#c3ccdd;letter-spacing:.01em}
.rc-field>span i{font-style:normal;font-weight:400;color:var(--muted);letter-spacing:.04em}
.rc-field input,.rc-field textarea{width:100%;font:14.5px/1.45 var(--sans);color:var(--text);
  background:rgba(7,10,18,.85);border:1px solid var(--line);border-radius:11px;padding:11px 13px;
  transition:border-color .18s ease,box-shadow .18s ease}
.rc-field textarea{resize:vertical;min-height:70px}
.rc-field input::placeholder,.rc-field textarea::placeholder{color:#5b6577}
.rc-field input:focus,.rc-field textarea:focus{outline:none;border-color:var(--cyan)}
.rc-field input:focus-visible,.rc-field textarea:focus-visible{outline:none;border-color:var(--cyan);box-shadow:0 0 0 3px rgba(199,155,255,.20)}
/* design pass 2026-07-15: the field the validation flagged (aria-invalid, set in main.js) gets a
   GOLD ring — same hue as the .rc-err text, not an alien red — so the eye lands on WHAT to fix.
   Wins over the violet focus ring while flagged; typing clears the attribute (main.js). */
.rc-field input[aria-invalid="true"],.rc-field textarea[aria-invalid="true"]{border-color:rgba(255,204,110,.75);box-shadow:0 0 0 3px rgba(255,204,110,.14)}
.rc-hp{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0;opacity:0}

.rc-send{margin-top:4px;font:700 14.5px/1 var(--sans);color:#06121a;cursor:pointer;
  border:0;border-radius:11px;padding:13px 16px;background:var(--cta);
  box-shadow:0 6px 22px rgba(150,110,242,.32);transition:transform .16s ease,box-shadow .2s ease,opacity .2s ease}
.rc-send:hover{transform:translateY(-2px);box-shadow:0 10px 30px rgba(150,110,242,.45),0 0 32px rgba(150,110,242,.24)}
.rc-send:active,.rc-send.pressing{transform:translateY(0) scale(.99);transition-duration:.09s}
.rc-send:disabled{opacity:.6;cursor:default;transform:none;box-shadow:none}
.rc-send:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(199,155,255,.28)}
.rc-err{margin:2px 0 0;font:12.5px/1.4 var(--sans);color:rgba(255,204,110,.9)}
.rc-err[hidden]{display:none}
/* R13 beat E. Consent became a gate this round, so this line stopped being an edge case and became
   something people actually meet — and it was landing in a single frame under the send row, which
   reads as a glitch rather than an answer. It ARRIVES now: 160ms, the card's own ease-out curve, a
   3px rise. A keyframe rather than a transition because the element goes display:none → block, and
   `both` so it holds. Re-showing restarts it; changing the text in place deliberately does not. */
@keyframes rcErrIn{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
@keyframes rcErrFade{from{opacity:0}to{opacity:1}}
.rc-err:not([hidden]){animation:rcErrIn .16s cubic-bezier(.23,1,.32,1) both}
.rc-alt{margin-top:16px;font:12.5px/1.4 var(--sans);color:var(--muted);text-align:center}
.rc-alt a{color:var(--cyan);text-decoration:none;border-bottom:1px solid rgba(199,155,255,.3)}
.rc-alt a:hover{color:#dcc4ff}

/* ── THE QUALIFIER (Beat G) ───────────────────────────────────────────────────────────────
   Three questions, then the contact step, one screen at a time inside the SAME cosmic card —
   the collapse-from-the-pointer birth, the rim flash and the card language are untouched; the
   quiz lives inside them. Selection speaks the page's two accents and nothing else: WARM GOLD
   is the light (the ring, the tick, the icon) and VIOLET is the action (the fill). */
.rq-prog{display:flex;align-items:center;gap:10px;margin:-6px 0 16px}
.rq-dots{display:flex;gap:6px}
.rq-dot{width:6px;height:6px;border-radius:50%;background:rgba(199,155,255,.20);
  transition:background .22s ease,box-shadow .22s ease,transform .22s cubic-bezier(.23,1,.32,1)}
.rq-dot.done{background:rgba(199,155,255,.55)}
.rq-dot.now{background:var(--gold);box-shadow:0 0 10px rgba(255,204,110,.5);transform:scale(1.3)}
.rq-count{font:600 10.5px/1 var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}

/* THE STAGE. Its height is animated across a step change (measured in main.js), so the card never
   jumps when a 6-option step hands over to a 3-option one — and it returns to `auto` the moment the
   swap ends, so a growing textarea or a phone keyboard is never fighting a pinned pixel height. */
.rq-stage{position:relative;transition:height .26s cubic-bezier(.23,1,.32,1)}
.rq-stage.rq-swap{overflow:hidden}
.rq-pane[hidden]{display:none}
.rq-pane.is-out{position:absolute;left:0;right:0;top:0;pointer-events:none}
.rq-pane>.rq-help{margin-top:6px}
.rq-pane>.rq-grid,.rq-pane>.rq-list,.rq-pane>.rq-echo,.rq-pane>#rc-form{margin-top:14px}

/* Enter and exit MIRROR each other and the exit is the shorter of the two (emil; apple §7): a step
   forward pushes the old pane left and brings the new one in from the right, Back reverses both.
   A short blur rides along so the two panes read as ONE surface changing rather than two cards
   crossing (emil "blur to mask a crossfade"). */
@keyframes rqIn{from{opacity:0;transform:translateX(18px);filter:blur(5px)}to{opacity:1;transform:none;filter:blur(0)}}
@keyframes rqInBack{from{opacity:0;transform:translateX(-18px);filter:blur(5px)}to{opacity:1;transform:none;filter:blur(0)}}
@keyframes rqOut{from{opacity:1;transform:none;filter:blur(0)}to{opacity:0;transform:translateX(-14px);filter:blur(4px)}}
@keyframes rqOutBack{from{opacity:1;transform:none;filter:blur(0)}to{opacity:0;transform:translateX(14px);filter:blur(4px)}}
.rq-pane.rq-enter{animation:rqIn .24s cubic-bezier(.23,1,.32,1) both;animation-delay:var(--rqd,0ms)}
.rq-pane.is-out{animation:rqOut .16s cubic-bezier(.32,.72,0,1) both}
.rq-stage[data-dir="back"] .rq-pane.rq-enter{animation-name:rqInBack}
.rq-stage[data-dir="back"] .rq-pane.is-out{animation-name:rqOutBack}
.rq-pane.rq-enter .rq-stg{animation:rcWire .3s cubic-bezier(.23,1,.32,1) backwards;
  animation-delay:calc(var(--rqd,0ms) + var(--i,0)*50ms + 60ms)}
.rq-pane.rq-enter .rc-field input,
.rq-pane.rq-enter .rc-field textarea{animation:rcWireLine .62s ease-out backwards;
  animation-delay:calc(var(--rqd,0ms) + var(--i,0)*50ms + 60ms)}

.rq-q{font-family:var(--display);font-size:19px;font-weight:600;line-height:1.22;letter-spacing:-.2px;color:var(--text)}
.rq-q:focus,.rq-q:focus-visible{outline:none}
.rq-help{font:12.5px/1.45 var(--sans);color:var(--muted)}

/* `1fr` rows, not auto: "Lead gen & prospecting" is the one label that wraps to two lines, and with
   auto rows that made ONE band of the grid 14px taller than the two around it — six chips that are
   supposed to read as one set, in three different heights. Uniform costs 28px and looks made. */
.rq-grid{display:grid;gap:8px;grid-auto-rows:1fr}
.rq-g2{grid-template-columns:1fr 1fr}
.rq-list{display:flex;flex-direction:column;gap:8px}
.rq-opt{display:flex;align-items:center;gap:10px;width:100%;padding:11px 12px;
  border:1px solid var(--line);border-radius:12px;background:rgba(7,10,18,.72);color:var(--text);
  font:500 13px/1.25 var(--sans);letter-spacing:.01em;text-align:left;cursor:pointer;
  transition:border-color .16s ease,background .16s ease,box-shadow .16s ease,color .16s ease,
             transform .13s cubic-bezier(.23,1,.32,1)}
@media (hover:hover) and (pointer:fine){
  .rq-opt:hover{border-color:rgba(199,155,255,.42);background:rgba(18,16,30,.86)}
  .rq-opt:hover .rq-ico{opacity:1}
}
/* The press rides a class WE own, not `:active` — measured in beat E: Chromium drops `:active` the
   moment the element shifts a pixel under a held pointer, and an emulated/real TOUCH press never
   sets it at all. `:active` is kept alongside as the belt. */
.rq-opt.pressing,.rq-opt:active{transform:scale(.97)}
.rq-opt:focus-visible{outline:none;border-color:var(--cyan);box-shadow:0 0 0 3px rgba(199,155,255,.22)}
.rq-ico{flex:0 0 auto;width:20px;height:20px;color:var(--eyebrow);opacity:.85;transition:color .16s ease,opacity .16s ease}
.rq-ico svg{display:block;width:20px;height:20px}
.rq-lab{min-width:0}
.rq-opt[aria-pressed="true"],.rq-opt.is-on{
  border-color:rgba(255,204,110,.72);color:#fff;
  background:linear-gradient(180deg,rgba(150,110,242,.30),rgba(150,110,242,.15));
  box-shadow:0 0 0 1px rgba(255,204,110,.28),0 6px 20px rgba(150,110,242,.24)}
.rq-opt[aria-pressed="true"] .rq-ico,.rq-opt.is-on .rq-ico{color:var(--gold);opacity:1}
.rq-wide{justify-content:space-between;padding:13px 14px;font-size:14px}
.rq-tick{flex:0 0 auto;width:16px;height:16px;border-radius:50%;border:1px solid rgba(199,155,255,.28);
  transition:border-color .16s ease,background .16s ease,box-shadow .16s ease}
.rq-opt.is-on .rq-tick{border-color:var(--gold);background:var(--gold);box-shadow:0 0 10px rgba(255,204,110,.45)}
.rq-mid{justify-content:center;text-align:center;padding:14px 10px;font-size:14px;font-variant-numeric:tabular-nums}

/* ── THE FULL CATALOGUE, ONE TAP AWAY (R12 beat A) ───────────────────────────────────────
   The owner's question was "how do we not cap people at six services". The answer is progressive
   disclosure, not a longer grid and not a <select>: the six flagship tiles keep the visitor's whole
   attention, and a quiet dashed row under them opens the other fourteen as a chip cloud. Dashed
   because a dashed edge reads as "there is more behind this" where a solid one would read as a
   seventh option; and the chips are deliberately SMALLER than the tiles — a text pill, no icon —
   so the hierarchy survives being opened. Selected state is inherited from `.rq-opt[aria-pressed]`
   unchanged: one selection language for both tiers. */
.rq-more{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;margin-top:8px;
  padding:9px 12px;border:1px dashed rgba(199,155,255,.22);border-radius:12px;background:none;
  color:var(--muted);font:600 11.5px/1 var(--sans);letter-spacing:.05em;cursor:pointer;
  transition:color .18s ease,border-color .18s ease,background .18s ease,
             transform .13s cubic-bezier(.23,1,.32,1)}
@media (hover:hover) and (pointer:fine){
  .rq-more:hover{color:var(--text);border-color:rgba(199,155,255,.42);background:rgba(18,16,30,.55)}
}
.rq-more.pressing,.rq-more:active{transform:scale(.985)}
.rq-more:focus-visible{outline:none;border-color:var(--cyan);box-shadow:0 0 0 3px rgba(199,155,255,.22)}
.rq-more[aria-expanded="true"]{border-style:solid;border-color:rgba(199,155,255,.30);color:#b9c2d3}
/* the + turns into a − : two bars, and one of them rotates away as the cloud unfurls */
.rq-more-x{position:relative;flex:0 0 auto;width:9px;height:9px}
.rq-more-x::before,.rq-more-x::after{content:"";position:absolute;left:0;top:4px;width:9px;height:1px;
  background:currentColor;transition:transform .26s cubic-bezier(.23,1,.32,1)}
.rq-more-x::after{transform:rotate(90deg)}
.rq-more[aria-expanded="true"] .rq-more-x::after{transform:rotate(0deg)}
.rq-more-n{color:rgba(255,204,110,.9);letter-spacing:.04em}
.rq-more-n[hidden]{display:none}
/* height is written in px by main.js and released to `auto` when the travel ends — overflow goes
   back to visible with it, or the chips' 3px focus ring would be sheared off at the box edge. */
.rq-cloud{height:0;overflow:hidden;transition:height .28s cubic-bezier(.23,1,.32,1)}
.rq-cloud.is-done{overflow:visible}
.rq-cloud-in{display:flex;flex-wrap:wrap;gap:6px;padding-top:10px}
.rq-cloud .rq-chip-opt{display:inline-flex;width:auto;padding:7px 11px;border-radius:999px;
  font:500 12px/1.15 var(--sans);letter-spacing:.01em}

.rq-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px}
.rq-send-row{margin-top:2px}
.rq-send-row .rc-send{margin-top:0;flex:0 0 auto}
.rq-skip,.rq-back{border:0;background:none;padding:6px 2px;border-radius:6px;cursor:pointer;
  font:600 11px/1 var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--muted);
  transition:color .18s ease,transform .13s cubic-bezier(.23,1,.32,1)}
.rq-skip:hover,.rq-back:hover{color:var(--text)}
.rq-skip.pressing,.rq-back.pressing,.rq-skip:active,.rq-back:active{transform:scale(.96)}
.rq-skip:focus-visible,.rq-back:focus-visible{outline:none;color:var(--text);box-shadow:0 0 0 3px rgba(199,155,255,.22)}
.rq-next{flex:0 0 auto;border:0;border-radius:11px;padding:11px 18px;cursor:pointer;
  font:700 13px/1 var(--sans);color:#06121a;background:var(--cta);
  box-shadow:0 6px 20px rgba(150,110,242,.28);transition:transform .16s ease,box-shadow .2s ease}
.rq-next:hover{transform:translateY(-2px);box-shadow:0 10px 28px rgba(150,110,242,.42)}
.rq-next.pressing,.rq-next:active{transform:translateY(0) scale(.97);transition-duration:.1s}
.rq-next:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(199,155,255,.28)}

/* the answers, echoed on the contact step so the person can see what I am about to be told */
.rq-echo{display:flex;flex-wrap:wrap;gap:6px}
.rq-echo[hidden]{display:none}
.rq-chip{font:500 11.5px/1 var(--sans);letter-spacing:.02em;color:#cdb9f2;border-radius:999px;
  border:1px solid rgba(199,155,255,.26);background:rgba(150,110,242,.10);padding:6px 10px}

/* CONSENT TO CALL OR TEXT — small and quiet by the owner's call, and never attention-grabbing.
   The box alone is under the 24px tap floor, so the whole row is the label and its padding
   carries the target. The LABEL sentence is verbatim from the marketing site; the disclosure under
   it is not any more, and index.html carries the reason. */
/* R13 beat C, the owner: "the yes checkbox smaller — it's huge, it's just a checkbox, people don't
   read it fully anyway." So it stops presenting as a filled card the size of a field and becomes a
   row: less padding, a hairline instead of a fill, and the disclosure a size smaller. 61px → 52px
   here and 96 → 80 at 390, next to a message box that just tripled — which is where most of the
   "huge" actually lived. The tap target is still the whole row, and still over the 24px floor. */
/* the timings here are the ERROR moment's, not a hover's: the row's edge, the box's own ring and the
   .rc-err line below all have to land together or a blocked send reads as three separate events
   (apple-design 13, harmony). 160ms on the card's ease-out, the same as rcErrIn. */
.rq-consent{display:flex;align-items:flex-start;gap:9px;padding:7px 10px;border-radius:11px;
  border:1px solid transparent;background:none;cursor:pointer;
  transition:border-color .16s cubic-bezier(.23,1,.32,1),background .16s cubic-bezier(.23,1,.32,1),color .16s ease}
/* the fill and the hairline are GONE (R13 beat C): with a border and a background this row carried
   the same visual weight as a form field, which is what made it read "huge" beside four of them.
   The padding stays — it is the tap target — and the invisible border is what the invalid state
   lights up, so the error can appear without moving a single pixel of layout. */
@media (hover:hover) and (pointer:fine){.rq-consent:hover .rq-cl{color:#f0f4fb}}
/* The BOX is skinned, and that is a legibility decision rather than a decorative one: the UA default
   on this near-black card renders a white filled square, which made the one control that must never
   grab attention the brightest object in the lower half of the card (read at 1:1). It is still a real
   <input type="checkbox"> — form semantics, reset(), the label association and AT are all untouched;
   only its paint is ours. Ticked = the violet action colour under the gold ring, the card's own two
   accents and no third one. */
.rq-consent input{appearance:none;-webkit-appearance:none;flex:0 0 auto;width:15px;height:15px;margin-top:2px;
  display:grid;place-content:center;cursor:pointer;border-radius:5px;
  border:1px solid rgba(199,155,255,.34);background:rgba(7,10,18,.9);
  transition:border-color .16s ease,background .16s ease,box-shadow .16s ease}
/* R13 beat E: from scale(.4) + opacity 0, never from scale(0) — nothing in the world appears out of
   nothing, and the tick has a shape even before it has a size. */
.rq-consent input::before{content:"";width:9px;height:9px;background:#06121a;transform:scale(.4);opacity:0;
  clip-path:polygon(14% 47%,0 62%,38% 100%,100% 20%,85% 6%,36% 70%);
  transition:transform .16s cubic-bezier(.23,1,.32,1),opacity .12s ease}
.rq-consent input:checked{background:var(--cta);border-color:rgba(255,204,110,.6);box-shadow:0 0 0 1px rgba(255,204,110,.2)}
.rq-consent input:checked::before{transform:scale(1);opacity:1}
@media (hover:hover) and (pointer:fine){.rq-consent:hover input{border-color:rgba(199,155,255,.5)}}
.rq-consent input:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(199,155,255,.26)}
/* R13 beat B: the box is REQUIRED now, so it can be the thing that stopped the send — and it has to
   answer in the SAME language as every other field (gold, never red; the ring says "fix this", and
   the form's `input` listener clears it the instant it is ticked). Two rules on purpose: the first
   is the box itself and works everywhere; the second lifts the whole row's edge so the eye lands on
   the sentence, not just on a 15px square, and degrades to nothing where :has() is missing. */
.rq-consent input[aria-invalid="true"]{border-color:rgba(255,204,110,.75);box-shadow:0 0 0 3px rgba(255,204,110,.16)}
.rq-consent:has(input[aria-invalid="true"]){border-color:rgba(255,204,110,.5);background:rgba(255,204,110,.05)}
.rq-ctext{min-width:0}
/* the transition lives HERE, not on the row: beat C moved the hover onto this element's colour, and
   the row's own `color` transition never reached it — the hover was snapping. */
.rq-cl{display:block;font:500 13.5px/1.35 var(--sans);color:#dbe2ee;transition:color .16s ease}
/* small type wants a hair of positive tracking to stay legible (apple-design 15) — it is the one
   thing that buys the size back down without the line turning into a smudge. */
.rq-cd{display:block;margin-top:2px;font:10.5px/1.45 var(--sans);letter-spacing:.006em;color:#767f92}

/* ── THE SEND LEAVES THE PAGE ────────────────────────────────────────────────────────────
   A successful send hands off to thanks.html, and the handoff has to feel like one moment, not
   two pages. The card DISSOLVES — it goes out the way it came in (scale about the same pointer
   origin, per apple-design §7), at its own height so nothing reflows and nothing snaps; the blur
   is what stops you reading it as a card sliding away, and lets it read as the surface itself
   letting go (emil). The veil then takes the whole frame to black, and the thank-you page's
   renderer comes up out of that black.
   The exit is SHORTER than the birth (300ms against 540ms): the entrance is the flourish, the
   exit is the system getting out of the way. */
.rc-scrim.rc-sent .rc-card{
  transform:scale(.965);opacity:0;filter:blur(6px);
  transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .26s cubic-bezier(.23,1,.32,1),filter .3s cubic-bezier(.23,1,.32,1);
}
.rc-scrim.rc-sent{pointer-events:none}
#rc-veil{position:fixed;inset:0;z-index:400;background:#04060c;opacity:0;pointer-events:none;
  transition:opacity .38s cubic-bezier(.23,1,.32,1)}
#rc-veil.is-on{opacity:1;pointer-events:auto}   /* nothing is clickable once we are leaving */
#rc-veil[hidden]{display:none}

/* success state — the form is swapped out for a quiet thank-you with a drawn-in check.
   This is now the FALLBACK for a navigation that never happened, not the normal path. */
.rc-done{display:none;flex-direction:column;align-items:center;text-align:center;gap:8px;padding:14px 4px 6px}
.rc-card.done .rq-stage,.rc-card.done .rq-prog{display:none}
.rc-card.done .rc-eyebrow,.rc-card.done .rc-title,.rc-card.done .rc-sub{display:none}
.rc-card.done .rc-done{display:flex}
.rc-check{width:54px;height:54px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  background:radial-gradient(circle at 50% 40%,rgba(199,155,255,.22),rgba(150,110,242,.14));
  border:1px solid rgba(199,155,255,.42);color:#e2d2ff}
.rc-check svg{width:26px;height:26px;stroke-dasharray:26;stroke-dashoffset:26;animation:rcCheck .5s .1s ease forwards}
@keyframes rcCheck{to{stroke-dashoffset:0}}
.rc-done h3{font-family:var(--display);font-size:21px;font-weight:600;color:var(--text)}
.rc-done p{font:14px/1.5 var(--sans);color:var(--muted)}

@media (max-width:480px){
  .rc-card{padding:24px 20px 18px;border-radius:18px}
  .rc-title{font-size:26px}
  .rq-q{font-size:18px}
  .rq-opt{padding:10px 11px;font-size:12.5px}
  /* the `.rq-cl{font-size:13.5px}` that lived here is gone: R13 beat C made 13.5 the base size, so
     this override became a no-op — a rule that repeats its own default is a trap for the next
     reader, who will change one and expect the other to follow. */
}
/* 41px measured on the phone run — a hair under the 44px thumb target the primary action deserves.
   Gated on the POINTER, not the width: a landscape phone is 844px wide and still a thumb. */
@media (pointer:coarse){.rc-send{padding:15px 16px}}
/* SHORT VIEWPORT (landscape phone, and any window under ~520px tall). Read at 1:1 on 844x390: the
   constant header — eyebrow, a 26px title, its rule, a two-line sub — ate 230 of the 350 usable px
   and left ONE AND A HALF rows of options above the fold, so the first screen of the quiz looked
   like a wall of copy. The framing is what gives way; the QUESTION is the thing that has to be on
   screen. Same short-viewport family as the R9 a1 landscape fix, and no taller viewport can match. */
@media (max-height:520px){
  .rc-card{padding:18px 20px 16px}
  .rc-eyebrow{display:none}
  .rc-title{font-size:20px}
  .rc-title::after{width:34px;height:2px;margin-top:8px}
  .rc-sub{display:none}
  .rq-prog{margin:12px 0 10px}
  .rq-q{font-size:16px}
  .rq-row{margin-top:12px}
}
/* A landscape phone is SHORT AND WIDE. Fighting the height with smaller type only goes so far — the
   honest move is to spend the width it actually has: the card widens and the six chips lay out
   three-across, which removes a whole row (measured: 385px of content into a 350px card became
   324px, so nothing is clipped and nothing had to be deleted). */
@media (max-height:520px) and (min-width:700px){
  .rc-card{width:min(600px,100%)}
  .rq-g2{grid-template-columns:repeat(3,1fr)}
}
/* ── STEP 4 IS A RECTANGLE, NOT A COLUMN (R12 beat B) ───────────────────────────────────
   The owner's note: "the last page goes too long, top to bottom almost — make it rectangular."
   He is right — at 1920x1080 the contact step measured 440x848 in a 1080px viewport, a
   floor-to-ceiling strip of a card. On a real desktop pointer there is width to spend and no
   reason not to: the card widens to 760 and the four fields pair off. Everything is CSS off a
   `data-step` attribute (rqGo/rqReset) — no layout JS, and the DOM order is untouched, so the tab
   path is still Name → Email → Phone → Message → consent → Back → Send.
   Gated on min-width AND pointer:fine: a tablet or a landscape phone can never match, so steps
   1-3 and every touch device are byte-identical by construction. The card only widens at step 4,
   long after `summonFrom` has written its transform-origin, so the birth is untouched. */
@media (min-width:880px) and (pointer:fine){
  .rc-card{transition:transform .54s cubic-bezier(.16,1.01,.29,1),opacity .22s ease,
                      width .42s cubic-bezier(.23,1,.32,1)}
  .rc-card[data-step="4"]{width:min(760px,100%)}
  /* R13 beat C, the owner: "message text box BIGGER, and the yes checkbox smaller". The three short
     answers ride ONE row of three columns and the message takes the full width beneath them. That
     is what actually makes it bigger — 706x94 instead of 345x66, a shade over three times the area
     — and it dissolves the R12 ledge at the same time, because nothing sits beside the message any
     more to leave a hole under. DOM order is untouched, so the tab path still reads exactly as the
     eye does: Name → Email → Phone → Message → consent → Back → Send. */
  .rc-card[data-step="4"] #rc-form{display:grid;grid-template-columns:repeat(3,1fr);
    column-gap:14px;row-gap:13px;align-items:start}
  /* the message, consent, the send row and the error are decisions about the WHOLE form */
  .rc-card[data-step="4"] #rc-form>.rc-msg-f,
  .rc-card[data-step="4"] #rc-form>.rq-consent,
  .rc-card[data-step="4"] #rc-form>.rq-send-row,
  .rc-card[data-step="4"] #rc-form>.rc-err{grid-column:1/-1}
  /* an explicit height, not the `rows` attribute: R12 measured that `rows` wins over min-height, so
     the honest way to set this box is to set it. 94px is three lines of the field's own 14px/1.5. */
  .rc-card[data-step="4"] #rc-form textarea{height:94px;min-height:0}
}
@keyframes rqFade{from{opacity:0}to{opacity:1}}
@keyframes rqFadeOut{from{opacity:1}to{opacity:0}}
@media (prefers-reduced-motion:reduce){
  .rc-scrim,.rc-card{transition:none}
  .rc-card{transform:none}
  /* the cursor-birth is motion for its own sake — strip it entirely, keep a plain appearance */
  .rc-scrim.is-open .rc-card{animation:none}
  #rc-spark,#rc-spark.fire{display:none}
  .rc-check svg{animation:none;stroke-dashoffset:0}
  /* Reduced motion is a CROSSFADE, not nothing: the step still has to read as a change of state.
     Every selector below MATCHES the one it overrides (beat C's lesson — a media query adds no
     specificity, and `.rq-stage[data-dir=back] .rq-pane.rq-enter` is (0,3,0); a bare `.rq-pane`
     here would silently lose and the slide would still play). */
  .rq-stage{transition:none}
  .rq-pane.rq-enter{animation:rqFade .2s ease both}
  .rq-pane.is-out{animation:rqFadeOut .12s ease both}
  .rq-stage[data-dir="back"] .rq-pane.rq-enter{animation-name:rqFade}
  .rq-stage[data-dir="back"] .rq-pane.is-out{animation-name:rqFadeOut}
  .rq-pane.rq-enter .rq-stg{animation:none}
  .rq-pane.rq-enter .rc-field input,
  .rq-pane.rq-enter .rc-field textarea{animation:none}
  .rq-dot{transition:none}
  /* the catalogue still OPENS under reduced motion — it just arrives instead of unfurling
     (main.js writes the height directly rather than animating it) */
  .rq-cloud{transition:none}
  .rq-more-x::before,.rq-more-x::after{transition:none}
  /* R13 beat E, and it lives HERE rather than in the earlier reduced-motion block for the reason
     the comment above gives: both selectors are (0,2,0), so only source order decides, and the
     rules being overridden are written further up this file than that block. */
  .rq-consent input::before{transition-property:opacity}   /* the tick appears, it stops travelling */
  .rc-err:not([hidden]){animation:rcErrFade .16s ease both}
  /* the press scale STAYS: it is the acknowledgement of a tap, not vestibular motion, and it is
     the one piece of feedback a touch visitor has that the control heard them (beat E). Only the
     hover LIFT goes, since that is travel for its own sake. */
  .rq-next:hover{transform:none}
}

/* R9 a1 (landscape phone, owner-authorized): at 844x390 / 932x430 the fixed bottom-left
   Scroll/Top pill sat ON the chapter copy (hero AND hub — evidence agent/verify-out/r9/
   spot_land_hero.png + spot_land_hub.png), and the centred #lookhint ran through the copy's
   last lines: the desktop vh-based anchors collapse at a 390px-tall viewport. SHORT-VIEWPORT
   COARSE-POINTER only (real landscape phones): the copy lifts above the pill band and holds a
   left column, and #lookhint drops into the same bottom control strip as portrait (pill left,
   hint centred — the R7 P4 strip language). Desktop windows (pointer:fine) and every portrait
   phone (height > 520) can never match → those views are byte-identical by construction.
   The pill itself is untouched (one unified slot, the R7 P4 either-or law). */
@media (max-height:520px) and (pointer:coarse){
  .chapter{bottom:64px;max-width:min(480px,52vw)}
  #lookhint{bottom:24px;font-size:12px;gap:7px}
  #lookhint .lh-glyph{font-size:14px}
}

/* R9 a2 (owner-authorized): at <=380px the flex topbar squeezed both CTAs until their labels
   wrapped to TWO lines ("See it / live", "Work with / me" - agent/verify-out/r9/spot_360_hero.png).
   Two compact rungs hold the bar to ONE row: nowrap the labels and tighten chrome so
   logo + sound + both CTAs genuinely fit at 360 (and 320). Tap targets stay >=24px
   (snd 30/28px; CTAs ~31px tall). 381px+ is untouched. */
@media (max-width:380px){
  #topbar{padding:12px 10px}
  .logo{font-size:15px}
  .nav-cta{gap:5px}
  .navbtn{font-size:12px;padding:8px 10px;white-space:nowrap}
  .navbtn.snd{width:30px;height:30px}
  .navbtn.snd svg{width:15px;height:15px}
}
@media (max-width:340px){
  #topbar{padding:12px 8px}
  .logo{font-size:14px}
  .nav-cta{gap:4px}
  .navbtn{font-size:11px;padding:7px 8px}
  .navbtn.snd{width:28px;height:28px}
}
