/* Page-level styles for Couple home */
@import url('colors_and_type.css');

/* box-sizing site-wide; plus kill the blue tap-highlight flash on touch
   devices (every interactive element). Focus-visible outlines + our own
   hover/active states still convey interaction. */
* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

/* Every raster <img> carries intrinsic width/height attrs for CLS reservation
   (the image right-sizing ruling). This base rule makes those attrs safe: it
   forces height to track the aspect ratio, so an HTML `height` attribute can
   never impose a fixed pixel height on a responsive (width:100% / aspect-ratio)
   image and squash it tall-and-skinny. Author rules that set an explicit height
   (fixed object-fit boxes, sized avatars) still win by specificity. */
img {
  height: auto;
}

/* Nav links — resting ink, brand purple on hover */
.nav-link,
.nav-sublink {
  transition: color 140ms ease;
}
.nav-link:hover,
.nav-sublink:hover {
  color: var(--couple-purple) !important;
  text-decoration: none !important;
}
.nav-sublink:hover {
  background: var(--lavender);
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--white);
  font-family: 'Nunito', system-ui, sans-serif;
  /* Default body text on white = ink-700 per the Guide. Headings reach for
     ink-600 (#444) explicitly via .h1-hero / .h2-section / heading classes. */
  color: var(--fg-primary);
  /* Clip, NOT hidden. `overflow-x: hidden` coerces the other axis to
     `overflow-y: auto`, which turns <body> into a scroll container and
     hijacks the document scroll away from the viewport — silently killing
     every `position: sticky` descendant (the ≤640 Profile/Account section
     titles, the Plans pricing pill, the Referral action bar). `clip` crops
     horizontal overflow identically but is a NON-scrolling value, so
     overflow-y stays `visible`, the root keeps the scroll, and sticky pins
     correctly. (Was the latent bug behind the Jun 15 sticky-title ruling;
     fixed globally Jun 15 2026 — supersedes the per-page clip patches.) */
  overflow-x: clip;
  max-width: 100vw;
}

/* Section helper widths */
.page { width: 100%; max-width: 1440px; margin: 0 auto; background: var(--white); position: relative; }

/* Mobile-only line break helper */
.br-mobile-only { display: none; }
@media (max-width: 640px) {
  .br-mobile-only { display: inline; }
}

/* Announcement ribbon — copy variants. The ribbon (sections.jsx Banner)
   renders BOTH spans; desktop/tablet shows the full string, ≤640 swaps to
   the shorter mobile string. The plate itself is auto-height (min 44) and
   the nav MEASURES it for its offset — never assume 44. */
.top-banner-short { display: none; }
@media (max-width: 640px) {
  .top-banner-full  { display: none; }
  .top-banner-short { display: inline; }
}

/* ============= RESPONSIVE ============= */

/* Convention: the responsive ladders below write gutters/padding/gap as raw px
   (80/64/56/48/40/20 …) rather than var(--space-*), even though those values
   map exactly onto the --space-* scale. This is deliberate — below the fluid
   range the ≤1100 guard pins the token max-anchors anyway, and keeping the
   overrides as literals avoids forcing a global stylesheet cache-bust on every
   gutter retune. A production port should fold these back onto var(--space-*)
   once the inline-style / !important split is removed. */

/* ---------- Wide tablet / small laptop — 1100 and below ---------- */
@media (max-width: 1100px) {
  /* Sections — pull in the 90px desktop gutters */
  .section { padding: 80px 56px !important; }
  .hero-padded { padding: 0 56px !important; }

  /* Hero — keep split, tighten gap and copy padding */
  .hero-grid { gap: 40px !important; }

  /* Pricing — stays 3-col but with tighter padding inside cards.
     (S4: the .plan-name 30px pin here was redundant — --type-display-sm is
     guard-pinned to its 30 max-anchor across the whole ≤1100 band already.) */
  .plan-card { padding: 28px !important; }

  /* Vibe — single-row scroll-snap carousel at this band too */
  .vibe-grid {
    display: flex !important;
    flex-direction: row !important;
    grid-template-columns: unset !important;
    gap: 20px !important;
    overflow-x: auto !important;
    scroll-snap-type: x mandatory !important;
    scroll-padding-left: 56px !important;
    -webkit-overflow-scrolling: touch !important;
    padding: 4px 56px 16px !important;
    margin: 0 -56px !important;
    scrollbar-width: none !important;
  }
  .vibe-grid::-webkit-scrollbar { display: none !important; }
  .vibe-grid > .vibe-card {
    flex: 0 0 280px !important;
    scroll-snap-align: start !important;
  }
  .vibe-arrows { display: none !important; }

  /* Nav */
  .nav-bar { padding: 0 28px !important; }
  .nav-list { gap: 28px !important; }
}

/* Home marquee H1 — step from 60 down to the universal 48 across the
   tablet band. Scoped ≥641: below that the MOBILE DISPLAY TIER
   (colors_and_type.css) owns it — --fs-display-xl re-pins at 38. */
@media (max-width: 1100px) and (min-width: 641px) {
  .h1-hero--marquee { font-size: 48px !important; letter-spacing: -0.8px !important; }
}

/* ---------- Pricing 2-col-with-featured-on-top extended up to 1060 ---------- */
@media (max-width: 1060px) and (min-width: 769px) {
  .pricing-grid {
    grid-template-columns: repeat(2, 1fr) !important;
    gap: 20px !important;
  }
  .pricing-grid > .plan-card:first-child {
    grid-column: 1 / -1 !important;
  }
}

/* ---------- Mid tablet — 900 and below ---------- */
@media (max-width: 900px) {
  .section { padding: 64px 48px !important; }
  .hero-padded { padding: 0 48px !important; }
  /* Tablet hero — DS Guide says hero top drops from 128 → 96 here. */
  .hero-section { padding-top: 96px !important; }

  /* Hero — still split here; tighten further */
  .hero-grid { gap: 32px !important; grid-template-columns: 1fr 1fr !important; }

  /* Features — 2-col */
  .features-grid { grid-template-columns: repeat(2, 1fr) !important; gap: 20px !important; }

  /* Pricing — 2-col with the featured (first) plan spanning both columns on top.
     Active for 769–1060 via dedicated rule above, and from 768 down to whatever
     the next pricing rule overrides at — kept here for legacy. */
  .pricing-grid {
    grid-template-columns: repeat(2, 1fr) !important;
    gap: 20px !important;
  }
  .pricing-grid > .plan-card:first-child {
    grid-column: 1 / -1 !important;
  }

  /* Closing CTA — reorder like mobile: headline → polaroids → button (centered).
     No overhang at this width; polaroids sit inside the purple, above the button. */
  .closing-grid {
    display: flex !important;
    flex-direction: column !important;
    align-items: stretch !important;
    overflow: hidden !important;
    padding: 56px !important;
    gap: 24px !important;
    margin-bottom: 0 !important;
  }
  .closing-grid > .closing-copy { display: contents !important; }
  .closing-headline {
    text-align: left !important;
    order: 1 !important;
    margin: 0 !important;
  }
  .closing-grid .polaroid-wrap {
    order: 2 !important;
    justify-self: center !important;
    align-self: center !important;
    margin: 0 auto !important;
    --p-scale: 0.62 !important;
    height: calc(280px * 0.62) !important;
    position: relative !important;
  }
  .closing-grid .polaroid-trio {
    --p-scale: 0.62 !important;
    transform: translateX(9px) scale(var(--p-scale)) !important;
    transform-origin: center !important;
  }
  .closing-grid .closing-btn {
    order: 3 !important;
    margin: 4px auto 0 !important;
    /* inline-flex, not block — block wiped out .btn-pill's own
       align-items:center/justify-content:center, leaving the label
       riding high in the fixed-height pill instead of centered. */
    display: inline-flex !important;
  }

  /* Vibe — horizontal scroll-snap carousel at tablet widths too.
     Single row, fixed card widths so titles don't crush. Mirrors the
     mobile carousel pattern but with bigger cards. */
  .vibe-grid {
    display: flex !important;
    flex-direction: row !important;
    grid-template-columns: unset !important;
    gap: 16px !important;
    overflow-x: auto !important;
    scroll-snap-type: x mandatory !important;
    scroll-padding-left: 48px !important;
    -webkit-overflow-scrolling: touch !important;
    padding: 4px 48px 16px !important;
    margin: 0 -48px !important;
    scrollbar-width: none !important;
  }
  .vibe-grid::-webkit-scrollbar { display: none !important; }
  .vibe-grid > .vibe-card {
    flex: 0 0 260px !important;
    scroll-snap-align: start !important;
  }
  .vibe-arrows { display: none !important; }
}

/* ---------- Features — keep 1-col until 700 ---------- */
@media (max-width: 700px) {
  .features-grid { grid-template-columns: 1fr !important; gap: 20px !important; }
}

/* ---------- Narrow tablet — 768 and below (above mobile 640) ---------- */
@media (max-width: 768px) {
  .section { padding: 64px 40px !important; }
  .hero-padded { padding: 0 40px !important; }

  /* Hero stacks here. Polaroid moves between the headline/copy and the
     CTA block ("Answer a few questions…"), so we use display:contents on
     .hero-copy to flatten its two halves into the grid and reorder. */
  .hero-grid { grid-template-columns: 1fr !important; gap: 28px !important; }

  .hero-copy { display: contents !important; }
  .hero-copy-top { order: 1; }
  .hero-polaroid-col { order: 2; padding: 8px 12px 0 !important; overflow: visible !important; display: flex !important; justify-content: center !important; }
  .hero-copy-bot { order: 3; margin-top: 16px !important; }

  .hero-section {
    padding-bottom: 56px !important;
    margin-bottom: 0 !important;
    border-radius: 0 0 32px 32px !important;
    overflow: hidden !important;
  }
  .hero-polaroid-box {
    margin-bottom: 0 !important;
    max-width: calc(min(100vw - 72px, 460px) * 0.7) !important;
    margin-left: auto !important;
    margin-right: auto !important;
    position: relative !important;
    z-index: 1 !important;
  }

  /* Closing CTA — reorder like mobile: headline → polaroids → button (centered).
     No overhang here; polaroids sit inside the purple, above the button. */
  .closing-grid {
    display: flex !important;
    flex-direction: column !important;
    align-items: stretch !important;
    overflow: hidden !important;
    padding: 56px !important;
    gap: 24px !important;
  }
  .closing-grid > .closing-copy { display: contents !important; }
  .closing-headline {
    text-align: left !important;
    order: 1 !important;
    margin: 0 !important;
  }
  .closing-grid .polaroid-wrap {
    order: 2 !important;
    justify-self: center !important;
    align-self: center !important;
    margin: 0 auto !important;
  }
  .closing-grid .closing-btn {
    order: 3 !important;
    margin: 4px auto 0 !important;
    display: inline-flex !important;
  }

  /* Find-your-person — stack columns; image+copy stays readable */
  .find-grid { grid-template-columns: 1fr !important; gap: 40px !important; }

  /* Pricing — single column, constrained max-width and centered */
  .pricing-grid {
    grid-template-columns: 1fr !important;
    max-width: 540px !important;
    margin: 0 auto !important;
  }
  .pricing-grid > .plan-card:first-child {
    grid-column: auto !important;
  }

  /* Closing CTA already 1-col from 900; just tighten padding */
  .closing-grid { padding: 48px 40px !important; }
}

