/* ==========================================================================
   Heal EMDR — healemdr.com
   One stylesheet for every page. Every value below is the app's own token
   (lib/theme/app_colors.dart, app_typography.dart, app_spacing.dart) or a
   component recipe lifted from lib/design_system — the comments say which.
   The only web-only additions are the display sizes of the hero H1 and the
   section H2 (the app has no page-hero role), agreed 2026-09-23.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* AppColors */
  --bg: #fef7e9;                 /* background — the page cream */
  --surface: #ffffff;            /* surface — cards, inputs, sheets */
  --text: #000000;               /* textPrimary */
  --text-2: #66635d;             /* textSecondary */
  --text-3: rgba(0, 0, 0, 0.4);  /* textTertiary */
  --text-muted: #989898;         /* textMuted */
  --text-disabled: #b8b4ae;      /* textDisabled */
  --primary: #000000;            /* primary */
  --on-primary: #ffffff;         /* primaryForeground */
  --border: #000000;             /* border — focused field */
  --border-light: rgba(0, 0, 0, 0.1); /* borderLight — unfocused field */
  --disabled-fill: #e5ded2;      /* buttonDisabledFill */
  --icon-bg: #fef7e9;            /* iconBgCream — the emoji badge chip */
  --card-beige: #f3ebd9;         /* cardBeige */
  --green-fill: #def1d2;         /* backgroundGreen — tag pill */
  --success: #567f3b;
  --error: #e05c3a;
  --black-60: rgba(0, 0, 0, 0.6);
  --scrim: rgba(121, 121, 121, 0.8); /* scrimGrey — modal barrier */

  /* GlassContainer.onColor (the testimonial card) */
  --glass-fill: linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.4));
  --glass-rim: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.5));
  --glass-shadow: 0 8px 60px rgba(255, 255, 255, 0.1);
  /* GlassContainer.onCream (the story dialog) */
  --cream-shadow: 0 8px 23px rgba(0, 0, 0, 0.04);

  /* AppTypography */
  --font: "Satoshi", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* AppSpacing — 8 dp grid plus the standard 12 */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-6: 24px;
  --s-8: 32px;
  --s-12: 48px;
  --gutter: 24px;                /* screenPadding */
  --content-max: 1200px;         /* web: the brief's max content width */
  --header-h: 80px;              /* headerHeight (phone + tablet: a little more air; 72 on desktop) */

  /* Radii */
  --r-button: 12px;              /* HealButton resolved radius */
  --r-pill: 11px;                /* 36 dp pills (story pill, watch button) */
  --r-card: 12px;                /* cardRadius */
  --r-glass: 16px;               /* GlassContainer */
  --r-modal: 24px;               /* modalRadius / HealDialogCard */
  --r-badge: 9px;                /* EmojiBadge */

  /* Motion — the brief's curves */
  --ease-rise: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-fade: cubic-bezier(0.44, 0, 0.56, 1);
  --press-in: 100ms;             /* CardPressEffect kPressInMs */
  --press-out: 150ms;            /* CardPressEffect kReleaseMs */
  --press-scale: 0.97;           /* CardPressEffect kPressScale */
}

/* --------------------------------------------------------------------------
   2. Fonts — the app's Satoshi (pubspec.yaml), self-hosted as WOFF2.
      font-display: swap so text never waits on the download; the two weights
      used above the fold are preloaded from each page's <head>.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Satoshi";
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url("/assets/fonts/Satoshi-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "Satoshi";
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  src: url("/assets/fonts/Satoshi-Medium.woff2") format("woff2");
}
@font-face {
  font-family: "Satoshi";
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  src: url("/assets/fonts/Satoshi-Bold.woff2") format("woff2");
}

/* --------------------------------------------------------------------------
   3. Base
   -------------------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
body {
  margin: 0;
  /* The hero blob bleeds past its section on purpose; clip stray horizontal
     overflow at the page level (clip, not hidden: it does not create a scroll
     container, so the sticky header keeps working, and it leaves the vertical
     axis alone so the bleed above and below the hero stays visible). On body
     the value propagates to the viewport, so main clips too, at its own box. */
  overflow-x: clip;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
}
h1,
h2,
h3,
p {
  margin: 0;
}
a {
  color: inherit;
}
img,
svg {
  display: block;
  max-width: 100%;
  height: auto;
}
button {
  font: inherit;
  color: inherit;
}
/* No grey flash on tap (iOS/Android), and a focus ring only for keyboard
   focus: pointer/touch focus and the focus a dialog hands back after closing
   must not draw the double ring iOS shows. */
