/* ==========================================================================
   V7 VOICE-FIRST
   Studied from the aaru.com screenshots Isaac supplied:
     01. TRUE BLACK STAGE. The page is #000, the cards are the only surfaces.
     02. FLOATING NAV PILL, centered, slightly lifted off the top edge,
         wordmark left and a TWO-BAR hamburger right inside the same pill.
     03. ROUNDED CARDS as the hero objects, on a surface barely lighter
         than the stage.
     04. MENU AS A MODAL PANEL: same pill radius, hairline dividers between
         items, small pill buttons at the bottom, page blurred behind it.
   Ours, not theirs: fully greyscale stage; brand purple #5E37FB appears only
   on the menu's Book a demo pill. (Pulsing mic, kicker line, footer line and
   the record's purple status dot were all cut at Isaac's request.)

   THE SEQUENCE: capture card types the deal -> when the message finishes,
   the record card appears on the right and fills itself out row by row.
   ========================================================================== */

/* ---------- cross-page transitions ----------
   Continue/Back navigations morph instead of hard-refreshing. The browser
   snapshots the old page and crossfades to the new one, and elements that
   carry the same view-transition-name on BOTH pages (the nav pill, FIG 1.1,
   the Continue action) are carried across as one persistent thing instead of
   fading out and back in. Browsers without support (Firefox) get a 200ms
   fade-out fallback from main.js/brain.js; either way, no hard cut. */
@view-transition { navigation: auto; }
.pill { view-transition-name: pill; }
#fig, #fig1 { view-transition-name: fig-capture; }
/* The Continue/Back control deliberately has NO view-transition-name. It had
   one, and clicking Back morphed the visible button into the arriving page's
   still-hidden copy (fresh pages load with the control unrevealed until the
   sequence ends), which read as the button expanding into the screen. Small
   controls ride the root crossfade; only elements that are VISIBLE on both
   sides of a navigation (pill, fig 1.1) earn a name. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-image-pair(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
/* fallback fade for non-supporting browsers */
body { transition: opacity 220ms ease; }
body.leaving { opacity: 0; }

@font-face {
  font-family: Strichpunkt;
  src: url('assets/StrichpunktSans-Variable.ttf') format('truetype');
  font-weight: 100 900;
  font-display: swap;
}

