/* =====================================================================
   SynergyAI — public SEO landing pages (styles/seo.css)

   SCOPE / SAFETY: this file is linked ONLY from the standalone marketing
   pages under /seo/*.html. It is never linked from index.html, so it
   cannot reach the shared app DOM and no mode-scoping is required. Do
   NOT link it from the app.

   Design tokens and the eight-theme background stage are lifted from
   about.html (which mirrors the app's universal.css liquid-glass recipe),
   so these pages read as native SynergyAI surfaces. about.html was deleted
   once its themes and floating controls were ported here — this file and
   src/seo-theme.js are now the only copy of that system outside the app.

   The one divergence that matters: a marketing page's LCP is a ranking
   factor, so the theme VIDEO is never in the markup. seo-theme.js attaches
   it after first paint, and the solid --bg-deep paints underneath instantly.
   ===================================================================== */

*, *::before, *::after { box-sizing: border-box; }

:root {
  /* glass recipe (from the app's universal.css, via about.html) */
  /* PURE LIQUID GLASS, NO DARK TINT (K, 2026-09-17: "get rid of the tint").
     A light white fill, a white hairline, blur + saturation, a soft inset top
     highlight and a light drop shadow — the app's own recipe. There is no dark
     underlay anywhere under page chrome: legibility comes from the blur (the
     theme behind a panel is smeared to low frequency), from type weight, and
     from a hairline text-shadow on light text (--text-lift). */
  /* THE COLOUR PAGE CHROME IS DRAWN IN. Every fill, hairline, tick, focus ring
     and sheen on a page surface is an alpha of this — one declaration, so the
     Boring theme can flip the whole system to black without a single
     !important and without a per-component override list. Controls and
     captions that sit ON a picture keep their literal white: they are over
     imagery, not over the page, in every theme. */
  --chrome-rgb: 255, 255, 255;
  --lg-fill: rgba(255, 255, 255, .06);
  --lg-fill-hover: rgba(255, 255, 255, .10);
  --lg-fill-raised: rgba(255, 255, 255, .085);
  --lg-stroke: rgba(255, 255, 255, .20);
  --lg-stroke-hi: rgba(255, 255, 255, .42);
  --lg-blur: blur(22px) saturate(1.30);
  /* text-bearing panels blur harder, so a busy theme reads as soft colour fields */
  --lg-blur-text: blur(34px) saturate(1.45);
  /* a tight halo (1px ring + 2px drop) plus a soft wide one: it keeps white type
     crisp over bright blurred colour without darkening any area of the panel */
  --text-lift: 0 0 1px rgba(0, 0, 0, .34), 0 1px 2px rgba(0, 0, 0, .42), 0 0 14px rgba(0, 0, 0, .18);
  --lg-shadow:
    0 1px 0 rgba(255, 255, 255, .55) inset,
    0 -1px 0 rgba(255, 255, 255, .16) inset,
    0 0 0 1px rgba(255, 255, 255, .08) inset,
    0 24px 60px -28px rgba(0, 0, 0, .38);

  --text: #ffffff;
  /* raised with the tint gone: muted copy now sits on bright blurred colour, not
     smoke. Hierarchy comes from size and weight, not from fading type out. */
  --text-muted: rgba(255, 255, 255, .96);
  --text-faint: rgba(255, 255, 255, .86);

  --r-sm: 10px; --r-md: 14px; --r-lg: 22px; --r-xl: 28px;
  --ease: cubic-bezier(.2, .7, .2, 1);

  --font-display: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-ui: "Space Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --maxw: 1140px;

  --step-0: 1rem;
  --step-1: clamp(1.15rem, 1.05rem + .5vw, 1.35rem);
  --step-2: clamp(1.4rem, 1.1rem + 1.4vw, 2rem);
  --step-3: clamp(2rem, 1.2rem + 3vw, 3.15rem);

  --space-md: clamp(24px, 3vw, 40px);
  --space-lg: clamp(36px, 4vw, 72px);
  --space-xl: clamp(52px, 5vw, 104px);

  /* Base theme. Each of the eight themes overrides these below; the fallback
     matters because #bgVideo reads --video-tone before a theme is stamped. */
  --bg-deep: #050a06;
  --video-tone: saturate(1.10) brightness(.78) contrast(1.06);

  /* The app's liquid-button orb palette (universal.css --orb-1..6). The haze
     is what makes a primary control read as SynergyAI rather than as a white
     pill from any design system. */
  --orb-1: #2c5a36; --orb-2: #1f4a2c; --orb-3: #3a8b5d;
  --orb-4: #4fa564; --orb-5: #27ae60; --orb-6: #6fc99a;
}

html { scroll-behavior: smooth; scroll-padding-top: 84px; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  margin: 0;
  text-shadow: var(--text-lift);
  min-height: 100vh;
  background: var(--bg-deep);
  color: var(--text);
  font-family: var(--font-display);
  font-weight: 500;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* Chrome's UA stylesheet sets text-shadow:none on form controls, so the lift
   never reached a <button> (the mode rails, the theme toggle). The exclusions
   below (.rs-sound.is-on, .rs-flag …) are more specific and still win. */
button, summary { text-shadow: inherit; }

/* ===================== BACKGROUND STAGE =====================
   Ported from about.html, which itself mirrors the app's .bg-stage system, so
   the eight themes render note-for-note the same here as they do in the product.
   Two deliberate divergences from about.html:
     • no scroll parallax (it needed a JS scroll handler; not worth the jank
       budget on a page whose job is to be fast), so inset:0 instead of the
       overscanned top:-4%/height:108%.
     • the video is attached AFTER first paint by seo-theme.js, never in markup,
       so a multi-MB theme video can never sit on the critical path. The solid
       --bg-deep paints immediately underneath it.
   The ::before/::after of .bg-stage belong to the THEMES (diffused and trippy-1
   use both, lsd uses ::before) — do not claim them for anything else. */
.bg-stage {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  overflow: hidden; background: var(--bg-deep);
}
#bgVideo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: none; filter: var(--video-tone);
}
html[data-theme="my-garden"] #bgVideo,
html[data-theme="her-garden"] #bgVideo,
html[data-theme="hanley-sky"] #bgVideo { display: block; }
/* No scrim over the stage (removed 2026-09-17, K: no tint on the background).
   Every block of copy rides a glass panel instead — the slabs, the hero panel,
   the CTA band, the footer — so nothing needs the whole theme darkened. */

/* ============================ LAYOUT ============================ */
.wrap { position: relative; z-index: 1; max-width: var(--maxw); margin: 0 auto; padding: 0 clamp(18px, 4vw, 32px); }
main { position: relative; z-index: 1; }

/* ============================ HEADER ============================ */
.site-head {
  position: sticky; top: 0; z-index: 30;
  /* liquid glass, not a dark bar */
  backdrop-filter: var(--lg-blur-text); -webkit-backdrop-filter: var(--lg-blur-text);
  background: var(--lg-fill);
  border-bottom: 1px solid rgba(var(--chrome-rgb), .16);
  box-shadow: 0 1px 0 rgba(var(--chrome-rgb), .14) inset, 0 10px 30px -18px rgba(0, 0, 0, .35);
}
/* The sticky header crosses white photos further down the page: a heavier blur
   and a firmer halo keep the nav and Launch app legible there — still no fill. */
.site-head {
  --text-lift: 0 0 1px rgba(0, 0, 0, .5), 0 1px 3px rgba(0, 0, 0, .5), 0 0 12px rgba(0, 0, 0, .3);
  -webkit-backdrop-filter: blur(44px) saturate(1.15); backdrop-filter: blur(44px) saturate(1.15);
}
.head-actions .btn-primary { border-color: rgba(var(--chrome-rgb), .72); }
.site-head .wrap { display: flex; align-items: center; gap: 20px; height: 64px; }
.brand {
  font-family: var(--font-ui); font-weight: 700; font-size: 1.05rem;
  letter-spacing: -.01em; color: var(--text); text-decoration: none; white-space: nowrap;
}
.site-nav { margin-left: auto; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.site-nav a {
  font-family: var(--font-ui); font-size: .88rem; font-weight: 600;
  color: var(--text-muted); text-decoration: none;
  padding: 7px 11px; border-radius: 999px; transition: background .2s var(--ease), color .2s var(--ease);
}
.site-nav a:hover { background: var(--lg-fill-hover); color: var(--text); }
.site-nav a[aria-current="page"] { color: var(--text); background: var(--lg-fill); }
@media (max-width: 860px) { .site-nav { display: none; } }

/* ============================ TYPE ============================ */
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 800; letter-spacing: -.022em; line-height: 1.12; margin: 0; }
h1 { font-size: var(--step-3); }
h2 { font-size: var(--step-2); }
h3 { font-size: var(--step-1); font-weight: 700; letter-spacing: -.014em; }
h4 { font-size: 1.02rem; font-weight: 700; letter-spacing: -.01em; }
p { margin: 0 0 1em; color: var(--text-muted); }
a { color: var(--text); }
strong { font-weight: 700; color: var(--text); }

.eyebrow {
  font-family: var(--font-ui); font-size: .76rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--text-faint);
  margin: 0 0 14px;
}
.lede { font-size: var(--step-1); color: var(--text-muted); max-width: 68ch; }

/* ===================== GLASS PRIMITIVE =====================
   The hero and every section heading used to sit as bare text directly on the
   animated stage. On My Garden or Pop that is a moving, high-contrast backdrop
   under white type — legible in a screenshot, tiring to actually read. Every
   block of prose now rides a glass panel, which is how about.html held up on
   all eight themes. */
.glass {
  position: relative;
  background: var(--lg-fill);
  border: 1.5px solid var(--lg-stroke);
  -webkit-backdrop-filter: var(--lg-blur-text); backdrop-filter: var(--lg-blur-text);
  box-shadow: var(--lg-shadow);
  border-radius: var(--r-lg);
}
.glass::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(180deg, rgba(var(--chrome-rgb), .14), rgba(var(--chrome-rgb), 0) 42%);
  opacity: .6; pointer-events: none;
}
.glass > * { position: relative; }
/* .sec-head is on a slab now; the .glass class on it in markup must not re-panel it. */
.sec-head.glass { background: none; border: none; border-bottom: 1px solid rgba(var(--chrome-rgb), .14); box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none; border-radius: 0; }
.sec-head.glass::before { display: none; }
.glass-raised {
  background: var(--lg-fill-raised);
  border-color: var(--lg-stroke-hi);
}

/* ============================ HERO ============================ */
.hero { padding: clamp(38px, 5.5vw, 68px) 0 var(--space-lg); }
.hero-panel { padding: clamp(28px, 4vw, 52px); border-radius: var(--r-xl); }
.hero h1 { max-width: 20ch; margin-bottom: 20px; }
.hero .lede { margin-bottom: 28px; }
/* 2026-09-13: measured 0px between the Launch app button and the tag row. */
.hero .cta-row + .tag-row { margin-top: 20px; }

/* ═══ THE CONSOLE HERO FITS THE FIRST SCREEN (2026-09-20) ═══
   K: "the new main page console is over sized, needs to fit on the screen
   without having to scroll … compacted just enough so it does not look
   broken". Every word stays — this is type scale, line-height and margins,
   not a copy rewrite. The budget is set by the shortest laptop we target
   (1366x768): 65px of sticky header, 23px of hero padding, then the panel
   itself must close 12px above the fold. Scoped to .hero-console, which is
   only the features hero: the nine section pages keep their own hero.

   Measured at 1366x768, first load, signed out:
     panel padding 22/20 · eyebrow 23 · headline 53 · lede 90 · CTAs 39
     + 16 to the console = 243, leaving ~400px for the console itself. */
@media (min-width: 950px) {
  /* 22 top, not 16: on a 28px --r-xl corner a 16px top inset put the
     eyebrow's cap height inside the arc while the left gutter was 34px, and
     that asymmetry is the one place the compaction read as squeezed rather
     than tightened. The 6px is paid for out of the lede's bottom margin
     below, so the fit budget does not move. */
  .hero-panel.hero-console { padding: 22px clamp(24px, 2.6vw, 34px); }
  .hero-console .eyebrow { font-size: .72rem; line-height: 1.3; margin-bottom: 6px; }
  /* one line at every laptop width: the measure, not the words, was what put
     this headline on two lines and cost 43px of the console below it */
  .hero-console h1 { font-size: clamp(1.8rem, 1rem + 1.9vw, 2.45rem); max-width: 34ch; margin-bottom: 8px; }
  .hero-console .lede { font-size: 1.02rem; line-height: 1.45; max-width: 104ch; margin-bottom: 8px; }
  .hero-console .cta-row { gap: 10px; }
  .hero-console .cta-row .btn { padding: 12px 22px; font-size: .9rem; line-height: 1.25; }
  .hero-console .demo-hub { margin-top: 12px; }
}

/* SHORT SCREEN, BIG TEXT (2026-09-20). The budget above tracks the copy until
   it reaches the console's 410px floor — and at the shortest laptop band with
   a 20px browser default it reaches it: 768 - 8.8rem - 208 = 384, the clamp
   holds 410, and the 26px the copy gained pushed the panel's bottom edge 5px
   below the fold. The floor cannot give way (below it the text surfaces lose
   their result line), so the air does. `max-height: 44rem` is a media query,
   so its rem is the BROWSER default, not the page's: it asks "is this screen
   short for this reader's text?" — 704px at a 16px root (our four target
   bands do not match, so nothing moves for a default reader), 792px at 18 and
   880px at 20, where they all do. Margins only: no type scale, no line-height,
   no top inset — the reader asked for bigger words and keeps every one of
   them at the size they asked for. Worth 22px, measured. */
@media (min-width: 950px) and (max-height: 44rem) {
  .hero-panel.hero-console { padding-bottom: 16px; }
  .hero-console .eyebrow { margin-bottom: 4px; }
  .hero-console h1 { margin-bottom: 4px; }
  .hero-console .lede { margin-bottom: 4px; }
  .hero-console .demo-hub { margin-top: 6px; }
}

/* THE CONSOLE IS THE PAGE ON A PHONE TOO (2026-09-21). At 390x844 the console
   started 663px down — 79% of the first screen was headline, lede and one
   button, and the lede alone ran 10-11 lines at the phone's own type scale.
   Nothing here is a copy rewrite: the same words at a phone's reading size
   and leading, which is ~100px, and it is what lifts the console far enough
   up the first screen for its own visibility gate to fire on arrival (see
   WATCH THE CONSOLE in seo-demo.js). */
@media (max-width: 600px) {
  .hero-console .lede { font-size: .95rem; line-height: 1.5; }
}

.cta-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-ui); font-size: .95rem; font-weight: 600;
  padding: 13px 26px; border-radius: 999px; text-decoration: none;
  border: 1px solid var(--lg-stroke); color: var(--text);
  background: var(--lg-fill);
  backdrop-filter: var(--lg-blur); -webkit-backdrop-filter: var(--lg-blur);
  box-shadow: var(--lg-shadow);
  transition: background .22s var(--ease), border-color .22s var(--ease), transform .22s var(--ease);
}
.btn:hover { background: var(--lg-fill-hover); border-color: var(--lg-stroke-hi); transform: translateY(-1px); }
/* Primary = pure liquid glass: raised fill, bright stroke, blur, sheen.
   No haze, no tint — colour belongs to the theme behind the glass, never to
   the control itself. */
.btn-primary {
  background: rgba(var(--chrome-rgb), .16);
  color: var(--text); border-color: rgba(var(--chrome-rgb), .55); font-weight: 700;
  box-shadow: var(--lg-shadow), 0 1px 0 rgba(var(--chrome-rgb), .35) inset;
}
.btn-primary:hover {
  background: rgba(var(--chrome-rgb), .22);
  color: var(--text); border-color: rgba(var(--chrome-rgb), .7);
}

/* ============================ SECTIONS ============================
   ONE GLASS SLAB PER SECTION. The first attempt glassed .sec-head and .card
   individually, which was verified on features.html — where every scrap of
   content happens to live inside a card — and therefore passed while nine
   other pages still had loose <h3> + <p> blocks sitting as raw text on the
   video. Making the section's own container the glass surface fixes the whole
   class at once: anything inside a section is on glass by construction,
   whatever shape the prose takes.

   Cards nested inside the slab are therefore plain fills with NO
   backdrop-filter of their own — a backdrop-filter inside a backdrop root
   cannot blur what is behind its parent anyway, so it would cost paint work
   and buy nothing. */
section { padding: calc(var(--space-lg) * .34) 0; scroll-margin-top: 84px; }
main > section > .wrap {
  padding: clamp(18px, 2.4vw, 30px);
  border-radius: var(--r-xl);
  background: var(--lg-fill);
  border: 1.5px solid var(--lg-stroke);
  -webkit-backdrop-filter: var(--lg-blur-text); backdrop-filter: var(--lg-blur-text);
  box-shadow: var(--lg-shadow);
}
/* These three carry their own panel already, so they opt out of the slab. */
/* .related is NOT in this list: unlike .hero and .cta-band it has no panel of
   its own, so opting it out left its heading and cards as bare text. */
main > section.hero > .wrap,
main > section.cta-band > .wrap {
  padding: 0; background: none; border: none; box-shadow: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
/* Loose prose sitting directly on a slab wants a readable measure and a little
   air between blocks — it is no longer inside a card that provided both. */
main > section > .wrap > h3 { margin-top: 30px; }
main > section > .wrap > h3:first-child { margin-top: 0; }
main > section > .wrap > p,
main > section > .wrap > ul,
main > section > .wrap > ol { max-width: 74ch; }

/* The heading no longer needs its own panel — it is already on the slab. */
.sec-head {
  margin-bottom: 18px;
  padding: 0 0 14px;
  border-bottom: 1px solid rgba(var(--chrome-rgb), .14);
}
.sec-head h2 { margin-bottom: 12px; }
.sec-head p { max-width: 70ch; }
.sec-head p:last-child { margin-bottom: 0; }
.related .sec-head { padding: 0 0 14px; margin-bottom: 16px; }
.related .sec-head h2 { margin-bottom: 0; }
.cta-band .sec-head { padding: 0; border: none; }
.sec-head {
  margin-bottom: var(--space-md);
  padding: clamp(20px, 2.6vw, 30px) clamp(22px, 3vw, 34px);
  border-radius: var(--r-lg);
}
.sec-head h2 { margin-bottom: 12px; }
.sec-head p { max-width: 70ch; }
.sec-head p:last-child { margin-bottom: 0; }


/* ============================ CARDS ============================ */
/* start-aligned: with folds inside cards, a closed card must hug its title —
   stretch would leave it tall and empty beside an open neighbour. */
.grid { display: grid; gap: 12px; align-items: start; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(258px, 1fr)); }

.card {
  padding: 17px 18px; border-radius: var(--r-md);
  border: 1px solid rgba(var(--chrome-rgb), .16);
  background: rgba(var(--chrome-rgb), .05);
}
.card h3 { margin-bottom: 10px; }
.card h4 { margin-bottom: 8px; }
.card p:last-child { margin-bottom: 0; }
.card > .tag-row { margin-top: 14px; }

a.card { display: block; text-decoration: none; transition: background .22s var(--ease), border-color .22s var(--ease), transform .22s var(--ease); }
a.card:hover { background: var(--lg-fill-hover); border-color: var(--lg-stroke-hi); transform: translateY(-2px); }
a.card .more { font-family: var(--font-ui); font-size: .87rem; font-weight: 600; color: var(--text); margin-top: 14px; display: inline-block; }

/* Engine / feature spec card — the deep-index unit. */
.spec { margin-top: 14px; border-top: 1px solid rgba(var(--chrome-rgb), .12); padding-top: 14px; }
.spec dl { margin: 0; display: grid; grid-template-columns: minmax(96px, auto) 1fr; gap: 7px 16px; }
.spec dt { font-family: var(--font-ui); font-size: .8rem; font-weight: 700; color: var(--text-faint); letter-spacing: .01em; }
.spec dd { margin: 0; font-size: .9rem; color: var(--text-muted); }

/* Feature bullet lists — every sub-feature is crawlable text. */
.feat-list { list-style: none; margin: 12px 0 0; padding: 0; }
.feat-list li {
  position: relative; padding-left: 18px; margin-bottom: 7px;
  font-size: .93rem; color: var(--text-muted);
}
.feat-list li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 5px; height: 5px; border-radius: 50%; background: rgba(var(--chrome-rgb), .5);
}
.feat-list li:last-child { margin-bottom: 0; }

.tag-row { display: flex; flex-wrap: wrap; gap: 7px; }
.tag {
  font-family: var(--font-mono); font-size: .74rem; font-weight: 600;
  padding: 4px 10px; border-radius: 999px;
  border: 1px solid rgba(var(--chrome-rgb), .18); color: var(--text-muted);
  background: rgba(var(--chrome-rgb), .04);
}

/* ============================ TABLE ============================ */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--r-lg); }
table {
  width: 100%; border-collapse: collapse; font-size: .9rem;
  border: 1px solid var(--lg-stroke); border-radius: var(--r-lg); overflow: hidden;
  background: var(--lg-fill);
  /* no backdrop-filter: the table sits inside its section slab (the backdrop
     root), so a blur here saw nothing — it only painted the caption as a second,
     lighter band with its own rounded corners */
}
/* inset like the cells, so the rounded corner never clips the first letter */
caption { text-align: left; font-size: .86rem; font-weight: 600; color: var(--text-faint); padding: 0 13px 10px; }
th, td { padding: 9px 13px; text-align: left; border-bottom: 1px solid rgba(var(--chrome-rgb), .10); }
th { font-family: var(--font-ui); font-size: .8rem; font-weight: 600; color: var(--text); letter-spacing: .02em; white-space: nowrap; }
td { color: var(--text-muted); }
tbody tr:last-child th, tbody tr:last-child td { border-bottom: none; }

/* ============================ FAQ ============================ */
/* Answers are plain visible DOM text — never collapsed — so there is no
   question about crawlability or rendered-content parity. */
.faq { display: grid; gap: 9px; }
.faq-item { padding: 14px 18px; border-radius: var(--r-md); border: 1px solid rgba(var(--chrome-rgb), .14); background: rgba(var(--chrome-rgb), .035); -webkit-backdrop-filter: var(--lg-blur); backdrop-filter: var(--lg-blur); }
.faq-item h3 { font-size: 1.02rem; margin-bottom: 8px; }
.faq-item p:last-child { margin-bottom: 0; }

