/* Design tokens pulled directly from the CofeeAPP client (src/theme/*.ts) —
   keep in sync manually if the app's palette/type scale changes. */
:root {
  /* palette */
  --espresso: #4A3326;
  --coffee: #6F4E37;
  --caramel: #C8964F;
  --crema: #D4A373;
  --cream: #F7F3EF;
  --ink: #271D10;
  --stone: #7A6E64;
  --muted-text: #887A70;
  --mist: #E6DDD4;
  --success: #3F8F5B;
  --terracotta: #AB734C;
  --tan: #D7A675;
  --dark-container: #674B37; /* app's colors.darkContainer — NOT --coffee (#6F4E37) */
  --sand: #F2DCC5;
  --background-light: #E4D4B7;
  --review-accent: #0891B2;
  --favorite: #1BA83C;
  --brand-orange: #E8742E;
  --unwanted: #CE5A3A; /* destructive — app's `colors.destructive` */

  /* semantic */
  --color-primary: var(--coffee);
  --color-primary-dark: var(--espresso);
  --color-accent: var(--caramel);
  --color-bg: var(--background-light);
  --color-surface: var(--cream);
  --color-text: var(--ink);
  --color-text-secondary: var(--stone);
  --color-text-muted: var(--muted-text);
  --color-border: var(--mist);
  --color-on-primary: #FFFFFF;
  --color-destructive: var(--unwanted);

  /* type (Rubik, matching the app) */
  --font-body: 'Rubik', -apple-system, BlinkMacSystemFont, sans-serif;

  /* Web type scale — the "Web_ready" Figma frame steps the whole site down to
     a compact 10–18px scale (Rubik Medium). Most body copy = 10px, small
     headings = 12px, blob titles = 14px, the one large caps heading = 18px. */
  --fs-body: 10px;
  --fs-sm: 12px;
  --fs-md: 14px;
  --fs-h2: 18px;

  /* spacing scale (src/theme/spacing.ts) */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 12px;
  --space-lg: 16px;
  --space-xl: 24px;
  --space-xxl: 32px;
  --space-xxxl: 48px;

  /* radius scale */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;
  --radius-xl: 28px;
  --radius-xxl: 32px;
  --radius-pill: 999px;

  /* The hero + phone showcase are laid out at fixed pixel sizes matching
     the Figma reference (a ~1440px-wide frame). Left alone, they'd stay
     that exact pixel size forever, so on a large/high-res display (e.g.
     2560px+) they'd end up looking tiny — the same absolute size, just
     surrounded by much more empty background — rather than filling a
     similar proportion of the screen the way the 1440px reference does.
     .hero-scale / .showcase scale by this factor to compensate.

     The actual value is computed in JS and written to this property (see
     the inline <script> in index.html's <head>) — it grows from 1440px up
     to a 2560px ("2K") viewport, then holds steady beyond that so 4K/5K
     displays just get more centered empty space, never further scaling.
     This is only the fallback default (1 = no scaling), used for the brief
     moment before that script runs or if JS is disabled. */
  --ui-scale: 1;
}

* {
  box-sizing: border-box;
}

html {
  /* Always reserve room for the vertical scrollbar so the page doesn't shift
     sideways when its height crosses the viewport (e.g. expanding a patch note). */
  scrollbar-gutter: stable;
}

html, body {
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-body);
  background: var(--color-bg);
  color: var(--color-text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--color-primary);
}

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

.container {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 var(--space-xl);
}

/* ---------- Header (legal pages only — the main landing page has none) ---------- */
/* `width: 100%` is load-bearing: <body> is a flex column, and `.container`'s
   `margin: 0 auto` are auto CROSS-axis margins, which suppress flex stretch. Without
   an explicit width the header shrink-to-fits its logo and the auto margins then
   centre it mid-page. With it, the header fills the 1080px column and the logo sits
   at the column's left edge, aligned with the article text below. */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Keep `.container`'s horizontal inset (the shorthand would otherwise zero it),
     so the logo lines up with the article text below rather than the column edge. */
  padding: var(--space-lg) var(--space-xl);
  width: 100%;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-weight: 600;
  font-size: 20px;
  color: var(--color-text);
  text-decoration: none;
}

/* Optical alignment. The wordmark's leftmost ink is the tilted "your" tag, but the
   eye reads the "d" as where the logo begins. In the 229-unit artwork the "d"
   starts at x≈27.07, so at the rendered 150px width it sits ~17.7px in from the
   image box. Pull the logo left by exactly that, so the "d" lands on the text
   column below and the "your" tag overhangs into the margin. */
.site-header .brand {
  margin-left: calc(-27.07 / 229 * 150px);
}

/* The brand is the full "your Dial" wordmark (assets/wordmark.svg) on the
   legal-page headers — dark letters on the light page. */
.brand img {
  display: block;
  width: 150px;
  height: auto;
}

.site-header nav a {
  color: var(--color-text-secondary);
  text-decoration: none;
  font-size: 14px;
  margin-left: var(--space-xl);
}

.site-header nav a:hover {
  color: var(--color-primary-dark);
}

