/* =========================================================================
   KinCompanion Design System — global entry point.
   Consumers link THIS file only. Import order: fonts → tokens → utilities.
   This file is @import lines ONLY. Put real rules in the imported files.
   ========================================================================= */

/* =========================================================================
   KinCompanion · Webfonts
   Inter — the closest free, embeddable match to SF Pro (Apple system font).
   Used across the entire UI, display and numerals. Self-hosted woff2 (latin).
   ========================================================================= */

/* ONE FILE, ONE FACE: Inter is a VARIABLE font (wght 100-900) and the five
   inter-*.woff2 that used to sit here were BYTE-IDENTICAL COPIES of it
   (verified by checksum, 09-01 pass 8) — five @font-face rules pointing at
   five copies of the same file, so a first visit downloaded 4 x 47.3 KB of
   the same bytes and a reload revalidated all four. Declaring the weight
   RANGE lets one file serve 400/500/600/700 and any weight between; subset
   to the characters the site actually sets (161 codepoints, the variable
   axis kept) it is 29 KB. 236 KB -> 29 KB, and it is content-stable, so
   /fonts is served immutable and a reload costs nothing.
   If the character set ever grows (a new language, a new symbol), RE-SUBSET
   via the recipe in DESIGN-LOG pass 8 and ship it under a NEW filename —
   immutable means the old name can never be updated in place. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/inter-var.woff2") format("woff2-variations");
}

/* =========================================================================
   KinCompanion · Color tokens
   One brand color: Companion Teal. Amber/honey is RETIRED.
   Surfaces are iOS system grey with plain white cards.
   Warm umber shadows (never pure black). Sentence-case, calm, low-friction.
   ========================================================================= */

:root {
  /* ---- Brand · Companion Teal scale ----------------------------------- */
  /* ---- Brand · Companion Teal scale ----------------------------------
     One vibrant teal family. #3F8FA6 is the primary (500). The ramp stays
     aqua/teal at every step — it never sinks into navy. --teal mirrors 500
     so consumers can theme from a single knob. */
  --teal: #3F8FA6;          /* primary alias (= --teal-500) */

  --teal-50:  #EDF7FA;   /* faintest wash */
  --teal-100: #D5EDF3;   /* tints, selected rows */
  --teal-200: #ACDAE5;   /* soft fills */
  --teal-300: #7DC4D6;   /* borders-strong, vivid soft */
  --teal-400: #54AAC2;   /* bright accent · icons */
  --teal-500: #3F8FA6;   /* PRIMARY · brand · logo fill */
  --teal-600: #337C92;   /* hover */
  --teal-700: #2B6A7E;   /* deep teal · text/links (AA) */
  --teal-800: #24586A;   /* deep, still teal */
  --teal-900: #1F4A59;   /* deepest — rich teal, NOT navy */

  /* Glossy-button gradient stops (teal material) — exact production recipe */
  --teal-glossy-1: #6FB6CC;
  --teal-glossy-2: #5FAAC4;
  --teal-glossy-3: #4F9BB5;
  --teal-glossy-4: #7FC4DC;

  /* ---- Aqua accents (for max-vibrancy gradients only) ------------------ */
  --aqua-300: #66D2DA;   /* bright cyan-teal highlight */
  --aqua-400: #43BFCB;   /* vivid aqua */
  --aqua-500: #2BA8BC;   /* saturated teal-cyan */

  /* ---- Neutrals · iOS system grey ------------------------------------- */
  --grey-bg:     #F2F2F7;   /* app background — system grey */
  --white:       #FFFFFF;   /* cards — plain white */
  --ink:         #28292A;   /* primary text + ALL headlines (Paul, 2026-08-30:
                               "make all headlines #28292A" — was #000) */
  --ink-soft:    #1C1C1E;   /* softened headings option */
  --grey-500:    #71717A;   /* secondary text / captions */
  --grey-400:    #9CA3AF;   /* placeholder / tertiary */
  --hairline:    #E5E5EA;   /* iOS hairline border */
  --hairline-soft: #ECECEF;
  --switch-off:  #E3E3E8;   /* toggle off-track */

  /* ---- Semantic · destructive (muted brick-red) ----------------------- */
  /* Warmer & redder than terracotta, calmer than coral. Delete / stop. */
  --clay-500: #C1554A;
  --clay-600: #AC4A40;   /* pressed */
  --clay-100: #F4E1DE;   /* tint surface */

  /* Destructive glossy-button gradient stops */
  --clay-glossy-1: #D77565;
  --clay-glossy-2: #CB6354;
  --clay-glossy-3: #C75A4D;
  --clay-glossy-4: #B14A3F;
  --clay-glossy-5: #E08D7E;

  /* ---- Decorative only · Heart coral ---------------------------------- */
  /* Used for the love/heart mark and the logo lockup ONLY. Never a button. */
  --heart: #EC5876;

  /* =====================================================================
     SEMANTIC ALIASES — reference these in product code, not the raw scale
     ===================================================================== */

  /* Surfaces */
  --surface-app:       var(--grey-bg);
  --surface-card:      var(--white);
  --surface-raised:    var(--white);
  --surface-sunken:    var(--grey-bg);
  --surface-tint:      var(--teal-50);     /* selected / hovered rows */

  /* Text */
  --text-primary:      var(--ink);
  --text-secondary:    var(--grey-500);
  --text-tertiary:     var(--grey-400);
  --text-on-teal:      var(--white);
  --text-link:         var(--teal-700);
  --text-accent:       var(--teal-500);    /* section titles + accent icons */

  /* Brand / interactive */
  --brand:             var(--teal-500);
  --brand-hover:       var(--teal-600);
  --brand-soft:        var(--teal-400);
  --accent-section:    var(--teal-500);     /* legacy var name kept; teal only */

  /* Lines */
  --border:            var(--hairline);
  --border-strong:     var(--teal-200);
  --ring:              var(--teal-500);     /* focus ring (was amber) */

  /* Status */
  --danger:            var(--clay-500);
  --danger-hover:      var(--clay-600);
  --danger-tint:       var(--clay-100);
}

/* =========================================================================
   KinCompanion · Gradient tokens
   A vibrant teal gradient system anchored on Companion Teal (#3F8FA6).
   Aqua highlights add energy; the deep end stays rich teal, never navy.
   Use --grad-* for fills/backgrounds; the glossy CTA keeps its own recipe.
   ========================================================================= */