/* Mobile — 640 and below */
@media (max-width: 640px) {
  /* Sections — full bleed, 20px gutters */
  .section { padding: 56px 20px !important; }
  .hero-section {
    padding-top: 96px !important;
    padding-bottom: 56px !important;
    margin-bottom: 0 !important;
    overflow: hidden !important;
    /* border-radius lives in the "Softer container radii" group below. */
  }
  .hero-padded { padding: 0 20px !important; }

  /* Stack everything. Polaroid sits between the headline/copy and the
     "Answer a few questions…" CTA block — see the @media 768 rule for the
     display:contents + order reorder. */
  .hero-grid { grid-template-columns: 1fr !important; gap: 28px !important; }

  .hero-polaroid-col { padding: 8px 12px 0 !important; overflow: visible !important; justify-content: center !important; }
  .hero-polaroid-box {
    max-width: calc(min(100vw - 40px, 460px) * 0.65) !important;
    margin: 0 auto !important;
    position: relative !important;
    z-index: 1 !important;
  }
  .find-grid { grid-template-columns: 1fr !important; gap: 56px !important; }
  .features-grid { grid-template-columns: 1fr !important; gap: 16px !important; }
  .pricing-grid { grid-template-columns: 1fr !important; gap: 16px !important; max-width: none !important; }
  .closing-grid { grid-template-columns: 1fr !important; gap: 24px !important; padding: 40px 28px !important; }
  .vibe-header { flex-direction: column !important; align-items: flex-start !important; gap: 20px !important; }

  /* Vibe — horizontal scroll-snap carousel on mobile.
     Full-bleed (negative margin cancels section gutter) so cards drag to
     the screen edges, but padding-left + scroll-padding hold the first
     card at the page gutter on initial render. */
  .vibe-grid {
    display: flex !important;
    flex-direction: row !important;
    grid-template-columns: unset !important;
    gap: 14px !important;
    overflow-x: auto !important;
    scroll-snap-type: x mandatory !important;
    scroll-padding-left: 20px !important;
    -webkit-overflow-scrolling: touch !important;
    padding: 4px 20px 16px 20px !important;
    margin: 0 -20px !important;
    scrollbar-width: none !important;
  }
  .vibe-grid > .vibe-card {
    flex: 0 0 min(78%, 280px) !important;
  }
  .vibe-grid::-webkit-scrollbar { display: none; }
  .vibe-grid > * { scroll-snap-align: start !important; }
  .vibe-arrows { display: none !important; }

  /* Type — mobile scale. The MOBILE DISPLAY TIER (colors_and_type.css)
     re-pins every fluid --fs-* token at its min-anchor value below 641,
     so token-routed type (marquee 38, h1-hero 30, h2-section 24,
     lead/body-lg 18, eyebrow-lg 16) needs no per-class step here.
     S4 RECONCILIATION (the type tier is the only mobile type lever):
     the surviving per-class px deviations were retired so everything
     routes through the tier — feature/vibe card titles now take the h3
     tier (18), plan-name the display-sm tier (20), and vibe-sub the frozen
     body-sm (16, not the improvised 14). The feature-body / plan-tagline /
     plan-feature pins were no-ops (their recipes are already the fixed 16).
     Only the marquee TRACKING stays — letter-spacing, not size. */
  .h1-hero--marquee { letter-spacing: -0.5px !important; } /* tracking eases as the tier takes 60→38 */

  /* Announcement ribbon — caption-size all-caps so the mobile copy holds
     1–2 lines on the auto-height plate (nav offset is measured). */
  .top-banner {
    font-size: 14px !important;
    line-height: 1.4 !important;
    letter-spacing: 0.06em !important;
    padding: 12px 20px !important;
  }

  /* Coming Soon pill stays single-line */
  .coming-soon-pill {
    font-size: 12px !important;
    padding: 4px 8px !important;
    white-space: nowrap !important;
  }

  /* Hero copy */
  .hero-copy { gap: 28px !important; }
  .hero-copy-top { padding-top: 0 !important; }

  /* Closing CTA — left-justify text, then polaroids, then button.
     On mobile we flatten the inner copy block (display: contents) so the
     headline, polaroid stack, and button all become siblings of the grid
     container and can be ordered: headline → polaroids → button.
     Polaroids sit INSIDE the purple, above the centered button. */
  .closing-grid {
    display: flex !important;
    flex-direction: column !important;
    align-items: stretch !important;
    overflow: hidden !important;
    padding: 40px 28px !important;
    margin-bottom: 0 !important;
  }
  .closing-grid .polaroid-wrap {
    margin: 0 auto !important;
    position: relative !important;
  }
  .closing-grid > .closing-copy { display: contents !important; }
  .closing-headline {
    /* font-size comes from the tier (h2-section → 24); only the wrap
       behavior is page-owned here. */
    line-height: 1.2 !important;
    text-wrap: pretty !important;
    word-break: normal !important;
    overflow-wrap: break-word !important;
    max-width: 100% !important;
    text-align: left !important;
    order: 1 !important;
  }
  .closing-grid .polaroid-wrap { order: 2 !important; }
  .closing-grid .closing-btn {
    order: 3 !important;
    margin-top: 4px !important;
    margin-left: auto !important;
    margin-right: auto !important;
    display: inline-flex !important;
  }

  /* Polaroid stacks — fluid scale based on viewport width.
     The trio's three cards are visually offset right of their box center,
     so we shift the box right ~9px (in trio coords) to center the cluster. */
  .polaroid-trio {
    --p-scale: 0.62;
    position: relative !important;
    width: 360px !important;
    height: 280px !important;
    margin: 0 auto !important;
    transform: translateX(9px) scale(var(--p-scale)) !important;
    transform-origin: center !important;
    flex-shrink: 0 !important;
  }
  /* Reserve only the visual height (scaled). Trio is 280px tall, scaled. */
  .polaroid-wrap {
    --p-scale: 0.62;
    height: calc(280px * var(--p-scale) + 30px) !important;
    overflow: visible !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    width: 100% !important;
  }

  /* Nav — swap to hamburger */
  .nav-bar {
    padding: 0 16px !important;
    height: 56px !important;
    width: calc(100% - 16px) !important;
  }
  .nav-list { display: none !important; }
  .nav-hamburger { display: grid !important; }

  /* Feature cards — tighter padding when 2-up on mobile */
  .feature-card-body { padding: 16px !important; gap: 12px !important; }
  .feature-card-play { width: 40px !important; height: 40px !important; }
  .feature-card-play svg { width: 14px !important; height: 14px !important; }

  /* Buttons — never wrap label. All sizes collapse to 56px on mobile. */
  .btn-pill { height: 56px !important; font-size: 18px !important; padding: 0 24px !important; white-space: nowrap !important; }
  .btn-pill--lg { font-size: 18px !important; padding: 0 28px !important; }

  /* Softer container radii on mobile — desktop radii feel oversized at this width. */
  .hero-section { border-radius: 0 0 22px 22px !important; }
  .feature-card { border-radius: 16px !important; }
  .plan-card { border-radius: 16px !important; }
  .closing-grid { border-radius: 22px !important; }
  .nav-bar { border-radius: 16px !important; }
  .find-frame { border-radius: 8px !important; }
  /* The menu is a full-screen takeover at ≤640 (Modal Takeover ruling) —
     no corner radius when it meets every viewport edge. */
  .mobile-menu-panel { border-radius: 0 !important; }
}

/* ============================================================
   HERO SHELL — the lavender, rounded-bottom signature
   Used on Home, Plans, Welcome, Matches, Match Detail, Events, Event Detail.
   Funnel pages (Intake, Get to Know You, Referral, Event Invite,
   Checkout, Confirmation) deliberately skip this — see the DS Guide's
   "Lavender anchors every marketing hero" principle.

   One class carries the base recipe + the responsive padding-top /
   padding-bottom progression every hero used to duplicate inline. Page-
   specific classes (wc-hero-shell, md-hero-shell, etc.) survive as
   scoping hooks for sibling rules (.wc-hero-pad, .md-hero-grid) — but
   they no longer redeclare the shell rhythm.
   ============================================================ */
.hero-shell-lavender {
  position: relative;
  overflow: hidden;
  background: var(--lavender);
  /* S2 — desktop rhythm + radius route through the fluid --rhythm-* tokens
     (1366↔1920 ramps + the height-density rule). The ≤1100 rules below
     keep using fixed --space-* steps; the token guard pins the base values
     there anyway, so tablet/mobile stay pixel-identical. */
  border-radius: 0 0 var(--rhythm-hero-radius) var(--rhythm-hero-radius);
  padding-top: var(--rhythm-hero-top);
  padding-bottom: var(--rhythm-80);
  color: var(--couple-purple-dark);
  /* Plate Width ruling (Jun 2026) — the plate overhangs the 1440 content/
     nav cap by --shell-overhang per side (0 at ≤1456, 40 at ≥1536, then
     frozen). The inner hero pads are all maxWidth:1440 + auto margins, so
     copy stays exactly where it was — only the lavender grows. The footer
     shell carries the identical overhang; the two plates flex together
     (same curve, same width).
     NB: this geometry needs width:auto to widen under negative margins.
     Inside the .intake-page flex wrapper an explicit width:100% breaks it
     (overhang collapses into a left shift) — see the
     `.intake-page > section.hero-shell-lavender` exception in the INTAKE
     PAGE block before reusing this pattern in a new wrapper. */
  margin-inline: calc(-1 * var(--shell-overhang));
}
@media (max-width: 1100px) {
  .hero-shell-lavender { padding-top: var(--space-128); padding-bottom: var(--space-64); }
}
@media (max-width: 900px) {
  .hero-shell-lavender { padding-top: var(--space-96); padding-bottom: var(--space-56); }
}
@media (max-width: 768px) {
  .hero-shell-lavender { padding-top: var(--space-96); }
}
@media (max-width: 640px) {
  .hero-shell-lavender {
    padding-top: var(--space-96);
    padding-bottom: var(--space-48);
    /* Softer mobile radius — desktop's 44px feels oversized at 640. */
    border-radius: 0 0 var(--r-card) var(--r-card);
  }
}

/* ============================================================
   HOME HERO COMPOSITION (Fluid Scale Migration · S2, retuned S2b)
   ============================================================
   The polaroid stack is width-driven (100% of its column, 1:1 aspect), so
   its fixed 460px cap set the hero's height at every desktop width. Since
   S2b both tokens ride the ELEMENT SCALE (--el-px — see colors_and_type.css)
   rather than bespoke clamps: the design values stay legible below and the
   ramp, the ≤1100 guard, and any future
   retune are owned by the one tier token. Consumed inline in sections.jsx's
   <Hero>. Not height-keyed: the rhythm tokens already trim the shell on
   short viewports; the artwork itself should not also shrink.
   @1536: stack ≈ 403px, min-height ≈ 473px. */
:root {
  --home-hero-minh:     calc(540 * var(--el-px));
  --home-polaroid-maxw: calc(460 * var(--el-px));
}

/* .hero-shell-plain — the deliberate alternative for the pages
   that opt out of the lavender hero: Match Detail (×3, the single-profile
   browsing exception) and the single-document reading surfaces (Privacy,
   Terms, the Vibe article) where the user is studying ONE thing.
   NB: Profile + Account left this family on Jun 18 2026 (the
   settings-destination amendment) — they're returned-to destinations, not
   linear funnels, so their mastheads moved to .hero-shell-lavender while
   their bodies stay white. Referrals followed on the same day, so the whole
   old account trio now rides the lavender band (see the Guide's
   settings-destination ruling).
   Same horizontal rhythm + identical responsive padding-top as the
   lavender shell so the nav-to-content cadence is unchanged, but no
   tinted plate, no rounded corners — the page stays on white the
   whole way and the photo + name carry the visual weight. Bottom
   padding is intentionally tighter than the lavender shell because
   there is no color change for the eye to "land" on; the body
   section's own top padding owns the gap below. See the "Lavender
   anchors every marketing hero" principle in the Design System
   Guide for the rationale. */
.hero-shell-plain {
  position: relative;
  /* No `overflow: hidden` — unlike the lavender shell, this one has no
     rounded corners and no decorative children to clip, so we let
     shadows from the photo (--shadow-elevated) and the Pair CTA
     (--shadow-cta purple glow) extend naturally past the shell's
     bottom edge into the body section without being chopped. */
  background: var(--white);
  /* S2 — deliberately NOT fluid: 96 here is nav clearance (the floating nav
     bottoms out ~90px), already at the system floor — there is no room to
     flex down. The white heroes keep their fixed cadence; revisit per-page
     in the S4/S5 sweeps if a page proves otherwise. */
  padding-top: var(--space-96);
  padding-bottom: var(--space-32);
  color: var(--fg-primary);
}
@media (max-width: 1100px) {
  .hero-shell-plain { padding-top: var(--space-80); padding-bottom: var(--space-24); }
}
@media (max-width: 900px) {
  .hero-shell-plain { padding-top: var(--space-64); padding-bottom: var(--space-16); }
}
@media (max-width: 640px) {
  .hero-shell-plain { padding-top: var(--space-56); padding-bottom: var(--space-8); }
}
/* TRAP: the padding-TOP ramp above (80/64/56) drops BELOW the ~80px floating
   nav, so the bare base shell clips its topmost element under the nav on
   tablet/mobile. Every consumer therefore re-pins padding-top to the
   --space-96 nav-clearance floor at ≤900 (compound selector beats these
   single-class rules): .rf-hero, .legal-hero,
   .va-hero-shell, and .md-hero-shell. (.acct-hero / .profile-hero left this
   family Jun 18 2026 — they ride .hero-shell-lavender now, which carries its
   own ≤900 floor; their redundant overrides are harmless and equal.) The
   padding-BOTTOM ramp stays in
   effect (consumers override top only). Any NEW .hero-shell-plain consumer
   MUST add the same ≤900 padding-top:96 override. */

/* ============================================================
   MATCH DETAIL — shared rhythm
   Three files (match-detail / -sarah / -james) used to redeclare these
   responsive rules inline. Pulled here so the three pages stay in lockstep.
   ============================================================ */
.md-back-link { transition: color 160ms ease, gap 160ms ease; }
.md-back-link:hover { color: var(--couple-purple-hover) !important; gap: 12px !important; }

