/* =============================================================================
   Clavis — site A · "Instrument"
   A precision instrument's readout, not a brochure. Evidence, not pitch.

   00  Fonts
   01  Tokens (light is primary, dark is a full sibling)
   02  Reset + base
   03  Typography
   04  The rail grid — the page's spine
   05  Rules, marks and small parts
   06  Buttons and links
   07  Header and footer
   08  Hero
   09  Spec blocks, tables, comparison
   10  The dual-track scrub — the signature interaction
   11  Pricing
   12  Forms
   13  Video slot
   14  Motion + reveal
   15  Reduced motion, print, forced colours
   ============================================================================= */

/* --- 00 Fonts -------------------------------------------------------------- */
/* Self-hosted latin subsets, variable throughout, so one file covers a family's
   whole weight (and, for the display face, width) range. 142KB for all three —
   this page is reached on 4G from a video, so the type budget is real. */

@font-face {
  font-family: 'Archivo';
  src: url('../fonts/archivo-var-latin.woff2') format('woff2');
  font-weight: 400 700;
  font-stretch: 62% 125%;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist';
  src: url('../fonts/geist-var-latin.woff2') format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist Mono';
  src: url('../fonts/geistmono-var-latin.woff2') format('woff2');
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --- 01 Tokens ------------------------------------------------------------- */
/* Every colour is defined here, on bare :root, in the light palette. The dark
   blocks below REDEFINE these same tokens and introduce none of their own — so
   no colour can exist only inside a media query. */

:root {
  color-scheme: light;

  /* Ground — a warm greige, not a white. Paper you would actually print on.   */
  --paper:      #E8E6DE;   /* the page                                         */
  --surface:    #F2F1EC;   /* raised panels, bubbles, table bodies             */
  --sunken:     #DEDBD2;   /* recessed wells: the audit column, code strips    */

  /* Ink */
  --ink:        #12141A;   /* primary text                                     */
  --ink-2:      #44474E;   /* secondary — 7.45:1 on paper                      */
  --ink-3:      #55585F;   /* tertiary: mono metadata — 5.70:1 paper, 5.15:1 sunken */

  /* Structure — rules carry alpha so one value works on any ground.           */
  --rule:       rgba(18, 20, 26, 0.16);
  --rule-soft:  rgba(18, 20, 26, 0.09);

  /* Signal. One accent, spent sparingly: CTAs, the rail index, a held state.  */
  --accent:      #1B33C0;
  --accent-ink:  #1B33C0;  /* the same blue works as small text — 7.36:1 paper */
  --accent-wash: #DCDFF6;
  --on-accent:   #F2F1EC;  /* 8.33:1 on the accent fill                        */

  /* Semantic. Never decorative.                                               */
  --verified:   #1F6B3F;   /* audit, checked, confirmed                        */
  --verified-wash: #DBE8DF;
  --chip:       rgba(18, 20, 26, 0.07);   /* the ground behind an action name  */

  /* Faces */
  --display: 'Archivo', 'Helvetica Neue', Helvetica, Arial, 'Segoe UI', Roboto, sans-serif;
  --body:    'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --mono:    'Geist Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;

  /* Metrics */
  --rail:      11.5rem;    /* the left annotation rail                         */
  --gutter:    clamp(3rem, 5vw, 5rem);
  --shell:     78rem;      /* outer container                                  */
  --measure:   68ch;       /* running text: 45–75 characters                   */
  --pad:       clamp(1.5rem, 5vw, 4rem);
  --bay:       clamp(6rem, 12vw, 13rem);   /* vertical rhythm between sections */
  --radius:    2px;        /* the page is drawn square. Only the glass rounds. */

  /* Motion */
  --ease:      cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur:       0.55s;

  --ring:      0 0 0 2px var(--paper), 0 0 0 4px var(--accent);
}

/* The chaptering ground. A band that flips to the opposite family so the page
   reads as chapters rather than one scroll — and because it redefines the same
   tokens rather than introducing its own, every component inside it follows
   without knowing it is there. Which family is the page and which is the
   interruption swaps with the theme. */
.invert {
  color-scheme: dark;
  --paper:      #12141A;
  --surface:    #191C23;
  --sunken:     #0C0E13;
  --ink:        #E8E6DE;
  --ink-2:      #B4B7BF;
  --ink-3:      #94979F;
  --rule:       rgba(232, 230, 222, 0.20);
  --rule-soft:  rgba(232, 230, 222, 0.11);
  --accent:      #93A8FF;
  --accent-ink:  #93A8FF;
  --accent-wash: #1A2040;
  --on-accent:   #12141A;
  --verified:   #6FC48F;
  --verified-wash: #10231A;
  --chip:       rgba(232, 230, 222, 0.10);
  --ring:      0 0 0 2px var(--paper), 0 0 0 4px var(--accent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    color-scheme: dark;
    --paper:      #12141A;
    --surface:    #191C23;
    --sunken:     #0C0E13;
    --ink:        #E8E6DE;
    --ink-2:      #B4B7BF;
    --ink-3:      #94979F;
    --rule:       rgba(232, 230, 222, 0.20);
    --rule-soft:  rgba(232, 230, 222, 0.11);
    --accent:      #93A8FF;
    --accent-ink:  #93A8FF;
    --accent-wash: #1A2040;
    --on-accent:   #12141A;
    --verified:   #6FC48F;
    --verified-wash: #10231A;
    --chip:       rgba(232, 230, 222, 0.10);
    --ring:       0 0 0 2px var(--paper), 0 0 0 4px var(--accent);
  }
  /* In the dark, the interruption cannot be darker still — it lifts instead. */
  :root:not([data-theme='light']) .invert {
    --paper:      #1D2028;
    --surface:    #262A33;
    --sunken:     #171A21;
    --rule:       rgba(232, 230, 222, 0.24);
    --rule-soft:  rgba(232, 230, 222, 0.14);
  }
}

:root[data-theme='dark'] {
  color-scheme: dark;
  --paper:      #12141A;
  --surface:    #191C23;
  --sunken:     #0C0E13;
  --ink:        #E8E6DE;
  --ink-2:      #B4B7BF;
  --ink-3:      #94979F;
  --rule:       rgba(232, 230, 222, 0.20);
  --rule-soft:  rgba(232, 230, 222, 0.11);
  --accent:      #93A8FF;
  --accent-ink:  #93A8FF;
  --accent-wash: #1A2040;
  --on-accent:   #12141A;
  --verified:   #6FC48F;
  --verified-wash: #10231A;
  --chip:       rgba(232, 230, 222, 0.10);
  --ring:       0 0 0 2px var(--paper), 0 0 0 4px var(--accent);
}
:root[data-theme='dark'] .invert {
  --paper:      #1D2028;
  --surface:    #262A33;
  --sunken:     #171A21;
  --rule:       rgba(232, 230, 222, 0.24);
  --rule-soft:  rgba(232, 230, 222, 0.14);
}

/* --- 02 Reset + base ------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  /* NOT scroll-behavior: smooth. ScrollTrigger measures the page by scrolling it
     to 0 and back; with CSS smooth scrolling that measurement is animated rather
     than synchronous, so every start/end is recorded against the wrong offset and
     the pinned scrub releases itself. site.js does the smooth anchor scroll
     instead, per click, where it cannot poison a refresh. */
  scroll-padding-top: 5rem;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 1.0625rem;         /* 17px */
  line-height: 1.62;
  font-weight: 400;
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  /* Nothing may ever scroll the body sideways. */
  overflow-x: clip;
}

img, svg, video, iframe { max-width: 100%; height: auto; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }

::selection { background: var(--accent); color: var(--on-accent); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}
:focus:not(:focus-visible) { outline: none; }

.skip {
  position: absolute;
  left: 0.75rem;
  top: -4rem;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--mono);
  font-size: 0.8125rem;
  text-decoration: none;
  transition: top 0.18s var(--ease);
}
.skip:focus { top: 0.75rem; }

.vh {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- 03 Typography --------------------------------------------------------- */

h1, h2, h3, h4 {
  font-family: var(--display);
  font-optical-sizing: auto;
  font-weight: 550;
  /* Narrowed a little. An instrument's labels are condensed, not wide. */
  font-stretch: 90%;
  line-height: 1.06;
  letter-spacing: -0.022em;
  text-wrap: balance;
  margin: 0;
}

h1 {
  font-size: clamp(2.55rem, 6.1vw, 4.5rem);
  font-weight: 600;
  font-stretch: 86%;
  letter-spacing: -0.036em;
  line-height: 0.98;
}
h2 {
  font-size: clamp(1.95rem, 3.7vw, 2.85rem);
  letter-spacing: -0.028em;
}
h3 {
  font-size: clamp(1.2rem, 1.7vw, 1.4rem);
  font-stretch: 94%;
  letter-spacing: -0.012em;
  line-height: 1.2;
}
h4 {
  font-size: 1.0625rem;
  font-stretch: 96%;
  letter-spacing: -0.004em;
  line-height: 1.3;
}
/* Six headings that sat directly under an h2 as h4s, skipping a level in the
   outline. Promoted to h3 and given the old h4 metrics, so the document is
   correct for a screen reader and identical on the screen. */
h3.subhead {
  font-size: 1.0625rem;
  font-stretch: 96%;
  letter-spacing: -0.004em;
  line-height: 1.3;
}

p { margin: 0 0 1.15em; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: clamp(1.15rem, 1.9vw, 1.375rem);
  line-height: 1.5;
  color: var(--ink-2);
  letter-spacing: -0.008em;
  max-width: 60ch;
}

strong, b { font-weight: 620; color: var(--ink); }
em { font-style: italic; }

.mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'ss02' on;
}

/* Digits align wherever they are compared. */
.num, table, .price, time, .stat__value { font-variant-numeric: tabular-nums; }

/* --- 04 The rail grid ------------------------------------------------------ */
/* An asymmetric two-column spine: a narrow annotation rail on the left carrying
   a running index and a monospace label that says something true about the
   section it marks, then the content column at ~68ch.
   A single hairline runs the full height of the page at the rail boundary. */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--pad);
}

.spine {
  position: relative;
}
/* The continuous hairline. Drawn once, spans every section.
   .spine already starts at the shell's padding edge, which is also where every
   .bay grid starts — so the gutter's centre is rail + half a gutter from here,
   and --pad must not appear in this sum or the line drifts as the viewport
   resizes. */
.spine::before {
  content: '';
  position: absolute;
  inset-block: 0;
  left: calc(var(--rail) + (var(--gutter) / 2));
  width: 1px;
  background: var(--rule);
  pointer-events: none;
}

.bay {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  column-gap: var(--gutter);
  padding-block: var(--bay);
  position: relative;
}
.bay + .bay { border-top: 1px solid var(--rule); }
/* A change of ground is already a divider; a rule on top of it is a seam. */
.bay.invert,
.bay.invert + .bay { border-top: 0; }
.foot.invert { background: var(--paper); color: var(--ink); }

.bay__rail {
  position: sticky;
  top: 6rem;
  align-self: start;
  height: max-content;
  padding-right: 0.75rem;
}

.rail__index {
  display: block;
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--accent-ink);
  font-variant-numeric: tabular-nums;
  margin-bottom: 0.5rem;
}
.rail__label {
  display: block;
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
  max-width: 9.5rem;
}
.rail__note {
  display: block;
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.6875rem;
  line-height: 1.5;
  color: var(--ink-3);
  max-width: 9.5rem;
  letter-spacing: 0.01em;
}

.bay__body > * + * { margin-top: 1.6rem; }
.bay__body > h2 + p,
.bay__body > h2 + .lede { margin-top: 1.15rem; }

/* Narrow: the rail becomes an in-flow strip above the content. It never
   overlaps the text, and the spine hairline is retired. */
@media (max-width: 61.25rem) {
  .spine::before { display: none; }
  .bay {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 1.5rem;
  }
  .bay__rail {
    position: static;
    display: flex;
    align-items: baseline;
    gap: 0.9rem;
    flex-wrap: wrap;
    padding: 0 0 0.7rem;
    border-bottom: 1px solid var(--rule);
  }
  .rail__index { margin-bottom: 0; }
  .rail__label, .rail__note { max-width: none; }
  .rail__note {
    width: 100%;
    margin-top: 0.15rem;
    padding-top: 0;
    border-top: 0;
  }
}

/* Chaptering. A band that flips to the opposite ground so the page reads as
   chapters rather than one long scroll. The band has to escape the shell to
   reach the viewport edges, so it is painted by a full-bleed pseudo-element
   rather than by the section's own background — the section itself only
   redefines the tokens, which is what makes every component inside it follow.

   body carries overflow-x: clip, so the 100vw bleed cannot open a scrollbar. */
.bay.invert {
  position: relative;
  isolation: isolate;
  padding-block: var(--bay);
  color: var(--ink);
}
.bay.invert::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset-block: 0;
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  background: var(--paper);
}
/* The spine's hairline is drawn once for the whole page and resolves --rule
   against the LIGHT ground, so it disappears inside a band. Each band redraws
   its own segment in its own rule colour. */
@media (min-width: 61.25rem) {
  .bay.invert::after {
    content: '';
    position: absolute;
    z-index: -1;
    inset-block: 0;
    left: calc(var(--rail) + (var(--gutter) / 2));
    width: 1px;
    background: var(--rule);
  }
}

/* --- 05 Rules, marks and small parts --------------------------------------- */

.eyebrow {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 1rem;
}
.eyebrow--accent { color: var(--accent-ink); }

/* A hairline with a short brass leader — the page's recurring mark. */
.hr {
  border: 0;
  height: 1px;
  background: var(--rule);
  margin: 2.5rem 0;
  position: relative;
}
.hr::before {
  content: '';
  position: absolute;
  left: 0; top: 0;
  width: 2.25rem; height: 1px;
  background: var(--accent);
}

.key {
  color: var(--accent);
  flex: none;
}

/* A confirmed/checked state. Semantic green only. */
.tick {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--verified);
}
.tick svg { width: 0.85em; height: 0.85em; }

.checklist {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--measure);
}
.checklist li {
  display: grid;
  grid-template-columns: 1.4rem 1fr;
  gap: 0.65rem;
  padding: 0.7rem 0;
  border-top: 1px solid var(--rule-soft);
  align-items: start;
}
.checklist li:first-child { border-top: 0; padding-top: 0; }
.checklist svg { width: 0.95rem; height: 0.95rem; margin-top: 0.42rem; }

.plainlist { list-style: none; margin: 0; padding: 0; max-width: var(--measure); }
.plainlist li {
  padding: 0.55rem 0 0.55rem 1.1rem;
  border-top: 1px solid var(--rule-soft);
  position: relative;
  color: var(--ink-2);
}
.plainlist li:first-child { border-top: 0; }
.plainlist li::before {
  content: '';
  position: absolute;
  left: 0; top: 1.32em;
  width: 0.45rem; height: 1px;
  background: var(--accent);
}

/* Two- and three-up grids that never become the same rounded accent card. */
.cols {
  display: grid;
  gap: 1px;
  background: var(--rule);
  border-block: 1px solid var(--rule);
  max-width: none;
}
@media (min-width: 40rem) { .cols--2 { grid-template-columns: 1fr 1fr; } }
@media (min-width: 48rem) { .cols--3 { grid-template-columns: repeat(3, 1fr); } }
.cols > * {
  background: var(--paper);
  padding: 1.5rem 1.5rem 1.6rem;
  margin: 0;
}
.cols h3 + p, .cols h4 + p { margin-top: 0.55rem; }
.cols p { color: var(--ink-2); max-width: 42ch; margin-bottom: 0; }

/* --- 06 Buttons and links -------------------------------------------------- */

a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 0.18em; }

.bay__body a:not(.btn):not(.rail__label) {
  color: var(--ink);
  text-decoration-color: var(--accent);
}
.bay__body a:not(.btn):hover { color: var(--accent-ink); }

.btn {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  --btn-rule: var(--ink);
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.82rem 1.35rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-rule);
  border-radius: 0;
  font-family: var(--body);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.004em;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.2s var(--ease), background-color 0.2s var(--ease),
              color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--primary {
  --btn-bg: var(--accent);
  --btn-fg: var(--on-accent);
  --btn-rule: var(--accent);
}
.btn--primary:hover { filter: brightness(1.07); }

