/* ============================================================================
   Huddle marketing site
   ----------------------------------------------------------------------------
   The token block below is MIRRORED from frontend/src/index.css (:root and the
   prefers-color-scheme: dark override). Keep the two in sync by hand.

   This is deliberate duplication: the marketing site is plain static HTML with
   no build step and must not depend on the app bundle, so it cannot import the
   app's stylesheet. If you change a brand colour, the type ramp, a radius or a
   shadow in the app, change it here too.

   Palette note: the deep teal from the logo mark (#163b3e) is an ACCENT only --
   it appears in the mark itself and in small details. Large dark surfaces use a
   warm near-black instead, so the page reads as one warm paper palette rather
   than two competing brands.
   ========================================================================= */

:root {
  /* --- Ground and ink (mirrored) ----------------------------------- */
  --color-bg: #faf9f7;
  --color-surface: #ffffff;
  --color-subtle-bg: #f1efea;
  --color-text: #1c1b19;
  --color-muted: #6f6b62;
  --color-faint: #8a857b; /* NOT a text colour -- furniture only */
  --color-border: #e7e3dc;

  /* --- Brand (mirrored) -------------------------------------------- */
  --color-accent: #d4673e;
  --color-accent-strong: #b8552f;
  --color-accent-text: #a8532c;
  --color-accent-contrast: #ffffff;
  --color-success: #147038;

  /* --- PR / celebration (mirrored) --------------------------------- */
  --color-pr-bg: #fbe9e0;
  --color-pr-border: #f0c4ab;
  --color-pr-text: #a8532c;

  /* --- Marketing-only brand tones (from the logo mark) ------------- */
  --color-teal: #163b3e; /* accent only */
  --color-amber: #f2a65a;

  /* The one dark surface on the page. Warm near-black, not teal. */
  --color-inverse-bg: #1c1b19;
  --color-inverse-text: #f7f5f1;
  --color-inverse-muted: rgba(247, 245, 241, 0.74);

  /* --- Elevation (mirrored) ---------------------------------------- */
  --shadow-1: 0 1px 2px rgba(28, 27, 25, 0.06);
  --shadow-2: 0 4px 12px rgba(28, 27, 25, 0.08);
  --shadow-3: 0 8px 24px rgba(28, 27, 25, 0.12);
  --shadow-4: 0 24px 48px rgba(28, 27, 25, 0.2);
  --elevation-hairline: none;

  /* --- Marketing-only elevation (not mirrored -- the app has no product screenshots
     floating over its own background) --- */
  --contact-shadow: rgba(20, 19, 15, 0.45);

  /* --- Space (mirrored, 4px grid) ---------------------------------- */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;

  /* --- Radius (mirrored) ------------------------------------------- */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 20px;
  --radius-full: 999px;

  /* --- Motion (mirrored) ------------------------------------------- */
  --dur-base: 180ms;
  --ease-out: cubic-bezier(0.2, 0, 0, 1);

  /* --- Marketing type ramp ------------------------------------------
     Larger than the app's, which tops out at 34px because it is a tool used
     mid-set. A landing page is read at arm's length, once. */
  --text-display: clamp(38px, 5.4vw, 72px);
  --text-h2: clamp(28px, 3.4vw, 42px);
  --text-h3: clamp(19px, 1.6vw, 22px);
  --text-lead: clamp(17px, 1.5vw, 20px);
  --text-body: 17px;
  --text-sm: 15px;
  --text-xs: 13px;

  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #171614;
    --color-surface: #221f1c;
    --color-subtle-bg: #2a2723;
    --color-text: #f1efea;
    --color-muted: #a39d92;
    --color-faint: #807a70;
    --color-border: #35322c;

    --color-accent-text: #e07a52;

    --color-pr-bg: #3a2417;
    --color-pr-border: #5a3520;
    --color-pr-text: #e8935f;

    --color-success: #4fa97a;

    /* In dark mode the inverse band has to go the other way to stay a band at
       all -- a near-black block on a near-black page reads as a hole. */
    --color-inverse-bg: #2f2b26;
    --color-inverse-text: #f7f5f1;
    --color-inverse-muted: rgba(247, 245, 241, 0.72);

    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.5);
    --shadow-3: 0 8px 24px rgba(0, 0, 0, 0.6);
    --shadow-4: 0 24px 48px rgba(0, 0, 0, 0.7);
    --elevation-hairline: inset 0 1px 0 rgba(255, 255, 255, 0.05);

    /* Pure black rather than the light-mode warm tone, and stronger -- same reasoning as
       --shadow-4's own light->dark jump (0.2 -> 0.7 opacity): --color-bg is much darker here,
       so the shadow needs more contrast just to register against it at all. */
    --contact-shadow: rgba(0, 0, 0, 0.65);
  }
}