a,
button,
summary,
[tabindex] {
  -webkit-tap-highlight-color: transparent;
}
:focus {
  outline: none;
}
:focus:not(:focus-visible) {
  outline: none;
  box-shadow: none;
}
:focus-visible {
  outline: 2px solid var(--border);
  outline-offset: 3px;
}
strong {
  font-weight: 700;
}

main {
  overflow-x: clip;
}
.container {
  width: 100%;
  max-width: calc(var(--content-max) + 2 * var(--gutter));
  margin: 0 auto;
  padding: 0 var(--gutter);
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* Copy that differs between the phone and the wider layouts. */
.only-wide {
  display: none;
}
.only-phone {
  display: inline;
}

/* --------------------------------------------------------------------------
   4. Type roles — named after the app's tokens
   -------------------------------------------------------------------------- */
.screen-title {
  font-size: 24px;
  font-weight: 500;
  line-height: 1.2;
}
.section__title {
  /* web-only display size; Satoshi Medium like screenTitle */
  font-size: 28px;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.section__body {
  /* screenBody: Regular, secondary colour, 1.4 */
  font-size: 18px;
  font-weight: 400;
  line-height: 1.4;
  color: var(--text-2);
}
.section__body strong {
  color: var(--text);
}
.eyebrow {
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--text-3);
}

/* --------------------------------------------------------------------------
   5. Buttons — HealButton primaryFilled: black fill, white 14/500 label on
      a 1.0 line box, min-height 44, radius 12, no border, no shadow.
      Hug buttons get the 12 px side padding; pills are the 36 dp variant.
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 16px;
  border: 0;
  border-radius: var(--r-button);
  background: var(--primary);
  color: var(--on-primary);
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform var(--press-out) ease-out, background-color 150ms ease-out, opacity 150ms;
}
.btn:hover {
  background: #1c1c1f;
}
.btn:active {
  transform: scale(var(--press-scale));
  transition-duration: var(--press-in);
  outline: none;
  box-shadow: none;
}
.btn:disabled {
  background: var(--disabled-fill);
  color: var(--text-3);
  cursor: default;
  transform: none;
}
.btn--hug {
  padding: 0 12px;
}
.btn--pill {
  min-height: 36px;
  padding: 0 12px;
  border-radius: var(--r-pill);
}
.btn--text {
  background: transparent;
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.btn--text:hover {
  background: transparent;
  color: var(--text-2);
}
.btn--block {
  width: 100%;
}
.link-arrow {
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* --------------------------------------------------------------------------
   6. Header — logo left, Download App right, the app's header height.
      Sticky, page cream, with a FadeScrim-style soft edge instead of a rule
      so content scrolls under it the way the app's header works.
   -------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  /* Transparent at the top of the page so the hero blob shows through;
     main.js adds .is-scrolled once the page moves and the cream comes back. */
  background: transparent;
  transition: background-color 200ms ease-out;
}
.site-header.is-scrolled {
  background: var(--bg);
}
.site-header::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 20px;
  background: linear-gradient(var(--bg), rgba(254, 247, 233, 0));
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms ease-out;
}
.site-header.is-scrolled::after {
  opacity: 1;
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--header-h);
}
.logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}
.logo svg {
  width: 74px;
  height: 27px;
}

/* --------------------------------------------------------------------------
   7. Footer
   -------------------------------------------------------------------------- */
.site-footer {
  padding: 40px 0 48px;
}
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4) var(--s-6);
  font-size: 14px;
  font-weight: 500;
}
.site-footer__copy {
  color: var(--text-2);
}
.site-footer__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
}
.site-footer__nav a {
  text-decoration: none;
}
.site-footer__nav a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* --------------------------------------------------------------------------
   8. Blob — AnimatedBlobBackground. Two pre-blurred bitmaps (σ50, baked
      with a 4σ transparent margin, tools/blob/bake_blobs.js), opacity .57,
      each spinning about its own centre once every 10 s in opposite
      directions; blue dx −40, green dx +40, both dy −10 from the box centre.
   -------------------------------------------------------------------------- */