.btn--ghost { --btn-bg: transparent; --btn-rule: var(--rule); }
.btn--ghost:hover { --btn-rule: var(--ink); }

.btn--sm { padding: 0.55rem 0.95rem; font-size: 0.875rem; }

.btn__meta {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  opacity: 0.72;
}

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  align-items: center;
}
.cta-note {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--ink-3);
  letter-spacing: 0.01em;
  max-width: 52ch;
  line-height: 1.6;
}

/* --- 07 Header and footer -------------------------------------------------- */

/* The floating pill, after E-Pulsive's own nav: centred, width-to-content, a
   translucent ground over a blur so the page moves underneath it. It is one
   element rather than a full-width bar, which is what keeps the top of the page
   feeling open — there is no edge-to-edge chrome cutting the hero off. */
/* The hero is dark whichever theme the reader is in, so a light pill sitting on
   it reads as a mistake. The pill takes the hero's own ink while it is over the
   hero and hands it back at the first section — set from JS, because "am I over
   the hero" is a scroll position and CSS cannot see one. */
.masthead--onhero {
  color-scheme: dark;
  --paper: #0A1A20;
  --ink: #EDEAE1;
  --ink-2: #B9C4C2;
  --rule: rgba(237, 234, 225, 0.22);
  --rule-soft: rgba(237, 234, 225, 0.14);
  --accent-ink: #94D2BD;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 12px 32px -14px rgba(0, 0, 0, 0.6);
}
.masthead {
  position: fixed;
  top: calc(1rem + env(safe-area-inset-top, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 50;
  width: max-content;
  max-width: calc(100vw - 2rem);
  display: flex;
  align-items: center;
  padding: 0.55rem 1.6rem 0.55rem 1.4rem;
  border: 1px solid var(--rule-soft);
  border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 72%, transparent);
  -webkit-backdrop-filter: saturate(1.5) blur(20px);
  backdrop-filter: saturate(1.5) blur(20px);
  box-shadow: 0 1px 2px rgba(18, 20, 26, 0.06), 0 12px 32px -14px rgba(18, 20, 26, 0.28);
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.masthead__inner {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 2vw, 1.75rem);
  min-width: 0;
}
.navlinks {
  display: none;
  align-items: center;
  gap: clamp(0.9rem, 1.8vw, 1.5rem);
}
.navlinks a {
  font-size: 0.875rem;
  letter-spacing: -0.005em;
  color: var(--ink-2);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.3s var(--ease);
}
.navlinks a:hover { color: var(--ink); }
/* Four points, one weight. The ask does not get a box round it — in a pill this
   small a filled button is the only loud thing on the page's quietest element. */
.navlinks { display: flex; }
@media (max-width: 45.99rem) {
  /* Narrow keeps the wordmark and the ask. Section links are a convenience;
     the booking link is the job. */
  .navlinks a:not([data-book]) { display: none; }
}

/* No logo exists. The wordmark is set in type — deliberately. */
.wordmark {
  font-family: var(--display);
  font-size: 1.0625rem;
  font-weight: 640;
  font-stretch: 82%;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  display: inline-flex;
  align-items: baseline;
  gap: 0.05em;
  white-space: nowrap;
}
/* The one glyph of brass in the wordmark: the "key". */
.wordmark i { font-style: normal; color: var(--accent-ink); }


.foot {
  border-top: 1px solid var(--rule);
  padding-block: clamp(4rem, 8vw, 7rem) clamp(3.5rem, 6vw, 5rem);
  font-size: 0.9375rem;
  color: var(--ink-2);
}
.foot__grid {
  display: grid;
  gap: 2rem;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 48rem) {
  .foot__grid { grid-template-columns: 1.4fr 1fr 1fr; gap: 3rem; }
}
.foot h3 {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.9rem;
}
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { padding: 0.28rem 0; }
.foot a { text-decoration-color: var(--rule); }
.foot a:hover { text-decoration-color: var(--accent); }
.foot__legal {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.6875rem;
  line-height: 1.7;
  letter-spacing: 0.02em;
  color: var(--ink-3);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.75rem;
}

/* --- 08 Hero ---------------------------------------------------------------
   Ported from the React/Tailwind component Mario picked, rebuilt in plain CSS
   so the page keeps its no-build, no-framework constraint (HANDOFF §7). Four
   parts: a masked grid, a clipped arc, gradient-clipped display type, and two
   entrance animations. Everything is expressed in the page's own tokens, so it
   follows the theme instead of carrying the component's hardcoded white. */

.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-block: clamp(7rem, 17vh, 11rem) clamp(5rem, 12vh, 9rem);
  text-align: center;
  background: linear-gradient(to bottom, var(--surface), var(--surface) 50%, var(--sunken) 88%);
}
.hero__inner { position: relative; z-index: 1; width: 100%; }

/* --- the alternate hero ------------------------------------------------------
   A Static Mesh Gradient (Paper Shaders, Apache-2.0) instead of the grid and
   the arc. The canvas is decoration: the gradient below it is what a reader
   without WebGL sees, and it is dark enough on its own to carry the light ink,
   so nothing here waits on a GPU. */
