@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&display=swap');@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap');@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');/* ============================================================================
   RntlOS — light theme design tokens
   ----------------------------------------------------------------------------
   ONE source of truth for colour and type. Every value below was contrast-
   checked against the two page backgrounds before being written down; the
   measured ratio is in the comment, so a future change can be re-checked
   rather than re-guessed.

   ⚠️ ALWAYS GIVE var() A LITERAL FALLBACK when consuming these. A var() that
   resolves to nothing is an INVALID value and computes to `inherit` — a dim
   line then silently renders at full strength. (Same rule the AdminPanel
   learned the hard way; see CLAUDE.md.)

   ⚠️ This file is loaded FIRST (main.jsx) so component CSS can override it.
   `interactions.css` is still loaded LAST and keeps winning specificity ties.
   ========================================================================= */

:root {
  /* ---- brand ------------------------------------------------------------
     #6A5ACD on #FFFFFF = 5.31:1  ·  on #F5F5F5 = 4.87:1   → AA for body text
     white on #6A5ACD   = 5.31:1                            → AA for buttons */
  --rf-primary: #6A5ACD;
  --rf-primary-strong: #574AB0;   /* hover/pressed on primary surfaces */
  --rf-primary-deep: #453A8C;     /* text-on-light when 4.5 is not enough */
  --rf-primary-soft: #EEECFA;     /* tinted section band / chip background */
  --rf-primary-tint: rgba(106, 90, 205, 0.08);
  --rf-primary-ring: rgba(106, 90, 205, 0.38);   /* focus ring / glow */

  /* ---- surfaces --------------------------------------------------------- */
  --rf-bg: #F5F5F5;               /* page */
  --rf-surface: #FFFFFF;          /* cards, nav, panels */
  --rf-surface-2: #FAFAFA;        /* subtle alternate band */
  --rf-surface-ink: #111114;      /* deliberate dark band (video, footer) */

  --rf-border: #E4E4E7;
  --rf-border-strong: #D4D4D8;
  --rf-border-hover: var(--rf-primary);

  /* ---- ink --------------------------------------------------------------
     Ratios quoted against #F5F5F5, the WORSE of the two backgrounds.
     --rf-ink       #000000 → 18.1:1
     --rf-ink-body  #333333 → 11.6:1
     --rf-ink-dim   #4A4A52 →  8.1:1
     --rf-ink-faint #6B6B75 →  4.8:1   ← the floor. Do not lighten it.
     A 5th, lighter step was tried (#767680) and measured 4.1:1 — it FAILS AA
     and is deliberately absent. If something needs to look quieter than
     --rf-ink-faint, make it smaller or lighter in WEIGHT, not in colour. */
  --rf-ink: #000000;
  --rf-ink-body: #333333;
  --rf-ink-dim: #4A4A52;
  --rf-ink-faint: #6B6B75;
  --rf-ink-invert: #FFFFFF;       /* on --rf-primary or --rf-surface-ink */

  /* ---- elevation --------------------------------------------------------
     Neutral-black shadows only. A coloured shadow on a light page reads as a
     smudge; opacity does the work instead. */
  --rf-shadow-sm: 0 1px 2px rgba(16, 16, 20, 0.06);
  --rf-shadow: 0 4px 16px rgba(16, 16, 20, 0.08);
  --rf-shadow-lg: 0 18px 48px rgba(16, 16, 20, 0.12);
  --rf-shadow-primary: 0 10px 30px rgba(106, 90, 205, 0.28);

  /* ---- radii + motion ---------------------------------------------------- */
  --rf-radius-sm: 8px;
  --rf-radius: 14px;
  --rf-radius-lg: 22px;
  --rf-radius-pill: 999px;
  --rf-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --rf-dur: 180ms;

  /* ---- typography -------------------------------------------------------
     Session 71: the operator brought a design-token extractor and asked for
     the fonts design of its target site. Extracted LIVE from designmonks.co
     (computed styles over 3 927 nodes, 2026-08-20), the system is three
     families with three roles:
       Red Hat Display 700 — every heading (h1 48px/1.2, h3 24–40px)
       Outfit 400          — body, nav, buttons, labels (16–18px/1.5)
       Playfair Display    — italic serif ACCENT words inside headings
     Heading and body load from index.html as 300..900 VARIABLE ranges so
     every weight the stylesheets declare (300–800) renders a real face, no
     faux bold. Fallbacks are system SANS now — Georgia left with the serifs.
     ⚠️ Fraunces (session 68's all-serif look) left these tokens in session
     71; its italic-accent role moved to --rf-font-accent below. Poppins and
     Open Sans left in session 68. Do not reintroduce any of them without
     the operator asking. */
  --rf-font-heading: 'Red Hat Display', 'Segoe UI', Arial, sans-serif;
  --rf-font-body: 'Outfit', 'Segoe UI', Arial, sans-serif;
  /* The ONE serif left on the site: the italic accent words inside headings
     (home hero, signup/login promo sentence, /blog insight strip) — the same
     role Playfair plays on the reference site. Italic 400..700 is loaded;
     always consume this WITH a literal serif fallback, per the rule above. */
  --rf-font-accent: 'Playfair Display', Georgia, 'Times New Roman', serif;
  --rf-font-mono: ui-monospace, SFMono-Regular, Consolas, monospace;

  /* Fluid type scale — clamp() so nothing needs a breakpoint to stay legible. */
  --rf-h1: clamp(2.25rem, 1.30rem + 3.6vw, 4.00rem);
  --rf-h2: clamp(1.75rem, 1.25rem + 2.0vw, 2.75rem);
  --rf-h3: clamp(1.30rem, 1.10rem + 0.8vw, 1.75rem);
  --rf-body: clamp(1rem, 0.96rem + 0.16vw, 1.0625rem);
  --rf-small: 0.875rem;
  --rf-eyebrow: 0.75rem;

  /* ---- layout rhythm ----------------------------------------------------- */
  --rf-max: 1200px;
  --rf-gutter: clamp(18px, 4vw, 40px);
  --rf-section-y: clamp(56px, 8vw, 112px);

  /* Vertical space the FIXED nav occupies: its own top padding + the pill.
     `.home-page` reserves exactly this much so content never starts underneath
     it — and a full-bleed first section (the hero) cancels it with an equal
     negative margin and pays it back as its own top padding, so the section
     runs to the top of the document with the bar floating over it.
     ⚠️ TWO CONSUMERS, ONE VALUE. They must agree to the pixel or the hero
     either leaves a gap or slides under the bar; that is why this is a token
     and not the same clamp() typed out twice. */
  --rf-nav-reserve: calc(clamp(12px, 2vw, 24px) + 52px);
}

/* ---------------------------------------------------------------------------
   Base element styling. Scoped to nothing — this IS the site's baseline.
   ------------------------------------------------------------------------ */

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

.rf-theme {
  background: var(--rf-bg, #F5F5F5);
  color: var(--rf-ink-body, #333333);
  font-family: var(--rf-font-body, sans-serif);
  font-size: var(--rf-body, 1rem);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ⚠️ THE LIVE BASELINE FOR EVERY HEADING ON THE SITE. `.rf-theme` below is
   applied by NO JSX (checked session 68), so without this element rule the
   heading face only reached elements some page rule happened to cover —
   /pricing, /blog and /demo still carry Quicksand on their page roots, and
   their h3–h6 were silently inheriting it. One element-level rule (0,0,1)
   fills every gap and still loses to any classed rule that names its own
   face, which is the correct cascade for deliberate exceptions. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--rf-font-heading, Georgia, serif);
}

.rf-theme h1,
.rf-theme h2,
.rf-theme h3,
.rf-theme h4,
.rf-theme h5,
.rf-theme h6 {
  font-family: var(--rf-font-heading, serif);
  color: var(--rf-ink, #000);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0 0 0.5em;
  /* Headings are the one place a 6-font site looked deliberate; keep the
     tightening that made it read as designed. */
  text-wrap: balance;
}

.rf-theme h1 { font-size: var(--rf-h1); }
.rf-theme h2 { font-size: var(--rf-h2); }
.rf-theme h3 { font-size: var(--rf-h3); font-weight: 600; }

.rf-theme p { margin: 0 0 1rem; max-width: 68ch; }

.rf-theme a {
  color: var(--rf-primary, #6A5ACD);
  text-decoration-color: color-mix(in srgb, var(--rf-primary, #6A5ACD) 40%, transparent);
  text-underline-offset: 3px;
  transition: color var(--rf-dur, 180ms) ease;
}

.rf-theme a:hover { color: var(--rf-primary-strong, #574AB0); }

.rf-theme :focus-visible {
  outline: 3px solid var(--rf-primary-ring, rgba(106, 90, 205, 0.38));
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---- layout helpers ------------------------------------------------------ */

.rf-container {
  width: 100%;
  max-width: var(--rf-max, 1200px);
  margin-inline: auto;
  padding-inline: var(--rf-gutter, 24px);
}

/* Sections separate by WHITESPACE first, then an optional hairline or a tint.
   Three variants so alternating bands never need a bespoke rule. */
.rf-section { padding-block: var(--rf-section-y, 80px); }
.rf-section--surface { background: var(--rf-surface, #fff); }
.rf-section--tint { background: var(--rf-primary-soft, #EEECFA); }
.rf-section--ink {
  background: var(--rf-surface-ink, #111114);
  color: var(--rf-ink-invert, #fff);
}
.rf-section--ink h1,
.rf-section--ink h2,
.rf-section--ink h3 { color: var(--rf-ink-invert, #fff); }
.rf-section--divide { border-top: 1px solid var(--rf-border, #E4E4E7); }

.rf-eyebrow {
  display: inline-block;
  font-family: var(--rf-font-body, sans-serif);
  font-size: var(--rf-eyebrow, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--rf-primary, #6A5ACD);
  margin-bottom: 12px;
}

/* ---- buttons ------------------------------------------------------------- */

.rf-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 46px;
  padding: 12px 26px;
  border: 1px solid transparent;
  border-radius: var(--rf-radius-pill, 999px);
  font-family: var(--rf-font-body, sans-serif);
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  transition:
    background var(--rf-dur, 180ms) ease,
    border-color var(--rf-dur, 180ms) ease,
    color var(--rf-dur, 180ms) ease,
    box-shadow var(--rf-dur, 180ms) ease;
}

.rf-btn--primary {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #fff);      /* 5.31:1 */
  box-shadow: var(--rf-shadow-primary);
}
.rf-btn--primary:hover { background: var(--rf-primary-strong, #574AB0); }

.rf-btn--ghost {
  background: transparent;
  color: var(--rf-ink-body, #333);
  border-color: var(--rf-border-strong, #D4D4D8);
}
.rf-btn--ghost:hover {
  color: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);
}

/* ---- reduced motion ------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .rf-theme a,
  .rf-btn { transition: none; }
}

/* ============================================================================
   LEGACY COMPONENT BRIDGE
   ----------------------------------------------------------------------------
   Shared classes that predate the theme and are used across many pages. Fixed
   HERE rather than per page so one edit reaches all 20 of them.
   Every ratio below is measured against the ground the element actually sits
   on, and is stated so it can be re-checked rather than re-guessed.
   ========================================================================= */

/* ⚠️ THE PRIMARY CTA WAS DARK-ON-DARK AFTER THE TOKEN FLIP, ON EVERY PAGE.
   It was `background: var(--text)` — white text on a near-black page — so
   retargeting --text to #333333 turned the site's loudest button into
   #0A0A0A on #333333, a measured 1.57:1. It becomes the brand, which is also
   what the brief asks the primary colour to carry. White on #6A5ACD = 5.31:1. */
.home-cta-primary,
.home-pill-btn {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
  border-color: var(--rf-primary, #6A5ACD);
}

.home-cta-primary:hover,
.home-pill-btn:hover {
  background: var(--rf-primary-strong, #574AB0);
  border-color: var(--rf-primary-strong, #574AB0);
  color: var(--rf-ink-invert, #FFFFFF);
}

.home-cta-primary *,
.home-pill-btn * { color: inherit; }

/* ---- semantic colours retuned for a light ground -------------------------
   These were picked to glow on near-black and are far too light on white.
   Replacements are the same hues, darkened until they clear AA.
     #4ADE80 → #15803D  (5.01:1 on #FFFFFF)
     #FBBF24 → #B45309  (5.02:1)
     #F87171 → #B91C1C  (5.94:1)
     #A06EFF → the brand
     #9CA3AF → --rf-ink-faint (4.8:1) — it measured 2.6:1 here */
.home-page .text-success,
.home-page [class*="-success"],
.home-page .home-hero-proof-star { color: #15803D; }

.home-page .home-hero-proof-stars,
.home-page [class*="-stars"],
.home-page [class*="-rating"] { color: #B45309; }

.home-page [class*="-danger"],
.home-page [class*="-error"] { color: #B91C1C; }

/* The old dim/faint greys, wherever they were written literally. */
.home-page .home-hero-sub,
.home-page .home-section-sub,
.home-page [class*="-muted"],
.home-page [class*="-dim"] { color: var(--rf-ink-body, #333333); }

/* ⚠️ DOUBLED CLASS, DELIBERATELY. `.home-cta-primary { background: var(--text) }`
   lives in Landing.css, which loads AFTER this file, so at equal specificity
   (0,1,0) it wins on order and the CTA stayed #0A0A0A on #333333 (1.57:1)
   even with the bridge above in place. `.x.x` is (0,2,0) and wins on
   specificity regardless of load order. Do not "simplify" it back. */
.home-cta-primary.home-cta-primary,
.home-pill-btn.home-pill-btn,
.faq-talk-btn.faq-talk-btn {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
  border-color: var(--rf-primary, #6A5ACD);
}
.home-cta-primary.home-cta-primary:hover,
.home-pill-btn.home-pill-btn:hover,
.faq-talk-btn.faq-talk-btn:hover {
  background: var(--rf-primary-strong, #574AB0);
  border-color: var(--rf-primary-strong, #574AB0);
}
.home-cta-primary.home-cta-primary *,
.home-pill-btn.home-pill-btn *,
.faq-talk-btn.faq-talk-btn * { color: var(--rf-ink-invert, #FFFFFF); }

/* ============================================================================
   NIGHT-SKYLINE ACCENT PALETTE
   ----------------------------------------------------------------------------
   Sampled from the reference palette (night city). It does NOT replace the
   primary — #6A5ACD is already threaded through every page and measured — it
   extends it with the deep grounds and the cool accents the light theme was
   missing, for high-emphasis surfaces.

   Measured on --rf-night (#0B0E1A), which is where they are used:
     white       19.2:1
     --rf-sky     7.2:1
     --rf-indigo  3.9:1   (non-text: buttons/borders, ≥3:1)
     white on --rf-indigo  4.96:1
   ========================================================================= */
:root {
  --rf-night: #0B0E1A;      /* near-black navy — the darkest ground */
  --rf-night-2: #1B2233;    /* one step up, for gradients */
  --rf-slate: #3E4557;      /* mid slate-blue */
  --rf-indigo: #5A5FE8;     /* vivid periwinkle — the "neon" accent */
  --rf-sky: #56A5E0;        /* sky blue — supporting accent on dark */
  --rf-mist: #F1F8FD;       /* cool near-white */

  /* ⚠️ THE BRAND PURPLE IS NOT USABLE AS TEXT ON THE DARK GROUNDS.
     #6A5ACD measures 3.62:1 on --rf-night — fine for a border or an icon
     (≥3:1) and fine for large display type, but UNDER the 4.5 floor for any
     body-size label. Asking for "purple text" on the footer therefore needs a
     lighter step, not the primary. This one is the primary lightened until it
     clears AA on that ground and no further, so it still reads as the brand.
       #8B7BE0 on --rf-night (#0B0E1A) = 5.50:1
     Do NOT use it on light grounds — it measures 3.0:1 on #FFFFFF. */
  --rf-primary-on-dark: #8B7BE0;
}

/* ⚠️ GHOST VARIANTS MUST OPT BACK OUT OF THE BRAND FILL. The doubled-class
   rule above matches `.home-pill-btn--ghost` too (it carries both classes),
   so the nav's secondary "Login" button became a solid purple pill identical
   to "Book Demo" — two primary buttons side by side, and the hierarchy
   between them gone. A ghost is defined by NOT being filled. */
.home-pill-btn--ghost.home-pill-btn--ghost {
  background: transparent;
  color: var(--rf-ink-body, #333333);
  border: 1px solid var(--rf-border-strong, #D4D4D8);
}
.home-pill-btn--ghost.home-pill-btn--ghost:hover {
  background: var(--rf-primary-tint, rgba(106, 90, 205, 0.08));
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-primary-deep, #453A8C);   /* 7.4:1 */
}
.home-pill-btn--ghost.home-pill-btn--ghost * { color: inherit; }

/* Ghost CTAs hovered to `border-color: #ffffff` — a white outline on a light
   page is no outline at all. The brand carries the hover instead. */
.home-hero-cta-ghost,
.home-cta-ghost {
  border-color: var(--rf-border-strong, #D4D4D8);
  color: var(--rf-ink-body, #333333);
}
.home-hero-cta-ghost:hover,
.home-cta-ghost:hover {
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-primary-deep, #453A8C);
  background: var(--rf-primary-tint, rgba(106, 90, 205, 0.08));
}

/* The nav wordmark's accent half ("FYV.") was a mid-grey at 3.17:1 — it is
   14px+ body-weight text in the nav, so its floor is 4.5, not 3. It becomes
   the brand, which is also what the accent half of a logotype should be. */
.home-logo-accent.home-logo-accent,
.sf-logo-accent.sf-logo-accent { color: var(--rf-primary, #6A5ACD); }

/* ⚠️ THE SAME GHOST-PILL FILL, FOR THE PORTALLED NAV. The rule in Landing.css
   is scoped to `.home-page`, and the nav renders through a portal to <body> —
   outside that scope — so its Login button kept the dark theme's WHITE wipe
   circle while the pricing button had the brand one. Two instances of one
   component with different hover fills is drift.

   Doubled class to out-specify `.home-pill-btn--ghost::before` in Landing.css,
   which loads after this file. white on #6A5ACD = 5.31:1. */
.home-pill-btn--ghost.home-pill-btn--ghost::before {
  background: var(--rf-primary, #6A5ACD);
}
.home-pill-btn--ghost.home-pill-btn--ghost:hover,
.home-pill-btn--ghost.home-pill-btn--ghost:hover * {
  background: transparent;
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
}

/* ============================================================================
   THE HERO GROUND — one violet surface, shared by every hero on the site
   ----------------------------------------------------------------------------
   This is the HOME hero's background, lifted out of `LandingHero.css` so the
   marketing pages can carry the identical surface (operator: "implement the
   background of home hero section into hero section of all pages … except
   demo"). `/features`, `/blog`, `/about` and `/pricing` add `rf-hero-ground`
   to the element that already painted their purple band; the home hero's
   `.hero-stage` reads the same tokens directly from here.

   ⚠️ THE VALUES LIVE HERE AND NOWHERE ELSE — FIVE SURFACES CONSUME THEM. The
   arrangement this replaces was one gradient pasted into four page
   stylesheets, byte-identical "for consistency", which is precisely the drift
   this repo keeps paying for (the closing-CTA button now differs on three
   pages for exactly that reason). Change a number here and every hero moves
   together; paste a copy into a page file and they stop moving together.

   ── WHY THESE NUMBERS ────────────────────────────────────────────────────
   ⚠️ THE BLOOM ALPHAS AND RADII ARE A CONTRAST BUDGET, NOT A LOOK. On the home
   hero they were 0.52/76% and 0.80/66%; the sweep passed at 1280 and FAILED at
   375 ("sign" 4.48, "contracts," 4.03), where pure white measured only 4.56:1
   — no ink change could have fixed it, the ground itself had to move. Now that
   four more pages consume them, re-measure EVERY consuming page at both a
   desktop and a phone width after touching any of these four numbers.

   ⚠️ EVERY RADIAL FADES TO ITS OWN HUE AT ALPHA 0, NEVER TO `transparent`.
   `transparent` is rgba(0,0,0,0), so interpolating to it drags the ramp
   through grey and leaves a dirty band across the bloom. (The four page
   gradients this replaces all faded to `transparent`; they no longer do.)

   ⚠️ NO COMPUTED-STYLE SWEEP CAN READ THIS GROUND. It is stacked gradients, so
   an audit sees `transparent` on the element and falls through to an ancestor.
   SAMPLE it: screenshot the section with `* { color: transparent }` injected,
   then score each text node's own colour against the composited pixel behind
   it. An analytic bound was tried once on the home hero and was WRONG by a
   wide margin in the unsafe direction.

   Brightest painted point is (50%, 2%), the core of bloom 1: rgb(81,64,165).
   Measured against it — the floor for anything placed on this ground:
     #FFFFFF  7.99:1      #E4DEFF  6.16:1      #C4B8FF  4.43:1
   That is a STRICTLY DARKER peak than the band this replaced (#5C4EB0, where
   the same three inks measured 6.62 / 5.10 / 3.67), so every page's existing
   on-purple ink gained headroom rather than losing it.
   ========================================================================= */
:root {
  /* ⚠️ THE SHORTHAND-FREE COLOUR IS NOT BELT-AND-BRACES. Without it a browser
     that drops the gradients shows the page ground straight through and every
     white glyph on the hero disappears. */
  --rf-hero-ground: #1D1246;

  /* 1. the bloom, sitting just under the nav · 2. the body of the violet ·
     3. the base ramp, darkening into whatever band follows. */
  --rf-hero-bloom:
    radial-gradient(118% 66% at 50% 2%, rgba(124, 106, 236, 0.42) 0%, rgba(124, 106, 236, 0) 62%),
    radial-gradient(96% 60% at 50% 34%, rgba(76, 58, 168, 0.70) 0%, rgba(76, 58, 168, 0) 74%),
    linear-gradient(180deg, #2C1C64 0%, #1D1246 54%, #0D0824 100%);

  /* The dot field.
     ⚠️ 0.30 IS NOT A TYPO AND THE MASK IS HALF THE POINT. The first pass used
     0.17 with a mask that faded out by 80% — a ~2% lift over a violet this
     saturated, dying exactly where the bloom is brightest. It was invisible,
     and no contrast ratio says "this texture is too quiet"; only looking at a
     screenshot found it. The mask stops the grid running to a hard edge, which
     reads as a texture that got cropped. */
  --rf-hero-dots: radial-gradient(rgba(255, 255, 255, 0.30) 1px, rgba(255, 255, 255, 0) 1px);
  --rf-hero-dots-size: 22px 22px;
  --rf-hero-dots-mask: radial-gradient(96% 88% at 50% 26%, #000 0%, rgba(0, 0, 0, 0.55) 62%, rgba(0, 0, 0, 0) 100%);

  /* Sinks the corners and hands the bottom edge to the band below. */
  --rf-hero-vignette:
    radial-gradient(124% 92% at 50% 38%, rgba(13, 8, 36, 0) 44%, rgba(13, 8, 36, 0.58) 100%),
    linear-gradient(180deg, rgba(13, 8, 36, 0) 64%, rgba(13, 8, 36, 0.88) 100%);
}

/* ⚠️ THE GRADIENT TOKENS ARE CONSUMED WITHOUT A LITERAL FALLBACK, DELIBERATELY,
   AND IT IS THE ONE PLACE IN THIS FILE THAT DOES SO. A fallback here would be
   a second copy of the exact value the token exists to de-duplicate. The
   degradation is safe rather than silent: an unresolved var() makes the
   property `unset`, which for `background-image` is `none` — so a hero that
   lost the token paints the flat `--rf-hero-ground` violet (which DOES carry a
   literal) instead of rendering wrong ink on a wrong surface.

   ⚠️ THREE PAINTED LAYERS, AND THE ORDER IS LOAD-BEARING: bloom → dots →
   vignette. The dots sit OVER the colour and UNDER the darkening, which is
   what stops them fizzing at the top edge. `::before` and `::after` paint in
   tree order at the same z-index, so the dots are ::before. */
.rf-hero-ground {
  /* ⚠️ `isolation` IS WHAT MAKES THE `z-index: -1` BELOW WELL-DEFINED. A
     negative-z child only stays inside its parent — painting above the
     parent's own background and below all of its content — when the parent
     forms a stacking context. `.blog-hero-band`, `.pricing-hero-band` and
     `.about-hero` already do (position + z-index); `.ft-hero` does not, and
     without this its dots would slide behind the section and vanish. This is
     the case Landing.css's dot-grid note warns about, closed rather than
     avoided. */
  position: relative;
  isolation: isolate;

  background-color: var(--rf-hero-ground, #1D1246);
  background-image: var(--rf-hero-bloom);
}

.rf-hero-ground::before,
.rf-hero-ground::after {
  content: '';
  position: absolute;
  inset: 0;
  /* Above this element's own background, below every one of its children. */
  z-index: -1;
  /* ⚠️ A FULL-BLEED DECORATIVE LAYER MUST NEVER BE HIT-TESTABLE. `opacity: 0`
     and a negative z-index both hide an element; NEITHER removes it from hit
     testing, and a stray pointer-eating layer over a hero is a bug that reads
     as "this button does nothing" (§DF, /about's founder stage). */
  pointer-events: none;
}

.rf-hero-ground::before {
  background-image: var(--rf-hero-dots);
  background-size: var(--rf-hero-dots-size, 22px 22px);
  -webkit-mask-image: var(--rf-hero-dots-mask);
  mask-image: var(--rf-hero-dots-mask);
}

.rf-hero-ground::after {
  background-image: var(--rf-hero-vignette);
}

/* ---------------------------------------------------------------------------
   .rf-hero-ground--frame — the hero owns the WHOLE first screen, and the next
   chapter's rounded curve starts exactly AT the fold instead of above it.

   ⚠️⚠️ `100svh + lip`, NOT `100svh`, AND THAT IS THE WHOLE FIX. Every colour
   change on this site is a rounded lip: the following band sets
   `border-radius: <lip> <lip> 0 0` and `margin-top: calc(-1 * <lip>)`, so it
   climbs over the bottom `<lip>` of the hero. A band that is exactly one screen
   tall therefore shows a white curve INSIDE the first frame — which is the
   sliver the operator reported on /features, /pricing and /blog. To leave one
   screen of colour STANDING after that overlap the band has to be one screen
   PLUS the lip. The lip itself is untouched: the corners stay rounded, they
   just round below the fold.

   ⚠️ THE BAND'S BOTTOM PADDING MUST GROW BY THE SAME LIP, or centred copy sits
   half a lip low — `justify-content: center` centres inside the CONTENT box,
   and the bottom lip of that box is under the curve. `/blog` and `/pricing`
   already carry `padding-bottom: var(--xx-lip)` for this reason; `/features`
   and `/about` had to add it. Blog.css measured that error at 24px and its note
   is worth reading before touching either number.

   ⚠️ EACH PAGE MAPS ITS OWN LIP — `--rf-hero-lip: var(--ft-lip)` and friends.
   The four tokens happen to hold the same clamp today; restating that number
   here would make five copies of it, which is the thing this file exists to
   stop. The `0px` fallback is a real behaviour, not a guard: a page that adds
   the class without mapping a lip simply gets the old peeking-curve hero back.

   ⚠️ `svh`, NEVER `vh` OR `dvh`. `vh` is the LARGEST viewport (mobile toolbars
   retracted), so a `vh`-sized hero is taller than what is on screen while the
   chrome shows — it would overflow the frame it was told to fill. `dvh`
   resizes live and makes the band jump as the toolbar hides. `svh` is the
   smallest, i.e. the one value that is always a guarantee: if the chrome
   retracts, the surplus reveals a little of the next band, which is the safe
   direction to be wrong in.

   ⚠️ THIS RELIES ON `* { box-sizing: border-box }` (App.css) so `min-height`
   includes the padding. Without it the band would be a screen plus the lip
   plus both paddings, and the fold would land mid-copy.
   ------------------------------------------------------------------------ */
.rf-hero-ground--frame {
  min-height: calc(100svh + var(--rf-hero-lip, 0px));
}
:root {
  /* Re-pointed at the light theme (theme.css). These legacy names are read
     all over the site, so retargeting them converts every already-tokenised
     surface in one place instead of by hand in 40 stylesheets. */
  --text: #333333;      /* 11.6:1 on #F5F5F5 */
  --text-h: #000000;    /* 18.1:1 */
  --bg: #f5f5f5;
  --border: #e4e4e7;
  --code-bg: #f0eff5;
  --accent: #6a5acd;    /* 4.87:1 on #F5F5F5, 5.31:1 on #FFF */
  --accent-bg: rgba(106, 90, 205, 0.1);
  --accent-border: rgba(106, 90, 205, 0.5);
  --social-bg: rgba(240, 239, 245, 0.6);
  --shadow:
    rgba(16, 16, 20, 0.1) 0 10px 15px -3px, rgba(16, 16, 20, 0.05) 0 4px 6px -2px;

  --sans: var(--rf-font-body, 'Open Sans', Roboto, system-ui, sans-serif);
  --heading: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  --mono: var(--rf-font-mono, ui-monospace, Consolas, monospace);

  font: 18px/145% var(--sans);
  letter-spacing: 0.18px;
  /* `light` only. `light dark` let the UA repaint form controls and
     scrollbars dark on a light page the moment the OS was in dark mode. */
  color-scheme: light;
  color: var(--text);
  /* Light viewport base. This is the html/:root background and paints BEHIND
     the z-index:-1 particle canvas; body and the page wrappers stay
     transparent above it. */
  background: var(--rf-bg, #f5f5f5);
  /* `style`, not `none`, since session 71: Outfit (the body face) ships NO
     italic cut, so with synthesis fully off every body <em> (testimonial
     roles, the Coverflow price) silently rendered UPRIGHT. `style` lets the
     browser slant Outfit for those while still forbidding faux-BOLD, which
     is what `none` was protecting against. The heading/accent italics are
     real faces (Playfair italic loads 400..700) and are never synthesized. */
  font-synthesis: style;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  @media (max-width: 1024px) {
    font-size: 16px;
  }
}

/* ⚠️ THE `prefers-color-scheme: dark` OVERRIDE BLOCK WAS REMOVED, DELIBERATELY.
   It re-pointed every token above at the old dark palette, so on a machine
   with the OS in dark mode the whole site would have flipped straight back
   out of the new light theme — the redesign would have looked like it simply
   did not apply, for roughly half of visitors. RntlOS now ships ONE theme.
   If a real dark mode is ever wanted it needs its own audited palette (every
   ratio in theme.css re-measured against a dark ground), not a token swap. */

body {
  margin: 0;
  /* Transparent so the html/:root dark base + the z-index:-1 particle canvas
     show through. (An opaque body background would paint over the canvas.) */
  background: transparent;
}

#root {
  width: 100%;
  margin: 0;
  text-align: left;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}

h1,
h2 {
  font-family: var(--heading);
  font-weight: 500;
  color: var(--text-h);
}

h1 {
  font-size: 56px;
  letter-spacing: -1.68px;
  margin: 32px 0;
  @media (max-width: 1024px) {
    font-size: 36px;
    margin: 20px 0;
  }
}
h2 {
  font-size: 24px;
  line-height: 118%;
  letter-spacing: -0.24px;
  margin: 0 0 8px;
  @media (max-width: 1024px) {
    font-size: 20px;
  }
}
p {
  margin: 0;
}

code,
.counter {
  font-family: var(--mono);
  display: inline-flex;
  border-radius: 4px;
  color: var(--text-h);
}

code {
  font-size: 15px;
  line-height: 135%;
  padding: 4px 8px;
  background: var(--code-bg);
}
/* ===========================================================================
   Sign in — two-panel layout.
   Left: dark promo panel (tools → one console + mini dashboard mockup).
   Right: white sign-in card.

   The page NEVER scrolls: the shell is locked to 100dvh and every vertical
   rhythm value is clamped against vh, so the form compresses to fit short
   viewports instead of overflowing.
   ========================================================================= */

.login-shell {
  --ink: #141416;
  --muted: #71717a;
  --muted-2: #a1a1aa;
  --line: #e4e4e7;
  /* Field / button corner radius. Matches the refreshed sign-in card's softer,
     less pill-like fields (was 28px). */
  --pill: 12px;

  /* --- Promo accent -------------------------------------------------------
     Every accented thing on the promo panel reads from these tokens, so
     re-theming the panel is editing this block and nothing else.
     --accent-rgb / --accent-2-rgb are the bare channel triplets the rgba()
     calls below need.

     NOTE: Signup.css declares its own copy of this block for the `su-` panel.
     Both pages now run the SAME pitch and the SAME reveal, so keep the two
     blocks in step — if one moves and the other doesn't, that is the drift
     this repo keeps warning about.

     ⚠️ THE PANEL'S GROUND IS NOW THE SITE'S SHARED HERO GROUND, PAINTED BY
     THE SHELL. `.login-shell` carries `rf-hero-ground` (theme.css) — the
     same violet every marketing hero paints — so ONE surface runs unbroken
     behind the promo panel AND the card gap. The operator asked for exactly
     that: the card should read as sitting ON the panel's ground, and a flat
     shell colour next to the bloomed panel read as a black strip over the
     card's top edge. The whole navy-era background system (--panel-base navy
     + --wash + --glow-deep + --bloom + the 46px engineering grid) is
     DELETED, not overridden; see the ⚠️ where `.promo--os { background }`
     used to be.

     The inks are the site's on-purple set, scored in theme.css against the
     ground's brightest painted point rgb(81,64,165):
       #FFFFFF 7.99 · #E4DEFF 6.16 · #C4B8FF 4.43
     #C4B8FF sits under 4.5, so as TEXT it is large/bold only here (the
     24px+ price) — everywhere else it colours decorative glyphs the adjacent
     text restates. Small faint text gets #CFC6F4, a mid-step of the same hue
     computing 4.9:1 against that same worst-case pixel — derived, not
     tasted. Scoring every ink against the ground's brightest possible pixel
     is stricter than any per-position sample, so no text node can fall
     under its floor anywhere on the panel.

     PREVIOUS values, if we ever go back:
       navy (s16):  --panel-base #0a1730 · --wash rgba(37,78,168,.55)
                    --glow-deep rgba(29,78,216,.3) · --bloom rgba(56,189,248,.2)
                    --accent #a1a1aa · --accent-2 #c2c7cf · --accent-soft #d4d4d8
                    ink #f8fafc · dim #cbd5e1 · faint #94a3b8
                    lines rgba(190,196,206,.16/.3) · fill rgba(200,206,216,.06)
       blue accents (s14): --accent #38bdf8 · --accent-2 #2dd4bf
       monochrome: --accent #a1a1aa · --panel-base #0b0d10
       charcoal:   --panel-base #212429 */
  --accent: #C4B8FF;              /* brand lavender — price + glyphs; as text, LARGE/BOLD ONLY */
  --accent-2: #E4DEFF;            /* pale lavender — savings pill, guarantee icon */
  --accent-rgb: 196, 184, 255;
  --accent-2-rgb: 228, 222, 255;

  /* Promo surface scale. Hairlines and text tones, so a card only ever has to
     name a role rather than re-derive an rgba(). Lavender-white now: the
     chrome sits in the ground's own hue, not the navy era's slate grey. */
  --panel-line: rgba(228, 222, 255, 0.16);
  --panel-line-2: rgba(228, 222, 255, 0.32);
  --panel-fill: rgba(228, 222, 255, 0.06);
  --panel-ink: #ffffff;
  --panel-dim: #E4DEFF;
  --panel-faint: #CFC6F4;
  /* The line that cancels each price during the reveal. Red-400 rather than a
     hard red: it must read as "struck out" over the violet card without
     shouting louder than the offer that replaces it. */
  --strike: #f87171;

  /* Gap that lets the card's RIGHT corners round against the dark backdrop
     (the left ones already did). Scales with the viewport so the inset stays
     proportional from small laptops up to ultra-wide displays. */
  --card-gap: clamp(10px, 1.4vw, 22px);

  display: grid;
  /* Fluid form column: ~520px at 1440 (the reference width), and it grows /
     shrinks with the viewport instead of being pinned to one resolution. */
  grid-template-columns: minmax(0, 1fr) clamp(430px, 36vw, 560px);
  width: 100%;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;                     /* the login page itself never scrolls */
  /* ⚠️ NO `background` HERE — the shell carries `rf-hero-ground` (theme.css)
     in the JSX, so the hero violet (bloom on the element, dots on ::before,
     vignette on ::after) paints across the WHOLE shell, promo and card gap
     alike, and the white card floats on it. A `background` shorthand on this
     class loads after theme.css and would silently flatten all three layers
     back to one colour — that flat colour next to the panel's bloomed violet
     is exactly the black-strip-over-the-card bug this replaced. */
  color: var(--ink);
  font-family: var(--rf-font-body, Georgia, serif);
  letter-spacing: normal;
}

/* ---------- LEFT : promo panel ---------- */
/* Generic shell for the panel. Everything specific to the RntlOS OS pitch
   hangs off `.promo--os` below, so this rule stays a plain dark column. */
.promo {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(24px, 4.4vh, 52px) clamp(32px, 4.4vw, 64px);
  color: var(--panel-ink);
  min-width: 0;
  /* Clips the decorative ring below AND guarantees the panel can never grow
     the page. The reveal itself lives on the content, not on this box. */
  overflow: hidden;
}

/* --- The stage -------------------------------------------------------------
     .promo        clipped frame · hosts the decorative ring
     .promo-stage  centres the pitch · nothing moves in here
     .promo-inner  the capped content column

   ⚠️ WHY THE RING IS NOT IN .promo-stage. .promo-ring is a 640px circle
   positioned 260px BELOW .promo's bottom edge. An absolutely positioned
   descendant that overflows its scroll/clip box still counts toward layout
   there, and when the inner box was briefly a real scroll container that
   bought it 260px of empty scroll at every size — measured 1160px of
   scrollHeight against 900px of client height while the content had 205px to
   SPARE. Decoration stays on the clipped parent; only content goes inside. */
.promo-stage {
  flex: 1;
  min-height: 0;                   /* let the flex child actually shrink */
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  overflow: hidden;
}

/* --- The OS pitch panel ---------------------------------------------------
   ⚠️ THE GROUND IS NOT PAINTED HERE — OR ON THIS ELEMENT AT ALL. The SHELL
   carries `rf-hero-ground` (theme.css): bloom on the shell, dot field on its
   ::before, vignette on its ::after, all under the content via isolation +
   z-index, so one violet runs behind this panel and the card gap alike. DO
   NOT re-add a local `background` here or on `.login-shell`: Login.css loads
   after theme.css, so it would silently beat the shared surface and this
   page would drift off the brand exactly the way the four marketing pages
   once did (see theme.css's hero-ground note).

   The pitch fits without scrolling because its two phases share one grid
   cell (see .lpo-swap) - the column is the headline plus the taller phase,
   not the sum of everything. */

/* Decorative ring, bottom-right. A REAL element (span in Login.jsx) now, not
   .promo::before — the shared hero class owns the ::before/::after pair of
   whatever element carries it (today the shell, previously this aside), and
   a ring rule merging per-property with the dots produces neither layer, so
   the ring stays off pseudo-elements entirely. Lives directly under .promo
   (clipped, never scrolls) rather than inside .promo-stage, so it is
   unaffected by anything the reveal does to the content. */
.promo-ring {
  position: absolute;
  inset: auto -220px -260px auto;
  width: 640px;
  height: 640px;
  border-radius: 50%;
  border: 1px solid var(--panel-line);
  pointer-events: none;
  opacity: 0;
  animation: rfy-circle-in 1.4s 0.3s ease forwards;
}

/* The promo's content column. Capped and centred so it can't be marooned
   against the left edge of a very wide panel: at 3440px the panel is 2880px
   wide, and a left-aligned column would leave ~2000px of dead space between
   it and the card. Owns the width for the whole column. */
.promo-inner {
  width: 100%;
  max-width: 620px;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
/* ===========================================================================
   THE REVEAL - plays ONCE on load, then holds. No loop.

   Four beats (times are the base values below, before --reveal-scale):
     1  0.15s  the headline arrives, sitting low so it reads as centred
     2  1.70s  it lifts into place, opening the space below it
     3  2.45s  the six tools you rent today appear and are left up long enough
               to actually read; at 5.00s each is STRUCK THROUGH in turn; at
               6.50s the whole block disappears
     4  7.25s  what replaces it rises into that same space: the offer, the
               guarantee, the proof row
   Last animation ends at ~9.3s.

   /!\ ONE KNOB: --reveal-scale multiplies EVERY delay and duration here, so
   the pacing can be changed by editing one number instead of retiming twenty
   lines by hand - which is how it has been adjusted twice already.
   1 = as shipped. 0.5 = twice as fast. 1.3 = a third slower again.

   /!\ THIS RUNS LONGER THAN FIVE SECONDS, WHICH IS WHY THE SKIP RULE AT THE
   BOTTOM OF THIS BLOCK EXISTS AND IS NOT OPTIONAL. WCAG 2.2.2 (Pause, Stop,
   Hide) requires a mechanism for automatic motion that lasts over five
   seconds alongside other content. Pausing would be the WRONG mechanism here
   - it would strand the offer and the guarantee off-screen forever - so
   touching the form finishes the reveal instantly instead. Retime back under
   5s and you may drop that rule; while it is over, don't.

   Everything below animates opacity/transform ONLY - never a layout property
   - so the reveal cannot disturb a shell locked to 100dvh that never scrolls.
   ========================================================================= */
.promo--os { --reveal-scale: 1; }

/* --- Beats 1 and 2: the headline arrives low, then lifts ----------------- */
.lpo-head {
  /* Pushed down by roughly half the height the swap area will occupy, so the
     headline reads as vertically centred before anything else exists. A
     transform, not a margin - it must not move the layout it sits in. */
  transform: translateY(clamp(56px, 12vh, 128px));
  animation-name: rfy-head-lift;
  animation-duration: calc(0.85s * var(--reveal-scale));
  animation-delay: calc(1.7s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-fill-mode: forwards;
}
@keyframes rfy-head-lift { to { transform: translateY(0); } }

/* Longhands throughout rather than the `animation` shorthand: a later
   shorthand would reset the calc()-based duration set here. */
.promo-badge,
.promo-title,
.promo-sub,
.lpo-heading,
.lpo-cost,
.lpo-total,
.lpo-proof-item {
  opacity: 0;
  animation-name: rfy-fadeUp;
  animation-duration: calc(0.65s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-fill-mode: forwards;
}
.lpo-offer,
.lpo-guarantee {
  opacity: 0;
  animation-name: rfy-riseIn;
  animation-duration: calc(0.75s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-fill-mode: forwards;
}

.promo-badge { animation-delay: calc(0.15s * var(--reveal-scale)); }
.promo-title { animation-delay: calc(0.35s * var(--reveal-scale)); }
.promo-sub   { animation-delay: calc(0.6s * var(--reveal-scale)); }

/* --- Beat 3: the stack you rent today, appearing then cancelled ---------- */
.lpo-heading { animation-delay: calc(2.45s * var(--reveal-scale)); }
.lpo-cost:nth-child(1) { animation-delay: calc(2.75s * var(--reveal-scale)); }
.lpo-cost:nth-child(2) { animation-delay: calc(2.91s * var(--reveal-scale)); }
.lpo-cost:nth-child(3) { animation-delay: calc(3.07s * var(--reveal-scale)); }
.lpo-cost:nth-child(4) { animation-delay: calc(3.23s * var(--reveal-scale)); }
.lpo-cost:nth-child(5) { animation-delay: calc(3.39s * var(--reveal-scale)); }
.lpo-cost:nth-child(6) { animation-delay: calc(3.55s * var(--reveal-scale)); }
.lpo-total { animation-delay: calc(3.95s * var(--reveal-scale)); }

/* The strike: a line sweeps left to right across each row. The price is being
   CANCELLED, not merely hidden, and that is the whole reason for showing it.
   The full list sits un-struck from 3.55s to 5.00s - that gap is deliberate
   reading time, and it is the first thing to lengthen if this ever feels
   rushed again. `position: relative` on the rows is what these lines are
   measured against; without it each would size itself to the panel. */
.lpo-cost,
.lpo-total { position: relative; }
.lpo-cost::after,
.lpo-total::after {
  content: "";
  position: absolute;
  left: 11px;
  right: 11px;
  top: 50%;
  height: 1.5px;
  border-radius: 2px;
  background: var(--strike);
  transform: scaleX(0);
  transform-origin: left center;
  animation-name: rfy-strike;
  animation-duration: calc(0.45s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-fill-mode: forwards;
  pointer-events: none;
}
@keyframes rfy-strike { to { transform: scaleX(1); } }

.lpo-cost:nth-child(1)::after { animation-delay: calc(5s * var(--reveal-scale)); }
.lpo-cost:nth-child(2)::after { animation-delay: calc(5.13s * var(--reveal-scale)); }
.lpo-cost:nth-child(3)::after { animation-delay: calc(5.26s * var(--reveal-scale)); }
.lpo-cost:nth-child(4)::after { animation-delay: calc(5.39s * var(--reveal-scale)); }
.lpo-cost:nth-child(5)::after { animation-delay: calc(5.52s * var(--reveal-scale)); }
.lpo-cost:nth-child(6)::after { animation-delay: calc(5.65s * var(--reveal-scale)); }
.lpo-total::after { animation-delay: calc(5.9s * var(--reveal-scale)); }

/* ...and then the block goes. `visibility: hidden` in the final keyframe is
   load-bearing twice over: it takes the cancelled prices out of the
   accessibility tree once they have stopped being true on screen, and it
   stops the now-invisible block swallowing pointer events from the offer card
   that shares its grid cell. */
.lpo-costs {
  animation-name: rfy-costs-out;
  animation-duration: calc(0.6s * var(--reveal-scale));
  animation-delay: calc(6.5s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
  animation-fill-mode: forwards;
}
@keyframes rfy-costs-out {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to   { opacity: 0; transform: translateY(-14px) scale(0.985); visibility: hidden; }
}

/* --- Beat 4: what takes its place --------------------------------------- */
.lpo-offer     { animation-delay: calc(7.25s * var(--reveal-scale)); }
.lpo-guarantee { animation-delay: calc(7.75s * var(--reveal-scale)); }
.lpo-proof-item:nth-child(1) { animation-delay: calc(8.25s * var(--reveal-scale)); }
.lpo-proof-item:nth-child(2) { animation-delay: calc(8.38s * var(--reveal-scale)); }
.lpo-proof-item:nth-child(3) { animation-delay: calc(8.51s * var(--reveal-scale)); }
.lpo-proof-item:nth-child(4) { animation-delay: calc(8.64s * var(--reveal-scale)); }

/* --- The skip, which IS the WCAG 2.2.2 mechanism ------------------------- */
/* Touching the form finishes the reveal on the spot. This is a "stop" that
   costs no content - pausing would leave the offer and the guarantee never
   shown, which is worse than the motion. It is also simply the right
   behaviour: nobody signing in wants a show running beside the field they are
   typing into.

   /!\ IT IS DRIVEN BY A CLASS, NOT BY :focus-within, AND THAT IS A FIX. The
   obvious `.login-shell:has(.form-side:focus-within)` version was written
   first and REVERTS: the moment focus leaves the form the rule stops
   matching, the real delays come back, and anything the skip had revealed
   goes back to opacity 0 and replays. Measured — offer and guarantee at 1
   while the email field held focus, back to 0 after blur, so clicking from
   the field onto the panel undid the skip. `.reveal-done` is set once by an
   onFocus on the shell in Login.jsx and never removed: a one-way latch.

   !important is doing real work here. It has to beat twenty-odd per-element
   animation-delay rules plus two pseudo-elements, and no specificity trick
   reaches all of them at once. */
.login-shell.reveal-done .promo-inner *,
.login-shell.reveal-done .lpo-cost::after,
.login-shell.reveal-done .lpo-total::after {
  animation-delay: 0s !important;
  animation-duration: 1ms !important;
}

/* --- 1. Eyebrow ---------------------------------------------------------- */
.promo-badge {
  /* align-self is load-bearing now that the badge has a border: .promo-inner
     is a flex column with the default `align-items: stretch`, so without it
     the pill would run the full width of the column. */
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 13px 6px 11px;
  border: 1px solid var(--panel-line);
  border-radius: 99px;
  background: var(--panel-fill);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--panel-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  margin-bottom: clamp(10px, 1.8vh, 18px);
}
.promo-badge .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  background: var(--accent);
  /* Deliberately NOT a pulsing "live" dot. The reveal is specified to play
     once and stop, and an infinite heartbeat is motion that never stops - it
     would also drag the panel back under WCAG 2.2.2. A static halo gives the
     same lit look with no animation. */
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.16), 0 0 10px rgba(var(--accent-rgb), 0.5);
}
.lpo-label-sep { color: var(--panel-faint); }

/* --- 2. Headline · 3. Subheadline ---------------------------------------- */
.promo-title {
  /* The SENTENCE deliberately has no font-family: it inherits the shell's
     sans stack, so the panel and the sign-in card share one face. The accent
     span below carries the serif — see its note for why that overrules the
     older "no second face on this page" decision that used to live here. */
  font-weight: 700;
  font-size: clamp(28px, 3.1vw, 42px);
  line-height: 1.08;
  letter-spacing: -0.025em;
  margin: 0 0 clamp(8px, 1.2vh, 12px);
  color: var(--panel-ink);
}
.promo-title .accent {
  /* The site's one type gesture, matched to the home hero's
     .hero-headline-accent: the serif italic accent via --rf-font-accent
     (Playfair Display since session 71's Design Monks flip — one definition
     in theme.css). LandingHero.css documents why on this exact violet ground
     the accent is carried by the FACE and not by a hue — lavender text here
     sinks into the backdrop it came from. That overrules this file's older
     "modern sans only" note deliberately: now the panel paints the hero's
     ground, it takes the hero's headline idiom too. Playfair loads from
     index.html (same reliance /blog's insight strip has); Georgia is the
     degrade. */
  font-family: var(--rf-font-accent, Georgia, 'Times New Roman', serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--panel-ink);
}

.promo-sub {
  font-size: clamp(13px, 1.02vw, 14.5px);
  color: var(--panel-dim);
  max-width: 52ch;
  line-height: 1.6;
  margin: 0 0 clamp(16px, 2.8vh, 28px);
  font-weight: 500;
}

/* --- The swap cell -------------------------------------------------------
   /!\ THE TWO PHASES SHARE ONE GRID CELL, AND THAT IS THE WHOLE TRICK.
   `.lpo-costs` (what you pay today) and `.lpo-after` (the offer, guarantee
   and proof) are both placed at 1/1, so phase 2 occupies exactly the space
   phase 1 vacates. Nothing collapses and nothing reflows - the transition is
   opacity and transform only, which is what keeps the panel safe inside a
   shell locked to 100dvh.

   Stacking them normally instead would make the column ~695px tall and bring
   back the scrolling this replaced. The cell is sized by the TALLER child
   (phase 2), so during phase 1 there is deliberate empty space below the
   prices; that space is where the offer is about to land. */
.lpo-swap {
  display: grid;
  width: 100%;
  min-width: 0;
}
.lpo-swap > * {
  grid-area: 1 / 1;
  align-self: start;
  min-width: 0;
}
.lpo-head { width: 100%; }

/* --- 4. What you're paying right now ------------------------------------- */
/* No margin-bottom: in the swap cell it would offset phase 1 against phase 2.
   The stacked reduced-motion fallback restores one. */
.lpo-costs { margin-bottom: 0; }

.lpo-heading {
  margin: 0 0 clamp(8px, 1.3vh, 12px);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  line-height: 1.5;
  text-transform: uppercase;
  color: var(--panel-faint);
}

/* Two columns: six stacked rows would cost ~100px of height this panel does
   not have, and the pairs read as one bill either way. */
.lpo-cost-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 7px 10px;
}

.lpo-cost {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--panel-line);
  border-radius: 11px;
  background: var(--panel-fill);
  font-size: 12.5px;
  /* Explicit line-height: the row centres a 13px glyph against a 13px icon,
     and an inherited value from an ancestor would push the check off-centre.
     Same hazard the payouts table hit — see CLAUDE.md. */
  line-height: 1.45;
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}
/* Not a link and not clickable, so no pointer cursor — the hover is a
   readability lift, not an affordance. */
.lpo-cost:hover {
  border-color: var(--panel-line-2);
  background: rgba(228, 222, 255, 0.09);
  transform: translateY(-1px);
}
.lpo-cost-check {
  width: 13px;
  height: 13px;
  flex: none;
  color: var(--accent);
}
.lpo-cost-name {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  color: var(--panel-dim);
  /* A longer tool name truncates rather than wrapping the row to two lines
     and breaking the grid's even banding. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lpo-cost-price {
  flex: none;
  font-weight: 700;
  color: var(--panel-ink);
  font-variant-numeric: tabular-nums;
}

/* --- 5. Total ------------------------------------------------------------ */
/* Deliberately neutral rather than alarm-red: the accent belongs to the offer
   below, and two competing colours would blunt it. */
.lpo-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin: clamp(9px, 1.5vh, 13px) 0 0;
  padding: 10px 14px;
  border: 1px solid var(--panel-line-2);
  border-radius: 12px;
  background: rgba(228, 222, 255, 0.1);
}
.lpo-total-label {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.45;
  color: var(--panel-dim);
}
.lpo-total-value {
  font-size: clamp(17px, 1.5vw, 20px);
  font-weight: 700;
  line-height: 1.2;
  color: var(--panel-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.lpo-total-value small {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--panel-faint);
  margin-left: 4px;
}

/* --- 6. The offer -------------------------------------------------------- */
.lpo-offer {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(12px, 1.6vw, 20px);
  flex-wrap: wrap;
  margin-bottom: clamp(9px, 1.5vh, 14px);
  padding: clamp(12px, 1.9vh, 16px) clamp(14px, 1.3vw, 18px);
  border: 1px solid rgba(var(--accent-rgb), 0.34);
  border-radius: 16px;
  background:
    radial-gradient(130% 170% at 100% 0%, rgba(var(--accent-rgb), 0.15), transparent 62%),
    rgba(var(--accent-rgb), 0.05);
  box-shadow:
    0 20px 46px -30px rgba(var(--accent-rgb), 0.75),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
/* A soft corner glow behind the copy, on its own layer so it can never
   affect the card's box. STATIC: this used to breathe on a 7s infinite loop,
   which is exactly the perpetual motion the reveal is meant to replace. */
.lpo-offer::after {
  content: "";
  position: absolute;
  inset: -40% -20% auto auto;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(var(--accent-rgb), 0.18), transparent 68%);
  pointer-events: none;
  opacity: 0.85;
}
.lpo-offer-copy { min-width: 0; flex: 1 1 240px; }
.lpo-offer-title {
  margin: 0 0 4px;
  font-size: clamp(14.5px, 1.25vw, 16.5px);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--panel-ink);
}
.lpo-offer-body {
  margin: 0;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--panel-dim);
}
.lpo-offer-price {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  flex: none;
  text-align: right;
}
.lpo-offer-price b {
  font-size: clamp(24px, 2.2vw, 30px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.lpo-offer-price b small {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--panel-faint);
}
/* A lighter grey + a border, so the saving does not depend on tone alone to
   be read as a distinct claim rather than part of the price. */
.lpo-save {
  padding: 3px 9px;
  border: 1px solid rgba(var(--accent-2-rgb), 0.36);
  border-radius: 99px;
  background: rgba(var(--accent-2-rgb), 0.12);
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--accent-2);
  white-space: nowrap;
}

/* --- 7. Guarantee -------------------------------------------------------- */
.lpo-guarantee {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: clamp(13px, 2.4vh, 24px);
  padding: clamp(11px, 1.7vh, 15px) clamp(13px, 1.2vw, 16px);
  border: 1px solid var(--panel-line);
  border-radius: 16px;
  background: var(--panel-fill);
}
.lpo-guarantee-badge {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  border: 1px solid rgba(var(--accent-2-rgb), 0.3);
  border-radius: 10px;
  background: rgba(var(--accent-2-rgb), 0.12);
  color: var(--accent-2);
}
.lpo-guarantee-icon { width: 18px; height: 18px; }
.lpo-guarantee-copy { min-width: 0; }
.lpo-guarantee-title {
  margin: 0 0 3px;
  font-size: 13.5px;
  font-weight: 700;
  line-height: 1.35;
  color: var(--panel-ink);
}
.lpo-guarantee-body {
  margin: 0;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.55;
  color: var(--panel-dim);
}

/* --- 8. Social proof ----------------------------------------------------- */
.lpo-proof {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin: 0;
  padding: clamp(11px, 1.9vh, 16px) 0 0;
  border-top: 1px solid var(--panel-line);
}
.lpo-proof-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  transition: transform 0.2s ease;
}
.lpo-proof-item:hover { transform: translateY(-2px); }
.lpo-proof-icon {
  width: 15px;
  height: 15px;
  color: var(--accent);
  margin-bottom: 3px;
}
.lpo-proof-value {
  font-size: 15px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--panel-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.lpo-proof-label {
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--panel-faint);
}
@keyframes rfy-riseIn {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes rfy-fadeUp {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes rfy-circle-in {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: scale(1); }
}
/* ---------- RIGHT : sign-in card ---------- */
.form-side {
  background: #ffffff;
  /* Rounded on ALL four corners; the margin below opens the gap on the right
     (and top/bottom) so that rounding actually reads against the backdrop. */
  border-radius: 22px;
  margin: var(--card-gap) var(--card-gap) var(--card-gap) 0;
  display: flex;
  flex-direction: column;
  padding: clamp(18px, 3vh, 34px) clamp(24px, 3.2vw, 48px) clamp(14px, 2.2vh, 26px);
  min-height: 0;                        /* let the flex column shrink to fit */
  overflow: hidden;                     /* never scrolls */
}

/* Brand (left) + Sign Up (right). */
.form-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: clamp(12px, 3.4vh, 40px);
  flex: none;
}

/* "Sign up" swap — a quiet bordered pill in the top-right, mirroring the
   refreshed card's header. */
.head-signup {
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--line);
  padding: 7px 14px;
  border-radius: 99px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}
.head-signup:hover { background: #f5f6f8; border-color: #d8dae0; }
.head-signup:active { transform: scale(0.97); }
.head-signup:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Brand row — a button so it keeps the "go home" navigation. Spacing below is
   owned by .form-head (which pairs it with the Sign Up button). */
.brand-row {
  display: flex;
  align-items: center;
  gap: 10px;
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  border-radius: 6px;
  transition: opacity 0.15s ease;
}
.brand-row:hover { opacity: 0.85; }
.brand-row:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
.brand-mark {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--ink);
  color: #fff;
  display: grid;
  place-items: center;
  font-size: 15px;
  font-weight: 700;
  flex: none;
}
.brand-name { font-size: 16.5px; font-weight: 700; }

.form-wrap {
  width: 100%;
  max-width: 360px;
  margin: 0 auto;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 0;
}

.field {
  display: flex;
  flex-direction: column;
  gap: clamp(5px, 0.9vh, 8px);
  margin-bottom: clamp(10px, 1.8vh, 18px);
}
.field label { font-size: 13px; font-weight: 600; color: var(--ink); }

.input-wrap { position: relative; }
/* Filled, borderless-at-rest fields — the refreshed card's softer look. Focus
   lifts them to white with the ink outline + a soft ring. */
.input {
  width: 100%;
  padding: clamp(10px, 1.6vh, 14px) 18px;
  border: 1.5px solid transparent;
  border-radius: var(--pill);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  outline: none;
  transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
  background: #f5f6f8;
}
.input::placeholder { color: #6b7280; }
.input:focus { background: #fff; border-color: var(--ink); box-shadow: 0 0 0 4px rgba(20, 20, 22, 0.06); }
.input:disabled { background: #f0f1f3; cursor: not-allowed; }
/* Leaves room for the Show/Hide button. */
.input.has-action { padding-right: 62px; }

/* Per-field validation error state — red border only, so the layout doesn't
   jump as errors come and go. */
.input.is-error { border-color: #dc2626; }
.input.is-error:focus { border-color: #dc2626; box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.08); }

/* Inline per-field error text, rendered immediately below each input. */
.login-field-err {
  color: #b91c1c;
  font-size: 12px;
  line-height: 1.4;
  margin: 0 0 0 4px;
  font-weight: 600;
}

.show-btn {
  position: absolute;
  right: 18px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  padding: 0;
}
.show-btn:hover { color: var(--ink); }
.show-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 4px; }

.row-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 2px 0 clamp(12px, 2.2vh, 22px);
}
.remember {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  user-select: none;
}
.remember input {
  appearance: none;
  -webkit-appearance: none;
  width: 17px;
  height: 17px;
  border: 1.5px solid var(--line);
  border-radius: 5px;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: all 0.15s ease;
  flex: none;
  margin: 0;
}
.remember input:checked { background: var(--ink); border-color: var(--ink); }
.remember input:checked::after {
  content: "";
  width: 9px;
  height: 5px;
  border-left: 2px solid #fff;
  border-bottom: 2px solid #fff;
  transform: rotate(-45deg) translateY(-1px);
}
.remember input:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.forgot {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  background: transparent;
  border: 0;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.forgot:hover { text-decoration: underline; }
.forgot:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 4px; }

/* Server-error slot — always rendered so the staggered entrance animation's
   nth-child indices stay stable when a message appears. */
.login-error-slot:empty { display: none; }
.login-error {
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: #991b1b;
  border-radius: 12px;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 14px;
}

.submit {
  width: 100%;
  padding: clamp(11px, 1.8vh, 15px);
  border: none;
  border-radius: var(--pill);
  background: var(--ink);
  color: #fff;
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 700;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: transform 0.12s ease, background 0.15s ease;
}
.submit:hover:not(:disabled) { background: #000; }
.submit:active:not(:disabled) { transform: scale(0.98); }
.submit:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.submit:disabled { background: #d4d4d8; color: #fafafa; cursor: not-allowed; }
.submit .arrow { transition: transform 0.18s ease; }
.submit:hover:not(:disabled) .arrow { transform: translateX(3px); }

.divider {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: clamp(12px, 2.4vh, 24px) 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted-2);
  white-space: nowrap;
}
.divider::before,
.divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }

.social-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.social {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: clamp(10px, 1.6vh, 13px);
  border: 1px solid var(--line);
  border-radius: var(--pill);
  background: #fff;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  /* position + overflow clip the ghost circle below to the pill shape;
     isolation keeps it inside this button's own stacking context. */
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition: color 0.28s ease, border-color 0.32s ease;
}

/* Ghost hover — the site's Login-button effect (home-pill-btn--ghost),
   inverted for a light card: a circle ripples out from the centre to fill the
   pill and the label inverts. z-index:-1 inside the isolated context paints
   the circle ABOVE the button's own white background but still behind the
   label, so the text and icons stay legible as it sweeps past. */
.social::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150%;
  aspect-ratio: 1;                      /* a circle */
  border-radius: 50%;
  background: var(--ink);
  transform: translate(-50%, -50%) scale(0);
  transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: -1;
  pointer-events: none;
}
/* Stays #fff so the corners the circle hasn't reached yet don't flash grey.
   Apple's mark is fill="currentColor" and inverts with the label; Google's
   keeps its brand colours, which is what their guidelines require on dark. */
.social:hover:not(:disabled) { background: #fff; color: #fff; border-color: var(--ink); }
.social:hover:not(:disabled)::before { transform: translate(-50%, -50%) scale(1); }
.social:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.social:disabled { opacity: 0.55; cursor: not-allowed; }
.social svg { width: 17px; height: 17px; flex: none; }

.login-sso-note {
  margin: 10px 2px 0;
  font-size: 12.5px;
  font-weight: 600;
  color: #3f3f46;
  text-align: center;
}

.switch-line {
  text-align: center;
  margin: clamp(14px, 2.8vh, 28px) 0 0;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--muted);
}
.switch-link {
  color: var(--ink);
  font-weight: 700;
  text-decoration: none;
  background: transparent;
  border: 0;
  padding: 0;
  font-family: inherit;
  font-size: inherit;
  cursor: pointer;
}
.switch-link:hover { text-decoration: underline; }
.switch-link:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 3px; }

.trust {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  margin-top: clamp(10px, 2vh, 20px);
  font-size: 12px;
  font-weight: 600;
  color: var(--muted-2);
}

/* Form entrance stagger. The child count is fixed at 9 (email, password,
   remember/forgot row, error slot — always rendered — submit, divider, socials,
   switch line, trust) so these indices never shift at runtime. */
.form-wrap > * { opacity: 0; animation: rfy-fadeUp 0.45s ease forwards; }
.form-wrap > *:nth-child(1) { animation-delay: 0.05s; }
.form-wrap > *:nth-child(2) { animation-delay: 0.12s; }
.form-wrap > *:nth-child(3) { animation-delay: 0.19s; }
.form-wrap > *:nth-child(4) { animation-delay: 0.26s; }
.form-wrap > *:nth-child(5) { animation-delay: 0.33s; }
.form-wrap > *:nth-child(6) { animation-delay: 0.4s; }
.form-wrap > *:nth-child(7) { animation-delay: 0.47s; }
.form-wrap > *:nth-child(8) { animation-delay: 0.54s; }
.form-wrap > *:nth-child(9) { animation-delay: 0.61s; }

/* ---------- Autofill ---------- */
/* Suppress the browser's yellow autofill tint so the pill keeps its look. */
.input:-webkit-autofill,
.input:-webkit-autofill:hover,
.input:-webkit-autofill:active {
  -webkit-text-fill-color: var(--ink);
  -webkit-box-shadow: 0 0 0 1000px #f5f6f8 inset;
  box-shadow: 0 0 0 1000px #f5f6f8 inset;
  caret-color: var(--ink);
  transition: background-color 5000s ease-in-out 0s;
}
/* Focused autofilled field lifts to white, matching a normal focused field. */
.input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  -webkit-box-shadow: 0 0 0 1000px #ffffff inset;
  box-shadow: 0 0 0 1000px #ffffff inset;
  caret-color: var(--ink);
  transition: background-color 5000s ease-in-out 0s;
}

/* React autofill detection hack — Chrome / Safari / Edge don't fire change
 * events when the password manager autofills inputs, so our React state stays
 * empty and the Sign in button thinks the form is invalid.
 *
 * Trick: attach a CSS animation to :-webkit-autofill. The browser kicks off the
 * animation when autofill applies; we listen for the resulting `animationstart`
 * event (see Login.jsx onAutofillStart) and copy the input's value into state.
 * The keyframe is intentionally inert — only the event matters.
 *
 * Firefox doesn't use :-webkit-autofill, but it fires real input events on
 * autofill, so the existing onChange covers it.
 *
 * `opacity: 1` is a no-op — lightningcss rejects truly empty keyframe blocks
 * during prod minify. */
@keyframes rfy-autofill-start {
  from { opacity: 1; }
  to   { opacity: 1; }
}
.input:-webkit-autofill {
  animation-name: rfy-autofill-start;
  animation-duration: 1ms;
}

/* ---------- Responsive ---------- */
/* Below this the two-column layout has no room, so drop the promo panel and
   let the form take the full width. Still capped to the viewport — no scroll. */
@media (max-width: 1060px) {
  .login-shell { grid-template-columns: minmax(0, 1fr); }
  .promo { display: none; }
  .form-side {
    margin: 0;
    border-radius: 0;
    padding: clamp(18px, 3vh, 32px) clamp(20px, 6vw, 32px) clamp(14px, 2.2vh, 24px);
  }
}

@media (max-width: 420px) {
  .form-wrap { max-width: 100%; }
  .social { font-size: 12.5px; gap: 7px; }
  .head-signup { font-size: 12.5px; }
}

/* The proof row deliberately stays FOUR columns all the way down to the 1060px
   cut-off where the panel is dropped entirely. A 2x2 fallback was tried and
   removed: it costs a whole extra row of height, which is the one resource
   this panel is short of, and it bought nothing — at 1060 each stat still has
   ~127px, enough for the longest label ("Renters Onboarded", ~100px at 10.5px)
   on one line. Measured, not assumed. */

/* Very short viewports (landscape phones / short laptops). The loop carries
   every block through the window regardless of height, so nothing is ever
   unreachable — this just keeps the rhythm from feeling cramped. The form is
   untouched. */
@media (max-height: 720px) {
  .promo { padding-block: 22px; }
  .lpo-cost { padding: 6px 11px; }
  .lpo-cost-list { gap: 6px 9px; }
  .lpo-guarantee-body { font-size: 12px; }
}
@media (max-height: 600px) {
  .form-head { margin-bottom: 10px; }
}

/* ---------- Large screens ---------- */
/* The promo's content keeps a sane measure instead of stretching the mock
   across an ultra-wide panel. */
@media (min-width: 1500px) {
  .form-wrap { max-width: 380px; }
}
@media (min-width: 1800px) {
  .form-wrap { max-width: 400px; }
}

@media (prefers-reduced-motion: reduce) {
  .promo-ring,
  .promo-badge,
  .promo-badge .dot,
  .promo-title,
  .promo-sub,
  .lpo-head,
  .lpo-heading,
  .lpo-cost,
  .lpo-total,
  .lpo-costs,
  .lpo-offer,
  .lpo-offer::after,
  .lpo-guarantee,
  .lpo-proof-item,
  .form-wrap > * {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .lpo-cost,
  .lpo-proof-item { transition: none; }

  /* /!\ TURNING THE REVEAL OFF IS NOT ENOUGH ON ITS OWN. The reveal ENDS with
     the price list struck out and gone, so simply killing the animations
     would leave a reduced-motion visitor with the cancelled state as their
     only state - i.e. permanently less information than everyone else. Motion
     preferences must cost motion, never content.

     So the fallback un-overlaps the two phases and shows BOTH, stacked, with
     nothing struck: the costs get their margin back, the strike lines are
     removed outright, and the head sits at its natural position. That column
     is ~695px tall and will not fit a short viewport, so the stage becomes a
     real scroll container - the only place in this design that scrolls. */
  .lpo-head { transform: none; }
  .lpo-costs { animation: none; opacity: 1; visibility: visible; transform: none;
               margin-bottom: clamp(12px, 2.2vh, 22px); }
  .lpo-cost::after,
  .lpo-total::after { display: none; }
  .lpo-swap { display: block; }
  .lpo-swap > * { min-width: 0; }
  .promo-stage {
    justify-content: flex-start;
    overflow-y: auto;
    overscroll-behavior: contain;   /* a panel scroll must not chain to the page */
    scrollbar-width: thin;
    scrollbar-color: var(--panel-line-2) transparent;
  }
  /* Centres while it fits, top-anchored the moment it doesn't. A centred flex
     column that overflows clips its own top and strands the first rows. */
  .promo-stage > .promo-inner { margin-block: auto; }

  .submit,
  .submit .arrow,
  .social { transition: none; }
  /* The ghost fill still happens — it's the hover affordance, not decoration.
     Only the sweep is dropped, matching the site's ghost button. */
  .social::before { transition: none; }
}
/* ===== The platform wordmark ===============================================
 *
 * ⚠️ THE COMPONENT MUST NOT CHANGE THE LAYOUT WHEN A LOGO APPEARS. A sidebar
 * that grows the moment one is uploaded pushes every nav item down and reads as
 * a broken deploy, so the image is sized by HEIGHT and the text lockup keeps
 * whatever type its own surface already gave it (these rules deliberately set
 * no font-size — the call sites' classes still do).
 */

/* ⚠️ HEIGHT IS SET INLINE, WIDTH IS AUTO. A platform wordmark is a WIDE lockup,
   not a square mark: constraining the width squashes it, and `object-fit` on an
   `<img>` with auto width does nothing. `max-width: 100%` is the only guard
   needed so a very wide logo shrinks rather than forcing a sideways scroll. */
.plogo-img {
  display: block;
  width: auto;
  max-width: 100%;
  object-fit: contain;
}

.plogo-text { white-space: nowrap; }
/* ============================================================================
   RntlOS — main navigation (light theme redesign, session 27)
   ----------------------------------------------------------------------------
   Loaded by LandingNav.jsx. The bar shell (.home-topbar / .home-nav-pill) is
   still styled in Landing.css; this file owns the LINKS, the dropdowns and the
   mobile panel.

   ⚠️ The nav is PORTALLED TO <body>, so it sits outside `.home-page` and does
   NOT inherit that page's token block. Everything here reads the :root tokens
   from theme.css, with literal fallbacks — a var() that resolves to nothing is
   invalid and computes to `inherit`, which on a portalled element means a
   colour from <body>, not from the design system.
   ========================================================================= */

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

.rf-nav {
  font-family: var(--rf-font-body, 'Open Sans', Roboto, system-ui, sans-serif);
}

/* ---- desktop list -------------------------------------------------------- */

.rf-nav-list {
  display: flex;
  align-items: center;
  gap: 2px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.rf-nav-item { position: relative; }

/* ⚠️ ONE INK SET, LIGHT, ON EVERY PAGE. These used to be `--rf-ink-body` with
   a whole parallel `.is-on-dark` block overriding them. The bar is now dark in
   both of its states (see LandingNav.jsx), so a second set would be dead
   weight that reads as live — and the duality is exactly what let /features
   ship a white pill override while every other page had a dark one. */
.rf-nav-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 14px 11px;
  min-height: 40px;
  border: none;
  border-radius: var(--rf-radius-pill, 999px);
  background: transparent;
  color: rgba(255, 255, 255, 0.92);
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition:
    color var(--rf-dur, 180ms) ease,
    background var(--rf-dur, 180ms) ease;
}

.rf-nav-link:hover,
.rf-nav-link.is-open {
  color: #ffffff;
  background: rgba(255, 255, 255, 0.12);
}

/* ⚠️ CURRENT PAGE — COLOUR ALONE IS NOT ENOUGH, which is why the operator's
   "bar below" is the right instinct and is what ships. WCAG 1.4.1 (Use of
   Colour): a hover state and a "you are here" state that differ only in hue
   are indistinguishable to a colour-blind visitor and identical in a
   screenshot. The underline is the non-colour cue; `aria-current` carries the
   same fact to assistive tech.
   ⚠️ WHITE, NOT THE BRAND LAVENDER. `--rf-primary-on-dark` (#8B7BE0) is a
   NON-TEXT indicator here so its floor is 3:1, which it clears on the frosted
   glass — but the transparent state puts it on the hero's bloom, where the
   margin is thin and the whole point of the mark is that it is unmissable.
   White is 7.99:1 at the hero's brightest point and needs no second thought;
   the brand is already carried by the Book Demo pill two inches away. */
.rf-nav-link.is-current { color: #ffffff; }

.rf-nav-link.is-current::after {
  content: '';
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 1px;
  height: 2px;
  border-radius: 2px;
  background: #ffffff;
}

.rf-nav-link:focus-visible {
  outline: 3px solid rgba(255, 255, 255, 0.9);
  outline-offset: 2px;
}

/* ⚠️ THE Product / Company DROPDOWN STYLES LIVED HERE AND ARE DELETED.
   The operator asked for the flat six links back (§CG), so `.rf-nav-caret`,
   `.rf-nav-trigger`, `.rf-menu*` and `.rf-nav-item--has-menu` had no markup
   left to style. Removed rather than kept "in case" — CSS for elements that
   no longer render is the same dead weight as an unreferenced component, and
   it reads as live. Recoverable from git if dropdowns ever return. */


/* ---- burger -------------------------------------------------------------- */

.rf-nav-burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;                 /* 44px — minimum comfortable touch target */
  height: 44px;
  padding: 0 10px;
  border: 1px solid rgba(255, 255, 255, 0.34);
  border-radius: var(--rf-radius-sm, 8px);
  background: transparent;
  cursor: pointer;
}

.rf-nav-burger span {
  display: block;
  height: 2px;
  border-radius: 2px;
  background: #ffffff;
  transition: transform var(--rf-dur, 180ms) var(--rf-ease, ease),
              opacity var(--rf-dur, 180ms) ease;
}

.rf-nav-burger.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.rf-nav-burger.is-open span:nth-child(2) { opacity: 0; }
.rf-nav-burger.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.rf-nav-burger:focus-visible {
  outline: 3px solid var(--rf-primary-ring, rgba(106, 90, 205, 0.38));
  outline-offset: 2px;
}

/* ---- mobile panel -------------------------------------------------------- */

/* ⚠️ SOLID, NEVER TRANSLUCENT — and that is deliberate even though the bar
   above it is glass. A blurred panel over the hero's dot grid is where menu
   text stops being readable, and this panel can also hang over the white
   chapter once you have scrolled. One opaque night ground is legible in every
   case the bar can be in. */
.rf-mobile {
  display: none;
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  right: 0;
  z-index: 39;
  max-height: min(70vh, 560px);
  overflow-y: auto;
  padding: 12px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--rf-radius, 14px);
  background: var(--rf-night, #0B0E1A);
  box-shadow: 0 26px 60px -24px rgba(0, 0, 0, 0.85);
}

.rf-mobile-group + .rf-mobile-group { margin-top: 10px; }

.rf-mobile-link {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 11px 12px;
  border: none;
  border-radius: var(--rf-radius-sm, 8px);
  background: transparent;
  color: rgba(255, 255, 255, 0.92);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.rf-mobile-link:hover { background: rgba(255, 255, 255, 0.10); }

.rf-mobile-link.is-current {
  background: rgba(255, 255, 255, 0.12);
  color: #ffffff;
  /* Non-colour cue, same reasoning as the desktop underline. A bar down the
     leading edge is the vertical-list equivalent of an underline. */
  box-shadow: inset 3px 0 0 #ffffff;
}

.rf-mobile-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}

/* ---- breakpoint ---------------------------------------------------------- */

/* ⚠️⚠️ SCOPED TO `.home-topbar` BECAUSE THIS RULE HAS NEVER ACTUALLY APPLIED.
   It was a bare `.home-nav-actions { display: none }` — (0,1,0) — and
   `Landing.css`'s `.home-nav-actions { display: flex }` is also (0,1,0) and
   loads AFTER this file, so it won on ORDER. **A media query adds no
   specificity**, which is what makes this class of bug invisible in review:
   the rule reads as a breakpoint, not as a fight it loses.
   The visible symptom was two Logins on one phone screen — one in the bar
   beside the burger, one inside the panel the burger opens — plus a Book Demo
   that vanished only below 400px, via a *different* Landing.css rule that had
   been patching around this one. (0,2,0) settles it. */
@media (max-width: 900px) {
  .home-topbar .rf-nav-list,
  .home-topbar .home-nav-actions { display: none; }
  .rf-nav-burger { display: flex; }
  .rf-mobile.is-open { display: block; }
}

@media (prefers-reduced-motion: reduce) {
  .rf-nav-link,
  .rf-nav-burger span,
  .home-nav-pill.home-nav-pill,
  .home-logo-text.home-logo-text { transition: none; }
}

/* ============================================================================
   THE BAR'S TWO GROUNDS — ONE COMPONENT, EVERY PAGE
   ----------------------------------------------------------------------------
   Operator: "right now it changes with every page but i want it to be same for
   every page … make that bar transparent with the options high contrast and
   visible for every background".

   There were THREE bars before this: this component (home / features / about),
   `.blog-topbar` and `.pricing-topbar` (in-flow pill headers with `<span
   onClick>` links), and `.demo-topbar`. They are now one. That also closes the
   two worst defects on the list: `/pricing` and `/blog` had **0 of 6
   keyboard-focusable nav links** (WCAG 2.1.1 Level A, recorded as the most
   severe accessibility defect in CONTEXT.md), and `/blog`'s ghost "Login"
   rendered as a solid purple pill identical to "Book Demo" beside it.

   ⚠️ TWO STATES, ONE INK SET — see the note in LandingNav.jsx for why a
   permanently transparent bar is not possible on this site.
     · default            frosted night glass. Legible over ANY section.
     · `.is-over-hero`    fully transparent. Measured, not routed.
   Because both grounds are dark, every colour below is light and there is no
   second set to keep in sync. The `.is-on-dark` block this replaces was that
   second set.

   ⚠️ Doubled classes throughout: `Landing.css` owns `.home-nav-pill`,
   `.home-pill-btn` and the logo, and it loads AFTER this component stylesheet,
   so at equal specificity it wins on ORDER.
   ========================================================================= */

/* ⚠️ `--rf-night` AT 72%, NOT A VIOLET TINT — A MEASURED CHOICE, KEPT. The
   first cut of the dark bar used `rgba(11, 8, 32, 0.52)`, a violet-leaning
   glass that composited to rgb(39,27,95) over the hero's bloom; on that ground
   the wordmark accent measured **4.27:1** and failed, because
   `--rf-primary-on-dark` is documented as 5.50:1 *on `--rf-night`* and that
   was not `--rf-night`. Tinting the pill toward whatever is behind it is what
   pushes a token onto a ground it was never measured for. */
/* ⚠️⚠️ 0.82, NOT 0.72 — AND THE REASON IS THE STATE THIS BAR NEVER USED TO BE
   IN. The old dark treatment only ever appeared OVER THE HERO, where 0.72
   glass composites against a near-black violet. This bar is now the scrolled
   state too, which means 0.72 over the WHITE chapter: measured
   **rgb(79,81,90)**, a mid grey, not the near-black `--rf-night` every value
   here was picked against. On it the wordmark accent fell to **2.26:1**.
   Both halves of the fix are needed — this opacity AND the accent below — and
   neither is enough alone: at 0.72 even #C4B8FF only reaches 4.38, and at 0.82
   #8B7BE0 still only reaches 3.29. Re-measure over a WHITE section, never just
   over a hero. */
.home-nav-pill.home-nav-pill {
  background: rgba(11, 14, 26, 0.82);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  backdrop-filter: blur(16px) saturate(1.3);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 18px 44px -26px rgba(0, 0, 0, 0.9);
  transition:
    background var(--rf-dur, 180ms) ease,
    border-color var(--rf-dur, 180ms) ease,
    box-shadow var(--rf-dur, 180ms) ease;
}

/* ---- transparent, while a hero is still under the bar -------------------- */
/* ⚠️ THE BLUR HAS TO GO TOO, NOT JUST THE FILL. `backdrop-filter` on a
   transparent element still blurs everything behind it, so the hero's dot grid
   and the top of its bloom would smear in a bar-shaped rectangle — which reads
   as a rendering fault, not as transparency. Same for the shadow: a drop
   shadow with nothing casting it is a grey smudge on the hero. */
.home-topbar.is-over-hero .home-nav-pill.home-nav-pill {
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-color: transparent;
  box-shadow: none;
}

.home-logo-text.home-logo-text { color: #ffffff; }
/* ⚠️⚠️ #C4B8FF, NOT `--rf-primary-on-dark` (#8B7BE0), AND CERTAINLY NOT
   `--rf-primary`. Three grounds, one colour has to clear all of them, and
   17.92px/700 is UNDER the 18.66px large-text threshold so the floor is 4.5:
     #6A5ACD  3.6:1 on any dark ground            — a border colour, not a label
     #8B7BE0  3.19 on the hero · 2.26 on the glass over white  ← what failed
     #C4B8FF  6.18 on the hero · 6.37 on the glass over white  ← ships
   `--rf-primary-on-dark` is documented as 5.50:1 **on `--rf-night`** and that
   is true — but the glass is not `--rf-night`, it is `--rf-night` diluted by
   whatever is behind it. A token measured on one ground is not a token for
   every dark ground; fourth time this repo has hit that (§CB8, §CD2, §CG4).
   ⚠️ THIS IS A LITERAL ON PURPOSE. The bar is portalled to <body>, outside
   every page's token block, so the four `--xx-on-purple-accent` variables that
   already hold this exact value are unreachable from here — the file header
   says so. Keep it in step with them by hand if the ramp ever moves. */
.home-logo-accent.home-logo-accent { color: #C4B8FF; }

.home-logo:focus-visible { outline: 3px solid rgba(255, 255, 255, 0.9); outline-offset: 4px; }

/* ---- Book Demo: the brand fill, the one saturated thing in the bar -------- */
/* ⚠️ IT WAS WHITE-ON-BLACK BEFORE (Landing.css fills `.home-pill-btn` with
   `var(--text)`), i.e. the same colour as the ghost's hover and no relation to
   the brand. The operator asked for purple; the rim is what separates it from
   a frosted ground of the same family. white on #6A5ACD = 5.31:1. */
.home-topbar .home-pill-btn.home-pill-btn {
  background: var(--rf-primary, #6A5ACD);
  color: #ffffff;
  border: none;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.24);
}
.home-topbar .home-pill-btn.home-pill-btn:hover {
  /* ⚠️ LIGHTER ON HOVER, WHICH IS BACKWARDS FROM A LIGHT BAND AND RIGHT HERE —
     the direction follows the GROUND, not the button (§DD6). The house hover
     #574AB0 keeps the label legible but drops the FILL to ~2.8:1 on a dark
     bar, so the control loses its edge exactly when you point at it. #7565CF
     keeps the label at 4.65:1 and strengthens the fill. Same value as the home
     page's closing CTA and the blog hero's jump button. */
  background: #7565CF;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.45);
}

/* ---- Login: transparent, and PURPLE on hover ----------------------------- */
/* Operator: "login button transparent which will on hover become purple".
   ⚠️ THE WIPE IS THE FILL. `.home-pill-btn--ghost::before` is a circle that
   scales from 0 to cover the pill (Landing.css owns the geometry); it filled
   WHITE, which made the hover state read as a second primary button and made
   the label go dark. Pointing it at the brand is the whole change — but the
   LABEL has to be re-stated as white in the same rule, or it keeps the dark
   value from the light-theme rules in theme.css and lands at 2.2:1 on purple.
   ⚠️ (0,3,0): theme.css's `.home-pill-btn--ghost.home-pill-btn--ghost::before`
   is (0,2,0) and Landing.css's is (0,1,0)+pseudo — both would otherwise win
   this on load order. */
.home-topbar .home-pill-btn--ghost.home-pill-btn--ghost {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.55);
  color: #ffffff;
  box-shadow: none;
}
.home-topbar .home-pill-btn--ghost.home-pill-btn--ghost::before {
  background: var(--rf-primary, #6A5ACD);
}
.home-topbar .home-pill-btn--ghost.home-pill-btn--ghost:hover,
.home-topbar .home-pill-btn--ghost.home-pill-btn--ghost:hover * {
  background: transparent;              /* the ::before IS the fill */
  border-color: var(--rf-primary, #6A5ACD);
  color: #ffffff;                       /* 5.31:1 on the brand */
}

.home-topbar .home-pill-btn:focus-visible {
  outline: 3px solid rgba(255, 255, 255, 0.9);
  outline-offset: 3px;
}


/* Fully-opaque black footer — the last section on the page. Inter font. */

.site-footer {
  background: #000000;
  color: #ffffff;
  font-family: var(--rf-font-body, Georgia, serif);
  width: 100%;
  box-sizing: border-box;
  padding: clamp(48px, 7vw, 88px) clamp(20px, 6vw, 80px) clamp(28px, 4vw, 44px);
}

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

.site-footer-inner { max-width: 1280px; margin: 0 auto; }

/* ===== Top: brand + 3 link columns, evenly spread ===== */

.sf-top {
  display: grid;
  /* Brand capped to its content width, the three link columns share the rest
     equally and left-align — balanced, no fr-stretched brand gap. */
  grid-template-columns: minmax(220px, 300px) repeat(3, minmax(0, 1fr));
  gap: clamp(28px, 4vw, 64px);
  padding-bottom: clamp(36px, 5vw, 56px);
}

.sf-brand { min-width: 0; }

/* The wrapper's 3 columns participate directly in the .sf-top grid above; on
   narrow screens (below) it becomes a real flex row under the stacked brand. */

.sf-cols { display: contents; }

.sf-logo { font-size: 24px; font-weight: 800; letter-spacing: -0.01em; color: #ffffff; }

.sf-logo-accent { color: #9a9aa0; }

.sf-tagline {
  margin: 14px 0 0;
  color: rgba(255, 255, 255, 0.55);
  font-size: 14px;
  line-height: 1.6;
  max-width: 30ch;
}

.sf-col { display: flex; flex-direction: column; align-items: flex-start; gap: 11px; min-width: 0; }

.sf-col-title {
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
  font-weight: 700;
  margin: 0 0 6px;
}

.sf-col-link {
  background: none;
  border: none;
  padding: 0;
  color: rgba(255, 255, 255, 0.6);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  transition: color 0.15s ease;
}

.sf-col-link:hover { color: #ffffff; }

.sf-contact-item { display: flex; flex-direction: column; gap: 3px; }

.sf-contact-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.4);
  font-weight: 700;
}

.sf-contact-item a {
  color: rgba(255, 255, 255, 0.85);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: color 0.15s ease;
}

.sf-contact-item a:hover { color: #ffffff; }

/* ===== Bottom bar ===== */

.sf-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding-top: 22px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  flex-wrap: wrap;
}

.sf-copy { color: rgba(255, 255, 255, 0.4); font-size: 13px; font-weight: 600; }

.sf-socials { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Brand-coloured social buttons: TRANSPARENT at rest (just a brand-coloured
   border + light label), and on hover the button FILLS with its brand colour,
   the label/icon switching to a readable foreground (--brand-fg: white on the
   dark brands, near-black on the white X). */

.sf-social {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-radius: 999px;
  border: 1.5px solid var(--brand, rgba(255, 255, 255, 0.4));
  background: transparent;
  color: #ffffff;
  text-decoration: none;
  font-size: 13px;
  font-weight: 700;
  transition: background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}

.sf-social svg { width: 16px; height: 16px; flex: none; }

.sf-social:hover {
  transform: translateY(-1px);
  background: var(--brand);
  color: var(--brand-fg, #ffffff);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 28%, transparent);
}

/* Instagram — gradient BORDER at rest (padding-box matches the #000 footer so
   only the ring shows), fills with the full gradient on hover. */

.sf-social--ig {
  border-color: transparent;
  color: #ffffff;
  background:
    linear-gradient(#000, #000) padding-box,
    linear-gradient(45deg, #feda75, #fa7e1e, #d62976, #962fbf, #4f5bd5) border-box;
}

.sf-social--ig:hover {
  color: #ffffff;
  background: linear-gradient(45deg, #feda75, #fa7e1e, #d62976, #962fbf, #4f5bd5);
  box-shadow: 0 0 0 4px rgba(214, 41, 118, 0.30);
}

.sf-backtop {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 999px;
  padding: 9px 16px;
  color: #ffffff;
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.sf-backtop:hover { border-color: #ffffff; background: rgba(255, 255, 255, 0.08); }

/* ===== Responsive ===== */

/* Medium: brand stacks on top, the three link columns sit in a row below. */

@media (max-width: 960px) {
  .sf-top { display: flex; flex-direction: column; gap: clamp(28px, 5vw, 44px); }
  .sf-cols { display: flex; gap: clamp(28px, 6vw, 72px); }
}

/* Large phone: link columns wrap to two-up. */

@media (max-width: 560px) {
  .sf-cols { flex-wrap: wrap; gap: 28px 40px; }
  .sf-col { flex: 1 1 40%; }
}

/* Phone: single column. */

@media (max-width: 400px) {
  .sf-col { flex-basis: 100%; }
  .sf-bottom { flex-direction: column; align-items: flex-start; }
}

/* Large / HD screens: the content was capped at 1280px, so on a 1920 monitor it
   sat in a narrow column with ~310px side gaps while the giant wordmark below
   ran full-bleed. Widen the inner container so the footer content fills the
   screen too. Scoped to min-width — smaller layouts are untouched. */

@media (min-width: 1600px) {
  .site-footer-inner { max-width: 1520px; }
}

@media (min-width: 1900px) {
  .site-footer-inner { max-width: 1680px; }
}

/* ============================================================================
   LIGHT THEME (redesign — session 27)
   ⚠️ The social row was built for a #000 footer and every part of it assumed
   that ground: the label is `color: #ffffff`, and the Instagram pill fills
   with `linear-gradient(#000,#000) padding-box` specifically so the black
   matches the footer and only its gradient RING shows. On the light footer
   that becomes white text on near-white, plus a black pill floating on grey.
   ========================================================================= */

/* ⚠️ THE FOOTER STAYS DARK — it is not an oversight. `.site-footer` is its own
   #000 ground, and a dark footer is the right close for a light page: it
   anchors the document and separates the legal/nav tail from the content
   above without needing a rule. It only moves from pure #000 to the palette's
   night navy so it belongs to the same system as the closing CTA.
   The labels therefore stay WHITE (19.2:1 here); an earlier pass set them to
   ink #333333, which measured 1.66:1 on this ground. */

.site-footer { background: var(--rf-night, #0B0E1A); }

.sf-social {
  color: #FFFFFF;
  border-color: rgba(255, 255, 255, 0.35);
}

/* ⚠️ BRAND FILLS ARE DARKENED BEFORE WHITE IS PUT ON THEM. Raw social brand
   hues do not clear 4.5:1 against white text — Facebook #1877F2 measures
   4.23:1. Mixing 20% black takes it to 5.84:1 and does the same for every
   other brand, without hand-tuning each one. (The same correction the receipt
   PDF needed this session — see §BT2.) */

.sf-social:hover {
  background: color-mix(in srgb, var(--brand, #6A5ACD) 80%, #000);
  color: #FFFFFF;
  border-color: transparent;
}

/* Instagram: the identifying gradient stays as the RING at rest, with the
   padding-box fill matched to the light footer instead of #000. */

.sf-social--ig {
  color: #FFFFFF;
  /* The padding-box fill must MATCH THE FOOTER GROUND — that is the whole
     trick that leaves only the gradient ring visible. It follows the footer
     to night navy; left at #000 it would show as a black pill once the
     footer moved. */
  background:
    linear-gradient(var(--rf-night, #0B0E1A), var(--rf-night, #0B0E1A)) padding-box,
    linear-gradient(45deg, #feda75, #fa7e1e, #d62976, #962fbf, #4f5bd5) border-box;
}

/* HOVER FILLS WITH INSTAGRAM'S OWN RAMP — operator-reported (session 28). It
   had been the site's purple→indigo pair, which on an Instagram button reads as
   the wrong platform.

   ⚠️ IT IS THREE OF INSTAGRAM'S FIVE STOPS, NOT ALL FIVE, AND THAT IS FORCED.
   White on the two warm stops measures #FEDA75 → 1.35:1 and #FA7E1E → 2.60:1,
   and there is no single label colour that clears the whole yellow→indigo sweep
   — black is fine on the warm end and fails from #D62976 on (3.66:1). Measured,
   both directions. So the fill uses the magenta→purple→indigo half, which are
   Instagram's real stops (no invented colours) and hold white throughout:
     #D62976  4.73:1   ·   #962FBF  6.08:1   ·   #4F5BD5  5.54:1
   The full five-stop ramp is still what the RESTING RING draws, so the
   authentic gradient is on screen either way. Do not "complete" this list. */

.sf-social--ig:hover {
  background: linear-gradient(45deg, #d62976, #962fbf, #4f5bd5);
  color: #FFFFFF;
  box-shadow: 0 0 0 4px rgba(214, 41, 118, 0.32);   /* Instagram magenta glow */
}

/* Secondary footer text was a mid-grey tuned for #000 and measures 3.79:1 on
   the night navy — under the 4.5 floor for body-size text. White at 78% alpha
   composites to ~14:1 and keeps the intended "quieter than the links" tone. */

.sf-contact-label,
.sf-copy,
.sf-legal,
.sf-legal span { color: rgba(255, 255, 255, 0.78); }

/* ============================================================================
   SESSION 28 — PER-SECTION PALETTE, TO THE OPERATOR'S SPEC
   Footer: dark ground, light text, purple brand accent. The ground is already
   --rf-night (§CB10) and the labels are already white, so what follows is the
   contrast defect that audit missed plus the brand accent.
   ========================================================================= */

/* ⚠️ A REAL AA FAILURE, PRE-EXISTING. The column headings are
   rgba(255,255,255,0.4), which composites to 3.80:1 on the night ground at
   13px/700 — under the 4.5 floor. They read as "quiet" because they are
   uppercase, letterspaced and small, and NONE of that is a reason to also take
   the contrast below the line: make it quieter with size and weight, not with
   alpha.

   ⚠️ AND FIXING IT NAIVELY INVERTS THE HIERARCHY. The first pass took them to
   0.78 (11.7:1) — legal, and now LOUDER than the links they label, which sit
   at 0.6 (7.2:1). A section label shouting over its own items is a worse
   reading experience than the failure it replaced; it was obvious on sight and
   invisible to the sweep, which scores each node alone and never compares two.
   So the pair is set TOGETHER: the links go up to become the loudest thing in
   the column, the labels sit at the lowest alpha that still clears AA. */

.sf-col-title,
.sf-contact-label { color: rgba(255, 255, 255, 0.62); }

/*  7.7:1 */

.sf-col-link,
.sf-contact-item a { color: rgba(255, 255, 255, 0.85); }

/* 13.9:1 */

/* ⚠️ THE BRAND PURPLE IS 3.62:1 HERE. The wordmark accent is 24px/800, so it
   is large text and 3:1 clears it — but only just, and it is the one purple
   the eye lands on in the footer. The on-dark step takes it to 5.50:1.
   Doubled class to out-specify theme.css's own `.sf-logo-accent.sf-logo-accent`
   rule, which sets the primary. */

.site-footer .sf-logo-accent.sf-logo-accent {
  color: var(--rf-primary-on-dark, #8B7BE0);
}

/* The back-to-top control's 1px rgba(255,255,255,0.3) ring is 2.94:1 — a
   control boundary, floor 3:1, missing it by a hair. */

.sf-backtop { border-color: rgba(255, 255, 255, 0.45); }

/* 4.3:1 */

/* ============================================================================
   SESSION 31 — MONOCHROME AT REST, PLATFORM COLOUR ON HOVER
   ----------------------------------------------------------------------------
   Operator brief: "social media links using purple or white icons". The row
   was five different brand hues at REST — Instagram's five-stop gradient ring,
   Facebook blue, LinkedIn blue — which is the loudest thing in a footer whose
   job is to be quiet.

   ⚠️⚠️ ONLY THE RESTING STATE CHANGES. THE HOVER FILL IS THE PLATFORM'S OWN
   COLOUR AND MUST STAY THAT WAY — it is how someone finds the right icon at a
   glance, and those fills are already contrast-solved above (§CD mixes each
   brand hue 20% black so white clears 4.5:1; Instagram uses three of its five
   stops for the same reason). A later pass replaced every hover with the site
   purple and the operator reported it immediately as broken flow. **Do not do
   that again.** At rest the row reads as one white set; on hover each button
   becomes ITSELF.

   ⚠️ INSTAGRAM HAS TO BE RE-FLATTENED EXPLICITLY. Its resting state is a
   two-layer background (padding-box fill + gradient border-box ring) with
   `border-color: transparent`, so setting `border-color` alone does NOT reach
   it and it would stay the one coloured button in a white row. Both layers are
   reset here; its gradient returns on hover with everything else.

   ⚠️ SPECIFICITY: this block must sit AFTER the §CD block above — it retargets
   the same properties at the same weight and wins on order alone. It also sets
   NO `:hover` rule, deliberately, so §CD's brand fills survive untouched.
   ========================================================================= */

.sf-social {
  border-color: rgba(255, 255, 255, 0.45);          /* 4.3:1 — control boundary */
  color: #FFFFFF;                                   /* 19.2:1 */
  background: transparent;
}

.sf-social--ig {
  border-color: rgba(255, 255, 255, 0.45);
  background: transparent;
}

/* Keyboard parity: the hover reward must also be the focus reward, or a
   keyboard user never sees which button they are on. */

.sf-social:focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 3px;
}
/* ============================================================================
   FooterWordmark — the 21st.dev "CinematicFooter" giant-name treatment
   (`.footer-giant-bg-text`), transposed onto this site's dark closing band
   (session 68). Their tokens resolve on a dark theme to foreground = white,
   so the color-mix percentages became white alphas — then both were RAISED at
   the operator's request ("make it little more visible"): stroke 5% → 12%,
   gradient 10% → 24% fading by 70%. Weight is 800, not their 900 — chosen
   as Fraunces's loaded ceiling (§HF3), kept under Red Hat Display (which
   loads to 900) because the derived size below was re-measured at 800 — and
   the face is the site's heading face via the token, not their Plus Jakarta:
   the treatment was the ask, the letterforms stay the brand's.

   ⚠️ 33.7vw IS DERIVED, NOT PICKED. "RntlOS" measures 3.087em wide in this
   face at this tracking (Red Hat Display 800 since session 71's font flip —
   re-measured live; it was 2.905em in Fraunces, i.e. 35.8vw then), so 33.7vw
   makes the run ≈ 104vw — a slight bleed off BOTH edges (operator: "on left
   there are free space") instead of the centred-with-margins look 26vw gave
   a 6-glyph word. The band flex-centres its overflowing child, which is what
   makes the bleed symmetric; re-derive the number if the word, face or
   tracking changes. Band height ≈ 25.3vw — almost exactly the old
   hover-SVG's 25.2vw.

   ⚠️ THE SECTION (`.rentfyv-hero`, Landing.css) STILL OWNS THE CROP — its
   `overflow: hidden` plus this element's negative bottom margin is what sinks
   the glyph bases below the page edge, exactly like the reference's
   `-bottom-[5vh]`. Decorative only: aria-hidden, no pointer, no selection.
   ========================================================================= */
.fw-band {
  line-height: 0;
  /* Centre the overflowing run so it bleeds BOTH edges equally — a plain
     block overflows to the right only, which read as "free space on left". */
  display: flex;
  justify-content: center;
}

.fw-giant {
  display: block;
  flex: none;
  white-space: nowrap;
  font-family: var(--rf-font-heading, Georgia, serif);
  font-size: 33.7vw;
  line-height: 0.75;
  font-weight: 800;
  letter-spacing: -0.05em;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, 0.12);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.24) 0%, rgba(255, 255, 255, 0) 70%);
  -webkit-background-clip: text;
  background-clip: text;
  margin-bottom: -5vh;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
/* ===== PhoneInput — country picker + auto-formatting national number =====
 *
 * Styled to inherit the surrounding form's tokens. The root .pin element gets
 * `is-valid` (green ring) or `is-invalid` (red ring) once the user has typed
 * something + blurred the field. Tone-neutral when empty.
 *
 * The visual tokens (background, border, text colors) are pulled from CSS
 * variables that exist on .dashboard-page, .login-container, .signup-container,
 * .modal-dialog and similar containers, with sensible fallbacks for when this
 * is dropped into a context without them.
 */

.pin {
  position: relative;
  display: flex;
  align-items: center;
  /* Defaults are tuned for white-background forms (Signup, Login, ForgotPassword).
   * Dark-themed contexts (.dashboard-page, .modal-dialog) override below. */
  background: var(--input-bg, #FFFFFF);
  border: 1px solid var(--input-border, #D1D5DB);
  border-radius: 28px;
  height: 46px;
  padding: 0 12px 0 4px;
  gap: 8px;
  box-sizing: border-box;
  width: 100%;
  font-family: inherit;
  color: var(--input-text, #0A0A0A);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.pin:focus-within {
  border-color: var(--input-focus, #6B7280);
}

/* Dark-theme contexts: pull tokens from the surrounding scope so the input
 * matches the dashboard / modal palette (white text on dark panel). */
:where(.dashboard-page, .modal-dialog) .pin {
  background: var(--panel-2, #0F0F12);
  border-color: var(--border-2, #262626);
  color: var(--text, #FAFAFA);
}
:where(.dashboard-page, .modal-dialog) .pin:focus-within {
  border-color: var(--accent, #FAFAFA);
}

/* Validity tones — nice on both light and dark surrounding forms. */
.pin.is-invalid {
  border-color: #DC2626;
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.12);
}
.pin.is-valid {
  border-color: #16A34A;
  box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.10);
}
.pin.is-valid::after {
  content: '';
  position: absolute;
  right: 14px;
  top: 50%;
  width: 14px;
  height: 14px;
  transform: translateY(-50%);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2316A34A' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") no-repeat center / 14px;
  pointer-events: none;
}
.pin.is-disabled { opacity: 0.5; pointer-events: none; }

/* Country trigger ----------------------------------------------------------- */
.pin-country {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: 0;
  padding: 4px 10px;
  border-radius: 22px;
  cursor: pointer;
  font: inherit;
  color: inherit;
  flex-shrink: 0;
  height: 38px;
  transition: background 0.12s ease;
}
.pin-country:hover { background: rgba(0, 0, 0, 0.04); }
.pin-country:focus-visible { outline: 2px solid var(--accent, #6B7280); outline-offset: 2px; }

.pin-flag { font-size: 16px; line-height: 1; }
.pin-code {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: inherit;
}
.pin-caret { color: var(--text-faint, #6B7280); }

.pin-divider {
  width: 1px;
  height: 22px;
  background: var(--input-border, var(--border, #E5E7EB));
  flex-shrink: 0;
}

/* Number input -------------------------------------------------------------- */
.pin-input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  font-family: inherit;
  font-size: 13px;
  color: inherit;
  padding: 0 4px 0 0;
  letter-spacing: 0.01em;
}
.pin-input::placeholder { color: #9CA3AF; opacity: 0.85; }
:where(.dashboard-page, .modal-dialog) .pin-input::placeholder { color: var(--text-faint, #6B7280); }
/* Pad right when the validity check icon is showing so it doesn't overlap. */
.pin.is-valid .pin-input { padding-right: 22px; }

/* Hide browser-native password / reveal artifacts on the phone input. */
.pin-input::-ms-reveal,
.pin-input::-ms-clear { display: none; }

/* Autofill — keep the surrounding background instead of letting Chrome paint it light blue. */
.pin-input:-webkit-autofill,
.pin-input:-webkit-autofill:hover,
.pin-input:-webkit-autofill:focus,
.pin-input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0 1000px #FFFFFF inset !important;
  -webkit-text-fill-color: #0A0A0A !important;
  caret-color: #0A0A0A;
  transition: background-color 5000s ease-in-out 0s;
}
:where(.dashboard-page, .modal-dialog) .pin-input:-webkit-autofill,
:where(.dashboard-page, .modal-dialog) .pin-input:-webkit-autofill:hover,
:where(.dashboard-page, .modal-dialog) .pin-input:-webkit-autofill:focus,
:where(.dashboard-page, .modal-dialog) .pin-input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0 1000px var(--panel-2, #0F0F12) inset !important;
  -webkit-text-fill-color: var(--text, #FAFAFA) !important;
  caret-color: var(--text, #FAFAFA);
}

/* Country dropdown menu ----------------------------------------------------- */
.pin-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  width: 320px;
  max-width: calc(100vw - 24px);
  /* Defaults for white-background forms; overridden inside .dashboard-page / .modal-dialog. */
  background: #FFFFFF;
  border: 0.5px solid #E5E7EB;
  color: #0A0A0A;
  border-radius: 12px;
  z-index: 60;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.18);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: pinMenuIn 0.14s ease;
}
:where(.dashboard-page, .modal-dialog) .pin-menu {
  background: var(--panel, #0A0A0A);
  border-color: var(--border, #1F1F1F);
  color: var(--text, #FAFAFA);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5);
}
@keyframes pinMenuIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.pin-search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 0.5px solid #E5E7EB;
  color: #6B7280;
}
:where(.dashboard-page, .modal-dialog) .pin-search {
  border-bottom-color: var(--border, #1F1F1F);
  color: var(--text-faint, #6B7280);
}
.pin-search input {
  flex: 1;
  background: transparent;
  border: 0;
  outline: 0;
  font: inherit;
  font-size: 13px;
  color: #0A0A0A;
}
:where(.dashboard-page, .modal-dialog) .pin-search input {
  color: var(--text, #FAFAFA);
}
.pin-search input::placeholder { color: #9CA3AF; }
:where(.dashboard-page, .modal-dialog) .pin-search input::placeholder { color: var(--text-faint, #6B7280); }

.pin-list {
  list-style: none;
  margin: 0;
  padding: 4px;
  max-height: 280px;
  overflow-y: auto;
}
.pin-empty {
  padding: 18px;
  text-align: center;
  color: #6B7280;
  font-size: 12.5px;
}
.pin-option {
  display: grid;
  grid-template-columns: 22px 1fr auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: transparent;
  border: 0;
  padding: 8px 10px;
  border-radius: 8px;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
}
.pin-option:hover { background: rgba(0, 0, 0, 0.05); }
.pin-option.is-selected { background: rgba(0, 0, 0, 0.07); }
:where(.dashboard-page, .modal-dialog) .pin-option:hover { background: var(--panel-2, rgba(255, 255, 255, 0.05)); }
:where(.dashboard-page, .modal-dialog) .pin-option.is-selected { background: var(--panel-3, rgba(255, 255, 255, 0.08)); }
.pin-option-flag { font-size: 16px; line-height: 1; }
.pin-option-name { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: inherit; }
.pin-option-dial { font-size: 12px; color: #6B7280; }
:where(.dashboard-page, .modal-dialog) .pin-option-dial { color: var(--text-faint, #6B7280); }

/* ===== Violet hero-ground contexts ==========================================
 * The home page's Let's Connect form panel and /book-demo both sit on the
 * shared `rf-hero-ground` violet and use borderless UNDERLINE fields, so in
 * these two scopes the pill box becomes an underline row to match its sibling
 * inputs. Same zero-specificity `:where()` idiom as the dashboard/modal
 * contexts above — these rules win on ORDER, keep them after the base rules.
 *
 * Colour pairings are the ones LetsConnect.css's session-68 block already
 * measured on this exact surface (0.62 white = the 3.3:1 boundary alpha;
 * error #FCA5A5 ≈ 9:1; the valid green is lightened to #86EFAC because the
 * default #16A34A ring/check was tuned for white grounds). The country MENU
 * keeps its white default — a light popover over the violet, like any
 * dropdown. */
:where(.lets-connect-page, .bookdemo-page) .pin {
  background: transparent;
  border: 0;
  border-bottom: 2px solid rgba(255, 255, 255, 0.62);
  border-radius: 0;
  padding: 0;
  color: #FFFFFF;
}
:where(.lets-connect-page, .bookdemo-page) .pin:focus-within {
  border-bottom-color: #FFFFFF;
}
:where(.lets-connect-page, .bookdemo-page) .pin:hover {
  border-bottom-color: #FFFFFF;
}
:where(.lets-connect-page, .bookdemo-page) .pin.is-invalid {
  border-color: transparent;
  border-bottom-color: #FCA5A5;
  box-shadow: none;
}
:where(.lets-connect-page, .bookdemo-page) .pin.is-valid {
  border-color: transparent;
  border-bottom-color: #86EFAC;
  box-shadow: none;
}
:where(.lets-connect-page, .bookdemo-page) .pin.is-valid::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2386EFAC' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  right: 4px;
}
:where(.lets-connect-page, .bookdemo-page) .pin-country {
  padding-left: 0;
}
:where(.lets-connect-page, .bookdemo-page) .pin-country:hover {
  background: rgba(255, 255, 255, 0.1);
}
:where(.lets-connect-page, .bookdemo-page) .pin-country:focus-visible {
  outline: 3px solid rgba(255, 255, 255, 0.55);
  outline-offset: 2px;
}
:where(.lets-connect-page, .bookdemo-page) .pin-caret { color: rgba(255, 255, 255, 0.7); }
:where(.lets-connect-page, .bookdemo-page) .pin-divider { background: rgba(255, 255, 255, 0.35); }
:where(.lets-connect-page, .bookdemo-page) .pin-code { font-size: 14px; }
:where(.lets-connect-page, .bookdemo-page) .pin-input {
  font-size: 16px;
  caret-color: #FFFFFF;
}
:where(.lets-connect-page, .bookdemo-page) .pin-input::placeholder { color: rgba(255, 255, 255, 0.6); }
:where(.lets-connect-page, .bookdemo-page) .pin-input:-webkit-autofill,
:where(.lets-connect-page, .bookdemo-page) .pin-input:-webkit-autofill:hover,
:where(.lets-connect-page, .bookdemo-page) .pin-input:-webkit-autofill:focus,
:where(.lets-connect-page, .bookdemo-page) .pin-input:-webkit-autofill:active {
  /* A solid stand-in for the gradient ground — autofill inset must be one colour. */
  -webkit-box-shadow: 0 0 0 1000px #241A52 inset !important;
  -webkit-text-fill-color: #FFFFFF !important;
  caret-color: #FFFFFF;
}

/* ===== /demo form context ===================================================
 * The demo page's light form card (Demo.css) — boxed inputs with a 12px
 * radius whose rim is `--dm-line-ctl` (#86868F, chosen for the 3:1 component-
 * boundary floor on the #FAFAFA card; the default #D1D5DB rim was tuned for
 * pure-white grounds and measures under it there). Focus mirrors
 * `.demo-input:focus-within` — ring, not just a border hue. */
:where(.demo-page) .pin {
  background: var(--dm-bg, #FFFFFF);
  border-color: var(--dm-line-ctl, #86868F);
  border-radius: 12px;
  color: var(--dm-ink, #000000);
}
:where(.demo-page) .pin:focus-within {
  border-color: var(--dm-accent, #6A5ACD);
  box-shadow: 0 0 0 3px var(--dm-accent-soft, #EEECFA);
}
:where(.demo-page) .pin-input { font-size: 14px; }
:where(.demo-page) .pin-input::placeholder { color: var(--dm-ink-faint, #6B6B75); opacity: 1; }
:where(.demo-page) .pin-caret { color: var(--dm-ink-faint, #6B6B75); }
:where(.demo-page) .pin-divider { background: var(--dm-line-strong, #D4D4D8); }




/* ===== RntlOS Home — Brand monochrome · fully responsive ===== */
/* Archivo Black — heavy geometric sans, matches the "TREND" reference's
   thick block-letter face used for the 3D extrusion style. */
/* Inter — used by the "Let's Connect" section and the feature-stack heading. */
/* Manrope — RentfyvLoader's wordmark + the HeroAdminPanel mockup. Weight 800
   is REQUIRED here: the loader's wordmark declares 800, and with only 700
   loaded the browser silently substitutes the lighter cut — which is why the
   loader read as not-bold. */
/* ⚠️ FRAUNCES LEFT THIS IMPORT IN SESSION 71 (the Design Monks flip). The
   site's faces are now Red Hat Display / Outfit / Playfair Display, loaded
   from index.html as variable ranges and consumed ONLY via the theme.css
   tokens (--rf-font-heading / --rf-font-body / --rf-font-accent). Do not
   re-add a family here — index.html is where theme fonts load. */
/* Thio Filmen Trial — heavy display sans used ONLY for the hero heading.
   NOTE: this is the DEMO/TRIAL release (personal use only). A commercial license
   from twinletter.com is required before production launch. */
@font-face {
  font-family: 'Thio Filmen Trial';
  src: url('/assets/ThioFilmenTrial-CqTBCjvn.ttf') format('truetype'),
       url('/assets/ThioFilmenTrial-D2Wd5fkX.otf') format('opentype');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
.home-page {
  /* LIGHT THEME. The elevation logic is unchanged and simply inverted: the
     page is the darkest surface of the three and cards sit ABOVE it by being
     lighter, so every `--card` consumer keeps reading as raised without a
     single call site being edited.

     Ratios against --bg #F5F5F5 (the worse of the two grounds):
       --text       #333333  11.6:1
       --text-dim   #4A4A52   8.1:1
       --text-faint #6B6B75   4.8:1  ← the floor; do not lighten
     The old --text-faint (#6B7280) was tuned for a charcoal page and measures
     just 4.0:1 here, so it was replaced rather than kept. */
  --bg: #F5F5F5;
  --card: #FFFFFF;
  --card-2: #FAFAFA;
  --card-border: #E4E4E7;
  --card-border-hover: #6A5ACD;

  --text: #333333;
  --text-dim: #4A4A52;
  --text-faint: #6B6B75;
  /* These two were white-alpha, i.e. they only ever showed up ON a dark
     ground. On a light page a white wash is invisible, so they become the
     brand instead — which is also what the brief asks accents to be. */
  --accent-soft: rgba(106, 90, 205, 0.55);
  --accent-glow: rgba(106, 90, 205, 0.18);

  background: var(--bg);
  color: var(--text);
  font-family: var(--rf-font-body, 'Open Sans', Roboto, system-ui, sans-serif);
  min-height: 100vh;
  width: 100%;
  /* Reserve space for the now-fixed top nav (.home-topbar) so content isn't
     hidden under it — matches the nav's height (top padding + pill).
     ⚠️ THE VALUE LIVES IN theme.css AS `--rf-nav-reserve`, because the hero
     cancels this exact amount to run full-bleed to the top of the document
     (see LandingHero.css). Change it there, not here. */
  padding-top: var(--rf-nav-reserve, calc(clamp(12px, 2vw, 24px) + 52px));
  /* `clip` (not `hidden`) so we still clip the full-bleed hero overflow WITHOUT
     turning .home-page into a scroll container (hidden forces overflow-y:auto,
     which collapses ScrollTrigger pin-spacing for the pinned sections inside). */
  overflow-x: clip;
  /* Anchor the full-page dot-grid canvas; required for its `position: absolute;
     inset: 0` to span the whole landing page rather than the viewport. */
  position: relative;
}
/* Ambient low-light depth. Fixed to the viewport (like the dot-grid canvas)
   so a few ultra-subtle cool glows sit behind every section, giving a modern
   dark aesthetic rather than a flat black field. Painted ABOVE the --bg base
   colour but BELOW the dot grid and all content:
     - pointer-events:none keeps it click-through;
     - z-index:0 + source order (::before precedes the canvas child) keeps the
       glow behind the dots, while real content (z-index:1) stays on top.
   Alphas are intentionally tiny — this should read as faint depth, not colour. */
.home-page::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(820px 560px at 16% 4%, rgba(72, 78, 108, 0.16), transparent 60%),
    radial-gradient(1000px 680px at 92% 14%, rgba(48, 54, 84, 0.13), transparent 58%),
    radial-gradient(1200px 900px at 50% 112%, rgba(40, 44, 68, 0.15), transparent 55%);
}
.home-page * { box-sizing: border-box; }
.home-page p,
.home-page h1,
.home-page h2,
.home-page h3 { overflow-wrap: anywhere; word-break: break-word; }
.hero-accent {
  /* Solid grey equivalent to #FAFAFA at 0.55 opacity composited over #000.
     Switched from `opacity: 0.55` to a solid colour so the dot-grid canvas
     beneath this text can't bleed through the glyphs. Visually identical
     to the previous translucent treatment on the black page background. */
  color: #8C8C8C;
}
/* ===== Pill nav ===== */
.home-topbar {
  padding: clamp(12px, 2vw, 24px) clamp(14px, 3vw, 32px) 0;
  display: flex;
  justify-content: center;
  /* Auto-hiding fixed header: pinned to the viewport top (the .home-page wrapper
     reserves its height via padding-top, so nothing jumps), then slides up out of
     view on scroll-down and back in on scroll-up (toggled via .is-hidden from JS).
     Fixed, not sticky — the page's overflow-x:clip breaks position:sticky. */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}
.home-topbar.is-hidden {
  transform: translateY(-135%);
}
@media (prefers-reduced-motion: reduce) {
  .home-topbar { transition: none; }
}
.home-nav-pill {
  width: 100%;
  max-width: 1180px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px 10px clamp(20px, 3vw, 32px);
  /* Lifted glass pill — was rgba(20,20,20,.6) which blends into the charcoal
     page; this composites a touch lighter so the pill reads above --bg. */
  background: rgba(46, 48, 55, 0.6);
  border: 0.5px solid #2C2D34;
  border-radius: 60px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.home-logo {
  cursor: pointer;
  display: flex;
  align-items: center;
  flex-shrink: 0;
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  border-radius: 6px;
  transition: opacity 0.15s ease;
}
.home-logo:hover { opacity: 0.85; }
.home-logo:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; }
.home-logo-text {
  color: var(--text);
  font-size: clamp(15px, 1.4vw, 18px);
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.home-logo-accent { color: var(--text); opacity: 0.55; }
.home-nav-links {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 28px);
}
.home-nav-links span {
  color: var(--text-dim);
  font-size: clamp(12px, 1vw, 14px);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s ease;
}
.home-nav-links span:hover { color: var(--text); }
.home-nav-links span.home-nav-active { color: var(--text); font-weight: 600; }
.home-nav-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.home-pill-btn {
  background: var(--text);
  color: #0A0A0A;
  padding: 11px 22px;
  border-radius: 30px;
  border: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  min-height: 40px;
  transition: background 0.15s ease, transform 0.1s ease;
  /* Host for the click ripple — clip the expanding circle to the pill shape. */
  position: relative;
  overflow: hidden;
}
.home-pill-btn:hover { background: #E5E5E5; }
.home-pill-btn:active { transform: translateY(1px); }
/* Login (ghost) button: transparent at rest; on HOVER a white circle ripples out
   from the centre to fill the pill and the label inverts to black. The base
   .home-pill-btn already sets position:relative + overflow:hidden, which clips
   the expanding circle to the pill shape. */
.home-pill-btn--ghost {
  background: transparent;
  color: var(--text);
  border: 0.5px solid #2A2A2A;
  isolation: isolate;                       /* keep the fill behind the label, inside the pill */
  transition: color 0.28s ease, border-color 0.32s ease;
}
.home-pill-btn--ghost::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150%;
  aspect-ratio: 1;                          /* a circle */
  border-radius: 50%;
  background: #ffffff;
  transform: translate(-50%, -50%) scale(0);
  transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: -1;                              /* behind the "Login" label */
  pointer-events: none;
}
.home-pill-btn--ghost:hover {
  background: transparent;                  /* the white ::before is the fill, not the bg */
  color: #0A0A0A;                           /* black label on hover */
  border-color: #ffffff;
}
.home-pill-btn--ghost:hover::before {
  transform: translate(-50%, -50%) scale(1);
}
@media (prefers-reduced-motion: reduce) {
  .home-pill-btn--ghost::before { transition: none; }
}
/* ===== Eyebrow badges ===== */
.home-eyebrow-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--card);
  border: 0.5px solid var(--card-border);
  padding: 7px 18px;
  border-radius: 24px;
  margin-bottom: 28px;
}
.home-eyebrow-star {
  color: var(--text);
  font-size: 14px;
  line-height: 1;
  /* Blink only the star/dot in the eyebrow badge — the text stays steady.
     1.2s loop with steps(1) gives a crisp on/off blink (no fade). */
  animation: home-eyebrow-blink 1.2s steps(1, end) infinite;
}
@keyframes home-eyebrow-blink {
  0%, 50%   { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .home-eyebrow-star { animation: none; }
}
.home-eyebrow-badge > span:last-child {
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
}
.home-eyebrow-text {
  display: inline-block;
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  margin-bottom: 18px;
  opacity: 0.55;
}
/* ===== Hero — centred, with a dashboard screen that rises on scroll ===== */
.home-hero {
  padding: clamp(18px, 2.6vw, 40px) clamp(18px, 3vw, 40px) 0;
  max-width: 1320px;
  margin: 0 auto;
  position: relative;
  isolation: isolate;
  /* Solid near-black base. This used to be the fallback under the cinematic
     background video; the video layer was removed (operator request), so this
     is now the hero background proper — keep it, or whatever the page paints
     underneath shows through. Never purple. */
  background-color: #0a0a0b;
}
/* Fill the viewport so the dashboard screen's TOP half peeks in and the rest
   sits below the fold (revealed as the user scrolls). */
.home-hero--center {
  text-align: center;
  min-height: 100vh;
  box-sizing: border-box;
}
.home-hero-center {
  max-width: 920px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Fill the first screen and centre the copy in it, so the dashboard — which
     follows in normal flow — starts BELOW the fold instead of overlapping the
     hero. (The scroll-rise that used to lift the dashboard is removed; the
     background video that used to set this height is removed too — the 100vh
     is the design now, not the video's box.) */
  min-height: 100vh;
  justify-content: center;
  /* The box is 100vh tall but sits ~82px down the page (in-flow nav + section pad),
     so a dead-centred copy pushes the bottom proof line past the fold. Bias the copy
     UP within the box (reclaiming the top whitespace) so the whole block — down to the
     App-Store proof line — fits on the first screen. Keeps the box at 100vh so the
     dashboard still starts below the fold (no overlap). */
  padding-bottom: 120px;
  position: relative;
  z-index: 2;
}
/* Badge pill */
.home-hero-badge {
  /* Reference eyebrow: gray uppercase caps, wide tracking — no pill, no dot. */
  display: inline-flex;
  align-items: center;
  font-family: var(--rf-font-body, Georgia, serif);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: #9a9aa0;                 /* gray */
  background: none;
  border: none;
  padding: 0;
  border-radius: 0;
  margin-bottom: 30px;
}
.home-hero-badge-dot { display: none; }
/* Centred headline, with "one dashboard" in the purple→blue gradient. */
.home-hero-heading {
  /* Dead rule — no JSX renders this class (the live hero is .hero-headline,
     LandingHero.css). Kept on the token so it cannot drift if revived. */
  font-family: var(--rf-font-heading, 'Segoe UI', Arial, sans-serif);
  font-weight: 300;
  font-size: clamp(42px, 6.4vw, 82px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  color: #ffffff;                 /* colour 1 of 2: white */
  margin: 0 0 14px;
  max-width: 16ch;
  font-optical-sizing: auto;
}
/* Heading is all white now — neutralise the purple→blue gradient on the
   highlighted word so it matches the rest of the heading. */
.home-hero-hl {
  /* Italic accent — colour 2 of 2: gray (ref uses brass here). */
  font-style: italic;
  font-weight: 400;
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  color: #9a9aa0;
  -webkit-text-fill-color: #9a9aa0;
}
/* CTA row: white "Book a 15-min demo" + ghost "Start free trial". */
.home-hero-ctas {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
  margin-bottom: 20px;
}
.home-hero-cta-demo { display: inline-flex; align-items: center; gap: 9px; }
.home-hero-cta-arrow { transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1); }
.home-hero-cta-demo:hover .home-hero-cta-arrow { transform: translateX(4px); }
.home-hero-cta-ghost {
  position: relative;                         /* anchor the ::before fill */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--text);
  border: 1px solid var(--card-border-hover);
  padding: 14px 26px;
  border-radius: 30px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  min-height: 44px;
  overflow: hidden;                           /* clip the circular fill to the pill */
  isolation: isolate;                         /* keep the fill behind the label, inside the pill */
  transition: color 0.28s ease, border-color 0.32s ease, transform 0.1s ease;
}
/* Same white circular fill-on-hover as the Login button (home-pill-btn--ghost):
   a white circle scales up from the centre, turning the label black. */
.home-hero-cta-ghost::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150%;
  aspect-ratio: 1;                            /* a circle */
  border-radius: 50%;
  background: #ffffff;
  transform: translate(-50%, -50%) scale(0);
  transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: -1;                                /* behind the label */
  pointer-events: none;
}
.home-hero-cta-ghost:hover {
  background: transparent;                    /* the white ::before is the fill, not the bg */
  color: #0A0A0A;                             /* black label on hover */
  border-color: #ffffff;
  transform: translateY(-1px);
}
.home-hero-cta-ghost:hover::before {
  transform: translate(-50%, -50%) scale(1);
}
.home-hero-cta-ghost:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) {
  .home-hero-cta-ghost::before { transition: none; }
}
/* App-Store proof line under the CTAs. */
.home-hero-proof {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  justify-content: center;
  color: var(--text-dim);
  font-size: 13.5px;
  font-weight: 600;
}
.home-hero-proof-item { display: inline-flex; align-items: center; gap: 8px; }
.home-hero-proof-muted { color: var(--text-faint); font-weight: 500; }
.home-hero-stars { color: #fbbf24; letter-spacing: 2px; font-size: 13px; }
.home-hero-proof-divider { width: 1px; height: 15px; background: var(--card-border-hover); }
/* ===== Dashboard screen + purple→blue glow (rises on scroll) ===== */
.home-hero-stage {
  position: relative;
  /* Size the dashboard to the viewport HEIGHT (not just a fixed width) so the
     hero composition fits the frame consistently at every resolution: it scales
     down on short screens (so the dashboard isn't badly cut) and caps at 1500px
     on tall ones. Wide enough that, even mid-rise (scaled), it covers the proof
     line so the ★★★★★ / "cities" text never peeks out beside it. */
  /* Bigger dashboard — break out beyond the 1320px hero column and centre it on
     the hero. Capped by width (95vw), a px max, AND height (168vh) so it still
     shrinks on short screens and never gets cut off. */
  width: min(1720px, 95vw, 168vh);
  margin: clamp(8px, 1.4vw, 22px) 0 0;
  left: 50%;
  transform: translateX(-50%);
  perspective: 2400px;
  /* Above the copy (z-index:2) so the dashboard STACKS over the sub/CTA block as
     it rises on scroll. At rest the screen sits below the copy and they don't
     overlap, so this has no visual effect until the scroll-rise begins. */
  z-index: 3;
}
/* (removed) .home-hero-stage-glow — the purple→blue radial glow behind the
   dashboard. The cinematic video background replaces it. */
.home-hero-screen {
  position: relative;
  z-index: 1;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 16px;
  overflow: hidden;
  background: #0f0f12;
  transform-origin: center center;
  /* Neutral edge highlight + depth (was a purple→blue glow — removed). */
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.05),
    0 50px 110px -40px rgba(0, 0, 0, 0.85);
  transform: none; /* flat — the dashboard sits statically below the hero video (no scroll-rise / tilt) */
  /* NOTE: deliberately NO `will-change: transform`. On this rotateX-tilted
     element holding a big screenshot it forces a fixed-resolution GPU texture
     that gets sampled → the dashboard looks blurry. Without it the browser
     re-rasterizes at the displayed resolution each frame → sharp. */
}
.home-hero-screen-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 11px 14px;
  border-bottom: 0.5px solid var(--card-border);
  background: #141418;
}
.home-hero-dot { width: 11px; height: 11px; border-radius: 50%; background: #2a2a30; flex: none; }
.home-hero-dot--r { background: #ff5f57; }
.home-hero-dot--y { background: #febc2e; }
.home-hero-dot--g { background: #28c840; }
.home-hero-url {
  margin-left: 10px;
  font-size: 11px;
  color: var(--text-faint);
  font-weight: 600;
}
/* container-type lets the live sidebar size itself in cqw units so it scales
   proportionally with the screen at any GSAP scale. */
.home-hero-screen-shot { position: relative; background: var(--card-2); container-type: inline-size; }
/* Base page defines the box height; overlay pages stack on top and crossfade. */
.hhs-img { display: block; }
.hhs-img--base { width: 100%; height: auto; }
.hhs-img--over {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.4s ease;
  pointer-events: none;
}
.hhs-img--over[data-active='true'] { opacity: 1; }
/* Clickable sidebar hotspots over the left 16% of the screenshot. */
.hhs-sidebar {
  position: absolute;
  left: 0;
  top: 0;
  width: 16%;
  height: 100%;
  z-index: 4;
}
.hhs-hot {
  position: absolute;
  left: 0;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: 6px;
  transition: background 0.15s ease, box-shadow 0.15s ease;
}
.hhs-hot:hover { background: rgba(139, 108, 255, 0.16); box-shadow: inset 3px 0 0 #8b6cff; }
.hhs-hot[data-active='true'] { box-shadow: inset 3px 0 0 rgba(139, 108, 255, 0.7); }
.hhs-hot:focus-visible { outline: 2px solid #8b6cff; outline-offset: -2px; }
/* Live sidebar overlay — covers the screenshot's baked sidebar (left 16%),
   matching the admin dark sidebar. Sized in cqw so it scales with the screen. */
.hhs-livebar {
  position: absolute;
  left: 0;
  top: 0;
  width: 16%;
  height: 100%;
  z-index: 5;
  background: #0c0c0e;
  border-right: 1px solid rgba(255, 255, 255, 0.05);
  display: flex;
  flex-direction: column;
  padding: 2.2cqw 1.1cqw;
  overflow: hidden;
}
.hhs-lb-logo {
  color: #f4f4f5;
  font-weight: 700;
  font-size: 1.7cqw;
  letter-spacing: -0.01em;
  white-space: nowrap;
  margin: 0.4cqw 0.6cqw 2.2cqw;
}
.hhs-lb-accent { color: #8b6cff; }
.hhs-lb-nav { display: flex; flex-direction: column; gap: 0.5cqw; flex: 1; min-height: 0; }
.hhs-lb-item {
  display: flex;
  align-items: center;
  gap: 1cqw;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  padding: 0.95cqw 0.85cqw;
  border-radius: 1cqw;
  color: #9a9aa1;
  font-family: inherit;
  font-weight: 600;
  font-size: 1.06cqw;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease;
}
.hhs-lb-item svg { width: 1.5cqw; height: 1.5cqw; flex: none; }
.hhs-lb-item:hover { color: #f4f4f5; background: rgba(255, 255, 255, 0.04); }
.hhs-lb-item[data-active='true'] { background: #1c1c1f; color: #fff; font-weight: 700; }
.hhs-lb-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.8cqw;
  margin-top: auto;
  padding: 1.1cqw 0.85cqw;
  border-radius: 1.4cqw;
  border: none;
  background: #fafafa;
  color: #0a0a0b;
  font-family: inherit;
  font-weight: 700;
  font-size: 1.06cqw;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.1s ease;
}
.hhs-lb-cta svg { width: 1.3cqw; height: 1.3cqw; flex: none; }
.hhs-lb-cta:hover { background: #ffffff; }
.hhs-lb-cta:active { transform: translateY(1px); }
/* Mobile: drop the perspective tilt, render flat. */
@media (max-width: 767px) {
  .home-hero-heading { max-width: 18ch; }
  .home-hero-stage { perspective: none; }
  .home-hero-screen { transform: none; }
}
/* ===== Trust strip — "Fleets running on RntlOS" (drifting marquee) ===== */
.home-trust {
  position: relative;
  z-index: 1;
  padding: 30px 0;
  border-top: 0.5px solid var(--card-border);
  border-bottom: 0.5px solid var(--card-border);
  /* OPAQUE (was rgba(0,0,0,0.18)) so the fixed Antigravity particle field does
     not show through this strip. Matches the hero veil base colour above. */
  background: #060311;
}
.home-trust-label {
  text-align: center;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-weight: 700;
  margin: 0 0 18px;
}
.home-trust-marquee {
  width: 100%;
  overflow: hidden;
  /* Fade the names into the page at both edges. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 8%, #000 92%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 8%, #000 92%, transparent 100%);
}
.home-trust-track {
  display: flex;
  width: max-content;
  will-change: transform;
}
.home-trust-group {
  display: flex;
  align-items: center;
  gap: clamp(28px, 5vw, 64px);
  padding-right: clamp(28px, 5vw, 64px);
}
.home-trust-item {
  /* Rendered as a <button> so the names are clickable (→ testimonials). */
  appearance: none;
  background: none;
  border: none;
  padding: 4px 2px;
  margin: 0;
  font-family: inherit;
  font-weight: 700;
  font-size: clamp(15px, 2vw, 20px);
  color: var(--text-dim);
  letter-spacing: -0.01em;
  white-space: nowrap;
  opacity: 0.7;
  cursor: pointer;
  transition: color 0.18s ease, opacity 0.18s ease, transform 0.18s ease;
}
.home-trust-item:hover,
.home-trust-item:focus-visible {
  color: var(--text);
  opacity: 1;
  transform: translateY(-1px);
  outline: none;
}
.home-trust-item:focus-visible { text-decoration: underline; text-underline-offset: 4px; }
/* ===== Fisheye SVG heading ===== */
/* The heading is now a single <svg> rendered by LandingFisheyeHeading.
   It scales to its container width, preserves aspect ratio via the
   viewBox (900x520), and gets a comfortable max-width so it doesn't
   blow up on ultra-wide displays. */
.fisheye-heading {
  display: block;
  width: 100%;
  max-width: 900px;
  margin: 0 auto 22px;
  /* SVG <text> elements aren't selectable by default — make sure the
     barrel-distorted text still appears crisply on dark backgrounds. */
  shape-rendering: geometricPrecision;
  text-rendering: geometricPrecision;
}
/* Each line is wrapped in a <g class="fisheye-line"> with inline CSS
   transforms driving the scaleX/opacity reveal. Nothing else to do here —
   the inline styles set the transform and transform-origin per line. */
.fisheye-line {
  /* GPU promotion smooths the scaleX transition on lower-end devices */
  will-change: transform, opacity;
}
@media (prefers-reduced-motion: reduce) {
  .fisheye-line { transition: none !important; }
}
.home-hero-sub {
  font-family: var(--rf-font-body, Georgia, serif);
  color: #9a9aa0;                 /* gray */
  font-size: 18px;                /* exact ref size */
  font-weight: 500;
  line-height: 1.65;
  max-width: 56ch;
  margin: 20px auto 18px;
}
/* Bold spans in the sub stay white (ref: .sub b { color: ink }). */
.home-hero-sub b,
.home-hero-sub strong { color: #ffffff; font-weight: 600; }
.home-hero-cta { margin-bottom: 22px; }
.home-cta-primary {
  background: var(--text);
  color: #0A0A0A;
  padding: 14px 28px;
  border-radius: 30px;
  border: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  min-height: 44px;
  transition: background 0.15s ease, transform 0.1s ease;
}
.home-cta-primary:hover { background: #E5E5E5; }
.home-cta-primary:active { transform: translateY(1px); }
.home-hero-promo {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--card);
  border: 0.5px solid var(--card-border);
  padding: 6px 6px 6px 16px;
  border-radius: 30px;
  margin-bottom: 16px;
  flex-wrap: wrap;
  justify-content: center;
}
.hero-email-input span {
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 400;
}
.hero-email-button {
  background: var(--text);
  color: #0A0A0A;
  padding: 9px 18px;
  border-radius: 24px;
  border: none;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.home-hero-trust {
  display: flex;
  justify-content: center;
  gap: 20px;
  margin-bottom: 40px;
  flex-wrap: wrap;
}
.trust-item {
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.trust-check {
  color: var(--text);
  font-weight: 700;
}
/* ===== Dashboard mockup ===== */
.home-dashboard {
  margin: 0 auto;
  max-width: 1100px;
  background: var(--card);
  border: 0.5px solid var(--card-border);
  border-radius: 18px;
  padding: 16px;
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: 16px;
  text-align: left;
  position: relative;
  box-shadow: 0 0 80px -20px var(--accent-glow);
}
.dash-sidebar {
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  border-radius: 12px;
  padding: 14px 12px;
  display: flex;
  flex-direction: column;
  min-height: 480px;
}
.dash-side-logo { margin-bottom: 22px; }
.dash-side-logo .home-logo-text { font-size: 14px; }
.dash-side-section {
  color: var(--text-faint);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.1em;
  margin-bottom: 10px;
}
.dash-side-menu {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
}
.dash-side-menu li {
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 500;
  padding: 7px 10px;
  border-radius: 6px;
}
.dash-side-menu li.dash-side-active {
  background: #1A1A1A;
  color: var(--text);
}
.dash-side-upsell {
  background: var(--text);
  color: #0A0A0A;
  text-align: center;
  padding: 8px;
  border-radius: 8px;
  font-size: 11px;
  font-weight: 600;
  margin-top: auto;
}
.dash-main {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}
.dash-topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 4px;
}
.dash-title {
  color: var(--text);
  font-size: 14px;
  font-weight: 700;
}
.dash-pill {
  background: var(--card-2);
  color: var(--text-dim);
  padding: 5px 12px;
  border-radius: 20px;
  border: 0.5px solid var(--card-border);
  font-size: 10px;
  font-weight: 500;
}
.dash-overview-card {
  background: var(--text);
  color: #0A0A0A;
  border-radius: 10px;
  padding: 14px;
  position: relative;
  overflow: hidden;
}
.dash-overview-head {
  display: flex;
  justify-content: space-between;
  margin-bottom: 12px;
  font-size: 11px;
  font-weight: 600;
  color: #0A0A0A;
}
.dash-pill-mini {
  background: #0A0A0A;
  color: var(--text);
  padding: 3px 8px;
  border-radius: 10px;
  font-size: 9px;
}
.dash-table { display: flex; flex-direction: column; gap: 6px; font-size: 9px; min-width: 0; }
.dash-table-row {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr 1fr 1fr;
  gap: 8px;
  align-items: center;
  padding: 6px 4px;
  border-radius: 4px;
  color: #1A1A1A;
}
.dash-table-row.dash-table-head {
  color: #6B7280;
  font-weight: 600;
  font-size: 8px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.dash-status {
  font-weight: 600;
  font-size: 8px;
  padding: 3px 7px;
  border-radius: 8px;
  background: #E5E5E5;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #1A1A1A;
}
.dash-status-inprogress { background: #1A1A1A; color: var(--text); }
.dash-status-cancelled { background: #FEE2E2; color: #991B1B; }
.dash-status-complete { background: #D1FAE5; color: #065F46; }
.dash-bottom-row {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 12px;
  min-width: 0;
}
.dash-chart-card {
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  border-radius: 10px;
  padding: 12px;
}
.dash-chart-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 12px;
}
.dash-tiny { color: var(--text-faint); font-size: 9px; font-weight: 500; margin: 0; }
.dash-big {
  color: var(--text);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 4px 0 0;
}
.dash-chart {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 130px;
}
.dash-bar {
  flex: 1;
  background: #1A1A1A;
  border-radius: 2px 2px 0 0;
}
.dash-bar:nth-child(odd) { background: #FAFAFA; opacity: 0.92; }
.dash-fleet-list {
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  border-radius: 10px;
  padding: 12px;
}
.dash-fleet-list ul { list-style: none; padding: 0; margin: 8px 0 0; display: flex; flex-direction: column; gap: 6px; }
.dash-fleet-list li {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 9px;
  color: var(--text-dim);
}
.dash-fleet-thumb {
  width: 16px;
  height: 12px;
  background: #1A1A1A;
  border-radius: 2px;
  flex-shrink: 0;
}
.dash-stats-col { display: flex; flex-direction: column; gap: 8px; }
.dash-stat {
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  border-radius: 10px;
  padding: 10px;
}
.dash-business-card {
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  border-radius: 10px;
  padding: 10px;
}
.dash-spark { width: 100%; height: 28px; margin-top: 6px; }
/* ===== macOS window chrome (wraps the dashboard mockup's right panel) ===== */
.mac-window {
  background: #0a0a0a;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  box-shadow:
    0 14px 40px rgba(0, 0, 0, 0.55),
    0 4px 10px rgba(0, 0, 0, 0.40);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Lets the window participate in narrow grid cells without horizontal overflow */
  min-width: 0;
}
.mac-titlebar {
  position: relative;
  height: 44px;
  background: #161616;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.mac-traffic-lights {
  position: absolute;
  left: 20px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  gap: 8px;
  align-items: center;
}
.mac-light {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
  /* Subtle inner darkening at the bottom edge so the dots feel like real
     macOS buttons rather than flat circles. */
  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.18);
}
.mac-light-red    { background: #ff5f57; }
.mac-light-yellow { background: #febc2e; }
.mac-light-green  { background: #28c840; }
.mac-title {
  font-family: 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, Consolas, monospace;
  font-size: 13px;
  color: rgba(255, 255, 255, 0.7);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Reserve horizontal room on both sides of the title so it stays visually
     centered against the traffic lights and doesn't crash into them. */
  max-width: calc(100% - 160px);
  letter-spacing: 0;
}
.mac-body {
  padding: 24px;
  flex: 1;
  min-width: 0;
}
@media (max-width: 1024px) {
  .mac-body { padding: 16px; }
  .mac-title { font-size: 12px; max-width: calc(100% - 120px); }
}
@media (max-width: 560px) {
  .mac-titlebar { height: 38px; }
  .mac-traffic-lights { left: 12px; gap: 6px; }
  .mac-light { width: 10px; height: 10px; }
  .mac-title { font-size: 11px; max-width: calc(100% - 90px); }
}
.home-hero-stamp {
  margin-top: 32px;
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.15em;
}
/* ===== Stats strip ===== */
.home-stats-strip {
  /* Unified home section spacing (matches the testimonials / FAQ / pricing
     sections) so the gap between every standard section is consistent. */
  padding: clamp(56px, 8vw, 100px) clamp(18px, 3vw, 32px);
  border-top: 0.5px solid transparent;
  border-bottom: 0.5px solid transparent;
  background: var(--card-2);
}
.stats-strip-inner {
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(20px, 3vw, 40px);
  text-align: center;
}
.stat-block-num {
  color: var(--text);
  font-size: clamp(28px, 3.5vw, 42px);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0 0 4px;
}
.stat-block-label {
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 500;
  margin: 0;
}
/* ===== Feature sections ===== */
.home-feature {
  padding: clamp(48px, 7vw, 90px) clamp(18px, 3vw, 32px);
}
/* ===== Scroll-driven feature card deck (desktop only — GSAP pins this and
   deals the three cards out as you scroll; see LandingFeatures.jsx). ===== */
.feature-stack {
  position: relative;
  /* Two columns: sticky-stacking cards on the LEFT, a sticky heading on the
     RIGHT. Pure CSS sticky — NO GSAP pin, NO Lenis. */
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(20px, 4vw, 64px);
  padding: clamp(40px, 6vw, 90px) clamp(18px, 3vw, 40px) clamp(64px, 11vw, 170px);
  background: transparent; /* shows the jet-black band behind it */
  /* Tunable stack feel: pin offset, per-card peek step, scroll gap between cards. */
  --stack-top: 120px;
  --stack-step: 22px;
  --stack-gap: 16vh;
}
.feature-stack-cards-col {
  min-width: 0;
}
/* Each card is a sticky element pinned a little lower than the previous one, so
   the cards beneath peek. The margin gives the scroll distance between stacks. */
.feature-stack-item {
  position: sticky;
  top: calc(var(--stack-top) + var(--i, 0) * var(--stack-step));
  display: flex;
  justify-content: center;
  margin-bottom: var(--stack-gap);
}
.feature-stack-item:last-child {
  margin-bottom: 0;
}
/* Right column: a heading that sticks (stays put) while the cards scroll/stack. */
.feature-stack-aside {
  min-width: 0;
}
.feature-stack-heading {
  position: sticky;
  top: 38vh;
  margin: 0;
  /* A HEADING — takes the heading token (session 68; it hardcoded Inter). */
  font-family: var(--rf-font-heading, Georgia, serif);
  font-size: clamp(34px, 4.6vw, 72px);
  font-weight: 800;
  line-height: 1.04;
  letter-spacing: -0.02em;
  color: #ffffff; /* override the global h2 dark colour → readable on the dark band */
}
.feature-stack-emoji {
  font-weight: 400;
}
/* Mobile / narrow: single column — heading on top, cards stack below. */
@media (max-width: 860px) {
  .feature-stack {
    grid-template-columns: 1fr;
    --stack-top: 90px;
  }
  .feature-stack-aside {
    order: -1;
  }
  .feature-stack-heading {
    position: static;
    top: auto;
    text-align: center;
    margin-bottom: clamp(16px, 4vw, 32px);
    font-size: clamp(30px, 8vw, 48px);
  }
}
/* Feature deck card content sits ABOVE its AnimatedGradient backdrop. */
.feature-stack-card .home-feature {
  position: relative;
  z-index: 1;
}
/* Compact the feature content INSIDE the deck cards so the cards stay small
   (the mobile/reduced .home-feature layout is left at full size). */
.feature-stack-card .home-feature {
  padding: clamp(16px, 1.8vw, 26px) clamp(16px, 1.8vw, 24px);
}
.feature-stack-card .home-feature-inner {
  gap: clamp(14px, 1.8vw, 26px);
}
.feature-stack-card .feature-title {
  font-size: clamp(16px, 1.7vw, 22px);
  margin-bottom: 8px;
}
.feature-stack-card .feature-body {
  font-size: 12.5px;
  line-height: 1.5;
  margin-bottom: 12px;
}
.feature-stack-card .feature-eyebrow {
  font-size: 10.5px;
}
.feature-stack-card .feature-link {
  font-size: 12px;
}
.feature-stack-card .feature-list {
  font-size: 12.5px;
  gap: 6px;
}
.feature-stack-card {
  width: min(480px, 100%); /* smaller cards, sit in the left column */
  position: relative; /* containing block + clip for the AnimatedGradient */
  overflow: hidden;
  will-change: transform;
  background: var(--card);
  border: 0.5px solid var(--card-border);
  border-radius: 22px;
  box-shadow:
    0 30px 70px rgba(0, 0, 0, 0.6),
    0 0 0 1px rgba(255, 255, 255, 0.02);
  backface-visibility: hidden;
  /* Slight per-card tilt so the stacked edges read as a deck (card 0: -1.4°,
     card 1: 0°, card 2: +1.4°). */
  transform: rotate(calc((var(--i, 1) - 1) * 1.4deg));
}
/* Compact the existing section padding so each block fits a card without any
   change to the card's inner content/typography. */
.feature-stack-card .home-feature {
  padding: clamp(22px, 3vw, 42px);
}
.home-feature-inner {
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1.1fr;
  align-items: center;
  gap: clamp(28px, 5vw, 64px);
}
.home-feature-reverse .home-feature-inner > .feature-text { order: 2; }
.home-feature-reverse .home-feature-inner > .feature-art { order: 1; }
.feature-eyebrow {
  display: inline-block;
  color: var(--text);
  opacity: 0.55;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  margin-bottom: 16px;
}
.feature-title {
  color: var(--text);
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 0 0 18px;
}
.feature-body {
  color: var(--text-dim);
  font-size: 15px;
  font-weight: 400;
  line-height: 1.6;
  margin: 0 0 22px;
}
.feature-list {
  list-style: none;
  padding: 0;
  margin: 0 0 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.feature-list li {
  color: var(--text);
  font-size: 14px;
  font-weight: 400;
  display: flex;
  align-items: center;
  gap: 10px;
}
.feat-check {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--text);
  color: #0A0A0A;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
  flex-shrink: 0;
}
.feature-link {
  background: transparent;
  border: none;
  padding: 6px 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: gap 0.15s ease, opacity 0.15s ease;
}
.feature-link:hover { gap: 10px; opacity: 0.85; }
.feature-link span { font-size: 16px; }
.feature-art-svg { width: 100%; height: auto; display: block; border-radius: 14px; }
/* ===== PAS — the cost of the old way (left) + the fix card (right) ===== */
.home-pas {
  padding: clamp(56px, 8vw, 100px) clamp(18px, 3vw, 32px);
}
.home-pas-inner {
  max-width: 1180px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(32px, 4.5vw, 60px);
  align-items: stretch;
  text-align: left;
}
.home-pas-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #9b8cff;
  margin-bottom: 18px;
}
.home-pas-eyebrow-dot { width: 6px; height: 6px; border-radius: 50%; background: #8b6cff; flex: none; }
.home-pas-title {
  font-size: clamp(28px, 3.6vw, 44px);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0 0 26px;
  max-width: 16ch;
}
.home-pas-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.home-pas-item {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  font-size: clamp(14px, 1.3vw, 16px);
  color: var(--text-dim);
  font-weight: 500;
}
.home-pas-x {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(248, 113, 113, 0.14);
  color: #f87171;
  display: grid;
  place-items: center;
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  margin-top: 1px;
}
.home-pas-fix {
  background: linear-gradient(160deg, var(--card-2), var(--card));
  border: 0.5px solid var(--card-border-hover);
  border-radius: 20px;
  padding: clamp(26px, 3vw, 38px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  display: flex;
  flex-direction: column;
}
.home-pas-tag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #4ade80;
  margin-bottom: 14px;
}
.home-pas-tag-dot { width: 6px; height: 6px; border-radius: 50%; background: #4ade80; flex: none; }
.home-pas-fix-title {
  font-size: clamp(20px, 2.2vw, 24px);
  font-weight: 700;
  color: var(--text);
  margin: 0 0 12px;
  letter-spacing: -0.01em;
}
.home-pas-fix-body {
  color: var(--text-dim);
  font-size: clamp(14px, 1.3vw, 16px);
  line-height: 1.6;
  margin: 0;
}
.home-pas-link {
  margin-top: 20px;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  padding: 0;
  transition: gap 0.2s ease;
}
.home-pas-link:hover { gap: 12px; }
@media (max-width: 820px) {
  .home-pas-inner { grid-template-columns: 1fr; gap: 26px; }
  .home-pas-title { max-width: none; }
}
/* ===== Guided look inside — alternating value rows (chart / calendar) ===== */
.home-tour {
  padding: clamp(56px, 8vw, 100px) clamp(18px, 3vw, 32px);
  text-align: left;
}
.home-tour-inner { max-width: 1180px; margin: 0 auto; }
.home-tour-head { text-align: center; margin-bottom: clamp(40px, 6vw, 66px); }
.home-tour-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #9b8cff;
  margin-bottom: 16px;
}
.home-tour-eyebrow-dot { width: 6px; height: 6px; border-radius: 50%; background: #8b6cff; flex: none; }
.home-tour-title {
  font-size: clamp(28px, 3.8vw, 46px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0 auto;
  max-width: 20ch;
}
.home-tour-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(32px, 4.5vw, 56px);
  align-items: center;
  margin-bottom: clamp(48px, 7vw, 90px);
}
.home-tour-row:last-child { margin-bottom: 0; }
.home-tour-text h3 {
  font-size: clamp(22px, 2.6vw, 32px);
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0 0 16px;
  max-width: 15ch;
}
.home-tour-text p {
  color: var(--text-dim);
  font-size: clamp(14px, 1.4vw, 17px);
  line-height: 1.6;
  margin: 0;
  max-width: 42ch;
}
.home-tour-cap { margin-top: 18px; font-size: 14px; font-weight: 700; color: #9b8cff; }
/* Asset frames */
.home-tour-asset {
  border: 0.5px solid var(--card-border);
  border-radius: 18px;
  background: var(--card-2);
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.7);
  overflow: hidden;
}
.home-tour-asset-pad { padding: 22px; }
.home-tour-asset--shot img { width: 100%; display: block; }
/* ===== Live calendar UI (luxury_calendar_ui.zip) in a Mac 3-dot panel ===== */
.home-tour-calcol { min-width: 0; }
.home-tour-cal {
  width: 100%;
  border: 0.5px solid var(--card-border);
  border-radius: 16px;
  overflow: hidden;
  background: #090b10;
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.7);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.home-tour-cal-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 11px 14px;
  background: #0b0f17;
  border-bottom: 0.5px solid #171b24;
}
.home-tour-cal-dot { width: 11px; height: 11px; border-radius: 50%; flex: none; }
.home-tour-cal-dot--r { background: #ff5f57; }
.home-tour-cal-dot--y { background: #febc2e; }
.home-tour-cal-dot--g { background: #28c840; }
.home-tour-cal-body { padding: 14px; }
.home-tour-cal-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.home-tour-cal-filters { display: flex; gap: 7px; }
.lux-pill {
  background: #0f1118;
  border: 1px solid #1f2430;
  color: #cdd3df;
  padding: 7px 11px;
  border-radius: 10px;
  font-size: 10.5px;
  font-weight: 600;
  white-space: nowrap;
}
.home-tour-cal-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  background: #090b10;
  border: 1px solid #171b24;
  border-radius: 14px;
  overflow: hidden;
}
.home-tour-cal-dayhead {
  padding: 9px 6px;
  background: #0b0f17;
  color: #7d8597;
  border-bottom: 1px solid #171b24;
  border-right: 1px solid #171b24;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.home-tour-cal-cell {
  min-height: 62px;
  border-right: 1px solid #171b24;
  border-bottom: 1px solid #171b24;
  padding: 7px 6px;
  position: relative;
  overflow: hidden;
}
.home-tour-cal-dayhead:nth-child(7n),
.home-tour-cal-cell:nth-child(7n) { border-right: none; }
.home-tour-cal-date { color: #76839a; font-size: 10px; }
.home-tour-cal-booking {
  margin-top: 6px;
  padding: 5px 7px;
  border-radius: 8px;
  font-size: 9px;
  font-weight: 600;
  color: #e7ebf2;
  display: flex;
  align-items: center;
  gap: 5px;
}
.lux-booking-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.lux-blue { background: #182033; border-left: 3px solid #4f7cff; }
.lux-white { background: #1d1d1d; border-left: 3px solid #ffffff; }
.lux-red { background: #301414; border-left: 3px solid #ff5858; }
.lux-green { background: #13261c; border-left: 3px solid #35d07f; }
.lux-tag {
  background: #ff4d4d;
  color: #ffffff;
  padding: 2px 5px;
  border-radius: 6px;
  font-size: 7.5px;
  text-transform: uppercase;
  flex: none;
}
/* Bar chart */
.home-tour-bars {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  height: 160px;
  padding: 14px 4px 0;
}
.home-tour-bar {
  flex: 1;
  border-radius: 6px 6px 0 0;
  background: linear-gradient(180deg, #8b6cff, rgba(139, 108, 255, 0.22));
}
.home-tour-bar--b2 { background: linear-gradient(180deg, #5b9dff, rgba(91, 157, 255, 0.22)); }
.home-tour-legend {
  display: flex;
  justify-content: space-between;
  margin-top: 12px;
  font-size: 11px;
  color: var(--text-faint);
  font-weight: 600;
}
.home-tour-legend--top { margin-top: 0; margin-bottom: 6px; }
.home-tour-pos { color: #4ade80; }
@media (max-width: 820px) {
  .home-tour-row { grid-template-columns: 1fr; gap: 24px; }
}
/* ===== Tools grid ===== */
.home-tools {
  /* Compact vertical padding: this section lives inside the pinned showcase,
     which scales the whole slide to fit one viewport. Less chrome → the slide
     fits at a far larger scale, so the dashboard + text stay readable. */
  padding: clamp(20px, 3vw, 36px) clamp(18px, 3vw, 32px);
  text-align: center;
  border-top: 0.5px solid transparent;
}
.home-tools-inner { max-width: 1180px; margin: 0 auto; }
/* Two columns: smaller card deck on the LEFT, Mac-window dashboard on the RIGHT. */
.home-tools-cols {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: clamp(26px, 4vw, 60px);
  align-items: center;
  width: 100%;
  margin-top: clamp(14px, 2vw, 22px);
  position: relative; /* positioning context + offsetParent for the connector web */
}
/* The cards + dashboard sit ABOVE the SVG connector web. */
.home-tools-cols > .tools-grid,
.home-tools-cols > .home-tools-side,
.home-tools-cols > .home-tools-pipe-left,
.home-tools-cols > .home-tools-mac { position: relative; z-index: 1; }
/* ===== Data-pipeline layout: all cards LEFT, dashboard RIGHT, glowing ropes
   converging from each card into a hub at the dashboard's edge. ===== */
.home-tools-cols--rope {
  grid-template-columns: max-content 1fr;
  align-items: center;
  gap: clamp(36px, 6vw, 104px); /* rope span — tightened so the dashboard gets more width */
  position: relative;
}
.home-tools-pipe-left {
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2.4vw, 26px); /* more distance between the pills */
}
.home-tools-cols--rope .home-tools-card-wrap { width: max-content; }
/* Small, round pills — icon + struck-through (replaced) tool name only.
   width:max-content so each pill is only as long as its content. */
.home-tools-cols--rope .tool-card { width: max-content; padding: 12px 26px 12px 12px; border-radius: 999px; }
.home-tools-cols--rope .tool-cross-out { gap: 13px; margin-bottom: 0; }
.home-tools-cols--rope .tool-icon-box { width: 42px; height: 42px; border-radius: 50%; font-size: 18px; }
.home-tools-cols--rope .tool-strike { font-size: 18.5px; }
/* The rope SVG layer — fades in when the section scrolls into view. */
.home-tools-rope {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.8s ease;
  overflow: visible;
}
.home-tools-cols--rope.is-on .home-tools-rope { opacity: 1; }
/* Thick glowing-blue ropes: a blurred glow underlay, a gradient core, and a
   bright flowing pulse on top. */
.rope-glow {
  fill: none;
  stroke: #2b8cff;
  stroke-width: 9;
  opacity: 0.35;
  stroke-linecap: round;
  filter: blur(3px);
}
.rope-core {
  fill: none;
  stroke: url(#ropeGrad);
  stroke-width: 3.5;
  stroke-linecap: round;
  opacity: 0.95;
}
.rope-flow {
  fill: none;
  stroke: #cdebff;
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-dasharray: 1 24;
  opacity: 0.95;
  animation: ropeFlow 1.5s linear infinite;
}
.rope-origin { fill: #7dc5ff; filter: drop-shadow(0 0 4px #2b8cff); }
.rope-hub-glow { fill: url(#ropeHubGlow); }
.rope-hub-core { fill: #e8f4ff; filter: drop-shadow(0 0 6px #4da6ff); }
.rope-hub-ring {
  fill: none;
  stroke: #5db4ff;
  stroke-width: 1.5;
  transform-box: fill-box;
  transform-origin: center;
  animation: ropePulse 2.3s ease-out infinite;
}
@keyframes ropeFlow { to { stroke-dashoffset: -50; } }
@keyframes ropePulse {
  0%   { transform: scale(1);   opacity: 0.7; }
  70%  { opacity: 0; }
  100% { transform: scale(2.9); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .rope-flow, .rope-hub-ring { animation: none; }
}
/* Mobile / narrow: single column — cards then dashboard, no ropes. */
@media (max-width: 980px) {
  .home-tools-cols--rope {
    grid-template-columns: 1fr;
    gap: 16px;
    max-width: 460px;
    margin-left: auto;
    margin-right: auto;
  }
  .home-tools-cols--rope .home-tools-mac { margin: 6px auto; }
}
/* ===== Integration web — SVG connectors wiring each tool card into the
   RentFYV admin panel (combination of cards → RntlOS). Sits behind the
   cards; its opacity is driven by the deck's scroll progress so it appears as
   the cards settle into the grid. ===== */
.home-tools-web {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0; /* ramped up via the deck ScrollTrigger onUpdate */
  overflow: visible;
}
.tw-line { fill: none; stroke: url(#twGrad); stroke-width: 1.5; opacity: 0.4; }
.tw-flow {
  fill: none;
  stroke: #b9a6ff;
  stroke-width: 1.7;
  opacity: 0.9;
  stroke-dasharray: 6 16;
  animation: twFlow 1.5s linear infinite;
}
.tw-origin { fill: #8b6cff; }
.tw-hub-glow { fill: url(#twHubGlow); }
.tw-hub-core { fill: #ffffff; }
.tw-hub-ring {
  fill: none;
  stroke: #5b9dff;
  stroke-width: 1.5;
  transform-box: fill-box;
  transform-origin: center;
  animation: twPulse 2.4s ease-out infinite;
}
@keyframes twFlow { to { stroke-dashoffset: -44; } }
@keyframes twPulse {
  0%   { transform: scale(1);   opacity: 0.7; }
  70%  { opacity: 0; }
  100% { transform: scale(2.7); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .tw-flow, .tw-hub-ring { animation: none; }
}
/* Mac-style window panel framing the dashboard screenshot. */
.home-tools-mac {
  position: relative; /* containing block for the old-way overlay card */
  border-radius: 16px;
  overflow: hidden;
  background: #0f0f12;
  border: 0.5px solid var(--card-border, #2c2d34);
  box-shadow: 0 36px 80px -34px rgba(0, 0, 0, 0.75);
}
.home-tools-mac-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 11px 14px;
  background: #141418;
  border-bottom: 0.5px solid var(--card-border, #2c2d34);
}
.home-tools-mac-bar span { width: 11px; height: 11px; border-radius: 50%; }
.home-tools-mac-bar span:nth-child(1) { background: #ff5f57; }
.home-tools-mac-bar span:nth-child(2) { background: #febc2e; }
.home-tools-mac-bar span:nth-child(3) { background: #28c840; }
.home-tools-mac-shot { display: block; }
.home-tools-mac-shot img { display: block; width: 100%; height: auto; }
@media (max-width: 980px) {
  .home-tools-cols { grid-template-columns: 1fr; gap: 32px; }
  .home-tools-mac { max-width: 560px; margin: 0 auto; width: 100%; }
}
.home-section-title {
  color: var(--text);
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0 0 40px;
}
.tools-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr); /* 2×3 deck in the left column */
  gap: 13px;
  text-align: left;
  position: relative; /* offsetParent for the cards' stack→grid deal math */
}
/* Desktop stack mode — a tall scroll track whose inner stage is position:sticky
   and centres its content while the cards deal from a centred stack out to the
   grid (see LandingTools.jsx). NON-pinned scrub: no GSAP pin-spacer, so the
   section below ("Watch Our Demo") flows after the real height with no overlap. */
.home-tools--stack {
  min-height: 300vh;
  display: block;
  padding-top: 0;
  padding-bottom: 0;
}
.home-tools--stack .home-tools-inner {
  position: sticky;
  top: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.tool-card {
  background: var(--card);
  border: 0.5px solid var(--card-border);
  border-radius: 14px;
  padding: 17px 18px; /* smaller / more compact cards */
  transition: border-color 0.15s ease;
  will-change: transform; /* GPU-promote for the scroll-driven stack→deal */
}
.tool-card:hover { border-color: var(--card-border-hover); }
/* ===== Hub-and-spoke layout: 3 cards · dashboard · 3 cards ===== */
.home-tools-cols--hub {
  grid-template-columns: 1fr 1.28fr 1fr;
  align-items: center;
  gap: clamp(26px, 3.4vw, 52px);
}
.home-tools-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.home-tools-card-wrap { width: 100%; }
/* Smaller, more compact cards in the hub layout. */
.home-tools-cols--hub .tool-card {
  padding: 12px 13px;
  border-radius: 12px;
}
.home-tools-cols--hub .tool-cross-out { gap: 9px; margin-bottom: 7px; }
.home-tools-cols--hub .tool-icon-box {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  font-size: 12px;
}
.home-tools-cols--hub .tool-strike { font-size: 13px; }
.home-tools-cols--hub .tool-tagline { font-size: 11.5px; line-height: 1.42; }
/* Web fades in once the section scrolls into view. */
.home-tools-cols--hub .home-tools-web { transition: opacity 0.8s ease; }
.home-tools-cols--hub.is-on .home-tools-web { opacity: 1; }
/* Mobile / narrow: single column — left cards, dashboard, right cards stacked.
   No connector web (it's desktop-only). */
@media (max-width: 980px) {
  .home-tools-cols--hub { grid-template-columns: 1fr; gap: 16px; max-width: 460px; margin-left: auto; margin-right: auto; }
  .home-tools-cols--hub .home-tools-mac { margin: 6px auto; }
}
.tool-cross-out {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}
.tool-icon-box {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: #1A1A1A;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  font-size: 14px;
  font-weight: 700;
  flex-shrink: 0;
}
.tool-strike {
  color: var(--text);
  font-size: 17px;
  font-weight: 600;
  text-decoration: line-through;
  text-decoration-color: var(--text-faint);
  text-decoration-thickness: 1.5px;
}
/* ── "Cancel the stack" improvements: lede, toggle, prices, old-way card, payoff ── */
.home-tools-lede {
  color: var(--text-dim);
  font-size: clamp(14px, 1.4vw, 17px);
  font-weight: 500;
  max-width: 48ch;
  margin: 12px auto 0;
  text-align: center;
}
.home-tools-toggle {
  display: flex;
  width: max-content;
  gap: 6px;
  background: var(--card, #16161a);
  border: 1px solid var(--card-border, #2c2d34);
  border-radius: 999px;
  padding: 6px;
  margin: 16px auto 4px;
}
.home-tools-toggle button {
  font-family: inherit;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: -0.01em;
  color: var(--text-dim);
  background: transparent;
  border: none;
  border-radius: 999px;
  padding: 13px 32px;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease, transform 0.15s ease;
}
.home-tools-toggle button:hover:not(.on) { color: var(--text); }
.home-tools-toggle button.on { background: #fafafa; color: #0a0a0b; }
.home-tools-toggle button:active { transform: scale(0.97); }
/* Tool price — shown only in "The old way" mode (the stack you pay for). */
.tool-price { margin-left: auto; padding-left: 14px; font-size: 12px; font-weight: 700; color: var(--text-faint); white-space: nowrap; }
.home-tools--one .tool-price { display: none; }
.home-tools--old .home-tools-cols--rope .tool-strike { text-decoration: none; color: var(--text); }
.home-tools--old .tool-price { color: #f87171; }
/* Old-way cost card — overlays the dashboard in "old" mode (same footprint). */
.home-tools-oldcard {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: clamp(20px, 4vw, 40px);
  background: rgba(12, 12, 14, 0.93);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.home-tools--old .home-tools-oldcard { display: flex; }
.oldcard-big { font-size: clamp(34px, 5vw, 48px); font-weight: 700; letter-spacing: -0.03em; color: #f87171; line-height: 1; }
.oldcard-big span { font-size: 0.42em; }
.oldcard-sub { font-size: 13px; color: var(--text-dim); font-weight: 600; margin-top: 6px; }
.oldcard-rule { height: 1px; width: min(320px, 70%); background: var(--card-border, #2c2d34); margin: 18px 0; }
.oldcard-mini { display: flex; justify-content: space-between; gap: 30px; width: min(340px, 80%); font-size: 14px; font-weight: 600; color: var(--text-dim); margin-bottom: 10px; }
.oldcard-mini b { color: var(--text); }
.oldcard-warn { font-size: 12.5px; color: var(--text-faint); font-weight: 600; margin-top: 6px; max-width: 36ch; }
/* Payoff CTA below the stage. */
.home-tools-payoff { text-align: center; margin-top: 16px; }
.home-tools-payoff p { color: var(--text-dim); font-weight: 600; font-size: 15px; margin: 0 auto 16px; max-width: 52ch; }
.home-tools-payoff p b { color: var(--text); }
.home-tools-payoff-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: inherit;
  font-weight: 700;
  font-size: 15px;
  cursor: pointer;
  border: none;
  border-radius: 999px;
  padding: 13px 26px;
  background: #fafafa;
  color: #0a0a0b;
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.2s ease;
}
.home-tools-payoff-btn:hover { transform: translateY(-1px); box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.12); }
.home-tools-foot { display: block; margin-top: 14px; font-size: 11px; color: var(--text-faint); font-weight: 600; }
.tool-tagline {
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 400;
  line-height: 1.55;
  margin: 0;
}
/* ===== Watch demo ===== */
.home-watch {
  padding: clamp(56px, 8vw, 100px) clamp(18px, 3vw, 32px);
  text-align: center;
}
.home-watch-inner { max-width: 1000px; margin: 0 auto; }
.watch-frame {
  position: relative; /* positioning context for the cursor-following play btn */
  margin-top: 32px;
  border-radius: 18px;
  overflow: hidden;
  border: 0.5px solid var(--card-border);
}
.watch-frame-svg {
  width: 100%;
  height: auto;
  display: block;
}
/* ── Scroll-driven 3D tilt for the video panel (LandingWatch) ──────────────
   Non-pinned pattern (pinning is banned on this page): the TRACK is tall and
   gives the scroll length; the STAGE is position:sticky so the panel holds in
   view while it tilts; the CARD is the element GSAP rotates. PERSPECTIVE sits
   on the stage (the card's DIRECT parent) — putting it on the card itself would
   render the tilt flat. */
.watch-tilt-track {
  height: 260vh; /* 100vh hold + 160vh tilt scrub — keep in sync with TILT_END in LandingWatch.jsx */
  width: 100%;
  position: relative;
}
.watch-tilt-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  perspective: 1200px;
}
.watch-tilt-card {
  width: 100%;
  max-width: 1000px; /* same width budget as .home-watch-inner → frame size unchanged */
  transform-style: preserve-3d;
  will-change: transform;
}
/* Play button — centred via negative margins so transform:translate() is free
   to drive the cursor-follow (handled by rAF in LandingWatch.jsx; no transform
   transition here so it doesn't fight the per-frame updates). */
.watch-play {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 84px;
  height: 84px;
  margin: -42px 0 0 -42px;
  border: none;
  border-radius: 50%;
  background: #fafafa;
  color: #0a0a0a;
  display: grid;
  place-items: center;
  cursor: pointer;
  z-index: 2;
  transform: translate(0, 0);
  will-change: transform;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.45);
  transition: background 0.18s ease, box-shadow 0.2s ease, scale 0.18s ease;
}
.watch-play:hover { background: #fff; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55); scale: 1.06; }
.watch-play:active { scale: 0.96; }
.watch-play:focus-visible { outline: 2px solid #fafafa; outline-offset: 4px; }
.watch-play-icon {
  width: 30px;
  height: 30px;
  margin-left: 4px; /* optical-centre the triangle */
}
@media (max-width: 767px) {
  .watch-play { width: 64px; height: 64px; margin: -32px 0 0 -32px; }
  .watch-play-icon { width: 24px; height: 24px; }
}
/* ===== Pricing preview ===== */
.home-pricing-section {
  padding: clamp(56px, 8vw, 100px) clamp(18px, 3vw, 32px);
  border-top: 0.5px solid transparent;
  text-align: center;
}
.home-pricing-inner { max-width: 1100px; margin: 0 auto; }
/* === Tilted 3-card pricing layout =========================================
 * Three cards laid out horizontally:
 *   - Left card  → tilted slightly counter-clockwise, scaled down
 *   - Centre card → "Best Deal" hero, scaled up, lifted forward via z-index
 *   - Right card → tilted clockwise, scaled down
 *
 * The Reveal wrapper handles the entrance translateY fade; the inner
 * `.home-tier-card` element carries the static tilt transform so the two
 * never collide on the same property.
 *
 * Colour theme follows the brand purple-blue used by the MacBook neon
 * backlight: saturated `#A06EFF` core with cool blue (`#5A82FF`) shadows.
 * The highlight tier uses a vivid gradient for the body so it pops against
 * the dark surrounding cards. */
.home-pricing-stage {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: stretch;
  gap: 0;
  max-width: 1100px;
  margin: 60px auto 36px;
  padding: 30px 0;
  text-align: left;
  perspective: 1400px;
}
.home-tier-wrap {
  /* Sets the layout slot for each card. The card inside carries its own
     tilt — leaving the wrap untransformed lets Reveal animate translateY
     freely on entrance. */
  display: flex;
  align-items: center;
}
.home-tier-wrap--left  { z-index: 1; transform-origin: right center; }
.home-tier-wrap--right { z-index: 1; transform-origin: left center; }
.home-tier-wrap--center { z-index: 3; }
/* Desktop deck mode — a tall scroll track whose inner stage is position:sticky
   (vertically centred) while the cards deal from a centred stack out to their
   tilted slots (see LandingPricing.jsx). NON-pinned scrub: no GSAP pin-spacer,
   so the marquee below flows after the real height with no overlap. */
.home-pricing-section--stack {
  min-height: 300vh;
  padding-top: 0;
  padding-bottom: 0;
}
.home-pricing-section--stack .home-pricing-inner {
  position: sticky;
  top: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Lift the centred content (incl. "See all plans") up so it doesn't touch the
     outline-text strip in the next section. */
  padding-bottom: clamp(56px, 9vh, 110px);
}
.home-pricing-section--stack .home-tier-wrap {
  will-change: transform; /* transform-only deck animation, GPU-promoted */
}
.home-tier-card {
  position: relative;
  /* Responsive: cards scale with the viewport so they read well on big screens
     and still fit on smaller ones (instead of a fixed 280/320px). */
  width: clamp(238px, 20.5vw, 326px);
  background: #0F0F12;
  border: 1px solid rgba(170, 110, 255, 0.35);
  border-radius: 22px;
  padding: clamp(26px, 2.3vw, 36px) clamp(22px, 1.9vw, 30px);
  display: flex;
  flex-direction: column;
  color: var(--text);
  box-shadow:
    0 0 40px -10px rgba(170, 110, 255, 0.35),
    0 22px 50px -10px rgba(0, 0, 0, 0.7);
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.4s ease;
  will-change: transform;
}
/* Side cards: rotate outward + scale down + nudge inward so the centre
 * card visually overlaps their inner edges. The translate keeps them from
 * disappearing behind the centre when scaled. */
.home-tier-card--left {
  transform: translateX(40px) rotate(-7deg) scale(0.9);
}
.home-tier-card--right {
  transform: translateX(-40px) rotate(7deg) scale(0.9);
}
/* Centre "hero" card: bright white body, lifted forward, larger by ~12%.
 * White-on-dark is the brand's monochrome highlight pattern; a faint
 * purple halo around the chassis keeps the card tied to the rest of the
 * site's neon accent palette without flooding the foreground. */
.home-tier-card--center,
.home-tier-card.home-tier-highlight {
  width: clamp(272px, 23.5vw, 380px);
  background:
    radial-gradient(ellipse 100% 80% at 50% 0%, rgba(255, 255, 255, 1) 0%, #ECECEC 100%);
  border: 1px solid rgba(255, 255, 255, 0.9);
  color: #0A0A0A;
  transform: scale(1.08);
  box-shadow:
    0 0 80px -15px rgba(170, 110, 255, 0.6),
    0 30px 70px -10px rgba(0, 0, 0, 0.8);
}
/* Best Deal badge floats above the top edge of the centre card. Dark
 * inverse of the white card so it remains legible. */
.home-tier-badge {
  position: absolute;
  top: -16px;
  left: 50%;
  transform: translateX(-50%);
  background: #0A0A0A;
  color: #FAFAFA;
  padding: 6px 18px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 8px 22px -4px rgba(0, 0, 0, 0.7);
  white-space: nowrap;
  z-index: 1;
}
.home-tier-name {
  font-size: clamp(15px, 1.05vw, 18px);
  font-weight: 600;
  margin: 0 0 14px;
  color: var(--text);
}
.home-tier-card--center .home-tier-name { color: #0A0A0A; }
.home-tier-price {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin-bottom: 22px;
}
.home-tier-price-num {
  color: var(--text);
  font-size: clamp(33px, 2.4vw, 44px);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
}
.home-tier-card--center .home-tier-price-num {
  font-size: clamp(44px, 3.2vw, 58px);
  color: #0A0A0A;
}
.home-tier-price-per {
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 600;
}
.home-tier-card--center .home-tier-price-per { color: #4B5563; }
/* Yearly: struck full annual price + discount tag, above the discounted price. */
.home-tier-was {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 5px;
}
.home-tier-strike {
  color: var(--text-dim);
  font-size: 16px;
  font-weight: 700;
  text-decoration: line-through;
  text-decoration-color: #f87171;
  text-decoration-thickness: 2px;
}
.home-tier-savetag {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #c9b6ff;
  background: rgba(139, 108, 255, 0.16);
  padding: 2px 7px;
  border-radius: 999px;
}
.home-tier-card--center .home-tier-strike { color: #6B7280; text-decoration-color: #ef4444; }
.home-tier-card--center .home-tier-savetag { color: #6d4bd8; background: rgba(139, 108, 255, 0.14); }
/* Monthly / Yearly billing toggle (above the pricing cards). */
.home-billing { display: flex; justify-content: center; margin: 4px 0 0; }
.home-billing-toggle {
  display: inline-flex;
  gap: 2px;
  padding: 4px;
  background: var(--card);
  border: 0.5px solid var(--card-border);
  border-radius: 999px;
}
.home-billing-opt {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 8px 18px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.home-billing-opt.is-active { background: var(--text); color: #0A0A0A; }
.home-billing-save {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #ffffff;
  background: linear-gradient(100deg, #8b6cff, #5b9dff);
  padding: 2px 7px;
  border-radius: 999px;
}
.home-tier-price-custom {
  color: var(--text);
  font-size: 36px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
}
.home-tier-features {
  list-style: none;
  padding: 0;
  margin: 0 0 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
}
.home-tier-features li {
  display: flex;
  align-items: center;
  gap: 11px;
  color: var(--text);
  font-size: 13.5px;
  line-height: 1.3;
}
.home-tier-card--center .home-tier-features li { color: #1A1A1A; }
.home-tier-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: rgba(170, 110, 255, 0.18);
  color: #A66BFF;
  flex-shrink: 0;
}
.home-tier-card--center .home-tier-check {
  /* Filled black circle with a white check — high contrast against the
   * white card body, mirrors the dark CTA below for visual consistency. */
  background: #0A0A0A;
  color: #FAFAFA;
}
/* Side-card CTAs: solid white with dark ink. White-on-black is the
 * brand's primary button pattern, so the side cards now look like the
 * rest of the site instead of the purple gradient from the reference. */
.home-tier-cta {
  background: #FAFAFA;
  color: #0A0A0A;
  padding: 13px 18px;
  border-radius: 14px;
  border: none;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 6px 18px -4px rgba(170, 110, 255, 0.35);
  transition: transform 0.18s ease, box-shadow 0.25s ease, background 0.2s ease;
}
.home-tier-cta:hover {
  transform: scale(1.03);
  background: #FFFFFF;
  box-shadow: 0 9px 24px -4px rgba(170, 110, 255, 0.55);
}
/* Centre card sits on a WHITE body — a white CTA would disappear into
 * the background. Flip it to solid black so the click target stays
 * obvious and contrasts the rest of the centre card's dark ink. */
.home-tier-card--center .home-tier-cta {
  background: #0A0A0A;
  color: #FAFAFA;
  box-shadow: 0 6px 18px -4px rgba(0, 0, 0, 0.6);
}
.home-tier-card--center .home-tier-cta:hover {
  background: #1A1A1A;
  box-shadow: 0 9px 24px -4px rgba(0, 0, 0, 0.75);
}
/* Hover a SIDE card → it "reveals full": un-tilts, scales up, lifts and jumps
   ABOVE the centre card (wrap z-index) with an intensified purple highlight.
   The centre keeps its own focus treatment. */
@media (hover: hover) {
  /* !important to beat the inline z-index GSAP sets on the wraps for the deck. */
  .home-tier-wrap--left:hover,
  .home-tier-wrap--right:hover { z-index: 5 !important; }

  /* Stay tilted — just grow, lift and highlight (keeps the rotation/offset). */
  .home-tier-card--left:hover,
  .home-tier-card--right:hover {
    border-color: rgba(170, 110, 255, 0.85);
    box-shadow:
      0 0 90px -12px rgba(170, 110, 255, 0.75),
      0 34px 72px -12px rgba(0, 0, 0, 0.85);
  }
  .home-tier-card--left:hover  { transform: translateX(40px) rotate(-7deg) scale(1.05) translateY(-12px); }
  .home-tier-card--right:hover { transform: translateX(-40px) rotate(7deg) scale(1.05) translateY(-12px); }

  .home-tier-card--center:hover {
    transform: scale(1.11) translateY(-4px);
    box-shadow:
      0 0 100px -12px rgba(170, 110, 255, 0.85),
      0 38px 80px -10px rgba(0, 0, 0, 0.85);
  }
}
.home-pricing-link { margin-top: 28px; margin-bottom: clamp(24px, 4vh, 56px); }
/* ── Mobile: untilt + stack ──
 * Below ~860px the tilted layout collapses into a vertical column with
 * each card flat-faced. Keeps the visual identity (purple highlight tier
 * + Best Deal badge) without the side cards spilling off-screen. */
@media (max-width: 860px) {
  .home-pricing-stage {
    flex-direction: column;
    gap: 22px;
    perspective: none;
    margin: 40px auto 28px;
    padding: 24px 0;
    align-items: center;
  }
  .home-tier-wrap--left,
  .home-tier-wrap--right,
  .home-tier-wrap--center { z-index: 1; }
  .home-tier-card,
  .home-tier-card--left,
  .home-tier-card--right,
  .home-tier-card--center {
    width: min(360px, 92vw);
    transform: none;
  }
  .home-tier-card--center { transform: scale(1.02); }
  @media (hover: hover) {
    .home-tier-card--left:hover,
    .home-tier-card--right:hover { transform: translateY(-4px); }
    .home-tier-card--center:hover { transform: scale(1.02) translateY(-4px); }
  }
}
/* ===== Scroll-velocity marquee strip ("LET'S ELEVATE YOUR FLEET") =====
   Matches the hero "Cruise Control" heading: Archivo Black 900, white #FAFAFA
   words + gray #8C8C8C accent words, uppercase, tight. overflow:hidden clips
   the over-edge text so no horizontal page scrollbar appears. */
/* ===== Crossing diagonal marquee strips (X) ===== */
.xmq {
  position: relative;
  width: 100%;
  overflow: hidden;
  height: clamp(190px, 21vw, 300px);
  background: #0E0F12;   /* dark backdrop behind the crossing strips */
  user-select: none;
}
/* Each strip is a full-bleed diagonal band; the two cross at the centre. */
.xmq-strip {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 132%;
  display: flex;
  align-items: center;
  overflow: hidden;
  white-space: nowrap;
  padding: clamp(10px, 1.3vw, 18px) 0;
}
.xmq-strip--a {
  transform: translate(-50%, -50%) rotate(-5.5deg);
  background: #0a0a0b;   /* black strip (top) */
  z-index: 2;
  box-shadow: 0 18px 50px -22px rgba(0, 0, 0, 0.55);
}
.xmq-strip--b {
  transform: translate(-50%, -50%) rotate(5.5deg);
  background: #ffffff;   /* white strip (behind) */
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  z-index: 1;
}
.xmq-track { display: flex; width: max-content; will-change: transform; }
.xmq-group { display: flex; flex: none; align-items: center; white-space: nowrap; }
.xmq-phrase { display: inline-flex; align-items: center; }
.xmq-item {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  padding: 0 0.55em;
  font-family: 'Archivo Black', 'Quicksand', sans-serif;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: 1;
  font-size: clamp(17px, 2.1vw, 32px);
}
.xmq-star { font-size: 0.7em; line-height: 1; }
/* Black strip: white words + a purple accent star. */
.xmq-strip--a .xmq-item { color: #ffffff; }
.xmq-strip--a .xmq-star { color: #8b6cff; }
/* White strip: black words + an accent star. */
.xmq-strip--b .xmq-item { color: #0a0a0b; }
.xmq-strip--b .xmq-star { color: #8b6cff; }
/* ===== Testimonials ===== */
.home-testimonials {
  padding: clamp(56px, 8vw, 100px) clamp(18px, 3vw, 32px);
  text-align: center;
  border-top: 0.5px solid transparent;
}
.home-testimonials-inner { max-width: 1100px; margin: 0 auto; }
/* App-Store rating line under the "Real fleets. Real results." heading. */
.home-testi-rating {
  margin: 14px auto 0;
  color: var(--text-dim);
  font-size: clamp(13px, 1.3vw, 15px);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}
.home-testi-rating strong { color: var(--text); font-weight: 700; }
.home-testi-stars { color: #fbbf24; letter-spacing: 2px; }
/* ============================================ */
/* ===== Testimonials marquee (continuous) ===== */
/* ============================================ */
/* Full-bleed row of cards that drifts at a constant speed (gsap.ticker in
   LandingTestimonials.jsx) and pauses on hover. Edges fade via a mask. */
.testi-marquee {
  position: relative;
  width: 100%;
  margin-top: 44px;
  overflow: hidden;
  -webkit-mask: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.testi-track {
  display: flex;
  width: max-content;
  will-change: transform;
}
/* align-items: stretch → every card matches the tallest in the group, so the
   row reads as uniform tiles regardless of quote length. */
.testi-mq-group { display: flex; flex: none; align-items: stretch; }
/* Card = the previous testimonial design (dark note-card, top-right corner
   cut). The centred card gets the previous "focused" white treatment via the
   .is-center class, toggled as the cards drift past the middle. */
.testi-mq-card {
  flex: none;
  width: 360px;
  min-height: 420px;
  margin-right: 22px;
  /* Lifted off the charcoal page (was #161616, which now blends into --bg). */
  background: #1E1F24;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  padding: 32px;
  text-align: left;
  color: rgba(255, 255, 255, 0.5);
  display: flex;
  flex-direction: column;
  gap: 14px;
  white-space: normal;
  /* Note-card corner cut — top-right cut at a 30px diagonal (as before). */
  clip-path: polygon(0 0, calc(100% - 30px) 0, 100% 30px, 100% 100%, 0 100%);
  transition:
    background-color 700ms cubic-bezier(0.4, 0, 0.2, 1),
    color 700ms cubic-bezier(0.4, 0, 0.2, 1),
    border-color 700ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* Focused (centre) card — white, matching the previous design. */
.testi-mq-card.is-center {
  background: #ffffff;
  color: #0a0a0a;
  border-color: #ffffff;
}
.testi-mq-card.is-center .testi-avatar {
  background: rgba(10, 10, 10, 0.06);
  border-color: rgba(10, 10, 10, 0.12);
  color: rgba(10, 10, 10, 0.78);
}
.testi-mq-card.is-center .testi-quote { color: #0a0a0a; }
.testi-mq-card.is-center .testi-body { color: rgba(10, 10, 10, 0.72); }
.testi-mq-card.is-center .testi-attribution { color: rgba(10, 10, 10, 0.6); }
@media (max-width: 600px) {
  .testi-mq-card { width: 300px; min-height: 380px; padding: 26px; margin-right: 16px; }
  .testi-marquee { margin-top: 32px; }
}
/* ============================================ */
/* ===== Stagger testimonial carousel (legacy — no longer rendered) ===== */
/* ============================================ */
/* 3D stage. Children (cards) are positioned absolutely from the center
   and use translateX/translateZ/rotateY to fan out. perspective + preserve-3d
   on the stage enables the depth. */
.testi-stage {
  position: relative;
  width: 100%;
  max-width: 1200px;
  height: 460px;
  margin: 36px auto 0;
  perspective: 1500px;
  transform-style: preserve-3d;
}
/* Base card. Position rules + transforms get layered on by the position
   classes below — this rule only defines the static look. */
.testi-card {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 360px;
  min-height: 420px;     /* 4:5 ratio at 360 wide; accommodates copy variance */
  background: #161616;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  padding: 32px;
  text-align: left;
  color: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 14px;
  transform-origin: 50% 50%;
  /* Slow, gentle drift — cards glide between positions over ~2s so the
     carousel always feels like it's slowly moving rather than snapping. */
  transition:
    transform 2000ms cubic-bezier(0.4, 0, 0.2, 1),
    opacity 2000ms cubic-bezier(0.4, 0, 0.2, 1),
    background-color 1200ms cubic-bezier(0.4, 0, 0.2, 1),
    border-color 1200ms cubic-bezier(0.4, 0, 0.2, 1),
    color 1200ms cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform, opacity;
  /* Note-card corner cut — top-right cut at a 30 px diagonal */
  clip-path: polygon(0 0, calc(100% - 30px) 0, 100% 30px, 100% 100%, 0 100%);
}
/* Position variants — all transforms include the base centering translate. */
.testi-card-focused {
  transform: translate(-50%, -50%) translateX(0) translateZ(0) rotateY(0deg);
  opacity: 1;
  z-index: 10;
  background: #FFFFFF;
  color: #0A0A0A;
  border-color: #FFFFFF;
}
.testi-card-near-left {
  transform: translate(-50%, -50%) translateX(-320px) translateZ(-100px) rotateY(8deg);
  opacity: 0.7;
  z-index: 5;
}
.testi-card-near-right {
  transform: translate(-50%, -50%) translateX(320px) translateZ(-100px) rotateY(-8deg);
  opacity: 0.7;
  z-index: 5;
}
.testi-card-far-left {
  transform: translate(-50%, -50%) translateX(-580px) translateZ(-200px) rotateY(12deg);
  opacity: 0.4;
  z-index: 1;
}
.testi-card-far-right {
  transform: translate(-50%, -50%) translateX(580px) translateZ(-200px) rotateY(-12deg);
  opacity: 0.4;
  z-index: 1;
}
.testi-card-hidden {
  transform: translate(-50%, -50%) translateZ(-400px);
  opacity: 0;
  pointer-events: none;
  z-index: 0;
}
/* Avatar — initials disc by default; .testi-avatar-img variant for actual
   <img> swaps in when landingData.js eventually grows an `avatar` URL. */
.testi-avatar {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.12);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 15px;
  color: rgba(255, 255, 255, 0.75);
  flex-shrink: 0;
  margin-bottom: 4px;
}
.testi-avatar-img {
  object-fit: cover;
  padding: 0;
}
.testi-card-focused .testi-avatar {
  background: rgba(10, 10, 10, 0.06);
  border-color: rgba(10, 10, 10, 0.12);
  color: rgba(10, 10, 10, 0.78);
}
/* Card text */
.testi-quote {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.4;
  margin: 0;
  color: rgba(255, 255, 255, 0.78);
}
.testi-card-focused .testi-quote {
  color: #0A0A0A;
}
.testi-body {
  font-size: 13px;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.42);
  margin: 0;
  flex: 1;
}
.testi-card-focused .testi-body {
  color: rgba(10, 10, 10, 0.72);
}
.testi-attribution {
  font-size: 12px;
  font-style: italic;
  margin: 0;
  color: rgba(255, 255, 255, 0.5);
}
.testi-card-focused .testi-attribution {
  color: rgba(10, 10, 10, 0.6);
}
.testi-attribution em { font-style: italic; }
/* Arrow controls */
.testi-controls {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 16px;
  margin-top: 40px;
}
.testi-arrow {
  width: 44px;
  height: 44px;
  border-radius: 8px;
  background: #161616;
  border: 1px solid rgba(255, 255, 255, 0.10);
  color: rgba(255, 255, 255, 0.78);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-family: inherit;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease,
    color 0.15s ease,
    transform 0.1s ease;
}
.testi-arrow:hover {
  background: #1f1f1f;
  border-color: rgba(255, 255, 255, 0.22);
  color: #FFFFFF;
}
.testi-arrow:active { transform: translateY(1px); }
.testi-arrow:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.4);
  outline-offset: 2px;
}
/* ----- Responsive ----- */
/* Tablet: hide the far cards, keep focused + near on each side */
@media (max-width: 1023px) {
  .testi-stage { height: 440px; }
  .testi-card-near-left {
    transform: translate(-50%, -50%) translateX(-280px) translateZ(-80px) rotateY(8deg);
  }
  .testi-card-near-right {
    transform: translate(-50%, -50%) translateX(280px) translateZ(-80px) rotateY(-8deg);
  }
  .testi-card-far-left,
  .testi-card-far-right {
    opacity: 0;
    pointer-events: none;
    transform: translate(-50%, -50%) translateZ(-200px);
  }
}
/* Mobile: focused card only — side cards display: none so they don't
   intercept taps. Arrow buttons still cycle through them. */
@media (max-width: 639px) {
  .testi-stage { height: 480px; }
  .testi-card {
    width: 280px;
    min-height: 420px;
    padding: 24px;
  }
  .testi-card-near-left,
  .testi-card-near-right,
  .testi-card-far-left,
  .testi-card-far-right { display: none; }
}
/* prefers-reduced-motion — fade-only crossfade between focused cards.
   Side cards fade out entirely; no 3D rotation, no translateZ. */
@media (prefers-reduced-motion: reduce) {
  .testi-card {
    transition:
      opacity 200ms ease,
      background-color 200ms ease,
      border-color 200ms ease,
      color 200ms ease;
    transform: translate(-50%, -50%);
  }
  .testi-card-focused      { transform: translate(-50%, -50%); opacity: 1; z-index: 10; }
  .testi-card-near-left,
  .testi-card-near-right,
  .testi-card-far-left,
  .testi-card-far-right,
  .testi-card-hidden       { transform: translate(-50%, -50%); opacity: 0; pointer-events: none; }
}
/* ===== Testimonial marquee (auto-scrolling) ===== */
.testimonial-marquee {
  margin-top: 36px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  /* Soft fade at the left and right edges */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 8%, #000 92%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, #000 8%, #000 92%, transparent 100%);
  text-align: left;
}
.marquee-row {
  width: 100%;
  overflow: hidden;
  /* Avoid forcing scrollbars on cards inside */
  position: relative;
}
.marquee-track {
  display: flex;
  gap: 16px;
  width: max-content;
  will-change: transform;
}
/* Top row scrolls right-to-left; bottom row scrolls left-to-right */
.marquee-row-left .marquee-track {
  /* Slow, steady queue — one full lap of the (duplicated) list. */
  animation: marqueeLeft 48s linear infinite;
}
.marquee-row-right .marquee-track {
  animation: marqueeRight 44s linear infinite;
}
@keyframes marqueeLeft {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
@keyframes marqueeRight {
  from { transform: translate3d(-50%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}
/* Pause on hover so users can read */
@media (hover: hover) {
  .marquee-row:hover .marquee-track {
    animation-play-state: paused;
  }
}
.testimonial-card {
  background: var(--card);
  border: 0.5px solid var(--card-border);
  border-radius: 14px;
  padding: 24px;
  width: 360px;
  min-width: 360px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
}
.testimonial-quote {
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  margin: 0 0 12px;
  line-height: 1.4;
}
.testimonial-body {
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 400;
  line-height: 1.6;
  margin: 0 0 18px;
}
.testimonial-author { display: flex; align-items: center; gap: 10px; }
.testimonial-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  font-size: 11px;
  font-weight: 700;
  flex-shrink: 0;
}
.testimonial-name { color: var(--text); font-size: 13px; font-weight: 600; margin: 0; }
.testimonial-role { color: var(--text-faint); font-size: 11px; margin: 2px 0 0; }
/* Reduced motion — stop the queue and let it be a normal scrollable strip. */
@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; }
  .marquee-row { overflow-x: auto; }
}
/* ===== FAQ ===== */
.home-faq {
  padding: clamp(56px, 8vw, 100px) clamp(18px, 3vw, 32px);
  border-top: 0.5px solid transparent;
  /* Self-contained dark-theme tokens so this FAQ renders correctly even when
     reused OUTSIDE .home-page (e.g. on the /demo page). Same values as the
     .home-page theme, so the landing FAQ is visually unchanged. */
  --text: #fafafa;
  --text-dim: #9ca3af;
  --text-faint: #6b7280;
  --card-border: #2c2d34;
  --card-border-hover: #3a3b43;
}
.home-faq-inner { max-width: 1080px; margin: 0 auto; }
.home-faq-title { text-align: center; margin-bottom: 40px; }
.home-faq-list { display: flex; flex-direction: column; gap: 12px; }
/* Start Now CTA below the FAQ accordion — centered, with breathing room
   between it and the last FAQ item. Reuses .home-cta-primary for the
   actual button styling so it matches every other primary CTA on the page. */
.home-faq-cta {
  display: flex;
  justify-content: center;
  margin-top: 36px;
}
.home-faq-item {
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  border-radius: 14px;
  padding: 18px 24px;
  cursor: pointer;
  transition: border-color 0.15s ease;
}
.home-faq-item:hover, .home-faq-item.open { border-color: var(--card-border-hover); }
.home-faq-q {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}
.home-faq-q span:first-child {
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
}
.home-faq-toggle {
  color: var(--text-dim);
  font-size: 18px;
  flex-shrink: 0;
}
.home-faq-item.open .home-faq-toggle { color: var(--text); }
.home-faq-a {
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.6;
  margin: 12px 0 0;
}
/* ============================================ */
/* ===== FAQ — two-panel master / detail ====== */
/* ============================================ */
.home-faq { --faq-accent: #a06eff; --faq-accent-2: #5a82ff; }
.faq-split {
  display: grid;
  grid-template-columns: 1fr 1.05fr;
  gap: clamp(24px, 4vw, 60px);
  align-items: start;
  text-align: left;
  margin-top: clamp(28px, 4vw, 48px);
}
/* ----- Left: question list (rounded, numbered, linked to the panel) ----- */
.faq-q-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.faq-q-item {
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 14px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 14px;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  padding: 18px 20px;
  transition: background 0.25s var(--ease, cubic-bezier(.22,1,.36,1)), border-color 0.25s ease,
    box-shadow 0.3s ease, padding 0.25s var(--ease, cubic-bezier(.22,1,.36,1));
}
.faq-q-item:hover { background: rgba(255, 255, 255, 0.03); }
/* ACTIVE: filled card + inset accent bar + glow + slight inset */
.faq-q-item.is-active {
  background: var(--card-2);
  border-color: var(--card-border-hover);
  padding-left: 26px;
  box-shadow: inset 4px 0 0 0 var(--faq-accent), 0 0 0 1px rgba(139, 108, 255, 0.18),
    0 18px 40px -24px rgba(139, 108, 255, 0.5);
}
/* Connector line + arrowhead from the active question into the panel. */
.faq-q-item.is-active::after {
  content: '';
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  height: 2px;
  width: clamp(24px, 4vw, 60px);
  background: linear-gradient(90deg, var(--faq-accent), var(--faq-accent-2));
  z-index: 5;
  animation: faqLineIn 0.35s var(--ease, cubic-bezier(.22,1,.36,1));
}
.faq-q-item.is-active::before {
  content: '';
  position: absolute;
  left: calc(100% + clamp(24px, 4vw, 60px) - 2px);
  top: 50%;
  transform: translateY(-50%);
  width: 0;
  height: 0;
  border: 6px solid transparent;
  border-left-color: var(--faq-accent-2);
  z-index: 6;
  animation: faqLineIn 0.35s var(--ease, cubic-bezier(.22,1,.36,1));
}
@keyframes faqLineIn { from { opacity: 0; } to { opacity: 1; } }
.faq-q-num {
  flex: none;
  width: 24px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-faint);
  transition: color 0.25s ease;
}
.faq-q-item.is-active .faq-q-num { color: var(--faq-accent); }
.faq-q-text {
  flex: 1;
  color: var(--text-dim);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.4;
  transition: color 0.2s ease;
}
.faq-q-item:hover .faq-q-text,
.faq-q-item.is-active .faq-q-text { color: var(--text); }
.faq-q-toggle {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--card-border-hover);
  color: var(--text-dim);
  transition: background 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
.faq-q-item.is-active .faq-q-toggle {
  background: linear-gradient(135deg, var(--faq-accent), var(--faq-accent-2));
  border-color: transparent;
  color: #fff;
}
@media (max-width: 860px) {
  .faq-q-item.is-active::after,
  .faq-q-item.is-active::before { display: none; }
}
/* Help block under the list */
.faq-help { margin-top: clamp(28px, 4vw, 44px); }
.faq-help-text { color: var(--text-faint); font-size: 13px; margin: 0 0 16px; }
.faq-talk-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: linear-gradient(135deg, var(--faq-accent), var(--faq-accent-2));
  color: #fff;
  border: none;
  border-radius: 30px;
  padding: 12px 22px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.22s ease;
}
.faq-talk-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 26px rgba(124, 110, 255, 0.4);
}
.faq-talk-btn:active { transform: translateY(0); }
/* ----- Right: answer card ----- */
.faq-answer-col {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--card-2), #101013);
  border: 1px solid var(--card-border-hover);
  border-radius: 20px;
  padding: clamp(28px, 3.5vw, 44px) clamp(28px, 3.5vw, 44px) clamp(28px, 3.5vw, 44px) clamp(30px, 3.6vw, 44px);
  min-height: 340px;
}
/* Accent edge where the connector "arrives". */
.faq-answer-col::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: linear-gradient(180deg, var(--faq-accent), var(--faq-accent-2));
}
.faq-bignum {
  position: absolute;
  top: 6px;
  right: 24px;
  font-size: clamp(88px, 12vw, 150px);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
  color: rgba(255, 255, 255, 0.045);
  pointer-events: none;
  user-select: none;
}
.faq-answer-body { animation: faqFade 0.4s cubic-bezier(0.22, 1, 0.36, 1) both; }
@keyframes faqFade {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.faq-answer-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text-faint);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin: 0 0 22px;
}
.faq-eyebrow-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--faq-accent);
  box-shadow: 0 0 8px rgba(160, 110, 255, 0.6);
}
.faq-answer-q {
  position: relative;
  color: var(--text);
  font-size: clamp(20px, 2.3vw, 28px);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin: 0 0 18px;
  max-width: 24ch;
}
.faq-answer-a {
  color: var(--text-dim);
  font-size: 15px;
  line-height: 1.7;
  margin: 0;
  max-width: 54ch;
}
.faq-dots { display: flex; gap: 8px; margin-top: clamp(24px, 3vw, 32px); position: relative; z-index: 2; }
.faq-dot {
  width: 28px;
  height: 5px;
  border-radius: 99px;
  border: none;
  padding: 0;
  cursor: pointer;
  background: var(--card-border-hover);
  transition: background 0.3s ease, width 0.3s var(--ease, cubic-bezier(.22,1,.36,1));
}
.faq-dot.is-active { background: linear-gradient(90deg, var(--faq-accent), var(--faq-accent-2)); width: 44px; }
@media (max-width: 860px) {
  .faq-split { grid-template-columns: 1fr; gap: 28px; }
  .faq-answer-col { min-height: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .faq-answer-body { animation: none; }
  .faq-talk-btn:hover { transform: none; }
}
/* ===== Final CTA ===== */
.home-final-cta {
  /* Unified home section spacing (matches the other standard sections). */
  padding: clamp(56px, 8vw, 100px) clamp(18px, 3vw, 32px);
  display: flex;
  justify-content: center;
}
.home-final-cta-card {
  width: 100%;
  max-width: 800px;
  /* 3D viewing context for the inner tilt card. `preserve-3d` is required so the
     Reveal's resting transform (translate3d/scale on this same element) doesn't
     flatten the child's rotateX/rotateY into a 2D skew. */
  perspective: 1000px;
  transform-style: preserve-3d;
}
/* The visible card: dark surface + the interactive tilt. */
.home-final-cta-inner {
  position: relative;
  background: var(--card);
  border: 0.5px solid var(--card-border);
  border-radius: 24px;
  padding: clamp(40px, 6vw, 80px) clamp(20px, 4vw, 40px);
  text-align: center;
  overflow: hidden; /* clip the glare to the rounded shape */
  transform-style: preserve-3d;
  transition: transform 0.15s ease-out; /* smooth ease-back on mouseleave */
  will-change: transform;
}
/* Cursor-following white glare. Subtle + monochrome; clipped to the rounded card
   by the parent's overflow:hidden + border-radius:inherit. pointer-events:none so
   the button underneath stays clickable and keeps its hover-glow. Only visible
   while the pointer is over the card. */
.home-final-cta-glare {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease-out;
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%),
    rgba(255, 255, 255, 0.12), transparent 40%);
  z-index: 0;
}
.home-final-cta-inner.is-tilting .home-final-cta-glare {
  opacity: 1;
}
/* Card content sits ABOVE the glare overlay. */
.home-final-cta-inner > h2,
.home-final-cta-inner > .home-final-cta-recap,
.home-final-cta-inner > .home-cta-primary,
.home-final-cta-inner > .home-final-cta-fud {
  position: relative;
  z-index: 1;
}
.home-final-cta-card h2 {
  color: var(--text);
  font-size: clamp(28px, 4.5vw, 48px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 0 0 24px;
}
/* Recap row (above the button) — green checks + the three top benefits. */
.home-final-cta-recap {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px 28px;
  margin: 0 0 28px;
  color: var(--text-dim);
  font-size: clamp(13px, 1.4vw, 15px);
  font-weight: 600;
}
.home-final-cta-recap > span { display: inline-flex; align-items: center; gap: 8px; }
.home-final-cta-ck { color: #4ade80; font-weight: 700; }
/* Reassurance row (below the button) — smaller + dimmer. */
.home-final-cta-fud {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin-top: 18px;
  color: var(--text-faint);
  font-size: 13px;
  font-weight: 600;
}
.home-final-cta-fud > span { display: inline-flex; align-items: center; gap: 6px; }
/* ===== Footer ===== */
.home-footer {
  padding: clamp(40px, 6vw, 60px) clamp(18px, 3vw, 32px) 28px;
  border-top: 0.5px solid transparent;
}
.home-footer-inner { max-width: 1180px; margin: 0 auto; }
.home-footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1.2fr;
  gap: clamp(28px, 5vw, 60px);
  margin-bottom: clamp(40px, 5vw, 60px);
}
.home-footer-brand .home-logo-text { display: inline-block; font-size: 20px; margin-bottom: 16px; }
.home-footer-brand p {
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.6;
  margin: 0 0 20px;
  max-width: 280px;
}
.home-socials { display: flex; gap: 10px; }
.social-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.social-icon:hover { background: #1A1A1A; border-color: var(--card-border-hover); }
.home-footer-col h4 {
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  margin: 0 0 16px;
}
.home-footer-col span {
  display: block;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 400;
  line-height: 2.1;
  cursor: pointer;
  transition: color 0.15s ease;
}
.home-footer-col span:hover { color: var(--text); }
.home-footer-col span.home-nav-active { color: var(--text); font-weight: 600; }
.home-footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  padding-top: 24px;
  border-top: 0.5px solid transparent;
}
.home-footer-bottom > span { color: var(--text-faint); font-size: 12px; }
.home-footer-legal { display: flex; flex-wrap: wrap; gap: 16px 24px; }
.home-footer-legal span { color: var(--text-faint); font-size: 12px; cursor: pointer; }
.home-footer-legal span:hover { color: var(--text); }
/* ===== Responsive breakpoints ===== */
@media (max-width: 1024px) {
  .home-feature-inner { grid-template-columns: 1fr; gap: 32px; }
  .home-feature-reverse .home-feature-inner > .feature-text { order: 2; }
  .home-feature-reverse .home-feature-inner > .feature-art { order: 1; }
  .feature-art { max-width: 560px; margin: 0 auto; width: 100%; }

  .stats-strip-inner { grid-template-columns: repeat(2, 1fr); }
  .tools-grid { grid-template-columns: repeat(2, 1fr); }

  .home-footer-grid { grid-template-columns: 1fr 1fr; }
  .home-footer-brand { grid-column: span 2; }
}
/* Smaller marquee cards on tablets/phones */
@media (max-width: 720px) {
  .testimonial-card {
    width: 300px;
    min-width: 300px;
    padding: 22px;
  }
  .testimonial-marquee { gap: 14px; }
  .marquee-track { gap: 14px; }
  .marquee-row-left .marquee-track { animation-duration: 30s; }
  .marquee-row-right .marquee-track { animation-duration: 34s; }
}
@media (max-width: 480px) {
  .testimonial-card {
    width: 260px;
    min-width: 260px;
    padding: 20px;
  }
  .testimonial-quote { font-size: 14px; }
  .testimonial-body { font-size: 12px; }
}
/* Reduced motion: stop the auto-scroll, show a normal grid instead */
@media (prefers-reduced-motion: reduce) {
  .marquee-row-left .marquee-track,
  .marquee-row-right .marquee-track {
    animation: none;
    transform: none;
    flex-wrap: wrap;
    justify-content: center;
  }
  .testimonial-marquee {
    -webkit-mask-image: none;
    mask-image: none;
  }
}
@media (max-width: 880px) {
  .home-nav-links { display: none; }
  .home-nav-pill { padding: 10px 10px 10px 22px; }

  .home-dashboard {
    grid-template-columns: 1fr;
    padding: 12px;
  }
  .dash-sidebar {
    flex-direction: row;
    min-height: auto;
    align-items: center;
    overflow-x: auto;
  }
  .dash-side-logo { margin: 0 14px 0 0; }
  .dash-side-section { display: none; }
  .dash-side-menu { flex-direction: row; flex: 0 0 auto; }
  .dash-side-menu li { white-space: nowrap; padding: 6px 10px; }
  .dash-side-upsell { margin-top: 0; margin-left: auto; padding: 6px 14px; flex-shrink: 0; }

  .dash-bottom-row { grid-template-columns: 1fr; }
  .dash-stats-col { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
  .dash-business-card { grid-column: span 3; }

  .dash-table-row {
    grid-template-columns: 1.5fr 1fr 1fr;
  }
  .dash-table-row > span:nth-child(n+4) { display: none; }
}
@media (max-width: 720px) {
  .home-pricing-grid { grid-template-columns: 1fr; max-width: 460px; }
  .testimonial-grid { grid-template-columns: 1fr; max-width: 520px; margin-left: auto; margin-right: auto; }
  .home-hero-promo { flex-direction: column; gap: 4px; padding: 8px; }
  .hero-email-input { padding: 6px 12px; }
  .hero-email-button { width: 100%; }
}
@media (max-width: 560px) {
  .home-nav-pill { padding: 8px 8px 8px 18px; border-radius: 40px; gap: 8px; }
  .home-pill-btn { padding: 9px 14px; font-size: 12px; min-height: 36px; }

  .stats-strip-inner { grid-template-columns: 1fr 1fr; }
  .tools-grid { grid-template-columns: 1fr; }

  .home-footer-grid { grid-template-columns: 1fr; }
  .home-footer-brand { grid-column: span 1; }
  .home-footer-bottom { flex-direction: column; align-items: flex-start; }

  .dash-overview-card { padding: 10px; }
  .dash-table { font-size: 8px; }
  .dash-status { font-size: 7px; padding: 2px 5px; }
}
@media (max-width: 400px) {
  .home-nav-actions .home-pill-btn:first-child { display: none; }
  .home-logo-text { font-size: 14px; }
  .home-hero-title { font-size: clamp(28px, 8vw, 36px); }
  .home-cta-primary { padding: 12px 22px; font-size: 13px; }
}
@media (max-width: 340px) {
  .home-topbar { padding: 10px 10px 0; }
  .home-nav-pill { padding: 6px 6px 6px 14px; }
  .home-hero { padding: 32px 14px 40px; }
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .home-pill-btn,
  .home-cta-primary,
  .home-tier-cta,
  .feature-link,
  .social-icon,
  .home-faq-item,
  .tool-card { transition: none; }
}
/* ============================================ */
/* ===== Scroll-triggered reveal animations ===== */
/* ============================================ */
.reveal {
  opacity: 0;
  will-change: transform, opacity;
  transition:
    opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.7s cubic-bezier(0.22, 1, 0.36, 1),
    filter 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.reveal-up { transform: translate3d(0, 28px, 0); }
.reveal-left { transform: translate3d(-32px, 0, 0); }
.reveal-right { transform: translate3d(32px, 0, 0); }
.reveal-scale { transform: scale(0.96); filter: blur(2px); }
.reveal-fade { transform: none; }
.reveal.in-view {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
  filter: none;
}
/* Word-by-word reveal (Antigravity style): each word rises + unblurs in turn.
   The per-word transition-delay (stagger) is set inline by <WordReveal>. */
.wreveal { opacity: 1; }
/* container stays visible; words animate */
.wreveal-w {
  display: inline-block;
  opacity: 0;
  transform: translate3d(0, 0.85em, 0);
  filter: blur(8px);
  will-change: opacity, transform, filter;
  transition:
    opacity 0.6s cubic-bezier(0.2, 0.7, 0.2, 1),
    transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1),
    filter 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.wreveal.in-view .wreveal-w {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  filter: blur(0);
}
@media (prefers-reduced-motion: reduce) {
  .wreveal-w { opacity: 1 !important; transform: none !important; filter: none !important; transition: none !important; }
}
/* Disable for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal-up,
  .reveal-left,
  .reveal-right,
  .reveal-scale,
  .reveal-fade {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
  }
}
/* ============================================ */
/* ===== Glow pulse on primary CTAs ===== */
/* ============================================ */
@keyframes glowPulse {
  0%, 100% {
    box-shadow:
      0 0 0 0 rgba(250, 250, 250, 0.35),
      0 0 24px -6px rgba(250, 250, 250, 0.0);
  }
  50% {
    box-shadow:
      0 0 0 6px rgba(250, 250, 250, 0),
      0 0 36px -4px rgba(250, 250, 250, 0.45);
  }
}
.glow-pulse {
  animation: glowPulse 2.6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .glow-pulse { animation: none; }
}
/* ============================================ */
/* ===== Hero ambient glow ===== */
/* ============================================ */
.home-hero {
  position: relative;
}
/* (removed) .home-hero::before — an ambient white radial glow (+ ambientShift
   animation) that sat behind the hero. The cinematic video background replaces
   it; keeping it would just be a dead, animated glow layer under the video. */
.home-hero > * { position: relative; z-index: 1; }
/* Interactive dot-grid canvas — viewport-fixed so dots stay anchored to
   the screen as the user scrolls. This means the dots appear behind every
   section of the page (not just the hero) and the canvas only ever has to
   manage ~1500 dots, not 10k+ for a tall page. */
.home-page > .dot-grid-bg {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  display: block;
  /* Layer at z-index 0; siblings get z-index: 1 explicitly (see rule below)
     so we don't rely on negative z-index semantics, which Chrome sometimes
     paints BEHIND the parent's solid background. */
  z-index: 0;
  /* Click-through so every section's controls (and the interactive MacBook
     screen) stay usable. */
  pointer-events: none;
  /* Faint dotted grid — pure CSS, near-zero cost. A single fixed backdrop over
     the dark page that reads continuously behind every transparent section. */
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.05) 1px, transparent 1.6px);
  background-size: 28px 28px;
}
/* Every other direct child of .home-page (nav, sections, footer) is hoisted
   to z-index: 1 so they sit ABOVE the canvas. The :not() pin makes this
   automatically apply to any new sections added later — no per-section work. */
.home-page > *:not(.dot-grid-bg) {
  position: relative;
  z-index: 1;
}
/* ── Bright-white content band (Stats → Pricing, before the marquee) ────────
   Opaque white panel that covers the dot-grid here. It re-scopes the design
   tokens to a LIGHT theme — the sections inside read their colours from these
   tokens (var(--text)/var(--card)/…), so text + cards flip to light with no
   per-component edits. (The few hardcoded-colour spots are handled below.) */
/* ===== Section scroll-reveal — an IntersectionObserver (see Landing.jsx) adds
   `is-in` as each content section scrolls into view, firing a one-shot keyframe
   that fades + slides it up. The hidden start lives in the keyframe (`from`), NOT
   as a persistent base style, so a section is never permanently hidden (fully
   fail-safe). `--fade` is opacity-only (sticky-internal sections, so no transform
   breaks pinning). Reduced-motion → no animation. ===== */
.home-sreveal.is-in {
  animation: homeReveal 0.85s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.home-sreveal--fade.is-in { animation-name: homeRevealFade; }
@keyframes homeReveal {
  from { opacity: 0; transform: translateY(48px); }
  to   { opacity: 1; transform: none; }
}
@keyframes homeRevealFade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .home-sreveal.is-in { animation: none; }
}
.home-light-band {
  background: #000; /* opaque — middle content band: NO particles behind it */
  border-radius: 0 0 40px 40px; /* rounded BOTTOM where it meets the white marquee strip */
  --bg: #000000;
  --card: #1e1f24;
  --card-2: #232429;
  --card-border: #2c2d34;
  --card-border-hover: #3a3b43;
  --text: #fafafa;
  --text-dim: #9ca3af;
  --text-faint: #6b7280;
}
/* Nested sections are no longer direct children of .home-page, so re-assert the
   positioning context their absolutely-positioned children rely on. */
.home-light-band > * { position: relative; }
/* The "See RntlOS in Action" scroll-zoom sits in the middle — keep it opaque
   so the particle canvas does NOT show behind it (particles are hero + bottom
   only). The component sets this too; this is the page asserting it over the
   band's own background. */
.home-page .demo-launcher { background: #000; }
/* Rounded jet-black band BELOW the white marquee strip (Testimonials → FinalCta).
   Inherits the page's dark tokens, so its sections read light-on-black as before. */
.home-dark-band {
  background: transparent; /* was #000 — transparent so the particle canvas shows behind */
  border-radius: 40px 40px 0 0; /* rounded TOP where it meets the white strip */
}
/* Oversized "RENTFYV" hover-reveal wordmark above the footer. Sits on the dark
   page; the SVG scales with the container width (no fixed height → no layout
   shift), and overflow:hidden guards against any horizontal scroll. */
/* Landing page background is jet black (About keeps the charcoal --bg). With a
   black page, EVERY rounded-corner notch on the page (the bands, the white
   contact panel, etc.) reveals black instead of grey charcoal. */
.home-page:not(.about-page) {
  background: transparent; /* was #000 — body provides the dark base; particle canvas shows behind */
}
/* Jet-black backing for the closing zone → the white contact panel's rounded
   corners reveal black, not the page's charcoal. */
.home-closing {
  background: transparent; /* was #000 — body provides the black base; particle canvas shows behind */
}
.rentfyv-hero {
  width: 100%;
  /* The page ends on the wordmark's HALFWAY line: the SVG's negative bottom margin
     (below) pulls its bottom half past the page edge, where overflow:hidden crops
     it — so only the top half of the giant letters shows. */
  padding: clamp(16px, 3vw, 40px) 0 0;
  overflow: hidden; /* crop the bleeding edges + the bottom half of the wordmark */
  background: #000000; /* jet black (matches the footer above it) */
}
/* The `.text-hover-svg` sizing block that lived here died in session 68 —
   the TextHoverEffect hover-reveal was replaced by the ghost-gradient
   `FooterWordmark` (components/FooterWordmark.css), which renders inside this
   same `.rentfyv-hero` band and relies on ITS `overflow: hidden` for the
   bottom crop. The per-page background overrides on `.rentfyv-hero`
   (blog/pricing/demo) are still load-bearing. */
/* Jet-black islands inside the band: the stats strip + the feature cards.
   Restore the DARK-theme tokens on them so their token-driven children (numbers,
   labels, titles, borders) render light-on-black again. */
.home-light-band .home-stats-strip,
.home-light-band .feature-stack-card {
  background: #000;
  --card: #161618;
  --card-2: #161618;
  --card-border: #2c2d34;
  --card-border-hover: #3a3b43;
  --text: #fafafa;
  --text-dim: #9ca3af;
  --text-faint: #6b7280;
}
/* "No More Juggling Multiple Tools" cards — recoloured with the three brand
   pastels (columns share a colour). Dark text (#16171b clears 4.5:1 even on the
   darkest, Glaucous), a white icon chip, and flat colour + soft shadow for a
   modern look. No transform hover — GSAP drives the stack→deal animation. */
/* Uniform dark "step-card" surface (matches the mockup's How-it-works cards):
   one dark colour for every card + a faint white hairline border. */
.home-light-band .tool-card {
  background: #16161a;
  border-color: rgba(255, 255, 255, 0.08);
  box-shadow: 0 12px 30px -14px rgba(0, 0, 0, 0.5);
}
.home-light-band .tool-card .tool-strike {
  color: #ffffff;
  /* Clearly-visible "replaced" cut line through the old-tool name. */
  text-decoration-color: #ff6b6b;
  text-decoration-thickness: 2px;
}
.home-light-band .tool-card .tool-tagline { color: rgba(255, 255, 255, 0.85); }
.home-light-band .tool-card .tool-icon-box {
  background: rgba(255, 255, 255, 0.92);
  color: #16171b;
}
/* Left tool pills (the "Cancel the Stack" rope column) are interactive: the
   cursor turns to a pointer and the whole pill lights up WHITE on hover. Text
   and icon invert to dark so they stay legible; the red "replaced" cut line
   reads fine on white. */
.home-tools-cols--rope .tool-card {
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.home-tools-cols--rope .tool-card .tool-strike,
.home-tools-cols--rope .tool-card .tool-icon-box {
  transition: color 0.2s ease, background-color 0.2s ease;
}
.home-tools-cols--rope .tool-card:hover {
  background: #ffffff;
  border-color: #ffffff;
  transform: translateX(4px);
  box-shadow: 0 16px 36px -12px rgba(0, 0, 0, 0.55);
}
.home-tools-cols--rope .tool-card:hover .tool-strike { color: #16171b; }
.home-tools-cols--rope .tool-card:hover .tool-icon-box {
  background: #16171b;
  color: #ffffff;
}
/* Pricing cards in the white band ──────────────────────────────────────────
   The side (dark #0F0F12) cards inherit the band's dark --text → near-invisible
   text. Restore WHITE bold text. The middle (highlight) card becomes the teal
   MacBook colour with BLACK bold text (black clears 4.5:1 on the teal). */
.home-light-band .home-tier-card:not(.home-tier-highlight) .home-tier-name,
.home-light-band .home-tier-card:not(.home-tier-highlight) .home-tier-price-num,
.home-light-band .home-tier-card:not(.home-tier-highlight) .home-tier-features {
  color: #ffffff;
  font-weight: 700;
}
.home-light-band .home-tier-card:not(.home-tier-highlight) .home-tier-price-per {
  color: rgba(255, 255, 255, 0.85);
}
.home-light-band .home-tier-card.home-tier-highlight {
  background: #ffffff; /* bright white highlight card */
}
.home-light-band .home-tier-card.home-tier-highlight .home-tier-name,
.home-light-band .home-tier-card.home-tier-highlight .home-tier-price-num,
.home-light-band .home-tier-card.home-tier-highlight .home-tier-price-custom,
.home-light-band .home-tier-card.home-tier-highlight .home-tier-features,
.home-light-band .home-tier-card.home-tier-highlight .home-tier-features li {
  color: #000000; /* black, bold on the white card */
  font-weight: 700;
}
.home-light-band .home-tier-card.home-tier-highlight .home-tier-price-per {
  color: #1a1a1a;
  font-weight: 600;
}
@keyframes ambientShift {
  0% { transform: translateX(-50%) scale(1); opacity: 0.85; }
  100% { transform: translateX(-50%) scale(1.08); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .home-hero::before { animation: none; }
}
/* ============================================ */
/* ===== Floating badges (Mobile Friendly / Automated Emails) ===== */
/* ============================================ */
.feature-art-phone {
  position: relative;
}
.float-badge {
  position: absolute;
  background: var(--text);
  color: #0A0A0A;
  padding: 8px 14px;
  border-radius: 22px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
  white-space: nowrap;
  z-index: 2;
  pointer-events: none;
}
.float-badge-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #0A0A0A;
  flex-shrink: 0;
}
/* Top-left badge with subtle pointer line */
.float-badge-tl {
  top: 14%;
  left: 4%;
  transform: rotate(-4deg);
  animation: floatYA 4.2s ease-in-out infinite;
}
.float-badge-tl::after {
  content: '';
  position: absolute;
  bottom: -10px;
  right: 12px;
  width: 60px;
  height: 1px;
  background: linear-gradient(to right, var(--text), transparent);
  transform: rotate(28deg);
  transform-origin: left center;
  opacity: 0.6;
}
/* Bottom-right badge */
.float-badge-br {
  bottom: 14%;
  right: 4%;
  transform: rotate(3deg);
  animation: floatYB 4.6s ease-in-out infinite;
}
.float-badge-br::after {
  content: '';
  position: absolute;
  top: -10px;
  left: 18px;
  width: 60px;
  height: 1px;
  background: linear-gradient(to left, var(--text), transparent);
  transform: rotate(-28deg);
  transform-origin: right center;
  opacity: 0.6;
}
@keyframes floatYA {
  0%, 100% { transform: rotate(-4deg) translateY(0); }
  50%      { transform: rotate(-4deg) translateY(-6px); }
}
@keyframes floatYB {
  0%, 100% { transform: rotate(3deg) translateY(0); }
  50%      { transform: rotate(3deg) translateY(-7px); }
}
@media (prefers-reduced-motion: reduce) {
  .float-badge-tl,
  .float-badge-br { animation: none; }
}
/* Hide badges on very small phones so they don't crowd */
@media (max-width: 480px) {
  .float-badge { font-size: 11px; padding: 6px 10px; }
  .float-badge::after { display: none; }
  .float-badge-tl { top: 8%; left: 2%; }
  .float-badge-br { bottom: 8%; right: 2%; }
}
/* ============================================ */
/* ===== Subtle hover lift on cards ===== */
/* ============================================ */
/* `.home-tier-card` is intentionally OMITTED here — the tilted-pricing
 * layout has its own hover transforms defined above that combine the tilt
 * rotation with a translateY. Adding a flat translateY here would override
 * the rotation and flatten the cards on hover. */
.tool-card,
.testimonial-card {
  transition: transform 0.25s ease, border-color 0.2s ease, box-shadow 0.25s ease;
}
@media (hover: hover) {
  .tool-card:hover,
  .testimonial-card:hover {
    transform: translateY(-3px);
    border-color: var(--card-border-hover);
  }
}
/* ============================================================
   Large-screen widening (≥1600px). On 1080p+ monitors the fixed
   ~920–1180px content columns leave very large side gutters, and the
   capped 100px section padding reads as too much vertical gap. Widen
   the shared content containers and trim the vertical rhythm so the
   page fills a wide screen. Scoped to min-width, so smaller layouts
   (already dialled in) are left EXACTLY as they are.
   ============================================================ */
@media (min-width: 1600px) {
  .home-tour-inner,
  .home-tools-inner,
  .home-footer-inner { max-width: 1380px; }
  .home-pricing-inner,
  .home-testimonials-inner { max-width: 1300px; }
  .home-pricing-stage { max-width: 1240px; }
  .home-faq-inner { max-width: 1240px; }
  .home-watch-inner { max-width: 1160px; }

  /* HERO on large/HD screens — just scale the type up so the headline fills the
     wider screen. The copy keeps the base min-height:100vh so the dashboard stays
     BELOW the video (no scroll-rise). */
  .home-hero-center { max-width: 1010px; }
  .home-hero-heading { font-size: clamp(84px, 5.4vw, 100px); }
  .home-hero-sub { font-size: 19px; max-width: 62ch; }

  /* Trim the capped 100px vertical padding so tall monitors don't feel
     stretched. padding-block only — the horizontal gutter is untouched. */
  .home-stats-strip,
  .home-pas,
  .home-tour,
  .home-tools,
  .home-watch,
  .home-pricing,
  .home-testimonials,
  .home-faq,
  .home-final-cta { padding-block: 78px; }
}
@media (min-width: 1900px) {
  .home-tour-inner,
  .home-tools-inner,
  .home-footer-inner { max-width: 1500px; }
  .home-pricing-inner,
  .home-testimonials-inner { max-width: 1400px; }
  .home-pricing-stage { max-width: 1340px; }
  .home-faq-inner { max-width: 1320px; }
  .home-watch-inner { max-width: 1240px; }
  .home-hero-center { max-width: 1080px; }
  .home-hero-heading { font-size: clamp(96px, 5.4vw, 110px); }
  .home-hero-sub { font-size: 20px; }
}
/* ============================================================================
   LIGHT THEME OVERRIDES  (redesign — session 27)
   ----------------------------------------------------------------------------
   Written as a trailing override block rather than 45 edits scattered through
   the 4,200 lines above. Three reasons:
     1. The originals stay intact, so this is one block to delete if the theme
        is ever rolled back.
     2. Every chrome surface that flips is listed in ONE place and can be read
        as a set.
     3. ⚠️ IT LETS THE PRODUCT MOCKUPS STAY DARK BY SIMPLY NOT LISTING THEM.
        Roughly half the dark values in this file are not chrome at all — they
        DRAW THE ADMINPANEL. `.dash-*`, `.mac-window`/`.mac-titlebar`,
        `.home-tools-mac*`, `.home-tour-cal*`, `.lux-*`, `.hhs-*`,
        `.home-hero-screen*` and `.home-hero-dot` are a picture of a dark app.
        Inverting them would turn the product screenshots into white boxes —
        the redesign would delete the thing the page exists to show.
        DO NOT "finish the job" by sweeping those too.
   ========================================================================= */
/* ---- section grounds ---------------------------------------------------- */
.home-page .home-hero { background-color: var(--rf-bg, #F5F5F5); }
.home-page .home-trust { background: var(--rf-surface, #FFFFFF); }
/* The big content band. Was pure #000 and is the largest element on the page. */
.home-page .home-light-band { background: var(--rf-bg, #F5F5F5); }
/* Alternate band so sections separate by a subtle ground change, not a rule. */
.home-page .home-dark-band {
  background: var(--rf-surface, #FFFFFF);
  color: var(--rf-ink-body, #333333);
}
.home-page .home-closing { background: var(--rf-surface-2, #FAFAFA); }
/* ⚠️ Two surfaces stay INK on purpose, and it is a design decision, not a
   miss: the demo panel surrounds a video (a light frame around dark footage
   reads as a mistake), and the oversized wordmark is the page's closing
   flourish, which needs a ground the letterforms can cut out of. */
.home-page .demo-launcher { background: var(--rf-surface-ink, #111114); }
.home-page .rentfyv-hero { background: var(--rf-surface-ink, #111114); }
/* ---- cards riding those bands ------------------------------------------- */
.home-page .home-light-band .feature-stack-card,
.home-page .home-light-band .tool-card,
.home-page .testi-card,
.home-page .testi-mq-card,
.home-page .home-tier-card {
  background: var(--rf-surface, #FFFFFF);
  border-color: var(--rf-border, #E4E4E7);
  color: var(--rf-ink-body, #333333);
  box-shadow: var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
}
.home-page .home-light-band .tool-card:hover,
.home-page .testi-card:hover,
.home-page .home-tier-card:hover {
  border-color: var(--rf-primary, #6A5ACD);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}
/* The icon tile inside a tool card was near-black on near-black; on white it
   becomes the brand tint so it still reads as a distinct surface. */
.home-page .tool-icon-box,
.home-page .home-tools-cols--rope .tool-card:hover .tool-icon-box {
  background: var(--rf-primary-soft, #EEECFA);
  color: var(--rf-primary, #6A5ACD);
}
/* ---- pricing ------------------------------------------------------------ */
/* ⚠️ THE `--center` CHECK AND CTA OVERRIDES THAT USED TO SIT HERE ARE DELETED.
   They existed because the middle card was BRAND PURPLE, so its tick disc and
   its button each needed a value the surrounding purple would not swallow.
   The card is night navy now and both take the shared brand rules — an
   override that merely restates what the base rule already says is the kind
   of dead agreement that survives a redesign and then silently contradicts
   the next one. The badge keeps a rule of its own, at the end of this file. */
/* ---- marquee + testimonial controls ------------------------------------- */
.home-page .xmq { background: var(--rf-primary-soft, #EEECFA); }
.home-page .xmq-strip--a { background: var(--rf-primary, #6A5ACD); }
.home-page .testi-arrow {
  background: var(--rf-surface, #FFFFFF);
  border: 1px solid var(--rf-border-strong, #D4D4D8);
  color: var(--rf-ink-body, #333333);
}
.home-page .testi-arrow:hover {
  background: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
}
.home-page .social-icon:hover {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
}
/* ---- type ---------------------------------------------------------------
   Headings move to Poppins; body stays Open Sans (set on .home-page). The
   site had six families and this is where the two land on the landing page. */
.home-page h1,
.home-page h2,
.home-page h3,
.home-page .home-section-title,
.home-page .home-hero-heading {
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  color: var(--rf-ink, #000000);
}
/* Eyebrows become the brand — the brief's "primary for accents". */
.home-page .home-eyebrow-text { color: var(--rf-primary, #6A5ACD); opacity: 1; }
/* Anything that was explicitly white text now sits on a light ground. Scoped
   to the flipped bands ONLY, so white text inside the dark product mockups
   and the two ink sections is untouched. */
.home-page .home-light-band .home-section-title,
.home-page .home-dark-band .home-section-title,
.home-page .home-trust .home-section-title { color: var(--rf-ink, #000000); }
/* ---- text riding the flipped cards --------------------------------------
   The card GROUNDS flipped above; their text was hardcoded white further up
   the file and inheritance cannot reach past a rule. Measured before this
   block: 92 contrast failures, every one of them white-on-white.

   `*` is deliberate here and is safe because each selector is anchored to a
   card that this same block just made light — it cannot leak into the dark
   product mockups, which sit outside these scopes. */
.home-page .home-tier-card,
.home-page .home-tier-card *,
.home-page .testi-card,
.home-page .testi-card *,
.home-page .testi-mq-card,
.home-page .testi-mq-card *,
.home-page .home-light-band .tool-card,
.home-page .home-light-band .tool-card *,
.home-page .home-light-band .feature-stack-card,
.home-page .home-light-band .feature-stack-card * {
  color: var(--rf-ink-body, #333333);
  border-color: var(--rf-border, #E4E4E7);
}
/* Headings inside those cards go full ink, not body grey. */
.home-page .home-tier-card h1, .home-page .home-tier-card h2,
.home-page .home-tier-card h3, .home-page .home-tier-name,
.home-page .home-tier-price,
.home-page .testi-card h3, .home-page .testi-card h4,
.home-page .home-light-band .tool-card h3,
.home-page .home-light-band .feature-stack-card h2,
.home-page .home-light-band .feature-stack-card h3 {
  color: var(--rf-ink, #000000);
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
}
/* Re-assert white ON the brand-filled elements, which the sweep above would
   otherwise have turned grey-on-purple. These sit LAST so they win. */
.home-page .home-tier-badge,
.home-page .home-tier-badge *,
.home-page .home-tier-card--center .home-tier-cta,
.home-page .home-tier-card--center .home-tier-cta *,
.home-page .home-tier-card--center .home-tier-check,
.home-page .home-tier-card--center .home-tier-check *,
.home-page .testi-arrow:hover,
.home-page .testi-arrow:hover *,
.home-page .xmq-strip--a,
.home-page .xmq-strip--a * {
  color: var(--rf-ink-invert, #FFFFFF);
}
/* Quiet supporting copy — the floor at 4.8:1, never lighter. */
.home-page .home-tier-card .home-tier-note,
.home-page .home-tier-card .home-tier-period,
.home-page .testi-card .testi-role,
.home-page .testi-mq-card .testi-role {
  color: var(--rf-ink-faint, #6B6B75);
}
/* ---- pricing cards, re-solved for a light band --------------------------
   The rules ~L3994 were written for DARK side-cards on a white band: white
   bold text on #0F0F12, with the middle card inverted to white-on-white to
   stand out. Now every card is light, so that logic produces white-on-white
   (measured: 38 failures, all of them here) AND the highlight card loses the
   only thing that marked it as recommended.

   Solved by flipping which card is the odd one out: side cards are plain
   white with ink text, and the RECOMMENDED card takes the brand. That is
   also what the brief asks the primary colour to do — carry emphasis.

   ⚠️ Specificity: the originals are (0,4,0) because of the `:not()`. These
   repeat that structure with `.home-page` in front to reach (0,5,0). Dropping
   the prefix silently loses the fight and the white-on-white returns. */
.home-page .home-light-band .home-tier-card:not(.home-tier-highlight) .home-tier-name,
.home-page .home-light-band .home-tier-card:not(.home-tier-highlight) .home-tier-price-num,
.home-page .home-light-band .home-tier-card:not(.home-tier-highlight) .home-tier-features {
  color: var(--rf-ink, #000000);
}
.home-page .home-light-band .home-tier-card:not(.home-tier-highlight) .home-tier-features li {
  color: var(--rf-ink-body, #333333);
}
.home-page .home-light-band .home-tier-card:not(.home-tier-highlight) .home-tier-price-per {
  color: var(--rf-ink-faint, #6B6B75);   /* 4.8:1 on white */
}
/* ⚠️⚠️ THE RECOMMENDED CARD IS NIGHT NAVY, NOT BRAND PURPLE (operator: "the
   middle one black with purple button … color scheme exactly like pricing
   cards"). These values are `/pricing`'s `.tier-card-highlight`, deliberately
   — the two pricing surfaces on this site now say "this is the important
   tier" the same way.
   ⚠️ AND IT IS WHAT FREES THE BUTTON AND THE BADGE. On a purple card the CTA
   had to invert to white and the badge could be neither purple (invisible on
   the card) nor white (invisible where it overhangs the page) — both problems
   are the SAME problem: the card had taken the brand colour, so nothing on it
   could use the brand. Moving the card to navy hands the brand back to the
   two elements that should carry it. Three earlier badge treatments in this
   file were all working around that; see the badge rule at the end.
     white on #0B0E1A        19.2:1
     #6A5ACD on #0B0E1A       3.62:1  (the button's fill, floor 3)
     card vs #F5F5F5 band    17.6:1                                        */
.home-page .home-light-band .home-tier-card.home-tier-highlight {
  background: var(--rf-night, #0B0E1A);
  border-color: rgba(255, 255, 255, 0.12);
  box-shadow: 0 20px 48px rgba(11, 14, 26, 0.28);
}
.home-page .home-light-band .home-tier-card.home-tier-highlight .home-tier-name,
.home-page .home-light-band .home-tier-card.home-tier-highlight .home-tier-price-num,
.home-page .home-light-band .home-tier-card.home-tier-highlight .home-tier-price-custom,
.home-page .home-light-band .home-tier-card.home-tier-highlight .home-tier-features,
.home-page .home-light-band .home-tier-card.home-tier-highlight .home-tier-features li,
.home-page .home-light-band .home-tier-card.home-tier-highlight .home-tier-price-per {
  color: var(--rf-ink-invert, #FFFFFF);
}
/* ⚠️⚠️ THE YEARLY-ONLY LINE, WHICH IS WHY IT WAS MISSED TWICE. `.home-tier-was`
   — the struck full annual price — renders ONLY when the billing toggle is on
   Yearly, so it is absent from the default view and from any screenshot of it.
   The light-theme sweep paints every descendant `--rf-ink-body` (#333333) and
   the white re-assert list above never named it, so on the navy card it
   measured **1.52:1** and its "30% off" tag **1.31:1**. It was already wrong
   before this card changed colour (2.14:1 on the purple), so this is a fix,
   not a regression I introduced — but a card colour change is exactly the
   moment it becomes indefensible.
   ⚠️ SWEEP BOTH BILLING MODES. Nothing about the monthly view can tell you
   this is here; the monthly sweep measured 0 failures at all three widths
   while these two nodes were failing underneath it. */
.home-page .home-light-band .home-tier-card.home-tier-highlight .home-tier-strike {
  color: #A8AFC0;                    /* 8.7:1 on #0B0E1A — a "was" price is secondary */
  text-decoration-color: #FF5A5F;    /* /pricing's strike red */
}
/* ⚠️ ITS CTA IS THE BRAND NOW, NOT A WHITE PILL. It inverted to white only
   because the card underneath was already purple; on navy the brand is
   available again and this becomes the same button the two side cards wear,
   which is the point — one action, one colour, three tiers.
   ⚠️ AND ITS HOVER LIGHTENS, WHICH IS THE OPPOSITE OF THE SIDE CARDS'.
   Deliberate, and the same split `/pricing` documents: the house hover
   #574AB0 keeps the label fine but drops the FILL to 2.76:1 on navy, so the
   control loses its edge exactly when you point at it. #7565CF goes the other
   way — label 4.65:1, fill 4.14:1. Darkening is right on a white card and
   wrong on this one; do not "align" the three hovers. */
.home-page .home-light-band .home-tier-card.home-tier-highlight .home-tier-cta {
  background: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);         /* 5.31:1 */
  box-shadow: 0 6px 18px -4px rgba(0, 0, 0, 0.45);
}
.home-page .home-light-band .home-tier-card.home-tier-highlight .home-tier-cta:hover {
  background: #7565CF;
  border-color: #7565CF;
  box-shadow: 0 9px 24px -4px rgba(0, 0, 0, 0.6);
}
/* ---- FAQ, feature stack and final CTA -----------------------------------
   Found by auditing the RENDERED page rather than by reading the stylesheet:
   an earlier pass guessed `.feature-stack-card` when the shipped class is
   `.fstack-card`, so that rule matched nothing and the cards stayed dark
   while their headings had already gone black — 1.16:1. Measure, don't
   assume the class name. */
/* ⚠️ THE CARD'S SURFACE IS NOT SET HERE ANY MORE — see FeatureStack.css.
   Two things this rule got wrong, both worth remembering:
   1. `background:` here was DEAD CODE that read as live. The ground is an
      inline style from LandingFeatures.jsx (`style={{backgroundColor:
      card.color}}`), and inline beats every selector short of !important.
   2. `border-color` + `box-shadow` here are (0,2,0) and silently BEAT the
      component's own (0,1,0) light-theme block, downgrading its elevation to
      a 0 4px 16px/.08 shadow — on cards that are 1.05:1 against their band
      and rely on that edge to stack at all.
   The component owns its own look; do not re-add surface properties here. */
.home-page .fstack-title { color: var(--rf-ink, #000000); }
.home-page .fstack-card p,
.home-page .fstack-card li { color: var(--rf-ink-body, #333333); }
/* FAQ — the accordion sat on the dark band. */
.home-page .faq-q-item {
  background: var(--rf-surface, #FFFFFF);
  border-color: var(--rf-border, #E4E4E7);
}
.home-page .faq-q-item.is-active {
  background: var(--rf-primary-soft, #EEECFA);
  border-color: var(--rf-primary, #6A5ACD);
}
.home-page .faq-q-text { color: var(--rf-ink-body, #333333); }
.home-page .faq-q-item.is-active .faq-q-text { color: var(--rf-ink, #000000); }
.home-page .faq-q-num { color: var(--rf-primary-deep, #453A8C); }
/* 3.26 → AA */
.home-page .faq-answer-a { color: var(--rf-ink-body, #333333); }
/* Final CTA check marks — #4ADE80 measured 1.74:1 on white. */
.home-page .home-final-cta-ck { color: #15803D; }
/* 5.01:1 */
/* Hero highlight + sub were tuned for a near-black stage. */
.home-page .home-hero-hl { color: var(--rf-primary, #6A5ACD); }
/* ⚠️ `.home-page .hero-stage .reveal { color: --rf-ink-body }` WAS HERE AND IS
   DELETED (session 29, §CH). It was written when the hero stage was part of the
   light flip; the stage is a violet ground again, so it painted #333333 onto it
   — the eyebrow measured **1.28:1** and the trust line 1.51:1, both invisible.
   At (0,3,0) it also out-specified the hero's own (0,2,0) rules, so the hero
   could not correct it from its own stylesheet. **A leftover override aimed at
   a surface that has since changed is worse than no rule**: it is invisible in
   review, wins on specificity, and only a pixel sweep finds it. If the hero
   ever goes light again, the colour belongs in LandingHero.css, not here. */
/* faq-q-num on the tinted active row needed the deepest brand step (2.92 → AA). */
.home-page .faq-q-item.is-active .faq-q-num,
.home-page .faq-q-num { color: var(--rf-primary-deep, #453A8C); }
/* The marquee stars were #8B6CFF — 3.69:1 on white. They are 12px, so their
   floor is 4.5 regardless of weight; the brand proper clears it at 5.31:1.
   ⚠️ Found only at 375px: the fluid type scale drops the surrounding headings
   under 24px there, which RAISES their required ratio from 3.0 to 4.5. A
   desktop-only contrast pass reports this page as clean. Always sweep both. */
.home-page .xmq-star { color: var(--rf-primary, #6A5ACD); }
/* ⚠️ The marquee has TWO alternating strips. Making `--a` the brand ground and
   the stars the brand ink produced primary-on-primary — a measured 1:1, i.e.
   invisible. Everything riding the brand strip inverts; the other strip keeps
   ink on the tint. Fixing the star colour alone was not enough, because the
   GROUND had moved in the same pass. */
.home-page .xmq-strip--a,
.home-page .xmq-strip--a * { color: var(--rf-ink-invert, #FFFFFF); }
.home-page .xmq-strip:not(.xmq-strip--a),
.home-page .xmq-strip:not(.xmq-strip--a) * { color: var(--rf-ink-body, #333333); }
.home-page .xmq-strip:not(.xmq-strip--a) .xmq-star { color: var(--rf-primary, #6A5ACD); }
/* ============================================================================
   FINAL CTA — the page's conversion moment, rebuilt on the night palette
   ----------------------------------------------------------------------------
   ⚠️ IT WAS INVISIBLE. `.home-final-cta-inner` is `background: var(--card)`,
   which the light theme retargeted to #FFFFFF — a white card on a #F5F5F5
   page separated by a 0.5px hairline. The element carrying the page's most
   important action had the LEAST visual weight on it.

   Fixed by hierarchy rather than by nudging the border: the closing CTA is
   now the heaviest surface on the page. That also restores three things the
   light flip had quietly broken — the cursor glare, the button's glow-pulse
   and the card's 3D tilt were all designed to read against a dark ground and
   do essentially nothing on white.
   ========================================================================= */
.home-page .home-final-cta-inner {
  background:
    radial-gradient(120% 100% at 50% 0%, var(--rf-night-2, #1B2233), transparent 62%),
    var(--rf-night, #0B0E1A);
  border: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}
/* 19.2:1 — the strongest type contrast on the page, which is what the most
   important heading should have. */
.home-page .home-final-cta-card h2,
.home-page .home-final-cta-inner > h2 { color: var(--rf-ink-invert, #FFFFFF); }
/* ⚠️⚠️ THE ACCENT IS THE BRAND, NOT THE SKY BLUE (operator-reported: the card's
   colours "aren't according to our color scheme").
   `--rf-sky #56A5E0` reads as CYAN, and it was carrying the loudest half of the
   page's most important heading plus the three check marks — so the conversion
   moment was the one surface on the site not wearing the brand. It measured
   7.2:1, i.e. this was never a contrast problem and no sweep would have found
   it; it was an identity one.
   `--rf-primary-on-dark #8B7BE0` is the primary lightened until it clears AA on
   this ground: **5.50:1 on #0B0E1A, 4.54:1 at the gradient's lightest point
   (#1B2233, directly behind this heading)** — both over the 4.5 floor, and this
   line is 28–48px display type anyway.
   ⚠️ Do NOT "simplify" it to `--rf-primary`: that is 3.62:1 here, fine for the
   BUTTON's fill below but under the floor for text. Same two-step ramp the
   navy tier card on /pricing runs (§DA8). */
.home-page .home-final-cta-inner .hero-accent { color: var(--rf-primary-on-dark, #8B7BE0); }
.home-page .home-final-cta-recap,
.home-page .home-final-cta-recap > span { color: var(--rf-ink-invert, #FFFFFF); }
/* ⚠️ Overrides the #15803D set earlier in this file. That green was chosen for
   a WHITE card; on navy it drops to 2.4:1. Ticks follow the ground they sit
   on, not the one they were first drawn on.
   Now the brand's on-dark step rather than the sky blue, with the heading — and
   it matches the tick every tier card on /pricing wears, so the same mark means
   the same thing sitewide. 5.50:1 on #0B0E1A, 4.54:1 on the gradient's light
   end. These are 13–15px, so 4.5 is the real floor and both clear it. */
.home-page .home-final-cta-ck { color: var(--rf-primary-on-dark, #8B7BE0); }
/* Reassurance line — quiet, but still AA on navy. */
.home-page .home-final-cta-fud,
.home-page .home-final-cta-fud > span { color: rgba(255, 255, 255, 0.72); }
/* The CTA itself: the brightest element on the darkest card, so the eye lands
   on it last and stops. Beats the site-wide `.x.x` brand rule via (0,3,0).

   ⚠️⚠️ IT IS THE BRAND NOW, NOT `--rf-indigo` (operator-reported, same ask as
   the accent above). #5A5FE8 is a periwinkle: it sat one hue away from the
   brand while every other primary button on the site — the nav's "Book Demo",
   /pricing's three Subscribe buttons, the FAQ's "Talk to us" — is #6A5ACD. The
   page's single most important control was the one that did not look like ours.
   Measured on the card it actually sits on: **label 5.31:1, fill 3.62:1 against
   #0B0E1A** — both clear (a fill is a graphic, floor 3:1).

   ⚠️⚠️ AND ITS HOVER LIGHTENS, WHICH IS BACKWARDS FROM EVERY BRAND BUTTON ON A
   LIGHT BAND, DELIBERATELY. THE DIRECTION FOLLOWS THE GROUND, NOT THE BUTTON.
   The house hover `--rf-primary-strong #574AB0` keeps the label fine (6.97:1)
   but drops the FILL to 2.76:1 on this navy — the control loses its edge
   exactly when you point at it. #7565CF measures **label 4.65:1, fill 4.14:1**,
   so both axes stay above their floors and the fill gets STRONGER on hover.
   This is byte-identical to the call §DA7 made for /pricing's navy tier card;
   do not "align" it with the light-band buttons.

   ⚠️ This also closes the home half of issue 39: the old hover lightened to
   #6E72F0, measured live at **3.94:1** under a white 14px label — a hover state
   that FAILED AA on a button whose resting state passed.

   ⚠️ `/blog` and `/features` still paint this same card's button `--rf-indigo`
   (Blog.css ~L953, Features.css ~L1364) and were deliberately kept byte-
   identical to this rule. They are NOT changed here — that was outside the ask
   — so home and those two now differ. Bring them across in one pass or the
   drift is permanent. */
.home-page .home-final-cta-inner .home-cta-primary,
.home-page .home-final-cta-inner .home-cta-primary.home-cta-primary {
  background: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
  box-shadow: 0 10px 34px rgba(106, 90, 205, 0.45);
}
.home-page .home-final-cta-inner .home-cta-primary.home-cta-primary:hover {
  background: #7565CF;
  border-color: #7565CF;
}
/* ============================================================================
   FAQ — the two-panel master/detail, rebuilt for the light theme
   ----------------------------------------------------------------------------
   ⚠️ THE ANSWER PANEL WAS HALF-CONVERTED AND THE AUDIT COULD NOT SEE IT.
   `.faq-answer-col` is `linear-gradient(180deg, var(--card-2), #101013)`: the
   token flip turned the FIRST stop light while the second stayed a hardcoded
   near-black, so the panel faded from white to black down its own height and
   the body copy became unreadable partway down. A contrast sweep that reads
   `backgroundColor` reports this element as TRANSPARENT — gradients live in
   `background-image` — so every text node on it was measured against an
   ancestor instead, and the page audited clean at 0 failures. Gradients are a
   blind spot; check them explicitly.

   Design call: the panel stays LIGHT. An FAQ answer is prose to be read, not
   a call to action — reserving the night-navy ground for the closing CTA is
   what keeps "dark = act now" meaningful on this page.
   ========================================================================= */
/* Accent pair retuned from the reference palette (was #A06EFF → #5A82FF,
   both tuned to glow on near-black). */
.home-page .home-faq {
  --faq-accent: var(--rf-primary, #6A5ACD);
  --faq-accent-2: var(--rf-indigo, #5A5FE8);
}
.home-page .faq-answer-col {
  background: linear-gradient(180deg, var(--rf-surface, #FFFFFF), var(--rf-mist, #F1F8FD));
  border: 1px solid var(--rf-border, #E4E4E7);
  box-shadow: var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
}
/* The ghost numeral was white at 0.045 alpha — literally invisible on a light
   panel. Inverted to the palette's night at a comparable weight. */
.home-page .faq-bignum { color: rgba(11, 14, 26, 0.06); }
.home-page .faq-answer-q { color: var(--rf-ink, #000000); }
.home-page .faq-answer-a { color: var(--rf-ink-body, #333333); }
/* 11.6:1 */
/* Eyebrow ("● QUESTION 1") — the raw accent measures 3.3:1 on this panel, so
   the deep step carries the text while the dot keeps the brand hue (a dot is
   a graphic, floor 3:1). */
.home-page .faq-answer-eyebrow { color: var(--rf-primary-deep, #453A8C); }
.home-page .faq-answer-eyebrow::before,
.home-page .faq-answer-eyebrow > span:first-child { color: var(--rf-primary, #6A5ACD); }
/* Question list. The hover was rgba(255,255,255,0.03) — a white wash on a
   white card, i.e. no hover state at all on a light page. */
.home-page .faq-q-item {
  background: var(--rf-surface, #FFFFFF);
  border: 1px solid var(--rf-border, #E4E4E7);
}
.home-page .faq-q-item:hover {
  background: var(--rf-primary-tint, rgba(106, 90, 205, 0.08));
  border-color: var(--rf-primary, #6A5ACD);
}
.home-page .faq-q-item.is-active {
  background: var(--rf-primary-soft, #EEECFA);
  border-color: var(--rf-primary, #6A5ACD);
}
/* The +/− toggle sat on a dark pill. */
.home-page .faq-q-toggle,
.home-page .faq-q-item .faq-toggle {
  background: var(--rf-surface, #FFFFFF);
  border: 1px solid var(--rf-border-strong, #D4D4D8);
  color: var(--rf-ink-body, #333333);
}
.home-page .faq-q-item.is-active .faq-q-toggle,
.home-page .faq-q-item.is-active .faq-toggle {
  background: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
}
/* Progress dots: inactive needs to be visible on the light panel, which a
   white-alpha fill was not. */
.home-page .faq-dot { background: var(--rf-border-strong, #D4D4D8); }
.home-page .faq-dot.is-active {
  background: linear-gradient(90deg, var(--rf-primary, #6A5ACD), var(--rf-indigo, #5A5FE8));
}
.home-page .faq-help-text { color: var(--rf-ink-faint, #6B6B75); }
/* 4.8:1 */
/* The "Save 30%" badge: white on a #8B6CFF → #5B9DFF gradient measured 2.72:1
   at the light end. Both stops move into the brand pair, where white holds
   ≥4.96:1 across the whole ramp (5.31 at #6A5ACD, 4.96 at #5A5FE8).
   ⚠️ Only visible to a GRADIENT-AWARE audit — a sweep that reads
   backgroundColor sees this element as transparent and clears it. */
.home-page .home-billing-save {
  background: linear-gradient(100deg, var(--rf-primary, #6A5ACD), var(--rf-indigo, #5A5FE8));
  color: var(--rf-ink-invert, #FFFFFF);
}
/* ============================================================================
   FAQ — FIGURE/GROUND (operator-reported: "background is white and panel is
   white as well")
   ----------------------------------------------------------------------------
   The previous pass made the panel READABLE but not DISTINCT: the section
   ground, the question cards and the answer panel were all #FFFFFF, so there
   were three surfaces and no separation between any of them. Readability and
   hierarchy are different problems; fixing the first does not fix the second.

   Solved with the two things a light theme actually has to work with:

   1. ELEVATION NEEDS A NON-WHITE GROUND. White cards only read as "raised"
      when the surface behind them is darker. The band moves to the page grey,
      and the cards get real shadows — so the stack is ground → card → panel
      instead of one flat sheet.
   2. MASTER/DETAIL WANTS A MATERIAL CHANGE, not just a bigger shadow. The
      list is a set of choices; the panel is the answer to the one you picked.
      Making the panel the palette's night navy separates the two instantly and
      gives the eye an obvious place to land. Three lines of copy on a dark
      ground is a caption, not an article — the reading-comfort argument for a
      light panel does not apply at this length.
   ========================================================================= */
/* 1 — the band stops being white so anything on it can lift off it.
   This also fixes the testimonial cards, which sit in the same band. */
.home-page .home-dark-band { background: var(--rf-bg, #F5F5F5); }
/* 2 — question list: white cards, genuinely elevated. */
.home-page .faq-q-item {
  background: var(--rf-surface, #FFFFFF);
  border: 1px solid var(--rf-border, #E4E4E7);
  box-shadow: var(--rf-shadow-sm, 0 1px 2px rgba(16, 16, 20, 0.06));
}
.home-page .faq-q-item:hover {
  background: var(--rf-surface, #FFFFFF);
  border-color: var(--rf-primary, #6A5ACD);
  box-shadow: var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
}
.home-page .faq-q-item.is-active {
  background: var(--rf-primary-soft, #EEECFA);
  border-color: var(--rf-primary, #6A5ACD);
  box-shadow: var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
}
/* 3 — the answer panel becomes the focal surface. */
.home-page .faq-answer-col {
  background:
    radial-gradient(120% 90% at 100% 0%, var(--rf-night-2, #1B2233), transparent 60%),
    var(--rf-night, #0B0E1A);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}
/* Everything drawn on it follows the ground — the mistake this session keeps
   re-teaching. Ratios are against #0B0E1A. */
.home-page .faq-answer-q { color: var(--rf-ink-invert, #FFFFFF); }
/* 19.2:1 */
.home-page .faq-answer-a { color: rgba(255, 255, 255, 0.82); }
/* 14.4:1 */
.home-page .faq-answer-eyebrow { color: var(--rf-sky, #56A5E0); }
/*  7.2:1 */
.home-page .faq-answer-eyebrow::before,
.home-page .faq-answer-eyebrow > span:first-child { color: var(--rf-sky, #56A5E0); }
/* The ghost numeral goes back to a white wash — it is on a dark panel again. */
.home-page .faq-bignum { color: rgba(255, 255, 255, 0.06); }
/* Progress dots: the inactive track needs to read on navy, not on white. */
.home-page .faq-dot { background: rgba(255, 255, 255, 0.22); }
.home-page .faq-dot.is-active {
  background: linear-gradient(90deg, var(--rf-sky, #56A5E0), var(--rf-indigo, #5A5FE8));
}
/* ============================================================================
   ⚠️⚠️ LOCAL TOKEN SCOPES — the real cause of "text disappears on hover"
   ----------------------------------------------------------------------------
   Retargeting `.home-page`'s token block was NOT enough. THREE more scopes
   inside this file redefine the same names for a dark ground:

     .home-faq                              (L~3099)
     .home-light-band                       (L~3869)
     .home-light-band .feature-stack-card   (L~3938)

   each setting `--text: #fafafa`, `--text-dim: #9ca3af`, `--text-faint:
   #6b7280`. Any rule that reads `var(--text)` inside them resolves to
   NEAR-WHITE regardless of what `.home-page` says, because a nested scope
   wins. `.home-faq` even documents why it exists — it is self-contained so
   the FAQ can be reused on /demo outside `.home-page`.

   ⚠️ A STATIC CONTRAST SWEEP CANNOT SEE THIS. The visible text all had
   explicit colour rules, so the page audited clean at 0 failures; only paths
   that go THROUGH the token — here `.faq-q-item:hover .faq-q-text { color:
   var(--text) }` — were broken, and :hover is not in the computed styles a
   sweep reads. Measured: rest #333333 on white = 12.63:1, hover #fafafa on
   white = 1.04:1, i.e. the text vanished under the cursor.

   Fixed at the TOKEN, not at the hover rule, so every other consumer of these
   names inside those scopes is corrected at the same time.
   ========================================================================= */
.home-page .home-faq,
.home-page .home-light-band,
.home-page .home-light-band .feature-stack-card,
/* Also correct when the FAQ is reused outside .home-page (its whole reason
   for carrying its own tokens) — e.g. the /demo page. */
.home-faq {
  --text: #333333;        /* 11.6:1 on #F5F5F5 */
  --text-dim: #4A4A52;    /*  8.1:1 */
  --text-faint: #6B6B75;  /*  4.8:1 — the floor */
  --card: #FFFFFF;
  --card-2: #FAFAFA;
  --card-border: #E4E4E7;
  --card-border-hover: #6A5ACD;
}
/* The FAQ list sits on light cards, so its hover/active text is ink. The
   shipped rule is `.faq-q-item:hover .faq-q-text` at (0,3,0) — these match
   that weight and add `.home-page` to win. */
.home-page .faq-q-item:hover .faq-q-text,
.home-page .faq-q-item.is-active .faq-q-text {
  color: var(--rf-ink, #000000);
}
/* Billing period toggle. `.home-billing-opt.is-active` was
   `background: var(--text); color: #0A0A0A` — the "white pill, black label on
   a dark page" idiom. Retargeting --text to #333333 made it #0A0A0A on
   #333333: 1.57:1. Exactly the same failure the primary CTA had, from exactly
   the same idiom — worth grepping `background: var(--text)` before calling a
   theme flip done. */
.home-page .home-billing-toggle {
  background: var(--rf-surface, #FFFFFF);
  border: 1px solid var(--rf-border, #E4E4E7);
}
.home-page .home-billing-opt { color: var(--rf-ink-body, #333333); }
.home-page .home-billing-opt.is-active {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);   /* 5.31:1 */
}
/* ⚠️ EVERY REMAINING `background: var(--text); color: #0A0A0A` SITE.
   That pair is the "white pill, near-black label" idiom the dark theme used
   for its emphasis chips. Retargeting --text to #333333 turns every one of
   them into #0A0A0A on #333333 — 1.57:1. Found by grepping the idiom rather
   than waiting for each to be reported one at a time:
     .home-pill-btn · .home-cta-primary   → already handled by the brand rule
     .hero-email-button · .feat-check · .float-badge   → below
     .dash-side-upsell · .dash-overview-card  → INSIDE THE ADMIN MOCKUP, so
       they keep the dark-on-light-pill look deliberately and are NOT listed. */
.home-page .hero-email-button,
.home-page .feat-check,
.home-page .float-badge {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);   /* 5.31:1 */
}
.home-page .hero-email-button:hover {
  background: var(--rf-primary-strong, #574AB0);
}
/* ============================================================================
   TESTIMONIALS — figure/ground (operator-reported: "cards arent [visible]")
   ----------------------------------------------------------------------------
   Same class of fault as the FAQ (§CB11), and the numbers say why a border and
   a shadow were not enough on their own: white cards on the #F5F5F5 band
   measure **1.07:1**. That is below the 3:1 floor WCAG 1.4.11 sets for a
   meaningful non-text boundary, so the card edge was doing no work and the
   whole row read as one sheet.

   Fixed by moving the SECTION rather than shouting with the shadow: a tinted
   ground lets plain white cards float without a heavy drop shadow, which is
   the calmer and more durable solution (a shadow strong enough to separate
   1.07:1 surfaces reads as grubby at this card size).
   ========================================================================= */
.home-page .home-testimonials {
  background: var(--rf-primary-soft, #EEECFA);
  padding-block: var(--rf-section-y, clamp(56px, 8vw, 112px));
}
.home-page .testi-card,
.home-page .testi-mq-card {
  background: var(--rf-surface, #FFFFFF);
  /* Border now clears the 3:1 non-text floor against the tint it sits on. */
  border: 1px solid var(--rf-border-strong, #D4D4D8);
  box-shadow: var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
}
.home-page .testi-card:hover,
.home-page .testi-mq-card:hover {
  border-color: var(--rf-primary, #6A5ACD);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}
/* Quote, attribution and the avatar monogram. */
.home-page .testi-card h3,
.home-page .testi-card h4,
.home-page .testi-mq-card h3,
.home-page .testi-quote { color: var(--rf-ink, #000000); }
.home-page .testi-body,
.home-page .testi-mq-card p { color: var(--rf-ink-body, #333333); }
.home-page .testi-role,
.home-page .testi-author { color: var(--rf-ink-faint, #6B6B75); }
/* 4.8:1 */
.home-page .testi-avatar,
.home-page .testi-mq-avatar {
  background: var(--rf-primary-soft, #EEECFA);
  color: var(--rf-primary-deep, #453A8C);   /* 7.4:1 */
  border: 1px solid var(--rf-border, #E4E4E7);
}
/* The section heading + proof line sit on the tint, not on white. */
.home-page .home-testimonials .home-section-title { color: var(--rf-ink, #000000); }
/* ⚠️ The stats strip was still `background: #000` — a black section sitting
   INSIDE the now-light `.home-light-band`. It survived every earlier pass
   because nothing in it failed a contrast check: its text was light and its
   ground was dark, so the pair was internally consistent and the audit was
   happy. It only surfaced once the read-along tokens went to ink and the
   words landed on black at 2.39:1.

   Lesson: a section can be fully AA-compliant and still be the wrong colour.
   Contrast checks correctness WITHIN a surface; nothing checks that the
   surface belongs to the theme. */
.home-page .home-stats-strip {
  background: var(--rf-surface, #FFFFFF);
  border-block: 1px solid var(--rf-border, #E4E4E7);
}
.home-page .home-stats-strip .shl-word { color: var(--rf-ink-dim, #4A4A52); }
.home-page .home-stats-strip h2,
.home-page .home-stats-strip h3,
.home-page .home-stats-strip [class*="num"],
.home-page .home-stats-strip [class*="value"] { color: var(--rf-ink, #000000); }
.home-page .home-stats-strip [class*="label"],
.home-page .home-stats-strip p { color: var(--rf-ink-body, #333333); }
/* `.hero-accent` (the greyed second line of a two-tone heading) was
   rgb(140,140,140) — 2.88:1 on the testimonials tint, just under the 3.0
   floor that 44px bold text needs. It is a *de-emphasised* line by design, so
   it takes the faint token rather than full ink: 4.5:1 on the tint. */
.home-page .hero-accent { color: var(--rf-ink-faint, #6B6B75); }
/* ============================================================================
   CROSSING MARQUEE + THE BAND'S ROUNDED LIP + RATING STARS
   ========================================================================= */
/* ---- 1. The two crossing ribbons ----------------------------------------
   Original: BLACK strip over WHITE strip on a near-black ground — a
   high-contrast X where each ribbon read against the other AND against the
   page. The theme flip kept that literally, so the white ribbon ended up on a
   near-white ground and dissolved into it; only one of the two X arms was
   still legible.

   The device only works when BOTH ribbons separate from the ground and from
   each other. So both go saturated on a light ground — brand purple over
   night navy, which is the light-theme translation of "black over white" and
   keeps the two arms clearly distinct.
     purple vs ground  4.95:1 · navy vs ground 17.9:1 · purple vs navy 3.6:1
     white on purple   5.31:1 · white on navy   19.2:1                      */
.home-page .xmq {
  background: var(--rf-mist, #F1F8FD);   /* cool near-white — its own band */
}
.home-page .xmq-strip--a {
  background: var(--rf-primary, #6A5ACD);
  box-shadow: 0 18px 50px -22px rgba(16, 16, 20, 0.45);
}
.home-page .xmq-strip--b {
  background: var(--rf-night, #0B0E1A);
  border-top-color: rgba(255, 255, 255, 0.08);
  border-bottom-color: rgba(255, 255, 255, 0.08);
}
.home-page .xmq-strip--a .xmq-item,
.home-page .xmq-strip--b .xmq-item { color: var(--rf-ink-invert, #FFFFFF); }
/* ---- 2. The light band curves over the dark section above ----------------
   The demo band ends on a hard horizontal edge. Rounding the TOP of the
   following light band and lifting it over that edge reads as a new sheet
   sliding over the previous one — it marks a chapter break without needing a
   rule, and it is the same idiom `.home-dark-band` already used.

   ⚠️ The negative margin is what makes the corner notches reveal the DARK
   section. Without it the notches expose the page grey and the curve is
   invisible. It does NOT disturb the demo section's scroll maths: DemoLauncher
   derives progress from its OWN height, which a following sibling's margin
   cannot change. */
.home-page .xmq {
  position: relative;
  z-index: 3;
  margin-top: -44px;
  border-radius: 44px 44px 0 0;
  /* Clip the rotated ribbons to the rounded lip, or they overhang the curve. */
  overflow: hidden;
}
/* ---- 3. Rating stars use the real app-store gold ------------------------
   #FFC107 is the canonical rating amber (Google Play; Amazon and IMDb sit
   within a few points of it). The previous value was a brand purple, which
   reads as decoration rather than as a rating.

   ⚠️ Deliberate accessibility note: NO recognisable rating gold clears 3:1 on
   a white ground — gold is simply a light hue (#FFC107 measures 1.63:1). That
   is acceptable HERE, and only because the rating is also stated in text
   beside every star row ("4.9 on the App Store"). The stars reinforce a value
   that is already available non-graphically, so they are not the sole carrier
   of the information. If a star row is ever used WITHOUT the numeral, it needs
   a darker amber or a dark ground. */
.home-page .xmq-star,
.home-page .home-eyebrow-star,
.home-page .home-hero-stars,
.home-page .home-hero-proof-stars,
.home-page .home-testi-stars,
.home-page [class*="-stars"],
.home-page [class*="-rating"] { color: #FFC107; }
/* On the two dark ribbons the same gold is comfortably clear (11.8:1 on navy,
   3.26:1 on purple), so one value serves every placement. */
.home-page .xmq-strip--a .xmq-star,
.home-page .xmq-strip--b .xmq-star { color: #FFC107; }
/* ⚠️ `:not()` CONTRIBUTES ITS ARGUMENT'S SPECIFICITY. An earlier rule in this
   file, `.home-page .xmq-strip:not(.xmq-strip--a) .xmq-star`, is (0,4,0) — not
   the (0,3,0) it looks like — so the gold rule above lost and the stars stayed
   brand purple. Doubling the strip class matches that weight, and being later
   in the file wins the tie. */
.home-page .xmq-strip.xmq-strip .xmq-star { color: #FFC107; }
/* ============================================================================
   PRICING SECTION — band lips, card colours, CTA hierarchy
   ========================================================================= */
/* ---- 1. Rounded lips at both theme changes ------------------------------
   Page order here is: [dark coverflow] → pricing (light) → demo band (dark).
   Both boundaries were hard horizontal edges. Each light/dark change now gets
   the same "next sheet slides over the last" lip already used on `.xmq`.

   ⚠️ The negative margin is the load-bearing half — a border-radius alone
   rounds the corner against the SAME colour and is invisible. The block has
   to lap over the one above for the notches to reveal it. */
.home-page .home-pricing-section {
  position: relative;
  z-index: 2;
  background: var(--rf-bg, #F5F5F5);
  border-radius: 44px 44px 0 0;
  margin-top: -44px;
  padding-top: clamp(48px, 6vw, 88px);
}
/* The "Cancel the stack" deck — the one dark block in the light band that was
   still meeting its neighbour on a hard horizontal edge (operator-reported).
   Same idiom as `.home-pricing-section` below it and `.xmq` above: lap over the
   previous section and round the leading corners.

   ⚠️ THE RULE IS ON THE WRAPPER, NEVER ON `.cf-deck`. The deck is GSAP-pinned,
   so while pinned it is `position: fixed` covering the viewport — a radius on it
   would show as two light notches at the top of the SCREEN for the whole pin.
   See the note in Landing.jsx.

   ⚠️ `overflow: hidden` is load-bearing here, unlike everywhere else this idiom
   appears. The deck paints its own opaque ground (it must — it is fixed over
   a scrolling page), so a radius on the wrapper alone is painted straight over
   by the deck's square corners. The wrapper has to CLIP, not just round.
   It cannot break the pin the way it would break a `sticky` child: nothing in
   this element's ancestry has a transform/filter/contain (verified in the live
   DOM), so the fixed deck's containing block is the viewport and it escapes the
   clip — which is exactly the full-bleed behaviour the pinned state wants.

   ⚠️ And the negative margin is the load-bearing half of the LOOK, as everywhere
   else here: a radius alone rounds the corner against the same colour and is
   invisible. The block has to lap over the one above for the notches to reveal
   it. The 44px matches the rest of the page's lips exactly. */
.home-page .home-deck-lip {
  position: relative;
  z-index: 2;
  margin-top: -44px;
  border-radius: 44px 44px 0 0;
  overflow: hidden;
  /* ⚠️ MUST EQUAL `.cf-deck`'s own background (Coverflow.css). This wrapper is
     what the rounded corners actually reveal while the deck is pinned — a
     fixed child escapes this clip — so a mismatch shows as two different darks
     meeting at the curve. Both are `--rf-night` since the deck was re-grounded
     off pure black onto the site's night palette. */
  background: var(--rf-night, #0B0E1A);
}
/* The dark demo band starts below — same treatment, opposite polarity. */
.home-page .demo-launcher {
  position: relative;
  z-index: 2;
  border-radius: 44px 44px 0 0;
  margin-top: -44px;
  /* ⚠️ NO `overflow: hidden` HERE. This section's child is `position: sticky`,
     and an ancestor with a clipping overflow becomes a scroll container, which
     kills the stick. border-radius still clips this element's own background
     paint, which is all the lip needs. */
}
/* ---- 2. Tier cards ------------------------------------------------------
   Three problems, all from dark-theme leftovers:
     · the tick chip was rgba(170,110,255,0.18) — the OLD accent purple;
     · feature rows were #000 at weight 700, so every line shouted as loudly
       as the price, flattening the card's own hierarchy;
     · the side CTAs were #FAFAFA on a #FFFFFF card with a #E4E4E7 border —
       effectively invisible, which is why "Start Now" reads as missing. */
/* ⚠️ THE TICK DISC IS THE BRAND ON ALL THREE CARDS (operator: "purple ticks on
   all 3 cards"), which is `/pricing`'s `.tier-check` exactly. It was a pale
   `--rf-primary-soft` chip with a deep-purple glyph — quieter, but it made the
   tick read as a background swatch rather than as a mark, and it disagreed
   with the same list on `/pricing` one click away.
     white tick on #6A5ACD    5.31:1   (both card grounds)
     disc vs the white card   5.31:1
     disc vs the navy card    3.62:1   (a non-text mark, floor 3)
   ⚠️ ONE RULE FOR ALL THREE, AND THAT IS THE POINT. The centre card used to
   need its own `.home-tier-card--center .home-tier-check` override because
   its ground was purple and a purple disc would have vanished into it. With
   the card navy, the same disc works on every tier — so the override is dead
   weight and is deleted below rather than left agreeing by coincidence. */
.home-page .home-tier-check {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
}
/* ⚠️⚠️ THE DESCENDANT SELECTOR IS THE WHOLE FIX, AND WITHOUT IT THE TICK IS
   BLACK ON THE TWO WHITE CARDS. The glyph is an inline `<svg>` drawn with
   `stroke="currentColor"`, so it needs `color` on ITSELF — and this page's
   light-theme ink sweep, `.home-page .home-tier-card *` (0,2,0), sets every
   descendant to #333333. Colouring only the disc leaves the sweep's value on
   the SVG inside it: measured `pathStroke: rgb(51,51,51)` on a #6A5ACD disc.
   The highlight card escaped by accident — a `--center …-check *` entry in the
   white re-assert list further up happens to name it — which is exactly why
   this read as "one card is wrong" rather than as a rule that was never
   written. Equal specificity to the sweep, so it wins on ORDER; keep it after.
   ⚠️ AND IT IS WHY THE AUDITS MISSED IT. A computed-style check on the disc
   reports the SPAN's `color` (white, correct) and a text-node contrast sweep
   never sees an SVG at all. Assert on `getComputedStyle(path).stroke`. */
.home-page .home-tier-check * {
  color: var(--rf-ink-invert, #FFFFFF);
  stroke: currentColor;
}
/* ⚠️ ONE SOLID BRAND PILL ON ALL THREE CARDS — `/pricing`'s
   `.tier-price-save-tag` verbatim, and for the reason its note records: this
   tag went through a glow-on-black lavender and a pale tint with brand-deep
   ink before landing on the solid pill, because a tag whose FILL is a wash has
   to be re-tuned for every card ground it lands on. A solid brand pill is the
   same object on white and on navy, so there is nothing left to re-tune.
   Home's version was `#c9b6ff` on a 16% wash (a dark-theme value) with a
   `--center` override on top; on the navy card the sweep painted its label
   #333333 and it measured **1.31:1**. white on #6A5ACD = 5.31:1. */
.home-page .home-tier-savetag,
.home-page .home-tier-savetag * {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
}
/* Feature rows drop to body weight/colour. The price and the CTA are the
   emphasis in a pricing card; the feature list is scannable support. */
.home-page .home-tier-card .home-tier-features,
.home-page .home-tier-card .home-tier-features li {
  color: var(--rf-ink-body, #333333);   /* 12.6:1 */
  font-weight: 500;
}
/* ---- 3. CTA hierarchy across the three tiers ---------------------------
   One filled button, two outlines — so the recommended tier is unambiguous.
   The highlight card already carries the filled white CTA on brand (§CB5). */
.home-page .home-tier-card:not(.home-tier-highlight) .home-tier-cta {
  background: transparent;
  border: 1.5px solid var(--rf-primary, #6A5ACD);   /* 5.31:1 — clears the 3:1 non-text floor */
  color: var(--rf-primary-deep, #453A8C);           /* 7.4:1 */
  font-weight: 600;
}
.home-page .home-tier-card:not(.home-tier-highlight) .home-tier-cta:hover {
  background: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);             /* 5.31:1 */
}
/* ---- 4. "See all plans" goes black on hover ----------------------------
   Scoped to this section deliberately: `.home-pill-btn--ghost` is also the
   nav's Login button, which should keep the brand hover. A secondary action
   under a pricing table reading as neutral-but-definite is the right note —
   it must not compete with the tier CTAs above it. */
.home-page .home-pricing-section .home-pill-btn--ghost.home-pill-btn--ghost:hover {
  background: var(--rf-ink, #000000);
  border-color: var(--rf-ink, #000000);
  color: var(--rf-ink-invert, #FFFFFF);             /* 21:1 */
}
/* ---- Pricing CTAs, revised (operator) -----------------------------------
   Previous pass made the two side CTAs outlines that FILLED with brand on
   hover, to mark the middle tier as the recommended one. The operator wants
   that brand fill to be their RESTING state instead.

   That is sound: the hierarchy is already carried by the CARD — the middle
   tier is a solid purple panel against two white ones — so spending the
   button treatment on the same message was redundant, and it left the two
   side CTAs reading as secondary/disabled when they are real actions.
   All three tiers now have an equally committed button; the recommended tier
   still wins on the surface behind it.

   ⚠️ A hover state is KEPT (a darker brand step). "Not on hover" means the
   colour should not be hover-ONLY — it does not mean the button should stop
   responding to the pointer. A button with no hover feedback reads as inert.
     white on #6A5ACD 5.31:1 · on #574AB0 (hover) 6.97:1                    */
.home-page .home-tier-card:not(.home-tier-highlight) .home-tier-cta {
  background: var(--rf-primary, #6A5ACD);
  border: 1.5px solid var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
  font-weight: 600;
}
.home-page .home-tier-card:not(.home-tier-highlight) .home-tier-cta:hover {
  background: var(--rf-primary-strong, #574AB0);
  border-color: var(--rf-primary-strong, #574AB0);
  color: var(--rf-ink-invert, #FFFFFF);
}
/* "See all plans" hovers to the middle card's brand rather than black, so the
   whole pricing block resolves to one accent instead of two. */
.home-page .home-pricing-section .home-pill-btn--ghost.home-pill-btn--ghost:hover {
  background: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);   /* 5.31:1 */
}
/* ⚠️ THE GHOST PILL'S FILL IS A `::before` CIRCLE, NOT ITS BACKGROUND.
   `.home-pill-btn--ghost::before` is a WHITE circle that scales 0 → 1 on
   hover (see ~L224) — the dark theme's "white wipes in behind a black label"
   effect. Setting `background` on the button therefore does nothing visible:
   the circle paints over it. Combined with a white label that produced
   white-on-white, i.e. the text vanished on hover.

   So the CIRCLE takes the brand colour and the label goes white. The wipe
   animation is preserved — only its colour changes — and the button resolves
   to the same purple as the middle pricing card.
     white on #6A5ACD = 5.31:1                                              */
.home-page .home-pill-btn--ghost::before { background: var(--rf-primary, #6A5ACD); }
.home-page .home-pill-btn--ghost.home-pill-btn--ghost:hover {
  background: transparent;                    /* the circle is the fill */
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
}
.home-page .home-pill-btn--ghost.home-pill-btn--ghost:hover * {
  color: var(--rf-ink-invert, #FFFFFF);
}
/* ============================================================================
   RUNNING ORDER CHANGED (operator): demo band → TESTIMONIALS → marquee → FAQ
   ----------------------------------------------------------------------------
   The rounded lip belongs to whichever block follows the dark demo band, so it
   moves from `.xmq` to `.home-testimonials`. `.xmq` keeps a radius but loses
   its negative margin: it no longer laps a DARK section, and a -44px overlap
   onto the testimonial cards above would clip them.
   ========================================================================= */
.home-page .home-testimonials {
  position: relative;
  z-index: 2;
  margin-top: -44px;
  border-radius: 44px 44px 0 0;
}
/* Marquee now sits between two light sections — flush, no overlap. */
.home-page .xmq {
  margin-top: 0;
  border-radius: 0;
}
/* The dark band that follows is now FAQ + final CTA only; it keeps its own
   rounded top, which still reads correctly against the marquee above it. */
/* ⚠️⚠️ THE BADGE IS THE BRAND — AND THIS IS THE FOURTH TREATMENT IT HAS HAD.
   The history is the useful part, because every earlier one was working
   around the same root cause: the card underneath was BRAND PURPLE.
     1. brand      — vanished into the purple card it labels
     2. night navy — legible, but a third colour on a two-colour block
     3. white + a brand border — legible on both grounds, but it read as a
        second white surface floating over a purple one
   With the card night navy (operator's call), the brand is free again and the
   badge can just be the brand — which is what `/pricing`'s `.tier-badge` has
   always been, so the two pricing blocks finally agree.
   ⚠️ IT STRADDLES TWO GROUNDS, which is what killed options 1 and 3: it
   overhangs the card's top edge, so half sits on the card and half on the
   page. The brand clears both.
     white label on #6A5ACD   5.31:1
     badge vs the navy card   3.62:1   (a non-text edge, floor 3)
     badge vs the #F5F5F5 page 4.87:1                                       */
.home-page .home-tier-badge,
.home-page .home-tier-badge * {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 8px 22px -4px rgba(16, 16, 20, 0.28);
}
/* ---- the demo band's ground --------------------------------------------
   It was a flat #111114 — 260vh of unmodulated near-black, which on a light
   site reads as a void rather than as a stage. It stays DARK (a light frame
   around dark video footage looks like a mistake, and the footage itself is a
   dark admin UI), but it now gets the same night-navy gradient as the closing
   CTA card: a lighter crown at the top falling to the palette's deepest
   value. That gives the section depth, ties it to the reference palette
   instead of generic black, and makes the light band above it read as
   deliberately lifting off a surface rather than stopping at a void. */
.home-page .demo-launcher {
  background:
    radial-gradient(120% 80% at 50% 0%, var(--rf-night-2, #1B2233), transparent 62%),
    var(--rf-night, #0B0E1A);
}
/* ============================================================================
   DEMO BAND GOES LIGHT (operator: "our dashboard is black and you've made the
   background black as well")
   ----------------------------------------------------------------------------
   ⚠️ I HAD THIS BACKWARDS. Two passes ago I kept this band dark on the
   reasoning that "a light frame around dark video footage reads as a mistake."
   That is true of EDGE-TO-EDGE video. It is wrong here, because the whole
   point of this section is that the panel ZOOMS — for most of the scroll it is
   an object with edges sitting on a ground, and the footage is a screen
   recording of the dark AdminPanel. Dark object on dark ground = no edge, so
   the product shot dissolved into the page exactly where it should have been
   most legible.

   A light ground is also the standard treatment for a dark app screenshot, and
   it costs nothing at the end of the zoom: once the panel fills the viewport
   the ground is not visible at all.
   ========================================================================= */
.home-page .demo-launcher {
  background:
    radial-gradient(120% 80% at 50% 0%, var(--rf-primary-tint, rgba(106, 90, 205, 0.08)), transparent 62%),
    var(--rf-bg, #F5F5F5);
}
/* Copy follows the ground back to ink. */
.home-page .demo-launcher .overlay-text { color: var(--rf-ink, #000000); }
/* 18.1:1 */
.home-page .demo-launcher .overlay-eyebrow { color: var(--rf-primary, #6A5ACD); }
/*  4.9:1 */
/* The launch button rejoins the site's primary treatment — it was inverted to
   white only because it sat on the dark band. */
.home-page .demo-launcher .demo-button {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);   /* 5.31:1 */
}
.home-page .demo-launcher .demo-button:hover {
  background: var(--rf-primary-strong, #574AB0);
}
/* The panel now needs a real edge: the footage is near-black, so it separates
   from the light ground by luminance alone, but a hairline + shadow makes it
   read as a SCREEN rather than a hole punched in the page. */
.home-page .demo-launcher .video-player {
  border: 1px solid var(--rf-border-strong, #D4D4D8);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}
/* ============================================================================
   SESSION 28 — PER-SECTION PALETTE, TO THE OPERATOR'S SPEC
   ----------------------------------------------------------------------------
   A section-by-section colour brief: FAQ white with a light-grey expanded row,
   Let's Connect light grey, the closing panel deep purple, footer dark.
   Only the FAQ lives in this file; the other two are in LetsConnect.css and
   SiteFooter.css beside their own components.

   ⚠️ THIS REVERSES §CB11's NIGHT-NAVY ANSWER PANEL, deliberately and on
   instruction. Keep CB11's actual finding in view, because it still applies:
   a contrast sweep scores TEXT against its ground and says NOTHING about
   whether two adjacent SURFACES are distinguishable. The spec's white section
   + light-grey panel is 1.05:1 of fill difference, so the separation has to be
   carried by the BORDER and the ELEVATION instead of by the fill — which is
   what every rule below is doing. Do not simplify those away.
   ========================================================================= */
/* Ground goes white per the spec. Scoped to `.home-faq`, NOT to
   `.home-dark-band` — that band also holds the final-CTA section, which keeps
   the page grey so its navy card still lifts off something (§CB8). */
.home-page .home-faq { background: var(--rf-surface, #FFFFFF); }
/* Resting rows are white ON white, so the hairline is the only edge there is:
   it has to be the strong one. --rf-border (#E4E4E7) measures 1.13:1 here and
   disappears. */
.home-page .faq-q-item {
  background: var(--rf-surface, #FFFFFF);
  border: 1px solid var(--rf-border-strong, #D4D4D8);   /* 1.48:1 */
}
/* Expanded row: light grey, purple border, purple icon — the spec's three
   signals for the open state.
   ⚠️ THE FILL CANNOT CARRY THIS ON ITS OWN. #F5F5F5 on #FFFFFF is 1.05:1, so
   the state is really told by the 5.31:1 purple border and the 4px purple
   inset bar. That bar is in the SHIPPED rule's box-shadow
   (`inset 4px 0 0 0 var(--faq-accent)`, L~3200) and §CB11's override replaced
   the whole shorthand, silently dropping it — restored here. */
.home-page .faq-q-item.is-active {
  background: var(--rf-bg, #F5F5F5);
  border-color: var(--rf-primary, #6A5ACD);
  box-shadow:
    inset 4px 0 0 0 var(--rf-primary, #6A5ACD),
    var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
}
/* The answer panel is the expanded accordion's body, so it takes the same
   light grey. It keeps the shadow AND gains the strong hairline, because on a
   white ground a #F5F5F5 panel has no edge of its own. Its 4px accent bar
   (`.faq-answer-col::before`) is already the brand→indigo pair. */
.home-page .faq-answer-col {
  background: var(--rf-bg, #F5F5F5);
  border: 1px solid var(--rf-border-strong, #D4D4D8);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}
/* Everything drawn on it follows the ground. Ratios against #F5F5F5. */
.home-page .faq-answer-q { color: var(--rf-ink, #000000); }
/* 18.1:1 */
.home-page .faq-answer-a { color: var(--rf-ink-body, #333333); }
/* 11.6:1 */
/* ⚠️ --rf-sky was chosen for the navy panel and measures 2.45:1 on #F5F5F5 —
   an 11px/700 label, so its floor is 4.5. The brand clears it and is also what
   the spec asks the accent to be. */
.home-page .faq-answer-eyebrow,
.home-page .faq-answer-eyebrow::before,
.home-page .faq-answer-eyebrow > span:first-child {
  color: var(--rf-primary, #6A5ACD);                               /*  4.87:1 */
}
/* Ghost numeral inverts back to an ink wash — it is on a light panel again. */
.home-page .faq-bignum { color: rgba(11, 14, 26, 0.05); }
/* ⚠️ THE INACTIVE PROGRESS DOTS ARE A CONTROL, NOT DECORATION — 3:1, not 0.
   --rf-border-strong (#D4D4D8) measures 1.02:1 on this panel, i.e. an
   invisible tab in a tablist. --rf-ink-faint is the lightest step that clears
   it. The active dot is also 44px wide against 28px, so the state never rests
   on colour alone. */
.home-page .faq-dot { background: var(--rf-ink-faint, #6B6B75); }
/*  4.83:1 */
.home-page .faq-dot.is-active {
  background: linear-gradient(90deg, var(--rf-primary, #6A5ACD), var(--rf-indigo, #5A5FE8));
}
/* ============================================================================
   THE PRICING PREVIEW FITS ONE FRAME (operator: "our pricing section of home
   page isn't in frame … this section includes the above pricing label till see
   all plan button … it should have to be responsive as well")
   ----------------------------------------------------------------------------
   Measured before: **995px against a 635px viewport — 360px over** — and it
   overflowed at every desktop height tested (720, 800, even 1440x900 by
   116px). None of that was one bad value; it was ~330px of spacing sized in
   `vw` and `px` by a section whose problem is HEIGHT.

   ⚠️⚠️ EVERY VERTICAL SIZE HERE IS CAPPED IN `vh` AS WELL AS `vw`. A width-only
   scale cannot see a short window: at 1280x635 and 1280x900 this section
   computed the SAME 995px. Same failure and the same fix as §CH2 (the home
   hero) and §DO5 (the blog hero) — third time, so it is the house pattern now.

   ⚠️ SCOPED TO `.home-pricing-section`, WITHOUT EXCEPTION. `.home-section-title`,
   `.home-eyebrow-text` and `.home-pill-btn--ghost` are all SHARED with other
   sections and other pages; an unscoped clamp here would silently reflow the
   whole site. Every selector below carries the section.

   ⚠️ ONE FRAME IS A ≥861px PROMISE ONLY, DELIBERATELY. Below that the three
   cards stack, and three stacked cards plus a two-line display heading cannot
   fit a phone without shrinking the cards to uselessness — 1552px at 390x844.
   Those viewports scroll, by design, exactly as /features' hero does at the
   same breakpoint. Do not "finish the job" by adding a mobile branch.
   ========================================================================= */
@media (min-width: 861px) {
  .home-page .home-pricing-section {
    /* One rhythm for the section, so the gaps compress together instead of
       drifting apart one clamp at a time. */
    /* ⚠️ THE FLOORS ARE LOW ON PURPOSE AND COST NOTHING ABOVE ~620px OF
       VIEWPORT. Each `clamp` min only binds once the `vh` term drops under it,
       i.e. only on a genuinely short window — at 635 every one of these still
       resolves to its vh value, so lowering the floors changed no size anyone
       normally sees and bought ~36px on a 560px frame. Verified by measuring
       before and after at 635: identical. */
    /* ⚠️⚠️ THE CARD'S DERIVED HEIGHT IS CAPPED AGAINST THE FRAME, AND THIS
       REPLACED A `max-height: 760px` STEP THAT PUT A CLIFF IN THE MIDDLE OF
       THE RANGE. The step gave 1366x768 the tall-frame ratio with a wide card
       and overflowed by 20px — one pixel outside a breakpoint and the section
       stopped fitting. Height is what the frame rations, so the limit belongs
       on the height, expressed in the frame's own units: everything that is
       not the tallest card costs ~390px at these sizes, so this is what is
       left for it. The ratio still governs wherever the frame can pay for it
       (800 and up); below that the card widens instead of growing, which is
       the honest trade — a card can get BIGGER or MORE PORTRAIT on a short
       frame, not both.
       ⚠️ IT IS A `min-height` CAP, SO IT NEVER CLIPS: if the CONTENT is taller
       than this the card still grows. It only stops the derived floor from
       pushing the section past the fold. */
    /* ⚠️ THE 390px WAS STALE AND IT WAS THE BIGGEST SINGLE CONSTRAINT ON CARD
       SIZE (session 29). It encodes "everything that is not the tallest card",
       and it was measured when the card carried FOUR feature rows. The home
       preview shows three now (the live catalog's `HOME_FEATURES`), so the
       surrounding chrome costs less than the constant claimed and the budget
       was under-reporting the card's room by up to 95px.
       ⚠️ IT IS A SINGLE CONSTANT AGAINST A COST THAT IS ITSELF `vh`-BASED, so
       it can only be right at one height. Measured non-card cost: 295 @595,
       360 @720, 382 @800 — near-linear, so the constant is now the same shape
       as the thing it estimates instead of a flat guess pinned to the tall end.
       Verified against all three: the estimate lands within ~10px of measured
       and the section still fits at every size in the table below. */
    /* ⚠️ 44.4svh, NOT 42svh, SINCE SESSION 71: the chrome this constant
       estimates GREW by 2.4svh — +0.5svh on --hp-s (title→toggle step) and
       +1.9svh on the "See all plans" margin (see .home-pricing-link). With
       the old constant the budget still handed the card the old chrome's
       room and the section overflowed a 1280x720 frame by 3px (measured,
       both billings — the budget term is the one that binds at 720). At 633
       and below the card is content-bound, so this changes nothing there. */
    --hp-hcap: calc(100svh - (44.4svh + 72px));

    --hp-x: clamp(5px, 1.1vh, 14px);
    /* ⚠️ --hp-s WAS 1.7vh AND THE TITLE→TOGGLE STEP MEASURED 10.8px AT 633 —
       tighter than the toggle→cards step below it (17.9px), which inverted
       the grouping: the toggle read as glued to the heading it does not
       belong to. 2.2vh puts it at ~14px there and grows with the frame; the
       section had 40px of unspent frame at 633, so this comes out of slack,
       not out of the cards (measured, session 71). */
    --hp-s: clamp(10px, 2.6vh, 30px);
    /* ⚠️ --hp-m WENT 2.4vh → 3.4vh IN SESSION 71 PART 3 (operator: "add space
       between cards and heading and the … monthly/yearly button"). It is the
       toggle→cards step; the ~6px it costs at 633 is paid back to the pixel
       by the padding-bottom trim below, so the section's height barely moves
       (measured: net +4 at 633, both billings still fit every frame). */
    --hp-m: clamp(12px, 3.4vh, 44px);

    /* ⚠️ THE TOP PADDING HAS A 48px FLOOR BECAUSE OF THE LIP. This section
       pulls itself up 44px over the one above and rounds its own top corners;
       content inside that 44px sits in the curve. The floor is the lip plus a
       little, not a taste value. */
    /* ⚠️ THE 48px FLOOR WAS WRONG AND IT WAS COSTING THE CARDS 16px. It was
       set to "clear the lip", but the lip at the TOP is this section's OWN
       rounded corner — the section paints its own opaque ground right up to
       y=0, and the radius only eats the extreme left and right. Centred
       content does not have to clear it. (The BOTTOM padding is different and
       genuinely does: there the band below laps over us — see §DT9.) */
    padding-top: clamp(32px, min(6vw, 7vh), 88px);
    /* ⚠️⚠️ THE 44px IS THE LIP OF THE BAND BELOW, NOT SPACING — AND LEAVING IT
       OUT IS WHAT CLIPPED "See all plans". `.home-page .demo-launcher` sets
       `margin-top: -44px` + a 44px radius, so it climbs over the last 44px of
       this section; its sticky stage then paints on top. Measured: the button's
       bottom sat **14px INSIDE that lap** at 1280x635 (and 1px at 1440x900, so
       it was one nudge from breaking there too). It reads as "cut on hover"
       because the ghost only fills purple on hover — at rest the covered strip
       blends into the light band and nobody sees it.
       Same lip-clearance rule the four heroes carry (§DN); this section is the
       one that did not, because its padding was written as spacing. */
    /* ⚠️ THE FLEXIBLE PART SHRANK 2.4vh → 1.6vh IN SESSION 71 PART 3
       (operator: "move see all plans little down — there is unused space
       below that button"). Only the spacing HALF moved; the 44px lip term is
       the clearance and is untouchable — at 1.6vh the button still floats
       ~10px above the band's edge at 633 (and the 10px floor holds that on
       the shortest frames). The ~6px freed here is what funds --hp-m above. */
    padding-bottom: calc(44px + clamp(10px, 1.6vh, 56px));
  }

  .home-page .home-pricing-section .home-eyebrow-text { margin-bottom: var(--hp-x); }

  /* ⚠️ THE HEADING'S HARD `<br>` IS DROPPED ON A SHORT FRAME, AND IT IS THE
     ONLY WAY THE CARDS GET TO GROW. "Plans that grow / with your fleet" is a
     deliberate two-line stack, but two lines of display type cost ~32px — and
     on a 635px frame that 32px is the difference between a 300px card and a
     330px one. A single line reads perfectly well at this size (~434px of the
     1100px column), and the two-line stack returns the moment there is room.
     ⚠️ 700px, NOT A WIDTH. Nothing about the viewport's WIDTH makes the second
     line unaffordable; at 1280x800 the section already fits with the stack
     intact, so keying this to a width would flatten the heading on screens
     that never needed it. */
  @media (max-height: 700px) {
    .home-page .home-pricing-section .home-section-title br { display: none; }

    /* ⚠️ A LITTLE OFF THE SHARED GAP, WHICH IS WORTH ~4px BECAUSE IT IS SPENT
       FOUR TIMES — the eyebrow's margin, the stage's top/bottom padding and the
       "See all plans" margin all read `--hp-x`. Trimming it here rather than at
       the token's own definition keeps every frame above 700px exactly as it
       was. The floor only drops from 5px to 4px and does not bind until ~455px
       of frame, so the rotated card corners still clear the heading. */
    .home-page .home-pricing-section { --hp-x: clamp(4px, 0.9vh, 14px); }

    /* ⚠️⚠️ THE YEARLY VIEW OVERFLOWED THE FOLD BY 21px AND MONTHLY DID NOT —
       which is why it survived every measurement taken in monthly. Switching to
       Yearly adds `.home-tier-was` (the struck annual price + the 30% tag) to
       the TALLEST object in the section: 30px of row plus 5px of margin, on a
       frame that only had 14px spare. Measured at 1280x595: monthly 581 (fits),
       yearly 616 (21 over). Pre-dates session 29 — verified by re-creating the
       old geometry and measuring both: 616 either way.
       ⚠️ COMPRESSING THIS ROW IS FREE IN MONTHLY, because the row does not
       exist there — which is exactly why the fix belongs here rather than in
       the section's shared spacing, where it would shrink a view that already
       fits. The type stays legible: the struck price is a secondary figure
       whose job is to be crossed out, and the tag is a badge. */
    .home-page .home-pricing-section .home-tier-was {
      gap: 6px;
      margin-bottom: 0;
    }
    /* ⚠️ THE LAST 8px, TAKEN FROM INSIDE THE CARD RATHER THAN FROM ITS PADDING.
       Compressing the was-row alone left yearly 2px over at 1280x595. The
       obvious next lever is the card's vertical padding — and it is the wrong
       one: it is already only 17.9px against 17.3px horizontal, and taking it
       below the horizontal is precisely the inversion an earlier pass measured
       as "crushed" (0.68 ratio) and fixed. Feature spacing is the honest place;
       these two only bind under 700px of frame. */
    .home-page .home-pricing-section .home-tier-features {
      gap: clamp(5px, 0.9vh, 14px);
      margin-bottom: clamp(6px, 1.2vh, 24px);
    }
    .home-page .home-pricing-section .home-tier-strike {
      font-size: 12px;
      text-decoration-thickness: 1.5px;
      /* Its own line-height: the inherited one is what makes this row 30px tall
         rather than the 14px its glyphs need. */
      line-height: 1.2;
    }
    .home-page .home-pricing-section .home-tier-savetag {
      font-size: 9px;
      padding: 1px 6px;
      line-height: 1.3;
    }
  }

  .home-page .home-pricing-section .home-section-title {
    font-size: clamp(24px, min(4vw, 4.4vh), 44px);
    margin-bottom: var(--hp-s);
  }

  /* The stage carried 156px of pure spacing (60 + 36 margins, 30 + 30 padding)
     — the single biggest line item in the overflow.
     ⚠️ THE VERTICAL PADDING CANNOT GO TO ZERO. The side cards are rotated 7deg,
     so their corners reach ~20px past their own boxes; with no padding they
     collide with the heading above and the button below. `--hp-x` keeps a
     floor of 6px and grows with the frame. */
  .home-page .home-pricing-section .home-pricing-stage {
    margin: var(--hp-m) auto var(--hp-x);
    padding: var(--hp-x) 0;
  }

  /* ⚠️⚠️ THE WIDTH IS CAPPED BY `vh` TOO, AND THAT IS THE POINT — "width should
     have to be according to height" (operator). Capping only the HEIGHT to fit
     the frame, as the first pass did, leaves the width where it was and the
     card goes square: measured **1.03 w/h on the centre card at 1280x635, and
     1.00-1.06 at every size tested**, i.e. a landscape pricing card. A pricing
     card is a portrait object; the price and the feature list want a column,
     not a banner.
     ⚠️ THESE CAPS DO NOTHING ON A TALL SCREEN. `min(20.5vw, 33vh)` only binds
     when the viewport is short — at 1440x900 and 1920x1080 the vw term still
     wins and the widths are unchanged at 295 and 326. It is exactly the same
     shape as the height work above: the frame drives every dimension, so the
     card keeps its proportion instead of only its height.
     ⚠️ THE CENTRE STAYS ~1.15x THE SIDES. It is the recommended tier and the
     size difference is the hierarchy; matching them would flatten it. */
  .home-page .home-pricing-section .home-tier-card {
    /* ⚠️⚠️ THE HEIGHT IS DERIVED FROM THE WIDTH, WHICH IS WHAT FINALLY MAKES
       THE PROPORTION HOLD. Capping the width by `vh` fixes a SHORT frame, but
       on a tall one the width cap stops binding while the card's height stays
       content-bound (four features and a button) — so the centre card went
       square again from the other direction: measured 0.93-0.97 at 720/800/900
       with the width caps alone.
       `min-height` is the safe half of the pair: it sets a FLOOR, so the card
       can still grow when its content needs more room and nothing is ever
       clipped. `aspect-ratio` was the obvious tool and is the wrong one here —
       with `height: auto` it makes the ratio-derived height the USED height and
       lets taller content overflow, which at 1280x635 would have cut the CTA
       off the bottom of the card by ~24px. */
/* ⚠️ FOUR BOUNDS, AND EACH ONE IS A DIFFERENT REAL LIMIT:
         22.5vw  three cards have to sit side by side
         41vh    a short frame cannot hold a tall card
         the max(): the card may not be wider than the HEIGHT BUDGET can
                    justify — otherwise the width cap stops binding on a
                    mid-height frame and the card goes square from the other
                    direction (measured 0.96 at 1280x720 with this missing)
         320px   it stops growing eventually
       The `max(255px, …)` floor is what stops a short frame collapsing the
       width: there the CONTENT sets the height (~304px), not the budget, so
       the budget's own estimate would under-size the card. */
    /* ⚠️ THE THREE CAPS WERE ALL RAISED TOGETHER (session 29, operator: "the
       size of home pricing cards very small"). Raising one alone does nothing —
       a different one binds at each frame, so the card only grows where the
       BINDING cap moves. Measured which one binds where before touching them:
       41vh @595, the budget term @720, 22.5vw @800. */
    --hp-w: clamp(215px, min(24.5vw, 47vh, max(255px, calc(var(--hp-hcap) * 0.82))), 340px);
    width: var(--hp-w);
    min-height: min(calc(var(--hp-w) / 0.78), var(--hp-hcap));

    /* ⚠️ VERTICAL PADDING MUST BE >= HORIZONTAL, AND IT WAS INVERTED. Measured
       16.5px top against 24.3px sides — a 0.68 ratio, which is what actually
       reads as "crushed" independently of the aspect ratio. Optical convention
       for a card is equal or slightly taller padding; both are capped by `vh`
       now so they compress together instead of the vertical one alone. */
    padding: clamp(14px, min(2.3vw, 3vh), 36px) clamp(14px, min(1.9vw, 2.9vh), 30px);
  }
  .home-page .home-pricing-section .home-tier-card--center,
  .home-page .home-pricing-section .home-tier-card.home-tier-highlight {
    /* The hero card sits a touch wider in proportion than the sides — it has
       one more feature row and it is meant to read as the larger object. */
    --hp-w: clamp(240px, min(27.5vw, 50vh, max(272px, calc(var(--hp-hcap) * 0.86))), 380px);
    width: var(--hp-w);
    min-height: min(calc(var(--hp-w) / 0.82), var(--hp-hcap));
  }
  /* ⚠️ THE LIST'S TRAILING MARGIN IS 24px OF DEAD AIR INSIDE EVERY CARD — it
     separates the features from the CTA, which the CTA's own weight already
     does. Measured: 24px x nothing, on the tallest object in the section. */
  .home-page .home-pricing-section .home-tier-features {
    gap: clamp(6px, 1.1vh, 14px);
    margin-bottom: clamp(8px, 1.9vh, 24px);
  }
  .home-page .home-pricing-section .home-tier-cta { padding-block: clamp(9px, 1.4vh, 14px); }

  /* ⚠️⚠️ "See all plans" CARRIED 53px OF MARGIN FOR A 43px BUTTON — 28 above
     and 25 below, the single largest remaining line item once the stage was
     fixed. The bottom margin is pure duplication: the SECTION's own
     padding-bottom already ends the block, so the two stacked and neither was
     wrong on its own. Same "three gaps that stack" pattern /pricing's hero
     recorded. */
  /* ⚠️ BUT `var(--hp-x)` HERE OVER-CORRECTED TO AN 8.4px VISUAL GAP (measured
     at 1280x633, session 71 — operator: "see all plans button is very close
     to cards"). Two things eat the token that the token cannot see: the
     stage's margin-bottom and this margin-top are ADJACENT SIBLING MARGINS,
     so they COLLAPSE to one --hp-x rather than stacking to two; and the
     centre card is scaled past its box (1.02/1.08) while the side cards'
     rotated corners reach ~20px past theirs, so the cards' painted edge sits
     BELOW the stage box the margins measure from. This clamp is the gap
     stated as what it must actually clear — its own vh term, its floor high
     enough that the worst overhang still reads as a gap. Paid from the 40px
     of frame the section left unspent at 633, NOT from the cards.
     (It also no longer reads --hp-x, so the max-height:700 trim of that
     token now spends three times, not four — its comment says four.) */
  .home-page .home-pricing-section .home-pricing-link {
    margin-top: clamp(14px, 3vh, 32px);
    margin-bottom: 0;
  }

  /* ⚠️⚠️ THE SESSION-71 BREATHING ROOM IS PAID BACK ON A GENUINELY SHORT
     FRAME. The wider title→toggle step (--hp-s 2.2vh) and the real
     cards→button gap above cost ~15px, funded by frame slack — and at ≤620px
     of frame there IS no slack: measured at 1280x595, MONTHLY still fit but
     YEARLY overflowed the fold by 11px (the was-row rides the tallest object;
     same monthly-masks-yearly trap the max-height:700 block records). This
     branch returns the three line items to their measured-to-fit economy
     values there: --hp-s to its old curve, the button gap to a floor that
     still reads as a gap (~12px visual vs the 8.4px complaint), and the top
     padding to 6vh — the one place the short frame still had fat (41.65px at
     595, and the padding-top note above already established centred content
     owes the top corner nothing). Ledger at 595: yearly 606 − 17.3 ≈ 589
     ≤ 595, ~6px slack.
     ⚠️ THIS BRANCH MUST STAY AFTER THE `.home-pricing-link` RULE ABOVE — the
     media query adds no specificity, so it wins on ORDER alone; moved above
     it, its margin-top is silently dead. */
  @media (max-height: 620px) {
    .home-page .home-pricing-section {
      --hp-s: clamp(8px, 1.7vh, 22px);
      padding-top: clamp(32px, min(6vw, 6vh), 88px);
    }
    .home-page .home-pricing-section .home-pricing-link {
      margin-top: clamp(8px, 1.6vh, 14px);
    }
  }

  /* ⚠️⚠️ THE DECK'S INWARD PULL IS A PERCENTAGE NOW, NOT A FLAT 40px, AND THAT
     IS A DIRECT CONSEQUENCE OF NARROWING THE CARDS. The side cards are pulled
     toward the centre so the hero card laps their inner edges — the whole look
     of the deck. At the original 262/301px widths a flat 40px lapped mostly
     padding; at 205-250px the same 40px reaches the TEXT. Measured: the centre
     card covered **24-28px of the side cards' feature labels and CTA at every
     size**, cutting "Reporting & analy…" mid-word.
     ⚠️ A PERCENTAGE RESOLVES AGAINST THE ELEMENT'S OWN BORDER BOX, so the lap
     now scales with the card instead of being a constant that only happened to
     work at one width. 4% leaves the text clear by ~5px everywhere while still
     lapping ~13px of padding — the deck still reads as a deck.
     ⚠️ AND A `scrollWidth > clientWidth` CHECK CANNOT SEE THIS. Nothing
     overflows its own box; one card is painted over another. Occlusion has to
     be measured BETWEEN elements — the side card's rightmost text edge against
     the centre card's left edge. */
  .home-page .home-pricing-section .home-tier-card--left {
    transform: translateX(2.5%) rotate(-7deg) scale(0.9);
  }
  .home-page .home-pricing-section .home-tier-card--right {
    transform: translateX(-2.5%) rotate(7deg) scale(0.9);
  }

  /* ⚠️ THE CENTRE CARD'S LIFT SHRINKS WITH THE FRAME. It is `scale(1.08)`,
     which is 8% of the tallest object in the section — ~28px at desktop, and
     the last thing standing between the section and the fold on a short
     window. It keeps its full lift wherever there is room. */
  @media (max-height: 780px) {
    .home-page .home-pricing-section .home-tier-card--center,
    .home-page .home-pricing-section .home-tier-card.home-tier-highlight { transform: scale(1.02); }
  }
}


/* ============================================================================
   RntlOS Demo — DARK video hero, WHITE page, black footer
   ----------------------------------------------------------------------------
   Top to bottom — THREE grounds, two lips, the same shape /pricing has:

     DARK   the pill nav + the sticky video hero    (`.demo-topbar`, `.demo-hero`)
     WHITE  everything else — the form sheet, the FAQ and "get in touch"
     BLACK  the shared footer + the RENTFYV wordmark      (`.home-closing`)

   ⚠️ THE HERO CANNOT GO LIGHT AND THAT IS NOT AN OVERSIGHT. It is a full-bleed
   night-time video behind a scrim; there is no white version of it, and white
   copy over the scrim is the only readable option. So the light chapter starts
   at the form sheet, whose rounded top edge IS the dark→white lip.

   ⚠️⚠️ THE WHITE FORM SHEET WAS BUILT, REVERTED BY THE OPERATOR ON SIGHT, AND
   THEN ASKED FOR AGAIN ONCE /pricing WENT WHITE. Both calls were reasonable
   and the history matters: the first white sheet was rejected because the page
   was the ONLY light surface in the set and read as a different site. It is
   right now because /pricing carries the same purple→white→black shape, so
   /demo matches rather than diverges. **If the rest of the site ever goes back
   to dark, this is the page that should follow it — the reason it is white is
   consistency, not the contrast argument on its own.**

   ⚠️ "GET IN TOUCH" SITS BELOW THE FAQ, and the order is deliberate: answering
   objections before offering the human fallback means the fallback only
   catches people the FAQ did not convert, instead of pulling them out of the
   funnel on the way to it.

   ⚠️ ONE GROUND, ONE CHAPTER — so the form sheet carries the ONLY lip in the
   light chapter. The seams inside it (sheet→FAQ→touch) get nothing: a curve
   revealing white over white costs an overlap and buys nothing.

   ⚠️ THE HERO CHAPTER IS STILL LITERALS, as originally written, and the
   `--dm-*` tokens serve the white page. If you add a rule, work out which
   chapter it belongs to FIRST; a dark literal dropped into the light chapter
   is invisible and vice versa.
   ========================================================================= */

.demo-page {
  /* ---- the WHITE page. Ratios vs #FFFFFF, straight from theme.css -------- */
  --dm-bg: #FFFFFF;
  --dm-surface: #FAFAFA;
  --dm-line: #E4E4E7;
  --dm-line-strong: #D4D4D8;
  /* ⚠️ CONTROLS GET A HEAVIER RIM THAN CARDS. A boundary that identifies a UI
     component owes 3:1 (SC 1.4.11), and a white input on a #FAFAFA card has
     only 1.03:1 of fill to fall back on — the rim is the whole signal.
     `--dm-line-strong` is 1.42:1 there; this measures 3.31:1. */
  --dm-line-ctl: #86868F;
  --dm-ink: #000000;            /* 21:1   — headings */
  --dm-ink-body: #333333;       /* 12.6:1 — body copy */
  --dm-ink-soft: #4A4A52;       /* 8.6:1  — secondary copy */
  --dm-ink-faint: #6B6B75;      /* 5.1:1  — the floor, do not lighten */
  --dm-accent: #6A5ACD;         /* 5.31:1 — fills and large type */
  --dm-accent-deep: #453A8C;    /* 7.4:1  — brand as small text on light */
  --dm-accent-soft: #EEECFA;
  /* ⚠️ #3ecf8e IS 1.9:1 ON WHITE AND #fbbf24 IS 1.8:1 — both were picked to
     glow on near-black. These are theme.css's light-ground replacements. */
  --dm-ok: #15803D;             /* 5.02:1 */
  --dm-star: #B45309;           /* 5.02:1 */

  /* ---- the DARK hero chapter -------------------------------------------- */
  --dm-night: #0A0A0B;

  /* ⚠️ THE PAGE USED THREE PURPLES AND TWO EXIST NOWHERE ELSE ON THE SITE:
     `#6c5cff` (selected pill, tiles, focus ring) and `#9b8cff` (step
     numerals). Both are folded onto the brand below. `--dm-brand-line` is the
     ON-DARK step, kept because the hero chapter still needs it — the brand is
     only 3.62:1 on near-black, so a LINE or small TEXT there needs the
     lighter value even though fills do not. */
  --dm-brand: #6A5ACD;              /* = --rf-primary */
  --dm-brand-line: #8B7BE0;         /* = --rf-primary-on-dark */
  --dm-brand-wash: rgba(139, 123, 224, 0.16);

  /* The dark chapter's vertical spacing step — see `.demo-touch`. */
  --dm-section-y: clamp(28px, 5vw, 56px);

  /* The lip the dark -> white and white -> black seams both wear. Same clamp
     `--ft-lip` / `--bl-lip` / `--pr-lip` use, so every seam on the site
     curves identically. */
  --dm-lip: clamp(28px, 3.4vw, 44px);

  background: var(--dm-bg);
  font-family: var(--rf-font-body, Georgia, serif);
  min-height: 100vh;
  color: var(--dm-ink-body);
}

/* ⚠️ VISUALLY HIDDEN, NOT `display: none` — text that must reach a screen
   reader while taking no space. There is no site-wide `.sr-only` utility in
   this project (checked), so it is defined here and scoped to this page;
   without it the counter's "characters used" label would render as visible
   text. `clip-path` + 1px box is the technique that survives being inside a
   flex/grid parent, unlike `text-indent: -9999px`. */

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

/* ===== Hero ===== */

/* Sticky-reveal stack: the hero video pins to the top while the form scrolls
   up and stacks over it. The wrapper bounds how long the hero stays pinned. */

.demo-hero-stack { position: relative; z-index: 0; }

/* ⚠️⚠️ HOW FAR THE FORM SHEET RIDES UP OVER THE HERO — ONE TOKEN, BECAUSE TWO
   INDEPENDENT VALUES CANNOT BE KEPT IN AGREEMENT AND WERE NOT.
   The sheet is OPAQUE (#0A0A0A) and is pulled up over the pinned hero. Its
   lift used to be `clamp(-120px, -12vh, -64px)` while the hero's bottom
   padding was `clamp(60px, 9vh, 120px)` — and **12vh is greater than 9vh at
   every viewport**, so the sheet always covered the last 3vh of hero content.
   Measured at 1425x900: sheet top y=513, `.demo-hero-proof-muted` ("Trusted by
   exotic & luxury fleets across 30+ US cities") at y=514-540 — the line was
   entirely under the sheet, surviving only as a sliver peeking through the
   28px corner radius, which is exactly what it looked like: text behind the
   container.
   The hero's bottom padding is now DERIVED from the lift plus a clearance, so
   the last line of hero copy always ends above the sheet's top edge no matter
   what either value becomes. Change the lift here, never in two places. */

.demo-page { --demo-sheet-lift: clamp(64px, 12vh, 120px); }

.demo-hero {
  position: sticky;
  top: 0;
  z-index: 0;
  min-height: clamp(440px, 56vh, 640px);
  /* ⚠️ THE TOP PADDING NOW HAS TO CLEAR THE FIXED BAR, AND IT SAYS SO. This
     page's nav used to be an in-flow `<header>` above the hero; it is the
     shared portalled `<LandingNav>` now, so the hero runs to y=0 with the bar
     floating over it. The old `clamp(96px, 14vh, 180px)` happened to be larger
     than the reserve and would have looked fine — which is exactly the kind of
     coincidence that breaks the day someone tightens it. `max()` keeps the
     designed value wherever it is already sufficient and guarantees the
     clearance where it is not. */
  padding:
    max(clamp(96px, 14vh, 180px), calc(var(--rf-nav-reserve, 76px) + 24px))
    clamp(20px, 6vw, 80px)
    calc(var(--demo-sheet-lift) + clamp(20px, 3vh, 40px));
  display: flex;
  align-items: center;
  overflow: hidden;
}

/* (The hero's `.demo-hero-video` + `.demo-hero-scrim` rules are DELETED with
   their markup — the ground is `rf-hero-ground` now, painted by theme.css.)

   ⚠️ EVERY INK BELOW IS FROM THE HERO GROUND'S MEASURED TABLE (theme.css /
   Features.css `--ft-on-purple*`): white 7.99:1, #E4DEFF 6.16:1, the 0.34
   white line 3.4:1, and #FBBF24 stars 3.98:1 (aria-hidden decoration, 3:1
   floor — measured for /features' identical proof row). The old greys
   (#b6b6bd, #8a8a92, #d4d4da) were tuned for the near-black video + scrim
   and sit far under the floors on the violet. Do not re-check these with a
   computed-style sweep — the ground is stacked gradients (theme.css's own
   warning); trust the hero's numbers. */

.demo-hero-content {
  position: relative;
  z-index: 2;
  max-width: 560px;
}

.demo-hero-badge {
  display: inline-block;
  background: rgba(255, 255, 255, 0.08);
  border: 0.5px solid rgba(255, 255, 255, 0.34);
  color: #E4DEFF;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 7px 14px;
  border-radius: 20px;
  margin-bottom: 22px;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.demo-hero-content h1 {
  color: #FFFFFF;
  font-size: clamp(40px, 6vw, 72px);
  font-weight: 700;
  line-height: 1.0;
  letter-spacing: -0.03em;
  margin: 0 0 18px;
}

.demo-hero-content p {
  color: #E4DEFF;
  font-size: clamp(15px, 1.5vw, 18px);
  line-height: 1.55;
  margin: 0;
  max-width: 470px;
}

.demo-hero-proof {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 22px;
  color: #E4DEFF;
  font-size: 14px;
  font-weight: 600;
}

.demo-hero-proof-item { display: inline-flex; align-items: center; gap: 8px; }

.demo-hero-stars { color: #FBBF24; letter-spacing: 2px; }

.demo-hero-proof-dot { width: 4px; height: 4px; border-radius: 50%; background: rgba(255, 255, 255, 0.5); }

.demo-hero-proof-muted { color: #E4DEFF; }

/* ===== Form — THE HEAD OF THE WHITE CHAPTER =====
   ⚠️⚠️ THIS SHEET IS THE PAGE'S ONE COLOUR SEAM, so its rounded top IS the
   dark→white lip. Nothing below it wears one: the FAQ and "get in touch" sit
   on the same white, and a curve revealing white over white costs an overlap
   and buys nothing.
   ⚠️ THE RADIUS READS `--dm-lip` NOW, not a flat 28px. Every other seam on the
   site curves on that clamp (43.52px at desktop, 28 at 390); leaving this one
   at a fixed 28 made the page's most visible seam the only one that curved
   differently from its own footer. */

.demo-form-section {
  /* ⚠️ The bottom no longer clears a lip — the FAQ below is the same ground. */
  padding: var(--dm-section-y) clamp(16px, 4vw, 56px) 0;
  /* Pulled up so the rounded sheet starts overlapping the pinned hero; as the
     page scrolls, the sheet rises and stacks fully over the video.
     ⚠️ READS THE SHARED TOKEN — the hero's bottom padding is derived from this
     same value, which is what stops the sheet eating the last line of hero
     copy. See the note on `--demo-sheet-lift` above. */
  margin-top: calc(-1 * var(--demo-sheet-lift));
  position: relative;
  z-index: 3;
  /* ⚠️ WHITE RISING OVER DARK FOOTAGE — and the separation is the point. The
     sheet used to be the same near-black as the page, so it had almost no
     edge, which is exactly why the hero copy being eaten underneath it (see
     `--demo-sheet-lift`) went unnoticed for so long. Now the sticky-stack
     motion reads as a distinct surface arriving over the video, which is what
     it was always trying to say. */
  background: var(--dm-bg);
  border-radius: var(--dm-lip) var(--dm-lip) 0 0;
  box-shadow: 0 -24px 48px -16px rgba(0, 0, 0, 0.45);
}

.demo-form {
  /* Scale up on large screens so the form fills the space instead of leaving a
     wide empty margin at 1440px+/1920; still capped for ultra-wide readability. */
  width: 100%;
  max-width: min(1480px, 100%);
  margin: 0 auto;
  /* ⚠️ #FAFAFA ON #FFFFFF IS 1.03:1 OF FILL, so the card's edge is carried by
     the border and the shadow, not by the tint. Deliberate, and the same
     treatment /pricing's cards and the FAQ's answer panel use — do not
     "simplify" the border away because the fill looks close enough. */
  background: var(--dm-surface);
  border: 1px solid var(--dm-line-strong);
  border-radius: 24px;
  padding: clamp(26px, 4vw, 48px);
  box-shadow: 0 12px 32px rgba(16, 16, 20, 0.08);
}

.demo-form * { box-sizing: border-box; }

.demo-form-grid {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(24px, 3.2vw, 40px);
  /* Equal-height columns: the left column stretches to the rail's height and
     anchors its CTA to the bottom, so neither column leaves dead space. */
  align-items: stretch;
}

.demo-form-col { display: flex; flex-direction: column; gap: 18px; }

.demo-rail { gap: 16px; }

/* CTA pushed to the bottom of the left column → bottom-aligns with the rail. */

.demo-submit-wrap { margin-top: auto; padding-top: 8px; }

.demo-field { display: flex; flex-direction: column; gap: 9px; }

.demo-field > label { color: var(--dm-ink); font-size: 14px; font-weight: 700; }

.demo-label-opt { color: var(--dm-ink-faint); font-weight: 600; }

/* Per-field validation message (the Signup `su-err` pattern; house rules via
   lib/validateName + lib/validateEmail + the country-aware PhoneInput). RED is
   deliberately spent on nothing else on this page — see the required-marker
   note above — which is exactly what lets an error read as one. 7.7:1 on the
   #FAFAFA card. */

.demo-field-err {
  margin: 0;
  color: #B00020;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.4;
}

/* ⚠️ THE REQUIRED MARKER IS THE ACCENT, NOT RED. Red is the usual convention
   but it reads as an ERROR state on a field nobody has touched yet, and this
   page spends red on nothing else. Brand-deep is 7.4:1 on the card and
   distinct enough from the near-black label to scan — which is the marker's
   only job: telling required from "(optional)" at a glance.
   ⚠️ It is `aria-hidden` in the markup because `aria-required` already carries
   the semantic; without that a screen reader says "Full name star". */

.demo-label-req {
  color: var(--dm-accent-deep);
  font-weight: 700;
  margin-left: 1px;
}

/* ⚠️ THE FIELD IS WHITE ON THE #FAFAFA CARD — i.e. the input is the LIGHTER
   surface. That is the light-theme inversion of what this file used to do (a
   #16161b well sunk into a darker card): on a light ground elevation reads as
   "closer to the light", so an inset control goes brighter, not darker.
   ⚠️ AND THE RIM IS `--dm-line-ctl`, NOT THE CARD HAIRLINE. A text input is a
   UI COMPONENT, so its boundary owes 3:1 (SC 1.4.11), and white-on-#FAFAFA is
   1.03:1 of fill — the rim is the entire signal that says "you can type
   here". The card hairline measures 1.42:1 there; this measures 3.31:1. */

.demo-input {
  display: flex;
  align-items: center;
  gap: 11px;
  background: var(--dm-bg);
  border: 1px solid var(--dm-line-ctl);
  border-radius: 12px;
  padding: 0 14px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* ⚠️ FOCUS IS A RING, NOT JUST A BORDER COLOUR. A 1px hue change is under the
   3:1 floor as the only signal that a field is active. */

.demo-input:focus-within {
  border-color: var(--dm-accent);
  box-shadow: 0 0 0 3px var(--dm-accent-soft);
}

.demo-input svg { width: 17px; height: 17px; color: var(--dm-ink-faint); flex: none; }

.demo-input input,
.demo-input textarea {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  outline: none;
  color: var(--dm-ink);
  font-family: inherit;
  font-size: 14px;
  padding: 14px 0;
}

/* ⚠️ PLACEHOLDERS ARE TEXT AND OWE 4.5:1. The old dark-ground value was the
   difference between "this field is empty" and "this field is broken";
   `--dm-ink-faint` is the lightest step on white that still clears AA. */

.demo-input input::placeholder,
.demo-input textarea::placeholder { color: var(--dm-ink-faint); }

.demo-input--area { align-items: flex-start; position: relative; }

.demo-input--area svg { margin-top: 15px; }

.demo-input--area textarea { resize: none; min-height: 104px; padding-right: 58px; }

.demo-counter { position: absolute; right: 14px; bottom: 12px; color: var(--dm-ink-faint); font-size: 12px; }

/* Fleet size */

.demo-fleet-label { display: block; color: var(--dm-ink); font-size: 14px; font-weight: 700; }

.demo-fleet-sub { color: var(--dm-ink-faint); font-size: 13px; margin: 6px 0 16px; }

.demo-fleet { display: flex; flex-direction: column; gap: 10px; }

.demo-pills { display: flex; flex-wrap: wrap; gap: 10px; }

/* ⚠️ `--dm-line-ctl` RIM, see `.demo-input` — these are controls, not cards,
   and the two must move together or the form ends up with two different ideas
   of what a control edge looks like. */

.demo-pill {
  background: var(--dm-bg);
  border: 1px solid var(--dm-line-ctl);
  color: var(--dm-ink-body);
  border-radius: 999px;
  padding: 11px 22px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.demo-pill:hover { border-color: var(--dm-accent); color: var(--dm-ink); background: var(--dm-accent-soft); }

/* ⚠️ THE SELECTED PILL CANNOT REST ON ITS FILL ALONE — `--dm-accent-soft` on
   white is 1.11:1, so which fleet size is chosen would be told by the rim
   only. The inset ring doubles the rim's weight without changing its colour,
   and the ink goes brand-deep, so the state reads three ways at a glance.
   Same construction /pricing's selected billing option uses. */

.demo-pill.is-selected {
  border-color: var(--dm-accent);
  color: var(--dm-accent-deep);
  background: var(--dm-accent-soft);
  box-shadow: inset 0 0 0 1px var(--dm-accent);
}

/* What to expect */

.demo-expect {
  background: var(--dm-bg);
  border: 1px solid var(--dm-line-strong);
  border-radius: 16px;
  padding: 22px 24px;
}

.demo-expect-head {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--dm-ink);
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 16px;
}

/* ⚠️ A FILL, so it takes the brand itself. On white the two-step ramp collapses
   to one — `--dm-brand-line` exists for LINES on near-black and has no job in
   the light chapter. White glyph on the brand = 5.31:1. */

.demo-expect-plus {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--dm-accent);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.demo-expect-plus svg { width: 15px; height: 15px; }

.demo-expect-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 13px; }

.demo-expect-list li { display: flex; align-items: flex-start; gap: 11px; color: var(--dm-ink-body); font-size: 13.5px; line-height: 1.45; }

.demo-expect-list li svg { width: 16px; height: 16px; color: var(--dm-ok); flex: none; margin-top: 1px; }

/* What happens next — numbered steps */

.demo-steps { display: flex; flex-direction: column; gap: 14px; }

.demo-step { display: flex; gap: 12px; align-items: flex-start; }

/* ⚠️ `#9b8cff` ON THE OLD DARK DISC MEASURES 2.6:1 ON WHITE. The numeral is
   13px text and owes 4.5; brand-deep on the brand tint gives 7.0:1. */

.demo-step-n {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--dm-accent-soft);
  border: 1px solid var(--dm-line-strong);
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 700;
  color: var(--dm-accent-deep);
}

.demo-step-t { font-size: 13px; font-weight: 600; color: var(--dm-ink-soft); line-height: 1.45; }

.demo-step-t b { display: block; color: var(--dm-ink); font-weight: 700; margin-bottom: 2px; }

/* Customer testimonial card */

.demo-quote {
  background: var(--dm-bg);
  border: 1px solid var(--dm-line-strong);
  border-radius: 16px;
  padding: 22px 24px;
}

/* ⚠️ #fbbf24 IS 1.8:1 ON WHITE. Landing.css defends its gold star rows on the
   grounds that a rating always has its number in text beside it — that
   argument does NOT apply here: this row is five glyphs and nothing else
   states the score, so the row IS the rating. */

.demo-quote-stars { color: var(--dm-star); letter-spacing: 2px; font-size: 14px; margin-bottom: 12px; }

.demo-quote-text { color: var(--dm-ink-body); font-size: 14.5px; line-height: 1.55; margin: 0 0 16px; }

.demo-quote-who { display: flex; align-items: center; gap: 11px; }

/* ⚠️ A GRADIENT IS INVISIBLE TO A GROUND-WALKING CONTRAST AUDIT (it has no
   `backgroundColor`), so this initial has never been scored against what it
   actually paints. `#8b6cff → #5b9dff` carries its white 13px/700 initials at
   3.05:1 / 2.71:1 against a 4.5 floor. Darkening the ramp to the brand pair
   holds 5.31 / 7.40 and keeps the same purple->blue direction. Identical
   finding and fix to `.blog-av` (§CR8) — a dark-theme bug, not a leftover
   from the light experiment. */

.demo-quote-av {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: linear-gradient(135deg, #6A5ACD, #453A8C);
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: 13px;
  color: #fff;
  flex: none;
}

.demo-quote-nm { font-size: 13.5px; font-weight: 700; color: var(--dm-ink); }

.demo-quote-rl { font-size: 12px; color: var(--dm-ink-faint); font-weight: 600; }

/* ⚠️⚠️ THE SUBMIT BUTTON WAS `#FAFAFA` ON `#0A0A0A` — A WHITE PILL, WHICH ON A
   WHITE CARD IS AN INVISIBLE BUTTON. It is the single most important control
   on the page and the one literal a light flip destroys most completely — the
   same trap /pricing's `.tier-cta` hit. It becomes the brand fill every other
   primary CTA on the site uses: white label 5.31:1, and the fill is 3.20:1
   against the card so the control has an edge of its own. */

.demo-submit {
  width: 100%;
  background: var(--dm-accent);
  color: #FFFFFF;
  border: none;
  border-radius: 14px;
  padding: 18px;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  transition: background 0.15s ease, transform 0.1s ease;
}

/* ⚠️ HOVER DARKENS, because this button is on a LIGHT card. Lightening a
   filled brand button drops its white label below AA — the shipped defect on
   the home/features CTA (issue 39). #574AB0 holds white at 6.97:1.
   (Contrast with /pricing's middle card, where the same button sits on NAVY
   and has to lighten instead — the direction follows the ground, not the
   button.) */

.demo-submit:hover { background: #574AB0; }

.demo-submit:active { transform: translateY(1px); }

.demo-submit:disabled { opacity: 0.65; cursor: not-allowed; }

.demo-submit svg { width: 18px; height: 18px; }

.demo-privacy {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: var(--dm-ink-faint);
  font-size: 13px;
  margin: 16px 0 0;
}

.demo-privacy svg { width: 14px; height: 14px; color: var(--dm-ink-faint); }

@media (max-width: 820px) {
  .demo-form-grid { grid-template-columns: 1fr; }
}

/* ===== Get in touch band — THE TAIL OF THE WHITE CHAPTER =====
   ⚠️ IT PAINTS ITS OWN WHITE. The page ground is #0A0A0A, so a transparent
   section here would show black between the FAQ and the footer — this band
   has to carry the chapter's colour itself.
   ⚠️ NO LIP AND NO TOP PADDING. It sits on the same ground as the FAQ directly
   above it (one ground, one chapter), so a curve here would reveal white over
   white and cost an overlap for nothing; and the FAQ's own 100px bottom
   padding already is the gap between them. Stacking a second one made the
   seam twice as wide as any other on the page.
   ⚠️ THE BOTTOM CLEARS THE FOOTER'S LIP, which pulls up over this band by
   `--dm-lip`. */

.demo-touch {
  background: var(--dm-bg);
  padding: 0 clamp(16px, 4vw, 56px) calc(var(--dm-lip) + var(--dm-section-y));
}

.demo-touch-inner {
  max-width: min(1480px, 100%);
  width: 100%;
  margin: 0 auto;
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: 0.85fr 1.45fr 1fr;
  align-items: center;
  gap: clamp(20px, 3vw, 44px);
  /* ⚠️ #FAFAFA ON #FFFFFF IS 1.03:1 OF FILL, so the edge is carried by the
     border and the shadow, not the tint. Same treatment the FAQ's answer
     panel uses two sections up, so the white chapter reads as one system. */
  background: var(--dm-surface);
  border: 1px solid var(--dm-line-strong);
  border-radius: 22px;
  padding: clamp(24px, 3.5vw, 40px);
  box-shadow: 0 18px 48px rgba(16, 16, 20, 0.08);
}

.demo-touch-eyebrow {
  color: var(--dm-ink-faint);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin: 0;
}

/* ⚠️ ONE SECTION SCALE, NOT TWO. This was `clamp(26px, 3vw, 36px)` while the
   FAQ's heading two sections below renders at 44px — two <h2>s at the same
   level of the document, eight pixels apart, which reads as an accident
   rather than a hierarchy. Both now use `.home-section-title`'s clamp, which
   is the scale every other page's section heading already uses. */

.demo-touch-left h2 {
  color: var(--dm-ink);
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 6px 0 0;
}

.demo-touch-card {
  display: flex;
  align-items: center;
  gap: 16px;
  background: var(--dm-bg);
  border: 1px solid var(--dm-line-strong);
  border-radius: 16px;
  padding: 20px 22px;
}

/* ⚠️ A FILL — see `.demo-expect-plus`. */

.demo-touch-icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--dm-brand);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.demo-touch-icon svg { width: 20px; height: 20px; }

.demo-touch-body { flex: 1; min-width: 0; }

.demo-touch-body h3 { color: var(--dm-ink); font-size: 16px; font-weight: 700; margin: 0 0 4px; }

.demo-touch-body p { color: var(--dm-ink-body); font-size: 13px; line-height: 1.45; margin: 0; }

/* ⚠️ WAS `#FAFAFA` — WHITE TEXT, NOW ON A WHITE CARD. It is the band's only
   real action, so it takes the brand-deep step (7.4:1 on white) rather than
   plain ink: it has to read as a link, not as a heading. */

.demo-touch-cta {
  background: none;
  border: none;
  color: var(--dm-accent-deep);
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
}

.demo-touch-cta svg { width: 15px; height: 15px; }

.demo-touch-media { position: relative; height: 132px; border-radius: 14px; overflow: hidden; }

.demo-touch-media video { width: 100%; height: 100%; object-fit: cover; opacity: 0.85; }

/* ⚠️ THE SCRIM FADES THE CLIP INTO THE CARD IT SITS ON, so it moves whenever
   that card's colour does — and it just did. Left as `#0d0d11` it would be a
   near-black smear down one edge of the video on a #FAFAFA card, which is the
   exact failure mode this comment existed to warn about.
   ⚠️ THE VIDEO ITSELF STAYS DARK, deliberately: it is footage, not a surface —
   the same call Features.css records for its product mockups. */

.demo-touch-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, var(--dm-surface) 0%, rgba(250, 250, 250, 0) 55%);
}

@media (max-width: 920px) {
  .demo-touch-inner { grid-template-columns: 1fr; text-align: center; }
  .demo-touch-card { flex-direction: column; text-align: center; }
  .demo-touch-media { display: none; }
}

/* ===========================================================================
   THE SHARED FAQ, PORTED TO THE LIGHT CHAPTER
   ---------------------------------------------------------------------------
   ⚠️⚠️ `LandingFaq` IS SHARED WITH THE HOME PAGE AND /pricing, AND IT CARRIES
   ITS OWN DARK TOKENS. `.home-faq` declares `--text: #fafafa` and friends on
   ITSELF (Landing.css) precisely so it survives being reused outside
   `.home-page` — so dropping it on a white band produces near-white ink on
   white unless every one of them is re-pointed. Everything here is scoped to
   `.demo-page` and cannot reach the other two consumers.

   ⚠️ THE VALUES ARE A DELIBERATE COPY of Landing.css's `.home-page .faq-*`
   light block (and of the same port on /pricing) — that treatment is already
   measured and shipped. Marked as a copy so the three cannot drift apart.
   This is now the THIRD consumer needing the same port; if a fourth appears,
   the right move is to give `LandingFaq` a `light` prop instead.

   ⚠️ THIS IS THE PAGE'S ONLY COLOUR CHANGE, so it wears the rounded lip every
   other seam on the site wears — it pulls itself UP over the dark band above
   by exactly its own radius, so the white arrives on a curve. `z-index` is
   required, not decoration: the negative margin makes the two overlap, and
   without a stacking order the dark band paints over the very curve the lip
   exists to reveal.
   ⚠️ AND THE PADDING HAS TO CLEAR THE LIP, expressed that way rather than as a
   flat number — the first `--dm-lip` of this box IS the curve, and
   `.home-faq`'s own top padding falls to 31px at 390 against a 28px lip, which
   would drop the heading into the corner radius.
   ========================================================================= */

/* ⚠️ THE LIP THAT USED TO BE HERE IS GONE. The FAQ was the page's one colour
   change while the form sheet was dark; now the sheet is white and owns that
   seam, so a curve here would reveal white over white — a 44px overlap for
   nothing. Its top padding drops back to a plain value for the same reason:
   there is no corner radius left to clear. */

.demo-page .home-faq {
  background: var(--dm-bg);
  padding-top: clamp(40px, 6vw, 80px);

  --text: var(--dm-ink);
  --text-dim: var(--dm-ink-body);
  --text-faint: var(--dm-ink-faint);
  --card-2: var(--dm-surface);
  --card-border: var(--dm-line);
  --card-border-hover: var(--dm-line-strong);

  /* The accent pair fills the active toggle and the "Talk to Us" button, both
     of which carry WHITE glyphs; white on #a06eff is 3.0:1. The brand pair
     holds >=4.96:1 across its ramp. */
  --faq-accent: var(--dm-accent);
  --faq-accent-2: #5A5FE8;
}

/* Resting rows are white ON white, so the hairline is the only edge there is. */

.demo-page .faq-q-item {
  background: var(--dm-bg);
  border: 1px solid var(--dm-line-strong);
}

.demo-page .faq-q-item:hover {
  background: var(--dm-accent-soft);
  border-color: var(--dm-accent);
}

/* ⚠️ THE 4px INSET BAR MUST SURVIVE THE OVERRIDE — `box-shadow` is a shorthand,
   so restating it drops the marker the open row depends on, and the fill alone
   cannot carry the state (#EEECFA on #FFFFFF is 1.11:1). */

.demo-page .faq-q-item.is-active {
  background: var(--dm-accent-soft);
  border-color: var(--dm-accent);
  box-shadow:
    inset 4px 0 0 0 var(--dm-accent),
    0 4px 16px rgba(16, 16, 20, 0.08);
}

.demo-page .faq-q-num { color: var(--dm-accent-deep); }

.demo-page .faq-q-text { color: var(--dm-ink-body); }

.demo-page .faq-q-item:hover .faq-q-text,
.demo-page .faq-q-item.is-active .faq-q-text { color: var(--dm-ink); }

.demo-page .faq-q-toggle {
  background: var(--dm-bg);
  border: 1px solid var(--dm-line-strong);
  color: var(--dm-ink-body);
}

.demo-page .faq-q-item.is-active .faq-q-toggle {
  background: var(--dm-accent);
  border-color: var(--dm-accent);
  color: #FFFFFF;
}

/* ⚠️ THE ANSWER PANEL'S GRADIENT ENDS IN A LITERAL `#101013`, so re-pointing
   `--card-2` alone still leaves it fading to near-black. Restated whole. */

.demo-page .faq-answer-col {
  background: #F5F5F5;
  border: 1px solid var(--dm-line-strong);
  box-shadow: 0 18px 48px rgba(16, 16, 20, 0.12);
}

.demo-page .faq-answer-q { color: var(--dm-ink); }

.demo-page .faq-answer-a { color: var(--dm-ink-body); }

.demo-page .faq-answer-eyebrow,
.demo-page .faq-answer-eyebrow::before,
.demo-page .faq-answer-eyebrow > span:first-child { color: var(--dm-accent); }

.demo-page .faq-help-text { color: var(--dm-ink-faint); }

/* Ghost numeral was `rgba(255,255,255,0.045)` — invisible on a light panel. */

.demo-page .faq-bignum { color: rgba(11, 14, 26, 0.05); }

/* ⚠️ INACTIVE PROGRESS DOTS ARE A CONTROL, NOT DECORATION. They follow
   `--card-border-hover` (#D4D4D8), which is 1.02:1 on the panel. */

.demo-page .faq-dot { background: var(--dm-ink-faint); }

.demo-page .faq-dot.is-active {
  background: linear-gradient(90deg, var(--dm-accent), #5A5FE8);
}

/* The FAQ title's accent span — `.hero-accent` is passed in from Demo.jsx. */

.demo-page .home-faq .hero-accent { color: var(--dm-accent); }

/* ===========================================================================
   THE FOOTER — IDENTICAL TO EVERY OTHER PAGE
   ---------------------------------------------------------------------------
   ⚠️⚠️ THE CLOSING ZONE IS **TWO** PAINTED SURFACES. `.home-closing` is
   transparent; what fills it is `.site-footer` (its own #0B0E1A navy) stacked
   on `.rentfyv-hero` (the giant RENTFYV wordmark). `.rentfyv-hero`'s BASE rule
   in Landing.css is a literal `background: #000`, and it is overridden per
   page — `.home-page` and `.blog-page` both set `var(--rf-surface-ink)`.
   `.demo-page` carries neither class, so without the rule below the bottom
   ~348px renders pure black under a navy footer. /pricing had the identical
   defect; this is the same fix.
   ⚠️ #111114 IS NOT THE FOOTER'S #0B0E1A, AND THAT IS CORRECT — the two are a
   deliberate step apart on every page that ships. */

.demo-page .rentfyv-hero { background: var(--rf-surface-ink, #111114); }

/* The white -> black seam gets the same rounded lip every other colour change
   on this site wears.
   ⚠️ `overflow: hidden` IS LOAD-BEARING: `.home-closing` is transparent, so the
   curve is not painted by it — the footer paints its own opaque navy and would
   go straight over the rounded corners without a clip. */

.demo-page .home-closing {
  border-radius: var(--dm-lip) var(--dm-lip) 0 0;
  margin-top: calc(-1 * var(--dm-lip));
  overflow: hidden;
  position: relative;
  z-index: 2;
}

/* ⚠️ TWO FOCUS GROUNDS ON THIS PAGE, AND GETTING THEM THE WRONG WAY ROUND IS
   INVISIBLE UNTIL SOMEONE TABS. The dark chapter — nav, hero, the form and
   every control in it, and the "get in touch" band — takes a WHITE ring, where
   brand purple would measure ~2.3:1. The white FAQ takes the BRAND ring, where
   white is invisible. The dark rule is listed first and the FAQ rule second so
   the more specific ground wins by order at equal specificity. */

/* ⚠️ ONLY THE HERO CHAPTER KEEPS THE WHITE RING NOW. `.demo-form-section` has
   moved to the second list along with everything else that went white — a
   white ring on a white sheet is not an indicator. This rule has now been
   re-pointed twice as bands changed; if a section changes ground again, check
   which list it belongs in, because the failure is invisible until someone
   presses Tab. */

.demo-page .demo-topbar :focus-visible,
.demo-page .demo-hero :focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 3px;
}

.demo-page .demo-form-section :focus-visible,
.demo-page .home-faq :focus-visible,
.demo-page .demo-touch :focus-visible {
  outline: 3px solid var(--dm-accent);
  outline-offset: 2px;
}

/* FAQ Section */

.demo-faq {
  padding: 60px 40px;
}

.demo-faq-container {
  max-width: 800px;
  margin: 0 auto;
}

.demo-faq-subtitle {
  color: #9CA3AF;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-align: center;
  margin: 0 0 8px;
}

.demo-faq h2 {
  color: #FAFAFA;
  font-size: 32px;
  font-weight: 600;
  text-align: center;
  margin: 0 0 40px;
  letter-spacing: -0.02em;
}

.demo-faq-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.demo-faq-item {
  background: #0F0F12;
  border: 0.5px solid #1F1F1F;
  border-radius: 14px;
  padding: 20px 24px;
  cursor: pointer;
  transition: border-color 0.2s;
}

.demo-faq-item:hover {
  border-color: #2A2A2A;
}

.demo-faq-question {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.demo-faq-question span:first-child {
  color: #FAFAFA;
  font-size: 14px;
  font-weight: 600;
}

.demo-faq-icon {
  color: #FAFAFA;
  font-size: 16px;
}

.demo-faq-item:not(.expanded) .demo-faq-icon {
  color: #9CA3AF;
}

.demo-faq-answer {
  color: #9CA3AF;
  font-size: 13px;
  font-weight: 400;
  line-height: 1.6;
  margin: 12px 0 0;
}

/* Footer */

.demo-footer {
  padding: 50px 40px 24px;
  border-top: 0.5px solid transparent;
  background: #0A0A0A;
}

.demo-footer-container {
  max-width: 1100px;
  margin: 0 auto;
}

.demo-footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 40px;
  margin-bottom: 40px;
}

.demo-footer-brand p {
  color: #9CA3AF;
  font-size: 13px;
  font-weight: 400;
  line-height: 1.5;
  margin: 0;
  max-width: 320px;
}

.demo-footer-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}

.demo-footer-logo-icon {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #FAFAFA;
  display: flex;
  align-items: center;
  justify-content: center;
}

.demo-footer-logo-icon span {
  color: #0A0A0A;
  font-weight: 600;
  font-size: 14px;
}

.demo-footer-logo span {
  color: #FAFAFA;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.demo-footer-links h4,
.demo-footer-contact h4 {
  color: #FAFAFA;
  font-size: 13px;
  font-weight: 600;
  margin: 0 0 14px;
}

.demo-footer-links div,
.demo-footer-contact div {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.demo-footer-links span,
.demo-footer-contact span {
  color: #9CA3AF;
  font-size: 12px;
  font-weight: 400;
  cursor: pointer;
}

.demo-footer-contact .address {
  line-height: 1.5;
}

.demo-footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 24px;
  border-top: 0.5px solid transparent;
}

.demo-footer-bottom > span {
  color: #6B7280;
  font-size: 11px;
  font-weight: 400;
}

.demo-footer-legal {
  display: flex;
  gap: 20px;
}

.demo-footer-legal span {
  color: #6B7280;
  font-size: 11px;
  font-weight: 400;
  cursor: pointer;
}

/* Responsive */

@media (max-width: 768px) {
  .demo-nav {
    flex-direction: column;
    gap: 20px;
    padding: 20px;
  }
  
  .demo-hero-content h1 {
    font-size: 42px;
  }
  
  .demo-form-row {
    grid-template-columns: 1fr;
  }
  
  .demo-contact-grid {
    grid-template-columns: 1fr;
  }
  
  .demo-footer-grid {
    grid-template-columns: 1fr;
    gap: 30px;
  }
  
  .demo-footer-bottom {
    flex-direction: column;
    gap: 16px;
    text-align: center;
  }
  
  .demo-footer-legal {
    flex-wrap: wrap;
    justify-content: center;
  }
}/* ===========================================================================
   Create account — two-panel layout (mirrors the sign-in page).
   Left: dark promo panel (tools → one console + onboarding mockup).
   Right: white sign-up card.

   Every class is `su-` namespaced. These are global stylesheets in a bundled
   SPA, so generic names (.input / .field / .submit) would collide with
   Login.css — the prefix keeps the two pages fully isolated.

   The page NEVER scrolls: the shell is locked to 100dvh and every vertical
   rhythm value is clamped against vh, so the form compresses to fit short
   viewports instead of overflowing.
   ========================================================================= */

.su-shell {
  --ink: #141416;
  --muted: #71717a;
  --muted-2: #a1a1aa;
  --line: #e4e4e7;
  --line-dark: #232326;
  /* Field / button corner radius. Matches the refreshed card's softer, less
     pill-like fields (was 28px). */
  --pill: 12px;
  --danger: #f04444;

  /* --- Promo accent -------------------------------------------------------
     Every accented thing on the promo panel reads from these tokens, so
     re-theming the panel is editing this block and nothing else.
     --accent-rgb / --accent-2-rgb are the bare channel triplets the rgba()
     calls below need.

     NOTE: Login.css carries the same block for the sign-in panel. The two
     pages run the SAME pitch and the SAME reveal, so keep them in step — if
     one moves and the other doesn't, that is the drift this repo keeps
     warning about. Signup's own additions are marked further down.

     ⚠️ THE PANEL'S GROUND IS NOW THE SITE'S SHARED HERO GROUND, PAINTED BY
     THE SHELL. `.su-shell` carries `rf-hero-ground` (theme.css) — the same
     violet every marketing hero paints — so ONE surface runs unbroken behind
     the promo panel AND the card gap. The operator asked for exactly that:
     the card should read as sitting ON the panel's ground, and a flat shell
     colour next to the bloomed panel read as a black strip over the card's
     top edge. The whole navy-era background system (--panel-base navy +
     --wash + --glow-deep + --bloom + the 46px engineering grid) is DELETED,
     not overridden; see the ⚠️ where `.su-promo--os { background }` used to
     be.

     The inks are the site's on-purple set, scored in theme.css against the
     ground's brightest painted point rgb(81,64,165):
       #FFFFFF 7.99 · #E4DEFF 6.16 · #C4B8FF 4.43
     #C4B8FF sits under 4.5, so as TEXT it is large/bold only here (the
     24px+ price) — everywhere else it colours decorative glyphs the adjacent
     text restates. Small faint text gets #CFC6F4, a mid-step of the same hue
     computing 4.9:1 against that same worst-case pixel — derived, not
     tasted. Scoring every ink against the ground's brightest possible pixel
     is stricter than any per-position sample, so no text node can fall
     under its floor anywhere on the panel.

     PREVIOUS values, if we ever go back:
       navy (s16):  --panel-base #0a1730 · --wash rgba(37,78,168,.55)
                    --glow-deep rgba(29,78,216,.3) · --bloom rgba(56,189,248,.2)
                    --accent #a1a1aa · --accent-2 #c2c7cf · --accent-soft #d4d4d8
                    ink #f8fafc · dim #cbd5e1 · faint #94a3b8
                    lines rgba(190,196,206,.16/.3) · fill rgba(200,206,216,.06)
       blue accents (s14): --accent #38bdf8 · --accent-2 #2dd4bf
       monochrome: --accent #a1a1aa · --panel-base #0b0d10
       charcoal:   --panel-base #212429 */
  --accent: #C4B8FF;              /* brand lavender — price + glyphs; as text, LARGE/BOLD ONLY */
  --accent-2: #E4DEFF;            /* pale lavender — savings pill, guarantee icon */
  --accent-rgb: 196, 184, 255;
  --accent-2-rgb: 228, 222, 255;

  /* Promo surface scale. Hairlines and text tones, so a card only ever has to
     name a role rather than re-derive an rgba(). Lavender-white now: the
     chrome sits in the ground's own hue, not the navy era's slate grey. */
  --panel-line: rgba(228, 222, 255, 0.16);
  --panel-line-2: rgba(228, 222, 255, 0.32);
  --panel-fill: rgba(228, 222, 255, 0.06);
  --panel-ink: #ffffff;
  --panel-dim: #E4DEFF;
  --panel-faint: #CFC6F4;
  /* The line that cancels each price during the reveal. Red-400 rather than a
     hard red: it must read as "struck out" over the violet card without
     shouting louder than the offer that replaces it. */
  --strike: #f87171;

  /* Theme the shared PhoneInput component (it reads these) so the phone field
     matches the reference's pill instead of its default grey. */
  --input-bg: #ffffff;
  --input-border: #e4e4e7;
  --input-focus: #141416;
  --input-text: #141416;

  /* Gap that lets the card's right corners round against the dark backdrop. */
  --card-gap: clamp(10px, 1.4vw, 22px);

  display: grid;
  /* Fluid form column: ~520px at 1440 (the reference width), scaling with the
     viewport rather than pinned to one resolution. */
  grid-template-columns: minmax(0, 1fr) clamp(430px, 36vw, 560px);
  width: 100%;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;                     /* the page itself never scrolls */
  /* ⚠️ NO `background` HERE — the shell carries `rf-hero-ground` (theme.css)
     in the JSX, so the hero violet (bloom on the element, dots on ::before,
     vignette on ::after) paints across the WHOLE shell, promo and card gap
     alike, and the white card floats on it. A `background` shorthand on this
     class loads after theme.css and would silently flatten all three layers
     back to one colour — that flat colour next to the panel's bloomed violet
     is exactly the black-strip-over-the-card bug this replaced. */
  color: var(--ink);
  font-family: var(--rf-font-body, Georgia, serif);
  letter-spacing: normal;
}

/* ---------- LEFT : promo panel ---------- */
/* Generic shell for the panel. Everything specific to the RntlOS OS pitch
   hangs off `.su-promo--os` below, so this rule stays a plain dark column. */
.su-promo {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(24px, 4.4vh, 52px) clamp(32px, 4.4vw, 64px);
  color: var(--panel-ink);
  min-width: 0;
  /* Clips the decorative ring below AND guarantees the panel can never grow
     the page. The reveal itself lives on the content, not on this box. */
  overflow: hidden;
}

/* --- The stage -------------------------------------------------------------
     .su-promo        clipped frame · hosts the decorative ring
     .su-promo-stage  centres the pitch · nothing moves in here
     .su-promo-inner  the capped content column

   ⚠️ WHY THE RING IS NOT IN .su-promo-stage. .su-promo-ring is a 640px circle
   positioned 260px BELOW .su-promo's bottom edge. An absolutely positioned
   descendant that overflows its scroll/clip box still counts toward layout
   there, and when the inner box was briefly a real scroll container that
   bought it 260px of empty scroll at every size — measured 1160px of
   scrollHeight against 900px of client height while the content had 205px to
   SPARE. Decoration stays on the clipped parent; only content goes inside. */
.su-promo-stage {
  flex: 1;
  min-height: 0;                   /* let the flex child actually shrink */
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  overflow: hidden;
}

/* --- The OS pitch panel ---------------------------------------------------
   ⚠️ THE GROUND IS NOT PAINTED HERE — OR ON THIS ELEMENT AT ALL. The SHELL
   carries `rf-hero-ground` (theme.css): bloom on the shell, dot field on its
   ::before, vignette on its ::after, all under the content via isolation +
   z-index, so one violet runs behind this panel and the card gap alike. DO
   NOT re-add a local `background` here or on `.su-shell`: Signup.css loads
   after theme.css, so it would silently beat the shared surface and this
   page would drift off the brand exactly the way the four marketing pages
   once did (see theme.css's hero-ground note).

   The pitch fits without scrolling because its two phases share one grid
   cell (see .sup-swap) - the column is the headline plus the taller phase,
   not the sum of everything. */

/* Decorative ring, bottom-right. A REAL element (span in Signup.jsx) now, not
   .su-promo::before — the shared hero class owns the ::before/::after pair
   of whatever element carries it (today the shell, previously this aside),
   and a ring rule merging per-property with the dots produces neither layer,
   so the ring stays off pseudo-elements entirely. Lives directly under
   .su-promo (clipped, never scrolls) rather than inside .su-promo-stage, so
   it is unaffected by anything the reveal does to the content. */
.su-promo-ring {
  position: absolute;
  inset: auto -220px -260px auto;
  width: 640px;
  height: 640px;
  border-radius: 50%;
  border: 1px solid var(--panel-line);
  pointer-events: none;
  opacity: 0;
  animation: su-circle-in 1.4s 0.3s ease forwards;
}

/* The promo's content column. Capped and centred so it can't be marooned
   against the left edge of a very wide panel: at 3440px the panel is 2880px
   wide, and a left-aligned column would leave ~2000px of dead space between
   it and the card. Owns the width for the whole column. */
.su-promo-inner {
  width: 100%;
  max-width: 620px;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
/* ===========================================================================
   THE REVEAL - plays ONCE on load, then holds. No loop.

   Four beats (times are the base values below, before --reveal-scale):
     1  0.15s  the headline arrives, sitting low so it reads as centred
     2  1.70s  it lifts into place, opening the space below it
     3  2.45s  the six tools you rent today appear and are left up long enough
               to actually read; at 5.00s each is STRUCK THROUGH in turn; at
               6.50s the whole block disappears
     4  7.25s  what replaces it rises into that same space: the offer, the
               guarantee, the proof row
   Last animation ends at ~9.3s.

   /!\ ONE KNOB: --reveal-scale multiplies EVERY delay and duration here, so
   the pacing can be changed by editing one number instead of retiming twenty
   lines by hand - which is how it has been adjusted twice already.
   1 = as shipped. 0.5 = twice as fast. 1.3 = a third slower again.

   /!\ THIS RUNS LONGER THAN FIVE SECONDS, WHICH IS WHY THE SKIP RULE AT THE
   BOTTOM OF THIS BLOCK EXISTS AND IS NOT OPTIONAL. WCAG 2.2.2 (Pause, Stop,
   Hide) requires a mechanism for automatic motion that lasts over five
   seconds alongside other content. Pausing would be the WRONG mechanism here
   - it would strand the offer and the guarantee off-screen forever - so
   touching the form finishes the reveal instantly instead. Retime back under
   5s and you may drop that rule; while it is over, don't.

   Everything below animates opacity/transform ONLY - never a layout property
   - so the reveal cannot disturb a shell locked to 100dvh that never scrolls.
   ========================================================================= */
.su-promo--os { --reveal-scale: 1; }

/* --- Beats 1 and 2: the headline arrives low, then lifts ----------------- */
.sup-head {
  /* Pushed down by roughly half the height the swap area will occupy, so the
     headline reads as vertically centred before anything else exists. A
     transform, not a margin - it must not move the layout it sits in. */
  transform: translateY(clamp(56px, 12vh, 128px));
  animation-name: su-head-lift;
  animation-duration: calc(0.85s * var(--reveal-scale));
  animation-delay: calc(1.7s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-fill-mode: forwards;
}
@keyframes su-head-lift { to { transform: translateY(0); } }

/* Longhands throughout rather than the `animation` shorthand: a later
   shorthand would reset the calc()-based duration set here. */
.su-badge,
.su-promo-title,
.su-promo-sub,
.sup-heading,
.sup-cost,
.sup-total,
.sup-proof-item {
  opacity: 0;
  animation-name: su-fadeUp;
  animation-duration: calc(0.65s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-fill-mode: forwards;
}
.sup-offer,
.sup-guarantee {
  opacity: 0;
  animation-name: su-riseIn;
  animation-duration: calc(0.75s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-fill-mode: forwards;
}

.su-badge { animation-delay: calc(0.15s * var(--reveal-scale)); }
.su-promo-title { animation-delay: calc(0.35s * var(--reveal-scale)); }
.su-promo-sub   { animation-delay: calc(0.6s * var(--reveal-scale)); }

/* --- Beat 3: the stack you rent today, appearing then cancelled ---------- */
.sup-heading { animation-delay: calc(2.45s * var(--reveal-scale)); }
.sup-cost:nth-child(1) { animation-delay: calc(2.75s * var(--reveal-scale)); }
.sup-cost:nth-child(2) { animation-delay: calc(2.91s * var(--reveal-scale)); }
.sup-cost:nth-child(3) { animation-delay: calc(3.07s * var(--reveal-scale)); }
.sup-cost:nth-child(4) { animation-delay: calc(3.23s * var(--reveal-scale)); }
.sup-cost:nth-child(5) { animation-delay: calc(3.39s * var(--reveal-scale)); }
.sup-cost:nth-child(6) { animation-delay: calc(3.55s * var(--reveal-scale)); }
.sup-total { animation-delay: calc(3.95s * var(--reveal-scale)); }

/* The strike: a line sweeps left to right across each row. The price is being
   CANCELLED, not merely hidden, and that is the whole reason for showing it.
   The full list sits un-struck from 3.55s to 5.00s - that gap is deliberate
   reading time, and it is the first thing to lengthen if this ever feels
   rushed again. `position: relative` on the rows is what these lines are
   measured against; without it each would size itself to the panel. */
.sup-cost,
.sup-total { position: relative; }
.sup-cost::after,
.sup-total::after {
  content: "";
  position: absolute;
  left: 11px;
  right: 11px;
  top: 50%;
  height: 1.5px;
  border-radius: 2px;
  background: var(--strike);
  transform: scaleX(0);
  transform-origin: left center;
  animation-name: su-strike;
  animation-duration: calc(0.45s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  animation-fill-mode: forwards;
  pointer-events: none;
}
@keyframes su-strike { to { transform: scaleX(1); } }

.sup-cost:nth-child(1)::after { animation-delay: calc(5s * var(--reveal-scale)); }
.sup-cost:nth-child(2)::after { animation-delay: calc(5.13s * var(--reveal-scale)); }
.sup-cost:nth-child(3)::after { animation-delay: calc(5.26s * var(--reveal-scale)); }
.sup-cost:nth-child(4)::after { animation-delay: calc(5.39s * var(--reveal-scale)); }
.sup-cost:nth-child(5)::after { animation-delay: calc(5.52s * var(--reveal-scale)); }
.sup-cost:nth-child(6)::after { animation-delay: calc(5.65s * var(--reveal-scale)); }
.sup-total::after { animation-delay: calc(5.9s * var(--reveal-scale)); }

/* ...and then the block goes. `visibility: hidden` in the final keyframe is
   load-bearing twice over: it takes the cancelled prices out of the
   accessibility tree once they have stopped being true on screen, and it
   stops the now-invisible block swallowing pointer events from the offer card
   that shares its grid cell. */
.sup-costs {
  animation-name: su-costs-out;
  animation-duration: calc(0.6s * var(--reveal-scale));
  animation-delay: calc(6.5s * var(--reveal-scale));
  animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
  animation-fill-mode: forwards;
}
@keyframes su-costs-out {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to   { opacity: 0; transform: translateY(-14px) scale(0.985); visibility: hidden; }
}

/* --- Beat 4: what takes its place --------------------------------------- */
.sup-offer     { animation-delay: calc(7.25s * var(--reveal-scale)); }
.sup-guarantee { animation-delay: calc(7.75s * var(--reveal-scale)); }
.sup-proof-item:nth-child(1) { animation-delay: calc(8.25s * var(--reveal-scale)); }
.sup-proof-item:nth-child(2) { animation-delay: calc(8.38s * var(--reveal-scale)); }
.sup-proof-item:nth-child(3) { animation-delay: calc(8.51s * var(--reveal-scale)); }
.sup-proof-item:nth-child(4) { animation-delay: calc(8.64s * var(--reveal-scale)); }

/* --- The skip, which IS the WCAG 2.2.2 mechanism ------------------------- */
/* Touching the form finishes the reveal on the spot. This is a "stop" that
   costs no content - pausing would leave the offer and the guarantee never
   shown, which is worse than the motion. It is also simply the right
   behaviour: nobody signing in wants a show running beside the field they are
   typing into.

   /!\ IT IS DRIVEN BY A CLASS, NOT BY :focus-within, AND THAT IS A FIX. The
   obvious `.su-shell:has(.su-form-side:focus-within)` version was written
   first and REVERTS: the moment focus leaves the form the rule stops
   matching, the real delays come back, and anything the skip had revealed
   goes back to opacity 0 and replays. Measured — offer and guarantee at 1
   while the email field held focus, back to 0 after blur, so clicking from
   the field onto the panel undid the skip. `.reveal-done` is set once by an
   onFocus on the shell in Signup.jsx and never removed: a one-way latch.

   !important is doing real work here. It has to beat twenty-odd per-element
   animation-delay rules plus two pseudo-elements, and no specificity trick
   reaches all of them at once. */
.su-shell.reveal-done .su-promo-inner *,
.su-shell.reveal-done .sup-cost::after,
.su-shell.reveal-done .sup-total::after {
  animation-delay: 0s !important;
  animation-duration: 1ms !important;
}

/* --- 1. Eyebrow ---------------------------------------------------------- */
.su-badge {
  /* align-self is load-bearing now that the badge has a border: .su-promo-inner
     is a flex column with the default `align-items: stretch`, so without it
     the pill would run the full width of the column. */
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 13px 6px 11px;
  border: 1px solid var(--panel-line);
  border-radius: 99px;
  background: var(--panel-fill);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--panel-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  margin-bottom: clamp(10px, 1.8vh, 18px);
}
.su-badge .su-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  background: var(--accent);
  /* Deliberately NOT a pulsing "live" dot. The reveal is specified to play
     once and stop, and an infinite heartbeat is motion that never stops - it
     would also drag the panel back under WCAG 2.2.2. A static halo gives the
     same lit look with no animation. */
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.16), 0 0 10px rgba(var(--accent-rgb), 0.5);
}
.sup-label-sep { color: var(--panel-faint); }

/* --- 2. Headline · 3. Subheadline ---------------------------------------- */
.su-promo-title {
  /* The SENTENCE deliberately has no font-family: it inherits the shell's
     sans stack, so the panel and the sign-up card share one face. The accent
     span below carries the serif — see its note for why that overrules the
     older "no second face on this page" decision that used to live here. */
  font-weight: 700;
  font-size: clamp(28px, 3.1vw, 42px);
  line-height: 1.08;
  letter-spacing: -0.025em;
  margin: 0 0 clamp(8px, 1.2vh, 12px);
  color: var(--panel-ink);
}
.su-promo-title .su-accent {
  /* The site's one type gesture, matched to the home hero's
     .hero-headline-accent: the serif italic accent via --rf-font-accent
     (Playfair Display since session 71's Design Monks flip — one definition
     in theme.css). LandingHero.css documents why on this exact violet ground
     the accent is carried by the FACE and not by a hue — lavender text here
     sinks into the backdrop it came from. That overrules this file's older
     "modern sans only" note deliberately: now the panel paints the hero's
     ground, it takes the hero's headline idiom too. Playfair loads from
     index.html (same reliance /blog's insight strip has); Georgia is the
     degrade. */
  font-family: var(--rf-font-accent, Georgia, 'Times New Roman', serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--panel-ink);
}

.su-promo-sub {
  font-size: clamp(13px, 1.02vw, 14.5px);
  color: var(--panel-dim);
  max-width: 52ch;
  line-height: 1.6;
  margin: 0 0 clamp(16px, 2.8vh, 28px);
  font-weight: 500;
}

/* --- The swap cell -------------------------------------------------------
   /!\ THE TWO PHASES SHARE ONE GRID CELL, AND THAT IS THE WHOLE TRICK.
   `.sup-costs` (what you pay today) and `.sup-after` (the offer, guarantee
   and proof) are both placed at 1/1, so phase 2 occupies exactly the space
   phase 1 vacates. Nothing collapses and nothing reflows - the transition is
   opacity and transform only, which is what keeps the panel safe inside a
   shell locked to 100dvh.

   Stacking them normally instead would make the column ~695px tall and bring
   back the scrolling this replaced. The cell is sized by the TALLER child
   (phase 2), so during phase 1 there is deliberate empty space below the
   prices; that space is where the offer is about to land. */
.sup-swap {
  display: grid;
  width: 100%;
  min-width: 0;
}
.sup-swap > * {
  grid-area: 1 / 1;
  align-self: start;
  min-width: 0;
}
.sup-head { width: 100%; }

/* --- 4. What you're paying right now ------------------------------------- */
/* No margin-bottom: in the swap cell it would offset phase 1 against phase 2.
   The stacked reduced-motion fallback restores one. */
.sup-costs { margin-bottom: 0; }

.sup-heading {
  margin: 0 0 clamp(8px, 1.3vh, 12px);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  line-height: 1.5;
  text-transform: uppercase;
  color: var(--panel-faint);
}

/* Two columns: six stacked rows would cost ~100px of height this panel does
   not have, and the pairs read as one bill either way. */
.sup-cost-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 7px 10px;
}

.sup-cost {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--panel-line);
  border-radius: 11px;
  background: var(--panel-fill);
  font-size: 12.5px;
  /* Explicit line-height: the row centres a 13px glyph against a 13px icon,
     and an inherited value from an ancestor would push the check off-centre.
     Same hazard the payouts table hit — see CLAUDE.md. */
  line-height: 1.45;
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}
/* Not a link and not clickable, so no pointer cursor — the hover is a
   readability lift, not an affordance. */
.sup-cost:hover {
  border-color: var(--panel-line-2);
  background: rgba(228, 222, 255, 0.09);
  transform: translateY(-1px);
}
.sup-cost-check {
  width: 13px;
  height: 13px;
  flex: none;
  color: var(--accent);
}
.sup-cost-name {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  color: var(--panel-dim);
  /* A longer tool name truncates rather than wrapping the row to two lines
     and breaking the grid's even banding. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sup-cost-price {
  flex: none;
  font-weight: 700;
  color: var(--panel-ink);
  font-variant-numeric: tabular-nums;
}

/* --- 5. Total ------------------------------------------------------------ */
/* Deliberately neutral rather than alarm-red: the accent belongs to the offer
   below, and two competing colours would blunt it. */
.sup-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin: clamp(9px, 1.5vh, 13px) 0 0;
  padding: 10px 14px;
  border: 1px solid var(--panel-line-2);
  border-radius: 12px;
  background: rgba(228, 222, 255, 0.1);
}
.sup-total-label {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.45;
  color: var(--panel-dim);
}
.sup-total-value {
  font-size: clamp(17px, 1.5vw, 20px);
  font-weight: 700;
  line-height: 1.2;
  color: var(--panel-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sup-total-value small {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--panel-faint);
  margin-left: 4px;
}

/* --- 6. The offer -------------------------------------------------------- */
.sup-offer {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(12px, 1.6vw, 20px);
  flex-wrap: wrap;
  margin-bottom: clamp(9px, 1.5vh, 14px);
  padding: clamp(12px, 1.9vh, 16px) clamp(14px, 1.3vw, 18px);
  border: 1px solid rgba(var(--accent-rgb), 0.34);
  border-radius: 16px;
  background:
    radial-gradient(130% 170% at 100% 0%, rgba(var(--accent-rgb), 0.15), transparent 62%),
    rgba(var(--accent-rgb), 0.05);
  box-shadow:
    0 20px 46px -30px rgba(var(--accent-rgb), 0.75),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
/* A soft corner glow behind the copy, on its own layer so it can never
   affect the card's box. STATIC: this used to breathe on a 7s infinite loop,
   which is exactly the perpetual motion the reveal is meant to replace. */
.sup-offer::after {
  content: "";
  position: absolute;
  inset: -40% -20% auto auto;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(var(--accent-rgb), 0.18), transparent 68%);
  pointer-events: none;
  opacity: 0.85;
}
.sup-offer-copy { min-width: 0; flex: 1 1 240px; }
.sup-offer-title {
  margin: 0 0 4px;
  font-size: clamp(14.5px, 1.25vw, 16.5px);
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--panel-ink);
}
.sup-offer-body {
  margin: 0;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--panel-dim);
}
.sup-offer-price {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  flex: none;
  text-align: right;
}
.sup-offer-price b {
  font-size: clamp(24px, 2.2vw, 30px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sup-offer-price b small {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--panel-faint);
}
/* A lighter grey + a border, so the saving does not depend on tone alone to
   be read as a distinct claim rather than part of the price. */
.sup-save {
  padding: 3px 9px;
  border: 1px solid rgba(var(--accent-2-rgb), 0.36);
  border-radius: 99px;
  background: rgba(var(--accent-2-rgb), 0.12);
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--accent-2);
  white-space: nowrap;
}

/* --- 7. Guarantee -------------------------------------------------------- */
.sup-guarantee {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: clamp(13px, 2.4vh, 24px);
  padding: clamp(11px, 1.7vh, 15px) clamp(13px, 1.2vw, 16px);
  border: 1px solid var(--panel-line);
  border-radius: 16px;
  background: var(--panel-fill);
}
.sup-guarantee-badge {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  border: 1px solid rgba(var(--accent-2-rgb), 0.3);
  border-radius: 10px;
  background: rgba(var(--accent-2-rgb), 0.12);
  color: var(--accent-2);
}
.sup-guarantee-icon { width: 18px; height: 18px; }
.sup-guarantee-copy { min-width: 0; }
.sup-guarantee-title {
  margin: 0 0 3px;
  font-size: 13.5px;
  font-weight: 700;
  line-height: 1.35;
  color: var(--panel-ink);
}
.sup-guarantee-body {
  margin: 0;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.55;
  color: var(--panel-dim);
}

/* --- 8. Social proof ----------------------------------------------------- */
.sup-proof {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin: 0;
  padding: clamp(11px, 1.9vh, 16px) 0 0;
  border-top: 1px solid var(--panel-line);
}
.sup-proof-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  transition: transform 0.2s ease;
}
.sup-proof-item:hover { transform: translateY(-2px); }
.sup-proof-icon {
  width: 15px;
  height: 15px;
  color: var(--accent);
  margin-bottom: 3px;
}
.sup-proof-value {
  font-size: 15px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--panel-ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sup-proof-label {
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--panel-faint);
}
@keyframes su-riseIn {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes su-fadeUp {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes su-circle-in {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: scale(1); }
}
/* ---------- RIGHT : sign-up card ---------- */
.su-form-side {
  background: #ffffff;
  /* Rounded on all four corners; the margin opens the gap on the right (and
     top/bottom) so that rounding actually reads against the backdrop. */
  border-radius: 22px;
  /* Filled-field tokens the PhoneInput reads (see .su-field--phone). */
  --input-bg: #f5f6f8;
  --input-border: transparent;
  --input-focus: var(--ink);
  margin: var(--card-gap) var(--card-gap) var(--card-gap) 0;
  display: flex;
  flex-direction: column;
  padding: clamp(14px, 2vh, 32px) clamp(24px, 3.2vw, 48px) clamp(12px, 1.8vh, 24px);
  min-height: 0;                        /* let the flex column shrink to fit */
  overflow: hidden;                     /* never scrolls */
}

/* Brand (left) + Sign In (right). */
.su-form-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: clamp(8px, 1.7vh, 30px);
  flex: none;
}
.su-brand-row {
  display: flex;
  align-items: center;
  gap: 10px;
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  border-radius: 6px;
  transition: opacity 0.15s ease;
}
.su-brand-row:hover { opacity: 0.85; }
.su-brand-row:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
.su-brand-mark {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--ink);
  color: #fff;
  display: grid;
  place-items: center;
  font-size: 15px;
  font-weight: 700;
  flex: none;
}
.su-brand-name { font-size: 16.5px; font-weight: 700; }

/* "Sign in" swap — a quiet bordered pill in the top-right, mirroring the
   refreshed card's header. */
.su-head-signin {
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--line);
  padding: 7px 14px;
  border-radius: 99px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}
.su-head-signin:hover { background: #f5f6f8; border-color: #d8dae0; }
.su-head-signin:active { transform: scale(0.97); }
.su-head-signin:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.su-form-wrap {
  width: 100%;
  max-width: 360px;
  margin: 0 auto;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 0;
}

/* NOTE on the vh coefficients below: the reference card scrolls
   (`overflow-y:auto`) — this one must never scroll, and the sign-up form is
   tall (4 fields + strength meter + terms). So each rhythm value keeps the
   reference figure as its clamp MAX (reached on tall screens) but compresses
   on shorter ones, instead of overlapping the header/footer. */
.su-field {
  display: flex;
  flex-direction: column;
  gap: clamp(3px, 0.7vh, 8px);
  margin-bottom: clamp(6px, 1.2vh, 16px);
}
.su-field label { font-size: 13px; font-weight: 600; color: var(--ink); }
.su-opt { color: var(--muted-2); font-weight: 500; }
/* Reference gives the password block a little extra room for the meter. */
.su-field--pw { margin-bottom: clamp(8px, 1.5vh, 20px); }

/* The entrance animation on `.su-form-wrap > *` gives EVERY child its own
   stacking context, so the country dropdown's z-index is trapped inside this
   field and the later siblings (password / submit / terms) would paint over
   it. Lifting the whole field above them is what keeps the menu readable. */
.su-field--phone { position: relative; z-index: 20; }
/* Match the phone control to the refreshed filled fields: soft grey fill + 12px
   radius at rest, lifting to white with a ring on focus. Colours come from the
   --input-* tokens set on .su-form-side; the radius + ring are set here because
   the PhoneInput hard-codes those. Validity (green/red) states still win. */
.su-field--phone .pin { border-width: 1.5px; border-radius: 12px; }
.su-field--phone .pin:focus-within {
  background: #fff;
  box-shadow: 0 0 0 4px rgba(20, 20, 22, 0.06);
}
/* Placeholder + typed number match the other fields' readability. */
.su-field--phone .pin-input::placeholder { color: #6b7280; opacity: 1; }

/* Country dropdown — dark (matches the admin panel) even though the field
   itself stays light. Higher specificity than the component's light defaults.
   Values mirror the .dashboard-page palette (--panel / --panel-2 / --panel-3). */
.su-shell .pin-menu {
  background: #0a0a0a;
  border-color: #1f1f1f;
  color: #fafafa;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5);
}
.su-shell .pin-search { border-bottom-color: #1f1f1f; color: #8a8f9b; }
.su-shell .pin-search input { color: #fafafa; }
.su-shell .pin-search input::placeholder { color: #6b7280; }
.su-shell .pin-list::-webkit-scrollbar-thumb { background: #232326; }
.su-shell .pin-option:hover { background: #0f0f12; }
.su-shell .pin-option.is-selected { background: #14141a; }
.su-shell .pin-empty { color: #8a8f9b; }
.su-shell .pin-option-dial { color: #6b7280; }

.su-input-wrap { position: relative; }
/* Filled, borderless-at-rest fields — the refreshed card's softer look. Focus
   lifts them to white with the ink outline + a soft ring. */
.su-input {
  width: 100%;
  padding: clamp(8px, 1.4vh, 14px) 18px;
  border: 1.5px solid transparent;
  border-radius: var(--pill);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  outline: none;
  background: #f5f6f8;
  transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}
.su-input::placeholder { color: #6b7280; }
.su-input:focus { background: #fff; border-color: var(--ink); box-shadow: 0 0 0 4px rgba(20, 20, 22, 0.06); }
.su-input:disabled { background: #f0f1f3; cursor: not-allowed; }
.su-input.has-action { padding-right: 62px; }
.su-input.is-error { border-color: #dc2626; }
.su-input.is-error:focus { border-color: #dc2626; box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.08); }

.su-show-btn {
  position: absolute;
  right: 18px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  padding: 0;
}
.su-show-btn:hover { color: var(--ink); }
.su-show-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 4px; }

/* Inline per-field error text. */
.su-err {
  color: #b91c1c;
  font-size: 12px;
  line-height: 1.4;
  margin: 0 0 0 4px;
  font-weight: 600;
}

/* --- Shared PhoneInput, restyled to the reference's phone pill --- */
.su-shell .pin {
  height: auto;
  padding: 0;
  gap: 0;
  border-radius: var(--pill);
  border: 1px solid var(--line);
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.su-shell .pin:focus-within {
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(20, 20, 22, 0.07);
}
/* Country trigger = the reference's `.cc` chip. Zero vertical padding +
   align-self:stretch so the PILL's height is driven by the number input —
   otherwise the chip makes the phone field taller than the other inputs. */
.su-shell .pin-country {
  height: auto;
  align-self: stretch;
  padding: 0 14px 0 18px;
  border-radius: var(--pill) 0 0 var(--pill);
  gap: 5px;
}
.su-shell .pin-code { font-size: 13.5px; font-weight: 700; color: var(--ink); }
/* Full-height rule between code and number, matching the reference's
   `border-right` on the country chip. */
.su-shell .pin-divider { height: auto; align-self: stretch; background: var(--line); }
/* Vertical padding must track .su-input so the phone pill and the text
   inputs stay exactly the same height. */
.su-shell .pin-input {
  padding: clamp(8px, 1.4vh, 14px) 14px;
  font-size: 14px;
  font-weight: 500;
}
/* The component's own valid/invalid rings would fight the reference look —
   keep the neutral pill and let our inline .su-err carry the message. */
.su-shell .pin.is-valid,
.su-shell .pin.is-invalid { box-shadow: none; }
.su-shell .pin.is-valid { border-color: #16a34a; }
.su-shell .pin.is-invalid { border-color: #dc2626; }

/* Password strength meter */
.su-strength {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  margin-top: clamp(4px, 0.8vh, 9px);
}
.su-strength span {
  height: 4px;
  border-radius: 99px;
  background: var(--line);
  transition: background 0.25s ease;
}
.su-strength span.on-1 { background: #f04444; }
.su-strength span.on-2 { background: #f59e0b; }
.su-strength span.on-3 { background: #84cc16; }
.su-strength span.on-4 { background: #22c55e; }

.su-hint {
  font-size: 12px;
  font-weight: 500;
  color: var(--muted-2);
  margin-top: clamp(3px, 0.7vh, 7px);
}
.su-hint.is-err { color: #b91c1c; font-weight: 600; }

/* Server-error slot — always rendered so the entrance stagger's nth-child
   indices stay stable when a message appears. */
.su-error-slot:empty { display: none; }
.su-error {
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: #991b1b;
  border-radius: 12px;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 12px;
}

.su-submit {
  width: 100%;
  padding: clamp(10px, 1.5vh, 15px);
  margin-top: clamp(2px, 0.6vh, 6px);
  border: none;
  border-radius: var(--pill);
  background: var(--ink);
  color: #fff;
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 700;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: transform 0.12s ease, background 0.15s ease, opacity 0.15s ease;
}
.su-submit:hover:not(:disabled) { background: #000; }
.su-submit:active:not(:disabled) { transform: scale(0.98); }
.su-submit:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.su-submit:disabled { background: #d4d4d8; color: #fafafa; cursor: not-allowed; }
.su-arrow { transition: transform 0.18s ease; }
.su-submit:hover:not(:disabled) .su-arrow { transform: translateX(3px); }

.su-terms {
  text-align: center;
  font-size: 12px;
  font-weight: 500;
  color: #6b7280;
  margin: clamp(6px, 1.1vh, 14px) 0 0;
  line-height: 1.6;
}
/* Terms / Privacy — clickable link affordance (destinations wired later). */
.su-terms-link {
  color: var(--ink);
  font-weight: 600;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  text-decoration: none;
}
.su-terms-link:hover { text-decoration: underline; }
.su-terms-link:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 3px; }

.su-divider {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: clamp(8px, 1.5vh, 20px) 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted-2);
  white-space: nowrap;
}
.su-divider::before,
.su-divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }

.su-social-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.su-social {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: clamp(8px, 1.3vh, 13px);
  border: 1px solid var(--line);
  border-radius: var(--pill);
  background: #fff;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  /* position + overflow clip the ghost circle below to the pill shape;
     isolation keeps it inside this button's own stacking context. */
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition: color 0.28s ease, border-color 0.32s ease;
}

/* Ghost hover — the site's Login-button effect (home-pill-btn--ghost),
   inverted for a light card: a circle ripples out from the centre to fill the
   pill and the label inverts. z-index:-1 inside the isolated context paints
   the circle ABOVE the button's own white background but still behind the
   label, so the text and icons stay legible as it sweeps past.
   Mirrors .social in Login.css — keep the two in step. */
.su-social::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150%;
  aspect-ratio: 1;                      /* a circle */
  border-radius: 50%;
  background: var(--ink);
  transform: translate(-50%, -50%) scale(0);
  transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: -1;
  pointer-events: none;
}
/* Stays #fff so the corners the circle hasn't reached yet don't flash grey.
   Apple's mark is fill="currentColor" and inverts with the label; Google's
   keeps its brand colours, which is what their guidelines require on dark. */
.su-social:hover:not(:disabled) { background: #fff; color: #fff; border-color: var(--ink); }
.su-social:hover:not(:disabled)::before { transform: translate(-50%, -50%) scale(1); }
.su-social:active:not(:disabled) { transform: scale(0.98); }
.su-social:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.su-social:disabled { opacity: 0.55; cursor: not-allowed; }
.su-social svg { width: 17px; height: 17px; flex: none; }

.su-sso-note {
  margin: 10px 2px 0;
  font-size: 12.5px;
  font-weight: 600;
  color: #3f3f46;
  text-align: center;
}

.su-switch-line {
  text-align: center;
  margin: clamp(8px, 1.6vh, 24px) 0 0;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--muted);
}
.su-switch-link {
  color: var(--ink);
  font-weight: 700;
  text-decoration: none;
  background: transparent;
  border: 0;
  padding: 0;
  font-family: inherit;
  font-size: inherit;
  cursor: pointer;
}
.su-switch-link:hover { text-decoration: underline; }
.su-switch-link:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 3px; }

/* Form entrance stagger. Child count is fixed at 10 (company, email, phone,
   password, error slot — always rendered — submit, terms, divider, socials,
   switch line) so these indices never shift at runtime. */
.su-form-wrap > * { opacity: 0; animation: su-fadeUp 0.45s ease forwards; }
.su-form-wrap > *:nth-child(1)  { animation-delay: 0.05s; }
.su-form-wrap > *:nth-child(2)  { animation-delay: 0.12s; }
.su-form-wrap > *:nth-child(3)  { animation-delay: 0.19s; }
.su-form-wrap > *:nth-child(4)  { animation-delay: 0.26s; }
.su-form-wrap > *:nth-child(5)  { animation-delay: 0.33s; }
.su-form-wrap > *:nth-child(6)  { animation-delay: 0.4s; }
.su-form-wrap > *:nth-child(7)  { animation-delay: 0.47s; }
.su-form-wrap > *:nth-child(8)  { animation-delay: 0.54s; }
.su-form-wrap > *:nth-child(9)  { animation-delay: 0.61s; }
.su-form-wrap > *:nth-child(10) { animation-delay: 0.68s; }

/* ---------- Autofill ---------- */
.su-input:-webkit-autofill,
.su-input:-webkit-autofill:hover,
.su-input:-webkit-autofill:active {
  -webkit-text-fill-color: var(--ink);
  -webkit-box-shadow: 0 0 0 1000px #f5f6f8 inset;
  box-shadow: 0 0 0 1000px #f5f6f8 inset;
  caret-color: var(--ink);
  transition: background-color 5000s ease-in-out 0s;
}
/* Focused autofilled field lifts to white, matching a normal focused field. */
.su-input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  -webkit-box-shadow: 0 0 0 1000px #ffffff inset;
  box-shadow: 0 0 0 1000px #ffffff inset;
  caret-color: var(--ink);
  transition: background-color 5000s ease-in-out 0s;
}

/* ---------- Responsive ---------- */
/* Below this the two-column layout has no room, so drop the promo panel and
   let the form take the full width. Still capped to the viewport — no scroll. */
@media (max-width: 1060px) {
  .su-shell { grid-template-columns: minmax(0, 1fr); }
  .su-promo { display: none; }
  .su-form-side {
    margin: 0;
    border-radius: 0;
    padding: clamp(16px, 2.6vh, 32px) clamp(20px, 6vw, 32px) clamp(12px, 2vh, 24px);
  }
}

@media (max-width: 420px) {
  .su-form-wrap { max-width: 100%; }
  .su-social { font-size: 12.5px; gap: 7px; }
  .su-head-signin { font-size: 12.5px; }
}

/* The sign-up form is the tallest surface in the app (4 fields + strength
   meter + terms). Below this the vh-clamps alone can't reclaim enough room,
   so tighten the whole vertical rhythm — otherwise the centred form would
   overflow its card. */
@media (max-height: 740px) {
  .su-field { margin-bottom: 7px; gap: 3px; }
  .su-field--pw { margin-bottom: 8px; }
  .su-input,
  .su-shell .pin-input { padding-top: 8px; padding-bottom: 8px; }
  .su-strength { margin-top: 5px; }
  .su-hint { margin-top: 4px; font-size: 11.5px; }
  .su-submit { padding: 10px; margin-top: 2px; }
  .su-terms { margin-top: 6px; }
  .su-divider { margin: 8px 0; }
  .su-social { padding: 9px; }
  .su-switch-line { margin-top: 8px; }
}

/* Tighter still — squeeze the remaining rhythm at very short heights. */
@media (max-height: 700px) {
  .su-field { margin-bottom: 4px; gap: 2px; }
  .su-field--pw { margin-bottom: 6px; }
  .su-input,
  .su-shell .pin-input { padding-top: 7px; padding-bottom: 7px; }
  .su-strength { margin-top: 4px; }
  .su-hint { margin-top: 3px; }
  .su-submit { padding: 9px; margin-top: 0; }
  .su-social { padding: 8px; }
  .su-divider { margin: 6px 0; }
  .su-terms { margin-top: 4px; font-size: 11px; }
  .su-switch-line { margin-top: 6px; }
}

@media (max-height: 680px) {
  .su-badge { display: none; }
  .su-form-head { margin-bottom: 8px; }
}

/* ---------- Large screens ---------- */
@media (min-width: 1500px) {
  .su-form-wrap { max-width: 380px; }
}
@media (min-width: 1800px) {
  .su-form-wrap { max-width: 400px; }
}

@media (prefers-reduced-motion: reduce) {
  .su-promo-ring,
  .su-badge,
  .su-dot,
  .su-promo-title,
  .su-promo-sub,
  .sup-head,
  .sup-heading,
  .sup-cost,
  .sup-total,
  .sup-costs,
  .sup-offer,
  .sup-offer::after,
  .sup-guarantee,
  .sup-proof-item,
  .su-form-wrap > * {
    animation: none;
    opacity: 1;
    transform: none;
  }

  /* /!\ TURNING THE REVEAL OFF IS NOT ENOUGH ON ITS OWN. The reveal ENDS with
     the price list struck out and gone, so simply killing the animations
     would leave a reduced-motion visitor with the cancelled state as their
     only state - i.e. permanently less information than everyone else. Motion
     preferences must cost motion, never content.

     So the fallback un-overlaps the two phases and shows BOTH, stacked, with
     nothing struck. That column is taller than the viewport on short screens,
     so the stage becomes a real scroll container - the only thing on this
     page that scrolls. */
  .sup-head { transform: none; }
  .sup-costs { visibility: visible; margin-bottom: clamp(12px, 2.2vh, 22px); }
  .sup-cost::after,
  .sup-total::after { display: none; }
  .sup-swap { display: block; }
  .su-promo-stage {
    justify-content: flex-start;
    overflow-y: auto;
    overscroll-behavior: contain;   /* a panel scroll must not chain to the page */
    scrollbar-width: thin;
    scrollbar-color: var(--panel-line-2) transparent;
  }
  /* Centres while it fits, top-anchored the moment it doesn't. A centred flex
     column that overflows clips its own top and strands the first rows. */
  .su-promo-stage > .su-promo-inner { margin-block: auto; }

  .su-submit,
  .su-arrow,
  .su-social { transition: none; }
  /* The ghost fill still happens — it's the hover affordance, not decoration.
     Only the sweep is dropped, matching the site's ghost button. */
  .su-social::before { transition: none; }
}
/* ScrollHighlight — scroll-scrubbed word highlight.
   The dim base colour is applied inline per instance (see ScrollHighlight.jsx);
   SplitText wraps each word in a `.shl-word` span that the scrubbed tween
   recolours from `dim` → `bright` as the element scrolls through the viewport. */

.shl {
  /* Avoid an ugly word-break in the middle of the scrub; let words wrap whole. */
  overflow-wrap: break-word;
}

.shl-word {
  /* Only the colour animates — hint the compositor so the per-word scrub stays
     smooth on long paragraphs. */
  will-change: color;
}

/* Reduced-motion users get the fully-lit text immediately (set in JS); nothing
   to animate here. */
@media (prefers-reduced-motion: reduce) {
  .shl-word { will-change: auto; }
}
/* ============================================================================
   RntlOS Pricing — BANDED
   ----------------------------------------------------------------------------
   The page reads top to bottom as THREE grounds, each meeting the next on a
   rounded lip — the rhythm /features, /blog and /about already use:

     PURPLE   the pill nav + the hero                    (`.pricing-hero-band`)
     WHITE    everything else — the billing toggle, the three plans, the risk
              row, the proof strip, the comparison matrix, the FAQ and the
              closing CTA          (`.pricing-decide` + `.pricing-light`)
     BLACK    the shared footer + the RENTFYV wordmark    (`.home-closing`)

   ⚠️⚠️ THE TOKENS **DO** FLIP IN PLACE NOW, AND THIS REVERSES WHAT THIS FILE
   USED TO SAY. The old note argued against flipping because "the majority of
   this file serves the tier cards, and they stay on black". That premise is
   gone: the decision band is white, so the cards are the thing that changed,
   and light now wins the whole page below the hero. Keeping dark roots would
   mean overriding almost every rule in the file inside a wrapper — which is
   the mess the flip exists to avoid. Only the two grounds that are NOT white
   re-point the token names on their own wrapper:
     · `.pricing-hero-band`         → ink for the deep purple
     · `.pricing-light .pricing-cta-card` → ink for the navy CTA card
     · `.tier-card-highlight`       → ink for the navy recommended tiers

   ⚠️⚠️ AND THE TIER CARDS HAD TO INVERT, WHICH IS THE LOAD-BEARING PART OF
   THIS CHANGE. The two "recommended" tiers (Growth, Pro) were WHITE cards
   picked to shout on a black band. Dropping a white band behind them would
   have made the two tiers the page most wants you to buy **disappear**, while
   the entry-level Starter card — the only dark one — became the loudest thing
   on screen. The hierarchy is now the other way up: Starter is a white
   bordered card, Growth and Pro are the site's navy, which is already this
   site's language for "this is the important surface" (the closing CTA card
   on /blog, /about and this page's own). Same two-of-three diagonal, same
   blur-focus interaction, opposite polarity.

   ⚠️ Literal colours are still the thing that bites. Ones that have already
   caught this file out: `.pricing-pill-btn--ghost` (`#FAFAFA` on a now-white
   pill), `.pricing-compare-table th.is-best` (`#fff` on white), the Growth
   column's `rgba(255,255,255,0.03)` wash, `.cmp-yes` (`#4ade80`, 1.9:1 on
   white), and every purple glow shadow on the tier cards, which read as a
   lilac haze on a light ground. Before putting a dark surface on a light band
   here, give it literals.

   ⚠️ THE PAGE IS NOT A `.home-page`, the same call Blog.css records. That
   light-theme block is entirely `.home-page`-scoped; adopting it would put
   five other pages' worth of unverified rules onto this one to save a handful
   of lines. The FAQ and CTA values below are therefore a deliberate COPY of
   Landing.css's, marked as such so the two cannot drift silently.
   ========================================================================= */
.pricing-page {
  /* ---- the page's ground is LIGHT now. Ratios vs #FFFFFF, from theme.css. */
  --bg: #FFFFFF;
  --card: #FFFFFF;
  --card-2: #FAFAFA;
  --card-border: #E4E4E7;
  --card-border-hover: #D4D4D8;

  --text: #000000;          /* 21:1   — headings */
  --text-dim: #4A4A52;      /* 8.6:1  — body copy */
  --text-faint: #6B6B75;    /* 5.1:1  — the floor, do not lighten */

  --accent: #6A5ACD;        /* the brand, 5.31:1 on white */
  --accent-soft: #EEECFA;
  --accent-glow: rgba(106, 90, 205, 0.18);

  /* ⚠️ CONTROLS GET A HEAVIER RIM THAN CARDS — a boundary that identifies a
     UI component owes 3:1 (SC 1.4.11), and a white control on a white band
     has no fill difference to fall back on. `--card-border-hover` is 1.48:1
     here; this measures 3.31:1. Cards keep the lighter hairline. */
  --line-ctl: #86868F;

  /* ---- the navy the RECOMMENDED tiers and the closing CTA card share ----- */
  --pr-night-card: #0B0E1A;

  /* ---- ink for the DEEP PURPLE hero band --------------------------------
     Measured against #5C4EB0 — the lightest point the band ever paints, which
     is the bloom over the gradient, not either gradient stop. Same ramp and
     the same gradient /blog and /features use, so all three purple heroes are
     one surface with one contrast budget.
     ⚠️ NOTHING ON THAT BAND MAY GET LIGHTER. The bloom sets the ceiling. */
  --pr-on-purple: #FFFFFF;          /* 6.62:1 */
  --pr-on-purple-body: #E4DEFF;     /* 5.10:1 — the floor here */
  --pr-on-purple-accent: #C4B8FF;   /* 3.67:1 — LARGE TEXT ONLY (>=24px) */
  --pr-on-purple-line: rgba(255, 255, 255, 0.34);   /* decorative hairlines */
  /* ⚠️ CONTROLS GET THE HEAVIER RIM, NOT THE DECORATIVE ONE. A control's
     boundary is "visual information required to identify a UI component" and
     owes 3:1 (SC 1.4.11); 0.34 measures 2.23:1 on the darkest ground the band
     paints. /blog hit this exact number in §CR13c. */
  --pr-on-purple-ctl: rgba(255, 255, 255, 0.54);    /* 3.50:1 */

  /* ---- the LIGHT chapter, straight from theme.css. Ratios vs #FFFFFF ---- */
  --pr-ink: #000000;                /* 21:1   — headings */
  --pr-ink-body: #4A4A52;           /* 8.6:1  — body copy */
  --pr-ink-faint: #6B6B75;          /* 5.1:1  — the floor, do not lighten */
  --pr-accent: #6A5ACD;             /* 5.31:1 — fills and large type */
  --pr-accent-deep: #453A8C;        /* 7.4:1  — brand as small text on light */
  --pr-accent-soft: #EEECFA;        /* brand tint, e.g. a ghost button's wipe */
  --pr-line: #E4E4E7;
  --pr-line-strong: #D4D4D8;

  /* ---- ink on the NAVY CTA card ----------------------------------------- */
  --pr-night: #0B0E1A;
  --pr-on-night: #FFFFFF;           /* 19.2:1 */
  --pr-on-night-body: #D6DBE6;      /* 14.1:1 */
  --pr-on-night-dim: #A8AFC0;       /* 8.7:1  — the floor there */

  /* The rounded lip every colour change wears. One value, so the curve is
     identical at all three boundaries — and identical to /features and /blog,
     which use this same clamp as `--ft-lip` / `--bl-lip`. */
  --pr-lip: clamp(28px, 3.4vw, 44px);

  background: var(--bg);
  color: var(--text);
  font-family: var(--rf-font-body, Georgia, serif);
  min-height: 100vh;
  width: 100%;
}

.pricing-page * { box-sizing: border-box; }

/* ===========================================================================
   THE ROUNDED LIP AT EVERY COLOUR CHANGE
   ---------------------------------------------------------------------------
   A band pulls itself UP over the one above by exactly its own radius, so the
   new colour arrives on a curve instead of a hard rule. Same idiom as
   `.ft-lip` (/features), `.blog-lip` (/blog) and `.about-lip` (/about) —
   reused rather than reinvented, so every seam on the site curves identically.

   ⚠️ ONLY WHERE THE COLOUR FAMILY CHANGES. Three boundaries wear it: purple →
   black, black → white, white → black. The seams INSIDE the white chapter
   (compare → FAQ → CTA) are one ground and get nothing.

   ⚠️ `z-index` IS REQUIRED, NOT DECORATION. The negative margin makes these
   bands overlap; without a stacking order the band ABOVE paints over the very
   curve it is meant to reveal.

   ⚠️ AND THE LIPPED ELEMENT MUST BE OPAQUE, which is why `.pricing-decide` sets
   a background it appears to already have. The page ground is black too, but
   a transparent band would reveal the PAGE at the curve, not itself — and the
   band above it is purple, so the curve simply would not exist. */
.pricing-page .pricing-lip {
  border-radius: var(--pr-lip) var(--pr-lip) 0 0;
  margin-top: calc(-1 * var(--pr-lip));
  position: relative;
  z-index: 2;
}

/* ---- the four grounds ---------------------------------------------------- */

/* ⚠️ THE GRADIENT IS ON THE WRAPPER, NOT ON `.pricing-hero`. The pill nav is
   an in-flow <header> above the hero (this page does not use the fixed
   portalled <LandingNav>), so a hero-only background would leave the bar
   sitting on black above the purple. One element for both also keeps the
   radial bloom a single light source — two would each restart it at the seam.

   ⚠️⚠️ THIS BAND *WAS* CONTENT-SIZED ON PURPOSE, AND THE OPERATOR OVERRULED IT.
   The recorded reasoning was: those other heroes can afford a full frame
   because what follows them is more marketing, whereas this is a PRICING page
   — the prices are what the visitor came for, and a one-frame hero puts all
   three tier cards below the fold. That trade is REAL and it is now taken
   knowingly: the operator reported the white sliver at the bottom of this
   page as a defect and asked for a hero that fills the frame with nothing
   overlapping it. **The cost is that no price is on screen at first paint.**
   The three stacked gaps the old note tracked (this hero's padding,
   `.pricing-decide`'s padding-top, the toggle's margin-bottom) no longer sit
   in a fold budget, so trimming them buys nothing — if the prices are ever
   wanted back above the fold, this is the rule to revert, not those. */
/* ⚠️⚠️ THE GROUND IS NOT PAINTED HERE ANY MORE — `.rf-hero-ground` in theme.css
   does it. That is the HOME hero's surface (bloom + dot field + vignette),
   now shared with `/features`, `/blog` and `/about` (operator: "implement the
   background of home hero section into hero section of all pages … except
   demo"); the class is on `.pricing-hero-band` in the JSX.
   ⚠️ DO NOT RE-ADD A `background` HERE — this file loads after theme.css, so a
   local declaration wins silently and only this page drifts.
   ⚠️ THE SHARED LAYERS ARE SIZED IN PERCENTAGES OF THIS BOX, WHICH IS WHY THIS
   BAND KEEPS ITS CONTENT HEIGHT SAFELY. Unlike /features and /blog it is NOT
   `100svh` (see the note above — the prices have to be reachable), so the
   ramp and both blooms simply compress into a shorter band rather than being
   cropped. Nothing here needs a height to make the ground work. */
.pricing-hero-band {
  position: relative;
  z-index: 1;
  color: var(--pr-on-purple);

  /* ⚠️ EXACTLY THE LIP, AND THAT IS THE WHOLE POINT — not a spacing value.
     The black band covers the bottom `--pr-lip` of this one, so anything less
     puts the hero's last element under the curve. Stated in terms of the
     token so the two cannot drift apart. */
  /* ⚠️ THE BAR IS FIXED AND PORTALLED NOW, so this band pays its height as top
     padding — the `--rf-nav-reserve` idiom /features and /about already used.
     It used to CONTAIN the bar as an in-flow header. */
  padding-top: var(--rf-nav-reserve, 76px);
  padding-bottom: var(--pr-lip);

  /* ⚠️ ONE FRAME — the formula is shared (`.rf-hero-ground--frame` in
     theme.css); this only says which lip the page uses. Read that note before
     changing either half. ⚠️ DO NOT add a local `min-height`: this file loads
     after theme.css and would win silently. */
  --rf-hero-lip: var(--pr-lip);
  display: flex;
  flex-direction: column;

  /* Ink for everything on the purple. Rules inside consume these names and
     follow for free; the nav pill re-points them again below, because it is a
     white island sitting ON this band. */
  --text: var(--pr-on-purple);
  --text-dim: var(--pr-on-purple-body);
  --text-faint: var(--pr-on-purple-body);
  --card: rgba(255, 255, 255, 0.10);
  --card-2: rgba(255, 255, 255, 0.14);
  --card-border: var(--pr-on-purple-line);
  --card-border-hover: var(--pr-on-purple-ctl);
}

/* THE DECISION BAND — the head of the white chapter.
   ⚠️ THE CLASS IS `.pricing-decide`, NOT `.pricing-dark` ANY MORE. It was
   renamed rather than left lying, because a class called "dark" painting white
   is exactly the kind of thing the next person trusts and gets wrong.
   ⚠️ IT IS THE ONLY ONE OF THE TWO WHITE WRAPPERS THAT CARRIES A LIP. The
   purple -> white seam is here; `.pricing-light` below it is the SAME ground,
   so a curve there would reveal white over white and cost a 44px overlap for
   nothing (one ground, one chapter — the rule this site applies everywhere).
   ⚠️ Only the TOP padding clears a lip now. The bottom used to clear the white
   chapter pulling up over it; that seam no longer exists. */
.pricing-decide {
  background: var(--bg);
  padding-top: calc(var(--pr-lip) + clamp(18px, 2.2vw, 30px));
  padding-bottom: 0;
}

/* ⚠️ NO LIP, NO TOP PADDING FOR ONE — same ground as `.pricing-decide` above,
   so this is a continuation of that chapter, not a new band.
   `.pricing-compare` brings its own breathing room. */
/* ⚠️ THE TOKEN RE-POINTS THAT USED TO LIVE HERE ARE GONE — the root block now
   declares exactly these values, so restating them was pure duplication and a
   place for the two to drift apart. Only the background stays, because this
   wrapper still has to paint (the seam above it is a real element boundary). */
.pricing-light {
  background: #FFFFFF;
  padding-top: 0;
}

/* ⚠️ THE LAST LIP IS ON `.home-closing`, NOT ON `.site-footer`. The footer is
   a shared component six pages render; the closing wrapper is a plain div this
   page's own GlobalFooter owns.

   ⚠️ `overflow: hidden` IS LOAD-BEARING AND NOWHERE ELSE HERE. `.home-closing`
   is transparent, so the curve is not painted by it — the footer paints its
   own opaque navy and would go straight over the rounded corners without a
   clip. Nothing inside is sticky, so the clip costs nothing. */
.pricing-page .home-closing {
  border-radius: var(--pr-lip) var(--pr-lip) 0 0;
  margin-top: calc(-1 * var(--pr-lip));
  overflow: hidden;
  position: relative;
  z-index: 2;
}

/* ⚠️⚠️ THE CLOSING ZONE IS **TWO** PAINTED SURFACES, AND THIS PAGE WAS ONLY
   GETTING ONE OF THEM RIGHT. `.home-closing` is transparent; what fills it is
   `.site-footer` (its own #0B0E1A navy) on top of `.rentfyv-hero` (the giant
   RENTFYV wordmark). `.rentfyv-hero`'s BASE rule in Landing.css is a literal
   `background: #000` — and every other page overrides it:
       .home-page .rentfyv-hero { background: var(--rf-surface-ink, #111114) }
       .blog-page .rentfyv-hero { background: var(--rf-surface-ink, #111114) }
   `/pricing` carries neither class, so it fell through to that literal and the
   bottom 348px of the page rendered PURE BLACK under a navy footer — two
   visibly different darks stacked, which is what the operator reported.

   ⚠️ #111114 IS NOT THE FOOTER'S #0B0E1A, AND THAT IS CORRECT. The two are
   deliberately a step apart on every page that ships today; the defect was the
   size of the gap here (#000 vs #0B0E1A), not that there is one. Matching the
   other pages is the whole point — do not "improve" this to the navy unless
   home, blog, about and features all move with it. */
.pricing-page .rentfyv-hero { background: var(--rf-surface-ink, #111114); }

/* The eyebrow badge was a near-black chip with a #1F1F1F rim — both invisible
   on purple. It becomes a white wash, the same device /features' hero uses. */
.pricing-hero-band .pricing-eyebrow-badge {
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid var(--pr-on-purple-line);
}

/* ⚠️ LARGE-TEXT FLOOR, AND IT IS ONLY LEGAL BECAUSE OF THE SIZE. The muted
   half of the headline runs 62px/600 down to 28px at the smallest breakpoint,
   so 3:1 is its floor and 3.67 clears it. Nothing smaller on this band may
   use this token — that is what `--pr-on-purple-body` is for. */
.pricing-hero-band .pricing-hero-muted { color: var(--pr-on-purple-accent); }

/* The trial button keeps its white pill (it is the loudest thing on the band
   and white is the strongest surface available) but loses its purple glow —
   a purple shadow on a purple ground is a shadow nobody can see. */
.pricing-hero-band .pricing-trial-btn {
  box-shadow: 0 10px 30px -8px rgba(10, 6, 40, 0.55);
}
.pricing-hero-band .pricing-trial-btn:hover {
  background: #fff;
  box-shadow: 0 14px 36px -8px rgba(10, 6, 40, 0.7);
}

/* ⚠️ THE STATE BANNERS WERE PURPLE-ON-BLACK AND ARE NOW PURPLE-ON-PURPLE.
   `rgba(170,110,255,0.12)` over near-black read as a tinted card; over this
   band it is the band. Both banners move to a white wash and keep their
   MEANING in the ink and the rim, not in a fill nobody can see. */
.pricing-hero-band .pricing-gate-banner {
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid var(--pr-on-purple-ctl);
  color: var(--pr-on-purple);
}
/* The expired banner has to stay legible AND stay red. #FFB3B5 on this band
   measures 4.72:1; the rim carries the semantic, the ink carries the message. */
.pricing-hero-band .pricing-gate-banner.is-expired {
  background: rgba(255, 90, 95, 0.22);
  border-color: rgba(255, 154, 157, 0.75);
  color: #FFD9DA;                   /* 5.86:1 */
}
.pricing-hero-band .pricing-select-error { color: #FFD9DA; }
.pricing-hero-band .pricing-manage-msg { color: #C7F5DF; }   /* 5.4:1 */

.pricing-hero-band .pricing-manage-bar {
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid var(--pr-on-purple-line);
  color: var(--pr-on-purple-body);
}
.pricing-hero-band .pricing-hero :focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 3px;
}

/* ⚠️⚠️ `margin-block: auto`, NOT `flex: 1` + `justify-content: center` — AND
   THE DIFFERENCE IS A BUG THAT ONLY LOOKING FINDS. /blog centres its hero the
   second way, so that was the obvious thing to copy; here it breaks the page.
   The two heroes have different DOM: /blog's hero children are all blocks,
   this one's FIRST child is `.pricing-eyebrow-badge`, an `inline-flex` pill.
   Making its parent a flex column turns that pill into a flex ITEM, and the
   default `align-items: stretch` blew it to the full 1180px — a full-width
   slab reading "* PRICING" where a small chip belongs. Every geometry
   assertion still passed (right height, no overflow, no white in the frame);
   the screenshot is what caught it, exactly as §CH3 records.

   Keeping `.pricing-hero` a BLOCK and letting auto margins absorb the free
   space centres it just as well and leaves every child rendering exactly as it
   did before — the pill stays shrink-wrapped, the headline stays full-width.
   ⚠️ IT ALSO REMOVES THE NEED FOR A SHORT-FRAME GUARD. Auto margins only
   absorb POSITIVE free space; when the content is taller than the band they
   compute to 0 and the overflow goes downward, where scrolling can reach it.
   `justify-content: center` has to be undone by hand at `max-height: 560px`
   (as /blog and /about do) or it pushes the headline off the TOP. */
.pricing-hero-band > .pricing-hero {
  flex: 0 0 auto;
  margin-block: auto;
}

/* ----- Hero -----
   ⚠️ SYMMETRIC, BECAUSE CENTRING MAKES ASYMMETRIC PADDING A SKEW. This was
   `clamp(44px,5vw,64px)` top / `clamp(28px,3.2vw,40px)` bottom — sensible for
   a block that starts under a bar, meaningless for one that is now vertically
   centred, and measurably wrong: `justify-content: center` centres within the
   CONTENT box, so the difference lands as a downward shift. Same correction
   /blog's hero needed when it became a flex item.
   ⚠️ The old note here tracked this padding as part of a FOLD BUDGET (keeping
   the first tier card above 800px). That budget no longer exists — see the
   band rule above; the hero is the whole frame now, deliberately. */
.pricing-hero {
  text-align: center;
  padding: clamp(28px, 3.6vw, 48px) 32px;
}

.pricing-eyebrow-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--card);
  border: 0.5px solid #1F1F1F;
  padding: 7px 18px;
  border-radius: 24px;
  margin-bottom: 28px;
}

.pricing-eyebrow-star {
  color: var(--text);
  font-size: 14px;
  line-height: 1;
}

.pricing-eyebrow-badge > span:last-child {
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
}

.pricing-hero-title {
  color: var(--text);
  font-size: 62px;
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0;
}
.pricing-hero-muted { color: var(--text-faint); }
.pricing-hero-sub {
  color: var(--text-dim);
  font-size: clamp(15px, 1.6vw, 17px);
  font-weight: 500;
  line-height: 1.6;
  max-width: 54ch;
  margin: 18px auto 0;
}

/* ----- Billing toggle (Monthly / Yearly) -----
   ⚠️ `margin-top: 0`, NOT the 34px it carried in the hero. This control moved
   bands: it used to hang off the hero's sub-line, and it now OPENS the black
   decision band, whose own `padding-top` owns that gap. Leaving the margin
   would double the space and push the cards further below the fold — which is
   the one thing the non-full-frame hero above exists to prevent. */
.pricing-billing {
  display: flex;
  justify-content: center;
  margin-top: 0;
  margin-bottom: clamp(16px, 2vw, 26px);
}
/* ⚠️ THE TRACK IS #F5F5F5 ON WHITE — 1.05:1 of fill, so its edge is the rim.
   `--line-ctl` rather than the card hairline, because this is a CONTROL and
   its boundary owes 3:1. */
.pricing-billing-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: #F5F5F5;
  border: 1px solid var(--line-ctl);
  border-radius: 999px;
  padding: 5px;
}
.billing-opt {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  padding: 10px 22px;
  border-radius: 999px;
  transition: background 0.25s ease, color 0.25s ease;
}
.billing-opt:hover { color: var(--text); }
/* ⚠️ THE SELECTED OPTION WAS A WHITE PILL — on a now-white toggle that is no
   selection at all. It inverts to the brand fill: white label 5.31:1, and the
   pill reads as chosen against both the #F5F5F5 track and the white band.
   This is the third time on this site a "chosen" state written as a white pill
   has had to be re-authored after a ground change (§CR13b on /blog was the
   same mistake twice) — if you move this page's ground again, check it. */
.billing-opt.is-active {
  background: var(--accent);
  color: #FFFFFF;
}
.billing-save {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--pr-accent-deep);
  background: var(--accent-soft);
  padding: 2px 8px;
  border-radius: 999px;
}
/* ⚠️ WHEN YEARLY IS SELECTED THE BADGE INVERTS TO A WHITE PILL WITH PURPLE
   TEXT, so "Save 30%" still reads as the brand rather than going white-on-
   white-alpha inside the brand tab (which is what it did, and which made the
   one number the offer turns on the quietest thing in the control). Purple on
   white is 7.4:1, and the white pill is 5.31:1 against the brand tab it sits
   in. A brand tint on a brand fill would simply disappear. */
.billing-opt.is-active .billing-save {
  color: var(--pr-accent-deep);
  background: #FFFFFF;
}

/* ----- Single top-of-page free-trial CTA ----- */
.pricing-trial-cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  max-width: 620px;
  margin: 28px auto 0;
}
.pricing-trial-btn {
  background: #FAFAFA;
  color: #0A0A0A;
  border: none;
  border-radius: 30px;
  padding: 14px 34px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 8px 26px -6px rgba(170, 110, 255, 0.5);
  transition: transform 0.1s ease, box-shadow 0.25s ease, background 0.15s ease;
}
.pricing-trial-btn:hover { background: #fff; box-shadow: 0 12px 32px -6px rgba(170, 110, 255, 0.7); }
.pricing-trial-btn:active { transform: translateY(1px); }
.pricing-trial-btn:disabled { opacity: 0.6; cursor: default; }
.pricing-trial-note { color: var(--text-dim); font-size: 13px; line-height: 1.5; text-align: center; }

/* ----- Onboarding / trial-expired gate banner ----- */
.pricing-gate-banner {
  max-width: 620px;
  margin: 26px auto 0;
  padding: 12px 20px;
  border-radius: 12px;
  background: rgba(170, 110, 255, 0.12);
  border: 1px solid rgba(170, 110, 255, 0.35);
  color: #d9c7ff;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.5;
  text-align: center;
}
.pricing-gate-banner.is-expired {
  background: rgba(255, 90, 95, 0.12);
  border-color: rgba(255, 90, 95, 0.4);
  color: #ffb3b5;
}
.pricing-select-error {
  max-width: 620px;
  margin: 14px auto 0;
  text-align: center;
  color: #ff8a8d;
  font-size: 13px;
  font-weight: 500;
}

/* ----- Manage mode (logged-in plan switcher) ----- */
.pricing-manage-bar {
  max-width: 720px;
  margin: 26px auto 0;
  padding: 12px 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--card-border, #1f1f1f);
  color: var(--text-dim);
  font-size: 14px;
}
.pricing-manage-bar strong { color: var(--text); font-weight: 700; }
.pricing-manage-back {
  flex-shrink: 0;
  background: #fafafa;
  color: #0a0a0a;
  border: none;
  border-radius: 999px;
  padding: 8px 16px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease;
}
.pricing-manage-back:hover { background: #fff; }
.pricing-manage-msg {
  max-width: 620px;
  margin: 14px auto 0;
  text-align: center;
  color: #6ee7b7;
  font-size: 13px;
  font-weight: 600;
}
/* The card representing the user's current package gets a clear ring. */
.tier-card--current {
  outline: 2px solid rgba(170, 110, 255, 0.7);
  outline-offset: 2px;
}

/* ----- Tier cards (2x2 grid) ----- */
.pricing-tiers-section {
  padding: 0 32px 80px;
  display: flex;
  justify-content: center;
}

/* Single horizontal row of plans, equal height. max-width is sized so each
   card keeps the same ~280px width it had in the original 4-plan layout
   (3 cards × 280 + gaps), centered in the section rather than stretched. */
.pricing-tiers {
  width: 100%;
  max-width: 900px;
  display: flex;
  align-items: stretch;          /* equal-height cards */
  justify-content: center;
  gap: 22px;
  padding: 44px 8px;             /* room for the focus scale-up + the badge */
}

/* ⚠️ A FOURTH CARD WIDENS THE ROW; IT DOES NOT SHRINK THE OTHER THREE. The
   catalog can hold three or four plans on sale (PlanController::MIN_ACTIVE /
   MAX_ACTIVE), and the cards are flex children of a capped row — so without
   this, activating a fourth plan silently squeezes every card to ~210px and
   the feature lines start wrapping mid-phrase on all of them. 4 × 280 + 3
   gaps + the padding, the same arithmetic the 900px above uses for three. */
.pricing-tiers--four { max-width: 1204px; }

/* ⚠️ THE BASE CARD IS THE LIGHT ONE NOW (Starter). It was `#0F0F12` with a
   purple rim and a purple GLOW — a glow is a dark-ground device; on white it
   is a lilac haze around the card rather than light coming off it. The
   elevation is a neutral drop shadow instead, and the edge is a real hairline,
   because a white card on a white band has no fill difference to rely on. */
.tier-card {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  background: var(--card);
  border: 1px solid var(--card-border-hover);
  border-radius: 20px;
  padding: 36px 30px;
  display: flex;
  flex-direction: column;
  box-shadow: 0 12px 32px rgba(16, 16, 20, 0.08);
  /* Default = the slightly dimmed/blurred "out of focus" state. One card is
     pulled into focus at a time (see the focus rules below).

     ⚠️⚠️ 0.65, NOT THE 0.55 THIS SHIPPED WITH. A group opacity composites BOTH
     the ink and the card face toward the band behind them, so the resting
     cards lose real contrast. MEASURED IN THE LIVE APP, resting failures on
     this white band: **0.55 → 12 nodes, 0.65 → 3**. Ten percentage points of
     dim buys nine nodes back, and the 2.5px blur — untouched — is doing most
     of the "out of focus" work anyway. Do not push this back to 0.55.

     ⚠️ THREE NODES STILL FAIL AT 0.65 AND MORE OPACITY WILL NOT FIX THEM, so
     do not chase it: `.tier-description` and `.tier-price-period` on the
     non-focused Starter card (3.46 each — 0.80 would clear those), and
     `.tier-cta` at **2.71**, which is structural. A SATURATED FILL CANNOT
     SURVIVE THIS: white-on-brand dimmed toward a white band still measures
     3.42 at 0.80, because the fill washes toward the ground faster than the
     white label can. The only real fixes are exempting the button from the
     group opacity (a sharp button on a blurred card, which looks broken) or
     dropping the effect. All three resolve the instant the card is hovered or
     focused, and the effect is off entirely at <=1024px and on touch. */
  opacity: 0.65;
  filter: blur(2.5px);
  transform: scale(1);
  transform-origin: center center;
  transition:
    filter 0.28s ease,
    opacity 0.28s ease,
    transform 0.28s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.28s ease,
    border-color 0.25s ease;
  will-change: transform, filter, opacity;
}

/* ----- Hover-focus (Google-Flow style), pure CSS via container :hover -----
 * Default (cursor NOT in the row): the Growth "Best Deal" card is the sharp,
 * focused one; every other card stays dimmed + lightly blurred.
 * On row hover: only the hovered card is in focus; the rest (incl. Growth)
 * dim — so the focus follows the cursor and returns to Growth on leave. */
/* ⚠️ `:focus-within` IS THE KEYBOARD HALF OF THIS INTERACTION, AND IT WAS
   MISSING. A keyboard user never fires `:hover`, so tabbing to a plan's
   Subscribe button left them operating a card still sitting at 0.55 opacity
   behind a 2.5px blur — the page's core content, unreadable, for the one
   input method that cannot fix it by moving the mouse. The effect itself is
   untouched: same opacity, same blur, same scale, same default-focus card.

   ⚠️ AND `:not(:focus-within)` HAS TO JOIN `:not(:hover)` ON THE FIRST
   SELECTOR. Without it the Growth card stays sharp while focus is elsewhere
   in the row, so two cards read as focused at once and the whole point of the
   effect — one card at a time — is lost. */
.pricing-tiers:not(:hover):not(:focus-within) .tier-card--default-focus,
.pricing-tiers:hover .tier-card:hover,
.pricing-tiers:focus-within .tier-card:focus-within {
  opacity: 1;
  filter: none;
  transform: scale(1.03);
  z-index: 2;
  /* ⚠️ THE FOCUSED STATE IS THE BRAND RIM PLUS REAL ELEVATION, not a glow.
     The old value was a 70px purple bloom, which on white is a lilac smudge
     rather than a lift — and the rim was `rgba(...,0.55)`, i.e. 1.6:1 against
     the band. The brand rim measures 5.31:1, so "which card am I on" is
     carried by something you can actually see. */
  border-color: var(--accent);
  box-shadow: 0 24px 56px rgba(16, 16, 20, 0.16);
}

/* ⚠️⚠️ THE RECOMMENDED TIERS ARE NAVY NOW, AND THIS IS THE INVERSION THE WHOLE
   BAND CHANGE TURNS ON. Growth and Pro were a white radial-gradient face
   built to shout against a black band. On a white band that face IS the band:
   the two tiers the page most wants you to buy would have dissolved into the
   ground while Starter — the entry tier, and the only dark card — became the
   loudest object on screen. Exactly backwards.
   They take `--pr-night-card`, the same navy this page's closing CTA card and
   /blog's and /about's already use, so "this is the important surface" reads
   the same everywhere on the site. The ink tokens are re-pointed on the card
   itself so every child rule follows for free. */
.tier-card-highlight {
  background: var(--pr-night-card);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 20px 48px rgba(11, 14, 26, 0.28);

  --text: #FFFFFF;              /* 19.2:1 on the navy */
  --text-dim: #D6DBE6;          /* 14.1:1 */
  --text-faint: #A8AFC0;        /* 8.7:1 — the floor here */
  --card-border: rgba(255, 255, 255, 0.12);
}

.tier-card-highlight:hover {
  border-color: rgba(255, 255, 255, 0.28);
}

/* ⚠️ THE FOCUSED NAVY CARD TAKES A PURPLE RIM TOO, so all three cards mark
   focus the same way. It is the ON-DARK brand step, not `--accent`: this site
   runs a two-value brand ramp precisely because #6A5ACD is only 3.62:1 on
   near-black, and a RIM is a line, not a fill. #8B7BE0 measures **5.44:1** on
   the navy — near-identical prominence to the white cards' #6A5ACD at 5.31:1
   on white, which is what makes the three read as one treatment.
   ⚠️ The two values are never seen side by side anyway: only ONE card is
   focused at a time, so there is nothing to compare them against.
   ⚠️ This rule must stay AFTER the shared focus rule above, which sets
   `border-color: var(--accent)` — same specificity, so order decides. */
.pricing-tiers:not(:hover):not(:focus-within) .tier-card-highlight.tier-card--default-focus,
.pricing-tiers:hover .tier-card-highlight:hover,
.pricing-tiers:focus-within .tier-card-highlight:focus-within {
  border-color: #8B7BE0;
  box-shadow: 0 28px 60px rgba(11, 14, 26, 0.38);
}

/* "Best Deal" badge floats above Growth's top edge. Black inverse of the
 * white card body so it stays legible. Only one card wears it — Pro is
 * white too but stays unbadged. */
/* ⚠️ THE BADGE STRADDLES TWO GROUNDS, WHICH IS WHY IT IS THE BRAND. It floats
   on the Growth card's top edge — half on the navy card, half on the white
   band — so neither a black pill (invisible on navy) nor a white one
   (invisible on the band) works. The brand carries white at 5.31:1 and reads
   as a deliberate flag against both. */
.tier-badge {
  position: absolute;
  top: -14px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--accent);
  color: #FFFFFF;
  padding: 6px 18px;
  border-radius: 999px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 8px 22px -4px rgba(16, 16, 20, 0.28);
  white-space: nowrap;
  z-index: 1;
}

/* ⚠️ EVERY `.tier-card-highlight .x { color: ... }` OVERRIDE THAT USED TO SIT
   IN THIS BLOCK IS GONE, DELIBERATELY. They existed to put DARK ink on a white
   highlight card; the card is navy now, so the base rules' `var(--text)` /
   `var(--text-dim)` already resolve correctly through the tokens re-pointed on
   `.tier-card-highlight` itself. Re-adding one would put near-black text back
   on the navy. Only overrides carrying a LITERAL survive below. */
.tier-name {
  color: var(--text);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 12px;
}

.tier-description {
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.55;
  margin: 0 0 26px;
  min-height: 44px;
}

/* Price block wraps the optional struck "was" line + the live price. */
.tier-price-block { margin-bottom: 24px; }

.tier-price-was {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 6px;
}
.tier-price-strike {
  color: var(--text-dim);
  font-size: 19px;
  font-weight: 600;
  text-decoration: line-through;
  text-decoration-color: #FF5A5F;   /* prominent red strike */
  text-decoration-thickness: 3px;
}
/* ⚠️ ONE SOLID BRAND PILL ON ALL THREE CARDS. It went through three versions:
   `#C9A6FF` on a 0.18 wash (tuned to glow on black, ~2:1 on a white card),
   then a light `--accent-soft` tint with brand-deep ink, and now the solid
   brand with a white label — which is the only one that reads as PURPLE at a
   glance rather than as "a pale chip with dark writing on it".
   Measured: white label **5.31:1** on the fill; the pill itself **5.31:1**
   against the white cards and **3.62:1** against the navy one, so it is
   clearly a badge on every card it can land on.
   ⚠️ THIS IS WHY THE `.tier-card-highlight` OVERRIDE BELOW IT IS GONE — the
   base now produces exactly what the navy card needs, and keeping a duplicate
   rule that sets the same two values is just somewhere for them to drift. */
.tier-price-save-tag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #FFFFFF;
  background: var(--accent);
  padding: 2px 8px;
  border-radius: 999px;
}
/* (The `.tier-card-highlight .tier-price-save-tag` override that used to sit
   here is deliberately gone — see the note on `.tier-price-save-tag`. The base
   rule now paints the solid brand pill every card wants.) */

.tier-price {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 0;
}

.tier-price-amount {
  color: var(--text);
  font-size: 50px;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
}

.tier-price-period {
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 500;
}

.tier-features {
  list-style: none;
  margin: 0 0 28px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
}

.tier-features li {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--text);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.4;
}

/* "/ month" qualifier on the car-allowance feature (yearly billing). */
.tier-feat-permo {
  color: var(--text-dim);
  font-weight: 500;
  font-size: 0.9em;
}

/* ⚠️ THE TICK DISC IS THE BRAND ON THE LIGHT CARD, NOT NEAR-BLACK. `#1A1A1A`
   was the inverse of a dark card; on white it is the heaviest object in the
   feature list, louder than the price. The brand carries the white tick at
   5.31:1 and ties the list to the CTA below it. */
.tier-check {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent);
  color: #FFFFFF;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* On the navy card the disc inverts: a white-alpha fill with a white tick. A
   brand disc on navy is 3.62:1 and muddies into the card. */
/* ⚠️ THE SAME BRAND DISC AS THE WHITE CARDS, not a white-alpha one. It was a
   16% white wash so it would sit quietly on the navy; making it the brand
   means all three cards' feature lists are marked identically, which is what
   you want when the tick means the same thing on every tier. Measured on the
   navy: fill 3.62:1 against the card, white tick 5.31:1 on the fill. */
.tier-card-highlight .tier-check {
  background: var(--accent);
  color: #FFFFFF;
}

/* ⚠️⚠️ THE PRIMARY CTA WAS A WHITE PILL — INVISIBLE ON THE WHITE STARTER CARD.
   This is the button the whole page exists to get clicked, and it is the one
   literal the band change would have erased most completely. It becomes the
   brand fill every other primary CTA on the site uses: white label 5.31:1,
   and the fill itself is 3.20:1 against the card so the control has an edge.
   ⚠️ HOVER DARKENS. Lightening a filled brand button drops its white label
   below AA — the shipped defect on the home/features CTA (issue 39), and
   there is no reason to reproduce it here. #574AB0 holds white at 6.97:1. */
.tier-cta {
  width: 100%;
  background: var(--accent);
  color: #FFFFFF;
  padding: 14px;
  border-radius: 30px;
  border: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 6px 18px -4px rgba(106, 90, 205, 0.35);
  transition: background 0.15s ease, transform 0.1s ease, box-shadow 0.25s ease;
}

.tier-cta:hover {
  background: #574AB0;
  box-shadow: 0 9px 24px -4px rgba(106, 90, 205, 0.5);
}
.tier-cta:active { transform: translateY(1px); }

/* ⚠️ ON THE NAVY CARD THE BUTTON IS THE BRAND, NOT A WHITE PILL. Measured on
   the card it actually sits on: white label 5.31:1, and the fill 3.62:1
   against the navy — both clear, so it reads as the page's primary action in
   the page's own colour rather than as a second white surface competing with
   the two white cards either side of it.

   ⚠️ AND ITS HOVER LIGHTENS, WHICH IS THE OPPOSITE OF EVERY OTHER BRAND BUTTON
   ON THIS SITE — deliberately, because this one is on a DARK card. The house
   hover `#574AB0` keeps the label fine (6.97:1) but drops the FILL to 2.76:1
   against the navy, i.e. the control loses its edge exactly when you point at
   it. `#7565CF` goes the other way: label 4.65:1, fill 4.13:1. Darkening is
   right on a light band and wrong here; do not "align" this with the others. */
.tier-card-highlight .tier-cta {
  background: var(--accent);
  color: #FFFFFF;
  box-shadow: 0 6px 18px -4px rgba(0, 0, 0, 0.45);
}

.tier-card-highlight .tier-cta:hover {
  background: #7565CF;
  box-shadow: 0 9px 24px -4px rgba(0, 0, 0, 0.6);
}

/* Trial + Subscribe stacked. Primary = the existing solid .tier-cta; secondary
   ("Subscribe") = a quieter ghost/outline button. */
.tier-cta-group { display: flex; flex-direction: column; gap: 8px; }
.tier-cta:disabled { opacity: 0.55; cursor: default; }
/* ⚠️ WHITE-ALPHA BORDERS ARE A DARK-GROUND DEVICE. On the white Starter card
   `rgba(255,255,255,0.28)` is an invisible button outline; the ghost takes a
   real hairline and brand ink there, and keeps the white-alpha version on the
   navy cards. Both states of each pair are stated — a ghost whose resting
   colour moves without its hover colour is the bug /features hit three times. */
.tier-cta--secondary {
  background: transparent;
  color: var(--pr-accent-deep);
  border: 1px solid var(--line-ctl);
  box-shadow: none;
}
.tier-cta--secondary:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  box-shadow: none;
}
.tier-card-highlight .tier-cta--secondary {
  color: #FFFFFF;
  border-color: rgba(255, 255, 255, 0.45);
}
.tier-card-highlight .tier-cta--secondary:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.75);
}

/* Phones: a single flat column, every card sharp (no focus interaction).

   ⚠️⚠️ THESE SELECTORS CARRY `.pricing-page` FOR A REASON — WITHOUT IT THE PAGE
   HAD 328px OF HORIZONTAL OVERFLOW ON EVERY PHONE. Pre-existing, and a nasty
   interaction rather than a typo: the `@media (max-width: 1024px)` block sits
   LATER in this file and sets `flex-wrap: wrap` + `flex-basis: calc(50% - 10px)`
   for its 2x2 tablet grid. Below 700px BOTH queries match, so at equal
   specificity the later one won — leaving `flex-direction: column` (from here)
   combined with `wrap` and a PERCENTAGE basis. In a column flex container
   `flex-basis` is a HEIGHT, so two cards filled the column and the third
   wrapped into a SECOND COLUMN, 328px off the right edge. The browser then
   shrink-to-fits the whole document to 54%, which also drops the layout
   viewport to 718px and stops `max-width: 700px` matching at all — so the
   symptom hides its own cause.

   `.pricing-page .pricing-tiers` is (0,2,0) and wins on specificity rather
   than position, which is what this file does everywhere else. Do not
   "simplify" the prefix away. */
@media (max-width: 700px) {
  .pricing-page .pricing-tiers {
    flex-direction: column;
    flex-wrap: nowrap;
    max-width: 440px;
    padding: 16px 8px;
  }
  .pricing-page .tier-card { flex-basis: auto; opacity: 1; filter: none; transform: none; }
}

/* Touch / no-hover devices: never blur — all cards sharp + full opacity,
 * no scale. `!important` so it wins regardless of viewport-width rules. */
@media (hover: none) {
  .tier-card {
    opacity: 1 !important;
    filter: none !important;
    transform: none !important;
  }
}

/* Reduced motion: keep the focus legible (opacity/blur) but drop the
 * scale-up and the transitions. */
@media (prefers-reduced-motion: reduce) {
  .tier-card { transition: none; }
  .pricing-tiers:not(:hover):not(:focus-within) .tier-card--default-focus,
  .pricing-tiers:hover .tier-card:hover,
  .pricing-tiers:focus-within .tier-card:focus-within { transform: none; }
}

/* ----- Risk reversal row (under the cards) ----- */
.pricing-risk {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px 28px;
  max-width: 900px;
  margin: -48px auto 0;
  padding: 0 24px;
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 600;
}
.pricing-risk span { display: inline-flex; align-items: center; gap: 8px; }
/* ⚠️ #4ade80 MEASURES 1.9:1 ON WHITE — picked to glow on near-black. #15803D
   is theme.css's own light-ground replacement, 5.02:1. */
.risk-ck { color: #15803D; font-weight: 800; }

/* ===========================================================================
   THE PROOF STRIP — the home page's CROSSING DIAGONAL RIBBONS
   ---------------------------------------------------------------------------
   The plain `.home-trust` name marquee that used to sit here is gone; this
   page now renders `<LandingMarquee>`, the two-ribbon X.

   ⚠️⚠️ THE BASE `.xmq` RULES IN Landing.css ARE THE PRE-LIGHT-THEME ONES —
   a `#0E0F12` backdrop with a BLACK strip A and a WHITE strip B. The purple +
   navy version in the reference is `.home-page`-scoped (Landing.css ~L4922),
   and `/pricing` is deliberately not a `.home-page` (see this file's header).
   So the colours have to be restated here, scoped to `.pricing-page`, or the
   strip arrives as a dark slab in the middle of a white chapter.

   ⚠️ VALUES ARE A DELIBERATE COPY of that block, kept identical so the two
   pages' ribbons cannot drift apart. Its own measurements, re-checked here:
     purple vs ground 4.95:1 · navy vs ground 17.9:1 · purple vs navy 3.6:1
     white on purple  5.31:1 · white on navy  19.2:1
   ⚠️ AND `.xmq`'s OWN BAND TINT COMES WITH THEM. `--rf-mist` is a whisper off
   white (~1.03:1) — not a band change, just enough to give the ribbons an
   edge to be clipped against. Copying the strips without it would leave them
   floating on bare white, which is not what the reference shows.
   ========================================================================= */
.pricing-page .xmq {
  margin-top: clamp(40px, 6vw, 64px);
  background: var(--rf-mist, #F1F8FD);
}
.pricing-page .xmq-strip--a {
  background: var(--rf-primary, #6A5ACD);
  box-shadow: 0 18px 50px -22px rgba(16, 16, 20, 0.45);
}
.pricing-page .xmq-strip--b {
  background: var(--rf-night, #0B0E1A);
  border-top-color: rgba(255, 255, 255, 0.08);
  border-bottom-color: rgba(255, 255, 255, 0.08);
}
/* ⚠️ BOTH RIBBONS CARRY WHITE WORDS. The base sheet gives strip B near-black
   ink because that strip is WHITE by default; on the navy it is invisible. */
.pricing-page .xmq-strip--a .xmq-item,
.pricing-page .xmq-strip--b .xmq-item { color: #FFFFFF; }

/* ⚠️ THE STAR IS THE RATING GOLD, NOT THE BRAND — matching the reference. It
   measures 3.26:1 on the purple and 11.8:1 on the navy, and it is decorative
   punctuation between phrases rather than a rating that must be read, so the
   large-text floor is the right one. `.xmq-star` is (0,1,0) in the base sheet
   and the `.home-page` variants cannot reach this page, so a single scoped
   rule is enough here. */
.pricing-page .xmq-star { color: #FFC107; }

/* ---- the eyebrow above the ribbons --------------------------------------
   ⚠️ ALL OF IT LIVES HERE, NOT IN Landing.css, BECAUSE ONLY THIS PAGE PASSES
   A LABEL. The element is rendered conditionally by the shared component, so
   scoping its styling to `.pricing-page` means the home marquee cannot be
   reached by any of it — no shared-file risk at all.

   ⚠️ IT IS ABSOLUTELY POSITIONED WITH ITS OWN HEADROOM, and that pairing is
   load-bearing. The two ribbons are `position: absolute; top: 50%` inside
   `.xmq`, i.e. vertically CENTRED whatever the band's height — so a label in
   normal flow would be crossed by them, and padding on `.xmq` would not move
   them (percentages on an abs child resolve against the padding box, so the
   centre does not shift). Extra HEIGHT is what buys clear ground at the top;
   the label then sits in it. Change one, re-check the other. */
.pricing-page .xmq--labelled {
  height: calc(clamp(190px, 21vw, 300px) + clamp(34px, 3.6vw, 52px));
}
.pricing-page .xmq-label {
  position: absolute;
  top: clamp(14px, 1.8vw, 24px);
  left: 0;
  right: 0;
  z-index: 3;                    /* above BOTH ribbons (they are z-index 1 and 2) */
  margin: 0;
  text-align: center;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* ⚠️ NOT `--text-faint`. This sits on `--rf-mist`, not on the page white,
     and it is 12px — so it owes 4.5:1 against the tint it actually paints on,
     which the body step clears at 8.4:1. The old strip's label used the faint
     step because it sat on a near-black ground where that value was safe. */
  color: var(--text-dim);
  pointer-events: none;          /* decoration over a moving band */
}

/* ⚠️⚠️ THIS BAND ONCE HAD NO FOCUS RULE AT ALL — found by walking the page
   with a real Tab, not by reading the CSS, because the defect was the ABSENCE
   of a rule. The billing toggle and all three Subscribe buttons fell through
   to Chrome's default `1px auto` amber ring, which passed on the old black
   ground and failed badly on the cards. Keep both halves below.
   ⚠️ THE POLARITY FLIPPED WITH THE CARDS. The band is white now, so its ring
   is the brand (5.31:1); it is the NAVY cards that need the white one. */
.pricing-decide :focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

/* ⚠️ A brand ring on the navy card measures 3.62:1 and reads as a smudge;
   white is 19.2:1 there. Same rule, opposite grounds. */
.pricing-decide .tier-card-highlight :focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 3px;
}

/* ----- Plan comparison table ----- */
.pricing-compare {
  max-width: 1000px;
  margin: 0 auto;
  padding: clamp(48px, 7vw, 80px) 24px;
}
.pricing-compare-head { text-align: center; margin-bottom: 36px; }
.pricing-compare-head h2 {
  font-size: clamp(26px, 3.4vw, 40px);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--text);
}
.pricing-compare-head p { color: var(--text-dim); margin: 10px 0 0; font-weight: 500; }
.pricing-compare-scroll { overflow-x: auto; }
.pricing-compare-table {
  width: 100%;
  border-collapse: collapse;
  min-width: 680px;
}
.pricing-compare-table th,
.pricing-compare-table td {
  padding: 16px 14px;
  text-align: center;
  border-bottom: 1px solid var(--card-border);
  font-size: 14px;
  color: var(--text);
}
.pricing-compare-table thead th { font-size: 15px; font-weight: 700; padding-bottom: 18px; }
.pricing-compare-table th.is-best { color: #fff; }
.pricing-compare-table td:first-child,
.pricing-compare-table th:first-child {
  text-align: left;
  color: var(--text-dim);
  font-weight: 600;
  width: 34%;
}
/* Subtle highlight on the recommended (Growth) column. */
.pricing-compare-table th:nth-child(3),
.pricing-compare-table td:nth-child(3) { background: rgba(255, 255, 255, 0.03); }
.cmp-yes { color: #4ade80; font-weight: 800; }
.cmp-no { color: var(--text-faint); }

/* ----- CTA Card ----- */
.pricing-cta-section {
  padding: 40px 32px 100px;
  display: flex;
  justify-content: center;
  /* 3D viewing context for the card's tilt (otherwise rotateX/Y looks flat). */
  perspective: 1000px;
}

.pricing-cta-card {
  position: relative;
  width: 100%;
  max-width: 920px;
  background: var(--card);
  border: 0.5px solid var(--card-border);
  border-radius: 24px;
  padding: 80px 40px;
  text-align: center;
  overflow: hidden; /* clip the glare to the rounded shape */
  transform-style: preserve-3d;
  will-change: transform;
}

/* Higher-specificity than the global card-hover rule in interactions.css (which
   sets `transition` on `.pricing-cta-card` and is imported last). Re-declare the
   glow transitions AND add `transform` so the tilt follow + ease-back are smooth
   instead of snapping. */
.pricing-cta-section .pricing-cta-card {
  transition: transform 0.15s ease-out,
              border-color 200ms ease,
              box-shadow 200ms ease,
              background-color 200ms ease;
}

/* Cursor-following white glare. Subtle + monochrome, clipped by the card's
   overflow:hidden + border-radius:inherit. pointer-events:none so the button
   underneath stays clickable. Only visible while the pointer is over the card. */
.pricing-cta-glare {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease-out;
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%),
    rgba(255, 255, 255, 0.12), transparent 40%);
  z-index: 0;
}
.pricing-cta-card.is-tilting .pricing-cta-glare {
  opacity: 1;
}

/* Card content sits ABOVE the glare overlay. */
.pricing-cta-title,
.pricing-cta-sub,
.pricing-cta-button {
  position: relative;
  z-index: 1;
}

.pricing-cta-title {
  color: var(--text);
  font-size: 48px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 0 0 18px;
}

/* ⚠️⚠️ THE ACCENT HALF OF THE HEADLINE IS THE BRAND, NOT WHITE-AT-60%.
   It was `color: var(--text); opacity: 0.6` — on the navy card that is plain
   white dimmed to a grey that exists nowhere in the palette, so the page's
   closing headline was the one place on the site where the accent carried no
   brand at all. That is what the operator reported.
   `--rf-primary-on-dark` is the site's step for TEXT or a LINE on a dark
   ground (§DD1), and it is byte-identical to what home's closing CTA uses on
   the SAME navy card — so the two cards now read as one treatment.
     #8B7BE0 on #0B0E1A = 5.50:1  (a 48px headline's floor is 3)
   ⚠️ AND THE OPACITY HAD TO GO WITH IT. Left at 0.6 the token composites to
   ~rgb(90,79,138) and measures 2.6:1 — a dimmed brand colour is not the brand
   colour, it is an unmeasured one. Anything that needs to read quieter here
   should go smaller or lighter in WEIGHT, which is what theme.css says about
   --rf-ink-faint for the same reason.
   ⚠️ SCOPED: `.cta-accent` is a bare single-class name and this page is its
   only consumer (checked). It stays scoped so it cannot become a third
   site-wide accent by accident. */
.pricing-cta-card .cta-accent {
  color: var(--rf-primary-on-dark, #8B7BE0);
  opacity: 1;
}

.pricing-cta-sub {
  color: var(--text-dim);
  font-size: 15px;
  font-weight: 400;
  line-height: 1.5;
  max-width: 540px;
  margin: 0 auto 28px;
}

/* ⚠️⚠️ THE BRAND, NOT `var(--text)`. On this card `--text` is `--pr-on-night`,
   i.e. WHITE — so the page's final call to action was a white pill with a
   near-black label, the only primary button on the site not wearing the brand
   and indistinguishable from a secondary. Operator-reported.
   These four values are byte-identical to `.home-page .home-final-cta-inner
   .home-cta-primary` (Landing.css) on the SAME `--rf-night` card, which is the
   point: five pages close on one button.
     white on #6A5ACD          5.31:1   (label)
     #6A5ACD on #0B0E1A        3.62:1   (surface vs its own card, floor 3)
   ⚠️ NOT A CONTRAST UPGRADE — A BRAND ONE. White-on-navy measured 19.2:1 and
   was never unreadable; it was off-palette. Do not "restore" it on contrast
   grounds. */
.pricing-cta-button {
  background: var(--rf-primary, #6A5ACD);
  color: var(--rf-ink-invert, #FFFFFF);
  box-shadow: 0 10px 34px rgba(106, 90, 205, 0.45);
  padding: 14px 36px;
  border-radius: 30px;
  border: 1px solid var(--rf-primary, #6A5ACD);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}

/* ⚠️ THE HOVER GOES LIGHTER, WHICH IS BACKWARDS FROM A LIGHT BAND AND CORRECT
   HERE — the direction follows the GROUND, not the button (§DD6). The house
   hover #574AB0 keeps the label fine but drops the FILL to 2.76:1 on this
   navy, so the control loses its edge exactly when you point at it. #7565CF
   measures label 4.65:1 and fill 4.14:1 — both clear, and the fill gets
   STRONGER. Same value as home's card, the nav's Book Demo and the blog
   hero's jump button. */
.pricing-cta-button:hover {
  background: #7565CF;
  border-color: #7565CF;
}
.pricing-cta-button:active { transform: translateY(1px); }

/* ===========================================================================
   BAND 3 — everything that had to be re-authored for the WHITE ground.
   ---------------------------------------------------------------------------
   Most of the chapter follows `.pricing-light`'s token re-point for free —
   `.pricing-compare-head h2`, the table's ink, `.home-section-title` and
   `.faq-answer-eyebrow` all read `var(--text)`/`var(--text-dim)` and simply
   flip. What is below is the residue: LITERAL colours the tokens cannot
   reach, plus the two dark surfaces that live on this band on purpose.
   ========================================================================= */

/* ---- the comparison matrix ---------------------------------------------- */

/* ⚠️ WAS `color: #fff` — A LITERAL, SO THE TOKEN FLIP COULD NOT REACH IT, and
   the recommended column's own heading went white on white. It is a 15px/700
   label, so its floor is 4.5; the brand-deep clears it at 8.54:1 on the tint
   below and doubles as the signal that this is the highlighted column. */
.pricing-light .pricing-compare-table th.is-best { color: var(--pr-accent-deep); }

/* ⚠️ AND ITS COLUMN WASH WAS `rgba(255,255,255,0.03)` — white-on-white, i.e.
   the highlight simply stopped existing. A brand tint replaces it, but the
   ALPHA is measured rather than picked: the column carries the green ✓ glyph
   at 14px, and at 0.12 the tint drops that tick to 4.31:1. 0.07 composites to
   ~#F5F4FC, where the tick holds 4.58:1 and the ✕ holds 4.81:1. Do not
   deepen this without re-measuring both marks. */
.pricing-light .pricing-compare-table th:nth-child(3),
.pricing-light .pricing-compare-table td:nth-child(3) {
  background: rgba(106, 90, 205, 0.07);
}

/* ⚠️ #4ADE80 MEASURES 1.9:1 ON WHITE. It was picked to glow on near-black.
   #15803D is theme.css's own light-ground replacement for it — 5.02:1 on
   white, 4.58:1 on the tinted column. The ✕ follows `--text-faint`. */
.pricing-light .cmp-yes { color: #15803D; }

/* ---- the closing CTA card ------------------------------------------------
   ⚠️ A DARK CARD ON A LIGHT BAND, DELIBERATELY — the same navy treatment
   /blog and /about give their closing CTA, so the site ends every page the
   same way. It gets LITERAL ink tokens of its own rather than inheriting the
   band's, because everything inside it reads `var(--text)`. */
.pricing-light .pricing-cta-card {
  background: var(--pr-night);
  border: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: 0 18px 48px rgba(16, 16, 20, 0.12);

  --text: var(--pr-on-night);
  --text-dim: var(--pr-on-night-body);
  --card-border: rgba(255, 255, 255, 0.10);
}

/* ---- the FAQ ------------------------------------------------------------
   ⚠️⚠️ `LandingFaq` IS SHARED WITH THE HOME PAGE AND /demo, AND IT CARRIES ITS
   OWN DARK TOKENS. `.home-faq` declares `--text: #fafafa` and friends on
   itself (Landing.css) precisely so it survives being reused outside
   `.home-page` — which means dropping it on a white band produces near-white
   ink on white unless every one of them is re-pointed. Everything here is
   therefore scoped to `.pricing-light` and CANNOT reach the other two pages.

   ⚠️ THE VALUES ARE A DELIBERATE COPY of Landing.css's `.home-page .faq-*`
   light block, not an invention — that treatment is already measured and
   shipped. Marked as a copy so the two cannot drift apart silently. */
.pricing-light .home-faq {
  background: #FFFFFF;

  --text: var(--pr-ink);
  --text-dim: var(--pr-ink-body);
  --text-faint: var(--pr-ink-faint);
  --card-2: #FAFAFA;
  --card-border: var(--pr-line);
  --card-border-hover: var(--pr-line-strong);

  /* ⚠️ THE ACCENT PAIR MOVES TO THE BRAND. `#a06eff → #5a82ff` was chosen to
     glow on near-black; it fills the active toggle and the "Talk to Us"
     button, both of which carry WHITE glyphs, and white on #a06eff is 3.0:1.
     The brand pair holds >=4.96:1 across its whole ramp. */
  --faq-accent: var(--pr-accent);
  --faq-accent-2: #5A5FE8;
}

/* Resting rows are white ON white, so the hairline is the only edge there is:
   it has to be the strong one. `--pr-line` (#E4E4E7) measures 1.13:1 here. */
.pricing-light .faq-q-item {
  background: #FFFFFF;
  border: 1px solid var(--pr-line-strong);      /* 1.48:1 */
}
.pricing-light .faq-q-item:hover {
  background: var(--pr-accent-soft);
  border-color: var(--pr-accent);
}

/* ⚠️ THE 4px INSET BAR MUST SURVIVE THE OVERRIDE. `box-shadow` is a shorthand,
   so restating it drops the `inset 4px 0 0 0 var(--faq-accent)` the shipped
   rule uses to mark the open row — and the fill alone cannot carry the state
   (#EEECFA on #FFFFFF is 1.11:1). Landing.css records losing this exact bar
   the same way. */
.pricing-light .faq-q-item.is-active {
  background: var(--pr-accent-soft);
  border-color: var(--pr-accent);
  box-shadow:
    inset 4px 0 0 0 var(--pr-accent),
    0 4px 16px rgba(16, 16, 20, 0.08);
}

.pricing-light .faq-q-num { color: var(--pr-accent-deep); }
.pricing-light .faq-q-text { color: var(--pr-ink-body); }
.pricing-light .faq-q-item:hover .faq-q-text,
.pricing-light .faq-q-item.is-active .faq-q-text { color: var(--pr-ink); }

/* The +/− toggle sat on a dark pill. */
.pricing-light .faq-q-toggle {
  background: #FFFFFF;
  border: 1px solid var(--pr-line-strong);
  color: var(--pr-ink-body);
}
.pricing-light .faq-q-item.is-active .faq-q-toggle {
  background: var(--pr-accent);
  border-color: var(--pr-accent);
  color: #FFFFFF;
}

/* ⚠️ THE ANSWER PANEL'S GRADIENT ENDS IN A LITERAL `#101013`, so re-pointing
   `--card-2` alone still leaves it fading to near-black. Restated whole. It
   takes the light grey the open row does — it IS that row's body — and gains
   the strong hairline, because a #F5F5F5 panel has no edge of its own on
   white. Its 4px accent bar (`.faq-answer-col::before`) is already the pair. */
.pricing-light .faq-answer-col {
  background: #F5F5F5;
  border: 1px solid var(--pr-line-strong);
  box-shadow: 0 18px 48px rgba(16, 16, 20, 0.12);
}
.pricing-light .faq-answer-q { color: var(--pr-ink); }          /* 18.1:1 */
.pricing-light .faq-answer-a { color: var(--pr-ink-body); }     /* 11.6:1 */
.pricing-light .faq-answer-eyebrow,
.pricing-light .faq-answer-eyebrow::before,
.pricing-light .faq-answer-eyebrow > span:first-child {
  color: var(--pr-accent);                                      /*  4.87:1 */
}
.pricing-light .faq-help-text { color: var(--pr-ink-faint); }   /*  4.84:1 */

/* ⚠️ Ghost numeral was `rgba(255,255,255,0.045)` — invisible on a light panel.
   Inverted to an ink wash, the value the home page uses. */
.pricing-light .faq-bignum { color: rgba(11, 14, 26, 0.05); }

/* ⚠️ THE INACTIVE PROGRESS DOTS ARE A CONTROL, NOT DECORATION — 3:1, not 0.
   They follow `--card-border-hover` (#D4D4D8), which measures 1.02:1 on the
   panel, i.e. an invisible tab in a tablist. `--pr-ink-faint` is the lightest
   step that clears it. The active dot is also 44px wide against 28px, so the
   state never rests on colour alone. */
.pricing-light .faq-dot { background: var(--pr-ink-faint); }
.pricing-light .faq-dot.is-active {
  background: linear-gradient(90deg, var(--pr-accent), #5A5FE8);
}

/* ⚠️ TWO FOCUS GROUNDS ON THIS BAND TOO, AND THE SECOND ONE IS THE EASY MISS.
   The chapter is white, so its ring is the brand purple — but the closing
   CTA's button sits on the NAVY card, where a purple ring measures ~2.3:1.
   /about shipped exactly that gap (§CS11) until the page was walked with a
   real Tab. Stated here rather than discovered there. */
.pricing-light :focus-visible {
  outline: 3px solid var(--pr-accent);
  outline-offset: 2px;
}
.pricing-light .pricing-cta-card :focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 3px;
}

/* ----- Footer ----- */
.pricing-footer {
  padding: 60px 32px 28px;
  border-top: 0.5px solid transparent;
}

.pricing-footer-inner {
  max-width: 1180px;
  margin: 0 auto;
}

.pricing-footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1.2fr;
  gap: 60px;
  margin-bottom: 60px;
}

.pricing-footer-brand p {
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.6;
  margin: 0 0 20px;
  max-width: 280px;
}

.pricing-socials {
  display: flex;
  gap: 10px;
}

.social-icon {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.social-icon:hover {
  background: #1A1A1A;
  border-color: var(--card-border-hover);
}

.pricing-footer-col h4 {
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  margin: 0 0 16px;
}

.pricing-footer-col span {
  display: block;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 400;
  line-height: 2.1;
  cursor: pointer;
  transition: color 0.15s ease;
}

.pricing-footer-col span:hover { color: var(--text); }

.pricing-footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 24px;
  border-top: 0.5px solid transparent;
}

.pricing-footer-bottom > span {
  color: var(--text-faint);
  font-size: 12px;
}

.pricing-footer-legal {
  display: flex;
  gap: 28px;
}

.pricing-footer-legal span {
  color: var(--text-faint);
  font-size: 12px;
  cursor: pointer;
  transition: color 0.15s ease;
}

.pricing-footer-legal span:hover { color: var(--text); }

/* ===== Responsive ===== */
/* Large tablets / small laptops */
@media (max-width: 1024px) {

  /* Symmetric — the hero is vertically centred now, so a 64/48 split is a
     16px downward skew rather than spacing (see the base rule). */
  .pricing-hero { padding: 40px 24px; }
  .pricing-hero-title { font-size: 48px; }

  .pricing-tiers-section { padding: 0 24px 64px; }
  /* Below desktop: wrap the row to a 2x2 grid and drop the blur-focus
     interaction entirely — all cards sharp + full opacity, no scale. */
  .pricing-tiers { flex-wrap: wrap; max-width: 760px; gap: 20px; }
  .tier-card { flex-basis: calc(50% - 10px); opacity: 1; filter: none; transform: none; }
  /* ⚠️ THE `:focus-within` BRANCH HAS TO BE NEUTRALISED HERE TOO. Below
     desktop every card is already sharp, so the scale-up is the only thing
     left to cancel — miss this selector and a tabbed card jumps 3% while its
     siblings do not. */
  .pricing-tiers:not(:hover):not(:focus-within) .tier-card--default-focus,
  .pricing-tiers:hover .tier-card:hover,
  .pricing-tiers:focus-within .tier-card:focus-within { transform: none; z-index: auto; box-shadow:
    0 0 40px -10px rgba(170, 110, 255, 0.30),
    0 22px 50px -10px rgba(0, 0, 0, 0.65); }

  .pricing-cta-section { padding: 32px 24px 80px; }
  .pricing-cta-card { padding: 64px 32px; }
  .pricing-cta-title { font-size: 38px; }

  .pricing-footer { padding: 56px 24px 24px; }
  .pricing-footer-grid {
    grid-template-columns: 1fr 1fr;
    gap: 40px;
  }
  .pricing-footer-brand { grid-column: span 2; }
}

/* Tablet — collapse nav links, stack tier cards */
@media (max-width: 820px) {

  .pricing-hero-title { font-size: 42px; }

  .pricing-tiers { max-width: 560px; }
  .tier-description { min-height: 0; }

  .pricing-cta-card { padding: 56px 28px; }
  .pricing-cta-title { font-size: 32px; }
}

/* Mobile */
@media (max-width: 640px) {

  .pricing-hero { padding: 32px 18px; }   /* symmetric — see the base rule */
  .pricing-eyebrow-badge { padding: 6px 14px; margin-bottom: 22px; }
  .pricing-hero-title { font-size: 32px; line-height: 1.1; }

  .pricing-tiers-section { padding: 0 18px 48px; }
  .pricing-tiers { gap: 16px; }
  .tier-card { padding: 28px 22px; border-radius: 16px; }
  .tier-name { font-size: 19px; }
  .tier-description { font-size: 13px; margin-bottom: 22px; }
  .tier-price-amount { font-size: 42px; }
  .tier-features li { font-size: 13px; }

  .pricing-cta-section { padding: 20px 18px 56px; }
  .pricing-cta-card {
    padding: 44px 22px;
    border-radius: 18px;
  }
  .pricing-cta-title { font-size: 26px; }
  .pricing-cta-sub { font-size: 14px; }

  .pricing-footer { padding: 40px 18px 20px; }
  .pricing-footer-grid {
    grid-template-columns: 1fr;
    gap: 32px;
    margin-bottom: 40px;
  }
  .pricing-footer-brand { grid-column: span 1; }

  .pricing-footer-bottom {
    flex-direction: column;
    gap: 12px;
    align-items: flex-start;
  }
  .pricing-footer-legal {
    flex-wrap: wrap;
    gap: 14px;
  }
}

/* Small phones */
@media (max-width: 420px) {

  .pricing-hero-title { font-size: 28px; }
  .tier-price-amount { font-size: 38px; }
  .pricing-cta-title { font-size: 22px; }
}
/* Cursor-following hover glow (GlowOverlay). The button keeps its own styles;
   these only position the glow layer and lift the content above it. */
.has-hover-glow {
  position: relative;
  overflow: hidden; /* clips the glow to the button's own rounded shape */
}
/* Every real child (label, icon, eyebrow star/text) sits ABOVE the glow. */
.has-hover-glow > *:not(.hover-glow) {
  position: relative;
  z-index: 1;
}
.hover-glow {
  position: absolute;
  z-index: 0;
  border-radius: 50%;
  pointer-events: none;
  will-change: transform, opacity;
  transition: opacity 0.35s ease, transform 0.35s ease;
}

@media (prefers-reduced-motion: reduce) {
  .hover-glow {
    transition: opacity 0.2s ease;
  }
}
/* ============================================================================
   RntlOS Blog — LIGHT THEME, banded (session 32)
   ----------------------------------------------------------------------------
   The page reads top to bottom as four grounds, each meeting the next on a
   rounded lip — the rhythm /features and the home page already use:

     PURPLE   deep-purple band; the pill nav rides ON it (`.blog-hero-band`)
     WHITE    filters + featured post + the post grid  (`.blog-light`)
     GREY     the newsletter CTA band, #F5F5F5, holding the navy card
     BLACK    the shared footer + the RENTFYV wordmark (`.home-closing`)

   ⚠️⚠️ THIS FILE'S TOKENS FLIPPED IN PLACE, WHICH IS WHY SO MANY RULES BELOW
   HAVE AN EXPLICIT OVERRIDE. `--text`, `--card` and friends kept their names
   and changed their meaning (#FAFAFA → #000000, #0A0A0A → #FFFFFF), so every
   rule that consumed them followed the flip for free. That is the point — but
   it also means ANY surface that is still dark now reads a light value unless
   it says otherwise, and it fails in the worst possible way: the same colour
   on both sides. Three of them bit during this change and are marked ⚠️ where
   they live:
     · `.home-final-cta-inner` is `background: var(--card)` (Landing.css) → a
       white card with white type on it.
     · `.blog-chip.is-active` was `background: var(--text); color: #0a0a0b` →
       a black pill with near-black text.
     · `.blog-pill-btn--ghost` was `color: #FAFAFA` → white on a white pill.
   Before adding a dark surface here, give it literal values, not tokens.

   ⚠️ THIS PAGE IS NOT A `.home-page`, DELIBERATELY. It carries only
   `.blog-page`, so Landing.css's light-theme block — which is entirely
   `.home-page`-scoped — never reaches it. That block would have handed the CTA
   card its navy treatment for free (§CQ5's "check what a shared class already
   gives you"), but it also carries `.home-page h1,h2,h3 { color: var(--rf-ink) }`
   (0,1,1), the `:not(.about-page)` transparency rule, and a dozen
   `[class*="-dim"]`-style sweeps that would all land on this page's own class
   names. Adopting it to save ~15 lines of CTA CSS would put five other pages'
   worth of rules onto a page nobody has re-verified against them. The CTA
   values below are therefore a deliberate copy, kept byte-identical to
   Landing.css's so the two cards cannot drift apart visually.
   ========================================================================= */
.blog-page {
  /* ---- grounds ---------------------------------------------------------- */
  --bg: #FFFFFF;                /* the page, and the post chapter */
  --band-grey: #F5F5F5;         /* the CTA band */
  --card: #FFFFFF;
  --card-2: #FAFAFA;
  --card-border: #E4E4E7;
  --card-border-hover: #D4D4D8;

  /* ---- ink on light, straight from theme.css. Ratios vs #FFFFFF --------- */
  --text: #000000;              /* 21:1   — headings */
  --text-dim: #4A4A52;          /* 8.6:1  — body copy */
  --text-faint: #6B6B75;        /* 5.1:1  — the floor, do not lighten */

  /* ---- brand ------------------------------------------------------------ */
  --bl-accent: #6A5ACD;         /* 5.31:1 on white — fills and large type */
  --bl-accent-deep: #453A8C;    /* 7.4:1  — brand as small text on light */
  --bl-accent-soft: #EEECFA;    /* brand tint, e.g. a ghost button's wipe */

  /* ---- ink for the DEEP PURPLE hero band -------------------------------
     Measured against #5C4EB0 — the lightest point the band ever paints, which
     is the bloom over the gradient, not either gradient stop. Same ramp and
     the same three steps /features uses, so the two purple heroes are one
     surface with one contrast budget.
     ⚠️ NOTHING ON THAT BAND MAY GET LIGHTER. The bloom sets the ceiling;
     raising its alpha lifts the ground and drops all three at once. */
  --bl-on-purple: #FFFFFF;        /* 6.62:1 */
  --bl-on-purple-body: #E4DEFF;   /* 5.10:1 — the floor here */
  --bl-on-purple-accent: #C4B8FF; /* 3.67:1 — LARGE TEXT ONLY (≥24px) */
  --bl-on-purple-line: rgba(255, 255, 255, 0.34);

  /* ---- ink on the NAVY CTA card ----------------------------------------- */
  --bl-on-night: #FFFFFF;         /* 19.2:1 on #0B0E1A */
  --bl-on-night-body: #D6DBE6;    /* 14.1:1 */
  --bl-on-night-dim: #A8AFC0;     /* 8.7:1 — the floor here */

  /* ---- the dark ground the post ARTWORK sits on -------------------------
     ⚠️ THE CARD ART STAYS DARK ON PURPOSE. Every <CardArt> is a picture of
     the AdminPanel, which is a dark app — the identical rule Features.css
     records for its `--fta-*` mockup tokens and Landing.css for `.dash-*`.
     Its own token so a future edit to the page palette cannot reach it. */
  --bl-art-bg: #0D0D10;

  /* The rounded lip every colour change wears. One value, so the curve is
     identical at all three boundaries — and identical to /features, which
     uses this same clamp under the name `--ft-lip`. */
  --bl-lip: clamp(28px, 3.4vw, 44px);

  background: var(--bg);
  color: var(--text-dim);
  font-family: var(--rf-font-body, Georgia, serif);
  min-height: 100vh;
  width: 100%;
  overflow-x: hidden;
}

.blog-page * { box-sizing: border-box; }

/* ===========================================================================
   THE ROUNDED LIP AT EVERY COLOUR CHANGE
   ---------------------------------------------------------------------------
   A band pulls itself UP over the one above by exactly its own radius, so the
   new colour arrives on a curve instead of a hard rule. Same idiom as
   `.ft-lip` (/features) and `.home-deck-lip` / `.xmq` / `.home-pricing-section`
   (landing) — reused rather than reinvented, so every seam on the site curves
   identically.

   ⚠️ ONLY WHERE THE COLOUR FAMILY CHANGES. Three boundaries wear it: purple →
   white, white → grey, grey → black. The seams *inside* the white chapter
   (filters → featured → grid) are one ground and get nothing.

   ⚠️ `z-index` IS REQUIRED, NOT DECORATION. The negative margin makes these
   bands overlap; without a stacking order the band ABOVE paints over the very
   curve it is meant to reveal.

   ⚠️ AND THE LIPPED ELEMENT MUST BE OPAQUE. The page ground is already white,
   so a lip on a transparent section reveals nothing — which is why the post
   chapter needed a real `.blog-light` wrapper rather than a class on the
   first section inside it. */
.blog-page .blog-lip {
  border-radius: var(--bl-lip) var(--bl-lip) 0 0;
  margin-top: calc(-1 * var(--bl-lip));
  position: relative;
  z-index: 2;
}

/* ---- the four grounds ---------------------------------------------------- */

/* ⚠️ THE GROUND IS ON THIS WRAPPER, NOT ON `.blog-hero`, so the radial bloom
   is a single light source — two elements each painting their own copy would
   restart it at the seam. (This note used to say the wrapper existed to carry
   the in-flow pill nav as well; that nav is gone, the page uses the shared
   portalled `<LandingNav>`, and the band pays its height as `padding-top`.) */
/* ⚠️ ONE FRAME — AND THE HEIGHT FORMULA IS SHARED, NOT LOCAL.
   `.rf-hero-ground--frame` (theme.css) supplies
   `min-height: calc(100svh + var(--rf-hero-lip))`; the line below only says
   WHICH lip this page uses. Read that note before changing either half — it
   also explains why `svh` and not `vh`/`dvh`.
   ⚠️⚠️ THE "PEEKING CURVE" WAS DELIBERATE AND HAS NOW BEEN OVERRULED. This
   band used to be a bare `min-height: 100svh`, i.e. one screen of BAND but one
   screen MINUS the lip of visible purple, on the reasoning that a curve
   showing at the bottom edge is what says there is more below. The operator
   reported that white sliver as a defect on this page, /features and /pricing
   and asked for a hero that fills the frame with nothing overlapping it. The
   curve is unchanged — it now begins exactly AT the fold instead of above it.
   ⚠️ DO NOT RE-ADD A LOCAL `min-height` here: this file loads after theme.css,
   so it would win silently and put the sliver back on this page alone. */
/* ⚠️⚠️ THE GROUND IS NOT PAINTED HERE ANY MORE — `.rf-hero-ground` in theme.css
   does it. That is the HOME hero's surface (bloom + dot field + vignette),
   which this band now shares with `/features`, `/about` and `/pricing`
   (operator: "implement the background of home hero section into hero section
   of all pages … except demo"); the class is on `.blog-hero-band` in the JSX.
   The note above still holds — one element for the nav AND the hero is what
   keeps the bloom a single light source — it is just no longer this file's
   gradient. ⚠️ DO NOT RE-ADD A `background` HERE: this file loads after
   theme.css, so a local declaration wins silently and only this page drifts.
   The band's ink is unchanged and gained headroom; the shared ground's peak is
   rgb(81,64,165) against the #5C4EB0 this one used to reach. */
.blog-hero-band {
  position: relative;
  z-index: 1;
  color: var(--bl-on-purple);
  /* ⚠️ EXACTLY THE LIP, AND THAT IS THE WHOLE POINT — not a spacing value.
     The white chapter covers the bottom `--bl-lip` of this band, so the hero's
     flex box has to END where the VISIBLE purple ends, or the centred copy
     sits half a lip low. It was `clamp(24px,4vw,44px)`, which happened to
     equal the lip at desktop and was 4px out at 390 — a coincidence doing
     load-bearing work. Stated, so the two cannot drift apart. */
  /* ⚠️ THE BAR IS FIXED AND PORTALLED NOW, so this band pays its height as top
     padding — the `--rf-nav-reserve` idiom /features and /about already used.
     It used to CONTAIN the bar as an in-flow header, which is why this line
     did not exist before. Same token, one definition; a hardcoded copy that
     drifts either strands the hero under the bar or leaves a strip above it. */
  padding-top: var(--rf-nav-reserve, 76px);
  padding-bottom: var(--bl-lip);

  --rf-hero-lip: var(--bl-lip);
  display: flex;
  flex-direction: column;
}
.blog-hero-band > .blog-hero {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* ⚠️ `width: 100%` IS REQUIRED NOW THAT THE HERO IS A FLEX ITEM. It sets
     `max-width: 1100px; margin: 0 auto`, and on a flex item those auto margins
     override `align-self: stretch` — the box shrinks to fit-content instead of
     being a centred full-width box, which changes where the headline wraps.
     (§CP4 on /features, where it silently centred the chip row.) */
  width: 100%;
  /* ⚠️ SYMMETRIC, BECAUSE CENTRING MAKES ASYMMETRIC PADDING A SKEW. The rule
     above sets 80px top / 56px bottom — sensible for a block that starts under
     a bar, meaningless for one that is vertically centred, and measurably
     wrong: `justify-content: center` centres within the CONTENT box, so a 24px
     padding difference put the copy 24px below the optical centre of the
     visible purple. Measured, not eyeballed. */
  padding-block: clamp(32px, 5vw, 56px);
}

/* ⚠️ NOT `center` ON A SHORT FRAME. A flex container whose content is taller
   than its box splits the overflow between BOTH ends, so centring pushes the
   headline off the TOP where scrolling cannot reach it. Starting from the top
   sends overflow downward instead. The hero is short (eyebrow + h1 + one
   paragraph) so this only bites on a landscape phone — which is exactly the
   case nobody tests. */
@media (max-height: 560px) {
  .blog-hero-band > .blog-hero { justify-content: flex-start; }
}

/* ⚠️ THE PADDING HAS TO CLEAR THE LIP, AND IT IS EXPRESSED THAT WAY. The lip
   pulls this band up over the purple by exactly `--bl-lip`, so the first
   `--bl-lip` of its own box IS the curve — a flat number here would drop the
   chapter's first element into the corner radius at some widths and not
   others. The breathing room is what is added on top.
   ⚠️ That first element is now the FEATURED CARD (the filter row moved up into
   the hero), which is a heavy 2-column block rather than a light row of pills,
   so it wants more of a gap than the chips did. */
.blog-light {
  background: var(--bg);
  padding-top: calc(var(--bl-lip) + clamp(28px, 3.6vw, 52px));
}

/* Make sure long words / URLs never blow out the layout */
.blog-page p,
.blog-page h1,
.blog-page h2,
.blog-page h3,
.blog-page h4,
.blog-page span {
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* ----- Hero -----
   ⚠️⚠️ EVERY VERTICAL SIZE BELOW IS CAPPED IN `vh` AS WELL AS `vw`, AND THAT IS
   NOT STYLE — IT IS WHAT KEEPS THE HERO INSIDE ONE FRAME NOW THAT IT CARRIES
   THE INSIGHT STRIP AND THE JUMP CUE. A width-only scale cannot see a SHORT
   window: at 1280×635 (the operator's own frame) the stack came to ~561px
   against 525px of usable height and the cue fell off the bottom, while the
   identical rules fitted with room to spare at 1280×900. Same failure and
   the same fix §CH2 recorded on the home hero. Re-measure at 1280×635 after
   touching any of these, not just at your own window size. */
.blog-hero {
  text-align: center;
  padding: clamp(40px, 7vw, 80px) clamp(18px, 3vw, 32px) clamp(32px, 5vw, 56px);
  max-width: 1100px;
  margin: 0 auto;

  /* One rhythm for the whole stack, height-aware. Stated once so the gaps
     compress together instead of drifting apart one clamp at a time. */
  --bh-gap-xs: clamp(7px, 1.2vh, 13px);
  --bh-gap-sm: clamp(10px, 1.8vh, 20px);
  --bh-gap-md: clamp(14px, 2.4vh, 28px);
}

.blog-hero-title {
  color: var(--bl-on-purple);           /* 6.62:1 */
  /* fluid: 26px on tiny screens up to 60px on desktop — and capped against
     the VIEWPORT HEIGHT too, see the block note above. */
  font-size: clamp(26px, min(5.5vw, 7vh), 60px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0;
}

/* ⚠️ A LAVENDER, AND THE OPACITY HAD TO GO. The second half of the headline
   was `var(--text)` at 55% — the dark theme's way of making a quieter white.
   Over purple that composites to a muddy mauve, and once `--text` flipped to
   #000000 it became 55% BLACK on deep purple, i.e. gone. `--bl-on-purple-accent`
   is 3.67:1, which clears the LARGE-text floor and nothing else — this is a
   26–60px headline, so that is the right floor, but do not reuse this token on
   body-size copy. */
.blog-hero-accent {
  color: var(--bl-on-purple-accent);
  opacity: 1;
}

/* ⚠️ WAS #8b6cff, WHICH IS A LAVENDER FOR A BLACK PAGE — 2.4:1 on this band,
   at 12px, where the floor is 4.5. */
.blog-hero-eyebrow {
  color: var(--bl-on-purple-body);      /* 5.10:1 */
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin: 0 0 var(--bh-gap-xs);
  /* The flanking rules make this a row rather than a line of text. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 1.4vw, 18px);
}

/* A hairline that fades away from the label, with a dot at its inner end.
   ⚠️ THE LINE IS A GRADIENT, NOT A BORDER, so it dies out at the far end
   instead of stopping dead — a rule with two hard ends reads as a divider
   that got cut, which is the same complaint the hero's dot-field mask exists
   to answer. `currentColor` keeps both halves on the eyebrow's measured ink;
   do not give either its own colour. */
.blog-eyebrow-rule {
  position: relative;
  flex: 0 0 auto;
  width: clamp(24px, 5vw, 62px);
  height: 1px;
  background: linear-gradient(to right, transparent, currentColor);
  opacity: 0.55;
}
.blog-eyebrow-rule::after {
  content: '';
  position: absolute;
  right: -1px;
  top: 50%;
  width: 3px;
  height: 3px;
  margin-top: -1.5px;
  border-radius: 50%;
  background: currentColor;
}
.blog-eyebrow-rule--r {
  background: linear-gradient(to left, transparent, currentColor);
}
.blog-eyebrow-rule--r::after { right: auto; left: -1px; }

.blog-hero-sub {
  color: var(--bl-on-purple-body);
  font-size: clamp(14px, 1.5vw, 17px);
  font-weight: 500;
  line-height: 1.6;
  max-width: 54ch;
  margin: var(--bh-gap-sm) auto 0;
}

/* ⚠️ THIS RULE NOW OWNS THE FILTER CHIPS' FOCUS RING — it was pre-emptive
   until they moved into the hero, and it is load-bearing from that moment on.
   The hero is the one band where the obvious ring is wrong: `--rf-primary-ring`
   measures ~1.9:1 here (issue 33's token, which fails site-wide anyway) and so
   does solid brand purple. White is 6.62:1.
   ⚠️ Scoped to `.blog-hero`, NOT `.blog-hero-band`, because the nav pill also
   lives on the purple and is white — a white ring there would be invisible.
   That distinction is why the chips could be moved without breaking the nav. */
.blog-hero :focus-visible {
  outline: 3px solid var(--bl-on-purple);   /* 6.62:1 */
  outline-offset: 2px;
}

/* ===========================================================================
   CATEGORY FILTER CHIPS — now ON THE PURPLE HERO
   ---------------------------------------------------------------------------
   ⚠️ THE WHOLE BLOCK IS RE-GROUNDED, NOT REPOSITIONED. These were tuned for a
   white band (grey rim, ink label, brand-purple selected pill) and every one
   of those values is wrong on deep purple — the brand fill in particular is
   ~1.9:1 against the ground it would now sit on, i.e. a "selected" state you
   cannot see. The band's own three-step ramp replaces them.

   ⚠️ AND THE SELECTED CHIP INVERTS TO WHITE, which is the same decision
   /features made for its hero's primary button: a purple fill on a purple
   ground is not a control. So "selected" is the solid white pill, unselected
   are translucent — one visual language for "this one is chosen" across both
   purple heroes, rather than a third treatment invented here.
   ========================================================================= */
.blog-filters-section {
  display: flex;
  justify-content: center;
  /* Sits in the hero's centred stack now, so it owns only its top gap. `vh`
     units in the clamp so the gap shrinks with the frame, like the rest of
     the one-frame budget. */
  margin-top: var(--bh-gap-md);
  padding: 0;
}
.blog-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}
.blog-chip {
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 10px 20px;
  border-radius: 999px;
  /* ⚠️ 0.52, NOT THE 0.34 `--bl-on-purple-line` CARRIES — AND THE DIFFERENCE IS
     MEASURED, NOT TASTE. These chips are interactive controls, so their
     boundary is "visual information required to identify a UI component" and
     owes 3:1 against what surrounds it (SC 1.4.11). At 0.34 the rim paints
     rgb(141,132,191) on this band and measures **2.23:1** at the worst of the
     four grounds the band actually paints across the viewport range. 0.52 is
     the first step that clears 3 at ALL of them (3.10) and 0.54 buys headroom
     (3.36). The 8% fill cannot do this job — it is 1.22:1 — and raising IT
     until it could would make every idle chip look selected.
     ⚠️ Solved against the DARKEST ground the band paints, so it holds at every
     width rather than at the one that was open when it was written. */
  border: 1px solid rgba(255, 255, 255, 0.54);
  background: rgba(255, 255, 255, 0.08);
  color: var(--bl-on-purple);            /* 6.62:1 */
  cursor: pointer;
  line-height: 1.4;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease,
              transform 0.15s ease, box-shadow 0.18s ease;
}
/* Fills white on hover — the chip adopts the same inverted language as the
   selected state rather than being a third look. */
.blog-chip:hover {
  color: var(--bl-accent-deep);          /* 9.0:1 on white */
  border-color: #FFFFFF;
  background: #FFFFFF;
  transform: translateY(-1px);
  box-shadow: 0 8px 20px -10px rgba(10, 6, 40, 0.6);
}
/* ⚠️ HISTORY, SO NOBODY REINSTATES IT: this was `background: var(--text);
   color: #0a0a0b` — a white pill with near-black type — which the light flip
   turned into a BLACK pill with near-black type, 1.0:1, on the one control
   that says which filter you are looking at. It then became a brand-purple
   pill, correct on white and ~1.9:1 once the chips moved onto the purple.
   Twice wrong for the same reason: a selected state was written against the
   ground it happened to be on that week. */
.blog-chip.is-active {
  background: #FFFFFF;
  color: var(--bl-accent-deep);          /* 9.0:1 */
  border-color: #FFFFFF;
  box-shadow: 0 8px 22px -12px rgba(10, 6, 40, 0.7);
}
.blog-chip.is-active:hover {
  background: var(--bl-accent-soft);
  border-color: var(--bl-accent-soft);
  color: var(--bl-accent-deep);          /* 7.72:1 on #EEECFA */
}
/* ===========================================================================
   HERO — the insight strip and the jump cue
   ---------------------------------------------------------------------------
   ⚠️⚠️ THE PANEL FILL LIFTS THE GROUND, WHICH SPENDS CONTRAST. Everything here
   sits on `rgba(255,255,255,0.05)` over the shared violet, so every ink is
   scored against a LIGHTER surface than the band's own. 0.05 is the ceiling
   that keeps the panel legible as a surface while leaving `--bl-on-purple-body`
   above its 4.5 floor. Raise it and re-sweep; there is not much room.

   ⚠️ `--bl-on-purple-accent` (#C4B8FF) IS LARGE-TEXT ONLY (≥24px) AND IS
   DELIBERATELY ABSENT BELOW. The reference shows the italic half of the strip's
   headline in lavender, but that line is ~20px — under the large-text
   threshold, so the accent token would land under its floor on a lifted
   ground. The accent is carried by the SERIF ITALIC instead, which is the same
   gesture the home hero uses on "one dashboard" and costs no contrast at all.
   ========================================================================= */
.blog-hero-insight {
  margin-top: var(--bh-gap-md);
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, 1.2fr);
  align-items: center;
  gap: clamp(16px, 2.2vw, 34px);
  padding: clamp(12px, 1.7vh, 20px) clamp(16px, 1.8vw, 26px);
  border: 1px solid var(--bl-on-purple-line);
  border-radius: clamp(16px, 1.6vw, 22px);
  background: rgba(255, 255, 255, 0.05);
  /* ⚠️ THE HERO IS `text-align: center` AND THIS BLOCK IS NOT. Left-aligned
     prose beside centred figures is what makes the two halves read as a
     sentence and a scoreboard rather than one undifferentiated pile. */
  text-align: left;
}

.blog-insight-lead {
  display: flex;
  align-items: flex-start;
  gap: clamp(12px, 1.2vw, 16px);
  min-width: 0;
}

.blog-insight-icon {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 12px;
  background: var(--bl-accent);          /* the one brand fill on the band */
  color: var(--bl-on-purple);            /* white on #6A5ACD = 5.31:1 */
  border: 1px solid rgba(255, 255, 255, 0.22);
}
.blog-insight-icon svg { width: 21px; height: 21px; }

.blog-insight-text { min-width: 0; }

.blog-insight-title {
  color: var(--bl-on-purple);            /* white — the strip's loudest line */
  font-size: clamp(16px, 1.5vw, 20px);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin: 0;
}
/* ⚠️ THE SERIF ITALIC IS THE ACCENT — see the block note. The face is
   --rf-font-accent (Playfair Display since session 71, loaded from
   index.html) and Georgia is the fallback, so a blocked CDN degrades to a
   real serif rather than to faux-italic sans. No webfont is added by this
   file. */
.blog-insight-accent {
  font-family: var(--rf-font-accent, Georgia, 'Times New Roman', serif);
  font-style: italic;
  font-weight: 400;
  color: var(--bl-on-purple);
}

.blog-insight-copy {
  color: var(--bl-on-purple-body);       /* 5.10:1 on the band, less on the fill */
  font-size: clamp(12.5px, 1vw, 13.5px);
  font-weight: 500;
  line-height: 1.5;
  margin: 6px 0 0;
  max-width: 46ch;
}

.blog-insight-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}
.blog-stat {
  min-width: 0;                          /* or a long label blows the track */
  text-align: center;
  padding-inline: clamp(4px, 0.8vw, 12px);
}
/* Hairlines BETWEEN the figures only — a rule on the outer edges would fence
   the group off from the panel it already sits inside. */
.blog-stat + .blog-stat { border-left: 1px solid var(--bl-on-purple-line); }

.blog-stat-icon {
  display: block;
  color: var(--bl-on-purple-accent);     /* decorative glyph, not text */
  margin: 0 auto 4px;
  width: 20px;
  height: 20px;
}
.blog-stat-icon svg { width: 20px; height: 20px; display: block; }

.blog-stat-value {
  display: block;
  color: var(--bl-on-purple);
  font-size: clamp(15px, 1.4vw, 19px);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.01em;
}
.blog-stat-label {
  display: block;
  color: var(--bl-on-purple-body);
  font-size: clamp(10.5px, 0.85vw, 12px);
  font-weight: 500;
  line-height: 1.3;
  margin-top: 2px;
}

/* ---- the jump cue ------------------------------------------------------- */
.blog-hero-jump {
  position: relative;
  margin-top: var(--bh-gap-sm);
}

/* ⚠️ THE ARCS ARE POSITIONED, NOT IN FLOW, AND THAT IS A HEIGHT DECISION. In
   flow they add ~44px to a stack that has ~25px of slack at 1280x635. Absolute
   keeps them beside the button and costs the layout nothing. */
.blog-jump-arc {
  position: absolute;
  top: -2px;
  width: clamp(80px, 11vw, 120px);
  color: var(--bl-on-purple-line);
  pointer-events: none;
}
.blog-jump-arc svg { width: 100%; height: auto; display: block; }
.blog-jump-arc--l { right: calc(50% + 34px); }
.blog-jump-arc--r { left: calc(50% + 34px); }

.blog-jump-btn {
  font-family: inherit;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  margin: 0 auto;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.30);
  background: var(--bl-accent);
  color: var(--bl-on-purple);            /* 5.31:1 */
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
}
/* ⚠️ THE HOVER GOES LIGHTER, WHICH IS BACKWARDS FROM EVERY BRAND BUTTON ON A
   LIGHT BAND — and right here, for the reason §DD6 recorded on the home CTA:
   the direction follows the GROUND, not the button. The house hover #574AB0
   drops the FILL to ~2.8:1 on this violet, so the control loses its edge
   exactly when you point at it. #7565CF keeps the glyph at 4.65:1 and the fill
   at 4.14:1, and the fill gets STRONGER. Same value as the home page's. */
.blog-jump-btn:hover {
  background: #7565CF;
  border-color: rgba(255, 255, 255, 0.55);
  transform: translateY(2px);
}
.blog-jump-btn:active { transform: translateY(3px); }

.blog-jump-label {
  color: var(--bl-on-purple-body);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin: 10px 0 3px;
}
.blog-jump-sub {
  color: var(--bl-on-purple-body);
  font-size: clamp(12px, 1vw, 13px);
  font-weight: 500;
  margin: 0;
}

@media (prefers-reduced-motion: reduce) {
  .blog-jump-btn { transition: none; }
  .blog-jump-btn:hover, .blog-jump-btn:active { transform: none; }
}

/* ---- the strip, narrow ---------------------------------------------------
   ⚠️ ONE HINGE AT 860px, matching /features' hero and `.ft-row-inner`, so the
   page changes shape at one width rather than at three. Below it the frame
   stops being a budget: a phone cannot hold this stack in one screen and it
   scrolls by design — the band still ends BELOW the fold, so the "no white in
   the frame" guarantee is unaffected. */
@media (max-width: 860px) {
  .blog-hero-insight {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
    text-align: center;
  }
  .blog-insight-lead { flex-direction: column; align-items: center; text-align: center; }
  .blog-insight-copy { max-width: 40ch; margin-inline: auto; }
  .blog-insight-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 0; }
  /* The 4-up hairlines become a 2x2 grid's: only the right-hand cells get one,
     or the second row draws a rule under a gap. */
  .blog-stat + .blog-stat { border-left: 0; }
  .blog-stat:nth-child(even) { border-left: 1px solid var(--bl-on-purple-line); }
  .blog-jump-arc { display: none; }     /* no room, and pure decoration */
}

.blog-light :focus-visible {
  outline: 3px solid var(--bl-accent);
  outline-offset: 2px;
}

/* ===========================================================================
   THE WHITE CHAPTER'S SECTION HEAD
   ---------------------------------------------------------------------------
   Added with the chip move, not decoration. The chapter used to open on the
   filter row, which did two jobs at once: marked where the posts begin, and
   showed what you were looking at. With the control now a full screen up in
   the hero, both jobs were unowned — the chapter opened on a bare card and a
   reader scrolled past the fold had no way to tell a filter was applied.
   ========================================================================= */
.blog-section-head {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto clamp(20px, 2.6vw, 32px);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--card-border);
}
.blog-page .blog-section-title {
  font-size: clamp(20px, 2.2vw, 28px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
  margin: 0;
  color: var(--text);                    /* 21:1 on white */
}
/* Quiet, but it is the filter READOUT — it must stay legible, so it is the
   4.5 floor token and not something lighter. */
.blog-page .blog-section-count {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-faint);              /* 5.1:1 on white */
}

/* ----- Category tag on a thumbnail -----
   ⚠️ THIS ONE STAYS DARK, IN LITERALS. It floats on top of the <CardArt>
   artwork, which is a dark AdminPanel mockup — it is the one label on the
   white chapter whose ground is not the chapter's. Its border used to be
   `--card-border-hover`; that token now means #D4D4D8, a near-white rim, which
   is precisely the kind of quiet drift the artwork's own `--bl-art-bg` token
   exists to prevent. */
.blog-card-media { position: relative; }
.blog-cat-tag {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(10, 10, 11, 0.62);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: #FFFFFF;                        /* 14.6:1 on the tag */
}

/* ----- Author / date meta row ----- */
.blog-meta {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-faint);
  margin-bottom: 10px;
}
/* ⚠️ THE AVATAR CARRIES ITS OWN GROUND, so the page flip could not reach it —
   and a contrast walker cannot either, because a GRADIENT has no
   `backgroundColor` and the sweep scores its label against the card behind it
   instead (§CO8, third time in this repo). Measured against the real fill:
   the old #8b6cff → #5b9dff ramp put white at 3.05:1 / 2.71:1. It is
   aria-hidden decoration sitting next to the name it abbreviates, so the 3:1
   non-text floor applied and it was borderline rather than broken — but it was
   also the last dark-theme lavender left on the page. The brand ramp is the
   same gesture in the page's own colour and clears AA outright:
   white on #6A5ACD 5.31:1, on #453A8C 7.40:1. */
.blog-av {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--bl-accent), var(--bl-accent-deep));
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 700;
  color: #fff;
  flex-shrink: 0;
}
.blog-meta-dot {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--text-faint);
  flex-shrink: 0;
}

/* ----- Featured post ----- */
.blog-featured-section {
  padding: 0 clamp(18px, 3vw, 32px) clamp(28px, 4vw, 48px);
  display: flex;
  justify-content: center;
}
/* ⚠️ #FAFAFA, NOT #FFFFFF. `var(--card)` now resolves to white, and a white
   card on the white chapter separated by a 1px hairline is the exact failure
   Landing.css records for the home CTA: the heaviest block on the section had
   the least visual weight. One step of grey plus a real shadow gives it back
   its edge without needing a heavier border. */
.blog-featured {
  width: 100%;
  max-width: 1200px;
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: clamp(20px, 3vw, 36px);
  align-items: center;
  border: 1px solid var(--card-border);
  border-radius: 24px;
  padding: clamp(16px, 1.6vw, 22px);
  background: var(--card-2);
  box-shadow: 0 4px 16px rgba(16, 16, 20, 0.08);
  transition: border-color 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease;
}
@media (hover: hover) {
  .blog-featured:hover {
    border-color: var(--bl-accent);
    transform: translateY(-3px);
    box-shadow: 0 18px 48px rgba(16, 16, 20, 0.12);
  }
}
.blog-featured-media { aspect-ratio: 16 / 11; margin-bottom: 0; }
.blog-featured-body { padding: 8px clamp(8px, 1.6vw, 22px) 8px 6px; }
.blog-featured-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* ⚠️ WAS #8b6cff — 3.0:1 on white, at 11px, where the floor is 4.5. The
     deep step of the brand is the same hue and clears it. */
  color: var(--bl-accent-deep);          /* 7.4:1 */
  margin-bottom: 14px;
}
.blog-featured-pill::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--bl-accent);
}
.blog-featured-title {
  font-size: clamp(22px, 2.6vw, 32px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.14;
  margin: 0 0 12px;
  color: var(--text);
}
.blog-featured-excerpt {
  color: var(--text-dim);
  font-size: 15px;
  line-height: 1.6;
  margin: 0 0 16px;
  max-width: 48ch;
}
.blog-featured-link { margin-top: 2px; }
@media (max-width: 860px) {
  .blog-featured { grid-template-columns: 1fr; }
  .blog-featured-body { padding: 4px 8px 12px; }
}

.blog-empty {
  text-align: center;
  color: var(--text-dim);
  font-size: 14px;
  padding: 24px 0;
}

/* ----- Posts grid ----- */
.blog-grid-section {
  padding: 0 clamp(18px, 3vw, 32px) clamp(48px, 7vw, 80px);
  display: flex;
  flex-direction: column;
  align-items: center;
}

.blog-grid {
  width: 100%;
  max-width: 1200px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(16px, 2vw, 24px);
}

.blog-card {
  display: flex;
  flex-direction: column;
  background: transparent;
  border-radius: 14px;
  transition: transform 0.2s ease;
}

@media (hover: hover) {
  .blog-card:hover { transform: translateY(-2px); }
}

/* ⚠️ `--bl-art-bg`, NOT `var(--card)`. The tile is filled edge to edge by a
   dark <CardArt> SVG, so the ground behind it is only ever seen for the frame
   before the SVG paints — but `var(--card)` now means WHITE, which would make
   that frame a white flash on every card. It is also the honest value: this
   tile is a dark surface inside a light chapter, and it says so.
   The separation comes from a shadow rather than the border, because a
   #E4E4E7 hairline between a white page and near-black artwork is invisible
   at 1px and does no work. */
.blog-card-media {
  width: 100%;
  aspect-ratio: 5 / 3;
  background: var(--bl-art-bg);
  border: 1px solid var(--card-border);
  border-radius: 14px;
  overflow: hidden;
  margin-bottom: 18px;
  box-shadow: 0 4px 16px rgba(16, 16, 20, 0.08);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.blog-card:hover .blog-card-media {
  border-color: var(--bl-accent);
  box-shadow: 0 18px 48px rgba(16, 16, 20, 0.14);
}

.card-art-svg {
  width: 100%;
  height: 100%;
  display: block;
}

.blog-card-body {
  display: flex;
  flex-direction: column;
}

.blog-card-meta {
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  margin-bottom: 12px;
}

.blog-card-title {
  color: var(--text);
  font-size: clamp(16px, 1.4vw, 18px);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.01em;
  margin: 0 0 14px;
}

.blog-card-excerpt {
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 400;
  line-height: 1.6;
  margin: 0 0 16px;
}

.blog-card-link {
  background: transparent;
  border: none;
  padding: 6px 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  align-self: flex-start;
  min-height: 32px;
  transition: gap 0.15s ease, opacity 0.15s ease;
}

.blog-card-link:hover {
  gap: 10px;
  opacity: 0.85;
}

.blog-card-link span {
  font-size: 16px;
  line-height: 1;
}

/* ===========================================================================
   NEWSLETTER + DEMO CTA — its own band, holding the site's navy card
   ---------------------------------------------------------------------------
   ⚠️⚠️ THE CARD WOULD HAVE GONE WHITE-ON-WHITE. `.home-final-cta-inner` is
   `background: var(--card)` in Landing.css, and this file's `--card` now means
   #FFFFFF — so flipping the palette silently turned the page's single most
   important block into a white card with white type on a white band. That is
   the same failure Landing.css's own light-theme block records for the home
   page, and it is fixed the same way: the closing CTA becomes the HEAVIEST
   surface on the page rather than the lightest, which also restores the three
   effects it was designed around (the cursor glare, the button's glow-pulse
   and the 3D tilt all read against a dark ground and do nothing on white).

   ⚠️ THESE VALUES ARE A DELIBERATE COPY OF `.home-page .home-final-cta-inner`,
   kept byte-identical so the two cards cannot drift. See this file's header
   for why the page does not simply take the `.home-page` class and inherit
   them.

   The BAND under the card is #F5F5F5, not pure white — that is the ground the
   home page's CTA actually sits on, and it is also what keeps the lip from the
   white chapter above it visible. A white lip on a white ground is geometry
   nobody can see. */
/* Ground only — the curve and the −44px pull come from `.blog-lip`, which is
   the single source of truth for both. */
.blog-cta { background: var(--band-grey); }

/* Lift the newsletter content above the card's cursor glare overlay. */
.home-final-cta-inner > .blog-cta-title,
.home-final-cta-inner > .blog-cta-sub,
.home-final-cta-inner > .blog-cta-form,
.home-final-cta-inner > .blog-cta-or { position: relative; z-index: 1; }

.blog-page .home-final-cta-inner {
  background:
    radial-gradient(120% 100% at 50% 0%, var(--rf-night-2, #1B2233), transparent 62%),
    var(--rf-night, #0B0E1A);
  border: 1px solid rgba(255, 255, 255, 0.10);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}

/* ⚠️ (0,2,0) IS THE MINIMUM HERE. Landing.css's `.home-final-cta-card h2`
   is (0,1,1) and sets `color: var(--text)` — which is now #000000. A bare
   `.blog-cta-title` (0,1,0) loses that fight and the headline renders black on
   navy. Every rule in this block carries the `.blog-page` prefix for the same
   reason; do not "simplify" them away. */
.blog-page .blog-cta-title {
  font-size: clamp(24px, 3.4vw, 40px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.12;
  max-width: 20ch;
  margin: 0 auto 10px;
  color: var(--bl-on-night);             /* 19.2:1 */
}
.blog-page .blog-cta-sub {
  color: var(--bl-on-night-body);        /* 14.1:1 */
  font-weight: 600;
  font-size: 15px;
  margin: 0 auto 26px;
  max-width: 52ch;
}
.blog-cta-form {
  display: flex;
  gap: 10px;
  max-width: 460px;
  margin: 0 auto;
  flex-wrap: wrap;
  justify-content: center;
}
/* ⚠️ THE FIELD SITS INSIDE THE NAVY CARD, so its ink is inverted while
   everything else in the white chapter above is not. `color: var(--text)`
   would now be #000000 on #16161a — a field you can type into and not read. */
.blog-page .blog-cta-input {
  flex: 1;
  min-width: 220px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  color: var(--bl-on-night);
  background: #16161a;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  padding: 14px 20px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
/* `--text-faint` (#6B6B75) is 3.2:1 on #16161a — a placeholder is exempt from
   the 4.5 floor, but not from being legible. */
.blog-page .blog-cta-input::placeholder { color: rgba(255, 255, 255, 0.55); }
.blog-page .blog-cta-input:focus {
  outline: none;
  border-color: var(--rf-primary-on-dark, #8B7BE0);
  box-shadow: 0 0 0 3px rgba(139, 123, 224, 0.28);
}

/* Submit reuses .home-cta-primary + .glow-pulse; just stop it stretching. */
.blog-cta-btn { flex: none; }
.blog-cta-btn:disabled { cursor: default; animation: none; opacity: 0.75; }

/* ⚠️⚠️ THE BRAND, AND THIS RULE USED TO BE THE LAST OF THE DRIFT ISSUE 44
   TRACKED. It filled with `--rf-indigo #5A5FE8` while home, /features and
   /about all rendered `#6A5ACD` — four pages, one card design, two fills.
   Measured live before the change: home/features/about `rgb(106,90,205)`,
   /blog `rgb(90,95,232)`, /pricing WHITE. All five are now the first value.

   The original argument for indigo was real and is worth keeping: #6A5ACD as a
   SURFACE measures only 3.62:1 against this navy card, where indigo gives 3.9.
   Both clear the 3:1 floor for identifying a control, so it was never a
   failure — it was a preference paid for with a colour the brand does not use
   anywhere else. The hover settles it: this card's old #4A4FDE gave a good
   label (6.06:1) but a 2.62:1 SURFACE, while home's #7565CF gives 4.65 label
   AND 4.14 surface. Home's pair is better on the metric indigo was chosen to
   protect, so aligning is an improvement, not a compromise.
   (0,3,0) to clear theme.css's doubled-class rule. */
.blog-page .home-final-cta-inner .home-cta-primary,
.blog-page .home-final-cta-inner .home-cta-primary.home-cta-primary {
  background: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);
  color: var(--bl-on-night);
  box-shadow: 0 10px 34px rgba(106, 90, 205, 0.45);
}
/* ⚠️ THE HOVER GOES LIGHTER, MATCHING HOME — see the resting rule above for
   why this card stopped diverging. It used to darken to #4A4FDE on the
   reasoning that home's lighter value failed AA under a white label; that was
   measured against home's OLD #6E72F0 (3.94:1), which §DD6 has since replaced
   with #7565CF (label 4.65:1, fill 4.14:1). The defect the divergence existed
   to avoid no longer exists, so the divergence goes with it. */
.blog-page .home-final-cta-inner .home-cta-primary.home-cta-primary:hover {
  background: #7565CF;
  border-color: #7565CF;
}
.blog-page .home-final-cta-inner :focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 3px;
}

.blog-page .blog-cta-or {
  margin-top: 20px;
  font-size: 14px;
  color: var(--bl-on-night-dim);         /* 8.7:1 */
  font-weight: 600;
}
.blog-page .blog-cta-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-weight: 700;
  color: var(--bl-on-night);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* ⚠️ WAS #8b6cff. The brand purple family is not usable as body-size text on
   this ground — #6A5ACD measures 3.62:1 on --rf-night — which is exactly why
   theme.css carries `--rf-primary-on-dark`. */
.blog-page .blog-cta-link:hover { color: var(--rf-primary-on-dark, #8B7BE0); }   /* 5.50:1 */

/* ===========================================================================
   THE BLACK CLOSE
   ---------------------------------------------------------------------------
   ⚠️ THE LAST LIP IS ON `.home-closing`, NOT ON `.site-footer`. The footer is
   a shared component six pages render; the closing wrapper is a plain div this
   page's own GlobalFooter owns.

   ⚠️ `overflow: hidden` IS LOAD-BEARING AND NOWHERE ELSE HERE. `.home-closing`
   is transparent, so the curve is not painted by it — the footer paints its
   own opaque navy and would go straight over the rounded corners without a
   clip. Same thing /features hit (§CQ3) and the pinned deck before it.
   Nothing inside is sticky, so the clip costs nothing. */
.blog-page .home-closing {
  border-radius: var(--bl-lip) var(--bl-lip) 0 0;
  margin-top: calc(-1 * var(--bl-lip));
  overflow: hidden;
  position: relative;
  z-index: 2;
}

/* The unscoped `.rentfyv-hero` is #000000; home and features both resolve it
   to `--rf-surface-ink` through their `.home-page` class. Matching it here
   keeps the wordmark zone the same black on all three pages instead of one
   being a shade off. */
.blog-page .rentfyv-hero { background: var(--rf-surface-ink, #111114); }

/* ----- Footer ----- */
.blog-footer {
  padding: clamp(40px, 6vw, 60px) clamp(18px, 3vw, 32px) 28px;
  border-top: 0.5px solid transparent;
}

.blog-footer-inner {
  max-width: 1180px;
  margin: 0 auto;
}

.blog-footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1.2fr;
  gap: clamp(28px, 5vw, 60px);
  margin-bottom: clamp(40px, 5vw, 60px);
}

.blog-socials {
  display: flex;
  gap: 10px;
}

.social-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.social-icon:hover {
  background: #1A1A1A;
  border-color: var(--card-border-hover);
}

.blog-footer-col h4 {
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  margin: 0 0 16px;
}

.blog-footer-col span {
  display: block;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 400;
  line-height: 2.1;
  cursor: pointer;
  transition: color 0.15s ease;
}

.blog-footer-col span:hover { color: var(--text); }

.blog-footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  padding-top: 24px;
  border-top: 0.5px solid transparent;
}

.blog-footer-bottom > span {
  color: var(--text-faint);
  font-size: 12px;
}

.blog-footer-legal {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 24px;
}

.blog-footer-legal span {
  color: var(--text-faint);
  font-size: 12px;
  cursor: pointer;
  transition: color 0.15s ease;
}

.blog-footer-legal span:hover { color: var(--text); }

/* Small laptops / large tablets */
@media (max-width: 1024px) {
  .blog-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .blog-footer-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* Small tablets / large phones */
@media (max-width: 720px) {
  .blog-grid {
    grid-template-columns: 1fr;
    max-width: 520px;
    margin: 0 auto;
  }
  .blog-card-media { aspect-ratio: 16 / 10; }
}

/* Mobile */
@media (max-width: 560px) {
  .blog-footer-grid {
    grid-template-columns: 1fr;
  }

  .blog-footer-bottom {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
}

/* Tiny phones (older devices, ~320px) */
@media (max-width: 340px) {
  .blog-hero { padding: 32px 14px 28px; }
  .blog-grid-section { padding: 0 14px 40px; }
  .blog-footer { padding: 32px 14px 18px; }
}

/* Reduce motion preference */
@media (prefers-reduced-motion: reduce) {
  .blog-card,
  .blog-card-media,
  .blog-pill-btn,
  .blog-card-link,
  .social-icon {
    transition: none;
  }
}
/* ParallaxLayers — additive monochrome depth layers for the Final-CTA section.
   Self-contained: this file only adds rules; it does not edit any existing
   Landing.css rule. Black & white only, deliberately faint. */

/* Make the host section a positioned stacking context so the layers can sit at
   z-index:-1 — BELOW the in-flow CTA card (no z-index needed on the card, so
   the existing card markup/styles are untouched) but ABOVE the page bg. */
.home-final-cta {
  position: relative;
  z-index: 0;
}

.rfy-parallax {
  position: absolute;
  inset: 0;
  z-index: -1;            /* under the in-flow card, within the section context */
  overflow: hidden;       /* clip drifting layers to the section */
  pointer-events: none;   /* decorative — never intercept clicks */
}

.rfy-parallax-layer {
  position: absolute;
  display: block;
  will-change: transform; /* GPU compositing — moving layers only */
  pointer-events: none;
}

/* L1 — large soft glow, slowest. */
.rfy-parallax-l1 {
  top: -10%;
  left: 50%;
  width: 640px;
  height: 640px;
  margin-left: -320px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0) 68%);
}

/* L2 — thin ring outline, mid speed. */
.rfy-parallax-l2 {
  top: 14%;
  left: 12%;
  width: 320px;
  height: 320px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.06);
}

/* L3 — faint dot field, fastest. */
.rfy-parallax-l3 {
  right: 9%;
  bottom: 4%;
  width: 190px;
  height: 190px;
  border-radius: 50%;
  background: radial-gradient(rgba(255, 255, 255, 0.10) 1.5px, transparent 1.6px) 0 0 / 22px 22px;
  opacity: 0.55;
}

/* Perf guard: below 768px the JS skips the ScrollTriggers (static layers).
   Drop the extra layers + will-change so mobile stays light. */
@media (max-width: 767px) {
  .rfy-parallax-layer { will-change: auto; }
  .rfy-parallax-l2,
  .rfy-parallax-l3 { display: none; }
}

/* Reduced motion: no transforms run; keep the static layers cheap. */
@media (prefers-reduced-motion: reduce) {
  .rfy-parallax-layer { will-change: auto; }
}
/* ============================================================================
   About page — BANDED (session 32)
   ----------------------------------------------------------------------------
   Reads top to bottom as five grounds, each meeting the next on a rounded lip
   — the rhythm /features, /blog and the home page already use:

     PURPLE   .about-hero            the nav floats over it
     WHITE    .about-story           origin copy + the founding card
     WHITE    .about-stats           the "by the numbers" collage
     BLACK    .about-founder         the founder quote — the page's ONE dark band
     WHITE    .about-values          "what we stand for" cards
     GREY     .home-final-cta        #F5F5F5, holding the navy card
     BLACK    .home-closing          the shared footer + wordmark

   ⚠️ ONE BLACK CONTENT BAND, AND IT IS THE FOUNDER QUOTE — an operator
   decision, made so that section can carry its own animation against a dark
   ground. It replaces an earlier arrangement where `.about-stats` and
   `.about-values` were the two dark bands; if that ever comes back, the
   per-band ink below is what has to move with it, not just the `background`.

   ⚠️ AND IT IS THE ONE CASE §CQ2's RULE ARGUES AGAINST — a dark ground is
   cheapest on short card copy and costs most on long-form reading, which is
   exactly what a pull-quote is. It is defensible HERE because the quote is
   ~40 words at 30px, i.e. display type rather than a paragraph you settle
   into, and because it is one band rather than a chapter. Do not take it as
   licence to darken the story section.

   ⚠️ THE LIP IS ON FIVE SEAMS, NOT SIX. `.about-story` → `.about-stats` is
   white→white, so a curve there would reveal nothing while still costing a
   44px overlap — and it would split one continuous light chapter in two.

   ⚠️⚠️ THIS PAGE WAS THE WORST-BROKEN SURFACE ON THE SITE: 92 OF 257 TEXT
   NODES FAILED AA BEFORE THIS PASS, and CONTEXT.md's issue 38 recorded only
   12 — the earlier sweep could not see most of them. The four causes, all
   fixed at the root rather than by recolouring text:
     1. `--text`/`--card` had already flipped to light-theme values (the page
        carries `.home-page`), but LITERAL dark surfaces stayed: the founding
        card and the stat/value cards were `#16161a` with #333333 ink on them
        — 1.43:1.
     2. `<ScrollHighlight>` inside the dark value cards ran with its LIGHT
        defaults, painting #4A4A52 words on #16161a at 2.06:1 — ~60 nodes, and
        invisible to a CSS audit because GSAP writes it inline at runtime.
     3. `<ScrollHighlightRegion>` split the CTA card's heading and sub-line and
        painted them light-theme ink on the navy card — 1.81:1. §CO3's bug,
        third page to have it; fixed by a SKIP_CONTAINERS entry.
     4. `--about-accent` was `#8c8c8c`, a grey tuned for a near-black page, and
        it is used on FOUR different grounds.

   ⚠️ ONE ACCENT CANNOT SERVE FOUR GROUNDS — the standing rule, now stated on
   a fourth surface. `.about-accent` appears in the purple hero, on two white
   bands and on two black bands. It is a TOKEN re-pointed per band below, not
   a single value.

   ⚠️ EVERY COLOUR RULE BELOW CARRIES A `.about-page` PREFIX so it reaches
   (0,2,0) and clears `Landing.css`'s `.home-page h1,h2,h3` (0,1,1) and its
   `.home-page X` (0,2,0) rules — this file loads after Landing.css, so an
   equal-specificity rule here wins on order. Drop the prefix and a heading
   silently goes back to light-theme ink. Same trap Features.css records.
   ========================================================================= */
.about-page {
  /* ⚠️ WAS #8c8c8c — a grey accent for a near-black page, which measures
     3.0:1 on white and ~2:1 on the purple hero. This is the LIGHT-band value;
     the hero and the two black bands re-point it (see their blocks). */
  --about-accent: #6A5ACD;        /* 5.31:1 on #FFFFFF */
  --ab-accent-deep: #453A8C;      /* 7.4:1 — brand as small text on light */

  /* ---- ink for the PURPLE hero band -----------------------------------
     Measured against the lightest point that band paints, which is the bloom
     over the gradient and not either gradient stop. Same three-step ramp
     /features and /blog use, so all three purple heroes share one budget.
     ⚠️ NOTHING ON THAT BAND MAY GET LIGHTER — the bloom sets the ceiling. */
  --ab-on-purple: #FFFFFF;
  --ab-on-purple-body: #E4DEFF;
  --ab-on-purple-accent: #C4B8FF; /* LARGE TEXT ONLY (≥24px) */
  --ab-on-purple-line: rgba(255, 255, 255, 0.34);

  /* ---- the BLACK bands, and the ink on them ---------------------------
     `--rf-night`, the palette's existing darkest ground, not a new #000 — the
     same value `.site-footer` uses, so the page's dark punctuation and its
     dark close belong to one system.
     ⚠️ THE BRAND PURPLE IS 3.62:1 HERE — fine for a border or an icon, UNDER
     the 4.5 floor for any body-size label. Use `--rf-primary-on-dark`. */
  --ab-ink-band: #0B0E1A;
  --ab-ink-card: #12172A;
  --ab-ink-line: rgba(255, 255, 255, 0.13);
  --ab-on-ink: #FFFFFF;           /* 19.2:1 */
  --ab-on-ink-body: #D6DBE6;      /* 14.1:1 */
  --ab-on-ink-dim: #A8AFC0;       /* 8.7:1 — the floor here */

  /* One value for every curve on the page, and the same clamp `--ft-lip` and
     `--bl-lip` use, so a seam here matches a seam on /features and /blog. */
  --ab-lip: clamp(28px, 3.4vw, 44px);
}

.about-accent { color: var(--about-accent); }

/* ===========================================================================
   THE ROUNDED LIP AT EVERY COLOUR CHANGE
   ---------------------------------------------------------------------------
   A band pulls itself UP over the one above by exactly its own radius, so the
   new colour arrives on a curve. Same idiom as `.ft-lip` / `.blog-lip`.

   ⚠️ FIVE SEAMS WEAR IT, NOT SIX: purple→white, white→BLACK, BLACK→white,
   white→grey, grey→black. The white→white seam (`.about-story` →
   `.about-stats`) deliberately has none — see the file header.

   ⚠️ `z-index: 2` IS REQUIRED. `Landing.css` gives every direct child of
   `.home-page` `position: relative; z-index: 1`, and the negative margin makes
   these bands overlap — without a higher stacking order the band ABOVE paints
   over the curve it is meant to reveal.

   ⚠️⚠️ AND NO `overflow: hidden` ON THESE SECTIONS. `.about-stats` is a 250vh
   track whose inner is `position: sticky` — a clip would make the section its
   own scroll container and kill the pin outright. (It carries no lip today,
   but the warning stands — it is one preference away from getting one back.)
   The one place the clip IS
   required is `.home-closing` (see the bottom of this file), where the footer
   paints its own opaque navy over the curve. §CQ3, same distinction. */
.about-page .about-lip {
  border-radius: var(--ab-lip) var(--ab-lip) 0 0;
  margin-top: calc(-1 * var(--ab-lip));
  position: relative;
  z-index: 2;
}

/* ===== Hero — the PURPLE band =====
   ⚠️ IT RUNS TO THE TOP OF THE DOCUMENT. `.home-page` reserves
   `--rf-nav-reserve` so content clears the FIXED nav; without cancelling it a
   strip of page ground sits above the purple, which reads as a rendering
   fault. The reserve is cancelled and paid back as this section's own top
   padding — the identical idiom `LandingHero.css` and `.ft-hero` use, and the
   reason that value is a token rather than a number typed twice.

   ⚠️⚠️ THE GROUND IS NOT PAINTED HERE ANY MORE — `.rf-hero-ground` in theme.css
   does it. That is the HOME hero's surface (bloom + dot field + vignette),
   now shared with `/features`, `/blog` and `/pricing` (operator: "implement
   the background of home hero section into hero section of all pages …
   except demo"); the class is on `<section>` in About.jsx.
   ⚠️ DO NOT RE-ADD A `background` HERE — this file loads after theme.css, so a
   local declaration wins silently and this page alone stops matching.
   ⚠️ THE `--rf-nav-reserve` CANCEL IS STILL LOAD-BEARING and is now the reason
   the ground has to be full-bleed: the shared layers are `inset: 0` on this
   section, so they cover the strip under the fixed nav exactly as the home
   hero's do. Change the padding and the bloom's centre moves with it. */
.about-hero {
  position: relative;
  z-index: 1;
  text-align: center;
  margin-top: calc(-1 * var(--rf-nav-reserve, 76px));
  /* ⚠️ THE BOTTOM PADDING CARRIES THE LIP — it is not spacing. The next band
     climbs `--ab-lip` up over this one, so the last `--ab-lip` of this box is
     UNDER the curve; without it here, centring puts the whole stack half a lip
     low. Stated in terms of the token so the two cannot drift. */
  padding: calc(var(--rf-nav-reserve, 76px) + clamp(48px, 7vw, 104px))
           clamp(18px, 3vw, 32px)
           calc(clamp(48px, 7vw, 96px) + var(--ab-lip));
  color: var(--ab-on-purple);

  /* ⚠️ ONE FRAME. The formula is shared (`.rf-hero-ground--frame`, theme.css);
     this only says which lip the page uses. `/about` was NOT in the operator's
     list ("features pricing and blog"), but it had the identical defect — a
     content-sized band with the white curve landing inside the first screen —
     and leaving the fourth hero different is the drift this repo keeps paying
     for. Revert by dropping the class in About.jsx and this declaration.
     ⚠️ DO NOT add a local `min-height`: this file loads after theme.css. */
  --rf-hero-lip: var(--ab-lip);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* ⚠️ NOT `center` ON A SHORT FRAME — a flex container whose content is taller
   than its box splits the overflow between BOTH ends, so centring pushes the
   headline off the TOP where scrolling cannot reach it. Newly reachable: this
   hero was content-sized until now and could not overflow at all. Same guard
   and threshold as /blog and /pricing. */
@media (max-height: 560px) {
  .about-hero { justify-content: flex-start; }
}

/* The centred column the section used to BE — see the JSX note.
   ⚠️ `width: 100%` IS LOAD-BEARING NOW THAT THE PARENT IS A FLEX COLUMN: on a
   flex item the `auto` inline margins below override `align-self: stretch`, so
   without it this box shrinks to fit-content and the 92px headline re-wraps
   (§CP4). It was already here; do not "tidy" it away. */
.about-hero-inner {
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
}
/* ⚠️ (0,2,0): `.home-page h1,h2,h3 { color: var(--rf-ink) }` in Landing.css is
   (0,1,1) and would out-specify a bare `.about-hero-title`, painting the
   headline near-black on deep purple. This is the exact fight Features.css
   lost four times before it was written down. */
.about-page .about-hero-title {
  /* Session 68: Archivo Black → the site heading token (operator: "apply this
     font on all other headings for consistency"). 800 is Fraunces's loaded
     ceiling — 900 would silently resolve to it anyway; say what renders. */
  font-family: var(--rf-font-heading, Georgia, serif);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  line-height: 0.98;
  font-size: clamp(40px, 8vw, 92px);
  color: var(--ab-on-purple);            /* 6.62:1 */
  margin: 0 0 22px;
}
/* "RENT FYV" — the second line. 92px, so the large-text 3:1 floor applies and
   this lavender clears it at 3.67:1. Do NOT reuse it at body size. */
.about-page .about-hero .about-accent { color: var(--ab-on-purple-accent); }
.about-page .about-hero-sub {
  color: var(--ab-on-purple-body);       /* 5.10:1 */
  font-size: clamp(14px, 1.6vw, 18px);
  line-height: 1.65;
  max-width: 640px;
  margin: 0 auto;
}

/* ⚠️ THE EYEBROW BADGE IS A WHITE PILL ON A WHITE PAGE BY DEFAULT — `background:
   var(--card)` — so on the purple it became a bright white slab, louder than
   the 92px headline under it. Translucent instead, so it reads as a label on
   the band rather than a card floating over it. */
.about-page .about-hero .home-eyebrow-badge {
  background: rgba(255, 255, 255, 0.10);
  border-color: var(--ab-on-purple-line);
}
.about-page .about-hero .home-eyebrow-badge > span:last-child { color: var(--ab-on-purple); }
/* ⚠️ THE BLINKING "+" IS GOLD (#FFC107) SITE-WIDE, and Landing.css defends that
   at 1.63:1 on white on the grounds that every star row is accompanied by the
   rating in text. THAT ARGUMENT DOES NOT APPLY HERE — this is not a rating, it
   is a decorative glyph in a badge, so nothing states it otherwise. On this
   band it also has no reason to be gold. */
.about-page .about-hero .home-eyebrow-star { color: var(--ab-on-purple); }

/* The brand ring is ~1.9:1 on this ground; white is 6.62:1. */
.about-page .about-hero :focus-visible {
  outline: 3px solid var(--ab-on-purple);
  outline-offset: 3px;
}

/* ⚠️⚠️ THIS PAGE'S `body:has(.about-page) .home-nav-pill` OVERRIDE IS GONE,
   AND DELIBERATELY NOT REPLACED — the twin of the one Features.css carried.
   It raised the shared bar's WHITE frosted pill to 0.94 because at 0.72 it
   composited to a lavender slab over this hero and the wordmark accent
   dropped to 3.27:1. The bar has no white state any more (LandingNav.css):
   transparent over a hero, frosted NIGHT elsewhere, one light ink set. Two
   pages each patching a shared component from their own stylesheet is exactly
   how the bar came to "change with every page"; if the accent ever measures
   short again, fix it once in LandingNav.css. */
/* Stacked title — second line ("RENT FYV") sits muted under "ABOUT". */
.about-hero-title .about-accent { display: inline-block; }

/* Hero CTA — white pill (matches the home hero primary). */
.about-hero-cta { margin-top: clamp(24px, 3.5vw, 34px); }
.about-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: inherit;
  font-weight: 700;
  font-size: 15px;
  cursor: pointer;
  border: none;
  border-radius: 999px;
  padding: 15px 28px;
  transition: transform 0.25s var(--ease, cubic-bezier(.22,1,.36,1)), box-shadow 0.25s ease, background 0.25s ease;
  white-space: nowrap;
}
.about-btn:active { transform: scale(0.97); }
.about-btn-primary { background: #ffffff; color: #0a0a0b; }
.about-btn-primary:hover { transform: translateY(-1px); box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.12); }
.about-btn-lg { padding: 18px 34px; font-size: 16px; }
.about-btn-arrow { transition: transform 0.25s var(--ease, cubic-bezier(.22,1,.36,1)); }
.about-btn:hover .about-btn-arrow { transform: translateX(4px); }

/* ===== Our story — a WHITE band =====
   ⚠️⚠️ THE FOUNDING CARD WAS THE PAGE'S SINGLE WORST BLOCK AND THE CAUSE WAS
   ONE LITERAL. It was `background: #16161a` while every piece of type on it
   read `var(--text)` / `var(--text-dim)` — tokens that had already flipped to
   #333333 / #4A4A52 when the site went light. Nine nodes at **1.43–2.06:1**:
   the "2020", the caption and all three mini rows were dark-on-dark. The fix
   is the GROUND, not the ink — this is a content card on a white band, not a
   product mockup, so it has no reason to stay dark (the rule Features.css
   applies to `.ftf-*` explicitly does NOT extend to it).
   ⚠️ #FAFAFA, NOT #FFFFFF: a white card on a white band separated by a 1px
   hairline is the failure Landing.css records for the home CTA — the heaviest
   block on the section with the least weight on it. One step of grey plus a
   real shadow gives it an edge without a heavier border. */
.about-story { position: relative; z-index: 1; }
.about-story-grid { align-items: center; }
.about-story-art { align-self: center; width: 100%; }
.about-story-card {
  position: relative;
  overflow: hidden;
  width: 100%;
  border: 1px solid var(--rf-border, #E4E4E7);
  border-radius: 18px;
  padding: clamp(26px, 3vw, 34px);
  background: var(--rf-surface-2, #FAFAFA);
  box-shadow: var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
  cursor: pointer;
  transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
/* ⚠️ THE HOVER GLOW WAS `rgba(160,110,255,…)` — a lavender bloom that only
   reads on a dark ground. On white it is a lilac smudge; the brand edge plus a
   neutral shadow does the same job. Same change on every card below. */
.about-story-card:hover {
  border-color: var(--rf-primary, #6A5ACD);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
  transform: translateY(-4px);
}
/* The one place the brand appears as display type on this band — 68px, so it
   clears the large-text floor several times over at 5.31:1. */
.about-page .about-story-big {
  font-size: clamp(44px, 6vw, 68px);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--about-accent);
}
.about-page .about-story-cap { color: var(--rf-ink-dim, #4A4A52); font-weight: 600; margin-top: 8px; font-size: 14px; }
.about-story-rule { height: 1px; background: var(--rf-border, #E4E4E7); margin: 26px 0; }
.about-page .about-story-mini {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  font-size: 14px;
  color: var(--rf-ink-dim, #4A4A52);     /* 8.6:1 on #FAFAFA */
  font-weight: 600;
  margin-bottom: 14px;
}
.about-story-mini--last { margin-bottom: 0; }
.about-page .about-story-mini b { color: var(--rf-ink, #000000); }

/* ===== What we do ===== */
.about-values {
  position: relative;
  z-index: 1;
  /* Normal-height section (the stack→fan-out is a one-shot on-enter animation,
     no longer a tall pinned scroll track that stranded empty space). */
  padding: clamp(40px, 6vw, 72px) clamp(18px, 3vw, 32px);
  text-align: center;
}
.about-values-inner { max-width: 1100px; margin: 0 auto; }
.about-values-title { margin-bottom: clamp(28px, 4vw, 44px); }
.about-values-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  text-align: left;
}

.about-values-pin { position: relative; width: 100%; }
.about-values-sticky { width: 100%; }

/* Card: dark→ now a solid palette colour (set inline per card). Self-contained
   styling (no longer reuses .tool-card, which carried a transform transition +
   hover-lift that would fight the scroll-linked tilt/spread). */
.about-value-card {
  position: relative;
  overflow: hidden;
  border-radius: 16px;
  padding: 24px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  cursor: pointer;
  /* The scroll stack-fan drives `transform` (inline motion style), so hover
     feedback uses border + shadow (not transform) so the two never fight. */
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
/* ⚠️ `#A06EFF` WAS THE PRE-LIGHT-THEME ACCENT AND IS NOT A BRAND COLOUR — it
   is retired here even though this rule is DEAD (the page always carries
   `.about-page`, so the (0,2,0) block further down wins both properties). A
   dead rule holding a stale colour is a copy-paste hazard, which is exactly how
   the stray reached the cursor bloom in About.jsx. */
.about-value-card:hover {
  border-color: var(--rf-primary-on-dark, #8B7BE0);
  box-shadow: 0 22px 54px -26px rgba(139, 123, 224, 0.4);
}
/* 1.06, matching `.ft-tile:hover .ft-tile-ico` exactly — it was 1.08. */
.about-value-card:hover .about-value-icon { transform: scale(1.06); }

/* Stack reads cleanly: the middle card sits on top of the side cards. */
.about-values-grid.is-animated .about-value-card--mid { z-index: 2; }
.about-values-grid.is-animated .about-value-card--left,
.about-values-grid.is-animated .about-value-card--right {
  z-index: 1;
  will-change: transform;
}

/* Card content sits above the animated gradient backdrop. */
.about-value-content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.about-value-icon {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  background: var(--card-2);
  border: 0.5px solid var(--card-border);
  color: var(--text);
  font-size: 18px;
  margin-bottom: 6px;
  transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
}
.about-value-title { color: var(--text); font-size: 16px; font-weight: 700; margin: 0; }
.about-value-body { color: var(--text-dim); font-size: 13.5px; line-height: 1.55; margin: 0; }

/* On the solid colour cards: white title, light body, white icon chip (matches
   the Demo "Get in touch" cards). */
.about-value-card--colored .about-value-title { color: #ffffff; }
.about-value-card--colored .about-value-body { color: rgba(255, 255, 255, 0.8); }
.about-value-card--colored .about-value-icon {
  background: #ffffff;
  border-color: rgba(0, 0, 0, 0.08);
  color: #0A0A0A;
}
.about-value-icon svg { width: 21px; height: 21px; }

/* ===== Stats: sticky stack-and-deal on the page background ===== */
/* Tall outer wrapper supplies the scroll distance; the inner element pins via
   CSS `position: sticky` (NOT GSAP pin — that avoids the pin-spacer cascade
   that broke other sections). Height resets to auto on mobile / reduced-motion
   below, which also drops the pin. No backdrop — the section sits on the same
   dark page background as the rest of the About page. */
.about-stats {
  position: relative;
  height: 250vh;
}
.about-stats-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(32px, 5vw, 64px) clamp(18px, 4vw, 48px);
}
.about-stats-inner {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  text-align: center;
}
.about-stats-eyebrow { opacity: 0.7; }
.about-stats-heading {
  color: var(--text);
  font-size: clamp(28px, 4.5vw, 52px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
  margin: 0 0 clamp(24px, 4vw, 48px);
}

/* Desktop: scattered absolute collage. */
.about-stats-cluster {
  position: relative;
  height: clamp(360px, 42vw, 470px);
  max-width: 1000px;
  margin: 0 auto;
}
.about-stat-slot {
  position: absolute;
  width: clamp(180px, 22vw, 240px);
}
.about-stat-card {
  transform: rotate(var(--rot, 0deg)); /* fallback scatter when GSAP is skipped */
  will-change: transform;
  border-radius: 18px;
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  box-shadow: 0 26px 54px rgba(0, 0, 0, 0.55);
  cursor: pointer;
  /* GSAP drives `transform` here too → hover uses border + shadow only. */
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
.about-stat-card:hover {
  border-color: rgba(160, 110, 255, 0.55);
  box-shadow: 0 30px 60px -18px rgba(160, 110, 255, 0.45), 0 26px 54px rgba(0, 0, 0, 0.55);
}
.about-stat-num {
  font-size: clamp(30px, 4vw, 46px);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
}
.about-stat-label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
}

/* Solid palette cards (same trio as the value / Demo cards). Background colour
   is set inline per card; white number + light label for contrast. */
.about-stat-card--colored {
  color: #ffffff;
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.about-stat-card--colored .about-stat-label { color: rgba(255, 255, 255, 0.8); }

/* White cards — mixed into the collage for contrast on the dark background.
   Dark number (inherited from the card color) + muted slate label. */
.about-stat-card--light {
  color: #0a0a0b;
  border: 1px solid rgba(0, 0, 0, 0.06);
}
.about-stat-card--light .about-stat-label { color: #52525b; }

/* Reframing footnote under the cluster — keeps the IG vanity metric but pivots
   to "these are the B2B numbers that matter". */
.about-stats-foot {
  margin: clamp(28px, 4vw, 44px) auto 0;
  max-width: 60ch;
  color: rgba(255, 255, 255, 0.62);
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.55;
}

/* ===== From the founder — the black band, and the scroll-driven zoom =====
   The section is only a RUNWAY: its height is the scroll distance the zoom
   consumes, and `.about-founder-sticky` is the frame the visitor actually
   looks at. 260vh = one pinned screen plus 160vh of travel, which is
   `DemoLauncher`'s own default — reused rather than re-picked, so the two
   zooms on this site take about the same amount of scrolling.

   ⚠️ THE SECTION MUST NEVER GET `overflow: hidden`. It is the sticky child's
   containing block; clipping it makes it a scroll container and the pin dies
   on the spot (§CS8, and §CQ3 before that). The clip that keeps the grown
   portrait inside the frame goes on the STAGE, where it costs nothing —
   `overflow` on a sticky element itself does not break its own stickiness,
   only an ANCESTOR's does. */
.about-founder {
  position: relative;
  z-index: 1;
  min-height: var(--af-len, 260vh);
  padding: 0;                     /* the padding moved to the stage */
}
/* ⚠️ THE CLIP EARNS ITS KEEP TWICE. It bounds the grown portrait to the frame,
   AND — with the driven radius below — it is what makes the veil and the reveal
   layer respect the section's rounded lip instead of painting a square over it.
   One `overflow` doing both jobs is why the radius can live here rather than on
   each of the two layers.
   ⚠️ `overflow` on a STICKY element does not break its own stickiness; only an
   ANCESTOR's does. That is the whole reason the clip is here and not on the
   section, where it would kill the pin (§CS8). */
.about-founder-sticky {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
  /* Driven from About.jsx: the section's lip while its top is on screen, 0 once
     pinned. A static value here would round the top of the VIEWPORT for the
     whole pin — §CM2's two notches. Defaults to 0 so a frame before the first
     update is square rather than notched. */
  border-radius: var(--af-lip, 0px) var(--af-lip, 0px) 0 0;
  padding: clamp(48px, 8vw, 100px) clamp(18px, 3vw, 32px) clamp(36px, 5vw, 60px);
}
.about-founder-grid {
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 0.8fr 1.2fr;
  gap: clamp(28px, 4vw, 48px);
  align-items: center;
}

/* ⚠️ THE SLOT IS DELIBERATELY UNTRANSFORMED AND MUST STAY THAT WAY — it is the
   only thing the effect can measure without reading its own output back. If a
   future change needs to move the portrait, move `.about-founder-zoom-in`.
   ⚠️ AND IT CARRIES THE CIRCLE'S SIZE, so the slot and the circle are the same
   box and their centres cannot drift (see `.about-founder-photo`). A full-column
   circle would be ~413px across at desktop, which reads as a bubble rather than
   a portrait; the reference sits nearer 240. */
.about-founder-zoom {
  width: min(100%, clamp(190px, 22vw, 290px));
  margin-inline: auto;
}
.about-founder-zoom-in {
  /* JS writes a finished `transform` here (see About.jsx for why it is not
     composed from a custom property). `will-change` because this is the one
     element on the page transformed on every scroll frame. */
  will-change: transform;
  transform-origin: center center;
}
/* ⚠️ THE FADE IS ON THIS WRAPPER, NOT ON `.about-founder-text`, which is a
   `<Reveal>` and owns its own `opacity` + `transition`. Multiplying two
   opacities composes; overriding one fights it. */
/* Same pairing as `.about-founder-zoom-in` below: the fade and the hit test are
   driven off ONE number (`copy`) so a faded quote cannot keep swallowing the
   pointer over the values band behind it. */
.about-founder-copy {
  opacity: var(--af-copy, 1);
  pointer-events: var(--af-copy-hit, auto);
}
/* ⚠️ THE "FOUNDER PHOTO, REPLACE" TAG LEAVES WITH THE COPY, and it is not
   tidiness — it is INSIDE the element being scaled, so at full zoom an 11px
   dev note is rendered at ~38px across the frame. It is a placeholder label,
   not content, so fading it is right; anything that must survive the zoom has
   to live outside `.about-founder-zoom-in` instead. */
.about-founder-tag { opacity: var(--af-copy, 1); }
/* ⚠️ A CIRCLE, AND THE SIZE LIVES ON THE SLOT, NOT HERE. `.about-founder-zoom`
   sets the width; this fills it and squares itself with `aspect-ratio: 1`.
   That is not a style preference — the zoom measures the SLOT for the centre
   it has to travel to, so the moment the photo is narrower than its slot the
   two centres diverge and the portrait lands off-centre at full zoom. §CT4
   caught a 4px version of exactly that; a `max-width` here would have made it
   ~56px. Keep the slot and the circle the same box. */
.about-founder-photo {
  /* ---- portrait framing, the two numbers worth touching --------------------
     ⚠️ A SQUARE PHOTO IN A CIRCLE IS NOT "JUST CROP THE CORNERS". `object-fit:
     cover` with a square source in a square box crops NOTHING, so the subject
     stays wherever the photographer put them — and the circle then removes the
     corners, taking the shoulders and elbows while leaving the head stranded
     high. A portrait wants the head in the upper third and the shoulders across
     the widest part of the disc, so the image is zoomed and re-anchored.

     THE MAPPING, so the next change can be computed instead of guessed. With
     `scale(Z)` about `transform-origin: 50% O`, a point at height `f` of the
     SOURCE lands at height `O + (f − O)·Z` of the circle, and the visible
     window is `f0 = O(1 − 1/Z)` … `f1 = O + (1 − O)/Z`.

     Measured on `public/founder.jpg` (1254², subject centred at x≈52%): cap top
     f≈0.11, eye line f≈0.27, chin f≈0.45, crossed arms f≈0.78.
     At Z = 1.3, O = 8% → window 1.9%…78.8%, which puts the cap top at 12% of
     the circle (clear of the edge) and the eye line at 33% — the upper third a
     portrait wants, with the folded arms across the disc's widest point.
     ⚠️ O IS NOT "WHERE THE FACE IS" — raising it moves content UP, because
     `d(newF)/dO = 1 − Z` is NEGATIVE. That sign is the easy thing to get
     backwards; use the formula, not intuition. */
  --af-img-zoom: 1.3;
  --af-img-origin: 8%;      /* vertical transform-origin — see the mapping above */

  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  border: 1px solid var(--card-border, rgba(255, 255, 255, 0.12));
  overflow: hidden;
  display: grid;
  place-items: center;
  background:
    radial-gradient(120% 120% at 30% 20%, rgba(139, 108, 255, 0.25), transparent 55%),
    linear-gradient(160deg, #1a1a20, #0d0d10);
}

/* The portrait itself. ⚠️ `inset: 0` + `object-fit: cover` means it fills the
   disc for ANY source aspect ratio — a portrait-orientation photo would letter-
   box without it, and a circle with bars in it is unmistakable. The circle's own
   `overflow: hidden` is the mask, so the image needs no radius of its own.
   ⚠️ AND IT IS NOT `loading="eager"`: this sits well below the fold on a page
   whose hero already owns the critical path. */
.about-founder-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(var(--af-img-zoom, 1.3));
  transform-origin: 50% var(--af-img-origin, 8%);
  /* Matches the panel's own gradient, so a slow connection shows the placeholder
     ground rather than a white flash inside a dark circle. */
  background: #16161a;
}

/* ===========================================================================
   THE PORTAL — three layers inside the pinned frame
   ---------------------------------------------------------------------------
   1  `.about-reveal-next`   the real next section, sitting behind everything
   2  `.about-founder-veil`  the black band, ERASED by a growing circular hole
   3  `.about-founder-grid`  the quote and the portrait, above the hole

   ⚠️ THE CIRCLE IS A HOLE, NOT A DISC. The first build faded it to a white
   surface, which is what the operator rejected: a blank fill can never read as
   "the next page came out of the circle" no matter what colour it is. The veil
   is masked instead, so what appears inside the circle is whatever is behind
   the veil — and what is behind it is the actual values section.

   ⚠️ AND THE NEXT SECTION IS THE REAL ONE, NOT A COPY. `valuesBand` is declared
   once in About.jsx and rendered EITHER here or as a normal band, never both.
   That avoids the duplicated subtree this effect usually costs (and a second,
   lying a11y tree). It is only possible because the values content measures
   349px against an 800px frame — if that content ever grows past the frame,
   this has to become a scroll-through, not a pin.
   ========================================================================= */
.about-reveal-next {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  align-items: center;
  overflow: hidden;
  background: var(--rf-surface, #FFFFFF);   /* the values band's own ground */
}
/* Inside the frame it is a layer, not a band: no lip, no negative margin, no
   z-index of its own — those belong to the flow version. */
.about-values--revealed {
  width: 100%;
  margin: 0;
  border-radius: 0;
}

.about-founder-veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--ab-ink-band);
  pointer-events: none;
  /* ⚠️ THE HARD STOP (99% → 100%) IS WHAT MAKES IT A WINDOW. A soft radial
     stop would feather the edge into a vignette, which reads as the band
     fading rather than as a hole being opened.
     ⚠️ `-webkit-` FIRST AND UNPREFIXED SECOND — Safari still needs the prefixed
     property, and a browser that supports both must land on the standard one. */
  -webkit-mask-image: radial-gradient(circle var(--af-hole, 0px) at var(--af-hx, 50%) var(--af-hy, 50%),
                      transparent 99%, #000 100%);
  mask-image: radial-gradient(circle var(--af-hole, 0px) at var(--af-hx, 50%) var(--af-hy, 50%),
              transparent 99%, #000 100%);
}

/* The quote + portrait ride above the hole. */
/* ⚠️⚠️ AND THE WHOLE LAYER HAS TO STOP TAKING THE POINTER WHEN IT STOPS BEING
   VISIBLE. `opacity: 0` hides an element; it does NOT remove it from hit
   testing. This layer is `z-index: 2` over `.about-reveal-next`, which holds the
   REAL values band, so once the portrait had faded the cards behind it were
   fully on screen and completely unhoverable — measured at p = 0.92: the
   pointer landed on `img.about-founder-img`, on `.shl-word` inside the faded
   quote, and on `.about-founder-quote`.

   ⚠️⚠️ THE GATE IS ON THE **GRID**, NOT ON THE FADED CHILDREN, AND THAT
   DISTINCTION COST A ROUND TRIP. `pointer-events: none` excludes an element and
   its SUBTREE — it does nothing for its ANCESTORS' own boxes. Putting the flag
   on `.about-founder-zoom-in` and `.about-founder-copy` moved the hit from the
   `<img>` to `div.about-founder-zoom` and `div.about-founder-grid`, i.e. it
   looked like a fix and changed nothing the user can feel. Only the outermost
   layer-3 element takes the whole stage out of the hit test.

   Driven off `photoFade` (the LATER of the two fades) by About.jsx, so the
   layer is released at exactly the point it becomes invisible and not before;
   `copy` is long since 0 by then. Fallback `auto` = the resting state. */
.about-founder-sticky > .about-founder-grid {
  position: relative;
  z-index: 2;
  pointer-events: var(--af-photo-hit, auto);
}
/* ⚠️ THE PORTRAIT FADES, THE HOLE DOES NOT — they are two properties on two
   elements and they must stay independent. Fading the hole instead would dim
   the next section on its way in; fading the circle lets you see THROUGH the
   photo to what the hole has already opened. */
/* ⚠️ NO `pointer-events` HERE — it belongs on `.about-founder-grid` above, and
   putting it here instead is the half-fix documented there (the hit just moves
   up to `.about-founder-zoom`). */
.about-founder-zoom-in { opacity: var(--af-photo, 1); }
.about-founder-av {
  width: 108px;
  height: 108px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 34px;
  font-weight: 800;
  color: #fff;
  background: linear-gradient(135deg, #8b6cff, #5b9dff);
}
.about-founder-tag {
  position: absolute;
  bottom: 16px;
  left: 16px;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim, #71717a);
  font-weight: 700;
}
.about-founder-quote {
  font-size: clamp(22px, 2.8vw, 30px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.3;
  color: var(--text, #f4f4f5);
  margin: 14px 0 0;
}
.about-founder-byline { margin-top: 24px; }
.about-founder-nm { font-weight: 700; color: var(--text, #f4f4f5); }
.about-founder-rl { color: var(--text-dim, #71717a); font-size: 14px; font-weight: 600; margin-top: 2px; }
@media (max-width: 860px) {
  .about-founder-grid { grid-template-columns: 1fr; }
  /* ⚠️ ON THE SLOT, NOT ON `.about-founder-photo` — it used to cap the photo
     directly, which would now make the circle narrower than the box the zoom
     measures and land it off-centre. Same rule as the base sizing. */
  .about-founder-zoom { width: min(100%, 260px); margin-inline: 0; }
}

/* ===== Mid-page CTA — reuses the home final-CTA card styling (dark surface,
   tilt + glare, glow-pulse button). Only the subhead needs its own rule, since
   the home card has no sub-paragraph. Sits above the glare overlay (z-index). */
.about-cta-sub {
  position: relative;
  z-index: 1;
  color: var(--text-dim, #a1a1aa);
  margin: 0 auto 28px;
  font-weight: 600;
  max-width: 52ch;
  font-size: clamp(13px, 1.4vw, 15px);
}

/* ============================================================================
   THE BAND GROUNDS, AND THE INK EACH ONE CARRIES
   ----------------------------------------------------------------------------
   Everything above is layout and lives where it always did. This block is the
   colour system, kept together so the page's rhythm can be read in one place
   instead of being reconstructed from six scattered sections.
   ========================================================================= */

/* ---- WHITE bands ---------------------------------------------------------
   #FFFFFF, one step brighter than `.home-page`'s own #F5F5F5 ground, so the
   band reads as a band and the CTA's #F5F5F5 below still differs from it. */
.about-page .about-story,
.about-page .about-stats,
.about-page .about-values { background: var(--rf-surface, #FFFFFF); }

/* Headings and eyebrows on those bands take the page's light-theme ink, which
   `.home-page` already supplies — nothing to override. The two `.about-accent`
   words on them ("scale." / "platform.") take the LIGHT accent, which is the
   `--about-accent` default. Stated as a rule anyway, because both of them were
   pointed at the on-dark step while these bands were black and a silent
   inheritance is how that kind of thing survives a redesign. */
.about-page .about-stats .about-accent,
.about-page .about-values .about-accent { color: var(--about-accent); }   /* 5.31:1 on white */

/* ⚠️ IT WAS `rgba(255,255,255,0.62)` — WHITE ON #F5F5F5, 1.05:1, the one
   failure CONTEXT.md's issue 38 actually named. It has now been wrong on two
   different grounds in one session (white-on-white, then correct on black,
   now back on white), which is the argument for a solid token tied to the
   band rather than an alpha that changes meaning underneath you. */
.about-page .about-stats-foot { color: var(--rf-ink-dim, #4A4A52); }   /* 8.6:1 */

/* ---- The DARK CARDS, which now sit on WHITE bands -----------------------
   ⚠️ THE FILLS ARE INLINE (see About.jsx), so only rims and states are
   reachable from here — and the cards stay dark deliberately. Three navy value
   cards on a white band is the ordinary "dark feature card" pattern, and it is
   what keeps their white titles and the inverted icon tile correct with no
   further change. */
.about-page .about-stat-card--colored,
.about-page .about-value-card { border-color: var(--ab-ink-line); }
.about-page .about-stat-card--colored .about-stat-label { color: var(--ab-on-ink-body); }
.about-page .about-value-card--colored .about-value-body { color: var(--ab-on-ink-body); }

/* The tinted collage cards. ⚠️ THE FILL IS `CARD_LIGHT` IN About.jsx, NOT
   HERE — it is an inline style, so setting `background` in this rule would do
   nothing without `!important`. Only the ink, the rim and the lift are
   reachable from CSS. Measured on #EEECFA: the numeral and label at #453A8C
   are 8.05:1.
   ⚠️ A FIRMER RIM THAN THE PAGE'S OTHER LIGHT CARDS, DELIBERATELY. These six
   are rotated and absolutely positioned with no grid to anchor them, so the
   edge is the only thing describing where each card is; `.about-story-card`
   sits in a two-column layout and can afford a quieter one. Painted on white
   this rim is 1.75:1 against the band — versus 1.27:1 for the founding card
   the page already ships. */
.about-page .about-stat-card--light {
  color: var(--ab-accent-deep);
  border: 1px solid rgba(106, 90, 205, 0.40);
  box-shadow: var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
}
.about-page .about-stat-card--light .about-stat-label { color: var(--ab-accent-deep); }
/* The navy cards keep their heavy drop shadow; on white it is what lifts them
   off the band instead of the glow that only worked on black. */
.about-page .about-stat-card--colored {
  box-shadow: 0 26px 54px -22px rgba(16, 16, 20, 0.45);
}

/* ⚠️ THE HOVER EDGE FOLLOWS THE CARD, NOT THE BAND. The navy cards are still
   dark surfaces, so their rim needs the on-dark step (the brand is 3.62:1 on
   them); the tinted cards are light surfaces and take the brand itself.

   ⚠️⚠️ THE SHADOW IS A PURPLE GLOW, AND A NEUTRAL ONE HERE IS THE SAME AS NONE
   (operator-reported: "add same hover effect that we have added in features
   page cards"). It was `rgba(16,16,20,0.5)` — a BLACK shadow cast by a dark
   card onto a BLACK band, i.e. invisible. The whole hover therefore reduced to
   a 1px border change, which is why /features' cards read as responding and
   these did not. **The value is byte-identical to `.features-page .ft-dark
   .ft-tile:hover`** (Features.css ~L191) so the two pages' dark cards cannot
   drift; change one → change both. */
.about-page .about-stat-card--colored:hover,
.about-page .about-value-card:hover {
  border-color: var(--rf-primary-on-dark, #8B7BE0);
  box-shadow: 0 22px 54px -26px rgba(139, 123, 224, 0.4);
}
.about-page .about-stat-card--light:hover {
  border-color: var(--rf-primary, #6A5ACD);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}

/* ---- THE ONE BLACK BAND: the founder quote -------------------------------
   ⚠️ TURNING A LIGHT SECTION DARK IS NEVER JUST A `background` CHANGE. Every
   scroll-driven colour on it has to move too — the quote is a
   `<ScrollHighlight>` whose defaults are light-theme ink (fixed in the JSX),
   and its accent span, its byline and its eyebrow all read tokens tuned for
   white. §CQ5 recorded this the last time a band flipped. */
.about-page .about-founder {
  background: var(--ab-ink-band);
  color: var(--ab-on-ink-body);
}
.about-page .about-founder .about-founder-quote { color: var(--ab-on-ink); }
/* The brand purple is 3.62:1 on this ground — the on-dark step is 5.50:1, and
   this accent sits inside a 30px quote where either would technically pass.
   It takes the safe one anyway: the byline below it is 14px and shares the
   band, so one value for the whole section is one fewer thing to get wrong. */
.about-page .about-founder .about-accent { color: var(--rf-primary-on-dark, #8B7BE0); }
.about-page .about-founder .feature-eyebrow { color: var(--rf-primary-on-dark, #8B7BE0); }
.about-page .about-founder-nm { color: var(--ab-on-ink); }
.about-page .about-founder-rl { color: var(--ab-on-ink-dim); }   /* 8.7:1 */

/* ⚠️ THE ICON TILE HAD TO INVERT, NOT DIM. `.about-value-card--colored
   .about-value-icon` is a SOLID WHITE square with a near-black glyph — on a
   #12172A card inside a black band it is the brightest object in the section,
   louder than the heading beside it, and it drags the eye to an icon instead
   of a value. A low-alpha wash of the on-dark purple with the glyph in that
   same colour inverts the figure/ground instead of leaving a light artefact.
   Identical treatment to `.ft-tile-ico` on /features. */
.about-page .about-value-card--colored .about-value-icon {
  background: rgba(139, 123, 224, 0.16);
  border-color: var(--ab-ink-line);
  color: var(--rf-primary-on-dark, #8B7BE0);   /* 5.50:1 */
}
.about-page .about-value-card--colored:hover .about-value-icon {
  background: var(--rf-primary-on-dark, #8B7BE0);
  color: var(--ab-ink-band);                    /* 5.50:1, inverted */
}

/* ⚠️ THE RING FOLLOWS THE BAND, AND THE BANDS JUST SWAPPED. Brand purple is
   5.31:1 on white and 3.62:1 on `--rf-night`; the on-dark step is the reverse.
   These two rules were pointed at the opposite sections one revision ago —
   a focus ring is the easiest thing to leave behind in a band change, because
   nothing renders it until someone reaches for the keyboard.

   ⚠️ FOUR OF THESE FIVE SECTIONS HAVE NO FOCUSABLE CONTENT TODAY (censused,
   not assumed: the page's only two are the hero CTA and the closing CTA), so
   most of this block is pre-emptive. It is kept because the cost is nothing
   and the failure mode is silent — add a link to the founder quote without it
   and the ring lands at 3.62:1 on near-black. */
.about-page .about-story :focus-visible,
.about-page .about-stats :focus-visible,
.about-page .about-values :focus-visible {
  outline: 3px solid var(--rf-primary, #6A5ACD);
  outline-offset: 3px;
}
.about-page .about-founder :focus-visible {
  outline: 3px solid var(--rf-primary-on-dark, #8B7BE0);
  outline-offset: 3px;
}
/* ⚠️ AND THIS ONE IS NOT PRE-EMPTIVE — IT WAS A REAL GAP, FOUND BY WALKING THE
   PAGE WITH A REAL TAB. The closing "Book a demo" is one of the page's two
   focusables and no band rule reached it, so it fell through to Chrome's
   default `1px auto` amber ring on a navy card. `.home-final-cta` sits on the
   light band but its BUTTON sits on the dark card, which is why it needs its
   own rule rather than the band's. Matches the one Features.css carries. */
.about-page .home-final-cta-inner :focus-visible {
  outline: 3px solid #FFFFFF;            /* 19.2:1 on the card */
  outline-offset: 3px;
}

/* ---- The founder photo — a dark panel that is now ON the dark band --------
   ⚠️ IT STAYS DARK FOR A REASON THAT DID NOT CHANGE WITH THE BAND: it is a
   placeholder standing in for a PHOTOGRAPH, and inverting it would leave a
   white rectangle where an image goes. What DID change is the rim. It was
   `--rf-border` (#E4E4E7) while the section was white, which on `--rf-night`
   is a bright hairline drawing a box around the one thing that should recede.
   Its own gradient tops out near #1a1a20 — barely above the band — so it
   still needs an edge, just a quiet one. */
.about-page .about-founder-photo { border-color: var(--ab-ink-line); }
.about-page .about-founder-tag { color: rgba(255, 255, 255, 0.75); }
/* The avatar's own ramp was #8b6cff → #5b9dff, a pair chosen to glow on a
   near-black page. On the brand ramp the initials measure 5.31 / 7.40:1. */
.about-page .about-founder-av {
  background: linear-gradient(135deg, var(--about-accent), var(--ab-accent-deep));
}

/* ---- CTA band ------------------------------------------------------------
   #F5F5F5, the ground the home page's own CTA sits on — so the navy card,
   its glare, its tilt and its glow-pulse all land exactly as they do there.
   `.home-page .home-final-cta-inner` already supplies the navy fill and the
   white heading; the sub-line is this page's own element and was the only
   piece left reading light-theme ink on it (1.81:1).
   ⚠️ ParallaxLayers renders at `z-index: -1` INSIDE this section. A negative-z
   child paints above its parent's background, so giving the section a ground
   does not hide it — verified, not assumed. */
.about-page .home-final-cta { background: var(--rf-bg, #F5F5F5); }
.about-page .about-cta-sub { color: var(--ab-on-ink-body); }

/* ---- The black close -----------------------------------------------------
   ⚠️ THE LAST LIP IS ON `.home-closing`, NOT ON `.site-footer` — the footer is
   a shared component six pages render; the closing wrapper is a plain div this
   page's own GlobalFooter owns.
   ⚠️ AND `overflow: hidden` IS LOAD-BEARING HERE AND NOWHERE ELSE ON THIS
   PAGE. The footer paints its own opaque navy and would go straight over the
   rounded corners without a clip. Nothing inside is sticky, so the clip costs
   nothing — unlike on `.about-stats`, where it would have killed the pin. */
.about-page .home-closing {
  border-radius: var(--ab-lip) var(--ab-lip) 0 0;
  margin-top: calc(-1 * var(--ab-lip));
  overflow: hidden;
  position: relative;
  z-index: 2;
}

/* ===== Responsive ===== */
@media (max-width: 1023px) {
  .about-values-grid { grid-template-columns: repeat(2, 1fr); }
}
/* Mobile: drop the sticky pin + parallax entirely. Static section, cards in a
   clean readable grid over the video's first frame (poster). */
@media (max-width: 767px) {
  .about-values-grid { grid-template-columns: 1fr; }
  .about-stats { height: auto; }
  /* ⚠️ THE FOUNDER ZOOM DROPS HERE, AND THE THRESHOLD IS NOT A CHOICE — it has
     to be the SAME 767px `computeMotionMode()` uses in About.jsx, because that
     is what stops the JS running. If the two ever disagree you get a section
     with a 260vh runway and nothing driving it: a screen and a half of empty
     black. Same pairing the stats pin above already relies on. */
  .about-founder { min-height: 0; }
  .about-founder-sticky {
    position: static;
    height: auto;
    overflow: visible;
  }
  .about-founder-zoom-in { transform: none !important; }
  .about-founder-copy,
  .about-founder-tag { opacity: 1 !important; }
  /* ⚠️ AND THE PORTRAIT MUST GO BACK TO FULLY OPAQUE. A half-faded circle is
     the one failure mode here that looks like a missing image rather than a
     missing animation. The veil is not rendered at all in this mode (the JSX
     gates it on ), so there is no hole to reset. */
  .about-founder-zoom-in { opacity: 1 !important; }
  .about-stats-sticky {
    position: static;
    height: auto;
    padding: clamp(56px, 11vw, 90px) clamp(18px, 4vw, 28px);
  }
  .about-stats-cluster {
    position: static;
    height: auto;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 14px;
    max-width: 480px;
  }
  .about-stat-slot { position: static; width: auto; }
  .about-stat-card { transform: none; }
}
@media (max-width: 440px) {
  .about-stats-cluster { grid-template-columns: 1fr; max-width: 320px; }
}
/* Reduced motion: no pin, no parallax — static section in normal flow. The
   desktop absolute collage still applies (cards show scattered via the CSS
   --rot fallback); the video stays paused on its poster frame (autoplay is
   gated on motion in the JSX). */
@media (prefers-reduced-motion: reduce) {
  .about-stats { height: auto; }
  /* The founder zoom is a large, sustained scroll-linked movement — exactly
     what this query exists for. It becomes an ordinary two-column section:
     portrait at its resting size, quote at full opacity, no runway.
     ⚠️ `!important` on the two the JS writes: it sets them as INLINE styles
     (`transform`) and a custom property, and neither can be beaten by a normal
     rule. The effect also resets itself when `motion` flips — this is the
     belt to that braces, because the two must not disagree even for a frame. */
  .about-founder { min-height: 0; }
  .about-founder-sticky {
    position: static;
    height: auto;
    overflow: visible;
  }
  .about-founder-zoom-in { transform: none !important; }
  .about-founder-copy,
  .about-founder-tag { opacity: 1 !important; }
  /* ⚠️ AND THE PORTRAIT MUST GO BACK TO FULLY OPAQUE. A half-faded circle is
     the one failure mode here that looks like a missing image rather than a
     missing animation. The veil is not rendered at all in this mode (the JSX
     gates it on ), so there is no hole to reset. */
  .about-founder-zoom-in { opacity: 1 !important; }
  .about-stats-sticky {
    position: static;
    height: auto;
    padding: clamp(56px, 9vw, 110px) clamp(18px, 4vw, 48px);
  }
}
/* ============================================================================
   HeroAdminPanel — the admin-panel preview the hero zooms into.

   ⚠️ THIS IS A PICTURE OF THE DARK PRODUCT, NOT CHROME. It is deliberately
   excluded from the light theme (see the CB4 note in Landing.css): inverting
   it would turn the product screenshot into a white box.
   ========================================================================= */

.hap {
  /* ⚠️ SEPARATE HORIZONTAL AND VERTICAL RADII (the `x / y` form). The hero
     scales this panel non-uniformly while it is emerging from the tools card,
     so a single radius would be drawn as an ellipse — most visibly at rest,
     where the vertical squash is greatest. Each axis is pre-divided by its own
     scale in LandingHero.jsx. Falls back to a plain 10px when nothing drives
     it, so the panel is still correct with no JS. */
  border-radius: var(--hap-rx, 10px) / var(--hap-ry, 10px);
  overflow: hidden;
  /* Shadow and rim both deepen as the panel takes the frame, so it reads as a
     screen lifting off the page rather than a picture pasted onto it. */
  box-shadow:
    0 30px 80px -40px rgba(0, 0, 0, calc(0.6 + var(--tk, 0) * 0.3)),
    0 0 0 1px rgba(255, 255, 255, calc(0.07 + var(--tk, 0) * 0.09)),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

/* ⚠️ CONTAINER, NOT VIEWPORT. Everything in the sidebar overlay below is sized
   in `cqw` — percent of THIS box's own width — because the panel gets
   transform-scaled several times over during the takeover. Anything sized in
   px or vw keeps its layout value and is then multiplied by that scale, which
   is what blew the sidebar up to ~42px type sprawling across the screenshot.
   A cqw is a fraction of the panel, so the overlay grows in exact step with
   the image underneath it at any scale. */
.hap-screen {
  position: relative;
  display: block;
  line-height: 0;
  container-type: inline-size;
}

/* ---- the four pages -----------------------------------------------------
   The transition is the site's own Reveal gesture — the arriving page rises
   and resolves, the leaving one lifts away — on the shared easing.

   ⚠️ THREE STATES, NOT TWO. Without `is-leaving`, "not yet arrived" and "just
   left" share one resting transform, so the outgoing screenshot slides DOWN
   while fading, against the direction of travel. See the note in the .jsx.

   ⚠️ TRANSLATE IN %, NOT px. A percentage in translate() resolves against the
   ELEMENT's own box, so the movement stays proportional as the panel is scaled
   from a 720px card to the full frame. A px value would be multiplied by the
   zoom and turn a 10px drift into a 60px lurch. */
.hap-img {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0;
  transform: translateY(2.2%) scale(1.008);
  transition:
    opacity 0.52s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.52s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: 1;
}

/* Page 0 stays in flow: it is what gives the frame its height. Its opacity and
   transform still follow the rules above, so it behaves like the others. */
.hap-img--base { position: relative; }
.hap-img:not(.hap-img--base) { position: absolute; inset: 0; }

.hap-img.is-active {
  opacity: 1;
  transform: none;
  z-index: 3;
}

/* ⚠️ ABOVE THE BASE WHILE IT LEAVES. Without this the outgoing page fades out
   UNDER page 0, so every transition that is not to-or-from the dashboard shows
   a flash of the dashboard in the middle of it. */
.hap-img.is-leaving {
  opacity: 0;
  transform: translateY(-1.4%) scale(1.014);
  z-index: 2;
}

/* ---- the live sidebar, drawn over the screenshot's own ------------------ */
.hap-rail {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: 16%;
  padding: 3.4cqw 1.6cqw;
  box-sizing: border-box;
  background: #0d0d0f;
  display: flex;
  flex-direction: column;
  gap: 3.5cqw;
  z-index: 20;
  line-height: normal;
}
.hap-rail-logo {
  font-family: 'Manrope', system-ui, sans-serif;
  font-size: 1.55cqw;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: #fff;
  white-space: nowrap;
}
.hap-rail-logo span { color: #7a7a82; }

.hap-rail-nav {
  position: relative;          /* the marker is positioned against this */
  display: flex;
  flex-direction: column;
  gap: 0.9cqw;
}

/* THE SELECTION PILL — one element that moves, instead of a background that
   appears on one item and disappears from another. A slide says "you went
   there"; a swap just blinks.

   ⚠️ ITS BOX IS WRITTEN FROM JS IN LAYOUT px. Deriving it from the cqw scale
   would need the item's line box, which changes with the label and the type —
   see the useLayoutEffect in HeroAdminPanel.jsx. Because the marker lives
   inside the same transformed subtree as the rail, px here scale with the
   hero's zoom for free. */
.hap-rail-marker {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--mk-top, 0px);
  height: var(--mk-h, 0px);
  border-radius: 0.6cqw;
  /* The REAL admin panel's selected state: its --accent #FAFAFA on
     --accent-ink #0A0A0A, per the operator ("if customer selected then it
     should become white in sidebar"). */
  background: #fafafa;
  z-index: 0;
  transition:
    top 0.46s cubic-bezier(0.22, 1, 0.36, 1),
    height 0.46s cubic-bezier(0.22, 1, 0.36, 1);
}

.hap-rail-item {
  position: relative;
  z-index: 1;                  /* over the marker, so the label stays legible */
  display: flex;
  align-items: center;
  gap: 0.7cqw;
  padding: 0.75cqw 0.8cqw;
  border-radius: 0.6cqw;
  font-family: 'Manrope', system-ui, sans-serif;
  font-size: 1.05cqw;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  /* Inactive: dark and quiet. */
  color: #6b6b73;
  background: transparent;
  transition: color 0.34s ease, font-weight 0.34s ease;
}
.hap-rail-item svg { width: 1.05em; height: 1.05em; flex: none; }

/* Active: dark ink over the pill. The FILL is the marker's job now — leaving a
   background here too would draw a second pill that pops in at the destination
   while the real one is still travelling. */
.hap-rail-item[data-active='true'] {
  color: #0a0a0b;
  font-weight: 700;
}

.hap-sr {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .hap-img,
  .hap-rail-marker,
  .hap-rail-item { transition: none; }
}
/* ============================================================================
   RntlOS — Features page (LIGHT THEME, session 31)
   ----------------------------------------------------------------------------
   This page was the last surface still on the pre-session-27 dark theme
   (#0a0a0b ground, white type). Everything else flipped to the purple / grey /
   black-on-white system in theme.css, so /features was the one page that did
   not look like the site.

   ⚠️⚠️ IT WAS NOT MERELY INCONSISTENT — IT WAS UNREADABLE, AND THE CSS ALONE
   NEVER SHOWED IT. Two independent causes, both measured in the live app:

     1. `<ScrollHighlightRegion>` wraps this whole page and repaints every
        heading / paragraph / list item it splits from `#4A4A52` to `#111114`
        — LIGHT-theme ink, applied INLINE at runtime, on a near-black ground.
        Measured 1.9:1 on the pillar body copy. A CSS audit passes, because
        the CSS was right and GSAP overrode it.
     2. `.home-page h1, .home-page h2, .home-page h3 { color: var(--rf-ink) }`
        in Landing.css is (0,1,1) and out-specifies `.ft-row-title` (0,1,0),
        so every pillar heading computed **rgb(0,0,0) on rgb(10,10,11)** —
        1.0:1, literally invisible.

   ⚠️ CONSEQUENCE FOR ANYONE EDITING THIS FILE: every heading rule below is
   prefixed with `.features-page` to reach (0,2,0). Dropping that prefix
   silently loses the fight with Landing.css and the heading goes black again.
   This is the same trap theme.css records for `.home-cta-primary`.

   ⚠️ THE PRODUCT MOCKUPS STAY DARK, ON PURPOSE. `.ftf-*` (the browser window)
   and `.ft-phone*` (the handset) are a PICTURE OF THE ADMINPANEL, which is a
   dark app. Inverting them would delete the thing the page exists to show —
   the identical rule Landing.css's light-theme block records for `.dash-*`,
   `.mac-window` and friends. They read their own `--fta-*` tokens so a future
   edit to the page palette cannot reach them by accident.
   ========================================================================= */

/* ⚠️ THE FRAUNCES @import IS GONE. It was a render-blocking cross-origin font
   request serving exactly one element (the hero h1), and the site's heading
   face is Poppins (--rf-font-heading). One family, one fewer round trip. */

.features-page {
  /* ---- page palette — all of it derived from theme.css ------------------
     Ratios are quoted against the ground the value is actually used on and
     were measured in the live app, not estimated. */
  --ft-bg: #F5F5F5;             /* light-grey band */
  --ft-bg2: #FFFFFF;            /* white band */
  --ft-surface: #FFFFFF;        /* cards */
  --ft-surface2: #FAFAFA;       /* card hover / inset */
  --ft-line: #E4E4E7;
  --ft-line2: #D4D4D8;
  --ft-text: #000000;           /* headings          18.1:1 on #F5F5F5 */
  --ft-body: #333333;           /* body copy         11.6:1 */
  --ft-muted: #4A4A52;          /* secondary          8.1:1 */
  --ft-dim: #6B6B75;            /* the floor          4.8:1 — do not lighten */
  --ft-accent: #6A5ACD;         /* brand              5.31:1 on white */
  --ft-accent-deep: #453A8C;    /* brand, text-safe   7.4:1 on white */
  --ft-accent-soft: #EEECFA;    /* brand tint band */
  --ft-accent-tint: rgba(106, 90, 205, 0.08);

  /* ---- ink for the DEEP PURPLE hero band -------------------------------
     Measured against #5C4EB0, the lightest point that band ever paints (see
     the .ft-hero header). Three steps, mirroring the light-band set: a
     headline white, a body lavender that still clears AA at 12px, and an
     accent lavender that only clears the LARGE-text floor — so it is used on
     the 51px headline span and nowhere else. */
  --ft-on-purple: #FFFFFF;        /* 6.62:1 */
  --ft-on-purple-body: #E4DEFF;   /* 5.10:1 — the floor here, do not lighten */
  --ft-on-purple-accent: #C4B8FF; /* 3.67:1 — LARGE TEXT ONLY (≥24px) */
  --ft-on-purple-line: rgba(255, 255, 255, 0.34);   /* control rims, 3.4:1 */

  /* Semantic hues, darkened until they clear AA on white. The originals
     (#4ade80 / #fbbf24 / #f87171) were picked to glow on near-black and
     measure 1.6–2.4:1 here. */
  --ft-success: #15803D;        /* 5.01:1 on #FFFFFF */
  --ft-warn: #B45309;           /* 5.02:1 */
  --ft-danger: #B91C1C;         /* 5.94:1 */

  /* ---- the dark APP palette, for the product mockups only --------------- */
  --fta-bg: #0d0d10;
  --fta-surface: #16161a;
  --fta-surface2: #1d1d22;
  --fta-line: rgba(255, 255, 255, 0.08);
  --fta-line2: rgba(255, 255, 255, 0.16);
  --fta-text: #f4f4f5;
  --fta-dim: #a1a1aa;

  /* ---- THE BLACK BANDS, and the ink that lives on them -----------------
     `--rf-night`, the palette's existing darkest ground — NOT a new #000. It
     is already what `.site-footer` uses, so the page's dark punctuation and
     its dark close belong to one system instead of being two blacks that
     nearly match. Ratios measured on it:
       white       19.2:1   ← headings
       #D6DBE6     14.1:1   ← body copy
       #A8AFC0      8.7:1   ← the floor here, do not lighten
     ⚠️ THE BRAND PURPLE IS 3.62:1 ON THIS GROUND — fine for a border or an
     icon (≥3), UNDER the 4.5 floor for any body-size label. Icons on these
     bands use `--rf-primary-on-dark` (#8B7BE0, 5.50:1), the step §CD added
     for exactly this case. Do NOT use it on light grounds: 3.0:1 on white. */
  --ft-ink-band: #0B0E1A;
  --ft-ink-card: #12172A;
  --ft-ink-line: rgba(255, 255, 255, 0.13);
  --ft-on-ink: #FFFFFF;
  --ft-on-ink-body: #D6DBE6;
  --ft-on-ink-dim: #A8AFC0;

  /* The rounded lip every colour change wears. One value, so the curve is the
     same everywhere — see the `.ft-lip` block for why it is a shared class. */
  --ft-lip: clamp(28px, 3.4vw, 44px);

  --ft-radius: 18px;
  --ft-ease: cubic-bezier(0.22, 1, 0.36, 1);
  /* ONE section rhythm for the whole page — the brief's "consistent padding
     and margin sizes". Every band below uses this and nothing else. */
  --ft-section-y: clamp(56px, 8vw, 112px);
  --ft-gutter: clamp(18px, 4vw, 40px);

  background: var(--ft-bg2);
  line-height: 1.6;             /* the brief's readability floor */
}

/* ⚠️ KEEP THE PAGE OPAQUE. `Landing.css`'s `.home-page:not(.about-page)` rule
   makes every `.home-page` transparent so the site-wide Antigravity canvas
   shows through — which on a white page paints pink particles across the
   copy. Higher specificity than that rule, and loaded after it.
   (The two <AntigravityScoped> layers this page used to mount are gone with
   the dark ground: a magenta particle field is a dark-theme device, and
   dropping them also removes two WebGL canvases from the page.) */

.home-page.features-page { background: var(--ft-bg2); }

.features-page * { box-sizing: border-box; }

/* ===========================================================================
   THE ROUNDED LIP AT EVERY COLOUR CHANGE
   ---------------------------------------------------------------------------
   A section pulls itself UP over the one above by exactly its own radius, so
   the new colour arrives on a curve instead of a hard rule. Same idiom the
   landing page already uses for `.xmq`, `.home-pricing-section`,
   `.demo-launcher` and `.home-deck-lip` (§CB15/CB16, §CM2) — reused rather
   than reinvented so every band on the site curves identically.

   ⚠️ IT IS ONLY ON BOUNDARIES WHERE THE COLOUR FAMILY CHANGES, NOT ON EVERY
   SEAM. The six pillar rows alternate white/#F5F5F5, which is a tone shift
   inside ONE chapter — a lip there would be invisible against the grey and
   would chop a continuous section into six. The lip marks chapters: purple →
   light, light → black, black → light, light → purple.

   ⚠️ `z-index` IS REQUIRED, NOT DECORATION. The negative margin makes these
   sections overlap; without a stacking order the section ABOVE paints over
   the curve it is supposed to reveal. `.home-page > *` already sets
   `position: relative; z-index: 1`, so 2 is enough to win locally. */

.features-page .ft-lip {
  border-radius: var(--ft-lip) var(--ft-lip) 0 0;
  margin-top: calc(-1 * var(--ft-lip));
  position: relative;
  z-index: 2;
}

/* ===========================================================================
   THE BLACK BANDS — ONE `.ft-dark` CLASS, ONE RULE SET
   ---------------------------------------------------------------------------
   Applied in `Features.jsx` to the bento and security sections only. Two, not
   more, and not adjacent: they are the page's PUNCTUATION. Both are card
   grids with a heading and one line per card — nothing on them is long-form
   reading, which is the case where a dark ground measurably costs legibility.
   Putting them either side of the light Integrations band keeps the black
   from doubling up, and neither touches the purple hero or the purple CTA.
   ========================================================================= */

.features-page .ft-dark {
  background: var(--ft-ink-band);
  color: var(--ft-on-ink-body);
  border-top: none;
  border-bottom: none;
}

/* ⚠️ `.features-page` PREFIXES KEEP THESE AT (0,3,0) so they clear
   `.home-page h1,h2,h3` (0,1,1) — the trap recorded in this file's header,
   and the reason the page's headings were once invisible. */

.features-page .ft-dark .ft-section-head h2 { color: var(--ft-on-ink); }

.features-page .ft-dark .ft-section-head p { color: var(--ft-on-ink-body); }

.features-page .ft-dark .ft-eyebrow { color: var(--rf-primary-on-dark, #8B7BE0); }

.features-page .ft-dark .ft-eyebrow::before { background: var(--rf-primary-on-dark, #8B7BE0); }

.features-page .ft-dark .ft-tile h4,
.features-page .ft-dark .ft-sec h4 { color: var(--ft-on-ink); }

.features-page .ft-dark .ft-tile p,
.features-page .ft-dark .ft-sec p { color: var(--ft-on-ink-body); }

/* The cards lift OFF the black rather than sitting in it. */

.features-page .ft-dark .ft-tile,
.features-page .ft-dark .ft-sec {
  background: var(--ft-ink-card);
  border-color: var(--ft-ink-line);
  box-shadow: 0 20px 50px -30px rgba(0, 0, 0, 0.9);
}

.features-page .ft-dark .ft-tile:hover,
.features-page .ft-dark .ft-sec:hover {
  background: var(--ft-ink-card);
  border-color: var(--rf-primary-on-dark, #8B7BE0);
  box-shadow: 0 22px 54px -26px rgba(139, 123, 224, 0.4);
}

/* ⚠️ THE ICON TILE HAD TO INVERT, NOT JUST DIM. `--ft-accent-soft` (#EEECFA)
   is a near-white square — on black it is a bright blob, louder than the
   heading beside it — and the brand glyph inside it drops to 3.62:1. The tint
   becomes a low-alpha wash of the on-dark purple and the glyph takes that
   same colour at 5.50:1, which inverts the figure/ground relationship instead
   of leaving a light artefact behind. */

.features-page .ft-dark .ft-tile-ico,
.features-page .ft-dark .ft-sec-ico {
  background: rgba(139, 123, 224, 0.16);
  color: var(--rf-primary-on-dark, #8B7BE0);
}

.features-page .ft-dark .ft-tile:hover .ft-tile-ico,
.features-page .ft-dark .ft-sec:hover .ft-sec-ico {
  background: var(--rf-primary-on-dark, #8B7BE0);
  color: var(--ft-ink-band);            /* 5.50:1, inverted */
}

/* The brand ring is 3.62:1 here — a focus indicator wants more than that. */

.features-page .ft-dark :focus-visible { outline-color: var(--rf-primary-on-dark, #8B7BE0); }

/* ---- shared bits --------------------------------------------------------- */

.ft-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--rf-font-body, 'Open Sans', Roboto, system-ui, sans-serif);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ft-accent-deep);          /* 7.4:1 — it is 12px, so 4.5 is the floor */
  margin-bottom: 14px;
  line-height: 1.4;
}

.ft-eyebrow::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ft-accent);
  flex: none;
}

.ft-eyebrow--center { justify-content: center; }

/* Hero heading accent. ⚠️ `--ft-on-purple-accent` is a LARGE-TEXT-ONLY value
   (3.67:1). It is legal here because this span is part of the 51px headline;
   reusing it on anything body-sized fails AA. `-webkit-text-fill-color` has
   to move with `color` or the WebKit fill wins and the old value paints. */

.ft-accent {
  color: var(--ft-on-purple-accent);
  -webkit-text-fill-color: var(--ft-on-purple-accent);
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
}

.ft-muted { color: var(--ft-muted); }

/* ⚠️⚠️ THIS PAGE'S `body:has(.features-page) .home-nav-pill` OVERRIDE IS GONE,
   AND DELIBERATELY NOT REPLACED. It raised the shared nav's WHITE frosted pill
   from 0.72 to 0.94 opacity, because over this hero the 0.72 glass composited
   to a lavender slab and the wordmark accent dropped to 3.27:1.

   The nav has no white state any more (see LandingNav.css): it is transparent
   over a hero and frosted NIGHT everywhere else, with one light ink set. So
   the ground this override existed to correct no longer paints, and a
   page-scoped patch on a component five other pages share would be exactly the
   drift that made the bar "change with every page" in the first place. If the
   accent ever measures short again, fix it in LandingNav.css for all of them. */

/* ===========================================================================
   HERO — the shared violet ground, sized to fit ONE FRAME
   ---------------------------------------------------------------------------
   ⚠️⚠️ THE GROUND IS NOT PAINTED HERE ANY MORE. It comes from
   `.rf-hero-ground` in theme.css — the home hero's surface, which this page's
   hero now shares with `/blog`, `/about` and `/pricing` (operator: "implement
   the background of home hero section into hero section of all pages …").
   The class is on `<section className="ft-hero rf-hero-ground">` in the JSX.
   ⚠️ DO NOT RE-ADD A `background` HERE. This file loads after theme.css, so a
   local declaration wins silently and this page alone stops matching the
   others — which is exactly the state the shared class was created to end.

   ⚠️⚠️ DEEP PURPLE, NOT THE BRAND `#6A5ACD`, AND THE REASON IS MEASURED — the
   constraint that chose the old ramp still governs the new one.
   On a flat #6A5ACD ground white is 5.31:1 — fine on its own — but everything
   quieter than white runs out of room immediately: the "in one place."
   accent needs a lighter lavender, and #C4B8FF on #6A5ACD measures **2.46:1**,
   which fails even the 3:1 large-text floor. There is no lavender that both
   reads as an accent AND clears the floor on a mid-purple ground.
   The shared ground's brightest painted point is rgb(81,64,165), DARKER than
   the #5C4EB0 this band used to peak at, so all three inks gained headroom:
     white      #FFFFFF  7.99:1   (was 6.62)  ← headline, and the band's floor
     lavender   #E4DEFF  6.16:1   (was 5.10)  ← eyebrow, sub, proof (AA at 12px)
     accent     #C4B8FF  4.43:1   (was 3.67)  ← the 51px accent only
   ⚠️ NOTHING ON THIS BAND MAY GET LIGHTER, AND THE CEILING IS NOW SHARED. The
   radial bloom sets it; raising its alpha in theme.css lifts the ground and
   silently drops those three ratios on FIVE pages at once, not one.
   ========================================================================= */

.ft-hero {
  color: var(--ft-on-purple);

  /* ⚠️ THE HERO RUNS TO THE TOP OF THE DOCUMENT. `.home-page` reserves
     `--rf-nav-reserve` so content clears the FIXED nav; without cancelling it
     a white strip of page ground sits above the purple band, which reads as a
     rendering fault rather than a design. The reserve is cancelled and paid
     back as this section's own top padding — the identical idiom
     `LandingHero.css` uses, and the reason that value is a token.
     ⚠️ The nav STAYS WHITE over it. Its `.is-on-dark` treatment is keyed to
     `.home-hero--stack`, which this hero deliberately does not carry, so the
     bar needs no change — but do not add that class here. */
  margin-top: calc(-1 * var(--rf-nav-reserve, 76px));

  /* ⚠️ THE BOTTOM PADDING CARRIES THE LIP ON PURPOSE — it is not spacing.
     `.ft-row:first-of-type` climbs `--ft-lip` up over this band, so the last
     `--ft-lip` of this box is UNDER the curve. Without it here, `justify-
     content: center` centres inside a box whose bottom lip is hidden and the
     whole stack sits half a lip low. Stated in terms of the token so the two
     cannot drift apart. */
  padding: calc(var(--rf-nav-reserve, 76px) + clamp(18px, 2.2vh, 40px))
           var(--ft-gutter)
           calc(clamp(28px, 4vh, 56px) + var(--ft-lip));

  /* ⚠️ ONE FRAME — AND THE HEIGHT FORMULA IS SHARED, NOT LOCAL.
     `.rf-hero-ground--frame` (theme.css) supplies
     `min-height: calc(100svh + var(--rf-hero-lip))`; this line only tells it
     WHICH lip this page uses. It used to be a bare `min-height: 100svh` here,
     which is one screen of BAND but one screen MINUS the lip of visible
     purple — the white curve the operator reported at the bottom of the first
     frame. Read the note in theme.css before changing either half.
     ⚠️ DO NOT RE-ADD A LOCAL `min-height` — this file loads after theme.css,
     so it would win silently and put the curve back on this page only. */
  --rf-hero-lip: var(--ft-lip);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* ⚠️ `width: 100%` IS REQUIRED NOW THAT `.ft-hero` IS A FLEX COLUMN. On a
   block parent, `max-width` + `margin-inline: auto` centres a FULL-WIDTH box.
   On a flex item those auto margins override `align-self: stretch`, so the box
   shrinks to fit-content and centres — which silently centred the chip row
   under left-aligned copy. Stating the width restores the block behaviour. */

.ft-hero-inner {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: center;
}

.ft-hero-copy { min-width: 0; }

/* ⚠️ THE DISPLAY SIZES ON THIS PAGE ARE IN PIXELS, NOT `rem`, AND THAT IS
   DELIBERATE. `html` is set to **18px**, not the browser's 16 — so every
   `rem` in theme.css's scale resolves 12.5% larger than that file's own
   comments state, and `--rf-h1` (documented "36 → 64px") actually renders at
   40.5 → 72px. In a one-column centred hero nobody noticed; in this
   two-column hero it is the difference between a 3-line and a 5-line
   headline. Measured, not assumed: the token computed 69.48px at 1280.
   State the pixels you mean and the root size cannot move them. */

.features-page .ft-hero-title {
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  font-weight: 700;
  font-size: clamp(34px, 18px + 2.6vw, 54px);
  line-height: 1.08;
  letter-spacing: -0.02em;
  max-width: 16ch;
  margin: 0;
  color: var(--ft-on-purple);
  text-wrap: balance;
}

.ft-hero-sub {
  margin: 16px 0 0;
  max-width: 52ch;
  font-size: clamp(15px, 0.9rem + 0.2vw, 17px);
  color: var(--ft-on-purple-body);
  font-weight: 400;
  line-height: 1.55;
}

/* ⚠️ THE HERO'S EYEBROW IS ON PURPLE, SO IT CANNOT USE `--ft-accent-deep`
   (#453A8C on this ground is ~1.7:1). The pillar eyebrows below are on white
   and keep it — this override is scoped to the hero for that reason. */

.features-page .ft-hero .ft-eyebrow { color: var(--ft-on-purple-body); }

.features-page .ft-hero .ft-eyebrow::before { background: var(--ft-on-purple); }

.ft-hero-ctas {
  margin-top: 24px;
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

.ft-hero-ctas .home-cta-primary { display: inline-flex; align-items: center; gap: 8px; }

/* ⚠️ THE PRIMARY INVERTS TO WHITE — a purple fill on a purple band is not a
   button. (0,4,0) is required to beat theme.css's doubled
   `.home-cta-primary.home-cta-primary` (0,2,0); see the CTA block's header.
   This is deliberately the SAME treatment the final CTA band uses, so the
   page's two "Book a demo" buttons are one control, not two. */

.features-page .ft-hero .home-cta-primary.home-cta-primary {
  background: #FFFFFF;
  border-color: #FFFFFF;
  color: var(--ft-accent-deep);                  /* 9.0:1 */
  box-shadow: 0 12px 32px -14px rgba(10, 6, 40, 0.65);
}

.features-page .ft-hero .home-cta-primary.home-cta-primary * { color: var(--ft-accent-deep); }

.features-page .ft-hero .home-cta-primary.home-cta-primary:hover {
  background: var(--ft-accent-soft);
  border-color: var(--ft-accent-soft);
}

.features-page .ft-hero .home-hero-cta-ghost {
  background: transparent;
  border-color: var(--ft-on-purple-line);
  color: var(--ft-on-purple);
}

/* ⚠️⚠️ THE GHOST BUTTON HAS A WHITE ::before WIPE, AND OVERRIDING ONLY ITS
   `color` MAKES THE LABEL VANISH. `Landing.css` gives `.home-hero-cta-ghost`
   a white circle that scales from 0 → 1 on hover, and pairs it with
   `:hover { color: #0A0A0A }` so the label goes dark as the fill arrives.
   Setting a white hover label at (0,4,0) beat that pairing but not the wipe —
   white text on a white circle, so the button read as an empty pill.
   Operator-reported.

   ⚠️ THE FIX IS TO RESTYLE THE PAIR, NOT TO CANCEL THE WIPE. The fill is the
   site's signature hover and appears on the nav's Login button too. It becomes
   `--ft-accent-soft` rather than white for one reason: the PRIMARY button on
   these bands is already a solid WHITE pill, so a white wipe would make the
   two buttons identical at the moment of hover and lose the hierarchy between
   them. #453A8C on #EEECFA measures 7.72:1. */

/* Same promotion rule as the CTA's ghost, resolved for THIS band: the hero's
   primary is a solid WHITE pill with a deep-purple label (it has to be — a
   purple fill on a purple ground is not a button), so the ghost's wipe is
   white and its label is that same deep purple. Was `--ft-accent-soft`
   lavender, deliberately different; the operator asked for the match. */

.features-page .ft-hero .home-hero-cta-ghost::before { background: #FFFFFF; }

.features-page .ft-hero .home-hero-cta-ghost:hover {
  background: transparent;              /* the ::before IS the fill */
  border-color: #FFFFFF;
  color: var(--ft-accent-deep);         /* 9.0:1 on white — the primary's own label */
}

/* The brand purple ring is ~1.9:1 on this ground; white is 6.62:1. */

.features-page .ft-hero :focus-visible { outline-color: #FFFFFF; }

.ft-arrow { display: inline-block; transition: transform 0.2s var(--ft-ease); }

.home-cta-primary:hover .ft-arrow { transform: translateX(4px); }

.ft-hero-proof {
  margin-top: 16px;
  color: var(--ft-on-purple-body);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
}

.ft-stars { letter-spacing: 2px; }

/* ⚠️ (0,3,0) TO BEAT theme.css's `.home-page [class*="-stars"]` (0,2,0), which
   sets #B45309 — an amber tuned for WHITE, and 1.6:1 on this band. The stars
   are aria-hidden decoration, so the 3:1 non-text floor applies; #FBBF24
   measures 3.98:1 here. */

.features-page .ft-hero .ft-stars { color: #FBBF24; }

/* ---- the hero image + its purple wash ------------------------------------
   ⚠️ THE WASH IS A SIBLING OVERLAY, NOT A `background-blend-mode` ON THE IMG.
   The <img> is a real element so it can carry alt text, intrinsic width/height
   (no layout shift) and fetchpriority — none of which a CSS background can do.
   The gradient therefore rides on top of it, and is `pointer-events: none` +
   `aria-hidden` so it changes nothing but the picture. */

/* ⚠️ THE RIM IS LOAD-BEARING NOW, NOT DECORATION. The shot is a near-black
   dashboard and the band is deep purple — two dark tones, so the panel's edge
   is the only thing describing its shape. A heavier SHADOW cannot do this job
   (spreading darkness onto darkness is invisible); a lit 1px edge can. */

.ft-hero-media {
  position: relative;
  border-radius: var(--ft-radius);
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow:
    0 34px 80px -30px rgba(0, 0, 0, 0.65),
    0 0 0 1px rgba(0, 0, 0, 0.25);
  background: var(--fta-bg);        /* matches the shot, so no flash on load */
  min-width: 0;
}

/* The media holds the live HeroAdminPanel slideshow now (session 68), not a
   static <img> — `.ft-hero-wash` and the img sizing rule went with it (the
   panel's own `.hap-img` rules size the screenshots, and each carries
   width/height attributes so the box is still reserved before the bytes land).
   ⚠️ THE FRAME CHROME BELONGS TO `.ft-hero-media` — the panel's own radius,
   ring and shadow are zeroed here or the two rounded rectangles fight at the
   corners (the panel's 10px inside the media's larger --ft-radius shows dark
   notches). */

.ft-hero-media .hap {
  border-radius: 0;
  box-shadow: none;
}

/* ⚠️⚠️ THE ONE-FRAME BUDGET IS A HEIGHT PROBLEM, SO IT NEEDS A HEIGHT QUERY.
   Measured on the operator's own window: 1280×720 of Chrome is ~634px of
   VIEWPORT once the tab strip and omnibox are subtracted, and the hero stack
   needed ~756px including the nav reserve — so the chip row fell off the
   bottom. A width breakpoint cannot see this; two people at the same width
   have different frames depending on their bookmarks bar.

   Everything below is spent from a ~558px budget (634 minus the 76px nav
   reserve). The headline is the biggest line item, so it takes the biggest
   cut, and the image column shrinks with it so the two stay balanced.
   ⚠️ `min-height: 100svh` alone does NOT achieve this — it sets a floor, not a
   ceiling. Without these reductions the content simply overflows the frame it
   was told to fill. */

@media (min-width: 861px) and (max-height: 820px) {
  .features-page .ft-hero-title { font-size: clamp(30px, 2.9vw, 40px); }
  .ft-hero-sub { margin-top: 12px; font-size: 15.5px; line-height: 1.5; }
  .ft-hero-ctas { margin-top: 18px; }
  .ft-hero-proof { margin-top: 12px; font-size: 13px; }
  .ft-eyebrow { margin-bottom: 10px; }
  .ft-chips { margin-top: 18px; }
  .ft-chip { padding: 7px 14px; font-size: 13px; }
  .ft-hero-inner { gap: clamp(24px, 3vw, 44px); }
}

/* ⚠️ THE PROOF LINE IS THE LAST THING TO GO, AND THE THRESHOLD IS MEASURED,
   NOT PICKED. A first pass dropped it below 660px — which hid "★★★★★ 4.9 on
   the App Store" on the operator's own 634px frame, where the measured stack
   still had **71px of slack**. Social proof directly under the CTAs is worth
   more than the whitespace it costs, so it only goes when the frame genuinely
   cannot hold it. Below ~560px the chip row is what survives instead: the
   chips are navigation, the proof line is reassurance. */

@media (min-width: 861px) and (max-height: 560px) {
  .features-page .ft-hero-title { font-size: clamp(26px, 2.4vw, 32px); }
  .ft-hero-proof { display: none; }
}

/* ⚠️ THE HERO NEEDS ITS OWN BREAKPOINT — it does NOT inherit the pillars'.
   Caught by measuring, not by looking: at 375px the two columns resolved to
   **149.75px and 157.25px**, i.e. a headline in a 150px gutter. `minmax(0,1fr)`
   prevented the OVERFLOW (the page still had 0 h-scroll, which is why a scroll
   audit passes this) but nothing makes a 2-column grid become 1 column on its
   own. Same 860px hinge as `.ft-row-inner`, so the whole page changes shape at
   one width instead of two. */

@media (max-width: 860px) {
  /* ⚠️ THE GAPS ARE TIGHTER THAN THE DESKTOP CLAMPS, AND THAT IS THE ONE-FRAME
     BUDGET AGAIN. Measured at 390×844 with the desktop spacing the hero came
     to **855px** — 11px over, so the chip row was clipped by 6px. These three
     reductions reclaim ~24px and bring it to ~831px.
     ⚠️ NO FURTHER THAN THIS. A genuinely short phone (360×640) cannot hold a
     headline, sub-copy, two CTAs, a proof line, a product shot AND six chips
     in one frame, and the only way to force it would be dropping the shot —
     which is the thing the hero exists to show. Those viewports scroll, by
     design. */
  .ft-hero-inner { grid-template-columns: 1fr; gap: 16px; }
  .ft-chips { margin-top: 10px; }
  .ft-hero {
    /* ⚠️ THE LIP RIDES ALONG HERE TOO. A flat 24px would put the chip row
       under the white curve on every phone — the desktop rule above states
       why the token has to be part of this value. */
    padding-bottom: calc(24px + var(--ft-lip));
    /* ⚠️ NOT `center` HERE. A flex container whose content is TALLER than its
       box distributes the overflow to BOTH ends when centred — so on any phone
       that cannot hold the whole hero, centring pushes the headline off the
       TOP of the screen. Starting from the top means overflow only ever goes
       downward, where it is reachable by scrolling. */
    justify-content: flex-start;
  }
  .ft-hero-copy { text-align: left; }
  /* The shot is the supporting evidence, not the pitch — copy leads on a phone. */
  .ft-hero-media { order: 2; }
  .features-page .ft-hero-title { max-width: 20ch; }
  .ft-hero-sub { max-width: none; }
}

/* A single soft brand rim, so the panel reads as lit rather than pasted on. */

.ft-hero-media::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
}

/* ---- anchor chips -------------------------------------------------------- */

/* ⚠️ `margin-top` IS PART OF THE ONE-FRAME BUDGET. It was clamp(36,5vw,56) —
   56px at desktop, i.e. a tenth of the frame spent on one gap. */

.ft-chips {
  width: 100%;                     /* see .ft-hero-inner — same flex-item trap */
  max-width: 1180px;
  margin: clamp(20px, 2.6vh, 34px) auto 0;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.ft-chip {
  font-family: var(--rf-font-body, 'Open Sans', Roboto, system-ui, sans-serif);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ft-on-purple);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid var(--ft-on-purple-line);
  border-radius: 999px;
  padding: 9px 16px;
  cursor: pointer;
  line-height: 1.4;
  transition:
    color 0.18s ease, border-color 0.18s ease,
    background 0.18s ease, transform 0.15s ease, box-shadow 0.18s ease;
}

/* Fills white on hover, so the chip becomes the same "inverted" language as
   the primary button rather than a third treatment. */

.ft-chip:hover {
  color: var(--ft-accent-deep);                  /* 9.0:1 on white */
  border-color: #FFFFFF;
  background: #FFFFFF;
  transform: translateY(-1px);
  box-shadow: 0 8px 20px -10px rgba(10, 6, 40, 0.6);
}

/* ===========================================================================
   ONE PLATFORM — the convergence animation (white band)
   ========================================================================= */

/* ⚠️ THE HAIRLINES ARE GONE FROM EVERY SECTION THAT NOW WEARS A LIP. A 1px
   rule AND a 44px curve are two devices doing one job, and the rule cuts
   straight across the curve it sits on. The lip is the separator. */

.ft-stack {
  background: var(--ft-bg2);
  padding: var(--ft-section-y) var(--ft-gutter);
}

.ft-conv {
  position: relative;
  overflow: hidden;
  text-align: center;
  padding-top: clamp(40px, 6vw, 72px);
  padding-bottom: clamp(50px, 6vw, 78px);
}

.ft-conv-spot {
  position: absolute;
  left: 50%;
  top: 54%;
  width: 720px;
  height: 720px;
  transform: translate(-50%, -50%);
  /* Was a white wash (invisible on white). The brand tint does the same job. */
  background: radial-gradient(circle, rgba(106, 90, 205, 0.10) 0%, rgba(106, 90, 205, 0) 62%);
  pointer-events: none;
  z-index: 0;
}

.ft-conv-head { position: relative; z-index: 2; }

.ft-conv-line { overflow: hidden; }

.features-page .ft-conv-line h2 {
  margin: 0;
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.08;
  font-size: clamp(26px, 14px + 1.9vw, 42px);
  color: var(--ft-text);
}

.features-page .ft-conv-line h2.ft-muted { color: var(--ft-muted); }

.ft-conv-sub {
  margin: 16px auto 0;
  max-width: 34ch;
  color: var(--ft-body);
  font-size: clamp(15px, 1.4vw, 17px);
  font-weight: 400;
  line-height: 1.6;
}

.ft-conv-field { position: relative; width: 100%; height: 240px; margin-top: 16px; z-index: 2; }

.ft-conv-chip-pos { position: absolute; left: 50%; top: 50%; opacity: 0; will-change: transform; }

.ft-conv-chip {
  position: relative;
  white-space: nowrap;
  border: 1px solid var(--ft-line2);
  background: var(--ft-surface);
  color: var(--ft-body);
  font-weight: 600;
  font-size: 14px;
  padding: 10px 18px;
  border-radius: 999px;
  box-shadow: var(--rf-shadow-sm, 0 1px 2px rgba(16, 16, 20, 0.06));
  transition:
    color 0.5s cubic-bezier(.16, 1, .3, 1),
    border-color 0.5s cubic-bezier(.16, 1, .3, 1),
    background 0.5s cubic-bezier(.16, 1, .3, 1);
}

.ft-conv-chip.is-floating { animation: ftConvFloat 6s ease-in-out infinite; animation-delay: var(--d); }

@keyframes ftConvFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }

/* Struck-out tool: the danger hue, at its light-ground step. */

.ft-conv-chip.is-dead {
  color: var(--ft-danger);
  border-color: rgba(185, 28, 28, 0.28);
  background: rgba(185, 28, 28, 0.05);
  box-shadow: none;
}

.ft-conv-strike {
  position: absolute;
  left: 14px;
  right: 14px;
  top: 50%;
  height: 2px;
  background: var(--ft-danger);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: left;
  will-change: transform;
}

/* The resolution pill — the one place on the page that IS the brand. */

.ft-conv-solve { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }

.ft-conv-pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: var(--ft-accent);
  color: #FFFFFF;                       /* 5.31:1 */
  font-family: var(--rf-font-body, 'Open Sans', Roboto, system-ui, sans-serif);
  font-weight: 700;
  font-size: 16px;
  padding: 14px 26px;
  border-radius: 999px;
  cursor: pointer;
  border: 1px solid var(--ft-accent);
  box-shadow: var(--rf-shadow-primary, 0 10px 30px rgba(106, 90, 205, 0.28));
  will-change: transform;
  transition: background 0.2s ease, border-color 0.2s ease;
}

.ft-conv-pill:hover { background: var(--rf-primary-strong, #574AB0); border-color: var(--rf-primary-strong, #574AB0); }

.ft-conv-pill svg { display: block; }

.ft-conv-ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 60px;
  height: 40px;
  transform: translate(-50%, -50%);
  border: 1px solid var(--rf-primary-ring, rgba(106, 90, 205, 0.38));
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
}

.ft-conv-cap { display: block; margin-top: 16px; color: var(--ft-muted); font-weight: 500; font-size: 14px; }

/* Motion users: hide the animated bits up-front so GSAP reveals them (no flash
   of the resting state). Reduced-motion users keep the static layout. */

@media (prefers-reduced-motion: no-preference) {
  .ft-conv-sub,
  .ft-conv-cap,
  .ft-conv-solve,
  .ft-conv-spot { opacity: 0; }
}

@media (max-width: 720px) {
  .ft-conv-field { height: 300px; }
  .ft-conv-chip { font-size: 13px; padding: 8px 14px; }
}

/* ===========================================================================
   FEATURE PILLARS — full-bleed bands, alternating white / light grey
   ---------------------------------------------------------------------------
   ⚠️ THE BAND IS `.ft-row` AND THE GRID IS `.ft-row-inner`. They used to be the
   same element, which made an alternating full-bleed ground impossible: a
   background on a max-width box stops at the box. Splitting them is what lets
   the ground run edge to edge while the content stays in the 1180px measure.
   ========================================================================= */

.ft-pillars { padding: 0; }

.ft-row {
  /* ⚠️ scroll-margin must clear the FIXED nav, or an anchor chip lands the
     section title underneath the bar. --rf-nav-reserve is that exact height. */
  scroll-margin-top: calc(var(--rf-nav-reserve, 76px) + 16px);
  padding: var(--ft-section-y) var(--ft-gutter);
}

.ft-row:nth-child(odd) { background: var(--ft-bg); }

.ft-row:nth-child(even) { background: var(--ft-bg2); }

.ft-row-inner {
  max-width: 1180px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: center;
}

.ft-row--flip .ft-text { order: 2; }

.ft-text { min-width: 0; }

.features-page .ft-row-title {
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  /* ⚠️ PURPLE, PER THE BRIEF — and it needs the (0,2,0) prefix above to beat
     `.home-page h3 { color: var(--rf-ink) }`. #6A5ACD measures 5.31:1 on white
     and 4.87:1 on #F5F5F5, so it clears AA on BOTH bands at any size. */
  color: var(--ft-accent);
  font-size: clamp(24px, 14px + 1.6vw, 34px);
  font-weight: 700;
  letter-spacing: -0.02em;
  max-width: 17ch;
  line-height: 1.15;
  margin: 0;
  text-wrap: balance;
}

.ft-row-body {
  color: var(--ft-body);
  font-size: 17px;
  margin-top: 16px;
  max-width: 46ch;
  line-height: 1.6;
}

.ft-feat-list { list-style: none; margin: 22px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }

.ft-feat-list li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  font-size: 15px;
  font-weight: 500;
  color: var(--ft-body);
  line-height: 1.5;
}

.ft-ck {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--ft-accent-soft);
  color: var(--ft-accent-deep);
  display: grid;
  place-items: center;
  /* ⚠️ Its own line-height. The page inherits 1.6, which on a 20px box pushes
     the glyph off centre — the same trap CLAUDE.md records for the AdminPanel. */
  line-height: 1;
  margin-top: 2px;
}

/* The card that frames a mock visual. */

.ft-visual {
  border: 1px solid var(--ft-line);
  border-radius: var(--ft-radius);
  background: var(--ft-surface);
  box-shadow: var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
  overflow: hidden;
  min-width: 0;
  transition: transform 0.3s var(--ft-ease), border-color 0.25s ease, box-shadow 0.25s ease;
}

.ft-visual:hover {
  transform: translateY(-4px);
  border-color: var(--ft-accent);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}

/* `--bare` = the mock brings its own chrome (the app window, the handset, the
   calendar panel), so a second frame around it would double the border. */

.ft-visual--bare { background: transparent; border: none; box-shadow: none; }

.ft-visual--bare:hover { transform: none; box-shadow: none; }

.ft-vpad { padding: 22px; }

@media (max-width: 860px) {
  .ft-row-inner { grid-template-columns: 1fr; gap: 28px; }
  .ft-row--flip .ft-text { order: 0; }
}

/* ---- mock visual: reports table (Insights) ------------------------------- */

.ft-reports { display: flex; flex-direction: column; }

.ft-reports-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }

.ft-reports-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 14px;
  align-items: center;
  padding: 10px 2px;
  border-top: 1px solid var(--ft-line);
  font-size: 13.5px;
}

.ft-reports-row--head {
  border-top: none;
  padding: 0 2px 4px;
  color: var(--ft-dim);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  line-height: 1.4;
}

.ft-reports-row--head span:nth-child(2),
.ft-reports-row--head span:nth-child(3) { text-align: right; }

.ft-reports-car { display: flex; align-items: center; gap: 10px; color: var(--ft-text); font-weight: 600; min-width: 0; }

.ft-reports-car > :last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ft-reports-ic {
  flex: none;
  width: 28px;
  height: 20px;
  border-radius: 5px;
  background: var(--ft-accent-soft);
  color: var(--ft-accent);
  display: grid;
  place-items: center;
}

.ft-reports-ic svg { width: 16px; height: 16px; }

.ft-reports-rev { color: var(--ft-text); font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; min-width: 60px; }

.ft-reports-util { color: var(--ft-success); font-weight: 700; text-align: right; min-width: 44px; }

/* ---- mock visual: vetting rows + payments -------------------------------- */

.ft-legend { display: flex; justify-content: space-between; font-size: 11px; color: var(--ft-dim); font-weight: 600; line-height: 1.4; }

.ft-legend-strong { color: var(--ft-text); font-weight: 700; font-size: 13px; }

.ft-ok-text { color: var(--ft-success); }

.ft-rows { display: flex; flex-direction: column; gap: 8px; }

.ft-row-item {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  font-weight: 600;
  background: var(--ft-surface2);
  border: 1px solid var(--ft-line);
  border-radius: 9px;
  padding: 11px 12px;
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.ft-row-item:hover { border-color: var(--ft-line2); transform: translateX(3px); }

.ft-nm { flex: 1; color: var(--ft-text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ft-tag { font-size: 9.5px; padding: 3px 8px; border-radius: 999px; font-weight: 700; white-space: nowrap; line-height: 1.5; }

.ft-tag.ok { background: rgba(21, 128, 61, 0.10); color: var(--ft-success); }

.ft-tag.no { background: rgba(185, 28, 28, 0.10); color: var(--ft-danger); }

.ft-tag.warn { background: rgba(180, 83, 9, 0.10); color: var(--ft-warn); }

.ft-pay { display: flex; flex-direction: column; gap: 12px; }

.ft-pay-amt { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; color: var(--ft-text); margin-top: 6px; line-height: 1.15; }

.ft-pay-line { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; font-weight: 600; color: var(--ft-muted); padding: 10px 0; border-top: 1px solid var(--ft-line); }

.ft-pay-line b { color: var(--ft-text); }

/* ===========================================================================
   PRODUCT MOCKUPS THAT STAY DARK  (--fta-* only, never --ft-*)
   The AdminPanel is a dark app; these two are pictures of it.
   ========================================================================= */

.ftf-frame {
  position: relative;
  container-type: inline-size;
  border: 1px solid var(--fta-line2);
  border-radius: 16px;
  overflow: hidden;
  background: linear-gradient(180deg, #131317, var(--fta-bg));
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
  color: var(--fta-text);
  line-height: 1.5;
}

.ftf-chrome { display: flex; align-items: center; gap: 14px; padding: 12px 16px; border-bottom: 1px solid var(--fta-line); background: #0f0f12; }

.ftf-dots { display: flex; gap: 7px; }

.ftf-dots i { width: 11px; height: 11px; border-radius: 50%; display: inline-block; }

.ftf-dots .r { background: #ff5f57; }

.ftf-dots .y { background: #febc2e; }

.ftf-dots .g { background: #28c840; }

.ftf-tabs { display: flex; gap: 4px; background: var(--fta-surface); border: 1px solid var(--fta-line); border-radius: 999px; padding: 4px; margin-left: 4px; }

.ftf-tabs button {
  font-family: inherit;
  font-weight: 700;
  font-size: 12.5px;
  color: var(--fta-dim);
  background: transparent;
  border: none;
  border-radius: 999px;
  padding: 7px 16px;
  cursor: pointer;
  line-height: 1.4;
  transition: color 0.2s ease, background 0.2s ease;
}

.ftf-tabs button:hover { color: #fff; }

.ftf-tabs button.on { background: #fff; color: #0a0a0b; }

/* On the DARK window ground the brand purple is only 3.6:1 — fine for a
   boundary, but a focus ring wants headroom, so this one is white (19.2:1). */

.ftf-tabs button:focus-visible { outline: 3px solid #FFFFFF; outline-offset: 2px; }

.ftf-url { margin-left: auto; font-size: 11px; color: var(--fta-dim); font-weight: 600; }

.ftf-view { position: relative; }

.ftf-tbl { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 12.5px; }

.ftf-tbl th { text-align: left; font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--fta-dim); font-weight: 700; padding: 14px 12px 10px; overflow: hidden; text-overflow: ellipsis; }

.ftf-tbl td { padding: 13px 12px; border-top: 1px solid var(--fta-line); font-weight: 600; vertical-align: middle; }

.ftf-tbl tbody tr:first-child td { border-top: none; }

/* Fixed column widths so the table always fits the framed window. */

.ftf-tbl--cars th:nth-child(1) { width: 24%; }

.ftf-tbl--cars th:nth-child(2) { width: 8%; }

.ftf-tbl--cars th:nth-child(3) { width: 11%; }

.ftf-tbl--cars th:nth-child(4) { width: 13%; }

.ftf-tbl--cars th:nth-child(5) { width: 25%; }

.ftf-tbl--cars th:nth-child(6) { width: 9%; }

.ftf-tbl--cars th:nth-child(7) { width: 10%; }

.ftf-tbl--cust th:nth-child(1) { width: 25%; }

.ftf-tbl--cust th:nth-child(2) { width: 26%; }

.ftf-tbl--cust th:nth-child(3) { width: 19%; }

.ftf-tbl--cust th:nth-child(4) { width: 9%; }

.ftf-tbl--cust th:nth-child(5) { width: 11%; }

.ftf-tbl--cust th:nth-child(6) { width: 10%; }

.ftf-car { display: flex; align-items: center; gap: 10px; }

.ftf-ic { width: 26px; height: 26px; border-radius: 7px; background: var(--fta-surface2); display: grid; place-items: center; color: var(--fta-dim); flex: none; }

.ftf-ic svg { width: 14px; height: 14px; }

.ftf-av { width: 26px; height: 26px; border-radius: 50%; background: var(--fta-surface2); display: grid; place-items: center; color: var(--fta-dim); flex: none; }

.ftf-av svg { width: 13px; height: 13px; }

.ftf-nm { color: var(--fta-text); font-weight: 700; line-height: 1.15; min-width: 0; overflow: hidden; }

.ftf-sub { color: var(--fta-dim); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ftf-pill { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px; font-weight: 700; padding: 4px 9px; border-radius: 999px; white-space: nowrap; max-width: 100%; line-height: 1.5; }

.ftf-pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; flex: none; }

/* These stay the DARK-ground hues — they sit inside the app window. */

.ftf-pill.ok { background: rgba(74, 222, 128, 0.14); color: #4ade80; }

.ftf-pill.ok::before { background: #4ade80; }

.ftf-pill.warn { background: rgba(251, 191, 36, 0.14); color: #fbbf24; }

.ftf-pill.warn::before { background: #fbbf24; }

.ftf-pill.off { background: rgba(161, 161, 170, 0.18); color: var(--fta-dim); }

.ftf-pill.off::before { background: var(--fta-dim); }

.ftf-trip { display: inline-grid; place-items: center; min-width: 26px; height: 24px; background: var(--fta-surface2); border: 1px solid var(--fta-line); border-radius: 7px; font-size: 11px; font-weight: 700; color: var(--fta-text); }

.ftf-ivri { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700; color: var(--fta-text); }

.ftf-ivri .ftf-dot { width: 7px; height: 7px; border-radius: 50%; background: #4ade80; }

.ftf-ivri.amber .ftf-dot { background: #fbbf24; }

.ftf-callout {
  position: absolute;
  z-index: 5;
  right: 18px;
  bottom: 18px;
  background: #fff;
  color: #0a0a0b;
  font-size: 12px;
  font-weight: 700;
  padding: 9px 13px;
  border-radius: 10px;
  box-shadow: 0 14px 30px -10px rgba(0, 0, 0, 0.6);
  max-width: 190px;
  line-height: 1.3;
  animation: ftfCalloutFloat 3.6s ease-in-out infinite;
  will-change: transform;
}

.ftf-callout::after { content: ""; position: absolute; width: 10px; height: 10px; background: #fff; transform: rotate(45deg); left: -4px; top: 50%; margin-top: -5px; }

.ftf-k { display: block; font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ft-accent-deep); margin-bottom: 2px; }

@keyframes ftfCalloutFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-9px) rotate(-0.6deg); }
}

@media (prefers-reduced-motion: reduce) { .ftf-callout { animation: none; } }

/* ⚠️ THE FIXED PERCENTAGE WIDTHS ARE FINE AT 1180px AND CRAMPED AT 560. The
   window is one grid column wide (~557px at a 1280 viewport), so Model gets
   24% = 134px and "Lamborghini Huracan Evo" wraps to THREE lines while Year
   and Color sit half-empty beside it. Measured in the live app, not guessed.
   Dropping the two lowest-value columns buys the model name a single line.
   Container query, not a media query — the frame's width is what matters, and
   it is set by its grid column rather than by the viewport. */

@container (max-width: 640px) {
  .ftf-tbl--cars th:nth-child(2), .ftf-tbl--cars td:nth-child(2),
  .ftf-tbl--cars th:nth-child(3), .ftf-tbl--cars td:nth-child(3) { display: none; }
  .ftf-tbl--cars th:nth-child(1) { width: 38%; }
  .ftf-tbl--cars th:nth-child(4) { width: 18%; }
  .ftf-tbl--cars th:nth-child(5) { width: 26%; }
  .ftf-tbl--cust th:nth-child(3), .ftf-tbl--cust td:nth-child(3) { display: none; }
  .ftf-tbl--cust th:nth-child(1) { width: 32%; }
  .ftf-tbl--cust th:nth-child(2) { width: 32%; }
}

@container (max-width: 440px) {
  .ftf-tbl th:nth-child(4), .ftf-tbl td:nth-child(4) { display: none; } /* hide VIN when tight */
}

/* The handset. A phone bezel is dark and its screen shows the real app. */

.ft-phone-pad { display: grid; place-items: center; padding-block: 8px; }

.ft-phone {
  width: 210px;
  border: 8px solid #1a1a1f;
  border-radius: 32px;
  background: #0c0c0f;
  padding: 14px 12px;
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}

.ft-phone-top { height: 5px; width: 50px; background: #2a2a30; border-radius: 99px; margin: 0 auto 14px; }

.ft-pc { background: var(--fta-surface); border: 1px solid var(--fta-line); border-radius: 12px; padding: 12px; margin-bottom: 10px; transition: border-color 0.15s ease; }

.ft-pc:last-child { margin-bottom: 0; }

.ft-pc:hover { border-color: var(--fta-line2); }

.ft-pc-t { font-size: 11px; font-weight: 700; color: var(--fta-text); line-height: 1.4; }

.ft-pc-t.ft-ok-text { color: #4ade80; }

/* dark-ground green — it is on the phone */

.ft-pc-s { font-size: 10px; color: var(--fta-dim); margin-top: 3px; font-weight: 600; line-height: 1.4; }

/* ===========================================================================
   SECTION HEADS (bento / security)
   ========================================================================= */

.ft-section-head { text-align: center; max-width: 44ch; margin: 0 auto 44px; }

.features-page .ft-section-head h2 {
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  font-size: clamp(26px, 14px + 1.9vw, 42px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--ft-text);
  margin: 0;
  text-wrap: balance;
}

.ft-section-head p { color: var(--ft-body); margin: 12px 0 0; font-weight: 400; line-height: 1.6; }

/* Pinned stack-and-deal track (bento + security). */

.ft-deal-pin { position: relative; }

.ft-deal-pin.is-pinned { min-height: 138vh; }

.ft-deal-sticky { padding: var(--ft-section-y) var(--ft-gutter); }

.ft-deal-pin.is-pinned .ft-deal-sticky {
  position: sticky;
  top: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: clamp(64px, 12vh, 130px);
  padding-bottom: clamp(32px, 4vw, 56px);
}

.ft-deal-stage { width: 100%; max-width: 1180px; margin: 0 auto; }

/* ===========================================================================
   BENTO GRID  (light-grey band)
   ========================================================================= */

/* Ground + ink come from `.ft-dark`; the hairlines it replaces would be
   invisible on that ground anyway. */

.ft-bento-section { border: none; }

.ft-bento { width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }

.ft-tile {
  border: 1px solid var(--ft-line);
  border-radius: 16px;
  background: var(--ft-surface);
  padding: 26px;
  min-width: 0;
  cursor: pointer;                       /* see the note on `.ft-integ-pill` */
  box-shadow: var(--rf-shadow-sm, 0 1px 2px rgba(16, 16, 20, 0.06));
  /* transform is driven by the scroll "deal" animation (inline motion style),
     so hover feedback leans on border / shadow instead. */
  transition: transform 0.3s var(--ft-ease), border-color 0.25s ease,
    background 0.25s ease, box-shadow 0.25s ease;
}

.ft-tile:hover {
  transform: translateY(-5px);
  border-color: var(--ft-accent);
  background: var(--ft-surface);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}

.ft-tile:hover .ft-tile-ico { background: var(--ft-accent); color: #FFFFFF; transform: scale(1.06); }

.ft-tile-ico {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: var(--ft-accent-soft);
  display: grid;
  place-items: center;
  color: var(--ft-accent);
  margin-bottom: 16px;
  transition: background 0.25s ease, color 0.25s ease, transform 0.25s var(--ft-ease);
}

.ft-tile-ico svg { width: 20px; height: 20px; }

.features-page .ft-tile h4 {
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  font-size: 17px;
  font-weight: 700;
  line-height: 1.3;
  margin: 0 0 7px;
  color: var(--ft-text);
}

.ft-tile p { font-size: 14px; color: var(--ft-body); margin: 0; line-height: 1.6; }

@media (max-width: 860px) { .ft-bento { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

@media (max-width: 560px) { .ft-bento { grid-template-columns: 1fr; } }

/* ===========================================================================
   INTEGRATIONS  (white band)
   ========================================================================= */

/* ⚠️ NO `background` HERE — it comes from `.ft-dark`. Integrations is the
   MIDDLE of a three-section black chapter (bento · integrations · security),
   so its ground must be identical to its neighbours or a seam appears where
   the design says there is none. */

.ft-integ {
  text-align: center;
  padding: var(--ft-section-y) var(--ft-gutter);
}

.ft-integ-wrap { max-width: 1100px; margin: 0 auto; display: flex; flex-direction: column; align-items: center; }

/* (0,3,0) — clears `.home-page h2` (0,1,1); see this file's header. */

.features-page .ft-dark .ft-integ-head h2 { color: var(--ft-on-ink); }

.features-page .ft-dark .ft-integ-head p { color: var(--ft-on-ink-body); }

.features-page .ft-integ-head h2 {
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  font-size: clamp(26px, 14px + 1.9vw, 42px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--ft-text);
  margin: 0;
  text-wrap: balance;
}

.ft-integ-head p { color: var(--ft-body); margin: 12px auto 32px; max-width: 46ch; font-weight: 400; line-height: 1.6; }

.ft-integ-row { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }

/* ⚠️ `cursor` IS INHERITED AND ITS DEFAULT OVER TEXT IS THE I-BEAM — which is
   why these read as "select this text" instead of "this responds"
   (operator-reported). Nothing here is a real control, so the UA gave the
   card's padding an arrow and its label a caret: two different cursors on one
   object, and neither matching the purple hover the element clearly has.
   `pointer` is the convention this site already set on `.about-value-card`,
   whose hover is the same treatment.
   ⚠️ NOT extended to `.ft-row-item`: that is a row inside a PRODUCT MOCKUP
   (the vetting list), not a section element — a pointer there would promise an
   interaction that the picture cannot honour. Same call as the deck's macOS
   traffic-light dots. */

.ft-integ-pill {
  position: relative;
  overflow: hidden;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  color: var(--ft-body);
  border: 1px solid var(--ft-line2);
  border-radius: 12px;
  padding: 12px 20px;
  background: var(--ft-surface);
  line-height: 1.4;
  box-shadow: var(--rf-shadow-sm, 0 1px 2px rgba(16, 16, 20, 0.06));
  transition: color 0.2s ease, border-color 0.2s ease, transform 0.15s ease, box-shadow 0.25s ease, background 0.25s ease;
}

.ft-integ-pill:hover {
  color: var(--ft-accent-deep);
  border-color: var(--ft-accent);
  background: var(--ft-accent-tint);
  transform: translateY(-2px);
  box-shadow: var(--rf-shadow, 0 4px 16px rgba(16, 16, 20, 0.08));
}

/* On the black chapter the pills become raised dark chips. `--ft-on-ink-body`
   is 14.1:1 on the card surface; the on-dark purple carries the hover at
   5.50:1 (the brand purple itself is 3.62:1 here and would fail as a label). */

.features-page .ft-dark .ft-integ-pill {
  background: var(--ft-ink-card);
  border-color: var(--ft-ink-line);
  color: var(--ft-on-ink-body);
  box-shadow: 0 14px 34px -24px rgba(0, 0, 0, 0.9);
}

.features-page .ft-dark .ft-integ-pill:hover {
  background: var(--ft-ink-card);
  border-color: var(--rf-primary-on-dark, #8B7BE0);
  color: var(--rf-primary-on-dark, #8B7BE0);
}

.ft-integ-name { display: inline-block; }

/* ===========================================================================
   SECURITY  (light-grey band)
   ========================================================================= */

.ft-sec-section { border: none; }

.ft-sec-grid { width: 100%; margin: 44px auto 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }

.ft-sec {
  border: 1px solid var(--ft-line);
  border-radius: 14px;
  background: var(--ft-surface);
  padding: 24px;
  text-align: center;
  min-width: 0;
  cursor: pointer;                       /* see the note on `.ft-integ-pill` */
  box-shadow: var(--rf-shadow-sm, 0 1px 2px rgba(16, 16, 20, 0.06));
  transition: transform 0.3s var(--ft-ease), border-color 0.25s ease,
    background 0.25s ease, box-shadow 0.25s ease;
}

.ft-sec:hover {
  transform: translateY(-4px);
  border-color: var(--ft-accent);
  box-shadow: var(--rf-shadow-lg, 0 18px 48px rgba(16, 16, 20, 0.12));
}

.ft-sec:hover .ft-sec-ico { background: var(--ft-accent); color: #FFFFFF; transform: scale(1.06); }

.ft-sec-ico {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: var(--ft-accent-soft);
  display: grid;
  place-items: center;
  color: var(--ft-accent);
  margin: 0 auto 14px;
  transition: background 0.25s ease, color 0.25s ease, transform 0.25s var(--ft-ease);
}

.ft-sec-ico svg { width: 20px; height: 20px; }

.features-page .ft-sec h4 {
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.35;
  margin: 0 0 6px;
  color: var(--ft-text);
}

.ft-sec p { font-size: 13px; color: var(--ft-body); margin: 0; line-height: 1.55; }

@media (max-width: 860px) { .ft-sec-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ===========================================================================
   TESTIMONIAL  (white band)
   ========================================================================= */

.ft-quote-section {
  background: var(--ft-bg2);
  padding: var(--ft-section-y) var(--ft-gutter);
}

.ft-quote { max-width: 780px; margin: 0 auto; text-align: center; }

.ft-quote-stars { letter-spacing: 3px; font-size: 18px; margin-bottom: 20px; line-height: 1; }

.features-page .ft-quote blockquote {
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  font-size: clamp(21px, 2.6vw, 30px);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.35;
  color: var(--ft-text);
  margin: 0;
  text-wrap: balance;
}

.ft-quote-who { margin-top: 24px; display: flex; align-items: center; gap: 12px; justify-content: center; }

.ft-quote-av {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--ft-accent);
  display: grid;
  place-items: center;
  font-weight: 700;
  color: #FFFFFF;
  line-height: 1;
  flex: none;
}

.ft-quote-meta { text-align: left; }

.ft-quote-nm { font-weight: 700; font-size: 15px; color: var(--ft-text); line-height: 1.4; }

.ft-quote-rl { color: var(--ft-dim); font-size: 13px; font-weight: 600; line-height: 1.4; }

/* ===========================================================================
   CTA BAND — the HOME PAGE's pattern: a dark card floating on a light ground
   ---------------------------------------------------------------------------
   ⚠️⚠️ ALMOST ALL OF THIS IS DELETION, NOT NEW CSS, AND THAT IS THE POINT.
   `Landing.css` already solves this exact card — the navy fill, the white
   heading, the indigo button, the 0.72 reassurance line — and every one of
   those rules is scoped to **`.home-page`**, which this page ALSO carries
   (`className="home-page features-page"`). The purple overrides written
   earlier were the only thing stopping them applying. Removing them does not
   approximate the home page; it *is* the home page's CTA.

   ⚠️ SO DO NOT RE-ADD COLOUR RULES HERE. Anything set on
   `.features-page .home-final-cta*` at (0,3,0)+ ties with the `.home-page`
   rules and wins on load order, silently forking the two CTAs apart again.
   What stays below is only what the home page has no equivalent of: this
   page's own `.ft-cta-*` classes and its ghost button (home has no ghost).

   ⚠️ AND THE CARD IS DARK ON PURPOSE. Landing.css records why: the glare, the
   button's glow-pulse and the 3D tilt were all designed to read against a
   dark ground and do essentially nothing on white. A white card here would
   have quietly killed three effects that still run.
   ========================================================================= */

.features-page .home-final-cta {
  position: relative;
  /* #F5F5F5, matching `.home-dark-band` — the exact ground home's CTA sits
     on. It also keeps the lip from the white testimonial above it visible;
     white-on-white would have made that curve disappear. */
  background: var(--ft-bg);
  padding: var(--ft-section-y) var(--ft-gutter);
}

.features-page .home-final-cta-card { position: relative; z-index: 1; }

.ft-cta-sub,
.ft-cta-btns,
.ft-cta-fud { position: relative; z-index: 1; }

/* Features-only classes — home's card has no equivalent, so these are the two
   rules that genuinely have to be written. Measured on `--rf-night`. */

.features-page .ft-cta-sub {
  color: rgba(255, 255, 255, 0.92);      /* 16.4:1 */
  font-weight: 400;
  max-width: 48ch;
  margin: 14px auto 0;
  line-height: 1.6;
}

.ft-cta-btns { margin-top: 28px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }

.ft-cta-btns .home-cta-primary { display: inline-flex; align-items: center; gap: 8px; }

.features-page .ft-cta-fud {
  color: rgba(255, 255, 255, 0.72);      /* 10.2:1 — home's own value */
  margin-top: 18px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
}

/* THE GHOST PROMOTES TO ITS PRIMARY ON HOVER (operator: *"on hover start free
   trial button should become as of same color as book a demo"*). The wipe
   takes the sibling's exact fill instead of an unrelated white, so hovering
   the secondary previews what the primary looks like rather than introducing
   a third colour. Home's CTA has no ghost, so this resting state is the one
   thing here with no `.home-page` equivalent to inherit.

   ⚠️⚠️ THE HOVER `color` MUST BE RESTATED, AND "let the default handle it" IS
   THE TRAP THAT PRODUCED THE REPORTED BUG TWICE. `Landing.css` pairs its wipe
   with `:hover { color: #0A0A0A }` at (0,2,0). The RESTING rule below is
   (0,4,0) — and **a higher-specificity rule with no state still wins during
   :hover**, because specificity is not re-evaluated per state. Override half
   the pair and the label keeps its resting colour on top of the new fill;
   white-on-white measured **contrast 1.0**, i.e. an empty pill.
   ⚠️ RULE: if you override a ghost's resting `color`, you MUST also override
   its `:hover` colour. There is no "inherit the default hover". */

.features-page .home-final-cta-inner .home-hero-cta-ghost {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.42);   /* 8.9:1 — control boundary */
  color: var(--ft-on-ink);                   /* 19.2:1 on the navy card */
}

/* ⚠️ THE FILL IS ON `::before`, NOT `background`. The wipe is a circle that
   scales up from the centre; setting `background` would paint a rectangle
   behind it and the pill would flash its final colour before the animation
   ran. Both are set anyway — `background` stays transparent so only the
   circle shows. */

.features-page .home-final-cta-inner .home-hero-cta-ghost::before {
  background: var(--rf-indigo, #5A5FE8);     /* the primary's exact fill */
}

.features-page .home-final-cta-inner .home-hero-cta-ghost:hover {
  background: transparent;
  border-color: var(--rf-indigo, #5A5FE8);
  color: var(--rf-ink-invert, #FFFFFF);      /* 4.96:1 — theme.css's measured pair */
}

.features-page .home-final-cta-inner :focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 3px;
}

/* ⚠️ THE LAST LIP IS ON `.home-closing`, NOT ON `.site-footer`. The footer is
   a SHARED component used by five other pages; the closing WRAPPER is a plain
   div this page already owns. `overflow: hidden` is load-bearing here and
   nowhere else the idiom appears — the footer paints its own opaque navy, so
   without a clip it would paint straight over the rounded corners (the same
   thing §CM2 hit with the pinned deck). Nothing inside is sticky, so the clip
   costs nothing. */

.features-page .home-closing {
  border-radius: var(--ft-lip) var(--ft-lip) 0 0;
  margin-top: calc(-1 * var(--ft-lip));
  overflow: hidden;
  position: relative;
  z-index: 2;
}

/* ===========================================================================
   FOCUS — every interactive element on the page, one rule
   The brief's "keyboard navigation and proper focus states". theme.css only
   reaches elements inside `.rf-theme`, which this page is not.

   ⚠️ SOLID BRAND, NOT `--rf-primary-ring`. That token is
   `rgba(106, 90, 205, 0.38)`, which composites over white to ~rgb(198,193,224)
   — a measured **1.8:1**, under WCAG 2.2 SC 1.4.11's 3:1 floor for a focus
   indicator. It reads as a "focus colour" and is barely visible. Solid
   #6A5ACD is 5.31:1 on white and 4.87:1 on the grey band, so the ring clears
   the floor on BOTH page grounds.
   (Left as a note rather than fixed in theme.css: that token is consumed by
   every page, so retuning it is its own pass with its own sweep.) */

.features-page :focus-visible {
  outline: 3px solid var(--rf-primary, #6A5ACD);
  outline-offset: 2px;
  border-radius: 6px;
}

@media (prefers-reduced-motion: reduce) {
  .ft-chip,
  .ft-tile,
  .ft-sec,
  .ft-visual,
  .ft-integ-pill,
  .ft-row-item,
  .ft-arrow { transition: none; }
  .ft-chip:hover,
  .ft-tile:hover,
  .ft-sec:hover,
  .ft-visual:hover,
  .ft-integ-pill:hover,
  .ft-row-item:hover { transform: none; }
}

/* ⚠️ DELETED WITH THE REWRITE, NOT MISPLACED: `.ft-cal*` (the 7×5 mini
   calendar), `.ft-bars` (the bar chart) and `.ft-who` had ZERO markup left —
   the calendar pillar moved to the real <CalendarPanel> and the insights
   pillar to the reports table. Confirmed by grep against Features.jsx before
   removing. Recoverable from git if a mock ever wants them back. */
/* ===== ScrollShowcase — pinned, scroll-scrubbed slide showcase =====
   Lives inside the dark .home-light-band, so it inherits the band's tokens
   (--card, --text, etc.). The brand accent (purple) drives the warm glow + the
   active nav highlight — NOT literal gold. */

.scroll-showcase {
  position: relative;
  height: 100vh;            /* the pinned stage = one viewport */
  overflow: hidden;          /* clip the glow + slide drift */
  display: flex;
  flex-direction: column;
  isolation: isolate;
  /* The preceding sticky feature-stack leaves an empty "tail" as its last card
     scrolls up. Pull the showcase up into that tail (scaled by viewport) so the
     transition gap before the slideshow stays tight on tall screens. Reset for
     the un-pinned mobile layout below. */
  margin-top: clamp(-300px, -22vh, -90px);
}

/* Warm radial glow — position (--gx/--gy) and intensity (--gi) are tweened per
   slide by GSAP. Brand-purple, kept subtle. */
.ss-glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  --gx: 16%;
  --gy: 36%;
  --gi: 0.8;
  background: radial-gradient(
    55% 60% at var(--gx) var(--gy),
    rgba(139, 108, 255, calc(0.18 * var(--gi))),
    transparent 62%
  );
}

/* Stage holds the absolutely-stacked slides. */
.ss-stage {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  min-height: 0;
}

/* Each slide fills the stage and centres its content; cross-faded by GSAP. */
.ss-slide {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.ss-slide-content {
  width: 100%;
}
/* Scaled down by JS to fit the pinned stage (so nothing is clipped). */
.ss-slide-fit {
  width: 100%;
  transform-origin: center center;
}

/* Trim the existing sections' vertical padding so they fit the stage. */
.ss-slide-content > section {
  padding-top: 0;
  padding-bottom: 0;
}

/* ===== Compact the slide content so each slide fits one viewport at default
   (100%) zoom — smaller headings, cards, calendar and chart. ===== */
.scroll-showcase .home-section-title,
.scroll-showcase .home-tour-title {
  font-size: clamp(24px, 3.6vw, 42px);
  margin-bottom: clamp(12px, 1.8vh, 20px);
}
.scroll-showcase .home-eyebrow-text { margin-bottom: 6px; }
.scroll-showcase .home-tour-text h3 { font-size: clamp(19px, 2.1vw, 27px); }
.scroll-showcase .home-tour-text p { font-size: clamp(13px, 1.2vw, 15px); }

/* Pricing — smaller card deck + tighter spacing so "See all plans" fits. */
.scroll-showcase .home-pricing-stage { margin: 24px auto 12px; padding: 14px 0; }
.scroll-showcase .home-tier-card { width: 226px; padding: 20px 18px; border-radius: 18px; }
.scroll-showcase .home-tier-card--center,
.scroll-showcase .home-tier-card.home-tier-highlight { width: 256px; }
.scroll-showcase .home-tier-price-num { font-size: 28px; }
.scroll-showcase .home-tier-card--center .home-tier-price-num { font-size: 38px; }
.scroll-showcase .home-tier-features { gap: 8px; margin-bottom: 16px; }
.scroll-showcase .home-tier-features li { font-size: 12px; }
.scroll-showcase .home-tier-cta { padding: 10px 14px; font-size: 12px; }
.scroll-showcase .home-pricing-link { margin: 12px 0 0; }

/* Calendar — shorter cells + tighter panel. */
.scroll-showcase .home-tour-cal-cell { min-height: 44px; padding: 6px 5px; }
.scroll-showcase .home-tour-cal-body { padding: 12px; }

/* Revenue chart — shorter bars. */
.scroll-showcase .home-tour-bars { height: 120px; }

/* ===== Sticky bottom nav (inside the pinned section) ===== */
.ss-nav {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: clamp(8px, 1.4vw, 18px);
  padding: 14px clamp(16px, 3vw, 32px) clamp(16px, 3vh, 28px);
}
.ss-nav-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint, #6b7280);
  padding: 8px 6px;
  transition: color 0.25s ease;
}
.ss-nav-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.6;
  transition: transform 0.25s ease, opacity 0.25s ease;
}
.ss-nav-item:hover { color: var(--text-dim, #9ca3af); }
.ss-nav-item.is-active { color: #b9a6ff; }            /* brand-accent highlight */
.ss-nav-item.is-active .ss-nav-dot {
  background: #8b6cff;
  opacity: 1;
  transform: scale(1.5);
  box-shadow: 0 0 10px rgba(139, 108, 255, 0.8);
}

/* ===== Mobile / reduced-motion: un-pin, render stacked normally ===== */
@media (max-width: 767px) {
  .scroll-showcase {
    height: auto;
    overflow: visible;
    display: block;
    margin-top: 0; /* no pin → no tail to pull into */
  }
  .ss-glow { display: none; }
  .ss-stage { position: static; }
  .ss-slide {
    position: static;
    inset: auto;
    display: block;
    overflow: visible;
  }
  .ss-slide-content { max-height: none; }
  .ss-slide-content > section { padding-top: revert; padding-bottom: revert; }
  /* The label nav only makes sense for the pinned version. */
  .ss-nav { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-showcase {
    height: auto;
    overflow: visible;
    display: block;
  }
  .ss-glow { display: none; }
  .ss-stage { position: static; }
  .ss-slide {
    position: static;
    inset: auto;
    display: block;
    overflow: visible;
  }
  .ss-slide-content { max-height: none; }
  .ss-slide-content > section { padding-top: revert; padding-bottom: revert; }
  .ss-nav { display: none; }
}
/* ============================================================================
   Hero — a centred stack on a violet ground that zooms into the product.

   FIRST FRAME (must fit ONE screen with no scrolling — operator's ask):
       proof → eyebrow → problem → display headline → guarantee → sub →
       TOOL PANEL → CTAs → trust
   ON SCROLL:
       hold · copy fades · the panel grows · it dissolves into the admin
       screen · the screen fills the frame · it walks four of its own pages.

   ── SHAPE ────────────────────────────────────────────────────────────────
   `.home-hero--stack` is `--hero-scroll` tall and provides the runway;
   `.hero-stage` inside it is `position: sticky` at one screen and holds
   everything. Progress is measured off the SECTION (never the panel) in
   LandingHero.jsx and arrives here as custom properties; CSS only composes.

   ⚠️ THE CLIP IS ON THE STAGE, NEVER ON THE SECTION. An ancestor with a
   clipping overflow becomes the scroll container for a sticky child, which
   leaves the stage with no scroll range so it never pins — the exact trap
   CB16 recorded when `overflow: hidden` nearly killed the demo section's zoom.

   ── FITTING THE FRAME ────────────────────────────────────────────────────
   ⚠️ THE VERTICAL RHYTHM IS CAPPED IN vh, NOT ONLY vw. The first cut sized
   type and gaps from width alone, so at 1280×720 the trust line fell off the
   bottom — a wide, SHORT window is the case a vw-only scale cannot see. Every
   size below that affects height uses `min(<vw>, <vh>)` inside its clamp, so
   the stack shrinks when the window is short as well as when it is narrow.

   ── CONTRAST: HOW TO CHECK THIS SECTION ──────────────────────────────────
   The ground is three stacked gradients, so no computed-style sweep can read
   it — it sees the element as transparent and falls through to an ancestor
   (CB9). An analytic bound was tried and was WRONG: compositing the layers'
   peaks by hand gave ≈#6451C8, while the real sampled peak was rgb(129,116,193)
   and two nodes the bound had passed measured 4.04 and 2.44.

   SAMPLE it instead (harness described in §CF5/§CG6): screenshot the section
   with `.home-hero--stack *{color:transparent}` injected, then score each text
   node's own colour against the composited pixel behind it. Two traps that
   produce FICTIONAL results, both hit here:
     · an empty collect (Vite reloaded) reads as "0 failures over 0 nodes";
     · a reload BETWEEN the collect and the shot makes every ground the page
       grey and reports the section failing at ~1.08:1.
   Assert the section is in the DOM after the screenshot as well as before.

   ⚠️ SWEEP BOTH WIDTHS. A narrower hero is TALLER, so the same percentage
   gradient radii push body copy up into the bloom's core: the only failures of
   the final pass were at 375 and both were invisible at 1280.
   ========================================================================= */

/* ---------------------------------------------------------------------------
   The section — pure runway. `.home-hero` (Landing.css) gives it a 1320px
   column, padding and a near-black ground; all of them are replaced here.

   ⚠️ TRIPLE CLASS, DELIBERATELY. `.home-page .home-hero { background-color:
   var(--rf-bg) }` in Landing.css is (0,2,0) and that file loads AFTER this
   one, so a matching (0,2,0) rule loses on order and the hero renders
   page-grey. (0,3,0) wins regardless of load order — the same reason the light
   theme needed `.x.x` throughout (CB5).
   ------------------------------------------------------------------------ */
.home-page .home-hero--stack.home-hero--stack {
  max-width: none;
  padding: 0;
  height: var(--hero-scroll, 420vh);
  position: relative;
  isolation: isolate;
  background: none;

  /* ⚠️ RUN TO THE TOP OF THE DOCUMENT, UNDER THE FIXED NAV (operator: "on the
     above of hero section there is white blank space that shouldn't have to be
     there"). `.home-page` reserves `--rf-nav-reserve` so nothing starts beneath
     the bar; the hero gives that back here and the STAGE re-adds it as its own
     top padding. Nothing else on the page is affected.
     ⚠️ BOTH HALVES READ THE SAME TOKEN. A hardcoded copy that drifts shows
     either a strip of page grey above the hero or slides it under the bar. */
  margin: calc(-1 * var(--rf-nav-reserve, 76px)) 0 0;

  /* ⚠️ NO `overflow` HERE — see the header note. The stage does the clipping. */
}

/* ---------------------------------------------------------------------------
   The pinned stage — one screen, and the frame everything is composed inside.
   ------------------------------------------------------------------------ */
.home-hero--stack .hero-stage{
  /* Rest values, so the hero is correct with no JS at all. */
  --p: 0;
  --copy: 1;
  --grow: 0;
  --swap: 0;
  --tk: 0;

  /* ⚠️ ONE VALUE FOR EVERY SECONDARY GLYPH IN THE SECTION. It was four separate
     `rgba(255,255,255,0.90)` literals; at 375 the sub's "contracts," measured
     4.36:1 while the other three passed, so the tempting fix was to raise only
     that one — which is how a stylesheet ends up with four near-identical
     alphas and no way to tell which are load-bearing.
     ⚠️ THIS IS THE FLOOR, NOT A STARTING POINT. Pure white itself measures only
     5.03:1 at that spot, so there is very little room under it. If something
     needs to look quieter, make it smaller or lighter in WEIGHT — theme.css
     says the same about --rf-ink-faint. */
  --hero-ink-soft: rgba(255, 255, 255, 0.95);

  /* Vertical rhythm. Height-aware so the stack compresses on a short window
     instead of pushing its last line off the frame. */
  --hero-gap-xs: clamp(7px, 1.3vh, 13px);
  --hero-gap-sm: clamp(10px, 1.9vh, 18px);
  --hero-gap-md: clamp(13px, 2.5vh, 26px);

  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;          /* svh = smallest viewport: fits with mobile chrome shown */
  width: 100%;
  overflow: hidden;        /* the clip belongs HERE, not on the section */
  z-index: 1;
  text-align: center;

  /* ⚠️⚠️ THE GROUND NOW LIVES IN theme.css AND THIS IS NO LONGER ITS ONLY
     CONSUMER. `/features`, `/blog`, `/about` and `/pricing` carry the same
     surface via the `.rf-hero-ground` class (operator: "implement the
     background of home hero section into hero section of all pages … except
     demo"), and all five read these tokens. EDIT THE TOKENS, NOT A COPY —
     re-inlining a literal here is how five heroes stop matching.

     The whole rationale that used to sit in this comment — why every radial
     fades to its own hue rather than to `transparent`, and why these four
     alphas and radii are a contrast budget rather than a look — moved with the
     values. Read it there before changing anything.

     ⚠️ THE SHORTHAND-FREE background-color IS NOT BELT-AND-BRACES: without it a
     browser that drops the gradients shows the page grey straight through and
     every white glyph on the hero disappears. */
  background-color: var(--rf-hero-ground, #1D1246);
  background-image: var(--rf-hero-bloom);
}

/* ---- the decorative ground ---------------------------------------------- */
.home-hero--stack .hero-bg{
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* The dot field, masked so it fades before the edges. Shared with the four
   marketing heroes — see the token block in theme.css for why the alpha is
   0.30 and why the mask is half the point. */
.home-hero--stack .hero-bg-dots{
  position: absolute;
  inset: 0;
  background-image: var(--rf-hero-dots);
  background-size: var(--rf-hero-dots-size, 22px 22px);
  -webkit-mask-image: var(--rf-hero-dots-mask);
  mask-image: var(--rf-hero-dots-mask);
}

/* Sinks the corners and hands the bottom edge to the section below. */
.home-hero--stack .hero-bg-vignette{
  position: absolute;
  inset: 0;
  background-image: var(--rf-hero-vignette);
}

/* ---- the stack ---------------------------------------------------------- */
.home-hero--stack .hero-stack{
  position: relative;
  z-index: 1;
  box-sizing: border-box;
  height: 100%;
  max-width: 1060px;
  margin: 0 auto;
  padding:
    calc(var(--rf-nav-reserve, 76px) + var(--hero-gap-sm))
    var(--rf-gutter, 24px)
    clamp(20px, 3.4vh, 44px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* The two blocks that clear the frame for the product.
   ⚠️ `visibility: hidden` AT THE END, NOT JUST opacity 0. `.hero-tail` holds
   two real buttons; an invisible-but-focusable control is a keyboard trap that
   a sighted user cannot see and a screen-reader user cannot escape. The class
   is toggled from JS once the fade has finished, so nothing is interpolated —
   `visibility` is not interpolatable and would flip at the START of a fade. */
.home-hero--stack .hero-copy,
.home-hero--stack .hero-tail{
  width: 100%;
  /* ⚠️ FLEX COLUMNS, NOT PLAIN BLOCKS. `.hero-proof` is inline-flex and
     `.hero-eyebrow` is inline-block; in a block container they are both
     inline-level and flowed onto the SAME LINE — the rating chip rendered
     straight on top of the eyebrow. They used to be direct children of the
     flex `.hero-stack`, which had been doing this for them.
     ⚠️ A GEOMETRY SWEEP DOES NOT CATCH THIS. Two overlapping elements both
     still sit inside the frame, so the "does the first frame fit" check
     reported a clean pass over a broken layout. Looking is what found it. */
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: var(--copy, 1);
  transform: translateY(calc(-26px * (1 - var(--copy, 1))));
  will-change: opacity, transform;
}
.home-hero--stack .hero-stage.is-copy-out .hero-copy,
.home-hero--stack .hero-stage.is-copy-out .hero-tail{ visibility: hidden; pointer-events: none; }

/* ---- proof cluster ------------------------------------------------------
   Placed at the top, where the reference puts its rating.

   ⚠️ IT SITS IN A CHIP BECAUSE THE MEASUREMENT SAID SO. Sampling the real
   composited pixel behind this row gave rgb(129,116,193) — brighter than the
   three layers were calculated to peak at, because this row sits in the core
   of bloom 1. On that ground white 15px measured 4.04:1 and the gold stars
   2.44:1. White is already the brightest possible ink, so the only remaining
   lever was the GROUND. The chip takes them to 12.5 and 7.6. It is also what
   the reference does with its rating. If the bloom is ever brightened,
   re-measure THIS row first — it is the worst case, not the headline. */
.home-hero--stack .hero-proof{
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-bottom: var(--hero-gap-sm);
  padding: clamp(5px, 0.9vh, 7px) 16px;
  border-radius: var(--rf-radius-pill, 999px);
  background: rgba(13, 8, 36, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.16);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  font-family: var(--rf-font-body, 'Open Sans', system-ui, sans-serif);
  font-size: 13.5px;
  line-height: 1.2;
}
.home-hero--stack .hero-proof-score{ font-weight: 700; font-size: 15px; color: #ffffff; }
/* Gold is a light hue and never clears 4.5:1 on a light ground; on the chip it
   does (7.6:1). It would also have been acceptable below the floor for the same
   reason as the testimonial stars (CB15) — the value is stated in text right
   beside it — but there was no need to spend that exemption here. */
.home-hero--stack .hero-proof-star{ color: #FFC107; letter-spacing: 2px; font-size: 13px; }
.home-hero--stack .hero-proof-label{ color: var(--hero-ink-soft, rgba(255, 255, 255, 0.95)); font-weight: 500; }

/* ---- eyebrow ------------------------------------------------------------ */
.home-hero--stack .hero-eyebrow{
  display: inline-block;
  margin-bottom: var(--hero-gap-xs);
  font-family: var(--rf-font-body, 'Open Sans', system-ui, sans-serif);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: #ffffff;
}

/* ---- problem line -------------------------------------------------------
   The heading system's opener (operator's rewrite): name the pain, strike the
   tools. ⚠️ THE STRUCK WORDS STAY AT THE SECTION'S SOFT-INK FLOOR — the
   --hero-ink-soft note above says the floor is not a starting point, so the
   de-emphasis is carried by the line-through and a lighter WEIGHT, never by a
   lower alpha. The mockup's #8f83c2 would have failed exactly the way the
   sub's "contracts," did at 375. */
.home-hero--stack .hero-problem{
  margin: 0 0 var(--hero-gap-xs);
  font-family: var(--rf-font-body, 'Open Sans', system-ui, sans-serif);
  font-size: clamp(14px, min(1.35vw, 2.3vh), 17px);
  font-weight: 600;
  line-height: 1.45;
  color: #ffffff;
  /* ⚠️ WIDE ENOUGH FOR ONE LINE. The sentence is ~70 chars; 56ch wrapped it
     to two lines at every width and those 24px were exactly what pushed the
     trust line off a 1280×720 frame. Measured, not guessed. */
  max-width: 74ch;
}
.home-hero--stack .hero-problem s{
  font-weight: 500;
  color: var(--hero-ink-soft, rgba(255, 255, 255, 0.95));
  text-decoration-thickness: 1.5px;
}

/* ---- headline -----------------------------------------------------------
   Session 71: the sentence is the HEADING SANS (Red Hat Display via the
   token — the Design Monks system), and the accent is the one serif gesture
   left: Playfair Display italic via --rf-font-accent, exactly the sans-
   heading + serif-italic-flourish pairing the reference site uses. The
   session-68 all-Fraunces version this replaces is in git if it is ever
   wanted back.

   The accent keeps italic 400 on the money claim. Playfair italic loads as
   a 400..700 range (index.html), so bolding it WOULD render a real face —
   but the contrast here is roman-sans vs italic-serif; it needs no weight.

   ⚠️ THE CAP CAME DOWN WITH THE LONGER SENTENCE (68 → 58, 7.4vh → 6.2vh).
   The headline grew from 8 words to 14, so at the old cap it ran four lines
   and pushed the trust line off a 720-tall frame. The height budget lives in
   the whole stack — re-measure 1280×720 AND 375×812 before raising this. */
.home-hero--stack .hero-headline{
  margin: 0 0 var(--hero-gap-xs);
  /* The token IS Red Hat Display since session 71's Design Monks flip. */
  font-family: var(--rf-font-heading, 'Segoe UI', Arial, sans-serif);
  font-optical-sizing: auto;
  font-size: clamp(26px, min(5vw, 6.2vh), 58px);
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -0.01em;
  color: #ffffff;
  text-wrap: balance;
  max-width: min(100%, 940px);
}
.home-hero--stack .hero-headline-accent{
  font-family: var(--rf-font-accent, Georgia, 'Times New Roman', serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.01em;
  color: #ffffff;
  /* The old rule painted this with a purple→blue background-clip gradient.
     Neutralised: on a violet ground the accent is carried by the FACE, not by
     a hue that would sink into the backdrop. */
  background: none;
  -webkit-text-fill-color: currentColor;
}

/* ---- guarantee line -----------------------------------------------------
   /pricing's risk-reversal row (`.pricing-risk`), re-toned for the dark
   ground. ⚠️ #15803D IS THE LIGHT-GROUND CHECK AND WOULD SINK HERE — #4ADE80
   is the dark-ground green that file's own comment says it replaced (1.9:1 on
   white, ~8:1 on this violet). The ✓ is aria-hidden decoration; the words
   carry the meaning. One ✓ per item so a wrapped row never orphans a
   separator. */
.home-hero--stack .hero-guarantee{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 4px 18px;
  margin: 0 0 var(--hero-gap-sm);
  font-family: var(--rf-font-body, 'Open Sans', system-ui, sans-serif);
  font-size: clamp(12.5px, min(1.1vw, 1.9vh), 14px);
  font-weight: 500;
  line-height: 1.45;
  color: var(--hero-ink-soft, rgba(255, 255, 255, 0.95));
}
.home-hero--stack .hero-guarantee-item{
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  white-space: nowrap;
}
.home-hero--stack .hero-guarantee-ck{ color: #4ADE80; font-weight: 800; }
.home-hero--stack .hero-guarantee b{ color: #ffffff; font-weight: 700; }

/* ---- sub ---------------------------------------------------------------- */
.home-hero--stack .hero-sub{
  margin: 0 0 var(--hero-gap-md);
  max-width: 62ch;
  font-family: var(--rf-font-body, 'Open Sans', system-ui, sans-serif);
  font-size: clamp(14px, min(1.2vw, 2.1vh), 17px);
  line-height: 1.6;
  font-weight: 400;
  color: var(--hero-ink-soft, rgba(255, 255, 255, 0.95));
}
/* Pure white at 700 — emphasis by face and weight, the alpha floor stands. */
.home-hero--stack .hero-sub b{ color: #ffffff; font-weight: 700; }

/* ============================================================================
   THE MORPH BOX — the tools panel, and the product it becomes.

   ⚠️ `.hero-morph` IS NEVER TRANSFORMED. It is the ruler the takeover measures
   against: the JS reads its rect every frame to work out how far and how much
   the admin screen must travel and grow. Give it a transform of its own and
   the sequence starts measuring its own output, and progress creeps at a
   standstill. Both children carry the motion instead.
   ========================================================================= */
.home-hero--stack .hero-morph{
  position: relative;
  width: 100%;
  max-width: 720px;
  margin: 0 auto var(--hero-gap-md);
}

/* ---- the six tools ------------------------------------------------------
   ⚠️ TWO ELEMENTS ON PURPOSE. `.hero-tools-reveal` is the `<Reveal>` wrapper
   and owns the ENTRANCE (its `.reveal.in-view` rule is (0,2,0) and loads after
   this file); `.hero-tools` owns the SCROLL. Merging them left the panel stuck
   at opacity 1 for the whole sequence — `--swap` reached 1.0000 while the
   element still computed 1.00 — so it never dissolved into the product. */
.home-hero--stack .hero-tools-reveal{ width: 100%; }

.home-hero--stack .hero-tools{
  width: 100%;
  padding: var(--hero-gap-sm) 18px clamp(12px, 2vh, 18px);
  border-radius: 26px;
  background: rgba(255, 255, 255, 0.065);
  border: 1px solid rgba(255, 255, 255, 0.16);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow:
    0 26px 60px -34px rgba(0, 0, 0, 0.9),
    inset 0 1px 0 rgba(255, 255, 255, 0.14);
  /* Grows toward the frame, then dissolves as the product resolves in its
     place. Capped well under the panel's final size — it is being REPLACED,
     not zoomed into. */
  opacity: var(--tools-op, 1);
  transform: scale(var(--tools-scale, 1));
  transform-origin: center;
  will-change: transform, opacity;
}

.home-hero--stack .hero-tools-caption{
  margin: 0 0 var(--hero-gap-xs);
  font-family: var(--rf-font-body, 'Open Sans', system-ui, sans-serif);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--hero-ink-soft, rgba(255, 255, 255, 0.95));
}

/* ⚠️ minmax(0, 1fr), NOT 1fr. A bare `fr` is minmax(auto, 1fr) and sizes from
   content, so "Google Calendar" would push its own column wider than the five
   beside it and the badges would stop lining up. Same trap the AdminPanel's
   list tables hit twice (CLAUDE.md). */
.home-hero--stack .hero-tools-list{
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 10px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.home-hero--stack .hero-tool{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(5px, 1.1vh, 9px);
  min-width: 0;
}

.home-hero--stack .hero-tool-badge{
  position: relative;
  width: clamp(38px, min(3.8vw, 5.6vh), 48px);
  height: clamp(38px, min(3.8vw, 5.6vh), 48px);
  flex: none;
  border-radius: 50%;
  background: #ffffff;
  display: grid;
  place-items: center;
  overflow: hidden;
  box-shadow: 0 8px 18px -8px rgba(6, 4, 20, 0.75);
}
.home-hero--stack .hero-tool-badge svg{
  width: 48%;
  height: 48%;
  /* 7.4:1 on white. An icon only needs 3:1, but this is the brand's own
     text-on-light step and it keeps the six marks reading as one family. */
  color: var(--rf-primary-deep, #453A8C);
}

.home-hero--stack .hero-tool-name{
  font-family: var(--rf-font-body, 'Open Sans', system-ui, sans-serif);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.32;
  color: #ffffff;
  overflow-wrap: anywhere;
}

/* THE CANCEL — all six, drawn once on arrival and left struck.
   ⚠️ IT IS A STATE, NOT A LOOP. An earlier version cycled the strike through
   one badge at a time; the operator asked for the icons to READ as cut, and a
   mark that is only present one sixth of the time does not say that. The
   stagger is a one-shot entrance, `forwards`, and then it is simply the truth
   about those six products.

   ⚠️ ORDER MATTERS IN THE TRANSFORM. Rightmost is applied first, so scaleX
   grows the bar along its OWN axis and rotate then lays that axis across the
   circle. Swap them and the bar grows horizontally and then tips over, which
   sweeps a wedge instead of drawing a line. */
.home-hero--stack .hero-tool-strike{
  position: absolute;
  left: 6px;
  right: 6px;
  top: 50%;
  height: 2.5px;
  border-radius: 2px;
  background: #B91C1C;                 /* the theme's danger red — 5.9:1 on white */
  transform-origin: left center;
  transform: translateY(-50%) rotate(-19deg) scaleX(0);
  opacity: 0;
  animation: heroToolStrike 0.42s cubic-bezier(0.22, 1, 0.36, 1) var(--strike-delay, 0.55s) forwards;
}

@keyframes heroToolStrike {
  from { transform: translateY(-50%) rotate(-19deg) scaleX(0); opacity: 1; }
  to   { transform: translateY(-50%) rotate(-19deg) scaleX(1); opacity: 1; }
}

/* ---- the product, resolving out of the panel it replaces ---------------- */
.home-hero--stack .hero-panel{
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  opacity: var(--swap, 0);
  /* Read right to left: sit at rest exactly ON the tools card's box, grow to
     the measured full-frame scale, then travel to the centre of the stage.
     --tk-* are measured every frame in LandingHero.jsx.

     ⚠️ TWO SCALE AXES, NOT ONE. The card is a wide flat rectangle and the
     screenshot is 16:10, so a single uniform scale can only reproduce the
     screenshot's aspect ratio — it overhangs the card vertically at rest,
     which is the size mismatch that made this read as one panel appearing on
     top of another instead of emerging from inside it. They converge on the
     same value by the end of the takeover, so nothing is distorted once the
     screen is up.

     ⚠️ EVERY DIVISION THAT PRODUCED THESE HAPPENED IN JS. A custom property
     substitutes as TEXT, so `calc(var(--a)/var(--b))` becomes a nested calc as
     a divisor and the browser drops the WHOLE transform — silently, while
     other properties on the same element keep resolving. */
  transform:
    translate(-50%, -50%)
    translate(var(--tk-x, 0px), var(--tk-y, 0px))
    scale(var(--tk-sx, 1), var(--tk-sy, 1));
  transform-origin: center;
  pointer-events: none;
  will-change: transform, opacity;
}

/* The finished panel's FRAME — the reference's (LinkedHelper) violet ring
   around an inset, rounded screen. A pseudo rather than a border on `.hap`:
   the .hap is overflow:hidden (it clips the walkthrough pages) so nothing
   inside it can ring the outside, and HeroAdminPanel is shared with the
   Features hero, which must not inherit this. Rides --tk so it is absent at
   rest and arrives WITH the takeover; every colour is mixed from the brand
   #6A5ACD (the hero's own rule — no new hue). The ring is drawn by masking
   the padding-box out of a gradient, which keeps the radius; the pseudo lives
   inside the scaled subtree, so its 2px reads ~3px at full frame — the
   reference's own weight. Radius tracks the panel's per-axis values (+2 for
   the offset) so the ring and the corner stay concentric mid-squash. */
.home-hero--stack .hero-panel::after{
  content: '';
  position: absolute;
  inset: -2px;
  padding: 2px;
  border-radius: calc(var(--hap-rx, 10px) + 2px) / calc(var(--hap-ry, 10px) + 2px);
  background: linear-gradient(135deg, #9789e2, #6a5acd 46%, #453a8c);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask-composite: exclude;
  opacity: var(--tk, 0);
  pointer-events: none;
}

/* ============================================================================
   THE TWO CTAs — the existing buttons, unchanged in label and behaviour.

   ⚠️ THE PRIMARY HAD TO INVERT, AND THAT IS MEASURED, NOT TASTE. theme.css
   fills `.home-cta-primary` with the brand for a light page. On this ground
   #6A5ACD-on-violet measures 1.54:1 — the page's loudest button would have
   been very nearly invisible. White carries it here (7.9:1 against the ground)
   with the brand's deep step as the label (7.4:1 on white).
   ========================================================================= */
.home-hero--stack .hero-ctas{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
  margin: 0 0 var(--hero-gap-sm);
}

/* (0,3,0) — beats theme.css's `.home-cta-primary.home-cta-primary` (0,2,0) and
   Landing.css's `.home-cta-primary` whichever way the files land. */
.home-hero--stack .home-cta-primary.home-cta-primary {
  background: #ffffff;
  color: var(--rf-primary-deep, #453A8C);
  border: 1px solid #ffffff;
  padding: clamp(12px, 1.8vh, 15px) 30px;
  font-size: 15px;
  box-shadow: 0 16px 34px -16px rgba(0, 0, 0, 0.7);
}
.home-hero--stack .home-cta-primary.home-cta-primary * { color: inherit; }
.home-hero--stack .home-cta-primary.home-cta-primary:hover {
  background: #F1EFFC;
  border-color: #F1EFFC;
  color: var(--rf-primary-deep, #453A8C);
  transform: translateY(-2px);
}
.home-hero--stack .home-cta-primary.home-cta-primary:active { transform: translateY(1px); }

/* The ghost keeps its white circular wipe — authored for a dark ground, and
   this is a dark ground again, so it works as originally designed. The light
   theme's brand-tint hover is overridden back off. */
.home-hero--stack .home-hero-cta-ghost.home-hero-cta-ghost {
  background: transparent;
  color: #ffffff;
  border: 1px solid rgba(255, 255, 255, 0.62);   /* 3.3:1 — control boundary */
  padding: clamp(12px, 1.8vh, 15px) 30px;
  font-size: 15px;
}
.home-hero--stack .home-hero-cta-ghost.home-hero-cta-ghost::before { background: #ffffff; }
.home-hero--stack .home-hero-cta-ghost.home-hero-cta-ghost:hover {
  background: transparent;
  border-color: #ffffff;
  color: var(--rf-primary-deep, #453A8C);        /* on the white wipe: 7.4:1 */
  transform: translateY(-2px);
}

/* ---- trust line --------------------------------------------------------- */
.home-hero--stack .hero-trustline{
  display: block;
  font-family: var(--rf-font-body, 'Open Sans', system-ui, sans-serif);
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--hero-ink-soft, rgba(255, 255, 255, 0.95));
  max-width: 46ch;
  margin: 0 auto;
}

/* ============================================================================
   THE HAND-OFF TO THE SECTION BELOW.
   The site's established treatment at a theme boundary (CB15/CB16): the light
   block curves over the dark one.

   ⚠️ THE NEGATIVE MARGIN IS THE LOAD-BEARING HALF. A radius on its own rounds
   a corner against the element's own colour and is invisible; the block has to
   lap over the hero for the notches to reveal the curve.
   ========================================================================= */
.home-page .home-hero--stack + .home-trust {
  position: relative;
  z-index: 2;
  margin-top: -44px;
  border-radius: 44px 44px 0 0;
  border-top: none;
}

/* ============================================================================
   Responsive
   ========================================================================= */

/* Six across stops being readable well before the phone breakpoint — at 720px
   each column is ~105px and "Google Calendar" is already wrapping. Three keeps
   the badges on a comfortable pitch and the names to at most two lines. */
@media (max-width: 720px) {
  .home-hero--stack .hero-tools-list{ grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px 10px; }
}

/* ⚠️ THIS BLOCK IS A HEIGHT BUDGET, NOT A STYLE PREFERENCE. The heading
   system added two lines and ~60 chars of headline; at 375×812 the stack ran
   ~130px past the stage, and the stage CLIPS (overflow: hidden — a pinned
   hero cannot scroll its own content). Every step below buys those pixels
   back: tighter gap tokens, one type step down on each line, smaller badges.
   NOTHING IS HIDDEN — all nine rows survive. Re-measure 375×812 after any
   change to this section's content; that viewport, not 1280, is the binding
   constraint. */
@media (max-width: 640px) {
  .home-hero--stack .hero-stage{
    --hero-gap-xs: clamp(5px, 0.9vh, 8px);
    --hero-gap-sm: clamp(7px, 1.3vh, 11px);
    --hero-gap-md: clamp(9px, 1.7vh, 14px);
  }
  .home-hero--stack .hero-stack{ padding-inline: 18px; padding-bottom: 14px; }
  .home-hero--stack .hero-proof{ font-size: 12.5px; padding: 4px 12px; }
  .home-hero--stack .hero-proof-score{ font-size: 13.5px; }
  .home-hero--stack .hero-problem{ font-size: 13px; }
  .home-hero--stack .hero-headline{ letter-spacing: -0.02em; font-size: clamp(23px, 6.6vw, 26px); }
  .home-hero--stack .hero-guarantee{ font-size: 12px; gap: 3px 12px; }
  .home-hero--stack .hero-sub{ font-size: 13px; }
  .home-hero--stack .hero-tools{ padding: 10px 10px 12px; border-radius: 22px; }
  .home-hero--stack .hero-tools-list{ gap: 8px; }
  .home-hero--stack .hero-tool-badge{ width: 34px; height: 34px; }
  .home-hero--stack .hero-tool-name{ font-size: 11px; }
  .home-hero--stack .hero-ctas{ width: 100%; gap: 10px; }
  .home-hero--stack .home-cta-primary.home-cta-primary,
  .home-hero--stack .home-hero-cta-ghost.home-hero-cta-ghost { flex: 1 1 220px; padding: 10px 22px; font-size: 14px; }
  .home-hero--stack .hero-trustline{ font-size: 12.5px; }
  .home-page .home-hero--stack + .home-trust { margin-top: -30px; border-radius: 30px 30px 0 0; }
}

/* ⚠️ 2-UP STARTED AT 380px AND THAT WAS TOO EARLY — measured at 375×812 it made
   the panel three rows tall (~330px) and pushed BOTH CTAs off the first screen,
   on the one viewport where a visible CTA matters most. Three columns there
   leave ~98px each, which "Google Calendar" fits on two lines. */
@media (max-width: 340px) {
  .home-hero--stack .hero-tools-list{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ============================================================================
   Reduced motion — the hero becomes an ordinary static section.

   ⚠️ IT RESTS AT THE START OF THE SEQUENCE, NOT THE END. Landing on the
   finished state would leave a full-frame screenshot with no copy and no way
   to reach either CTA. The runway collapses so there is no dead scroll, and
   the product simply is not part of the story for this visitor.

   ⚠️ THE STRIKES BECOME PERMANENT RATHER THAN ABSENT. Removing the animation
   entirely would leave six un-struck logos under a caption saying "Replaces",
   which is a weaker read than the motion version — so every tool rests struck.
   The meaning survives; only the movement goes.
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  .home-page .home-hero--stack.home-hero--stack { height: auto; }
  .home-hero--stack .hero-stage{ position: relative; height: auto; min-height: 100svh; }
  .home-hero--stack .hero-panel{ display: none; }
  .home-hero--stack .hero-tools{ transform: none; opacity: 1; }
  .home-hero--stack .hero-copy,
  .home-hero--stack .hero-tail{ opacity: 1; transform: none; }

  .home-hero--stack .hero-tool-strike{
    animation: none;
    opacity: 1;
    transform: translateY(-50%) rotate(-19deg) scaleX(1);
  }
  .home-hero--stack .home-cta-primary.home-cta-primary,
  .home-hero--stack .home-hero-cta-ghost.home-hero-cta-ghost { transition: none; }
  .home-hero--stack .home-cta-primary.home-cta-primary:hover,
  .home-hero--stack .home-hero-cta-ghost.home-hero-cta-ghost:hover { transform: none; }
}
/* DemoLauncher — "See RntlOS in Action". The demo panel ZOOMS OPEN AS YOU
   SCROLL, from a small inset card to the full viewport.

   Everything is composed from ONE value, --dl-p (0 at rest → 1 fully open),
   written to .demo-launcher-sticky once per animation frame by the component.
   Only transform and opacity are driven by it, so the zoom stays on the
   compositor and never relayouts the page.

   Every rule is scoped under .demo-launcher on purpose: the markup uses the
   generic class names from the brief (.overlay, .video-player, .button-text),
   which unscoped would be a collision waiting to happen in a stylesheet this
   size. Scoping keeps the structure verbatim and the CSS safe.

   Colours carry literal fallbacks — a var() that resolves to nothing is an
   INVALID value and computes to `inherit`, which silently renders wrong. */

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

/* The tall wrapper is nothing but scroll distance for the sticky stage. */
.demo-launcher {
  position: relative;
  width: 100%;
  height: var(--dl-len, 260vh);
  background: #000;
}

.demo-launcher-sticky {
  --dl-p: 0;                       /* fallback: with no JS everything sits at rest */
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;                  /* dvh so mobile URL-bar chrome cannot clip it */
  overflow: hidden;

  /* The panel's scale against the stage. Safe to compose here — it is a plain
     interpolation, no division by a variable.
     ⚠️ The BUTTON's equivalent (--dl-btn-*) is NOT composed in CSS: it needs
     `panelScale / restScale`, and since a custom property substitutes as TEXT
     that becomes a nested calc used as a dividend, which the browser rejects —
     dropping the whole transform silently. The component writes those as plain
     numbers instead. Do not "tidy" them back into calc(). */
  --dl-sx: calc(var(--dl-sx0, 0.14) + (1 - var(--dl-sx0, 0.14)) * var(--dl-p));
  --dl-sy: calc(var(--dl-sy0, 0.06) + (1 - var(--dl-sy0, 0.06)) * var(--dl-p));
}

/* Never transformed — this is the box the component measures. */
.demo-launcher .demo-button-slot {
  position: relative;
  z-index: 2;
  display: inline-block;
}

/* Centred: at rest the stage shows ONLY the copy and the button. The panel
   starts mapped onto the button itself, so there is no card sitting below the
   copy to make room for any more. */
.demo-launcher .overlay {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  padding: clamp(18px, 3vw, 32px);
  text-align: center;
}

/* ---- the copy ----------------------------------------------------------
   Fades and lifts over the FIRST HALF of the zoom (p 0 → 0.5), so it is gone
   before the panel is large enough to sit behind it. `min()` clamps the eased
   value at 1 rather than letting it run past. */

/* The heading leaves EARLY — it has to be gone before the button has grown
   enough to reach it, or the pill slides up over live text. */
.demo-launcher .overlay-eyebrow,
.demo-launcher .overlay-text {
  --dl-copy: min(1, calc(var(--dl-p) * 4));   /* gone by p = 0.25 */
  position: relative;
  z-index: 2;
  opacity: calc(1 - var(--dl-copy));
  transform: translate3d(0, calc(var(--dl-copy) * -48px), 0);
}

.demo-launcher .overlay-eyebrow {
  display: inline-block;
  color: var(--text, #FAFAFA);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  margin-bottom: 18px;
  /* The eyebrow's resting opacity is 0.55, so it has to fade from there. */
  opacity: calc(0.55 * (1 - var(--dl-copy)));
}

.demo-launcher .overlay-text {
  display: block;
  color: var(--text, #FAFAFA);
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0 0 40px;
}

/* ⚠️ THE BUTTON ITSELF GROWS. It is scaled onto exactly the same box as the
   panel (--dl-bx/--dl-by is that box in the button's own units) and translated
   so their centres coincide, so what the eye follows is one pill enlarging —
   not a black rectangle fading in over a small button, which is what an
   early cross-fade looked like.

   It stays fully opaque well past the point where it is visibly big, and only
   then hands over to the video. Its measured box comes from .demo-button-slot,
   which is never transformed. */
.demo-launcher .demo-button {
  position: relative;
  transform-origin: 50% 50%;
  transform:
    translate3d(var(--dl-btn-tx, 0px), var(--dl-btn-ty, 0px), 0)
    scale(var(--dl-btn-sx, 1), var(--dl-btn-sy, 1));
  /* Corner tracks the panel's: 30px at rest → square when open. */
  border-radius: var(--dl-btn-rx, 30px) / var(--dl-btn-ry, 30px);
  /* Opaque until the pill is unmistakably large (p 0.32), gone by 0.48. */
  opacity: clamp(0, calc((0.48 - var(--dl-p)) * 6.25), 1);

  background: var(--text, #FAFAFA);
  color: #0A0A0A;
  padding: 14px 28px;
  border: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  min-height: 44px;
  cursor: pointer;
  will-change: transform;
  transition: background 180ms ease;
}

/* ⚠️ The LABEL cannot ride the scale — the button grows ~7× wide and ~15×
   tall, and non-uniform scaling would smear the text long before anyone could
   read it. It fades out almost immediately, leaving a clean surface to grow. */
.demo-launcher .button-text {
  display: inline-block;
  opacity: clamp(0, calc((0.1 - var(--dl-p)) * 14), 1);
}

.demo-launcher .demo-button:hover { background: #E5E5E5; }

.demo-launcher .demo-button:focus-visible {
  outline: 2px solid var(--text, #FAFAFA);
  outline-offset: 3px;
}

/* `visibility: hidden` takes these out of the tab order and the accessibility
   tree — an invisible but still focusable control is a keyboard trap, and
   opacity alone does not remove one. Two thresholds because the button
   cross-fades with the panel long before the heading finishes leaving. */
.demo-launcher-sticky.is-button-gone .demo-button { visibility: hidden; }

/* ⚠️⚠️ THE OPEN VIDEO IS A CLICK TARGET, AND ONLY ONCE THE CTA HAS GONE.
   Browser autoplay policy will not grant audio until the document has a real
   user gesture, and a visitor who only ever scrolls has given it none — so the
   demo opens silent through no fault of the code. The recovery has to be ONE
   obvious gesture, and the largest, most natural target is the video itself.
   Gated on `is-button-gone` for the same reason `.video-player` is
   `pointer-events: none` at rest: before that the panel sits exactly on top of
   "Watch the demo" and would eat the click that opens the whole section.
   ⚠️ The pointer cursor appears ONLY while the click would actually do
   something (`is-sound-claimable`), so it never advertises an action that is
   already done. */
.demo-launcher-sticky.is-button-gone .video-player { pointer-events: auto; }
/* ⚠️ ON THE PANEL, NOT ON THE STAGE. The stage's class attribute is owned by
   `update()` (imperative `classList.toggle` every frame); a React-driven class
   up there is rewritten away on the next render, taking `is-button-gone` and
   `is-zoomed` with it. See the JSX comment. */
.demo-launcher .video-player.is-sound-claimable { cursor: pointer; }

.demo-launcher-sticky.is-zoomed .overlay-eyebrow,
.demo-launcher-sticky.is-zoomed .overlay-text {
  visibility: hidden;
}

/* ---- the panel that grows OUT OF the button ----------------------------
   The panel fills the stage and is mapped onto the BUTTON's own rect at rest,
   so opening it is that map running backwards to identity — which is what
   makes the video read as having been inside the button all along.

   --dl-sx0/--dl-sy0/--dl-dx0/--dl-dy0 are the button's measured pose, written
   by the component. CSS cannot derive them: calc() cannot divide a length by
   a length, so neither a scale factor nor an aspect ratio is expressible here.
   The fallbacks describe a small centred box, so a failed script leaves
   something sane rather than an invalid value. */

.demo-launcher .video-player {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  background: #000;
  /* Nothing inside is interactive, and at rest this sits exactly on top of the
     button — without this it would swallow the click that opens it. */
  pointer-events: none;
  /* See the `.is-button-gone` rule further down: once the CTA is out of the
     way this becomes clickable, so the whole video can act as the "give me
     sound" target. It must stay `none` HERE or the CTA is unclickable. */

  transform:
    translate3d(
      calc(var(--dl-dx0, 0px) * (1 - var(--dl-p))),
      calc(var(--dl-dy0, 0px) * (1 - var(--dl-p))),
      0)
    scale(var(--dl-sx), var(--dl-sy));

  /* 30px is the button's own pill radius. Each axis divides by ITS OWN scale
     via the `h-radii / v-radii` syntax — one shared radius under a non-uniform
     scale draws visibly elliptical corners. */
  border-radius:
    calc((1 - var(--dl-p)) * 30px / var(--dl-sx)) /
    calc((1 - var(--dl-p)) * 30px / var(--dl-sy));

  /* ⚠️ INVISIBLE UNTIL THE SCROLL STARTS, and it stays invisible while the
     button does the growing. At p = 0 nothing of the video is on screen — the
     section is just the heading and the button. The video only arrives once
     the pill is already large (0.28 → 0.44), which is what makes the zoom read
     as the BUTTON opening rather than a rectangle appearing over it. */
  opacity: clamp(0, calc((var(--dl-p) - 0.28) * 6.25), 1);
  will-change: transform, opacity;
}

.demo-launcher .video-player-media {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  /* `cover`: the panel matches the viewport's aspect only at full size, so
     letterboxing would leave black bars inside the card at rest. */
  object-fit: cover;
  background: #000;
}

.demo-launcher .video-player-error {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  padding: 24px;
  color: var(--text-dim, #9CA3AF);
  font-size: 14px;
}

.demo-launcher .video-player-error p { margin: 0; }

/* ---- reduced motion -----------------------------------------------------
   ⚠️ NOT "jump to the finished state". The finished state is a fullscreen
   video with the heading and the CTA scrolled off — landing there would
   DELETE content and the only booking link from anyone with the preference
   set. Instead the section degrades to an ordinary static one: copy on top,
   the demo below it at a sane size, nothing pinned and nothing moving. */

@media (prefers-reduced-motion: reduce) {
  .demo-launcher { height: auto; }

  .demo-launcher-sticky {
    position: static;
    height: auto;
    padding: clamp(56px, 8vw, 100px) 0;
  }

  .demo-launcher .overlay { height: auto; }

  /* Copy back to its normal resting appearance. */
  .demo-launcher .overlay-eyebrow { opacity: 0.55; transform: none; }
  .demo-launcher .overlay-text,
  .demo-launcher .demo-button { opacity: 1; transform: none; }
  .demo-launcher .demo-button { visibility: visible; }

  /* Panel returns to the flow as a plain 16:9 card under the copy. */
  .demo-launcher .video-player {
    position: relative;
    inset: auto;
    width: 100%;
    max-width: 1100px;
    margin-top: clamp(28px, 4vw, 48px);
    aspect-ratio: 16 / 9;
    transform: none;
    border-radius: 16px;
    opacity: 1;
    will-change: auto;
  }

  /* Neither zoom class can be applied under this preference, but be explicit —
     hiding the CTA here would be the exact bug this block exists to avoid. */
  .demo-launcher-sticky.is-zoomed .overlay-eyebrow,
  .demo-launcher-sticky.is-zoomed .overlay-text,
  .demo-launcher-sticky.is-button-gone .demo-button {
    visibility: visible;
  }
}

/* ============================================================================
   LIGHT THEME (redesign — session 27)
   ⚠️ This section is a DELIBERATE dark band (`--rf-surface-ink`) — a light
   frame around dark video footage reads as a mistake. So its copy must NOT
   follow the page's ink tokens, which it did after the theme flip: the
   heading and eyebrow inherited #333333 onto near-black at 1.49:1, and the
   button hit the `background: var(--text); color: #0A0A0A` white-pill idiom
   for a fifth time (1.57:1).

   Any surface that opts out of the page theme has to restate its own text
   colours. Inheriting is what breaks it.
   ========================================================================= */

.demo-launcher .overlay-text { color: var(--rf-ink-invert, #FFFFFF); }        /* 19.2:1 */
.demo-launcher .overlay-eyebrow { color: var(--rf-sky, #56A5E0); opacity: 1; } /*  7.2:1 */

.demo-launcher .demo-button {
  background: var(--rf-ink-invert, #FFFFFF);
  color: var(--rf-night, #0B0E1A);   /* 19.2:1 — inverted pill on the dark band */
}
.demo-launcher .demo-button:hover { background: var(--rf-mist, #F1F8FD); }
.demo-launcher .button-text { color: inherit; }

/* ============================================================================
   SOUND CONTROL (session 29, §CJ)

   The demo video now carries an audio track, so it needs a way to hear it.

   ⚠️ IT CANNOT AUTOPLAY WITH SOUND, AND THAT IS NOT A SETTING. Every current
   browser refuses to autoplay an unmuted video; starting unmuted does not get
   sound, it gets a video that never starts. So it autoplays muted and this is
   the affordance — which is also what the reference site does with its own
   showreel.

   ⚠️ IT IS HIDDEN UNTIL THE CTA HAS GONE, for a reason the panel's own comment
   already records: at rest `.video-player` sits exactly on top of the "Book a
   live demo" button, and anything interactive over that region swallows the
   click that opens the whole section. `is-button-gone` (p > 0.48) is the point
   the stage itself declares that button finished with.

   ⚠️ `visibility`, NOT opacity ALONE. An invisible-but-focusable control is a
   keyboard trap — the same rule this component applies to its own CTA. It is
   toggled, never interpolated: `visibility` is not interpolatable and would
   flip at the START of a fade, so it rides a delay instead.
   ========================================================================= */

.demo-launcher .dl-sound {
  position: absolute;
  z-index: 3;                       /* over .video-player (z-index 1) */
  right: clamp(14px, 2.4vw, 30px);
  bottom: clamp(14px, 2.4vw, 30px);

  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 9px 16px 9px 13px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  /* Sits on video frames of unknown brightness, so it brings its own dark
     ground rather than trusting whatever is behind it. */
  background: rgba(11, 14, 26, 0.62);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: #ffffff;
  font-family: var(--rf-font-body, 'Open Sans', system-ui, sans-serif);
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;

  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity 220ms ease,
    background 180ms ease,
    border-color 180ms ease,
    visibility 0s linear 220ms;
}

.demo-launcher-sticky.is-button-gone .dl-sound {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0s;
}

/* ⚠️ AND IT GOES AGAIN THE MOMENT THE PANEL DOES. The control lives inside the
   sticky stage, so once that unpins it travels up the page with it and the
   NEXT SECTION paints over its lower half — measured, `.home-testimonials` was
   the topmost element at the button's bottom edge from the moment the stage
   unpinned. That is the "sound button cuts" the operator reported, and the
   right fix is not a z-index: a control for a video that has scrolled off
   screen should not be on screen either.

   ⚠️ (0,4,0) so it beats the `is-button-gone` rule above (0,3,0) on
   SPECIFICITY rather than on source order — this file has already been bitten
   by order-dependent ties. */
.demo-launcher-sticky.is-button-gone .dl-sound.is-off-screen {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.demo-launcher .dl-sound:hover {
  background: rgba(11, 14, 26, 0.82);
  border-color: rgba(255, 255, 255, 0.55);
}
.demo-launcher .dl-sound:focus-visible {
  outline: 3px solid var(--rf-primary-ring, rgba(106, 90, 205, 0.38));
  outline-offset: 2px;
}

/* Lit when sound is ON, so the state is carried by more than the label. */
.demo-launcher .dl-sound[aria-pressed='true'] {
  background: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);   /* white on #6A5ACD = 5.31:1 */
}
.demo-launcher .dl-sound[aria-pressed='true']:hover {
  background: var(--rf-primary-strong, #574AB0);
  border-color: var(--rf-primary-strong, #574AB0);
}

.demo-launcher .dl-sound-icon { display: inline-flex; }
.demo-launcher .dl-sound-icon svg { width: 17px; height: 17px; display: block; }
.demo-launcher .dl-sound-label { color: inherit; white-space: nowrap; }

@media (max-width: 560px) {
  /* The label is the thing that carries the meaning, so it is the last thing
     to go — the icon shrinks to a circle only when there is genuinely no room.
     `aria-label` on the button keeps it announced either way. */
  .demo-launcher .dl-sound { padding: 9px 12px; gap: 0; }
  .demo-launcher .dl-sound-label { display: none; }
}

/* Reduced motion: there is no zoom, so `is-button-gone` is never set and the
   control would never appear. The panel is a plain card under the copy there,
   and nothing overlaps the CTA, so it is simply always available. */
@media (prefers-reduced-motion: reduce) {
  .demo-launcher .dl-sound {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: none;
  }
}
/* Two-column feature section:
   LEFT  = sticky outline heading ("What you have now")
   RIGHT = scroll scale-stacking feature cards (vibrant). Each card pins (sticky
   h-screen) and scales down so they stack with a slight peek — driven by motion
   on NATIVE scroll, NO Lenis (so the MacBook scroll + GSAP ScrollTrigger stay
   untouched). */
.feature-stack-v2 {
  position: relative;
  background: transparent;
  /* The heading + cards are vertically centred inside 100vh sticky columns, so
     on tall viewports (e.g. 1920x1080) they sit ~half a screen down, leaving a
     big entrance gap below the stats. Pull the section up by a viewport-
     proportional amount (into the empty space above) so that gap stays modest
     and scales with resolution instead of ballooning. Desktop sticky layout
     only — reset for the mobile / reduced-motion static layouts below. */
  margin-top: clamp(-230px, -16vh, -70px);
}

/* Heading in the sticky left column, beside the cards. Left-aligned, Quicksand
   heading font, matches the site's section titles. */
.fstack-heading {
  font-family: var(--heading);
  text-align: left;
  max-width: 14ch;
  margin: 0;
  color: #fafafa;
  font-size: clamp(28px, 3.6vw, 48px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

/* Row: left heading | right cards. align-items:flex-start so the sticky left
   column keeps its own 100vh height instead of stretching to the tall row. */
.fstack-layout {
  position: relative;
  z-index: 1; /* above the background wordmark */
  display: flex;
  align-items: flex-start;
  gap: clamp(24px, 4vw, 64px);
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 clamp(16px, 4vw, 48px);
  box-sizing: border-box;
}

/* ===== Big tone-on-tone RENTFYV wordmark, parallaxing behind the cards ===== */
/* Sticky 100vh layer pinned to the viewport while the section scrolls; the
   negative margin cancels its flow height so it overlays the cards instead of
   pushing them down. The inner track is parallaxed by motion (see LandingFeatures). */
.fstack-bg {
  position: sticky;
  top: 0;
  height: 100vh;
  margin-bottom: -100vh;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
}
.fstack-bg-track {
  display: flex;
  flex-direction: column;
  align-items: center;
  will-change: transform;
}
.fstack-bg-word {
  /* Outlined letters, matching the end-of-page RENTFYV wordmark (TextHoverEffect):
     Quicksand 700, transparent fill + a thin stroke. Lighter/more visible than the
     end wordmark's faint 0.14 resting outline. */
  font-family: var(--rf-font-body, Georgia, serif);
  font-weight: 700;
  font-size: 18vw;
  line-height: 0.92;
  letter-spacing: 0;
  text-transform: uppercase;
  white-space: nowrap;
  user-select: none;
  color: transparent;
  -webkit-text-fill-color: transparent;
  -webkit-text-stroke: 2px rgba(255, 255, 255, 0.2);
}
.fstack-bg-accent { -webkit-text-stroke: 2px rgba(255, 255, 255, 0.14); } /* FYV a touch fainter */

/* Desktop sticky effect only. */
@media (max-width: 860px) {
  .fstack-bg { display: none; }
}

/* LEFT — sticky, vertically-centered heading that stays put while the cards
   scroll past on the right. ~42% of the row. */
.fstack-headcol {
  flex: 0 0 42%;
  max-width: 42%;
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  box-sizing: border-box;
}
.fstack-outline {
  /* tuned for the left column so "Everything in one screen" wraps over 2–3 lines */
  font-size: clamp(2.5rem, 4.6vw, 5.5rem);
}
/* Use the hero heading's font (Quicksand) instead of the default Archivo Black
   from .outline-text — scoped to this section only. (0,2,0) beats .outline-text. */
.feature-stack-v2 .fstack-outline {
  font-family: var(--rf-font-body, Georgia, serif);
  font-weight: 700; /* Quicksand's heaviest — the default 900 has no Quicksand cut */
}

/* RIGHT — the stacking cards column (~58%). */
.fstack-cards {
  flex: 1 1 58%;
  min-width: 0;
}

.fstack-cardwrap {
  height: 100vh;
  display: flex;
  align-items: center;
  /* flex-end (not center): the card's right edge lines up with the column's
     right padding edge, mirroring the left-aligned heading — so the left margin
     (before the heading) and the right margin (after the card) are equal. When
     centred, the narrower card floated ~70px off the right edge, making the
     right gutter look bigger than the left. */
  justify-content: flex-end;
  position: sticky;
  top: 0;
  box-sizing: border-box;
}

/* Cards are SMALLER now — they live in the right column, so cap the width and
   trim padding / type. Scale-stacking animation + per-card colours unchanged. */
.fstack-card {
  position: relative;
  width: 100%;
  max-width: 540px;
  border-radius: 22px;
  padding: clamp(20px, 2.4vw, 34px);
  color: #ffffff;
  /* Faint white hairline so the uniform dark cards still read apart when
     stacked (matches the mockup's How-it-works step cards). */
  border: 1px solid rgba(255, 255, 255, 0.08);
  /* Site font — Quicksand, same as the rest of the home page (was Inter). */
  font-family: var(--rf-font-body, Georgia, serif);
  box-shadow: 0 26px 70px rgba(0, 0, 0, 0.5);
  transform-origin: top; /* scale from the top so the cards stack downward */
  box-sizing: border-box;
}
.fstack-card * {
  box-sizing: border-box;
}

.fstack-eyebrow {
  display: block;
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #9b8cff; /* purple eyebrow, like the mockup's "STEP 01" */
  margin-bottom: 8px;
}
.fstack-title {
  font-family: inherit; /* Quicksand from .fstack-card — same as the hero heading */
  color: #ffffff;
  text-align: center;
  font-size: clamp(18px, 2vw, 28px);
  font-weight: 700;
  line-height: 1.18;
  letter-spacing: -0.02em; /* match the site's section titles */
  margin: 0 0 clamp(18px, 2.4vw, 30px);
}

.fstack-body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(16px, 2.4vw, 30px);
  align-items: center;
}
.fstack-left {
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 1.8vw, 22px);
  min-width: 0;
}
.fstack-desc {
  font-size: clamp(13px, 1vw, 15px);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.9);
  margin: 0;
}
.fstack-link {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  color: #ffffff;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  padding: 0;
}
.fstack-link svg {
  transition: transform 0.2s ease;
}
.fstack-link:hover svg {
  transform: translateX(4px);
}

/* The visual slot: rounded, overflow-hidden so the 2→1 scaling visual is clipped. */
.fstack-visual {
  border-radius: 14px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.22);
  aspect-ratio: 480 / 300;
  min-width: 0;
}
.fstack-visual-inner {
  width: 100%;
  height: 100%;
  transform-origin: center center;
}
.fstack-visual-svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Reduced-motion: single column — heading on top, cards in normal flow (no
   h-screen sticky, no scale). The entrance-gap pull-up only applies to the
   desktop sticky/centred layout, so cancel it here. */
.feature-stack-v2--static { margin-top: 0; }
.feature-stack-v2--static .fstack-layout {
  flex-direction: column;
  align-items: stretch;
}
.feature-stack-v2--static .fstack-headcol {
  position: static;
  height: auto;
  flex-basis: auto;
  max-width: none;
  padding: 6vh 0 3vh;
}
.feature-stack-v2--static .fstack-cardwrap {
  height: auto;
  position: static;
  margin-bottom: clamp(24px, 4vw, 48px);
}
.feature-stack-v2--static .fstack-card {
  max-width: none;
}

/* Mobile / narrow: single column — outline heading on top, cards full width
   below. No horizontal scroll. */
@media (max-width: 860px) {
  .feature-stack-v2 { margin-top: 0; } /* mobile stacks in normal flow — no pull-up */
  .fstack-layout {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }
  .fstack-headcol {
    position: static;
    height: auto;
    flex-basis: auto;
    max-width: none;
    justify-content: flex-start;
    padding: 8vh 0 2vh;
  }
  .fstack-outline {
    font-size: clamp(2.25rem, 11vw, 3.5rem);
  }
  .fstack-cards {
    flex-basis: auto;
    width: 100%;
  }
  .fstack-card {
    max-width: none;
  }
  .fstack-body {
    grid-template-columns: 1fr;
    gap: clamp(16px, 5vw, 24px);
  }
  .fstack-cardwrap {
    height: auto;
    min-height: 100vh;
    padding: 6vh 0;
  }
  .fstack-title {
    font-size: clamp(20px, 6vw, 28px);
    margin-bottom: 18px;
  }
}

/* ===== Large-screen widening (≥1600px) =====
   Match the rest of the page's wide-screen treatment: on 1080p+ monitors let
   the two-column row grow wider so it fills the screen instead of sitting in a
   ~1240px column with huge gutters. The cards grow with it so the widening
   doesn't open a big empty gap between the heading and the (right-aligned)
   card. Symmetric left/right margins hold at every width. Scoped to min-width,
   so smaller layouts are untouched. */
@media (min-width: 1600px) {
  .fstack-layout { max-width: 1360px; }
  .fstack-card { max-width: 600px; }
}
@media (min-width: 1900px) {
  .fstack-layout { max-width: 1460px; }
  .fstack-card { max-width: 640px; }
}

/* ---- light theme (redesign — session 27) --------------------------------
   ⚠️ The card GROUND is an inline style written from LandingFeatures.jsx
   (`style={{ backgroundColor: card.color }}`), so no CSS rule here can win
   against it — inline beats every selector short of !important. The colour
   was changed at source in that file; these rules only handle everything
   drawn ON it, which was all tuned for a near-black card. */

/* The section heading is a literal `#fafafa` above. It renders dark today only
   because `.rf-theme h2` (0,1,1) out-specifies `.fstack-heading` (0,1,0) — an
   accident, not a decision. State it, so a change to that base rule cannot
   silently turn the heading white again. */
.fstack-heading { color: var(--rf-ink, #000000); }

.fstack-card {
  color: var(--rf-ink-body, #333333);
  font-family: var(--rf-font-body, 'Open Sans', Roboto, system-ui, sans-serif);

  /* ⚠️ ELEVATION IS THE ONLY THING THAT SEPARATES THESE CARDS FROM ANYTHING.
     They are #FFFFFF on a #F5F5F5 band — 1.05:1 — and each card covers the
     one below it except for a ~22px peek strip, so the whole stacking device
     lives or dies on that edge. Measured before this: the seam between two
     stacked cards was a 1px --rf-border hairline at 1.13:1 sitting on a
     1.05:1 ground, i.e. nothing.
     The border draws the edge on ALL FOUR sides, which a drop shadow cannot —
     shadows fall downward and the peek strips sit ABOVE the front card. The
     two shadow layers then seat it (near) and lift it (far). */
  border: 1px solid var(--rf-border-strong, #D4D4D8);   /* 1.48:1 on #FFFFFF */
  box-shadow:
    0 1px 2px rgba(16, 16, 20, 0.05),
    0 10px 24px -6px rgba(16, 16, 20, 0.12),
    0 32px 64px -24px rgba(16, 16, 20, 0.22);
}

/* ONE optical axis per card. The eyebrow and title were CENTRED over a
   left-aligned paragraph, a left-aligned link and a right-hand visual — four
   different axes in a 540px box. The dark original could carry that because
   the card was a solid object on a dark page; a white card on a near-white
   band has no ground holding it together, so the same layout reads as
   unresolved rather than as a deliberate centred header. */
.fstack-eyebrow,
.fstack-title { text-align: left; }

.fstack-title {
  font-family: var(--rf-font-heading, 'Poppins', Montserrat, system-ui, sans-serif);
  color: var(--rf-ink, #000000);
}

/* 13px (the clamp floor at every width ≤1250px) was the smallest text on the
   page, sitting under a 28px title — the card's own argument read as a
   footnote to its headline. */
.fstack-desc {
  color: var(--rf-ink-body, #333333);
  font-size: clamp(14px, 1.2vw, 15px);
  line-height: 1.6;
}

/* The two body columns were an even 1fr 1fr, which at the card's 540px cap left
   the paragraph a 223px measure — ~29 characters, so it broke mid-word
   ("month-|end spreadsheet") and stacked seven short ragged lines beside a
   squat image. Raising the type made that worse, not better. Weighting the
   split toward the copy takes the measure to ~33 characters and evens the two
   blocks' heights; the visual keeps its 480/300 ratio either way.

   ⚠️ MUST BE IN A min-width QUERY. The mobile `@media (max-width: 860px)` block
   that collapses this grid to ONE column sits EARLIER in this file and is the
   same (0,1,0) specificity — a media query adds none — so an unscoped rule here
   wins at every width and puts two columns back on a 375px phone. */
@media (min-width: 861px) {
  .fstack-body { grid-template-columns: 1.25fr 1fr; }
}

/* #9B8CFF measured 2.1:1 on white. The brand proper clears AA at this size
   (11px/700 → floor 4.5) and is the only colour on an otherwise black-on-white
   card, so it does the work of marking each card's subject. */
.fstack-eyebrow { color: var(--rf-primary, #6A5ACD); }   /* 5.31:1 on #FFFFFF */

/* The mockups ARE the product — a picture of the dark AdminPanel — so they stay
   dark (§CB4). What changed is that they now sit on WHITE, where a bare
   near-black rectangle reads as a hole punched in the card rather than as a
   screen on it. Same correction §CB18 made on the demo panel: seat it.
   The old rgba(0,0,0,.22) slot was a grey wash that only ever showed as a halo
   around the artwork's own edge. */
.fstack-visual {
  background: var(--rf-night, #0B0E1A);
  box-shadow: 0 10px 24px -8px rgba(11, 14, 26, 0.45);
}

/* ⚠️ A WHITE STROKE ON A #F5F5F5 BAND. The big parallax RENTFYV wordmark behind
   the cards was drawing in a colour LIGHTER than its own ground, so the whole
   decorative layer showed only as a ghost. Tone-on-tone means darker here.
   ⚠️ AND THE DARK THEME'S ALPHA DOES NOT PORT ACROSS. Reusing its 0.20 as
   0.10 ink read as a wireframe pattern competing with the heading — dark ink
   on light is perceptually far heavier than light ink on dark at the same
   alpha. 0.05 then went too far the other way and the operator called it dull.
   Settled at 0.09 / 0.14: clearly present as a brand watermark, still well
   under the 0.10+ that started competing with the headline.

   Safe to raise further if wanted — it is decoration behind OPAQUE cards, and
   the only live text over it is the section heading, which is #000000 on the
   composited stroke (#E5E5E6 at this alpha) for 15.5:1. Nothing here can fail
   contrast; the limit is taste, not legibility. */
.fstack-bg-word { -webkit-text-stroke: 2px rgba(11, 14, 26, 0.09); }
.fstack-bg-accent { -webkit-text-stroke: 2px rgba(106, 90, 205, 0.14); }

/* The card's link was white-on-white once the ground flipped (1:1). */
.fstack-link,
.fstack-link * {
  color: var(--rf-primary-deep, #453A8C);   /* 7.4:1 on #FFFFFF */
  border-color: var(--rf-border-strong, #D4D4D8);
  background: transparent;
}
.fstack-link:hover,
.fstack-link:hover * {
  color: var(--rf-primary, #6A5ACD);
  border-color: var(--rf-primary, #6A5ACD);
}
/* SmokeBackground — fills its position:relative parent, sits below the cards. */
.smoke-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.smoke-bg-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* ---- CSS fallback (no WebGL): navy blurred blobs, vignette-masked ----
   ⚠️ These four values MIRROR the shader's ramp (see SmokeBackground.jsx). They
   were neutral greys and are now the site's night steps, for the same reason:
   `mix-blend-mode: screen` is additive, so a neutral grey lifts all three
   channels equally and greys the navy ground out from under the deck. */
.smoke-fallback {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* Same centre-dense / edge-fade vignette as the shader vignette. */
  -webkit-mask-image: radial-gradient(ellipse 72% 72% at 50% 50%, #000 30%, transparent 78%);
          mask-image: radial-gradient(ellipse 72% 72% at 50% 50%, #000 30%, transparent 78%);
}
.smoke-fallback i {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  mix-blend-mode: screen;     /* additive navy haze on the night-navy deck */
  will-change: transform;
}
.smoke-fallback i:nth-child(1) { width: 60%; height: 80%; left: 20%; top: 8%;  opacity: 0.45; background: radial-gradient(circle, #1B2233, transparent 70%); animation: smkA 34s ease-in-out infinite; }
.smoke-fallback i:nth-child(2) { width: 52%; height: 62%; left: 8%;  top: 30%; opacity: 0.26; background: radial-gradient(circle, #6B7290, transparent 70%); animation: smkB 40s ease-in-out infinite; }
.smoke-fallback i:nth-child(3) { width: 56%; height: 66%; left: 36%; top: 28%; opacity: 0.42; background: radial-gradient(circle, #0E121D, transparent 70%); animation: smkC 30s ease-in-out infinite; }
.smoke-fallback i:nth-child(4) { width: 46%; height: 56%; left: 30%; top: 16%; opacity: 0.30; background: radial-gradient(circle, #2A3247, transparent 70%); animation: smkD 38s ease-in-out infinite; }

@keyframes smkA { 0%, 100% { transform: translate(0, 0) scale(1); }     50% { transform: translate(6%, -5%) scale(1.12); } }
@keyframes smkB { 0%, 100% { transform: translate(0, 0) scale(1); }     50% { transform: translate(-7%, 4%) scale(1.15); } }
@keyframes smkC { 0%, 100% { transform: translate(0, 0) scale(1.05); }  50% { transform: translate(5%, 6%) scale(0.95); } }
@keyframes smkD { 0%, 100% { transform: translate(0, 0) scale(1); }     50% { transform: translate(-4%, -6%) scale(1.1); } }

@media (prefers-reduced-motion: reduce) {
  .smoke-fallback i { animation: none; }
}
/* "Cancel the stack" deck — Radiance-style single-card stack, SCROLL-DRIVEN
   (see Coverflow.jsx for the reference breakdown and the pin note). The
   section pins at 'top top', so EVERYTHING here is sized to fit one viewport
   frame: the card is vh-clamped, the display text is container-sized, and the
   header is tight. Section-level classes (cf-deck, cf-h2, cf-lede,
   cf-eyebrow, cf-dia) carry over from the old coverflow; everything
   deck-specific is cfr-*. The arrow buttons are gone — scroll advances the
   deck. */
/* ⚠️⚠️ THE PALETTE IS THE SITE'S NIGHT RAMP, NOT A LOCAL ONE (operator-reported:
   "color scheme isn't according to our color scheme"). What was here — a pure
   #0a0a0b ground, a #8b5cf6 stray purple and a #e0607f rose — belonged to no
   page on this site. Three rules now govern every value below:

     1. THE GROUND IS `--rf-night`, the same navy the footer, the closing CTA
        card and the FAQ answer panel already use. Pure black is reserved for
        the video bands (`.demo-launcher`), where footage is the surface.
     2. THE ACCENT IS THE BRAND, AT THE ON-DARK STEP. `--rf-primary` (#6A5ACD)
        is only 3.62:1 on this ground — fine for a FILL or an icon, UNDER the
        4.5 floor for an 11px eyebrow. `--rf-primary-on-dark` (#8B7BE0) is the
        primary lightened until it clears AA there and no further: 5.50:1 on
        #0B0E1A, 5.39:1 on #111114. Same two-step ramp §CZ1 records for /demo.
        ⚠️ Do NOT use #8B7BE0 on the LIGHT fix card — it measures 3.0:1 on
        white. That card takes `--rf-primary` proper (5.08:1 on #fafafa).
     3. ONE SEMANTIC COLOUR SURVIVES, AND IT IS THE COST. Red on the "old way"
        cards is doing real work (six bills, one painful total), so it stays —
        but as #FF6B6B, the value this page already uses to strike out a
        replaced tool, not a rose that appears nowhere else. 5.72:1 on the card.
        The GREEN is gone: "the fix" is our product, so it wears the brand, and
        the fix card is already the deck's only white surface — it does not
        need a second signal. */
.cf-deck {
  --cf-bg: var(--rf-night, #0B0E1A);
  --cf-ink: #f4f4f5;
  /* Cool slate-grey, not neutral: a neutral grey on a navy ground reads dirty.
     6.24:1 on the band · 5.15:1 on the card. */
  --cf-muted: #8A93A8;
  --cf-muted-2: #5A6178;
  --cf-card: #1B2233;            /* --rf-night-2 — the card lifts off the band */
  --cf-card-2: #141A28;
  --cf-line: rgba(255, 255, 255, 0.10);
  --cf-line-2: rgba(255, 255, 255, 0.06);
  --cf-accent: var(--rf-primary-on-dark, #8B7BE0);   /* 5.50:1 on the band */
  --cf-accent-ink: var(--rf-primary, #6A5ACD);       /* the LIGHT card's step */
  --cf-cost: #FF6B6B;                                /* 5.72:1 on the card */
  --cf-ease: cubic-bezier(0.16, 1, 0.3, 1);

  position: relative;
  /* Pinned at 'top top' → the whole deck must fit ONE viewport. height (not
     min-height): letting content grow the section past 100vh is exactly the
     doesn't-fit-the-frame bug this layout exists to prevent. */
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(24px, 4vh, 44px) 0 clamp(18px, 3vh, 32px);
  box-sizing: border-box;
  /* Opaque so the section above never bleeds through while pinned.
     ⚠️ TWO CONSUMERS, ONE VALUE: `.home-page .home-deck-lip` in Landing.css
     paints the SAME colour, because the wrapper is what shows through the
     rounded lip while the deck is pinned (fixed elements escape the wrapper's
     clip). Change one → change both, or the curve reveals a different dark. */
  background: var(--rf-night, #0B0E1A);
  color: var(--cf-ink);
  font-family: var(--rf-font-body, Georgia, serif);
}
.cf-deck::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 52%;
  width: 1000px;
  height: 640px;
  transform: translate(-50%, -50%);
  /* Brand bloom rather than a neutral grey wash — the same thing the hero and
     the closing CTA do to keep a dark surface from reading as a flat hole. */
  background: radial-gradient(ellipse, rgba(106, 90, 205, 0.10), transparent 60%);
  pointer-events: none;
  z-index: 0;
}

/* Keep all real content above the smoke canvas. */
.cf-deck > .cf-eyebrow-top,
.cf-deck > .cf-h2,
.cf-deck > .cf-lede,
.cf-deck > .cfr-stage,
.cf-deck > .cfr-count { position: relative; z-index: 1; }

.cf-eyebrow-top { display: inline-flex; align-items: center; gap: 8px; color: var(--cf-accent); font-weight: 700; font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; }
.cf-dia { width: 6px; height: 6px; background: currentColor; transform: rotate(45deg); display: inline-block; flex: none; }
/* Header, tightened for the one-frame budget (the display text beside the
   card is the section's real headline now — this one just introduces it). */
/* ⚠️⚠️ `.cf-deck` IS LOAD-BEARING ON BOTH OF THESE, AND WITHOUT IT THE HEADING
   RENDERED PURE BLACK ON THE BAND — 1.02:1, i.e. the section's own title was
   invisible. This was PRE-EXISTING and found by measuring, not by reading:
   `.home-page h1, .home-page h2, .home-page h3 { color: var(--rf-ink) }` in
   Landing.css is **(0,1,1)** and a bare `.cf-h2` is **(0,1,0)**, so the page
   rule wins on specificity no matter what order the files load in. Blog.css,
   About.css and Features.css all record the same trap in their headers; the
   deck was the surface nobody had gone back for.
   `.cf-deck .cf-h2` is (0,2,0) and clears it. Do not "simplify" it back.
   The `.cf-dim` half takes the prefix for a different reason — see `.cfr-dim`
   below: theme.css's `.home-page [class*="-dim"]` is (0,2,0) and would only
   have lost to it on load order. */
.cf-deck .cf-h2 { margin: 10px 0 0; font-weight: 700; letter-spacing: -0.03em; font-size: clamp(24px, 3vw, 38px); text-align: center; line-height: 1.06; color: var(--cf-ink); padding: 0 18px; }
.cf-deck .cf-h2 .cf-dim { color: var(--cf-muted); }
.cf-lede { margin: 8px auto 0; max-width: 52ch; text-align: center; color: var(--cf-muted); font-weight: 500; font-size: clamp(13px, 1.3vw, 15px); line-height: 1.45; padding: 0 18px; }

/* ── Stage: display text | card stack | display text ── */
.cfr-stage {
  position: relative;
  width: 100%;
  max-width: 1240px;
  margin-top: clamp(18px, 3.2vh, 34px);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: clamp(20px, 3.5vw, 56px);
  padding: 0 clamp(20px, 3.5vw, 48px);
  box-sizing: border-box;
}

/* ── Side display text (the reference's big flanking words) ──
   One absolutely-stacked group per card; GSAP shows the active one. The
   container reserves the band height so the grid never reflows. */
.cfr-side {
  position: relative;
  /* The container: the columns are 1fr tracks beside a FIXED-width card, so a
     viewport-unit font overflows them at mid widths — the type is sized in cqw
     on the CHILD groups instead (an element's own style cannot query its own
     size; cqw on the container itself silently falls back to the viewport).
     Height is therefore viewport-derived, decoupled from the font. */
  container-type: inline-size;
  height: clamp(96px, 20vh, 150px); /* tag line + two display lines */
  min-width: 0;
}
.cfr-side--l { text-align: right; }
.cfr-side--r { text-align: left; }
.cfr-tgroup {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* 14.5cqw ≈ the widest line ("dashboard.", measured 6.17ch-em) at ~90% of
     the column width, so the display text scales with its track, not the
     viewport. Capped at 44px — the operator flagged the type as oversized. */
  font-size: clamp(22px, 14.5cqw, 44px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.12;
  text-transform: uppercase;
}
/* Each line is its own mask — the slide-up swap clips at the line box. */
.cfr-line { display: block; overflow: hidden; }
.cfr-line-in { display: block; will-change: transform; }
/* The small annotation line above the right-hand text (reference: the year). */
.cfr-line--tag {
  font-size: clamp(10px, 1vw, 12px);
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--cf-muted);
  line-height: 1.7;
  margin-bottom: 0.25em;
}

/* ── The card stack — small, per the reference's proportions ── */
.cfr-cardbox {
  position: relative;
  width: min(300px, 80vw);
  height: clamp(300px, 52vh, 396px);
}
.cfr-card {
  position: absolute;
  inset: 0;
  border-radius: 18px;
  border: 1px solid var(--cf-line);
  background: linear-gradient(180deg, var(--cf-card), var(--cf-card-2));
  box-shadow: 0 32px 64px -32px rgba(0, 0, 0, 0.85);
  overflow: hidden;
  padding: 18px 18px 16px;
  display: flex;
  flex-direction: column;
  will-change: transform, opacity;
  /* ⚠️ The repo-wide inherited-line-height hazard (see CLAUDE.md): an ancestor
     supplies ~26.1px line boxes, so every 11-12px line in here rendered 26px
     tall and the card content outgrew its vh-clamped box (the clipped "$100").
     An explicit line-height makes the content height actually follow the font
     sizes below. */
  line-height: 1.45;
}
/* The fix card is the deck's one LIGHT surface. */
.cfr-card--light {
  background: linear-gradient(180deg, #fafafa, #ececef);
  border-color: rgba(0, 0, 0, 0.08);
  color: #0a0a0b;
  box-shadow: 0 32px 64px -28px rgba(0, 0, 0, 0.9);
}

.cf-eyebrow { display: inline-flex; align-items: center; gap: 7px; font-weight: 700; font-size: 10px; letter-spacing: 0.13em; text-transform: uppercase; color: var(--cf-accent); flex: none; align-self: flex-start; }
.cf-eyebrow.bad { color: var(--cf-cost); }
/* "The fix" is our product, so it wears the brand — the green that used to be
   here was the deck's third off-palette hue. Restated on the light card below,
   where the on-dark step would fail. */
.cf-eyebrow.good { color: var(--cf-accent); }
.cfr-bad { color: var(--cf-cost); }
/* ⚠️⚠️ DOUBLED CLASS, DELIBERATELY — this rendered #333333 on the navy card
   (~1.2:1, the word "incl." was simply not there). Another PRE-EXISTING
   specificity loss found by measuring: theme.css's legacy bridge
   `.home-page [class*="-dim"] { color: var(--rf-ink-body) }` is **(0,2,0)** and
   matches this class by substring. A bare `.cfr-dim` is (0,1,0) and loses
   outright; `.cf-deck .cfr-dim` would only TIE and win on load order, which is
   not a guarantee worth resting a colour on. (0,3,0) wins regardless.
   ⚠️ Any new `-dim` / `-muted` / `-stars` / `-success` / `-danger` / `-error`
   class added inside this deck walks into the same bridge. */
.cf-deck .cfr-dim.cfr-dim { color: var(--cf-muted); }

/* Card 1 — tool list + total. */
.cfr-tools { list-style: none; margin: 10px 0 0; padding: 0; }
.cfr-tools li { display: flex; justify-content: space-between; align-items: center; padding: 5px 0; border-bottom: 1px solid var(--cf-line-2); font-weight: 600; font-size: 12px; }
.cfr-tools li:last-child { border-bottom: 0; }
.cfr-total { margin-top: auto; padding-top: 10px; border-top: 1px solid var(--cf-line); }
.cfr-big { font-size: 30px; font-weight: 700; letter-spacing: -0.03em; line-height: 1; }
.cfr-big em { font-style: normal; color: var(--cf-cost); }
.cfr-big small { font-size: 14px; color: var(--cf-muted); font-weight: 600; }
.cfr-total > span { display: block; color: var(--cf-muted); font-size: 11px; font-weight: 600; margin-top: 4px; }

/* Card 2 — the midnight inbox. */
.cfr-winbar { display: flex; align-items: center; gap: 5px; margin: 10px 0 8px; padding-bottom: 8px; border-bottom: 1px solid var(--cf-line-2); }
.cfr-winbar i { width: 8px; height: 8px; border-radius: 50%; flex: none; }
/* ⚠️ WINDOW CHROME, NOT PALETTE — the authentic macOS traffic lights, and they
   are literals on purpose. These three dots are DEPICTING an application
   window; pointing them at brand tokens would make the illustration read as UI
   the operator can act on. They are 8px and decorative (no text, no control), so
   they owe no contrast ratio. This is also why the deck no longer has a green
   token: the only green left on the page is this dot, and it is a picture. */
.cfr-winbar i:nth-child(1) { background: #ff5f57; }
.cfr-winbar i:nth-child(2) { background: #febc2e; }
.cfr-winbar i:nth-child(3) { background: #28c840; }
.cfr-winbar span { margin-left: 5px; font-size: 10px; color: var(--cf-muted); font-weight: 600; }
.cfr-bubbles { display: grid; gap: 6px; align-content: start; min-height: 0; }
.cfr-bubble { border: 1px solid var(--cf-line-2); border-radius: 9px; padding: 7px 9px; font-size: 11px; color: #D5DBE8; font-weight: 500; background: rgba(255, 255, 255, 0.02); line-height: 1.3; }
.cfr-bubble > span { font-size: 8.5px; color: var(--cf-muted); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; display: block; margin-bottom: 2px; }
/* The one overdue thread — same cost red as the prices, at a border's weight. */
.cfr-bubble.late { border-color: rgba(255, 107, 107, 0.35); }

/* Card 3 — the fix (light theme inside). */
/* ⚠️ THE ACCENT DROPS A STEP HERE, AND IT HAS TO. `--cf-accent` is the on-dark
   #8B7BE0, which measures 3.0:1 on this white face — the exact inverse of why
   the band needs it. `--rf-primary` proper is 5.08:1 on #fafafa. Same rule the
   navy/white tier cards on /pricing follow: the step follows the GROUND. */
.cfr-card--light .cf-eyebrow.good { color: var(--cf-accent-ink, #6A5ACD); }
.cfr-lead { margin: 10px 0 0; font-size: 12px; line-height: 1.45; font-weight: 600; color: #3f3f46; }
.cfr-wins { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 9px; }
.cfr-wins li { display: flex; gap: 9px; align-items: flex-start; font-weight: 600; font-size: 12px; line-height: 1.35; color: #18181b; }
/* The brand tick on its brand-soft chip — byte-identical to the treatment every
   tier card on /pricing uses, so the same mark means the same thing sitewide.
   Glyph 4.55:1 on the chip. */
.cfr-ic { flex: none; width: 19px; height: 19px; border-radius: 6px; display: grid; place-items: center; margin-top: 1px; background: var(--rf-primary-soft, #EEECFA); color: var(--cf-accent-ink, #6A5ACD); }
.cfr-wm { margin-top: auto; font-size: 12px; font-weight: 700; letter-spacing: 0.02em; color: #0a0a0b; }
/* ⚠️ WAS #8b8b93 — 2.87:1 at 12px bold, a real pre-existing AA failure, and on
   the one word that is the brand name. The nav wordmark's accent half is
   `--rf-primary`, but that measures 4.50:1 down here (this is the #ececef END
   of the card's gradient, and `.cfr-wm` is `margin-top: auto` so it always sits
   there). The deep step clears it with room: 7.96:1. */
.cfr-wm b { color: var(--rf-primary-deep, #453A8C); }

/* ── Bottom-centre index (the reference's "01 / 06") ── */
.cfr-count {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-top: clamp(14px, 2.6vh, 26px);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--cf-ink);
  font-variant-numeric: tabular-nums;
}
.cfr-mask { display: block; overflow: hidden; line-height: 1.25; }
.cfr-num { display: block; will-change: transform; }
.cfr-den { color: var(--cf-muted); }

/* ── Short viewports: compact the card innards WITH the card ──
   The card is vh-clamped but its content was fixed-size, so on windows under
   ~760px of viewport height the card shrank below its own content and clipped
   the "$100" total (operator screenshot). Below that line, everything inside
   steps down one size so the tallest card (~270px compact) always fits the
   50vh card. 760 is measured, not round: the base content needs 372px and
   52vh only reaches that at 716px + the cap margin. */
@media (max-height: 760px) {
  /* ⚠️ `.cf-deck` HERE TOO, AND NOT FOR CASCADE REASONS — FOR ARITHMETIC. The
     base rule had to go to (0,2,0) to beat `.home-page h2` (see above), and a
     media query does NOT add specificity: a bare `.cf-h2` in here is still
     (0,1,0) and would lose to the base rule despite coming later, silently
     reverting the short-viewport step-down that keeps the deck inside one
     frame. If the base selector changes again, change this one with it. */
  .cf-deck .cf-h2 { font-size: clamp(22px, 2.6vw, 32px); }
  .cf-lede { font-size: 12.5px; max-width: 56ch; }
  .cfr-stage { margin-top: 12px; }
  .cfr-cardbox { height: clamp(280px, 50vh, 340px); }
  .cfr-card { padding: 14px 16px 13px; }
  .cfr-tools { margin-top: 7px; }
  .cfr-tools li { padding: 4px 0; font-size: 11.5px; }
  .cfr-total { padding-top: 8px; }
  .cfr-big { font-size: 24px; }
  .cfr-big small { font-size: 12px; }
  .cfr-total > span { font-size: 10px; margin-top: 3px; }
  .cfr-winbar { margin: 8px 0 6px; padding-bottom: 6px; }
  .cfr-bubbles { gap: 5px; }
  .cfr-bubble { padding: 5px 8px; font-size: 10.5px; }
  .cfr-lead { font-size: 11px; margin-top: 8px; }
  .cfr-wins { gap: 7px; margin-top: 9px; }
  .cfr-wins li { font-size: 11px; }
  .cfr-ic { width: 16px; height: 16px; border-radius: 5px; }
  .cfr-side { height: clamp(80px, 17vh, 120px); }
  .cfr-tgroup { font-size: clamp(20px, 12.5cqw, 36px); }
  .cfr-count { margin-top: clamp(8px, 1.8vh, 16px); }
}

/* ── Narrow: stack the display text above/below the card ── */
@media (max-width: 980px) {
  .cfr-stage {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: 12px;
    padding: 0 16px;
  }
  .cfr-side { width: 100%; height: clamp(66px, 12vh, 96px); }
  .cfr-tgroup { font-size: clamp(20px, 8.5cqw, 32px); }
  .cfr-side--l { text-align: center; order: 0; }
  .cfr-cardbox { order: 1; height: clamp(280px, 42vh, 360px); }
  .cfr-side--r { text-align: center; order: 2; }
  .cfr-tools li { padding: 4.5px 0; } /* the shorter mobile card: 6 rows − 6px */
}

/* Short PHONES (stacked layout + little height): the three stacked rows plus
   the header ran ~9px past the frame at 375x667 — reclaim it from chrome
   (padding, gaps, side bands), never from the card. */
@media (max-width: 980px) and (max-height: 700px) {
  .cf-deck { padding: 16px 0 12px; }
  .cfr-stage { gap: 8px; margin-top: 10px; }
  .cfr-side { height: clamp(58px, 10.5vh, 80px); }
  .cfr-count { margin-top: 8px; }
}


/* The contact section. ⚠️ The white/black values in this base block are the
   ORIGINAL light palette and are all overridden by the SESSION 68 block at
   the END of the file — the panel sits on the shared hero ground (violet)
   now. Edit colours THERE, not here; geometry and type still live here.
   Uses Inter (overriding the site's Quicksand) to match the reference. */

.lets-connect {
  background: #ffffff;
  color: #000000;
  font-family: var(--rf-font-body, Georgia, serif);
  width: 100%;
  box-sizing: border-box;
  padding: clamp(56px, 9vw, 130px) clamp(20px, 6vw, 80px);
  /* Rounded white panel — TOP corners only; the bottom edge sits straight (flush
     to the footer) with no rounded notch. */
  border-radius: clamp(28px, 4vw, 48px) clamp(28px, 4vw, 48px) 0 0;
}

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

.lets-connect-inner {
  max-width: 1080px;
  margin: 0 auto;
}

.lc-heading {
  font-family: inherit; /* override the global `h2 { font-family: var(--heading) }` */
  color: #000; /* override the global `h2 { color: var(--text-h) }` → pure black on white */
  font-size: clamp(52px, 11vw, 140px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 0.95;
  margin: 0 0 clamp(36px, 6vw, 72px);
}

.lc-form {
  display: flex;
  flex-direction: column;
  gap: clamp(22px, 3vw, 34px);
}

.lc-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.lc-field label {
  font-size: 15px;
  font-weight: 600;
  color: #000;
}

.lc-field input {
  font-family: inherit;
  font-size: clamp(16px, 2vw, 20px);
  color: #000;
  background: transparent;
  border: none;
  border-bottom: 2px solid #000;
  padding: 10px 0;
  width: 100%;
  outline: none;
}

.lc-field input::placeholder {
  color: rgba(0, 0, 0, 0.38);
}

.lc-fleet {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 6px;
}

.lc-fleet-label {
  font-size: clamp(17px, 2.2vw, 22px);
  font-weight: 700;
  color: #000;
}

.lc-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.lc-pill {
  font-family: inherit;
  font-size: 16px;
  font-weight: 600;
  color: #000;
  background: #fff;
  border: 1.5px solid #000;
  border-radius: 999px;
  padding: 11px 30px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.lc-pill:hover:not(.is-selected) {
  background: rgba(0, 0, 0, 0.06);
}

.lc-pill.is-selected {
  background: #000;
  color: #fff;
}

/* Per-field validation message — sits inside its .lc-field, under the input
   (the Signup `su-err` pattern; the old single `.lc-error` summary is gone). */

.lc-field-err {
  color: #b00020;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.4;
  margin: 0;
}

/* Liquid-glass Submit button. SIZE/SHAPE unchanged from the original (width,
   padding, border-radius, font) — only the surface is restyled. The ring is an
   inset box-shadow (not a real border) so the box stays pixel-identical. */

.lc-submit {
  font-family: inherit;
  width: 100%;
  margin-top: clamp(14px, 2.5vw, 24px);
  font-size: clamp(18px, 2.2vw, 22px);
  font-weight: 700;
  color: #000; /* black text */
  border: 1.5px solid #000; /* black border (border-box → outer size unchanged) */
  border-radius: 14px;
  padding: clamp(18px, 2.4vw, 26px);
  cursor: pointer;
  position: relative;
  overflow: hidden; /* keep the glass surface clipped to the 14px radius */
  isolation: isolate;
  /* White frosted glass base + top sheen. */
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.85) 0%, rgba(255, 255, 255, 0.55) 100%),
    rgba(255, 255, 255, 0.7);
  /* Liquid refraction (Chromium, when content sits behind) + frost; degrades to
     the white base + sheen everywhere else. */
  -webkit-backdrop-filter: blur(3px) saturate(1.1) url(#lc-liquid-glass);
  backdrop-filter: blur(3px) saturate(1.1) url(#lc-liquid-glass);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9), /* top edge highlight */
    inset 0 -3px 8px rgba(0, 0, 0, 0.06), /* soft bottom inner shadow */
    0 12px 30px -14px rgba(0, 0, 0, 0.35); /* soft drop shadow */
  transition: box-shadow 0.2s ease, transform 0.12s ease, color 0.28s ease;
}

/* Black circular fill on hover — same effect as the Login button, inverted:
   a black circle scales up from the centre and the label turns white. */

.lc-submit::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #000;
  transform: translate(-50%, -50%) scale(0);
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: -1;
  pointer-events: none;
}

.lc-submit:hover {
  color: #fff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 1),
    inset 0 -3px 8px rgba(0, 0, 0, 0.08),
    0 16px 38px -14px rgba(0, 0, 0, 0.4);
}

.lc-submit:hover::before {
  transform: translate(-50%, -50%) scale(1);
}

.lc-submit:active {
  transform: translateY(1px);
}

@media (prefers-reduced-motion: reduce) {
  .lc-submit::before { transition: none; }
}

@media (max-width: 600px) {
  .lc-pill {
    padding: 10px 22px;
  }
}

/* ============================================================================
 * Sticky stacking panels — scoped ENTIRELY under .lets-connect-page so nothing
 * global or on other pages is affected. Pure CSS position:sticky (no JS): each
 * panel pins at the top of the viewport and the NEXT panel (later in the DOM)
 * slides up over it. The page root is near-black, so it shows through each
 * panel's rounded TOP corners as the incoming panel rises = the stacked-card
 * reveal. Tweak: panel height = .lcp-panel min-height; corner size = the
 * border-radius on .lcp-panel--dark (dark panels) / .lets-connect (form panel).
 * ========================================================================== */

.lets-connect-page {
  position: relative;
  font-family: var(--rf-font-body, Georgia, serif);
}

.lets-connect-page .lcp-panel {
  position: sticky;
  top: 0;
  min-height: 100vh;
  box-sizing: border-box;
  overflow: hidden; /* clip each panel's fill to its rounded top corners */
}

/* The dark statement panel (the intro — the only dark panel since session 68). */

.lets-connect-page .lcp-panel--dark {
  background: #0b0b0d;
  color: #ffffff;
  border-radius: clamp(24px, 4vw, 44px) clamp(24px, 4vw, 44px) 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: clamp(48px, 9vw, 120px) clamp(20px, 6vw, 80px);
}

.lets-connect-page .lcp-inner { max-width: 900px; margin: 0 auto; }

.lets-connect-page .lcp-eyebrow {
  margin: 0 0 clamp(18px, 3vw, 30px);
  font-size: clamp(12px, 1.3vw, 15px);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

.lets-connect-page .lcp-statement {
  margin: 0;
  font-size: clamp(46px, 10vw, 128px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 0.95;
  color: #ffffff;
}

.lets-connect-page .lcp-sub {
  margin: clamp(20px, 3vw, 32px) auto 0;
  max-width: 620px;
  font-size: clamp(16px, 2vw, 22px);
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.68);
}

.lets-connect-page .lcp-scroll-hint {
  display: inline-block;
  margin-top: clamp(28px, 5vw, 52px);
  font-size: 12.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
}

/* The form panel is the existing .lets-connect (white bg + its own rounded top).
   As a 100vh sticky panel it must fit ONE viewport so the Submit stays visible —
   so we shrink the oversized heading + tighten the vertical rhythm (vh-based, so
   it also holds up on short screens), and allow the panel to scroll internally as
   a safety net so the Submit is ALWAYS reachable no matter the viewport height. */

.lets-connect-page .lcp-panel--form {
  overflow-y: auto;
}

.lets-connect-page .lcp-panel--form.lets-connect {
  padding-top: clamp(28px, 5vh, 90px);
  padding-bottom: clamp(28px, 5vh, 90px);
}

.lets-connect-page .lcp-panel--form .lets-connect-inner { margin: auto; width: 100%; }

.lets-connect-page .lcp-panel--form .lc-heading {
  font-size: clamp(30px, 5vw, 68px);       /* was clamp(52px, 11vw, 140px) — too tall for a form panel */
  margin-bottom: clamp(18px, 3vh, 36px);
}

.lets-connect-page .lcp-panel--form .lc-form {
  gap: clamp(15px, 2.2vh, 26px);
}

.lets-connect-page .lcp-panel--form .lc-submit {
  margin-top: clamp(10px, 1.6vh, 20px);
}

/* Smaller corners on phones (spec: reduce radius on small screens). */

@media (max-width: 600px) {
  .lets-connect-page .lcp-panel--dark { border-radius: 18px 18px 0 0; }
}

/* Respect reduced-motion: drop the sticky overlap, fall back to a plain vertical
   stack (no scroll-driven covering). */

@media (prefers-reduced-motion: reduce) {
  .lets-connect-page .lcp-panel {
    position: static;
    min-height: 0;
  }
}

/* Large / HD screens: the form (1080px) and intro/closing text (900px) sat with
   big side gaps on a 1920 monitor. Widen them so the panel fills the screen like
   the rest of the site. Scoped to min-width — smaller layouts are untouched. */

@media (min-width: 1600px) {
  /* Trim the panel's 80px side padding and widen the form so it uses the screen
     instead of sitting in a narrow centred column with big white gutters. The
     form goes TWO-COLUMN (name | email, phone | fleet, submit full-width) so the
     wider panel is filled WITHOUT absurdly long single-line inputs. */
  /* The two-column form is short, so in the 100vh panel it left a big void at
     the bottom. Centre it vertically so the leftover space is balanced (and
     reads as intentional) instead of dumped below the Submit button. */
  .lets-connect-page .lcp-panel--form.lets-connect {
    padding-left: 48px;
    padding-right: 48px;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  .lets-connect-inner { max-width: 1520px; }
  .lets-connect-page .lcp-inner { max-width: 1060px; }
  .lets-connect-page .lcp-panel--form .lc-heading { font-size: clamp(80px, 5.4vw, 96px); }
  .lets-connect-page .lcp-panel--form .lc-form {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(32px, 3vw, 60px);
    row-gap: clamp(18px, 2.4vh, 30px);
    align-items: start;
  }
  /* (Field errors live INSIDE their .lc-field now, so only the submit spans.) */
  .lets-connect-page .lcp-panel--form .lc-form > .lc-submit { grid-column: 1 / -1; }
}

@media (min-width: 1900px) {
  .lets-connect-page .lcp-panel--form.lets-connect { padding-left: 40px; padding-right: 40px; }
  .lets-connect-inner { max-width: 1720px; }
  .lets-connect-page .lcp-inner { max-width: 1180px; }
  .lets-connect-page .lcp-panel--form .lc-heading { font-size: clamp(92px, 5.4vw, 104px); }
}

/* Scroll hint sits on the dark intro panel; 4.04:1 was just under the floor. */

.lcp-panel--dark .lcp-scroll-hint { color: rgba(255, 255, 255, 0.78); }

/* ============================================================================
   SESSION 68 — THE FORM PANEL SITS ON THE SHARED HERO SURFACE
   (operator: "change its background from white to our hero section background
   and then manage text, fields and button colors according to industry and
   ui/ux standards"). This block REPLACES session 28's light-grey palette and
   must stay at the END of the file — its (0,2,0) selectors beat the base
   rules above on specificity, and `.rf-hero-ground`'s own background-color
   (theme.css, loaded first) on order.

   Every colour pairing below is one the hero already MEASURED on this exact
   surface (LandingHero.css):
     · white text                      5.03:1 at the bloom's worst spot — floor
     · rgba(255,255,255,0.62) border   3.3:1  — the ghost CTA's boundary
     · #453A8C deep ink on white       7.4:1  — the inverted-primary label
   ⚠️ Do not re-check these with a computed-style sweep — the ground is
   stacked gradients and reads as transparent (CB9); sample pixels or trust
   the hero's numbers.

   The "Talk soon." outro was removed earlier in session 68; the sequence is
   intro → form, and the footer follows the form. */

/* The ground. background-color AND background-image both set here because the
   base rule at the top uses the `background` SHORTHAND (which clears images)
   and wins on order over theme.css's `.rf-hero-ground`. The dots and vignette
   ride the class's own pseudos. */

.lets-connect-page .lets-connect.rf-hero-ground {
  background-color: var(--rf-hero-ground, #1D1246);
  background-image: var(--rf-hero-bloom);
  color: #ffffff;
}

.lets-connect-page .lc-heading { color: #ffffff; }

.lets-connect-page .lc-field label,
.lets-connect-page .lc-fleet-label { color: #ffffff; }

/* Fields on the violet.
   ⚠️ An input's border is a UI-component boundary (WCAG 1.4.11), floor 3:1 —
   0.62 white is the hero's own measured 3.3:1 boundary alpha. Text and caret
   go white; hover brightens the rule to full white (feedback by contrast
   step, same device as before, inverted). */

.lets-connect-page .lc-field input {
  color: #ffffff;
  caret-color: #ffffff;
  border-bottom-color: rgba(255, 255, 255, 0.62);
}

.lets-connect-page .lc-field input:hover { border-bottom-color: #ffffff; }

.lets-connect-page .lc-field input::placeholder { color: rgba(255, 255, 255, 0.6); }

/* ⚠️ THE FOCUS RING MUST BE WHITE-BASED HERE. Session 28's brand-purple ring
   (--rf-primary-ring) is purple-on-violet — the same near-invisibility the
   hero measured for its primary CTA (1.54:1). Keep the keyboard fix, change
   its ink. */

.lets-connect-page .lc-field input:focus-visible {
  border-bottom-color: #ffffff;
  outline: 3px solid rgba(255, 255, 255, 0.55);
  outline-offset: 4px;
  border-radius: 2px;
}

/* Fleet pills: ghost at rest (the hero's ghost-CTA treatment), WHITE when
   selected — selection reads as the loudest fill on this ground, and the
   label takes the deep ink the inverted primary uses (7.4:1 on white). */

.lets-connect-page .lc-pill {
  color: #ffffff;
  background: transparent;
  border-color: rgba(255, 255, 255, 0.62);
}

.lets-connect-page .lc-pill:hover:not(.is-selected) {
  background: rgba(255, 255, 255, 0.1);
  border-color: #ffffff;
}

.lets-connect-page .lc-pill.is-selected {
  background: #ffffff;
  border-color: #ffffff;
  color: var(--rf-primary-deep, #453A8C);
}

.lets-connect-page .lc-pill:focus-visible {
  outline: 3px solid rgba(255, 255, 255, 0.55);
  outline-offset: 3px;
}

/* Error: the light-ground danger red vanishes on violet; red-300 measures ~9:1
   here (the AdminPanel's dark theme makes the same light-red move). */

.lets-connect-page .lc-field-err { color: #FCA5A5; }

/* Submit = the hero's INVERTED primary, verbatim reasoning: brand #6A5ACD on
   this violet measures 1.54:1 (LandingHero.css) — the loudest control on the
   panel would be nearly invisible — so white carries it with the deep ink as
   label, and the hover is the hero primary's own #F1EFFC step.
   ⚠️ THE LIQUID-GLASS REMAINS DEAD (session 28's call): opaque fill, no
   backdrop-filter, and `::before` stays `content: none` — the old hover
   circle painted at z-index -1, invisible behind any opaque background. */

.lets-connect-page .lc-submit {
  background: #ffffff;
  border-color: #ffffff;
  color: var(--rf-primary-deep, #453A8C);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: 0 16px 34px -16px rgba(0, 0, 0, 0.7);
}

.lets-connect-page .lc-submit::before { content: none; }

.lets-connect-page .lc-submit:hover {
  background: #F1EFFC;
  border-color: #F1EFFC;
  color: var(--rf-primary-deep, #453A8C);
  box-shadow: 0 18px 38px -16px rgba(0, 0, 0, 0.75);
}

.lets-connect-page .lc-submit:focus-visible {
  outline: 3px solid rgba(255, 255, 255, 0.55);
  outline-offset: 3px;
}
/* Public quote confirmation page (/q/{token}) — brand dark, monochrome. */
.qc-page {
  min-height: 100dvh;
  background:
    radial-gradient(900px 460px at 50% -10%, rgba(255, 255, 255, 0.05), transparent 60%),
    #000;
  color: #fafafa;
  font-family: var(--rf-font-body, Georgia, serif);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 40px 18px 60px;
  letter-spacing: -0.01em;
}

.qc-brand {
  background: none;
  border: 0;
  color: #fafafa;
  font-family: inherit;
  font-weight: 800;
  font-size: 20px;
  letter-spacing: -0.04em;
  cursor: pointer;
  margin-bottom: 32px;
}
.qc-brand span { color: #8c8c8c; }

.qc-card {
  width: 100%;
  max-width: 460px;
  background: #0a0a0a;
  border: 1px solid #1f1f1f;
  border-radius: 20px;
  box-shadow: 0 22px 60px -30px rgba(0, 0, 0, 0.95);
  padding: 28px 26px;
}

/* ---- Centered state cards (loading / success / error) ---- */
.qc-state { text-align: center; padding: 22px 6px; }
.qc-state h1 { font-size: 22px; font-weight: 700; margin: 14px 0 6px; letter-spacing: -0.02em; }
.qc-sub { color: #9ca3af; font-size: 14px; line-height: 1.6; }
.qc-sub strong { color: #fafafa; }

.qc-icon {
  width: 56px; height: 56px; border-radius: 50%;
  display: grid; place-items: center; margin: 0 auto;
  font-size: 26px; font-weight: 700;
}
.qc-icon--good { background: #fafafa; color: #000; }
.qc-icon--bad { background: #1f1f1f; color: #f87171; border: 1px solid #2a2a2a; }

.qc-spinner {
  width: 30px; height: 30px; margin: 0 auto;
  border-radius: 50%;
  border: 2.5px solid #1f1f1f; border-top-color: #fafafa;
  animation: qc-spin 0.7s linear infinite;
}
@keyframes qc-spin { to { transform: rotate(360deg); } }

/* ---- Quote detail view ---- */
.qc-head { text-align: center; margin-bottom: 22px; }
.qc-eyebrow {
  display: block; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.1em; color: #646469; margin-bottom: 10px;
}
.qc-head h1 { font-size: 23px; font-weight: 700; letter-spacing: -0.025em; }
.qc-head .qc-sub { margin-top: 8px; }

.qc-label {
  display: block; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: 0.08em; color: #646469; margin-bottom: 3px;
}

.qc-block {
  border: 1px solid #1a1a1a; border-radius: 12px;
  padding: 14px 16px; margin-bottom: 12px;
}
.qc-vehicle { font-size: 17px; font-weight: 700; }

.qc-dates {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
  border: 1px solid #1a1a1a; border-radius: 12px;
  padding: 14px 16px; margin-bottom: 12px;
}
.qc-dval { font-size: 13.5px; font-weight: 600; }

.qc-pricing {
  border: 1px solid #1a1a1a; border-radius: 12px;
  padding: 8px 16px 14px; margin-bottom: 20px;
}
.qc-prow {
  display: flex; justify-content: space-between;
  font-size: 13px; color: #9ca3af; padding: 6px 0;
}
.qc-prow--total {
  border-top: 1px solid #1a1a1a; margin-top: 6px; padding-top: 13px;
  color: #fafafa;
}
.qc-prow--total span:first-child { font-weight: 700; font-size: 15px; }
.qc-prow--total span:last-child { font-weight: 800; font-size: 18px; }

.qc-err {
  color: #f87171; font-size: 13px; text-align: center;
  margin: 0 0 14px;
}

.qc-confirm {
  width: 100%;
  background: #fafafa; color: #000;
  border: 0; border-radius: 12px;
  font-family: inherit; font-weight: 700; font-size: 15px;
  padding: 14px; cursor: pointer;
  transition: transform 0.14s ease, opacity 0.14s ease;
}
.qc-confirm:hover:not(:disabled) { transform: translateY(-1px); }
.qc-confirm:disabled { opacity: 0.6; cursor: progress; }

.qc-fine {
  text-align: center; font-size: 11.5px; color: #646469;
  margin: 12px 0 0; line-height: 1.5;
}
/* ===== CardArtwork, the credit-card visual on the payment page =====
   ⚠️ PORT of AdminPanel/src/components/CardArtwork.css. CHANGE ONE, CHANGE
   BOTH. Geometry, brand tints, chip and sheen are byte-identical on purpose so
   the object an operator sees in the panel and the one a renter sees on a
   payment link are the same object. The ONE deliberate difference is the
   typeface: the panel hardcodes Quicksand, this takes the site's own body
   token so the card sits inside the site's type system.
   Renders masked digits only; see CardArtwork.jsx for the privacy rule. */

.cardart {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  aspect-ratio: 1.586;              /* ISO/IEC 7810 ID-1, the real card ratio */
  width: 100%;
  max-width: 360px;
  padding: 20px 22px;
  border-radius: 14px;
  border: 0.5px solid rgba(255, 255, 255, 0.14);
  background: linear-gradient(135deg, #2A2A2E 0%, #46464C 45%, #1E1E22 100%);
  color: #F5F5F7;
  /* Reads on WHITE here, not on the panel's near-black, so the shadow has to
     do the lifting the dark ground used to do for free. */
  box-shadow: 0 18px 34px -14px rgba(20, 12, 48, 0.55);
  font-family: var(--rf-font-body, 'Segoe UI', Arial, sans-serif);
}

/* Brushed-metal highlight sweeping across the face. */
.cardart-sheen {
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 32%, rgba(255, 255, 255, 0.13) 46%, rgba(255, 255, 255, 0.03) 54%, transparent 66%);
  pointer-events: none;
}

/* Brand tints, kept subtle so the artwork reads as one family. */
.cardart--visa      { background: linear-gradient(135deg, #1A1F3A 0%, #2C3E7A 48%, #12142A 100%); }
.cardart--mastercard{ background: linear-gradient(135deg, #2E2320 0%, #5A3A2E 48%, #1E1614 100%); }
.cardart--amex      { background: linear-gradient(135deg, #12303A 0%, #1F5E70 48%, #0C1F26 100%); }
.cardart--discover  { background: linear-gradient(135deg, #33240F 0%, #6B4A19 48%, #221806 100%); }

.cardart--compact { max-width: 260px; padding: 15px 16px; border-radius: 11px; }

.cardart-top {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

/* EMV chip: four contact pads on a rounded gold plate. */
.cardart-chip {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  width: 34px;
  height: 26px;
  padding: 4px;
  border-radius: 5px;
  background: linear-gradient(140deg, #D8C48A 0%, #A8935A 50%, #E4D4A4 100%);
  flex-shrink: 0;
}
.cardart-chip span { background: rgba(0, 0, 0, 0.16); border-radius: 1px; }
.cardart--compact .cardart-chip { width: 27px; height: 21px; }

.cardart-brand {
  font-size: 17px;
  font-weight: 800;
  font-style: italic;
  letter-spacing: 0.03em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  white-space: nowrap;
  /* ⚠️ Its own line-height. The page inherits a ~26px line box (CLAUDE.md),
     which would drop the brand well below the chip it is meant to sit level
     with. Same reason the number and the field values carry theirs. */
  line-height: 1;
}
.cardart--mastercard .cardart-brand { font-style: normal; font-weight: 600; text-transform: lowercase; }
.cardart--compact .cardart-brand { font-size: 14px; }

.cardart-number {
  position: relative;
  margin: 0;
  display: flex;
  gap: 14px;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: 0.06em;
  line-height: 1.2;
  /* Tabular figures stop the number jittering as digits arrive in the preview. */
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}
.cardart--compact .cardart-number { font-size: 13px; gap: 9px; }

.cardart-bottom {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 14px;
}

.cardart-field { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cardart-field--exp { align-items: flex-start; flex-shrink: 0; }

.cardart-field-label {
  font-size: 8px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.55);
}

.cardart-field-value {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  line-height: 1.4;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cardart--compact .cardart-field-value { font-size: 11px; }

@media (max-width: 480px) {
  .cardart { max-width: 100%; padding: 16px 17px; }
  .cardart-number { font-size: 14px; gap: 9px; }
}
/* ===== CheckoutCard, the white form standing on the violet hero ground =====
 *
 * ⚠️ THIS WAS A DARK GLASS CARD UNTIL THE PAGE MOVED ONTO `rf-hero-ground`
 * (operator: "background like our hero sec and then fonts and form white").
 * Every ink below is Demo.css's `--dm-*` set, ALREADY MEASURED against #FFFFFF
 * and reused verbatim rather than re-derived:
 *     --dm-ink        #000000   21:1     headings
 *     --dm-ink-body   #333333   12.6:1   body copy
 *     --dm-ink-soft   #4A4A52   8.6:1    secondary copy
 *     --dm-ink-faint  #6B6B75   5.1:1    the floor, do not lighten it
 *     --dm-line-ctl   #86868F   3.31:1   control rims (SC 1.4.11)
 *     --dm-accent     #6A5ACD   5.31:1   fills and large type
 *     --dm-accent-deep#453A8C   7.4:1    brand as small text on light
 * ⚠️ THE CONTROL RIM IS HEAVIER THAN THE CARD HAIRLINE ON PURPOSE. A white
 * input on a white card has ~1.03:1 of fill, so the rim is the ENTIRE signal
 * that says "you can type here"; `--dm-line-strong` measures 1.42:1 there and
 * would not carry it. Demo.css's own note is the reference.
 *
 * The tokens are declared locally so this component renders correctly wherever
 * it is mounted, without depending on a `.demo-page` ancestor scope. */

.cco-card {
  --dm-bg: #FFFFFF;
  --dm-surface: #FAFAFA;
  --dm-line: #E4E4E7;
  --dm-line-strong: #D4D4D8;
  --dm-line-ctl: #86868F;
  --dm-ink: #000000;
  --dm-ink-body: #333333;
  --dm-ink-soft: #4A4A52;
  --dm-ink-faint: #6B6B75;
  --dm-accent: #6A5ACD;
  --dm-accent-deep: #453A8C;
  --dm-accent-soft: #EEECFA;
  /* #DC2626 measures 4.83:1 on white. #FCA5A5 was the violet-ground error ink
     and is 1.9:1 here, i.e. invisible. */
  --dm-err: #DC2626;
  --dm-err-soft: #FEF2F2;
  --dm-err-line: #FCA5A5;

  width: 100%;
  max-width: 420px;
  margin: 0 auto;
  background: var(--dm-bg);
  border: 1px solid var(--dm-line);
  border-radius: 20px;
  padding: 26px 24px 22px;
  /* The card floats on a saturated violet, so the shadow is tinted with the
     ground rather than neutral black, which would read as a grey halo. */
  box-shadow: 0 28px 60px -20px rgba(13, 8, 36, 0.55), 0 2px 6px rgba(13, 8, 36, 0.10);
  color: var(--dm-ink-body);
  font-family: var(--rf-font-body, 'Segoe UI', Arial, sans-serif);
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.cco-head { display: flex; flex-direction: column; gap: 3px; }
/* ⚠️ Bold, and in the HEADING family (operator: "for fonts use bold font").
   `font-family: inherit` would take the body face; every other title on this
   site's white surfaces uses --rf-font-heading. */
.cco-title {
  margin: 0;
  font-family: var(--rf-font-heading, 'Segoe UI', Arial, sans-serif);
  font-size: 22px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15;
  color: var(--dm-ink);
}
.cco-sub { margin: 0; font-size: 13px; line-height: 1.45; color: var(--dm-ink-soft); }

/* The method tabs (Card / Apple Pay / Google Pay) and their .cco-wallet-note
   were deleted with the stubs they styled, §GF. Card is the only method this
   product can take today, so the form starts straight at the artwork. */

/* ---- the physical card ---- */
.cco-art { display: flex; justify-content: center; margin: 1px 0 3px; }

/* ---- fields ---- */
/* ⚠️ `min-width: 0`. .cco-field is a GRID ITEM inside .cco-row, and a grid
   item's default auto minimum is its min-content, which for an <input> is the
   browser's ~20-character default size and no amount of parent width shrinks
   it. Without this the two-up Expiry/CVC row refuses to go below ~490px and
   forces the whole payment column wider than a phone. */
.cco-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.cco-label { font-size: 12.5px; font-weight: 700; color: var(--dm-ink); }
.cco-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }

.cco-input-wrap {
  display: flex; align-items: center; gap: 9px;
  height: 46px; padding: 0 13px;
  background: var(--dm-bg);
  border: 1px solid var(--dm-line-ctl);
  border-radius: 12px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
/* ⚠️ FOCUS IS A RING, NOT JUST A BORDER COLOUR. A 1px hue change is under the
   3:1 floor as the only signal that a field is active. Demo.css's rule. */
.cco-input-wrap:focus-within {
  border-color: var(--dm-accent);
  box-shadow: 0 0 0 3px var(--dm-accent-soft);
}
.cco-input-wrap.is-err { border-color: var(--dm-err); background: var(--dm-err-soft); }
.cco-input-icon { display: inline-flex; color: var(--dm-ink-faint); flex: none; }
.cco-input {
  flex: 1; min-width: 0; background: transparent; border: 0; outline: none;
  color: var(--dm-ink); font-size: 15px; font-weight: 600; font-family: inherit;
  letter-spacing: 0.02em;
}
.cco-input::placeholder { color: #8E8E97; font-weight: 400; letter-spacing: 0.03em; }
.cco-input--plain { letter-spacing: normal; }
.cco-brand {
  font-size: 10.5px; font-weight: 800; flex: none;
  text-transform: uppercase; letter-spacing: 0.05em; line-height: 1;
  padding: 4px 7px; border-radius: 5px;
  background: var(--dm-accent-soft); color: var(--dm-accent-deep);
  white-space: nowrap;
}

/* ---- per-field messages ------------------------------------------------
   A red ring with no words tells the payer something is wrong and not what,
   which on a payment form is exactly where people give up. The hint slot uses
   the same box so a field does not change height when its message appears:
   `min-height` rather than a conditional margin, because a form that reflows
   under the thumb on every blur is worse than one that is 16px taller. */
.cco-fielderr,
.cco-hint {
  font-size: 12px;
  line-height: 1.45;
  min-height: 16px;
}
.cco-fielderr { color: var(--dm-err); font-weight: 600; }   /* 4.83:1 on white */
.cco-hint { color: var(--dm-ink-faint); }                    /* 5.1:1, the floor */

/* ---- card-on-file consent ---------------------------------------------- */
.cco-consent {
  border: 1px solid var(--dm-line-strong);
  background: var(--dm-surface);
  border-radius: 12px;
  padding: 13px 14px;
  transition: border-color .15s ease, background-color .15s ease;
}
.cco-consent.is-declined { border-color: var(--dm-accent); background: var(--dm-accent-soft); }
.cco-consent-row { display: flex; align-items: flex-start; gap: 11px; cursor: pointer; }
/* ⚠️ The checkbox needs its OWN line-height and a measured margin. This page
   inherits a ~26px line box (CLAUDE.md), roughly double the 13px copy beside
   it, so a control aligned against the text baseline sits ~4px high. */
.cco-check {
  flex: none;
  width: 17px; height: 17px;
  margin: 2px 0 0;
  accent-color: var(--dm-accent);
  cursor: pointer;
}
.cco-consent-copy { display: flex; flex-direction: column; gap: 4px; }
.cco-consent-copy b { font-size: 13px; font-weight: 700; color: var(--dm-ink); line-height: 1.4; }
.cco-consent-copy span { font-size: 11.8px; line-height: 1.5; color: var(--dm-ink-soft); }

/* ---- consent declined: the form has no job left, so it offers the way out - */
.cco-declined {
  border: 1px solid var(--dm-line-strong);
  background: var(--dm-surface);
  border-radius: 12px;
  padding: 15px 16px;
  display: flex; flex-direction: column; gap: 4px;
}
.cco-declined-title { margin: 0; font-size: 14px; font-weight: 800; color: var(--dm-ink); }
.cco-declined-sub { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--dm-ink-soft); }
.cco-alt {
  align-self: flex-start; margin-top: 8px;
  height: 40px; padding: 0 16px;
  background: var(--dm-bg); color: var(--dm-accent-deep);
  border: 1px solid var(--dm-accent); border-radius: 10px;
  font-size: 13px; font-weight: 700; font-family: inherit; cursor: pointer;
  transition: background-color .15s ease;
}
.cco-alt:hover { background: var(--dm-accent-soft); }

.cco-error {
  margin: 0; font-size: 13px; font-weight: 600; color: var(--dm-err);
  background: var(--dm-err-soft); border: 1px solid var(--dm-err-line);
  border-radius: 10px; padding: 10px 12px;
}
.cco-error:focus-visible { outline: 2px solid var(--dm-err); outline-offset: 2px; }

/* ---- submit ---- */
/* The page's primary. Deep violet fill with white ink measures 7.4:1, the
   `--dm-accent-deep` pairing theme.css records for brand-as-text on light. */
.cco-pay {
  height: 52px; margin-top: 2px;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  background: var(--dm-accent-deep); color: #FFFFFF;
  border: 0; border-radius: 12px;
  font-size: 15.5px; font-weight: 800; font-family: inherit; letter-spacing: -0.01em;
  cursor: pointer;
  transition: transform .15s ease, background-color .15s ease, box-shadow .15s ease;
}
.cco-pay:hover:not(:disabled) { background: #3A3078; transform: translateY(-1px); box-shadow: 0 12px 26px -8px rgba(69, 58, 140, 0.65); }
.cco-pay:active:not(:disabled) { transform: translateY(0); }
.cco-pay:disabled { opacity: 0.55; cursor: not-allowed; }

.cco-spin {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.35); border-top-color: #FFFFFF;
  animation: cco-spin 0.7s linear infinite;
}
@keyframes cco-spin { to { transform: rotate(360deg); } }

.cco-secure {
  margin: 0; display: flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 12px; line-height: 1.45; text-align: center;
  color: var(--dm-ink-faint);
}
.cco-secure svg { color: var(--dm-ink-faint); flex: none; }

@media (max-width: 460px) {
  .cco-card { padding: 20px 17px 18px; border-radius: 16px; }
  .cco-title { font-size: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  .cco-pay, .cco-input-wrap, .cco-consent, .cco-alt { transition: none; }
  .cco-spin { animation-duration: 2s; }
}
/* ===== Public booking payment page (/pay/{token}) =====
 *
 * ⚠️ THE GROUND IS `rf-hero-ground` (theme.css) AND IS NOT PAINTED HERE. The
 * page carries the class; theme.css owns the bloom, the dot field and the
 * vignette, plus the `position: relative` + `isolation: isolate` that keep its
 * `z-index: -1` pseudo-elements inside this element. Re-declaring any of that
 * locally is the duplication theme.css exists to stop, and a local
 * `background` would paint OVER the bloom.
 *
 * ⚠️ EVERY ON-VIOLET PAIRING IS LetsConnect.css's SESSION-68 MEASURED BLOCK,
 * reused rather than re-derived:
 *     white text                      5.03:1 at the bloom's worst spot (floor)
 *     #E4DEFF lavender body copy      6.16:1 (theme.css's hero table)
 *     rgba(255,255,255,0.62) borders  3.3:1  (UI-component boundary floor 3:1)
 *     error #FCA5A5                   light-red-on-violet (~9:1)
 * ⚠️ #C4B8FF IS IN THAT TABLE TOO, AT 4.43:1, AND IS THEREFORE NOT A TEXT INK.
 * It is UNDER the 4.5 AA floor for normal text, and 11px uppercase at weight
 * 800 is not "large text" (that starts at 18.66px bold). It was used here for
 * the eyebrow, the Refundable tag, the expiry line and the host label before
 * this was caught, and all four moved to #E4DEFF. Only the host-links ICON
 * keeps it, because a graphic owes 3:1, not 4.5:1. To make a line quieter on
 * this ground, do what theme.css's own ink note says: make it smaller or
 * lighter in WEIGHT, not in colour.
 * ⚠️ Do NOT re-check these with a computed-style sweep. The ground is stacked
 * gradients and reads as transparent to an audit, so every ratio comes back
 * against `rgba(0,0,0,0)`; sample pixels or trust the hero's numbers
 * (theme.css's own warning).
 *
 * The checkout column's WHITE card carries its own measured light set. It is
 * declared in CheckoutCard.css, not here.
 *
 * ⚠️ NO EM DASHES IN USER-FACING COPY (CLAUDE.md, standing operator rule).
 */

.pb-page {
  min-height: 100dvh;
  display: flex; flex-direction: column;
  /* Jet black beneath/below the violet so any overscroll reads as one dark
     ground, matching .bookdemo-page. The violet itself is rf-hero-ground's. */
  background-color: #0b0b0d;
  color: #ffffff;
  font-family: var(--rf-font-body, 'Segoe UI', Arial, sans-serif);
}

/* ---- masthead: the OPERATOR's letterhead ------------------------------
   ⚠️ NOT the platform wordmark that used to sit here. A renter who has only
   ever dealt with the operator being asked for a card by a brand they have
   never heard of is the most reliable way to make a real payment page look
   like a phishing page. The platform keeps the footer credit. */
.pb-topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px;
  padding: 18px clamp(16px, 4vw, 40px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.pb-brand { display: flex; align-items: center; gap: 11px; min-width: 0; }
/* Contained, never stretched: an operator logo can be any aspect ratio, and a
   squashed mark reads as a broken page on the one screen that must not. */
.pb-brand-logo { height: 30px; width: auto; max-width: 148px; object-fit: contain; display: block; }
/* The no-logo mark. A deliberate design rather than a gap, see initials(). */
.pb-brand-mark {
  flex: none; width: 30px; height: 30px; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.34);
  font-size: 12px; font-weight: 800; letter-spacing: 0.02em;
  /* ⚠️ Its own line-height. This page inherits a ~26px line box (CLAUDE.md),
     which would push two initials off-centre inside a 30px disc. */
  line-height: 1;
}
.pb-brand-mark[hidden] { display: none; }
.pb-brand-name {
  font-family: var(--rf-font-heading, 'Segoe UI', Arial, sans-serif);
  font-size: 17px; font-weight: 800; letter-spacing: -0.01em; color: #ffffff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pb-secure {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  font-size: 12.5px; font-weight: 600; color: #E4DEFF;
}
.pb-secure svg { flex: none; }

.pb-main { flex: 1; display: flex; align-items: center; justify-content: center; padding: clamp(20px, 5vw, 56px) 20px; }

/* ⚠️ minmax(0, 1fr), NEVER a bare `1fr`. A bare fr is `minmax(auto, 1fr)`, and
   a grid item's auto minimum is its MIN-CONTENT, which here is the checkout
   form's two-up Expiry/CVC row: ~500px of un-shrinkable inputs. At 375px that
   made both columns render 533px inside a 335px grid, and the excess was CUT
   OFF rather than scrolling (a payment form with no visible button, on the
   device most payment links are opened on). Pair it with `min-width: 0` on the
   cells. Same family as the bookings-table trap in CLAUDE.md. */
.pb-grid {
  width: 100%; max-width: 1020px;
  display: grid; grid-template-columns: minmax(0, 1fr) 440px; gap: clamp(24px, 4vw, 56px);
  align-items: start;
}

/* ⚠️ NO PANEL, NO BORDER, NO CARD. The summary sits DIRECTLY on the violet now:
   the ground is the design, and boxing white type inside a translucent panel on
   top of a gradient produced a muddy second surface with no job. The checkout's
   white card is the only raised object on the page, which is what makes it read
   as the thing to act on. */
.pb-summary { display: flex; flex-direction: column; min-width: 0; }

/* Recognition before anything else: the renter should know this is THEIR
   booking before they read a figure. First name only, the link is forwardable
   and the server ships nothing more than that. */
.pb-greet { margin: 0 0 16px; font-size: 15px; font-weight: 600; color: #E4DEFF; }

.pb-eyebrow {
  font-size: 11.5px; font-weight: 800; letter-spacing: 0.14em;
  text-transform: uppercase; color: #E4DEFF;
}
.pb-veh {
  margin: 10px 0 6px;
  font-family: var(--rf-font-heading, 'Segoe UI', Arial, sans-serif);
  font-size: clamp(28px, 4vw, 40px); font-weight: 800;
  letter-spacing: -0.03em; line-height: 1.04; color: #ffffff;
}
.pb-dates { margin: 0 0 26px; font-size: 14px; font-weight: 500; color: #E4DEFF; }

.pb-lines { display: flex; flex-direction: column; gap: 2px; }
.pb-line {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  font-size: 14.5px; font-weight: 500; color: #E4DEFF;
  padding: 11px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}
.pb-line b { color: #ffffff; font-weight: 700; flex: none; }
.pb-note { font-style: normal; font-size: 11px; font-weight: 600; color: #E4DEFF; margin-left: 8px; }
.pb-line--total { border-bottom: none; padding-top: 15px; font-size: 16px; color: #ffffff; font-weight: 700; }
.pb-line--total b { font-size: 20px; font-weight: 800; }

/* The amount. The one figure on this page that has to be unmistakable, so it
   gets a lifted plate and the heaviest weight in the type scale. */
.pb-due {
  margin-top: 22px; padding: 17px 19px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.34);
  border-radius: 14px;
  display: flex; flex-direction: column; gap: 4px;
}
.pb-due-label { font-size: 12.5px; font-weight: 700; letter-spacing: 0.02em; color: #E4DEFF; }
.pb-due-amt {
  font-family: var(--rf-font-heading, 'Segoe UI', Arial, sans-serif);
  font-size: clamp(28px, 3.4vw, 34px); font-weight: 800; letter-spacing: -0.02em;
  line-height: 1.1; color: #ffffff;
}
.pb-due-note { font-size: 12.5px; color: #E4DEFF; line-height: 1.5; }
/* An expiring link is a fact the payer needs BEFORE deciding to come back
   later, so it sits with the amount, not in the small print. */
.pb-expiry { margin: 12px 0 0; font-size: 12.5px; font-weight: 500; color: #E4DEFF; }

/* ---- what happens next -------------------------------------------------
   ⚠️ THIS IS THE ANSWER TO "so did I just pay?", not decoration. It exists
   because the left column ran empty for most of a screen AND because the page
   makes a promise it has to explain (nothing is charged here). Filling space
   with real information beats filling it with a bigger gap. */
.pb-steps {
  list-style: none; margin: 30px 0 0; padding: 26px 0 0;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
  display: flex; flex-direction: column; gap: 14px;
}
.pb-step { display: flex; align-items: center; gap: 13px; }
.pb-step-n {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: #ffffff; color: #453A8C;      /* the inverted primary, 7.4:1 */
  font-size: 12.5px; font-weight: 800;
  /* ⚠️ Own line-height: the inherited 26.1px box would drop the numeral. */
  line-height: 1;
}
.pb-step-t { font-size: 14px; font-weight: 600; color: #ffffff; }

/* ---- your host ---------------------------------------------------------- */
.pb-host {
  margin-top: 30px; padding-top: 26px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}
.pb-host-label {
  display: block; margin-bottom: 12px;
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.16em;
  text-transform: uppercase; color: #E4DEFF;
}
.pb-host-row { display: flex; align-items: center; gap: 11px; min-width: 0; }
.pb-host-logo {
  height: 34px; width: 34px; border-radius: 9px; object-fit: cover; flex: none;
  background: rgba(255, 255, 255, 0.12);
}
.pb-host-name {
  font-family: var(--rf-font-heading, 'Segoe UI', Arial, sans-serif);
  font-size: 16px; font-weight: 700; color: #ffffff;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pb-host-links {
  list-style: none; margin: 13px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 9px;
}
.pb-host-links li { display: flex; align-items: center; gap: 9px; font-size: 13.5px; min-width: 0; }
.pb-host-links svg { flex: none; color: #C4B8FF; }
.pb-host-links a, .pb-host-links span {
  color: #ffffff; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ⚠️ The underline is ALWAYS on, not hover-only. Touch has no hover, and a
   contact detail that is secretly a link is a link nobody presses. */
.pb-host-links a { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(255, 255, 255, 0.55); }
.pb-host-links a:hover { text-decoration-color: #ffffff; }
.pb-host-note { margin: 14px 0 0; font-size: 12.5px; line-height: 1.55; color: #E4DEFF; }

/* ---- checkout column ---------------------------------------------------- */
/* Column, not a centred row: the save-card path stacks a disclosure above the
   form, and `align-items: center` on a row silently shrank both to content. */
.pb-checkout {
  display: flex; flex-direction: column; gap: 14px;
  min-width: 0;
}

/* ---- what this page can do instead of charging ------------------------- */
/* Amber on the violet, the /features proof-row treatment. Deliberately NOT the
   white card's amber: this notice sits ON the ground, not on the card. */
.pb-disclose {
  width: 100%;
  padding: 15px 17px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(251, 191, 36, 0.55);
  border-radius: 14px;
}
.pb-disclose-title { margin: 0 0 5px; font-size: 13.5px; font-weight: 800; color: #FBBF24; }
.pb-disclose-sub { margin: 0; font-size: 13px; line-height: 1.55; color: #ffffff; font-weight: 500; }

/* ---- success / loading / dead-link states ------------------------------
   `.pb-notice` centres on its own as the whole page body (a dead or unknown
   link has no booking to show beside it); `--inline` drops it into the checkout
   column when the booking IS known and only the paying part is unavailable. */
.pb-status { margin: 0; font-size: 15px; font-weight: 500; color: #E4DEFF; }
.pb-notice { max-width: 460px; padding: 30px; text-align: center; }
.pb-notice-title {
  margin: 0 0 8px;
  font-family: var(--rf-font-heading, 'Segoe UI', Arial, sans-serif);
  font-size: 24px; font-weight: 800; letter-spacing: -0.02em; color: #ffffff;
}
.pb-notice-sub { margin: 0; font-size: 14.5px; line-height: 1.6; color: #E4DEFF; }
.pb-notice--inline {
  max-width: 100%;
  width: 100%;
  padding: 24px 22px;
  text-align: left;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.34);
  border-radius: 16px;
}
.pb-notice--inline .pb-notice-title { font-size: 18px; }
.pb-notice--inline .pb-notice-sub { font-size: 13.5px; }

/* ---- reaching a human -------------------------------------------------- */
.pb-contact { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.pb-contact li { font-size: 14px; font-weight: 600; }
.pb-contact a { color: #ffffff; text-decoration: underline; text-underline-offset: 3px; }
.pb-contact a:hover { color: #F1EFFC; }

.pb-back {
  margin-top: 18px; padding: 0; background: none; border: 0;
  color: #E4DEFF; font-family: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.pb-back:hover { color: #ffffff; text-decoration: underline; }

/* ---- success ------------------------------------------------------------ */
.pb-success { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 12px; max-width: 480px; padding: 30px; }
.pb-success-check { width: 84px; height: 84px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; background: radial-gradient(circle at 30% 30%, #34D399, #059669); box-shadow: 0 16px 40px rgba(5, 150, 105, 0.4); }
/* Not the green tick. A tick is the universal "your money went through" mark,
   and nothing has been charged: a card glyph on the inverted primary says what
   actually happened. */
.pb-success-check--saved {
  background: #ffffff;
  color: #453A8C;
  box-shadow: 0 16px 40px rgba(13, 8, 36, 0.45);
}
.pb-success-title {
  margin: 6px 0 0;
  font-family: var(--rf-font-heading, 'Segoe UI', Arial, sans-serif);
  font-size: clamp(26px, 4vw, 32px); font-weight: 800; letter-spacing: -0.02em; color: #ffffff;
}
.pb-success-sub { margin: 0; font-size: 15px; color: #E4DEFF; line-height: 1.55; }
.pb-next {
  margin-top: 6px; width: 100%;
  padding: 16px 18px; text-align: left;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.34);
  border-radius: 14px;
}
.pb-next-title { margin: 0 0 5px; font-size: 14px; font-weight: 800; color: #ffffff; }
.pb-next-sub { margin: 0; font-size: 13.5px; line-height: 1.6; color: #E4DEFF; }

/* ---- footer ------------------------------------------------------------- */
.pb-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 16px clamp(16px, 4vw, 40px);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 12px; font-weight: 500; color: #E4DEFF;
}
/* The platform credit is a link (App.css's `a` rules are nested/scoped and
   never reach this page, so without this it renders UA-default blue). */
.pb-foot a { color: #E4DEFF; text-decoration: none; }
.pb-foot a:hover { color: #ffffff; text-decoration: underline; }

@media (max-width: 900px) {
  .pb-grid { grid-template-columns: minmax(0, 1fr); max-width: 460px; gap: 30px; }
  /* Stacked, the checkout is what the renter came for, so it leads and the
     supporting detail follows it. `order` only, no markup change: the DOM keeps
     summary-then-form, which is the right reading order for a screen reader
     and the right source order for the h1. */
  .pb-checkout { order: -1; }
}

@media (max-width: 460px) {
  .pb-topbar { padding: 14px 16px; }
  .pb-brand-name { font-size: 15px; }
  .pb-brand-logo { height: 26px; max-width: 112px; }
  /* The lock + "Secure payment" must not wrap next to a long company name. */
  .pb-secure { font-size: 11.5px; }
  .pb-main { padding: 24px 16px 32px; }
  .pb-foot { flex-direction: column; align-items: flex-start; gap: 5px; }
}
/* ===== /credits/{token} — the operator's screening-credit invoice ==========
 *
 * ⚠️ EVERY CLASS IS NAMESPACED `pc-*`. All CSS is bundled globally on this site,
 * so a bare name reaches every other page. `rf-hero-ground` is BORROWED from
 * theme.css and never redefined here: it is the site's one violet, and a second
 * copy of it is how two pages end up nearly the same colour.
 *
 * ⚠️ THE PANEL IS WHITE ON THE VIOLET, WHICH IS THE WHOLE POINT OF THE LAYOUT.
 * Every ink on the ground is taken from the measured set /features and /demo
 * already use (#E4DEFF body on violet, white headings); every ink inside the
 * white panel is the ordinary dark scale. Do not put ground inks inside the
 * panel or panel inks on the ground — that is the pairing that fails contrast.
 */

.pc-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  /* `position: relative` so the ground's ::before dots and ::after vignette
     (theme.css) stack under the content rather than over it. */
  position: relative;
}

/* ------------------------------- chrome --------------------------------- */

.pc-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 24px;
  position: relative;
  z-index: 1;
}

/* ⚠️ AN ANCHOR NOW, so it needs the reset an anchor brings with it: no
   underline on the lockup itself (the mark plus the name reads as one object,
   not as body text), but a real hover and a real focus ring, because an
   identity that is clickable and does not look it is worse than one that is
   not clickable at all. */
.pc-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  text-decoration: none;
  border-radius: 12px;
}

.pc-brand:hover .pc-brand-name { text-decoration: underline; text-underline-offset: 3px; }
.pc-brand:hover .pc-brand-mark { background: rgba(255, 255, 255, 0.22); }
.pc-brand:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* The uploaded wordmark, in place of the initials mark and the name.
   ⚠️ HEIGHT-SIZED WITH AUTO WIDTH: a platform lockup is WIDE, not square, so a
   width constraint squashes it. `max-width` is the only guard, so a very wide
   logo shrinks rather than pushing the topbar into a sideways scroll. */
.pc-brand-img {
  display: block;
  height: 30px;
  width: auto;
  max-width: 200px;
  object-fit: contain;
}

/* The footer credit. Underlined ALWAYS rather than on hover: it sits alone in
   quiet text at the foot of the page, where a link nobody suspects is a link
   nobody clicks. */
.pc-foot-link {
  color: #E4DEFF;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.pc-foot-link:hover { color: #fff; text-decoration-thickness: 2px; }
.pc-foot-link:focus-visible { outline: 2px solid #fff; outline-offset: 2px; border-radius: 3px; }

.pc-brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.28);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  /* Its own line-height — the site's inherited one would push the initials
     off centre inside a 34px square. */
  line-height: 1;
}

.pc-brand-name {
  color: #fff;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.pc-secure {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: #E4DEFF;
  font-size: 12.5px;
  line-height: 1.4;
  white-space: nowrap;
}

.pc-main {
  flex: 1 1 auto;
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  padding: 12px 24px 56px;
  position: relative;
  z-index: 1;
  min-width: 0;
}

.pc-foot {
  padding: 18px 24px 28px;
  text-align: center;
  color: #C9BFF5;
  font-size: 12px;
  line-height: 1.5;
  position: relative;
  z-index: 1;
}

.pc-status {
  color: #E4DEFF;
  font-size: 15px;
  text-align: center;
  padding: 80px 0;
  line-height: 1.5;
}

/* ------------------------------- layout --------------------------------- */

/* ⚠️ minmax(0, …) on both tracks — a bare `1fr` refuses to shrink below its
   content, and a long operator email would push the page into a sideways
   scroll on a phone. */
.pc-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
  gap: 32px;
  align-items: start;
  min-width: 0;
}

@media (max-width: 900px) {
  .pc-grid { grid-template-columns: minmax(0, 1fr); gap: 24px; }
}

/* ------------------------- left: the invoice ---------------------------- */

.pc-left { min-width: 0; padding-top: 18px; }

.pc-eyebrow {
  margin: 0 0 10px;
  color: #C9BFF5;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.4;
}

.pc-title {
  margin: 0 0 16px;
  color: #fff;
  font-size: clamp(30px, 4.2vw, 42px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.12;
}

.pc-lead,
.pc-reassure {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 0 0 24px;
  color: #E4DEFF;
  font-size: 14.5px;
  line-height: 1.6;
  max-width: 48ch;
}

.pc-lead-tick {
  display: inline-flex;
  flex: 0 0 20px;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  line-height: 1;
}

.pc-facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px 24px;
  margin: 0 0 24px;
  padding: 20px 22px;
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.16);
  min-width: 0;
}

.pc-facts > div { min-width: 0; }
.pc-facts-wide { grid-column: 1 / -1; }

.pc-facts dt {
  color: #C9BFF5;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.4;
}

.pc-facts dd {
  margin: 5px 0 0;
  color: #fff;
  font-size: 14.5px;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

/* The number the page is about. Big enough to be the answer to "how much?". */
.pc-amount { font-size: 26px !important; font-weight: 800; letter-spacing: -0.01em; }

.pc-dim { color: #C9BFF5; }

.pc-help {
  margin: 0;
  color: #C9BFF5;
  font-size: 13px;
  line-height: 1.6;
}

.pc-help a { color: #fff; text-decoration: underline; text-underline-offset: 2px; }
.pc-help a:hover { text-decoration-thickness: 2px; }

/* ------------------------- right: the panel ----------------------------- */

/* ⚠️ WHITE, per the brief, and it is also what makes the form read as a form
   rather than as more hero. The card component brings its own internals. */
.pc-panel {
  background: #fff;
  border-radius: 18px;
  padding: 26px 24px;
  box-shadow: 0 26px 60px -28px rgba(20, 12, 54, 0.55);
  min-width: 0;
}

.pc-panel-title {
  margin: 0 0 8px;
  color: #101014;
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.3;
}

.pc-panel-sub {
  margin: 0 0 18px;
  color: #4b5563;
  font-size: 13.5px;
  line-height: 1.6;
}

.pc-panel-sub b { color: #101014; }

.pc-panel--done { text-align: center; }

.pc-done-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  margin-bottom: 14px;
  border-radius: 50%;
  background: #EAF6EF;
  color: #0E8A43;
  line-height: 1;
}

.pc-contact {
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}

.pc-contact li {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
  line-height: 1.5;
}

.pc-contact-label {
  flex: 0 0 52px;
  color: #6b7280;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.pc-contact a {
  color: #4B3FA8;
  font-size: 14px;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
  overflow-wrap: anywhere;
}

/* The "your credits are already active" repeat, inside the white panel, so it
   takes panel inks rather than the ground's. */
.pc-panel .pc-reassure {
  margin: 0;
  padding: 12px 14px;
  border-radius: 12px;
  background: #F4F2FD;
  color: #3f3a52;
  font-size: 12.5px;
}

.pc-panel .pc-lead-tick {
  background: #E4DEFF;
  color: #4B3FA8;
}

/* ------------------------------- notices -------------------------------- */

.pc-notice {
  max-width: 520px;
  margin: 48px auto;
  padding: 30px 26px;
  border-radius: 18px;
  background: #fff;
  box-shadow: 0 26px 60px -28px rgba(20, 12, 54, 0.55);
  text-align: center;
}

.pc-notice-title {
  margin: 0 0 10px;
  color: #101014;
  font-size: 21px;
  font-weight: 700;
  line-height: 1.3;
}

.pc-notice-sub {
  margin: 0 0 8px;
  color: #4b5563;
  font-size: 14px;
  line-height: 1.6;
}

.pc-notice-sub a { color: #4B3FA8; text-decoration: underline; text-underline-offset: 2px; }
/* ===========================================================================
   Forgot Password — Login/Signup's arrangement (§DU), session 68:
   the SHELL carries `rf-hero-ground` (theme.css) and paints the shared hero
   violet across the whole page; the left panel centres the brand with its
   line below; the right panel is a white card, rounded on ALL corners,
   floating on the ground with a gap. The page NEVER scrolls: 100dvh shell,
   overflow hidden, vh-clamped rhythm — Login.css's contract.

   ⚠️ NO `background` ON `.fp-shell` — this file loads after theme.css, so a
   shorthand here would silently flatten the bloom/dots/vignette to one
   colour (Login.css documents the identical trap on `.login-shell`).

   Ink tokens on the violet are Login's own scored set (against the ground's
   brightest painted point rgb(81,64,165)): #FFFFFF 7.99 · #E4DEFF 6.16 ·
   #C4B8FF 4.43 (large/bold ONLY — the brand accent qualifies at 56px+).
   ========================================================================= */
.fp-shell {
  /* Gap that lets the card's corners round against the violet — Login's
     --card-gap, same clamp. */
  --fp-card-gap: clamp(10px, 1.4vw, 22px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(430px, 36vw, 560px);
  width: 100%;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;                     /* the page itself never scrolls */
  font-family: var(--rf-font-body, Georgia, serif);
  color: #0A0A0A;
}

/* Left — the brand, centred on the shell's ground. Paints nothing itself. */
.fp-promo {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: clamp(24px, 4.4vh, 52px) clamp(32px, 4.4vw, 64px);
  min-width: 0;
  overflow: hidden;                     /* clips the decorative ring */
}

/* Login's promo ring, same geometry — a real span, never a pseudo (the hero
   class owns ::before/::after of the element carrying it). */
.fp-ring {
  position: absolute;
  inset: auto -220px -260px auto;
  width: 640px;
  height: 640px;
  border-radius: 50%;
  border: 1px solid rgba(228, 222, 255, 0.16);
  pointer-events: none;
}

.fp-brand { position: relative; max-width: 560px; }
.fp-brand-name {
  margin: 0 0 clamp(10px, 2.2vh, 20px);
  font-family: var(--rf-font-heading, Georgia, serif);
  font-size: clamp(56px, 6.5vw, 96px);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  color: #ffffff;
}
/* 4.43:1 — passes the 3:1 large-text floor only because the wordmark is
   56px+/800. Do not reuse this hue on body-size text here (Login's rule). */
.fp-brand-accent { color: #C4B8FF; }
.fp-brand-sub {
  margin: 0 auto;
  max-width: 44ch;
  font-size: clamp(14px, 1.2vw, 16px);
  line-height: 1.6;
  color: #E4DEFF;                       /* 6.16:1 at the ground's brightest */
}

/* Right — the white card, rounded on ALL corners, floating on the violet. */
.fp-right {
  background: #FFFFFF;
  margin: var(--fp-card-gap) var(--fp-card-gap) var(--fp-card-gap) 0;
  border-radius: 22px;                  /* Login's form-side radius */
  padding: clamp(20px, 3.6vh, 40px) clamp(28px, 3vw, 48px);
  display: flex;
  flex-direction: column;
  color: #0A0A0A;
  min-height: 0;                        /* let the column shrink to fit */
  overflow: hidden;                     /* never scrolls — rhythm compresses */
  box-shadow: 0 24px 80px -30px rgba(0, 0, 0, 0.5);
}

.fp-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: clamp(16px, 4.5vh, 48px);   /* was a fixed 56px — vh-clamped for the no-scroll contract */
}

.fp-logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  border-radius: 6px;
  transition: opacity 0.15s ease;
}
.fp-logo:hover { opacity: 0.85; }
.fp-logo:focus-visible { outline: 2px solid #0A0A0A; outline-offset: 4px; }
.fp-logo-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: #0A0A0A;
  color: #FAFAFA;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 14px;
}
.fp-logo-text { font-weight: 700; font-size: 15px; }

.fp-signin {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: 1px solid #D1D5DB;
  color: #0A0A0A;
  padding: 8px 16px;
  height: 38px;
  border-radius: 24px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.fp-signin:hover { border-color: #0A0A0A; background: #FAFAFA; }
.fp-signin-arrow { font-size: 14px; }

.fp-form-container {
  flex: 1;
  min-height: 0;                 /* participate in the no-scroll compression */
  display: flex;
  flex-direction: column;
  justify-content: center;       /* centre the step vertically, Login-style */
  max-width: 380px;
  width: 100%;
  margin: 0 auto;
}

/* Header icon (sent / success states) */
.fp-icon-wrap {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: #F5F5F5;
  border: 1px solid #E5E7EB;
  color: #0A0A0A;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 20px;
  flex-shrink: 0;
}
.fp-icon-wrap--success {
  background: #0A0A0A;
  color: #FAFAFA;
  border-color: #0A0A0A;
}
/* Sent confirmation — green mail badge (matches the improved mockup). */
.fp-icon-wrap--sent {
  width: 64px;
  height: 64px;
  background: rgba(34, 197, 94, 0.12);
  border-color: rgba(34, 197, 94, 0.25);
  color: #16a34a;
}

/* Field label above the input */
.fp-label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  color: #0A0A0A;
  margin: 0 0 7px 4px;
}

.fp-title {
  margin: 0 0 8px;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: #0A0A0A;
}

.fp-subtitle {
  margin: 0 0 28px;
  color: #6B7280;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.55;
}
.fp-subtitle strong { color: #0A0A0A; font-weight: 600; }

/* === Form fields — IDENTICAL sizing across all 3 fields (email, new pw, confirm pw) === */
.fp-form-group {
  margin-bottom: 14px;
}

.fp-input-field {
  background: #FFFFFF;
  border: 1px solid #D1D5DB;
  border-radius: 28px;
  padding: 0 22px;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 46px;
  box-sizing: border-box;
  transition: border-color 0.15s ease;
}

.fp-input-field:focus-within { border-color: #8b6cff; box-shadow: 0 0 0 3px rgba(139, 108, 255, 0.15); }
.fp-input-field.is-error { border-color: #DC2626; }

.fp-input {
  border: none;
  outline: none;
  width: 100%;
  min-width: 0;
  height: 100%;
  font-family: inherit;
  font-size: 13px;
  background: transparent;
  color: #0A0A0A;
}
.fp-input::placeholder { color: #6B7280; }
.fp-input:focus, .fp-input:focus-visible { outline: none; box-shadow: none; }

/* Suppress browser autofill yellow tint */
.fp-input:-webkit-autofill,
.fp-input:-webkit-autofill:hover,
.fp-input:-webkit-autofill:focus,
.fp-input:-webkit-autofill:active {
  -webkit-text-fill-color: #0A0A0A;
  -webkit-box-shadow: 0 0 0 1000px #FFFFFF inset;
  box-shadow: 0 0 0 1000px #FFFFFF inset;
  caret-color: #0A0A0A;
  transition: background-color 5000s ease-in-out 0s;
}

/* One-time-code field (verify step) — larger, centered, spaced digits. */
.fp-input-field--otp { height: 56px; }
.fp-otp-input {
  text-align: center;
  font-size: 24px;
  font-weight: 700;
  letter-spacing: 0.4em;
  /* letter-spacing adds trailing space after each glyph, nudging the run
     right; the matching left indent re-centers it. */
  text-indent: 0.4em;
  color: #0A0A0A;
}
.fp-otp-input::placeholder { color: #D1D5DB; font-weight: 600; }

.fp-toggle {
  background: transparent;
  border: none;
  padding: 4px 6px;
  margin: 0 -6px 0 0;
  color: #6B7280;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
  border-radius: 4px;
}
.fp-toggle:hover { color: #0A0A0A; }
.fp-toggle:focus-visible { outline: 2px solid #0A0A0A; outline-offset: 2px; }

.fp-error {
  color: #DC2626;
  font-size: 12px;
  font-weight: 500;
  margin: 6px 16px 0;
  line-height: 1.4;
}

/* Password strength meter */
.fp-strength {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 16px 0;
}
.fp-strength-bars {
  display: flex;
  gap: 4px;
  flex: 1;
}
.fp-strength-bar {
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background: #E5E7EB;
  transition: background 0.15s ease;
}
.fp-strength-bar.is-on.lvl-1 { background: #DC2626; }
.fp-strength-bar.is-on.lvl-2 { background: #F59E0B; }
.fp-strength-bar.is-on.lvl-3 { background: #6B7280; }
.fp-strength-bar.is-on.lvl-4 { background: #0A0A0A; }
.fp-strength-label {
  font-size: 11px;
  font-weight: 600;
  color: #6B7280;
  flex-shrink: 0;
  min-width: 56px;
  text-align: right;
}

/* Primary button */
.fp-button {
  background: #0A0A0A;
  color: #FAFAFA;
  border: none;
  border-radius: 28px;
  height: 48px;
  width: 100%;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  margin-top: 8px;
  transition: background 0.15s ease, transform 0.08s ease;
}
.fp-button:hover:not(:disabled) { background: #000; }
.fp-button:active:not(:disabled) { transform: scale(0.99); }
.fp-button:disabled { opacity: 0.5; cursor: not-allowed; }

/* Sliding arrow on the "Send reset link" button */
.fp-btn-arrow {
  display: inline-block;
  margin-left: 2px;
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}
.fp-button:hover:not(:disabled) .fp-btn-arrow { transform: translateX(4px); }

/* Resend + back links */
.fp-resend {
  margin: 16px 0 0;
  text-align: center;
  color: #6B7280;
  font-size: 12px;
  font-weight: 500;
}
.fp-resend-btn {
  background: none;
  border: none;
  padding: 0;
  color: #0A0A0A;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.fp-resend-btn:disabled { opacity: 0.5; cursor: not-allowed; text-decoration: none; }
.fp-resend-timer { color: #9CA3AF; font-variant-numeric: tabular-nums; }

.fp-back-link {
  background: transparent;
  border: none;
  padding: 4px 8px;
  margin: 24px auto 0;
  display: inline-block;
  color: #0A0A0A;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  border-radius: 4px;
}
.fp-back-link:hover { text-decoration: underline; }
.fp-back-link:focus-visible { outline: 2px solid #0A0A0A; outline-offset: 2px; }

.fp-footer {
  margin-top: clamp(14px, 3vh, 32px);
  color: #6B7280;
  font-size: 12px;
  text-align: left;
}

/* ===== Responsive ===== */
/* One column: the brand panel steps aside (Login hides its promo the same
   way) and the card takes the whole frame with the gap on all four sides. */
@media (max-width: 900px) {
  .fp-shell { grid-template-columns: 1fr; }
  .fp-promo { display: none; }
  .fp-right { margin: var(--fp-card-gap); padding: 28px 24px; }
  .fp-form-container { max-width: 100%; }
  .fp-title { font-size: 24px; }
}

@media (max-width: 480px) {
  .fp-right { margin: 8px; border-radius: 16px; padding: 22px 16px; }
  .fp-input-field { padding: 0 18px; }
}
/* /book-demo — the demo scheduling page (the Let's Connect hand-off).
 *
 * The band carries `rf-hero-ground` (theme.css): the shared violet hero
 * surface every marketing page uses, which is also the ground the visitor
 * just left on the Let's Connect panel — the two read as one flow.
 *
 * ⚠️ EVERY COLOUR PAIRING HERE IS ONE LetsConnect.css's SESSION-68 BLOCK
 * ALREADY MEASURED ON THIS EXACT SURFACE — reused, not re-derived:
 *   · white text                       5.03:1 at the bloom's worst spot (floor)
 *   · #E4DEFF lavender body copy       6.16:1 (theme.css's hero table)
 *   · rgba(255,255,255,0.62) borders   3.3:1  (UI-component boundary floor 3:1)
 *   · white fill + #453A8C deep ink    7.4:1  (the inverted primary)
 *   · hover step #F1EFFC               the hero primary's own hover
 *   · error #FCA5A5                    light-red-on-violet (~9:1)
 * ⚠️ Do not re-check these with a computed-style sweep — the ground is stacked
 * gradients and reads as transparent to an audit; sample pixels or trust the
 * hero's numbers (theme.css's own warning).
 *
 * All styles scoped under .bookdemo-page. */

.bookdemo-page {
  /* Jet black behind/below the violet band so the footer's closing zone and
     any overscroll read as one dark ground, matching .home-closing. */
  background: #0b0b0d;
  font-family: var(--rf-font-body, Georgia, serif);
}

/* The band. `rf-hero-ground` brings position/isolation + the bloom/dots/
   vignette; this only shapes the box. The nav is FIXED, so the top padding
   pays the `--rf-nav-reserve` idiom /blog and /pricing use. */
.bd-band {
  min-height: 100svh;
  padding: calc(var(--rf-nav-reserve, 76px) + clamp(20px, 4vh, 48px))
           clamp(20px, 5vw, 64px)
           clamp(56px, 9vh, 110px);
  color: #ffffff;
}

.bd-inner {
  max-width: 1180px;
  margin: 0 auto;
}

/* ── Header ── */
.bd-head { margin-bottom: clamp(28px, 4vh, 48px); }
.bd-eyebrow {
  margin: 0 0 12px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #E4DEFF;
}
.bd-title {
  /* Two classes deep on purpose — .home-page h1 (0,1,1) forces var(--rf-ink)
     onto bare headings; this (0,2,0) outranks it if the page ever gains that
     wrapper class (the issue-45 trap). */
  margin: 0;
  font-family: inherit;
  font-size: clamp(30px, 5vw, 56px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.02;
  color: #ffffff;
}
.bookdemo-page .bd-title { color: #ffffff; }
.bd-sub {
  margin: clamp(12px, 2vh, 18px) 0 0;
  max-width: 56ch;
  font-size: clamp(15px, 1.8vw, 18px);
  line-height: 1.55;
  color: #E4DEFF;
}

/* ── Two-column form: details | scheduler ── */
.bd-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.3fr);
  column-gap: clamp(32px, 5vw, 72px);
  row-gap: clamp(18px, 3vh, 28px);
  align-items: start;
}
.bd-grid > .bd-error,
.bd-grid > .bd-confirm { grid-column: 1 / -1; }
@media (max-width: 900px) {
  .bd-grid { grid-template-columns: minmax(0, 1fr); }
}

.bd-sec-title {
  margin: 0 0 clamp(16px, 2.4vh, 24px);
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #ffffff;
}
.bookdemo-page .bd-sec-title { color: #ffffff; }

/* ── Fields — the Let's Connect underline treatment, verbatim ── */
.bd-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: clamp(16px, 2.4vh, 24px);
}
.bd-field label {
  font-size: 14px;
  font-weight: 600;
  color: #ffffff;
}
.bd-field input {
  font-family: inherit;
  font-size: 16px;
  color: #ffffff;
  caret-color: #ffffff;
  background: transparent;
  border: none;
  border-bottom: 2px solid rgba(255, 255, 255, 0.62);
  padding: 9px 0;
  width: 100%;
  outline: none;
}
.bd-field input:hover { border-bottom-color: #ffffff; }
.bd-field input::placeholder { color: rgba(255, 255, 255, 0.6); }
.bd-field input:focus-visible {
  border-bottom-color: #ffffff;
  outline: 3px solid rgba(255, 255, 255, 0.55);
  outline-offset: 4px;
  border-radius: 2px;
}

/* Fleet pills — ghost at rest, white when selected (the panel's own idiom). */
.bd-fleet { display: flex; flex-direction: column; gap: 12px; }
.bd-fleet-label { font-size: 14px; font-weight: 600; color: #ffffff; }
.bd-pills { display: flex; flex-wrap: wrap; gap: 10px; }
.bd-pill {
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.2;
  color: #ffffff;
  background: transparent;
  border: 1.5px solid rgba(255, 255, 255, 0.62);
  border-radius: 999px;
  padding: 9px 22px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.bd-pill:hover:not(.is-selected) {
  background: rgba(255, 255, 255, 0.1);
  border-color: #ffffff;
}
.bd-pill.is-selected {
  background: #ffffff;
  border-color: #ffffff;
  color: var(--rf-primary-deep, #453A8C);
}

/* ── Day strip — one horizontal row, scrollable when narrow ── */
.bd-days {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding-bottom: 8px;
  scrollbar-width: thin;
  margin-bottom: clamp(16px, 2.4vh, 22px);
}
.bd-day {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 64px;
  padding: 10px 12px;
  font-family: inherit;
  line-height: 1.2;
  color: #ffffff;
  background: transparent;
  border: 1.5px solid rgba(255, 255, 255, 0.62);
  border-radius: 14px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.bd-day:hover:not(:disabled):not(.is-selected) {
  background: rgba(255, 255, 255, 0.1);
  border-color: #ffffff;
}
.bd-day.is-selected {
  background: #ffffff;
  border-color: #ffffff;
  color: var(--rf-primary-deep, #453A8C);
}
.bd-day:disabled {
  /* A closed day (weekend / fully booked) — disabled controls are exempt from
     the contrast minimums, and 0.38 reads unmistakably "off". */
  opacity: 0.38;
  cursor: default;
}
.bd-day-dow { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; }
.bd-day-num { font-size: 20px; font-weight: 700; }
.bd-day-mon { font-size: 12px; font-weight: 500; }

/* ── Time grid ── */
.bd-times {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 10px;
}
.bd-time {
  position: relative;
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.2;
  color: #ffffff;
  background: transparent;
  border: 1.5px solid rgba(255, 255, 255, 0.62);
  border-radius: 10px;
  padding: 11px 8px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.bd-time:hover:not(:disabled):not(.is-selected) {
  background: rgba(255, 255, 255, 0.1);
  border-color: #ffffff;
}
.bd-time.is-selected {
  background: #ffffff;
  border-color: #ffffff;
  color: var(--rf-primary-deep, #453A8C);
}
.bd-time.is-taken {
  /* Gone, and says so: struck time + the tag, not colour alone. */
  border-style: dashed;
  border-color: rgba(255, 255, 255, 0.32);
  color: rgba(255, 255, 255, 0.55);
  text-decoration: line-through;
  cursor: default;
}
.bd-time-tag {
  display: block;
  margin-top: 2px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: rgba(255, 255, 255, 0.55);
}

.bd-tz-note {
  margin: clamp(14px, 2vh, 20px) 0 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: #E4DEFF;
}

.bd-loading {
  margin: 8px 0 0;
  font-size: 15px;
  color: #E4DEFF;
}
.bd-grid-fail p {
  margin: 8px 0 14px;
  font-size: 15px;
  color: #FCA5A5;
}

.bd-error {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: #FCA5A5;
}

/* Per-field validation message — the Signup `su-err` pattern, sitting inside
   its .bd-field under the input. Kept distinct from .bd-error, which stays
   the SUBMIT-level line (slot/server problems). */
.bd-field-err {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.4;
  color: #FCA5A5;
}

/* ── Confirm — the panel's inverted primary (white fill, deep ink) ── */
.bd-confirm {
  font-family: inherit;
  width: 100%;
  font-size: clamp(17px, 2vw, 20px);
  font-weight: 700;
  color: var(--rf-primary-deep, #453A8C);
  background: #ffffff;
  border: 1.5px solid #ffffff;
  border-radius: 14px;
  padding: clamp(16px, 2.2vw, 22px);
  cursor: pointer;
  box-shadow: 0 16px 34px -16px rgba(0, 0, 0, 0.7);
  transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.12s ease;
}
.bd-confirm:hover:not(:disabled) {
  background: #F1EFFC;
  border-color: #F1EFFC;
  box-shadow: 0 18px 38px -16px rgba(0, 0, 0, 0.75);
}
.bd-confirm:active:not(:disabled) { transform: translateY(1px); }
.bd-confirm:disabled { opacity: 0.7; cursor: default; }

/* Shared focus ring — white-based on the violet, the panel's own ring. */
.bd-pill:focus-visible,
.bd-day:focus-visible,
.bd-time:focus-visible,
.bd-confirm:focus-visible {
  outline: 3px solid rgba(255, 255, 255, 0.55);
  outline-offset: 3px;
}

/* ── Confirmation view ── */
.bd-done {
  max-width: 640px;
  margin: 0 auto;
  padding-top: clamp(24px, 8vh, 80px);
  text-align: center;
}
.bd-done-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: #ffffff;
  color: var(--rf-primary-deep, #453A8C);
  margin-bottom: clamp(18px, 3vh, 28px);
}
.bd-done-check svg { width: 30px; height: 30px; }
.bd-done-when {
  margin: clamp(14px, 2.4vh, 22px) 0 0;
  font-size: clamp(17px, 2.2vw, 21px);
  font-weight: 600;
  color: #ffffff;
}
.bd-done-ref {
  margin: 8px 0 0;
  font-size: 14px;
  letter-spacing: 0.04em;
  color: #E4DEFF;
}
.bd-done-note {
  margin: clamp(16px, 2.6vh, 24px) auto 0;
  max-width: 46ch;
  font-size: 15.5px;
  line-height: 1.6;
  color: #E4DEFF;
}
.bd-done-home {
  width: auto;
  min-width: 220px;
  margin-top: clamp(24px, 4vh, 40px);
}
/* ===========================================================================
   RentfyvLoader — hollow wordmark that fills with a rising liquid, then the
   view zooms into a single letter as the overlay clears.

   ⚠️ THE FILL IS TWO STACKED COPIES OF THE WORD, NOT AN ALPHA RAMP.
   The brief's sketch raised the fill by animating `color: rgba(..., progress)`,
   which fades the WHOLE word up uniformly — every letter lightening together.
   That reads as a fade-in, not as a loading bar. What was asked for was a fill
   that "feels continuous and smooth, like a loading bar", i.e. a wipe that
   travels across the wordmark.

   So: a solid mid-grey copy of the word sits underneath (the empty vessel),
   and an identical WHITE copy is stacked exactly on top and revealed by a clip that RISES from
   the bottom — every letter at the same level at the same time. The glyphs
   align because the markup is identical. This also keeps the stroke crisp at
   every stage; with an alpha ramp the stroke and fill wash into each other
   around 50%.

   The clip's top edge is a travelling sine wave, rewritten each frame by the
   component, which is what makes it read as liquid rather than as a ruled
   line sliding up.

   Tailwind utilities from the sketch, mapped:
     fixed inset-0 z-[9999] flex items-center justify-center → .rl-overlay
     bg-gradient-to-b from-black via-zinc-900 to-black       → the gradient below
     text-5xl md:text-7xl font-extrabold                     → .rl-letter
     [-webkit-text-stroke-width:2px]                         → .rl-outline .rl-letter
     [-webkit-text-stroke-color:#9ca3af]                     → same (gray-400)
   ========================================================================= */

.rl-overlay {
  --rl-fill: 0;                    /* 0 → 1, written each frame by the component */
  --rl-zoom: 1;                    /* scale during the zoom phase */
  --rl-zoom-ms: 800ms;
  --rl-fade-ms: 500ms;

  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Sampled off the reference capture: near-black field, mid-grey unfilled
     glyph, pure white liquid. */
  background: #141414;
  overflow: hidden;                /* the zoom must not create a scrollbar */
  opacity: 1;
  transition: opacity var(--rl-fade-ms) cubic-bezier(0.4, 0, 0.2, 1);
}
.rl-overlay.is-leaving {
  opacity: 0;
  pointer-events: none;
}

/* The thing that zooms. `transform-origin` is set inline from the measured
   centre of the target letter, so scaling drives straight into that glyph
   rather than into the middle of the word.

   ⚠️ THE TRANSLATE IS WHY THE ZOOM LANDS IN THE MIDDLE OF THE SCREEN
   (operator: "it is zoomed into R which is on left side but it should have to
   zoomed in middle"). Scaling about the R's centre keeps that point FIXED, and
   the R sits at the left of the wordmark — so the word flew apart around a
   pivot near the left edge and the final full-frame moment happened off to one
   side. `--rl-dx/--rl-dy` carry the R's centre to the stage's centre over the
   same transition, so it glides in as it grows.

   ⚠️ TWO ELEMENTS, BECAUSE THE TWO MOTIONS NEED DIFFERENT TIMING. Composed on
   one element they share one `transform` transition, and the zoom's easing is
   deliberately ease-IN (slow, then a hard push through the glyph) — so the
   centring inherited that curve and the R barely moved until the last moments,
   by which point the overlay is already fading out. Measured: the glyph centre
   was still at 0.28 of the viewport a third of the way in and only reached
   0.44 as it disappeared. The travel now runs on its own shorter ease-OUT and
   is finished before the push begins.

   ⚠️ TRANSLATE ON THE OUTER, SCALE ON THE INNER — in that nesting order. The
   outer's translate is applied in the PARENT's space, unscaled, which is what
   lets the offset be a plain measured length instead of something that has to
   be divided by the zoom. Nest them the other way and the travel is multiplied
   by 9.

   Everything defaults to identity, so with no JS the loader still reads. */
.rl-stage {
  position: relative;
  transform: translate(var(--rl-dx, 0px), var(--rl-dy, 0px));
  transition: transform var(--rl-centre-ms, 440ms) cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

/* `transform-origin` is set inline from the measured centre of the target
   letter, so the scale drives straight into that glyph. */
.rl-zoom {
  position: relative;   /* anchors .rl-word--fill's `position: absolute` */
  transform: scale(var(--rl-zoom));
  transition: transform var(--rl-zoom-ms) cubic-bezier(0.55, 0, 0.85, 0.35);
  will-change: transform;
}

/* Both layers share this. The fill copy is laid exactly over the outline copy,
   so every glyph registers to the pixel. */
.rl-word {
  display: flex;
  gap: clamp(4px, 0.9vw, 12px);
  white-space: nowrap;
}
.rl-word--fill {
  position: absolute;
  inset: 0;
  /* THE LIQUID LINE RISES; it does not sweep across.
     A left-to-right wipe fills letter by letter — R first, V last. What was
     asked for is every letter at the same level together (all at 10%, then all
     at 20%), which is a level rising through the whole word at once, i.e. a
     clip from the TOP down: inset(<unfilled> 0 0 0).

     This declaration is the fallback and the reduced-motion shape. In normal
     use the component overwrites clip-path each frame with a polygon whose top
     edge is a travelling sine wave, so the surface ripples like a liquid
     instead of being a ruled line. */
  clip-path: inset(calc((1 - var(--rl-fill)) * 100%) 0 0 0);
}

.rl-letter {
  display: inline-block;
  font-family: 'Manrope', system-ui, 'Segoe UI', Roboto, sans-serif;
  font-weight: 800;
  /* Big enough to read as a logo rather than a caption. 13vw keeps all seven
     letters on one line down to ~360px; the 10rem cap stops it overwhelming a
     wide desktop. */
  font-size: clamp(3rem, 13vw, 10rem);
  line-height: 1.15;               /* room for the stroke, which draws outside the em box */
  letter-spacing: 0.02em;
  transition: opacity 420ms ease;
}

/* The UNFILLED word. Measured from the reference: a solid mid-grey glyph on a
   near-black field — not a hollow outline. An outline reads as a wireframe and
   gives the liquid nothing to fill; a solid grey letter reads as an empty
   vessel, which is what makes the rising white look like liquid inside it.

   The stroke is kept as an opt-in (set --rl-stroke to e.g. 2px) because the
   earlier brief asked for a hollow treatment; at 0 it costs nothing. */
.rl-word--outline .rl-letter {
  color: var(--rl-empty, #424242);
  -webkit-text-fill-color: var(--rl-empty, #424242);
  -webkit-text-stroke-width: var(--rl-stroke, 0);
  -webkit-text-stroke-color: var(--rl-stroke-color, #9ca3af);
}

/* Fill copy: solid, no stroke — the outline underneath supplies the edge. */
.rl-word--fill .rl-letter {
  color: var(--rl-liquid, #ffffff);            /* reference samples pure white */
  -webkit-text-fill-color: var(--rl-liquid, #ffffff);
}

/* "loading... N %" — sits under the right end of the word, as in the
   reference. Deliberately OUTSIDE .rl-stage so the zoom does not drag it
   across the screen; it just fades with the overlay. */
.rl-readout {
  position: absolute;
  right: clamp(16px, 5vw, 64px);
  bottom: clamp(16px, 6vh, 56px);
  display: flex;
  gap: 0.5em;
  align-items: baseline;
  font-family: 'Manrope', system-ui, sans-serif;
  font-size: clamp(11px, 1.1vw, 14px);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #f3f4f6;
  transition: opacity 300ms ease;
}
.rl-readout-pct { font-variant-numeric: tabular-nums; min-width: 3.2em; text-align: right; }
/* Gone the moment the zoom starts — the number has done its job. */
.rl-overlay.is-zooming .rl-readout { opacity: 0; }

/* During the zoom every letter except the target drops away, so the focus is
   unmistakably on one glyph rather than on a word flying past. */
.rl-overlay.is-zooming .rl-letter[data-target='false'] { opacity: 0; }

/* --- Reduced motion ------------------------------------------------------
   The fill stays: it is the progress indication, and it is a clip, not motion
   across the screen. The ZOOM goes — a 9x scale is the largest movement this
   app makes, and it is exactly what this preference is about. The overlay
   still fades, since that is what reveals the page. */
@media (prefers-reduced-motion: reduce) {
  .rl-stage,
  .rl-zoom { transform: none; transition: none; }
  /* !important beats the per-frame inline polygon: the ripple is decorative
     motion, the level is the information. */
  .rl-word--fill { clip-path: inset(calc((1 - var(--rl-fill)) * 100%) 0 0 0) !important; }
  .rl-overlay.is-zooming .rl-letter[data-target='false'] { opacity: 1; }
}
/* RntlOS AI assistant — floating chat widget.
 * Matches the marketing site: near-black surfaces (#0A0A0A / #0F0F12),
 * hairline #1F1F1F borders, white text, and the brand's purple→blue neon
 * signature (#A06EFF → #5A82FF). Motion is transform/opacity-only and
 * fully disabled under prefers-reduced-motion. */

.rfy-chat {
  --rfy-bg: #0a0a0a;
  --rfy-bg-2: #0f0f12;
  --rfy-line: #1f1f1f;
  --rfy-line-2: #2a2a2a;
  --rfy-text: #fafafa;
  --rfy-dim: #9ca3af;
  --rfy-faint: #6b7280;
  --rfy-purple: #a06eff;
  --rfy-blue: #5a82ff;
  --rfy-grad: linear-gradient(135deg, #a06eff 0%, #6b7bff 50%, #5a82ff 100%);
  /* Grey logo/avatar (the launcher button + teaser avatar) — the visible
     "chatbot logo". Kept separate from the purple UI accents inside the panel. */
  --rfy-logo: linear-gradient(135deg, #74747e 0%, #565660 50%, #45454e 100%);
  --rfy-logo-ring: #8a8a92;

  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 2147483000;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 16px;
  font-family: system-ui, 'Segoe UI', Roboto, sans-serif;
}

/* ===== Launcher ===== */
.rfy-chat-launcher-wrap {
  position: relative;
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); /* spring-back from magnetic pull */
  align-self: flex-end;
}
.rfy-chat-launcher {
  position: relative;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.14);
  cursor: pointer;
  color: #fff;
  background: var(--rfy-logo);
  display: grid;
  place-items: center;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s ease;
  animation: rfyBreathe 3.6s ease-in-out infinite;
}
.rfy-chat-launcher:hover { transform: scale(1.1); }
.rfy-chat-launcher:active { transform: scale(0.94); }
.rfy-chat-launcher-icon {
  display: grid; place-items: center;
  /* Spin the inner glyph as it swaps bot → X on open/close. */
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.rfy-chat-launcher-icon.is-close { transform: rotate(180deg); }

/* Neon breathing — animates the glow only (box-shadow), never layout. */
@keyframes rfyBreathe {
  0%, 100% { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45), 0 0 0 0 rgba(150, 150, 160, 0); }
  50%      { box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5), 0 0 34px 2px rgba(150, 150, 160, 0.5); }
}

/* Attention ping ring — only until the user first opens the widget. */
.rfy-chat-ping {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--rfy-logo-ring);
  animation: rfyPing 2.2s cubic-bezier(0, 0, 0.2, 1) infinite;
  pointer-events: none;
}
@keyframes rfyPing {
  0%   { opacity: 0.7; transform: scale(1); }
  70%  { opacity: 0;   transform: scale(1.55); }
  100% { opacity: 0;   transform: scale(1.55); }
}

/* ===== Panel (ring container) ===== */
/* The panel is now just the 1.5px-padded frame that holds the rotating gradient
   ring; the real card surface lives in .rfy-chat-inner. */
.rfy-chat-panel {
  position: relative;
  box-sizing: border-box;
  width: min(376px, calc(100vw - 32px));
  height: min(560px, calc(100vh - 130px));
  padding: 2px; /* the gap where the animated ring shows as a border */
  color: var(--rfy-text);
  border-radius: 20px;
  overflow: hidden; /* clip the rotating ring to the rounded box */
  box-shadow:
    0 30px 70px rgba(0, 0, 0, 0.6),
    0 0 0 1px rgba(255, 255, 255, 0.03);
  transform-origin: bottom right;
  animation: rfyPanelIn 0.42s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes rfyPanelIn {
  from { opacity: 0; transform: translateY(20px) scale(0.92); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* Rotating gradient border ring — a bright purple→blue arc sweeps around the
   panel. The inner card covers the centre, so only the 1.5px padding reveals
   it as an animated border. Angle is animated via a registered custom property
   so the sweep is smooth (no element rotation → no corner gaps). */
@property --rfy-ring-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
.rfy-chat-ring {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  background: conic-gradient(from var(--rfy-ring-angle),
    rgba(160, 110, 255, 0.12),
    #a06eff,
    #5a82ff,
    #6b7bff,
    rgba(90, 130, 255, 0.12),
    rgba(160, 110, 255, 0.12));
  animation: rfyRingSpin 6s linear infinite;
}
@keyframes rfyRingSpin { to { --rfy-ring-angle: 360deg; } }

/* The actual card surface — holds all the content + decorative layers. */
.rfy-chat-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--rfy-bg);
  border-radius: 18px; /* 20px panel − 2px padding so corners align */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); /* glass edge highlight */
}

/* Animated dark gradient sheen — drifts behind everything. */
.rfy-chat-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(135deg, #1a1a1f 0%, #0a0a0a 50%, #131318 100%);
  background-size: 200% 200%;
  animation: rfyBgShift 20s ease-in-out infinite;
}
@keyframes rfyBgShift {
  0%   { background-position: 0% 0%; }
  50%  { background-position: 100% 100%; }
  100% { background-position: 0% 0%; }
}

/* Floating particle field — small dots rise from the bottom and fade out. */
.rfy-chat-particles {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.rfy-chat-particle {
  position: absolute;
  bottom: -8px;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: rgba(190, 170, 255, 0.5);
  box-shadow: 0 0 6px rgba(160, 110, 255, 0.5);
  opacity: 0;
  animation: rfyParticle var(--dur, 6s) linear infinite;
  animation-delay: var(--delay, 0s);
}
@keyframes rfyParticle {
  0%   { transform: translate(0, 0); opacity: 0; }
  12%  { opacity: 0.9; }
  85%  { opacity: 0.45; }
  100% { transform: translate(var(--dx, 0), -480px); opacity: 0; }
}

/* Soft brand glow blob bleeding from the top of the panel. */
.rfy-chat-glow {
  position: absolute;
  top: -40%;
  left: 50%;
  width: 150%;
  height: 70%;
  transform: translateX(-50%);
  background: radial-gradient(ellipse 50% 60% at 50% 50%,
    rgba(124, 110, 255, 0.22), rgba(90, 130, 255, 0.08) 45%, transparent 70%);
  pointer-events: none;
  animation: rfyGlowDrift 9s ease-in-out infinite;
}
@keyframes rfyGlowDrift {
  0%, 100% { opacity: 0.7; transform: translateX(-52%) scale(1); }
  50%      { opacity: 1;   transform: translateX(-48%) scale(1.08); }
}

/* ===== Header (glass) ===== */
.rfy-chat-head {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 13px 14px;
  background: rgba(15, 15, 18, 0.7);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--rfy-line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.rfy-chat-id { display: flex; align-items: center; gap: 11px; }
.rfy-chat-avatar {
  width: 36px; height: 36px; border-radius: 12px;
  display: grid; place-items: center; color: #fff;
  background: var(--rfy-grad);
  box-shadow: 0 0 18px rgba(124, 110, 255, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.rfy-chat-id-text { display: flex; flex-direction: column; line-height: 1.2; gap: 1px; }
.rfy-chat-name { font-weight: 600; font-size: 14px; letter-spacing: -0.01em; }
.rfy-chat-status { font-size: 11px; color: var(--rfy-dim); display: flex; align-items: center; gap: 6px; }
.rfy-chat-dot {
  position: relative;
  width: 7px; height: 7px; border-radius: 50%;
  background: #34d399;
}
.rfy-chat-dot::after {
  content: ''; position: absolute; inset: -3px; border-radius: 50%;
  border: 1.5px solid #34d399; animation: rfyPing 2s ease-out infinite;
}
.rfy-chat-actions { display: flex; align-items: center; gap: 8px; }
/* Model chip (e.g. "Llama 3.3") — neutral grey pill, parallels the
   reference's model label. */
.rfy-chat-model {
  font-size: 11px;
  font-weight: 600;
  color: var(--rfy-dim);
  letter-spacing: 0.01em;
  white-space: nowrap;
}
/* "Pro" badge — small accent pill. */
.rfy-chat-pro {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #fff;
  background: linear-gradient(135deg, #ff5a5f, #e0353b);
  border-radius: 6px;
  padding: 2px 7px;
  box-shadow: 0 2px 8px rgba(224, 53, 59, 0.35);
}
.rfy-chat-iconbtn {
  background: transparent; border: none; cursor: pointer;
  color: var(--rfy-dim);
  width: 32px; height: 32px; border-radius: 9px;
  display: grid; place-items: center;
  transition: background 0.18s ease, color 0.18s ease, transform 0.18s ease;
}
.rfy-chat-iconbtn:hover { background: rgba(255, 255, 255, 0.07); color: var(--rfy-text); }
.rfy-chat-iconbtn:active { transform: scale(0.9); }
.rfy-chat-iconbtn.is-on { color: var(--rfy-purple); }

/* ===== Messages ===== */
.rfy-chat-body {
  position: relative;
  z-index: 1;
  flex: 1;
  overflow-y: auto;
  padding: 18px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  scrollbar-width: thin;
  scrollbar-color: var(--rfy-line-2) transparent;
}
.rfy-chat-body::-webkit-scrollbar { width: 8px; }
.rfy-chat-body::-webkit-scrollbar-thumb { background: var(--rfy-line-2); border-radius: 4px; }

.rfy-msg {
  max-width: 85%;
  padding: 10px 13px;
  border-radius: 16px;
  font-size: 13.5px;
  line-height: 1.55;
  white-space: pre-wrap;
  word-wrap: break-word;
  animation: rfyMsgIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: calc(min(var(--i, 0), 8) * 35ms);
}
@keyframes rfyMsgIn {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.rfy-msg--assistant {
  align-self: flex-start;
  background: #16161a;
  border: 1px solid var(--rfy-line);
  color: #e9e9ee;
  border-bottom-left-radius: 5px;
}

/* Contact line inside the greeting bubble */
.rfy-chat-contact {
  display: block;
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid var(--rfy-line);
  font-size: 12.5px;
  color: var(--rfy-dim);
}
.rfy-chat-contact a { color: var(--rfy-purple); font-weight: 600; text-decoration: none; }
.rfy-chat-contact a:hover { text-decoration: underline; }

/* Quick actions (shown under the greeting before the chat starts): a 2-col
   icon grid of suggested questions, a dashed FAQ row, then the demo CTA. */
.rfy-chat-quick {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}
.rfy-chat-quickgrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.rfy-chat-action {
  display: flex;
  align-items: center;
  gap: 9px;
  background: var(--rfy-bg-2);
  border: 1px solid var(--rfy-line-2);
  border-radius: 12px;
  padding: 12px 12px;
  color: var(--rfy-text);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.2;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.1s ease;
}
.rfy-chat-action:hover { border-color: var(--rfy-purple); background: rgba(160, 110, 255, 0.1); }
.rfy-chat-action:active { transform: translateY(1px); }
.rfy-chat-action-ic { flex: none; width: 18px; height: 18px; display: grid; place-items: center; color: var(--rfy-purple); }
.rfy-chat-faq {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: transparent;
  border: 1px dashed var(--rfy-line-2);
  border-radius: 12px;
  padding: 12px;
  color: var(--rfy-dim);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.rfy-chat-faq:hover { border-color: var(--rfy-purple); color: var(--rfy-text); }
.rfy-chat-faq-ic { display: grid; place-items: center; color: var(--rfy-purple); }
/* Ghost button — same effect as the site's Login button: transparent with an
   outline at rest, a white circle fills on hover and the label turns black. */
.rfy-chat-book {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: transparent;
  border: 1px solid var(--rfy-line-2);
  border-radius: 14px;
  padding: 14px;
  color: var(--rfy-text);
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition: color 0.28s ease, border-color 0.32s ease, transform 0.15s ease;
}
.rfy-chat-book::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #ffffff;
  transform: translate(-50%, -50%) scale(0);
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: -1;
  pointer-events: none;
}
.rfy-chat-book:hover {
  transform: translateY(-1px);
  color: #0a0a0a;
  border-color: #ffffff;
}
.rfy-chat-book:hover::before { transform: translate(-50%, -50%) scale(1); }
.rfy-chat-book:active { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .rfy-chat-book::before { transition: none; }
}
.rfy-chat-book-arrow { display: grid; place-items: center; transition: transform 0.2s ease; }
.rfy-chat-book:hover .rfy-chat-book-arrow { transform: translateX(3px); }
.rfy-chat-book-note { text-align: center; font-size: 11.5px; color: var(--rfy-faint); margin: 1px 0 0; }

/* Proactive nudge bubble next to the launcher (shown while chat is closed).
   The wrap supplies the 3D perspective + a gentle idle float; the bubble
   itself tilts toward the cursor (see useCardTilt). */
.rfy-chat-teaser-wrap {
  perspective: 700px;
  animation: rfyTeaserFloat 4.5s ease-in-out infinite;
}
.rfy-chat-teaser {
  position: relative;
  max-width: 250px;
  /* Translucent "glass": see-through with a soft blur, but tinted dark enough
     that the light text + icon stay readable over any background. */
  background: rgba(18, 18, 22, 0.5);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  color: #fafafa;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 16px;
  border-bottom-right-radius: 6px;
  padding: 12px 14px;
  box-shadow: 0 18px 44px -14px rgba(0, 0, 0, 0.55);
  transform-style: preserve-3d;
  will-change: transform;
  /* Smooth follow + ease-back for the cursor tilt. */
  transition: transform 0.15s ease-out;
  animation: rfyTeaserIn 0.35s cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Cursor-following glare, revealed only while tilting. */
.rfy-chat-teaser-glare {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease-out;
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%),
    rgba(160, 110, 255, 0.28), transparent 46%);
}
.rfy-chat-teaser.is-tilting .rfy-chat-teaser-glare { opacity: 1; }
@keyframes rfyTeaserFloat {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-5px); }
}
.rfy-chat-teaser::after {
  content: "";
  position: absolute;
  bottom: -5px;
  right: 24px;
  width: 12px;
  height: 12px;
  background: rgba(18, 18, 22, 0.5);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  border-right: 1px solid rgba(255, 255, 255, 0.16);
  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
  transform: rotate(45deg);
  border-radius: 0 0 3px 0;
}
.rfy-chat-teaser-body {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.rfy-chat-teaser-ic {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: #fff;
  background: var(--rfy-logo);
}
.rfy-chat-teaser-x {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #16161a;
  color: #fff;
  border: 1px solid var(--rfy-line-2);
  display: grid;
  place-items: center;
  cursor: pointer;
  z-index: 1;
}
.rfy-chat-teaser-x:hover { background: #222; }
@keyframes rfyTeaserIn {
  from { opacity: 0; transform: translateY(10px) scale(0.95); }
  to   { opacity: 1; transform: none; }
}

.rfy-msg--user {
  align-self: flex-end;
  background: var(--rfy-grad);
  color: #fff;
  border-bottom-right-radius: 5px;
  box-shadow: 0 4px 16px rgba(124, 110, 255, 0.3);
}
.rfy-msg--error {
  background: rgba(248, 113, 113, 0.1);
  color: #fca5a5;
  border: 1px solid rgba(248, 113, 113, 0.3);
}

/* Typing indicator */
.rfy-msg--typing { display: flex; gap: 5px; align-items: center; padding: 14px 15px; }
.rfy-msg--typing span {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--rfy-faint);
  animation: rfyBlink 1.3s infinite ease-in-out;
}
.rfy-msg--typing span:nth-child(2) { animation-delay: 0.18s; }
.rfy-msg--typing span:nth-child(3) { animation-delay: 0.36s; }
@keyframes rfyBlink {
  0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }
  40%           { opacity: 1; transform: translateY(-3px); }
}

/* ===== Composer ===== */
.rfy-chat-foot {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border-top: 1px solid var(--rfy-line);
  background: rgba(15, 15, 18, 0.7);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

/* Input "card" — textarea on top, toolbar row beneath, all inside one
   rounded bordered box (matches the reference). */
.rfy-chat-composer {
  background: #131316;
  border: 1px solid var(--rfy-line);
  border-radius: 16px;
  padding: 12px 12px 10px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.rfy-chat-composer:focus-within {
  border-color: rgba(160, 110, 255, 0.55);
  box-shadow: 0 0 0 3px rgba(160, 110, 255, 0.14);
}
.rfy-chat-input {
  width: 100%;
  display: block;
  resize: none;
  max-height: 120px;
  background: transparent;
  border: 0;
  color: var(--rfy-text);
  padding: 0;
  font-size: 14px;
  font-family: inherit;
  line-height: 1.5;
  outline: none;
}
.rfy-chat-input::placeholder { color: var(--rfy-faint); }

.rfy-chat-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
}
.rfy-chat-tools { display: flex; align-items: center; gap: 4px; }
.rfy-chat-tool {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: 9px;
  border: 0;
  background: transparent;
  color: var(--rfy-dim);
  padding: 0;
}
.rfy-chat-tool--btn { cursor: pointer; transition: background 0.18s ease, color 0.18s ease; }
.rfy-chat-tool--btn:hover { background: rgba(255, 255, 255, 0.07); color: var(--rfy-text); }
.rfy-chat-tool--btn.is-on { color: var(--rfy-purple); background: rgba(160, 110, 255, 0.12); }

.rfy-chat-toolbar-right { display: flex; align-items: center; gap: 10px; }
.rfy-chat-count {
  font-size: 11px;
  color: var(--rfy-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Send button — WHITE per request, dark icon. */
.rfy-chat-send {
  position: relative;
  overflow: hidden;
  flex: none;
  width: 38px; height: 38px;
  border: none; border-radius: 11px; cursor: pointer;
  color: #0a0a0a;
  background: #ffffff;
  display: grid; place-items: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
  transition: transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.18s ease, box-shadow 0.2s ease;
}
.rfy-chat-send:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 7px 20px rgba(0, 0, 0, 0.45); }
.rfy-chat-send:active:not(:disabled) { transform: scale(0.92); }
.rfy-chat-send:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }
.rfy-chat-send svg { position: relative; z-index: 1; }

/* Ripple injected on click */
.rfy-ripple {
  position: absolute;
  border-radius: 50%;
  /* Dark tint so the ripple reads on the white send button. */
  background: rgba(0, 0, 0, 0.18);
  transform: scale(0);
  animation: rfyRipple 0.55s ease-out;
  pointer-events: none;
}
@keyframes rfyRipple { to { transform: scale(2.4); opacity: 0; } }

@media (max-width: 480px) {
  .rfy-chat { right: 14px; bottom: 14px; }
  .rfy-chat-panel { height: min(74vh, calc(100vh - 100px)); }
}

@media (prefers-reduced-motion: reduce) {
  .rfy-chat-launcher,
  .rfy-chat-ping,
  .rfy-chat-glow,
  .rfy-chat-ring,
  .rfy-chat-bg,
  .rfy-chat-particle,
  .rfy-chat-dot::after,
  .rfy-msg,
  .rfy-msg--typing span { animation: none !important; }
  .rfy-chat-panel,
  .rfy-chat-teaser,
  .rfy-chat-teaser-wrap { animation: none; }
  .rfy-chat-teaser { transition: none; }
  .rfy-chat-launcher-wrap { transition: none; }
}
/* Fixed, full-viewport, non-interactive layer for the Antigravity ring.
   z-index 300 sits above the page section backgrounds (which top out at ~60) so
   the ring is visible in the dark areas, but below the positioned top UI — nav
   (1000), loader (9999), modals (100000), chat widget (2147483000). The canvas
   is transparent (gl alpha) so only the particles render over the dark page. */
.antigravity-bg {
  position: fixed;
  inset: 0;
  /* ⚠️ z-index -1 MADE THIS LAYER UNRENDERABLE ON THE LIGHT THEME, EVERYWHERE.
     A negative-z fixed element paints above the ROOT background but below every
     in-flow descendant's background, so it only ever showed through sections
     that had no ground of their own. That was true of the old dark page; it is
     not true now. Measured: `.home-light-band` is `rgb(245,245,245)` at
     `z-index: 1; position: relative`, and `.home-stats-strip` is `rgb(255,255,255)`
     — opaque grounds covering the whole requested range, so the canvas was
     rendering a live, uncorrupted WebGL context into a layer nothing could see.
     Back to 300, which is what the comment above always described: above every
     page section (they top out at ~60) and below nav (1000), loader (9999),
     modals (100000) and the chat widget. The layer is pointer-events:none, so
     raising it intercepts nothing.
     Making the section grounds transparent instead was the alternative and is
     far worse — it would mean unpicking the light theme's backgrounds across
     the stats strip, both bands, the demo band and the FAQ. */
  z-index: 300;
  pointer-events: none;
  /* Sparse 1.2px points, so this is a haze rather than a wash. Kept low because
     it now sits OVER content: see the colour note in AntigravityBackground.jsx
     for why the particle colour had to change too. */
  opacity: 0.34;
}
/* Out of the allowed scroll range (see AntigravityBackground.jsx). Faded rather
   than unmounted so the WebGL context survives; `visibility` follows so the
   canvas leaves the compositor's work list once it is invisible.
   ⚠️ The transition is on OPACITY ONLY — adding `visibility` to it would make
   the fade-out jump, since visibility is not an interpolatable property and
   would flip at the start of the transition rather than the end. It is covered
   by the shorthand's default `visibility 0s` at the end instead. */
.antigravity-bg {
  transition: opacity 420ms ease;
}
.antigravity-bg.is-out {
  opacity: 0;
  visibility: hidden;
  transition: opacity 420ms ease, visibility 0s linear 420ms;
}

.antigravity-bg canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

@media (prefers-reduced-motion: reduce) {
  /* The animation is mouse-driven and idle-only; hide it entirely for users
     who prefer reduced motion. */
  .antigravity-bg { display: none; }
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  /* Theme body face, with the old system stack as the literal fallback so a
     missing token still renders a real sans (per the theme.css var() rule). */
  font-family: var(--rf-font-body, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.app-container {
  min-height: 100vh;
}

.app-nav {
  display: flex;
  gap: 10px;
  padding: 16px;
  background: #1A1A1A;
  border-bottom: 1px solid #2A2A2A;
}

.app-nav button {
  padding: 8px 16px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: #9CA3AF;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
}

.app-nav button:hover {
  color: #FAFAFA;
  background: #2A2A2A;
}

.app-nav button.active {
  color: #FAFAFA;
  background: #0A0A0A;
}
  }
}

#next-steps {
  display: flex;
  border-top: 1px solid var(--border);
  text-align: left;

  & > div {
    flex: 1 1 0;
    padding: 32px;
    @media (max-width: 1024px) {
      padding: 24px 20px;
    }
  }

  .icon {
    margin-bottom: 16px;
    width: 22px;
    height: 22px;
  }

  @media (max-width: 1024px) {
    flex-direction: column;
    text-align: center;
  }
}

#docs {
  border-right: 1px solid var(--border);

  @media (max-width: 1024px) {
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
}

#next-steps ul {
  list-style: none;
  padding: 0;
  display: flex;
  gap: 8px;
  margin: 32px 0 0;

  .logo {
    height: 18px;
  }

  a {
    color: var(--text-h);
    font-size: 16px;
    border-radius: 6px;
    background: var(--social-bg);
    display: flex;
    padding: 6px 12px;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    transition: box-shadow 0.3s;

    &:hover {
      box-shadow: var(--shadow);
    }
    .button-icon {
      height: 18px;
      width: 18px;
    }
  }

  @media (max-width: 1024px) {
    margin-top: 20px;
    flex-wrap: wrap;
    justify-content: center;

    li {
      flex: 1 1 calc(50% - 8px);
    }

    a {
      width: 100%;
      justify-content: center;
      box-sizing: border-box;
    }
  }
}

#spacer {
  height: 88px;
  border-top: 1px solid var(--border);
  @media (max-width: 1024px) {
    height: 48px;
  }
}

.ticks {
  position: relative;
  width: 100%;

  &::before,
  &::after {
    content: '';
    position: absolute;
    top: -4.5px;
    border: 5px solid transparent;
  }

  &::before {
    left: 0;
    border-left-color: var(--border);
  }
  &::after {
    right: 0;
    border-right-color: var(--border);
  }
}
/* ============================================================
   Global hover interactions — site-wide (Website marketing app)
   --------------------------------------------------------------
   1. Buttons gently SCALE UP on hover (and press-in on :active).
   2. Cards HIGHLIGHT on hover (brighter border + soft outer glow).

   Design notes:
   - Buttons use an ELEMENT-level selector so any component that already
     defines its own :hover transform (e.g. the chat launcher's scale 1.1)
     keeps it — `transform` isn't additive across rules, so the more
     specific rule wins. We only fill the gap for buttons that had none.
   - Excluded from the button transition: `.watch-play` (its transform is
     driven per-frame by JS — a CSS transition would make it lag), the
     self-contained chat widget (`rfy-chat-*`, has its own interactions), and
     anything opting out via `.no-hover-scale`.
   - `.no-hover-scale` exists because `transform` is NOT additive: a button that
     uses transform for POSITIONING (e.g. the password Show/Hide toggle, centred
     with `translateY(-50%)`) would have that centring replaced by `scale(1.05)`
     on hover and visibly jump out of its field. Such buttons opt out here.
   - Cards highlight WITHOUT transform (border + box-shadow only) so the
     effect composes cleanly with tilted / GSAP-animated cards that own
     their transform. Tilted pricing cards + the moving testimonial cards
     already have their own richer hover, so they're intentionally omitted.
   - Loaded last (see main.jsx) so it wins specificity ties.
   - All motion is gated behind prefers-reduced-motion: no-preference.
   ============================================================ */

/* ---------- Buttons: subtle grow on hover ---------- */
@media (prefers-reduced-motion: no-preference) {
  /* Transition — raised specificity (`button[class]`) so it wins over the
     component-level `transition` most CTAs already declare; otherwise the
     scale would snap. We re-list the common hover props (background/color/
     border) so existing colour transitions still animate. */
  button[class]:not(.watch-play):not(.no-hover-scale):not([class*="rfy-chat"]):not(:disabled):not([aria-disabled="true"]),
  button:not([class]):not(:disabled):not([aria-disabled="true"]) {
    transition: transform 180ms cubic-bezier(0.2, 0.7, 0.2, 1),
                box-shadow 180ms ease,
                filter 180ms ease,
                background-color 180ms ease,
                color 180ms ease,
                border-color 180ms ease;
  }
  /* Hover/active scale — element-level so any component that defines its own
     :hover transform (chat launcher, etc.) still wins and keeps its effect. */
  button:not(.watch-play):not(.no-hover-scale):not([class*="rfy-chat"]):not(:disabled):not([aria-disabled="true"]):hover {
    transform: scale(1.05);
  }
  button:not(.watch-play):not(.no-hover-scale):not([class*="rfy-chat"]):not(:disabled):not([aria-disabled="true"]):active {
    transform: scale(0.97);
  }
}

/* ---------- Cards: highlight on hover ----------
   Transform-free highlight = brighter border ring + soft outer glow + a
   faint cool halo. Works on every page regardless of theme-token scope
   because the colours are literal. */
.tool-card,
.blog-card,
.demo-contact-card,
.qc-card,
.pricing-cta-card,
.stat-card,
.sstat-card {
  transition: border-color 200ms ease,
              box-shadow 200ms ease,
              background-color 200ms ease;
}

.tool-card:hover,
.blog-card:hover,
.demo-contact-card:hover,
.qc-card:hover,
.pricing-cta-card:hover,
.stat-card:hover,
.sstat-card:hover {
  border-color: rgba(255, 255, 255, 0.22);
  box-shadow:
    0 16px 44px rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(255, 255, 255, 0.06),
    0 0 26px rgba(150, 155, 170, 0.12);
}