.blob {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.blob__shape {
  position: absolute;
  left: 50%;
  top: 50%;
  max-width: none; /* the global img rule would squeeze it to the viewport width */
  opacity: 0.57;
  will-change: transform;
  animation: blob-spin 10s linear infinite;
}
.blob__shape--blue {
  width: 735px;
  height: 947px;
  /* centre (−367.5, −473.5) then dx −40, dy −10 */
  margin: -483px 0 0 -408px;
}
.blob__shape--green {
  width: 703px;
  height: 947px;
  /* centre (−351.5, −473.5) then dx +40, dy −10 */
  margin: -483px 0 0 -312px;
  animation-direction: reverse;
}
@keyframes blob-spin {
  from {
    transform: scale(var(--blob-scale, 1)) rotate(0deg);
  }
  to {
    transform: scale(var(--blob-scale, 1)) rotate(360deg);
  }
}

/* The hero's blob is allowed out of its box; the testimonial one stays clipped. */
.hero .blob {
  overflow: visible;
}

/* --------------------------------------------------------------------------
   9. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  /* No overflow clipping: the blob bleeds above and below the section (the
     page-level overflow-x: clip on body keeps it from widening the page). */
  padding: 40px 0 56px;
  text-align: center;
  --blob-scale: 0.8;
  /* Phone and tablet: fill the first screen minus the header and the logo
     strip (~150px), and centre the hero content in that space so the
     headline sits mid-viewport with the logos still visible below. */
  min-height: calc(100svh - var(--header-h) - 150px);
  display: flex;
  align-items: center;
  justify-content: center;
}
.hero__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-6);
}
.hero__title {
  font-size: 40px;
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -0.02em;
  max-width: 14ch;
}
.hero__sub {
  font-size: 18px;
  font-weight: 400;
  line-height: 1.4;
  color: var(--text-2);
}
.hero__sub strong {
  color: var(--text);
}

/* The "Free trial · Cancel anytime" pill, above the H1 at every width. A
   white surface pill with the app's light border and 14/500 label. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  border-radius: 60px;
  background: var(--surface);
  border: 1px solid var(--border-light);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-2);
}
.pill strong {
  font-weight: 500;
  color: var(--text);
}
.pill__sep {
  color: var(--text-3);
}

/* Store badges — each official artwork centred in a black container cut
   like the header's Download App button (44 px tall, the button's 12 px
   radius, 12 px side padding), so the pair reads as two primary buttons.
   The artwork is 36 px tall, a 4 px inset top and bottom. Same at every
   width; the two badges share an aspect, so the containers come out equal. */
.store-badges {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--s-3);
}
.store-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  padding: 0 var(--s-3);
  border-radius: var(--r-button);
  background: var(--primary);
  transition: transform var(--press-out) ease-out, background-color 150ms ease-out;
}
.store-badge:hover {
  background: #1c1c1f;
}
.store-badge:active {
  transform: scale(var(--press-scale));
  transition-duration: var(--press-in);
}
.store-badge img {
  height: 36px;
  width: auto;
}

/* --------------------------------------------------------------------------
   10. Logo strip — an infinite ticker inside the content column, faded
       120 px at both edges like the previous site's. Measured there
       2026-09-23: leftwards at 20 px/s, marks at 50 % opacity, no pause on
       hover; its 50 px gap read too tight, so ours is 96 px. Each copy of
       the set is one <ul> padded by the gap on its right, so four copies are
       exactly four periods and translateX(-25 %) loops with no seam. The
       duration is set by main.js from the measured set width (÷ 20 px/s).
   -------------------------------------------------------------------------- */