:root {
  --black: #000000;
  --card: #0E0F12;
  --card-line: rgba(155, 163, 185, 0.14);
  --pill-bg: #101114;
  --ink: #F3F4F5;
  --dim: #9AA2B4;
  --faint: #62687A;
  --purple: #5E37FB;
  --purple-soft: #8E6BFF;
  /* --mono RETIRED 2026-07-31, deliberately deleted rather than left unused so
     it cannot creep back in. There is no monospace anywhere on this site, and
     no mono in the brand at all. See the .mono rule for the reasoning. */
  /* The figure rail's annotation face. Brand deck, Type System: "Strichpunkt
     Sans is our typeface across every surface" and the system defines only
     Header / Body / Buttons. There is NO mono in the brand at all, so the
     rail's ui-monospace was an addition, not a requirement, and it resolved
     to SF Mono on a Mac: the default look of every generated site going.
     Strichpunkt tracked wide in caps is both on-brand and genuinely
     uncommon, and it is already loaded, so it costs no request. Real
     drafting sheets letterspace a grotesque anyway; a typewriter face reads
     as code, not as engineering. Single swap point if this ever changes. */
  --label: Strichpunkt, 'Helvetica Neue', Arial, sans-serif;
  --radius: 26px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body { height: 100%; }

body {
  background: var(--black);
  color: var(--ink);
  font-family: Strichpunkt, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }

/* ---------- text selection: brand purple (Isaac, 2026-07-30) ----------
   #5E37FB straight off the brand deck (Brand Guidelines V1, Color), which is
   already what --purple holds. The deck reserves it for "action and
   emphasis... where a decision or next step lives"; a selection is
   user-initiated emphasis, so it earns the signal colour rather than the
   browser's default blue, which was the one non-brand colour left on the
   stage.

   White on #5E37FB measures 6.15:1, so selected text stays legible; --ink
   would be 5.54:1 and slightly muddier against that much saturation.

   ::selection and ::-moz-selection MUST stay SEPARATE rules. If a browser
   fails to parse one selector in a comma-separated GROUP it discards the
   entire rule, so combining them would silently kill selection styling in
   one engine or the other. */
::selection { background: var(--purple); color: #fff; }
::-moz-selection { background: var(--purple); color: #fff; }
/* ---------- THE MONOSPACE IS GONE, SITE-WIDE (Isaac, 2026-07-31) ----------
   "I do not like seeing any of the UI monospace across any of these demos on
   our website... it looks very vibe coding, and a lot of people use that, and
   we do not want to come off as that."

   The class name stays `.mono` because it sits on ~63 elements across nine
   pages and renaming it buys nothing; treat it as "small caps label" now. This
   supersedes the 2026-07-30 note that kept `--mono` for the record and Feed
   micro-labels because they mirrored the app's own font-mono classes. Isaac's
   call overrides app parity here, and the brand deck already agreed: there is
   no mono in the GravityCore brand at all.

   Same recipe as the fig labels, the footer small caps and .klabel: off the
   mono, off --faint (3.45:1 on the card, under the 4.5:1 AA floor) to --dim at
   7.48:1, size up a touch, tracking down from 0.16em because generous tracking
   reads as technical drafting at large sizes and as loose confetti at 10px.

   Weight 500, NOT 600. Pills and buttons take 600 via .rec__tag / .sub__pill
   per the deck's "Buttons and UI labels use SemiBold", but .mono also lands on
   long metadata sentences like .sub__meta, and SemiBold across a whole sentence
   of 9.5px metadata reads shouty.

   SIZE STAYS 10px. Bumping it to 10.5 was tried and reverted: .mono lands on
   the record's fact labels, so it grew that card 4.6px and pushed page 1's
   Continue row from 764 to 768 while pages 2 and 4 held at 761, widening a
   pre-existing 3px misalignment to 7px across the view transition Continue
   morphs through. The brief was the face, not the size, and Strichpunkt at 10px
   already reads far better than the mono did because it is proportional and now
   carries 7.48:1 instead of 3.45:1. Pills set their own larger sizes. */
.mono {
  font-family: var(--label);
  font-size: 10px;
  font-variation-settings: "wght" 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
}

/* ---------- nav pill: ONE component in both states ----------
   The menu is the same pill grown downward, per the aaru reference. The
   invariants that make the morph read as smooth:
     1. Width is CONSTANT between states, so the lockup never moves. (A
        centered pill that widened would slide its left edge outward, and
        the lockup with it.)
     2. The burger IS the close button. Its two bars rotate into the X in
        place, so the control never changes position.
     3. Height animates via grid-template-rows 0fr -> 1fr, which eases the
        pill downward without hardcoding a pixel height. */
.pill {
  position: fixed;
  top: 22px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  width: min(320px, calc(100vw - 40px));
  background: var(--pill-bg);
  border: 1px solid rgba(155, 163, 185, 0.10);
  border-radius: 16px;
  overflow: hidden;
}
.pill__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 15px 16px 15px 22px;
}
.pill__brand { display: flex; align-items: center; }
.pill__brand img { display: block; }

/* the two-bar burger; the bars rotate into the X in place */
.pill__burger {
  background: none;
  border: 0;
  cursor: pointer;
  padding: 8px 6px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
/* same 400ms / same curve as the panel: the bars becoming the X and the pill
   growing are one gesture, and they used to finish 100ms apart */
.pill__burger span {
  display: block;
  width: 22px;
  height: 1.6px;
  background: var(--dim);
  transition: background 160ms ease, transform 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
.pill__burger:hover span { background: var(--ink); }
.pill.open .pill__burger span:first-child { transform: translateY(3.3px) rotate(45deg); }
.pill.open .pill__burger span:last-child  { transform: translateY(-3.3px) rotate(-45deg); }

/* ---------- the growth: closed is 0fr, open is 1fr ----------
   Rebuilt for smoothness 2026-07-30 (Isaac: janky, wanted aaru's feel).
   THREE separate things were fighting each other, and all three had to go:

   1. THE SCROLLBAR. `overflow-y: auto` on the element whose height is being
      animated means that for the WHOLE transition the box is shorter than
      its content, so a scrollbar is present the entire time and vanishes at
      the end. Worse, it reserved gutter, which is why the divider rules
      stopped short of the right edge and appeared to twitch. The height cap
      still has to exist (see the 375x568 note below), so the scrollbar is
      hidden rather than the overflow removed: it can still scroll, it just
      never draws or takes width.
   2. PADDING SNAPPING. `padding-bottom` went 0 -> 22px only in the .open
      rule and was never transitioned, so the content lurched 22px on frame
      one while the container was still near zero height. aaru transitions
      padding-bottom on the same clock as the rows; so do we now.
   3. TWO CLOCKS. Height ran 340ms on one curve while opacity ran 300ms on
      another with a 120ms delay, so the panel read as two events instead of
      one movement. Everything now shares 400ms cubic-bezier(0.4, 0, 0.2, 1),
      which is aaru's own timing. */
.pill__menu {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
.pill.open .pill__menu { grid-template-rows: 1fr; }
.pill__menuinner {
  min-height: 0;
  /* The cap exists because the menu grew: six rows plus four Use cases
     sub-rows reaches ~641px open, which at 375x568 pushed Pricing and the
     buttons 95px past the bottom of a position:fixed, overflow:hidden pill,
     i.e. off screen with no way to reach them. X stays hidden so this can
     never add a sideways scroll, and the collapse still works because the
     0fr row and min-height:0 set the height, not the content. */
  overflow: hidden auto;
  max-height: calc(100vh - 96px);
  overscroll-behavior: contain;   /* don't chain the scroll to the page */
  scrollbar-width: none;          /* Firefox: no bar, no gutter */
  padding: 0 22px;
  opacity: 0;
  transition:
    opacity 400ms cubic-bezier(0.4, 0, 0.2, 1),
    padding-bottom 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* WebKit/Blink: same, and it must be its own rule. An unrecognised
   pseudo-element invalidates the whole selector list it appears in. */
.pill__menuinner::-webkit-scrollbar { width: 0; height: 0; }
.pill.open .pill__menuinner {
  opacity: 1;
  padding-bottom: 22px;
}

/* click-outside surface while the pill is open */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 25;
  background: rgba(0, 0, 0, 0.45);
}
.scrim[hidden] { display: none; }

/* ---------- menu content (lives inside the pill's grown region) ---------- */
/* The menu names the four sections of the walk (Capture, Compound, Execute,
   Distribution) rather than product nouns, then Use cases, then Pricing.
   NOTE the child combinator: `.menu__list a` used to be a descendant
   selector, which would now also style the sub-list links inside Use cases
   at 17px with a divider. Top-level rows only. */
.menu__list { display: flex; flex-direction: column; }
.menu__list > a, .menu__row {
  padding: 15px 2px;
  border-top: 1px solid var(--card-line);
  font-size: 17px;
  letter-spacing: -0.01em;
  transition: color 160ms ease, padding-left 160ms ease;
}
.menu__list > a:hover { color: var(--purple-soft); padding-left: 6px; }

/* ---------- the Use cases expander ----------
   Read off aaru.com's own menu, which is the reference Isaac supplied: a
   real <button aria-expanded> whose sibling panel animates on
   grid-template-rows. That is the SAME mechanism the pill itself uses to
   grow, so the menu opening and this row opening are one idea at two scales.

   `aria-expanded` on the button is the ONLY state. The CSS keys off it via
   the sibling combinator, so there is no class to keep in sync with the
   attribute and no way for the two to disagree. */
.menu__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  background: none;
  border-left: 0;
  border-right: 0;
  border-bottom: 0;
  color: inherit;
  font-family: inherit;
  font-weight: 400;
  text-align: left;
  cursor: pointer;
}
.menu__row:hover { color: var(--purple-soft); }

/* the plus becomes the minus IN PLACE, the same way the burger becomes the
   X: one bar holds still, the other rotates back to horizontal */
.menu__pm {
  position: relative;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #1A1B1F;
  display: grid;
  place-items: center;
}
.menu__pm span {
  position: absolute;
  width: 10px;
  height: 1.4px;
  background: var(--dim);
  transition: transform 400ms cubic-bezier(0.4, 0, 0.2, 1), background 160ms ease;
}
.menu__pm span:last-child { transform: rotate(90deg); }
.menu__row[aria-expanded="true"] .menu__pm span:last-child { transform: rotate(0deg); }
.menu__row:hover .menu__pm span { background: var(--ink); }

/* same clock as the pill's own growth: one idea at two scales */
.menu__sub {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
.menu__row[aria-expanded="true"] + .menu__sub { grid-template-rows: 1fr; }
.menu__subinner {
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* sub-items carry no divider and sit a step down in size and colour, so the
   group reads as one row that opened rather than five new rows */
.menu__sub a {
  padding: 9px 2px;
  font-size: 14.5px;
  color: var(--dim);
  letter-spacing: -0.005em;
  transition: color 160ms ease, padding-left 160ms ease;
}
.menu__sub a:first-child { padding-top: 3px; }
.menu__sub a:last-child { padding-bottom: 15px; }
.menu__sub a:hover { color: var(--ink); padding-left: 6px; }
.menu__actions { display: flex; gap: 10px; margin-top: 22px; }
/* Isaac 2026-07-30: these read too small and too thin next to aaru's. They
   were 10px MONO at 0.14em tracking, which is the thinnest, most widely
   spaced thing on the page, so the words had no weight to hold onto.
   The deck answers this directly, Type System: "Buttons and UI labels use
   SemiBold to stay legible at small sizes without feeling heavy."
   NO font-family here on purpose: dropping the --mono override lets them
   inherit Strichpunkt from body, which is what "our typeface across every
   surface" means. 12px SemiBold with tracking pulled 0.14 -> 0.06em, since
   loose tracking is exactly what was making them feel spindly. Label colour
   goes --dim -> --ink too; a button is an action and should not whisper. */
.menu__btn {
  font-size: 12px;
  font-variation-settings: "wght" 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  background: #1A1B1F;
  padding: 12px 20px;
  border-radius: 999px;
  transition: color 160ms ease, background 160ms ease;
}
.menu__btn:hover { background: #232429; }
.menu__btn--solid { background: var(--purple); color: #fff; }
/* must restate the background: .menu__btn:hover is also (0,2,0) and would
   otherwise drop the brand button to the ghost button's grey on hover */
.menu__btn--solid:hover { background: var(--purple); color: #fff; opacity: 0.9; }

/* page blurs behind the open menu, like the reference */
.stage { transition: filter 240ms ease, opacity 240ms ease; }
body.menu-open .stage { filter: blur(9px); opacity: 0.55; }

/* ---------- stage: two cards ----------
   Capture sits left of center but pulled IN toward the middle (Isaac's 30%
   note), lifted high on the stage. The record joins on the right when the
   message finishes. */
.stage {
  min-height: 100%;
  display: grid;
  grid-template-columns: minmax(0, 480px) minmax(0, 530px);
  justify-content: center;
  align-content: start;
  align-items: start;
  column-gap: clamp(22px, 2.6vw, 40px);
  padding: 118px clamp(20px, 4vw, 56px) 72px;
}
/* left column: spoken deal, then the pitch it proves */
.col { grid-column: 1; min-width: 0; }
/* right column: the record card with Continue hanging off its corner */
.reccol { grid-column: 2; min-width: 0; }
.card {
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: var(--radius);
}

/* ---------- capture ----------
   No box. The words sit straight on the black, in exactly the spot the card
   used to hold them (same width, same padding-derived offsets), so the only
   drawn surface on the stage is the record that answers them. */
.card--capture {
  width: 100%;
  /* Reserved height for the finished message. Without it the pitch below
     would be shoved down line by line as the cursor types. */
  min-height: 250px;
  padding: clamp(24px, 3vw, 36px);
  background: none;
  border: 0;
  opacity: 0;
  animation: cardIn 900ms ease 200ms forwards;
}
@keyframes cardIn { to { opacity: 1; } }
.card__type {
  font-size: clamp(16px, 1.6vw, 20px);
  line-height: 1.6;
  letter-spacing: -0.005em;
  color: var(--dim);
}
.caret {
  display: inline-block;
  width: 2px;
  height: 1.15em;
  background: var(--ink);
  vertical-align: text-bottom;
  margin-left: 2px;
  animation: blink 1.05s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* ---------- pitch: what the sequence just proved ----------
   Resolves in after the record finishes filling, so it reads as the caption
   to something you watched rather than a headline you were handed. */
.pitch {
  padding: 0 clamp(24px, 3vw, 36px);
  opacity: 0;
  transform: translateY(9px);
  transition: opacity 700ms ease, transform 700ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.pitch.in { opacity: 1; transform: none; }
/* Page 3 has no typed capture, so the header takes the caret's place. The
   top padding replaces the capture block's, keeping the headline's first
   line on the same baseline the typing starts on elsewhere. */
.pitch--lead { padding-top: clamp(24px, 3vw, 36px); }
.pitch--lead .pitch__title { max-width: 17em; }
/* Page 3 has no demo card, so its header column can take room from the
   right one. The TOTAL stays 1050px (620 + 40 gap + 390), which is what
   keeps Back/Continue at the same x as every other page. Without this the
   header only had 408px of measure and its two-line break made three. */
/* Page 3 carries a chat on the left and a workbook on the right, so both
   sides need real room: 560 clears the headline's 477px first line, 700
   gives the workbook enough width to read as a real spreadsheet.
   Back/Continue follow the demo's bottom-right corner (the rule everywhere
   in this flow), so they sit further right here than on pages 1-2 while
   holding the same y. */
.stage--lead { grid-template-columns: minmax(0, 560px) minmax(0, 700px); }
/* Page 4's Feed demo runs 10% wider than pages 1-2's 530 (Isaac, 2026-07-30):
   the subscription name is long enough to wrap at 530, and the Recipients row
   was tight. The left column is unchanged, so this is purely the demo growing.
   CONSEQUENCE, and it is deliberate: Back/Book a demo hug the demo's
   bottom-right corner, so page 4's action row now sits 26.5px right of pages
   1-2's (right edge 1262.7 vs 1236.2 at 1440 wide). Half the added 53px, not
   all of it, because the whole grid is centered and absorbs the other half on
   the left. Page 3 already breaks that alignment for the same reason
   (.stage--lead, right edge 1361). The y is untouched at 761, so the row
   still holds its line across all four pages. */
.stage--feed { grid-template-columns: minmax(0, 480px) minmax(0, 583px); }

/* ---------- the chat: Giles running the playbook ---------- */
.chat {
  margin-top: clamp(28px, 4vh, 44px);
  background: #131418;
  border: 1px solid rgba(155, 163, 185, 0.13);
  border-radius: 20px;
  padding: 18px 18px 16px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 0 90px rgba(158, 168, 198, 0.08),
    0 28px 60px -30px rgba(0, 0, 0, 0.9);
  opacity: 0;
  transform: translateY(9px);
  transition: opacity 700ms ease, transform 700ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.chat.in { opacity: 1; transform: none; }
.chat__you {
  margin-left: auto;
  width: fit-content;
  max-width: 88%;
  background: #22242A;
  border-radius: 999px;
  padding: 9px 16px;
  font-size: 13.5px;
  letter-spacing: -0.005em;
}
.chat__giles {
  margin-top: 16px;
  color: var(--dim);
  font-size: 13.5px;
  line-height: 1.62;
}
.chat__giles b { color: var(--ink); font-weight: 600; }
.chat__status {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
}
/* The mark PULSES while Giles thinks; it does not spin (Isaac 2026-07-30).
   A spinner reads as a loading state, which is the wrong idea: he is
   reasoning, not fetching. */
.chat__mark {
  display: block;
  flex: none;
  animation: chatPulse 1.8s ease-in-out infinite;
}
@keyframes chatPulse {
  0%, 100% { opacity: 0.45; transform: scale(0.92); }
  50%      { opacity: 1;    transform: scale(1); }
}
.chat__status .mono {
  font-size: 10.5px;
  letter-spacing: 0.1em;
  color: var(--faint);
  text-transform: none;
}
.chat__bar {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-top: 16px;
  /* roomier than before: at 10/13 the clip's stroke and the placeholder's
     descenders were grazing the rounded edge */
  padding: 11px 11px 11px 15px;
  background: #0E0F12;
  border: 1px solid rgba(155, 163, 185, 0.13);
  border-radius: 13px;
  color: var(--faint);
}
.chat__placeholder {
  flex: 1;
  min-width: 0;
  font-size: 13.5px;
  line-height: 1.5;      /* room for descenders; was clipping on the g and p */
  color: var(--faint);
}
/* overflow:visible so the stroked paths are never trimmed by the svg box */
.chat__clip, .chat__mic { flex: none; overflow: visible; }
.chat__send {
  flex: none;
  width: 27px;
  height: 27px;
  border-radius: 8px;
  background: var(--purple);
  color: #fff;
  display: grid;
  place-items: center;
}

/* ---------- the workbook: the firm's own model, in black ----------
   The real add-in runs over a white Excel window. Rendered white here it
   would read as a pasted screenshot, so the whole chrome is restated in the
   page's own near-black. */
.wb {
  background: #0C0D10;
  border: 1px solid rgba(155, 163, 185, 0.16);
  border-radius: 14px;
  overflow: hidden;           /* the grid clips wherever the height lands */
  /* Fallback only. execute.js sets the real height so the sheet ends level
     with the chat's bottom; this keeps the shape sane if JS never runs. */
  height: 536px;
  display: flex;
  flex-direction: column;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 0 110px rgba(158, 168, 198, 0.10),
    0 34px 70px -30px rgba(0, 0, 0, 0.9);
  opacity: 0;
  transform: translateY(9px);
  transition: opacity 700ms ease, transform 700ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.wb.in { opacity: 1; transform: none; }

.wb__title {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid rgba(155, 163, 185, 0.10);
}
.wb__lights { display: flex; gap: 6px; }
.wb__lights i { width: 9px; height: 9px; border-radius: 50%; background: #2A2C33; }
.wb__name {
  flex: 1;
  text-align: center;
  font-size: 11.5px;
  color: var(--dim);
  letter-spacing: -0.005em;
}
.wb__share {
  font-size: 10.5px;
  color: var(--faint);
  border: 1px solid rgba(155, 163, 185, 0.14);
  border-radius: 6px;
  padding: 3px 9px;
}

.wb__ribbon {
  display: flex;
  align-items: center;
  gap: 15px;
  padding: 8px 14px;
  border-bottom: 1px solid rgba(155, 163, 185, 0.10);
  font-size: 10.5px;
  color: var(--faint);
}
.wb__tab { letter-spacing: -0.005em; }
.wb__tab.on { color: var(--ink); box-shadow: inset 0 -2px 0 var(--purple); padding-bottom: 5px; }
.wb__addin {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px;
  background: #000;
  border: 1px solid rgba(155, 163, 185, 0.18);
  border-radius: 7px;
  color: #fff;
  font-size: 10.5px;
  font-weight: 500;
}
/* the shared mark ships brand purple; the add-in button wants it white */
.wb__addin img, .wb__panehead img { filter: brightness(0) invert(1); }

.wb__formula {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 14px;
  border-bottom: 1px solid rgba(155, 163, 185, 0.10);
  /* Was var(--mono). The one place with a real argument for a monospace, since
     Excel's own formula bar is monospaced, but Isaac's 2026-07-31 call is that
     the face appears nowhere on the site. Tracking is pulled slightly negative
     so `=SUM(B6:B8)` still reads as a formula rather than as a label. */
  font-family: var(--label);
  font-size: 10.5px;
  letter-spacing: -0.005em;
  color: var(--dim);
}
.wb__cell {
  border: 1px solid rgba(155, 163, 185, 0.14);
  border-radius: 4px;
  padding: 2px 9px;
  color: var(--dim);
}
.wb__eq { color: var(--dim); }

.wb__body { flex: 1; display: flex; min-height: 0; }

.wb__grid {
  flex: 1;
  border-collapse: collapse;
  font-size: 10px;
  table-layout: fixed;
}
.wb__grid th, .wb__grid td {
  padding: 0 9px;
  height: 26px;
  border-right: 1px solid rgba(155, 163, 185, 0.07);
  border-bottom: 1px solid rgba(155, 163, 185, 0.07);
  text-align: right;
  color: var(--dim);
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wb__grid th { color: var(--faint); font-size: 9.5px; }
.wb__grid th:first-child, .wb__grid td:first-child {
  text-align: left;
  width: 44%;
  color: var(--dim);
}
.wb__grid tr.sum td { color: var(--ink); font-weight: 500; }
.wb__grid .tot { color: var(--ink); }
.wb__grid tr.sum .tot { font-weight: 600; }

.wb__pane {
  width: 194px;
  flex: none;
  border-left: 1px solid rgba(155, 163, 185, 0.10);
  padding: 12px 13px;
  background: rgba(155, 163, 185, 0.02);
}
.wb__panehead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 11.5px;
  color: var(--ink);
  font-weight: 500;
}
.wb__panehead span:first-child { display: inline-flex; align-items: center; gap: 7px; }
.wb__x { color: var(--faint); font-size: 13px; }
.wb__panelabel { margin: 16px 0 9px; }
.wb__steps { list-style: none; display: flex; flex-direction: column; gap: 2px; }
.wb__steps li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 10.5px;
  line-height: 1.35;
  color: var(--faint);
}
.wb__steps i {
  font-style: normal;
  font-family: var(--label);   /* was var(--mono); see the .mono note */
  font-size: 9.5px;
  color: var(--dim);
  flex: none;
}
.wb__steps b { margin-left: auto; font-weight: 400; }
.wb__steps .done { color: var(--dim); }
/* was #7ED9A6. There is NO green anywhere in the brand deck, and Isaac had
   already rejected green-for-success on the Feed status pill. A finished step
   is not a NEXT step, so it does not earn purple either: it goes white. */
.wb__steps .done b { color: var(--ink); }
.wb__steps .now {
  background: rgba(94, 55, 251, 0.16);
  color: var(--ink);
}
.wb__steps .now i { color: var(--purple-soft); }
.wb__steps .now b { color: var(--purple-soft); }
.pitch__title {
  font-size: clamp(25px, 2.5vw, 33px);
  line-height: 1.14;
  letter-spacing: -0.022em;
  font-variation-settings: "wght" 500;
  max-width: 11em;
}
.pitch__body {
  margin-top: 14px;
  color: var(--dim);
  font-size: clamp(14px, 1.15vw, 15.5px);
  line-height: 1.62;
  max-width: 34em;
}
/* consecutive paragraphs need more air than the headline-to-body gap, or
   they read as one block (page 3 runs two) */
.pitch__body + .pitch__body { margin-top: 18px; }

/* ---------- the prompt marquee: PARKED, NO CONSUMER ON ANY PAGE ----------
   Built for page 4's left column 2026-07-30 and pulled the same day: Isaac
   liked it but it made that column too busy beside the Feed demo. He wants
   it somewhere else, so these rules are kept ON PURPOSE rather than deleted
   with the markup. **The markup lives in `_parked-marquee.html`**, which is
   not a page and is not linked from anything; paste it into whatever page
   ends up wanting it and this section wakes up. If that reuse never happens,
   delete this block AND that file together.

   Original build notes follow, because they are what makes it work.

   Isaac asked for something like the tile marquee near the bottom of
   meuze.ai, in the black gap between the pitch copy and the bottom of the
   Feed demo. I read their real implementation rather than working from the
   screenshot. What they do, and what we take:

     TAKEN: three rows; each row overflow:hidden with a horizontal
     mask-image so tiles dissolve at both edges (this is the detail that
     makes it read as a stream passing THROUGH rather than a filmstrip in a
     box); transparent tile fill with a hairline border; a small mono
     category label above a nowrap prompt; the middle row running counter to
     the outer two.

     NOT TAKEN: their tiles are border-radius 0, their prompt face is a
     serif, and they accent the first word of each prompt in amber #F59E0B
     or green. All three are theirs, not ours. This stage is greyscale with
     purple reserved for ACTION and light blue for machine-extracted
     metadata; a coloured verb is neither, so the prompts stay in the grey
     family and get their hierarchy from --faint label vs --dim prompt. The
     radius is 8px because everything else here is rounded.

   Their transform is JS-driven off scroll position. We have no build and no
   scroll framework, so this is a plain CSS marquee.

   THE SEAMLESS-LOOP GOTCHA: the track holds the SAME set of tiles twice and
   animates to translateX(-50%), so the second set lands exactly where the
   first began. That only works if the spacing is a margin-right on each
   TILE, never `gap` on the track. With gap, N tiles have N-1 gaps, so half
   the total width is one set plus half a gap, and the loop jumps 4.5px every
   cycle. Keep the margin on the tile. */
.mq {
  margin-top: clamp(26px, 4vh, 38px);
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.mq__row {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
}
.mq__track { display: flex; width: max-content; will-change: transform; }
/* three different durations so the rows never settle into a visible pattern.
   All slow on purpose: this sits under the headline and must not pull the
   eye off it. */
.mq__track--a { animation: mqLeft 48s linear infinite; }
.mq__track--b { animation: mqRight 58s linear infinite; }
.mq__track--c { animation: mqLeft 42s linear infinite; }
@keyframes mqLeft  { from { transform: translateX(0); }      to { transform: translateX(-50%); } }
@keyframes mqRight { from { transform: translateX(-50%); }   to { transform: translateX(0); } }

.mq__tile {
  flex: none;
  margin-right: 9px;          /* the spacing. NOT gap. See the note above. */
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 10px 14px 11px;
  border: 1px solid var(--card-line);
  border-radius: 8px;
}
.mq__q {
  font-size: 13px;
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--dim);
  white-space: nowrap;        /* a prompt that wraps stops reading as a chip */
}

/* ---------- the fig rail ----------
   Full content width, three equal slots. Only CAPTURE renders on this page;
   COMPOUND and DISTRIBUTE fill slots 2 and 3 on the pages behind Continue.
   Because every page shares this grid, the figures accumulate onto one
   perfect line as the user clicks through. */
.figrow {
  grid-column: 1 / -1;
  position: relative;   /* anchors the .figbridge overlay */
  display: grid;
  /* FOUR slots since 2026-07-30: Capture, Compound, Execute, Distribute.
     At four across, the stage's 1050px content column is too tight, so the
     rail breaks out wider than its grid tracks (justify-self centers the
     overflow), which also shifts fig 1.1 left, per Isaac's note. */
  grid-template-columns: repeat(4, 1fr);
  width: min(1320px, calc(100vw - 64px));
  justify-self: center;
  gap: clamp(20px, 2.4vw, 40px);
  margin-top: clamp(44px, 6.5vh, 72px);
}

/* The flow from one figure to the next: dots marching rightward, out of the
   structured record and INTO the compound stack. Spans the whole distance
   (placed by brain.js), not just the grid gap; a CSS border cannot animate
   its dashes, so the dots are a repeating gradient sliding one period per
   cycle, which reads as continuous travel toward Compound. */
.figbridge {
  position: absolute;
  height: 2px;
  background-image: repeating-linear-gradient(90deg,
    rgba(200, 208, 226, 0.55) 0 2.5px,
    transparent 2.5px 9px);
  animation: bridgeFlow 0.8s linear infinite;
  opacity: 0;
  transition: opacity 600ms ease 200ms;
}
.figbridge.in { opacity: 1; }
@keyframes bridgeFlow { to { background-position: 9px 0; } }

/* ---------- the rail is navigation (Isaac, 2026-07-30) ----------
   Every figure a user has already walked past links back to the page that
   built it. The figure for the page you are ON is deliberately NOT wrapped,
   the same way the current page is not a link in any nav; that is also why
   page 1 has no rail links at all (its only figure is its own).

   The affordance is restrained on purpose: the caption resolves faint -> ink
   and the drawing lifts 3px. The figure's own palette is left alone.
   Brightening a hovered figure would push it out of the family those alphas
   were tuned against, which is the exact problem COMPOUND had.

   The lift is on .fig__svg, NOT .fig: .fig owns the reveal transform
   (translateY(9px) -> none) on a 700ms clock, and hover has no business
   sharing a property, or a duration, with that. */
.figlink { display: block; }
.figlink .fig__cap { transition: color 220ms ease; }
.figlink .fig__svg { transition: transform 320ms cubic-bezier(0.2, 0.8, 0.2, 1); }
.figlink:hover .fig__cap,
.figlink:focus-visible .fig__cap { color: var(--ink); }
.figlink:hover .fig__svg,
.figlink:focus-visible .fig__svg { transform: translateY(-3px); }
.figlink:focus-visible {
  outline: 1px solid var(--purple-soft);
  outline-offset: 10px;
  border-radius: 10px;
}

/* fig 1.1: technical line drawing, rebuilt from Isaac's reference. Loops on
   its own, independent of the one-shot capture sequence above it. */
/* figures auto-place left to right: capture, compound, distribute */
.fig {
  opacity: 0;
  transform: translateY(9px);
  transition: opacity 700ms ease, transform 700ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.fig.in { opacity: 1; transform: none; }
/* Brought to --dim with the labels 2026-07-30. .mono ships --faint, and once
   the in-figure labels went to --dim the heading was DIMMER than the parts it
   heads, which read as a mistake. Scoped here, not on .mono, which is shared
   with the record kickers, the meta lines and the footer. */
/* Overrides the .mono class still on these elements in the markup, rather
   than stripping that class from 20-odd tags across four pages. */
.fig__cap {
  display: block;
  margin-bottom: 14px;
  color: var(--dim);
  font-family: var(--label);
  font-size: 10.5px;
  font-variation-settings: "wght" 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.fig__svg { display: block; width: 100%; height: auto; overflow: visible; }

.fig__axis {
  stroke: rgba(155, 163, 185, 0.16);
  stroke-width: 1;
  stroke-dasharray: 2 5;
  animation: figDrift 3.2s linear infinite;
}
@keyframes figDrift { to { stroke-dashoffset: -14; } }

.fig__ring { fill: none; stroke: rgba(155, 163, 185, 0.34); stroke-width: 1; }
.fig__glyph {
  fill: none;
  stroke: rgba(200, 208, 226, 0.62);
  stroke-width: 1.2;
  stroke-linecap: round;
}
/* In-figure part labels: VOICE, STRUCTURED RECORD, AGENTS, WORKSTATION,
   BRIEFING, RECIPIENTS, and 1.2's layer names.

   Enlarged and brightened 2026-07-30 (Isaac: "make that a little easier to
   read"). These are SVG units in a 470-wide viewBox rendered at ~304px, so
   they are scaled by 0.647: the old font-size 9 was landing at 5.8 ACTUAL
   pixels while the mono FIG caption right above them is true HTML 10px,
   which is why the labels read as unreadable noise next to a crisp heading.
   13 lands at ~8.4px. Do not reason about this number without the 0.647.

   --faint on pure black is about 3.6:1, under AA for small text; --dim is
   about 7.6:1. Tracking comes down 0.18 -> 0.14em too: generous tracking
   reads as technical drafting at large sizes and as loose confetti at 6px,
   and the tighter value also buys back width headroom. */
/* 12.5/0.12em, not the 13/0.16em the mono ran at. Strichpunkt's CAPS are
   wider than SF Mono's fixed advance, so like-for-like numbers pushed
   OUTCOMES from 439.2 to 450.9 units and flow 2 (which starts just past it)
   ended up overlapping the label again. Narrower here buys that back. */
.fig__label {
  fill: var(--dim);
  font-family: var(--label);
  font-size: 12.5px;
  font-variation-settings: "wght" 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
/* The sentence under fig 1.2 is a CAPTION, not a part label, and it is 44
   characters against Recipients' 10. At the label's 13 it would run about
   459 of the 470 units and hang off the left edge of the figure. Kept
   smaller so it stays inside the box; still up from 9, still brightened. */
.fig__note {
  fill: var(--dim);
  font-family: var(--label);
  font-size: 11px;
  font-variation-settings: "wght" 450;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* speech: bars rise and fall in a travelling wave */
.fig__wave rect { fill: rgba(200, 208, 226, 0.42); transform-box: fill-box; transform-origin: center; }
.fig__wave rect { animation: figBar 1.9s ease-in-out infinite; }
.fig__wave rect:nth-child(1)  { animation-delay: 0.00s; }
.fig__wave rect:nth-child(2)  { animation-delay: 0.07s; }
.fig__wave rect:nth-child(3)  { animation-delay: 0.14s; }
.fig__wave rect:nth-child(4)  { animation-delay: 0.21s; }
.fig__wave rect:nth-child(5)  { animation-delay: 0.28s; }
.fig__wave rect:nth-child(6)  { animation-delay: 0.35s; }
.fig__wave rect:nth-child(7)  { animation-delay: 0.42s; }
.fig__wave rect:nth-child(8)  { animation-delay: 0.49s; }
.fig__wave rect:nth-child(9)  { animation-delay: 0.56s; }
.fig__wave rect:nth-child(10) { animation-delay: 0.63s; }
.fig__wave rect:nth-child(11) { animation-delay: 0.70s; }
.fig__wave rect:nth-child(12) { animation-delay: 0.77s; }
.fig__wave rect:nth-child(13) { animation-delay: 0.84s; }
.fig__wave rect:nth-child(14) { animation-delay: 0.91s; }
@keyframes figBar {
  0%, 100% { transform: scaleY(0.28); opacity: 0.4; }
  45%      { transform: scaleY(1);    opacity: 1; }
}

/* the crossing: dots pulse toward the record, then the arrowhead lands */
.fig__travel circle { fill: rgba(200, 208, 226, 0.5); animation: figDot 1.9s ease-in-out infinite; }
.fig__travel circle:nth-child(1) { animation-delay: 0.95s; }
.fig__travel circle:nth-child(2) { animation-delay: 1.05s; }
.fig__travel circle:nth-child(3) { animation-delay: 1.15s; }
@keyframes figDot { 0%, 60%, 100% { opacity: 0.16; } 22% { opacity: 1; } }
.fig__head {
  fill: none;
  stroke: rgba(200, 208, 226, 0.6);
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  animation: figDot 1.9s ease-in-out 1.25s infinite;
}

/* the record: cells fill in sequence, then the check confirms */
.fig__panel { fill: rgba(155, 163, 185, 0.022); stroke: rgba(155, 163, 185, 0.26); stroke-width: 1; }
.fig__panelline { stroke: rgba(155, 163, 185, 0.18); stroke-width: 1; }
.fig__title { fill: rgba(200, 208, 226, 0.45); }
.fig__fills rect {
  fill: rgba(200, 208, 226, 0.5);
  transform-box: fill-box;
  transform-origin: left center;
  animation: figFill 1.9s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.fig__fills rect:nth-child(1) { animation-delay: 1.30s; }
.fig__fills rect:nth-child(2) { animation-delay: 1.36s; }
.fig__fills rect:nth-child(3) { animation-delay: 1.42s; }
.fig__fills rect:nth-child(4) { animation-delay: 1.48s; }
.fig__fills rect:nth-child(5) { animation-delay: 1.54s; }
.fig__fills rect:nth-child(6) { animation-delay: 1.60s; }
@keyframes figFill {
  0%, 68%  { transform: scaleX(0); }
  84%, 100% { transform: scaleX(1); }
}
/* .fig__check / .fig__tick / @keyframes figCheck DELETED 2026-07-30. The
   confirmation mark in each panel's top-right corner (record, workstation,
   briefing) pulsed 0.18 -> 1 on a 1.9s loop, and Isaac cut it: at the rail's
   size it read as a flashing tick rather than a drawn detail, which is the
   blinking chrome the whole stage avoids. Markup removed from all seven
   instances across the four pages, so these rules had no consumers left.
   Do not reinstate a pulsing confirm mark on the figures. */

/* ---------- fig 1.2 COMPOUND ----------
   Three sources feeding a stack of wireframe planes: deals, outcomes,
   market. The stack breathes top to bottom, the sources pulse in sequence,
   the stems drift downward. Quiet, like everything else on the rail. */
/* Brightness note (Isaac, 2026-07-30): COMPOUND first shipped in the dim
   rgba(155,163,185) line family at 0.2-0.36 alpha and read as washed out
   next to CAPTURE, whose waveform and glyphs run rgba(200,208,226) at
   0.42-0.62. The planes are this figure's hero, so they sit at the top of
   that range, and the breathe floor stays high (0.72) so the figure never
   sinks back into the murk mid-cycle. */
.fig2__src {
  fill: none;
  stroke: rgba(200, 208, 226, 0.60);
  stroke-width: 1;
  animation: fig2Pulse 2.4s ease-in-out infinite;
}
.fig2__src:nth-child(2) { animation-delay: 0.4s; }
.fig2__src:nth-child(3) { animation-delay: 0.8s; }
@keyframes fig2Pulse { 0%, 100% { opacity: 0.5; } 30% { opacity: 1; } }
.fig2__stem {
  stroke: rgba(200, 208, 226, 0.38);
  stroke-width: 1;
  stroke-dasharray: 2 4;
  animation: figDrift 3.2s linear infinite;
}
.fig2__plane {
  fill: rgba(200, 208, 226, 0.03);
  stroke: rgba(200, 208, 226, 0.55);
  stroke-width: 1;
  animation: fig2Breathe 3.6s ease-in-out infinite;
}
.fig2__plane:nth-child(2) { animation-delay: 0.5s; }
.fig2__plane:nth-child(3) { animation-delay: 1.0s; }
@keyframes fig2Breathe { 0%, 100% { opacity: 0.72; } 50% { opacity: 1; } }
.fig2__leader {
  stroke: rgba(200, 208, 226, 0.35);
  stroke-width: 1;
  stroke-dasharray: 2 4;
}

/* ---------- fig 1.3 EXECUTE ----------
   Agents converging on one workstation: three rings feeding a wireframe
   panel where a sparkline draws itself and output rows land underneath.
   Same family as 1.1 and 1.2 by construction: it reuses .fig__panel,
   .fig__panelline, .fig__title, .fig__fills,
   .fig2__stem and .fig__travel, so only the two new marks need styling. */
.fig3__ring {
  fill: none;
  stroke: rgba(200, 208, 226, 0.50);
  stroke-width: 1;
  animation: fig2Pulse 2.4s ease-in-out infinite;
}
.fig3__dot {
  fill: rgba(200, 208, 226, 0.75);
  animation: fig2Pulse 2.4s ease-in-out infinite;
}
/* The stagger down a stack of rings. This used to be written as
   .fig3__ring:nth-of-type(2/3), which matched NOTHING: nth-of-type counts by
   ELEMENT type, and rings and dots are interleaved <circle>s, so the 2nd
   circle in the group is a dot, never a ring. All six pulsed in unison.
   Explicit classes instead, shared by fig 1.3's agents and fig 1.4's
   recipients. Must stay AFTER the two rules above, whose shorthand resets
   animation-delay to 0. */
.fig__d2 { animation-delay: 0.35s; }
.fig__d3 { animation-delay: 0.70s; }
/* the analysis: the line draws itself left to right, then holds */
.fig3__spark {
  fill: none;
  stroke: rgba(200, 208, 226, 0.72);
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 260;
  stroke-dashoffset: 260;
  animation: fig3Draw 3.6s ease-in-out infinite;
}
@keyframes fig3Draw {
  0%       { stroke-dashoffset: 260; }
  55%, 88% { stroke-dashoffset: 0; }
  100%     { stroke-dashoffset: 0; opacity: 0; }
}

/* ---------- fig 1.4 DISTRIBUTE ----------
   The rail's closing figure, and the exact inverse of 1.2: COMPOUND
   converges three sources into one stack, DISTRIBUTE diverges one briefing
   into three recipients. Every mark is already in the family, so this
   section carries no rules of its own by design: the panel, header rule,
   title bar, check/tick and writing fills are .fig__* (shared with 1.1), the
   fan-out channels are .fig2__stem (shared with 1.2 and 1.3), and the
   recipients are .fig3__ring/.fig3__dot with .fig__d2/.fig__d3 (shared with
   1.3's agents). The cadence is the loop itself: it writes, it checks, it
   sends, it happens again. If a mark ever needs its own rule here, take it
   from the same rgba(200,208,226,*) bright line family the rest of the rail
   uses; the dim rgba(155,163,185,*) family reads washed out beside it. */

/* ---------- page 2's demo: the Firm knowledge base ----------
   A mini of the real product's Firm page (Isaac's screenshot): the Firm
   title, the Knowledge | Context | Markets tabs, and the typed capture
   landing as an entry: the CBRE report with its PDF, market topics, and the
   user's commentary. Shares the record card's shell (float, reveal, row
   stagger); min-height pins it to the record card's settled 629px so
   Continue sits at the same (x, y) on both pages.
   629 was measured with ALL transitions and animations force-disabled; any
   measurement taken mid-cascade or on a frozen transition clock comes back
   7px short. */
.card--brainrec, .card--feedrec { min-height: 629px; }

/* Pages whose right column has no demo card yet (page 3) stand one of these
   in its place, so Back/Continue land at the same (x, y) as every other
   page. Same 629px as the record card's settled height. */
.reccol__spacer { height: 629px; }

.ktabs {
  display: flex;
  gap: 20px;
  margin-top: 16px;
  border-bottom: 1px solid var(--card-line);
}
.ktabs span {
  padding: 6px 2px 10px;
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--dim);
}
/* purple underline is fidelity to the real Firm page UI, same rule as the
   record card's call-for-offers chip */
.ktabs .on { color: var(--ink); box-shadow: inset 0 -2px 0 var(--purple); }

.kentry {
  border: 1px solid var(--card-line);
  border-radius: 12px;
  margin-top: 14px;
  padding: 14px 15px 15px;
}
.kentry__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}
.kentry__head h3 {
  font-size: 15.5px;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.kpdf {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 12px;
  padding: 8px 12px;
  background: #15161A;
  border: 1px solid rgba(155, 163, 185, 0.12);
  border-radius: 9px;
  font-size: 12.5px;
  color: var(--dim);
}
.kpdf svg { flex: none; color: var(--faint); }
.kpdf b { color: var(--ink); font-weight: 500; }
/* The chip cannot break, at any width. `.kentry` is only 304px wide at 390
   (it is nested inside the record card's own padding), leaving the chip 227px
   of text budget, so a long name has nowhere to go but a second line, and the
   size followed it onto a third. The name absorbs the shortfall via ellipsis
   and the size stays whole. min-width:0 is required: flex items default to
   min-width:auto, which refuses to shrink below max-content and defeats
   text-overflow entirely. */
.kpdf__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kpdf__size { flex: none; white-space: nowrap; }
/* MARKET TOPICS / USER COMMENTARY. Isaac called these fuzzy (2026-07-31) and
   the measurement agrees, but the cause was CONTRAST rather than weight: at
   `.mono`'s --faint (#62687A) on the card's #0E0F12 they sat at 3.45:1, under
   the 4.5:1 AA floor and less than half the contrast of everything else on the
   card, which is 7:1 or better. Now --dim at 7.48:1.

   Same recipe already applied to the fig labels and the footer small caps, and
   for the same reason: off --faint, off the mono, size up, tracking down. They
   were also the last ui-monospace holdouts on a card that is otherwise entirely
   Strichpunkt, so this makes the card consistent rather than splitting it.
   SemiBold per the deck's Type System: "Buttons and UI labels use SemiBold to
   stay legible at small sizes without feeling heavy."

   Beats .mono on source order, not specificity: both are (0,1,0) and .mono is
   ~1080 lines earlier. Keep this rule below it. */
.klabel {
  display: block;
  margin: 14px 0 8px;
  font-family: var(--label);
  font-size: 11px;
  font-variation-settings: "wght" 600;
  letter-spacing: 0.1em;
  color: var(--dim);
}
.kchips { display: flex; flex-wrap: wrap; gap: 7px; }
/* light blue per Isaac 2026-07-30: topics are machine-extracted metadata,
   so they get their own family, distinct from the greyscale UI chrome */
.kchips span {
  padding: 5px 11px;
  background: rgba(96, 150, 255, 0.10);
  border: 1px solid rgba(130, 175, 255, 0.30);
  border-radius: 999px;
  font-size: 12px;
  color: #A7C7FF;
}
.kcommentary {
  color: var(--dim);
  font-size: 13px;
  line-height: 1.6;
  font-style: italic;
}

/* ---------- page 4's demo: the Feed ----------
   A mini of the real product's Feed page, in the app's own order: 01
   Subscriptions, then 02 Published articles. The spoken request lands as the
   subscription; because it ended with "run the first one now", the article
   it published lands underneath. Shares the record card's shell (float,
   reveal, row stagger) and the same pinned 629px, so Back and the closing
   action sit at the same (x, y) as on every other page. */
/* Section heads inside the Feed card. Replaced the mono "01 · SUBSCRIPTIONS"
   kickers 2026-07-30 (Isaac): the numbering and the mono caps went, leaving a
   plain bold white heading. wght 600 rather than the deck's header 500,
   because these label sections of a UI panel rather than heading a page,
   which is the deck's "buttons and UI labels use SemiBold" case. */
.feedhead {
  margin: 22px 0 12px;
  font-size: 15px;
  font-variation-settings: "wght" 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.feedbox {
  border: 1px solid var(--card-line);
  border-radius: 12px;
  padding: 14px 15px 15px;
}

.sub__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}
.sub__head h3 {
  font-size: 15.5px;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.3;
}
.sub__pills { display: flex; gap: 6px; flex: none; }
/* Both pills are neutral. The app renders Active in its success colour; the
   site's cadence pill and status pill read the same weight on purpose
   (Isaac, 2026-07-26), and green is not in this stage's palette anyway. */
/* Isaac, 2026-07-31: give these "that exact effect" the Deal brain pill got, so
   the site has ONE pill treatment rather than two that nearly match. Values are
   .rec__tag's, minus its border colour and padding, which stay this component's
   own. They already inherited Strichpunkt (no .mono class in the markup), so
   what actually changes is the weight to SemiBold, the tracking, and the case.

   NOTE: this UPPERCASES them, to MONTHLY / ACTIVE, because .rec__tag is
   uppercase and matching it was the instruction. If sentence case is wanted
   back, delete the text-transform line and nothing else. */
.sub__pill {
  padding: 3px 9px;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  font-size: 10.5px;
  font-variation-settings: "wght" 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
}
.sub__prompt {
  margin-top: 10px;
  color: var(--dim);
  font-size: 13px;
  line-height: 1.6;
}
/* ---------- the meta separator: a DRAWN hairline, not a character ----------
   Isaac, 2026-07-31: "replace the dots with something more unique to us because
   everybody has those dots on their site." He is right that `·` is the default
   everywhere, as are `•` and `|`.

   So it is not a glyph at all: it is a 1px rule, which is this site's actual
   visual language. Every card divider, every figure axis, every panel edge here
   is a hairline, and there is already precedent for drawing punctuation rather
   than typing it, since empty values use .rec__bar instead of a dash character.
   A drawn rule also sits at an exact height and weight, where a typed pipe
   inherits whatever the face gives it.

   0.42em tall reads as a divider between clauses without competing with the
   cap height. aria-hidden in the markup: it is decoration, and most screen
   readers either skip `·` or announce it as "middle dot", so nothing is lost.

   If a mark is preferred over a rule, swapping this for a small open ring
   (border-radius 999px, 3px square, 1px border) would echo the icon's orbit and
   is the one other idea I would back. */
.meta__sep {
  display: inline-block;
  width: 1px;
  height: 0.42em;
  margin: 0 0.62em;
  /* Raised off the baseline by 0.06em. Strichpunkt's x-height is ~0.52em, so a
     0.42em rule sitting ON the baseline is bottom-weighted against the
     lowercase; this centres it in the x-height band. Checked by scaling
     .sub__meta to 34px in place, since 1px at 9.5px cannot be judged from a
     screenshot. */
  vertical-align: 0.06em;
  background: currentColor;
  /* 0.7, not 0.5. At 1px wide and ~4px tall, half-opacity was legible on a
     retina panel and nearly invisible at 1x. It still reads as subordinate to
     the text, which is the point of a divider. */
  opacity: 0.7;
}

.sub__meta {
  margin-top: 10px;
  font-size: 9.5px;
  line-height: 1.6;
  text-transform: none;      /* dates and 7am PT do not survive uppercasing */
  letter-spacing: 0.06em;
}
.sub__rows {
  display: flex;
  flex-direction: column;
  margin-top: 12px;
  border-top: 1px solid var(--card-line);
}
.sub__rows span {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 0 0;
  font-size: 12.5px;
  color: var(--dim);
}
.sub__rows span svg { flex: none; color: var(--faint); }
.sub__chev { margin-left: auto; }

.post { margin-top: 14px; }
.post__title {
  font-size: 15.5px;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.3;
}
.post__dek {
  margin-top: 8px;
  color: var(--dim);
  font-size: 13px;
  line-height: 1.6;
}
/* NO `gap` HERE. It was 8px, meant only to hold the calendar icon off the text,
   but text nodes in a flex container become anonymous flex items, so once
   .meta__sep landed here it collected that 8px on EVERY side on top of its own
   0.62em margins: 13.88px per side against .sub__meta's 5.88px, which is what
   Isaac spotted (2026-07-31, "way too wide and not in line with spacing for
   other sections"). The icon's spacing is now its own margin, so the separator
   is governed only by .meta__sep and matches .sub__meta exactly.

   Second time this exact flex-anonymous-item trap has bitten on this card;
   .rec__blockhead's justify-content did the same thing. If a separator ever
   looks wrong inside a flex row, check for `gap` before anything else. */
.post__meta {
  display: flex;
  align-items: center;
  margin-top: 11px;
  font-size: 9.5px;
  text-transform: none;      /* same reason as .sub__meta */
  letter-spacing: 0.06em;
}
.post__meta svg { flex: none; margin-right: 8px; }

/* ---------- record card ----------
   A mini of the real Deal Record page demo: title + tag, mono-labelled fact
   cells, a pricing block with the as-heard quote, ownership story, status.
   Hidden until the capture finishes, then rows land one by one. */
.card--record {
  width: 100%;
  /* bottom padding tighter than the sides so the card hugs the ownership
     story block now that Continue lives outside it */
  padding: clamp(22px, 2.8vw, 32px) clamp(22px, 2.8vw, 32px) 18px;
  /* the float, matched to the live site's deal-record demo. The stage is
     pure #000, so a dark drop shadow alone is invisible; the lift comes from
     a wide cool halo behind the card plus a hairline top highlight, with the
     drop shadow only grounding the bottom edge. */
  border-color: rgba(155, 163, 185, 0.20);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 0 110px rgba(158, 168, 198, 0.11),
    0 34px 70px -30px rgba(0, 0, 0, 0.9);
  opacity: 0;
  transform: translateX(14px);
  transition: opacity 600ms ease, transform 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
  visibility: hidden;
}
.card--record.show { opacity: 1; transform: none; visibility: visible; }

.rec__item {
  opacity: 0;
  transform: translateY(7px);
  transition: opacity 460ms ease, transform 460ms ease;
}
.rec__item.in { opacity: 1; transform: none; }

.rec__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--card-line);
}
.rec__title {
  font-size: clamp(22px, 2vw, 27px);
  letter-spacing: -0.02em;
  font-variation-settings: "wght" 500;
  line-height: 1.2;
}
.rec__sub { color: var(--faint); font-size: 12.5px; margin-top: 5px; }
/* The DEAL RECORD / DEAL BRAIN pill. Isaac, 2026-07-31: the Deal brain one
   "looks a little fuzzy, and it could be more crisp." Unlike .klabel above this
   was NOT a contrast problem, since --white already puts it at 17.4:1. It was
   the face and the weight: SF Mono at 400 with 1.6px of tracking, the only
   400-weight thing on a card where everything else is Strichpunkt 500.

   THE WHITE TREATMENT IS NOW THE ONLY TREATMENT (Isaac, 2026-07-31): he asked
   for page 1's DEAL RECORD pill to match page 2's DEAL BRAIN pill. Those were
   the only two pills of this class on the site and the sole difference between
   them was colour, so `.rec__tag--white` was folded in here and the modifier
   deleted rather than left as a class that every instance carries. If a dimmer
   pill is ever wanted, add a --dim modifier then; do not resurrect --white.

   The border rgba is --ink at 28%. Note it is NOT var(--card-line): the pill
   sits on a card that already uses that value, so it needs its own brighter
   hairline to read as a raised object rather than an inset one.

   Beats .mono on source order, same as .klabel. */
.rec__tag {
  border: 1px solid rgba(243, 244, 245, 0.28);
  border-radius: 999px;
  padding: 6px 11px;
  white-space: nowrap;
  color: var(--ink);
  font-family: var(--label);
  font-size: 10.5px;
  font-variation-settings: "wght" 600;
  letter-spacing: 0.08em;
}

/* call-for-offers chip, matched to the real Deal Record page UI: filled
   surface, calendar icon + mono caps label in grey, date in the sans face,
   white, normal case */
.rec__cfo {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 14px;
  padding: 8px 14px 8px 12px;
  background: #15161A;
  border: 1px solid rgba(155, 163, 185, 0.12);
  border-radius: 999px;
  color: var(--faint);
}
.rec__cfo b {
  color: var(--ink);
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: -0.005em;
}
.rec__cfo svg { flex: none; }

.rec__kicker { margin: 16px 0 10px; }
.rec__facts {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  border: 1px solid var(--card-line);
  border-radius: 12px;
  overflow: hidden;
}
.rec__facts div {
  padding: 10px 13px 11px;
  border-right: 1px solid var(--card-line);
  border-bottom: 1px solid var(--card-line);
}
.rec__facts div:nth-child(2n) { border-right: 0; }
.rec__facts div:nth-child(n+3) { border-bottom: 0; }
.rec__facts span { display: block; margin-bottom: 3px; }
.rec__facts b { font-size: 14.5px; font-weight: 500; letter-spacing: -0.01em; }

.rec__block {
  border: 1px solid var(--card-line);
  border-radius: 12px;
  margin-top: 12px;
  overflow: hidden;
}
/* `justify-content: space-between` was dropped in the 2026-07-31 cut of the
   corner numbers. It existed only to push the <i>02</i> / <i>03</i> badge to
   the right edge, and once the badges went it turned harmful: text nodes inside
   a flex container become ANONYMOUS FLEX ITEMS, so "Ownership | story" was
   three items and space-between threw them to the far corners with a ~161px
   gap either side of the divider.

   `display: flex` is kept as the minimal diff. Block also fixes the spreading
   and measured identically, so either works; flex just changes less. Verified
   against production in matched iframes at 1440: blockhead heights 34.5 both,
   record card 624.6 both, Continue row y and right edge unchanged. */
.rec__blockhead {
  display: flex;
  padding: 9px 13px;
  border-bottom: 1px solid var(--card-line);
  background: rgba(155, 163, 185, 0.04);
}
/* The divider in "Ownership | story". `.mono` puts 0.16em of tracking on this
   label, and the plain spaces that used to sit either side of the pipe
   inherited it, so the divider floated in a gap visibly wider than the letter
   gaps around it (Isaac, 2026-07-31). Spaces removed from the markup, gap set
   here instead.

   ASYMMETRIC ON PURPOSE. CSS adds tracking AFTER each character, so the "p"
   ending "Ownership" already contributes 1.6px of space before the pipe, while
   the pipe with its own tracking zeroed contributes none after it. Equal
   margins would therefore look lopsided; margin-right carries the extra 1.6px
   so the two optical gaps match. Re-derive both values if .mono's font-size or
   letter-spacing ever changes. */
.rec__pipe { letter-spacing: 0; margin: 0 0.36em 0 0.2em; }
.rec__pricing {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  padding: 12px 13px 10px;
}
.rec__pricing span.mono { display: block; margin-bottom: 5px; }
.rec__big {
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.02em;
  display: block;
  line-height: 1.1;
}
.rec__under { display: block; color: var(--faint); font-size: 12px; margin-top: 3px; }
/* empty-value bar drawn with CSS, never with a dash character */
.rec__bar {
  display: block;
  width: 26px;
  height: 3px;
  border-radius: 2px;
  background: #2A2C33;
  margin: 11px 0 10px;
}
.rec__quote {
  padding: 9px 13px 11px;
  border-top: 1px solid var(--card-line);
  color: var(--faint);
  font-size: 12.5px;
  font-style: italic;
}
.rec__story { padding: 11px 13px 13px; color: var(--dim); font-size: 13.5px; }

/* action, OUTSIDE the card, hanging on the demo's bottom right corner:
   label in the ink face, arrow in brand purple */
.rec__continue {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
  padding-right: 6px;
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--ink);
  visibility: hidden;
}
.rec__continue.in { visibility: visible; }

/* page 2 pairs Back with Continue in one row, same corner. Back mirrors
   Continue's grammar: arrow on the left, pointing home. */
.rec__nav {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 26px;
  margin-top: 14px;
}
/* Continue keeps its own padding-right so its border box matches page 1's
   exactly and the view transition carries it without a nudge */
.rec__nav .rec__continue { margin-top: 0; }
.rec__back {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--dim);
  visibility: hidden;
  transition: color 160ms ease;
}
.rec__back.in { visibility: visible; }
.rec__back:hover { color: var(--ink); }
.rec__back span {
  color: var(--purple-soft);
  transition: transform 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.rec__back:hover span { transform: translateX(-4px); }
.rec__continue span {
  color: var(--purple-soft);
  transition: transform 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.rec__continue:hover span { transform: translateX(4px); }

/* ---------- pricing: minimal two-card band ---------- */
.pricing {
  padding: clamp(90px, 13vh, 150px) clamp(20px, 4vw, 56px) clamp(80px, 11vh, 130px);
  text-align: center;
}
/* Brought down from clamp(40px, 5.4vw, 72px) on 2026-07-30 (Isaac). At 72 it
   was the largest type anywhere in the flow, 2.2x the stage's own h1
   (clamp(25px, 2.5vw, 33px)), so a secondary band was out-shouting the page
   headline. 44 still reads as a section head without inverting that. */
.pricing__title {
  font-size: clamp(30px, 3.4vw, 44px);
  line-height: 1.2;
  letter-spacing: -0.02em;   /* deck: headers track -2% */
  font-variation-settings: "wght" 500;
}
.pricing__cards {
  margin: clamp(40px, 6vh, 64px) auto 0;
  max-width: 900px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(18px, 2.4vw, 30px);
  text-align: left;
}
.price {
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: 18px;
  padding: clamp(24px, 2.6vw, 34px);
  display: flex;
  flex-direction: column;
}
/* margin-top was 14px to clear the PLATFORM / SERVICES eyebrow above it.
   Both eyebrows were cut 2026-07-30, so h3 is now the card's first child and
   that margin was pure dead space at the top of the card. */
.price h3 {
  font-size: clamp(19px, 1.6vw, 23px);
  letter-spacing: -0.015em;
  font-variation-settings: "wght" 500;
}
.price__lead { margin-top: 8px; color: var(--dim); font-size: 15px; }
.price__body {
  margin-top: 12px;
  color: var(--faint);
  font-size: 14px;
  line-height: 1.6;
  flex: 1;
}
.price__btn {
  margin-top: 24px;
  display: block;
  text-align: center;
  padding: 13px 18px;
  border-radius: 999px;
  background: #17181C;
  border: 1px solid rgba(155, 163, 185, 0.14);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.005em;
  transition: background 180ms ease, border-color 180ms ease;
}
.price__btn:hover { background: #1D1E23; border-color: rgba(155, 163, 185, 0.26); }

/* ---------- footer ---------- */
.footer {
  border-top: 1px solid var(--card-line);
  padding: clamp(48px, 7vh, 72px) clamp(20px, 4vw, 56px) clamp(56px, 8vh, 84px);
}
.footer__inner {
  max-width: 1240px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  gap: clamp(36px, 5vw, 80px);
  flex-wrap: wrap;
}
.footer__brand img { display: block; }
/* Strichpunkt, not mono, and legible (Isaac 2026-07-30). This line had the
   same faults the fig labels had: 10px --mono at 0.16em in --faint, which is
   small, thin, loosely spaced and only 3.6:1 on black. `font-family:
   inherit` rather than naming a stack, because body IS Strichpunkt and the
   deck's rule is "our typeface across every surface"; the only reason an
   override is needed at all is that the .mono class on this element sets
   --mono first. Scoped to the footer: .mono itself stays as it is, because
   the record cards and the workbook formula bar mirror the real app's own
   font-mono and should keep it. */
.footer__brand .mono {
  margin-top: 16px;
  font-family: inherit;
  font-size: 11.5px;
  font-variation-settings: "wght" 450;
  letter-spacing: 0.1em;
  color: var(--dim);
}
/* THREE columns since 2026-07-30, not four: Isaac cut Product entirely and
   reduced Platform to a single Book a demo, leaving Platform / Company /
   Connect. Page navigation now lives only in the pill. */
.footer__cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(120px, 1fr));
  gap: clamp(28px, 4vw, 64px);
}
.footer__cols b {
  display: block;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  margin-bottom: 16px;
}
.footer__cols a {
  display: block;
  color: var(--dim);
  font-size: 14px;
  padding: 5px 0;
  transition: color 160ms ease;
}
.footer__cols a:hover { color: var(--ink); }

/* the legal bar, mirroring the live site's bottom row. --dim not --faint:
   these are links and small caps, and --faint is only 3.6:1 on black */
.footer__legal {
  max-width: 1240px;
  margin: clamp(40px, 6vh, 64px) auto 0;
  padding-top: 22px;
  border-top: 1px solid var(--card-line);
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
}
/* The copyright, moved down here from under the lockup 2026-07-30 and set
   first in the bar. Same type as the links beside it so the row reads as one
   line, but wght 450 against their 500: it is a statement, not something you
   can click, and that is the only cue distinguishing it. */
.footer__copy {
  font-size: 11.5px;
  font-variation-settings: "wght" 450;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
}

/* no font-family: inherits Strichpunkt from body, same reasoning as above.
   Tracking comes down with the switch, since a grotesque's caps do not need
   the 0.16em a mono was carrying. */
.footer__legal a {
  font-size: 11.5px;
  font-variation-settings: "wght" 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
  transition: color 160ms ease;
}
.footer__legal a:hover { color: var(--ink); }

/* ---------- legal pages ----------
   Privacy / Terms / Security / Accessibility / Cookies. The COPY is lifted
   verbatim from the live site (`Website Build/public/*.html`) because it is
   legal text and must not be paraphrased; only the shell is v7's. The live
   pages ship their own light-theme `legal.css` with Inter and JetBrains
   Mono, none of which belongs here, so none of it came across.
   A single measure column, no fig rail, no pricing. */
.legal {
  max-width: 720px;
  margin: 0 auto;
  padding: clamp(120px, 16vh, 160px) clamp(20px, 4vw, 56px) clamp(80px, 12vh, 130px);
}
.legal__back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 30px;
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--dim);
  transition: color 160ms ease;
}
.legal__back span { color: var(--purple-soft); transition: transform 200ms cubic-bezier(0.2, 0.8, 0.2, 1); }
.legal__back:hover { color: var(--ink); }
.legal__back:hover span { transform: translateX(-4px); }

.legal h1 {
  font-size: clamp(30px, 3.4vw, 44px);
  line-height: 1.2;
  letter-spacing: -0.02em;
  font-variation-settings: "wght" 500;
}
.legal-updated {
  margin-top: 14px;
  font-family: var(--label);   /* was var(--mono); see the .mono note */
  font-size: 10.5px;
  font-variation-settings: "wght" 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
}
.legal-lead {
  margin-top: 26px;
  font-size: clamp(15px, 1.3vw, 16.5px);
  line-height: 1.68;
  color: var(--ink);
}
.legal h2 {
  margin-top: 46px;
  font-size: clamp(17px, 1.5vw, 20px);
  letter-spacing: -0.015em;
  font-variation-settings: "wght" 500;
}
.legal h3 {
  margin-top: 26px;
  font-size: 15.5px;
  font-variation-settings: "wght" 500;
  letter-spacing: -0.01em;
}
.legal p {
  margin-top: 14px;
  color: var(--dim);
  font-size: 15px;
  line-height: 1.72;
}
.legal ul { margin-top: 14px; padding-left: 19px; }
.legal li {
  margin-top: 7px;
  color: var(--dim);
  font-size: 15px;
  line-height: 1.72;
}
.legal li::marker { color: var(--faint); }
/* purple is the brand's action colour and a link is an action */
.legal a { color: var(--purple-soft); }
.legal a:hover { text-decoration: underline; }

/* ==========================================================================
   USE-CASE PAGES (currently just /underwriting), 2026-07-31.

   Layout from a reference Isaac supplied: hero and a progression ladder in a
   left column, demo to the right. The right column is RESERVED AND EMPTY until
   he specifies the demo, which is why the page reads left-weighted at desktop.
   ========================================================================== */
.uc {
  display: grid;
  /* Same shape as .stage's two columns, so a use-case page and a walk page sit
     on the same underlying measure. The left column is a hair wider than the
     walk's 480 because it carries a display-scale h1 rather than a demo card. */
  /* Right column widened 530 -> 680 on 2026-07-31. Isaac wanted the "Broker
     believes" card moved right until it sat behind nothing, but it already
     ended 3px from the old column's edge: ownership's right edge is at 351, so
     any card clearing it needs ~680px to still fit. The grid had 216px of
     unused slack at 1440, and spending it also closes the dead gap that used to
     sit right of the demo. The whole centred grid shifts ~75px left as a
     result, which is the visible cost. */
  grid-template-columns: minmax(0, 560px) minmax(0, 680px);
  justify-content: center;
  gap: 22px;
  padding: clamp(150px, 18vh, 200px) clamp(20px, 4vw, 56px) clamp(90px, 12vh, 140px);
}
.uc__title {
  /* Reuses the display size already established by .pricing__title and
     .legal h1 rather than inventing a third scale. Deliberately larger than the
     walk pages' 33px h1: there it shares a row with a demo card, here it is the
     page's whole opening statement. */
  font-size: clamp(30px, 3.4vw, 44px);
  line-height: 1.2;
  letter-spacing: -0.02em;
  font-variation-settings: "wght" 500;
  max-width: 15em;
}

/* ---------- the progression ladder ----------
   A rail down the left gutter with a marker per stage. Each <li> owns its own
   1px SEGMENT of that rail rather than the list drawing one continuous line,
   which means the active segment can brighten without any JS measuring
   heights, and the rail automatically fits however many stages exist. */
.lad {
  list-style: none;
  margin-top: clamp(38px, 5vh, 56px);
}
.lad__item {
  position: relative;
  padding-left: 26px;
}
/* the rail segment for this stage */
.lad__item::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--card-line);
  transition: background 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* the first and last segments stop at the first and last markers, so the rail
   does not overhang the ladder at either end */
.lad__item:first-child::before { top: 15px; }
.lad__item:last-child::before { bottom: auto; height: 15px; }
.lad__item.is-on::before { background: rgba(155, 163, 185, 0.42); }

.lad__row {
  display: flex;
  align-items: baseline;
  gap: 0;
  width: 100%;
  padding: 11px 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  font-size: 16px;
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: var(--dim);
  transition: color 220ms ease;
}
.lad__item.is-on .lad__row { color: var(--ink); font-variation-settings: "wght" 500; }
.lad__row:hover { color: var(--ink); }
.lad__row:focus-visible {
  outline: 1px solid var(--purple-soft);
  outline-offset: 6px;
  border-radius: 4px;
}

/* A DOT since 2026-07-31. It began as a square (reasoning then: the fig rail
   already owned rings and dots, and a square matched the panels/hairlines),
   but Isaac overrode it: "our entire site is more rounded corners and edges...
   not really any hard square features." He is right about the site at large:
   the pill, every card, every button and bubble is rounded, and these five
   7px squares were the only hard corners on the page. Everything else about
   the marker (size, colours, rail seat, first-line alignment) is unchanged. */
.lad__mark {
  position: absolute;
  left: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-top: 7px;
  border: 1px solid rgba(155, 163, 185, 0.42);
  /* var(--black) is the page background, so the hollow marker punches a clean
     hole in the rail behind it rather than letting the line show through */
  background: var(--black);
  transition: background 400ms cubic-bezier(0.4, 0, 0.2, 1),
              border-color 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* Active marker takes full --purple, not --purple-soft. The soft purple exists
   for TEXT contrast (#5E37FB is 3.42:1 on black); this is a 7px shape, so it
   takes the brand value, matching how .wb__tab.on and .ktabs .on draw their
   purple underlines. The LABEL stays white rather than going purple, which is
   also those two rules' pattern: white text, purple marker. */
.lad__item.is-on .lad__mark {
  background: var(--purple);
  border-color: var(--purple);
}

/* Same 0fr -> 1fr mechanism and the same 400ms curve as the pill and its Use
   cases expander, so every disclosure on this site opens on one clock. */
.lad__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
.lad__row[aria-expanded="true"] + .lad__panel { grid-template-rows: 1fr; }
.lad__panelinner {
  min-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: opacity 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
.lad__row[aria-expanded="true"] + .lad__panel .lad__panelinner { opacity: 1; }
.lad__panelinner p {
  padding-bottom: 16px;
  max-width: 34em;
  font-size: 14.5px;
  line-height: 1.62;
  color: var(--dim);
}

/* ---------- the capture demo (right column of /underwriting) ----------
   "Capturing" and a trailing flow, the real app icon, then scattered bubbles
   wired up into it. Height is fixed so the wires have a stable box to be
   measured against; the bubbles carry --x/--y/--w so all their geometry lives
   in the markup where it can be nudged without hunting through this file. */
/* Stage 1's demo, not the page's: it shows only while "Capture what forms
   miss" is the open ladder stage (Isaac, 2026-07-31). The other stages will
   earn their own demos later; until then their right column is dark. Hidden by
   opacity so the column's box never reflows, on the site's one 400ms clock;
   the JS also sets `inert` so the bubbles leave the accessibility tree when
   they leave the screen. */
.cap {
  position: relative;
  /* The scatter is AUTHORED for a 680px canvas: every card's --x/--y/--w was
     tuned against it, and the composition only works because no two cards
     intersect at those exact numbers. So the canvas is a fixed 680 and
     underwriting.js SCALES it down whenever the column is narrower, rather
     than letting the cards reflow out of the column and off the page. Without
     this, 1100px viewports pushed cards 27px past the viewport edge and broke
     the site's zero-horizontal-scroll invariant. transform-origin is top left
     so it shrinks toward the column's own corner; the JS keeps `height` in step
     with the scale so no dead space opens under it. */
  width: 680px;
  height: 620px;   /* the spread scatter ends ~575px down; JS scales this */
  transform-origin: top left;
  transition: opacity 400ms cubic-bezier(0.4, 0, 0.2, 1);
}
.cap.is-off { opacity: 0; }
/* "CAPTURING", centred above the icon, breathing slowly: Isaac wants it to
   read as a live recording. Opacity-only, on a slow clock, which is the same
   licence as page 3's chat mark ("pulses, it does not spin"): a considered
   pulse that MEANS something is not the blinking chrome the stage bans. */
.cap__now {
  position: absolute;
  top: 28px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 12px;
  font-variation-settings: "wght" 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  animation: capBreathe 2.8s ease-in-out infinite;
}
@keyframes capBreathe { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }

.cap__app {
  position: absolute;
  top: 74px;
  left: 50%;
  transform: translateX(-50%);
}
/* Raised off the page, not flat against it. On a #000 ground a dark drop
   shadow is invisible, so the float is built from the cues that DO read on
   black: a rim light along the top edge where light would strike, a darker
   bottom edge, and a soft backlight pooled behind it. The backlight is the
   "lit stage" glow the deployed site's dark mode already used behind its first
   demo, so this is house vocabulary rather than a new effect. */
/* Pushed harder 2026-07-31 ("more dramatic, I really wanna feel that float"):
   the pool doubled in reach and brightness, and a second tighter halo hugs the
   icon so the glow reads as coming OFF it rather than parked behind it. Still
   the neutral grey light family: purple stays reserved for action. */
/* both glow layers live on ::before (backgrounds stack), freeing ::after for
   the specular ring below */
.cap__app::before {
  content: "";
  position: absolute;
  inset: -90px;
  border-radius: 50%;
  background:
    radial-gradient(circle,
      rgba(200, 208, 226, 0.22) 0%,
      transparent 34%),
    radial-gradient(circle,
      rgba(200, 208, 226, 0.16) 0%,
      rgba(155, 163, 185, 0.07) 40%,
      transparent 68%);
  pointer-events: none;
}
/* THE TESTFLIGHT RING (Isaac, 2026-07-31: "this nice little light ring around
   it... that's really the effect that I want"). A 1px ring that is BRIGHTER
   along the top edge and falls off down the sides, which is what makes it read
   as dimensional light rather than an outline. A border cannot do this, since
   border-color takes one value for the whole perimeter; this is the 1px
   gradient ring the glass-pill exploration documented, drawn as a gradient
   clipped to a ring by a padding-box/border-box mask pair composited to
   exclude. Both mask spellings are needed: Safari only honours the -webkit-
   pair, and an engine that drops one keeps the other. */
.cap__app::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: 22.37%;
  padding: 1px;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.45) 0%,
    rgba(255, 255, 255, 0.13) 38%,
    rgba(255, 255, 255, 0.05) 72%,
    rgba(255, 255, 255, 0.16) 100%);   /* faint bounce light along the base */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
.cap__app img {
  position: relative;
  z-index: 1;
  display: block;
  width: 88px;
  height: 88px;
  /* 22.37% is the standard approximation of iOS's squircle mask, which the
     source asset does not carry because the OS applies it at render time.
     The old flat border is gone: the ring above replaces it, and doubling
     them up read as a double outline. */
  border-radius: 22.37%;
  box-shadow:
    inset 0 -1px 0 rgba(0, 0, 0, 0.65),         /* grounded lower edge */
    0 24px 54px rgba(0, 0, 0, 0.7);             /* carves it out of the glow behind */
}

/* wires sit UNDER the bubbles, so a bubble always reads as the origin of its
   own line rather than being crossed by it */
.cap__wires { position: absolute; inset: 0; z-index: 0; }
.cap__wires i {
  position: absolute;
  height: 2px;
  transform-origin: left center;
  background-image: repeating-linear-gradient(90deg,
    rgba(200, 208, 226, 0.4) 0 2.5px,
    transparent 2.5px 9px);
  animation: bridgeFlow 0.8s linear infinite;
  /* the dots emerge from the bubble and dissolve as they reach the app, so the
     convergence reads as arrival rather than as six lines hitting a wall */
  mask-image: linear-gradient(90deg, transparent 0%, #000 22%, transparent 96%);
  opacity: 0;
  transition: opacity 600ms ease;
}
.cap__wires i.in { opacity: 1; }

.cap__bubbles { position: relative; z-index: 1; }
/* Floating black panels, restyled 2026-07-31 to a Dovetail reference Isaac
   supplied (their stacked tag/settings panels). Was .chat__you's grey #22242A;
   now a near-black surface off the frozen palette, and the float is carried by
   a top rim light plus a deep shadow that only becomes VISIBLE where one card
   overlaps another or a wire passes beneath, exactly how the reference reads.
   That is also why the scatter keeps cards overlapping: no overlap, no float. */
/* Second pass 2026-07-31: text up 12.5 -> 13.5 and to --ink ("easier to read
   ... white so it stands out more"), and the cards spread apart rather than
   cascaded. Spreading removed most of the card-on-card crossings the old
   shadow needed to be seen, so each card now carries a faint halo of the same
   grey light as the icon's pool: on a #000 ground that halo IS the float. */
.cap__b {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  margin: 0;
  background: #101114;
  border: 1px solid rgba(155, 163, 185, 0.2);
  border-radius: 14px;
  padding: 10px 16px;
  font-size: 13.5px;
  line-height: 1.5;
  letter-spacing: -0.005em;
  /* Medium 500, not the body's 400: 13.5px light-on-dark at 400 is where
     Strichpunkt goes soft, and Isaac asked for "crispy... sharp" (2026-07-31).
     Not 600: the deck's SemiBold guidance is for LABELS, and across whole
     sentences it reads shouty (same call as the .mono note). Matches the
     ladder's active-row weight, so the demo and the ladder agree. */
  font-variation-settings: "wght" 500;
  color: var(--ink);
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.09),   /* rim light */
    0 14px 34px rgba(0, 0, 0, 0.7),             /* depth where anything crosses */
    0 0 44px rgba(155, 163, 185, 0.09);         /* the halo that reads on black */
}

/* ---------- stage 2's demo: documents in, Giles's read out ----------
   Built 2026-07-31 to Isaac's spec (Dovetail-inspired doc cards). Shares the
   .cap canvas contract: authored at 680x620, scaled by fitCanvas, shown only
   while its ladder stage is open. */

/* the second and later demos overlay the first, which stays in flow and
   carries the column's height. .uc__demo MUST be the positioned ancestor:
   without it the layer resolves against the page and renders on top of the
   hero, which is exactly what the first screenshot of this build showed. */
.uc__demo { position: relative; }
.cap--layer { position: absolute; top: 0; left: 0; }

/* a document card: floating black like .cap__b, but panel-shaped with a
   header bar, straight off the Dovetail reference */
.doc {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  background: #101114;
  border: 1px solid rgba(155, 163, 185, 0.2);
  border-radius: 12px;
  overflow: hidden;
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.09),
    0 14px 34px rgba(0, 0, 0, 0.7),
    0 0 44px rgba(155, 163, 185, 0.09);
}
.doc__head {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 8px 11px;
  border-bottom: 1px solid rgba(155, 163, 185, 0.13);
  background: rgba(155, 163, 185, 0.05);
  font-size: 10px;
  font-variation-settings: "wght" 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink);
}
/* the neutral file glyph is a PLACEHOLDER until Isaac's Excel/PDF SVGs land */
.doc__head svg { flex: none; color: var(--dim); }
/* height-only: excel.svg is near-square but pdf.svg is portrait (75x93), and
   a forced 13x13 box would squash it */
.doc__head img { flex: none; display: block; height: 13px; width: auto; }
.doc__body { padding: 10px 11px 11px; }
.doc__body svg { display: block; width: 100%; height: auto; }

/* Giles's read, under the app. The message text is page 3's .chat__giles
   voice; the input row IS page 3's .chat__bar, reused wholesale. */
.pchat {
  position: absolute;
  left: 100px;
  top: 396px;
  width: 480px;
  background: #101114;
  border: 1px solid rgba(155, 163, 185, 0.2);
  border-radius: 16px;
  padding: 16px 18px 15px;
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.09),
    0 14px 34px rgba(0, 0, 0, 0.7),
    0 0 44px rgba(155, 163, 185, 0.09);
}
.pchat__msg { display: flex; align-items: flex-start; gap: 11px; }
.pchat__msg img { flex: none; display: block; margin-top: 2px; }
.pchat__msg p {
  margin: 0;
  color: var(--dim);
  font-size: 13.5px;
  line-height: 1.62;
}
.pchat__msg b { color: var(--ink); font-weight: 600; }

/* ---------- stage 3's demo: floor plan in, market survey out ----------
   Built 2026-07-31. The plan card is TILTED with a cursor riding its corner:
   a held mid-drag pose, not an animation, per the stage's play-once-and-hold
   rule. Same floating-black family as the other demo cards. */
.fplan {
  position: absolute;
  left: 8px;
  top: 172px;
  width: 232px;
  padding: 12px;
  background: #101114;
  border: 1px solid rgba(155, 163, 185, 0.2);
  border-radius: 14px;
  /* mid-drag: lifted and slightly turned toward the app */
  transform: rotate(-4deg);
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.09),
    0 18px 40px rgba(0, 0, 0, 0.72),
    0 0 44px rgba(155, 163, 185, 0.09);
}
/* first-of-type ONLY: the cursor is also a direct-child svg, and this rule at
   (0,1,1) outguns .fplan__cursor at (0,1,0), so an unscoped version inflates
   the cursor to the card's full width regardless of what that class says */
.fplan > svg:first-of-type { display: block; width: 100%; height: auto; }
.fplan__cursor {
  position: absolute;
  /* explicit size: the `.fplan > svg` width:100% rule above also matches this
     sibling svg, which blew the cursor up to the card's full 232px width */
  width: 17px;
  height: 25px;
  right: 26px;
  bottom: 18px;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8));
}

/* the survey the app writes. doc__head is reused for the header bar. */
.msurv {
  position: absolute;
  left: 452px;
  top: 186px;
  width: 226px;
  background: #101114;
  border: 1px solid rgba(155, 163, 185, 0.2);
  border-radius: 12px;
  overflow: hidden;
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.09),
    0 14px 34px rgba(0, 0, 0, 0.7),
    0 0 44px rgba(155, 163, 185, 0.09);
}
.msurv__body { padding: 9px 12px 12px; }
.msurv__cols,
.msurv__row {
  display: flex;
  margin: 0;
  padding: 5px 0;
}
/* fixed column tracks so the three columns align down the panel */
.msurv__cols span:nth-child(1), .msurv__row span:nth-child(1) { flex: 1 1 auto; min-width: 0; }
.msurv__cols span:nth-child(2), .msurv__row span:nth-child(2) { flex: 0 0 52px; text-align: right; }
.msurv__cols span:nth-child(3), .msurv__row span:nth-child(3) { flex: 0 0 40px; text-align: right; }
.msurv__cols {
  font-size: 9px;
  font-variation-settings: "wght" 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  border-bottom: 1px solid rgba(155, 163, 185, 0.13);
}
.msurv__row {
  font-size: 11.5px;
  font-variation-settings: "wght" 500;
  color: var(--ink);
  border-bottom: 1px solid rgba(155, 163, 185, 0.08);
}
.msurv__row span:nth-child(2), .msurv__row span:nth-child(3) { color: var(--dim); }
/* the row still being written: bars where values will land */
.msurv__row--ghost { border-bottom: 0; padding-top: 9px; }
.msurv__row--ghost span {
  height: 4px;
  border-radius: 2px;
  background: rgba(200, 208, 226, 0.16);
}
.msurv__row--ghost span:nth-child(1) { margin-right: 22px; }
.msurv__row--ghost span:nth-child(2) { margin-right: 6px; }

/* ---------- stage 4's demo: the big sheet writing itself ----------
   Built 2026-07-31. One large OM-style sheet; its summary lines type
   themselves in whenever the stage is opened (openRow re-adds .run, which
   restarts the one-shot animation), and the app sits on its bottom edge. */
.cmp {
  position: absolute;
  left: 90px;
  top: 18px;
  width: 500px;
  background: #101114;
  border: 1px solid rgba(155, 163, 185, 0.2);
  border-radius: 14px;
  overflow: hidden;
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.09),
    0 18px 40px rgba(0, 0, 0, 0.72),
    0 0 44px rgba(155, 163, 185, 0.09);
}
.cmp__body { padding: 18px 20px 26px; }
.cmp__hero { display: flex; gap: 18px; }
.cmp__photo {
  flex: none;
  width: 170px;
  height: 118px;
  border-radius: 6px;
  background: rgba(155, 163, 185, 0.10);
  border: 1px solid rgba(155, 163, 185, 0.26);
}
.cmp__col { flex: 1; display: flex; flex-direction: column; gap: 16px; padding-top: 8px; }
.cmp__col span {
  height: 5px;
  border-radius: 2.5px;
  background: rgba(200, 208, 226, 0.16);
}
/* the lines that write themselves: brighter than the static ones, growing
   from the left on a slow stagger. One shot, held (forwards); .run is the
   trigger so every visit to the stage replays it. */
.cmp__typed { margin-top: 26px; display: flex; flex-direction: column; gap: 17px; }
.cmp__typed span {
  width: var(--w);
  height: 5px;
  border-radius: 2.5px;
  background: rgba(200, 208, 226, 0.30);
  transform: scaleX(0);
  transform-origin: left center;
}
.cap.run .cmp__typed span {
  animation: cmpType 1.6s cubic-bezier(0.2, 0.8, 0.2, 1) var(--d) forwards;
}
@keyframes cmpType { to { transform: scaleX(1); } }

/* the thinking line reuses page 3's .chat__status, whose 16px top margin was
   designed to sit under the giles paragraph; flush inside the panel instead */
.pchat > .chat__status { margin-top: 2px; }
/* Isaac, 2026-07-31: the input bar read "fat" inside this panel (page 3's
   11px padding rendered it 51px tall here). Slimmed to ~38px IN THE PANEL
   ONLY; page 3's own bar is untouched. */
.pchat .chat__bar { padding: 6px 8px 6px 13px; margin-top: 12px; }

/* ---------- stage 5's demo: page 3's own workbook and chat ----------
   The markup is execute.html's verbatim; only the COMPOSITION is this page's.
   Every rule is scoped under #uwc so page 3 is untouched. */
#uwc .wb {
  position: absolute;
  /* widened one data column 2026-07-31 (Isaac): the Jun column page 3 shows
     came back, so left moved 270 -> 212 and width 410 -> 468, still flush to
     the canvas's right edge */
  left: 212px;
  top: 20px;
  width: 468px;
  /* base .wb carries height 536 (page 3's no-JS fallback) and overflow
     hidden, so the grid clips mid-sheet exactly as it does there */
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.07),
    0 18px 40px rgba(0, 0, 0, 0.72);
}
/* the same trims the real page makes under 980px, because the sheet is 410
   wide here against page 3's 700: no side pane, no Jun column, and the three
   middle ribbon tabs go so the add-in never clips */
#uwc .wb__pane { display: none; }
#uwc .wb__tab:nth-child(3), #uwc .wb__tab:nth-child(4), #uwc .wb__tab:nth-child(5) { display: none; }

#uwc .chat {
  position: absolute;
  left: 0;
  top: 140px;
  width: 411px;   /* +10% twice over, Isaac 2026-07-31: 340 -> 374 -> 411 */
  margin-top: 0;
  z-index: 2;
  /* it floats OVER the sheet's left edge, so it needs the demo family's
     lift: deep shadow where it crosses the workbook, faint halo elsewhere */
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.09),
    0 18px 40px rgba(0, 0, 0, 0.75),
    0 0 44px rgba(155, 163, 185, 0.09);
}