@media (max-width: 1100px) {
  .md-hero-pad   { padding: 0 56px !important; }
  .md-shell      { padding: 80px 56px 96px !important; }
  .md-hero-grid  { gap: 48px !important; }
}
@media (max-width: 900px) {
  /* Nav clearance — Match Detail was the lone .hero-shell-plain consumer
     missing the documented "96px tablet/mobile" hero-top override its
     siblings (Account/Profile/Referrals/Legal/Vibe Article) all carry.
     The base shell ramps DOWN to 64/56 below 900, but the floating nav
     bottoms out ~90px, so the "Back to matches" row — the hero's topmost
     element — slid under the nav. Re-pin to the 96 nav-clearance floor;
     compound selector beats the shared shell's single-class responsive
     rules (incl. its ≤640 56px), matching legal.css's documented approach. */
  .hero-shell-plain.md-hero-shell { padding-top: var(--space-96); }
  .md-hero-pad   { padding: 0 48px !important; }
  .md-shell      { padding: 64px 48px 80px !important; }
  .md-hero-grid  { grid-template-columns: 1fr !important; gap: 40px !important; }
  .md-hero-photo { max-width: 420px !important; }
}
@media (max-width: 768px) {
  .md-hero-pad   { padding: 0 40px !important; }
  .md-shell      { padding: 56px 40px 80px !important; }
}
@media (max-width: 640px) {
  .md-hero-pad      { padding: 0 20px !important; }
  .md-shell         { padding: 48px 20px 64px !important; }
  .md-h1            { font-size: 38px !important; letter-spacing: -1px !important; }
  .md-h2            { font-size: 30px !important; }
  /* Chemistry ring — mobile display tier. The .score-num re-pins to its
     24px min-anchor here (MOBILE DISPLAY TIER, colors_and_type.css) but
     --el-px does not, so the hero ring box stayed frozen at 112px: the
     percentage shrank inside an unchanged ring and the oversized ring rode
     up over the eyebrow's status line. The fix is the --ring-scale lever
     (element-mass, not a per-class type pin — the Mobile chapter's rule).

     Jun 16 2026 (ring top-anchor + breathing-room amendment): the ring no
     longer centres on the name (top:50% was the desktop inline style); on
     mobile it TOP-ANCHORS to the name so its top edge lines up with the
     name's top edge (top:0 / transform:none, overriding the inline center).
     That alone clears the eyebrow (the ring can't extend above the name
     into the status line any more), so the old centred-ring clearance
     hack — margin-top:8px on .md-hero-name-with-ring — is removed and the
     eyebrow→name gap returns to the section's natural 24.
     And --ring-scale goes 0.63 → 0.75 (box ~71 → ~84px) WITHOUT touching
     the 24/18px numeral: at 0.63 the `%` sat ~2.7px off the ring stroke
     (touching); the numeral block is fixed px (font unchanged) so a larger
     box is the only lever that buys clearance — 0.75 opens it to ~9px. */
  .md-hero-ring          { --ring-scale: 0.75; top: 0 !important; transform: none !important; }
  .md-hero-actions  { flex-wrap: wrap !important; gap: 20px !important; flex-direction: row !important; align-items: center !important; }
  .md-hero-actions .btn-pill { flex: 1 1 auto !important; }
}

/* ============================================================
   EVENT DETAIL — shared rhythm
   Page chrome lifted out of event-detail.jsx's <style> block so
   responsive rules live next to the other page-level breakpoints
   (matches .md-* / .wc-* / .plans-* shape).
   ============================================================ */

/* Base — desktop. S3 — vertical paddings + the big section/host gaps ride
   the fluid rhythm ramps (96 horizontal gutter fixed, S2 precedent); the
   ≤1100 blocks below keep their fixed px and the token guard pins the
   max-anchor values there anyway. */
.edx-shell {
  padding: var(--rhythm-64) 96px var(--rhythm-96);
  max-width: 1440px;
  margin: 0 auto;
}
.edx-shell-inner {
  max-width: 1260px;
  display: flex; flex-direction: column;
  gap: var(--rhythm-128);
}
.edx-timeline {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column;
  gap: 32px;
  /* Expect-plate ruling (Jun 2026, amended) — the numbered steps are
     reading copy: the list caps at the 760 measure + the 88px disc gutter
     (64 disc column + 24 gap; the FAQ 808 arithmetic), and the lavender
     plate hugs this column (+80px insets) and centers on the frame.
     Same cap as .ev-expect-list / .ip-expect-list; the plate max-widths
     live inline beside each plate's padding. */
  max-width: calc(var(--measure-intro) + 88px);
}
.edx-host-grid {
  display: flex; flex-direction: column;
  gap: var(--rhythm-96);
}
.edx-checklist {
  list-style: none; padding: 0; margin: 0;
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 20px var(--rhythm-56);
}
@media (max-width: 1100px) {
  .edx-hero-pad { padding: 0 56px !important; }
  .edx-shell    { padding: 56px 56px 96px !important; }
  /* Event detail hero is slightly squatter than the other heroes —
     padding-bottom: 56 here instead of the universal 64. */
  .edx-hero-shell { padding-bottom: var(--space-56) !important; }

  .edx-shell-inner { gap: 96px; }
  .edx-hero-grid { gap: 48px !important; }
  .edx-expect-h2 { white-space: normal !important; text-wrap: balance !important; }

  /* Highlights — alternating two-column blocks at this width step. */
  .edx-highlight-grid { gap: 48px !important; }
  .edx-highlight-h    { font-size: 38px !important; }

  /* Lavender containers — mirrors .ev-expect-shell on the events page. */
  .edx-expect-shell,
  .edx-cta-shell { padding: 64px 56px !important; }
}
@media (max-width: 980px) {
  .edx-hero-grid {
    grid-template-columns: 1fr !important;
    gap: 40px !important;
  }
  .edx-hero-image img { max-width: 560px; margin: 0 auto; }
}
@media (max-width: 900px) {
  .edx-hero-pad { padding: 0 48px !important; }
  .edx-shell    { padding: 56px 48px 80px !important; }

  .edx-shell-inner { gap: 80px; }
  .edx-h1          { font-size: 48px !important; }
  .edx-host-grid   { gap: 64px !important; }
  .edx-host-name   { font-size: 38px !important; }
  .edx-host-row    { gap: 48px !important; }
  .edx-checklist   { grid-template-columns: 1fr !important; gap: 16px !important; }

  /* Highlights stack — media above copy, ignore reverse. */
  .edx-highlight-grid {
    grid-template-columns: 1fr !important;
    gap: 28px !important;
  }
  .edx-highlight-copy      { order: 2 !important; }
  .edx-highlight-media-col { order: 1 !important; }

  .edx-expect-shell,
  .edx-cta-shell { padding: 56px 40px !important; border-radius: var(--r-card-lg) !important; }
}
@media (max-width: 768px) {
  .edx-hero-pad { padding: 0 40px !important; }
  .edx-shell    { padding: 48px 40px 80px !important; }

  .edx-shell-inner { gap: 64px; }
  .edx-h1          { font-size: 38px !important; letter-spacing: -1px !important; }
  /* Hero fact pairs (DATE/TIME/WHERE/COST) stay flex-wrap per the
     label-over-value ruling — they don't become the Matches shared-baseline
     grid. But left-clustered at their natural width they bunched together
     and stranded the right of the row; growing each pair to a half-width
     basis flows them 2-up and spreads them across the available width
     (left edges align: Date/Where at 0, Time/Cost at the midline).
     Mobile reflow only; ≥769 (incl. the frozen ≥1920) is untouched. */
  .edx-facts-row   { gap: 24px 24px !important; }
  .edx-facts-row > * { flex: 1 1 40% !important; min-width: 0 !important; }
  .edx-host-row    { grid-template-columns: 1fr !important; gap: 28px !important; }
  .edx-host-copy      { order: 2 !important; }
  .edx-host-photo-col { order: 1 !important; }
  .edx-host-photo     { max-width: 360px !important; margin: 0 !important; }
}
@media (max-width: 640px) {
  .edx-hero-pad { padding: 0 20px !important; }
  .edx-shell    { padding: 40px 20px 64px !important; }

  .edx-shell-inner   { gap: 56px; }
  .edx-h1            { font-size: 38px !important; line-height: 1 !important; }
  /* "What to expect" steps reflow — the gradient number disc shrinks to a
     36px badge inline with the step title (row 1) and the body drops full
     width beneath (row 2), reclaiming the left gutter. Mirrors the Events
     page treatment. The content wrapper dissolves (display:contents) so the
     title and body place into the li grid independently; class-scoped so the
     body span (and the editor's data-om-text span) is never matched. */
  .edx-timeline-item {
    grid-template-columns: auto 1fr !important;
    grid-template-areas:
      "num  title"
      "body body" !important;
    column-gap: 12px !important;
    row-gap: 8px !important;
    align-items: center !important;
  }
  .edx-step-num {
    grid-area: num !important;
    width: 36px !important; height: 36px !important;
    margin-top: 0 !important;
    font-size: 18px !important;
  }
  .edx-step-content { display: contents !important; }
  .edx-step-title { grid-area: title !important; font-size: 20px !important; }
  .edx-step-body  { grid-area: body !important; }
  /* Tutorial CTA stacks on the phone: the question on top, the "Watch our
     tutorial" link + play button on its own line below — side by side the
     two halves wrapped awkwardly. Mirrors the Events page treatment. */
  .edx-tutorial-link { flex-direction: column !important; align-items: flex-start !important; gap: 6px !important; }
  .edx-host-name     { font-size: 30px !important; letter-spacing: -0.5px !important; }
  .edx-host-photo    { max-width: 280px !important; }
  .edx-highlight-h   { font-size: 30px !important; letter-spacing: -0.5px !important; }

  .edx-expect-shell,
  .edx-cta-shell { padding: 48px 24px !important; border-radius: var(--r-card) !important; }
}

/* ============= PLANS & PRICING PAGE =============
   Sticky plan nav. The hero itself rides on .hero-shell-lavender for
   its padding rhythm — only .plans-hero-specific bits (margin-bottom,
   compressed-viewport tweaks) live below. */

/* ============================================================
   STICKY IN-PAGE RAILS — Plans pill nav + FAQ category rail.
   ONE shared pattern (Mobile Review S3). Both pin near the fixed nav and
   stay at z-index 15 per the nav-layering ruling (below the nav's 20 so
   its dropdowns layer over them). Above 640 each rail is unchanged; the
   ≤640 MOBILE TREATMENT is shared and lives in one place at the bottom of
   this block: the pill chrome holds still and an inner *-track scrolls
   horizontally with a scroll-shadow edge-fade affordance. The TRACK is
   masked, never the pill — so the pill keeps its border / radius / shadow
   and the fade reveals the pill's own white bg. JS (the shared useRailFade
   hook in sections.jsx, consumed by plans.jsx + faq.jsx) toggles
   --fade-l/--fade-r (0|1) at the scroll ends;
   the default (right fade on) is honest at rest even with JS off.
   ============================================================ */

/* ---- FAQ category rail (lifted here from FAQ.html at S3 so both rails
        share this block; mirrors PlanNav — white pill, purple active fill,
        --shadow-card → --shadow-elevated when stuck; hugs the 808 reading
        column per the FAQ reading-column ruling) ---- */
.faq-rail {
  position: sticky;
  top: 92px;                 /* clears the fixed nav (16 top + 64 tall) */
  z-index: 15;               /* nav-layering ruling: below the nav (20) */
  max-width: 1440px;
  margin: 24px auto 0;
  padding: 0 96px;
  box-sizing: border-box;
}
.faq-rail-pill {
  display: flex;
  gap: 0;
  background: var(--white);
  border: var(--border-hairline) solid var(--ink-100);
  border-radius: var(--r-pill);
  padding: 8px;
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: box-shadow 200ms ease;
  max-width: 808px;          /* hugs the reading column below */
  margin-inline: auto;       /* reading-column centering ruling */
}
.faq-rail.is-stuck .faq-rail-pill { box-shadow: var(--shadow-elevated); }
.faq-rail-link {
  flex: 1 1 auto;            /* shares leftover space within the 808 pill */
  display: flex; align-items: center; justify-content: center;
  padding: 12px 16px;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  font: 700 16px/1 var(--font-display);
  color: var(--fg-heading);
  letter-spacing: -0.2px;
  white-space: nowrap;
  cursor: pointer;
  transition: background 200ms ease, color 200ms ease;
}
.faq-rail-link:hover { background: var(--lavender-hover); color: var(--couple-purple); }
.faq-rail-link.is-active { background: var(--couple-purple); color: var(--white); }
.faq-rail-link.is-active:hover { background: var(--couple-purple-hover); color: var(--white); }

/* ---- Plans pill nav (chrome is inline in plans.jsx) ---- */
.plan-nav-wrap { padding: 0 96px; }
.plan-nav-item:hover { background: var(--lavender-hover) !important; }
/* Active item carries .is-active; hover on the active item darkens it. */
.plan-nav-item.is-active:hover { background: var(--couple-purple-hover) !important; }

/* ---- Shared inner scroll track ----
   Above 640 it's a transparent pass-through: flex + full width, so items
   distribute exactly as before and nothing changes visually. */
.plan-nav-track, .faq-rail-track {
  display: flex;
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  gap: 0;
}