/* ============================ CTA BAND ============================ */
.cta-band {
  /* the same floating gutter as the slabs — edge-to-edge glass read as a bar */
  width: min(var(--maxw), calc(100% - 2 * clamp(12px, 3vw, 32px)));
  margin: var(--space-lg) auto; padding: clamp(32px, 5vw, 56px);
  border-radius: var(--r-xl); text-align: center;
  border: 1px solid var(--lg-stroke);
  background: var(--lg-fill);
  backdrop-filter: var(--lg-blur-text); -webkit-backdrop-filter: var(--lg-blur-text);
  box-shadow: var(--lg-shadow);
}
.cta-band h2 { margin-bottom: 12px; }
.cta-band p { max-width: 56ch; margin-left: auto; margin-right: auto; }
.cta-band .cta-row { justify-content: center; margin-top: 22px; }

/* ============================ RELATED ============================ */
.related { padding-top: var(--space-md); }
.related h2 { font-size: var(--step-1); margin-bottom: 16px; }

/* ============================ FOOTER ============================ */
.site-foot {
  position: relative; z-index: 1; margin-top: var(--space-xl);
  padding: 0 0 clamp(16px, 3vw, 32px);
}
/* The footer's links used to sit bare on the stage under the scrim. With no
   scrim they ride a glass slab like every section. */
.site-foot > .wrap {
  width: min(var(--maxw), calc(100% - 2 * clamp(12px, 3vw, 32px)));
  padding: clamp(13px, 1.6vw, 18px) clamp(18px, 3vw, 28px);
  border-radius: var(--r-xl);
  background: var(--lg-fill);
  border: 1.5px solid var(--lg-stroke);
  -webkit-backdrop-filter: var(--lg-blur-text); backdrop-filter: var(--lg-blur-text);
  box-shadow: var(--lg-shadow);
}
/* ONE BAR, NOT A SITEMAP (2026-09-22). K: "that section seems excessive and
   unnecessary … just keep one with the privacy policy and all of that, one
   single bar … privacy, terms and contact us of some kind". The six-column
   link grid is gone from all ten pages; the studios are reached from the
   sticky header (seven of them), each page's own Keep-exploring cards, and
   the landing page's no-JS index. */
.foot-base { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; justify-content: space-between; font-size: .84rem; font-weight: 600; color: var(--text-faint); }
.foot-base nav { display: flex; flex-wrap: wrap; gap: 16px; }
.foot-base a { color: var(--text-faint); text-decoration: none; }
.foot-base a:hover { color: var(--text); }
@media (max-width: 560px) { .foot-base { justify-content: center; gap: 8px 14px; text-align: center; } }

/* ============================ A11Y ============================ */
:focus { outline: none; }
:focus-visible { outline: 2px solid rgba(var(--chrome-rgb), .85); outline-offset: 3px; border-radius: 6px; }
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  padding: 12px 20px; background: #fff; color: #06110c; border-radius: 0 0 var(--r-sm) 0;
  font-family: var(--font-ui); font-weight: 600; text-decoration: none;
}
.skip:focus { left: 0; }
/* The text lift is for light type on glass. Dark type on a light fill, and
   gradient-clipped type (transparent fill), must not carry it. */
.skip, .rs-flag, .rs-sound.is-on, .st-site-hero { text-shadow: none; }
/* Mode-rail focus ring drawn inside the item: the rail is a scroller and would
   clip a ring drawn outside the first item. */
.ag-item:focus-visible { outline-offset: -3px; }
/* visually hidden, still read */
.demo:not(.demo-showstage) .demo-made { display: none; }
.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; }