/* ---------- Hero + phone showcase: one pinned scene (see
   js/phones-scroll.js) ----------
   #introPin is a tall scroll runway. #introSticky is the ONE sticky
   element inside it (top:0, 100vh), so it stays fixed on screen for the
   whole runway — hero doesn't move. The phone showcase (#phonesSticky,
   absolutely positioned below hero) reveals via opacity/transform only
   (see js/phones-scroll.js) rather than being clipped — #introSticky
   deliberately has no overflow:hidden, since the collage (950px) is
   taller than the 100vh sticky box and clipping would permanently hide
   whatever falls past that box, even once scrolled further (a sticky
   element's own box never grows to expose more of an overflowing child).
   Only once phone1(+caption1) fully rises does the runway run out,
   releasing #introSticky so hero + phones scroll away together normally,
   with phone2/phone3(+captions) continuing to reveal from then on. Below
   the 901px breakpoint there's no runway/sticky — plain stacked flow,
   everything shown normally (see the same breakpoint in
   phones-scroll.js). */
.intro-pin {
  position: relative;
}

@media (min-width: 901px) {
  /* Short runway — the scroll distance the hero stays pinned before it
     releases and scrolls away (phone1 + caption1 rise into place over this
     span; see PINNED_STEPS in js/phones-scroll.js). SHORTER = hero starts
     its exit sooner, so the fully-composed frame (all phones up) is reached
     with less scrolling. Runway itself = this height minus 100vh (the
     sticky element's own height): 115vh → ~15vh of pinned scroll. */
  .intro-pin {
    height: 115vh;
  }

  .intro-sticky {
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    flex-direction: column;
    /* Centered during the intro so the build-on animation plays EXACTLY as before
       (the block re-centers as each piece appears). Once the intro finishes,
       js/hero-animation.js adds `.intro-anchored`, which top-anchors the hero so it
       settles at the SAME height as the Patch notes / Roadmap tabs (no shift when
       switching tabs). The hero glides between the two via a FLIP (hero-animation.js). */
    justify-content: center;
    padding-bottom: 32vh;
  }

  /* Post-intro resting position: top-anchored, matching the tab pages. */
  html.intro-anchored .intro-sticky {
    justify-content: flex-start;
    padding-bottom: 0;
  }

  /* Absolutely positioned (NOT a normal flex child) — .phone-collage's own
     950px height would otherwise always reserve that much space in
     #introSticky's flex layout regardless of its children's opacity,
     forcing hero to be centered against a ~1400px-tall combined group and
     squeezed almost entirely off-screen even before any scrolling. Taking
     it out of flow means hero centers against only its own height, and
     the phone showcase overlays independently, positioned by
     js/phones-scroll.js to sit right below (slightly overlapping) hero. */
  .phones-sticky {
    position: absolute;
    left: 0;
    right: 0;
    /* Decorative only — its full-width transparent box overlaps the hero/tab
       region and would otherwise swallow clicks meant for the tab bar. Let
       pointer events pass through to the tabs beneath (the phones aren't
       interactive; the scroll hint is a separate fixed element). */
    pointer-events: none;
  }

  /* See --ui-scale in :root. Scaling .showcase (max-width:1200px) rather
     than #phonesSticky (which spans the FULL viewport width via left:0;
     right:0) — scaling that full-width element made it render past both
     screen edges (transform grows outward from transform-origin without
     changing the box's own document-flow width), creating a real
     horizontal scrollbar. .showcase's own max-width keeps its scaled
     (up to 1200px * 1.5 = 1800px) render comfortably inside typical large
     viewports instead. transform-origin anchors growth from the top edge
     (so it starts at the same place hero ends) while growing evenly
     left/right (still centered, since it grows from its own center) and
     downward. */
  .showcase {
    transform: scale(var(--ui-scale));
    transform-origin: top center;
  }
}

/* See --ui-scale in :root and the comment on .hero-scale in index.html for
   why this is a separate wrapper from #heroContent rather than applied to
   it directly. */
.hero-scale {
  transform: scale(var(--ui-scale));
  transform-origin: top center;
}

/* ---------- Scroll hint (Figma atom 332:100619) ----------
   Fixed to the bottom-center of the viewport so it sits at the bottom of
   the hero screen. Starts hidden (opacity 0); js/scroll-indicator.js adds
   .is-visible a beat after the hero composes and toggles it on scroll —
   the 1s opacity transition is the slow fade in/out the brief asked for.
   Scaled with --ui-scale to stay proportional to the rest of the composition
   on large displays. */
.scroll-indicator {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(40px + 5vh);
  display: flex;
  justify-content: center;
  opacity: 0;
  /* Fade OUT — the transition of the state being animated TO wins, and
     removing .is-visible lands back on this base rule (500ms, snappier). */
  transition: opacity 500ms ease;
  pointer-events: none;
  z-index: 50;
}

.scroll-indicator.is-visible {
  opacity: 1;
  /* Fade IN stays slower (1s) — a gentle arrival after the hero composes. */
  transition: opacity 1000ms ease;
}

/* Clickable ONLY while shown, and only over the visible hint (not the full-width
   strip) — clicking it auto-scrolls the logo to the top (js/scroll-indicator.js). */