.hero--shader {
  /* the fallback IS the design when WebGL is missing — same four recipe
     colours, low to high, in the same order */
  background:
    radial-gradient(120% 90% at 22% 18%, #005F73 0%, transparent 62%),
    radial-gradient(90% 80% at 82% 30%, #94D2BD 0%, transparent 55%),
    radial-gradient(110% 90% at 62% 96%, #E9D8A6 0%, transparent 58%),
    #001219;
}
.hero__shader {
  position: absolute;
  inset: 0;
  z-index: -2;
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.5s var(--ease);
}
.hero__shader[data-shader-ready] { opacity: 1; }

/* The mesh runs from near-black to sand, so somewhere behind the headline it is
   always light. The scrim is not a mood — it is what makes the contrast a
   constant instead of a function of where the sand landed. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(to bottom,
      rgba(0, 18, 25, 0.50) 0%,
      rgba(0, 18, 25, 0.30) 42%,
      rgba(0, 18, 25, 0.60) 100%);
}

/* Inside the flood the light-ground brand colours would sink. Both scenes and
   both heroes share this problem and both solve it the same way: the channel
   keeps its identity, the ramp moves to where it can be read. */
/* Pixel-measured against the mesh where the name actually sits, rgb(52,87,85):
   the ramp this replaced ran 3.04 / 2.47 / 2.64 — two of its three stops under
   the 3:1 floor large text still has to clear. These run 3.54 / 3.36 / 3.58.
   Instagram's ramp is a family of colours, not three exact ones, so lifting it
   keeps the identity and buys the contrast. */
.hero--shader .door--wa { color: #25D366; }
.hero--shader .igs--1 { stop-color: #FF87B0; }
.hero--shader .igs--2 { stop-color: #D98CE8; }
.hero--shader .igs--3 { stop-color: #A9A4FF; }
.hero--shader .door--ig .chname {
  background-image: linear-gradient(100deg, #FF87B0 0%, #D98CE8 52%, #A9A4FF 100%);
}
.hero--shader .hero__title { background-image: none; color: var(--ink); }
/* Periwinkle on teal is a third palette arguing with two. The ask takes the
   sand off the top of the mesh's own ramp instead — still one signal colour,
   still the loudest thing in the section, and it belongs to this ground. */
.hero--shader .btn--primary {
  --btn-bg: #E9D8A6;
  --btn-rule: #E9D8A6;
  --btn-fg: #001219;
}
.hero--shader .btn--ghost { --btn-rule: rgba(233, 216, 166, 0.42); --btn-fg: var(--ink); }
.hero--shader .btn--ghost:hover { --btn-rule: #E9D8A6; }


/* The badge. The one pill on a page that is otherwise drawn square — it reads
   as a stamp rather than a control, which is the point. */
.hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 1.15rem;
  border: 2px solid var(--rule-soft);
  border-radius: 999px;
  background: linear-gradient(to top right,
              color-mix(in srgb, var(--ink) 5%, transparent), transparent);
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-decoration: none;
  animation: hero-in 0.8s var(--ease) both;
}
.hero__eyebrow:hover { color: var(--ink); border-color: var(--rule); }
.hero__chev {
  width: 0.9rem;
  height: 0.9rem;
  flex: none;
  transition: transform 0.3s var(--ease);
}
.hero__eyebrow:hover .hero__chev { transform: translateX(3px); }
/* On a phone the badge would wrap and leave the chevron stranded on its own
   line, so it tightens rather than breaks. */
@media (max-width: 30rem) {
  .hero__eyebrow {
    font-size: 0.625rem;
    letter-spacing: 0.06em;
    padding: 0.45rem 0.85rem;
    gap: 0.4rem;
  }
}

.hero__title {
  max-width: 21ch;
  margin: clamp(1.5rem, 4vh, 2.5rem) auto 0;
  font-family: var(--display);
  font-size: clamp(2.1rem, 5.9vw, 4.6rem);
  font-weight: 700;
  font-stretch: 96%;
  letter-spacing: -0.038em;
  line-height: 0.98;
  text-wrap: balance;
  color: var(--ink);
  animation: hero-in 0.9s 0.06s var(--ease) both;
}
/* Guarded, because the fallback for an unsupported background-clip is invisible
   text, not ungradiented text. */
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .hero__title {
    background-image: linear-gradient(to bottom right,
                      var(--ink) 30%,
                                      color-mix(in srgb, var(--ink) 55%, transparent));
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
  }
}

.hero__sub {
  max-width: 56ch;
  margin: clamp(1.25rem, 3vh, 1.85rem) auto clamp(1.75rem, 4vh, 2.5rem);
  font-size: clamp(1.0625rem, 1.5vw, 1.25rem);
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: var(--ink-2);
  text-wrap: balance;
  animation: hero-in 0.9s 0.12s var(--ease) both;
}

/* --- the cycling vertical ---------------------------------------------------
   One word of the headline changes: gyms, studios, health clinics, spas. All
   four are in the DOM; one is on at a time and the slot's WIDTH is animated to
   whichever that is, so the rest of the sentence closes up behind it instead of
   sitting against a gap the width of the longest word.

   Without JavaScript the first word stands and the others never show — and the
   whole list is in the accessible text beside it, so a screen reader is told
   every vertical rather than whichever one the timer happened to land on. */
.cycle {
  display: inline-grid;
  vertical-align: baseline;
  /* NOT overflow:hidden. It clipped the descender of "gyms" and shaved the last
     glyph off whichever word was mid-transition. Nothing follows this span in
     the sentence, and every inactive word is at opacity 0, so there is nothing
     for a clip to protect against — the width transition alone does the work. */
  transition: width 0.5s var(--ease);
  color: #E9D8A6;
}
.cycle__w {
  grid-area: 1 / 1;
  justify-self: start;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(0.32em);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.cycle__w.is-on { opacity: 1; transform: none; }
/* No JS, no measurement: the slot sizes to its content and only the first word
   is ever painted, which is exactly what the static sentence needs. */
.no-js .cycle,
.cycle:not([style]) { width: auto; }
.no-js .cycle__w:not(.is-on) { display: none; }

@media (prefers-reduced-motion: reduce) {
  .cycle { transition: none; }
  .cycle__w { transition: none; }
}

/* --- the four doors, as a masthead ------------------------------------------
   Borrowed from the press strip on the E-Pulsive site: a small centred label
   between two hairlines, then the names in one row of equal cells divided by
   rules. It used to be a rotator, one name at a time — but a rotator makes the
   reader wait to learn the fourth door, and the whole claim is that there are
   four. A masthead says it in one glance.

   The names are set in the page's own display face. Tracing somebody's
   registered logotype and calling it their logo is worse than setting their
   name honestly, and none of these four publish a webfont to borrow. */
.doorbar {
  margin: clamp(2.25rem, 6vh, 3.75rem) auto clamp(2.25rem, 6vh, 3.75rem);
  max-width: 56rem;
  animation: hero-in 0.9s 0.12s var(--ease) both;
}
.doorbar__cap {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(0.85rem, 2.4vw, 1.5rem);
  margin: 0 0 clamp(0.6rem, 1.6vh, 1.1rem);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--ink-2);
  white-space: nowrap;
  /* Running text carries `max-width: var(--measure)`. This is not running text,
     and inheriting it is what left the label sitting against the left edge of a
     56rem block instead of over the middle of the row it labels. */
  max-width: none;
  margin-inline: auto;
}
.doorbar__rule {
  flex: 0 0 auto;
  width: clamp(1.75rem, 5vw, 3rem);
  height: 1px;
  background: currentColor;
  opacity: 0.4;
}

/* The caption names what somebody can DO before it names the doors they can do
   it through — the four member verbs, then the owner's one, then the connector.
   It is ONE flex child so the cap's gap separates the rules from the phrase
   without opening a hole inside it. */
.doorbar__verbs {
  flex: 0 1 auto;
  min-width: 0;
}
.doorbar__from {
  font-style: normal;
  opacity: 0.66;
  margin-left: 0.34em;
}

/* Four verbs plus a connector is roughly twice the phrase this replaced, and at
   0.25em tracking that is wider than a phone. Below the fold-out the rules go
   (they frame a line that no longer fits between them), the phrase wraps, and
   the tracking comes in far enough to keep two tidy lines instead of four. */
@media (max-width: 40rem) {
  .doorbar__cap { white-space: normal; gap: 0; }
  .doorbar__rule { display: none; }
  .doorbar__verbs { letter-spacing: 0.14em; text-align: center; }
  .doorbar__from { margin-left: 0.4em; }
}
.doorbar__row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: stretch;
  justify-content: center;
}
/* Equal cells divided by hairlines, the first without one — so the rules read
   as dividers BETWEEN names rather than as a box drawn around them. */
.door {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.34em;
  padding: clamp(0.85rem, 2.2vh, 1.35rem) clamp(0.5rem, 1.6vw, 1.1rem);
  border-left: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
  font-family: var(--display);
  font-size: clamp(0.95rem, 1.85vw, 1.4rem);
  font-weight: 600;
  font-stretch: 96%;
  letter-spacing: -0.025em;
  white-space: nowrap;
  /* One after the next, the way the strip it is borrowed from arrives. */
  animation: hero-in 0.7s var(--ease) both;
  animation-delay: calc(0.24s + var(--door-i) * 0.09s);
}
.door:first-child { border-left: 0; }
.chname { font-weight: 640; letter-spacing: -0.03em; }
.ico, .chmark { flex: none; }
.door--gpt .chmark { height: 1.14em; }
.door--mail .chmark { height: 0.86em; }
.door--wa .chmark, .door--ig .chmark { height: 1.02em; }

/* Light ground: WhatsApp's own darker brand green, and the pink-to-indigo half
   of Instagram's gradient. The bright #25D366 and the yellow-orange end of the
   Instagram ramp both drop under 3:1 on paper, and a name carrying the meaning
   of the line has to clear the contrast floor like any other word. */
.door--wa { color: #128C7E; }
.door--gpt, .door--mail { color: var(--ink); }
.igs--1 { stop-color: #E1306C; }
.igs--2 { stop-color: #A93AAF; }
.igs--3 { stop-color: #5851DB; }
/* The name takes the same ramp as the mark, so the lockup reads as one object. */
.door--ig .chname {
  background-image: linear-gradient(100deg, #E1306C 0%, #A93AAF 52%, #5851DB 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .door--ig .chname { color: #A93AAF; }
}

/* Four across needs about this much room. Below it the row becomes two by two,
   and the dividers turn with it — a vertical rule between the columns, a
   horizontal one between the rows. */
@media (max-width: 44rem) {
  .doorbar__row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .door { border-left: 0; }
  .door:nth-child(even) { border-left: 1px solid color-mix(in srgb, var(--ink) 16%, transparent); }
  .door:nth-child(n + 3) { border-top: 1px solid color-mix(in srgb, var(--ink) 16%, transparent); }
}

.hero__cta {
  justify-content: center;
  animation: hero-in 0.9s 0.18s var(--ease) both;
}
.hero__note {
  margin: 1rem auto 0;
  max-width: 46ch;
  text-align: center;
  animation: hero-in 0.9s 0.24s var(--ease) both;
}

@keyframes hero-in {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}

/* The readout sits on the arc, so it needs to be above it and to read centred
   like everything else in this section. */
.readout {
  position: relative;
  z-index: 1;
  margin-top: clamp(3rem, 9vh, 6rem);
  display: grid;
  gap: 1px;
  background: var(--rule-soft);
  border-block: 1px solid var(--rule-soft);
  animation: hero-in 0.9s 0.3s var(--ease) both;
}
@media (min-width: 34rem) { .readout { grid-template-columns: 1fr 1fr; } }
@media (min-width: 62rem) { .readout { grid-template-columns: repeat(4, 1fr); } }
.readout__cell {
  background: var(--surface);
  padding: 1.15rem 1.15rem 1.25rem;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.readout__k {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.readout__v {
  font-family: var(--display);
  font-size: clamp(1.4rem, 2.4vw, 1.85rem);
  font-weight: 700;
  font-stretch: 92%;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.readout__s {
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--ink-2);
  max-width: none;
}

/* --- 09 Spec blocks, tables, comparison ------------------------------------ */

.spec {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
  max-width: none;
}
.spec caption {
  text-align: left;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding-bottom: 0.75rem;
}
.spec th, .spec td {
  text-align: left;
  padding: 0.8rem 1rem 0.8rem 0;
  border-top: 1px solid var(--rule);
  vertical-align: baseline;
}
.spec thead th {
  border-top: 0;
  border-bottom: 1px solid var(--ink);
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
  padding-bottom: 0.6rem;
}
.spec td:last-child, .spec th:last-child { padding-right: 0; }
.spec .n {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  width: 1%;
  min-width: 7rem;
  padding-left: 1rem;
}
.spec tbody th { font-weight: 500; }
.spec tr.is-total td, .spec tr.is-total th {
  border-top: 1px solid var(--ink);
  font-weight: 640;
  color: var(--ink);
}
.spec tr.is-cap td, .spec tr.is-cap th {
  background: var(--accent-wash);
  color: var(--ink);
}
.spec tr.is-cap .n { color: var(--ink); font-weight: 640; }

/* Any table too wide for its column scrolls inside its own well, never the
   page. min-width:0 is load-bearing: without it the well is a flex/grid item
   at min-content width and pushes the whole layout sideways instead. */
.scroller {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
  min-width: 0;
}

/* The 11-screens vs 1-message comparison. */
.compare {
  display: grid;
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}
@media (min-width: 44rem) { .compare { grid-template-columns: 1fr 1fr; } }
.compare__side { background: var(--paper); padding: 1.5rem; }
.compare__side--ours { background: var(--surface); }
.compare__h {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 1rem;
}
.compare__side--ours .compare__h { color: var(--accent-ink); }
.compare__count {
  font-family: var(--display);
  font-size: clamp(3rem, 8vw, 4.5rem);
  font-weight: 600;
  font-stretch: 84%;
  letter-spacing: -0.04em;
  line-height: 0.9;
  font-variant-numeric: tabular-nums;
  display: block;
}
.compare__side--ours .compare__count { color: var(--accent); }
.compare__unit {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.9375rem;
  color: var(--ink-2);
}
.steps {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  counter-reset: s;
}
.steps li {
  counter-increment: s;
  display: grid;
  grid-template-columns: 1.75rem 1fr;
  gap: 0.5rem;
  padding: 0.3rem 0;
  font-size: 0.875rem;
  color: var(--ink-2);
  line-height: 1.45;
}
.steps li::before {
  content: counter(s, decimal-leading-zero);
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  padding-top: 0.2em;
}
.compare__caveat {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
  font-size: 0.875rem;
  color: var(--ink-2);
  line-height: 1.5;
}

/* A quiet quotation block for the founder's own words. */
.saidblock {
  border-left: 2px solid var(--accent);
  padding: 0.15rem 0 0.15rem 1.35rem;
  max-width: 54ch;
}
.saidblock p {
  font-size: clamp(1.15rem, 2vw, 1.4rem);
  line-height: 1.4;
  letter-spacing: -0.014em;
  font-family: var(--display);
  font-stretch: 94%;
  font-weight: 480;
  color: var(--ink);
}
.saidblock footer {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-top: 0.9rem;
}

/* --- 10 The dual-track scrub ----------------------------------------------- */
/* The page's one engineered peak. Scroll drives two synchronised tracks:
   a WhatsApp exchange on the left, the audit that the exchange caused on the
   right. Human sentence, checked action, moving together. */

.scrub {
  position: relative;

  /* Every scene declares the same seven stage tokens, and nothing below this
     line names a channel colour directly. */
  --stage-ground: var(--sunken);
  --stage-ink: var(--ink);
  --stage-ink-2: var(--ink-2);
  --stage-rule: var(--rule);
  --stage-pill: var(--surface);
  --stage-ok: var(--verified);
  --stage-lift: 0 1px 2px rgba(0, 0, 0, 0.07), 0 16px 36px -26px rgba(0, 0, 0, 0.45);

  --stage-gut: clamp(1rem, 2.6vw, 2.25rem);
  --stage-top: calc(4.9rem + env(safe-area-inset-top, 0px));   /* under the nav pill */
  /* The caption and the log are the same width and sit at the same height on
     opposite edges, which is what leaves the device dead centre rather than
     shunted left by whatever the log happened to need. */
  --side-w: clamp(15rem, 24vw, 24rem);
  --stage-band: min(74svh, 100% - var(--stage-top) - var(--stage-gut));

  background: var(--stage-ground);
  color: var(--stage-ink);
}
.scrub__viewport { padding-block: clamp(2.5rem, 6vh, 4.5rem); }

/* Static — what a reader gets with no JS, no GSAP, or too little screen to
   compose on. Everything in flow, everything drawn, caption first. */
.stage {
  width: 100%;
  max-width: 108rem;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 3.5rem);
  display: flex;
  flex-direction: column;
  gap: clamp(1.25rem, 3vw, 2.5rem);
}
.stage__cap { order: -1; }
.stage__surface,
.stage__side { min-width: 0; }

/* Live — one screen, composed. The surface fills it, the caption sits top-left
   under the nav pill, the pills run down the right. */
.scrub.is-live .scrub__viewport {
  height: 100svh;
  padding-block: 0;
  overflow: hidden;
}
.scrub.is-live .stage {
  position: relative;
  display: block;
  height: 100%;
  max-width: none;
  padding: 0;
}
.scrub.is-live .stage__surface {
  position: absolute;
  inset: 0;
  display: grid;
  /* An explicit 1fr track, not an implicit auto one. With an auto row the
     surface's height is indefinite, so the device's own `height: 100%` cannot
     resolve, the row grows to the device's max-content height instead, and the
     device is then centred in a track taller than the screen — which reads as a
     phone sliding off the bottom edge. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  place-items: center;
  padding: var(--surf-pad, 0);
}
/* The stack never travels: it GROWS, and its centre stays on the same line as
   the caption opposite it. Each new line pushes the block open equally above and
   below, so the column reads as one object gaining mass rather than as a list
   scrolling past a window. site.js does the arithmetic; the CSS only has to give
   it a box with a definite height to centre inside. */
.scrub.is-live .stage__side {
  position: absolute;
  z-index: 2;
  top: 50%;
  right: var(--stage-gut);
  transform: translateY(-50%);
  width: var(--side-w);
  height: var(--stage-band);
  display: flex;
  flex-direction: column;
}
.scrub.is-live .stage__cap {
  position: absolute;
  z-index: 3;
  top: 50%;
  left: var(--stage-gut);
  transform: translateY(-50%);
  width: var(--side-w);
  padding: 0.7rem 0.95rem 0.75rem;
  border: 1px solid var(--stage-rule);
  border-radius: 0.7rem;
  background: color-mix(in srgb, var(--stage-pill) 74%, transparent);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  backdrop-filter: saturate(1.4) blur(14px);
}

/* The caption is a museum label on somebody else’s screen: whose screen it is,
   what is happening on it, how far through you are — then it gets out of the way. */
.stage__cap .eyebrow { margin-bottom: 0.4rem; color: var(--stage-ink-2); }
.stage__cap h2 {
  margin: 0;
  font-size: clamp(1rem, 1.5vw, 1.35rem);
  line-height: 1.22;
  letter-spacing: -0.018em;
  color: var(--stage-ink);
  max-width: 24ch;
}
.scrub__stamp {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0.05rem 0.4rem;
  border: 1px solid var(--stage-rule);
  border-radius: 999px;
  font-size: 0.9em;
  letter-spacing: 0.1em;
  color: var(--stage-ink-2);
}
.scrub__progress { display: none; }
.scrub.is-live .scrub__progress {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.7rem 0 0;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--stage-ink-2);
  font-variant-numeric: tabular-nums;
}
.scrub__bar {
  flex: 1;
  max-width: 6rem;
  height: 1px;
  background: var(--stage-rule);
  position: relative;
  overflow: hidden;
}
.scrub__bar span {
  position: absolute;
  inset: 0 auto 0 0;
  width: 100%;
  background: var(--stage-ink);
  transform: scaleX(0);
  transform-origin: left center;
}

/* --- the audit, as pills -----------------------------------------------------
   The log used to sit in a boxed well with a header and a footer. It does not
   any more: one pill per step, loose on the channel’s own ground, arriving as
   the reader scrolls. What never changes between the four scenes is the LINE
   inside the pill — action · actor · outcome · ref — because that sameness is
   the whole argument. */
.stage__log {
  position: relative;
  flex: 1;
  min-height: 0;
}
/* Pills fade at the edges rather than being guillotined — a loose pill cut in
   half by an invisible box is the one thing that would give the box away. */
.scrub.is-live .stage__log {
  --fade: linear-gradient(to bottom, transparent 0, #000 11%, #000 89%, transparent 100%);
  overflow: hidden;
  /* The clip box is pushed out past the pills on both sides, or it shears the
     border and the drop shadow off every edge the moment the column clips. */
  margin-inline: -1rem;
  padding-inline: 1rem;
  -webkit-mask-image: var(--fade);
  mask-image: var(--fade);
}
.pills {
  list-style: none;
  margin: 0;
  padding: 1.1rem 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  will-change: transform;
}
.logline {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.6rem;
  align-items: baseline;
  padding: 0.6rem 0.95rem 0.62rem 0.85rem;
  border: 1px solid var(--stage-rule);
  border-radius: 999px;
  background: color-mix(in srgb, var(--stage-pill) 80%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: var(--stage-lift);
  white-space: nowrap;
  overflow: hidden;
  max-width: 100%;
}
/* The step the reader is on goes solid and firms its edge. No colour: inside a
   flood, the page’s own accent would be a third palette arguing with two. */
.logline.is-live {
  background: var(--stage-pill);
  border-color: color-mix(in srgb, var(--stage-ink) 38%, var(--stage-rule));
}
.logline--detail { border-radius: 0.75rem; white-space: normal; }
/* A saga is not a line, it is four. Each step gets its own pill, set in from
   the left so the group reads as branching off the `book_session · system:saga`
   line above it — the argument this section makes (one action, one log) only
   holds if the action is atomic, and this is where that is shown rather than
   asserted. Smaller and quieter than the lines they hang off: they are the
   inside of one entry, not five more entries. */
.logline--step {
  margin-left: 1.7rem;
  padding: 0.34rem 0.75rem 0.36rem 0.66rem;
  font-size: 0.88em;
  opacity: 0.92;
}
.pills .logline--step + .logline--step { margin-top: -0.22rem; }
/* Below this the column is not tall enough to open a saga without pushing the
   ends of the log out of its own window, and a clipped first line is a worse
   trade than an unopened one. The saga still reads in full in §03. */
.logline--step { display: none; }
@media (min-height: 52rem) and (min-width: 68rem) {
  .logline--step { display: grid; }
}
.logline__t { color: var(--stage-ink-2); font-size: 0.9em; }
.logline__m { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.logline__a { color: var(--stage-ink); font-weight: 600; }
.logline__sep { color: var(--stage-ink-2); padding-inline: 0.3em; opacity: 0.65; }
.logline__actor { color: var(--stage-ink-2); }
.logline__out { color: var(--stage-ok); font-weight: 600; }
.logline__out--check { color: var(--stage-ink); font-weight: 600; }
.logline__ms { color: var(--stage-ink-2); }
.logline__k { display: none; }   /* “actor ” is noise once the line is a pill */
/* Below this the column is too narrow to carry the ref without eating the
   outcome, which is the field this whole section is arguing about. */
@media (max-width: 74rem) {
  .logline__ms,
  .logline__sep--ref { display: none; }
  /* Below this the phone still takes its share of the width off the top, so
     what is left for the log is narrower than the ref-drop alone recovers. */
  .pills { font-size: 0.65rem; }
}

/* --- the phone --- */
.phone {
  width: 100%;
  max-width: 20rem;
  margin-inline: auto;
  border: 1px solid var(--ink);
  border-radius: 1.9rem;
  background: var(--surface);
  padding: 0.45rem;
  box-shadow: 0 1px 0 var(--rule), 0 18px 40px -32px rgba(0, 0, 0, 0.55);
}
.phone__screen {
  border-radius: 1.5rem;
  overflow: hidden;
  background: var(--paper);
  display: flex;
  flex-direction: column;
}
.phone__bar {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.7rem 0.85rem;
  border-bottom: 1px solid var(--rule);
  background: var(--surface);
  flex: none;
}
/* The studio's own mark rather than initials: the bolt from the E-Pulsive
   logo, inlined so the avatar costs nothing and inherits its colour. */
.phone__avatar {
  width: 1.75rem; height: 1.75rem;
  border-radius: 50%;
  overflow: hidden;
  display: grid;
  place-items: center;
  color: #ffffff;
  background: #000000;
  flex: none;
}
.bolt { width: 62%; height: 62%; display: block; }
.phone__who { min-width: 0; }
.phone__name {
  display: block;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.phone__sub {
  display: block;
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-top: 0.1rem;
  /* A second line here would push the header bar taller and steal it from the
     thread; every door's subtitle truncates rather than wraps. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.phone__thread {
  flex: 1;
  position: relative;
}
.scrub.is-live .phone__thread { overflow: hidden; }
.thread__inner {
  position: relative;
  padding: 0.9rem 0.8rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  will-change: transform;
}

.msg {
  max-width: 84%;
  padding: 0.5rem 0.7rem 0.55rem;
  font-size: 0.8125rem;
  line-height: 1.42;
  border: 1px solid var(--rule);
  position: relative;
}
.msg--in {           /* the studio's assistant */
  align-self: flex-start;
  background: var(--surface);
  border-radius: 0.15rem 0.75rem 0.75rem 0.75rem;
}
.msg--out {          /* the member */
  align-self: flex-end;
  background: var(--sunken);
  border-radius: 0.75rem 0.15rem 0.75rem 0.75rem;
}
.msg--staff {        /* a human on the studio's team */
  align-self: flex-start;
  background: var(--verified-wash);
  border-color: color-mix(in srgb, var(--verified) 35%, transparent);
  border-radius: 0.15rem 0.75rem 0.75rem 0.75rem;
}
.msg time {
  display: block;
  margin-top: 0.25rem;
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  text-align: right;
}
.msg__who {
  display: block;
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--verified);
  margin-bottom: 0.2rem;
}


.scrub__legend {
  margin-top: clamp(1.25rem, 3vw, 1.75rem);
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
  display: grid;
  gap: 0.75rem 2.5rem;
  font-size: 0.875rem;
  color: var(--ink-2);
  max-width: 74ch;
}
@media (min-width: 44rem) { .scrub__legend { grid-template-columns: 1fr 1fr; } }
.scrub__legend p { margin: 0; max-width: none; }
.scrub__legend b { color: var(--ink); font-weight: 600; }

/* --- 10b Four doors, four grounds --------------------------------------------
   Four scenes, four silhouettes: a phone on a WhatsApp wallpaper, a phone on
   Instagram's ground, a mail client filling the screen, a console filling it.

   What never changes is the LOG LINE ITSELF: action · actor · outcome · ref,
   identical in all four. That identity IS the argument; prettying the line up
   per scene would quietly refute the thing the section exists to prove.

   The colour rule moved when the scenes went full screen. It used to be that a
   channel's palette lived strictly INSIDE THE GLASS. Now the glass is the whole
   viewport, so the rule is a boundary rather than a frame: inside these four
   sections the channel owns everything, including the caption and the pills;
   everywhere else on the page the accent is Clavis's own and no channel colour
   is allowed to touch it. The flood reads as a quotation because it stops. */

/* Both tracks are addressed by their data hook, not by a device class, so a new
   device only has to carry the attribute to inherit the scrubbing geometry. */
[data-thread-window],
[data-log-window] { position: relative; min-height: 0; }
.scrub.is-live [data-thread-window],
.scrub.is-live [data-log-window] { overflow: hidden; }

/* --- shared phone chrome -----------------------------------------------------
   Rebuilt against a mirrored iPhone rather than from memory: the status bar
   with its island, the nav bar, the day divider, the stamp riding the last line
   of the bubble, the composer row, the home indicator. The parts a person
   recognises before they read a word are the parts that make this read as a
   phone and not as a rounded rectangle with green boxes in it. */
/* A thread opens on a date, not on a message. */
.daysep {
  align-self: center;
  margin: 0.1rem 0 0.35rem;
  padding: 0.16rem 0.65rem;
  border-radius: 0.45rem;
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}
.phone__back { flex: none; height: 0.95rem; width: auto; }
.phone__icons { flex: none; margin-left: auto; display: flex; align-items: center; gap: 0.85rem; }
.barc { height: 0.9rem; width: auto; }
.cmpic { height: 1.05rem; width: auto; flex: none; opacity: 0.85; }
.phone__composer {
  display: flex;
  flex: none;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.5rem;
  border-top: 1px solid var(--rule);
  background: var(--surface);
}
.phone__input {
  flex: 1;
  min-width: 0;
  font-size: 0.75rem;
  color: var(--ink-3);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.24rem 0.6rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.phone__send {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 0.625rem;
  background: var(--ink);
  color: var(--paper);
}

/* --- 1 · WhatsApp ------------------------------------------------------------
   The classic header green rather than the current one: it is the colour people
   actually picture when you say WhatsApp, and at 7.7:1 against white it carries
   the header text without a fight. */
.scrub--wa {
  --wa-bar: #075e54;
  --wa-bar-ink: #ffffff;
  --wa-bar-ink-2: #cfe0dc;
  --wa-ground: #efeae2;
  --wa-in: #ffffff;
  --wa-out: #d9fdd3;
  --wa-ink: #111b21;
  --wa-ink-2: #5c6b73;
  --wa-tick: #0a6ba6;
  --wa-send: #00745a;
  --wa-btn: #046a99;
  --wa-shadow: rgba(11, 20, 26, 0.13);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .scrub--wa {
    --wa-bar: #202c33;
    --wa-bar-ink-2: #8696a0;
    --wa-ground: #0b141a;
    --wa-in: #202c33;
    --wa-out: #005c4b;
    --wa-ink: #e9edef;
    --wa-ink-2: #b9c6cb;
    --wa-tick: #7fd3f5;
    --wa-btn: #53bdeb;
    --wa-shadow: rgba(0, 0, 0, 0.4);
  }
}
:root[data-theme='dark'] .scrub--wa {
  --wa-bar: #202c33;
  --wa-bar-ink-2: #8696a0;
  --wa-ground: #0b141a;
  --wa-in: #202c33;
  --wa-out: #005c4b;
  --wa-ink: #e9edef;
  --wa-ink-2: #b9c6cb;
  --wa-tick: #7fd3f5;
  --wa-btn: #53bdeb;
  --wa-shadow: rgba(0, 0, 0, 0.4);
}
.phone--wa .phone__bar {
  background: var(--wa-bar);
  border-bottom-color: transparent;
}
.phone--wa .phone__name { color: var(--wa-bar-ink); }
.phone--wa .phone__sub { color: var(--wa-bar-ink-2); }
.phone--wa .phone__back,
.phone--wa .phone__icons { color: var(--wa-bar-ink); }
.phone--wa .phone__avatar {
  background: rgba(255, 255, 255, 0.2);
  border-color: transparent;
  color: var(--wa-bar-ink);
}
/* A speckle, not a doodle print. Three offset radial stops read as paper grain
   at thumbnail size and cost nothing — no image request on a 4G page. */
.phone--wa .phone__thread {
  background-color: var(--wa-ground);
  background-image:
    radial-gradient(circle at 22% 32%, rgba(0, 0, 0, 0.035) 0 1.5px, transparent 1.6px),
    radial-gradient(circle at 72% 61%, rgba(0, 0, 0, 0.03) 0 2px, transparent 2.1px),
    radial-gradient(circle at 47% 86%, rgba(0, 0, 0, 0.025) 0 1px, transparent 1.1px);
  background-size: 48px 44px, 62px 58px, 36px 40px;
}
/* Same silhouette as the Instagram scene: one radius all round, no tail. The
   tails were drawn with borders on a pseudo-element and read as a notch rather
   than as a point at this size. */
.phone--wa .msg {
  border-color: transparent;
  border-radius: 1.15rem;
  padding: 0.5rem 0.8rem 0.55rem;
  color: var(--wa-ink);
  box-shadow: 0 1px 0.5px var(--wa-shadow);
}
/* The stamp rides the last line of the bubble the way WhatsApp draws it, rather
   than taking a line of its own. Floated, so it drops to its own line only when
   the last line genuinely has no room — which is the same rule the app uses. */
/* WhatsApp tucks the stamp into the last line of the bubble when it fits and
   drops it to its own line when it does not — which is why it must be exactly
   one TEXT line tall, not one stamp line tall. Given that height, both cases
   put the stamp's baseline on the same grid and leave the same gap to the
   bubble's bottom edge; with the stamp's own line-height instead, the tucked
   case sat a few pixels lower than the dropped one and every bubble disagreed.
   1.42em of the BUBBLE's 0.8125rem — written out, because em here would resolve
   against the stamp's smaller size. */
.phone--wa .msg__stamp {
  float: right;
  display: flex;
  align-items: flex-end;
  height: 1.154rem;
  margin: 0 0 0 0.6rem;
  line-height: 1;
}
.phone--wa .msg__stamp time { display: inline; }
.phone--wa .msg--in { background: var(--wa-in); }

/* Quick-reply buttons. Real behaviour, not decoration: inside the 24-hour
   window the assistant sends interactive replies, and one tap is the whole
   argument the hero makes. They hang off the bottom of the bubble as full-width
   rows the way WhatsApp draws them, so the bubble has to clip to its radius. */
.msg--btns { overflow: hidden; padding-bottom: 0; }
.qbtns { display: block; clear: both; margin: 0.5rem -0.7rem 0; }
.qbtn {
  display: block;
  padding: 0.46rem 0.5rem;
  text-align: center;
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.2;
  color: var(--wa-btn);
  border-top: 1px solid color-mix(in srgb, var(--wa-ink) 12%, transparent);
}
.phone--wa .qbtn { padding: 0.56rem 0.5rem 0.44rem; }
.phone--wa .msg--out { background: var(--wa-out); }
.phone--wa .msg time { color: var(--wa-ink-2); }
.msg__stamp {
  display: block;
  margin-top: 0.25rem;
  text-align: right;
  white-space: nowrap;
}
.msg__stamp time { display: inline; margin-top: 0; }
.tick {
  margin-left: 0.3rem;
  font-size: 0.625rem;
  letter-spacing: -0.22em;
  color: var(--wa-tick);
}
.phone--wa .phone__composer {
  background: var(--wa-ground);
  border-top-color: transparent;
}
.phone--wa .phone__input {
  background: var(--wa-in);
  border: 0;
  border-radius: 999px;
  padding: 0.4rem 0.85rem;
  color: var(--wa-ink-2);
}
.phone--wa .phone__send { background: var(--wa-send); color: #ffffff; }

/* --- 2 · Instagram ---------------------------------------------------------- */
.scrub--ig {
  --ig-ground: #ffffff;
  --ig-in: #efefef;
  --ig-ink: #000000;
  --ig-ink-2: #737373;
  --ig-ink-3: #5f5f5f;
  --ig-btn: #0a6ba6;
  --ig-rule: #dbdbdb;
  --ig-card: #e4e4e4;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .scrub--ig {
    --ig-ground: #000000;
    --ig-in: #262626;
    --ig-ink: #ffffff;
    --ig-ink-2: #a8a8a8;
    --ig-ink-3: #a8a8a8;
    --ig-btn: #4db5f5;
    --ig-rule: #262626;
    --ig-card: #303030;
  }
}
:root[data-theme='dark'] .scrub--ig {
  --ig-ground: #000000;
  --ig-in: #262626;
  --ig-ink: #ffffff;
  --ig-ink-2: #a8a8a8;
  --ig-ink-3: #a8a8a8;
  --ig-btn: #4db5f5;
  --ig-rule: #262626;
  --ig-card: #303030;
}
.phone--ig .phone__screen { background: var(--ig-ground); }
.phone--ig .phone__bar {
  background: var(--ig-ground);
  border-bottom-color: var(--ig-rule);
}
.phone--ig .phone__name {
  color: var(--ig-ink);
  font-family: var(--body);
  font-weight: 640;
  letter-spacing: 0;
}
.phone--ig .phone__sub {
  color: var(--ig-ink-2);
  text-transform: none;
  letter-spacing: 0;
  font-family: var(--body);
  font-size: 0.625rem;
}
.phone--ig .phone__back,
.phone--ig .phone__icons { color: var(--ig-ink); }
.phone--ig .phone__avatar--ring {
  position: relative;
  border-color: transparent;
  background: var(--ig-in);
  color: var(--ig-ink-3);
  box-shadow: 0 0 0 1.5px var(--ig-ground), 0 0 0 3.5px #d62976;
}
/* The story ring, drawn as a masked conic sweep. Guarded, because the fallback
   without a mask would be a gradient DISC over the initials, not a ring. */
@supports (mask: radial-gradient(#000, #000)) or (-webkit-mask: radial-gradient(#000, #000)) {
  .phone--ig .phone__avatar--ring { box-shadow: none; }
  .phone--ig .phone__avatar--ring::after {
    content: '';
    position: absolute;
    inset: -4px;
    border-radius: 50%;
    background: conic-gradient(from 20deg, #feda75, #fa7e1e, #d62976, #962fbf, #4f5bd5, #feda75);
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
            mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
  }
}
.phone--ig .phone__thread { background: var(--ig-ground); }

/* Instagram DMs, as they actually are: pure black, fully-round bubbles, a
   violet ramp outbound, circular chrome buttons in the bar, and — the detail
   that gives every fake away — NO timestamp on the bubbles. The time is a rule
   between runs, once, and nowhere else. */
.phone--ig .msg time { display: none; }
.phone--ig .daysep {
  background: none;
  padding: 0.55rem 0 0.35rem;
  font-size: 0.5625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ig-ink-2);
}
.phone--ig .msg {
  border-radius: 1.15rem;
  padding: 0.5rem 0.8rem 0.55rem;
  border: 0;
}
.phone--ig .msg--out {
  background-image: linear-gradient(160deg, #A033FF 0%, #7A2FF0 100%);
  color: #ffffff;
}
.phone--ig .phone__name { display: inline-flex; align-items: center; gap: 0.2rem; }
.phone__chev { font-style: normal; opacity: 0.55; font-size: 1.1em; line-height: 0.9; }
.igbtn {
  width: 1.6rem;
  height: 1.6rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--ig-in);
  color: var(--ig-ink);
}
.igbtn svg { height: 0.72rem; width: auto; }
.igcam {
  flex: none;
  width: 1.6rem;
  height: 1.6rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #3797F0;
  color: #ffffff;
}
.igcam svg { height: 0.85rem; width: auto; }
.phone--ig .phone__composer { gap: 0.6rem; border-top: 0; background: var(--ig-ground); }
.phone--ig .phone__input {
  background: var(--ig-in);
  border: 0;
  border-radius: 999px;
  padding: 0.4rem 0.85rem;
  color: var(--ig-ink-3);
}
.phone--ig .cmpic { color: var(--ig-ink); opacity: 1; }
.phone--ig .msg { color: var(--ig-ink); }
.phone--ig .msg--in { background: var(--ig-in); }
/* A payment link is not sent as a link. `send_instagram_dm` upgrades the
   message to Meta's BUTTON TEMPLATE — body text plus one `web_url` button
   titled "Pay now" — so the URL itself never appears in the thread. Same rail
   as WhatsApp's reply buttons, in Instagram's own link blue. */
.phone--ig .msg--btns { padding-bottom: 0.5rem; overflow: visible; }
.phone--ig .qbtns { margin: 0.45rem 0 0; }
.phone--ig .qbtn {
  padding: 0.4rem 0.7rem 0.42rem;
  border: 1px solid color-mix(in srgb, var(--ig-ink) 22%, transparent);
  border-radius: 999px;
  font-weight: 600;
  color: var(--ig-btn);
}


/* --- 3 · Email, as a desktop client -----------------------------------------
   The silhouette breaks here on purpose: two phones, then two machines. A mail
   thread has a list rail beside it and a reading pane, and pretending that is a
   phone conversation was what made scene 3 read as scene 1 in a different font. */
.mailapp {
  /* The stage's ink belongs to the overlays, not to the app underneath them.
     The client is page-palette throughout, so it takes the page's ink back. */
  color: var(--ink);
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 0.4rem;
  box-shadow: 0 18px 40px -34px rgba(0, 0, 0, 0.5);
}
.scrub.is-live .mailapp { height: clamp(13rem, 40svh, 22rem); }
.mailapp__bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  background: var(--sunken);
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.06em;
  color: var(--ink-3);
}
.mailapp__dots { display: flex; gap: 0.3rem; flex: none; }
.mailapp__dots i {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--rule);
}
.mailapp__title {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mailapp__search {
  margin-left: auto;
  flex: none;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.1rem 0.65rem;
}
.mailapp__body { flex: 1; min-height: 0; display: flex; }
.maillist {
  display: none;
  flex: none;
  width: 12.5rem;
  flex-direction: column;
  overflow: hidden;
  background: var(--paper);
  border-right: 1px solid var(--rule);
}
.maillist__h {
  padding: 0.55rem 0.75rem 0.45rem;
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.maillist__row {
  display: block;
  padding: 0.42rem 0.75rem;
  border-top: 1px solid var(--rule-soft);
}
.maillist__row b,
.maillist__row i {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.maillist__row b { font-size: 0.75rem; font-weight: 600; color: var(--ink-2); }
.maillist__row i { font-style: normal; font-size: 0.6875rem; color: var(--ink-3); }
.maillist__row--on {
  background: var(--surface);
  box-shadow: inset 2px 0 0 var(--accent);
}
.maillist__row--on b { color: var(--ink); }
.mailread { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.mailread__head {
  flex: none;
  padding: 0.65rem 0.95rem 0.6rem;
  border-bottom: 1px solid var(--rule);
}
.mailread__subject {
  display: block;
  font-family: var(--display);
  font-size: 1rem;
  font-weight: 580;
  font-stretch: 94%;
  letter-spacing: -0.006em;
  color: var(--ink);
}
.mailread__meta {
  display: block;
  margin-top: 0.22rem;
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.mailread__thread { flex: 1; }
.mailread .thread__inner { padding: 0; gap: 0; }
.mailread .msg {
  max-width: none;
  align-self: stretch;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.65rem;
  border: 0;
  border-top: 1px solid var(--rule-soft);
  border-radius: 0;
  background: transparent;
  padding: 0.7rem 0.95rem 0.8rem;
}
.mailread .msg:first-child { border-top: 0; }
.mailread .msg--staff { background: var(--verified-wash); }
.msg__avatar {
  flex: none;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.02em;
  background: var(--chip);
  color: var(--ink-2);
}
.msg__avatar--bot { background: var(--accent-wash); color: var(--accent-ink); }
.msg__avatar--staff {
  background: var(--surface);
  color: var(--verified);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--verified) 40%, transparent);
}
.msg__body { min-width: 0; }
.mailread .msg__who {
  display: block;
  font-family: var(--body);
  font-size: 0.75rem;
  font-weight: 640;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  margin-bottom: 0.25rem;
}
.msg__addr {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--ink-3);
}
.mailread .msg--staff .msg__who { color: var(--verified); }
.mailread .msg time { text-align: left; }
.quote {
  display: block;
  margin-top: 0.5rem;
  padding-left: 0.65rem;
  border-left: 2px solid var(--rule);
  font-size: 0.75rem;
  color: var(--ink-3);
}
.mailread__actions {
  flex: none;
  display: flex;
  gap: 0.4rem;
  padding: 0.5rem 0.95rem;
  border-top: 1px solid var(--rule);
  background: var(--paper);
}
.ghostbtn {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--ink-2);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.16rem 0.65rem;
}
/* The log runs UNDER the window here rather than beside it — a wide, shallow
   strip. Same lines, same order, and at this width nothing has to be dropped. */
.scrub.is-live .audit--strip { height: auto; }

/* --- 4 · ChatGPT, as a desktop console ---------------------------------------
   Forced dark in both page themes. It is the one scene that is somebody else's
   product end to end, and a black console against a paper drawer states exactly
   whose log the right-hand column is. */
.scrub--gpt {
  --gpt-bg: #212121;
  --gpt-side: #171717;
  --gpt-user: #303030;
  --gpt-ink: #ececec;
  --gpt-ink-2: #b4b4b4;
  --gpt-rule: #383838;
}
.gptapp {
  display: flex;
  min-height: 0;
  overflow: hidden;
  background: var(--gpt-bg);
  color: var(--gpt-ink);
  border: 1px solid var(--gpt-rule);
  border-radius: 0.4rem;
  box-shadow: 0 18px 44px -34px rgba(0, 0, 0, 0.65);
}
.scrub.is-live .gptapp { height: clamp(13rem, 40svh, 22rem); }
.gptapp__side {
  display: none;
  flex: none;
  width: 11rem;
  flex-direction: column;
  gap: 0.12rem;
  padding: 0.7rem 0.5rem;
  overflow: hidden;
  background: var(--gpt-side);
  border-right: 1px solid var(--gpt-rule);
}
.gptapp__logo {
  font-family: var(--display);
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--gpt-ink);
  padding: 0 0.4rem 0.55rem;
}
.gptapp__new {
  font-size: 0.75rem;
  color: var(--gpt-ink-2);
  border: 1px solid var(--gpt-rule);
  border-radius: 0.4rem;
  padding: 0.26rem 0.5rem;
  margin-bottom: 0.5rem;
}
.gptapp__chat {
  font-size: 0.75rem;
  color: var(--gpt-ink-2);
  padding: 0.26rem 0.5rem;
  border-radius: 0.35rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gptapp__chat--on { background: var(--gpt-user); color: var(--gpt-ink); }
.gptapp__conn {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.5rem 0;
  border-top: 1px solid var(--gpt-rule);
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #8fcfa8;
}
.gptapp__conn i {
  flex: none;
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: #6fc48f;
}
.gptapp__main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.gptapp__bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0.9rem;
  border-bottom: 1px solid var(--gpt-rule);
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gpt-ink-2);
}
.gptapp__title {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gptapp__mode {
  margin-left: auto;
  flex: none;
  border: 1px solid var(--gpt-rule);
  border-radius: 999px;
  padding: 0.1rem 0.55rem;
}
.gptapp__thread { flex: 1; }
/* The thread runs the full width of the pane, flush with the composer beneath
   it. It used to be a 38rem column centred in the pane — which is what ChatGPT
   itself does — but the composer here is not centred with it, so the two
   disagreed and the gap on the right read as a bug rather than as a margin. */
.gptapp .thread__inner {
  padding: 0.8rem 0.85rem 0.9rem;
  gap: 0.75rem;
}
.gptapp .msg { border: 0; color: var(--gpt-ink); }
.gptapp .msg--out {
  background: var(--gpt-user);
  border-radius: 1.15rem;
  max-width: 80%;
  padding: 0.55rem 0.85rem 0.6rem;
}
.gptapp .msg--in {
  background: transparent;
  max-width: 100%;
  align-self: stretch;
  padding: 0 0.15rem;
}
.gptapp .msg time { color: #9e9e9e; }
.gptapp .msg--in time { text-align: left; }
.gptapp__composer {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0.85rem 0.8rem;
  padding: 0.4rem 0.4rem 0.4rem 0.85rem;
  background: var(--gpt-user);
  border-radius: 1.5rem;
}
.gptapp__input {
  flex: 1;
  min-width: 0;
  font-size: 0.8125rem;
  color: #9d9d9d;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gptapp__send {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #ececec;
  color: #171717;
  font-size: 0.75rem;
}

/* The confirmation gate. Every write from ChatGPT sits behind one — showing it
   is the caveat that has to travel with the claim, so it is drawn, not stated. */
.confirm {
  border: 1px solid var(--rule);
  background: var(--surface);
  border-radius: 0.5rem;
  padding: 0.6rem 0.7rem 0.65rem;
  margin-top: 0.1rem;
}
.confirm__h {
  display: block;
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: 0.45rem;
}
.confirm ul { list-style: none; margin: 0 0 0.55rem; padding: 0; }
.confirm li {
  font-family: var(--mono);
  font-size: 0.625rem;
  line-height: 1.7;
  color: var(--ink-2);
}
.confirm__act { color: var(--ink); font-weight: 600; }
.confirm__row { display: flex; gap: 0.4rem; }
.confirm__btn {
  font-family: var(--body);
  font-size: 0.6875rem;
  font-weight: 600;
  padding: 0.24rem 0.65rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--ink-2);
}
.confirm__btn--go {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.gptapp .confirm { background: #2b2b2b; border-color: #414141; }
.gptapp .confirm__h { color: #e8b44a; }
.gptapp .confirm li { color: #c9c9c9; }
.gptapp .confirm__act { color: #ececec; }
.gptapp .confirm__btn { color: #c9c9c9; border-color: #4d4d4d; }
.gptapp .confirm__btn--go {
  background: #d99a2b;
  border-color: #d99a2b;
  color: #12151a;
}


/* --- the four grounds ---------------------------------------------------------
   Each scene maps the channel's own tokens onto the seven stage tokens. Nothing
   else in the section knows which channel it is standing in. */

/* 1 · WhatsApp — the wallpaper floods and the phone floats on it */
.scrub--wa {
  background-image:
    radial-gradient(circle at 22% 32%, rgba(0, 0, 0, 0.05) 0 2px, transparent 2.2px),
    radial-gradient(circle at 72% 61%, rgba(0, 0, 0, 0.042) 0 2.6px, transparent 2.8px),
    radial-gradient(circle at 47% 86%, rgba(0, 0, 0, 0.035) 0 1.4px, transparent 1.6px);
  background-size: 72px 66px, 93px 87px, 54px 60px;

  --stage-ground: var(--wa-ground);
  --stage-ink: var(--wa-ink);
  --stage-ink-2: var(--wa-ink-2);
  --stage-rule: color-mix(in srgb, var(--wa-ink) 15%, transparent);
  --stage-pill: var(--wa-in);
  --stage-ok: #00745a;
}
/* 2 · Instagram — white or black, edge to edge, and nothing else */
.scrub--ig {
  --stage-ground: var(--ig-ground);
  --stage-ink: var(--ig-ink);
  --stage-ink-2: var(--ig-ink-3);
  --stage-rule: var(--ig-rule);
  --stage-pill: var(--ig-in);
  --stage-ok: var(--verified);
}
/* 3 · Email — a generic client on a desktop ground, never a branded one. There
   is no add-in, so no vendor mark appears anywhere in this scene. */
.scrub--mail { --stage-ground: var(--sunken); }
/* 4 · ChatGPT — forced dark in both page themes, because it is */
.scrub--gpt {
  --stage-ground: var(--gpt-bg);
  --stage-ink: var(--gpt-ink);
  --stage-ink-2: var(--gpt-ink-2);
  --stage-rule: var(--gpt-rule);
  --stage-pill: #2f2f2f;
  --stage-ok: #6fc48f;
  --stage-lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 16px 36px -24px rgba(0, 0, 0, 0.9);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .scrub--wa { --stage-ok: #53bd94; }
  :root:not([data-theme='light']) .scrub--ig { --stage-ok: #6fc48f; }
}
:root[data-theme='dark'] .scrub--wa { --stage-ok: #53bd94; }
:root[data-theme='dark'] .scrub--ig { --stage-ok: #6fc48f; }

/* The two phones float: the surface centres them and they take whatever height
   is left over, so a short landscape screen shrinks the device instead of
   clipping it. Width follows from the aspect ratio, never the other way round. */
/* On the two phone scenes the overlays are positioned off the DEVICE, not off
   the screen: the caption's top edge meets the phone's top edge, and the gap
   between the phone and the log equals the gap between the log and the edge of
   the screen. So the device's size has to be knowable in CSS — hence a height
   in viewport units that the width is derived from, rather than the other way
   round. The phone's own aspect-ratio does the same sum independently and the
   two agree because 9/19.5 = 0.4615 is written into both. */
.scrub--wa,
.scrub--ig {
  --dev-h: min(74svh, calc(100svh - 2 * var(--stage-top)));
  --dev-w: calc(var(--dev-h) * 0.4615);
  --stage-band: var(--dev-h);
  --surf-pad: var(--stage-gut);
}
.scrub--wa.is-live .stage__cap,
.scrub--ig.is-live .stage__cap {
  top: calc(50% - var(--dev-h) / 2);
  left: var(--stage-gut);
  right: auto;
  width: var(--side-w);
  transform: none;
}
.scrub--wa.is-live .stage__side,
.scrub--ig.is-live .stage__side {
  left: calc(50% + var(--dev-w) / 2 + var(--stage-gut));
  right: var(--stage-gut);
  width: auto;
}
.scrub.is-live .phone {
  display: flex;
  flex-direction: column;
  height: 100%;
  max-height: var(--stage-band);
  width: auto;
  max-width: 100%;
  aspect-ratio: 9 / 19.5;
  margin: 0;
  padding: 0.5rem;
  border-radius: 2.2rem;
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.12), 0 44px 70px -34px rgba(0, 0, 0, 0.6);
}
.scrub.is-live .phone__screen {
  height: auto;
  flex: 1;
  min-height: 0;
  border-radius: 1.75rem;
}

/* The two machines cannot compose the way the phones do. A phone is narrow, so
   it sits dead centre with a caption on one side and the log on the other and
   nothing collides. A mail client and a console are wide at every size worth
   reading — floated over one, the caption and the pills sat ON the very
   interface they are annotating. So these two put BOTH annotations in a single
   left column, caption above and pills directly beneath it, and give the app
   the width that is left. Nothing overlaps anything. */
/* The two desktop scenes carry the longest action names on the page
   (`set_conversation_handover`, `create_staff_decision`) in a column that is a
   FRACTION of the screen rather than everything left over beside a phone. So
   they take a wider column, a slightly smaller mono, and they drop the per-line
   `ref` — the one field that can go without taking the argument with it, since
   what these two scenes are arguing is WHO called the action, not which run it
   belonged to. The phones, which have the room, still print all four. */
.scrub--mail,
.scrub--gpt { --side-w: clamp(21rem, 30vw, 25rem); }
.scrub--mail .pills,
.scrub--gpt .pills { font-size: 0.65rem; }
.scrub--mail .logline__ms,
.scrub--mail .logline__sep--ref,
.scrub--gpt .logline__ms,
.scrub--gpt .logline__sep--ref { display: none; }

.scrub--mail.is-live .stage,
.scrub--gpt.is-live .stage {
  display: grid;
  /* Column 1 is the annotation column, at the same width the phone scenes give
     it, so a pill is the same length in all four. Row 1 takes whatever the
     caption needs; row 2 is what is left over — and it is that row, definite
     because the grid's own height is, that site.js centres the growing stack
     inside. */
  grid-template-columns: var(--side-w) minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  column-gap: clamp(1.5rem, 3.4vw, 3rem);
  /* Twice the gap the pills keep between themselves. The caption is a label for
     the whole column, not the first entry in it, and at an even spacing it read
     as one. */
  row-gap: 1.6rem;
  padding: var(--stage-top) var(--stage-gut) var(--stage-gut);
}
.scrub--mail.is-live .stage__cap,
.scrub--gpt.is-live .stage__cap {
  position: static;
  transform: none;
  grid-area: 1 / 1;
  width: auto;
}
.scrub--mail.is-live .stage__side,
.scrub--gpt.is-live .stage__side {
  position: static;
  transform: none;
  grid-area: 2 / 1;
  width: auto;
  height: auto;
  min-height: 0;
}
/* Growing about a centre is right when the column stands alone opposite a
   caption on the far side of a phone. Directly under that caption it is wrong:
   the first pill would start halfway down the column and creep upward toward
   the thing it belongs to. So these two anchor to the top and build downward,
   and the fade goes with them — nothing to soften at an edge the stack starts
   flush against. */
.scrub.is-live .stage__log[data-log-anchor='top'] {
  --fade: linear-gradient(to bottom, #000 0, #000 88%, transparent 100%);
}
/* Bottom-anchored: the newest pill sits on the floor of the window and the
   stack rises out of it as the reader scrolls — so the fade belongs at the TOP,
   where lines leave, and the bottom stays flush where they arrive. */
.scrub.is-live .stage__log[data-log-anchor='bottom'] {
  --fade: linear-gradient(to bottom, transparent 0, #000 12%, #000 100%);
}
/* It rises from the bottom RIGHT, so the pills hang off the right edge rather
   than the left — and the saga steps indent inward from that edge, which is the
   same gesture mirrored. */
.stage__log[data-log-anchor='bottom'] .pills { align-items: flex-end; }
.stage__log[data-log-anchor='bottom'] .logline--step {
  margin-left: 0;
  margin-right: 1.7rem;
}
.stage__log[data-log-anchor='top'] .pills { padding-top: 0.5rem; }
/* The app spans both rows, so its top edge meets the caption's top edge — the
   same alignment the phone scenes get out of the device's own height. */
.scrub--mail.is-live .stage__surface,
.scrub--gpt.is-live .stage__surface {
  position: static;
  inset: auto;
  grid-area: 1 / 2 / -1 / -1;
  padding: 0;
}
/* All four scenes share one composition: the annotations to the left, the
   channel's own screen beside them. The two desktop scenes take the PAGE's
   palette for those annotations rather than the channel's — dark chrome beside
   a light mail client, light chrome beside a black console. That contrast is
   the point: these are Clavis's notes on somebody else's screen, and they
   should never be mistaken for part of it. */
.scrub--mail {
  --stage-ink: #E8E6DE;
  --stage-ink-2: #B4B7BF;
  --stage-rule: rgba(232, 230, 222, 0.24);
  --stage-pill: #12141A;
  --stage-ok: #6FC48F;
  --stage-lift: 0 2px 4px rgba(0, 0, 0, 0.18), 0 22px 44px -26px rgba(0, 0, 0, 0.7);
  --glow: #F2F1EC;
}
.scrub--gpt {
  --stage-ink: #12141A;
  --stage-ink-2: #44474E;
  --stage-rule: rgba(18, 20, 26, 0.18);
  --stage-pill: #F2F1EC;
  --stage-ok: #1F6B3F;
  --stage-lift: 0 2px 4px rgba(0, 0, 0, 0.35), 0 22px 44px -26px rgba(0, 0, 0, 0.8);
  --glow: #F2F1EC;
}
/* Opaque, not glass. On the phones a pill floats over the channel's own
   wallpaper and the blur earns its keep; here it sits on a plain ground with
   nothing worth showing through, so the blur would only soften the type. */
.scrub--mail.is-live .logline,
.scrub--gpt.is-live .logline,
.scrub--mail.is-live .stage__cap,
.scrub--gpt.is-live .stage__cap { background: var(--stage-pill); }
.scrub--mail.is-live .mailapp,
.scrub--gpt.is-live .gptapp {
  width: 100%;
  height: 100%;
  max-width: 78rem;
  border-width: 1px;
  border-radius: 0.7rem;
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--glow) 62%, transparent),
    0 0 0 7px color-mix(in srgb, var(--glow) 22%, transparent),
    0 0 44px 3px color-mix(in srgb, var(--glow) 26%, transparent),
    0 2px 4px rgba(0, 0, 0, 0.14),
    0 34px 60px -40px rgba(0, 0, 0, 0.6);
}

/* --- the four rigs, once there is room for them ------------------------------
   Below this width a scene still fills the screen, but it stacks: caption on
   top, surface in the middle sized by what is left, pills as a strip along the
   bottom. The four silhouettes are a desktop argument; on a phone the argument
   is just the content. */
@media (min-width: 60rem) {
  .maillist { display: flex; }
  .gptapp__side { display: flex; }
}

@media (max-width: 59.99rem) {
  .scrub {
    --stage-top: calc(3.9rem + env(safe-area-inset-top, 0px));
    --strip-h: clamp(4.75rem, 13svh, 7rem);
  }
  .scrub.is-live .stage {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    padding: var(--stage-top) var(--stage-gut) var(--stage-gut);
  }
  .scrub.is-live .stage__cap,
  .scrub.is-live .stage__surface,
  .scrub.is-live .stage__side { position: static; }
  .scrub.is-live .stage__cap { order: -1; flex: none; max-width: none; }
  .scrub.is-live .stage__surface { flex: 1; min-height: 0; padding: 0; }
  .scrub.is-live .stage__side { flex: none; width: auto; height: var(--strip-h); }
  .scrub.is-live .stage__cap h2 { font-size: 0.9375rem; max-width: none; }
  .scrub.is-live .scrub__progress { margin-top: 0.5rem; }

  /* The two desktop rigs cannot go edge to edge here — there is no edge to go
     to — so they take the frame back and sit in the stack like the phones. */
  .scrub--mail.is-live .mailapp,
  .scrub--gpt.is-live .gptapp {
    border-width: 1px;
    border-radius: 0.5rem;
  }
  .mailread .msg { padding: 0.6rem 0.7rem 0.7rem; gap: 0.5rem; }
  .mailread__head { padding: 0.55rem 0.7rem 0.5rem; }
  .mailread__subject { font-size: 0.875rem; }
  .mailread__actions { padding: 0.45rem 0.7rem; }
  .mailapp__bar { padding: 0.45rem 0.7rem; font-size: 0.5625rem; }
  .gptapp .thread__inner { padding: 0.7rem 0.75rem 0.8rem; gap: 0.6rem; }
  .gptapp__composer { margin: 0 0.7rem 0.7rem; }
  .gptapp__bar { padding: 0.45rem 0.7rem; }
}

/* --- 11 Pricing ------------------------------------------------------------ */

.plans {
  display: grid;
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  min-width: 0;
}
@media (min-width: 54rem) { .plans { grid-template-columns: 1fr 1fr; } }
.plan {
  min-width: 0;
  background: var(--paper);
  padding: clamp(1.25rem, 3vw, 1.9rem);
  display: flex;
  flex-direction: column;
}
.plan--founding { background: var(--surface); }
.plan__tag {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.9rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.plan--founding .plan__tag { color: var(--accent-ink); }
.plan__expiry {
  border: 1px solid var(--accent);
  color: var(--accent-ink);
  padding: 0.12rem 0.4rem;
  letter-spacing: 0.08em;
}
.plan h3 { margin-bottom: 0.6rem; }
.plan__price {
  font-family: var(--display);
  font-size: clamp(2.4rem, 5vw, 3.1rem);
  font-weight: 600;
  font-stretch: 84%;
  letter-spacing: -0.035em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  margin: 0.4rem 0 0.15rem;
}
.plan__per {
  font-family: var(--body);
  font-size: 0.9375rem;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-2);
}
.plan__scope {
  font-size: 0.875rem;
  color: var(--ink-2);
  margin: 0.5rem 0 1.25rem;
}
.plan__cta { margin-top: auto; padding-top: 1.75rem; }

.definition {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-left: 2px solid var(--accent);
  padding: 1.35rem 1.5rem;
  max-width: var(--measure);
}
.definition h4 {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: 0.75rem;
}
.definition p { font-size: 0.9375rem; color: var(--ink-2); }
.definition p:last-child { margin-bottom: 0; }

/* --- 12 Forms -------------------------------------------------------------- */

.capture {
  background: var(--surface);
  border: 1px solid var(--rule);
  padding: clamp(1.25rem, 3vw, 1.9rem);
  max-width: 46rem;
}
.field {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 1.1rem;
}
.field label {
  flex: 1 1 16rem;
  display: block;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.field input {
  display: block;
  width: 100%;
  margin-top: 0.4rem;
  padding: 0.72rem 0.85rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 0;
  color: var(--ink);
  font-family: var(--body);
  font-size: 1rem;
  letter-spacing: 0;
  text-transform: none;
  transition: border-color 0.18s var(--ease);
}
.field input::placeholder { color: var(--ink-3); }
.field input:hover { border-color: var(--ink-3); }
.field input:focus { border-color: var(--accent); }
.field .btn { align-self: flex-end; flex: none; }
.field__submit { display: flex; align-items: flex-end; }

.capture__small {
  margin-top: 1rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  line-height: 1.6;
  color: var(--ink-3);
  max-width: 52ch;
}
.formstatus {
  margin-top: 1rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--verified);
  display: none;
}
.formstatus.is-shown { display: block; }
.formstatus.is-error { color: var(--accent-ink); }
.hp {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- 13 Video slot --------------------------------------------------------- */

.videoslot {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--sunken);
  border: 1px solid var(--rule);
  display: grid;
  place-items: center;
  overflow: hidden;
  max-width: none;
}
.videoslot::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(var(--rule-soft) 1px, transparent 1px),
    linear-gradient(90deg, var(--rule-soft) 1px, transparent 1px);
  background-size: 2.5rem 2.5rem;
  opacity: 0.55;
}
.videoslot__body {
  position: relative;
  text-align: center;
  padding: 1.5rem;
  max-width: 34ch;
}
.videoslot__mark {
  width: 3rem; height: 3rem;
  margin: 0 auto 1rem;
  border: 1px solid var(--accent);
  display: grid;
  place-items: center;
  color: var(--accent);
}
.videoslot__mark svg { width: 1rem; height: 1rem; }
.videoslot p {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.7;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  max-width: none;
}

/* --- 13b Further parts ------------------------------------------------------ */

/* The three-line ledger strip in the thesis section: one action, three callers.
   The whole argument, in nine words, before the reader has scrolled. */
.ledgerstrip {
  background: var(--sunken);
  border: 1px solid var(--rule);
  border-left: 2px solid var(--accent);
  padding: 1rem 1.1rem 1.15rem;
  max-width: var(--measure);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.ledgerstrip__cap {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 0.75rem;
  max-width: none;
}
.ledgerstrip__line {
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.9;
  font-variant-numeric: tabular-nums;
  margin: 0;
  max-width: none;
  white-space: nowrap;
}
@media (max-width: 30rem) { .ledgerstrip__line { font-size: 0.6875rem; } }

/* Eleven blank screens against one message. No invented step labels — the
   count is the only thing the brief licenses, so the count is all that is drawn. */
.screens {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.screens li {
  width: 1.4rem;
  height: 2rem;
  border: 1px solid var(--rule);
  background: var(--surface);
}
.screens--one {
  display: flex;
  justify-content: flex-end;
  margin-top: 1.25rem;
}
.screens--one .msg {
  max-width: 100%;
  margin: 0;
  font-size: 0.8125rem;
  background: var(--sunken);
  border-radius: 0.75rem 0.15rem 0.75rem 0.75rem;
}

/* A build note. Deliberately unmistakable as scaffolding: nobody could mistake
   one of these for finished page copy, which is the point of a visible marker. */
.buildnote {
  border: 1px dashed var(--accent);
  background: var(--accent-wash);
  padding: 0.95rem 1.15rem 1.05rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.7;
  color: var(--ink-2);
  max-width: var(--measure);
}
.buildnote b {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-ink);
}
.buildnote .mono { color: var(--ink); }

/* The awkward questions. */
.qa { margin: 0; max-width: var(--measure); }
.qa dt {
  font-family: var(--display);
  font-size: 1.0625rem;
  font-weight: 580;
  font-stretch: 94%;
  letter-spacing: -0.008em;
  line-height: 1.3;
  padding-top: 1.15rem;
  border-top: 1px solid var(--rule);
  margin-top: 1.15rem;
}
.qa dt:first-of-type { border-top: 0; margin-top: 0; padding-top: 0; }
.qa dd {
  margin: 0.6rem 0 0;
  color: var(--ink-2);
}

.tickmark { color: var(--verified); }
.logline--detail { color: var(--ink-3); white-space: normal; }
.logline--detail .logline__actor { color: var(--ink-3); }

.plan .spec .n { min-width: 9.5rem; }

@media (max-width: 34rem) {
  .spec { font-size: 0.875rem; }
  .spec .n, .plan .spec .n { min-width: 5.5rem; padding-left: 0.5rem; }
  .spec th, .spec td { padding-right: 0.5rem; }
}

/* The booking embed. Space is reserved up front so mounting Cal does not shove
   the page around under somebody's thumb. */
.calembed {
  border: 1px solid var(--rule);
  background: var(--surface);
  padding: 0.5rem;
  margin-top: 1.75rem;
  max-width: none;
}
.calembed__mount {
  min-height: 34rem;
  width: 100%;
}
.calembed__mount:empty {
  display: grid;
  place-items: center;
}
.calembed__mount:empty::before {
  content: 'Loading the booking calendar';
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.calembed__mount[data-cal-state='failed']:empty::before {
  content: 'The calendar did not load';
  color: var(--accent-ink);
}
.calembed__fallback {
  margin: 0.65rem 0.25rem 0.15rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.02em;
  color: var(--ink-3);
  max-width: none;
}
.calembed__fallback a { color: var(--ink-2); text-decoration-color: var(--accent); }
.calembed__fallback a:hover { color: var(--accent-ink); }
@media (max-width: 34rem) { .calembed__mount { min-height: 30rem; } }

/* --- 13c The scrub on a phone ----------------------------------------------
   Both tracks must fit one viewport at once, or the interaction loses its whole
   point. Everything shrinks; nothing is dropped. site.js additionally measures
   the result and stands the pin down if it still will not fit the screen. */
@media (max-width: 59.99rem) {
  .scrub__viewport { padding-block: clamp(1rem, 3vh, 2rem); }
  .scrub__head { padding-bottom: 0.6rem; margin-bottom: 0.75rem; gap: 0.35rem 1rem; }
  .scrub__head .eyebrow { margin-bottom: 0.25rem; font-size: 0.625rem; }
  .scrub__head h2 { font-size: clamp(1.15rem, 4.6vw, 1.5rem); max-width: 22ch; }
  .tracks { gap: 1rem; }
  .phone {
    max-width: 15.5rem;
    padding: 0.3rem;
    border-radius: 1.5rem;
  }
  .phone__screen { border-radius: 1.25rem; }
  /* Narrow: the two tracks are stacked, so the frame cannot have its full
     proportion and still leave the audit column room. It stays phone-ish. */
  .scrub.is-live .phone { --screen-h: clamp(13rem, 40svh, 22rem); max-width: 12.5rem; }
  .scrub.is-live .phone__screen { height: var(--screen-h); }
  .msg { font-size: 0.75rem; }
  .phone__bar { padding: 0.5rem 0.7rem; }
  .phone__avatar { width: 1.5rem; height: 1.5rem; }
  .scrub.is-live .audit { height: auto; min-height: 0; }
  .scrub.is-live .audit__window { height: clamp(6rem, 17svh, 9.5rem); flex: none; }
  .audit__foot { padding: 0.5rem 0.7rem; font-size: 0.5625rem; line-height: 1.55; }
  .audit__bar { padding: 0.45rem 0.7rem; font-size: 0.5625rem; letter-spacing: 0.08em; }
  .audit__bar .sp { display: none; }
}

/* On a phone the audit line has room for action, actor and outcome but not for
   the correlation ref as well — and a truncated line would cut the OUTCOME,
   which is the field the whole section exists to show. Drop the ref instead,
   with the separator that would be left dangling in front of it. */
@media (max-width: 34rem) {
  .logline__ms, .logline__sep--ref { display: none; }
  /* And the word "actor" goes too. "human:member" is unmistakably an actor;
     six characters of label are not worth truncating the outcome for. */
  .logline__k { display: none; }
  .logline { gap: 0.5rem; }
}

/* Narrower still — a Galaxy Fold cover screen, an old 320dp Android. Below this
   width even action + actor + outcome will not fit on one line, so the line
   wraps. Height is cheap; a truncated "held" or "checked" proves nothing. */
@media (max-width: 22.5rem) {
  /* Narrowest tier — a Galaxy Fold cover screen, an old 320dp Android. Wrapping
     was tried here and is worse than it looks: the log tails, so a two-row line
     can lose its first row off the top of the window and leave an orphaned
     "held ·" with no action name against it. So the line stays one row and the
     ACTOR is dropped instead, leaving action · outcome — the pair this section
     is actually arguing about, and the one that must never be cut. The actors
     are explained in prose in the section directly below.
     The detail row is prose, not a field line, so it still wraps. */
  .logline:not(.logline--detail) .logline__actor,
  .logline:not(.logline--detail) .logline__sep--actor { display: none; }
  .logline--detail { white-space: normal; }
  .logline--detail .logline__m { overflow: visible; text-overflow: clip; }
  .logline { grid-template-columns: 2.4rem 1fr; }

  /* And the two tracks give a little more height back, so the pinned block
     still fits a 568px screen rather than being stood down by site.js. */
  .scrub__viewport { padding-block: 0.9rem; }
  /* --screen-h alone drives the frame here; an earlier hard-coded height on
     .phone__screen used to sit in this block and quietly beat it. */
  .scrub.is-live .phone { --screen-h: clamp(12rem, 34svh, 20rem); }
  .scrub__head { padding-bottom: 0.6rem; margin-bottom: 0.75rem; }
  .scrub.is-live .audit__window { height: clamp(6rem, 16svh, 8rem); }
  .audit__foot { padding: 0.45rem 0.6rem; }
  /* The desktop rigs shrink here too, or scene 3 and scene 4 are stood down on
     the narrowest screens and four scenes render static in a row. */
  .scrub.is-live .mailapp,
  .scrub.is-live .gptapp { height: clamp(11.5rem, 36svh, 20rem); }
  .scrub.is-live .tape__window { height: clamp(3.5rem, 10svh, 5.5rem); }
  .tape__head { padding: 0.4rem 0.6rem 0.35rem; }
  .tape__mark { font-size: 0.75rem; }
  .tape__foot { padding: 0.4rem 0.6rem 0.5rem; }
  .tape__edge { height: 5px; }
  .tape__edge--top {
    -webkit-mask: conic-gradient(from -45deg at 50% 100%, #000 0 90deg, transparent 0) 50% / 9px 5px repeat-x;
            mask: conic-gradient(from -45deg at 50% 100%, #000 0 90deg, transparent 0) 50% / 9px 5px repeat-x;
  }
  .tape__edge--bot {
    -webkit-mask: conic-gradient(from 135deg at 50% 0, #000 0 90deg, transparent 0) 50% / 9px 5px repeat-x;
            mask: conic-gradient(from 135deg at 50% 0, #000 0 90deg, transparent 0) 50% / 9px 5px repeat-x;
  }
  .mailread__head { padding: 0.45rem 0.6rem 0.4rem; }
  .mailread__actions { padding: 0.35rem 0.6rem; }
  .ghostbtn { font-size: 0.625rem; padding: 0.12rem 0.5rem; }
}

/* --- the claim, as one line -------------------------------------------------
   The section that used to hold this argument ran a claim and a scope across a
   card grid and a four-step rail. It is now one sentence, centred, on its own
   ground — and it sits outside .shell, so the scenes that follow it can reach
   the viewport edges instead of being boxed into the rail grid.

   The measure is deliberately short. A centred line only reads as a statement
   while the eye can take it in without travelling; past about forty characters
   a wide, centred paragraph becomes work, and the sentence stops sounding like
   a claim and starts sounding like copy. */
.creed {
  padding-block: clamp(5.5rem, 15vh, 10rem);
  padding-inline: var(--pad);
}
.creed__line {
  margin: 0 auto;
  /* An em measure, not a ch one: it scales with the display size, so the line
     count holds from 1.6rem to 2.6rem instead of collapsing to five lines on a
     wide screen. */
  max-width: 21em;
  text-align: center;
  text-wrap: balance;
  font-family: var(--display);
  font-optical-sizing: auto;
  font-weight: 550;
  font-stretch: 90%;
  font-size: clamp(1.6rem, 3.3vw, 2.6rem);
  line-height: 1.12;
  letter-spacing: -0.026em;
  color: var(--ink);
}

/* --- the sandbox -------------------------------------------------------------
   A phone you can type into. The chrome is the same .phone--wa the scenes use,
   so the demo and the illustration are visibly the same object — one of them is
   just wired up. The composer is DISABLED until CLAVIS.DEMO_ENDPOINT is set,
   and site.js will not invent a reply to fill the gap: a demo that answers from
   a script is the exact lie the first real question exposes. */
.demo {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 2.75rem);
  align-items: start;
}
@media (min-width: 48rem) {
  .demo { grid-template-columns: minmax(0, 19rem) minmax(0, 1fr); }
}
.demo__phone {
  width: 100%;
  max-width: 19rem;
  margin-inline: auto;
}
.demo__thread {
  overflow-y: auto;
  overscroll-behavior: contain;
}
.demo__composer {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.55rem;
  border-top: 1px solid color-mix(in srgb, var(--wa-ink) 10%, transparent);
  background: var(--wa-in);
}
.demo__input {
  flex: 1;
  min-width: 0;
  padding: 0.5rem 0.8rem;
  border: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--wa-ink) 6%, transparent);
  color: var(--wa-ink);
  font-family: var(--body);
  font-size: 0.8125rem;
}
.demo__input::placeholder { color: color-mix(in srgb, var(--wa-ink) 52%, transparent); }
.demo__input:disabled { cursor: not-allowed; }
.demo__send {
  flex: none;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #25D366;
  color: #06281c;
  cursor: pointer;
}
.demo__send svg { width: 0.9rem; height: 0.9rem; }
.demo__send:disabled { background: color-mix(in srgb, var(--wa-ink) 18%, transparent); cursor: not-allowed; }
.demo__side > * + * { margin-top: 1.1rem; }
.demo__small { font-size: 0.875rem; color: var(--ink-2); }
/* Three dots while the studio is actually thinking — shown only when a request
   is genuinely in flight, never as set dressing. */
.demo__typing { display: inline-flex; gap: 0.25rem; }
.demo__typing i {
  width: 0.32rem;
  height: 0.32rem;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.35;
  animation: demo-dot 1.1s var(--ease) infinite;
}
.demo__typing i:nth-child(2) { animation-delay: 0.15s; }
.demo__typing i:nth-child(3) { animation-delay: 0.3s; }
@keyframes demo-dot { 0%, 60%, 100% { opacity: 0.3; } 30% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .demo__typing i { animation: none; } }

/* --- the sandbox page -------------------------------------------------------
   Its own page, and the only two-pane layout on the site: the sentence you type
   on the left, the studio it changed on the right. Same two halves the four
   scenes argue about, except this time both are yours. */
.demopage .masthead { position: sticky; }
.sandbox {
  max-width: 96rem;
  margin-inline: auto;
  padding: calc(6rem + env(safe-area-inset-top, 0px)) var(--pad) clamp(3rem, 7vh, 5rem);
}
/* The head spans the section rather than capping itself, so that what sits
   inside it can choose its own width and its own alignment. */
.sandbox__head { margin-bottom: clamp(1.75rem, 4vh, 2.75rem); }
/* Prose keeps a reading measure and stays left, where a reader's eye starts. */
.sandbox__head h1,
.sandbox__head .sandbox__small { max-width: 46rem; }
.sandbox__head h1 {
  margin: 0.55rem 0 0.9rem;
  font-family: var(--display);
  font-size: clamp(1.9rem, 4.4vw, 3rem);
  font-weight: 700;
  font-stretch: 96%;
  letter-spacing: -0.034em;
  line-height: 1.02;
}
/* Three steps, because a visitor who has to guess how to start a demo does not
   start it. Numbered rather than bulleted: the order is the whole point. */
/* --- what the assistant can do: the press-strip treatment ----------------

   Shaped after E-Pulsive's "As featured in" row — a centred label between two
   hairlines, then items divided by thin vertical rules. No boxes, no fills,
   no radius: the rules ARE the structure, which is what makes that row read
   as a masthead line-up rather than a feature table.

   Eight items in four columns, so desktop lands on exactly two rows. The
   count and the column count are chosen together; changing one without the
   other is what puts an orphan on a third row. */
/* Centred on the page as a block, not merely centred text: it is the one part
   of this head that is not prose, so it is not bound to the left edge the
   heading and the note read from. */
.capstrip {
  max-width: 68rem;
  margin: clamp(1.75rem, 4vh, 2.75rem) auto 1.6rem;
}

/* The label, and its two hairlines. Width is clamped rather than fluid so the
   rules stay symmetrical about the text at every viewport. */
.capstrip__label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.15rem;
  margin: 0;
  /* Escape the global `p { max-width: var(--measure) }`. This is a rule-and-
     label device spanning the strip, not running text — left capped at 68ch it
     centred its contents inside a box narrower than the grid, which read as
     the whole label being off to the left. */
  max-width: none;
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--ink-2);
}
.capstrip__label::before,
.capstrip__label::after {
  content: "";
  height: 1px;
  width: clamp(3rem, 11vw, 8rem);
  background: var(--rule);
}

.capstrip__grid {
  list-style: none;
  margin: clamp(1.5rem, 3.5vh, 2.25rem) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  row-gap: clamp(1.75rem, 4vh, 2.5rem);
}
.cap {
  padding: 0 1.15rem;
  text-align: center;
  /* The divider between cells. Removed on the first of each row so a row never
     opens with a rule hanging off its left edge. */
  border-left: 1px solid var(--rule);
}
.cap:nth-child(4n + 1) { border-left: 0; }

.cap__name {
  margin: 0;
  font-family: var(--display);
  font-size: 1rem;
  font-weight: 640;
  letter-spacing: -0.014em;
  line-height: 1.3;
  color: var(--ink);
}
/* The sentence to type. Muted and quiet — it is a caption under a masthead,
   not a second headline competing with the name above it. */
.cap__try {
  margin: 0.5rem 0 0;
  font-size: 0.8438rem;
  line-height: 1.5;
  color: var(--ink-2);
}

@media (max-width: 61.25rem) {
  .capstrip__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cap:nth-child(4n + 1) { border-left: 1px solid var(--rule); }
  .cap:nth-child(2n + 1) { border-left: 0; }
}
@media (max-width: 40rem) {
  .capstrip__grid { grid-template-columns: minmax(0, 1fr); row-gap: 1.6rem; }
  .cap { border-left: 0; padding: 0; }
}

.sandbox__panes {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1.5rem, 3.5vw, 2.5rem);
  align-items: start;
}
/* Side by side only once the dashboard has enough width to be a dashboard
   rather than a squeezed picture of one. */
@media (min-width: 64rem) {
  .sandbox__panes { grid-template-columns: 21rem minmax(0, 1fr); }
}
/* The two doors a member can arrive through, as a segmented control. Both lanes
   are the SAME booking system — that is the whole point of the pair, so they
   share a column rather than sitting side by side competing for it. */
.sandbox__switch {
  display: inline-flex;
  margin: 0 0 0.9rem;
  padding: 0.2rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--sunken);
}
.swtab {
  appearance: none;
  border: 0;
  border-radius: 999px;
  padding: 0.36rem 0.95rem 0.38rem;
  background: transparent;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  cursor: pointer;
  transition: background 0.22s var(--ease), color 0.22s var(--ease);
}
.swtab:hover { color: var(--ink-2); }
.swtab.is-on {
  background: var(--ink);
  color: var(--paper);
}
.swtab:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }

/* A website is not a phone, so the column grows when the web door is chosen.
   The transition is on the grid track itself, which is what stops the dashboard
   opposite from jumping. */
@media (min-width: 64rem) {
  .sandbox__panes { transition: grid-template-columns 0.32s var(--ease); }
  .sandbox__panes[data-lane='web'] { grid-template-columns: 34rem minmax(0, 1fr); }
}

/* The web door's frame. Same browser chrome as the dashboard opposite, because
   it is the same kind of object: somebody else's screen. */
.webframe {
  border: 1px solid var(--rule);
  border-radius: 0.6rem;
  overflow: hidden;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  height: clamp(28rem, 66svh, 40rem);
}
.webframe__bar {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--rule);
  background: var(--sunken);
}
.webframe__body { flex: 1; min-height: 0; display: flex; }
.webframe__body iframe { flex: 1; width: 100%; height: 100%; border: 0; }

/* The chat lane's frame. Same shape as .webframe__body above, for the same
   reason: the framed thing is the real widget and it should fill its pane
   exactly, with no bezel of ours around it. */
.phone__body { flex: 1; min-height: 0; display: flex; position: relative; }
.phone__body iframe { flex: 1; width: 100%; height: 100%; border: 0; }

/* --- the chat frame's loading veil ---------------------------------------

   The framed widget is a single-page app and its assembly is VISIBLE: bundle,
   spinner, a lone greeting, then the opening exchange. Watching a chat build
   itself is the tell that something is a mock-up, which is the one impression
   this pane cannot afford — it is the real widget, and has to look it.

   So it opens as a bone-shaped copy of the finished thread on the SAME black
   ground the frame uses (`bg-black` in DemoChat.tsx). Matching the ground is
   the whole trick: nothing flashes, nothing moves, and the only thing that
   changes is the shapes resolving into a conversation. site.js drives it. */
.chatveil {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: #000;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 0.7rem;
  padding: 1rem 0.85rem 1.1rem;
  opacity: 1;
  transition: opacity 320ms ease;
}
.chatveil.is-gone { opacity: 0; pointer-events: none; }
.chatveil__row { display: flex; }
.chatveil__row--me { justify-content: flex-end; }
.chatveil__bubble {
  display: block;
  height: 2.05rem;
  border-radius: 1.05rem;
  /* Low, but not invisible. On a black ground the first pass at 0.06 read as
     an empty box rather than as a thread arriving, which is the opposite of
     the point — the shapes have to be legible enough to promise a chat. */
  background-color: rgba(255, 255, 255, 0.075);
  background-image: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 20%,
    rgba(255, 255, 255, 0.09) 50%,
    rgba(255, 255, 255, 0) 80%
  );
  background-size: 220% 100%;
  background-repeat: no-repeat;
  animation: chatveil-shimmer 1.9s ease-in-out infinite;
}
/* The composer, which in the real frame is pinned to the bottom and is the
   first thing the eye looks for in a chat. Leaving it out made the veil read
   as an empty box rather than a chat that has not arrived. */
.chatveil__composer {
  flex: none;
  height: 2.6rem;
  margin-top: 0.35rem;
  border-radius: 1.3rem;
  background: rgba(255, 255, 255, 0.06);
}
@keyframes chatveil-shimmer {
  from { background-position: 140% 0; }
  to   { background-position: -40% 0; }
}
/* The frame is held invisible until it says it is ready, then cross-fades in
   as the veil goes out — the two transitions are the same length on purpose. */
.phone__body iframe { transition: opacity 320ms ease; }
.phone__body iframe.is-loading { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .chatveil,
  .phone__body iframe { transition: none; }
  .chatveil__bubble { animation: none; }
}
.phone__placeholder {
  margin: auto;
  max-width: 22rem;
  padding: 1.25rem;
  text-align: center;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--ink-3);
}
/* The scoped account pane. Styled after the real dashboard's two pages, but it
   only ever holds ONE member — see the comment in site.js for why that is a
   source-level guarantee rather than a filtered view. */
.dash__scope {
  margin: 0.75rem 0 0; max-width: none;
  font-size: 0.75rem; line-height: 1.5; color: var(--ink-3);
}
.data-row { cursor: pointer; }
.data-row:hover td { background: var(--sunken); }
.data-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
.dash__back { margin: 0 0 0.5rem; }
.dash__backbtn {
  appearance: none; border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; font-size: 0.8125rem; color: var(--accent-ink);
}
.dash__backbtn:hover { text-decoration: underline; }
.dash__pills { margin: 0.35rem 0 1rem; }
.dash__pill {
  display: inline-block; padding: 0.12rem 0.45rem; border-radius: 0.25rem;
  font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.1em;
}
.dash__pill--ok { background: color-mix(in srgb, var(--verified) 22%, transparent); color: var(--ink); }
.dash__stats {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px; background: var(--rule); border: 1px solid var(--rule);
  border-radius: 0.4rem; overflow: hidden; margin-bottom: 1rem;
}
.dash__stats > div { background: var(--surface); padding: 0.7rem 0.8rem; }
.dash__k {
  display: block; font-family: var(--mono); font-size: 0.5625rem;
  letter-spacing: 0.12em; color: var(--ink-3); margin-bottom: 0.3rem;
}
.dash__stats b { font-family: var(--display); font-size: 1.15rem; font-weight: 700; }
.dash__cards { display: grid; gap: 0.75rem; margin-bottom: 0.75rem; }
@media (min-width: 40rem) { .dash__cards { grid-template-columns: 1fr 1fr; } }
.dash__card { border: 1px solid var(--rule); border-radius: 0.4rem; overflow: hidden; }
.dash__card h3 {
  margin: 0; padding: 0.45rem 0.7rem; background: var(--sunken);
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.12em; font-weight: 500;
}
.dash__row3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); padding: 0.7rem; gap: 0.5rem; }
.dash__row3 b { display: block; font-family: var(--display); font-size: 1.05rem; font-weight: 700; }
.dash__row3 span { font-family: var(--mono); font-size: 0.5625rem; letter-spacing: 0.1em; color: var(--ink-3); }
.dash__dl { margin: 0; padding: 0.7rem; display: grid; grid-template-columns: 9rem minmax(0, 1fr); gap: 0.4rem 0.75rem; }
.dash__dl dt { font-family: var(--mono); font-size: 0.5625rem; letter-spacing: 0.12em; color: var(--ink-3); align-self: center; }
.dash__dl dd { margin: 0; font-size: 0.875rem; overflow-wrap: anywhere; }

.dash__note {
  margin: 0.9rem 0 0;
  max-width: none;
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--ink-2);
}
.dash__note b { display: block; margin-bottom: 0.4rem; color: var(--ink); }
.dash__note--todo { font-size: 0.8125rem; color: var(--ink-3); }

.webframe__note {
  margin: 0;
  align-self: center;
  padding: 1.5rem 1.35rem;
  max-width: none;
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--ink-2);
}
.webframe__note b { display: block; margin-bottom: 0.45rem; color: var(--ink); }

.sandbox__label {
  margin: 0 0 0.7rem;
  max-width: none;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
/* The phone here is a live object, not a picture of one, so it needs a definite
   height for the thread to fill and scroll inside — the scenes get that from
   `.scrub.is-live`, which this page never enters. */
.sandbox__pane--phone .phone {
  max-width: 21rem;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  height: clamp(28rem, 66svh, 40rem);
}
.sandbox__pane--phone .phone__screen { flex: 1; min-height: 0; }
.sandbox__pane--phone .phone__thread { flex: 1; min-height: 0; }
/* Centred on the page, not just internally: the 52rem measure had no auto
   margin, so the block sat hard against the left edge of a 96rem section and
   its centred text still read as off-centre. */
.sandbox__foot {
  margin-top: clamp(2rem, 5vh, 3.25rem);
  max-width: 52rem;
  margin-inline: auto;
}
.sandbox__foot > * + * { margin-top: 1.1rem; }
.sandbox__small { font-size: 0.9375rem; color: var(--ink-2); }

/* --- what to have to hand ------------------------------------------------
   Centred as a block, and the three values are buttons rather than text: the
   card number's whole job is to end up in a Stripe field, and retyping sixteen
   digits off a sentence is the fiddliest moment in the demo. */
.prep {
  margin: clamp(1.75rem, 4vh, 2.5rem) auto 0;
  max-width: 52rem;
  text-align: center;
}
.prep__title {
  margin: 0 auto;
  max-width: 46rem;
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 400;
  line-height: 1.6;
  letter-spacing: 0;
  color: var(--ink-2);
}
.prep__fields {
  list-style: none;
  margin: 1.15rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
}
.copyfield {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
  padding: 0.5rem 0.8rem;
  border: 1px solid var(--rule);
  border-radius: 5px;
  background: transparent;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
  transition: border-color 140ms var(--ease), background 140ms var(--ease);
}
.copyfield:hover { border-color: var(--ink-3); background: var(--chip); }
.copyfield:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
.copyfield__k {
  font-family: var(--mono);
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-3);
}
.copyfield__v {
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--ink);
}
/* The confirmation replaces the label rather than appearing beside it, so the
   button does not change width and shove its neighbours along when tapped. */
.copyfield.is-copied .copyfield__k { color: var(--verified); }
.copyfield.is-copied { border-color: var(--verified); }
@media (prefers-reduced-motion: reduce) { .copyfield { transition: none; } }

/* The page's close: the note and its two buttons, centred. The note keeps a
   reading measure and centres as a block; `.cta-row` is centred rather than
   left-packed so the pair sits under the middle of the text above it. */
.sandbox__close {
  margin-top: clamp(1.75rem, 4vh, 2.75rem);
  text-align: center;
}
.sandbox__close .sandbox__small {
  max-width: 46rem;
  margin-inline: auto;
}
.sandbox__close .cta-row {
  /* Only the centring and the lead-in — display, wrap and gap already come
     from the base `.cta-row`, and restating them here would fork the pair's
     spacing from every other button row on the site. */
  justify-content: center;
  margin-top: 1.15rem;
}

/* The dashboard pane. A browser frame with the studio's own /members page in
   it — an iframe of the real thing once DEMO_DASHBOARD_URL is set, and until
   then the same page's real empty state rather than a mock-up of a full one. */
.dash {
  display: flex;
  flex-direction: column;
  min-height: clamp(24rem, 60svh, 38rem);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 0.55rem;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 26px 50px -34px rgba(0, 0, 0, 0.5);
}
.dash__bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.8rem;
  background: var(--sunken);
  border-bottom: 1px solid var(--rule);
}
.dash__dots { display: inline-flex; gap: 0.32rem; }
.dash__dots i {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 22%, transparent);
}
.dash__url {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dash__body { flex: 1; min-height: 0; padding: clamp(1rem, 2.4vw, 1.6rem); overflow: auto; }
/* The framed member page fills the pane the projection used to occupy. */
.dash__frame {
  flex: 1;
  width: 100%;
  height: 100%;
  min-height: 0;
  border: 0;
  display: block;
}

/* --- the framed dashboard's seat -------------------------------------------
   The pane frames Layer's REAL member page. That dashboard pins
   `color-scheme: light` on purpose — it is an operator's instrument panel —
   so framed as-is it landed here as a bright slab bolted onto a dark page.

   The first attempt dimmed it from THIS side, with a filter and a warm wash
   over the iframe. That was the wrong layer: it produced a duller slab,
   because the problem was never brightness. It was that the CARDS inside did
   not belong to the page around them, and no amount of tinting a rectangle
   fixes what is happening inside the rectangle.

   So the re-theming moved to where the cards actually live — see
   "MEMBER-VIEW GLASS" at the foot of the dashboard's own styles.css, scoped to
   the member-view route and to nothing else. The page now arrives dark and
   glass-surfaced under its own power, and all that is left to do here is seat
   it: flush to the chrome, and dissolved at the bottom edge. */
.dash--glass {
  position: relative;
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--ink) 8%, transparent),
    0 1px 2px rgba(0, 0, 0, 0.06),
    0 30px 60px -34px rgba(0, 0, 0, 0.65);
}

/* Flush, like a real browser window. The body's padding insets the
   PROJECTION's markup; around a framed page it is just a gutter of pane
   background, which is most of what made the frame read as a floating slab. */
.dash--glass .dash__body {
  padding: 0;
  overflow: hidden;
}

/* The bottom edge dissolves instead of being guillotined mid-section.
   `pointer-events: none` is load-bearing: the framed dashboard is live, and
   every click and scroll has to reach it underneath this. */
.dash--glass::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    transparent 78%,
    color-mix(in srgb, var(--paper) 40%, transparent) 91%,
    color-mix(in srgb, var(--paper) 82%, transparent) 100%
  );
}

.page-title {
  margin: 0 0 1rem;
  font-family: var(--display);
  font-size: 1.35rem;
  font-weight: 640;
  letter-spacing: -0.02em;
}
.dash__tablewrap { overflow-x: auto; }
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8125rem;
}
.data-table th,
.data-table td {
  padding: 0.55rem 0.7rem;
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--rule-soft);
}
.data-table th {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  border-bottom-color: var(--rule);
}
/* The first column carries no left padding, so its text starts on the same
   line as the page title above it and the header rule beneath it. Every other
   column keeps its inset: the padding is there to separate columns from each
   other, and the leftmost one has nothing to its left to separate from. */
.data-table th:first-child,
.data-table td:first-child { padding-left: 0; }
.data-table tbody tr { cursor: pointer; }
.data-table tbody tr:hover { background: var(--chip); }
.data-table td { font-family: var(--mono); font-size: 0.75rem; }
/* The members pane's waiting state. Not an "empty" message any more: the pane
   is waiting for the visitor to say something the studio can act on, and three
   pulsing dots say that without claiming the studio has no members. */
.empty .demo__typing { vertical-align: middle; }
.empty {
  margin: 1.1rem 0 0;
  max-width: none;
  font-size: 0.875rem;
  color: var(--ink-3);
}

/* --- 14 Motion + reveal ---------------------------------------------------- */

/* Hidden ONLY when .js-reveal is present, which site.js adds at the moment it
   confirms an IntersectionObserver is going to run. A blocked script therefore
   leaves every section visible rather than blank. */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
.js-reveal [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* --- 15 Reduced motion, print, forced colours ------------------------------ */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .js-reveal [data-reveal] { opacity: 1; transform: none; }
  /* The scrub degrades to both tracks fully rendered and static — not to
     nothing. That is now the DEFAULT geometry rather than an override: site.js
     only adds .scrub.is-live once it has built the timeline, and it never does so
     under reduced motion. These two lines are belt and braces. */
  .msg, .logline { opacity: 1 !important; }
  .thread__inner, .audit__log { will-change: auto; transform: none !important; }
}

@media (forced-colors: active) {
  .btn, .plan, .capture, .phone, .audit, .cols > *, .videoslot { border-color: CanvasText; }
  .msg, .logline { opacity: 1 !important; }
}

@media print {
  .masthead, .themetoggle, .scrub__progress { display: none; }
  body { background: #fff; color: #000; }
  .bay { break-inside: avoid; }
}

/* =============================================================================
   THE DEMO APP — one screen at a time, switched from a bar at the bottom.

   The earlier layout put the member's lanes on the left and the studio's view
   on the right, which reads as a diagram of the product. This reads as the
   product: a full-height stage, one lane visible, and a nav that names the
   three of them. The bar is bottom-anchored INSIDE the app rather than fixed
   to the viewport, so it belongs to the demo and not to the page around it.
   ========================================================================== */
.demoapp {
  display: flex;
  flex-direction: column;
  /* Tall enough to be the page's subject, short enough that the closing copy
     below still signals there is more. `svh` so mobile browser chrome
     collapsing does not resize the stage mid-conversation. */
  height: clamp(32rem, 78svh, 54rem);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 0.7rem;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 30px 60px -38px rgba(0, 0, 0, 0.5);
}
.demoapp__stage { flex: 1; min-height: 0; display: flex; }
.demoapp .lane { flex: 1; min-width: 0; min-height: 0; display: flex; }
.demoapp .lane[hidden] { display: none; }

/* Each lane's own object fills the stage: no phone bezel floating in space, no
   browser frame with a margin around it. The chrome that made them read as
   objects on the old two-pane layout is exactly what looks wrong full-bleed. */
.demoapp .phone,
.demoapp .webframe,
.demoapp .dash {
  flex: 1;
  min-width: 0;
  min-height: 0;
  max-width: none;
  height: auto;
  margin: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: var(--surface);
  display: flex;
  flex-direction: column;
}
.demoapp .phone__screen { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.demoapp .phone__thread { flex: 1; min-height: 0; }
.demoapp .phone__body { flex: 1; min-height: 0; }
.demoapp .webframe__body,
.demoapp .dash__body { flex: 1; min-height: 0; }
.demoapp .dash__body { overflow: auto; }
/* The thread is centred at reading width rather than stretched across a
   desktop stage — a chat line the full width of a monitor is unreadable. */
.demoapp .thread__inner,
.demoapp .demo__composer { max-width: 44rem; margin-inline: auto; width: 100%; }

.demonav {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.25rem;
  padding: 0.4rem 0.4rem calc(0.4rem + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--rule);
  background: var(--surface-2, var(--surface));
}
.demonav__btn {
  appearance: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  padding: 0.5rem 0.4rem;
  border: 0;
  border-radius: 0.5rem;
  background: transparent;
  color: var(--ink-3);
  font: inherit;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
}
.demonav__btn svg { width: 1.15rem; height: 1.15rem; }
.demonav__btn:hover { color: var(--ink-2); }
.demonav__btn.is-on { color: var(--ink); background: var(--wash, rgba(127, 127, 127, 0.12)); }
.demonav__btn:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }

/* --- Desktop: stop hiding the studio's half ---------------------------------
   On a phone there is only room for one screen, so all three lanes take turns
   and the bar names them. On a desktop the two halves should be visible AT THE
   SAME TIME, because the whole argument of this page is that a member's message
   and the studio's record are one object — and you cannot watch a row appear in
   response to a sentence if the sentence is on another screen.

   So the two CUSTOMER lanes keep taking turns in the left column, and Admin —
   the only management-facing lane — stops taking turns and simply stands in the
   right column. The bar shrinks to the two lanes that still switch. */
@media (min-width: 64rem) {
  .demoapp { height: clamp(34rem, 80svh, 56rem); }
  .demoapp__stage {
    display: grid;
    grid-template-columns: minmax(0, 25rem) minmax(0, 1fr);
    /* A 1px gap over the rule colour draws the divider without either lane
       needing a border that would double up against the shell's own. */
    gap: 1px;
    background: var(--rule);
  }
  .demoapp .lane { background: var(--surface); }
  .demoapp .lane[data-lane-panel="chat"],
  .demoapp .lane[data-lane-panel="account"] { grid-column: 1; grid-row: 1; }
  .demoapp .lane[data-lane-panel="admin"] { grid-column: 2; grid-row: 1; }
  /* Admin is never hidden here, whichever lane the bar has selected. */
  .demoapp .lane[data-lane-panel="admin"][hidden] { display: flex; }
  /* The left column is always the chat here. This also covers the resize case
     the old admin-only guard covered: a phone-width visitor who selected
     another lane and then widened would otherwise land on an empty column,
     and with the bar gone would have no way back. */
  .demoapp .lane[data-lane-panel="chat"][hidden] { display: flex; }
  .demoapp .lane[data-lane-panel="account"] { display: none; }

  /* No lane bar on a desktop. Both halves are already on screen, and the
     widget carries its OWN Home / Account nav inside the frame — so the outer
     pair was a second Account button sitting a few pixels under the first,
     switching a column the visitor could already switch from within.

     Hidden here rather than deleted from the markup: below 64rem the lanes
     genuinely take turns and this bar is the only way to reach them. */
  .demonav { display: none; }
}

/* --- Desktop: two devices, not one console ---------------------------------
   The stage used to be a single shell — one border, one rounded rectangle, a
   1px rule down the middle — with the member's chat in the left column and the
   studio's dashboard in the right. Read as an object it was one machine
   showing two screens, which is not what this page is claiming: these are two
   different people, on two different devices, looking at the same moment from
   opposite sides.

   So the shell dissolves and the lanes become the devices. A phone on the
   left, a tablet on the right, set apart with real space between them.

   It also settles the greyish frame that used to sit around the black chat:
   that was the shell's own `--surface` showing through in the gap between the
   lane and the framed widget. With the shell transparent and the bezel near
   black, there is nothing left to show. */
@media (min-width: 64rem) {
  .demoapp {
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    /* The devices cast shadows now, and a clipped shadow reads as a seam. */
    overflow: visible;
  }

  .demoapp__stage {
    grid-template-columns: minmax(0, 22.5rem) minmax(0, 1fr);
    /* Real space, not a rule. This is the whole point of the change. */
    gap: clamp(1.25rem, 3.2vw, 3rem);
    background: transparent;
    /* And a little air on the outer edge, so the tablet reads as an object
       standing on the page rather than something the page cropped. */
    padding-right: clamp(0.25rem, 1.2vw, 1rem);
  }

  .demoapp .lane { background: transparent; }

  /* Both devices share one bezel treatment: near black, a hairline highlight
     where the light catches the top edge, and a long soft shadow underneath.
     Only the radius differs, which is most of what separates a phone from a
     tablet at a glance. */
  .demoapp .lane[data-lane-panel="chat"] .phone,
  .demoapp .lane[data-lane-panel="admin"] .dash {
    background: #0A0B0D;
    box-shadow:
      0 0 0 1px rgba(255, 255, 255, 0.09),
      inset 0 1px 0 rgba(255, 255, 255, 0.06),
      0 44px 80px -46px rgba(0, 0, 0, 0.9);
  }

  /* The phone. */
  .demoapp .lane[data-lane-panel="chat"] .phone {
    border-radius: 2.4rem;
    padding: 0.4rem;
  }
  .demoapp .lane[data-lane-panel="chat"] .phone__screen {
    border-radius: 2.05rem;
    overflow: hidden;
    background: #000;
  }

  /* The tablet. Same height as the phone for free — they are two items in one
     grid row — so only the shape has to say which is which. */
  .demoapp .lane[data-lane-panel="admin"] .dash {
    border-radius: 1.4rem;
    padding: 0.55rem;
  }
  .demoapp .lane[data-lane-panel="admin"] .dash__bar {
    border-radius: 0.95rem 0.95rem 0 0;
  }
  .demoapp .lane[data-lane-panel="admin"] .dash__body {
    border-radius: 0 0 0.95rem 0.95rem;
    overflow: hidden;
  }

  /* The bottom fade goes with the bezel. It existed to soften a hard cut on a
     pane that ran flush to its frame; a screen ending at its own bezel is not
     a hard cut, it is where the device stops. */
  .demoapp .lane[data-lane-panel="admin"] .dash--glass::after {
    display: none;
  }

  /* The tablet's SCREEN is dark whatever theme the visitor is in.

     Before verification the pane shows the projection, and the projection is
     built from THIS site's tokens — so for a light-mode visitor it rendered
     dark ink straight onto the black bezel and could not be read at all. That
     only appeared once the bezel arrived; until then the lane's own surface
     was underneath it.

     Pinning the dark set here also stops the screen flipping from light to
     dark the moment the real member view frames over it. */
  .demoapp .lane[data-lane-panel="admin"] .dash {
    --paper: #12141A;
    --surface: #191C23;
    --sunken: #0C0E13;
    --ink: #E8E6DE;
    --ink-2: #B4B7BF;
    --ink-3: #94979F;
    --rule: rgba(232, 230, 222, 0.20);
    --rule-soft: rgba(232, 230, 222, 0.11);
    color: var(--ink);
  }
  .demoapp .lane[data-lane-panel="admin"] .dash__body { background: var(--paper); }
  .demoapp .lane[data-lane-panel="admin"] .dash__bar { background: var(--sunken); }
}