:root {
  /* ---- Core brand gradients ------------------------------------------- */
  /* Signature: bright top-left → primary → deep teal. The hero/cover look. */
  --grad-brand: linear-gradient(150deg, #63ADC4 0%, #4F9BB5 38%, var(--teal-500) 72%, var(--teal-600) 100%);
  /* Straight off the logo mark (public/img/logo-mark-a13.svg): its own stops,
     in its own order, so anything painted with it reads as part of the mark. */
  --grad-mark: linear-gradient(135deg, #4BD0DA 0%, #39C4D0 34%, #22ACBD 68%, #018BA3 100%);

  /* Vivid: max-energy aqua → teal. "Balls to the wall" accent fills. */
  --grad-vivid: linear-gradient(135deg, var(--aqua-300) 0%, var(--aqua-400) 28%, var(--teal-400) 60%, var(--teal-500) 100%);

  /* THE FOOTER'S OWN RAMP (Paul, 08-31: "make the footer colors less vivid.
     more like logo and cta color style. leave the type of gradient tho. i
     like the fade in general"). Same angle, same four stop positions, same
     deep close — only the two vivid aqua-cyans at the top are gone, replaced
     by the CTA button's own glossy set, which is what the lockup is painted
     in too. Ending on --teal-500 as before is deliberate: the footer's white
     type sits on the bottom of this ramp and a lighter close would have
     taken its contrast with it.
     It is its OWN token, not an edit to --grad-vivid: that one still fills
     .kin-grad-vivid, .kin-grad-text and the deck's --grad-head. */
  --grad-foot: linear-gradient(135deg, var(--foot-edge) 0%, var(--teal-glossy-1) 28%, var(--teal-glossy-2) 60%, var(--teal-500) 100%);
  /* THE SEAM COLOUR, named once. The bloom's ramp has to end on the footer's
     top-left stop or the two mismatch where they overlap — that is the class
     of bug that drew Paul's "hardcore seperator linie". Both now read this
     variable, so they cannot drift apart again. */
  --foot-edge: var(--teal-glossy-4);

  /* Soft: airy wash for section bands and large surfaces. */
  --grad-wash: linear-gradient(180deg, var(--teal-50) 0%, #FFFFFF 100%);

  /* Sky: light, optimistic top-down tint for app/page backdrops. */
  --grad-sky: radial-gradient(120% 80% at 80% -10%, var(--teal-100) 0%, var(--surface-app) 58%);

  /* Mesh: multi-stop vibrant blob field for marketing heroes. */
  --grad-mesh:
    radial-gradient(60% 80% at 12% 18%, color-mix(in srgb, var(--aqua-300) 70%, transparent) 0%, transparent 60%),
    radial-gradient(55% 75% at 88% 12%, color-mix(in srgb, var(--teal-400) 65%, transparent) 0%, transparent 62%),
    radial-gradient(70% 90% at 70% 95%, color-mix(in srgb, var(--teal-600) 60%, transparent) 0%, transparent 60%),
    linear-gradient(135deg, var(--teal-500), var(--teal-700));

  /* ---- Glossy CTA material (production recipe, 4 layers) --------------- */
  --grad-glossy:
    linear-gradient(90deg, transparent 0%, rgba(125,195,215,0.52) 30%, rgba(125,195,215,0.52) 70%, transparent 100%),
    linear-gradient(180deg, transparent 0%, rgba(125,195,215,0.52) 25%, rgba(125,195,215,0.52) 75%, transparent 100%),
    radial-gradient(ellipse 90% 140% at 50% 50%, transparent 50%, rgba(65,120,140,0.3) 100%),
    linear-gradient(135deg, var(--teal-glossy-1) 0%, var(--teal-glossy-2) 15%, var(--teal-glossy-3) 30%, var(--teal-glossy-2) 50%, var(--teal-glossy-2) 70%, var(--teal-glossy-4) 85%, var(--teal-glossy-1) 100%);

  /* Highlight glow used behind active/feature elements (Siri, hero blobs). */
  --grad-glow: radial-gradient(circle, rgba(255,255,255,0.26), transparent 62%); /* @kind color */
}

/* =========================================================================
   KinCompanion · Typography tokens
   One UI family: Hanken Grotesk (SF-Pro / Verdana stand-in).
   Nunito reserved for big rounded stat numerals (Apple-Health pattern).
   Sentence case everywhere. Never ALL CAPS. Tight tracking on headings.
   A single, stringent ramp — fixes the app's "many headline sizes" drift.
   ========================================================================= */

:root {
  /* ---- Families ------------------------------------------------------- */
  /* Inter — the closest free, embeddable stand-in for SF Pro. One family
     across the whole UI. Pair with the -apple-system stack so it falls back
     to the real San Francisco on Apple devices. */
  /* The display face: h1/h2-level headlines only. Body copy, UI, the card
     headings, the footer lead and article titles all stay on --font-sans. */
  --font-serif: Times, "Times New Roman", serif;
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Text",
    "Segoe UI", system-ui, sans-serif;
  --font-display: var(--font-sans);          /* same family, heavier weight */
  --font-rounded: var(--font-sans);          /* numerals use Inter (tabular) */

  /* ---- Weights -------------------------------------------------------- */
  --fw-regular: 400;   /* @kind other */
  --fw-medium: 500;    /* @kind other */
  --fw-semibold: 600;  /* @kind other */
  --fw-bold: 700;      /* @kind other */
  --fw-extrabold: 800; /* @kind other */ /* heaviest — large numerals */

  /* ---- Type ramp (size / line-height) --------------------------------
     Names follow the iOS text styles the app already leans on. */
  --text-display:    34px;   --lh-display:  40px;   /* hero / marketing */
  --text-title-1:    28px;   --lh-title-1:  34px;   /* page title  "Hi, Sara" */
  --text-title-2:    22px;   --lh-title-2:  28px;   /* major heading */
  --text-title-3:    17px;   --lh-title-3:  22px;   /* section header (card) */
  --text-body:       16px;   --lh-body:     24px;   /* default reading size */
  --text-callout:    15px;   --lh-callout:  21px;
  --text-subhead:    14px;   --lh-subhead:  20px;   /* captions, meta */
  --text-footnote:   13px;   --lh-footnote: 18px;
  --text-caption:    12px;   --lh-caption:  16px;   /* smallest legal size */

  /* Stat numerals (Inter, tabular feel via weight) */
  --text-stat:       28px;   --lh-stat:     28px;   /* StatCard value */
  --text-stat-unit:  20px;

  /* ---- Tracking ------------------------------------------------------- */
  --tracking-tight: -0.015em;   /* titles & display */
  --tracking-snug:  -0.01em;    /* body on large sizes / buttons */
  --tracking-normal: 0em;

  /* ---- Defaults ------------------------------------------------------- */
  --leading-body: 1.5;
}

/* =========================================================================
   KinCompanion · Spacing, radius & layout tokens
   4px base unit on an 8pt rhythm. The app's signature card padding is 14px.
   Generous whitespace, low element count per screen (calm, low-friction).
   ========================================================================= */

:root {
  /* ---- Spacing scale (4px base) --------------------------------------- */
  --space-0:  0px;
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;   /* default gap between cards/tiles */
  --space-3-5: 14px;  /* signature card padding */
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  /* Component conventions */
  --card-padding: 14px;
  --card-gap: 12px;
  --screen-pad-x: 16px;
  --content-max: 800px;   /* app column max-width */

  /* ---- Radius scale --------------------------------------------------- */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius:    14px;   /* DEFAULT — cards, sheets, glossy buttons */
  --radius-lg: 20px;
  --radius-xl: 24px;
  --radius-pill: 999px;   /* pill buttons, bottom-nav, badges */

  /* ---- Hit targets ---------------------------------------------------- */
  --tap-min: 44px;     /* never smaller (accessibility, older hands) */
  --control-h: 56px;   /* glossy primary button height */
}

/* =========================================================================
   KinCompanion · Elevation, shadow & motion tokens
   Soft, warm UMBER shadows (rgba 58,42,22) — never pure black, never amber.
   Hue-matched, low-opacity, large-blur: the "trust forms in 50ms" look.
   ========================================================================= */

:root {
  /* ---- Shadows -------------------------------------------------------- */
  --shadow-sm: 0 2px 8px rgba(58, 42, 22, 0.06);
  --shadow-md: 0 8px 24px -6px rgba(58, 42, 22, 0.12);
  --shadow-lg:
    0 1px 0 rgba(255, 255, 255, 0.6) inset,
    0 24px 60px -24px rgba(58, 42, 22, 0.22),
    0 2px 8px rgba(58, 42, 22, 0.06);
  /* Teal-tinted lift for the glossy primary button (was amber) */
  --shadow-accent:
    0 1px 0 rgba(255, 255, 255, 0.25) inset,
    0 -1px 0 rgba(0, 0, 0, 0.15) inset,
    0 12px 28px -10px rgba(63, 143, 166, 0.35),
    0 2px 6px rgba(0, 0, 0, 0.08);

  /* Frosted bottom-nav / sheet header backdrop */
  --backdrop-blur: blur(22px) saturate(140%); /* @kind other */
  /* The bar's colour is the page's, not white (Paul). The ALPHA stays: at
     0.72 behind the backdrop-filter it reads as #F8FAFA over the page and
     still frosts once the teal footer scrolls under it. Moves with --page. */
  --nav-bg: rgba(248, 250, 250, 0.72);
  --nav-border: rgba(58, 42, 22, 0.10);

  /* ---- The page ground --------------------------------------------------
     NOT --white, and deliberately its own token. The cards, the fields and
     the frosted bar stay pure white and now lift off the page instead of
     dissolving into it; only the ground moves. Sampled off the three sites
     Paul pointed at: slashy #F5F5F5 (neutral grey), tavus #F7F4EF (warm),
     panacea #FCF6EF (warm cream). Anything that has to MATCH the ground —
     body, the html ramp, .cta-section's blend base — reads --page. Anything
     that has to sit ON it keeps --white.

     Paul's call, after seeing all four rendered: #FAFAFA — neutral, not warm;
     the warm creams put a yellow cast under our cool teal. Re-picked
     2026-08-30 ("make background #F8FAFA"): two points cooler, a breath of
     the brand's own cast in the ground. Cards and the frosted bar still
     separate from it. */
  --page: #F8FAFA;

  /* ---- Pre-footer texture + handoff ------------------------------------
     --grain is ONE feTurbulence tile, inline so it costs no request and no
     file in public/. Two consumers, .cta-section::after and footer.vivid::after,
     and they must stay the same tile at the same background-size or the grain
     changes character across the boundary the whole thing exists to hide.
     fractalNoise 0.62 / 3 octaves puts a noise cell at ~1.6px; saturate 0
     takes the colour out so it only modulates lightness. stitchTiles + an
     explicit 0/0/100%/100% filter region is what makes it tile seamlessly —
     the default filter region overhangs by 10% and the seams show.

     The feComponentTransfer is not decoration. Raw fractalNoise only spreads
     about ±0.13 around 0.5, and `overlay` against a near-white base has a
     response slope of 2*(1-base) ≈ 0.14, so the two multiply out to nothing —
     the first build measured 2.8 residual over the bloom, below Chrome's own
     gradient dither. slope 2.6 / intercept -0.8 stretches 0.31..0.69 onto
     0..1 and the alpha is forced opaque. It stays CENTRED ON 0.5, which is
     overlay's identity point, so the layer adds texture without darkening or
     tinting what is under it. color-interpolation-filters is pinned to sRGB
     because the SVG default is linearRGB and the numbers above are sRGB.

     --foot-bleed is how far the footer is pulled UP over the bloom. It
     replaced a horizontal ramp on .cta-section::after that tried to guess the
     footer's top-edge colour: --grad-vivid is 135deg, so how far along the
     ramp the footer's top-right corner lands depends on the footer's HEIGHT,
     which changes with the viewport. It was computed for 1440x900; the real
     footer is 627px tall, which left the right-hand end two thirds of the way
     down the ramp instead of at the end — a ~20/255 step, and exactly the
     "hardcore seperator linie" Paul saw. Nothing guesses now: the footer
     paints its own gradient over the bloom and masks its own top edge in, so
     there is only one surface and it cannot mismatch itself. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.62' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='2.6' intercept='-0.8'/%3E%3CfeFuncG type='linear' slope='2.6' intercept='-0.8'/%3E%3CfeFuncB type='linear' slope='2.6' intercept='-0.8'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23g)'/%3E%3C/svg%3E");
  /* Lengthened 2026-08-28 ("smoother and less abrupt"): 170 -> 240 at the cap.
     The section's bottom padding grew by exactly the same delta at every width
     (floor +24, +4vw, cap +70), so the plain ground under the closing form —
     the air Paul asked for twice — is unchanged; only the fade got longer. */
  --foot-bleed: clamp(120px, 16vw, 240px);

  /* ---- Motion --------------------------------------------------------- */
  --ease-kin: cubic-bezier(0.4, 0, 0.2, 1); /* @kind other */
  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1); /* @kind other */
  --dur-fast: 180ms;  /* @kind other */
  --dur-base: 300ms;  /* @kind other */
  --dur-slow: 700ms;  /* @kind other */

  /* Press / hover deltas */
  --press-lift: -2px; /* @kind other */
  --hover-lift: -0.5px; /* @kind other */
}

/* =========================================================================
   KinCompanion · Base element styles + shared utilities
   Applied to specimen cards, components and UI kits so they share one look.
   ========================================================================= */

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

body {
  margin: 0;
  background: var(--surface-app);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: var(--leading-body);
  font-weight: var(--fw-regular);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-feature-settings: "cv11", "ss01";
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  letter-spacing: var(--tracking-tight);
  font-weight: var(--fw-semibold);
  margin: 0;
  color: var(--text-primary);
}

a {
  color: var(--text-link);
  text-decoration: none;
}

/* ---- Type helpers ----------------------------------------------------- */
.kin-display   { font-size: var(--text-display); line-height: var(--lh-display); font-weight: var(--fw-bold);     letter-spacing: var(--tracking-tight); }
.kin-title-1   { font-size: var(--text-title-1); line-height: var(--lh-title-1); font-weight: var(--fw-semibold); letter-spacing: var(--tracking-tight); }
.kin-title-2   { font-size: var(--text-title-2); line-height: var(--lh-title-2); font-weight: var(--fw-semibold); letter-spacing: var(--tracking-tight); }
.kin-title-3   { font-size: var(--text-title-3); line-height: var(--lh-title-3); font-weight: var(--fw-semibold); letter-spacing: var(--tracking-snug); }
.kin-body      { font-size: var(--text-body);    line-height: var(--lh-body);    font-weight: var(--fw-regular); }
.kin-callout   { font-size: var(--text-callout); line-height: var(--lh-callout); font-weight: var(--fw-regular); }
.kin-subhead   { font-size: var(--text-subhead); line-height: var(--lh-subhead); font-weight: var(--fw-regular); color: var(--text-secondary); }
.kin-footnote  { font-size: var(--text-footnote);line-height: var(--lh-footnote);font-weight: var(--fw-regular); color: var(--text-secondary); }
.kin-caption   { font-size: var(--text-caption); line-height: var(--lh-caption); font-weight: var(--fw-regular); color: var(--text-secondary); }
.kin-stat      { font-family: var(--font-rounded); font-size: var(--text-stat); line-height: var(--lh-stat); font-weight: var(--fw-bold); color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.kin-stat-unit { font-family: var(--font-rounded); font-size: var(--text-stat-unit); font-weight: var(--fw-bold); color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* ---- Elevation utilities --------------------------------------------- */
.shadow-kin-sm     { box-shadow: var(--shadow-sm); }
.shadow-kin-md     { box-shadow: var(--shadow-md); }
.shadow-kin-lg     { box-shadow: var(--shadow-lg); }
.shadow-kin-accent { box-shadow: var(--shadow-accent); }

/* ---- Card --------------------------------------------------------------*/
.kin-card {
  background: var(--surface-card);
  border-radius: var(--radius);
  padding: var(--card-padding);
}

/* ---- Gradient fills --------------------------------------------------- */
.kin-grad-brand { background: var(--grad-brand); }
.kin-grad-vivid { background: var(--grad-vivid); }
.kin-grad-wash  { background: var(--grad-wash); }
.kin-grad-sky   { background: var(--grad-sky); }
.kin-grad-mesh  { background: var(--grad-mesh); }
/* Gradient applied to text (clip to glyphs). */
.kin-grad-text {
  background: var(--grad-vivid);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ---- Focus ring ------------------------------------------------------- */
.kin-focus:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 35%, transparent);
}

/* ---- Reduced motion --------------------------------------------------- */
/* Calm by default: honor the OS "reduce motion" setting everywhere. Stops the
   Siri glow spin, nav settle, and any consumer animation from looping. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}



  :root {
    /* PowerPoint headline ramp == --grad-vivid */
    --grad-head: linear-gradient(118deg, var(--aqua-300) 0%, var(--aqua-400) 34%, var(--teal-400) 62%, var(--teal-500) 100%);

    /* Single source of truth for the left/right page margin.
       Every section gets this identical horizontal gutter via .wrap.
       Fluid ramp: 20px on a 320 phone, growing to 40px on wider screens. */
    --gutter: clamp(20px, 5.2vw, 40px);
    /* The bar's height, named once because the hero subtracts it to fill
       exactly the rest of the first screen. Changing it here moves both. */
    --nav-h: clamp(58px, 5.6vw, 68px);
  }
  * { box-sizing: border-box; }
  html, body { margin: 0; }
  /* Safety net against any sub-pixel horizontal spill at narrow widths.
     Kept on html only (not body) so we never create a scroll container on
     body that could clip a future position:sticky child. Construction below
     already prevents overflow at every tested checkpoint; this is a guard.
     `clip` where supported (night-3, Paul: "on mobile the header on the
     top doesn't stick"): `hidden` on the root makes iOS SAFARI treat it
     as a scroll container and position:sticky dies there — the classic
     WebKit trap; `clip` clips WITHOUT creating a scroll container, so
     the bar sticks. The `hidden` line stays as the legacy fallback. */
  html { overflow-x: hidden; overflow-x: clip; }
  /* NO scroll-snap here, and the reason is measured rather than assumed.
     `scroll-snap-type: y proximity` with one target on the film was built and
     tested on 2026-08-28: three consecutive 300px wheel steps all landed back
     on the snap point, i.e. a mouse-wheel user standing 300px away cannot get
     past it. Chrome's proximity threshold is roughly viewport-sized and CSS
     cannot narrow it. `mandatory` is worse. Wheel damping in JS is worse still:
     it needs preventDefault on every wheel event, kills trackpad momentum,
     breaks the keyboard, and does nothing at all on touch, where the momentum
     scroll runs off the main thread. If a dwell at the film is wanted, the
     honest mechanism is a sticky hold, and that is a decision about the film
     section, not a line of CSS here. */
  body { background: var(--page); color: var(--text-primary); font-family: var(--font-sans), Inter, -apple-system, sans-serif; -webkit-font-smoothing: antialiased; }
  /* Overscroll bounce colour. The rubber-band at the page ends reveals the
     canvas (root element) background, not any DOM node. We give html a
     white→deep-teal vertical gradient: the body's opaque white covers all
     content so the middle never shows, and only the edges feed the bounce —
     white on the top pull, the footer's deep teal on the bottom pull, so the
     footer appears to continue past the fold. */
  html { background: linear-gradient(180deg, var(--page) 0%, var(--page) 88%, var(--teal-500) 100%); }

  /* ---- iOS 26 Safari's forehead and chin -------------------------------
     Safari 26 (Liquid Glass) does not show page pixels in the status-bar
     strip above the page or the toolbar strip below it. It paints both in a
     colour it SAMPLES from the page, and the sampler reads exactly one
     thing: the `background-color` (or `backdrop-filter`) of a position:fixed
     / position:sticky element sitting at that edge — at least 80% wide, at
     least 3px tall, within 3px of the edge — and falls back to the BODY's
     background-color when it finds none. It never reads a background IMAGE,
     never a pseudo-element, and never an absolutely-positioned child of a
     fixed parent. theme-color no longer applies.

     So on a phone the hero's sky (a background-image on .opening-clouds) and
     the footer's ramp (a background-image on footer.vivid) were both
     invisible to it, it fell back to body's #F8FAFA, and it painted a flat
     near-white band above the clouds and below the footer — Paul, 08-31
     night-4: "the cloud back ground still cuts off at the top", "footer
     doesn't go all the way down". It is NOT a layout gap: measured in
     headless at 390x844 the clouds already reach y=-82 at scroll 0 and the
     footer's bottom IS the document's bottom.

     These two strips are nothing but sampling targets. z-index:-1 puts them
     at step 2 of the paint order, BELOW every in-flow block background
     including body's opaque one, so they are invisible in every browser and
     can only ever be read as colour. Their colours track the page: the top
     one carries the hero sky's own tone only while the hero is actually at
     the top (it rides the header's existing .is-scrolled, so no new scroll
     handler), the bottom one turns teal only while the footer covers the
     bottom edge (one IntersectionObserver in Base.astro). Everywhere else
     both are --page, which is what the sampler would have picked anyway.

     UNVERIFIED ON DEVICE: there is no Xcode and no iOS Simulator on this
     Mac, so this is reasoned from the WebKit behaviour, not measured. If it
     does not take, the guaranteed fallback for the top is a short fade of
     the sky to --page over the first ~70px — which costs the clouds behind
     the lockup that Paul asked for ("extend through"), hence not the first
     move. */
  .edge-tint {
    /* 6px: Safari's sampler wants at least 3px tall, at least 80% wide and
       within 3px of the edge, and nothing above that helps. Kept small on
       purpose — belt and braces. If some engine ever DID paint one of these
       despite the negative z-index, 6px at the extreme edge sits inside the
       safe area under the status bar / home indicator with viewport-fit=cover
       and still would not be seen. */
    position: fixed; left: 0; right: 0; height: 6px;
    z-index: -1; pointer-events: none;
    background-color: var(--page);
  }
  .edge-tint-top { top: 0; }
  .edge-tint-bot { bottom: 0; }
  /* Sampled at 390 wide off the live top row: the hero sky's own average.
     Only on the page that HAS a hero, and only before the bar frosts. */
  body:has(.opening) header:not(.is-scrolled) ~ .edge-tint-top { background-color: #EAF3F6; }
  /* The footer's real bottom row — the ramp AFTER its glow and grain, not
     raw --teal-500 (measured #4E9BB4, the ramp alone is #3F8FA6). */
  .edge-tint-bot.is-foot { background-color: #4E9BB4; }
  a { color: inherit; text-decoration: none; }

  /* The ONLY place horizontal page padding is defined.
     max-width + auto margins center the column; --gutter is the equal L/R inset.
     No element that also carries .wrap may override padding-left / padding-right;
     section-specific rules below set VERTICAL padding only, so the equal gutter
     holds by construction at every width. */
  .wrap { max-width: 1180px; margin: 0 auto; padding-left: var(--gutter); padding-right: var(--gutter); }

  .grad-text {
    background-image: var(--grad-head);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
  }

  /* =========================================================================
     Top bar — KinCompanion "Companion Glass" sticky nav.
     slashy logic (sticky top:0 z-50 · brand LEFT · links CENTERED · CTA RIGHT ·
     grow-from-zero underline) rebuilt on KinCompanion's
     own frosted tokens. Over the white hero it reads as a clean white bar with
     a whisper-faint warm hairline; the saturate-blur only sings once the teal
     footer scrolls under it. A single .is-scrolled class adds a shadow lift.
     ========================================================================= */
  header {
    position: sticky;
    top: 0;
    z-index: 50;
    /* Notched phones (viewport-fit=cover since 08-31 night-3): the bar's
       content steps below the notch; the ::before frost covers the inset
       too (inset 0). Zero everywhere else. */
    padding-top: env(safe-area-inset-top, 0px);
    /* TRANSPARENT AT REST since night-3 (Paul, on the cloud haze: "what
       I don't like is the hardcut off the header logo ... horizontal
       shift to plain white ... no cut there please. extend through"):
       the bar's 72% white wash milked the haze and drew a hard line at
       its bottom edge. At rest the hero's clouds now run through behind
       the lockup and the button; the fill arrives WITH .is-scrolled
       below, the same moment as the hairline, once content actually
       passes underneath. The backdrop-filter stays constant (it is
       invisible over the soft haze, and transitioning it is jank) —
       only background-color fades. */
    background-color: transparent;
    /* NO backdrop-filter on the bar itself any more (night-3, Paul:
       "dont show the line until h1 collides"): always-on blur drew a
       faint smear seam at the bar's bottom edge over the pixel clouds
       even at rest. The whole frost — fill AND blur — lives on the
       ::before below and fades in with .is-scrolled, so at rest the
       bar paints nothing at all. */
    /* NO border. The hairline is the first layer of the box-shadow instead,
       for two reasons:
         - It is not on at rest. A fresh page load opens with the lockup and
           the button on the same white as the headline under them, nothing
           drawn between; the line arrives with .is-scrolled, together with the
           lift, once there is actually content passing underneath. A shadow
           costs no layout, so it can appear and disappear without nudging the
           page by a pixel the way a border would.
         - It is THINNER than the 1px border it replaced. Blink rounds a
           `0.5px` border-width up to a whole CSS pixel — measured: two device
           rows at 2x — where a 0.5px shadow offset really does paint one
           device row. Halving it with a scaled pseudo-element is not an
           option: nothing in this header may carry a transform or a filter,
           both left visible artefacts under the backdrop-filter (see the log).
       Line, lift and frost arrive together. */
    transition: box-shadow var(--dur-base) var(--ease-out-soft);
  }
  /* The frost layer: fill + blur in one pseudo whose OPACITY fades —
     transitioning backdrop-filter itself is jank, but an opacity fade
     on the layer fades the blurred result cleanly. z -1 keeps the nav
     content above it; the h1-cross trigger means no content ever sits
     under the bar while this is still transparent. */
  header::before {
    content: ""; position: absolute; inset: 0; z-index: -1;
    background-color: var(--nav-bg);                 /* the 0.72 page-RGB frost */
    -webkit-backdrop-filter: var(--backdrop-blur);   /* Safari / iOS */
    backdrop-filter: var(--backdrop-blur);           /* blur(22px) saturate(140%) */
    opacity: 0;
    transition: opacity var(--dur-base) var(--ease-out-soft);
  }
  header.is-scrolled::before { opacity: 1; }
  header.is-scrolled { box-shadow: 0 0.5px 0 var(--nav-border), var(--shadow-sm); }

  /* @supports fallback: no backdrop-filter (legacy Firefox) -> the frost
     pseudo goes SOLID white when scrolled so the bar stays legible with
     no blur behind it; at rest it is transparent like everywhere else. */
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    header.is-scrolled::before { background-color: var(--white); }
  }

  /* Layout: brand left · links absolutely centered · CTA right.
     Height + gutter unchanged from the original bar. */
  /* One height, always. The bar does not react to scroll: a lockup that
     changes size mid-page draws the eye to the chrome instead of the content.
     Small is the resting state. */
  .nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    position: relative;                              /* anchor for centered links */
    height: var(--nav-h);
  }
  /* Brand lockup is one SVG (mark + wordmark, GTM > Logos > SVGs). --logo-h keeps
     the mark at the height the old PNG occupied, so the nav rhythm is unchanged;
     the ™ is derived from the same token because the lockup does not carry one. */
  .brand { --logo-h: clamp(32px, 3.6vw, 40px); display: inline-flex; align-items: flex-start; gap: 3px; text-decoration: none; border-radius: var(--radius-sm); }
  .brand-logo { height: var(--logo-h); width: auto; display: block; }
  /* Colour is the CTA-gradient lockup's darkest wordmark stop — the ™'s
     anchor since the lockup took the button's ramp (2026-08-30 night-2;
     saturated pass same night: #4F9BB5 -> #389FC2). */
  .tm { font-family: var(--font-sans); font-size: calc(var(--logo-h) * 0.26); font-weight: 600; line-height: 1; letter-spacing: 0; color: #389FC2; margin-top: calc(var(--logo-h) * 0.35); }

  /* Right-side button — a real button, deliberately the SECOND one.
     It carries the same 14px radius and weight as the hero CTA so it reads as
     part of the same family, but it is outlined instead of the glossy teal
     material: the page has one primary ask, and two glossy pills on the same
     screen split it. No arrow either — the rotating arrow stays the primary
     CTA's tell. */
  /* The right-hand button is the site's own CTA, not a style of its own. It
     carries `.cta` for the glossy material and `.nav-cta` purely for the bar's
     scale, so there is exactly one place the material is written.

     Three versions were tried and dropped before this: a glossy pill built by
     hand (rather than reusing .cta), an outlined white rectangle that filled
     with --grad-mark on hover, and the film's translucent glass. Do not
     reintroduce a header-only button style.

     Compound selector on purpose: this block sits ABOVE .cta in the file and a
     single class would lose the cascade to it. */
  .cta.nav-cta {
    height: 40px;
    min-height: 40px;
    padding: 0 20px;
    font-size: 14.5px;
    text-decoration: none;
  }

  /* Focus-visible ring (teal --ring) on the interactive bar elements. */
  .brand:focus-visible,
  .nav-cta:focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: 3px;
    border-radius: 10px;
  }

  /* Sticky bar must not cover the target on anchor jump — including the
     footer nav's targets. */
  #top, #waitlist, #building, #features { scroll-margin-top: clamp(66px, 7vw, 80px); }

  /* On mobile, drop the CTA entirely so it never crowds the wordmark.
     Contact is still reachable via the hero form and the footer mailto. */
  /* The button stays on the bar at every width; it used to be dropped below
     560. It only comes down a size, because the lockup beside it is ~192px at
     its own phone size and a 320px screen leaves 280px of column for both.
     Compound selector on purpose: `.cta` sets display/height/padding further
     DOWN this file at the same specificity and a media query adds none, so a
     bare `.nav-cta` here would lose the cascade to it. */
  @media (max-width: 560px) {
    .cta.nav-cta { height: 36px; min-height: 36px; padding: 0 14px; font-size: 13.5px; }
  }
  /* Smallest phones. The lockup is 6.008:1, so at its 32px floor it is 192px
     wide and the bar needs ~297px for both — more than the 280px a 320px
     screen leaves. The logo comes down with the button rather than the button
     being clipped (html carries overflow-x:hidden, so an overflow here would
     cut the button off silently instead of showing a scrollbar).
     Chrome on macOS will not open a window under 500px, so this width is
     reasoned from the geometry, not screenshotted. */
  @media (max-width: 360px) {
    .brand { --logo-h: 28px; }
    .cta.nav-cta { padding: 0 11px; font-size: 13px; }
  }
  /* ---- The opening: title, standfirst, waitlist, film ---------------------
     Type and rhythm measured off asendia.ai's hero in the browser, which is
     the reference Paul named ("i love the sizes and the layout"):

        title       64px / 70px / -2.4px tracking / 820px measure
        standfirst  18px / 28px / -0.2px / 560px measure / 60% ink
        gaps        16px title→standfirst, 24px standfirst→CTA

     Their face is Georgia at weight 400. Ours stays Inter at 700: the font was
     not what was being pointed at, a serif version of this site was built and
     rejected outright, and Inter 400 at 64px reads unfinished where Georgia
     400 reads editorial. Centred, and BLACK — no gradient on the title.

     The whole block is one flex column so the form can change places by width
     with `order` instead of a second copy in the markup: above the film on
     desktop, below it on phones.
     ------------------------------------------------------------------------ */
  .opening { position: relative; display: flex; flex-direction: column;
    /* overflow-x: CLIP, never hidden (08-31 night-3, Paul: "there's
       horizontal scroll. I don't want"): the haze pseudos' ±20% sideways
       overscan created +89px of real scrollable overflow on phones — the
       comment below's "sideways overspill is offscreen" was wrong, layout
       overflow scrolls. clip cuts the axis WITHOUT creating a scroll
       container, so the film's sticky pin inside this section survives
       (hidden would re-anchor it); overflow-y stays visible for the 140px
       upward overpaint behind the bar. */
    overflow-x: clip;
    /* isolation keeps the cloud sheets' negative z-index inside the
       section. NO overflow clip: the sheets deliberately overpaint
       140px UPWARD so the haze runs behind the at-rest-transparent
       header bar; sideways the section is full-bleed, so overspill is
       offscreen, and downward the mask ends the haze. (The blue/teal
       crizzle grounds and the tavus pixel sky all lived and left on
       2026-08-30 night-3 — "wieder weißer hintergrund wie davor" —
       recipes in DESIGN-LOG; the fog-strip ground before them too.) */
    isolation: isolate;
  }
  /* ---- The hero's cloud haze (Paul, 2026-08-30 night-3: "on hero I
     want a subtle cloudy thingy thing. just like here:
     https://openworklabs.com/#product", strengthened on "too subtle
     rn"). The reference, measured off their live DOM: a
     pointer-events-none strip pinned over the hero running a
     cloud-shader <canvas> at opacity 0.5 over near-white, with a 320px
     bottom fade onto the page colour. This is the CSS translation, no
     WebGL: two oversized gradient sheets in the teal family drifting
     VERY slowly against each other (transform-only,
     compositor-cheap), each masked so the haze runs to the page's very
     top ("extend through") and dissolves before the section ends —
     the mask replaces their fade div, so nothing ever paints over
     content. Ambient motion, not scroll-coupled. Reduced motion parks
     the sheets as a still haze. */
  .opening::before,
  .opening::after {
    content: ""; position: absolute; z-index: -1; pointer-events: none;
    /* Top overscan is FIXED PX and generous on purpose: it must clear
       the 68px bar plus the drift's largest downward excursion (~25px)
       with margin, so the sheet's tinted top edge can never dip into
       view behind the transparent bar. */
    /* The 140 was budgeted on 2026-08-30 as "68px bar + ~25px drift with
       margin". On 08-31 night-3 the bar grew `padding-top:
       env(safe-area-inset-top)` for viewport-fit=cover, and nothing here
       moved: on a notched phone the bar is 68+59=127, the drift's largest
       downward excursion is ~15px at this section height, and 142 > 140 —
       the sheet's tinted top edge dips into view behind the transparent
       bar. The inset is now IN the budget, so the margin is the same on
       every device and zero everywhere without a notch. */
    top: calc(-140px - env(safe-area-inset-top, 0px)); bottom: -12%; left: -20%; right: -20%;
    -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 55%, transparent 86%);
    mask-image: linear-gradient(to bottom, #000 0, #000 55%, transparent 86%);
  }
  /* Alphas are the v2 set (raised on "too subtle rn"; first cut was
     0.34/0.42/0.16 + 0.55/0.20) — these five numbers ARE the
     strength knob. */
  .opening::before {
    background:
      radial-gradient(46% 34% at 18% 30%, rgba(172, 218, 229, 0.55), transparent 70%),
      radial-gradient(52% 38% at 85% 18%, rgba(213, 237, 243, 0.65), transparent 72%),
      radial-gradient(38% 30% at 62% 60%, rgba(125, 196, 214, 0.30), transparent 70%),
      radial-gradient(30% 24% at 8% 72%, rgba(84, 170, 194, 0.14), transparent 70%);
    animation: cloud-drift-a 96s ease-in-out infinite alternate;
  }
  .opening::after {
    background:
      radial-gradient(50% 36% at 38% 16%, rgba(255, 255, 255, 0.7), transparent 70%),
      radial-gradient(44% 32% at 74% 44%, rgba(172, 218, 229, 0.34), transparent 72%);
    animation: cloud-drift-b 140s ease-in-out infinite alternate;
  }
  /* The drift SLEEPS while the opening is offscreen (09-01 pass 6): the
     two haze sheets are ~140%-wide composited layers whose animations
     otherwise tick for the whole session — ~43MB of textures animating
     under the band and the footer. An IO in index.astro toggles the
     class 80px out; on-screen nothing changes, and the layers themselves
     stay (the will-change below is the banked 08-31 win). */
  .opening.sky-idle::before,
  .opening.sky-idle::after { animation-play-state: paused; }
  /* THE PIXEL SKY, on the white ground (Paul kept it out of the
     reverted blue arc: "die fresheen wolken ... in eingefärbt will
     ich trotzdem"). tavus.io's measured device — coarse ordered-dither
     cumulus, chunky Bayer blocks, static — recoloured to blue-teal
     ink for the light page (tile ONLY via tools/build_pixel_clouds.py;
     the dark-ground palette is banked in DESIGN-LOG). z -2 keeps it
     UNDER both haze sheets (z -1), so the drift washes over the
     standing pixels; the radial mask thins the sky behind the
     headline/form window so the ink Times keeps its ground. Top runs
     to -140px with the sheets — the sky reaches the page top behind
     the transparent bar. */
  .opening-clouds {
    position: absolute; top: calc(-140px - env(safe-area-inset-top, 0px)); bottom: 0; left: 0; right: 0;
    z-index: -2; pointer-events: none;
    /* FINE PIXELS again (Paul: "make the clouds more pixely again.
       just like on tavus, but less than before"): 480x240 art pixels
       shown at 1280x640 = ~2.7-CSS-px blocks, vs the first cut's 4.
       The smooth frizzle tile + live-grain construction is banked in
       DESIGN-LOG. */
    background: url("/img/pixel-clouds.png") repeat;
    background-size: 1280px 640px;
    image-rendering: pixelated;
    opacity: 0.85;
    /* ON ITS OWN COMPOSITED LAYER (perf, 08-31 — Paul: the film scroll is
       "laggy und zuckelt"). This sheet is full-width, section-tall, a tile
       blown up 2.67x, and carries TWO intersected masks; without a layer of
       its own the browser re-rastered the tile and both masks for every row
       of it that scrolled into view. Promoting it rasters that once and lets
       the compositor move the texture. Measured with the CDP tracer: raster
       tasks through the film 960 -> 651 (-32%), and an A/A vs A/B screenshot
       diff puts the visual change inside the cloud-drift noise floor — it is
       a hint to the compositor, not a rendering change. */
    will-change: transform;
    /* TWO masks, INTERSECTED: the radial window keeps the
       headline/form ground clear, and the bottom run-out dissolves the
       clouds AS SHAPES well before "The Problem" (Paul: "clouds end
       right before as a whole instead of being cut off") — solid to
       66%, gone by 90%, so nothing ever touches the seam. Browsers
       without mask-composite fall back to the union — softer, never
       broken. */
    /* Run-out lowered 08-31 (solid 66->78, gone 90->97), and AGAIN
       09-01 (Paul: "the pixel cloud of the hero need to go even
       further down on the left and right of the launch demo video too.
       they fully stop (without beeing horizontally cut off) before thr
       'The Problem' picture"): first cut ran solid 88 / gone 99 and
       Paul read the shapes still fading at the box's straight bottom
       edge as CUT ("clouds below the demo video that feel cut off") —
       1% margin is none. Now solid 82 / transparent 93: still well
       below the film, but the dissolve COMPLETES ~200px above the
       section edge, so the last clumps die in open air, never against
       the line. The intersected radial window is what keeps the
       dissolve shape-whole; both layers must always be repeated
       together. */
    -webkit-mask-image:
      radial-gradient(62% 46% at 50% 34%, transparent 30%, rgba(0, 0, 0, 0.5) 60%, #000 85%),
      linear-gradient(to bottom, #000 0, #000 82%, transparent 93%);
    -webkit-mask-composite: source-in;
    mask-image:
      radial-gradient(62% 46% at 50% 34%, transparent 30%, rgba(0, 0, 0, 0.5) 60%, #000 85%),
      linear-gradient(to bottom, #000 0, #000 82%, transparent 93%);
    mask-composite: intersect;
  }
  @keyframes cloud-drift-a {
    from { transform: translate3d(-2.5%, -1.2%, 0); }
    to   { transform: translate3d(2.5%, 1.6%, 0); }
  }
  @keyframes cloud-drift-b {
    from { transform: translate3d(2%, 1%, 0); }
    to   { transform: translate3d(-2.4%, -1.4%, 0); }
  }
  @media (prefers-reduced-motion: reduce) {
    .opening::before, .opening::after { animation: none; }
  }
  /* width:100% is NOT redundant. Every child here carries `.wrap`, which sets
     `margin: 0 auto`, and an auto margin on the cross axis beats `align-items:
     stretch` in flexbox: the children shrank to their content and centred
     themselves, so the form came out 257px wide next to a 445px film. With an
     explicit width the max-width and the auto margins go back to doing what
     they do everywhere else on the page. */
  .opening > * { width: 100%; }

  .lede-inner {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    /* Deep. asendia sets their h1 237px down a 900px screen; this lands ours
       at about 226 with the 68px bar above it. */
    padding-top: clamp(64px, 11vw, 158px); padding-bottom: 0;
  }
  /* THE DISPLAY FIVE ARE TIMES, weight 400, from one rule so they cannot drift
     apart: the h1, the band headline, the section headline, the closing
     headline and the footer lead. This has now gone Times -> Inter -> Times ->
     Inter -> Times; the rule that survives every round is that THE FACE AND THE
     SIZES MOVE TOGETHER. Inter is materially wider, so on Inter the h1 has to
     drop to clamp(32px,5.4vw,78px)/1.0 and .sec-title to clamp(28px,3.4vw,44px)
     or their long lines wrap on their own and the <br>s stop owning the breaks.
     Those numbers are recorded here precisely so the next swap is one edit.

     `.pcard h3` and `.fl-title` are DELIBERATELY NOT in this list. Paul named
     the five above and only those; the card heads and the feature-list titles
     stay Inter, with their own weight below. (The band's display heads have
     cycled: .morph-head and .duo-head came and went with 2026-08-30's morph
     and duo; since 08-30 night the definition ledger's .def-term carries
     "The Problem"/"The Solution" in the display Times.)

     A full editorial redesign of the whole site on Instrument Serif is a
     separate thing and stays rejected. This is the headline level only. */
  .lede-title,
  .band h1, .band h2,
  .def-term,
  .sec-title,
  .cta-title {
    font-family: var(--font-serif);
    font-weight: 400;
    letter-spacing: -0.02em;
    /* Times has nothing between Roman and Bold, so weight is a stroke rather
       than a font-weight. In EM, not px: a flat px value would put four times
       the relative weight on the smaller headlines. Costs no layout at all,
       so none of the sizes above have to move with it.
       0.012em. The ladder, measured over the h1 on this build: no stroke 43.5,
       0.008 48.7 (+12%), 0.012 ~51 (+18%), 0.016 53.9 (+24%), real Times Bold
       63.8 (+47%). It went 0.008 -> "bolder" -> 0.016 -> "a little bit less
       bold" -> here. The Bold cut is a different, more compressed face and was
       looked at and passed over twice. */
    -webkit-text-stroke: 0.012em currentColor;
  }
  /* The h2 level sits ONE STEP UP the ladder (Paul, 2026-08-28: "make the h2
     thicker slightly"): 0.016em is +24% ink where the h1's 0.012em is +18%.
     All three h2s carry .reveal-h, so in practice this paints via the .rw rule
     below; the element-level width here is the no-JS / reduced-motion path. */
  .band h2, .sec-title, .cta-title, .def-term { -webkit-text-stroke-width: 0.016em; }
  /* The WIDTH does not inherit (the colour does, and re-resolves per word), so
     the reveal script's spans need their own or the three .reveal-h headlines
     stay thin while the h1 thickens. 0.016em = the h2 step: .reveal-h only
     exists on the three h2s, so this IS the h2 weight in the JS path. */
  .reveal-h .rw { -webkit-text-stroke-width: 0.016em; }
  /* The two that stay Inter. */
  .pcard h3, .fl-title { font-weight: 700; letter-spacing: -0.015em; }

  .lede-title {
    margin: 0; max-width: 820px;
    /* Inter's cap, not Times'. This number and the face move together or
       the <br> stops being the only break. */
    /* Times' cap. On Inter this has to come down to clamp(32px,5.4vw,78px)
       with line-height 1.0; see the rule above. */
    /* The FLOORS across all four display headlines are set by 320px, not by
       the desktop end: at 42 the h1 broke to three lines at 360, and at 38 it
       still did at 320 along with the band and section headlines. None of that
       showed up until the sweep went below 390. The vw term means nothing above
       ~515px is affected. */
    font-size: clamp(33px, 6.4vw, 92px);
    line-height: 0.96;
    /* No text-wrap: balance — the <br> in the markup owns the break and
       balancing fights it. */
    color: var(--ink);
  }
  /* Sized to `.band-sub` ("Starting with memory care."), and set further off
     the headline than asendia's 16px — theirs sits under a 64px line, ours
     under 92. */
  .lede-sub {
    margin: clamp(20px, 2.4vw, 34px) auto 0; max-width: 560px;
    font-weight: 500; font-size: clamp(17px, 2.2vw, 19px);
    line-height: 1.556; letter-spacing: -0.011em;
    color: var(--text-secondary);
  }

  /* 24px under the standfirst, their gap down to the call to action. .waitlist
     carries a top margin for the closing section, where it follows a
     paragraph; here the block's own padding does the spacing. */
  .openform { padding-top: 24px; }
  .openform .waitlist { margin-top: 0; }

  /* ---- Hero — PARKED -----------------------------------------------------
     Nothing on the page carries these classes any more: the headline and the
     standfirst were cut and the empty section was removed with them, so the
     film is the first thing under the bar. Kept, not deleted, the same way
     DemoPhone's block is kept — the hero was rebuilt four times in one session
     and the shape it lands in next is not obvious. If it never comes back,
     delete this block and the `.hero`/`.hero-inner` markup notes with it.
     ------------------------------------------------------------------------ */
  /* Plain white, and back in normal flow: the hero is a block at the TOP of
     the page, not a screen it fills. Two other versions are behind this one
     and neither is to come back — a faint teal wash carrying the hero into the
     film section, and a full-height hero with the column centred in it. */
  .hero { position: relative; overflow: hidden; }

  /* Vertical padding only; horizontal gutter inherited from .wrap.
     One column hard against the left edge: headline, one sentence, the ask,
     and the film immediately under it. The bottom padding is deliberately
     short — "direkt das video drunter" — so the two read as one block rather
     than as a hero and then a separate section. The phone mockup that used to
     hold the right half is parked (see "Phone" below). */
  .hero-inner {
    position: relative; z-index: 1;
    display: flex; flex-direction: column; align-items: flex-start; text-align: left;
    padding-top: clamp(32px, 4.6vw, 60px); padding-bottom: clamp(30px, 3.8vw, 48px);
  }

  h1.display {
    margin: 0; font-weight: 700; letter-spacing: -0.03em; line-height: 1.02;
    font-size: clamp(34px, 4.6vw, 64px); text-wrap: balance;
    color: var(--ink); max-width: 15em;
  }
  h1.display .grad-text { display: inline; }

  /* margin-right auto, not both: the measure caps the line length, the column
     stays flush left under the headline. */
  .sub { font-size: clamp(15.5px, 1.25vw, 17.5px); line-height: 1.6; color: var(--text-secondary); margin: clamp(16px, 1.9vw, 22px) auto 0 0; max-width: 34em; }

  /* ---- Waitlist ---- */
  /* The hero is a centred flex column, so its children are shrink-to-fit by
     default — width:100% is what lets the field use the full measure instead
     of collapsing to its content on narrow screens. */
  .waitlist { margin-top: clamp(28px, 3.6vw, 38px); width: 100%; }
  /* The form sits in the centred closing section, so the field centres in its
     column. It was margin:0 while it lived in the left-aligned hero. */
  .field { display: flex; gap: 12px; width: 100%; max-width: 560px; margin: 0 auto; text-align: left; }
  .field input {
    flex: 1; min-width: 0; height: 68px; padding: 0 24px; font-size: 18px; font-family: inherit; color: var(--ink);
    background: var(--white); border: 1px solid var(--hairline); border-radius: var(--radius);
    outline: none;
  }
  .field input::placeholder { color: var(--text-tertiary); }
  /* Focus changes NOTHING on the box (Paul, 2026-08-30 night: "no ring around
     the email box when clicking in" — the teal border followed the 4px halo
     out; outline was already none). The whole focus signal is the placeholder
     clearing and the caret blinking. Do not bring any ring, border shift or
     glow back. */
  .field input:focus::placeholder { color: transparent; }

  /* The phone-only STATUS line under the field (the resting "Join our
     Waitlist!" label lived one deploy on 08-31 night-4 and left on Paul's
     "looks completely off"). Empty at rest, so :empty in the 560 block keeps
     it hidden until the submit script writes a state into it. Centred: it is
     a moment's toast under a centred field now, not a hanging label. */
  .field-note { display: none; margin: 10px auto 0; max-width: 560px; width: 100%;
    font-family: var(--font-sans); font-size: 14.5px; font-weight: 500;
    letter-spacing: -0.01em; color: var(--text-secondary); text-align: center; }

  .cta {
    display: inline-flex; align-items: center; justify-content: center; gap: 10px; white-space: nowrap;
    height: 68px; padding: 0 30px; border: none; cursor: pointer;
    border-radius: var(--radius); color: #fff; font-family: inherit; font-weight: 700; font-size: 16.5px; letter-spacing: -0.01em;
    position: relative; overflow: hidden;
    background-image: var(--grad-glossy);
    background-blend-mode: multiply, multiply, normal, normal;
    /* Left exactly as it was. A dark edge line was tried on 2026-08-28, first
       as an outer ring and then as an inset hairline, together with closing the
       gradient's open ends so the rim stopped going light — and Paul took all
       of it back off: "make the cta button look like before". The white outer
       ring, the white inset rim and the gradient that fades out at the ends are
       the button. Do not re-propose darkening any of them. */
    /* The outer white brim is GONE (Paul): on a #FAFAFA page it was a pale
       halo with nothing to separate from. Inside the edge is untouched. */
    box-shadow: inset 0 0 2px 0.5px rgba(255,255,255,0.95), inset 0 0 5px 0.5px rgba(255,255,255,0.55), inset 0 2px 6px rgba(255,255,255,0.4), inset -8px -8px 16px rgba(100,180,200,0.65), var(--shadow-accent);
    transition: transform .3s var(--ease-out-soft);
  }
  .cta svg { width: 19px; height: 19px; transition: transform .3s var(--ease-out-soft); }

  /* ---- The header button's hover roll --------------------------------
     Two copies of the label in a clipped one-line box; on hover the stack
     slides DOWN by a full line, so the copy waiting above drops into place
     and the visible one leaves below. That direction is panacea's own,
     measured off their live button: at rest the hidden copy sits 30px above
     the visible one, and on hover both move down 30.

     THE TRANSFORM IS ON THE SPANS, NEVER ON THE BUTTON. A transform on the
     button itself promotes it to its own compositing layer under the
     header's backdrop-filter and leaves colour behind at the rounded corners
     when the layer is thrown away — that is a banked bug, see the log. The
     clipped box is inside the button and has no radius of its own, so there
     are no corners to leave anything at. Verified by forcing the hover in
     headless Chrome and sampling the corner pixels. */
  .nav-cta .roll { position: relative; display: block; overflow: hidden; height: 1.15em; }
  /* .68s (Paul, 2026-08-30: "make the hover scroll animation slower" — was
     .42s): a leisurely roll, still settled before a slow reader's eye moves on. */
  .nav-cta .roll > span { display: block; line-height: 1.15em; transition: transform .68s var(--ease-out-soft); }
  .nav-cta .roll > span + span { position: absolute; left: 0; right: 0; top: 0; transform: translateY(-100%); }
  .nav-cta:hover .roll > span { transform: translateY(100%); }
  .nav-cta:hover .roll > span + span { transform: translateY(0); }
  @media (prefers-reduced-motion: reduce) {
    .nav-cta .roll > span { transition: none; }
    .nav-cta:hover .roll > span,
    .nav-cta:hover .roll > span + span { transform: none; }
    .nav-cta .roll > span + span { display: none; }
  }
  .cta:hover svg { transform: rotate(45deg); }

  /* ---- Phone · PARKED again ----
     It came back on 2026-08-28 as the right-hand half of #features and went
     out the same day: Paul wanted pictures matching the four points there
     instead. `DemoPhone.astro` is intact and still exports its `kc:goto` /
     `kc:touched` hooks, so it can be dropped back into a section of its own
     without rebuilding it. Delete this block if that never happens. */
  .phonewrap { position: relative; display: flex; justify-content: center; align-items: center; }
  /* Responsive width so the phone never fights the gutter or overflows at 320.
     min() caps at the design width and never exceeds the available column.
     340 rather than the hero's 314: it now has a column of its own to fill,
     and the app inside is DOM scaled by --kc-scale, not an image, so a bigger
     frame costs no sharpness. */
  .phone {
    position: relative; z-index: 1; width: min(340px, 78vw); padding: 11px; border-radius: 52px;
    background: linear-gradient(160deg, #2b2b30, #0c0c0e 60%);
    box-shadow: 0 60px 110px -34px rgba(31,74,89,0.5), 0 18px 44px -16px rgba(31,74,89,0.28), inset 0 0 0 1.5px rgba(255,255,255,0.08);
  }
  .phone .notch { position: absolute; top: 22px; left: 50%; transform: translateX(-50%); width: 116px; height: 30px; background: #000; border-radius: 999px; z-index: 3; }
  .phscreen { position: relative; border-radius: 42px; overflow: hidden; background: var(--surface-app); aspect-ratio: 1206 / 2622; }
  .phscreen img { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }

  /* ---- Positioning band ---- */
  /* Vertical padding only; horizontal gutter comes from the inner .wrap. */
  .band { position: relative; padding-top: clamp(16px, 2.5vw, 28px); padding-bottom: clamp(60px, 9vw, 104px); }
  /* Top padding: halved 08-31, then CUT AGAIN 09-01 on Paul's own order
     ("gap is too large. lower please on desktop") — superseding the
     08-31 "zu knapp" reversal, which guarded the old plain-flow band;
     today's band pins and rides the full-span pull. ≤560 pins its own
     28px in the phone block below (the desktop cut is desktop-only). */
  /* No border-top: it drew a full-width hairline immediately under the film
     and read as a separator cutting the page in two. The padding separates. */
  .band h1, .band h2 { margin: 0; line-height: 1.05; font-size: clamp(26px, 4.2vw, 54px); color: var(--ink); text-wrap: balance; max-width: 16em; }
  .band-sub { font-size: clamp(17px, 2.2vw, 19px); color: var(--text-secondary); margin: 18px 0 0; font-weight: 500; }
  /* Equal-height pair: both cards stretch to match so the two read as one balanced set. */
  /* PARKED 2026-08-28: the band's two cards left this grid (for the card
     rows then, the morph now). Nothing carries .twocol right now. */
  .twocol { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: clamp(40px, 6vw, 64px); align-items: stretch; }

  /* PARKED 2026-08-30: the mirrored card rows (.bandrows/.bandrow/.band-visual,
     asendia's testimonial shape, 2026-08-28) left for the morph below — Paul:
     "I don't like the problem and the solution thing", the second mirrored
     row read as the same layout twice. Their rules went with the markup;
     the recipe (65/35 rows, min-height clamp(300px,26vw,380px), absolute
     imgs, grain ::after) is in DESIGN-LOG if the shape ever returns. */

  /* ---- The Problem / The Solution: two chapters and the fog strip ---------
     (2026-08-30 night-3, after night-2's stacked ledger stayed wrong for
     Paul as a LAYOUT; brief: "something super creative", researched
     against the YC Spring/Summer 2026 batches, and "we wanna include our
     nice frizzle picture thing there too" — his own reopening of images
     here after the "no images" close. The batch sweep's strongest
     pattern, which this builds: the problem lives on veiled imagery, the
     answer on flat type — the image treatment itself argues fog ->
     clarity — plus the filmic chapter-strip divider. Prior dead forms:
     card rows, morph, switchers, duo, split-state tile, glass caption
     stage, pinned split, the hairline ledger — DESIGN-LOG, none
     returns.)

     ONE form for every width: "The Problem" and its copy BARE ON THE
     PAGE (Times term at the sec-title voice, copy running under at FULL
     wrap width — the night-2 DNA Paul asked for stays), then the STRIP —
     a shallow full-wrap band in THE FILM'S PRE-SCROLL VEIL (pure source,
     live blur 12 + crizzle 0.7, the .fl-still recipe verbatim) with
     "The Solution" standing IN the fog in white Times — then the
     solution copy clear on the ground. The strip IS the separator now
     (the night-2 hairline died with the ledger). NO scroll stop, ever,
     and NOTHING here fades or rises — the terms' .reveal-h word flip
     (white on the strip) is the only choreography. */
  /* row-gap raised 08-31 with the hairline's removal (the rule was its own
     grid item and dropping it halved the air), then raised AGAIN 08-31
     night-3 on Paul's "increase gap between 'The Problem' and 'The
     Solution'" — roughly double the restored distance, so each chapter
     stands as its own room. */
  .defs { display: grid; margin: 0; row-gap: clamp(84px, 10vw, 150px); }
  /* Copy runs UNDER its term at the full wrap width (Paul, 08-30
     night-2: "die texte drunter laufen" / "run all the way from left
     to right") — that call stands. */
  .def { display: block; }
  .def .def-term { margin-bottom: clamp(10px, 1.4vw, 16px); }
  /* The chapter hairline is PARKED (Paul, 08-31: "remove the seperation
     line between problem and solution"). It lived one day. The span is out
     of index.astro; these rules stay so that re-adding it is one line of
     markup. It was its own grid item, so it also ate a row-gap on each side
     — the gap below compensates for that, do not read it as decoration. */
  .defs-rule { display: block; height: 1px; background: var(--hairline); }
  /* THE FOG STRIP, un-banked for the homepage (Paul, 08-31: "we
     banked one version of the 'The Solution' thing with the
     horizintal image and the glass text on white scroll thing. bring
     it back too") — the night-3 recipe verbatim. Layering inside the
     isolated context: img (blurred, scaled) -> ::before scrim ->
     ::after crizzle -> the term ABOVE the grain: the words are NOT
     part of the fog, the solution stands clear in front of it. */
  .def-strip {
    position: relative;
    height: clamp(200px, 24vw, 330px);
    --strip-r: clamp(18px, 2.2vw, 28px);
    border-radius: var(--strip-r);
    overflow: hidden;
    /* The .fl-stills clip-path, same reason: .def-strip-img is the same
       blurred+scaled composited child inside the same rounded clip, so it is
       exposed to the same WebKit bug on desktop Safari. (On phones the whole
       picture is hidden by the ledger degrade below, so this never bites
       there.) */
    clip-path: inset(0 round var(--strip-r));
    isolation: isolate;
    background: color-mix(in srgb, var(--teal-50) 60%, var(--page));
    margin-bottom: clamp(14px, 1.8vw, 22px);
  }
  .def-strip-img {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; display: block;
    /* The film's pre-scroll veil, standing — the .fl-still recipe
       verbatim: blur 12 ≈ the veil's 22 at the film's width, the
       scale swallows the edge bleed inside the rounded clip. The
       JPEG stays sharp; the veil is live CSS, never baked. */
    filter: blur(12px);
    transform: scale(1.06);
  }
  .def-strip::before {
    content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
    /* Quiet scrim UNDER THE TERM, and the term is centred since 08-31 — so
       this is a soft pool in the middle, not the old bottom-up wash. Ellipse
       rather than a horizontal band: a band draws its own edges across the
       picture, a radial has none. */
    background: radial-gradient(58% 64% at 50% 50%, rgba(8, 34, 46, 0.46), rgba(8, 34, 46, 0) 74%);
  }
  .def-strip::after {
    content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
    /* The veil's own streaked tile at the veil's full 0.7 — half the
       tile's pixels or retina renders it soft. Since 08-31 night the
       opacity reads a custom property so the fog-exchange script can
       breathe it; the fallback IS the standing value, so no-JS and
       reduced motion render exactly the settled strip. */
    background-image: url("/img/veil-grain.png");
    background-size: 210px 420px;
    mix-blend-mode: overlay;
    opacity: var(--fog-op, 0.7);
  }
  /* Compound on purpose: bare .def-term-fog loses its color to the
     base .def-term rule further down (same specificity, later
     source) — the .cta.nav-cta trap. */
  /* CENTRED AND BIG IN THE PICTURE (Paul, 08-31: "let's try a layout where
     the 'The Problem' and 'The Solution' are center and bigger in the
     horizontal pictures"). Was bottom-left at the sec-title's exact size;
     the term is now the strip's subject rather than its caption, so it takes
     its own clamp — between .sec-title's 54 ceiling and the h1's 92. The
     scrim moved with it (see ::before): a bottom-up wash lights nothing when
     the words are in the middle. */
  .def-term.def-term-fog {
    position: absolute; z-index: 3;
    left: 0; right: 0; top: 50%;
    transform: translateY(-50%);
    padding: 0 clamp(18px, 2.6vw, 34px);
    text-align: center;
    font-size: clamp(34px, 5.6vw, 80px);
    margin: 0; color: #fff;
  }
  /* The fog term's word flip runs in white (the site-wide .rw greys
     would drown on the blue): half-white -> white, stroke follows
     currentColor. No-JS never wraps .rw spans and reads the #fff. */
  .def-strip .reveal-h .rw { color: rgba(255, 255, 255, 0.5); }
  .def-strip .reveal-h .rw.on { color: #fff; }
  /* ---- ≤560: THE BAND APPEARS (Paul, 08-31: "on mobile I want the
     'The Problem' and the text below to not be there when you opemn.
     needs appear animation first, and then the grey to black text
     thing to in the headline right after") — his own MOBILE-ONLY
     reopening of the revoked appear; the desktop ledger still never
     fades or rises. Armed as html.bandrv-on by the band script in
     index.astro (≤560 + motion allowed + IO available), so no-JS,
     reduced motion and desktop all see the finished ledger with no
     CSS fork. While armed, the terms' word flip runs as the band
     script's TIMED cascade after the appear lands — Base's
     scroll-driven flip skips them (t.manual). */
  /* The appear runs at EVERY width since 08-31 ("the 'The Problem'
     card needs an scroll appear animation additionally to the h1
     scroll effect ... it appears during that process" — while the
     film shrinks out, the strip rises in). Arming still gates it:
     no-JS and reduced motion see the finished section. On desktop the
     terms keep the scroll-driven flip on top; the timed cascade stays
     phone-only. */
  html.bandrv-on .defs :is(.def, .defs-rule) {
    opacity: 0; transform: translateY(18px);
    transition: opacity .6s var(--ease-out-soft), transform .6s var(--ease-out-soft);
  }
  html.bandrv-on .defs .bandrv-in { opacity: 1; transform: none; }
  /* DESKTOP EXCEPTION (09-01, Paul: "the 'The Problem' picture thing
     needs to already be there and not appear on scroll on desktop"):
     the strip block stands from the first paint; phones keep the
     appear (his own mobile-appear call from 08-31 stands there). */
  @media (min-width: 561px) {
    html.bandrv-on .flowdefs .flow-stripdef { opacity: 1; transform: none; transition: none; }
  }
  /* (THE BAND SCROLL STOP IS DEAD, 08-31 night-3 close — "remove the
     stop scroll animation from the 'The Problem'". Three forms in one
     night: fog morph, through-white retime, word-typing pin; all
     recipes in DESIGN-LOG. Scroll-snap stays dead regardless. The
     ejection PULL on `.defs` is unaffected — it never depended on the
     stop.) */
  @media (max-width: 560px) {
    /* THE LEDGER DEGRADE IS GONE (09-01, Paul: "build it for mobile
       too" — his own revocation of "mobile bleibt wies ist und war"
       and of the older no-images-on-phones call, for this section):
       phones run the REAL strip and the REAL pin now. What stays here
       is phone fit only. Unarmed phones (no-JS, reduced motion) get
       the same static as unarmed desktop: colour strip under the
       standing veil, two plain chapters. */
    .defs-rule { display: block; height: 1px; background: var(--hairline); }
    /* Out-specifies the desktop indent rule, which sits LATER in the
       file — equal weight lost to source order (the .cta.nav-cta
       trap, again). */
    .defs .def .def-copy { padding-left: 0; }
    /* Shallower window at phone width: the 2.9:1 image against the
       200px floor showed 60% of the meadow; 42vw shows ~75%. */
    .flow-strip { height: clamp(148px, 42vw, 200px); }
    /* The band -> #features seam on phones: cut from 60+64 to 24+28 on
       09-01, then OPENED A STEP the same day (Paul: "slightly bigger
       gap between the end of the 'The Solution' text and the headline
       'Built around the people who matter'") — 32+40 = 72px total. And
       padding-top HOLDS its own value here — the 09-01 desktop gap cut
       ("lower please on desktop") is desktop-only. */
    .band { padding-top: 28px; padding-bottom: 32px; }
    .section.featline { padding-top: 40px; }
    /* THE FUF RISE (09-01 pass 5, Paul: "auf mobile soll das 'The
       Problem' bild nicht einfach beim scrollen spawnen. das sollen
       wie bei fuf.de elemente einzeln so hoch") — fuf.de's own
       reveal, measured off their live mobile CSS (.paragraph
       .container): travel 200px, transform .3s ease-out under a .5s
       ease-out fade, each block individually. Phones only; desktop
       keeps the subtle 18px rise (the strip there stands from first
       paint anyway). The start-state override carries :not(.bandrv-in)
       because it ties the base in-state rule at (0,3,1) and sits
       LATER in the file — without it the 200px start would win the
       tie on source order and nothing would ever rise (.cta.nav-cta
       lesson, again). The rise trigger moves with it: with 200px of
       travel the element rises INTO view across the bottom edge, so
       the observer may fire at entry (the -30% line belonged to the
       18px in-place pop, which had to happen deep in the viewport to
       be seen at all). */
    html.bandrv-on .defs :is(.def, .defs-rule) {
      transition: opacity .5s ease-out, transform .3s ease-out;
    }
    html.bandrv-on .defs :is(.def, .defs-rule):not(.bandrv-in) {
      transform: translateY(200px);
    }
  }
  /* (THE FOG-STRIP CHAPTERS ARE BANKED HERE, 2026-08-30 night-3 close
     — Paul: "es sieht hammer aus ... aber es passt nicht ganz in die
     startseite einfach". Approved as an ELEMENT, parked for another
     surface: full recall recipe in DESIGN-LOG's night-3 "BANKED"
     entry, strips stay in public/img/band via
     tools/build_band_strip.py. Their rules left with the markup.) */
  /* Times display voice via the shared display-set rule above, at THE
     SEC-TITLE'S EXACT SIZE: stacked full-width, each term is a display
     moment of its own — the section's only one — and the smaller
     26-44px cut read timid between the film and "Built around the
     people who matter." If .sec-title's clamp moves, this moves with
     it. */
  .def-term { margin: 0; font-size: clamp(27px, 4.2vw, 54px); line-height: 1.05; color: var(--ink); }
  /* SUPER-SLIGHT DENT (Paul, 08-31: "slightly dent in the text to
     the right. but super slightly") — a breath of indent under the
     strips, far short of the banked --term-inset docking; mobile
     ledger stays flush (media block resets it). It came out for one
     iteration with the centred column and came straight back with the
     full width. */
  .def .def-copy { padding-left: clamp(8px, 1vw, 14px); }
  /* CENTRED AGAIN (09-01, "make both texts below the images align
     center, instead of left" — every width; the typing re-centres each
     landing line and Paul chose it anyway). Dent zeroed with the
     centring: an asymmetric indent pushes the centring off-axis (the
     night-3 lesson). */
  .defs .def .def-copy { text-align: center; padding-left: 0; }
  /* (The ≤560 left-rag override left with the ledger on 09-01 — the
     copy is centred at every width now.) */
  /* (The copies carried .reveal-h for one iteration on 08-31 — word
     fill through scroll — and Paul pulled it the same hour: "dont
     apply the scroll reveal thing to text". The flip is the TERMS'
     device; body copy stands still. Guards recipe in DESIGN-LOG.) */
  /* THE LEDE-SUB'S EXACT SETTING — Paul sized this same copy to the
     standfirst in the morph era ("so groß wie ... KinCompanion lets you
     create a companion"); the two move together. */
  /* FULL measure on purpose (Paul, 08-30 night-2: "run all the way from
     left to right") — no max-width, the copy spans the wrap. */
  /* FULL WIDTH AT THE LEDE-SUB'S SETTING, and both halves of that are
     Paul-chosen. Two alternatives were built and rendered side by side on
     08-31 after "aus irgendeinem grund sieht der text drunter auch kacke
     aus" — a capped centred column and a larger full-width type — and he
     picked THIS one back off the sheet ("nimm A"). So the record is:
     **the measure is not the knob and neither is the size.**
     - Capped + centred is DEAD: it cuts the copy loose from the picture's
       edges and floats it as an island under a full-bleed strip. Paul, on
       sight: "noch viel schlimmer behinderter". Do not re-propose it.
     - The 22px cut is banked in DESIGN-LOG, not live.
     Full wrap width is his own 08-30 call ("die texte drunter laufen / run
     all the way from left to right"), and the size stays THE LEDE-SUB'S
     EXACT SETTING — the two were sized together and still move together. */
  .def-copy {
    margin: 0;
    font-size: clamp(17px, 2.2vw, 19px); line-height: 1.556; letter-spacing: -0.011em;
    color: var(--text-secondary);
  }
  /* The Problem runs to three paragraphs since 08-31. One em of the copy's
     own line-height between them — the blocks read as one voice taking a
     breath, not as a list. */
  .def-copy + .def-copy { margin-top: 0.95em; }

  /* ---- THE FLOWER PIN (08-31 night-4) --------------------------------
     Paul's own form and his own revocation of the dead scroll stop
     ("wenn man scrollt bleibt man dann auf scroll stecken erstmal"):
     ONE strip — the bare bud in b/w under the frizzle veil, blooming
     into the coral daisy in colour — pinned with the night-2/3
     sticky-hold mechanics (HEIGHT drive on the band's .wrap, never
     padding: a sticky child travels only within its parent's CONTENT
     box; and never scroll-snap). The problem copy TYPES itself in word
     by word through scroll (the night-3 typing form, un-banked), then
     the image blooms, the term crossfades left-aligned, the copy hands
     over. All choreography is inline writes from the pin script;
     unarmed (no-JS, reduced motion, ≤560) nothing here fires and the
     static page stands: colour flower under the standing veil, two
     plain chapters below. */
  .flowdefs { row-gap: clamp(48px, 6vw, 84px); }
  /* Overlay terms exist only for the pin; the plain ink terms are the
     semantic ones and the phones' heads. */
  .flow-term { display: none; }
  /* TOP CENTRE (the field composition's own spot; the bottom-left +
     upside-down round lived one deploy on 09-01 and was reverted —
     "revert on the flip and headline movement thing"). Size stays the
     night-4 step-smaller. Same specificity as the centred base rule
     (0,2,0) — source order wins, so this stays BELOW
     .def-term.def-term-fog in the file. */
  .def-term.flow-term { text-align: center; left: 0; right: 0;
    top: clamp(22px, 3.2vw, 46px); bottom: auto; transform: none;
    font-size: clamp(28px, 4vw, 58px);
    padding: 0 clamp(18px, 2.6vw, 34px); }
  /* THE OLD GLASS-TO-WHITE WORD FILL, back on both terms (09-01, "i
     need per word fill out with glass to white like we hadd it
     before") — the banked fog-term flip's exact half-white, but driven
     by the PIN's own windows now (Base's scroll flip cannot drive a
     pinned element: it stops moving through the viewport, so the count
     freezes). Spans are .twh, wrapped by the pin script only. */
  .flow-term .twh { color: rgba(255, 255, 255, 0.5); transition: color .22s linear; }
  .flow-term .twh.on { color: #fff; }
  /* PHONES: terms AND copies rag LEFT (09-01, Paul: "on mobile make the
     text below the ... pictures + the headlines problem and solution
     itself align left"). Desktop keeps the centred set. Placed AFTER
     both base rules on purpose — a media query adds no specificity, so
     only source order lets these win (the .cta.nav-cta lesson). */
  @media (max-width: 560px) {
    .def-term.flow-term { text-align: left; }
    .defs .def .def-copy { text-align: left; }
  }
  /* The baked ground already carries the term (dark slate left); the
     radial scrim pool would just grey the flower. */
  .flow-strip::before { display: none; }
  /* The standing frizzle is 10% lighter on this strip (Paul, 09-01: the
     bare buds and stems were smeared away — "slightly more visible
     through less blur ... like 10% ish"). The pin script writes inline
     from the same 10.8 so armed and unarmed agree. */
  .flowdefs .def-strip-img { filter: blur(10.8px); }
  /* PHONES: the frizzle at phone strength (09-01, Paul: "the black n
     white flowers can be barely be seen as flowers. too much blury
     thing"). The strip is ~340px wide at 390 against ~1116 on desktop,
     so the shared 10.8px blur is proportionally ~3x heavier there and
     smeared the buds to nothing. Blur and crizzle both come down; the
     pin script writes the same phone numbers inline so armed and
     unarmed agree. Placed AFTER the base rule on purpose — a media
     query adds no specificity, only source order lets it win (the
     .cta.nav-cta lesson). Desktop numbers are Paul-tuned and stay. */
  @media (max-width: 560px) {
    .flowdefs .def-strip-img { filter: blur(4.2px); }
    .flow-strip { --fog-op: 0.52; }
  }
  /* Colour sits over b/w (DOM order); unarmed it simply wins and the
     static strip is the settled colour state. */

  /* THE CHASE'S LAYER LIVES FROM LOAD, AND IT IS THE WRAP'S — NEVER
     THE STICKY'S (09-01, two passes of Paul's "supper laggy, jittery",
     then "es bleibt nicht perfekt stehen beim scroll stop sondern
     ruckelt hoch runter"): the ejection chase translates the band, and
     without will-change the compositor built the texture — two
     full-width blurred images plus the blend grain — on the FIRST
     write mid-scroll and threw it away at the pin: a hitch at each end
     of the scroll-in. But the transform (and this hint) must sit on
     #building's .wrap, NOT on .flowdefs: .flowdefs is the PINNED
     STICKY, and a transform node of its own takes the pin off the
     compositor's clean sticky path — with the typing sequence
     committing every frame, the pinned strip juddered up and down.
     The wrap is the sticky's parent: pre-pin the two move as one, at
     the pin the wrap's transform is already 0 by the runway maths.
     Desktop only — phones never chase (MQ_STATIC). */
  @media (min-width: 1025px) {
    #building > .wrap { will-change: transform; }
  }
  html.bandpin-on .flowdefs { position: sticky; row-gap: clamp(22px, 3vw, 36px); }
  html.bandpin-on .flowdefs .flow-stripdef { grid-row: 1; grid-column: 1; }
  /* Both chapters in ONE cell (the night-2 mechanic): the cell is as
     tall as the taller chapter, so nothing jumps at the handover. */
  html.bandpin-on .flowdefs .flow-ch { grid-row: 2; grid-column: 1; }
  html.bandpin-on .flowdefs .flow-term { display: block; }
  html.bandpin-on .flowdefs .flow-ink { display: none; }
  /* Start states; the pin script owns them from the first frame via
     inline writes (with inline transition:none — the bandrv .def
     transition would drag scroll-fed opacities 600ms behind the
     wheel, the night-2 lesson). Placed AFTER the bandrv rules in the
     file: html.bandpin-on .flowdefs .flow-ch-s (0,3,1) ties
     html.bandrv-on .defs .bandrv-in and later source wins, so the
     Solution block stays dark until its window even once bandrv-in
     lands. */
  html.bandpin-on .flowdefs .flow-ch-s { opacity: 0; }
  html.bandpin-on .flowdefs .flow-img-color { opacity: 0; }
  html.bandpin-on .flowdefs .flow-term-b { opacity: 0; }
  /* The two strip images get their OWN layers while the pin is armed —
     on the CHILDREN, never on .flowdefs itself (the banked sticky rule).
     Without them, every q2 opacity step of the bloom and every qblur
     step re-rastered the whole strip stack — both 2208x760 sources
     through their Gaussians plus scrim and grain — inside the sticky's
     one texture, measured as the blur window's dropped frames. Promoted,
     the opacity steps are property-tree updates and a blur step
     re-rasters only its own image. */
  html.bandpin-on .flowdefs .def-strip-img { will-change: filter, opacity; }
  /* The typing: spans hold their space from the start (opacity, never
     display), so the copy block's height — and with it the stage and
     the sticky top — never moves while words land. */
  /* GHOST WORDS, not invisible ones (09-01, Paul: "no one can really
     tell when the word for word animation text has stopped. makes it
     not really user friendly"): the untyped rest stands as a faint
     shadow of the whole paragraph, so the reader always sees how much
     is left and — the moment nothing faint remains — that it is done.
     The sequence itself is untouched; only the resting state changed. */
  /* COLOR, never opacity (09-01 pass 6): an opacity transition is
     compositor-accelerated, so every typed word spawned a transient
     composited layer for its 160ms — ~90 per pass, measured as ALL the
     dropped frames in the typing window. A color fade paints the same
     pixels (pure text over the page ground) with no layer churn; same
     device as the .twh term fill. 16% mix = the old opacity 0.16. */
  html.bandpin-on .flowdefs .def-copy .tw {
    color: color-mix(in srgb, var(--text-secondary) 16%, transparent);
    transition: color .16s linear;
  }
  html.bandpin-on .flowdefs .def-copy .tw.on { color: var(--text-secondary); }
  /* (A 3-column named-pains row — Times pre-heads, inset hairlines between,
     the Marble/YC-S26 device — lived here for one iteration on 08-31 night
     and died on Paul's sight: "looks ass". Full recipe in DESIGN-LOG. The
     copy is the two full-width paragraphs again, THE LEDE-SUB'S EXACT
     SETTING below.) */
  /* (The closing line lost its <b> 2026-08-30 night-2, Paul: "unbold
     this" — the copy is one even voice now.) */
  /* (No narrow fork any more: the stacked form IS the form.) */

  /* ---- The scroll stage (desktop #building) -------------------------------
     Paul's own revival, 2026-08-30 night-2 ("lets try that image
     transforms from black n white to colorful through scroll again and
     text + headline changes simultaneously to the color shift too") —
     his call reopens the endgame's scroll-switcher ban for this section.
     The ledger + panorama above is the <941px AND reduced-motion form
     and stays EXACTLY as approved ("looks good on mobile. keep it the
     way it is there").

     One pinned stage: the sticky is a full-viewport box with the 21:9
     panorama centred in it, so the pinned scroll length IS --ps2-drive
     (one knob), independent of stage size. The drive writes --ps2-t
     (copy swap) and --ps2-g = 1 - t (grayscale release) on the sticky —
     simultaneous by construction. The caption panel is the film's glass
     button material verbatim; the two copies stack in one grid cell and
     swap OUT-BEAT-IN: problem at 1-2t, solution at 2t-1, opacity's own
     [0,1] clamp holding the ends (the morph era's proven recipe — at the
     midpoint neither reads, no double exposure). Resting defaults (t=0,
     g=1) ARE the entry state: b/w, The Problem showing. The hold never
     collapses, so the one-way dwell's Safari compensation trap does not
     apply here. */
  /* (The night's ps2 image variants — glass stage, under-row still, split
     swap, tall reveal portrait — and the ps2/defs-wrap visibility fork
     all left with "no images for the problem the solution"; recipes in
     DESIGN-LOG. The reveal now lives on the ledger itself, above.) */
  /* ---- Cards: clean white panels (Slashy-style: white fill, soft 28px radius,
     hairline edge + a whisper of lift). PARKED AGAIN 2026-08-30 night: the
     band rows and their .bandcard consumers left for the split-state
     element, and nothing carries .pcard now — the recipe never moves. */
  .pcard {
    background: var(--white);
    border: 1px solid #E7E7EA;
    border-radius: 28px;
    padding: clamp(22px, 2.6vw, 30px);
    box-shadow: 0 1px 2px rgba(24, 24, 27, 0.04), 0 8px 24px -18px rgba(24, 24, 27, 0.18);
    transition: transform .7s cubic-bezier(.22, 1, .36, 1);
  }
  /* Hover: the same lift as before, just weaker (-1.5px vs -3px), slower
     (.7s vs .35s), and with no shadow change at all — the resting shadow
     above stays fixed. Reduced-motion users get no animation via the global
     reduced-motion block; touch layouts disable the lift below. */
  .pcard:hover {
    transform: translateY(-1.5px);
  }
  .pcard p { margin: 0; font-size: clamp(16px, 2.1vw, 17.5px); line-height: 1.62; color: #52525b; }
  .pcard p b { color: #18181b; font-weight: 700; }
  /* Times, from the rule above. The Inter version was 700 at
     clamp(18px,2.2vw,21px); Times 400 at the same size reads materially
     smaller and lighter next to the Inter body copy under it, so the size goes
     up a step and the line-height opens with it. font-weight and
     letter-spacing are NOT repeated here — they would win on source order and
     put the card heads back on a bolder setting than the four display
     headlines. */
  .pcard h3 { margin: 0 0 8px; font-size: clamp(18px, 2.2vw, 21px); line-height: 1.25; color: var(--ink); }
  /* Card icon (Cuida set, inlined SVGs): bare glyph beside the headline,
     sized to exactly the headline's font size (same clamp as .pcard h3). */
  .card-head { display: flex; align-items: center; gap: 10px; margin: 0 0 8px; }
  .card-head h3 { margin: 0; }
  /* Accent: Heart Coral — the decorative complement to Companion Teal from the
     design system (the logo heart's color; decorative only, never a button). */
  .card-head .picon { flex: none; display: inline-flex; color: var(--heart); }
  /* Sized to the headline's cap height beside it. */
  .card-head .picon svg { width: clamp(18px, 2.2vw, 21px); height: clamp(18px, 2.2vw, 21px); }
  .cardgrid { display: grid; gap: 24px; margin-top: clamp(36px, 6vw, 56px); }
  .cardgrid-2 { grid-template-columns: 1fr 1fr; }

  /* ---- #features: copy list left, live phone right ------------------------
     slashy.com's section, measured off their live page: a flex row, 48px gap,
     copy column at 42% capped around 523, headline 36/1.02 serif, then four
     items at a 129px pitch, each one a 20/26 weight-500 title over a 16/20.8
     description, with a hairline under it. The active item is inked and the
     rest are dimmed.

     Ours matches theirs in the right-hand half: one still per item, cross-
     faded as the active item changes. The stills are frames of our own launch
     film under withpanacea.com's treatment, blurred until figures read as
     shapes and faces never resolve. The list is a real <ol> of <button>s
     because clicking one does something.

     The live phone demo occupied this column for a few hours on 2026-08-28
     and was replaced by these; DemoPhone is parked again, intact.
     ------------------------------------------------------------------------ */
  /* A GRID, not a flex row, and that is the whole point. The still has to be
     exactly as tall as the four-item list — Paul's call — and no fixed aspect
     ratio can promise that, because that height moves with the type scale, the
     wrapping and the viewport. So the headline takes row 1 of the left column,
     the list takes row 2, and the still SPANS BOTH ROWS of the right column
     and stretches into them. The left column measures its own height and the
     picture simply is that height, at every width, for free.

     The first version was a flex row with a 4:3 box, and it left the picture
     stranded in the middle of a much taller column. */
  .featline-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) min(520px, 46%);
    /* auto auto, NOT auto 1fr. With 1fr the second track can grow to fill a
       container whose height it is itself deciding, and the still came out 27px
       past the bottom of the last hairline. Two auto tracks are exactly the
       headline and exactly the list, and the still spanning both is exactly
       their sum. */
    grid-template-rows: auto auto;
    column-gap: clamp(40px, 5vw, 72px);
    /* The gap under the headline is the GRID's, not the list's. It used to be
       a margin-top on .featline-list, which the still could not see: the still
       stretched from the top of that margin and came out 44px taller than the
       list and 44px higher up. Row-gap moves both cells together. */
    row-gap: clamp(28px, 3.4vw, 44px);
    align-items: start;
  }
  .featline-head { grid-column: 1; grid-row: 1; max-width: 15em; }
  .featline-list { grid-column: 1; grid-row: 2; }
  .featline-visual { grid-column: 2; grid-row: 1 / 3; align-self: stretch; min-height: 0; }

  /* ---- The stills --------------------------------------------------------
     All four are in the DOM, stacked in a grid cell so the box has a height
     without anything being absolutely positioned, and only opacity moves. A
     3:2 aspect-ratio holds the row's height steady before a single byte of
     image has arrived, so nothing jumps as they load.

     The grain is the SAME --grain tile as the pre-footer bloom and the footer,
     at the same 120px size. Only the blur is baked into the JPEGs. That keeps
     one texture across the whole page instead of three that drift apart, and
     it keeps the files at ~45 KB each — grain is high-frequency noise and is
     precisely what image compression is worst at. */
  .fl-stills {
    position: relative;
    /* No aspect-ratio: the height comes from the headline-plus-list beside it,
       and object-fit on the images absorbs whatever ratio that works out to. */
    width: 100%; height: 100%; min-height: 260px;
    --fl-r: clamp(14px, 1.6vw, 20px);
    border-radius: var(--fl-r); overflow: hidden;
    /* AND the same radius as a real clip-path, because on iOS Safari the
       border-radius half of `overflow:hidden` is dropped for COMPOSITED
       descendants (WebKit 98538 / 254151) and every child in here is one:
       .fl-still carries filter:blur(12px) + transform:scale(1.06), which is
       two reasons to promote it. Paul, 08-31 night-4: "when going through the
       quad picture element if i click through them manually they don't have
       rounded corners" — the tap is what promotes the layers, which is why
       the auto-advance looked fine and tapping did not. `isolation: isolate`
       below is the usual prescription for that bug and it is ALREADY here and
       already insufficient; clip-path is a genuine layer clip rather than a
       stacking-context hint, so it survives promotion. Cannot be verified on
       device here (no Xcode, no Simulator on this Mac); it is inert in Chrome,
       where the box is identical with and without it. */
    clip-path: inset(0 round var(--fl-r));
    background: color-mix(in srgb, var(--teal-50) 60%, var(--page));
    isolation: isolate;
  }
  /* ABSOLUTE, and that is load-bearing. Stacked in a grid cell they were still
     in flow, so their intrinsic 900x1200 fed the grid's min-content sizing:
     at 506 wide that is 675 tall, the container took that as a definite height,
     and `align-content: normal` then stretched BOTH auto tracks by 14px each to
     fill it. The headline and the list each grew, and the picture hung 27px
     past the last hairline. Out of flow they contribute no height at all, so
     the tracks are exactly the headline and exactly the list, and the picture
     is exactly their sum. */
  .fl-still {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; display: block;
    opacity: 0; transition: opacity .7s var(--ease-out-soft);
    /* THE FILM'S PRE-SCROLL VEIL, standing (Paul, 2026-08-30 night-2:
       "the blue theme images they need the same frizzle blur thing
       effect as the teaser video pre scroll"): CSS blur on the pure
       source — 12px here ≈ the veil's 22px at the film's width, same
       ratio — with the scale swallowing the edge bleed inside the
       rounded clip, exactly the veil's own recipe. The JPEGs stay
       sharp; the veil is live CSS, never baked. */
    filter: blur(12px);
    transform: scale(1.06);
  }
  .fl-still.is-shown { opacity: 1; }
  .fl-stills::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    /* The film veil's streaked CRIZZLE tile since 2026-08-30 last (Paul:
       "apply the no pixel logic only crizzle thing to all pictures") —
       the page --grain stays on the bloom and the footer only. Quieter
       than the veil's 0.7: standing pictures, not a curtain. The old
       0.14-of---grain tuning story lives in DESIGN-LOG. */
    background-image: url("/img/veil-grain.png");
    background-size: 210px 420px;
    mix-blend-mode: overlay;
    /* THE VEIL'S OWN 0.7 (was 0.22 tile / 0.32 bright-pure): the stills
       wear the film's full pre-scroll crizzle now, blur + streaked
       grain together (Paul, 2026-08-30 night-2: "the same frizzle blur
       thing effect as the teaser video pre scroll"). */
    opacity: 0.7;
  }
  @media (prefers-reduced-motion: reduce) {
    .fl-still { transition: none; }
  }

  /* Their 36px gap from the headline down to the first item. */
  .featline-list { list-style: none; margin: 0; padding: 0; }

  .fl-item { position: relative; }
  /* One button per item, so the whole block is the target and the keyboard
     gets it for free. Reset rather than restyle: the site's .cta rules must
     not reach in here. */
  .fl-btn {
    display: block; width: 100%; text-align: left;
    background: none; border: 0; padding: 0 0 clamp(16px, 1.8vw, 22px);
    margin: clamp(16px, 1.8vw, 22px) 0 0;
    font: inherit; color: inherit; cursor: pointer;
  }
  .fl-head { display: flex; align-items: center; gap: 10px; }
  .fl-title {
    font-size: clamp(18px, 1.9vw, 20px); line-height: 1.3;
    color: var(--ink);
    transition: color .35s var(--ease-out-soft);
  }
  .fl-desc {
    display: block; margin-top: 7px; max-width: 34em;
    font-size: clamp(15.5px, 1.7vw, 17px); line-height: 1.45;
    color: var(--text-secondary);
    transition: color .35s var(--ease-out-soft);
  }
  /* (The coral .picon glyphs left the list 2026-08-30 evening, Paul:
     "remove icons here" — bare titles now.) */

  /* Dimmed until it is the active one. Colour only — nothing moves, nothing
     collapses, so the list never reflows as it advances and all four
     descriptions stay readable. */
  .fl-item:not(.is-active) .fl-title { color: color-mix(in srgb, var(--ink) 42%, transparent); }
  .fl-item:not(.is-active) .fl-desc  { color: color-mix(in srgb, var(--text-secondary) 55%, transparent); }
  .fl-item:hover .fl-title { color: var(--ink); }

  /* The hairline under each item, and the timer that fills it. Theirs is the
     progress indicator for the auto-advance and it is the only moving part in
     the section. */
  .fl-rule { display: block; height: 1px; background: var(--hairline); position: relative; overflow: hidden; }
  .fl-fill {
    position: absolute; inset: 0;
    /* A darker grey, not teal (Paul, 2026-08-28): the timer is a mechanism,
       not an accent. --grey-500 is the darkest grey token on the page. */
    background: var(--grey-500);
    /* scaleX, never width: a width transition is a LAYOUT animation, one
       forced layout per frame for the whole 6s dwell — and it kept running
       after the section scrolled away (the script freezes it on exit now).
       The bar is 1px with square ends, so the scale is pixel-identical. */
    transform: scaleX(0); transform-origin: left center;
  }
  .fl-item.is-active .fl-fill { transform: scaleX(1); transition: transform var(--fl-dwell, 6s) linear; }

  @media (max-width: 900px) {
    /* One column: headline, list, then the still under them. It gets an
       aspect ratio back here, because there is no list beside it to take a
       height from. */
    .featline-row { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto; }
    .featline-head { grid-column: 1; grid-row: 1; max-width: none; }
    .featline-list { grid-column: 1; grid-row: 2; }
    .featline-visual { grid-column: 1; grid-row: 3; margin-top: clamp(32px, 6vw, 52px); }
    .fl-stills { height: auto; aspect-ratio: 4 / 3; }
  }
  @media (prefers-reduced-motion: reduce) {
    /* No timer and no dimming: with nothing advancing, a dimmed item is just
       unreadable text. The list still drives the phone on click. */
    .fl-fill { display: none; }
    .fl-item:not(.is-active) .fl-title { color: var(--ink); }
    .fl-item:not(.is-active) .fl-desc  { color: var(--text-secondary); }
  }

  /* ---- Consistent section system + repeating card grids ---- */
  .section { position: relative; padding: clamp(64px, 9vw, 104px) 0; }
  /* No text-wrap: balance. .sec-title carries an explicit <br> now and
     balancing fights one, the same as on .lede-title and .cta-title. */
  .sec-title { margin: 0; line-height: 1.05; font-size: clamp(27px, 4.2vw, 54px); color: var(--ink); max-width: 16em; }
  .sec-lead { font-size: clamp(17px, 2.2vw, 19px); color: var(--text-secondary); margin: 18px 0 0; font-weight: 500; max-width: 34em; }

  /* Scroll reveal: section headlines turn from light grey to ink WORD BY WORD
     in reading order (beginning of the sentence to the end, across line
     wraps) as the headline travels up the viewport. JS wraps each word in a
     .rw span and flips .on per scroll progress; the color transition smooths
     the boundary word. Plain colors — no background-clip — so glyph
     descenders render fully. No-JS / reduced-motion users get an unwrapped,
     plain ink headline. */
  .reveal-h .rw { color: #C7C7CC; transition: color .25s linear; }
  .reveal-h .rw.on { color: var(--ink); }
  /* (The .arv appear-reveal system — allia's fade+rise — lived under two
     hours on 2026-08-30 night-2 and was REVOKED whole ("don't like it at
     all"): rules, script and classes are gone. The word flip above is
     the page's one scroll choreography besides the film. Recipe in
     DESIGN-LOG if ever recalled.) */

  /* ---- Final CTA block ---- */
  /* ---- The bloom before the footer ---------------------------------------
     useauxos.com's pre-footer layer, structure copied 1:1 out of their
     computed styles: five stacked gradients — four radials pinned to fixed
     percentage origins over one 160deg linear base — under a linear mask that
     is transparent at the top and solid further down.

     Their positions and stops are kept exactly. Only the colours are ours, and
     they are one step lighter than a direct translation would be: the ramp
     ENDS ON --foot-edge, which IS the top-left stop of the footer's own ramp
     (one variable, read by both, so they cannot drift apart), because ending
     on --teal-500 (the true colour match to their #4364F7) made the bloom
     darker than the footer it met. Theirs has no such problem: their footer
     is white. It was --aqua-300 until 08-31, when the footer left the vivid
     aquas for the CTA's glossy set and this followed it.

     THREE THINGS CHANGED on 2026-08-28, all of them Paul's calls:

     1. The cloud starts BELOW THE HEADLINE, not inside it. It used to run from
        160px above the section, with the mask at 7% by 40% of the way down —
        which put roughly 12% of the cloud behind the top of "Be there, even /
        when you can't." --cta-head is the headline's own bottom edge, built
        out of the same clamps that size it (top padding + two lines at
        line-height 1.05), so it tracks the type instead of being a guess.
     2. There IS a grain layer now (::after). useauxos has none — that was
        measured twice, once off their computed styles and once off a lossless
        2x screenshot, whose residual noise (3.6) is Chrome's own gradient
        dither and is if anything LOWER than ours was (4.9). The speckle Paul
        is pointing at in their screenshot is compression. He wants the texture
        anyway, so it is built rather than borrowed.
     3. The handoff ::after ramp is GONE, replaced by the footer bleeding
        upward over the bloom. See --foot-bleed at the footer.

     This replaced a 22px grey dot grid — also theirs, from the section above
     that one — which at their alpha was invisible ("ich sehe die wolke nicht").
     Nothing here animates; theirs does not either.
     ------------------------------------------------------------------------ */
  .cta-section {
    position: relative;
    /* isolate, so the grain's mix-blend-mode blends against this section and
       not against the white page and the footer underneath it — and an
       explicit background with it, because an isolated group starts on a
       TRANSPARENT backdrop and a blend layer over transparent paints its own
       raw colour straight through. --page is what body is set to, so this is
       the page's own ground, named rather than assumed. */
    isolation: isolate;
    background: var(--page);
    overflow: hidden;
    /* The headline's own bottom edge, from the same clamps that size it:
       section top padding + two lines of .cta-title at line-height 1.05. The
       <br> after "even" is what makes it reliably two. */
    --cta-head: calc(clamp(72px, 10vw, 120px) + 2.1 * clamp(32px, 5vw, 60px));
    /* Bottom padding is deeper than the top, for two reasons: the footer bleeds
       up over the last --foot-bleed of this section and the form has to stay
       clear of that, and Paul asked for more air between the two on top of it.
       At 1440 this leaves 300px under the form, of which the footer's fade
       takes the last 170, so the form sits 130px clear of even the faintest
       teal. */
    /* Deliberately enormous (Paul). Grew in step with --foot-bleed when the
       fade lengthened (floor +24, +4vw, cap +70), so the plain ground under
       the form is the same ~290px at 1440 it always was — the extra padding
       is all fade. */
    padding: clamp(72px, 10vw, 120px) 0 clamp(304px, 36vw, 530px);
    text-align: center;
  }
  .cta-section::before {
    content: "";
    position: absolute; top: var(--cta-head); left: 0; right: 0; bottom: 0;
    pointer-events: none; z-index: 0;
    background-image:
      radial-gradient(120% 90% at 15% 20%, #F7FCFD 0%, transparent 55%),
      radial-gradient(110% 100% at 85% 15%, var(--teal-50) 0%, transparent 60%),
      radial-gradient(130% 110% at 70% 90%, var(--foot-edge) 0%, transparent 62%),
      radial-gradient(100% 90% at 25% 80%, var(--teal-100) 0%, transparent 58%),
      linear-gradient(160deg, var(--teal-50) 0%, var(--teal-100) 55%, var(--foot-edge) 100%);
    /* In percent, not pixels: theirs is a fixed 380px ramp over a fixed-height
       section, ours has to hold from a phone to a 1600px screen. Starting from
       nothing at the top is what keeps the headline on plain white. */
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.10) 26%, rgba(0,0,0,0.58) 64%, #000 100%);
    mask-image: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.10) 26%, rgba(0,0,0,0.58) 64%, #000 100%);
  }
  /* ---- The grain ---------------------------------------------------------
     One tiled feTurbulence tile, inline as a data URI so it costs no request
     and no file in public/. fractalNoise at baseFrequency 0.62 puts a noise
     cell at roughly 1.6px, which is the "pixelig" end of grain rather than the
     fine film-grain end; saturate 0 takes the colour out so it modulates
     lightness only. overlay, not soft-light: soft-light over a near-white base
     barely moves it (measured at 2.8 residual, under Chrome's own gradient
     dither), where overlay keeps the hue and still carries real texture.

     It rides the SAME shape as the cloud and then fades back out over the last
     --foot-bleed, because the footer carries its own copy of this tile over
     that stretch. Without the fade the two grains stack and the overlap band
     reads as a texture seam, which is the thing this is meant to remove.
     ------------------------------------------------------------------------ */
  .cta-section::after {
    content: "";
    position: absolute; top: var(--cta-head); left: 0; right: 0; bottom: 0;
    pointer-events: none; z-index: 0;
    background-image: var(--grain);
    background-size: 120px 120px;
    background-repeat: repeat;
    mix-blend-mode: overlay;
    opacity: 1;
    /* CAPPED AT 0.69, never full (Paul, 2026-08-28: "the pixel density gets
       dramatically more in the fade in effect"). overlay's response is
       2*(1-base), so as the bloom deepens toward aqua this same tile at full
       mask measured 12.7 residual at the footer's edge against 8.7 on the
       footer itself — a 45% overshoot, and the visible density explosion.
       0.69 is 8.7/12.66: the bloom grain now ARRIVES at the footer's own
       level and holds it, so the texture reads as one surface with no spike.
       The cap starts exactly at the footer's edge — calc(100% - --foot-bleed)
       — so it holds at every viewport, and the ramp up to it is stretched
       over the whole lower bloom instead of slamming to full at 62%.
       Below the edge it stays FLAT: the footer covering it at rising alpha is
       the cross-fade (masking this copy out too attenuated it twice — banked). */
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.25) 26%, rgba(0,0,0,0.69) calc(100% - var(--foot-bleed)), rgba(0,0,0,0.69) 100%);
    mask-image: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.25) 26%, rgba(0,0,0,0.69) calc(100% - var(--foot-bleed)), rgba(0,0,0,0.69) 100%);
  }
  /* The copy sits ABOVE the bloom. Without this the pseudo-elements — which
     are positioned and therefore paint over normal-flow content — swallowed
     the standfirst and the email field whole. */
  .cta-inner { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; }
  /* No text-wrap: balance — the <br> after "even" owns the break and balancing
     fights it, exactly as on .lede-title. */
  /* Same size as the h1, exactly (Paul). Written out rather than shared:
     only the SIZE is meant to track, the measures differ. */
  .cta-title { margin: 0; line-height: 0.96; font-size: clamp(33px, 6.4vw, 92px); color: var(--ink); max-width: 14em; }
  .cta-sub { margin: 18px 0 0; font-size: clamp(17px, 2.2vw, 20px); color: var(--text-secondary); max-width: 32em; }
  /* .cta-lg is gone with the button it styled: this section holds the real
     waitlist form now, not a link that scrolled up to one. */

  /* ---- Footer (vivid gradient) ---- */
  /* Soft approach zone above the footer: the white page drifts into a faint
     teal wash, then the dark footer rises out of it as a rounded "sheet"
     (echoes the app's iOS bottom-sheet language). The footer's rounded top
     corners reveal this wash, so there is no hard white-to-dark cut. */
  /* The approach wash and the padding above it existed to show the footer's
     rounded top corners lifting off the page. With a square top edge they
     would only be a pale teal band above a teal block, so both are gone. */
  /* The whole footer is lifted --foot-bleed into the bloom above it. Set on
     .foot-outer rather than on footer.vivid so a negative margin can never
     collapse out through the wrapper and land somewhere unexpected. */
  .foot-outer { background: transparent; margin-top: calc(-1 * var(--foot-bleed)); }
  /* Footer gradient: the design-system vivid ramp verbatim (--grad-vivid,
     aqua-300 -> aqua-400 -> teal-400 -> teal-500). No local darkened cut.

     THE HANDOFF LIVES HERE NOW. The footer overlaps the bloom by --foot-bleed
     and masks its own top edge from nothing to solid across that distance, so
     what fades in over the cloud is the footer's real gradient, at its real
     geometry, pixel for pixel. There is nothing left to compute and nothing
     that can drift: the previous version painted a separate horizontal ramp
     on .cta-section::after whose stops were derived from an assumed 1440x900
     footer, and the real footer being 627px tall is what drew the line.

     padding-top is the bleed as well, so every glyph in here sits below the
     fade zone and none of the footer's own content is ever half-transparent. */
  footer.vivid {
    position: relative; overflow: hidden;
    background-image: var(--grad-foot); color: #fff;
    padding-top: var(--foot-bleed);
    /* A smootherstep ramp (6t⁵−15t⁴+10t³ sampled every eighth), not the old
       near-linear four stops. Both ends of that ramp had a visible derivative
       kink — alpha ran straight into 0 at the top and straight into 1 at the
       bottom, and the eye reads those slope breaks as edges (mach bands) even
       when the values themselves are continuous. Smootherstep has zero slope
       at both ends, so the footer now eases out of nothing and settles into
       solid with no line at either boundary. */
    -webkit-mask-image: linear-gradient(180deg,
      transparent 0,
      rgba(0,0,0,0.02) calc(var(--foot-bleed) * 0.125),
      rgba(0,0,0,0.10) calc(var(--foot-bleed) * 0.25),
      rgba(0,0,0,0.27) calc(var(--foot-bleed) * 0.375),
      rgba(0,0,0,0.50) calc(var(--foot-bleed) * 0.5),
      rgba(0,0,0,0.73) calc(var(--foot-bleed) * 0.625),
      rgba(0,0,0,0.90) calc(var(--foot-bleed) * 0.75),
      rgba(0,0,0,0.98) calc(var(--foot-bleed) * 0.875),
      #000 var(--foot-bleed));
    mask-image: linear-gradient(180deg,
      transparent 0,
      rgba(0,0,0,0.02) calc(var(--foot-bleed) * 0.125),
      rgba(0,0,0,0.10) calc(var(--foot-bleed) * 0.25),
      rgba(0,0,0,0.27) calc(var(--foot-bleed) * 0.375),
      rgba(0,0,0,0.50) calc(var(--foot-bleed) * 0.5),
      rgba(0,0,0,0.73) calc(var(--foot-bleed) * 0.625),
      rgba(0,0,0,0.90) calc(var(--foot-bleed) * 0.75),
      rgba(0,0,0,0.98) calc(var(--foot-bleed) * 0.875),
      #000 var(--foot-bleed));
  }
  footer.vivid::before { content: ""; position: absolute; inset: 0; background: var(--grad-glow); opacity: 0.5; pointer-events: none; }
  /* The footer's half of the grain: the same tile at the same size as
     .cta-section::after, so the texture runs straight through the boundary
     instead of stopping at it. It fades IN over the bleed as the section's
     copy fades out, which keeps the total roughly constant across the overlap.
     overlay rather than soft-light here — soft-light all but disappears on a
     mid-tone teal, where on the near-white bloom it is exactly right. */
  footer.vivid::after {
    content: ""; position: absolute; inset: 0;
    pointer-events: none; z-index: 0;
    background-image: var(--grain);
    background-size: 120px 120px;
    background-repeat: repeat;
    mix-blend-mode: overlay;
    /* Not 1. overlay's response is 2*(1-base): on the footer's mid-teal that is
       about five times what it is on the near-white bloom, so at equal opacity
       the same tile reads far coarser down here. 0.30 measures the two within
       about one point of each other (12.1 over the bloom, 12.6 over the
       footer), which is what makes the texture read as ONE surface. */
    opacity: 0.30;
    /* Ahead of the colour mask above it, not level with it. The two grains are
       the same tile at different origins, so their amplitudes add in
       quadrature: to hold the total flat while the section's copy is being
       covered at (1 - alpha), this one has to be at sqrt(1 - (1-alpha)^2) of
       the colour mask's alpha. Recomputed for the smootherstep colour ramp,
       same construction as before (0.87 where the colour is at 0.50). Level
       ramps sum to 0.71 in the middle and leave a soft smooth band across
       the seam. */
    -webkit-mask-image: linear-gradient(180deg,
      transparent 0,
      rgba(0,0,0,0.20) calc(var(--foot-bleed) * 0.125),
      rgba(0,0,0,0.44) calc(var(--foot-bleed) * 0.25),
      rgba(0,0,0,0.69) calc(var(--foot-bleed) * 0.375),
      rgba(0,0,0,0.87) calc(var(--foot-bleed) * 0.5),
      rgba(0,0,0,0.96) calc(var(--foot-bleed) * 0.625),
      rgba(0,0,0,0.99) calc(var(--foot-bleed) * 0.75),
      #000 calc(var(--foot-bleed) * 0.875),
      #000 var(--foot-bleed));
    mask-image: linear-gradient(180deg,
      transparent 0,
      rgba(0,0,0,0.20) calc(var(--foot-bleed) * 0.125),
      rgba(0,0,0,0.44) calc(var(--foot-bleed) * 0.25),
      rgba(0,0,0,0.69) calc(var(--foot-bleed) * 0.375),
      rgba(0,0,0,0.87) calc(var(--foot-bleed) * 0.5),
      rgba(0,0,0,0.96) calc(var(--foot-bleed) * 0.625),
      rgba(0,0,0,0.99) calc(var(--foot-bleed) * 0.75),
      #000 calc(var(--foot-bleed) * 0.875),
      #000 var(--foot-bleed));
  }
  /* Vertical padding only; horizontal gutter inherited from .wrap. */
  /* Paul's own layout (2026-08-28 late): founders' signature block left,
     right-aligned contact block right, and the two BOTTOM-aligned so the
     social row sits on the quote's last row (the © line left on 08-29). */
  /* FLAT 2x2 GRID since 2026-08-30 night-2 (the quote changed twice in one
     night and the old px-fitted construction had to be re-measured each
     time): row 1 is the two Times labels — tops level BY CONSTRUCTION —
     row 2 is the quote and the contact stack, and align-items:end is what
     bottom-aligns row 2 so the socials sit on the quote's last row. The
     row-gap is the label-to-content breath on both sides. */
  .foot {
    position: relative; z-index: 1;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: clamp(48px, 7vw, 130px);
    row-gap: 14px;
    align-items: end;
    padding-top: clamp(48px, 7vw, 76px); padding-bottom: clamp(64px, 9vw, 116px);
  }
  .foot-l1 { grid-row: 1; grid-column: 1; }
  .foot-l2 { grid-row: 1; grid-column: 2; justify-self: end; }
  .foot .foot-lead { grid-row: 2; grid-column: 1; }
  .foot-contact {
    grid-row: 2; grid-column: 2;
    display: flex; flex-direction: column; align-items: flex-end; gap: 12px;
    text-align: right;
  }
  /* Its own grid row under the contact stack, so row 2 keeps its bottom
     alignment and the socials still sit on the quote's last row — putting
     it inside .foot-contact would have pushed them up off that line. */
  .foot-privacy {
    grid-row: 3; grid-column: 2; justify-self: end;
    font-size: 15px; font-weight: 500; letter-spacing: -0.01em;
    color: #fff; opacity: 0.62; transition: opacity .2s;
  }
  .foot-privacy:hover { opacity: 1; }
  /* The hard break vanishes when the columns stack. */
  @media (max-width: 900px) { .foot-lead br { display: none; } }
  /* Below ~900 everything stacks left in reading order: label, quote,
     label, contact. The second label carries the block gap itself (one
     row-gap would space all four evenly). The old `.foot-col.foot-cta`
     compound fix died with the flex columns — these are grid items now
     and the stacked overrides live right here. */
  @media (max-width: 900px) {
    .foot { grid-template-columns: minmax(0, 1fr); }
    .foot-l1 { grid-row: 1; }
    .foot .foot-lead { grid-row: 2; grid-column: 1; }
    .foot-l2 { grid-row: 3; grid-column: 1; justify-self: start; margin-top: 30px; }
    .foot-contact { grid-row: 4; grid-column: 1; align-items: flex-start; text-align: left; }
    .foot-privacy { grid-row: 5; grid-column: 1; justify-self: start; }
  }
  /* INTER 700, and that is a settled fact: Times ran here until 08-28
     (Paul: Inter bold), was recalled for ONE HOUR on 08-30 late ("make
     this times again") and reverted on sight ("change back to inter").
     Do not re-propose Times for the quote — the Times voice in this
     column is the "What we believe" label above it. Sized to THE
     FOOT-EMAIL'S EXACT CLAMP (Paul, 2026-08-30 night-2: "gleiche größe
     wie ... hello@kincompanion.ai") — the two rows and the email line
     are one size, so if the email's clamp ever moves, this moves with
     it. The old px-fitted line-heights died with the flat-grid footer:
     nothing needs fitting any more. */
  .foot-lead { font-family: var(--font-sans); font-weight: 700; letter-spacing: -0.015em; font-size: clamp(19px, 1.8vw, 25px); line-height: 1.5; margin: 0; max-width: none; text-wrap: balance; }
  /* Was a 12px uppercase tracked-out micro-label. Set as a plain sentence at
     a readable size instead — the all-caps eyebrow is the single most dated
     tell in the whole footer. */
  /* TIMES since the late-evening spec ("Talk to the Founders in times") —
     full white, display-stroked like the headlines, right-aligned by the
     column. It was 17px Inter for a few hours before that. */
  .foot-label {
    font-family: var(--font-serif); font-weight: 400; letter-spacing: -0.02em;
    -webkit-text-stroke: 0.012em currentColor;
    font-size: clamp(22px, 2.1vw, 30px); line-height: 1.15;
    color: #fff;
  }
  .foot-email { font-size: clamp(19px, 1.8vw, 25px); font-weight: 700; letter-spacing: -0.02em; color: #fff; transition: opacity .2s; word-break: break-word; }
  .foot-email:hover { opacity: 0.82; }
  /* Vertical padding only; horizontal gutter inherited from .wrap. */
  /* Divider drawn as an inset pseudo-element (not border-top) so it starts and
     ends exactly at the text edges — the .wrap gutter padding stays line-free. */
  /* .foot-bar is gone. It was a hairline with five social icons hanging off
     its left end, stranded in the gap between the founders block and the
     signature. The icons then spent a round at the far end of the copyright
     line, which was rejected too. They now close the contact column under the
     address, and the hairline that used to head their bar heads the signature.
     Do not give them a row of their own again. */
  .foot-social { display: flex; align-items: center; gap: 20px; }
  /* Full white at rest; on hover each icon takes ITS OWN network's brand
     colour (Paul, 2026-08-28 — this replaced the single Heart Coral hover).
     The colour rides in as --brand on the anchor, set per entry in the
     socials array in Footer.astro, so the data stays with the links. Heart
     Coral remains the fallback for any entry without one. */
  .foot-social a { display: inline-flex; color: #fff; transition: color .2s; }
  .foot-social a:hover { color: var(--brand, var(--heart)); }
  /* Sizes come from the width/height attributes (per-glyph, shared scale). */
  /* The only place a social icon size is written. Every glyph is on the same
     24x24 grid from the same set, so one rule makes them all read as one size;
     per-icon sizing is what made them look mismatched. */
  .foot-social svg { display: block; width: 26px; height: 26px; }

  /* ---- The signature ------------------------------------------------------
     The lockup at full container width as the page's last word. White on the
     vivid footer, and cropped a little at the bottom by the footer's own
     padding so it reads as a sign-off rather than another logo.

     Two movements, both driven by one .is-in class the observer adds when the
     footer arrives: the twelve glyphs rise in sequence (--i is the glyph index,
     set per path in the markup), then the mark takes over with a slow beat. */
  /* Deep on both sides. The signature is the page's last word and it was
     sitting shoulder to shoulder with the block above and the legal line
     below; it needs the room to read as a sign-off rather than as another
     row. */
  /* THE HALF-HEIGHT CUT IS GONE AT EVERY WIDTH (Paul, 09-01: "the footer
     logo shouldnt have that 50% then 100% on hover animation thing. should
     just always be there"). From 08-28 to 09-01 the svg rested at
     translateY(50%) inside this clipping box and rode back up on hover over
     .95s — the phones already stood it whole since 08-31 ("on mobile no
     cutoff logo on the bottom") and desktop has now followed. There is no
     transform and no hover state left on the wordmark: it is simply there,
     whole, on the page's bottom lip. Recipe banked in DESIGN-LOG. Do not
     reintroduce the cut, the ride, or any hover move on .sig.
     Shallow on top: the big air lives in .foot's bottom padding above, and
     padding-bottom stays 0 so the name closes the page on its own lip.
     `overflow: hidden` stays — the K's four units of overshoot above the
     viewBox have nowhere to bleed. */
  .foot-signature { position: relative; z-index: 1; padding-top: clamp(10px, 1.6vw, 20px); padding-bottom: 0; overflow: hidden; }
  /* NO hairline above the signature (Paul, 2026-08-28: "remove the seperator
     line after the citation and the socials"). It had followed the social bar
     here when that bar was dissolved; now the padding alone does the
     separation. Do not bring a rule back in any form. */

  /* The © · Privacy LINE is gone (Paul, 2026-08-29: "remove © 2026
     KinCompanion, Inc. · Privacy") and stays gone — no copyright, no
     middot, no bar. The LINK came back on its own on 09-01 ("also but the
     link to the privacy notice somewhere there") as .foot-privacy, one
     quiet word closing the contact column — its rules sit with the rest of
     the .foot grid above, ahead of the stacked overrides, because that is
     what decides the row on a phone. The Delaware/registered-agent sentence
     lives in the notice itself. */
  .sig { display: block; width: 100%; height: auto; overflow: visible; }
  /* The mark is paths AND circles (the two heads), so paint the whole tree
     rather than listing shapes: the source SVG's gradient fills were stripped
     on the way in, and anything not covered here falls back to black.
     THE WORDMARK IS SOLID WHITE at every width (09-01 — Paul's own
     revocation of his 08-31 glass-with-white-on-hover; the sheen era's
     rules sit below, collapsed). **NEVER A STROKE ON THESE GLYPHS.** A rim was the first attempt at
     translating the film buttons' `inset 0 0 0 1px` and Paul killed it on
     sight: "das hat einen rand der hässlich ist", "every single letter has
     the overlap thing with the think edge thing on the outside". Two separate
     faults, one cause. The thin edge is simply an outline on letterforms,
     which is ugly at this size. The overlaps are worse and structural: this
     wordmark's glyphs are built from OVERLAPPING SUBPATHS inside one path
     element — the K is a stem plus two diagonals — and a fill unions them
     silently while a stroke traces every subpath's own outline, so the seams
     where they cross are drawn straight through the letter. A solid white
     fill hid all of it for two years. Any future rim, glow or outline on
     `.sig` brings both back. */
  .sig, .sig * { fill: url(#sigGlass); }
  /* SOLID WHITE AT EVERY WIDTH (09-01, Paul: "make the 'KinCompanion'
     in the footer on desktop completely white too please" — his own
     revocation of the 08-31 glass resting state). The gradient STAYS
     as the paint server with every stop forced opaque — never swap the
     fill for a flat colour, and never a stroke (the subpath trap
     above). The hover pair (sheen -> white) died with the sheen; the
     ride-up and the arrival wave are untouched. */
  .sig-grad stop { stop-opacity: 1; }

  /* THE ARRIVAL WAVE IS GONE (Paul, 09-01: "mach footer logo animation
     weg"). It was `.sig-word path` resting at opacity 0 / translateY(0.5em) /
     blur(7px) with a 60ms `--i` stagger, released by `.foot-signature.is-in`
     from an IntersectionObserver. All of it is out — the rules, the observer
     and the per-path `--i` vars — and the recipe is banked in DESIGN-LOG.
     The glyphs are simply there.
     What STAYS is the half-height cut and its hover ride (.foot-signature
     .sig above): that is the layout, not the animation — the wordmark is cut
     on the page's true bottom edge and the ride is the only way to see it
     whole. Do not read "animation weg" as killing that.
     NO BEAT, NO PULSE, still and always: it shipped with a lub-dub every
     four seconds, was cut to a single beat, then cut entirely — "I dont want
     the logo pulse". Do not reintroduce one in any form. */

  /* The ™: small, flush right, sitting in the ascender headroom above the
     final "n" (see Footer.astro for why it lives inside the svg). Painted by
     `.sig, .sig *` like every glyph, so it needs no fill of its own — and no
     stroke, ever, same rule as the wordmark. */
  .sig-tm { font-family: var(--font-sans); font-size: 26px; font-weight: 600; }

  /* =========================================================================
     Breakpoint ladder. The shared --gutter token already handles equal L/R
     margins at every width via clamp, so breakpoints only restructure layout,
     never reintroduce horizontal padding.
     ========================================================================= */

  /* Tablet and below: collapse hero / item grid / footer to one column.
     The footer goes single-column HERE, so the footer CTA must left-align HERE
     too. Left-aligning only at a narrower phone width would leave the email
     awkwardly right-aligned inside a full-width column from 561 to 940px. */
  @media (max-width: 940px) {
    .twocol { grid-template-columns: 1fr; }
    /* (.bandrow narrow-column rules left with the band rows, 2026-08-30.) */
    .cardgrid-2 { grid-template-columns: 1fr; }
    /* Touch layouts: no hover lift (sticky :hover on tap feels broken). */
    .pcard:hover { transform: none; }
    /* No footer overrides here any more: the left rail IS the base layout. */
  }

  /* Phones: stack the email field + button to full width; stack the footer bar. */
  @media (max-width: 560px) {
    /* Phones read the display headlines BIGGER and the standfirsts
       smaller (Paul, 2026-08-30 night-2: "on mobile make h1 bigger and
       text below h1 smaller. exact same logic to the 'Be there, even
       when you can't'"). ONE rule for both — .cta-title is the h1's
       size, locked. Measured at 320 in headless: 35px is the last size
       that holds the h1 to two lines, 36 breaks a third — so 10.8vw
       lands 34.6 at 320 (safe) and the 40 cap arrives by 371. The 561px
       edge steps ~40 -> ~35.9 — accepted, devices don't cross it. */
    .lede-title, .cta-title { font-size: clamp(34px, 10.8vw, 40px); }
    /* And the standfirsts run NARROWER than the column (Paul: on mobile
       they ran wider than on desktop, where the measure caps them well
       inside the headline's width — 82% restores that proportion). */
    .lede-sub, .cta-sub { font-size: 15px; max-width: 82%; }
    /* Phones: the film comes before the form. Source order is title, form,
       film; `order` swaps the last two without duplicating the markup. */
    /* Tightened 09-01 (Paul: "align email + button closer to the video"):
       the two paddings between film and form roughly halved. */
    .opening .teaser { order: 2; padding-bottom: clamp(10px, 1.8vw, 14px); }
    /* Last in the block at this width, so it carries the run-out that the
       film's bottom padding provides on desktop. */
    .opening .openform { order: 3; padding-top: clamp(8px, 1.6vw, 12px); padding-bottom: clamp(52px, 9vw, 72px); }
    /* ONE ROW on phones since 08-31 night-4 (Paul: "put the enter email and
       the join waitlist in one row. 3/4 is the you@email.com on the left 1/4
       is the button but only with the arrow, no text"). It stacked to two
       full-width boxes before. The split is written as literal quarters of
       the row with half the 12px gap taken off each side, NOT as flex-grow
       3:1 — grow distributes only the FREE space, and the input's own 48px
       of padding plus its 2px border count as occupied before that division
       starts, so `flex: 3 1 0` measured 265.6/71.9 (3.69:1) where quarters
       measure 256.1/81.4 at 390. Basis over grow, so the numbers are the
       ones Paul named.
       .field keeps the base rule's flex-direction: row. */
    .field input { flex: 0 0 calc(75% - 6px); width: auto; }
    /* Scoped to the form, always. Unscoped, a bare .cta rule stretches the
       BAR's button to the full window and pushes it over the logo. */
    .field .cta { flex: 0 0 calc(25% - 6px); width: auto; min-width: 0; padding: 0; gap: 0; }
    /* The arrow alone. The label is not removed from the DOM — the submit
       script still writes "Joining…" / "You're on the list" into it, and
       .field-note mirrors that text into view down here. */
    .field .cta span { display: none; }
    /* Sent: the button is an empty box down here (no label, and the script
       strips the arrow), so it goes and the field takes the whole row. */
    .waitlist.is-sent .field .cta { display: none; }
    .waitlist.is-sent .field input { flex-basis: 100%; }
    .field-note:not(:empty) { display: block; }
  }

@media (max-width: 560px) {
  /* Phones (08-31 night-3, Paul: "the cloud background is too big, needs
     to be more fanned out"): the fixed 1280x640 tile showed a third of
     the pattern on a 390px screen. 200vw keeps the Bayer blocks chunky
     but fans the sky out — at 390px the tile renders 780x390, twice the
     pattern in view. Desktop and iPad keep 1280x640.

     AND the sky ENDS ABOVE THE FORM (same night, Paul: "on mobile no
     clouds behhind the email and waitlist button"): under 560 the form
     sits BELOW the film (order swap) at 69-100% of the section, where
     the desktop run-out (solid 78%, gone 97%) still painted sky. Both
     layers now dissolve before it: pixel sky gone by 66%, haze by 64%
     of its taller overscanned box. The radial window layer must be
     REPEATED here verbatim — mask-image overrides replace ALL layers,
     and dropping it would flood the headline ground; the composite
     properties carry over from the base rule. */
  /* Run-out LOWERED 09-01 (Paul: "extend the clouds a little lower
     almost to the email + sign up button" — with the form pulled up to
     the film in the same pass): measured at 390, the form's top sits at
     ~83% of this box, so the sky runs solid to 70% and is gone by 80%;
     the haze (its own taller overscanned box, form at ~75% there) to
     60/74. Still nothing behind the field itself — "almost to". The
     radial window layer must be REPEATED verbatim — mask-image
     overrides replace ALL layers. */
  /* THIRD mask layer, phones only (09-01, Paul: "on the top left above
     the logo is a pixel cloud thats cut off. remove just that cloud"):
     a soft void over the visible top-left corner. The box overscans the
     viewport by 140px + the notch inset, so the void's centre rides
     env(safe-area-inset-top) to land on the visible corner on every
     device. Intersected like the others, so it only ever REMOVES. */
  .opening-clouds {
    background-size: 200vw 100vw;
    -webkit-mask-image:
      radial-gradient(62% 46% at 50% 34%, transparent 30%, rgba(0, 0, 0, 0.5) 60%, #000 85%),
      linear-gradient(to bottom, #000 0, #000 70%, transparent 80%),
      radial-gradient(210px 170px at 24px calc(150px + env(safe-area-inset-top, 0px)), transparent 0 52%, #000 100%);
    mask-image:
      radial-gradient(62% 46% at 50% 34%, transparent 30%, rgba(0, 0, 0, 0.5) 60%, #000 85%),
      linear-gradient(to bottom, #000 0, #000 70%, transparent 80%),
      radial-gradient(210px 170px at 24px calc(150px + env(safe-area-inset-top, 0px)), transparent 0 52%, #000 100%);
  }
  .opening::before,
  .opening::after {
    -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 60%, transparent 74%);
    mask-image: linear-gradient(to bottom, #000 0, #000 60%, transparent 74%);
  }
  /* (Two phone-only signature overrides lived here and are both base rules
     now: the solid-white stops since Paul took the whole wordmark white,
     and the un-cut stand since he killed the half-height cut on desktop
     too — 09-01, both. Nothing width-specific is left on the signature.) */
}

/* ===========================================================================
   Blog + content pages (blog index, posts, legal). Reuses the design tokens
   from global.css (--teal-*, --hairline, --gutter, --ink, --text-secondary).
   =========================================================================== */

.page-wrap {
  position: relative;
  padding-top: clamp(48px, 8vw, 88px);
  padding-bottom: clamp(64px, 10vw, 120px);
  min-height: 60vh;
  border-top: 1px solid var(--hairline);
}

.page-title {
  margin: 8px 0 0;
  font-weight: 700;
  letter-spacing: -0.026em;
  line-height: 1.06;
  font-size: clamp(32px, 4.4vw, 52px);
  color: var(--ink);
  text-wrap: balance;
}
.page-lead {
  margin: 16px 0 0;
  font-size: clamp(17px, 2.2vw, 20px);
  line-height: 1.55;
  color: var(--text-secondary);
  max-width: 34em;
}

/* ---- Blog index ---- */
/* Blog list: clean white panels (Slashy-style) matching the homepage .pcard recipe. */
.post-list { list-style: none; margin: clamp(32px, 6vw, 52px) 0 0; padding: 0; display: grid; gap: 20px; max-width: 760px; }
.post-item a { display: block; text-decoration: none; padding: clamp(30px, 3.6vw, 40px); background: var(--white); border: 1px solid #E7E7EA; border-radius: 28px; box-shadow: 0 1px 2px rgba(24, 24, 27, 0.04), 0 8px 24px -18px rgba(24, 24, 27, 0.18); transition: border-color .2s var(--ease-out-soft), box-shadow .2s var(--ease-out-soft), transform .2s var(--ease-out-soft); }
.post-item a:hover { border-color: #DADADE; box-shadow: 0 2px 4px rgba(24, 24, 27, 0.05), 0 14px 32px -18px rgba(24, 24, 27, 0.22); transform: translateY(-2px); }
.post-item time { display: block; font-size: 13px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: #71717a; }
.post-item h2 { margin: 10px 0 0; font-size: clamp(22px, 2.8vw, 29px); font-weight: 700; letter-spacing: -0.025em; line-height: 1.18; color: var(--ink); }
.post-item p { margin: 12px 0 0; font-size: 17px; line-height: 1.6; color: #52525b; max-width: 60ch; }
.post-more { display: inline-block; margin-top: 16px; font-size: 15px; font-weight: 700; color: #27272a; }

/* ---- Single post / legal column ---- */
.prose-col { max-width: 720px; margin: 0 auto; }
.back-link { display: inline-block; font-size: 14.5px; font-weight: 600; color: var(--teal-700); text-decoration: none; }
.back-link:hover { color: var(--teal-600); }
.article-title { margin: 22px 0 0; font-weight: 700; letter-spacing: -0.028em; line-height: 1.1; font-size: clamp(30px, 4.4vw, 48px); color: var(--ink); text-wrap: balance; }
.article-meta { margin: 14px 0 0; font-size: 14.5px; color: var(--text-tertiary); }

/* ---- Prose typography (markdown body) ---- */
.prose { margin-top: clamp(28px, 4vw, 40px); font-size: 18px; line-height: 1.7; color: #3f3f46; }
.prose > * + * { margin-top: 1.15em; }
.prose h2 { margin-top: 1.8em; font-size: clamp(23px, 2.8vw, 28px); font-weight: 700; letter-spacing: -0.02em; line-height: 1.25; color: var(--ink); }
.prose h3 { margin-top: 1.6em; font-size: clamp(19px, 2.3vw, 22px); font-weight: 700; letter-spacing: -0.01em; color: var(--ink); }
.prose p { margin-top: 1.1em; }
.prose a { color: var(--teal-700); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
.prose a:hover { color: var(--teal-600); }
.prose strong, .prose b { color: var(--teal-800); font-weight: 700; }
.prose ul, .prose ol { margin-top: 1.1em; padding-left: 1.3em; }
.prose li { margin-top: 0.4em; }
.prose li::marker { color: var(--teal-500); }
.prose blockquote { margin-top: 1.3em; padding-left: 18px; border-left: 3px solid var(--teal-300); color: var(--text-secondary); font-style: italic; }
.prose code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9em; background: var(--teal-50); padding: 2px 6px; border-radius: 6px; color: var(--teal-800); }
.prose hr { margin: 2em 0; border: none; border-top: 1px solid var(--hairline); }
.prose img { max-width: 100%; height: auto; border-radius: 14px; }

/* ---- Footer links ---- */
.foot-links { display: flex; gap: 20px; flex-wrap: wrap; }
.foot-links a { color: rgba(255,255,255,0.85); text-decoration: none; font-size: 13.5px; font-weight: 600; transition: color .2s; }
.foot-links a:hover { color: #fff; }
.foot-copy { color: rgba(255,255,255,0.85); }
/* Same 85%-white-to-white lift as the social icons, so the legal link reads as
   part of the bar rather than as body-prose underline. */
.foot-legal { color: inherit; text-decoration: none; transition: color .2s; }
.foot-legal:hover { color: #fff; text-decoration: underline; text-underline-offset: 2px; }
/* Legal identity, deliberately quieter than the copyright line above it: this is
   information that has to be findable, not read. */
/* .foot-entity is gone: the entity text is part of .foot-legal-line now. */