/* ---------- /document-creation's demo: sources into a drafted doc ----------
   Built 2026-08-01 to Isaac's spec from a second Dovetail reference (their
   "From noise to knowledge" section): five source cards clustered above,
   wired down into a "Generating doc" node, and beneath it the document being
   written. Shares the .cap canvas contract (680x620, scaled by fitCanvas) and
   reuses .doc for the source cards and cmpType for the typed lines. */

/* The convergence node is the APP (Isaac, 2026-08-01: "our black gravity
   core app that has the same floating effect", replacing the reference's
   bare purple dot). Same .cap__app anatomy as every other demo, at 54px:
   the icon centres on the draft's own centreline (outdoc spans 116..568,
   centre 342), so the node reads as feeding the document beneath it. The
   glow pool is tightened to the smaller icon, and the 88px icon's heavy
   drop shadow is eased so a 54px icon does not sit in an outsized pool of
   black. */
.cap__app--gen { top: 300px; left: 342px; }
.cap__app--gen::before { inset: -56px; }
.cap__app--gen img {
  width: 54px;
  height: 54px;
  box-shadow:
    inset 0 -1px 0 rgba(0, 0, 0, 0.65),
    0 16px 36px rgba(0, 0, 0, 0.7);
}
/* "GENERATING DOCUMENT", beside the icon, and the WORDS pulse (Isaac; the
   reference pulses its dot instead). capBreathe's clock, the same licence
   as CAPTURING's breathe: work visibly in progress. House label voice
   (.doc__head's), not a resurrected mono. Left edge clears the icon's
   centreline (342) by half the icon plus a step: 342 + 27 + 17. */