.scroll-indicator.is-visible .scroll-indicator-inner {
  pointer-events: auto;
  cursor: pointer;
}

/* Full-width flex centering above handles the horizontal centering; the
   scale lives here on the inner box so it pivots around its own
   bottom-center without nudging that centering off (a combined
   translateX(-50%) + scale on one element leaves it a few px off-center). */
.scroll-indicator-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 75px;
  transform: scale(var(--ui-scale, 1));
  transform-origin: bottom center;
}

/* Web_ready Scroll atom: Rubik Medium 10px, muted ink, ~4px above the chevrons. */
.scroll-indicator-label {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--fs-body); /* 10px */
  line-height: 1;
  color: var(--muted-text); /* #887a70 */
  margin-bottom: 4px;
}

/* Three chevrons stacked 9px apart (each 75×9), exactly as in the atom. */
.scroll-chevrons {
  position: relative;
  width: 75px;
  height: 27px;
}

.scroll-chevron {
  position: absolute;
  left: 0;
  display: block;
  width: 75px;
  height: 9px;
  /* Staggered so the bright peak travels top→bottom = downward motion. */
  animation: scroll-chevron-pulse 2.4s ease-in-out infinite;
}

.scroll-chevron path {
  fill: var(--terracotta);
}

.scroll-chevron-1 { top: 0; animation-delay: 0s; }
.scroll-chevron-2 { top: 9px; animation-delay: 0.3s; }
.scroll-chevron-3 { top: 18px; animation-delay: 0.6s; }

@keyframes scroll-chevron-pulse {
  0%, 100% { opacity: 0.1; }
  50% { opacity: 1; }
}

/* Respect reduced-motion: hold the atom's static top→bottom opacity ramp
   (0.1 / 0.5 / 1) instead of animating. */
@media (prefers-reduced-motion: reduce) {
  .scroll-chevron { animation: none; }
  .scroll-chevron-1 { opacity: 0.1; }
  .scroll-chevron-2 { opacity: 0.5; }
  .scroll-chevron-3 { opacity: 1; }
}

.hero {
  text-align: center;
  padding: var(--space-xxxl) 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: var(--color-bg);
}

.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;
}

/* Single rigid group containing the logo + copy + links — see
   js/hero-animation.js: revealing a new piece slides this WHOLE wrapper up
   together (FLIP), so the logo and the newly-revealed piece always move as
   one unit instead of the logo animating alone through space the next
   piece already occupies. */
.hero-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: transform 0.5s ease;
}

/* Animated wordmark — see js/hero-animation.js. Each .dl-piece's opacity and
   transform are driven by JS toggling data-phase on the wrap; the transition
   here just supplies the easing/duration so JS only needs to set end-state
   values (compact/mid/final), matching the app's own build-on sequence. */
.dial-logo-wrap {
  margin: 0 auto var(--space-xxxl); /* 48px gap to the subtitle (Web_ready) */
  width: 189px; /* Web_ready logo_full: 189 x 85.6 */
}

.dial-logo {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.dl-piece {
  transition: opacity 0.42s ease-out, transform 0.42s ease-out;
}

/* Sized/positioned to match the Figma tagline frame exactly (233.399 x
   23.483, ~17.6px below the logo) — the source SVG's own width/height are
   percentage-based with preserveAspectRatio="none", so an explicit size is
   required here or the browser distorts the letterforms (see the
   .playstore-wordmark comment for the same underlying issue). */
.dial-tagline {
  display: block;
  width: 179px;   /* Web_ready tagline: 178.9 x 18 */
  height: 18px;
  /* Not centered — the logo's own visual weight leans left (the "your"
     cluster + slanted strokes vs. the thin "l" bar), so Figma anchors the
     tagline ~19px from the left edge rather than mathematically centering it. */
  margin: 13px 0 0 19px;
  transition: opacity 0.4s ease;
}

.hero h1 {
  font-size: 44px;
  line-height: 1.15;
  margin: 0 0 var(--space-md);
  color: var(--color-primary-dark);
}

/* Matches Figma's Typography/Body Medium spec exactly: Rubik Medium 14px,
   --ink, 478px measure — not a guess, the earlier 19px/secondary-color
   version didn't match the source. */
.hero p.subtitle {
  font-size: var(--fs-sm); /* 12px (Web_ready) */
  font-weight: 500;
  color: var(--ink);
  max-width: 382px;
  margin: 0 auto var(--space-xxxl); /* 48px down to the tab bar */
}

/* Generic fade-up reveal, used for anything that should appear only once
   its animation stage triggers (see js/hero-animation.js). Starts
   display:none (set inline by the script) so it takes no layout space and
   the hero can stay perfectly centered on just the logo until then. */
.reveal-fade {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}

.reveal-fade.is-in {
  opacity: 1;
  transform: translateY(0);
}

.hero-ctas {
  display: flex;
  gap: var(--space-md);
  justify-content: center;
  flex-wrap: wrap;
}

/* ---------- Buttons / badges ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 14px 28px;
  border-radius: var(--radius-pill);
  font-weight: 600;
  font-size: 15px;
  text-decoration: none;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.btn-primary {
  background: var(--color-primary-dark);
  color: var(--color-on-primary);
}

.btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 8px 20px rgba(39, 29, 16, 0.25);
}

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

/* ---------- Download CTA (QR + store badges, under the hero copy) ----------
   Sizes below are pulled directly from Figma's "Store download button" atom
   (node 305:93523) — 180px QR frame with a 150px code inset 15px on each
   side, 120x40 badges, 24px badge radius, 20px gap between badges, and a
   64px gap between the QR block and the badges column (244px badge-column
   x minus the 180px QR frame width). */
/* `hidden` must win over the display:flex below (it's the beta-period default —
   the QR + badges only show at launch; see the launch note in index.html). */
.download-cta[hidden] { display: none; }
/* Figma node 402:50409: the button column sits at left 141 while the 120-wide
   QR frame starts at left 0 — so the gap between the two columns is 21. */
.download-cta {
  display: flex;
  gap: 21px;
  align-items: flex-start;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: var(--space-xxl);
}

/* Figma "Links / property1=AppLive" (node 402:50409) — exact spec.
   Frame 402:49609: column, gap 5.333, centered. */
.qr-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5.333px;
}