/* ============================================================================
   Base
   ========================================================================= */

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

html {
  scroll-behavior: smooth;
  /* Mobile Safari can boost font-size on narrow, text-heavy columns (its
     "text autosizing" heuristic) independently of the viewport meta tag. That
     inflation is applied per-element, not to the whole page uniformly, so it
     can make one paragraph wrap differently than its siblings even though
     nothing in this stylesheet targets it. Not reproducible in Chromium, so
     this is prophylactic rather than a verified fix for any specific element. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* Shrinking duration/delay above is NOT enough by itself: a fill-forwards
     animation this short can land between two rendered frames and never
     visibly reach its `to` keyframe, leaving the element stuck at its
     pre-animation state -- opacity:0 for .reveal, plain ink for the hero's
     accent word. Confirmed live (not theoretical): every .reveal section
     rendered permanently invisible under reduced motion before this was
     added. Force the resting state directly instead of trusting the timing
     trick to get there. */
  .reveal {
    opacity: 1 !important;
    transform: none !important;
  }

  .hero__title em {
    color: var(--color-accent-text) !important;
  }
}

/* ============================================================================
   Scroll motion — a tasteful fade-and-rise as a section enters the viewport.
   The base rule plays once on load (so nothing is ever stuck invisible on a
   browser without scroll-linked animations); @supports upgrades it to a real
   scroll-linked reveal. prefers-reduced-motion gets an explicit opacity/
   transform override above, not just a shrunk duration -- see the comment
   there for why the duration trick alone isn't reliable.

   animation-range is "entry" at both ends, not "cover": cover's 100% is the
   element's entire on-screen lifetime (first pixel visible to last), which
   scales with the element's own height -- for anything taller than the
   viewport (several of these sections are) that stretched the fade over a
   large chunk of normal reading scroll, so content low in the viewport
   stayed part-transparent and hard to read. "entry" is capped at roughly one
   viewport-height of scroll regardless of the element's height, so the fade
   now resolves quickly and consistently no matter how tall the section is.
   Found on the deployed lower site, 2026-08-24. */

.reveal {
  opacity: 0;
  transform: translateY(28px);
  animation: reveal-up 640ms var(--ease-out) both;
}

@supports (animation-timeline: view()) {
  .reveal {
    animation: reveal-up linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 35%;
  }
}