.gen {
  position: absolute;
  left: 386px;
  top: 321px;
  margin: 0;
  font-size: 10px;
  font-variation-settings: "wght" 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
  animation: capBreathe 2.8s ease-in-out infinite;
}

/* the wordmark card: wsj.svg is the full wordmark (797x68), so it stands
   alone in the head at 9px tall and the title moves into the body */
.doc--wsj .doc__head { padding-top: 10px; padding-bottom: 9px; }
.doc__cap {
  margin: 0 0 8px;
  font-size: 10px;
  font-variation-settings: "wght" 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink);
}
/* the wordmark ships black-on-transparent; invert to the head's own grey so
   it reads on the dark card without recolouring the asset at source */
.doc--wsj .doc__head img { height: 9px; filter: invert(0.82); }

/* the document being drafted. Same floating-black family as .cmp, but it
   DISSOLVES at its bottom edge (the site's Linear-style fade), reading as a
   first page still being written rather than a finished artifact. The mask
   eats the border and shadow with the card, which is exactly how .dfeed's
   nested fades behave. */
.outdoc {
  position: absolute;
  left: 116px;
  top: 396px;
  width: 452px;
  height: 224px;   /* runs to the canvas edge; the fade lands before it */
  background: #101114;
  border: 1px solid rgba(155, 163, 185, 0.2);
  border-radius: 14px;
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.09),
    0 18px 40px rgba(0, 0, 0, 0.72),
    0 0 44px rgba(155, 163, 185, 0.09);
  -webkit-mask-image: linear-gradient(180deg, #000 62%, transparent 97%);
  mask-image: linear-gradient(180deg, #000 62%, transparent 97%);
}
.outdoc__body { padding: 22px 24px; }
.outdoc__title {
  margin: 0;
  font-size: 17px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  font-variation-settings: "wght" 550;
  color: var(--ink);
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(155, 163, 185, 0.13);
}
.outdoc__sect {
  margin: 14px 0 16px;
  font-size: 10px;
  font-variation-settings: "wght" 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}
/* the prose writing itself: stage 4's typed bars verbatim (same 5px bars,
   same cmpType animation, same .run trigger), so the two pages' "the system
   is writing" moments run on one clock */
.outdoc__typed { display: flex; flex-direction: column; gap: 13px; }
.outdoc__typed span {
  width: var(--w);
  height: 5px;
  border-radius: 2.5px;
  background: rgba(200, 208, 226, 0.30);
  transform: scaleX(0);
  transform-origin: left center;
}
.cap.run .outdoc__typed span {
  animation: cmpType 1.6s cubic-bezier(0.2, 0.8, 0.2, 1) var(--d) forwards;
}

/* ---------- /market-analysis's demo: the sources weighed in the open ----------
   Built 2026-08-01 to Isaac's spec: his question upper right (.cap__b), and
   /document-creation's generated-doc surface grown LARGER and higher on the
   canvas, with stage 4's thinking chat floating INSIDE it ("within that
   document cutout"). Only the shell is new; the body reuses .outdoc__* so
   the two generated documents stay one voice, and the chat is .pchat +
   .chat__status wholesale. The fully-opaque chat over the dissolving page
   is stage 5's chat-over-workbook depth trick. */
.mdoc {
  position: absolute;
  left: 0;
  top: 208px;
  width: 520px;
  height: 412px;   /* runs to the canvas edge; the fade lands before it */
  background: #101114;
  border: 1px solid rgba(155, 163, 185, 0.2);
  border-radius: 14px;
  box-shadow:
    inset 0 1px 0 rgba(243, 244, 245, 0.09),
    0 18px 40px rgba(0, 0, 0, 0.72),
    0 0 44px rgba(155, 163, 185, 0.09);
  -webkit-mask-image: linear-gradient(180deg, #000 62%, transparent 97%);
  mask-image: linear-gradient(180deg, #000 62%, transparent 97%);
}

/* ---------- small screens: stack ---------- */
@media (max-width: 980px) {
  .stage {
    grid-template-columns: minmax(0, 530px);
    justify-content: center;
    padding-top: 100px;
    row-gap: 0;
  }
  /* Dissolve the column so its children become direct grid items. Otherwise
     the record, being .col's sibling, stacks ABOVE the message it was
     created from and the cause-and-effect reads backwards. */
  .col { display: contents; }
  .card--capture { order: 0; min-height: 0; }
  .reccol        { order: 1; grid-column: 1; }
  .pitch         { order: 2; margin-top: 26px; }
  /* Page 3 has no capture card, so without this the nav (inside .reccol,
     order 1) stacked ABOVE the header and the page opened on its buttons. */
  .pitch--lead   { order: -1; margin-top: 0; padding-top: 0; }
  /* ---------- THE RAIL ON PHONES: one line you swipe ----------
     SETTLED 2026-07-31. Isaac compared this against the stacked version on his
     own phone and picked it outright ("so much better"). The stacked branch and
     its `?rail=stack` toggle were deleted the same day; do not reintroduce
     stacking as a "simpler" mobile fallback, it was tried and lost.

     Why not stack: the rail is ONE picture, not four. Its whole job is
     accumulation (page 1 has CAPTURE, each Continue adds the next figure and
     draws a dashed flow into it), and you cannot perceive accumulation when
     1.5 figures fit on screen. Stacking also had to delete the flows, since
     there is no line to draw between vertically stacked items, so it paid
     926px on page 4 (26% of the page) AND lost the argument.

     Flex, not the desktop grid, because the four slots are FIXED there so
     figures accumulate in place. Reused here, pages 1-3 would carry 300px of
     empty reserved track per unfilled slot and you could swipe into nothing.
     Flex sizes the track to the figures that actually exist.

     300px slots, not smaller: .fig__label is 12.5 viewBox units against a 470
     viewBox, so it renders at 12.5 * (300/470) = 8.0px, which is desktop's
     8.09px exactly. At 260 it drops to 6.9px and at a 2x2 grid's 165px to
     4.4px. 300 is the floor, and it leaves the next figure peeking ~50px into
     a 390 screen, which is the affordance.

     Full-bleed via negative margins so the strip runs to both screen edges
     while the figures still line up with the 20px content gutter. The next
     figure bleeding off the edge is a stronger cue than one stopping short. */
  .figrow {
    order: 3;
    grid-column: 1;
    display: flex;
    gap: 26px;
    width: calc(100% + 40px);
    margin: clamp(30px, 4vh, 44px) -20px 0;
    padding: 0 20px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
  }
  /* own rule: an unrecognised pseudo-element invalidates its whole selector list */
  .figrow::-webkit-scrollbar { width: 0; height: 0; }
  /* the flex items are .figlink for walked-past figures and a bare .fig for
     the current page's own, so both have to be sized. .figbridge is
     position:absolute and therefore not a flex item, which is what lets the
     flows keep sitting over the track. */
  .figrow > .fig,
  .figrow > .figlink { flex: 0 0 300px; scroll-snap-align: center; }
  .figrow > .figlink > .fig { width: 100%; }
  .figslot       { display: none; }
  .figbridge     { display: block; }   /* one line again, so the flows return */
  .card--brainrec, .card--feedrec { min-height: 0; }  /* Continue follows the content */
  /* .col is display:contents here, so the marquee becomes a direct grid item
     and needs its own order or it lands above the demo it belongs under.
     Same order as .pitch; DOM order breaks the tie and keeps it below. */
  .mq { order: 2; margin-top: 26px; }
  /* the subscription's name and its two pills cannot share a line at 350px */
  .sub__head { flex-direction: column; align-items: flex-start; gap: 9px; }
  .reccol__spacer { display: none; }
  /* page 3: the workbook keeps its shape but loses the side pane, which
     cannot survive a 350px column; the grid alone still reads as a model */
  /* ---------- page 3 drops the workbook entirely on phones ----------
     Isaac, 2026-07-31: "we do not need to see the spreadsheet demo on mobile,
     it's too much." The chat carries the page on its own. This one rule also
     retires three defects at once: .wb__addin overflowed the sheet's right edge
     by 29px and was clipped mid-wordmark, "Page Layout" wrapped to two lines
     so the ribbon stood 50px tall instead of ~34, and the fixed pill landed
     squarely on the Excel tab row with the cut add-in poking out behind it,
     which was the single worst-looking thing on mobile. Saves 548px, taking
     execute.html from 4.04 screens to 3.39.

     KNOWN TRADE, accepted by Isaac: the page's headline is "Underwriting in
     plain language, IN YOUR OWN MODEL", and the workbook was the only proof of
     the second half. On phones that now rests on the EXECUTE figure's
     workstation panel. A stripped ~200px 6-row grid was offered as a middle
     option and not taken.

     The old rules that shrank the workbook to fit (height:auto, hiding
     .wb__pane and the second month column) are deleted rather than left dead
     under a display:none parent. */
  .wb { display: none; }
  .chat { margin-top: 26px; }
  /* ---------- the status line reserves TWO rows ----------
     Giles's status cycles every 2.6s through four strings, and at a 284px text
     budget two of them wrap to a second line while two do not. So the chat
     grew and shrank by 14.5px on a loop, shoving the action row, the rail,
     the pricing band and the footer up and down with it, forever (Isaac,
     2026-07-31: "I just want that chat interface to always stay that size for
     the two rows so it doesn't go up and down").

     Reserve the taller case rather than shorten the copy: the strings are the
     assumptions Giles is actually solving at step 3 and they earn their length.
     3.1em is two lines (line-height is 1.55 at 10.5px = 16.275px, so 32.55px),
     set in em on the span so it tracks the font-size.

     `flex-start`, not the default `center`: with a fixed 2-line box, centring
     would park a ONE-line string in the middle of it, so the text itself would
     still bob 8px between states even though the box held still. Top-aligned,
     the first line never moves and the reserved second line is just black. The
     18px mark against a 16.275px first line sits 0.86px proud, which is why it
     needs no offset. Desktop is untouched: at 530px the widest string fits on
     one line, so there is nothing to reserve and reserving it would add dead
     space and feed a wrong height into sizeWorkbook(). */
  .chat__status { align-items: flex-start; }
  .chat__status .mono { min-height: 3.1em; }
  /* The knowledge entry's title and its timestamp cannot share a row at 390:
     "Added just now" claimed 90.9px of 272, forcing the title onto two lines
     (Isaac, 2026-07-31). Cut on phones; the title's natural 198.5px then sits
     on one line. Page 1's record still carries "added just now" in its own
     meta line, so the freshness cue is not lost from the walk. */
  .kentry__head .mono { display: none; }
  .pricing__cards { grid-template-columns: 1fr; }
  .footer__inner { flex-direction: column; }
  /* use-case pages: one column, and the reserved demo slot collapses entirely
     so it cannot leave a dead grid row above the footer */
  .uc {
    grid-template-columns: minmax(0, 530px);
    padding-top: 120px;
  }
  /* The capture demo is HIDDEN on phones and still needs its own treatment.
     Its bubbles are absolutely positioned inside a 700px box measured for the
     530px desktop column; at 350px they would overlap into mush. Flagged to
     Isaac 2026-07-31 rather than shipped broken. */
  .uc__demo { display: none; }
  .lad__row { font-size: 15.5px; }
  /* THREE, not two. This said repeat(2,1fr) from when the footer had four
     columns and 2x2 was balanced; after the 2026-07-30 cut to three it left
     Connect orphaned alone on a second row. At 390 three tracks are ~98px
     and the longest label, "Book a demo", measures ~82px, so it fits. */
  .footer__cols { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- phones: the pill tracks the viewport ----------
   Above this width the pill is a fixed 320px centred over a wide page, which
   is right on desktop. On a phone it made the pill NARROWER THAN THE CONTENT:
   at 390 it sat at 35px insets while every card, record and figure sits at 20,
   so its edges fell 15px INBOARD of the page's own alignment line and it read
   as a shrunken widget floating over a wider page (Isaac, 2026-07-31). It now
   breaks OUT past the content instead, 12px against the content's 20px, which
   is aaru.com's move (their pill runs ~96% of viewport width against a ~22px
   content gutter). Resolves to 366px at 390, 406 at 430, 296 at 320.

   480px, not the 980px block, because that clears every phone in portrait (the
   widest common one is 430) and stops short of the 480-980 range where the
   layout is single-column but wide: a 616px pill at 640 reads as stretched,
   not deliberate.

   `100% - 24px`, NOT `100vw - 24px`. The pill is position:fixed, so a
   percentage resolves against the initial containing block, which EXCLUDES a
   classic scrollbar; 100vw includes it, so on any engine that reserves gutter
   the pill would overhang by the scrollbar width and introduce the horizontal
   scroll that all nine pages are verified free of.

   The width must be IDENTICAL in the closed and open states or the lockup
   slides, since the pill is centred and a widening box moves its own left
   edge. One declaration serves both states, so that invariant holds by
   construction rather than by keeping two numbers in step. */
@media (max-width: 480px) {
  .pill { width: calc(100% - 24px); }
}

/* ---------- reduced motion: land on the final state ---------- */
@media (prefers-reduced-motion: reduce) {
  .card--capture { animation: none; opacity: 1; }
  .caret { animation: none; }
  .pill__menu, .pill__menuinner, .pill__burger span { transition: none; }
  .card--record, .rec__item, .pitch, .fig { transition: none; }
  .fig__axis, .fig__wave rect, .fig__travel circle, .fig__head,
  .fig__fills rect,
  .fig2__src, .fig2__stem, .fig2__plane,
  .fig3__ring, .fig3__dot { animation: none; }
  .fig3__spark { animation: none; stroke-dashoffset: 0; }
  .figbridge { transition: none; animation: none; }
  /* the marquee holds still and shows its first set from the left */
  .mq__track { animation: none; }
  /* the rail links keep their colour cue and lose the lift entirely */
  .figlink .fig__cap, .figlink .fig__svg { transition: none; }
  .figlink:hover .fig__svg, .figlink:focus-visible .fig__svg { transform: none; }
  .chat, .wb { transition: none; }
  /* the ladder lands open with no travel; the marker keeps its colour cue */
  .lad__panel, .lad__panelinner, .lad__mark, .lad__item::before,
  .lad__row { transition: none; }
  /* the capture demo holds still: the pulse and dashes stop, wires stay
     drawn, and the stage-swap fade lands instantly */
  .cap__now, .cap__wires i { animation: none; }
  .cap__wires i { opacity: 1; transition: none; }
  .cap { transition: none; }
  /* stage 4's sheet and /document-creation's draft land fully written */
  .cmp__typed span,
  .outdoc__typed span { animation: none !important; transform: scaleX(1); }
  /* the generating label holds steady; the wires above already stop */
  .gen { animation: none; }
  .chat__mark { animation: none; }
  .fig__fills rect { transform: scaleX(1); }
  .fig__wave rect { transform: scaleY(1); }
}