/* ===================== THEMES (ported verbatim from about.html) ===================== */
/* ---- theme base colours (exact) ---- */
html[data-theme="my-garden"]  { --bg-deep: #050a06; --video-tone: saturate(1.10) brightness(.78) contrast(1.06); }
html[data-theme="her-garden"] { --bg-deep: #050a06; --video-tone: saturate(1.10) brightness(.78) contrast(1.06); }
html[data-theme="hanley-sky"] { --bg-deep: #02030a; --video-tone: saturate(1.15) brightness(.82) contrast(1.10); }
html[data-theme="diffused"]   { --bg-deep: #301626; }
html[data-theme="boring"] { --bg-deep: #f4f4f2; }
html[data-theme="black"]  { --bg-deep: #000000; }
html[data-theme="trippy-1"]   { --bg-deep: #03000d; }
html[data-theme="lsd"]{ --bg-deep: #0a0010; }

/* ---- legibility without a tint (2026-09-17) ----
   The three bright stages (L?D, Pop, Dune) put mid-to-high luminance colour
   behind white type. No fill: opaque text and a firmer halo on all three, and
   on the two neon stages the glass stops BOOSTING saturation (a wider blur at
   saturate(.9) turns the neon into calm colour fields). The flat themes keep
   the softer hierarchy they already passed with. */
html[data-theme="lsd"], html[data-theme="trippy-1"], html[data-theme="diffused"] {
  --text-muted: #fff;
  --text-faint: rgba(var(--chrome-rgb), .93);
  --text-lift: 0 0 1px rgba(0, 0, 0, .55), 0 1px 2px rgba(0, 0, 0, .55), 0 0 10px rgba(0, 0, 0, .34);
}
html[data-theme="lsd"], html[data-theme="trippy-1"] {
  --lg-blur-text: blur(48px) saturate(.9);
  --lg-blur: blur(30px) saturate(.9);
}
html[data-theme="black"] {
  --text-muted: rgba(255, 255, 255, .88);
  --text-faint: rgba(255, 255, 255, .72);
}

/* ── BORING IS THE APP'S BORING: light paper, dark text (2026-09-20, per K) ──
   In the product Boring is the one theme that flips the text: white page, all
   copy black (styles/_overrides.css, ov:boring-theme, 2026-06-06). These pages
   rendered it dark, which is a drift from the thing they advertise. The app
   needs an !important sweep because it is flipping a 30,000-line stylesheet it
   does not own rule by rule; here every fill and hairline is already an alpha
   of --chrome-rgb and every string of copy an alpha of --text, so the whole
   theme is this one block of tokens. No stage art: the boring stage is flat. */
html[data-theme="boring"] {
  --chrome-rgb: 0, 0, 0;
  --bg-deep: #f4f4f2;
  --text: #0a0a0a;
  --text-muted: rgba(10, 10, 10, .72);
  /* .56 black never reached 4.5:1 against the page's own composited panels
     (a section panel is rgb(231), a card rgb(220), the console rgb(210)); .64
     clears AA everywhere and still sits a clear step under --text-muted. */
  --text-faint: rgba(10, 10, 10, .64);
  /* the lift is a halo that holds WHITE type over busy art; on black type on
     white paper it is a smudge, and there is no art behind it anyway */
  --text-lift: none;
  /* HALF the dark theme's alphas, because on paper these fills STACK: the
     hero panel (raised) with the console (fill) inside it composited to a 14%
     grey wash and the page read as grey cards, not glass. At .035/.06 a panel
     lands near rgb(236) and the console near rgb(228), and the .28 stroke
     carries the edge — which is what liquid glass on a light theme is. */
  --lg-fill: rgba(0, 0, 0, .035);
  --lg-fill-hover: rgba(0, 0, 0, .06);
  --lg-fill-raised: rgba(0, 0, 0, .06);
  --lg-stroke: rgba(0, 0, 0, .28);
  --lg-stroke-hi: rgba(0, 0, 0, .50);
  --lg-blur: blur(14px) saturate(1.05);
  --lg-blur-text: blur(14px) saturate(1.05);
  --lg-shadow:
    0 0 0 1px rgba(0, 0, 0, .04) inset,
    0 10px 30px -18px rgba(0, 0, 0, .22);
}
/* THE STICKY HEADER (2026-09-20). Everywhere else the header is pure blur
   over a 5% WHITE fill, so a dark photo scrolling under it only ever makes the
   white type more legible. Under Boring the same fill is 5% BLACK, so the 44px
   backdrop blur took the picture's colour and the header went dark under black
   type — the brand, all seven nav links and Launch app disappeared over the
   first render on ai-image-generator. A light base means the blur can only
   lighten, and the header stays frosted paper over anything. */
html[data-theme="boring"] .site-head { --text-lift: none; background: rgba(244, 244, 242, .82); }
/* Table rules are the thinnest hairline that survives on paper: 10% black on a
   panel is barely a line. Scoped to Boring so the dark themes keep their .10. */
html[data-theme="boring"] th,
html[data-theme="boring"] td { border-bottom-color: rgba(0, 0, 0, .16); }
/* A caption chip's hairline is a hair firmer on paper than on art (.24 white
   over a photo reads heavier than .24 black over a near-white card). */
html[data-theme="boring"] .rs-cap .rs-chip,
html[data-theme="boring"] .rs-flagnote .rs-chip:not(.rs-flag) { border-color: rgba(0, 0, 0, .28); }
/* Media frames drop their cinema shadow: a near-black drop on white paper
   reads as a bruise, not as depth. The pictures keep their own contrast. */
html[data-theme="boring"] .rs-stage,
html[data-theme="boring"] .rs-pf,
html[data-theme="boring"] .rs-scf,
html[data-theme="boring"] .rs-hero,
html[data-theme="boring"] .rs-frame,
html[data-theme="boring"] .st-art {
  box-shadow: 0 10px 26px -18px rgba(0, 0, 0, .40);
}
/* the blurred fill behind a contained picture is dimmed for a dark page */
html[data-theme="boring"] .rs-bgimg { filter: blur(26px) saturate(1.05) brightness(1.06); }

/* -- diffused: two layered organic-blob pseudo-elements (exact) -- */
html[data-theme="diffused"] .bg-stage { background: rgb(48, 22, 38); }
html[data-theme="diffused"] .bg-stage::before {
  content: ""; position: absolute; inset: -15%;
  background:
radial-gradient(ellipse 60% 50% at 30% 30%, rgba(220, 130, 80, .60) 0%, transparent 65%),
radial-gradient(ellipse 50% 60% at 75% 60%, rgba(180, 90, 160, .55) 0%, transparent 65%),
radial-gradient(ellipse 45% 45% at 70% 25%, rgba(240, 180, 100, .45) 0%, transparent 65%),
radial-gradient(ellipse 55% 55% at 20% 75%, rgba(140, 80, 180, .55) 0%, transparent 70%),
radial-gradient(ellipse 60% 50% at 50% 90%, rgba(220, 90, 100, .50) 0%, transparent 70%);
  background-size: 240% 220%, 200% 280%, 260% 180%, 180% 260%, 220% 240%;
  mix-blend-mode: screen; filter: blur(8px) saturate(1.25);
  animation: dDrift1 31s ease-in-out infinite alternate, dDrift1b 19s linear infinite, dDrift1c 43s ease-in-out infinite alternate;
  z-index: 1; transform-origin: 50% 50%;
}
html[data-theme="diffused"] .bg-stage::after {
  content: ""; position: absolute; inset: -15%;
  background:
radial-gradient(ellipse 70% 60% at 50% 40%, rgba(255, 160, 120, .45) 0%, transparent 65%),
radial-gradient(ellipse 50% 80% at 80% 70%, rgba(120, 100, 200, .45) 0%, transparent 65%),
radial-gradient(ellipse 60% 50% at 15% 50%, rgba(200, 100, 140, .50) 0%, transparent 70%),
radial-gradient(ellipse 40% 70% at 90% 20%, rgba(180, 140, 90, .42) 0%, transparent 70%),
radial-gradient(ellipse 80% 40% at 30% 85%, rgba(160, 90, 130, .45) 0%, transparent 70%);
  background-size: 280% 200%, 220% 280%, 320% 200%, 200% 240%, 260% 220%;
  mix-blend-mode: screen; filter: blur(12px) saturate(1.3);
  animation: dDrift2 37s ease-in-out infinite alternate, dDrift2b 23s linear infinite reverse, dDrift2c 51s ease-in-out infinite alternate;
  z-index: 2; transform-origin: 50% 50%;
}
@keyframes dDrift1 {
  0% { background-position: 0% 0%, 100% 0%, 0% 100%, 100% 100%, 50% 50%; }
  25% { background-position: 40% 60%, 60% 40%, 100% 20%, 0% 80%, 20% 20%; }
  50% { background-position: 100% 40%, 0% 60%, 100% 0%, 0% 40%, 80% 80%; }
  75% { background-position: 60% 100%, 40% 80%, 60% 60%, 80% 20%, 10% 60%; }
  100% { background-position: 0% 60%, 100% 20%, 0% 80%, 100% 60%, 100% 50%; }
}
@keyframes dDrift1b { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes dDrift1c { 0%, 100% { filter: blur(8px) saturate(1.20); } 50% { filter: blur(14px) saturate(1.45); } }
@keyframes dDrift2 {
  0% { background-position: 100% 100%, 0% 0%, 100% 50%, 0% 100%, 50% 0%; }
  20% { background-position: 60% 40%, 40% 100%, 80% 0%, 60% 20%, 10% 80%; }
  40% { background-position: 0% 20%, 60% 100%, 0% 40%, 100% 60%, 90% 30%; }
  60% { background-position: 80% 60%, 20% 20%, 40% 100%, 60% 100%, 20% 60%; }
  80% { background-position: 20% 100%, 80% 40%, 60% 20%, 40% 60%, 70% 90%; }
  100% { background-position: 40% 60%, 80% 20%, 60% 100%, 20% 40%, 50% 20%; }
}
@keyframes dDrift2b { from { transform: rotate(0deg) scale(1); } 50% { transform: rotate(120deg) scale(1.08); } to { transform: rotate(360deg) scale(1); } }
@keyframes dDrift2c { 0%, 100% { filter: blur(12px) saturate(1.30); } 33% { filter: blur(18px) saturate(1.50); } 66% { filter: blur(10px) saturate(1.20); } }

/* -- trippy-1: counter-rotating repeating-radial rings (exact) -- */
html[data-theme="trippy-1"] .bg-stage { background: radial-gradient(ellipse 100% 100% at 50% 50%, #0a001a 0%, #03000d 60%, #000005 100%); backface-visibility: hidden; }
html[data-theme="trippy-1"] .bg-stage::before,
html[data-theme="trippy-1"] .bg-stage::after {
  content: ""; display: block; position: absolute; top: 50%; left: 50%;
  width: 250vmax; height: 250vmax; margin: -125vmax 0 0 -125vmax;
  background: repeating-radial-gradient(circle at 50% 50%,
rgba(255, 30, 200, .55) 0, rgba(255, 30, 200, .55) 28px,
rgba(30, 230, 220, .55) 28px, rgba(30, 230, 220, .55) 56px,
rgba(255, 200, 30, .50) 56px, rgba(255, 200, 30, .50) 84px,
rgba(140, 30, 255, .55) 84px, rgba(140, 30, 255, .55) 112px);
  mix-blend-mode: screen; filter: contrast(1.20) saturate(1.20);
  pointer-events: none; transform-origin: 50% 50%; will-change: transform; backface-visibility: hidden;
}
html[data-theme="trippy-1"] .bg-stage::before { z-index: 1; animation: t1Forward 16s linear infinite; }
html[data-theme="trippy-1"] .bg-stage::after { z-index: 2; animation: t1ForwardReverse 16s linear infinite -8s; }
@keyframes t1Forward {
  0% { transform: scale(1.00) rotate(0deg); } 25% { transform: scale(1.10) rotate(90deg); }
  50% { transform: scale(1.38) rotate(180deg); } 75% { transform: scale(1.85) rotate(270deg); }
  100% { transform: scale(2.50) rotate(360deg); }
}
@keyframes t1ForwardReverse {
  0% { transform: scale(1.00) rotate(0deg); } 25% { transform: scale(1.10) rotate(-90deg); }
  50% { transform: scale(1.38) rotate(-180deg); } 75% { transform: scale(1.85) rotate(-270deg); }
  100% { transform: scale(2.50) rotate(-360deg); }
}

/* -- lsd: static image (exact) -- */
html[data-theme="lsd"] .bg-stage { background: #0a0010; }
html[data-theme="lsd"] .bg-stage::before {
  content: ""; position: absolute; inset: 0;
  background-image: url('/Thems/L%3FD.png'); background-size: cover; background-position: center center; background-repeat: no-repeat;
  filter: saturate(1.3) contrast(1.1); z-index: 1;
}

/* -- black: solid -- */
html[data-theme="black"] .bg-stage { background: #000; }
/* -- boring: plain --bg-deep (#f4f4f2), nothing else: light paper, no art -- */
html[data-theme="boring"] .bg-stage { background: #f4f4f2; }
html[data-theme="boring"] .bg-stage::before,
html[data-theme="boring"] .bg-stage::after { display: none; }

/* ===================== HEADER ACTIONS =====================
   Theme picker + Launch app, inside the sticky header's own .wrap.

   2026-09-13 (K: "the Launch app button needs to be better aligned"): these
   used to float position:fixed against the VIEWPORT corner. Measured at 1440px
   they sat 6px below the nav's centre line and 122px outside the content column
   that the hero and every slab align to. In the header's flex row they share its
   centre line and its right edge at every width, and still ride the sticky
   header. The nav and this group both take margin-left:auto, so the free space
   splits between them: nav in the middle, actions flush to the column edge. */
.head-actions {
  position: relative; margin-left: auto; flex: 0 0 auto;
  display: flex; align-items: center; gap: 10px;
}
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 999px; cursor: pointer; padding: 0;
  color: var(--text);
  background: var(--lg-fill);
  border: 1.5px solid var(--lg-stroke);
  -webkit-backdrop-filter: var(--lg-blur); backdrop-filter: var(--lg-blur);
  box-shadow: var(--lg-shadow);
  transition: background .22s var(--ease), transform .22s var(--ease), border-color .22s var(--ease);
}
.theme-toggle:hover {
  background: var(--lg-fill-hover);
  border-color: var(--lg-stroke-hi);
}
.theme-toggle:active { transform: scale(.93); }
/* The theme art, not a glyph — the same per-theme art the app uses, on the app's
   own cadence: a deliberate 8s turn that speeds to 3s on hover so the control
   telegraphs that it does something. universal.css:5619-5634. */
.theme-toggle .coin {
  width: 22px; height: 22px; display: block; border-radius: 50%;
  animation: coinSpin 8s linear infinite;
  transform-origin: 50% 50%; will-change: transform;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .18)) drop-shadow(0 0 10px rgba(var(--chrome-rgb), .16));
}
.theme-toggle:hover .coin { animation-duration: 3s; }
@keyframes coinSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .theme-toggle .coin { animation: none; } }
.head-actions .btn { padding: 9px 18px; font-size: .9rem; }

/* NO THEME NAME, EVER (2026-09-20, K: "dont show the name of the themes just
   let them change"). The caption that used to flash "Dune" under the coin on
   every click is gone — element, rule, and the JS that wrote it. The control
   still says what it does and which theme is on, in the button's accessible
   name, and #themeSay announces each change to a screen reader. Neither is
   painted anywhere. */

/* Seven nav links + brand + actions fit one row down to ~1100px; below that the
   links tighten rather than wrapping the fixed-height header onto two lines. */
/* A laptop screen is short: the sticky bar gives 6px back to the hero under it. */
@media (min-width: 950px) { .site-head .wrap { height: 58px; } }
@media (max-width: 1100px) and (min-width: 861px) {
  .site-head .wrap { gap: 12px; }
  .site-nav a { padding: 7px 8px; font-size: .84rem; }
}
@media (max-width: 860px) {
  .head-actions { gap: 7px; }
  .head-actions .btn { padding: 8px 14px; font-size: .84rem; }
  .theme-toggle { width: 36px; height: 36px; }
}

/* ═══════════════════ ANIMATED PRODUCT DEMO ═══════════════════
   A scripted recreation of the real surface — cursor moves in, types a real
   prompt, picks a chip, hits Create, and the result rises in. Built in
   CSS/JS rather than a screen recording: a recording of a 5-minute render is
   a multi-MB file that dates the moment the UI changes, whereas this is a few
   KB, stays crisp at any size, and inherits the live theme.

   It sits ABOVE the prose deliberately. Search engines need the words; people
   need to see the thing work. The demo answers "what is this" in four seconds
   and the copy underneath answers "what exactly can it do".

   Honesty rule: every string shown here is one the product actually uses. The
   placeholder is verbatim from index.html; the chips, tabs and output shape
   are the real ones. A demo that shows something the app cannot do is a false
   promise with better art direction. */
.demo {
  position: relative; overflow: hidden;
  border-radius: var(--r-xl);
  border: 1.5px solid var(--lg-stroke);
  /* No backdrop-filter and no dark fill here ON PURPOSE: this panel sits inside
     the hero-panel, which is already a backdrop root — a nested blur cannot
     see the real background, it just re-blurs the parent and stacks grey.
     A faint fill over the parent's glass lets the theme read through. */
  background: rgba(var(--chrome-rgb), .05);
  box-shadow: var(--lg-shadow);
  /* Extra bottom padding reserves a lane for the pinned "Try it yourself"
     chip, so it never sits on top of the result card. */
  /* One uniform panel across all six surfaces — a fixed stage the deliverable
     can take over, sized to sit fully inside the first viewport. */
  padding: 15px 15px 54px; height: 396px;
  font-family: var(--font-ui);
  user-select: none;
}
.demo-chrome { display: flex; align-items: center; gap: 7px; margin-bottom: 11px; }
.demo-dot { width: 9px; height: 9px; border-radius: 50%; background: rgba(var(--chrome-rgb), .18); }
.demo-title {
  margin-left: 6px; font-size: .74rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-faint);
}

.demo-tabs { display: flex; gap: 6px; margin-bottom: 10px; }
.demo-tab {
  font-size: .78rem; font-weight: 600; padding: 6px 13px; border-radius: 999px;
  color: var(--text-faint); border: 1px solid transparent;
}
.demo-tab.on { color: var(--text); background: var(--lg-fill); border-color: var(--lg-stroke); }

.demo-field {
  position: relative; border-radius: var(--r-md);
  border: 1px solid var(--lg-stroke); background: rgba(var(--chrome-rgb), .045);
  padding: 12px 14px; min-height: 64px;
  font-size: .95rem; line-height: 1.5; color: var(--text);
  transition: border-color .25s var(--ease), background .25s var(--ease);
}
.demo-field.focus { border-color: var(--lg-stroke-hi); background: rgba(var(--chrome-rgb), .075); }
.demo-ph { color: var(--text-faint); }
.demo-caret {
  display: none; width: 2px; height: 1.05em; margin-left: 1px; vertical-align: -2px;
  background: var(--text); animation: demoBlink 1s step-end infinite;
}
.demo-field.focus .demo-caret { display: inline-block; }
@keyframes demoBlink { 50% { opacity: 0; } }

.demo-chips { display: flex; gap: 6px; flex-wrap: wrap; margin: 10px 0 12px; }
.demo-chip {
  font-size: .76rem; font-weight: 500; padding: 6px 12px; border-radius: 999px;
  color: var(--text-muted); border: 1px solid rgba(var(--chrome-rgb), .16);
  background: rgba(var(--chrome-rgb), .04);
  transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
}
.demo-chip.on { color: var(--text); background: rgba(var(--chrome-rgb), .13); border-color: var(--lg-stroke-hi); }

.demo-go {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--font-ui); font-size: .9rem; font-weight: 700;
  padding: 11px 22px; border-radius: 999px;
  background: rgba(var(--chrome-rgb), .16);
  color: var(--text); border: 1px solid rgba(var(--chrome-rgb), .55);
  box-shadow: var(--lg-shadow), 0 1px 0 rgba(var(--chrome-rgb), .35) inset;
  transition: transform .12s var(--ease), background .25s var(--ease), border-color .25s var(--ease);
}
.demo-go.press { transform: scale(.95); }
.demo-go.busy { background: var(--lg-fill); border-color: var(--lg-stroke); }
.demo-spin {
  display: none; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid rgba(var(--chrome-rgb), .28); border-top-color: var(--text);
  animation: demoSpin .7s linear infinite;
}
.demo-go.busy .demo-spin { display: block; }
@keyframes demoSpin { to { transform: rotate(360deg); } }


/* the pointer */
.demo-cursor {
  position: absolute; left: 0; top: 0; width: 20px; height: 20px; z-index: 5;
  opacity: 0; pointer-events: none;
  filter: drop-shadow(0 2px 5px rgba(0,0,0,.55));
  transition: transform .45s cubic-bezier(.45,.05,.18,1), opacity .25s linear;
}
.demo-cursor svg { width: 100%; height: 100%; display: block; }
.demo-ripple {
  position: absolute; width: 34px; height: 34px; margin: -17px 0 0 -17px;
  border-radius: 50%; border: 2px solid rgba(var(--chrome-rgb), .75);
  opacity: 0; pointer-events: none; z-index: 4;
}
.demo-ripple.go { animation: demoRipple .55s var(--ease) forwards; }
@keyframes demoRipple {
  from { opacity: .85; transform: scale(.25); }
  to   { opacity: 0;   transform: scale(1.5); }
}

/* Reduced motion: hold the finished state. */
@media (prefers-reduced-motion: reduce) {
  .demo-cursor, .demo-ripple { display: none; }
  .demo-go::before { animation: none; }
}

/* Hero becomes two columns once there is room for the demo beside the copy. */
@media (min-width: 950px) {
  .hero-panel.with-demo {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 460px);
    gap: clamp(28px, 3.5vw, 52px); align-items: center;
  }
  .hero-panel.with-demo .hero-copy { min-width: 0; }
}
@media (max-width: 949px) { .demo { margin-top: 26px; } }


/* ── demo: per-surface deliverables + the try affordance ──────────────
   The card itself is the try button (.demo-link): the reaction the film is
   built to provoke is "I want to type in that box", so the box opens the app
   in that surface's mode. The pinned "Try it yourself" chip names the deal. */
.demo-link { cursor: pointer; transition: border-color .25s var(--ease), transform .25s var(--ease); }
.demo-link:hover { border-color: var(--lg-stroke-hi); transform: translateY(-2px); }
.demo-link:focus-visible { outline: 2px solid rgba(var(--chrome-rgb), .85); outline-offset: 3px; }
.demo-try {
  position: absolute; right: 14px; bottom: 12px;
  font-size: .72rem; font-weight: 700; letter-spacing: .04em;
  color: var(--text-muted); padding: 5px 11px; border-radius: 999px;
  border: 1px solid rgba(var(--chrome-rgb), .18); background: rgba(var(--chrome-rgb), .05);
  transition: color .2s var(--ease), background .2s var(--ease);
}
.demo-link:hover .demo-try { color: var(--text); background: rgba(var(--chrome-rgb), .12); }

/* image / video result thumb */

/* chat reply bubble — the text is the deliverable */

/* vibe: four lines of arriving code */

/* workspace: a scheduled run confirmed */


/* ── demo: real button anatomy ────────────────────────────────────────
   Chat's send is an icon button (ce-send-btn, aria-label "Send"), so an
   icon-send demo gets the arrow, not text. 2026-09-13 (K): the demos carry no
   price badge — the marketing pages name no coins anywhere. */
.demo-go-icon { padding: 11px 14px; border-radius: 50%; }
.demo-go-icon.busy { border-radius: 999px; padding: 11px 18px; }
.demo-send-arrow { width: 16px; height: 16px; display: block; }
.demo-go.busy .demo-send-arrow { display: none; }


/* ═══════════════ ACT TWO — the deliverable takes the stage ═══════════════
   After the load pulse the console recedes (blur + fade, still ghosted behind)
   and the surface's REAL result card fills the panel. This is the frame the
   whole film exists for, so it gets the hold. */
.demo-chrome, .demo-tabs, .demo-field, .demo-chips, .demo-go {
  transition: opacity .45s var(--ease), filter .45s var(--ease);
}
.demo-showstage .demo-chrome, .demo-showstage .demo-tabs, .demo-showstage .demo-field,
.demo-showstage .demo-chips, .demo-showstage .demo-go {
  opacity: .07; filter: blur(5px);
}
.demo-stage {
  position: absolute; inset: 15px 15px 46px; z-index: 3;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; text-align: center;
  opacity: 0; transform: translateY(18px) scale(.94); filter: blur(4px); pointer-events: none;
  transition: opacity .6s var(--ease), transform .6s var(--ease), filter .6s var(--ease);
}
.demo-showstage .demo-stage { opacity: 1; transform: none; filter: none; pointer-events: auto; }

/* track — the shared-music glass-card, in miniature */
.st-art {
  position: relative; width: 124px; height: 124px; border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, rgba(255,255,255,.14) 0 17%, rgba(255,255,255,.05) 17% 19%, transparent 19%),
    repeating-radial-gradient(circle at 50% 50%, #101410 0 2px, #1a201b 2px 4px);
  border: 1px solid rgba(255,255,255,.22);
  box-shadow: 0 16px 40px -14px rgba(0,0,0,.8), 0 0 0 6px rgba(255,255,255,.04);
}
.st-art::after {
  content: "MRB"; position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-ui); font-size: .62rem; font-weight: 700;
  letter-spacing: .14em; color: rgba(255,255,255,.85);
}
.demo-audio .st-art { animation: stSpin 3.2s linear infinite; }
@keyframes stSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .demo-audio .st-art { animation: none; } }
.st-title { font-size: 1.05rem; font-weight: 800; color: var(--text); letter-spacing: -.01em; }
.st-playrow { display: flex; align-items: center; gap: 10px; width: min(78%, 300px); }
.st-play {
  --glyph: var(--glyph-play); --glyph-size: 38%;
  display: block; padding: 0; margin: 0; font: inherit; appearance: none; -webkit-appearance: none;
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: 50%;
  background: var(--lg-fill-raised);
  border: 1.5px solid var(--lg-stroke-hi); position: relative;
}
.st-play::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(var(--chrome-rgb), .95);
  -webkit-mask: var(--glyph) center / var(--glyph-size) no-repeat;
  mask: var(--glyph) center / var(--glyph-size) no-repeat;
}
.st-play { cursor: pointer; }
.st-play:hover { border-color: rgba(var(--chrome-rgb), .85); }
.st-play.playing { --glyph: var(--glyph-pause); --glyph-size: 31%; }
.st-prog { flex: 1; height: 4px; border-radius: 2px; background: rgba(var(--chrome-rgb), .18); overflow: hidden; }
.st-prog i { transition: width .3s linear; }
.st-prog i { display: block; height: 100%; width: 34%; border-radius: 2px; background: rgba(var(--chrome-rgb), .85); }
.st-time { font-family: var(--font-mono); font-size: .72rem; color: var(--text-faint); }
.st-meta { font-size: .78rem; color: var(--text-faint); }
.st-dl {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-ui); font-size: .78rem; font-weight: 600;
  padding: 8px 16px; border-radius: 10px;
  background: rgba(var(--chrome-rgb), .06); border: 1px solid rgba(var(--chrome-rgb), .14);
  color: var(--text-muted);
}

/* image / video — a gallery tile, edge to edge */
.st-tile {
  position: relative; width: 100%; height: 100%; border-radius: 14px; overflow: hidden;
  box-shadow: 0 18px 44px -16px rgba(0,0,0,.8);
  background-size: cover; background-position: center;
}
/* The real render, playing muted inside the tile. Poster paints first; the
   clip is attached only when the deliverable takes the stage. */
.st-vid { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.st-bg {
  position: absolute; inset: -8%;
  background-size: cover; background-position: center;
  filter: blur(22px) saturate(1.15) brightness(.55); transform: scale(1.15);
}
.st-img {
  position: relative; display: block; height: 100%; width: 100%;
  object-fit: contain;
  filter: drop-shadow(0 10px 30px rgba(0,0,0,.6));
}
.st-tile-motion { background:
  radial-gradient(ellipse 80% 50% at 50% 22%, rgba(240,214,150,.85) 0%, transparent 55%),
  linear-gradient(170deg, #57724a, #23483a 55%, #101c17); }
.st-tile-scrim {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 26px 16px 12px;
  background: linear-gradient(180deg, transparent, rgba(4,7,6,.78));
  display: flex; flex-direction: column; gap: 2px; text-align: left;
}
/* on its own dark scrim, over the picture — NOT page copy, so it does not
   flip with the theme (var(--text) would make it black on dark under Boring) */
.st-tile-scrim b { font-size: .95rem; font-weight: 800; color: #fff; }
.st-tile-scrim i { font-style: normal; font-size: .74rem; color: rgba(255,255,255,.66); }
.st-bigplay {
  position: absolute; left: 50%; top: 44%; width: 54px; height: 54px; margin: -27px 0 0 -27px; padding: 0; cursor: pointer;
  border-radius: 50%; background: rgba(6,10,8,.55); border: 1.5px solid rgba(255,255,255,.65);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.st-bigplay::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(255,255,255,.95);
  -webkit-mask: var(--glyph-play) center / 36% no-repeat;
  mask: var(--glyph-play) center / 36% no-repeat;
}
.st-badge {
  position: absolute; top: 10px; right: 10px;
  font-family: var(--font-mono); font-size: .7rem; font-weight: 600; color: #fff;
  padding: 3px 8px; border-radius: 6px; background: rgba(6,10,8,.6);
}

/* reply — the thread, your bubble then the model's */
.st-bub {
  max-width: 88%; text-align: left; font-size: .85rem; line-height: 1.5;
  padding: 10px 14px; border-radius: 14px;
}
.st-bub-user {
  align-self: flex-end; color: var(--text);
  background: rgba(var(--chrome-rgb), .14); border: 1px solid rgba(var(--chrome-rgb), .2);
  border-bottom-right-radius: 4px;
}
.st-bub-ai {
  align-self: flex-start; color: var(--text);
  background: rgba(var(--chrome-rgb), .055); border: 1px solid rgba(var(--chrome-rgb), .14);
  border-bottom-left-radius: 4px;
}
.st-tag { align-self: flex-start; font-family: var(--font-ui); font-size: .7rem; color: var(--text-faint); padding-left: 4px; }

/* code — the vc-preview-pane, with the built app actually running */
.st-pane {
  width: 100%; flex: 1; min-height: 0; border-radius: 12px; overflow: hidden;
  display: flex; flex-direction: column;
  background: rgba(var(--chrome-rgb), .06); border: 1px solid rgba(var(--chrome-rgb), .18);
}
.st-pane-hd {
  font-family: var(--font-ui); font-size: .68rem; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-faint); text-align: left;
  padding: 8px 12px; border-bottom: 1px solid rgba(var(--chrome-rgb), .1);
}
/* run — the rt-card */
.st-runcard {
  width: min(100%, 330px); text-align: left; display: flex; flex-direction: column; gap: 9px;
  padding: 16px 18px; border-radius: 14px;
  background: rgba(var(--chrome-rgb), .06); border: 1px solid rgba(var(--chrome-rgb), .16);
}
.st-run-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.st-run-head b { font-size: .95rem; font-weight: 800; color: var(--text); }
.st-pill {
  font-family: var(--font-ui); font-size: .64rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: rgba(var(--chrome-rgb), .95); padding: 3px 9px; border-radius: 999px;
  background: rgba(var(--chrome-rgb), .14); border: 1px solid rgba(var(--chrome-rgb), .5);
}
.st-run-line { font-size: .8rem; color: var(--text-muted); }
.st-run-peek { color: var(--text-faint); }


/* ── viewport fit: the whole console visible without scrolling ── */
@media (min-width: 950px) {
  /* 2026-09-20: 3vh put 40px above the hero on a tall laptop and 23 on a short
     one. The console below it needs those pixels more than the hero needs air. */
  .hero { padding: clamp(14px, 2.2vh, 32px) 0 clamp(18px, 3vh, 36px); }
  .hero-panel.with-demo { padding: clamp(22px, 3vw, 40px); }
  .hero-panel.with-demo .hero-copy h1 { font-size: clamp(1.7rem, 1rem + 2vw, 2.5rem); }
}
@media (max-width: 949px) { .demo { height: 420px; } }


/* ═══════════════ THE MASTER SHOWCASE (agents demo) ═══════════════
   The flagship interactive: a Command Center in miniature. The roster of
   Master agents (the eight the page documents) sits in a sidebar; the film
   plays each agent's mission — brief, three stages ticking, deliverable —
   and CLICKING any name jumps straight to that agent's film. */
.demo-agents .demo-body {
  /* 178px fits the longest Master name ("The Campaign Commander") without
     an ellipsis — a truncated roster reads as a bug, not a sidebar. */
  display: grid; grid-template-columns: 178px minmax(0, 1fr); gap: 12px;
  height: calc(100% - 30px);
}
.ag-side { display: flex; flex-direction: column; gap: 4px; overflow-y: auto; padding-right: 2px; }
.ag-item {
  font-family: var(--font-ui); font-size: .72rem; font-weight: 600; text-align: left;
  color: var(--text-faint); padding: 7px 10px; border-radius: 9px; cursor: pointer;
  background: transparent; border: 1px solid transparent; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
}
.ag-item:hover { color: var(--text); background: rgba(var(--chrome-rgb), .06); }
.ag-item.on {
  color: var(--text);
  background: var(--lg-fill-raised);
  border-color: var(--lg-stroke-hi);
}
/* min-height: 0 as well as min-width: 0 — .ag-main is a grid item whose
   automatic minimum size (brief + beats + result + gaps) otherwise grows past
   its own minmax(0, 1fr) track, and the surface's overflow:hidden then shaved
   the bottom ~10px off the artifact's frame. .demo-tabsmode .ag-main already
   carried this; the rail surfaces were simply never given it. */
.ag-main { display: flex; flex-direction: column; gap: 9px; min-width: 0; min-height: 0; }
.ag-role { font-size: .7rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); text-align: left; }
.ag-brief {
  border-radius: var(--r-md); border: 1px solid var(--lg-stroke);
  background: rgba(var(--chrome-rgb), .045); padding: 10px 12px; min-height: 42px;
  font-size: .85rem; line-height: 1.45; color: var(--text); text-align: left;
}
.ag-steps { display: flex; flex-direction: column; gap: 6px; }
.ag-step {
  display: flex; align-items: center; gap: 9px; text-align: left;
  font-size: .8rem; color: var(--text-faint);
  opacity: 0; transform: translateX(-8px); visibility: hidden;
  transition: opacity .3s var(--ease), transform .3s var(--ease), color .3s var(--ease), visibility 0s linear .3s;
}
/* hidden beats and results are hidden from screen readers too, not just faded:
   the last mode's text must never be read out beside the next mode's name */
.ag-step.in { opacity: 1; transform: none; visibility: visible; transition-delay: 0s; }
.ag-step.done { color: var(--text-muted); }
/* THE STEP TICK (K, 2026-09-21: "the tickmarks … are horrible and pixlated").
   The check used to be two 1.6px borders on a 6x3 box, rotated -45deg and
   placed by hard-coded left/top per breakpoint. A rotated hairline border has
   no hinting: at DPR 1, at a fractional DPR, or under any transform its two
   arms land between device pixels and fuzz into a grey smudge, and each new
   tick size needed its offsets re-guessed (two of them existed already).
   It is now ONE SVG glyph painted through a mask: a real stroked path with
   round caps and joins, scaled by the browser's own path rasteriser, which is
   resolution-independent by construction. `inset: 0` centres it on the ring's
   padding box, so the SAME rule is correct at 16px, at 20px and at 200% zoom
   with no per-size offsets at all. The colour is still a --chrome-rgb token
   (the mask only decides WHERE the paint lands), so Boring stays correct. */
.ag-tick {
  width: 16px; height: 16px; flex: 0 0 16px; border-radius: 50%;
  border: 1.5px solid rgba(var(--chrome-rgb), .28); position: relative;
  transition: border-color .25s var(--ease), background .25s var(--ease);
}
.ag-tick::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(var(--chrome-rgb), .95);
  -webkit-mask: var(--tick-glyph) center / 82% no-repeat;
  mask: var(--tick-glyph) center / 82% no-repeat;
  opacity: 0; transform: scale(.72);
  transition: opacity .25s var(--ease), transform .25s var(--ease);
}
.ag-step.done .ag-tick { border-color: rgba(var(--chrome-rgb), .75); background: rgba(var(--chrome-rgb), .16); }
.ag-step.done .ag-tick::after { opacity: 1; transform: none; }
.ag-out {
  margin-top: auto; display: flex; align-items: center; gap: 9px; text-align: left;
  padding: 11px 13px; border-radius: var(--r-md);
  background: var(--lg-fill-raised);
  border: 1px solid var(--lg-stroke-hi);
  opacity: 0; transform: translateY(10px); visibility: hidden;
  transition: opacity .4s var(--ease), transform .4s var(--ease), visibility 0s linear .4s;
}
.ag-out.in { opacity: 1; transform: none; visibility: visible; transition-delay: 0s; }
.ag-out b { font-size: .8rem; font-weight: 700; color: var(--text); }
.ag-out .st-pill { flex: 0 0 auto; }
@media (max-width: 949px) {
  .demo-agents .demo-body { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .ag-side { flex-direction: row; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; padding-bottom: 4px; }
  .ag-item { flex: 0 0 auto; }
}

/* ── vibe act two: the built app is a live analytics dashboard ── */
.st-chart {
  flex: 1; display: flex; align-items: flex-end; gap: 7px;
  padding: 14px 16px 12px; min-height: 0;
}
.st-chart i {
  flex: 1; border-radius: 4px 4px 2px 2px;
  background: linear-gradient(180deg, rgba(var(--chrome-rgb), .85), rgba(var(--chrome-rgb), .2));
  min-height: 8%;
}
.st-chart-head {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 10px 14px 0; text-align: left;
}
.st-chart-head b { font-size: .8rem; font-weight: 700; color: var(--text); }
.st-chart-head i { font-style: normal; font-family: var(--font-mono); font-size: .72rem; color: rgba(var(--chrome-rgb), .85); }

/* ── workspace act two: the routine card carries its ticked stages ── */
.st-run-stage { display: flex; align-items: center; gap: 8px; font-size: .78rem; color: var(--text-muted); }
.st-run-stage::before {
  content: ""; width: 13px; height: 13px; flex: 0 0 13px; border-radius: 50%;
  background: rgba(var(--chrome-rgb), .14); border: 1px solid rgba(var(--chrome-rgb), .5);
}


/* ═══════════════ BITE-SIZE FOLDS ═══════════════
   The loose h3-plus-prose runs fold into tappable rows at runtime
   (seo-theme.js): the visible layer of a section becomes its INDEX — every
   feature title scannable at a glance — and the paragraphs are one tap away.
   All text stays in the DOM, so nothing changes for search. Tables, grids
   and demos never fold: they are the showing, prose is the telling. */
.fold { border-bottom: 1px solid rgba(var(--chrome-rgb), .10); }
.fold:last-of-type { border-bottom: none; }
.fold > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 12px;
  padding: 11px 2px;
}
.fold > summary::-webkit-details-marker { display: none; }
/* the most-repeated glyph on the site (7-14 folds plus 8 FAQ items a page),
   and the same rotated-border construction as the ticks were. A stroked
   chevron through a mask instead — and the open state rotates the whole
   masked box by a flat 180deg, which is pixel-exact, instead of rotating a
   border pair to a new diagonal. */
.fold > summary::after {
  content: ""; margin-left: auto; flex: 0 0 auto;
  width: 11px; height: 7px;
  background: var(--text-faint);
  -webkit-mask: var(--glyph-chev-d) center / contain no-repeat;
  mask: var(--glyph-chev-d) center / contain no-repeat;
  transition: transform .25s var(--ease);
}
.fold[open] > summary::after { transform: rotate(180deg); }
.fold > summary h3 { margin: 0; font-size: .97rem; font-weight: 700; letter-spacing: -.01em; }
.fold > summary:hover h3 { color: var(--text); }
.fold-body { padding: 2px 2px 16px; }
.fold-body > p, .fold-body > ul, .fold-body > ol { max-width: 74ch; }
.fold-body > :last-child { margin-bottom: 0; }


/* The Workspace page console carries the longest beats (Automations wraps every
   one onto two lines in the 460px hero column), so its Done card ran 19px into
   the "Open the Workspace" chip's lane. Tighter rhythm on that one console only. */
@media (min-width: 950px) {
  .demo[data-demo="workspace"] .ag-main { gap: 6px; }
  .demo[data-demo="workspace"] .ag-steps { gap: 3px; }
  .demo[data-demo="workspace"] .ag-step { font-size: .76rem; line-height: 1.4; }
  .demo[data-demo="workspace"] .ag-brief { padding: 8px 11px; line-height: 1.4; }
  .demo[data-demo="workspace"] .ag-out { padding: 8px 11px; }
  .demo[data-demo="workspace"] .ag-out b { line-height: 1.35; }
}

/* ── vibe act two: a living website in the preview. Everything loops and
   moves — it depicts an ANIMATED site, so stillness would be the lie.
   Content colours belong to the built site, not to SynergyAI chrome. ── */
.st-site-body {
  position: relative; flex: 1; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; background: #0a0b12; padding: 12px;
}
.st-orb {
  position: absolute; width: 130px; height: 130px; border-radius: 50%;
  filter: blur(34px); opacity: .55; pointer-events: none;
}
.st-orb-a { background: #6ee7ff; left: -30px; top: -20px; animation: orbDriftA 7s ease-in-out infinite alternate; }
.st-orb-b { background: #b07bff; right: -34px; bottom: -30px; animation: orbDriftB 9s ease-in-out infinite alternate; }
@keyframes orbDriftA { to { transform: translate(46px, 34px) scale(1.25); } }
@keyframes orbDriftB { to { transform: translate(-40px, -30px) scale(1.15); } }
.st-site-nav { position: absolute; top: 10px; left: 12px; display: flex; gap: 4px; }
.st-site-nav i { width: 5px; height: 5px; border-radius: 50%; background: rgba(255,255,255,.35); }
.st-site-hero {
  position: relative; font-family: var(--font-display); font-weight: 800;
  font-size: 1.9rem; letter-spacing: .34em; padding-left: .34em;
  background: linear-gradient(90deg, #6ee7ff, #b07bff, #ff8bd4, #6ee7ff);
  background-size: 300% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: heroSheen 4s linear infinite;
}
@keyframes heroSheen { to { background-position: 300% 0; } }
.st-site-sub {
  position: relative; font-family: var(--font-ui); font-size: .64rem;
  letter-spacing: .3em; text-transform: uppercase; color: rgba(255,255,255,.6);
}
.st-site-cta {
  position: relative; overflow: hidden; margin-top: 4px;
  font-family: var(--font-ui); font-size: .68rem; font-weight: 700; color: #eaf4ff;
  padding: 6px 16px; border-radius: 999px; border: 1px solid rgba(255,255,255,.35);
}
.st-site-cta::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 40%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.45), transparent);
  left: 0; transform: translateX(-150%);
  animation: ctaSweep 2.6s var(--ease) infinite;
}
/* transform, not left: the sweep stays on the compositor instead of forcing layout every frame */
@keyframes ctaSweep { 60%, 100% { transform: translateX(300%); } }
/* Decoration runs only while it is seen: the Vibe preview's loops pause while
   the stage is hidden (typing) and whenever the console is halted off screen. */
.demo:not(.demo-showstage) .demo-stage *, .demo:not(.demo-showstage) .demo-stage *::after,
.demo.is-halted *, .demo.is-halted *::after { animation-play-state: paused !important; }
.st-site-cards { position: relative; display: flex; gap: 7px; margin-top: 8px; }
.st-site-cards i {
  width: 44px; height: 26px; border-radius: 6px;
  background: rgba(255,255,255,.09); border: 1px solid rgba(255,255,255,.2);
  animation: cardFloat 3s var(--ease) infinite;
}
.st-site-cards i:nth-child(2) { animation-delay: .4s; }
.st-site-cards i:nth-child(3) { animation-delay: .8s; }
@keyframes cardFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
@media (prefers-reduced-motion: reduce) {
  .st-orb, .st-site-hero, .st-site-cta::after, .st-site-cards i { animation: none; }
}


/* ── TABS-ON-TOP roster (Visual Studio, Music Studio) ──────────────────
   The agent rails are a left sidebar because Command Center and Workspace
   are left-nav surfaces. These three are not: the app puts a mode toggle
   ABOVE the composer (index.html .ms-mode-toggle), so the demo does too —
   which also gives the real artifact the full width of the panel. */
.demo-tabsmode .demo-body { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); gap: 10px; }
.demo-tabsmode .ag-side {
  flex-direction: row; overflow-x: auto; overflow-y: hidden;
  gap: 5px; padding-bottom: 2px; scrollbar-width: none;
}
.demo-tabsmode .ag-side::-webkit-scrollbar { display: none; }
/* Five tabs overflow the row: fade both edges so the row reads as scrollable,
   and pad them so a tab scrolled to the edge (and its focus ring) stays whole. */
.demo-results .ag-side {
  padding: 4px 12px 4px; margin: -4px -4px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent);
  mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent);
}
.demo-tabsmode .ag-item { flex: 0 0 auto; }
.demo-tabsmode .ag-role { display: none; }
.demo-tabsmode .ag-main { min-height: 0; }

/* the artifact inside a roster */
.ag-media {
  flex: 1; min-height: 0; margin-top: 2px;
  /* the artifact's own clip: it can never reach the result line under it */
  overflow: hidden;
  /* Two layers share ONE grid cell so a mode change crossfades instead of
     blanking the stage (seo-demo.js, setMedia). Grid, not absolute layers, on
     purpose: this box is `flex: 1` on most surfaces but `flex: 0 1 auto` for
     the song, and absolutely-positioned children would collapse that one to
     nothing. */
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
  opacity: 0; transform: translateY(10px); visibility: hidden;
  transition: opacity .45s var(--ease), transform .45s var(--ease), visibility 0s linear .45s;
}
.ag-media:empty { display: none; }
.ag-layer {
  grid-area: 1 / 1; min-width: 0; min-height: 0;
  /* One stretched cell: everything the layer can hold (a results stage, Pip's
     legacy tile, the song's player) fills it exactly as it did when the layer
     was a plain block — but a single-artifact stage can now opt out of the
     stretch and take its own shape instead (THE FRAME HUGS THE ARTIFACT). */
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
  transition: opacity .22s var(--ease), transform .22s var(--ease);
}
.ag-layer > * { grid-area: 1 / 1; }
/* THE PRE-REVEAL STATE. The artifact beside a brief must be THAT brief's
   artifact — holding the previous one while the next brief typed paired one
   mode's prompt with another mode's picture. The new one arrives at the mode
   change instead, quiet: dimmed and soft, with every label, badge and control
   held back until the reveal. Nothing here moves, counts up or spins — it must
   never read as a render happening now. It is the same preset file, waiting. */
.ag-layer.is-pre { opacity: .52; }
.ag-layer > .rs-stage, .ag-layer > .st-tile, .ag-layer > .ag-track, .ag-layer > .rs-fit { transition: filter .22s var(--ease); }
.ag-layer.is-pre > .rs-stage, .ag-layer.is-pre > .st-tile, .ag-layer.is-pre > .ag-track, .ag-layer.is-pre > .rs-fit { filter: blur(2.5px) saturate(.6); }
/* .rs-ghost is in this list too: without it the empty next scene is a dashed
   box with a picture in it and NO words, for the whole ~1.9s the brief types —
   the one reading this state exists to prevent. */
.ag-layer.is-pre .rs-chip, .ag-layer.is-pre .rs-sound, .ag-layer.is-pre .rs-scenebar,
.ag-layer.is-pre .rs-empty, .ag-layer.is-pre .rs-trim, .ag-layer.is-pre .rs-ghost,
.ag-layer.is-pre .rs-inset > span, .ag-layer.is-pre .rs-playbtn,
.ag-layer.is-pre .st-bigplay, .ag-layer.is-pre .st-badge, .ag-layer.is-pre .st-play { opacity: 0; }
/* the incoming artifact is laid out at zero before it crosses in; the outgoing
   one holds the stage until it has (a crossfade, never a gap) */
.ag-layer.is-enter { opacity: 0; transform: translateY(8px); }
.ag-layer.is-out { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  /* EXACTLY the selector list of the `transition: filter` rule above, or a
     visitor who asked the OS for less motion still gets the pre→reveal blur.
     It desynced when the fit box was added: every single-artifact result is
     now .ag-layer > .rs-fit > .rs-stage, so the old list reached none of them,
     and .ag-layer > .st-tile had already stopped matching anything. */
  .ag-layer, .ag-layer > .rs-stage, .ag-layer > .st-tile, .ag-layer > .ag-track, .ag-layer > .rs-fit { transition: none; }
}
/* Hidden media is invisible to the pointer and the keyboard too (seo-demo.js
   also sets inert) — a Sound button must never be pressable before it shows. */
.ag-media.in { opacity: 1; transform: none; visibility: visible; transition-delay: 0s; }
.ag-media .st-tile { height: 100%; min-height: 118px; }
/* With an artifact on screen the beats compress so the picture leads. */
.has-media .ag-steps { gap: 4px; }
.has-media .ag-step { font-size: .74rem; }
.has-media .ag-brief { min-height: 0; padding: 8px 11px; font-size: .8rem; }
.has-media .ag-out { padding: 8px 11px; }
.has-media .ag-out b { font-size: .74rem; }

/* the track, laid out for the rail */
.ag-track { display: flex; align-items: center; gap: 14px; height: 100%; padding: 4px 2px; }
.ag-track .st-art { width: 74px; height: 74px; flex: 0 0 74px; }
.ag-track .st-art::after { font-size: .5rem; }
.ag-track-meta { flex: 1; min-width: 0; }
.ag-track .st-playrow { width: 100%; }


/* ═══════════════ READABLE BUILDING BLOCKS (2026-09-13) ═══════════════
   K: "make the UI more intuitive, easy to read and more concise". The pages
   below the hero were ~52,000 words of prose and spec lists. Sections are now
   built from a small, fixed vocabulary — the SHOWING layer — with depth kept in
   authored <details class="fold"> rows, so every fact stays in the DOM for
   search while the first read is a scan, not a study.

     .glance   the numbers that matter, one sweep
     .points   one idea per tile: bold title + one short line
     .points-check   the same, for plain promises (pricing, privacy)
     .steps    how it works, in order
     .deep     the tap-to-open depth layer under a section
     .note     a short honest disclosure line
   Never add a class a page needs outside this list — extend it here instead. */

/* the slab's heading sits on the same edge as the content under it */
/* Below ~1200px the slabs used to run edge to edge (the hero panel touched both
   sides of the screen at 880px). A gutter keeps every panel floating on the theme. */
main > section > .wrap { width: min(var(--maxw), calc(100% - 2 * clamp(12px, 3vw, 32px))); padding: clamp(20px, 3vw, 36px); }
main > section > .wrap > .sec-head { padding: 0 0 16px; margin-bottom: 18px; border-radius: 0; }
.sec-head h2 { margin-bottom: 8px; }
.sec-head p { font-size: 1.04rem; max-width: 62ch; }
.sec-head .eyebrow { margin-bottom: 8px; }
main > section > .wrap > :where(.glance, .points, .steps, .sc, .grid, .table-scroll, .deep, .faq, .note, .cta-row)
  + :where(.glance, .points, .steps, .sc, .grid, .table-scroll, .deep, .faq, .note, .cta-row) { margin-top: 14px; }

.glance {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.glance li {
  padding: 14px 16px; border-radius: var(--r-md);
  background: rgba(var(--chrome-rgb), .05); border: 1px solid rgba(var(--chrome-rgb), .14);
}
.glance b {
  display: block; font-family: var(--font-display); font-weight: 800;
  font-size: clamp(1.4rem, 1.1rem + 1vw, 1.85rem); letter-spacing: -.02em; line-height: 1.1; color: var(--text);
}
.glance span { display: block; margin-top: 5px; font-size: .84rem; line-height: 1.4; color: var(--text-muted); }

.points, .steps {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.points li, .steps li {
  position: relative; padding: 16px 16px 15px; border-radius: var(--r-md);
  background: rgba(var(--chrome-rgb), .045); border: 1px solid rgba(var(--chrome-rgb), .13);
}
.points li::before {
  content: ""; position: absolute; left: 16px; top: -1px; width: 26px; height: 2px;
  border-radius: 2px; background: rgba(var(--chrome-rgb), .6);
}
.points b, .steps b { display: block; font-size: .98rem; font-weight: 700; line-height: 1.3; color: var(--text); margin-bottom: 4px; letter-spacing: -.01em; }
.points span, .steps span { display: block; font-size: .88rem; line-height: 1.5; color: var(--text-muted); }

.points-check li { padding-left: 46px; }
.points-check li::before {
  left: 15px; top: 17px; width: 18px; height: 18px; border-radius: 50%;
  background: rgba(var(--chrome-rgb), .12); border: 1px solid rgba(var(--chrome-rgb), .55);
}
/* The SAME check as the console's step tick (--tick-glyph, :root below), for
   the same reason: a 6x3 box with two 1.6px borders rotated -45deg has no
   hinting, so at DPR 1 its arms land between device pixels and the vertex
   blobs. One stroked path through a mask, centred on the ring by `inset`, is
   sharp at every DPR and needs no hand-placed left/top. K's screenshots were
   of the console ticks; these are the same construction on every page. */
.points-check li::after {
  content: ""; position: absolute; left: 15px; top: 17px; width: 18px; height: 18px;
  background: var(--text);
  -webkit-mask: var(--tick-glyph) center / 62% no-repeat;
  mask: var(--tick-glyph) center / 62% no-repeat;
}

.steps { counter-reset: step; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.steps li { counter-increment: step; }
.steps li::before {
  content: counter(step); display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; margin-bottom: 10px; border-radius: 50%;
  font-family: var(--font-ui); font-size: .8rem; font-weight: 700; color: var(--text);
  background: rgba(var(--chrome-rgb), .12); border: 1px solid rgba(var(--chrome-rgb), .45);
}

.deep { border-top: 1px solid rgba(var(--chrome-rgb), .12); }
.deep-label {
  margin: 0; padding: 14px 2px 2px;
  font-family: var(--font-ui); font-size: .72rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--text-faint);
}
.deep .fold-body .feat-list { margin-top: 4px; }
.note { font-size: .84rem; font-weight: 600; line-height: 1.5; color: var(--text-faint); max-width: 74ch; margin: 0; }

/* FAQ as an accordion: a question list you can scan, answers one tap away.
   The answers stay in the DOM, and their JSON-LD twins stay word-for-word. */
.faq { gap: 8px; }
details.faq-item { padding: 0; }
details.faq-item > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 14px; padding: 14px 18px;
}
details.faq-item > summary::-webkit-details-marker { display: none; }
details.faq-item > summary h3 { margin: 0; font-size: 1rem; line-height: 1.35; }
details.faq-item > summary:hover h3 { color: var(--text); }
details.faq-item > summary::after {
  content: ""; margin-left: auto; flex: 0 0 auto; width: 11px; height: 7px;
  background: var(--text-faint);
  -webkit-mask: var(--glyph-chev-d) center / contain no-repeat;
  mask: var(--glyph-chev-d) center / contain no-repeat;
  transition: transform .25s var(--ease);
}
details.faq-item[open] > summary::after { transform: rotate(180deg); }
details.faq-item > :not(summary) { padding: 0 18px; }
details.faq-item > :not(summary):last-child { padding-bottom: 16px; margin-bottom: 0; }


/* ═══════════════ FEATURE SHOWCASES (2026-09-13) ═══════════════
   Story Mode on the video page and Musician mode on the music page are SHOWN,
   not told (K). Pure CSS + inline SVG over one real SynergyAI generation (Pip,
   already on the page as the demo poster), so they add no weight to LCP. The
   motion is decoration only: every fact they carry is also plain text in the
   tiles beside them. (2026-09-17: the Story strip is no longer Pip crops — it
   is the real Director film, styled in the RESULTS block at the end.) Monochrome on purpose — colour belongs to the theme. */
.sc { position: relative; display: grid; gap: 14px; }
.sc-stage {
  position: relative; overflow: hidden; border-radius: var(--r-lg);
  border: 1px solid rgba(var(--chrome-rgb), .16);
  background: radial-gradient(120% 140% at 0% 0%, rgba(var(--chrome-rgb), .08), rgba(var(--chrome-rgb), 0) 60%), rgba(var(--chrome-rgb), .05);
  padding: clamp(14px, 2vw, 22px);
}
.sc-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; }
.sc-pill {
  font-family: var(--font-ui); font-size: .68rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 4px 10px; border-radius: 999px; color: var(--text);
  background: rgba(var(--chrome-rgb), .12); border: 1px solid rgba(var(--chrome-rgb), .42);
}
.sc-pill-ghost { background: transparent; color: var(--text-muted); border-color: rgba(var(--chrome-rgb), .22); }
.sc-meta { margin-left: auto; font-family: var(--font-mono); font-size: .72rem; font-weight: 600; color: var(--text-faint); }

.sc-length { margin-top: 16px; }
.sc-length-top { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 14px; margin-bottom: 9px; }
.sc-length-top b { font-size: 1.02rem; font-weight: 800; color: var(--text); letter-spacing: -.01em; }
.sc-length-top span { font-size: .84rem; color: var(--text-muted); }
.sc-track { position: relative; height: 8px; border-radius: 999px; background: rgba(var(--chrome-rgb), .1); overflow: hidden; }
.sc-track i {
  position: absolute; inset: 0 auto 0 0; width: 100%; border-radius: inherit;
  background: linear-gradient(90deg, rgba(var(--chrome-rgb), .35), rgb(var(--chrome-rgb)));
  transform-origin: left center; animation: scFill 7s var(--ease) infinite;
}
@keyframes scFill { 0% { transform: scaleX(.02); } 70%, 100% { transform: scaleX(1); } }
.sc-ticks { display: flex; justify-content: space-between; margin-top: 7px; font-family: var(--font-mono); font-size: .7rem; color: var(--text-faint); }

.sc-paths { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); }
.sc-path { padding: 16px 18px; border-radius: var(--r-md); background: rgba(var(--chrome-rgb), .05); border: 1px solid rgba(var(--chrome-rgb), .14); }
.sc-path .eyebrow { margin: 0 0 6px; font-size: .68rem; }
.sc-path h3 { font-size: 1.1rem; margin: 0 0 10px; }
.sc-path ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.sc-path li { position: relative; padding-left: 24px; font-size: .9rem; line-height: 1.45; color: var(--text-muted); }
.sc-path li::before {
  content: ""; position: absolute; left: 0; top: .2em; width: 15px; height: 15px; border-radius: 50%;
  background: rgba(var(--chrome-rgb), .1); border: 1px solid rgba(var(--chrome-rgb), .5);
}
/* same glyph again, on the 15px ring (see .points-check above) */
.sc-path li::after {
  content: ""; position: absolute; left: 0; top: .2em; width: 15px; height: 15px;
  background: var(--text);
  -webkit-mask: var(--tick-glyph) center / 62% no-repeat;
  mask: var(--tick-glyph) center / 62% no-repeat;
}