@keyframes reveal-up {
  from {
    opacity: 0;
    transform: translateY(28px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: 'Barlow', -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif;
  font-size: var(--text-body);
  line-height: 1.6;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1,
h2,
h3 {
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.1;
  text-wrap: balance;
}

p {
  margin: 0;
  text-wrap: pretty;
}

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

@media (hover: hover) {
  a:hover {
    color: var(--color-accent);
  }
}

a:focus-visible,
.btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  color: var(--color-text);
  border-radius: var(--radius-md);
}

.skip-link:focus {
  left: var(--space-4);
  top: var(--space-4);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ============================================================================
   Layout
   ========================================================================= */

.section {
  padding: clamp(56px, 7vw, 104px) clamp(20px, 5vw, 72px);
}

.section--tight {
  padding-block: clamp(48px, 5.5vw, 88px);
}

.section--subtle {
  background: var(--color-subtle-bg);
}

.container {
  max-width: 1240px;
  margin: 0 auto;
}

.container--narrow {
  max-width: 1060px;
  margin: 0 auto;
}

.container--pricing {
  max-width: 900px;
  margin: 0 auto;
}

.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(32px, 5vw, 72px);
  align-items: center;
}

@media (max-width: 900px) {
  .split {
    grid-template-columns: minmax(0, 1fr);
  }
  /* Text before its illustration on narrow screens regardless of source order,
     so the alternating desktop rhythm never strands a visual at the top. */
  .split > .split__media {
    order: 2;
  }
  .split > .split__text {
    order: 1;
  }
}

.split__text {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.eyebrow {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent-text);
}

.section__title {
  font-size: var(--text-h2);
  line-height: 1.08;
}

.section__head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  text-align: center;
  margin-bottom: clamp(32px, 4vw, 56px);
}

.prose {
  font-size: clamp(17px, 1.4vw, 18px);
  line-height: 1.6;
  color: var(--color-muted);
}

.prose--lead {
  font-size: var(--text-lead);
  max-width: 58ch;
}

/* ============================================================================
   Buttons
   ========================================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 54px;
  padding: 0 30px;
  border-radius: var(--radius-md);
  border: 1.5px solid transparent;
  font-family: inherit;
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out);
}

.btn--primary {
  background: var(--color-accent-strong);
  color: var(--color-accent-contrast);
}

@media (hover: hover) {
  .btn--primary:hover {
    background: var(--color-accent);
    color: var(--color-accent-contrast);
  }
}

.btn--secondary {
  background: transparent;
  border-color: var(--color-border);
  color: var(--color-text);
}

@media (hover: hover) {
  .btn--secondary:hover {
    border-color: var(--color-accent);
  }
}

.btn--small {
  min-height: 46px;
  padding: 0 22px;
  font-size: 16px;
}

.btn--full {
  width: 100%;
}

/* On the one inverse band. Same fill as every other primary CTA: white on
   --color-accent is 3.62:1 and this label is 18px/600, just under the
   18.66px-bold threshold for AA Large, so it would fail. */
.btn--on-dark {
  background: var(--color-accent-strong);
  color: var(--color-accent-contrast);
  border-color: rgba(247, 245, 241, 0.24);
}

@media (hover: hover) {
  .btn--on-dark:hover {
    background: var(--color-accent);
    border-color: rgba(247, 245, 241, 0.45);
    color: var(--color-accent-contrast);
  }
}

/* ============================================================================
   Header
   ========================================================================= */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) clamp(20px, 5vw, 72px);
  border-bottom: 1px solid var(--color-border);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--color-text);
}

.brand__mark {
  flex-shrink: 0;
}

.brand__word {
  font-size: clamp(26px, 2.6vw, 32px);
  font-weight: 700;
  letter-spacing: -0.025em;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: clamp(16px, 2.5vw, 32px);
}

.site-nav__link {
  font-size: 16px;
  font-weight: 500;
  color: var(--color-muted);
}

.site-nav__link--strong {
  font-weight: 600;
  color: var(--color-text);
}

@media (max-width: 760px) {
  .site-nav__link--optional {
    display: none;
  }
}

/* ============================================================================
   Hero — centred, with real product shots breaking the bottom edge
   ========================================================================= */

.hero {
  padding: clamp(24px, 3vw, 44px) clamp(20px, 5vw, 72px) clamp(40px, 5vw, 64px);
  overflow: hidden;
}

.hero__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(12px, 1.4vw, 18px);
  text-align: center;
  max-width: 1000px;
  margin: 0 auto;
}

.hero__title {
  font-size: var(--text-display);
  line-height: 1;
  letter-spacing: -0.035em;
  max-width: 18ch;
}