/* Node 402:49611 — 120x120, 1.333 coffee border, radius 10.667, cream fill.
   The QR leaf inside (402:49612) is 100x100 at a 10px offset, so the frame's
   padding is 10 and the image is NOT scaled to fill. */
.qr-frame {
  width: 120px;
  height: 120px;
  border-radius: 10.667px;
  background: var(--cream);
  border: 1.333px solid var(--coffee);
  display: flex;
  align-items: center;
  justify-content: center;
  /* No padding: the 10px inset comes from the leaf being a fixed 100px inside
     the 120px box. Padding + border would stack, squeezing the leaf to 97.3. */
  padding: 0;
}

/* Leaf pinned to 100x100 (node 402:49612), not stretched to fill — the border
   sits over the inset, exactly as in Figma where 10 + 100 + 10 = 120. */
.qr-frame img {
  width: 100px;
  height: 100px;
  display: block;
}

/* Node 402:49613 — Typography/WebsiteBody/Medium: Rubik Medium 10 / #271d10. */
.qr-caption {
  font-size: var(--fs-body); /* 10 */
  font-weight: 500;
  line-height: 1; /* token lineHeight 100% */
  color: var(--color-text);
}

.store-badges {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Figma node 402:49614 — column gap 20, and the column starts 10 below the
     QR frame's top edge (top: 10 against the QR block's top: 0). */
  gap: 20px;
  margin-top: 10px;
}

/* Official Apple/Google marketing badges (assets/apple-store-badge.svg,
   assets/google-play-badge.png) — not hand-built recreations. Both
   companies require their unmodified badge artwork under their brand
   guidelines, and we don't have their real fonts (SF Compact/Product Sans)
   to recreate one faithfully anyway. Live links since 2026-08-13 — both
   stores' terms only permit this artwork when it points at a real listing.

   Fixed to a shared WIDTH (not height): the two badges have different
   native aspect ratios, so matching their height instead left them
   noticeably different widths — a common badge-pairing convention is to
   line them up on a shared width and let height follow each one's own
   ratio. */
.store-badge-link {
  display: block;
  transition: opacity 120ms ease, transform 120ms ease;
}
.store-badge-link:hover { opacity: 0.85; }
.store-badge-link:active { transform: scale(0.985); }
.store-badge-link:focus-visible {
  outline: 3px solid var(--coffee);
  outline-offset: 3px;
  border-radius: 6px;
}

/* Figma nodes 402:49615 / 402:49616 are 120 wide. The design redraws the
   badges with a 24 radius; we deliberately do NOT — the official artwork ships
   with each store's own corner treatment and their brand guidelines forbid
   altering it. Width is the one dimension we match. */
.store-badge-official {
  display: block;
  width: 120px;
  height: auto;
}

/* ---------- Phone showcase ---------- */
.showcase {
  max-width: 1200px;
  margin: 0 auto;
  padding: var(--space-xxl) var(--space-xl) var(--space-xxxl);
  width: 100%;
}

.phone-collage {
  position: relative;
  width: 100%;
  height: 950px;
}

.phone-mockup {
  position: absolute;
  width: 250px; /* Web_ready (was 281) */
  filter: drop-shadow(0 24px 34px rgba(74, 51, 38, 0.28)) drop-shadow(0 4px 10px rgba(74, 51, 38, 0.16));
}

/* One image = frame + screen, cut out on transparency (see index.html). The
   old two-layer bezel/screen pair is gone, and with it the border-radius that
   rounded the bare screenshot's corners — the cut-out carries its own. */
.phone-mockup .phone-shot {
  width: 100%;
  height: auto;
  display: block;
}

/* Positions from the Web_ready "Phones" frame (1440 ref), mapped into the
   1200 collage: mock x minus the arrangement's left origin (243), centred
   (+114.5 offset); mock y kept as-is. Each caption blob overlaps its phone. */