.sc-feats { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.sc-feats li {
  display: grid; grid-template-columns: 36px minmax(0, 1fr); column-gap: 12px; align-items: start;
  padding: 14px; border-radius: var(--r-md);
  background: rgba(var(--chrome-rgb), .045); border: 1px solid rgba(var(--chrome-rgb), .13);
}
.sc-feats svg {
  grid-row: 1 / span 2; width: 36px; height: 36px; padding: 8px; box-sizing: border-box;
  border-radius: 10px; color: var(--text);
  background: rgba(var(--chrome-rgb), .09); border: 1px solid rgba(var(--chrome-rgb), .24);
}
.sc-feats b { font-size: .95rem; font-weight: 700; line-height: 1.3; color: var(--text); }
.sc-feats span { font-size: .86rem; line-height: 1.45; color: var(--text-muted); }

/* ── Musician mode: the band on one stage, bound to one master clock ── */
.sc-master { display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px; }
.sc-knob {
  display: inline-flex; flex-direction: column; justify-content: center; gap: 2px;
  padding: 7px 13px; border-radius: 10px;
  background: rgba(var(--chrome-rgb), .06); border: 1px solid rgba(var(--chrome-rgb), .18);
}
.sc-knob i { font-style: normal; font-family: var(--font-ui); font-size: .66rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--text-faint); }
.sc-knob b { font-family: var(--font-mono); font-size: .92rem; font-weight: 600; color: var(--text); }
.sc-master .sc-pill { align-self: center; margin-left: auto; }
.sc-chords { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)) 48px; gap: 8px; margin: 12px 0 14px; }
.sc-chord {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  padding: 9px 6px; border-radius: 10px;
  background: rgba(var(--chrome-rgb), .05); border: 1px solid rgba(var(--chrome-rgb), .16);
  animation: scBeat 4s steps(1, end) infinite; animation-delay: calc(var(--i, 0) * 1s);
}
.sc-chord b { font-family: var(--font-display); font-size: 1.15rem; font-weight: 800; color: var(--text); line-height: 1.15; }
.sc-chord i { font-style: normal; font-family: var(--font-mono); font-size: .72rem; font-weight: 600; color: var(--text-muted); }
@keyframes scBeat {
  0% { background: rgba(var(--chrome-rgb), .17); border-color: rgba(var(--chrome-rgb), .65); }
  25%, 100% { background: rgba(var(--chrome-rgb), .05); border-color: rgba(var(--chrome-rgb), .16); }
}
.sc-dice {
  display: flex; align-items: center; justify-content: center; border-radius: 10px; color: var(--text);
  background: rgba(var(--chrome-rgb), .08); border: 1px dashed rgba(var(--chrome-rgb), .35);
}
.sc-dice svg { width: 22px; height: 22px; animation: scRoll 6s var(--ease) infinite; }
@keyframes scRoll { 0%, 80% { transform: rotate(0); } 92%, 100% { transform: rotate(360deg); } }
.sc-lanes { display: grid; gap: 6px; }
.sc-lane {
  display: grid; grid-template-columns: 126px minmax(0, 1fr) 112px; align-items: center; gap: 12px;
  padding: 6px 10px; border-radius: 10px;
  background: rgba(var(--chrome-rgb), .035); border: 1px solid rgba(var(--chrome-rgb), .1);
}
.sc-lane-name { font-family: var(--font-ui); font-size: .78rem; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sc-wave { position: relative; display: block; height: 22px; overflow: hidden; }
/* The wave is four identical 120-unit periods drawn 400px apart, so sliding it
   exactly one period (-400px) loops with no seam, and a bar lands every ~7px. */
.sc-wave svg { position: absolute; left: 0; top: 0; width: 1600px; height: 100%; color: rgba(var(--chrome-rgb), .78); animation: scWave var(--d, 9s) linear infinite; }
@keyframes scWave { to { transform: translateX(-400px); } }
.sc-state {
  justify-self: end; font-family: var(--font-ui); font-size: .72rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px; white-space: nowrap;
  color: var(--text-muted); border: 1px solid rgba(var(--chrome-rgb), .26);
}
.sc-lane.is-frozen .sc-wave svg { animation-play-state: paused; color: var(--text); }
.sc-lane.is-frozen .sc-state { color: var(--text); border-style: dashed; border-color: rgba(var(--chrome-rgb), .7); }
.sc-lane.is-live .sc-state { color: var(--text); background: rgba(var(--chrome-rgb), .14); border-color: rgba(var(--chrome-rgb), .6); }
.sc-lane.is-making .sc-wave svg { opacity: .28; }
.sc-lane.is-making .sc-wave::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 34%; left: -40%;
  background: linear-gradient(90deg, transparent, rgba(var(--chrome-rgb), .55), transparent);
  animation: scMaking 1.8s var(--ease) infinite;
}
@keyframes scMaking { to { left: 110%; } }
.sc-lane.is-queued .sc-wave svg { display: none; }
.sc-lane.is-queued .sc-wave::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px;
  background: repeating-linear-gradient(90deg, rgba(var(--chrome-rgb), .32) 0 4px, transparent 4px 9px);
}