@media (max-width: 1100px) {
  .plan-nav-wrap { padding: 0 56px !important; }
  .faq-rail { padding: 0 56px; }
}
@media (max-width: 900px) {
  .plan-nav-wrap { padding: 0 48px !important; top: 80px !important; }
  .plan-nav-item { padding: 12px 12px !important; }
  .plan-nav-item span:first-child { font-size: 16px !important; }
  .plan-nav-item span:last-child { font-size: 12px !important; }
  .faq-rail { padding: 0 48px; top: 80px; }
  /* 5 variable-length topics no longer fit as equal segments — the track
     scrolls, items take their natural width. */
  .faq-rail-track { overflow-x: auto; scrollbar-width: none; }
  .faq-rail-track::-webkit-scrollbar { display: none; }
  .faq-rail-link { flex: 0 0 auto; font-size: 16px; padding: 12px 16px; }
}
@media (max-width: 768px) {
  .plan-nav-wrap { padding: 0 40px !important; }
  .faq-rail { padding: 0 40px; }
}
@media (max-width: 640px) {
  .plan-nav-wrap { padding: 0 16px !important; top: 80px !important; }
  .plan-nav { padding: 4px !important; }
  .faq-rail { padding: 0 16px; top: 80px; margin-top: 16px; }
  .faq-rail-pill { padding: 4px; }

  /* === SHARED MOBILE RAIL TREATMENT — horizontal scroll + edge-fade ===
     Mask the TRACK only; the pill keeps its chrome. --fade-l/--fade-r are
     0|1 multipliers JS toggles at the scroll ends; default = right fade on
     (overflow is present at rest) so the affordance is honest with JS off.
     The fade reveals the pill's own white bg — content slides under it. */
  .plan-nav-track, .faq-rail-track {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    --fade-l: 0;
    --fade-r: 1;
    --fade-w: 26px;
    -webkit-mask-image: linear-gradient(to right,
      transparent 0,
      #000 calc(var(--fade-l) * var(--fade-w)),
      #000 calc(100% - var(--fade-r) * var(--fade-w)),
      transparent 100%);
            mask-image: linear-gradient(to right,
      transparent 0,
      #000 calc(var(--fade-l) * var(--fade-w)),
      #000 calc(100% - var(--fade-r) * var(--fade-w)),
      transparent 100%);
  }
  .plan-nav-track::-webkit-scrollbar,
  .faq-rail-track::-webkit-scrollbar { display: none; }

  /* Items take natural width, never shrink, and snap to the left edge. */
  .plan-nav-item, .faq-rail-link {
    flex: 0 0 auto !important;
    scroll-snap-align: start;
  }

  /* ≥44px hit targets on both rails (nav-rail hit-target floor, S3). */
  .faq-rail-link { font-size: 14px; padding: 11px 16px; min-height: 44px; }
  .plan-nav-item {
    padding: 8px 14px !important;
    gap: 2px !important;
    min-height: 44px;
  }
  .plan-nav-item span:first-child { font-size: 14px !important; }
  .plan-nav-item span:last-child { font-size: 12px !important; letter-spacing: 0 !important; }

  /* When stuck (compact), match desktop pattern: hide price + dot, names
     only. Collapsed to short names the items no longer need their natural
     scroll width, so hand grow back (flex: 1 1 auto) to distribute the now-
     spare track width evenly — basis auto keeps natural width as the floor,
     so a phone too narrow for three names still scrolls instead of clipping.
     The edge-fade is recomputed only on track scroll/resize (not on the
     compact class flip), so its last --fade-r could linger over the now-
     filled third item — zero the fade width here so no stale mask shows; in
     the rare too-narrow case the track still scrolls, just without the
     affordance. */
  .plan-nav-wrap.is-stuck.compact .plan-nav-track { --fade-w: 0px; }
  .plan-nav-wrap.is-stuck.compact .plan-nav-item {
    flex: 1 1 auto !important;
    padding: 12px 10px !important;
    gap: 0 !important;
  }
  .plan-nav-wrap.is-stuck.compact .plan-nav-dot { display: none !important; }
  .plan-nav-wrap.is-stuck.compact .plan-nav-item .label {
    font-size: 12px !important;
    line-height: 1 !important;
    letter-spacing: -0.3px !important;
  }
  /* Fully remove the price line from the flow when collapsed so the label
     is true-centered within the purple active pill. */
  .plan-nav-wrap.is-stuck.compact .plan-nav-item .sub {
    display: none !important;
  }
}

@media (max-width: 1100px) {
  /* .plans-hero padding rhythm is owned by .hero-shell-lavender;
     only page-specific overrides land here. */
  .plans-hero-pad { padding: 32px 56px 0 !important; }
  .plans-h1 { letter-spacing: -0.8px !important; }
  .plan-block { padding: 64px 56px !important; }
  .plan-block-grid { gap: 48px !important; }
  .compare-section { padding: 32px 56px 64px !important; }
  .faq-section { padding: 32px 56px 64px !important; }
}

@media (max-width: 900px) {
  .plans-hero { margin-bottom: 56px !important; }
  .plans-hero-pad { padding: 24px 48px 0 !important; }
  .plan-block { padding: 56px 48px !important; }
  .plan-block-grid { grid-template-columns: 1fr !important; gap: 40px !important; }
  .plan-block-grid > div { order: unset !important; }
  /* Always copy first, mockup second on stacked */
  .plan-block-grid > div:first-child { order: 1 !important; }
  .plan-block-grid > div:last-child { order: 2 !important; }
  .faq-section { padding: 24px 48px 64px !important; }
  .faq-section h2 { font-size: 38px !important; }

  /* Compare table — hide on mobile (redundant with plan blocks above) */
  .compare-section { display: none !important; }
}

@media (max-width: 768px) {
  .plans-hero-pad { padding: 20px 40px 0 !important; }
  .plans-h1 { line-height: 1 !important; }
  .plans-sub { font-size: 18px !important; }
  .plan-block { padding: 48px 40px !important; }
  .plan-price-row .grad-purple { font-size: 48px !important; }

  /* .compare-section is display:none from @900 down — no need to
     size it at 768 or 640. */
  .faq-section { padding: 16px 40px 56px !important; }
  .faq-section h2 { font-size: 30px !important; }
}

@media (max-width: 640px) {
  .plans-hero { margin-bottom: 48px !important; }
  .plans-hero-pad { padding: 16px 20px 0 !important; }
  .plans-h1 { letter-spacing: -0.5px !important; } /* size from the tier (30) */
  .plan-block { padding: 40px 20px !important; }
  .plan-block-grid { gap: 32px !important; }
  .plan-price-row .grad-purple { font-size: 38px !important; }
  /* S4: .plan-name routes through --type-display-sm (tier → 20); the 24px
     pin here was the other half of the plan-name deviation, now retired.
     .plan-feature took the fixed body-sm (16) anyway — pin removed. */
  .plan-best-for { font-size: 18px !important; line-height: 1.5 !important; }

  .faq-section { padding: 8px 20px 48px !important; }
  .faq-section h2 { font-size: 30px !important; margin-bottom: 24px !important; }
}

/* ---------- Short-but-wide screens (e.g. 1080p @ 125% = 1536×864) ----------
   S2 RETIREMENT (Fluid Scale Migration, Jun 2026): this used to be a
   bespoke Plans-only compression block. Its vertical-rhythm work is now
   SYSTEM LAW — the height-density rule in colors_and_type.css shifts every
   --rhythm-* token one further step down on viewports ≤900px tall, and the
   Plans paddings route through those tokens (plans.jsx + the hero shell).
   What legitimately remains here: the sticky plan-nav top offset — chrome
   positioning, not rhythm; not expressible as a spacing token. (The
   legacy-scoped compressions that also lived here were deleted at S6 close
   with the review hooks.)
   The tokens own what each old compression line used to force:
     .plans-hero top      96px  ← --rhythm-hero-top density pin (96 flat)
     .plans-hero bottom   56px  ← --rhythm-80 density ramp (48↔64)
     .plan-block          48px  ← --rhythm-80 density ramp (48↔64)
     .plan-block-grid gap 56px  ← --rhythm-80 density ramp (48↔64)
     .compare-section     40/56 ← --rhythm-64 / --rhythm-80 density ramps
     .faq-section bottom  64px  ← --rhythm-80 density ramp
     micro-margins (eyebrow/best-for/price-row/h2/sub/hero-pad) — dropped:
     the two-step type ramp already compacts those lockups. */
@media (max-height: 900px) and (min-width: 1100px) {
  /* Sticky nav — closer to the top so it eats less vertical room */
  .plan-nav-wrap { top: 80px !important; }
}

/* ============= INTAKE PAGE ============= */
.intake-page {
  background: var(--white);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
/* Inside a flex column, the section children's inline `margin: 0 auto`
   would shrink them to natural width. Force full-width so they keep
   their existing inline max-width + padding behavior. */
.intake-page > section,
.intake-page > footer { width: 100%; }
/* Plate Width ruling — flex-wrapper exception. The explicit width:100%
   above stops the lavender hero plate's negative margin-inline from
   WIDENING the box: instead of overhanging both sides, the whole plate
   (content included) slid left by --shell-overhang (up to 40px at ≥1536)
   on every flex-wrapped page, while Home/Plans (block .page) stayed
   correct. Restore the symmetric geometry: 100% of the 1440 wrapper plus
   one overhang per side, so the pad re-centers and content stays pinned —
   identical to the block-context result. The footer plate needs no twin:
   .footer-shell sits inside a plain <footer>, where width:auto still
   widens under negative margins. */
.intake-page > section.hero-shell-lavender {
  width: calc(100% + 2 * var(--shell-overhang));
}
/* Push the footer to the bottom when content is short. */
.intake-page > section:last-of-type { flex: 1 0 auto; }
.intake-page > footer { flex: 0 0 auto; margin-top: auto; }

/* .h2-section — THE section-title voice. 38px Rubik 700, ink-600 on
   white. Per the Design System Guide: 60px is hero-display only, H2 =
   38px. Slap this class on any <h2> and you do not need to redeclare
   font / color / letter-spacing / margin inline. */
.h2-section {
  font: var(--type-h2);
  color: var(--fg-heading);
  letter-spacing: -0.5px;
  margin: 0;
  text-wrap: balance;
}
/* Lavender-surface modifier — swap the ink from white-bg #444 to
   lavender-bg #331C5C without each consumer overriding inline. Use on
   the "What to expect" sections, callout cards, etc. */
.h2-section--on-lavender { color: var(--couple-purple-dark); }

/* Two-column field grid */
.intake-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 32px 40px;
}

/* === Inputs === */
.intake-input {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  font: 500 18px/1.2 var(--font-body);
  color: var(--ink-700);
  background: transparent;
  outline: 0;
  transition: border-color 160ms ease, background 160ms ease, box-shadow 160ms ease;
}
.intake-input::placeholder { color: var(--lavender-placeholder); }

.intake-input--underline {
  border: 0;
  border-bottom: 2px solid var(--ink-200);
  border-radius: 0;
  padding: 12px 0;
}
.intake-input--underline:focus { border-bottom-color: var(--couple-purple); }

.intake-input--boxed {
  border: var(--border-hairline) solid var(--lavender-border);
  border-radius: var(--r-input);
  padding: 14px 16px;
  background: var(--white);
}
.intake-input--boxed:focus {
  border-color: var(--couple-purple);
  box-shadow: var(--shadow-focus);
}

/* === Field error state =====================================================
   ONE reusable error pattern for any text field on the site. Two pieces:
     1. `.intake-input--error` on the control — turns its border/underline red
        and swaps the focus ring to the red-tinted `--shadow-focus-error`.
        Works on both the --underline and --boxed variants and on the custom
        `.intake-select-trigger`.
     2. `.field-error` on a sibling <p> directly beneath — the message itself:
        red helper text, no icon (per the system), tucked tight under the field.
   The red is the brand's single warning color, --couple-red. Keep messages
   specific and human ("That doesn't look like an email — check for typos."),
   never a bare "Invalid input." See "Field error" in the Design System Guide.
   The bespoke .acct-pw-error rule is an earlier ancestor of this pattern
   and resolves to the same look (.np-mismatch retired -> .field-error--tight). */
.intake-input--error.intake-input--underline,
.intake-select-trigger.intake-input--error.intake-input--underline {
  border-bottom-color: var(--couple-red);
}
.intake-input--error.intake-input--boxed,
.intake-select-trigger.intake-input--error.intake-input--boxed {
  border-color: var(--couple-red);
}
.intake-input--error.intake-input--underline:focus { border-bottom-color: var(--couple-red); }
.intake-input--error.intake-input--boxed:focus {
  border-color: var(--couple-red);
  box-shadow: var(--shadow-focus-error);
}

.field-error {
  font: 500 14px/1.4 var(--font-body);
  color: var(--couple-red);
  margin: 8px 0 0;
}
/* When a field lives in a gap-spaced flex column (the common case — .rp-form,
   intake field stacks), the 8px top margin would stack on top of the column
   gap. `.field-error--tight` pulls it back so the message hugs its field. */
.field-error--tight { margin-top: -2px; }

/* Password field — boxed input + in-field eye toggle. Shared by the shared
   PasswordField (sections.jsx), consumed by the Sign-in and New-password
   interstitials; the .np-* layer was duplicated in both shells' <style>
   blocks before it moved here. */
.np-input-wrap { position: relative; }
/* Room for the eye toggle so masked dots never run under it. */
.np-input { padding-right: 52px; }
.np-eye {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  width: 36px; height: 36px; border-radius: var(--r-circle);
  border: 0; background: transparent; cursor: pointer;
  display: grid; place-items: center;
  color: var(--ink-400);
  transition: color 140ms ease, background 140ms ease;
}
.np-eye:hover { color: var(--couple-purple); background: var(--lavender-hover); }

/* Native date pickers — keep them subtle */
input.intake-input[type="date"] { color: var(--ink-700); }
input.intake-input[type="date"]:invalid { color: var(--lavender-placeholder); }

/* Select needs room for the chevron */
.intake-select { padding-right: 28px; cursor: pointer; }
.intake-select.intake-input--boxed { padding-right: 48px; }

/* === Native <select> — restore the affordance `appearance: none` strips ===
   The design's SelectField is a custom listbox (button + sibling chevron SVG +
   <ul>); v3 deliberately ships a NATIVE <select> instead, on /signup (2.5) and
   /profile (5.1) alike. But those selects carry `.intake-input`, whose
   `appearance: none` removes the OS dropdown arrow — leaving a boxed field with
   no indication it opens anything. The design's own chevron is reproduced as a
   background image: same 12×8 glyph, same 1.8 stroke, same 18px inset from the
   right edge as `intake.jsx` uses for the boxed variant. The purple is
   hardcoded because custom properties cannot be referenced inside a data URI —
   it is `--couple-purple` #702AD0; keep the two in step if the token moves.
   Only background-image/-position/-repeat/-size are set, so
   `.intake-input--boxed`'s own `background: var(--white)` still supplies the
   fill. (Found on devsite 2026-08-07 via /profile's walkthrough.) */