.phone-1 { left: 115px; top: 52px; z-index: 1; }  /* map (iPhone 13, mock 243,461) */
.phone-2 { left: 836px; top: 0px; z-index: 1; }   /* filters (iPhone 15, mock 964,409) */
.phone-3 { left: 437px; top: 395px; z-index: 2; } /* review (iPhone 14, mock 565,804) */

/* Exact spec from the Figma "Blob" atom (node 294:31560): 270px wide,
   cream-80% fill, 8px backdrop blur, 6px accent-soft glow, 32px radius,
   asymmetric padding (16 top / 32 sides / 24 bottom), and an 8px gap
   between the H1 title and the body — both set in Rubik Medium (500) with
   normal line-height. */
.caption-card {
  position: absolute;
  width: 270px;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  background: rgba(247, 243, 239, 0.8);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-radius: var(--space-xl); /* 24px (Web_ready blob) */
  padding: var(--space-lg) var(--space-lg) var(--space-lg) var(--space-xl); /* 16 / 16 / 16 / 24 */
  box-shadow: 0 0 6px rgba(215, 166, 117, 0.5);
  z-index: 3;
}

.caption-card h3 {
  font-size: var(--fs-md); /* 14px */
  font-weight: 500;
  line-height: normal;
  margin: 0;
  color: var(--espresso);
}

.caption-card p {
  font-size: var(--fs-body); /* 10px */
  font-weight: 500;
  line-height: normal;
  margin: 0;
  color: var(--muted-text);
}

.caption-1 { left: 279px; top: 182px; } /* blob over phone-1 (mock 407,591) */
.caption-2 { left: 679px; top: 235px; } /* blob over phone-2 (mock 807,644) */
.caption-3 { left: 574px; top: 541px; } /* blob over phone-3 (mock 702,950) */

/* ---------- Footer (Figma node 301:49682) ----------
   Coffee band, 32px top/bottom + 80px left/right padding. Content capped at
   1440 and centred so it aligns with the (capped) composition above instead
   of sprawling edge-to-edge on ultra-wide screens. */
.site-footer {
  position: relative;
  background: var(--coffee);
  color: var(--color-on-primary);
}

.footer-inner {
  width: 100%;
  max-width: 1440px;
  margin: 0 auto;
  box-sizing: border-box;
  padding: var(--space-xl) 80px; /* 24 vertical / 80 horizontal (Web_ready) */
  /* 16px between the top row and the divider/copyright group (Web_ready). */
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  /* Scale the footer content up on wide screens in lockstep with the rest of
     the composition (.hero-scale / .showcase use --ui-scale via transform).
     Without this the footer's 10px stays fixed while the scaled body grows,
     so it reads as too small on >1440 displays. `zoom` (not transform) so the
     coffee band's own height grows to match — the footer has no scroll-scene
     JS, so the zoom-coordinate caveat that rules it out on the hero doesn't
     apply here.

     --ui-scale is maintained by js/ui-scale.js. Don't worry about loading it on
     a new page: js/include-footer.js (which you need anyway to get the footer)
     pulls it in automatically if nothing else has. The `1` fallback only applies
     with JS off entirely — an unscaled footer, never a broken one. */
  zoom: var(--ui-scale, 1);
}

.footer-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-xl);
}

.footer-logo {
  display: block;
  width: 150px; /* Web_ready footer logo */
  height: auto;
}

/* Three link columns, 80px apart; Rubik Medium 10px, normal line-height. */
.footer-links {
  display: flex;
  gap: 80px;
  flex-wrap: wrap;
  font-weight: 500;
  font-size: var(--fs-body); /* 10px */
  line-height: normal;
}

.footer-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm); /* 8px */
}

.footer-col-title {
  color: #e8742e;
  margin: 0;
}

.footer-col-body {
  margin: 0;
  opacity: 0.8;
}

.footer-col-body p {
  margin: 0;
}

.footer-email {
  color: var(--crema);
  text-decoration: none;
}

.footer-col-link {
  margin: 0;
  opacity: 0.8;
}

.footer-col-link a,
.footer-email:hover {
  color: inherit;
}

.footer-col-link a {
  color: var(--color-on-primary);
  text-decoration: none;
}

.footer-col-link a:hover,
.footer-email:hover {
  text-decoration: underline;
}

/* Destructive link (Delete account) — the app's "unwanted" token. Full opacity so
   the red reads true against the brown footer (the base rule dims links to 0.8). */
.footer-col-link--danger {
  opacity: 1;
}

.footer-col-link--danger a {
  color: var(--color-destructive);
}

/* Divider + copyright, 4px apart (Web_ready bottom Frame). */
.footer-bottom-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.footer-divider {
  height: 1px;
  background: rgba(255, 255, 255, 0.13);
}

.footer-bottom {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* 32px box with a 26.233px avatar centred inside it (Figma). */
.footer-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
}

.footer-avatar img {
  width: 26.233px;
  height: 26.233px;
  border-radius: 50%;
  object-fit: cover;
}

.footer-copyright {
  font-weight: 500;
  font-size: var(--fs-body); /* 10px */
  line-height: normal;
  opacity: 0.4;
  margin: 0;
}