.logos {
  position: relative; /* paints above the hero blob's bleed */
  z-index: 1;
  padding: 24px 0 48px;
  text-align: center;
}
/* Small label: eyebrow-sized, secondary, a touch of tracking, 16 px above. */
.logos__caption {
  margin-bottom: var(--s-4);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-2);
}
.ticker {
  overflow: hidden;
  opacity: 0.5;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 120px, #000 calc(100% - 120px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 120px, #000 calc(100% - 120px), transparent);
}
.ticker__track {
  display: flex;
  width: max-content;
  will-change: transform;
  animation: ticker var(--ticker-duration, 67s) linear infinite;
}
.ticker__set {
  list-style: none;
  margin: 0;
  padding: 0 75px 0 0;
  display: flex;
  align-items: center;
  gap: 75px;
  flex: none;
}
.ticker__set img {
  width: auto;
  max-width: none;
}
.logo--who {
  height: 45px;
}
.logo--va {
  height: 40px;
}
.logo--apa {
  height: 50px;
}
.logo--nice {
  height: 26px;
}
.logo--samhsa {
  height: 44px;
}
@keyframes ticker {
  to {
    transform: translateX(-25%);
  }
}

/* --------------------------------------------------------------------------
   11. Section frame + cards
   -------------------------------------------------------------------------- */
.section {
  padding: 56px 0;
}
.section__head {
  margin-bottom: var(--s-6);
}
.section__head > * + * {
  margin-top: var(--s-3);
}

/* ChoiceCard, static mode (the paywall benefits): white card r12, padding 16,
   badge + content row gap 16 top-aligned, title 14/500, description 14/500
   secondary with a 4 dp gap. Cards sit 8 dp apart on the phone. */
.card-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-2);
}
.choice-card {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  padding: var(--s-4);
  background: var(--surface);
  border-radius: var(--r-card);
}
.choice-card__title {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
}
.choice-card__desc {
  margin-top: var(--s-1);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-2);
}

/* EmojiBadge: 32×32, radius 9, cream chip, 18 px glyph. */
.emoji-badge {
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--r-badge);
  background: var(--icon-bg);
  font-size: 18px;
  line-height: 1;
}

/* --------------------------------------------------------------------------
   12. Video — the native player with native controls (the MP4s from the
       Cloud Storage bucket, see main.js §4). The box keeps its aspect (4:5
       portrait cut on the phone, 16:9 from 768) and the glass radius, and
       the <video> fills it (object-fit: contain, so a rotation mid-play
       letterboxes instead of cropping). The <video> is in the markup;
       main.js only sets poster, src and load() and adds the captions after
       loadedmetadata. The one overlay is the desktop play glyph below.
   -------------------------------------------------------------------------- */
.video__inner {
  display: grid;
  gap: var(--s-8);
}
.video__text .section__body {
  margin-top: var(--s-3);
}
.video-embed {
  position: relative;
  width: 100%;
  padding: 125% 0 0 0;
  border-radius: var(--r-glass);
  overflow: hidden;
  background: var(--card-beige);
}
.video-embed__media {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: var(--card-beige);
}
/* The desktop play glyph: a 72 px white disc at 85 % with a black triangle
   (nudged 4 px right, its optical centre) over the poster, rendered ONLY
   where a mouse is: (hover: hover) and (pointer: fine), a media query, not
   JS, so a touch device never even lays it out (iOS 27 needs its native
   controls untouched at load, see main.js §4). Click → play(); hidden on
   `playing`. Not rendered without JS either. */
.video-play {
  display: none;
}
@media (hover: hover) and (pointer: fine) {
  .js .video-play {
    position: absolute;
    z-index: 1;
    left: 50%;
    top: 50%;
    width: 72px;
    height: 72px;
    margin: -36px 0 0 -36px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.85);
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
    color: var(--text);
    display: grid;
    place-items: center;
    cursor: pointer;
    transition: transform 150ms ease-out;
  }
  .js .video-play:hover,
  .js .video-play:active {
    transform: scale(1.06);
  }
  .js .video-play svg {
    width: 28px;
    height: 28px;
    margin-left: 4px;
  }
  .js .video-play.is-hidden {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   13. Testimonials — TestimonialSection + TestimonialCard + GlassContainer.
   -------------------------------------------------------------------------- */
.testimonials {
  position: relative;
  overflow: hidden;
  --blob-scale: 0.9;
}
.testimonials > .container,
.testimonial-track {
  position: relative;
  z-index: 1;
}
.testimonials__head {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
}
.section__emoji {
  font-size: 32px;
  line-height: 1.3;
}
.rating-badge {
  margin-top: var(--s-2);
  display: inline-block;
  padding: 8px 14px;
  border-radius: 60px;
  background: var(--surface);
  font-size: 16px;
  font-weight: 500;
}
.rating-caption {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-2);
}