select.intake-input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23702AD0' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 18px center;
  background-size: 12px 8px;
  padding-right: 48px;
  cursor: pointer;
}
/* The underline variant has no box inset, so the chevron sits at the edge —
   `intake.jsx` uses right: 4 for this variant. */
select.intake-input--underline {
  background-position: right 4px center;
  padding-right: 28px;
}

/* === Custom select trigger — button form, looks identical to inputs ===
   Inherits font/color from .intake-input via shared font-shorthand on the
   trigger element itself, so no per-variant font redeclaration is needed. */
.intake-select-trigger {
  text-align: left;
  background: transparent;
  display: flex; align-items: center;
  width: 100%;
  font: 500 18px/1 var(--font-body);
  color: var(--ink-700);
}
.intake-select-trigger .intake-select-value {
  display: inline-block;
  width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.intake-select-trigger.intake-select--placeholder { color: var(--lavender-placeholder); }
.intake-select-trigger.is-open.intake-input--underline { border-bottom-color: var(--couple-purple); }
.intake-select-trigger.is-open.intake-input--boxed {
  border-color: var(--couple-purple);
  box-shadow: var(--shadow-focus);
}
.intake-select-chevron { transition: transform 160ms ease; }

/* === Dropdown panel — reusable across the site === */
.dropdown-menu {
  position: absolute;
  left: 0; right: 0;
  top: calc(100% + 6px);
  z-index: 50;
  margin: 0; padding: 8px;
  list-style: none;
  background: var(--white);
  border: var(--border-hairline) solid var(--lavender-border);
  border-radius: var(--r-card-sm);
  box-shadow: var(--shadow-elevated), var(--shadow-subtle);
  max-height: 280px;
  overflow-y: auto;
  overscroll-behavior: contain;
  animation: dropdown-in 140ms ease-out;
}
.dropdown-menu--boxed { top: calc(100% + 8px); }

/* Collision flip (Standing ruling, June 2026): a menu that would clip the
   viewport bottom opens above its trigger instead. Decided once per open by
   useFlipUp in intake.jsx; the entrance slide is mirrored so motion still
   originates at the trigger — that's the invariant, not "always downward". */
.dropdown-menu.is-flipped { top: auto; bottom: calc(100% + 6px); animation-name: dropdown-in-up; }
.dropdown-menu--boxed.is-flipped { bottom: calc(100% + 8px); }

@keyframes dropdown-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes dropdown-in-up {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.dropdown-item {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px;
  border-radius: var(--r-sm);
  cursor: pointer;
  font: 500 16px/1.2 var(--font-body);
  color: var(--ink-700);
  transition: background 120ms ease, color 120ms ease;
  user-select: none;
}
.dropdown-item.is-active { background: var(--lavender); }
.dropdown-item.is-selected { color: var(--couple-purple); font-weight: 700; }
.dropdown-item.is-selected.is-active { background: var(--lavender); }

/* Visible scrollbar that stays well inside the menu's rounded (16px) corners:
   wide gutter with the thumb floated inward via a transparent border, track
   inset top/bottom by the corner radius, and no arrow buttons. (No standard
   scrollbar-width/color — it would disable the ::-webkit styling on Chromium.) */
.dropdown-menu::-webkit-scrollbar { width: 14px; }
.dropdown-menu::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
.dropdown-menu::-webkit-scrollbar-track { background: transparent; margin: var(--r-card-sm) 0; }
.dropdown-menu::-webkit-scrollbar-thumb {
  background-color: var(--lavender-edge);
  border-radius: var(--r-pill);
  border: 4px solid transparent;
  background-clip: padding-box;
  min-height: 40px;
}
.dropdown-menu::-webkit-scrollbar-thumb:hover { background-color: #b3a8cc; }

/* === Date picker — sits on top of the .dropdown-menu chrome === */
.datepicker { padding: 12px; width: 320px; max-height: none; overflow: visible; }
.datepicker-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 12px;
}
.datepicker-nav {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border: 0; background: transparent;
  border-radius: var(--r-sm);
  color: var(--couple-purple);
  cursor: pointer;
  transition: background 120ms ease;
}
.datepicker-nav:hover { background: var(--lavender); }
.datepicker-title {
  flex: 1;
  display: inline-flex; align-items: center; justify-content: center;
  height: 32px;
  border: 0; background: transparent;
  border-radius: var(--r-sm);
  font: 700 16px/1 var(--font-display);
  color: var(--ink-700);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.datepicker-title:hover { background: var(--lavender-hover); color: var(--couple-purple); }
.datepicker-dow {
  display: grid; grid-template-columns: repeat(7, 1fr);
  font: 700 14px/1 var(--font-display);
  letter-spacing: 1.4px;
  color: var(--ink-400);
  text-transform: uppercase;
  text-align: center;
  margin-bottom: 8px;
}
.datepicker-dow span { padding: 8px 0; }
.datepicker-grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px;
}
.datepicker-cell {
  height: 36px;
  display: grid; place-items: center;
  border: 0; background: transparent;
  border-radius: var(--r-pill);
  font: 500 14px/1 var(--font-body);
  color: var(--ink-700);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.datepicker-cell--empty { cursor: default; pointer-events: none; }
.datepicker-cell:not(.datepicker-cell--empty):hover { background: var(--lavender); color: var(--couple-purple); }
.datepicker-cell.is-today {
  box-shadow: inset 0 0 0 1.5px var(--lavender-border);
  color: var(--couple-purple);
  font-weight: 700;
}
.datepicker-cell.is-selected {
  background: var(--couple-purple); color: var(--white); font-weight: 700;
  box-shadow: var(--shadow-subtle);
}
.datepicker-cell.is-selected:hover { background: var(--couple-purple-hover); color: var(--white); }

.datepicker-years {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
  max-height: 240px; overflow-y: auto;
  overscroll-behavior: contain;
  padding: 4px 2px 4px 0;
}
.datepicker-year {
  height: 36px;
  border: 0; background: transparent;
  border-radius: var(--r-sm);
  font: 500 14px/1 var(--font-body);
  color: var(--ink-700);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.datepicker-year:hover { background: var(--lavender); color: var(--couple-purple); }
.datepicker-year.is-selected {
  background: var(--couple-purple); color: var(--white); font-weight: 700;
}
.datepicker-years::-webkit-scrollbar { width: 9px; }
.datepicker-years::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
.datepicker-years::-webkit-scrollbar-thumb {
  background: var(--lavender-border); border-radius: var(--r-pill); border: 2px solid var(--white);
}

/* === Step pill — hover === */
.intake-step-pill:hover { opacity: 1 !important; }

/* === Chips ===
   The design carries the chip's BASE look as inline styles on the element
   (`intake.jsx`'s ChipGroup), so the verbatim styles.css port inherited only
   the `:hover` supplement below — inline styles can't express `:hover`, which
   is also why it needs `!important` there. Nothing in v3 sets those inline
   styles (`signup.js` and `profile.php` both emit a bare `class="intake-chip"`),
   so until now `.intake-chip` had NO base rule anywhere in the codebase and
   chips rendered as unstyled native buttons. Ported here, at the design-system
   layer where the rest of the `.intake-*` kit already lives, rather than
   per-page — `.intake-input`/`.intake-grid-2`/`.intake-range` are all here.
   Values are `ChipGroup`'s own, unchanged (found on devsite 2026-08-07 via
   `/profile`'s walkthrough; `/signup` step 3 shares the same markup). */
.intake-chip {
  padding: 12px 20px;
  border-radius: var(--r-pill);
  border: var(--border-hairline) solid var(--lavender-border);
  background: var(--white);
  color: var(--fg-heading);
  font: 500 16px/1 var(--font-body);
  cursor: pointer;
  white-space: nowrap;
  transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
/* Selected. `signup.css` carries an identical copy of this pair, which is why
   the state showed on /signup but not on /profile — /profile does not load
   signup.css. Left in place there rather than reaching into a Batch-2 file;
   same declarations, so the duplicate is inert. */
.intake-chip[aria-pressed="true"],
.intake-chip.is-selected {
  border-color: var(--couple-purple);
  background: var(--lavender);
  color: var(--couple-purple);
  font-weight: 700;
}
.intake-chip:hover {
  border-color: var(--couple-purple) !important;
  color: var(--couple-purple) !important;
}

/* === Age range slider === */
.intake-range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  pointer-events: none;
  margin: 0;
}
.intake-range::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--white);
  border: 3px solid var(--couple-purple);
  cursor: grab;
  pointer-events: auto;
  box-shadow: var(--shadow-subtle);
}
.intake-range::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--white);
  border: 3px solid var(--couple-purple);
  cursor: grab;
  pointer-events: auto;
  box-shadow: var(--shadow-subtle);
}
.intake-range::-webkit-slider-thumb:active { cursor: grabbing; }
.intake-range::-webkit-slider-runnable-track { background: transparent; }
.intake-range::-moz-range-track { background: transparent; }