/* Decor dial — floats in the cream area just above the footer's right side
   (Web_ready: 48px, 80px from the right edge, its bottom ~27px above the
   footer's top edge). Anchored to the footer top (bottom:100% + margin) so it
   stays put regardless of footer height. The notch swings via js/footer-dial.js. */
.footer-decor-dial {
  position: absolute;
  right: 80px;
  bottom: 100%;
  margin-bottom: 27px;
  width: 48px;
  height: 48px;
  pointer-events: none;
}

.footer-decor-dial svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* ---------- Legal doc pages ----------
   No card — the doc sits directly on the page background so it blends in
   (the .container already supplies the side gutters). */
.legal-doc {
  background: transparent;
  padding: 0;
  margin: var(--space-xl) 0 var(--space-xxxl);
}

.legal-doc h1 {
  color: var(--color-primary-dark);
}

.legal-doc h2 {
  color: var(--color-primary-dark);
  font-size: 20px;
  margin-top: var(--space-xxl);
}

.legal-doc h3 {
  font-size: 16px;
  color: var(--color-text);
}

/* ---------- Mobile / tablet (below the desktop pinned breakpoint) ----------
   Layout mirrors the Figma mobile frame (335:14601): a zig-zag stack where
   each caption "blob" overlaps its own phone, no QR in the hero, and a
   compact footer. Everything in the phone stack is placed by PERCENT of the
   375-wide frame (locked via aspect-ratio) so the whole composition scales
   down cleanly on narrower phones instead of relying on fixed pixels. */
@media (max-width: 900px) {
  /* No scroll hint on mobile — touch users know to scroll, and the badges
     already sit near the fold. */
  .scroll-indicator {
    display: none;
  }
  /* ----- Hero: keep content off the edges; drop the QR (badges only) ----- */
  .hero {
    padding-left: 16px;
    padding-right: 16px;
  }
  .hero-scale,
  .hero-content {
    max-width: 100%;
  }
  .hero p.subtitle {
    max-width: 100%;
  }
  .qr-block {
    display: none;
  }
  .store-badges {
    margin-top: 0;
  }

  /* ----- Phone showcase: blob-over-phone, zig-zagged (Figma coords, as %
     of the 375 frame). The collage holds the frame's aspect so % tops map to
     the right place at any width; phones/blobs stay absolutely positioned. */
  .showcase {
    max-width: none;
    /* Extra bottom room so the phones aren't cramped against the footer. */
    padding: 0 16px 64px;
  }
  .phone-collage {
    position: relative;
    width: 100%;
    max-width: 375px;
    height: auto;
    margin: 0 auto;
    aspect-ratio: 375 / 1810;
    /* Held hidden through the hero's intro (logo → copy → links) — desktop
       uses the scroll pin for this; mobile has no pin, so gate on the
       intro-done flag set in js/hero-animation.js. Space is still reserved
       (opacity, not display) so revealing it doesn't reflow the page; the
       phones just fade in once the intro finishes. */
    opacity: 0;
    transition: opacity 0.6s ease;
  }

  html.intro-done .phone-collage {
    opacity: 1;
  }
  .phone-mockup {
    position: absolute;
    width: 70.667%; /* 265 / 375 */
  }
  .caption-card {
    position: absolute;
    width: 72%; /* 270 / 375 */
    max-width: none;
    text-align: left;
    margin: 0;
  }
  .phone-1   { left: 4.267%;  top: 0; }
  .caption-1 { left: 23.733%; top: 9.28%; }
  .phone-2   { left: 25.067%; top: 34.53%; }
  .caption-2 { left: 2.4%;    top: 52.82%; }
  .phone-3   { left: 4%;      top: 69.72%; }
  .caption-3 { left: 23.733%; top: 78.67%; }

  /* ----- Footer: 16px gutters, stacked link columns, no logo (saves the
     vertical real-estate on small screens) ----- */
  .footer-inner {
    padding: var(--space-xxl) 16px;
  }
  .footer-brand {
    display: none;
  }
  .footer-links {
    flex-direction: column;
    gap: var(--space-xxl);
  }

  /* Decor dial hugs the right edge, floating ~30px above the coffee band.
     Smaller than desktop (44px) — 66px read as too big on a phone. top =
     -(44 dial + 30 gap). */
  .footer-decor-dial {
    right: 16px;
    bottom: auto;
    top: -74px;
    width: 44px;
    height: 44px;
  }

  /* Legal-page header logo a touch smaller on narrow screens. */
  .brand img {
    width: 120px;
  }
}

@media (max-width: 600px) {
  .container {
    padding: 0 var(--space-lg);
  }
  .site-header nav a {
    margin-left: var(--space-md);
    font-size: 13px;
  }
}

/* ============================================================================
   Hero tabs (Get the App / Patch notes / Roadmap) — pulled from the Figma
   Web_ready frames. Sits where the download CTA used to; revealed as one unit
   with the subtitle (js/hero-animation.js), switched by js/tabs.js.
   ========================================================================== */
.hero-tabs { width: 100%; }