@media (max-width: 720px) {
  .sc-lane { grid-template-columns: 88px minmax(0, 1fr) 100px; gap: 8px; }
  .sc-chords { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .sc-dice { display: none; }
  .sc-master .sc-pill { margin-left: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sc-track i, .sc-chord, .sc-dice svg, .sc-wave svg, .sc-lane.is-making .sc-wave::after { animation: none !important; }
  .sc-track i { transform: none; }
}

/* Tile grids follow their tile count (2026-09-13 visual pass). auto-fit put six
   tiles into a 4 + 2 split at desktop width, which reads as something missing:
   3, 6 and 9 tiles sit in threes, 2 and 4 in twos, stats in even rows. */
@media (min-width: 900px) {
  .points, .sc-feats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .points:has(> li:nth-child(2):last-child),
  .points:has(> li:nth-child(4):last-child),
  .sc-feats:has(> li:nth-child(4):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .points:has(> li:first-child:last-child) { grid-template-columns: minmax(0, 1fr); }
  .glance:has(> li:nth-child(4):last-child),
  .glance:has(> li:nth-child(8):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .glance:has(> li:nth-child(6):last-child) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 600px) and (max-width: 899px) {
  .points, .sc-feats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .glance { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .glance:has(> li:nth-child(4):last-child),
  .glance:has(> li:nth-child(8):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 599px) {
  .points, .sc-feats { grid-template-columns: minmax(0, 1fr); }
  .glance { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Scope chip inside a showcase tile title. Ground truth (2026-09-13): five of the six
   Story tiles are Director-only — a tile has to say which path it belongs to, or the
   layout promises Story Mode things it cannot do. Sits inline inside the tile's <b>. */
.sc-tag {
  display: inline-block; vertical-align: 2px; margin-right: 7px; font-style: normal;
  font-family: var(--font-ui); font-size: .58rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; line-height: 1.4;
  color: var(--text-muted); border: 1px solid rgba(var(--chrome-rgb), .28);
}


/* ═══════════════ RESULTS (.rs-*) — real renders, shown (2026-09-17) ═══════════════
   K: "across all the visual studio option show results not just text". Every
   Visual Studio mode on the Images and Video pages now SHOWS a real render from
   our own account — in the hero console tabs and in the sections below it.
   The family is deliberately quiet: rounded frames, a faint inner border, a soft
   drop shadow, small mono chips for engine and duration, and a slight lift on
   hover. The pictures carry the colour; the chrome stays monochrome so it reads
   on all eight themes. Every <img> is lazy with its intrinsic size; every clip
   is preload="none" with its src attached only when it plays (seo-demo.js). */

/* ── shared atoms ── */
.rs-chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-mono); font-size: .75rem; font-weight: 600; line-height: 1.25;
  color: #fff; padding: 3px 8px; border-radius: 999px; white-space: nowrap;
  background: rgba(6, 9, 10, .68); border: 1px solid rgba(255, 255, 255, .24);
}
.rs-tl, .rs-tr, .rs-bl { position: absolute; z-index: 3; }
.rs-tl { top: 8px; left: 8px; }
.rs-tr { top: 8px; right: 8px; }
.rs-bl { left: 8px; bottom: 8px; }
.rs-flag { color: #1a1305; background: #f2c15c; border-color: rgba(255, 255, 255, .55); font-family: var(--font-ui); font-weight: 700; }
.rs-k { font-family: var(--font-ui); font-size: .75rem; font-weight: 600; letter-spacing: .02em; color: var(--text-muted); }
.rs-q { color: var(--text); }
/* an overflow guard only: quotes are cut in the markup at a word boundary so this never cuts a word */
.rs-cover { display: block; width: 100%; height: 100%; object-fit: cover; }
.rs-bgimg {
  position: absolute; left: -10%; top: -10%; width: 120%; height: 120%; max-width: none; z-index: 0;
  object-fit: cover; filter: blur(26px) saturate(1.1) brightness(.34); pointer-events: none;
}
/* The chain arrow, same treatment as the step tick: it WAS a hairline div plus
   a 7px box with two borders rotated 45deg, so the head stair-stepped and its
   arms ended in square chops that never met the shaft. One stroked SVG path
   through a mask instead — shaft and head are a single figure with round caps
   and a round join, rasterised by the path engine at whatever DPR it lands on.
   The viewBox IS the painted bounding box, so `contain` centres it exactly and
   the arrow keeps its proportions in a 22px chain cell and a 6% pair cell
   alike, with no per-context offsets. */
.rs-arrow { position: relative; flex: 0 0 22px; height: 22px; }
.rs-arrow::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(var(--chrome-rgb), .82);
  -webkit-mask: var(--glyph-arrow) center / contain no-repeat;
  mask: var(--glyph-arrow) center / contain no-repeat;
}

/* a clip: poster underneath, video on top, a play button only when manual */
.rs-player { position: relative; display: block; overflow: hidden; isolation: isolate; }
.rs-player .rs-fg, .rs-player .rs-vid {
  position: absolute; inset: 0; z-index: 1; display: block; width: 100%; height: 100%; object-fit: contain;
}
.rs-player .rs-vid { z-index: 2; background: transparent; }
/* The transport glyphs are masks too. A CSS border-triangle has a hard
   diagonal with no antialiasing hint of its own, and it sat on hard-coded
   left/top inside the button; the pause bars then had to re-declare four more
   offsets to line up with it. One glyph each, centred by the mask. */
:root {
  /* A TRIANGLE IN A CIRCLE IS CENTRED OPTICALLY, NOT GEOMETRICALLY: its mass
     sits on the left edge, so a geometric centre reads as sitting left. The
     border-triangles this replaced carried a hand-tuned right nudge per
     button; the nudge lives in the viewBox instead — the painted figure is
     off-centre INSIDE the image, so it scales with the mask and is right at
     34px, at 54px and at any zoom, with no per-control offset. */
  --glyph-play: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-1.3 0 16.6 18'%3E%3Cpath d='M3 2.4 13.9 9 3 15.6Z' fill='%23fff' stroke='%23fff' stroke-width='2.1' stroke-linejoin='round'/%3E%3C/svg%3E");
  --glyph-pause: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 16'%3E%3Cpath d='M2.6 3v10M11.4 3v10' fill='none' stroke='%23fff' stroke-width='4.4' stroke-linecap='round'/%3E%3C/svg%3E");
  /* the viewBox IS the painted bounding box, so `contain` centres it exactly */
  --glyph-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 12'%3E%3Cpath d='M1 6H18.6M13.9 1.3 18.7 6l-4.8 4.7' fill='none' stroke='%23fff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  /* the step tick, the page-body check bullets and the two accordion chevrons
     read these from here — one definition each, used at 15px, 16px, 18px and
     20px without a single per-size offset */
  --tick-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.4 8.4 6.5 11.5 12.6 4.7' fill='none' stroke='%23fff' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --glyph-chev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 7 12'%3E%3Cpath d='M1 1 6 6l-5 5' fill='none' stroke='%23fff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --glyph-chev-d: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 7'%3E%3Cpath d='M1 1 6 6l5-5' fill='none' stroke='%23fff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* EVERY masked glyph paints a solid box and lets the mask cut the shape out of
   it. That is right in every engine that has mask-image — but where it is
   missing the fallback is not a rough glyph, it is a filled disc inside each
   done tick and a solid rectangle in each play button. The empty ring, the gap
   and the bare button all read correctly with no glyph at all, so the paint is
   withheld entirely rather than left as a block. */
/* a LITERAL url, never var(--glyph-arrow): a declaration whose value is a
   var() parses as valid in every engine that has custom properties, so the
   query would answer "supported" without testing masks at all. */
@supports not ((-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E")) or (mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E"))) {
  .ag-tick::after, .points-check li::after, .sc-path li::after,
  .fold > summary::after, details.faq-item > summary::after,
  .rs-bridge::before, .rs-bridge::after, .rs-chain > figure + figure::before,
  .rs-arrow::after, .rs-playbtn::after, .st-play::after, .st-bigplay::after,
  .rs-handoff { display: none; }
}
.rs-playbtn {
  --glyph: var(--glyph-play); --glyph-size: 36%;
  display: block; opacity: 0; position: absolute; z-index: 4; left: 50%; top: 50%; width: 54px; height: 54px; margin: -27px 0 0 -27px;
  border-radius: 50%; cursor: pointer; padding: 0;
  background: rgba(6, 10, 8, .62); border: 1.5px solid rgba(255, 255, 255, .75);
  box-shadow: 0 10px 30px -10px rgba(0, 0, 0, .8);
  transition: opacity .25s var(--ease), transform .2s var(--ease);
}
.rs-playbtn::after {
  content: ""; position: absolute; inset: 0;
  background: #fff;
  -webkit-mask: var(--glyph) center / var(--glyph-size) no-repeat;
  mask: var(--glyph) center / var(--glyph-size) no-repeat;
}
/* Always present (a looping clip must be pausable, WCAG 2.2.2): quiet until
   hovered or focused, and shown outright when playback has to be manual. */
.rs-player:hover .rs-playbtn, .rs-playbtn:focus-visible { opacity: 1; }
.rs-manual:not(.is-playing) .rs-playbtn { opacity: 1; }
.rs-playbtn:hover { transform: scale(1.05); }
.is-playing .rs-playbtn { --glyph: var(--glyph-pause); --glyph-size: 27%; }

/* the Sound toggle — sound never starts on its own */
.rs-sound {
  position: absolute; z-index: 4; right: 10px; bottom: 10px;
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font-family: var(--font-ui); font-size: .78rem; font-weight: 700; line-height: 1;
  color: #fff; padding: 7px 12px 7px 9px; border-radius: 999px;
  background: rgba(6, 9, 10, .74); border: 1px solid rgba(255, 255, 255, .45);
  box-shadow: 0 8px 22px -10px rgba(0, 0, 0, .8);
  transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
}
.rs-sound svg { width: 16px; height: 16px; flex: 0 0 16px; }
.rs-sound:hover { border-color: rgba(255, 255, 255, .85); }
.rs-sound.is-on { background: #fff; color: #0b0f0c; border-color: #fff; }
.rs-sound.is-on .rs-snd-off, .rs-sound:not(.is-on) .rs-snd-on { display: none; }
/* a control that sits ON a picture keeps its white ring; a ring drawn around a
   FRAME is page chrome and flips with the page, or it is white on white */
.rs-sound:focus-visible, .rs-playbtn:focus-visible, .st-bigplay:focus-visible {
  outline: 2px solid rgba(255, 255, 255, .9); outline-offset: 2px;
}
.rs-scene:focus-visible, .rs-th:focus-visible,
.rs-sheet:focus-visible, .rs-charsheet:focus-visible, .rs-place:focus-visible {
  outline: 2px solid rgba(var(--chrome-rgb), .9); outline-offset: 2px;
}

/* ── the hero console, results edition ── */
/* Sized from the markup (data-demo), not from the class seo-demo.js adds after
   first paint — otherwise the hero grows 74-110px on load and shifts the page. */
.demo[data-demo="image"], .demo[data-demo="video"] { height: 470px; }
@media (max-width: 949px) { .demo[data-demo="image"], .demo[data-demo="video"] { height: 530px; } }
.demo-results .ag-brief { display: flex; flex-direction: column; gap: 2px; min-height: 0; padding: 8px 11px; }
.demo-results .rs-said { font-size: .75rem; font-weight: 600; letter-spacing: .02em; color: var(--text-faint); line-height: 1.3; }
/* A mode without a label (Pip) keeps the label line, so the picture never jumps between tabs. */
.demo-results .rs-said:empty { display: block; visibility: hidden; }
.demo-results .rs-said:empty::before { content: "\00a0"; }
.demo-results .ag-brief-text {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  font-size: .82rem; line-height: 1.4; min-height: 2.8em;
}
.demo-results .ag-brief .demo-caret { display: none; }
.demo-results .ag-steps {
  max-height: 96px; overflow: hidden;
  transition: max-height .5s var(--ease), opacity .3s var(--ease), margin .5s var(--ease);
}
.demo-results.rs-shown .ag-steps { max-height: 0; opacity: 0; margin-top: -9px; }
.demo-results .ag-media { margin-top: 0; }
/* Pip's legacy tile is an inline <span>; inside the results column it must be a block to take the stage. */
.demo-results .ag-media .st-tile { display: block; }
.demo-results .ag-out { margin-top: 0; }
.demo-results .ag-out b { font-size: .75rem; line-height: 1.35; text-wrap: balance; }
.demo-results .rs-seg { white-space: nowrap; }
/* On a phone some result lines take two lines: reserve both, so the picture keeps one size across tabs. */
@media (max-width: 600px) { .demo-results .ag-out b { display: block; min-height: 2.7em; } }
.rs-said:empty { display: none; }

.rs-stage {
  position: relative; display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; height: 100%; min-height: 0; overflow: hidden; isolation: isolate;
  border-radius: 14px; background: rgba(var(--chrome-rgb), .05);
  border: 1px solid rgba(var(--chrome-rgb), .14);
  box-shadow: 0 1px 0 rgba(var(--chrome-rgb), .07) inset, 0 18px 44px -18px rgba(0, 0, 0, .8);
}
.rs-stage > .rs-fg { position: relative; z-index: 1; display: block; width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 10px 26px rgba(0, 0, 0, .55)); }

/* ── THE FRAME HUGS THE ARTIFACT ─────────────────────────────────────────
   A ONE-artifact result (a still, a clip, a film, Pip's legacy tile) used to
   take the whole stage whatever its shape, so a 2:3 still showed 187px wide
   inside a 995px frame — a sliver between two huge blurred panels. The frame
   now takes the artifact's own aspect ratio (--ar, written by seo-demo.js from
   the roster's real pixel sizes) plus a thin margin, and centres itself in the
   media box. The blurred fill survives where it is genuinely needed: the last
   few px either side of a contained picture. Multi-frame results (pair, chain,
   contact sheet) are NOT in this list — they are wide by nature and keep the
   whole stage. */
/* THE FIT BOX (2026-09-20). Two NESTED ratio boxes give an exact "contain" of
   the artifact's shape inside its cell, using nothing but aspect-ratio and
   percentages — no container queries, no cq units:

     OUTER (.rs-fit)   height: 100%  →  width = H * --ar, clamped by max-width
     INNER (its child) width: 100%   →  height = W / --ar, clamped by max-height

   When the artifact is taller than the cell the OUTER is exact and the inner
   clamp never binds; when it is wider, max-width pins the OUTER to the cell and
   the INNER re-derives the height from that width. Either way the frame is the
   artifact's own shape and it CANNOT exceed its row in any engine.

   It replaces `.demo-results .ag-layer { container-type: size }` plus
   width/height in cqw/cqh. That recipe is exact in Chrome, but Safari resolves
   100cqh differently for a size container whose own height arrives through a
   flex chain, and the artifact painted outside the console's rounded panel and
   over its link row (K's screenshot, 2026-09-20). Nothing here depends on an
   engine agreeing about size containment. */
.rs-fit {
  grid-area: 1 / 1; justify-self: center; align-self: stretch;
  height: 100%; width: auto; max-width: 100%; min-width: 0; min-height: 0;
  aspect-ratio: var(--ar, 1.5);
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
}
.rs-fit > * {
  grid-area: 1 / 1; align-self: center; justify-self: stretch;
  width: 100%; height: auto; max-height: 100%;
  aspect-ratio: var(--ar, 1.5);
}
/* Pip's legacy tile hugs only inside the hub: there the whole frame is shown
   over a blurred fill, so hugging it loses nothing. On a section page the tile
   CROPS to fill (object-fit: cover), so hugging it would shrink the picture —
   the one place this change could take something away. Its fit box is inert
   there, and stretches like the layer it replaced. */
.rs-fit-tile, .rs-fit-tile > * { aspect-ratio: auto; width: 100%; height: 100%; }
.demo-hub .rs-fit-tile { width: auto; aspect-ratio: var(--ar, 1.5); }
.demo-hub .rs-fit-tile > * { height: auto; aspect-ratio: var(--ar, 1.5); }

/* pair: reference → new scene */
/* The PAIR is a shape, not a stage: two 2:3 frames with a gap between them.
   Every length here is a percentage of the frame's own width, so the group is
   a fixed ratio (--ar:1.4364, seo-demo.js PAIR_AR) and the .rs-fit box above
   hugs it exactly as it hugs one picture — and the frames can never be wider
   than the row they sit in, whatever the engine. */
/* 46 + 46 + 6 (arrow) + 1 + 1 (gaps) = 100% EXACTLY. At gap:2% the line came
   to 102% with flex-shrink:0 on every item, so the group overflowed its own
   content box by one gap and survived only because the 1%+1% padding is the
   same 2% — the frames sat flush on the stage's 1px border with ~0.1px before
   the radius started shaving their corners. 1% gaps are also the arithmetic
   --ar:1.4364 (seo-demo.js PAIR_AR) was derived from, so nothing resizes. */
.rs-c-pair { padding: 1%; gap: 1%; }
.rs-c-pair .rs-arrow { flex: 0 0 6%; }
.rs-pf {
  position: relative; z-index: 1; aspect-ratio: 2 / 3;
  border-radius: 10px; overflow: hidden; border: 1px solid rgba(var(--chrome-rgb), .22);
  box-shadow: 0 12px 28px -14px rgba(0, 0, 0, .9);
  flex: 0 0 46%; min-width: 0;
}

/* ═══ STORY MODE — a strip being assembled ════════════════════
   The surface used to be .rs-c-chain: three equal columns, each a 2:3 frame
   sized from its COLUMN with a label underneath. It read as three pictures.
   Now the stage is a strip over a scene bar:
     .rs-strip      four frames — keyframe, the clip, its captured last frame,
                    and the empty next scene the last frame opens
     .rs-scenebar   the two scenes as segments: solid for the one that exists,
                    dashed for the one that does not
   EVERY FRAME IS SIZED FROM THE STAGE'S HEIGHT, capped by its column, because
   this stage has two heights: the beats are up for the first seconds of the
   film (a short stage) and fold away with the result (a tall one). The .rs-fit
   pair does exactly that already — outer box takes the height and derives the
   width, inner box re-derives from the width when the column is the binding
   constraint — so the strip is exact in both, with no container query and no
   second set of numbers to keep in step. */
.rs-c-story { flex-direction: column; align-items: stretch; justify-content: center; gap: 7px; padding: 9px; }
/* gap 2, not 6: the three frames of scene 1 butt into one RUN. With the inner
   corners squared off too (below) the eye reads a strip being assembled rather
   than three separate pictures — which is the whole claim the surface makes. */
/* THE STRIP IS NO TALLER THAN ITS OWN FRAMES. Given all the height it could
   take, the frames stopped at the width their column allowed and sat centred
   in cells 42px wider than themselves: four separated pictures instead of a
   run, and a scene bar that lines up with the CELLS overshoots the frames by
   21px. 2.87 is the strip's own shape with four 2:3 frames, three 2px gaps and
   the 34px hand-off (4C + 6 + 34 wide by 1.5C tall, which never falls below
   2.87 for a console wider than 600px), so the height it asks for is exactly
   the height those frames want and the cells come out the width of the frames
   in them. max-height keeps it inside a short stage, where the beats are still
   up and the frames are smaller than their columns anyway. */
.rs-strip {
  position: relative; z-index: 1; flex: 0 1 auto; min-height: 0;
  width: 100%; aspect-ratio: 2.87; height: auto; max-height: 100%;
  display: flex; align-items: stretch; gap: 2px;
}
.rs-cell-key .rs-scf { border-top-right-radius: 2px; border-bottom-right-radius: 2px; }
.rs-cell-clip .rs-scf { border-radius: 2px; }
.rs-cell-last .rs-scf { border-top-left-radius: 2px; border-bottom-left-radius: 2px; }
/* the cut between the scene that exists and the one that does not */
.rs-cell-next { margin-left: 34px; }
/* a grid cell, so the .rs-fit box inside behaves exactly as it does on a stage */
.rs-cell { flex: 1 1 0; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); }
/* On a wide stage (the hub) the frames are capped by their COLUMN, not by the
   height, so the fit box has slack. Left centred, that slack opened a band
   between the frames and the scene bar that labels them — the label looked
   like it belonged to something else. The frames sit on the bar instead and
   the slack goes above, where the stage's own edge absorbs it. */
/* IN A STRIP, A FRAME FILLS ITS CELL. Everywhere else in this console the fit
   box CONTAINS the artifact — its own shape inside its cell, centred, with the
   slack left over. A filmstrip is the one place that is wrong: contained
   frames sat centred in cells up to 42px wider than themselves, so the three
   frames of scene 1 never butted into the run the surface is claiming, and a
   scene bar drawn on the cells overshot the frames it labels by 21px. Here the
   frame takes its COLUMN and its height is capped by the row, so cell and
   frame are the same box at every width and in both stage heights — the run
   reads as a run and the bar under it is exact by construction. The strip's
   own 2.87 (above) keeps the cap off the frames wherever there is room, so in
   practice they stay at the clip's 2:3 and the crop is a few px at most. */
.rs-c-story .rs-fit { width: 100%; height: auto; max-width: none; max-height: 100%; }
.rs-c-story .rs-fit > * { align-self: end; max-height: 100%; }
.rs-scf {
  position: relative; border-radius: 9px; overflow: hidden;
  border: 1px solid rgba(var(--chrome-rgb), .22); background: rgba(var(--chrome-rgb), .06);
  box-shadow: 0 12px 26px -16px rgba(0, 0, 0, .9);
}
.rs-scf .rs-player { position: absolute; inset: 0; }
/* the clip is one frame of a strip, not a stage: it CROPS to its frame like
   the stills either side of it, or the one cell in the run would letterbox
   while its neighbours filled */
.rs-c-story .rs-player .rs-fg, .rs-c-story .rs-player .rs-vid { object-fit: cover; }
/* THE HAND-OFF — the mark that carries the whole claim: the last frame on its
   left is the ghost on its right. It lives INSIDE the last frame's own fit
   box, as a second child of it, so it inherits that frame's exact box (same
   width, same height, same bottom alignment) and is then shifted into the gap
   by half of it plus the gap's own half. Centred on .rs-strip it was centred
   on the strip's SLACK instead: where the frames are capped by their column
   rather than by the stage's height — the hub, where this surface is largest —
   it floated 55px above the frames it connects and read as a stray mark near
   the top of the gap. The glyph keeps a fixed size (it is a mark, not a
   picture), so only its POSITION comes from the frame. */
.rs-handoff {
  z-index: 2; pointer-events: none;
  transform: translateX(calc(50% + 17px));
  background: rgba(var(--chrome-rgb), .82);
  -webkit-mask: var(--glyph-arrow) center / 20px 12px no-repeat;
  mask: var(--glyph-arrow) center / 20px 12px no-repeat;
}
/* THE EMPTY NEXT SCENE. Dashed, captioned in words, with the real last frame
   behind it at ghost opacity: what scene 2 would START from, and nothing a
   visitor could read as a rendered result. */
.rs-cell-next .rs-scf { border: 1.5px dashed rgba(var(--chrome-rgb), .55); box-shadow: none; }
/* .26 over a translucent slot contributed ~6% of the pixels: the hand-off is
   supposed to be SHOWN, and you could not tell the ghost was the same picture.
   It can be read now that the caption has its own plate and no longer depends
   on the slot staying pale. */
.rs-ghost { opacity: .45; filter: saturate(.75); }
/* THE CAPTION SITS ON A PLATE, like .rs-trim and every chip on this surface.
   It was the only text here painted straight onto theme art (--text at 10px
   over the ghosted clouds): 2.4:1 on trippy-1, 3.0:1 on the default, AA on
   Boring and black alone. The plate carries its own contrast on all nine.
   It is also no longer the loudest thing on the surface — 800-weight uppercase
   "NOT RENDERED YET" outshouted the three real artifacts and read as a failure
   state, which is the register this console was just cleared of. */
.rs-empty {
  position: absolute; inset: 0; z-index: 2; display: flex;
  align-items: center; justify-content: center; padding: 4px; overflow: hidden;
}
.rs-eplate {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  max-width: 100%; padding: 5px 7px; border-radius: 8px; text-align: center;
  background: rgba(6, 9, 10, .62); border: 1px solid rgba(255, 255, 255, .24);
}
.rs-empty b { font: 700 .64rem/1.2 var(--font-ui); letter-spacing: .01em; color: #fff; text-wrap: balance; }
.rs-empty i { font-style: normal; font: 500 .56rem/1.3 var(--font-ui); color: rgba(255, 255, 255, .84); text-wrap: balance; }
/* TRIM, drawn where it really sits: on the clip's own bottom edge, so the real
   control costs the strip no height. In and out handles, the word between. */
.rs-trim {
  position: absolute; z-index: 3; left: 6px; right: 6px; bottom: 6px; height: 15px;
  display: flex; align-items: center; gap: 5px; padding: 0 5px;
  border-radius: 8px; background: rgba(6, 9, 10, .62); border: 1px solid rgba(255, 255, 255, .26);
  overflow: hidden;
}
.rs-trim i { position: relative; flex: 1 1 0; min-width: 0; height: 3px; border-radius: 2px; background: rgba(255, 255, 255, .46); }
.rs-trim i::before {
  content: ""; position: absolute; top: 50%; margin-top: -5px; width: 3px; height: 10px;
  border-radius: 2px; background: #fff;
}
.rs-trim i:first-of-type::before { left: -2px; }
.rs-trim i:last-of-type::before { right: -2px; }
.rs-trim b { flex: 0 0 auto; font: 700 .56rem/1 var(--font-ui); letter-spacing: .07em; text-transform: uppercase; color: #fff; }
/* THE SCENE BAR. Segment widths mirror the strip above it: three frames of
   scene 1 against one empty slot, so the eye reads the grouping without a
   bracket. Solid = rendered, dashed = still to render. */
/* ONE set of numbers, not two. The bar used to be flex:3 / flex:1 plus a
   hand-tuned 34px margin against a strip built from four equal cells, four 2px
   gaps and a 20px hand-off with 7px margins — so the two never agreed and the
   error changed sign with width (the SCENE 1 bar stopped 9.5px short of the
   run it labels at 1440, and overshot by 11.4px at 949). The bar is a grid on
   the strip's OWN tracks instead: three cells, the 34px hand-off, one cell,
   2px gaps. Alignment is exact by construction at every width. */
.rs-scenebar {
  position: relative; z-index: 1; flex: 0 0 auto; height: 18px;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) 32px minmax(0, 1fr); gap: 2px;
}
/* .rs-scn, NOT .rs-seg — setOut() writes .rs-seg on every segment of a result
   line in a results console, and a second meaning for that name would restyle
   all of them. */
.rs-scn { display: flex; align-items: center; justify-content: center; min-width: 0; border-radius: 6px; padding: 0 6px; }
.rs-scn b { font: 700 .58rem/1 var(--font-ui); letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rs-scn-done { grid-column: 1 / 4; background: rgba(var(--chrome-rgb), .16); border: 1px solid rgba(var(--chrome-rgb), .32); }
.rs-scn-done b { color: var(--text); }
.rs-scn-next { grid-column: 5; border: 1px dashed rgba(var(--chrome-rgb), .45); }
/* a dashed border already says "not yet"; the label does not have to be faint
   as well — --text-faint over theme art fails AA on seven of the nine themes */
.rs-scn-next b { color: var(--text-muted); }

/* A chip on a strip frame is a caption on a ~90px picture, not on a stage:
   smaller, tighter to the corner, and it can never be wider than its frame.
   `display: block` is what makes the clamp degrade to an ellipsis instead of a
   cut word — text-overflow does nothing to the anonymous item inside a flex
   container, which is what .rs-chip's inline-flex makes of its own text. */
/* The clip cell here is ~91px wide, not a stage: the 54px transport covered
   62% of the picture and sat on the trim pill this surface exists to show. */
.rs-c-story .rs-playbtn { width: 34px; height: 34px; margin: -17px 0 0 -17px; border-width: 1px; }
.rs-c-story .rs-chip { display: block; font-size: .62rem; padding: 2px 6px; max-width: calc(100% - 12px); overflow: hidden; text-overflow: ellipsis; }
.rs-c-story .rs-tl { left: 6px; top: 6px; }
.rs-c-story .rs-tr { right: 6px; top: 6px; }

/* ─ THE BEATS PHASE IS A STRIP BEING ASSEMBLED, NOT A LABELLED ONE ─────────
   This stage has two heights and the frames are sized from it, so while the
   beats are still up every frame is 49x73 (17x25 on a phone) — and everything
   WRITTEN on a frame was laid out at full size inside it. Measured at 1440:
   "Keyframe" overflowed its chip by 27px, "Last frame" by 39px, and the empty
   slot's caption started 22.3px above the slot and ended 22.3px below it, so a
   visitor read the literal string "YET / Scene / 2 / opens / on" clipped into a
   dashed box — for 2.3s of a 6.1s cycle, and it is the ENTRY state.
   The answer is not to shrink the type again: it is that a caption belongs to a
   frame big enough to carry it. Until the beats fold away the surface shows the
   pictures only — the strip assembling — and the chips, the trim pill, the
   scene numbers and the empty slot's caption all arrive together with the
   result, on frames that are nearly twice the size. Freeing the scene bar's
   18px back to the strip makes those beats-phase frames ~36% larger too.
   The hub is excluded: it never sets rs-shown (its beats stay up beside the
   artifact), and its stage has one height, where everything fits. */
.demo-results:not(.rs-shown):not(.hub-surface) .rs-c-story .rs-chip,
.demo-results:not(.rs-shown):not(.hub-surface) .rs-c-story .rs-trim,
.demo-results:not(.rs-shown):not(.hub-surface) .rs-c-story .rs-empty,
.demo-results:not(.rs-shown):not(.hub-surface) .rs-c-story .rs-ghost { opacity: 0; }
.demo-results:not(.rs-shown):not(.hub-surface) .rs-c-story .rs-scenebar { display: none; }
.rs-c-story .rs-chip, .rs-c-story .rs-trim, .rs-empty, .rs-ghost { transition: opacity .35s var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .rs-c-story .rs-chip, .rs-c-story .rs-trim, .rs-empty, .rs-ghost { transition: none; }
}

/* A PHONE-WIDTH CONSOLE IS TALL, NOT WIDE. Four 2:3 frames in a row there are
   ~55px across — too small to read, and the frames float in the middle of a
   stage with 120px of slack under them. The scene that EXISTS keeps the top row
   at three readable frames; the empty next scene becomes a full-width dashed
   band, which is what an unrendered slot should look like anyway, and the
   hand-off arrow turns to point down into it. */
@media (max-width: 600px) {
  .rs-c-story {
    display: grid; gap: 6px;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: minmax(0, 1fr) auto auto auto;
  }
  .rs-strip { display: contents; }
  /* Top to bottom, in the order a story is read: the three frames, the bar
     that says they are SCENE 1, the hand-off pointing down out of the last of
     them, and the empty scene it opens. (Row 3 is the hand-off's; it is
     painted from inside the last frame's box — see .rs-handoff — so the row
     carries no item of its own and has to keep its own height.) */
  .rs-c-story { grid-template-rows: minmax(0, 1fr) auto 22px auto; }
  .rs-cell-key { grid-area: 1 / 1; }
  .rs-cell-clip { grid-area: 1 / 2; }
  .rs-cell-last { grid-area: 1 / 3; }
  /* Under the LAST FRAME, pointing down into the empty scene: the hand-off is
     the last frame's, not the clip's — which is literally where it lives. */
  /* half the frame, then the 6px gap, the 18px bar, the next 6px gap and half
     of the 22px row it lands in */
  .rs-handoff { transform: translateY(calc(50% + 41px)) rotate(90deg); }
  /* a 79px-wide thumbnail cannot carry a 12px mono caption without cutting it */
  .rs-c-story .rs-chip { font-size: .56rem; padding: 2px 4px; max-width: calc(100% - 10px); }
  /* the run's joins are a grid gap here, and the frames keep their own corners */
  .rs-cell-key .rs-scf, .rs-cell-clip .rs-scf, .rs-cell-last .rs-scf { border-radius: 9px; }
  .rs-c-story .rs-tl { left: 5px; }
  .rs-c-story .rs-tr { right: 5px; }
  .rs-cell-next { grid-area: 4 / 1 / 5 / 4; margin-left: 0; }
  /* The three fixed rows below the frames (hand-off 18 + dashed band 54 +
     scene bar 18 + 18 of gaps) took 114px of a 151px stage, leaving 37px for
     the pictures: three 17x25 stamps under ellipsised one-letter chips, with
     the thing that does NOT exist as the largest, most legible element on the
     surface. While the beats are up the real scene owns the stage; the
     hand-off, the empty band and the numbering arrive with the result, which
     is also when their captions become readable. */
  .demo-results:not(.rs-shown):not(.hub-surface) .rs-c-story { grid-template-rows: minmax(0, 1fr); }
  .demo-results:not(.rs-shown):not(.hub-surface) .rs-c-story .rs-handoff,
  .demo-results:not(.rs-shown):not(.hub-surface) .rs-c-story .rs-cell-next { display: none; }
  /* The empty scene is a BAND here, not a 2:3 slot: its height comes from its
     own caption, and the ghosted last frame is pinned behind that caption
     instead of setting the height from a 2:3 picture (which would be taller
     than the whole console). */
  .rs-cell-next .rs-fit, .rs-cell-next .rs-fit > * { aspect-ratio: auto; width: 100%; height: auto; }
  .rs-cell-next .rs-scf { position: relative; min-height: 54px; }
  .rs-cell-next .rs-ghost { position: absolute; inset: 0; }
  .rs-empty { position: relative; inset: auto; padding: 9px 10px; }
  .rs-scenebar { grid-area: 2 / 1 / 3 / 4; display: flex; gap: 6px; }
  /* under a FULL-WIDTH dashed band, a narrow SCENE 2 pill on the right maps to
     nothing: the band carries its own "Scene 2" caption, so the bar here is
     just the run it labels */
  .rs-scn-done { flex: 1 1 0; }
  .rs-scn-next { display: none; }
}

/* grid: one hero view + every view as a thumb */
.rs-c-sheet { padding: 8px; gap: 8px; justify-content: flex-start; align-items: stretch; }
.rs-hero {
  position: relative; z-index: 1; height: 100%; flex: 0 0 auto; border-radius: 10px; overflow: hidden;
  border: 1px solid rgba(var(--chrome-rgb), .22); background: rgba(var(--chrome-rgb), .06);
}
.rs-c-sheet-tall .rs-hero { aspect-ratio: 2 / 3; }
.rs-c-sheet-square .rs-hero { aspect-ratio: 1; }
.rs-hv { position: absolute; inset: 0; opacity: 0; transition: opacity .3s var(--ease); }
.rs-hero .rs-tl { font-family: var(--font-ui); max-width: calc(100% - 16px); white-space: normal; line-height: 1.2; text-align: left; }
.rs-hv.on { opacity: 1; }
/* The console sheet has no flagged state: .rs-reason (the quoted flag text) and
   .rs-th.is-flagged (the amber thumb dot) went with the renderer's flag branch,
   2026-09-21. The Images page's #product section keeps its own .rs-view
   .is-flagged / .rs-flagnote markup, which is unrelated to these. */
.rs-thumbs { position: relative; z-index: 1; flex: 1; min-width: 0; display: grid; gap: 6px; }
.rs-c-sheet-tall .rs-thumbs { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); }
.rs-th {
  position: relative; display: block; min-height: 0; padding: 0; cursor: pointer; overflow: hidden;
  border-radius: 7px; border: 1px solid rgba(var(--chrome-rgb), .14); background: rgba(var(--chrome-rgb), .06);
  opacity: .55; transition: opacity .3s var(--ease), border-color .3s var(--ease);
}
.rs-th:hover { opacity: .85; }
.rs-th.on { opacity: 1; border-color: rgba(var(--chrome-rgb), .9); }

/* Square shoots get square cells (and a square hero), so no product loses its
   sides; the 2-column thumb grid is for phone-width consoles only.
   THREE columns, not four: the square sheet carries six views, and 4 + 2 leaves
   a ragged half-row under a full one. 3 x 2 is the block the shoot actually is. */
/* THE ROWS ARE WHAT CANNOT GROW. `grid-template-rows: none; grid-auto-rows:
   auto` let each row size to its square cell with nothing bounding it, so the
   3-column sheet ran off the bottom of a stage that clips: 40px of the second
   row cut at 1440 while the beats were up, and 33-47px cut in the SETTLED
   layout everywhere from 820 to 949. Two bounded rows, and each thumb sized
   from its row the way the hub's sheets already are (see the comment on
   .demo-hub .rs-c-sheet-tall .rs-th) — nothing can push a thumb off the stage
   in either stage height, at any width. */
.rs-c-sheet-square .rs-thumbs { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); grid-auto-rows: 0; align-content: stretch; }
/* THE SMALLER OF THE TWO SIDES, WITHOUT HAVING TO ASK WHICH ONE IT IS.
   A square shoot keeps square cells — a thumb that fills a cell of some other
   shape crops a third of the product away, which is why this sheet has its own
   rules at all. But a square thumb has to be min(column, row), and which of
   the two is smaller changes with the width AND with the stage's two heights:
   the column at 1440 settled (72 against an 84 row), the row at 900 (114
   against 170), the row again everywhere while the beats are up (47 against
   96), the column again on a phone (45 against 72). `aspect-ratio` cannot say
   that: sized from its column the thumb overflows a short row, sized from its
   row a narrow column clamps it to a portrait box.
   An intrinsically square IMAGE under `max-width: 100%; max-height: 100%` with
   auto sizes resolves exactly that minimum, in every engine, with no
   breakpoint and no phase rule. So the cell is the SLOT and the picture is the
   thumb: it can never leave the stage (it is bounded in both directions) and
   it can never lose the product's sides.
   Before this, rows sized to their own square cells with nothing bounding
   them, and 33-47px of the bottom row was sliced off by a stage that clips
   (2026-09-21, every width from 601 to 949, and 40px at 1440 under the
   beats). */
.rs-c-sheet-square .rs-th {
  aspect-ratio: auto; width: 100%; height: 100%; min-width: 0; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: none; border-radius: 0; overflow: visible;
}
.rs-c-sheet-square .rs-th .rs-cover {
  width: auto; height: auto; max-width: 100%; max-height: 100%;
  border-radius: 7px; background: rgba(var(--chrome-rgb), .06);
  border: 1px solid rgba(var(--chrome-rgb), .14);
  transition: border-color .3s var(--ease);
}
.rs-c-sheet-square .rs-th.on .rs-cover { border-color: rgba(var(--chrome-rgb), .9); }
/* the ring belongs to the picture, which is what the cell shows */
.rs-c-sheet-square .rs-th:focus-visible { outline: none; }
.rs-c-sheet-square .rs-th:focus-visible .rs-cover { outline: 2px solid rgba(var(--chrome-rgb), .9); outline-offset: 2px; }
/* The hero view is sized from the sheet's own HEIGHT (a flex item stretched in
   a definite-height stage), with a width cap instead of container units. */
.rs-c-sheet-square .rs-hero { height: 100%; width: auto; max-width: 60%; align-self: center; }
@media (max-width: 949px) {
  .rs-hero { max-width: 60%; }
}
@media (max-width: 600px) {
  .rs-c-sheet .rs-thumbs,
  .rs-c-sheet-tall .rs-thumbs,
  .rs-c-sheet-square .rs-thumbs { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(4, minmax(0, 1fr)); gap: 5px; }
  /* Two columns, so SIX views are three rows — bounded, like every other
     breakpoint: an auto row sizes to its own square cell and the stage, which
     clips, is the only thing stopping it. A phone console is TALL, so the
     column is the smaller side again here (45px against a 72px row at 390). */
  .rs-c-sheet-square .rs-thumbs { grid-template-rows: repeat(3, minmax(0, 1fr)); }
}

/* clip + first-frame inset */
.rs-c-clip .rs-player, .rs-c-film .rs-player { position: absolute; inset: 0; }
.rs-player .rs-bgimg { z-index: 0; }
.rs-inset {
  position: absolute; z-index: 3; left: 8px; bottom: 8px; width: 30%; max-width: 132px; aspect-ratio: 3 / 2;
  border-radius: 8px; overflow: hidden; border: 1.5px solid rgba(255, 255, 255, .8);
  box-shadow: 0 10px 22px -8px rgba(0, 0, 0, .85);
}
.rs-inset > span {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 12px 6px 3px;
  font-family: var(--font-ui); font-size: .75rem; font-weight: 700; line-height: 1.2; color: #fff; text-align: left;
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, .78));
}

/* ── page sections ── */
main > section > .wrap > .rs { margin-top: 14px; }
main > section > .wrap > .sec-head + .rs { margin-top: 0; }
.rs figure, figure.rs-card { margin: 0; }
.rs > .note { margin: 16px auto 0; text-align: center; }
main > section > .wrap > .rs + * { margin-top: 18px; }
.rs .sc-bar { margin-bottom: 10px; }
/* result facts are captions: never under 12px, never broken mid-fact */
.rs .sc-meta, .rs .sc-pill { font-size: .75rem; }
.rs .sc-meta { text-wrap: balance; }
.rs .sc-meta .rs-engine, .sc-stage .sc-meta .rs-engine { color: var(--text-muted); }

.rs-frame {
  position: relative; display: block; overflow: hidden; isolation: isolate;
  aspect-ratio: var(--ar, 1); border-radius: var(--r-md);
  background: rgba(var(--chrome-rgb), .05); border: 1px solid rgba(var(--chrome-rgb), .16);
  box-shadow: 0 1px 0 rgba(var(--chrome-rgb), .08) inset, 0 20px 40px -24px rgba(0, 0, 0, .9);
  transition: transform .35s var(--ease), border-color .35s var(--ease), box-shadow .35s var(--ease);
}
.rs-frame::after {
  content: ""; position: absolute; inset: 0; z-index: 5; border-radius: inherit; pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(var(--chrome-rgb), .07);
}
.rs-frame > .rs-m { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }
.rs-frame > .rs-player { position: absolute; inset: 0; }
.rs-card:hover .rs-frame, .rs-view:hover .rs-frame, .rs-tile:hover {
  transform: translateY(-2px); border-color: rgba(var(--chrome-rgb), .32);
  box-shadow: 0 1px 0 rgba(var(--chrome-rgb), .1) inset, 0 26px 46px -24px rgba(0, 0, 0, .95);
}
@media (prefers-reduced-motion: reduce) {
  .rs-card:hover .rs-frame, .rs-view:hover .rs-frame, .rs-tile:hover { transform: none; }
  .rs-hv, .demo-results .ag-steps { transition: none; }
}
.rs-cap { display: flex; flex-direction: column; gap: 6px; margin: 10px 0 0; font-size: .88rem; line-height: 1.45; color: var(--text-muted); max-width: 74ch; }
.rs-cap + .rs-cap { margin-top: 8px; }
.rs-cap > b { font-size: .95rem; font-weight: 700; color: var(--text); letter-spacing: -.01em; }
.rs-chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* a chip in a caption row sits on the PAGE, not on a picture, so it flips with the page */
.rs-cap .rs-chip, .rs-flagnote .rs-chip:not(.rs-flag) { color: var(--text); background: rgba(var(--chrome-rgb), .08); border-color: rgba(var(--chrome-rgb), .24); }
.rs-view { margin: 0; min-width: 0; }
.rs-view-name { display: block; margin-top: 7px; font-family: var(--font-ui); font-size: .8rem; font-weight: 600; line-height: 1.3; color: var(--text); text-wrap: balance; }
.rs-view-sub { display: block; margin-top: 2px; font-size: .8rem; line-height: 1.35; color: var(--text-muted); }
.rs-view.is-flagged .rs-frame { border-color: rgba(242, 193, 92, .9); box-shadow: 0 0 0 1px rgba(242, 193, 92, .35), 0 20px 40px -24px rgba(0, 0, 0, .9); }
.rs-flagnote {
  display: flex; align-items: flex-start; gap: 9px; margin: 12px 0 0; max-width: 74ch;
  font-size: .86rem; line-height: 1.45; color: var(--text-muted);
}
.rs-flagnote .rs-flag { flex: 0 0 auto; margin-top: 1px; }
.rs-flagnote b { color: var(--text); }

/* #generate — a results gallery */
.rs-gal { display: grid; gap: 12px; grid-template-columns: minmax(0, 5fr) minmax(0, 3.5fr) minmax(0, 3.5fr); }
.rs-tile { margin: 0; }
.rs-g-tall { grid-row: 1 / span 2; aspect-ratio: auto; }
.rs-g-wide { grid-column: 2 / span 2; }
.rs-over {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 4;
  display: flex; flex-direction: column; align-items: flex-start; gap: 7px;
  padding: 44px 14px 13px; font-size: .84rem; line-height: 1.42; color: rgba(255, 255, 255, .92);
  background: linear-gradient(180deg, rgba(4, 6, 6, 0), rgba(4, 6, 6, .6) 38%, rgba(4, 6, 6, .9));
}
.rs-over .rs-q { color: #fff; }
/* A poster carries its own title along the bottom edge — its caption sits at the top instead. */
.rs-over.rs-over-top { top: 0; bottom: auto; padding: 13px 14px 44px; background: linear-gradient(0deg, rgba(4, 6, 6, 0), rgba(4, 6, 6, .6) 62%, rgba(4, 6, 6, .88)); }
/* The Director film's facts line: caption-floor size, and each fact wraps as a unit so no line starts on a separator. */
.sc-story .sc-stage > .sc-bar .sc-meta.rs-facts, .sc-story .sc-stage > .sc-bar .sc-pill { font-size: .75rem; }
.rs-facts { text-wrap: balance; }
.rs-facts > span { white-space: nowrap; }

/* #engines — one idea, two engines */
.rs-two { display: flex; gap: 14px; align-items: flex-start; }
.rs-two > .rs-card { flex: var(--ar) 1 0; min-width: 0; }

/* #editing — reference → described change → result */
.rs-ab {
  display: grid; align-items: center; gap: 16px; max-width: 940px; margin-inline: auto;
  grid-template-columns: minmax(0, 1fr) minmax(210px, 1.05fr) minmax(0, 1fr);
}
.rs-ab .rs-frame { --ar: 2 / 3; }
.rs-bridge {
  position: relative; display: flex; flex-direction: column; gap: 8px; padding: 18px 18px 16px;
  border-radius: var(--r-md); background: rgba(var(--chrome-rgb), .05); border: 1px solid rgba(var(--chrome-rgb), .16);
}
.rs-bridge::before, .rs-bridge::after {
  content: ""; position: absolute; top: 50%; width: 7px; height: 11px; margin-top: -5.5px;
  background: rgba(var(--chrome-rgb), .7);
  -webkit-mask: var(--glyph-chev) center / contain no-repeat;
  mask: var(--glyph-chev) center / contain no-repeat;
}
.rs-bridge::before { left: -12px; }
.rs-bridge::after { right: -13px; }
.rs-bridge .rs-q { font-family: var(--font-display); font-size: 1.08rem; font-weight: 700; line-height: 1.35; letter-spacing: -.01em; margin-bottom: 6px; }
.rs-keep { display: flex; flex-wrap: wrap; gap: 6px; }
.rs-keep i {
  font-style: normal; font-family: var(--font-ui); font-size: .78rem; font-weight: 600; color: var(--text);
  padding: 3px 9px; border-radius: 999px; border: 1px solid rgba(var(--chrome-rgb), .3); background: rgba(var(--chrome-rgb), .06);
}

/* #product — two contact sheets */
/* Both sheets from ONE cell size: 6 equal cells across (4 + 2), 3 inner gaps on
   the left and 1 on the right, so rows, names and flag notes line up. */
.rs-shoot {
  --c: calc((100% - 28px - 40px) / 6);
  display: grid; grid-template-columns: calc(4 * var(--c) + 30px) minmax(0, 1fr); gap: 16px 28px; align-items: start;
}
.rs-sheet { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.rs-shoot-solo .rs-sheet { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.rs-sheet .rs-frame { --ar: 1; background: #eceae6; }

/* #characters — a character sheet and a location */
.rs-cast { display: grid; gap: 26px; }
.rs-charsheet { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; grid-template-columns: repeat(6, minmax(0, 1fr)); }
.rs-charsheet .rs-frame { --ar: 2 / 3; }
.rs-view-hero { grid-column: 1 / span 2; grid-row: 1 / span 2; display: flex; flex-direction: column; }
.rs-view-hero .rs-frame { flex: 1 1 0; min-height: 0; aspect-ratio: auto; }
.rs-desc {
  align-self: start; aspect-ratio: 2 / 3; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center; gap: 8px; padding: 14px;
  border-radius: var(--r-md); border: 1px dashed rgba(var(--chrome-rgb), .26); background: rgba(var(--chrome-rgb), .04);
  font-size: .86rem; line-height: 1.45; color: var(--text);
}
.rs-place { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.rs-place .rs-frame { --ar: 1024 / 683; }

/* #motion (images), #studio (video) — result cards */
.rs-duo { display: flex; gap: 16px; align-items: flex-start; }
.rs-duo > .rs-card { flex: var(--ar) 1 0; min-width: 0; }
.rs-card .rs-frame .rs-dur { font-family: var(--font-mono); }

/* #motion (video) — the result beside the numbers */
.rs-split { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 24px; align-items: start; }
@media (min-width: 960px) { .rs-split { align-items: center; grid-template-columns: minmax(0, 260px) minmax(0, 1fr); } }
.rs-split-main > * + * { margin-top: 12px; }

/* #story — the real Director film, watchable in place */
/* The film's width sets the row height; the scene list fills that height
   (height:0 + min-height:100%) instead of stretching the row — a stretched
   row made the 3:2 frame wider than its column and slid it under the list. */
.rs-film-block { display: grid; grid-template-columns: minmax(0, 1.8fr) minmax(0, 1fr); gap: 14px; align-items: stretch; }
.rs-film-block .rs-frame { --ar: 3 / 2; align-self: start; }
.rs-scenes { list-style: none; margin: 0; padding: 0; display: grid; grid-template-rows: repeat(5, minmax(0, 1fr)); gap: 8px; height: 0; min-height: 100%; }
.rs-scene {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 12px;
  width: 100%; height: 100%; min-height: 0; padding: 4px 10px 4px 4px; margin: 0; cursor: pointer; text-align: left;
  font: inherit; font-size: .86rem; line-height: 1.3; color: var(--text-muted);
  border-radius: 12px; background: rgba(var(--chrome-rgb), .04); border: 1px solid rgba(var(--chrome-rgb), .12);
  transition: background .25s var(--ease), border-color .25s var(--ease), color .25s var(--ease);
}
.rs-scene:hover { background: rgba(var(--chrome-rgb), .08); color: var(--text); }
.rs-scene.is-now { background: rgba(var(--chrome-rgb), .13); border-color: rgba(var(--chrome-rgb), .62); color: var(--text); }
.rs-scene-img { position: relative; display: block; height: 100%; max-height: 64px; aspect-ratio: 3 / 2; border-radius: 8px; overflow: hidden; border: 1px solid rgba(var(--chrome-rgb), .18); }
.rs-scene-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.rs-scene-txt b { display: block; margin-bottom: 2px; font-family: var(--font-mono); font-size: .75rem; font-weight: 600; color: var(--text-faint); }
.rs-scene.is-now .rs-scene-txt b { color: var(--text); }

/* #story — a real Story Mode scene: keyframe → clip → last frame, prompts beside */
.rs-scene1 { padding: 16px 18px; border-radius: var(--r-md); background: rgba(var(--chrome-rgb), .05); border: 1px solid rgba(var(--chrome-rgb), .14); }
.rs-scene1-row { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 26px; align-items: center; }
.rs-scene1-caps .rs-cap:first-child { margin-top: 0; }
.rs-chain { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
.rs-chain > figure { position: relative; }
.rs-chain > figure + figure::before {
  content: ""; position: absolute; left: -14px; top: calc(50% - 23.5px); width: 7px; height: 11px;
  background: rgba(var(--chrome-rgb), .75);
  -webkit-mask: var(--glyph-chev) center / contain no-repeat;
  mask: var(--glyph-chev) center / contain no-repeat;
}
.rs-chain .rs-frame { --ar: 2 / 3; }
.rs-chain .rs-next .rs-frame { border: 1.5px dashed rgba(var(--chrome-rgb), .65); }

/* ── tablet ── */
@media (max-width: 959px) {
  .rs-shoot { grid-template-columns: minmax(0, 1fr); }
  .rs-shoot-solo .rs-sheet { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .rs-film-block { grid-template-columns: minmax(0, 1fr); }
  .rs-scenes { grid-template-rows: none; grid-template-columns: repeat(5, minmax(0, 1fr)); height: auto; min-height: 0; }
  .rs-scene { grid-template-columns: minmax(0, 1fr); align-items: start; gap: 7px; padding: 6px; }
  .rs-scene-img { width: 100%; height: auto; max-height: none; }
  .rs-split { grid-template-columns: minmax(0, 260px) minmax(0, 1fr); }
  .rs-split-main .points { grid-template-columns: minmax(0, 1fr); }
  .rs-desc { padding: 10px; gap: 6px; font-size: .8rem; line-height: 1.4; }
  .rs-charsheet .rs-desc { grid-column: 1 / -1; aspect-ratio: auto; }
  /* smaller gallery tiles: a lighter caption panel that leaves the picture visible */
  .rs-gal .rs-sq .rs-over, .rs-gal .rs-g-wide .rs-over { padding: 22px 9px 8px; gap: 5px; background: linear-gradient(180deg, rgba(4, 6, 6, 0), rgba(4, 6, 6, .8)); }
}

/* ── phone ── */
@media (max-width: 719px) {
  .rs-gal { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .rs-g-tall { grid-row: auto; grid-column: 1 / -1; aspect-ratio: 4 / 5; }
  .rs-g-wide { grid-column: 1 / -1; order: 3; }
  .rs-gal .rs-over { padding: 30px 9px 9px; font-size: .78rem; line-height: 1.35; gap: 5px; }
  .rs-gal .rs-chip { font-family: var(--font-ui); max-width: 100%; overflow: hidden; text-overflow: ellipsis; padding: 2px 7px; }

  .rs-two, .rs-duo { display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .rs-portrait .rs-frame { width: min(100%, 280px); }

  .rs-ab { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; align-items: start; }
  .rs-ab .rs-bridge { grid-column: 1 / -1; order: 3; }
  .rs-bridge::before, .rs-bridge::after { display: none; }

  .rs-sheet, .rs-shoot-solo .rs-sheet, .rs-charsheet, .rs-place, .rs-scenes {
    grid-template-columns: none; grid-template-rows: none; grid-auto-flow: column;
    overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory;
    scrollbar-width: none;
    /* room for the 2px + 2px focus ring, which a scroller would clip */
    padding: 4px; margin: -4px;
  }
  .rs-sheet::-webkit-scrollbar, .rs-charsheet::-webkit-scrollbar, .rs-place::-webkit-scrollbar, .rs-scenes::-webkit-scrollbar { display: none; }
  .rs-sheet { grid-auto-columns: 42%; }
  .rs-charsheet { grid-auto-columns: 40%; }
  .rs-place { grid-auto-columns: 78%; }
  .rs-scenes { grid-auto-columns: 42%; height: auto; min-height: 0; }
  .rs-sheet > li, .rs-charsheet > li, .rs-place > li, .rs-scenes > li { scroll-snap-align: start; }
  .rs-view-hero { grid-column: auto; grid-row: auto; display: block; }
  .rs-view-hero .rs-frame { aspect-ratio: 2 / 3; }
  .rs-desc { justify-content: flex-start; }
  .rs-charsheet .rs-desc { grid-column: auto; }

  .rs-split { grid-template-columns: minmax(0, 1fr); }
  .rs-scene1 { padding: 14px; }
  .rs-scene1-row { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .rs-chain { gap: 14px; }
  .rs-chain > figure + figure::before { left: -10px; }
  .rs-chain .rs-view-name { font-size: .75rem; }
}
@media (max-width: 959px) {
  .rs-gal .rs-sq .rs-over.rs-over-top { padding: 8px 9px 22px; background: linear-gradient(0deg, rgba(4, 6, 6, 0), rgba(4, 6, 6, .8)); }
}
@media (max-width: 479px) {
  /* At phone width a small square is ~170px: its prompt would cover the picture. The chip stays; the large tiles keep their prompts. */
  .rs-gal .rs-sq .rs-over .rs-q { display: none; }
}


/* ═══════════════ THE HUB — every surface in one console (features.html) ═══════════════
   K (2026-09-17): "one unified console that has all the examples that right now
   only individually show up on top of each section". The hub is chrome + a
   surface switcher + ONE live surface console (built by seo-demo.js from the
   same SCRIPTS entry its page uses) + two quiet links.

   A HEIGHT PER SURFACE, fixed in CSS from first paint, so a MODE change can
   never move the page; only a surface switch does, and then at the
   crossfade's own pace. The surface fills the panel.
   Like .demo it takes no backdrop-filter: it sits inside the hero-panel,
   which is already the backdrop root. */
.demo-hub {
  /* EACH SURFACE ITS OWN HEIGHT. One height for all seven left the text-only
     surfaces (Chat, Agents, Workspace) ending halfway down the panel, worst on
     a phone. The height still comes from CSS at first paint, and seo-demo.js
     changes data-surface ONLY on a switch, so the console can never move while
     the modes inside a surface cycle.
     READ THIS FIRST, then the two blocks below it. On a PHONE (the three
     max-width bands at the bottom of this rule) each height is still a
     measured px number: the tallest that surface's own modes want anywhere
     inside its band, probed at the band's narrowest width too, where the beats
     wrap hardest. On a LAPTOP and bigger there are no per-surface numbers any
     more: every surface takes the fit budget, and the four that carry no
     artifact take it only up to their own 450px ceiling. Both rules are
     stated, with their measurements, in the two comments that follow. */
  /* THE FIT BUDGET (2026-09-20). On a laptop the console's height is what the
     first screen has left for it, not a number picked in advance: the sticky
     header, the hero copy above it and the panel's own padding come off the
     viewport, and the console takes the rest. Measured at 1366x768 — the
     shortest laptop we target — the console's top edge sits 303px down and
     the panel closes 29px below it, so 100vh - 340px leaves ~12px of air
     under the whole hero, and every taller screen gets a bigger console
     instead of a bigger gap. The floor keeps it usable on a short window;
     the ceiling stops a 27" display from stretching it past what it needs.

     THE BUDGET TRACKS THE COPY (2026-09-20). A bare px constant cannot see the
     block it is supposed to clear: the eyebrow, headline, lede and CTAs are
     rem-based, so a reader whose browser default is 18px or 20px — an ordinary
     accessibility setting, not zoom — grew that block by 18px / 35px while the
     budget did not move, and the panel's own bottom edge went below the fold.
     Measured: 8.8rem of the block scales with the root (the h1 is part vw), so
     8.8rem + 208px is the same 349px at a 16px root and grows with the copy
     above it — UNTIL the clamp bottoms out on the 410px floor, after which the
     budget cannot shrink any further and the extra has to come out of the
     hero's own margins instead. That is what the `max-height: 44rem` band
     below the hero compaction is for; it is not a second guess at the budget. */
  /* The floor is 410px, not 360: below that the text surfaces (Chat, Agents,
     Workspace) lose their result line and the panel's clip HIDES the loss —
     a page that scrolls 30px is not broken, a console missing the line that
     says what it did is. 410 = the 296px those surfaces need plus the chrome.
     Two declarations, not one: 100vh on iOS Safari is the LARGE viewport (it
     excludes the toolbar that is actually on screen), so an engine that knows
     svh takes the small one, which is the conservative budget; anything older
     keeps the vh line above it. */
  /* THE CEILING IS WHAT THE SURFACE CAN FILL (2026-09-21). With the index
     sections gone the console IS the page, so a tall window must not leave a
     dead band under it: measured at 1920x1080 the old 560px ceiling, and the
     500/520px per-surface caps on top of it, left 206-266px of empty stage
     below the panel and 98-165px of empty panel inside it. The budget's
     ceiling is 640px, which is what Images and Video can fill: measured at
     1920x1080 over 22 samples each, their deepest painted element sits 0-7px
     off the panel floor in every frame, because the artifact column is up
     from the first frame to the last. It is NOT what the other five surfaces
     can fill; their own 450px ceiling, and why, is stated beside their
     declarations below. 640 is a ceiling, not a target: past ~700 even an
     artifact has more stage than it has picture. */
  --hub-fit: clamp(410px, calc(100vh - 8.8rem - 208px), 640px);
  --hub-fit: clamp(410px, calc(100svh - 8.8rem - 208px), 640px);
  /* TWO CEILINGS, BECAUSE TWO KINDS OF SURFACE (2026-09-21, measured).
     Images and Video grow through an ARTIFACT that is on screen the whole
     film — the picture and the clip are painted from the first frame — so
     every pixel of the budget lands on the thing the surface exists to show:
     at 640 their deepest painted element sits 0-7px off the panel floor, in
     every frame.
     The other five have no artifact up the whole time. Chat, Agents,
     Workspace and Music paint a brief, a rail and three beats that ARRIVE, so
     their fullest frame is the LAST one. Vibe Code looks like an artifact
     surface and is not: its Live Preview pane is absolute and paints nothing
     until the BUILD pulse, so for ~46% of its loop the panel holds a prompt
     box, four chips and a button and nothing else — this is the frame you
     land on when you pick the tab. At the 640 ceiling, sampled at 1920x1080
     and 1440x1000 through the loop, the lead-in left 305px (Chat), 260px
     (Agents), 170px (Workspace), 430px (Music) and 343px (Vibe Code) of blank
     panel — bigger than the 165px the page shipped with before this round,
     because the void grows 1:1 with the ceiling. Their own ceiling is 450px.
     Re-measured there, 22 samples each at 1920x1080: Workspace 0, Vibe Code 0
     (its composer takes the slack — see the desktop band), Agents 70, Chat
     115, and Music 240 on the two frames where the player is crossfading to
     the next track — the one transient left, and it decays (240, 185, 132,
     80, 0) rather than sitting. Every settled frame closes on the bottom edge.
     450 is also what puts the FAQ back in view on a tall window — the page's
     only other content, and the only cue that anything follows the console. */
  --h-chat: min(var(--hub-fit), 450px); --h-image: var(--hub-fit); --h-video: var(--hub-fit);
  --h-music: min(var(--hub-fit), 450px);
  --h-vibe: min(var(--hub-fit), 450px); --h-agents: min(var(--hub-fit), 450px); --h-workspace: min(var(--hub-fit), 450px);
  --hub-h: var(--h-chat);                       /* the console opens on Chat */
  /* NO-JS / FAILED-SCRIPT GUARD (repo rule: pair every JS-only visibility gate
     with a CSS guard). Everything inside .demo-hub is written by
     /src/seo-demo.js. With no JS at all the element is not there: no height,
     no glass, no dots — instead of an empty glass box above the fold, the
     hero ends on its one CTA and then on the <noscript> index of the nine
     studios (.hub-noscript, below), which is the page's whole promise in the
     one form that needs no script. Verified headless with JS disabled: the
     console has no box at all and the nine links render. */
  display: none;
  position: relative; flex-direction: column;
  /* NOTHING MAY PAINT OUTSIDE THE PANEL (2026-09-20, K's Safari screenshot:
     the two reference frames painted over the link row and past the rounded
     bottom edge). The sizing that let that happen is fixed at the source, but
     the panel clips to its own radius as well, so no future artifact — and no
     engine's idea of an intrinsic size — can reach the page outside it. */
  overflow: hidden;
  margin-top: 30px; padding: 11px 16px 10px;
  border-radius: var(--r-xl); border: 1.5px solid var(--lg-stroke);
  background: rgba(var(--chrome-rgb), .05);
  box-shadow: var(--lg-shadow);
  font-family: var(--font-ui); user-select: none;
}
/* When JS IS running, the box is reserved before first paint (the inline head
   script on features.html sets this class, and takes it back on `load` if the
   console was never built). It holds its space and paints nothing — no glass,
   no border, no dots — so the deferred build costs no layout shift on a page
   whose Core Web Vitals are a ranking signal. */
html.hub-reserved .demo-hub { display: flex; height: var(--hub-h); visibility: hidden; }
/* Built. Only now does the console exist to look at. */
html .demo-hub.hub-ready { display: flex; height: var(--hub-h); visibility: visible; transition: height .22s var(--ease); }
.demo-hub[data-surface="chat"]      { --hub-h: var(--h-chat); }
.demo-hub[data-surface="image"]     { --hub-h: var(--h-image); }
.demo-hub[data-surface="video"]     { --hub-h: var(--h-video); }
.demo-hub[data-surface="music"]     { --hub-h: var(--h-music); }
.demo-hub[data-surface="vibe"]      { --hub-h: var(--h-vibe); }
.demo-hub[data-surface="agents"]    { --hub-h: var(--h-agents); }
.demo-hub[data-surface="workspace"] { --hub-h: var(--h-workspace); }
@media (prefers-reduced-motion: reduce) { html .demo-hub.hub-ready { transition: none; } }
@media (max-width: 949px) {
  .demo-hub {
    /* Images and Video are taller here than the text surfaces on purpose: in
       one column the artifact only gets what the brief and the result line
       leave it, and at 660px a portrait still rendered 204x306 inside a
       632px-wide stage. 720 buys the artifact ~60px of height, which is where
       it is short at this width. */
    --h-chat: 478px; --h-image: 738px; --h-video: 738px; --h-music: 542px;
    --h-vibe: 622px; --h-agents: 498px; --h-workspace: 498px;
    margin-top: 26px; padding: 11px 12px 10px;
  }
}
@media (max-width: 600px) {
  .demo-hub {
    --h-chat: 526px; --h-image: 718px; --h-video: 718px; --h-music: 562px;
    --h-vibe: 674px; --h-agents: 542px; --h-workspace: 526px;
    border-radius: var(--r-lg);
  }
}
/* Small phones wrap every beat one line further: a band of its own, or the
   text surfaces would clip at 320px. */
@media (max-width: 380px) {
  .demo-hub {
    --h-chat: 542px; --h-image: 718px; --h-video: 718px; --h-music: 614px;
    --h-vibe: 718px; --h-agents: 606px; --h-workspace: 626px;
  }
}
/* The no-JS index (features.html). It only ever renders when the console did
   not, so it borrows the hero's own type and sits where the console would. */
.hub-noscript {
  list-style: none; margin: 30px 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 8px 20px;
  font-family: var(--font-ui); font-size: .95rem; font-weight: 600;
}
.hub-noscript a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }

/* THE HEADER ROW: window dots, the surface switcher, Pause — one band on a
   laptop (the switcher is written into this row by mountHub), two below 950px
   where there is no width to share. */
.hub-chrome { flex: 0 0 auto; margin-bottom: 8px; min-width: 0; flex-wrap: wrap; row-gap: 8px; }
/* one line always: a wrapped title would steal height from the stage on one surface only */
.hub-title { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (min-width: 950px) {
  /* the selected tab names the surface, so the chrome title is a second label
     for the same thing — it stays in the DOM for the phone layout */
  .hub-chrome .hub-title { display: none; }
  .hub-chrome .hub-tabs { order: 1; flex: 1 1 auto; }
  .hub-chrome .hub-pause { order: 2; margin-left: 10px; }
}
@media (max-width: 949px) {
  .hub-chrome .hub-pause { order: 1; }
  .hub-chrome .hub-tabs { order: 2; flex: 1 1 100%; }
}
/* Pause / play the whole demo (WCAG 2.2.2): the console moves on its own. */
.hub-pause {
  margin-left: auto; flex: 0 0 auto; display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font-family: var(--font-ui); font-size: .76rem; font-weight: 700; letter-spacing: .02em; line-height: 1;
  color: var(--text); padding: 7px 12px 7px 10px; border-radius: 999px;
  background: rgba(var(--chrome-rgb), .06); border: 1px solid rgba(var(--chrome-rgb), .3);
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.hub-pause[hidden] { display: none; }
.hub-pause:hover { background: rgba(var(--chrome-rgb), .12); border-color: rgba(var(--chrome-rgb), .6); }
.hub-pause:focus-visible { outline: 2px solid rgba(var(--chrome-rgb), .9); outline-offset: 2px; border-radius: 999px; }
.hub-pause i { position: relative; display: block; width: 10px; height: 11px; flex: 0 0 10px; }
.hub-pause i::before, .hub-pause i::after { content: ""; position: absolute; top: 0; bottom: 0; width: 3px; border-radius: 1px; background: currentColor; }
.hub-pause i::before { left: 1px; } .hub-pause i::after { right: 1px; }
.hub-pause[aria-pressed="true"] i::before { left: 1px; width: 0; border-radius: 0; background: none; border-left: 9px solid currentColor; border-top: 5.5px solid transparent; border-bottom: 5.5px solid transparent; }
.hub-pause[aria-pressed="true"] i::after { display: none; }

/* The switcher is the primary control: bigger type and pills than the mode tabs
   inside a surface (.ag-item, .72rem), so the two levels never read alike. */
.hub-tabs {
  flex: 0 0 auto; display: flex; gap: 6px; min-width: 0;
  overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
  scrollbar-width: none; padding: 4px; margin: -4px;
}
.hub-tabs::-webkit-scrollbar { display: none; }
.hub-tab {
  flex: 0 0 auto; cursor: pointer;
  font-family: var(--font-ui); font-size: .88rem; font-weight: 600; letter-spacing: -.005em; line-height: 1;
  color: var(--text-muted); padding: 9px 15px; border-radius: 999px;
  background: transparent; border: 1px solid rgba(var(--chrome-rgb), .14);
  transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
}
.hub-tab:hover { color: var(--text); background: rgba(var(--chrome-rgb), .07); }
/* Selected reads by weight and a bright double border — a lighter fill would
   lighten the area behind its own white label. */
.hub-tab[aria-selected="true"] {
  color: var(--text); font-weight: 800; background: rgba(var(--chrome-rgb), .07); border-color: rgba(var(--chrome-rgb), .85);
  box-shadow: 0 0 0 1px rgba(var(--chrome-rgb), .35) inset, 0 8px 22px -14px rgba(0, 0, 0, .45);
}
.hub-tab:focus-visible { outline: 2px solid rgba(var(--chrome-rgb), .9); outline-offset: 2px; border-radius: 999px; }
/* Below desktop the seven tabs overflow: the row scrolls inside the console,
   its edges fade so it reads as scrollable. */
@media (max-width: 949px) {
  .hub-tabs {
    padding-inline: 10px; margin-inline: -10px;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 12px, #000 calc(100% - 22px), transparent);
    mask-image: linear-gradient(90deg, transparent 0, #000 12px, #000 calc(100% - 22px), transparent);
  }
}
@media (max-width: 600px) {
  /* the console takes the hero panel's side padding back, so a phone gets ~340px of stage, not ~300 */
  .demo-hub { margin-inline: calc(-1 * clamp(28px, 4vw, 52px) + 10px); }
  /* TAP TARGETS, NOT LABELS (2026-09-21). With the index sections deleted the
     tab row and the link row ARE the phone's navigation — and they were the
     two smallest things on the page: measured at 390x844 the tabs were 36px
     tall and each link 23px at 12.8px type, against a 44px hero button and a
     39px h1 beside them. 23px is under the 24px minimum a pointer target may
     be, on the one control that leads to the studio page. The type grows a
     little and the box grows more; the three phone height bands above carry
     the 18px this costs the stage. */
  .hub-tab { font-size: .86rem; padding: 12px 15px; }
  /* the link row's own phone rules are BELOW the base .hub-links block, not
     here — see THE PHONE LINK ROW at the end of it */
}

/* the live surface */
/* A GRID TRACK, NOT A PERCENTAGE (2026-09-20). .hub-panel's own height comes
   from flex-grow, and WebKit treats a main-axis flexed size as INDEFINITE for
   percentage resolution — so `height: 100%` on the surface below resolved to
   nothing there, the surface sized to its content, and the clip cut 92-181px
   off every artifact in Safari (K's screenshot: the pair over the link row).
   A single minmax(0, 1fr) track is definite in both engines: the surface
   stretches into it and .demo-body's own height:100% one level down resolves
   too. Chrome measures byte-identical with and without it. */
.hub-panel {
  position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden;
  display: grid; grid-template-rows: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
  border-top: 1px solid rgba(var(--chrome-rgb), .12); padding-top: 10px;
  transition: opacity .11s var(--ease);
}
.hub-panel.is-swapping { opacity: 0; transition-duration: .09s; }
.demo-hub .demo.hub-surface {
  /* height comes from the grid track above — never a percentage of it */
  height: auto; min-height: 0; margin: 0; padding: 0; overflow: hidden;
  border: 0; border-radius: 0; background: none; box-shadow: none;
}
/* the switcher and the chrome title already name the surface; the page's own
   try chip is replaced by the hub's link row */
.demo-hub .hub-surface > .demo-chrome,
.demo-hub .hub-surface .demo-try { display: none; }
.demo-hub .hub-surface.demo-agents .demo-body { height: 100%; }
.demo-hub .hub-surface .demo-stage { inset: 0; }

.hub-links {
  flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
  gap: 4px 18px; padding-top: 8px; margin-top: 8px; border-top: 1px solid rgba(var(--chrome-rgb), .12);
}
.hub-links a {
  font-family: var(--font-ui); font-size: .8rem; font-weight: 600; letter-spacing: .01em; line-height: 1.3;
  color: var(--text-muted); text-decoration: none; padding: 3px 2px; border-radius: 6px;
}
.hub-links a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
.hub-links a:focus-visible { outline: 2px solid rgba(var(--chrome-rgb), .9); outline-offset: 2px; }
/* "Open the <Studio> page" is now the page's ONLY route into detail from the
   console — the nine index sections that used to carry those links are gone.
   Full text colour and the heavier weight, at the same size and in the same
   box, so it reads as the destination it is without taking a pixel back off
   the stage above it. */
.hub-links .hub-page { margin-right: auto; color: var(--text); font-weight: 700; }

/* ── THE PHONE LINK ROW ─────────────────────────────────────────────────
   BELOW the base block on purpose (2026-09-21). These declarations used to
   sit in the @media (max-width: 600px) band ~40 lines ABOVE `.hub-links`,
   at the same specificity — so the base rule, being later, won every one of
   them that it also declares: `flex-wrap: nowrap`, `align-items: flex-start`,
   `gap: 2px` and `font-size: .76rem` were all inert, and the row rendered at
   the desktop's .8rem. Only `flex-direction: column`, which the base block
   does not set, ever reached the page. Measured, not assumed: 12.8px type in
   a 23px box at 390x844, where the hero's own button is 44px.
   Always two lines on a phone: a row that wrapped only for the longer labels
   shrank the stage by 34px on those surfaces and moved the divider.
   And a bigger box, because with the index sections deleted this row is the
   phone's only route into detail and 23px is under the 24px minimum a
   pointer target may be. The three phone height bands carry the 18px it
   costs the stage. */
@media (max-width: 600px) {
  .hub-links { flex-direction: column; flex-wrap: nowrap; align-items: flex-start; gap: 2px; }
  .hub-links a { font-size: .82rem; padding: 7px 2px; }
  .hub-links .hub-page { margin-right: 0; }
  .hub-links .hub-try { align-self: flex-end; }
}

/* No artifact on the rail surfaces: the mission reads top-down beside the rail,
   level with its first item. NOT centred — a centred group re-centred every
   time the brief wrapped or a beat arrived, so the line being read slid up and
   dropped back at each mode. */
.demo-hub .hub-surface:not(.demo-tabsmode) .ag-main { justify-content: flex-start; padding-top: 4px; }
.demo-hub .hub-surface:not(.demo-tabsmode) .ag-out { margin-top: 6px; }

/* Rail surfaces (Chat, Agents, Workspace) take the width on desktop: a wider
   rail, larger type, roomier beats — the same console, sized for the stage. */
@media (min-width: 950px) {
  .demo-hub .hub-surface.demo-agents:not(.demo-tabsmode) .demo-body { grid-template-columns: 232px minmax(0, 1fr); gap: 26px; }
  .demo-hub .hub-surface.demo-agents:not(.demo-tabsmode) .ag-side { gap: 6px; }
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-item { font-size: .84rem; padding: 10px 14px; border-radius: 11px; }
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-main { gap: 14px; padding-right: 6px; }
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-role { font-size: .76rem; }
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-brief { font-size: 1.04rem; padding: 16px 18px; min-height: 58px; }
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-steps { gap: 12px; }
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-step { font-size: .95rem; gap: 12px; }
  /* only the ring changes size — the masked glyph re-centres itself */
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-tick { width: 20px; height: 20px; flex-basis: 20px; }
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-out { padding: 16px 18px; }
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-out b { font-size: .95rem; }
  /* ── THE RAIL FILLS ITS PANEL (2026-09-21) ──────────────────────────
     Chat, Agents and Workspace carry no artifact, so their column used to
     stop wherever the third beat happened to land: measured at 1920x1080,
     98-165px of empty panel under the result line — the "large empty area"
     that made the console look unfinished once the page had nothing else
     on it. The BEATS take the slack now: the list grows into whatever the
     panel has spare and spaces its three rows evenly through it, so the
     brief still starts at the top, the beats read as a run in progress and
     the result line rides the bottom edge at every height from the 410px
     floor to the 640px ceiling. Nothing here is centred — the brief and the
     first beat keep their own positions, which is what the top-aligned rule
     above exists to protect. */
  .demo-hub .hub-surface:not(.demo-tabsmode) .ag-steps,
  .demo-hub .hub-surface[data-surface="music"] .ag-steps { flex: 1 1 auto; justify-content: space-evenly; }
  /* Music (tabs, no rail) and Vibe Code get the same readable scale */
  .demo-hub .hub-surface[data-surface="music"] .ag-brief { font-size: .98rem; padding: 14px 16px; }
  .demo-hub .hub-surface[data-surface="music"] .ag-step { font-size: .9rem; }
  .demo-hub .hub-surface[data-surface="music"] .ag-out b { font-size: .9rem; }
  /* ── THE COMPOSER FILLS ITS PANEL (2026-09-21, measured) ─────────────
     Vibe Code is an artifact surface, but only for the half of its film
     that HAS an artifact: the Live Preview pane is absolute (inset: 0) and
     paints nothing until the BUILD pulse, so the lead-in is a prompt box,
     four stack chips and a button — 192px of content in a 535px panel.
     Sampled every 700ms for 35s at 1920x1080 and 1440x1000, that lead-in
     left 343px of empty glass under BUILD in 6 of every 13 frames (~46% of
     the loop), and it is the frame you land on the moment you pick the tab.
     The prompt box takes that slack: a builder's composer is the one box on
     this surface that is honestly meant to be big ("Describe what you want
     to build…"), so the panel reads as a composer waiting rather than as a
     void, and the film keeps the whole 640 budget for its Live Preview.
     The chips and BUILD keep their own size directly under it. */
  .demo-hub .hub-surface[data-surface="vibe"] { display: flex; flex-direction: column; }
  .demo-hub .hub-surface[data-surface="vibe"] .demo-field { flex: 1 1 auto; font-size: 1.05rem; padding: 16px 18px; min-height: 84px; }
  /* a flex item stretches across the cross axis: the pill would become a
     full-width bar without this */
  .demo-hub .hub-surface[data-surface="vibe"] .demo-go { align-self: flex-start; }
  /* the song sits right under its beats, with presence, instead of floating in a
     band — so it keeps its own size and the BEATS above it absorb the panel's
     spare height (THE RAIL FILLS ITS PANEL), which puts the player and the
     result line on the bottom edge rather than the song in the middle of a void */
  .demo-hub .hub-surface[data-surface="music"] .ag-media { flex: 0 1 auto; margin-top: 8px; }
  .demo-hub .hub-surface[data-surface="music"] .ag-track { gap: 22px; padding: 10px 12px; }
  .demo-hub .hub-surface[data-surface="music"] .ag-track .st-art { width: 104px; height: 104px; flex-basis: 104px; }
  .demo-hub .hub-surface[data-surface="music"] .ag-track .st-art::after { font-size: .62rem; }
}

/* ═══ THE ARTIFACT STANDS BESIDE THE BRIEF (desktop hub, results surfaces) ═══
   One column made the stage a ≈995×282 letterbox: a 2:3 still rendered 187px
   wide, a 16:9 clip 373×210 while the beats were up, and the artifact — the
   whole point of Images and Video — was the smallest thing in the console.
   The brief, the beats and the result line now read down a reading column on
   the left and the artifact takes the rest of the panel at full height. It
   also stops the artifact RESIZING mid-film: it no longer shares its height
   with the beats, so the picture holds one size from reveal to result. */
@media (min-width: 950px) {
  .demo-hub .hub-surface.demo-results .ag-main {
    display: grid;
    grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
    /* THE READING COLUMN SPREADS, THE ARTIFACT KEEPS ITS HEIGHT (2026-09-21).
       The spare row used to be the LAST one, so the three beats stayed bunched
       under the brief and the result line hung off the bottom edge with a hole
       between them: measured .ag-out top minus .ag-steps bottom, 14 samples a
       surface, 34-63px at 1366x768 but 254-283px (Images) and 273-283px
       (Video) at 1920x1080 — the hole grew 1:1 with the raised ceiling. The
       1fr is the BEATS' row now and they space themselves through it, so the
       column reads as one run from brief to result at every height.
       space-BETWEEN, not space-evenly: evenly puts air above the first beat
       too, which floats it away from the brief it belongs to — and while the
       beats are still arriving that leaves one lonely tick mid-column. Between
       keeps the first beat under the brief and the last one on the result.
       This costs the picture nothing: .ag-media spans 1 / -1, so its height is
       the whole stack's however the rows inside are shared out. */
    grid-template-rows: auto minmax(0, 1fr) auto;
    column-gap: 22px; row-gap: 9px;
  }
  .demo-hub .hub-surface.demo-results .ag-brief { grid-area: 1 / 1; }
  .demo-hub .hub-surface.demo-results .ag-steps { grid-area: 2 / 1; }
  .demo-hub .hub-surface.demo-results .ag-out { grid-area: 3 / 1; align-self: end; }
  .demo-hub .hub-surface.demo-results .ag-media { grid-area: 1 / 2 / -1 / -1; }
  /* The reading column is narrow, so the prompt gets the lines it needs — a
     verbatim prompt must never be cut by a clamp — and the height is reserved
     so the beats below it sit still. */
  .demo-hub .hub-surface.demo-results .ag-brief-text { -webkit-line-clamp: 4; min-height: 4.2em; }
  /* The beats are not competing with the artifact any more: they stay up
     through the result instead of folding away to make room. */
  .demo-hub .hub-surface.demo-results .ag-steps,
  .demo-hub .hub-surface.demo-results.rs-shown .ag-steps { max-height: none; opacity: 1; margin-top: 0; justify-content: space-between; }
  .demo-hub .hub-surface.demo-results .ag-step { align-items: flex-start; }
  .demo-hub .hub-surface.demo-results .ag-tick { margin-top: 1px; }
}

/* THE CONTACT SHEETS IN THE HUB. Beside the reading column the stage is tall
   rather than wide, so the hero view takes the full height at its own shape
   and the thumb grid takes what is left: 4x2 for 2:3 views, 2x4 for square
   ones (four square rows need less width than four square columns). */
/* Every thumb is sized from its ROW — a 1fr row inside a grid whose height is
   the stage's — and takes its width from its own ratio, capped at the column.
   The rows are what cannot grow, so nothing here can push a thumb off the
   stage, and no container query has to be resolved for it (2026-09-20). */
.demo-hub .rs-c-sheet-tall .rs-thumbs { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); justify-content: stretch; align-content: stretch; }
.demo-hub .rs-c-sheet-tall .rs-th { aspect-ratio: 2 / 3; height: 100%; width: auto; max-width: 100%; align-self: stretch; justify-self: center; }
.demo-hub .rs-c-sheet-square .rs-hero { width: auto; height: 100%; max-width: 100%; }
/* Four square rows have to fit the stage's HEIGHT, so each thumb is a square
   the height of its row — 1fr columns made each thumb taller than its row and
   pushed the fourth row off the bottom. */
/* 2 x 3, not 2 x 4: the square sheet carries SIX views since 2026-09-21, and a
   fourth row left a dead band under the thumb column. */
.demo-hub .rs-c-sheet-square .rs-thumbs { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(3, minmax(0, 1fr)); justify-content: stretch; align-content: stretch; }
/* the hub's square sheet is the same slot-and-picture pair (see
   .rs-c-sheet-square .rs-th above) — the cell fills its track, the square
   picture takes the smaller side of it */
.demo-hub .rs-c-sheet-square .rs-th { height: 100%; width: 100%; max-width: none; align-self: stretch; justify-self: stretch; }
@media (max-width: 949px) {
  /* One column again: the stage is wide and short, so thumbs are still sized
     from their rows — two rows of four instead of four rows of two. */
  /* and 3 x 2 rather than 4 x 2: four columns left a ragged 4 + 2 bottom row */
  .demo-hub .rs-c-sheet-square .rs-thumbs { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); justify-content: start; }
  .demo-hub .rs-c-sheet-tall .rs-thumbs { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); align-content: stretch; }
  .demo-hub .rs-c-sheet-square .rs-hero { width: auto; height: 100%; max-width: 60%; }
}
@media (max-width: 600px) {
  .demo-hub .rs-c-sheet-square .rs-thumbs { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(3, minmax(0, 1fr)); justify-content: stretch; }
  .demo-hub .rs-c-sheet-tall .rs-thumbs { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(4, minmax(0, 1fr)); justify-content: stretch; }
  .demo-hub .rs-c-sheet-tall .rs-th { aspect-ratio: auto; }
}

/* Pip in the hub: the stage is ~3.4:1, so a cover crop cut the sprout and the
   pot. Show the whole frame over a blurred fill, like every other result. */
.demo-hub .ag-media .st-tile-motion { background-size: contain; background-repeat: no-repeat; }
.demo-hub .ag-media .st-tile-motion::before {
  content: ""; position: absolute; inset: -8%; z-index: 0;
  background-image: inherit; background-size: cover; background-position: center;
  filter: blur(22px) saturate(1.15) brightness(.55);
}
.demo-hub .ag-media .st-tile-motion .st-vid { z-index: 1; object-fit: contain; }
.demo-hub .ag-media .st-tile-motion .st-bigplay, .demo-hub .ag-media .st-tile-motion .st-badge { z-index: 2; }