/* One phrase carries the accent color inline -- same emphasis pattern used in
   eyebrows, links and PR highlights elsewhere on the page -- so the headline
   isn't a flat block of ink. Deliberately NOT display:block: that was the old
   forced second line, and re-adding it would undo the whitespace trim.

   Italic for shape contrast against the roman weight around it -- still
   Barlow, no second typeface introduced. The color starts on the brighter
   --color-accent (also used for buttons/dots, ~3.4:1 against the page bg --
   passes AA-large, and this is a transitional state anyway) and settles into
   the deeper, text-safe --color-accent-text a beat after the page paints, so
   the emphasis reads as arriving rather than just being printed that way --
   orange to orange, not ink to orange.
   prefers-reduced-motion (above) zeroes both animation-duration AND
   animation-delay sitewide -- duration alone isn't enough here, since a
   0.01ms transition still sits at the wrong starting color for the full
   500ms delay otherwise. */
.hero__title em {
  font-style: italic;
  color: var(--color-accent);
  animation: hero-accent-in 700ms 500ms var(--ease-out) both;
}

@keyframes hero-accent-in {
  to {
    color: var(--color-accent-text);
  }
}

.hero__lead {
  font-size: var(--text-lead);
  line-height: 1.5;
  color: var(--color-muted);
  max-width: 48ch;
}

/* Sits after .hero__devices now, not right under the lead paragraph -- the product shots are
   the pitch, so the ask comes right after the proof rather than before it. margin-top clears
   the devices' own mask-fade + reserved phone-overlap padding, which already puts some empty
   space above this; the rest is deliberate, not doubled. */
.hero__cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(12px, 1.4vw, 18px);
  margin-top: clamp(16px, 2.2vw, 28px);
  text-align: center;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
}

/* The hero's tertiary action. Demoted from a third button when "Go Pro" joined "Start free":
   three buttons compete with each other, and at 390px they stack into most of the fold. A text
   link keeps the scroll cue without pretending to be a peer of the two real CTAs.

   No animation here on purpose -- anything animated needs an explicit prefers-reduced-motion
   override forcing the resting state, not just a duration cap (see .claude/rules/marketing.md). */
.hero__more {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  color: var(--color-accent-text);
  font-weight: 600;
  text-decoration: none;
}

.hero__more:hover {
  text-decoration: underline;
}

.hero__trust {
  font-size: var(--text-sm);
  color: var(--color-muted);
  /* Explicit rather than relying on .hero__cta's inherited text-align: center --
     this is the one piece of hero copy reported as showing up left-aligned on a
     real device, not reproduced here in Chromium at any width tested. Making it
     explicit costs nothing and rules out inheritance as the cause either way. */
  text-align: center;
}

/* A tablet with a phone leaning on it, grounded by an ordinary drop shadow rather than
   fading into the page. An earlier version faded the bottom out with a CSS mask, but a mask
   clips everything painted for the element, shadow included -- it was quietly swallowing
   each .device's own box-shadow (below) along with the pixels, so the pair read as
   dissolving rather than sitting above the page. device-ipad.jpg is cropped tight to its
   real content now anyway (no dead space below the "Log set" button), so there's nothing
   left worth fading past. */
.hero__devices {
  position: relative;
  z-index: 0; /* contains the ::after contact shadow's negative z-index to this element */
  width: min(820px, 100%);
  margin: clamp(20px, 2.8vw, 40px) auto 0;
  /* Room for the phone to hang below the tablet without crowding the CTA under it. */
  padding-bottom: clamp(20px, 3.5vw, 44px);
}

/* A dedicated contact shadow under the pair, on top of each .device's own --shadow-4 below.
   --shadow-4 alone reads fine up close but washes out to nearly nothing at full hero width --
   diffuse ambient shadows do that, they're subtle by design. This is a second, tighter, darker
   shadow concentrated right under the devices specifically, which is what actually reads as
   "resting on the page" at a glance instead of on close inspection. */