/* --- Tab bar (Figma TabBar: Rubik 18, active = medium + underline) --- */
.tabbar {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-md); /* 12 */
  margin: 0 0 var(--space-xxl); /* 32px down to the active panel (Web_ready) */
}
.tab {
  appearance: none;
  background: none;
  border: 0;
  padding: 2px;
  font-family: var(--font-body);
  font-size: var(--fs-sm); /* 12px (Web_ready NavButton) */
  font-weight: 400;
  line-height: 1;
  color: var(--ink);
  cursor: pointer;
  text-underline-position: from-font;
  transition: opacity 0.2s ease;
  /* Stack the visible label over a zero-height Medium-weight ghost of the same
     text (::after) so the button is always as wide as its bold state — hovering
     (which switches Regular→Medium) then can't widen it and reflow the bar. */
  display: inline-flex;
  flex-direction: column;
  align-items: center;
}
.tab::after {
  content: attr(data-label);
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 500;
  pointer-events: none;
  user-select: none;
}
/* Hover: Rubik Medium + underline + slight fade (Web_ready NavButton "Hover"). */
.tab:hover {
  font-weight: 500;
  text-decoration: underline;
  opacity: 0.75;
}
/* Selected tab: Rubik Medium + underline, ink colour. */
.tab.is-active {
  font-weight: 500;
  text-decoration: underline;
  opacity: 1;
}
/* Pressed: muted ink (Web_ready NavButton "Down"). */
.tab:active {
  font-weight: 500;
  text-decoration: underline;
  color: var(--muted-text);
}

/* --- Panels: active is in flow (sizes the container); others overlay --- */
.tab-panels {
  position: relative;
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  /* Inactive panels are absolute + opacity 0, but they keep their full height —
     the tall Patch notes panel was overflowing this box and extending the
     DOCUMENT's scrollable height, so Patch notes and Roadmap could scroll ~780px
     past the footer into empty space. `clip` removes that from the scroll area
     (unlike `hidden` it makes no scroll container); the clip margin keeps the
     30px slide-in and the cards' shadows from being cut. */
  overflow: clip;
  overflow-clip-margin: 40px;
}
.tab-panel {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  transform: translateX(30px);
  transition: opacity 0.3s ease, transform 0.3s ease;
  pointer-events: none;
}
.tab-panel.is-active {
  position: relative;
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

/* --- Beta "get the app" block (Figma 536:105392). The Figma frame places the app's
   Button component at 0.8 scale, so its values land fractional (12.8 / 19.2 / 44.8 /
   10.667); un-scaled and snapped to the token scale below. Stands in for the official
   store badges until launch — Apple's/Google's badge terms require those to point at a
   real store listing, which TestFlight and a request form are not. --- */
.beta-get {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xl); /* 24 */
}
.beta-get-lede {
  max-width: 400px; /* Figma 406 */
  margin: 0;
  font-size: var(--fs-sm); /* 12 */
  font-weight: 500;
  color: var(--ink);
  text-align: center;
}
.beta-get-row {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-xl); /* 24 — Figma 21 between the QR and the button column */
  flex-wrap: wrap;
}
/* Beta QR: 120 frame with the code inset 10 (Figma 120/100). */
.qr-frame--beta {
  width: 120px;
  height: 120px;
  padding: 10px;
  border-width: 2px;
  border-radius: var(--radius-md); /* 10 */
}
.qr-frame--beta img {
  display: block;
  width: 100%;
  height: 100%;
}
.qr-caption--beta { font-size: var(--fs-body); /* 10 */ }

.beta-get-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg); /* 16 */
  margin-top: 10px; /* Figma: column starts 10 below the QR's top edge */
}
.beta-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 232px;
  min-height: 44px;
  padding: var(--space-md) var(--space-lg); /* 12 / 16 */
  border-radius: var(--radius-pill);
  font-size: var(--fs-sm); /* 12 */
  font-weight: 500;
  letter-spacing: 0.15px;
  text-align: center;
  text-decoration: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.beta-btn:hover { opacity: 0.9; }
.beta-btn:active { transform: scale(0.985); }
.beta-btn:focus-visible { outline: 3px solid var(--coffee); outline-offset: 3px; }
.beta-btn--primary { background: var(--dark-container); color: var(--cream); }
.beta-btn--secondary { background: var(--tan); color: var(--ink); }

/* A QR is useless on the device you're already holding — phones get the buttons. */
@media (max-width: 640px) {
  .beta-get-row .qr-block { display: none; }
  .beta-get-links { margin-top: 0; }
}

/* --- Internal-beta card (Figma Links / property1=Internal Beta) --- */
.beta-card {
  width: 100%;
  max-width: 215px; /* Web_ready */
  background: var(--cream);
  border-radius: var(--radius-lg); /* 16 */
  padding: var(--space-lg) var(--space-xl); /* 16 vertical / 24 horizontal */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg); /* 16 */
  text-align: center;
}
.beta-card-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs); /* 4 */
  width: 100%;
}
.beta-card-title {
  margin: 0;
  font-size: var(--fs-sm); /* 12px */
  font-weight: 500;
  color: var(--ink);
}
.beta-card-sub {
  margin: 0;
  font-size: var(--fs-body); /* 10px */
  font-weight: 500;
  color: var(--muted-text);
}
.beta-card-cta {
  font-size: var(--fs-sm); /* 12px */
  font-weight: 500;
  color: var(--ink);
  text-decoration: underline;
  text-underline-position: from-font;
  transition: color 0.2s ease;
}
.beta-card-cta:hover { color: var(--coffee); }