/* The pager: one 284 dp card on the phone, three abreast from 768 (the
   track is exactly three cards + two gaps wide, so a card is 284 on desktop
   and shrinks on a tablet). A native snap scroller — swipe and drag keep
   working — that main.js turns into a seamless auto-advancing loop by
   cloning cards at both ends. Wide layouts fade the outer cards through a
   mask on the track. */
.testimonial-track {
  display: flex;
  gap: var(--s-2);
  /* Phone: full viewport width (out of the container's gutters) so the
     previous and next cards peek at both edges, like the app's paywall. */
  width: calc(100% + 2 * var(--gutter));
  margin: var(--s-6) 0 0 calc(-1 * var(--gutter));
  padding: var(--s-2) 0 var(--s-6);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
}
.testimonial-track::-webkit-scrollbar {
  display: none;
}
/* Snapping is off while main.js animates a move (it drives scrollLeft
   frame by frame) and while the mouse drags; a finger swipe keeps it. */
.testimonial-track.is-dragging,
.testimonial-track.is-animating {
  scroll-snap-type: none;
}
.testimonial-track.is-dragging {
  cursor: grabbing;
}

.glass-card {
  position: relative;
  border-radius: var(--r-glass);
  background: var(--glass-fill);
  box-shadow: var(--glass-shadow);
}
/* Rim-only gradient stroke (the _GradientBorderPainter): a 1 px ring
   painted by masking a gradient to the border box only. */
.glass-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: var(--glass-rim);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
.testimonial {
  flex: 0 0 284px;
  min-width: 0;
  padding: 32px 16px;
  scroll-snap-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.testimonial__avatar {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--surface);
  font-size: 24px;
  line-height: 1;
}
.testimonial__title {
  margin-top: var(--s-6);
  padding: 0 var(--s-6);
  font-size: 16px;
  font-weight: 700;
  line-height: 1.4;
}
/* The quote takes the slack so every card (all the same height, the track
   stretches them) keeps its pill on the same bottom line, 32 dp under the
   quote at the least. */
.testimonial__quote {
  flex: 1 0 auto;
  margin: var(--s-3) 0 var(--s-8);
  padding: 0 var(--s-6);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-2);
}
.testimonial .btn {
  margin-top: 0;
}

/* The story sheet — HealDialogCard: white, radius 24, 28/24 padding. */
.story-dialog {
  width: min(480px, calc(100vw - 2 * var(--gutter)));
  padding: 28px 24px 24px;
  border: 0;
  outline: none;
  border-radius: var(--r-modal);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--cream-shadow);
}
/* The sheet takes focus when it opens (so no button starts with a ring); it
   is not a control, so it never draws one itself. */
.story-dialog:focus,
.story-dialog:focus-visible,
.story-dialog__inner,
.story-dialog__inner:focus,
.story-dialog__inner:focus-visible {
  outline: none;
  border: 0;
}
.story-dialog::backdrop {
  background: var(--scrim);
}
.story-dialog__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s-3);
}
.story-dialog__stars {
  font-size: 16px;
  letter-spacing: 1px;
}
.story-dialog__title {
  font-size: 18px;
  font-weight: 700;
  line-height: 1.3;
}
.story-dialog__quote {
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--text-2);
}
.story-dialog__author {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
}
.story-dialog .btn {
  margin-top: var(--s-3);
}

/* --------------------------------------------------------------------------
   14. Download
   -------------------------------------------------------------------------- */
.download__inner {
  display: grid;
  gap: var(--s-8);
  align-items: center;
}
.download__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-4);
}
/* Phone: the badge row sits centred under the left-aligned copy; from 768 it
   lines up with the text (the 768 block below). */
.download__text .store-badges {
  align-self: center;
  justify-content: center;
  margin-top: var(--s-2);
}
.qr-block {
  display: none; /* phones scan nothing on their own screen */
}

/* --------------------------------------------------------------------------
   15. FAQ — native <details> in white cards; two columns from 768.
   -------------------------------------------------------------------------- */