.hero__devices::after {
  content: '';
  position: absolute;
  z-index: -1;
  left: 8%;
  right: 8%;
  bottom: 2px;
  height: clamp(26px, 4vw, 50px);
  background: radial-gradient(ellipse at center, var(--contact-shadow), transparent 72%);
  filter: blur(10px);
}

/* ============================================================================
   Device frames — pure CSS bezels, no chrome images
   ========================================================================= */

.device {
  background: linear-gradient(160deg, #33302b, #14130f);
  /* --shadow-4 is what grounds the hero pair now that .hero__devices no longer masks it
     away -- the same token the plan cards and compare table use, so the page's sense of
     "this is floating above the surface" reads consistently everywhere it appears. In dark
     mode a shadow alone doesn't do it: it's a near-black bezel cast onto a near-black page
     (--color-bg), so the shadow all but disappears exactly where the fade used to hide that
     same low contrast. The outer ring reuses the inset rim's own white tone to trace the
     bezel's edge regardless of how little the shadow shows against the page behind it. */
  box-shadow: var(--shadow-4), 0 0 0 1px rgba(255, 255, 255, 0.14), inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}

.device__screen {
  overflow: hidden;
  background: var(--color-bg);
  line-height: 0; /* no descender gap under the screenshot */
}

.device__screen img {
  width: 100%;
  height: auto;
}

.device--tablet {
  border-radius: clamp(20px, 3.2vw, 40px);
  padding: clamp(7px, 1.2vw, 14px);
}

.device--tablet .device__screen {
  border-radius: clamp(13px, 2.1vw, 27px);
}

/* Sized off the tablet, not the viewport: a phone is roughly a fifth of an iPad's
   landscape width, and holding that ratio is what keeps the pair reading as two
   real objects rather than two screenshots at arbitrary scales. */
.device--phone {
  position: absolute;
  right: 3%;
  bottom: -2%;
  width: 21%;
  border-radius: clamp(16px, 2.2vw, 26px);
  padding: clamp(3px, 0.6vw, 7px);
  z-index: 2;
}

/* A status-bar band above the screenshot, in the app's own header colour, so the
   island has somewhere to sit. Without it the island landed on top of the app's
   logo -- a browser screenshot starts at the page content, not below a status
   bar, so the space has to be added here. */
.device--phone .device__screen {
  border-radius: clamp(14px, 2vw, 23px);
  padding-top: clamp(11px, 1.6vw, 19px);
  background: var(--color-surface);
}

/* The dynamic island. Drawn, not captured -- a browser screenshot can't contain
   one. Decoration on a picture of a phone, which is honest enough. */
.device--phone::after {
  content: '';
  position: absolute;
  top: clamp(6px, 0.95vw, 11px);
  left: 50%;
  transform: translateX(-50%);
  width: clamp(26px, 3.6vw, 44px);
  height: clamp(7px, 0.9vw, 11px);
  border-radius: var(--radius-full);
  background: #14130f;
  z-index: 3;
}

/* Below this width the pair no longer fits, and the phone is the one to keep: a
   tablet screenshot scaled to 390px is unreadable, while a phone screenshot at
   phone size is exactly what a visitor on a phone is being sold. */
@media (max-width: 700px) {
  .device--tablet {
    display: none;
  }

  .device--phone {
    position: relative; /* keep the island anchored once it stops being absolute */
    right: auto;
    bottom: auto;
    width: min(260px, 66%);
    margin: 0 auto;
    border-radius: 34px;
    padding: 8px;
  }

  .device--phone .device__screen {
    border-radius: 27px;
    padding-top: 20px;
  }

  .device--phone::after {
    top: 12px;
    width: 58px;
    height: 15px;
  }
}

/* Below the desktop breakpoint the three-card row stops fitting; keep the one
   that actually shows the product. */
@media (max-width: 1000px) {
  .shot-card--side {
    display: none;
  }
  .shot-card--main {
    width: min(440px, 100%);
  }
}

/* ============================================================================
   Pillars
   ========================================================================= */

.pillars {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(28px, 3.5vw, 48px);
}

@media (max-width: 900px) {
  .pillars {
    grid-template-columns: minmax(0, 1fr);
  }

  /* .hero's bottom padding and this section's own top padding both apply at
     this boundary, same as at every section-to-section transition on the
     page -- but every other transition has a visible eyebrow/heading near the
     top that visually "catches" the gap. This section's <h2> is
     visually-hidden, so the same numeric spacing that reads as a normal
     breathing pause elsewhere reads as a chasm here. Trimmed for this one
     transition only, via the adjacent-sibling selector rather than a class,
     so no other section boundary is affected. */
  .hero + .section {
    padding-top: clamp(24px, 5vw, 56px);
  }
}

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

.pillar__icon {
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  background: var(--color-pr-bg);
  border: 1px solid var(--color-pr-border);
  color: var(--color-accent-text);
}

.pillar__title {
  font-size: var(--text-h3);
  line-height: 1.25;
}

/* Placed after the base .pillar rule (not inside the .pillars media query
   above) deliberately: an equal-specificity ".pillar" declared later in the
   file wins regardless of which one sits inside a matching media query, so a
   copy of this made the mistake of living above the base rule was silently
   overridden by it at every width. */
@media (max-width: 900px) {
  /* Icon-above-heading reads as a compact card in the 3-column desktop grid;
     stacked in a single full-width mobile column it reads as an unrelated
     icon floating over an unrelated headline. Icon and heading share a row
     instead, with the paragraph spanning full width below both. */
  .pillar {
    display: grid;
    grid-template-columns: 46px 1fr;
    column-gap: var(--space-4);
    row-gap: var(--space-2);
    align-items: center;
  }

  .pillar .prose {
    grid-column: 1 / -1;
  }
}

/* ============================================================================
   Product figures — real screenshots of the running app
   ========================================================================= */

/* Deliberately deeper than the app's own card elevation. In the app a card sits
   ON the page; here it is a photograph OF the app being held up, and the flatter
   shadow made these read as pasted-on rectangles. Device frames are kept for the
   hero alone so that moment stays distinct. */
.figure {
  margin: 0;
  border-radius: var(--radius-xl);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  /* The same contact shadow the hero's device pair uses (--contact-shadow), layered under
     --shadow-4's own softer, more diffuse spread -- shadow-4 alone reads fine up close but
     washes out at a glance, same issue the hero had. A pseudo-element ellipse (the hero's
     approach) can't work here: it would need to sit partly outside this box, and overflow:
     hidden below (required to clip the screenshot to these rounded corners) would cut it off.
     A second box-shadow layer isn't subject to that -- an element's own shadow is decoration
     on its border-box, not content, so its own overflow never clips it. */
  box-shadow: 0 18px 30px var(--contact-shadow), var(--shadow-4), var(--elevation-hairline);
  overflow: hidden;
}

/* height:auto matters for video in a way it doesn't for img: the base `img { height: auto }`
   rule up top covers images, but video has no such base rule, and its `height` HTML attribute
   applies as a real (low-specificity) height unless something overrides it. Without this,
   width:100% shrinks the box's width but the attribute pins height at its literal pixel value
   regardless -- harmless-looking on desktop (a modest ~10% too tall, letterboxed), badly broken
   on a narrow phone column (width shrinks much further, height doesn't move: nearly 2x too
   tall, most of the card empty). Found on the deployed lower site, 2026-08-24. */
.figure img,
.figure video {
  width: 100%;
  height: auto;
  display: block;
}

.figure--quiet {
  box-shadow: 0 12px 20px var(--contact-shadow), var(--shadow-3), var(--elevation-hairline);
}

.figure-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* The trends figures are now full real screenshots (tall, portrait) rather than
   cropped chart-only elements, so the stack is capped narrower than
   .container--narrow or it dwarfs the rest of the page. */
.figure-stack--trends {
  max-width: 640px;
  margin: 0 auto;
}

/* ============================================================================
   Pricing
   ========================================================================= */

.plans {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-6);
  margin-bottom: var(--space-8);
}