/* --- Patch-notes accordion (matches the app's advanced-filters panel) --- */
.patch-notes { width: 100%; }
.patch-notes-card {
  /* Transparent — no backer card; the notes sit directly on the page, separated
     by accentSoft dividers. */
  text-align: left;
}
.patch-note { border-bottom: 1px solid var(--tan); } /* accentSoft #d7a675 */
.patch-note:last-child { border-bottom: 0; }
.patch-note-head {
  appearance: none;
  background: none;
  border: 0;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: var(--space-sm) 0; /* 8px */
  cursor: pointer;
  font-family: var(--font-body);
  text-align: left;
}
.patch-note-title {
  font-size: var(--fs-body); /* 10px */
  font-weight: 500;
  color: var(--ink);
}

/* "OUT NOW!" flag on the current release — plain green text, no fill. Green
   rather than the brand terracotta because red/orange shades are reserved for
   destructive actions across the product. Uppercasing is done in CSS so screen
   readers read a normal word instead of spelling out letters. */
.patch-note-flag {
  margin-left: 6px;
  color: var(--success);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.patch-note-chevron {
  flex: none;
  width: 24px;
  height: 24px;
  color: var(--ink);
  transition: transform 0.25s ease;
}
.patch-note.is-open .patch-note-chevron { transform: rotate(90deg); }

/* Collapse via grid-template-rows 0fr→1fr (animatable auto-height). */
.patch-note-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease;
}
.patch-note.is-open .patch-note-body { grid-template-rows: 1fr; }
.patch-note-body-inner { overflow: hidden; }

.patch-note-group { padding: 4px 0 12px; }
.patch-note-group:last-child { padding-bottom: 16px; }
.patch-note-label {
  margin: 0 0 4px;
  font-size: var(--fs-body); /* 10px */
  font-weight: 500;
}
.patch-note-label--new { color: var(--favorite); }        /* #1BA83C */
.patch-note-label--improved { color: var(--review-accent); } /* #0891B2 */
/* Thematic section labels (grouped-by-feature notes) — brand terracotta eyebrow. */
.patch-note-label--section { color: var(--terracotta); text-transform: uppercase; letter-spacing: 0.08em; }
.patch-note-group ul {
  margin: 0;
  padding-left: 15px; /* Web_ready li indent (ms-15px) */
  display: flex;
  flex-direction: column;
  gap: var(--space-xs); /* 4px */
}
.patch-note-group li {
  font-size: var(--fs-body); /* 10px */
  font-weight: 500;
  color: var(--ink);
  line-height: 1.4;
}

/* --- Roadmap placeholder --- */
.roadmap-soon {
  margin: 48px 0;
  font-size: 18px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-text);
  text-align: center;
}

/* --- Non-app tabs have no phone showcase: hide it, drop the scroll hint, and
       collapse the pin runway so the hero sits normally above the footer. --- */
body:not(.tab--app) #phonesSticky,
body:not(.tab--app) #phonesSpacer,
body:not(.tab--app) .scroll-indicator {
  display: none;
}
/* Sticky footer on the short tabs: when the hero + panel don't fill the
   viewport, push the footer to the bottom instead of leaving it stranded
   mid-screen. The app tab keeps its (tall, pinned) block layout untouched. */
body:not(.tab--app) {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
body:not(.tab--app) .site-footer {
  margin-top: auto;
}
@media (min-width: 901px) {
  /* Tab pages (no phones): unpin the 100vh scene and let the hero flow as a normal
     content-tall block — top-anchored so the logo/tab bar sit where they do on the
     app tab, scaled the same (via layout-aware `zoom`, not `transform: scale`), and
     the page grows to fit a long patch-notes list with ~10vh before the footer. */
  body:not(.tab--app) .intro-pin {
    height: auto;
    min-height: 0;
  }
  body:not(.tab--app) .intro-sticky {
    position: static;
    /* Purely content-driven height — the page is exactly as tall as the hero +
       content needs, so it grows for a long patch-notes list and never clips. */
    height: auto;
    /* Top-anchor (not the app tab's vertical centering) so the header lands at the
       hero's natural top — the same spot as the app tab — and stays put no matter
       how tall the content is. The base 32vh bottom-shift (an app-tab centering
       trick over the phones) becomes ~10vh of breathing room before the footer. */
    justify-content: flex-start;
    padding-bottom: 10vh;
  }
  /* No 100vh min on the hero either (it's the flex parent that would otherwise
     re-inflate) — let both size to content. */
  body:not(.tab--app) .hero { min-height: 0; }
  /* Same visual scale-up as the app tab (no shrink), but layout-aware `zoom`
     instead of `transform: scale` so the grown page/footer actually accounts for
     the scaled height (a scale transform doesn't, so it clipped on wide screens). */
  body:not(.tab--app) .hero-scale {
    transform: none;
    zoom: var(--ui-scale, 1);
  }
}