.faq__grid {
  display: grid;
  gap: var(--s-2);
  margin-top: var(--s-6);
}
.faq__col {
  display: grid;
  gap: var(--s-2);
  align-content: start;
}
.faq-item {
  background: var(--surface);
  border-radius: var(--r-card);
  padding: 0 var(--s-4);
}
.faq-item__q {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-4) 0;
  border: 0;
  background: none;
  text-align: left;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.4;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.faq-item__q:active {
  background: none;
}
.faq-item__q::after {
  content: "+";
  flex: none;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--icon-bg);
  font-size: 18px;
  line-height: 1;
  color: var(--text);
  transition: transform 250ms ease-out;
}
.faq-item__q[aria-expanded="true"]::after {
  transform: rotate(45deg);
}
/* Expand/collapse: the panel is a one-row grid whose row animates 0fr → 1fr;
   the inner wrapper's min-height: 0 lets the row really collapse. */
.faq-item__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 250ms ease-out;
}
.faq-item__panel > div {
  min-height: 0;
  overflow: hidden;
}
.faq-item__panel.is-open {
  grid-template-rows: 1fr;
}
.faq-item__body {
  padding: 0 0 var(--s-4);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--text-2);
}
.faq__footer {
  margin-top: var(--s-8);
  font-size: 16px;
  font-weight: 400;
  color: var(--text-2);
}
.faq__footer .link-arrow {
  display: inline-block;
  margin-top: var(--s-2);
  color: var(--text);
}

/* --------------------------------------------------------------------------
   16. Inner pages (contact, delete account, legal, 404)
   -------------------------------------------------------------------------- */
.page {
  padding: 40px 0 72px;
}
.page__head {
  max-width: 720px;
}
.page__head .section__body {
  margin-top: var(--s-3);
}
.page__body {
  margin-top: var(--s-8);
}

/* Forms — HealTextInput: white, radius 12, 1 px light border that turns
   black on focus, 16 dp padding. 16 px type so iOS does not zoom the field. */
.form {
  display: grid;
  gap: var(--s-4);
  max-width: 560px;
}
.field {
  display: grid;
  gap: var(--s-2);
}
.field label {
  font-size: 14px;
  font-weight: 500;
}
.input {
  width: 100%;
  padding: var(--s-4);
  background: var(--surface);
  border: 1px solid var(--border-light);
  border-radius: var(--r-card);
  font: 500 16px/1.4 var(--font);
  color: var(--text);
}
.input::placeholder {
  color: var(--text-3);
}
.input:focus {
  outline: none;
  border-color: var(--border);
}
textarea.input {
  min-height: 160px;
  resize: vertical;
}
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.field__hint {
  font-size: 12px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-muted);
}
.field__hint:empty {
  display: none; /* no reserved space: a hint takes room only while it shows */
}
.field__hint.is-error {
  color: var(--error);
}
.form__actions {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.form-status {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.5;
}
.form-status--error {
  color: var(--error);
}
.form-success {
  max-width: 560px;
  padding: var(--s-6);
  background: var(--surface);
  border-radius: var(--r-card);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--text-2);
}
.form-success strong {
  color: var(--text);
}
.email {
  font-weight: 500;
  color: var(--text);
}

/* Delete account — five framed screens, one per step. */
.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-8);
  counter-reset: step;
}
.step {
  display: grid;
  gap: var(--s-4);
  align-content: start;
}
.step__text {
  display: flex;
  gap: var(--s-3);
  align-items: flex-start;
}
.step__num {
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--r-badge);
  background: var(--surface);
  font-size: 14px;
  font-weight: 700;
}
.step__title {
  font-size: 16px;
  font-weight: 500;
  line-height: 1.4;
}
.step__desc {
  margin-top: var(--s-1);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-2);
}
.step__shot {
  margin: 0;
  max-width: 300px;
}
.step__shot img {
  width: 100%;
}
.notes {
  margin-top: var(--s-12);
  display: grid;
  gap: var(--s-4);
  max-width: 720px;
}
.note {
  padding: var(--s-4);
  background: var(--surface);
  border-radius: var(--r-card);
  display: flex;
  gap: var(--s-4);
  align-items: flex-start;
}
.note__title {
  font-size: 14px;
  font-weight: 500;
}
.note__body {
  margin-top: var(--s-1);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-2);
}
.note__body a {
  color: var(--text);
}