@media (max-width: 780px) {
  .plans {
    grid-template-columns: minmax(0, 1fr);
  }
}

.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding: clamp(24px, 3vw, 36px);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-2), var(--elevation-hairline);
}

.plan--featured {
  border: 2px solid var(--color-accent);
  box-shadow: var(--shadow-3), var(--elevation-hairline);
}

.plan__flag {
  position: absolute;
  top: -13px;
  left: clamp(24px, 3vw, 36px);
  padding: 4px 12px;
  border-radius: var(--radius-full);
  background: var(--color-accent-strong);
  color: var(--color-accent-contrast);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.plan__head {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.plan__name {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
}

.plan--featured .plan__name {
  color: var(--color-accent-text);
}

.plan__price {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.plan__amount {
  font-size: clamp(40px, 4.5vw, 52px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
}

.plan__period {
  font-size: 18px;
  color: var(--color-muted);
}

.plan__blurb {
  font-size: 16px;
  color: var(--color-muted);
}

.plan__features {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.plan__features li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 16px;
  line-height: 1.5;
}

.plan__features svg {
  flex-shrink: 0;
  margin-top: 2px;
}

.plan__features li.is-pending {
  color: var(--color-muted);
}

.plan .btn {
  margin-top: auto;
}

.soon-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-full);
  background: var(--color-subtle-bg);
  border: 1px solid var(--color-border);
  font-size: 12px;
  font-weight: 600;
  color: var(--color-muted);
  white-space: nowrap;
}

.note {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
}

.note svg {
  flex-shrink: 0;
  margin-top: 2px;
  color: var(--color-muted);
}

.note p {
  font-size: 16px;
  line-height: 1.55;
  color: var(--color-muted);
}

/* ============================================================================
   Comparison table
   ========================================================================= */

.compare-wrap {
  overflow-x: auto;
  border-radius: var(--radius-xl);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  box-shadow: var(--shadow-2), var(--elevation-hairline);
  margin-bottom: var(--space-6);
}

.compare {
  width: 100%;
  min-width: 520px;
  border-collapse: collapse;
  text-align: left;
}

.compare caption {
  padding: var(--space-4) 28px;
  background: var(--color-subtle-bg);
  border-bottom: 1px solid var(--color-border);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
  text-align: left;
}

.compare th,
.compare td {
  padding: 15px 28px;
  font-size: 16px;
  font-weight: 400;
  border-bottom: 1px solid var(--color-border);
}

.compare thead th {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
  background: var(--color-subtle-bg);
}

.compare thead th:last-child {
  color: var(--color-accent-text);
}

.compare td:not(:first-child),
.compare thead th:not(:first-child) {
  text-align: center;
  width: 20%;
}

.compare tbody tr:last-child th,
.compare tbody tr:last-child td {
  border-bottom: 0;
}

.compare tbody th {
  font-weight: 400;
  color: var(--color-text);
}

.compare .is-highlight th,
.compare .is-highlight td {
  background: rgba(212, 103, 62, 0.05);
  font-weight: 600;
}

.compare .is-highlight td:last-child {
  font-weight: 700;
  color: var(--color-accent-text);
}

.dash {
  display: inline-block;
  width: 14px;
  height: 2px;
  border-radius: 2px;
  background: var(--color-faint);
  vertical-align: middle;
}

/* Below this width the table's real column count (520px min-width, tuned for the
   longest row label) no longer fits without horizontal scrolling. Rather than
   scale text/padding down to force a fit, the table restacks into one card per
   feature, each showing "Free" and "Pro" as labelled rows -- the same
   information, read top-to-bottom instead of side-to-side. Column headers move
   into generated content on each cell (data-label) rather than disappearing:
   the <thead> stays in the DOM (visually hidden, not display:none) so the
   Free/Pro column association a screen reader gets from the table markup on
   wider screens isn't lost here, it's just carried differently. */
@media (max-width: 620px) {
  .compare-wrap {
    overflow-x: visible;
  }

  .compare {
    min-width: 0;
  }

  .compare,
  .compare tbody,
  .compare tr {
    display: block;
    width: 100%;
  }

  .compare thead {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }

  .compare tbody tr {
    padding: 16px 20px;
    border-bottom: 1px solid var(--color-border);
  }

  .compare tbody tr:last-child {
    border-bottom: 0;
  }

  .compare th,
  .compare td {
    display: block;
    width: auto;
    padding: 0;
    border-bottom: 0;
    text-align: left;
    background: transparent;
  }

  /* .compare td:not(:first-child) (above, unscoped -- it also sets the desktop
     column widths) outranks a plain ".compare td" on specificity regardless of
     source order, so without this the value column stays pinned at the desktop
     20%/center rules and every value wraps into a ~60px sliver. Match its
     selector to win the cascade instead of fighting it with !important. */
  .compare td:not(:first-child) {
    width: auto;
    text-align: left;
  }

  .compare tbody th {
    padding-bottom: 10px;
    font-weight: 600;
  }

  .compare td {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    padding: 7px 0;
    font-size: 15px;
  }

  .compare td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-muted);
  }

  /* The value itself -- text wrapped in .compare__value, or the bare svg/.dash
     cells that were already a single child element. margin-left: auto pushes it
     to the row's right edge without giving it flex-grow, which matters for the
     icon cells: growing an <svg> flex item stretches its box past its 19x19
     attributes and distorts the checkmark. Text values keep their natural
     (shrink-to-fit) width and wrap right-aligned, so a multi-word value like
     "Your whole family" reads as one tidy block instead of the ragged,
     inconsistently-indented lines a bare wrapping text node produces as a flex
     item with no width of its own. */
  .compare td > * {
    margin-left: auto;
    text-align: right;
  }

  .compare .is-highlight {
    background: rgba(212, 103, 62, 0.05);
  }
}