/* === Code box === */
.intake-code-box {
  width: 56px;
  height: 64px;
  border: var(--border-hairline) solid var(--lavender-border);
  border-radius: var(--r-input);
  background: var(--white);
  text-align: center;
  font: 700 30px/1 var(--font-display);
  color: var(--ink-700);
  outline: 0;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.intake-code-box:focus {
  border-color: var(--couple-purple);
  box-shadow: var(--shadow-focus);
}

/* === Dropzone === */
.intake-dropzone {
  border: 2px dashed var(--lavender-edge);
  border-radius: var(--r-card-sm);
  padding: 48px 32px;
  display: flex; flex-direction: column; align-items: center;
  gap: 14px;
  text-align: center;
  background: var(--lavender-hover);
  cursor: pointer;
  transition: border-color 160ms ease, background 160ms ease;
}
.intake-dropzone:hover {
  border-color: var(--couple-purple);
  background: var(--lavender);
}


/* === Step 5 — Matches & plan picker === */
.mp-plan-card .btn-pill {
  /* Make CTA labels reliably fit in the narrow card width. */
  letter-spacing: 0.4px !important;
}
@media (max-width: 820px) {
  .mp-plan-grid {
    grid-template-columns: 1fr !important;
  }
  .mp-plan-card { min-height: 0 !important; }
}
@media (max-width: 640px) {
  /* Stack the photo above the info on narrow screens so the
     portrait stays large and legible. */
  .mp-match-card {
    grid-template-columns: 1fr !important;
    gap: 20px !important;
    padding: 20px !important;
  }
  .mp-match-photo-col {
    max-width: 220px;
    margin: 0 auto;
    width: 60%;
  }
  /* Stack the teach buttons too — they don't fit 3-across on narrow */
  .mp-teach-row {
    grid-template-columns: 1fr !important;
    max-width: none !important;
  }
}
.mp-teach-btn:hover {
  border-color: var(--lavender-edge) !important;
}

/* The intake-* CTA shadow was redundant — .btn-pill already carries
   --shadow-cta-gold by default. Modifier classes (--purple-grad, --ink,
   --google) set their own. No override needed here. */

/* === Back link === */
.intake-back-link {
  display: inline-flex; align-items: center; gap: 8px;
  border: 0; background: transparent; padding: 12px 0;
  color: var(--fg-heading);
  font: 700 16px/1 var(--font-display);
  letter-spacing: 0.4px;
  cursor: pointer;
}
.intake-back-link:hover { color: var(--couple-purple); }

/* === Tertiary CTA — text link with arrow. The quiet sibling of .btn-pill.
   Same anatomy as .intake-back-link (which is the original precedent) so
   "← Back" and "Next →" read as a matched pair. Use for low-stakes nav
   ("Next →", "Learn more →") where a gradient pill would shout. */
.cta-link {
  display: inline-flex; align-items: center; gap: 8px;
  border: 0; background: transparent; padding: 12px 0;
  color: var(--couple-purple);
  font: 700 16px/1 var(--font-display);
  letter-spacing: 0.4px;
  cursor: pointer;
  text-decoration: none;
  transition: color 160ms ease, gap 160ms ease;
}
.cta-link:hover { color: var(--couple-purple-hover); gap: 12px; }
.cta-link svg { transition: transform 160ms ease; flex-shrink: 0; }
.cta-link:hover svg { transform: translateX(2px); }

/* On dark surfaces (e.g. featured plan card) — white → gold. */
.cta-link--on-dark { color: var(--white); }
.cta-link--on-dark:hover { color: var(--couple-yellow); }

/* [hidden] must actually hide the pair above. Both carry an author
   `display: inline-flex`, which beats the UA `[hidden] { display: none }`
   rule regardless of specificity — the same cascade trap as `.toast[hidden]`
   below, on the two instances 5.4's audit surfaced and the 5.6 checkpoint
   fixed (owner-approved 2026-08-17): signup's #su-send-code stayed visible
   beside the code boxes after a code was sent (`signup.js` sets `.hidden`,
   and the button sits OUTSIDE `.su-actions`, so `signup.css`'s
   `.su-actions [hidden]` never reached it), and Get-to-Know-You's #ky-back
   rendered on question 1 (`hidden` in the markup, toggled by `know-you.js`,
   with no `[hidden]` rule anywhere in know-you.css). The override lives here
   beside the base rules — not in the page stylesheets — so every current and
   future consumer of these two shared classes is covered at the same layer
   that creates the trap. */
.intake-back-link[hidden] { display: none; }
.cta-link[hidden] { display: none; }

/* Larger tier — used on lavender hero/CTA shells where the link sits
   beside a 68px-tall .btn-pill--lg and needs more presence. Type-scale
   step (16 → 20), letter-spacing unchanged. S3 — display-CTA furniture:
   flexes one step with its pill (20↔18, --fs-button-cta), never below the
   fixed 16px base .cta-link. */
.cta-link--lg { font-size: var(--fs-button-cta); }

/* Small tier — 14px, for dense inline links that sit inside form/stat
   chrome rather than beside a CTA: the "Verify email" trigger and the
   "View transactions" balance link. Was hand-rolled twice (.verify-trigger,
   .acct-balance-link); folded into the one cta-link family so every
   purple-text-with-arrow link behaves identically (gap grows, arrow nudges
   on hover). */
.cta-link--sm { font-size: 14px; }

/* === Responsive === */
@media (max-width: 1100px) {
  .intake-welcome { padding: 128px 56px 80px !important; }
  .intake-stepper-strip { padding: 28px 56px 8px !important; }
  .intake-body { padding: 80px 56px 24px !important; }
  .intake-actions { padding: 32px 56px 80px !important; }
  /* Get to know you — the two-up choice grid (short-viewport ruling)
     collapses back to the original single column below the fluid range,
     so tablet/mobile render as originally approved. */
  .ky-options { grid-template-columns: 1fr !important; }
  /* The Get-to-know-you funnel shell reuses the intake's 96px horizontal
     base but was never given the intake's mobile inset ladder, so below
     the fluid range the answer column stayed pinched into a narrow,
     centered strip. Share the ladder — override left/right only so the
     vertical --rhythm-* tokens stay intact. */
  .ky-shell, .ky-actions { padding-left: 56px !important; padding-right: 56px !important; }
}
@media (max-width: 900px) {
  .intake-welcome { padding: 128px 48px 64px !important; }
  .intake-stepper-strip { padding: 24px 48px 8px !important; top: 80px !important; }
  .intake-body { padding: 64px 48px 24px !important; }
  .intake-actions { padding: 28px 48px 80px !important; }
  .intake-grid-2 { grid-template-columns: 1fr !important; gap: 28px !important; }
  .ky-shell, .ky-actions { padding-left: 48px !important; padding-right: 48px !important; }
}
@media (max-width: 768px) {
  .intake-welcome { padding: 96px 40px 64px !important; }
  .intake-stepper-strip { padding: 20px 40px 8px !important; }
  .intake-body { padding: 64px 40px 24px !important; }
  .intake-actions { padding: 24px 40px 64px !important; }
  .ky-shell, .ky-actions { padding-left: 40px !important; padding-right: 40px !important; }
}
@media (max-width: 640px) {
  .intake-welcome { padding: 96px 20px 56px !important; }
  .intake-stepper-strip { padding: 20px 20px 8px !important; top: 80px !important; }
  /* Extra top padding so the step heading clears the sticky stepper strip
     (stepper is ~86px tall pinned at top:78, so we need room below it). */
  .intake-body { padding: 96px 20px 20px !important; }
  .intake-actions { padding: 20px 20px 56px !important; }
  .intake-step-pill { padding: 8px 12px 8px 8px !important; }
  .intake-step-pill span:last-child { display: none; }
  .intake-step-connector { min-width: 16px !important; }
  .intake-stepper-numbered .intake-step-pill[data-active] span:last-child { display: inline; }

  .intake-code-box { width: 44px; height: 56px; font-size: 24px; }
  /* Keep actions on a single row on mobile — Back left, Next right.
     The inline `justify-content: space-between` from the JSX handles the alignment. */
  .intake-actions-inner { flex-direction: row !important; align-items: center !important; gap: 16px !important; }
  .intake-back-link { padding: 8px 0 !important; }
  .ky-shell, .ky-actions { padding-left: 20px !important; padding-right: 20px !important; }
}

/* Polaroid trio — stepped scale by viewport width, mobile only */
@media (max-width: 640px) and (min-width: 360px) {
  .polaroid-trio, .polaroid-wrap { --p-scale: 0.6 !important; }
}
@media (max-width: 640px) and (min-width: 390px) {
  .polaroid-trio, .polaroid-wrap { --p-scale: 0.65 !important; }
}
@media (max-width: 640px) and (min-width: 420px) {
  .polaroid-trio, .polaroid-wrap { --p-scale: 0.7 !important; }
}
@media (max-width: 640px) and (min-width: 480px) {
  .polaroid-trio, .polaroid-wrap { --p-scale: 0.75 !important; }
}
@media (max-width: 640px) and (min-width: 560px) {
  .polaroid-trio, .polaroid-wrap { --p-scale: 0.82 !important; }
}

/* ============================================================
   GLOBAL HEADING CLASSES
   These are the canonical voices from the Design System Guide.
   Each one is fully self-sufficient — if you slap the class on an
   element, you do NOT need to redeclare font / color / margin /
   letter-spacing inline. Add an inline style only when the call
   site genuinely deviates (e.g. .h2-section in a lavender section
   uses .h2-section--on-lavender; a Match Detail name uses a
   different ink because it sits on a purple plate).
   ============================================================ */
.h1-hero {
  /* Page hero H1 — used on every page hero except Home's marquee.
     Guide: 48px / Rubik 700 / line-height 1. */
  font: var(--type-h1);
  color: var(--couple-purple-dark);  /* heroes live on lavender */
  margin: 0;
  letter-spacing: -0.8px;
  text-wrap: balance;
}
.h1-hero--on-white { color: var(--fg-heading); }
/* Home marquee — the one place where 60px display type is allowed,
   per the Guide. Overrides .h1-hero's 48px to lift the hero headline. */
.h1-hero--marquee {
  font: var(--type-display-xl);
  letter-spacing: -1.2px;
}

.h3-eyebrow {
  /* Section eyebrow tier — the 20px gold/purple lockup above section
     titles on marketing pages. Guide: 20px / Rubik 700, 1.8px tracking. */
  font: var(--type-eyebrow-lg);
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--couple-orange);  /* default gold; override for purple */
  margin: 0;
}

/* Vibe card text — used by every VibeCard (Home preview strip, The Vibe
   grid, and the article "More from the Vibe" row).
   Base recipes here so the JSX doesn't need to re-declare `font:` inline;
   the mobile @media block below only steps the size down. */
.vibe-title {
  font: var(--type-h3);
  color: var(--fg-heading);
  margin: 0;
  letter-spacing: -0.3px;
}
.vibe-sub {
  font: var(--type-body-sm);
  color: var(--ink-500);
  margin: 0;
}

/* Plan name — Rubik 700 30px, the workhorse card-title voice.
   Used on Pricing cards (home), the intake matches-and-plans plan
   picker, and the meet-em-now name plate. Was hand-rolled inline in
   three files; codified here so they stay in lockstep. */
.plan-name {
  font: var(--type-display-sm);
  color: var(--fg-heading);
  letter-spacing: -0.5px;
  margin: 0;
}
.plan-name--on-dark  { color: var(--white); }
.plan-name--accent   { color: var(--couple-purple); }

/* Tight H3 voice — H3 type scale with -0.3px letter-spacing and a
   relaxed 1.2 line-height. Used as a card / row title on a non-heading
   element (date+inline span, event description). Where the consumer
   IS an <h3>, just use the element directly — it already carries
   `font: var(--type-h3); color: var(--fg-heading)` from
   colors_and_type.css. */
.text-h3-tight {
  font: var(--type-h3);
  color: var(--fg-heading);
  letter-spacing: -0.3px;
  line-height: 1.2;
}

/* Score number — the big numeric inside the chemistry ring on
   Matches, Match Detail (x3), and the Meet-Em-Now card. Same H2
   recipe as .h2-section but on an inline element, with a `%` suffix
   that drops to 18px. Consumer sets the dynamic color inline. */
.score-num {
  font: var(--type-h2);
  letter-spacing: -0.5px;
}
.score-num-pct {
  font-size: 18px;
  margin-left: 2px;
  letter-spacing: 0;
}

/* Body-lg — the lead paragraph voice (20px Nunito 500). Previously this
   class only carried the mobile font-size override below, so every
   consumer redeclared the base recipe inline. Now it stands on its own:
   slap `.body-lg` on a <p> and you're done. Use --on-lavender on hero /
   callout surfaces per the Guide's "Background dictates ink" rule. */
.body-lg {
  font: var(--type-body-lg);
  color: var(--ink-700);
  margin: 0;
  letter-spacing: -0.2px;
}
.body-lg--on-lavender { color: var(--couple-purple-dark); }
.body-lg--muted       { color: var(--ink-500); }

/* Section-intro measure — settled June 2026 (the intro-measure ruling;
   see the Guide's Standing Rulings). Reading copy that sits on a
   full-width frame — section intros under .h2-section, hero notes,
   trust footnotes — caps at --measure-intro (760px ≈ 72ch of body-lg,
   the legal/FAQ reading column) instead of running the 1248/1100
   content width. text-wrap: pretty keeps the rag even so the cap reads
   as intentional; the structure below (grids, tables, hairlines)
   carries the full-width symmetry. Exempt: prose deliberately capped
   narrower for composition (copy beside media, card interiors, funnel
   columns) — those keep their own measures. */
:root { --measure-intro: 760px; }
.measure-intro {
  max-width: var(--measure-intro);
  text-wrap: pretty;
}
/* Centered-measure modifier — the footnote-centering ruling (June 17 2026;
   see the Guide's Standing Rulings). A "footnote type" reading block (the
   trust footnotes on Home / Events / Event Detail) stays left-justified with
   ITSELF but centers its measure BLOCK on the content above it, rather than
   left-anchoring to the frame like a section intro. Only the horizontal
   margins are touched (text-align is untouched, so the rag stays left); inert
   on mobile, where the block already fills its container and auto margins
   collapse to zero — no breakpoint guard needed. Pair with .measure-intro and
   keep vertical margins inline. */
.measure-intro--center {
  margin-left: auto;
  margin-right: auto;
}

/* Tight label + helper text — the "dropzone label / uploaded note" voice.
   The label is one notch above body (18px Rubik 700 / lh 1.2) and the
   helper text is one notch below (14px Nunito 500 / lh 1.4 / ink-500).
   Used as a pair inside .intake-dropzone chrome, and inline across the
   Get-to-Know-You step. */
.label-tight {
  font: 700 18px/1.2 var(--font-display);
  color: var(--fg-heading);
  margin: 0;
}
.helper-text {
  font: 500 14px/1.4 var(--font-body);
  color: var(--ink-500);
  margin: 0;
}

/* Inline link — purple, bold, underlined. Use inside body copy ("Read
   the docs", "All-In Matches page", "Terms of Use"). Sibling to
   .link-underline (the bigger, display-font version used as standalone
   navigation). Was previously hand-rolled inline + as three identical
   component wrappers (WcInlineLink / EvInlineLink / EdxInlineLink). */
.link-inline {
  color: var(--couple-purple);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 4px;
}
.link-inline:hover { color: var(--couple-purple-hover); }

/* Quiet text link — the bare-text tertiary action that sits beside a
   pill (e.g. "Cancel plan" next to "Change plan", "Change plan" next to
   "Resume plan" on Account). Display-font sibling of .link-inline. Per
   the system convention, the underline is a static affordance that's
   always present — hover only darkens the purple, it never toggles the
   underline on/off. */