/* Legal pages — the three Termly policies as static HTML, written by
   tools/termly/import.js between the page's markers: the full content
   column (the logo's left edge to the Download App button's right edge) in
   the site's own type on the cream page. Title 24/500 black (screenTitle), the
   "Last updated" line 14/500 secondary, h2 20, h3 16, body 14 Regular
   secondary at 1.6, links black and underlined. Tables are the cookie
   cards (label / value rows) and the privacy notice's category table. */
.policy {
  max-width: none;
}
.policy__title {
  font-size: 24px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--text);
}
.policy__updated {
  margin-top: var(--s-2);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-2);
}
.policy h2 {
  margin-top: var(--s-8);
  font-size: 20px;
  font-weight: 500;
  line-height: 1.3;
  color: var(--text);
}
.policy h3 {
  margin-top: var(--s-6);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text);
}
.policy p,
.policy li,
.policy td,
.policy th {
  font-size: 14px;
  font-weight: 400;
  line-height: 1.6;
  color: var(--text-2);
}
.policy p {
  margin-top: var(--s-3);
}
.policy strong {
  color: var(--text);
}
.policy a {
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: 3px;
  word-break: break-word;
}
.policy ul,
.policy ol {
  margin: var(--s-3) 0 0;
  padding-left: 20px;
}
.policy li + li {
  margin-top: var(--s-1);
}
.policy li > p:first-child {
  margin-top: 0;
}
.policy table {
  width: 100%;
  margin-top: var(--s-3);
  border-collapse: collapse;
}
.policy table + table {
  margin-top: var(--s-4);
}
.policy th,
.policy td {
  padding: var(--s-2) var(--s-3) var(--s-2) 0;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--border-light);
}
.policy th {
  font-weight: 500;
  color: var(--text);
  white-space: nowrap; /* the cookie cards' labels ("Expires in:") stay on one line */
}
.policy td > p:first-child {
  margin-top: 0;
}

/* --------------------------------------------------------------------------
   17. Consent banner — small, bottom, app tokens. Built by main.js only
       when it has to be shown.
   -------------------------------------------------------------------------- */
.consent {
  position: fixed;
  left: var(--s-4);
  right: var(--s-4);
  bottom: var(--s-4);
  z-index: 100;
  margin: 0 auto;
  max-width: 560px;
  padding: var(--s-4) var(--s-6);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-4);
  background: var(--surface);
  border-radius: var(--r-glass);
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.12);
}
.consent__text {
  flex: 1 1 220px;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-2);
}
.consent__text a {
  color: var(--text);
}
.consent__actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