/* ============================================================================
   FAQ
   ========================================================================= */

.faq-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-10) clamp(32px, 4.5vw, 64px);
}

@media (max-width: 820px) {
  .faq-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-8);
  }
}

.faq__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.faq__q {
  font-size: 20px;
  line-height: 1.3;
}

/* ============================================================================
   Inverse band + footer
   ========================================================================= */

.band-inverse {
  background: var(--color-inverse-bg);
  color: var(--color-inverse-text);
}

.band-inverse .section__title,
.band-inverse h2 {
  color: var(--color-inverse-text);
}

.band-inverse .prose {
  color: var(--color-inverse-muted);
}

.cta-final {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  text-align: center;
}

.cta-final .prose {
  max-width: 52ch;
  font-size: var(--text-lead);
}

.site-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: var(--space-8) clamp(20px, 5vw, 72px);
  border-top: 1px solid var(--color-border);
}

.site-footer__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.site-footer__tagline {
  font-size: var(--text-sm);
  color: var(--color-muted);
  margin-left: var(--space-2);
}

.site-footer__links {
  display: flex;
  align-items: center;
  gap: 28px;
}

.site-footer__links a {
  font-size: var(--text-sm);
  color: var(--color-muted);
}


/* Terms and Privacy. Long-form prose rather than marketing sections, so headings need their own
   rhythm -- the section styles here are built for one heading per screen, not a dozen. */
.legal h2 {
  font-size: var(--text-xl, 1.25rem);
  margin-top: var(--space-8);
  margin-bottom: var(--space-3);
}

.legal .prose + .prose,
.legal ul.prose {
  margin-top: var(--space-3);
}

.legal ul.prose {
  padding-left: 1.25em;
  list-style: disc;
}

.legal ul.prose li + li {
  margin-top: var(--space-2);
}

.legal__meta {
  color: var(--color-muted);
  font-size: var(--text-sm, 0.9rem);
  margin-bottom: var(--space-6);
}