.link-quiet {
  border: 0; background: transparent; padding: 0; cursor: pointer;
  font: 700 16px/1 var(--font-display); letter-spacing: 0.2px;
  color: var(--couple-purple);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.link-quiet:hover { color: var(--couple-purple-hover); }

/* Reusable */
.eyebrow {
  /* Pill / caption eyebrow tier — 14px Rubik 700, 1.8px tracking,
     uppercase. Use as a className on any element that needs the
     small-caps voice (eyebrows above cards, table headers, captions). */
  display: inline-block;
  font: var(--type-eyebrow);
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: currentColor;
  padding: 0;
}
.eyebrow--purple { color: var(--couple-purple); }
.eyebrow--gold   { color: var(--couple-orange); }
.eyebrow--orange-deep { color: var(--couple-orange-deep); }
.eyebrow--cyan   { color: var(--couple-cyan-text); }
.eyebrow--ink    { color: var(--ink-500); }
.eyebrow--muted  { color: var(--ink-400); }
.eyebrow--heading { color: var(--fg-heading); }

/* Parenthetical aside trailing an eyebrow — inline on desktop, drops to its
   own line on the mobile display tier so it stops wrapping mid-phrase. */
@media (max-width: 640px) {
  .eyebrow-aside { display: block; }
}

.btn-pill {
  display: inline-flex; align-items: center; justify-content: center; gap: 12px;
  /* S2b — display-CTA tier: geometry + label ride the element scale
     (--el-px, 1↔0.82 across 1920↔1366) so the marketing pills compress
     with the type around them instead of reading oversized at compact.
     @1536: 56px tall, 18.6px label. ≤1100 pins --el-px = 1px, and the
     mobile rules below still force 56px — nothing moves there. .btn-pill--sm
     (56/18) is control chrome and stays frozen. Hit target ≥52.5px ✓ */
  height: calc(64 * var(--el-px));
  padding: 0 calc(40 * var(--el-px));
  min-width: calc(240 * var(--el-px));
  border: 0; border-radius: var(--r-pill); cursor: pointer;
  font: 700 var(--fs-button-cta) var(--font-display);
  background: var(--grad-cta-yellow);
  color: var(--couple-purple);
  box-shadow: var(--shadow-cta-gold);
  transition: transform 120ms ease, filter 120ms ease;
  white-space: nowrap;
}
.btn-pill:hover  { filter: brightness(1.03); }
.btn-pill:active { transform: scale(0.97); }

/* Size modifiers — desktop heights. Mobile collapses all to 56px below. */
.btn-pill--sm { height: 56px; padding: 0 28px; font-size: 18px; min-width: 0; letter-spacing: 1.2px; text-transform: uppercase; }
.btn-pill--lg { height: calc(68 * var(--el-px)); padding: 0 calc(40 * var(--el-px)); letter-spacing: 1.2px; text-transform: uppercase; }

.btn-pill--purple {
  background: var(--couple-purple); color: var(--lavender);
  /* Purple pills carry the purple CTA glow per the Guide's shadow table
     (--shadow-cta-gold is "gold gradient buttons only"). Lives here, in
     the class, so pages can't drift — Plans had patched it inline and the
     intake match step then shipped without it. */
  box-shadow: var(--shadow-cta);
}

/* === Alternates designed to pair with the pale-lavender hero.
   Each keeps the same pill geometry — only fill/text/shadow change.
   Label color is the hero's lavender (--lavender) instead of pure white,
   so the button reads as a "punched-out" piece of the section.
   :hover gets brightness alone via the base .btn-pill rule; no shadow
   re-declare needed (was a no-op). */
.btn-pill--purple-grad {
  background: var(--grad-cta-purple);
  color: var(--lavender);
  box-shadow: var(--shadow-cta);
}

.btn-pill--ink {
  background: var(--couple-purple-dark);
  color: var(--lavender);
  /* Buttons take a CTA shadow per the Guide's shadow table — the ink pill
     had drifted onto the card token. Same purple glow as its --purple /
     --purple-grad siblings in the CTA-style tweak. */
  box-shadow: var(--shadow-cta);
}

/* Destructive — red fill, white label. Red (#E63E29) is reserved for
   warnings & destructive actions only (per the Guide's color table), so
   this modifier only ever appears as the committing action inside a
   confirm dialog. Shadow tints red to match the fill. */
.btn-pill--danger {
  background: var(--couple-red);
  color: var(--white);
  box-shadow: 0 12px 28px rgba(230,62,41,0.28);
}
.btn-pill--danger:hover { filter: brightness(1.04); }

/* Quiet / neutral — the safe-default sibling that sits next to a primary
   or danger pill (e.g. "Keep match" beside "Unmatch"). White surface,
   hairline lavender border, ink-700 label. No shadow — it recedes. */
.btn-pill--quiet {
  background: var(--white);
  color: var(--ink-700);
  box-shadow: none;
  border: var(--border-hairline) solid var(--lavender-border);
}
.btn-pill--quiet:hover { background: var(--lavender-hover); filter: none; }

/* ============================================================
   CONFIRM DIALOG / MODAL  (pattern: see Design System Guide → Patterns)
   A centered white modal over a purple scrim. Used for destructive
   confirmations (Unmatch). Floating surface = white, one family with
   tooltips/popovers. Radius --r-card-lg (28px, "modal frames"),
   --shadow-hero. Motion is translate-only on the panel per the Guide.
   ============================================================ */
.cd-scrim {
  position: fixed; inset: 0; z-index: 1000;
  background: var(--scrim-dialog);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  display: grid; place-items: center;
  padding: 24px;
  animation: cd-scrim-in 160ms ease both;
}
/* This class's own `display: grid` is an author rule and beats the
   browser's default `[hidden] { display: none }` UA rule outright (author
   normal styles always win over UA normal styles, regardless of
   specificity) — so every `.cd-scrim` needs its own explicit override to
   actually respect the `hidden` attribute. Never caught before the events
   paywall dialog (2026-07-22) because this family had been ported for a
   future consumer but nothing had actually toggled `hidden` on one yet. */
.cd-scrim[hidden] { display: none; }
.cd-panel {
  position: relative;
  width: 100%; max-width: 440px;
  background: var(--white);
  border-radius: var(--r-card-lg);
  box-shadow: var(--shadow-hero);
  padding: 32px;
  animation: cd-panel-in 200ms cubic-bezier(.2,.8,.2,1) both;
}
@keyframes cd-scrim-in { from { opacity: 0; } to { opacity: 1; } }
/* Translate-only on the panel — never an opacity keyframe (the Guide's
   motion rule: opacity keyframes can leave the element stuck invisible). */
@keyframes cd-panel-in { from { transform: translateY(16px); } to { transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) {
  .cd-scrim, .cd-panel { animation: none !important; }
}
/* ---- The modal close (the Modal Takeover ruling, Jun 15 2026) ----
   On DESKTOP there is no × on any modal — the scrim + Esc dismiss, and an
   extra close glyph would be noise (the confirm dialog's founding rule).
   But ≤640 the full-screen takeover removes the tappable scrim, so every
   modal needs one visible way out that ISN'T a committing CTA. This is it:
   a 44px top-right × — the mobile menu's affordance, generalised across the
   whole modal family — that performs the SAFE, non-committing dismissal,
   exactly what a scrim-click / Esc does on desktop (for a confirm dialog
   that's the safe default: Keep match / Keep request / Cancel). It is the
   takeover's scrim. Mobile-tier only (display:none here, display:grid ≤640)
   so the frozen ≥1920 render never moves. Recipe matches the data sheets'
   close (.acct-sheet-close / .mmtx-close) verbatim: white, hairline lavender,
   ink-500 glyph → couple-purple on hover. Used on the confirm family
   (.cd-panel), the compose sheet (.pr-panel) and the crop sheet (.pc-panel),
   which have no flex header to seat an in-flow close; the data sheets and
   menu keep their existing in-header close of the same recipe. */
.cd-close {
  display: none;
  position: absolute; top: 16px; right: 16px; z-index: 2;
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--white); border: var(--border-hairline) solid var(--lavender-border);
  color: var(--ink-500); cursor: pointer; place-items: center;
  transition: background 140ms ease, color 140ms ease;
}
.cd-close:hover { background: var(--lavender-hover); color: var(--couple-purple); }
/* Consequence rows — small glyph + two lines of copy, directly on the white
   panel (the lavender surface this comment once described was removed). */
.cd-consequence {
  display: flex; align-items: flex-start; gap: 14px;
}
.cd-consequence + .cd-consequence { margin-top: 16px; }
.cd-consequence-icon {
  flex-shrink: 0;
  display: grid; place-items: center;
  color: var(--ink-500);
  /* Optically aligns the bare glyph to the cap-height of the 16px label. */
  margin-top: 2px;
}
/* Consequence row copy — lifted from the per-dialog inline styles into shared
   classes when the confirm dialogs were unified onto <ConfirmDialog>. */
.cd-consequence-title { font: 700 16px/1.2 var(--font-display); color: var(--ink-600); }
.cd-consequence-body { font: var(--type-body-sm); color: var(--ink-500); margin-top: 4px; }
.cd-actions { display: flex; gap: 12px; margin-top: 28px; }
.cd-actions .btn-pill { flex: 1 1 0; min-width: 0; padding: 0 20px; }
@media (max-width: 480px) {
  .cd-actions { flex-direction: column-reverse; }
  /* Stacked full-width pills — the same flex-reset the crop sheet's
     stacked-footer needs (the crop-dock amendment, Jun 16 2026). The base
     .cd-actions .btn-pill carries flex:1 1 0 for the equal-width desktop row;
     flex-basis:0 is harmless when it governs WIDTH in a row, but in this
     column it governs HEIGHT — and with no free space to grow into, the 56px
     .btn-pill height is discarded and each button collapses to a wide, short
     min-content bar (one line of text, no vertical padding). Reset to
     flex:0 0 auto to hand the main axis back to the 56px height. */
  .cd-actions .btn-pill { flex: 0 0 auto; width: 100%; }
}
/* MOBILE TAKEOVER (the Modal Takeover ruling, Jun 15 2026) — at ≤640 every
   member of the modal family becomes a full-screen takeover: the scrim stops
   centering a tray and the panel fills the viewport edge-to-edge, so no part
   of the page shows behind it. Dismiss stays reachable in-content (the action
   pills here; footer Cancel / × on the bespoke panels). Entrance is still the
   family's translate-only slide. Bottom padding clears the home indicator.

   Content is TOP-ANCHORED (justify-content:flex-start), not centered: a
   full-screen takeover reads top-down like a page, so the title lands near
   the top and any slack collects at the bottom. Vertical-centering a 100dvh
   panel floated content-light modals (the Event-access paywall, the short
   confirm dialogs) in a dead-centre band with empty space above and below,
   which read as a layout bug once the scrim was gone. (Top-anchoring, not
   centering — amended Jun 16 2026; see the Modal Takeover ruling.) */
@media (max-width: 640px) {
  .cd-scrim {
    display: block; padding: 0;
    overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  .cd-panel {
    width: 100%; max-width: none; min-height: 100dvh;
    border-radius: 0;
    display: flex; flex-direction: column; justify-content: flex-start;
    padding: 32px 24px calc(32px + env(safe-area-inset-bottom));
  }
  /* The takeover's scrim-equivalent — revealed only here. */
  .cd-close { display: grid; }
}

/* ============================================================
   VERIFY CHROME — email / phone verification status + dialog bits.
   Shared by VerifiableField (Account) and VerifyDialog (both pages).
   Was duplicated verbatim in every page's <style> block; codified
   here so the verified / not-verified / resend treatments can't drift
   (per the Guide's "surface it as a system change, not a one-off").
   The "Verify email/phone" trigger itself is now .cta-link.cta-link--sm,
   not a bespoke class.
   ============================================================ */
/* Verified — confirmed/active reads purple (actionable colour = done here). */
.verify-done {
  display: inline-flex; align-items: center; gap: 8px; align-self: flex-start;
  font: 700 14px/1 var(--font-display); letter-spacing: 0.2px;
  color: var(--couple-purple);
}
.verify-done svg { display: block; color: var(--couple-purple); }
/* Pending — neutral ring + ink label, no alarm. */
.verify-status {
  display: inline-flex; align-items: center; gap: 8px;
  font: 500 14px/1 var(--font-body); color: var(--ink-500); white-space: nowrap;
}
.verify-status::before {
  content: ''; width: 8px; height: 8px; border-radius: 50%;
  border: var(--border-hairline) solid var(--ink-400);
}
.verify-muted { font: 500 italic 14px/1 var(--font-body); color: var(--ink-400); }
/* Verify dialog — code boxes stretch to fill the 440px confirm panel;
   resend is a quiet underlined purple link. */
.vd-code-box { flex: 1 1 0; min-width: 0; width: auto; }
.vd-resend {
  /* Button-safe: "Resend code" is a real <button>, so reset the UA button
     chrome (it carries only this quiet underlined-purple link styling). */
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  font: 700 14px/1 var(--font-display); letter-spacing: 0.2px;
  color: var(--couple-purple);
  text-decoration: underline; text-underline-offset: 3px;
}
.vd-resend:hover { color: var(--couple-purple-hover); }

/* ============================================================
   EDIT-COMMIT CHROME — Save bar + confirmation toast.
   The floating tray that slides up when an always-editable "doing"
   page (Profile, Account) has unsaved changes, plus the quiet toast
   that confirms a commit. One definition so the two pages can't
   drift — they did once (Account had put --shadow-elevated on the
   pills, which the Guide forbids on buttons). Both now share the
   correct recipe: each pill floats on its OWN button shadow — the
   gold Save keeps --shadow-cta-gold, the quiet Discard takes
   --shadow-subtle; never the card/popover --shadow-elevated.

   Layout assumes the host page's content column is 1100px inside a
   1440px / 96px-gutter shell (the shared "doing"-page measure). The
   responsive gutter steps match .acct-shell / .profile-shell.
   ============================================================ */
.savebar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  padding: 0 0 28px; pointer-events: none;
  /* Resting (visible) state in the base rule so a throttled/suppressed
     animation still leaves the bar shown; the keyframe only slides it in. */
  transform: translateY(0);
  animation: savebar-in 320ms cubic-bezier(.2,.8,.2,1);
}
@keyframes savebar-in {
  from { transform: translateY(16px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}
.savebar-inner { max-width: 1440px; margin: 0 auto; padding: 0 96px; display: flex; }
.savebar-col {
  /* Match the page's content column so the actions align to the right
     edge of the form, not the far page gutter. */
  max-width: 1100px; width: 100%; display: flex; justify-content: flex-end;
}
.savebar-actions { pointer-events: auto; display: flex; align-items: center; gap: 12px; }
/* The gold Save keeps its built-in --shadow-cta-gold; the white Discard
   takes the gentle --shadow-subtle so it reads grounded without
   out-shouting the primary. NEVER --shadow-elevated on a button. */
.savebar-actions .btn-pill--quiet { box-shadow: var(--shadow-subtle); }
/* Reserve scroll room while the bar is mounted. */
.savebar-spacer { height: 124px; }

.toast {
  position: fixed; top: 104px; left: 50%; z-index: 70;
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 20px 12px 14px; background: var(--white);
  border-radius: var(--r-pill); box-shadow: var(--shadow-card);
  font: 700 16px/1 var(--font-display); color: var(--fg-heading);
  opacity: 1; transform: translate(-50%, 0);
  animation: toast-in 240ms cubic-bezier(.2,.8,.2,1);
}
/* This class's own `display: inline-flex` is an author rule and beats the
   browser's default `[hidden] { display: none }` UA rule outright (author
   normal styles always win over UA normal styles, regardless of specificity)
   — so `.toast` needs its own explicit override to actually respect the
   `hidden` attribute. Without it `#pf-toast` renders from page load reading
   "Changes saved" on a page the member has only just opened (found on devsite
   2026-08-07, `/profile`'s walkthrough item 1). Never caught before now
   because this chrome was ported verbatim with `.savebar` for a future
   consumer and `/profile` is the FIRST surface to toggle `hidden` on one.
   Fourth instance of this cascade trap in this file's family — see
   `.cd-scrim[hidden]`, `.vp-block[hidden]` (new-password/reset-password.css)
   and `.su-step[hidden]` (signup.css). */
.toast[hidden] { display: none; }
@keyframes toast-in {
  from { transform: translate(-50%, -16px); opacity: 0; }
  to   { transform: translate(-50%, 0); opacity: 1; }
}
.toast-check {
  width: 24px; height: 24px; border-radius: 50%; background: var(--couple-purple);
  display: grid; place-items: center; flex-shrink: 0;
}
@media (prefers-reduced-motion: reduce) { .savebar, .toast { animation: none; } }

@media (max-width: 1100px) { .savebar-inner { padding: 0 56px; } }
@media (max-width: 900px)  { .savebar-inner { padding: 0 48px; } }
@media (max-width: 768px)  { .savebar-inner { padding: 0 40px; } }
@media (max-width: 640px) {
  .savebar { padding: 0 0 16px; }
  .savebar-inner { padding: 0 20px; }
  /* min-width:0 lets the actions group shrink below its nowrap content so it
     can't overflow the content column. Without it the implicit min-width:auto
     floors the group at the labels' min-content width; the column is
     justify-content:flex-end, so the overflow extrudes LEFT and clipped the
     Discard pill off the screen edge on a narrow phone. */
  .savebar-actions { flex: 1 1 auto; min-width: 0; }
  .savebar-actions .btn-pill { flex: 1 1 0; min-width: 0; }
  .savebar-spacer { height: 108px; }
  .toast { top: 88px; }
}
/* ≤480 — a split row can't hold both full pills: the "Save changes" label
   needs a ~202px pill, but an equal flex:1 1 0 split only hands it half the
   row, so it overflows below ~456px. Stack the actions full-width — the same
   stacked-footer treatment the modal family uses (.cd-actions ≤480):
   column-reverse seats the primary Save on top, and flex:0 0 auto hands the
   main axis back to the 56px pill height (a flex-basis:0 governs HEIGHT in a
   column and would collapse each pill to a short min-content bar — the
   stacked-footer-is-family-wide ruling, Jun 17 2026). The spacer grows for
   the taller (two-pill) bar so the last field still clears. */
@media (max-width: 480px) {
  .savebar-actions { flex-direction: column-reverse; }
  .savebar-actions .btn-pill { flex: 0 0 auto; width: 100%; }
  .savebar-spacer { height: 164px; }
}

/* Yellow gradient on a richer, less candy-shadow — feels grounded on light bg. */
.btn-pill--gold-on-light {
  background: var(--grad-cta-gold);
  color: var(--couple-purple-dark);
  box-shadow: var(--shadow-cta-gold);
}

/* === Disabled state — solid lavender-grey (matches the chip/border tone
   already in the palette), no shadow, no hover lift. */
.btn-pill:disabled {
  background: var(--lavender-border);
  color: var(--ink-500);
  box-shadow: none;
  cursor: not-allowed;
}
.btn-pill:disabled:hover { filter: none; }

/* Google sign-in — white pill with brand G logo. Sentence case + no gold
   shadow per Google brand guidelines, otherwise sits on the same .btn-pill
   geometry as every other CTA on the page. */
.btn-pill--google {
  background: var(--white);
  color: var(--fg-heading);
  border: var(--border-hairline) solid var(--lavender-border);
  box-shadow: var(--shadow-subtle);
  text-transform: none;
  letter-spacing: -0.1px;
  font-weight: 700;
}
.btn-pill--google:hover {
  background: var(--lavender-hover);
  filter: none;
  box-shadow: var(--shadow-soft);
}
.intake-welcome .btn-pill--google,
.intake-welcome .btn-pill--google:hover {
  box-shadow: var(--shadow-subtle);
}

.link-underline {
  /* Button-safe: the hero "See how it works" control is a real <button>, so
     reset the UA button chrome here (no-ops on the <a>/<span> uses elsewhere)
     and the class renders identically on either element. */
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; padding: 0; margin: 0;
  font: 500 18px var(--font-display);
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 4px;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
}
.link-underline:hover { opacity: 0.85; }

/* Concentric rings — hero motif */
.rings {
  position: absolute; pointer-events: none;
  border-radius: 50%; border: 1px solid rgba(255,255,255,0.18);
}

/* Cycling word */
.cycle-stage {
  display: inline-grid;
  vertical-align: baseline;
  justify-items: start;
  text-align: left;
}
.cycle-stage > span {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 380ms ease, transform 380ms ease;
  white-space: nowrap;
}
.cycle-stage > span.is-active {
  opacity: 1;
  transform: translateY(0);
}

/* Gold gradient text — matches cycle banner */
.grad-gold {
  font-weight: 700;
  background: linear-gradient(180deg, var(--accent-top, var(--couple-yellow)), var(--accent-bot, var(--couple-orange)));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Global accent swap — flip every header .grad-gold across the page to brand purple.
   Driven by [data-accent="purple"] on <body> via the Tweaks panel. Body-copy
   .grad-gold (inside <p>) keeps the gold for visual contrast. */
body[data-accent="purple"] :is(h1, h2, h3):not(.body-lg) .grad-gold {
  --accent-top: var(--couple-violet-soft);
  --accent-bot: var(--couple-purple-hover);
}

/* Purple gradient text — sibling to .grad-gold */
.grad-purple {
  font-weight: 700;
  background: var(--grad-cta-purple);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* fade in helper */
@keyframes pulse-ring {
  0%   { transform: scale(0.96); opacity: 0.5; }
  50%  { transform: scale(1.04); opacity: 0.85; }
  100% { transform: scale(0.96); opacity: 0.5; }
}

@keyframes float-soft {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-6px); }
}

/* Placeholder-stripe surface — subtle diagonal pattern used as a stand-in
   for unrendered imagery (event hero, video thumbs, vibe cards). Wraps
   the canonical lavender surface + the placeholder-stripe overlay so call
   sites can drop one class instead of repeating the gradient inline. */
.placeholder-stripes {
  background-color: var(--lavender);
  background-image: var(--grad-placeholder-stripes);
}

/* Pricing card hover */
.plan-card { transition: transform 200ms ease, box-shadow 200ms ease; }
.plan-card:hover { transform: translateY(-4px); }

/* Vibe card */
.vibe-card { cursor: pointer; }
.vibe-card .img { transition: transform 300ms ease; overflow: hidden; }
.vibe-card:hover .img > * { transform: scale(1.04); }

/* Nav link hover */
.nav-link { cursor: pointer; }

/* ============= FOOTER ============= */
.footer-shell {
  background: var(--couple-purple);
  /* S2 — mirrors the hero plate: the footer's top corners and the hero's
     bottom corners are the same signature curve, so they flex together.
     Plate Width ruling (Jun 2026) — and the same overhang: both plates
     extend past the 1440 nav/content cap by --shell-overhang per side.
     .footer-desktop below is capped at 1440 + centered so the logo /
     tagline / socials hold their screen position; only the purple grows. */
  border-radius: var(--rhythm-hero-radius) var(--rhythm-hero-radius) 0 0;
  color: var(--white);
  overflow: hidden;
  margin-inline: calc(-1 * var(--shell-overhang));
}
.footer-mobile { display: none; }

.footer-desktop {
  /* Plate Width ruling — pin the content to the old 1440 box while the
     shell overhangs it; border-box, so 1440 includes the 56px paddings. */
  max-width: 1440px;
  margin: 0 auto;
  padding: 28px 56px;
  min-height: 56px;
  display: grid;
  /* Footer centering ruling (Jun 24 2026) — both side columns reserve the
     socials' fixed footprint (--footer-side: 5×40 icons + 4×12 gaps = 248px)
     so the center column is always mathematically centered. A bare 1fr is
     minmax(auto,1fr) and pins to its content width, so the wider socials side
     (248) outran the logo side (~72) and shoved the center block left by up to
     88px across ~901–1338. Reserving the larger side on BOTH equalizes them.
     At ≥~1346 the 1fr share already exceeds 248, so the floor is inert and
     ≥1440/≥1920 stay pixel-identical (the freeze holds). Cost: the legal links
     wrap a line earlier in the band — handled gracefully by FooterPipedRow. */
  --footer-side: 248px;
  grid-template-columns: minmax(var(--footer-side), 1fr) auto minmax(var(--footer-side), 1fr);
  align-items: center;
  gap: 24px;
}
.footer-desktop-icon {
  justify-self: start;
  display: flex;
  align-items: center;
}
.footer-desktop-socials {
  justify-self: end;
  display: flex;
  gap: 12px;
}
.footer-desktop-center {
  justify-self: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
}

@media (max-width: 900px) {
  .footer-shell {
    background: var(--couple-purple);
    border-radius: var(--r-card-lg) var(--r-card-lg) 0 0;
  }
  .footer-desktop { display: none !important; }
  .footer-mobile {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 40px 28px 32px;
    gap: 28px;
  }
  .footer-co {
    display: block;
    width: 80px;
    height: auto;
  }
  .footer-tagline {
    font: 700 30px/1 var(--font-display);
    letter-spacing: 0.5px;
    margin-top: -2px;
  }
  .footer-links {
    font: 500 16px/1.5 var(--font-body);
    color: var(--white);
    max-width: 280px;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: baseline;
    column-gap: 8px;
    row-gap: 4px;
  }
  .footer-links .sep {
    color: rgba(255,255,255,0.9);
  }
  .footer-links .nowrap { white-space: nowrap; }
  .footer-links-break {
    flex-basis: 100%;
    height: 0;
  }
  .footer-socials {
    display: flex;
    gap: 14px;
    margin-top: 4px;
  }
  .footer-social-btn {
    width: 44px;
    height: 44px;
    border-radius: var(--r-input);
    border: var(--border-hairline) solid rgba(255,255,255,0.95);
    display: grid;
    place-items: center;
    cursor: pointer;
  }
}

/* ============================================================
   CHECKOUT PAGE
   Single editorial column. Order (receipt) stacked above Payment.
   ============================================================ */
.ck-page {
  background: var(--white);
}

/* Section headers use .h2-section. (.ck-section-title was the checkout-only
   mobile font-size hook — absorbed by the MOBILE DISPLAY TIER, Jun 2026;
   the class stays in checkout.jsx as a structural hook only.) */

/* === Responsive === */
@media (max-width: 1100px) {
  .ck-shell { padding: 128px 56px 80px !important; }
}
@media (max-width: 900px) {
  .ck-shell { padding: 96px 48px 80px !important; }
}
@media (max-width: 768px) {
  .ck-shell { padding: 96px 40px 64px !important; }
  .ck-grid-3 { grid-template-columns: 1fr 1fr !important; }
  /* Line rows collapse: name + price on one row, then description,
     then qty controls. Description hides where it would crowd the row. */
  .ck-line-grid {
    grid-template-columns: 1fr auto !important;
    gap: 8px 16px !important;
  }
  .ck-line-desc {
    grid-column: 1 / -1 !important;
    order: 3;
  }
  .ck-line-controls {
    grid-column: 1 / -1 !important;
    justify-self: start !important;
    order: 4;
    margin-top: 4px;
  }
}
@media (max-width: 600px) {
  .ck-shell { padding: 96px 20px 56px !important; }
  .ck-header { margin-bottom: 40px !important; }
  .ck-stack { gap: 48px !important; }
  .ck-totals { min-width: 0 !important; width: 100%; }
  /* Coin bonus offer — stack copy over a full-width, left-aligned CTA. */
  .ck-coin-offer { gap: 20px !important; }
  .ck-coin-offer-cta { align-items: stretch !important; width: 100%; flex-direction: row !important; justify-content: space-between; align-items: center !important; }
  .ck-coin-offer-cta .btn-pill { flex: 0 0 auto; }
}
/* Mobile Review S5 — ≥44px touch floors on the cart's compact line
   controls (the qty stepper + the remove ×). These carry inline desktop
   sizes (32/30), so the mobile bump needs !important. Desktop ≥641 keeps
   the dense receipt sizing (pixel-freeze untouched). */
@media (max-width: 640px) {
  .ck-qty { height: 44px !important; }
  .ck-qty button { width: 40px !important; height: 44px !important; }
  .ck-line-remove { width: 44px !important; height: 44px !important; }
}

/* === Welcome page — responsive padding rhythm.
   The shell itself is now handled by `.hero-shell-lavender`; only
   horizontal paddings live here. (The old wc-hero H1 step was absorbed
   by the ≤640 mobile display tier, Jun 2026.) */
@media (max-width: 1100px) {
  .wc-hero-pad { padding: 0 56px !important; }
  .wc-shell    { padding: 80px 56px 80px !important; }
}
@media (max-width: 900px) {
  .wc-hero-pad    { padding: 0 48px !important; }
  .wc-shell       { padding: 64px 48px 80px !important; }
  .wc-shell > div { gap: 64px !important; }
}
@media (max-width: 768px) {
  .wc-hero-pad { padding: 0 40px !important; }
  .wc-shell    { padding: 56px 40px 64px !important; }
}
@media (max-width: 640px) {
  .wc-hero-pad    { padding: 0 20px !important; }
  .wc-shell       { padding: 48px 20px 56px !important; }
  .wc-shell > div { gap: 56px !important; }
}