/* --------------------------------------------------------------------------
   18. Motion — the Framer site's entrances, only when JS is running (the
       .js class comes from an inline script), and none at all under
       prefers-reduced-motion.
   -------------------------------------------------------------------------- */
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(-30px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
/* Hero children: 1 s rise, the first from 0.4 s, each next 150 ms later
   (--i set inline: 0.4 / 0.55 / 0.70 / 0.85 s, the last landing ~1.85 s). */
/* Copied from the Framer site: the whole hero block drops in as one unit
   from 30px above with a spring (stiffness 400, damping 30, mass 1), after
   a 0.4 s delay. The spring's slight overshoot-and-settle is reproduced with
   a linear() easing; ~0.7 s total. Children are left alone. */
.js .hero__inner {
  animation: spring-drop 0.7s linear(
    0, 0.208, 0.560, 0.845, 1.020, 1.084, 1.084, 1.052, 1.014, 0.991, 0.985, 0.990, 0.998, 1.002, 1.001, 1
  ) 0.4s both;
}
@keyframes spring-drop {
  from {
    opacity: 0;
    transform: translateY(-30px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* Logo section: opacity only, from 1.6 s (half a second after the hero
   block has settled at ~1.1 s) over 0.8 s. The ticker inside runs from
   page load, so it is already moving as it appears. */
.js .logos {
  animation: fade-in 0.8s var(--ease-fade) 1.6s both;
}
/* Below the fold: 0.9 s rise when the IntersectionObserver says so; the
   cards stagger 120 ms via --i, everything else starts at once. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(-30px);
  transition: opacity 0.9s var(--ease-rise), transform 0.9s var(--ease-rise);
  transition-delay: calc(var(--i, 0) * 120ms);
}
.js [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .js .hero [data-reveal-hero],
  .js .hero__inner,
  .js .logos {
    animation: none;
  }
  .js [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .blob__shape {
    animation: none;
  }
  .ticker {
    overflow-x: auto;
  }
  .ticker__track {
    animation: none;
  }
  .btn,
  .store-badge,
  .video-play,
  .faq-item__q::after,
  .faq-item__panel {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   19. Breakpoints — mobile first: ~768 tablet, ~1100 desktop.
   -------------------------------------------------------------------------- */
@media (min-width: 768px) {
  .only-wide {
    display: inline;
  }
  .only-phone {
    display: none;
  }
  .logo svg {
    width: 88px;
    height: 32px;
  }
  /* Tablet: the same first-screen fill as the phone (header 80 px, hero
     min-height to the logo strip, content centred), only the type grows. */
  .hero {
    padding: 72px 0 88px;
    --blob-scale: 1;
  }
  .hero__title {
    font-size: 48px;
    max-width: none;
  }
  .hero__sub {
    font-size: 20px;
  }
  .download__text .store-badges {
    align-self: flex-start;
    justify-content: flex-start;
  }
  .section {
    padding: 72px 0;
  }
  .section__title {
    font-size: 32px;
  }
  .card-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--s-4);
  }
  .choice-card__title {
    font-size: 16px;
  }
  .choice-card__desc {
    font-size: 15px;
  }
  .testimonial-track {
    gap: var(--s-4);
    width: min(100%, 884px);
    margin-left: auto;
    margin-right: auto;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 14%, #000 86%, transparent);
    mask-image: linear-gradient(to right, transparent, #000 14%, #000 86%, transparent);
  }
  .testimonial {
    flex-basis: calc((100% - 2 * var(--s-4)) / 3);
  }
  .video-embed {
    padding-top: 56.25%;
  }
  .faq__grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--s-4);
  }
  .faq__col {
    gap: var(--s-4);
  }
  .form__actions {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
  .steps {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--s-12) var(--s-8);
  }
  .step__shot {
    max-width: 260px;
  }
}

@media (min-width: 1100px) {
  :root {
    --header-h: 72px;
  }
  /* Desktop: the hero takes its natural height again. */
  .hero {
    padding: 96px 0 112px;
    min-height: 0;
    display: block;
  }
  .hero__title {
    font-size: 56px;
  }
  .section {
    padding: 96px 0;
  }
  .section__title {
    font-size: 40px;
  }
  .section__body {
    font-size: 20px;
  }
  .card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
  /* The QR block exists only on desktop: a phone or tablet scans nothing on
     its own screen. display: none plus the image's loading="lazy" means the
     SVG is never fetched below this width. */
  .download__inner {
    grid-template-columns: 1fr auto;
    gap: var(--s-12);
  }
  .qr-block {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s-4);
    width: 260px;
    padding: var(--s-6);
    background: var(--surface);
    border-radius: var(--r-card);
    text-align: center;
  }
  .qr-block img {
    width: 160px;
    height: 160px;
  }
  .qr-block__caption {
    font-size: 14px;
    font-weight: 500;
    line-height: 1.4;
    color: var(--text-2);
  }
  .video__inner {
    grid-template-columns: 1fr 1fr;
    align-items: center;
    gap: 64px;
  }
  .steps {
    grid-template-columns: repeat(5, 1fr);
    gap: var(--s-8) var(--s-6);
  }
  /* Five abreast: reserve the same text height in every column so the
     phones line up on one row whatever the caption length. */
  .step__text {
    min-height: 168px;
  }
  .step__shot {
    max-width: none;
  }
}

/* The QR code is for scanning with a phone, so it only makes sense on a
   device with a mouse. Touch devices of any width (iPad landscape included)
   never show it. */
@media (hover: none), (pointer: coarse) {
  .qr-block {
    display: none;
  }
}
