/* ==========================================================================
   Calo AI marketing site design system

   Tokens are copied from the Flutter app's lib/ui/foundation/app_colors.dart
   so the site and the app read as one product. Do not invent new brand hues
   here; if the app's palette changes, change it there first and mirror it.
   ========================================================================== */

:root {
  /* Brand */
  --primary: #16C784;
  --primary-dark: #0D9668;
  --primary-light: #E8F9F1;
  --brand-sweep-end: #7CE7B9;

  /* "Pro" gradient: same pair the app uses for premium surfaces */
  --pro-start: #1E3A5F;
  --pro-end: #2D5A87;

  /* Surfaces */
  --background: #F8F9FA;
  --surface: #ffffff;
  --surface-subtle: #F3F4F6;

  /* Text */
  --text-primary: #1A1A2E;
  --text-secondary: #6B7280;
  --text-tertiary: #9CA3AF;

  /* Lines */
  --border: #E8ECF0;
  --border-strong: #D1D5DB;

  /* Accents (macro colours from the app) */
  --protein: #8B5CF6;
  --fat: #F59E0B;
  --steps: #FF6B35;

  /* Type.
     Outfit sets the display voice: geometric, tight, confident at large sizes.
     Manrope carries the body: a taller x-height than Inter, so 15px copy still
     reads at arm's length. Inter stays in both stacks as the fallback, which is
     what the site shipped with, so a blocked Google Fonts request degrades to
     the previous typography rather than to Times. */
  --font-display: 'Outfit', 'Inter', system-ui, -apple-system, sans-serif;
  --font-sans: 'Manrope', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  /* Radii. Deliberately not one value: containers are softer than the elements
     nested inside them, which is what stops the page reading as one rounded
     rectangle repeated forty times. */
  --r-xs: 8px;
  --r-sm: 12px;
  --r-md: 16px;
  --r-lg: 24px;
  --r-xl: 32px;
  --r-2xl: 40px;
  --r-pill: 999px;

  /* Elevation.
     Shadows are tinted with the deep green the brand sits on rather than neutral
     black. On a green-tinted canvas a pure-black shadow reads as grey dirt under
     the card; a hue-matched one reads as depth. Every layer casts from the same
     direction (straight down) so the page has one light source. */
  --shadow-color: 14, 40, 31;
  --shadow-sm: 0 1px 2px rgba(var(--shadow-color), .05), 0 1px 3px rgba(var(--shadow-color), .05);
  --shadow-md: 0 6px 16px rgba(var(--shadow-color), .07), 0 2px 5px rgba(var(--shadow-color), .04);
  --shadow-lg: 0 18px 44px rgba(var(--shadow-color), .10), 0 6px 14px rgba(var(--shadow-color), .05);
  --shadow-xl: 0 34px 74px rgba(var(--shadow-color), .13), 0 10px 24px rgba(var(--shadow-color), .06);
  --shadow-brand: 0 10px 26px rgba(22, 199, 132, .28), 0 2px 6px rgba(22, 199, 132, .16);

  /* Stacking order, named once so nothing has to guess a number. */
  --z-raised: 5;
  --z-nav: 100;
  --z-grain: 200;
  --z-skip: 300;

  /* Motion. One spring-ish curve for anything that moves under the pointer,
     one longer ease for scroll reveals. */
  --ease-spring: cubic-bezier(.22, 1.2, .36, 1);
  --ease-out: cubic-bezier(.22, 1, .36, 1);

  /* Layout.
     The gutter is fluid so a 320px phone keeps its side padding proportional
     instead of spending 48px of a 320px screen on margins. --nav-h is shared
     by the bar, the drawer and scroll-padding-top, so the sticky header and
     anchor offsets can never drift apart. */
  --maxw: 1140px;
  --gutter: clamp(16px, 4.2vw, 24px);
  --nav-h: 68px;

  /* Width at which the nav collapses to a drawer. Kept as a comment rather
     than a custom property because media queries cannot read var(). 960px */
}

/* ===== Reset ===== */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* In-page anchors land below the sticky bar instead of under it. */
  scroll-padding-top: calc(var(--nav-h) + 16px);
}

body {
  font-family: var(--font-sans);
  background: var(--background);
  color: var(--text-primary);
  line-height: 1.65;
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  /* Emails, UIDs and URLs are the only strings here long enough to push a
     320px layout wide. Break them rather than let them set the page width. */
  overflow-wrap: break-word;
}

/* ===== Grain =====
   A single fixed layer of fractal noise over the whole document. Flat vector
   fills are the thing that makes a page read as rendered rather than printed;
   ~3% of noise on soft-light is below the threshold where you notice it and
   above the one where the surfaces stop looking plastic. It is inert to the
   pointer and hidden from assistive tech. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: .32;
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
}

/* Keyboard users get to the content without tabbing the whole nav. */
.skip-link {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: var(--z-skip);
  padding: 12px 20px;
  border-radius: var(--r-pill);
  background: var(--text-primary);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  box-shadow: var(--shadow-lg);
  transform: translateY(-160%);
  transition: transform .2s var(--ease-out);
}
.skip-link:focus-visible { transform: none; }

a { text-decoration: none; color: inherit; }
img { max-width: 100%; height: auto; display: block; }
button { font: inherit; cursor: pointer; border: none; background: none; }
ul { list-style: none; }

:focus-visible {
  outline: 2px solid var(--primary-dark);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ===== Layout helpers ===== */
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); }

/* Fluid vertical rhythm: a 96px band on a 1440px desktop is generous, on a
   360px-tall landscape phone it is most of the viewport. */
/* Bottom padding runs slightly longer than top: optically, a band reads as
   balanced only when the space under the last line beats the space over the
   first one. */
.section { padding: clamp(56px, 9vw, 96px) 0 clamp(64px, 10vw, 112px); }
.section--tight { padding: clamp(40px, 6vw, 64px) 0; }
.section--pull { padding-top: clamp(8px, 1.5vw, 20px); }
.section--alt { position: relative; background: var(--surface); }
/* The tinted canvas and the white bands used to meet on a hard line straight
   across the page. These fade the join over 80px at each end so the change of
   surface is felt rather than seen. */
.section--alt::before,
.section--alt::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  height: 80px;
  pointer-events: none;
}
.section--alt::before { top: 0; background: linear-gradient(180deg, var(--background), transparent); }
.section--alt::after { bottom: 0; background: linear-gradient(0deg, var(--background), transparent); }
.section--alt > * { position: relative; z-index: 1; }
/* The last band runs straight into the footer, which is the same surface. */
main > .section--alt:last-child::after { display: none; }

/* Ambient band behind the feature grid, so the tiles sit on a lit surface
   rather than on flat grey. Two soft ellipses, no hard edge anywhere. */
.section--features { position: relative; isolation: isolate; }
.section--features::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(ellipse 62% 48% at 14% 12%, rgba(22, 199, 132, .10) 0%, transparent 62%),
    radial-gradient(ellipse 48% 44% at 88% 78%, rgba(124, 231, 185, .13) 0%, transparent 66%);
}

.section-head {
  text-align: center;
  max-width: 640px;
  margin: 0 auto clamp(36px, 5vw, 56px);
}

/* Asymmetric variant: the headline holds the left column and the standfirst
   sits to its right, baseline-aligned. Used to break the run of centred heads
   so the page does not read as one template applied six times. */
.section-head--split {
  max-width: none;
  text-align: left;
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .8fr);
  gap: 20px clamp(32px, 6vw, 72px);
  align-items: end;
}
.section-head--split .eyebrow { grid-column: 1 / -1; }
.section-head--split h2 { grid-column: 1; margin: 0; }
.section-head--split .lead { grid-column: 2; padding-bottom: .35em; max-width: 46ch; }

/* Eyebrow: a hairline-ruled label, not a pill. Pill badges are the stock
   marketing-page tell, and at this size the pill's fill fought the card fills
   directly beneath it. The rules centre themselves in a centred head and read
   as a margin mark in a left-aligned one. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--primary-dark);
  margin-bottom: 18px;
}
.eyebrow::before,
.eyebrow::after {
  content: '';
  width: 22px;
  height: 1px;
  background: currentColor;
  opacity: .45;
}
.section-head--split .eyebrow::after,
.eyebrow--start::after { display: none; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  line-height: 1.08;
  letter-spacing: -.03em;
  font-weight: 700;
  text-wrap: balance;
}
h1 { font-size: clamp(2.4rem, 5.6vw, 3.9rem); letter-spacing: -.042em; line-height: 1.03; }
h2 { font-size: clamp(1.95rem, 4vw, 2.9rem); letter-spacing: -.036em; }
h3 { font-size: 1.2rem; font-weight: 600; letter-spacing: -.022em; line-height: 1.25; }
h4 { font-weight: 600; }

p { text-wrap: pretty; }

.lead {
  font-size: clamp(1rem, 1.6vw, 1.15rem);
  color: var(--text-secondary);
  line-height: 1.68;
  font-weight: 450;
}

/* Anything read as a quantity gets tabular figures, so a column of numbers
   lines up and a counting number does not jitter as digits change. */
.stat-num, .tile-big, .plan-price .amount, .bar-val, .macro-val,
.bento-metric, .bento-chip b { font-variant-numeric: tabular-nums; }

.text-gradient {
  background: linear-gradient(120deg, var(--primary) 0%, var(--brand-sweep-end) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ===== Buttons ===== */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px 28px;
  border-radius: var(--r-pill);
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -.01em;
  transition: transform .22s var(--ease-spring), box-shadow .22s var(--ease-out),
              background .18s ease, border-color .18s ease;
  white-space: nowrap;
  overflow: hidden;
}
.btn:active { transform: translateY(0) scale(.975); }

.btn--primary {
  /* Two stops rather than a flat fill: the top edge catches the same light as
     the card highlights, so the button belongs to the same lit scene. */
  background: linear-gradient(180deg, #1ED694 0%, var(--primary) 52%, #12B073 100%);
  color: #fff;
  box-shadow: var(--shadow-brand), inset 0 1px 0 rgba(255, 255, 255, .28);
}
.btn--primary:hover {
  background: linear-gradient(180deg, #16C784 0%, var(--primary-dark) 100%);
  transform: translateY(-2px);
  box-shadow: 0 16px 34px rgba(22, 199, 132, .34), inset 0 1px 0 rgba(255, 255, 255, .28);
}

.btn--ghost {
  background: rgba(255, 255, 255, .78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--text-primary);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
}
.btn--ghost:hover {
  border-color: var(--border-strong);
  background: #fff;
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.btn--pro {
  background: linear-gradient(120deg, var(--pro-start), var(--pro-end));
  color: #fff;
  box-shadow: 0 8px 24px rgba(30, 58, 95, .3);
}
.btn--pro:hover { transform: translateY(-2px); }

.btn--lg { padding: 17px 36px; font-size: 16px; }
.btn--block { width: 100%; }
/* Wide enough to read as the primary action, not so wide it becomes a bar. */
.pro-band .btn--block { max-width: 300px; margin-inline: auto; }

/* ===== Navbar ===== */
.nav {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  background: rgba(255, 255, 255, .72);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid transparent;
  transition: background .25s ease, border-color .25s ease, box-shadow .25s ease;
}
/* site.js adds this once the page has moved. At the top of a hero that already
   has its own glow, a hard bar and a border are just a line across the artwork;
   once content is scrolling under it, the bar has to separate itself. */
.nav.is-scrolled {
  background: rgba(255, 255, 255, .86);
  border-bottom-color: var(--border);
  box-shadow: 0 1px 0 rgba(var(--shadow-color), .03), 0 8px 24px rgba(var(--shadow-color), .05);
}
.nav-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--gutter);
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.brand {
  display: flex; align-items: center; gap: 10px;
  font-weight: 800; font-size: 17px;
  white-space: nowrap;   /* "Calo AI" used to break across two lines */
  flex-shrink: 0;
}
.brand img { width: 32px; height: 32px; border-radius: 8px; flex-shrink: 0; }

/* The gap shrinks before anything is forced to wrap, which is what keeps the
   full link row usable all the way down to the 960px collapse point. */
.nav-links { display: flex; align-items: center; gap: clamp(18px, 2.6vw, 32px); }
.nav-links a {
  position: relative;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
  transition: color .18s ease;
}
.nav-links a:hover { color: var(--text-primary); }
/* Scroll-spy underline. Scales from the centre so it draws itself in rather
   than appearing, and it is the only thing that moves in the bar. */
.nav-links a::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -6px;
  height: 2px;
  border-radius: 2px;
  background: var(--primary);
  transform: scaleX(0);
  transition: transform .28s var(--ease-spring);
}
.nav-links a.active { color: var(--text-primary); }
.nav-links a.active::after { transform: scaleX(1); }

.nav-cta { display: flex; align-items: center; gap: 12px; }

/* nav.js drops one or two buttons in here. As a bare inline <span> they wrapped
   onto a second line and spilled out of the 68px bar as soon as the signed-in
   pair ("Account" + "Sign out") stopped fitting. */
#navAuth { display: flex; align-items: center; gap: 10px; }

/* Bar buttons are tighter than page buttons: the signed-in nav carries five
   links plus three buttons, and that only fits a 960px viewport at this size. */
.nav .btn { padding: 10px 18px; font-size: 14px; }

.menu-toggle {
  display: none;
  flex-direction: column; justify-content: center;
  gap: 5px; padding: 8px;
}
.menu-toggle span {
  width: 22px; height: 2px; background: var(--text-primary); border-radius: 2px;
  transition: transform .2s ease, opacity .2s ease;
}

/* ===== Hero ===== */
.hero {
  position: relative;
  padding: clamp(48px, 8vw, 88px) 0 clamp(24px, 4vw, 48px);
  overflow: hidden;
}
/* Two offset washes rather than one centred circle. A single symmetrical glow
   behind a symmetrical hero is invisible; two overlapping ellipses of different
   sizes give the background a direction. */
.hero::before {
  content: '';
  position: absolute;
  top: -42%;
  left: 42%;
  transform: translateX(-50%);
  width: 1120px;
  height: 980px;
  background:
    radial-gradient(ellipse 52% 46% at 58% 42%, rgba(22, 199, 132, .16) 0%, transparent 68%),
    radial-gradient(ellipse 40% 52% at 22% 60%, rgba(124, 231, 185, .16) 0%, transparent 70%);
  pointer-events: none;
}
/* A hairline grid, faded out before it reaches the copy. Enough structure that
   the top of the page is not an empty wash, faint enough to stay unnoticed. */
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(26, 26, 46, .045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(26, 26, 46, .045) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(ellipse 78% 62% at 50% 8%, #000 0%, transparent 72%);
  mask-image: radial-gradient(ellipse 78% 62% at 50% 8%, #000 0%, transparent 72%);
}
.hero > * { position: relative; z-index: var(--z-raised); }
.hero-grid {
  position: relative;
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  gap: 56px;
  align-items: center;
}
.hero h1 { margin-bottom: 20px; }
.hero .lead { margin-bottom: 32px; max-width: 520px; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 28px; }

.hero-note {
  display: flex; align-items: center; gap: 8px;
  font-size: 14px; color: var(--text-tertiary);
}
.hero-note svg { flex-shrink: 0; }

/* The two ways in, stacked: the store button, then the browser button right
   under it at the same width, so neither reads as an afterthought. */
.hero-actions--stack {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  width: 100%;
  max-width: 380px;
  gap: 12px;
}
.hero-actions--stack .btn { justify-content: center; }

/* "Use it in your browser": a frosted card-button that pairs with the green
   store button without competing with it. Icon tile, two-line label, arrow. */
.web-cta {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 18px 12px 12px;
  border-radius: 20px;
  background: rgba(255, 255, 255, .82);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255, 255, 255, .9);
  color: var(--text-primary);
  text-align: left;
  transition: transform .22s var(--ease-spring), box-shadow .22s var(--ease-out),
              border-color .18s ease, background .18s ease;
}
.web-cta:hover {
  transform: translateY(-2px);
  background: #fff;
  border-color: rgba(22, 199, 132, .45);
  box-shadow: 0 14px 30px rgba(22, 199, 132, .16), var(--shadow-sm);
}
.web-cta:active { transform: translateY(0) scale(.985); }
.web-cta:focus-visible { outline: 3px solid rgba(22, 199, 132, .45); outline-offset: 3px; }
.web-cta-icon {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  background: linear-gradient(150deg, #E8F9F1 0%, #D2F4E4 100%);
  color: var(--primary-dark);
  box-shadow: inset 0 0 0 1px rgba(22, 199, 132, .18);
}
.web-cta-icon svg { width: 22px; height: 22px; }
.web-cta-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.web-cta-text strong { font-size: 15.5px; font-weight: 700; letter-spacing: -.01em; line-height: 1.25; }
.web-cta-text small { font-size: 12.5px; font-weight: 500; color: var(--text-secondary); line-height: 1.3; }
.web-cta-arrow {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--primary-dark);
  transition: transform .22s var(--ease-spring);
}
.web-cta:hover .web-cta-arrow { transform: translateX(3px); }

/* ===== Phone mockup =====
   Screenshots drop into .phone-screen as a plain <img>. The frame is pure CSS
   so a missing screenshot degrades to a neutral placeholder, not a broken box. */
.phone-stage {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 560px;
}
.phone-stage::after {
  content: '';
  position: absolute;
  bottom: -8px;
  left: 50%;
  width: 58%;
  height: 34px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(var(--shadow-color), .24) 0%, transparent 72%);
  filter: blur(8px);
  pointer-events: none;
  z-index: 0;
}

/* Entrance. Distinct from .reveal: this fires on load rather than on scroll,
   because the hero is already in view when the page arrives. */
.rise { animation: rise .8s var(--ease-out) both; animation-delay: var(--d, 0ms); }
@keyframes rise {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}
/* The frame hugs whatever screenshot it holds. It deliberately does NOT force
   an aspect ratio: the real exports range from 1:2.035 to 1:2.173, so pinning
   them all to 9/19.5 and using object-fit:cover cropped the right edge off the
   shorter ones. Letting the image set the height means nothing is ever cut. */
.phone {
  position: relative;
  width: 280px;
  background: #0f1115;
  border-radius: 38px;
  padding: 8px;
  box-shadow:
    var(--shadow-lg),
    0 0 0 2px rgba(255, 255, 255, .6) inset,
    0 24px 60px rgba(26, 26, 46, .22);
  z-index: 2;
}
.phone-screen {
  width: 100%;
  border-radius: 30px;
  overflow: hidden;
  background: var(--surface-subtle);
  position: relative;
  line-height: 0; /* kills the inline-image descender gap under the screenshot */
}
/* Static, not absolute: the image is what gives the frame its height. */
.phone-screen img {
  width: 100%;
  height: auto;
  display: block;
}

/* Fallback shape for a frame with no image in it. Uses aspect-ratio rather
   than a fixed height so an empty frame still looks like a phone. */
.phone-screen .ph-empty {
  aspect-ratio: 9 / 19.5;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; text-align: center; padding: 24px;
  background: linear-gradient(160deg, var(--primary-light), var(--surface));
  color: var(--text-tertiary); font-size: 13px; font-weight: 600;
  line-height: 1.5;
}

/* Secondary phone peeking behind the primary one */
.phone--back {
  position: absolute;
  width: 240px;
  right: 4%;
  top: 12%;
  transform: rotate(7deg) scale(.92);
  opacity: .85;
  z-index: 1;
  filter: blur(.4px);
}

/* Floating stat cards around the phone */
.float-card {
  position: absolute;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 12px 16px;
  box-shadow: var(--shadow-lg);
  display: flex; align-items: center; gap: 10px;
  font-size: 13px; font-weight: 700;
  z-index: 3;
  animation: bob 5s ease-in-out infinite;
}
.float-card small { display: block; font-size: 11px; font-weight: 600; color: var(--text-tertiary); }
.float-card .dot {
  width: 32px; height: 32px;
  flex: 0 0 auto;
  border-radius: 10px;
  display: grid; place-items: center;
  color: var(--primary-dark);
  background: linear-gradient(155deg, var(--primary-light), #fff);
  box-shadow: inset 0 0 0 1px rgba(22, 199, 132, .18);
}
.float-card .dot svg { width: 17px; height: 17px; }

.float-card--a { top: 1%; left: -14%; animation-delay: 0s; }
.float-card--b { bottom: 12%; right: -10%; animation-delay: 1.6s; }

@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

/* ===== Stats strip ===== */
.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  padding: 34px 28px;
  background: linear-gradient(180deg, #fff 0%, #FCFDFC 100%);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg), inset 0 1px 0 #fff;
  margin-top: -24px;
  position: relative;
  z-index: var(--z-raised);
}
/* Hairline separators between the figures instead of four boxes' worth of
   whitespace. Drawn on the cell, not the container, so they disappear on their
   own when the grid reflows to two columns. */
.stat { text-align: center; position: relative; }
.stat + .stat::before {
  content: '';
  position: absolute;
  left: -12px;
  top: 12%;
  bottom: 12%;
  width: 1px;
  background: linear-gradient(180deg, transparent, var(--border) 22%, var(--border) 78%, transparent);
}
.stat-num {
  font-family: var(--font-display);
  font-size: clamp(1.65rem, 3vw, 2.15rem);
  font-weight: 700;
  letter-spacing: -.035em;
  color: var(--text-primary);
  line-height: 1.1;
}
.stat-label {
  font-size: 12px;
  color: var(--text-tertiary);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  margin-top: 6px;
}

/* ===== Feature cards =====
   auto-fit rather than fixed breakpoints: the column count follows the space
   a card actually needs, so the grid reflows at 1024, 900 and 560 without any
   of those numbers being written down. min() keeps a single card from
   demanding 258px on a 320px screen. */
.grid-3 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 258px), 1fr));
  gap: 24px;
}
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; }

/* Exactly three items, where a 2+1 wrap would leave a visible orphan: hold
   three across until they genuinely stop fitting, then go straight to one. */
.grid-trio { grid-template-columns: repeat(3, 1fr); }

.card {
  position: relative;
  background: linear-gradient(180deg, #fff 0%, #FBFCFB 100%);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 32px 28px;
  box-shadow: var(--shadow-sm), inset 0 1px 0 #fff;
  transition: transform .28s var(--ease-spring), box-shadow .28s var(--ease-out),
              border-color .2s ease;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg), inset 0 1px 0 #fff;
  border-color: rgba(22, 199, 132, .28);
}
.card h3 { margin-bottom: 10px; }
.card p { color: var(--text-secondary); font-size: 15px; }

.card-icon {
  width: 48px; height: 48px;
  border-radius: 14px;
  display: grid; place-items: center;
  font-size: 22px;
  margin-bottom: 20px;
  color: var(--primary-dark);
  background: linear-gradient(160deg, var(--primary-light), #fff);
  box-shadow: inset 0 0 0 1px rgba(22, 199, 132, .18), var(--shadow-sm);
}
.card-icon svg { width: 22px; height: 22px; }
/* Kept for the dashboard, where these hues carry meaning: they are the app's
   protein / fat / steps colours, keyed to the macro they sit beside. They are
   deliberately no longer used as decoration on the marketing pages. */
.card-icon--protein { background: rgba(139, 92, 246, .12); color: var(--protein); box-shadow: inset 0 0 0 1px rgba(139, 92, 246, .18), var(--shadow-sm); }
.card-icon--fat     { background: rgba(245, 158, 11, .13); color: #B45309; box-shadow: inset 0 0 0 1px rgba(245, 158, 11, .2), var(--shadow-sm); }
.card-icon--steps   { background: rgba(255, 107, 53, .12); color: var(--steps); box-shadow: inset 0 0 0 1px rgba(255, 107, 53, .18), var(--shadow-sm); }
.card-icon--pro     { background: rgba(30, 58, 95, .10); color: var(--pro-start); box-shadow: inset 0 0 0 1px rgba(30, 58, 95, .16), var(--shadow-sm); }

/* ==========================================================================
   Bento feature grid

   Replaces a 3x2 run of identical cards. Six tiles of three different weights
   on a six-column grid:

     rows 1-2  hero tile (3 cols x 2 rows), carrying the scan demo
     row 1     meal plans (3 cols)
     row 2     AI coach   (3 cols)
     row 3     fasting | analytics | steps (2 cols each)

   Auto-placement does the rest, so source order is reading order and no tile
   needs an explicit grid-area.
   ========================================================================== */
.bento {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 18px;
}

.bento-card {
  --spot: rgba(22, 199, 132, .13);
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 30px 28px;
  border-radius: var(--r-xl);
  background: linear-gradient(180deg, #fff 0%, #FBFCFB 100%);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm), inset 0 1px 0 #fff;
  transition: transform .34s var(--ease-spring), box-shadow .34s var(--ease-out),
              border-color .25s ease;
}

/* Cursor spotlight. site.js writes --mx/--my as percentages of the tile; with
   JS off the layer simply never becomes visible, so this is additive only. */
.bento-card::before {
  content: '';
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s ease;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 0%), var(--spot), transparent 62%);
}
.bento-card:hover::before { opacity: 1; }
.bento-card:hover {
  transform: translateY(-5px);
  border-color: rgba(22, 199, 132, .3);
  box-shadow: var(--shadow-lg), inset 0 1px 0 #fff;
}
.bento-card > * { position: relative; }
/* The hero tile's copy is grouped so the tile can flip between a stack and a
   two-column split without auto-placement pulling the heading away from the
   icon. */
.bento-card--hero .bento-body { display: flex; flex-direction: column; }
.bento-card--hero .bento-art { display: flex; flex: 1 1 auto; min-height: 0; }
.bento-card--hero .scan { flex: 1 1 auto; aspect-ratio: auto; min-height: 300px; }

.bento-card--hero { grid-column: span 3; grid-row: span 2; }
.bento-card--wide { grid-column: span 3; }
.bento-card--third { grid-column: span 2; }

.bento-icon {
  width: 44px; height: 44px;
  flex: 0 0 auto;
  border-radius: 13px;
  display: grid; place-items: center;
  margin-bottom: 18px;
  color: var(--primary-dark);
  background: linear-gradient(155deg, #F0FBF6 0%, #fff 100%);
  box-shadow: inset 0 0 0 1px rgba(22, 199, 132, .2), 0 2px 6px rgba(var(--shadow-color), .06);
}
.bento-icon svg { width: 21px; height: 21px; }

.bento-card h3 { margin-bottom: 9px; }
.bento-card p {
  color: var(--text-secondary);
  font-size: 14.5px;
  line-height: 1.6;
  max-width: 46ch;
}

/* Every tile's demo art sits in this slot, pushed to the bottom edge, so the
   headings across a row stay level however long the copy above them runs. */
.bento-art { margin-top: auto; padding-top: 22px; }

/* ---------- Hero tile: the scan ---------- */
.scan {
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  aspect-ratio: 5 / 4;
  background: var(--surface-subtle);
  box-shadow: 0 14px 34px rgba(var(--shadow-color), .18);
}
.scan img { width: 100%; height: 100%; object-fit: cover; }

/* The reticle darkens everything outside itself with one enormous spread,
   which is how a camera tells you what it has locked onto. */
.scan-frame {
  position: absolute;
  inset: 11% 9% 30%;
  border: 1.5px solid rgba(255, 255, 255, .82);
  border-radius: 16px;
  box-shadow:
    0 0 0 1px rgba(22, 199, 132, .45),
    0 0 26px rgba(22, 199, 132, .3),
    0 0 0 600px rgba(6, 20, 15, .52);
}
.scan-frame::before,
.scan-frame::after {
  content: '';
  position: absolute;
  width: 20px; height: 20px;
  border: 2px solid var(--primary);
}
.scan-frame::before { top: -2px; left: -2px; border-right: 0; border-bottom: 0; border-radius: 16px 0 0 0; }
.scan-frame::after { bottom: -2px; right: -2px; border-left: 0; border-top: 0; border-radius: 0 0 16px 0; }

/* The sweep is a gradient band on a full-height layer, so it travels by
   percentages of its own box: no pixel distance to keep in sync with the tile
   height, and it animates on transform alone. */
.scan-beam {
  position: absolute;
  inset: 11% 9% 30%;
  border-radius: 16px;
  overflow: hidden;
  pointer-events: none;
}
.scan-beam::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    transparent 0 43%,
    rgba(22, 199, 132, .22) 47%,
    rgba(190, 255, 228, .95) 49.4% 50.6%,
    rgba(22, 199, 132, .22) 53%,
    transparent 57% 100%);
  animation: scanSweep 3.8s ease-in-out infinite;
}
@keyframes scanSweep {
  0%   { transform: translateY(-52%); opacity: 0; }
  14%  { opacity: 1; }
  86%  { opacity: 1; }
  100% { transform: translateY(52%); opacity: 0; }
}

.scan-result {
  position: absolute;
  left: 12px; right: 12px; bottom: 12px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 14px;
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, .84);
  backdrop-filter: blur(16px) saturate(170%);
  -webkit-backdrop-filter: blur(16px) saturate(170%);
  border: 1px solid rgba(255, 255, 255, .8);
  box-shadow: 0 12px 30px rgba(7, 22, 17, .26), inset 0 1px 0 rgba(255, 255, 255, .95);
}
.scan-result .tick {
  flex: 0 0 auto;
  width: 24px; height: 24px;
  border-radius: var(--r-pill);
  display: grid; place-items: center;
  background: var(--primary);
  color: #fff;
  box-shadow: 0 3px 10px rgba(22, 199, 132, .5);
}
.scan-result .tick svg { width: 13px; height: 13px; }
.scan-result .scan-copy { min-width: 0; }
.scan-result b {
  display: block;
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: -.015em;
  line-height: 1.3;
}
.scan-result em {
  display: block;
  font-style: normal;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

/* ---------- Meal plan tile: one day of a plan ---------- */
.menu-list { display: grid; gap: 8px; }
.menu-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 13px;
  border-radius: var(--r-sm);
  background: #fff;
  border: 1px solid var(--border);
  font-size: 13px;
  box-shadow: 0 1px 2px rgba(var(--shadow-color), .04);
}
.menu-row .slot {
  flex: 0 0 62px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.menu-row .dish { flex: 1 1 auto; font-weight: 600; min-width: 0; }
.menu-row .kcal {
  flex: 0 0 auto;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--primary-dark);
}

/* ---------- Coach tile: a two-line transcript ---------- */
.chat { display: grid; gap: 8px; }
.chat p {
  max-width: 88%;
  margin: 0;
  padding: 10px 14px;
  font-size: 13px;
  line-height: 1.5;
  border-radius: 16px;
}
.chat .ask {
  justify-self: end;
  background: var(--surface-subtle);
  color: var(--text-primary);
  border-bottom-right-radius: 5px;
  font-weight: 600;
}
.chat .reply {
  justify-self: start;
  background: linear-gradient(160deg, var(--primary) 0%, #12B073 100%);
  color: #fff;
  border-bottom-left-radius: 5px;
  box-shadow: 0 6px 16px rgba(22, 199, 132, .26);
}

/* ---------- Compact tiles ---------- */
.bento-card--third { padding: 26px 24px; }
.bento-card--third p { font-size: 14px; }

.bento-metric {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-top: 16px;
  font-family: var(--font-display);
  font-size: 1.45rem;
  font-weight: 700;
  letter-spacing: -.035em;
}
.bento-metric small {
  font-family: var(--font-sans);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* Fasting ring: a conic sweep with its middle masked out, so it is one element
   rather than a track div wrapping a fill div. */
.ring-wrap { display: flex; align-items: center; gap: 16px; }
.ring {
  width: 68px;
  aspect-ratio: 1;
  flex: 0 0 auto;
  border-radius: var(--r-pill);
  background: conic-gradient(
    var(--primary) 0 var(--p, 60%),
    var(--surface-subtle) var(--p, 60%) 100%);
  -webkit-mask: radial-gradient(circle, transparent 0 60%, #000 61%);
  mask: radial-gradient(circle, transparent 0 60%, #000 61%);
}
.ring-label {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.ring-label small {
  display: block;
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.spark { width: 100%; height: 62px; display: block; }

/* Steps: seven bars, each height driven by its own --h. */
.steps-bars { display: flex; align-items: flex-end; gap: 6px; height: 62px; }
.steps-bars i {
  flex: 1 1 0;
  min-width: 0;
  max-width: 22px;
  height: var(--h, 50%);
  border-radius: 4px 4px 2px 2px;
  background: linear-gradient(180deg, var(--brand-sweep-end), var(--primary));
  opacity: .55;
}
.steps-bars i:last-child {
  opacity: 1;
  box-shadow: 0 3px 10px rgba(22, 199, 132, .38);
}

/* ==========================================================================
   App tour

   One device, five screens, chosen from a list beside it. This replaces a rail
   of five phones that was always clipped mid-device on a desktop and could
   never line up, because the exports run from 1:2.035 to 1:2.245.

   Showing one screen at a time makes that spread a non-issue: the frame holds a
   single fixed ratio and object-fit: contain letterboxes against a white screen
   that the app's own chrome is already white against. Nothing is ever cropped.
   ========================================================================== */
.tour {
  display: grid;
  grid-template-columns: minmax(0, .78fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: center;
}

.tour-stage { position: relative; display: flex; justify-content: center; }
/* Contact shadow. Without it the device hovers in a void; with it the whole
   stage reads as a lit surface. */
.tour-stage::after {
  content: '';
  position: absolute;
  bottom: -14px;
  left: 50%;
  width: 66%;
  height: 30px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(var(--shadow-color), .26) 0%, transparent 72%);
  filter: blur(7px);
  pointer-events: none;
}

.phone--tour { width: min(268px, 74vw); }
.phone--tour .phone-screen {
  aspect-ratio: 1 / 2.09;
  background: #fff;
}
.phone--tour .phone-screen img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: opacity .26s var(--ease-out);
}
.phone--tour .phone-screen img.is-swapping { opacity: 0; }

.tour-list { display: grid; gap: 11px; }

.tour-tab {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  width: 100%;
  padding: 19px 18px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  text-align: left;
  color: var(--text-secondary);
  transition: background .22s ease, border-color .22s ease, transform .26s var(--ease-spring),
              box-shadow .22s ease;
}
.tour-tab:hover { background: var(--surface-subtle); }
.tour-tab[aria-selected="true"] {
  background: linear-gradient(180deg, #fff 0%, #FBFCFB 100%);
  border-color: rgba(22, 199, 132, .3);
  box-shadow: var(--shadow-md);
  transform: translateX(4px);
}

.tour-tab-icon {
  flex: 0 0 auto;
  width: 38px; height: 38px;
  margin-top: 1px;
  border-radius: 11px;
  display: grid; place-items: center;
  color: var(--text-tertiary);
  background: var(--surface-subtle);
  transition: background .22s ease, color .22s ease, box-shadow .22s ease;
}
.tour-tab-icon svg { width: 18px; height: 18px; }
.tour-tab[aria-selected="true"] .tour-tab-icon {
  color: var(--primary-dark);
  background: linear-gradient(155deg, #F0FBF6, #fff);
  box-shadow: inset 0 0 0 1px rgba(22, 199, 132, .22);
}

.tour-tab b {
  display: block;
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -.02em;
  color: var(--text-primary);
  line-height: 1.3;
}
.tour-tab small {
  display: block;
  margin-top: 3px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-secondary);
}

/* ===== Steps ===== */
/* A hairline runs behind the row and joins the three markers, so the steps
   read as one sequence rather than three unrelated columns. It is drawn on the
   container and clipped by the first and last marker's own background. */
.steps {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
  counter-reset: step;
}
/* 34px of card padding + half of the 56px marker = the marker's centre line. */
.steps::before {
  content: '';
  position: absolute;
  top: 62px;
  left: 16.66%;
  right: 16.66%;
  height: 2px;
  background: var(--border-strong);
  border-radius: 2px;
}
.step {
  position: relative;
  text-align: center;
  padding: 34px 26px 32px;
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, #fff 0%, #FBFCFB 100%);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm), inset 0 1px 0 #fff;
  transition: transform .3s var(--ease-spring), box-shadow .3s var(--ease-out),
              border-color .25s ease;
}
.step:hover {
  transform: translateY(-4px);
  border-color: rgba(22, 199, 132, .28);
  box-shadow: var(--shadow-lg), inset 0 1px 0 #fff;
}
.step-num {
  counter-increment: step;
  position: relative;
  width: 56px; height: 56px;
  margin: 0 auto 18px;
  border-radius: var(--r-pill);
  display: grid; place-items: center;
  background: linear-gradient(150deg, var(--brand-sweep-end) 0%, var(--primary) 55%, var(--primary-dark) 100%);
  color: #fff;
  font-family: var(--font-display);
  font-weight: 700; font-size: 20px;
  font-variant-numeric: tabular-nums;
  box-shadow: var(--shadow-brand), inset 0 1px 0 rgba(255, 255, 255, .35),
              0 0 0 7px #fff;
}
.step-num::before { content: counter(step); }
.step h3 { margin-bottom: 8px; }
.step p { color: var(--text-secondary); font-size: 15px; }

/* ===== Premium band ===== */
.pro-band {
  background: linear-gradient(135deg, var(--pro-start), var(--pro-end));
  border-radius: var(--r-2xl);
  padding: 68px 52px;
  color: #fff;
  position: relative;
  overflow: hidden;
  box-shadow: var(--shadow-xl), inset 0 1px 0 rgba(255, 255, 255, .14);
}
/* Same hairline grid as the hero, inverted. Without it this is the one large
   flat fill on the page and it reads as a pasted-in block. */
.pro-band::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, .05) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, .05) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(ellipse 80% 78% at 12% 100%, #000 0%, transparent 74%);
  mask-image: radial-gradient(ellipse 80% 78% at 12% 100%, #000 0%, transparent 74%);
}
.pro-band::before {
  content: '';
  position: absolute;
  top: -60%; right: -12%;
  width: 620px; height: 620px;
  background: radial-gradient(circle, rgba(22, 199, 132, .26) 0%, transparent 65%);
  pointer-events: none;
}
.pro-inner { position: relative; z-index: var(--z-raised); display: grid; grid-template-columns: 1.1fr .9fr; gap: 48px; align-items: center; }
.pro-band h2 { margin-bottom: 16px; }
.pro-band p { color: rgba(255, 255, 255, .82); }
.pro-list { display: grid; gap: 14px; margin-top: 28px; }

.pro-cta {
  padding: 34px 30px;
  border-radius: var(--r-lg);
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 18px 40px rgba(6, 20, 40, .26);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.pro-cta p { margin-top: 14px; font-size: 13px; color: rgba(255, 255, 255, .7); }
.pro-list li { display: flex; align-items: flex-start; gap: 12px; font-weight: 600; }
.pro-check {
  flex-shrink: 0;
  width: 22px; height: 22px; margin-top: 2px;
  border-radius: var(--r-pill);
  background: rgba(22, 199, 132, .18);
  box-shadow: inset 0 0 0 1px rgba(22, 199, 132, .45);
  display: grid; place-items: center;
  color: var(--brand-sweep-end);
}
.pro-check svg { width: 11px; height: 11px; }

/* ===== Pricing ===== */
.plans { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; max-width: 760px; margin: 0 auto; }
.plan {
  background: var(--surface);
  border: 2px solid var(--border);
  border-radius: var(--r-lg);
  padding: 36px 32px;
  position: relative;
  transition: border-color .2s ease, transform .2s ease, box-shadow .2s ease;
}
.plan:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.plan--featured { border-color: var(--primary); box-shadow: var(--shadow-md); }
.plan-badge {
  position: absolute; top: -13px; left: 50%; transform: translateX(-50%);
  background: var(--primary); color: #fff;
  font-size: 12px; font-weight: 800; letter-spacing: .04em;
  padding: 5px 16px; border-radius: var(--r-pill);
  text-transform: uppercase;
  white-space: nowrap;
}
.plan-name { font-size: 15px; font-weight: 700; color: var(--text-secondary); margin-bottom: 8px; }
.plan-price { display: flex; align-items: baseline; gap: 6px; margin-bottom: 4px; }
.plan-price .amount { font-size: 2.75rem; font-weight: 800; letter-spacing: -.03em; }
.plan-price .period { font-size: 15px; color: var(--text-tertiary); font-weight: 600; }
.plan-sub { font-size: 14px; color: var(--text-secondary); margin-bottom: 24px; min-height: 21px; }
.plan-features { display: grid; gap: 12px; margin-bottom: 28px; }
.plan-features li { display: flex; gap: 10px; font-size: 15px; color: var(--text-secondary); }
.plan-features .tick { color: var(--primary); font-weight: 800; flex-shrink: 0; }

/* ===== FAQ ===== */
.faq { max-width: 760px; margin: 0 auto; display: grid; gap: 12px; }
.faq-item {
  background: linear-gradient(180deg, #fff 0%, #FCFDFC 100%);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.faq-item:hover { border-color: var(--border-strong); }
.faq-item.open {
  border-color: rgba(22, 199, 132, .35);
  box-shadow: var(--shadow-md);
}
.faq-q {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 20px 24px;
  font-weight: 700; font-size: 16px; text-align: left;
  color: var(--text-primary);
}
/* Two crossed rules rather than a "+" glyph: the plus sign renders at a
   different weight in every font on the fallback stack, and the rotation to a
   cross was never quite centred optically. */
.faq-q::after {
  content: '';
  flex-shrink: 0;
  width: 13px; height: 13px;
  background:
    linear-gradient(currentColor, currentColor) center/13px 1.5px no-repeat,
    linear-gradient(currentColor, currentColor) center/1.5px 13px no-repeat;
  color: var(--text-tertiary);
  transition: transform .28s var(--ease-spring), color .2s ease;
}
.faq-item.open .faq-q::after { transform: rotate(135deg); color: var(--primary-dark); }
.faq-item.open .faq-q { color: var(--primary-dark); }
.faq-a {
  max-height: 0;
  overflow: hidden;
  transition: max-height .28s ease;
}
.faq-a p { padding: 0 24px 22px; color: var(--text-secondary); font-size: 15px; }

/* A dead end at the bottom of an FAQ is a support ticket that never gets
   filed. This is the way out of it. */
.faq-more {
  max-width: 760px;
  margin: 28px auto 0;
  padding: 22px 26px;
  border-radius: var(--r-md);
  background: linear-gradient(160deg, #F2FBF7 0%, var(--surface) 100%);
  border: 1px solid rgba(22, 199, 132, .22);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
}
.faq-more p { margin: 0; font-size: 15px; color: var(--text-secondary); }
.faq-more strong { display: block; color: var(--text-primary); font-weight: 700; }

/* ===== Auth / account panels ===== */
.panel {
  max-width: 460px;
  margin: 0 auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 40px 36px;
  box-shadow: var(--shadow-md);
  text-align: center;
}
.panel h2 { font-size: 1.5rem; margin-bottom: 10px; }
.panel p { color: var(--text-secondary); font-size: 15px; margin-bottom: 24px; }

.btn--google {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}
.btn--google:hover { background: var(--surface-subtle); }
.btn--google img { width: 18px; height: 18px; }

.badge-status {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 18px; border-radius: var(--r-pill);
  font-size: 14px; font-weight: 700;
}
.badge-status--active { background: var(--primary-light); color: var(--primary-dark); }
.badge-status--none   { background: var(--surface-subtle); color: var(--text-secondary); }

.kv { display: grid; gap: 14px; text-align: left; margin: 24px 0; }
.kv-row { display: flex; justify-content: space-between; gap: 16px; font-size: 15px; }
.kv-row dt { color: var(--text-secondary); flex-shrink: 0; }
/* min-width:0 lets a long billing email wrap instead of pushing the value out
   past the panel edge, which is what happened at 320px. */
.kv-row dd { font-weight: 700; min-width: 0; text-align: right; }

/* ===== Legal pages =====
   Long-form prose. Mirrors the layout of the app's privacy_policy_screen.dart
   and terms_of_service_screen.dart so the same policy reads the same in both
   places: a "last updated" pill, numbered sections separated by a rule, and
   label-led bullets. */
.legal {
  max-width: 760px;
  margin: 0 auto;
  color: var(--text-secondary);
  font-size: 15px;
  line-height: 1.75;
}

.legal h1 { font-size: 2rem; color: var(--text-primary); margin-bottom: 16px; }

.legal-updated {
  display: inline-block;
  padding: 8px 12px;
  border-radius: var(--r-sm);
  background: var(--primary-light);
  color: var(--primary-dark);
  font-size: 13px; font-weight: 500;
  margin-bottom: 24px;
}

.legal h2 {
  font-size: 1.25rem;
  color: var(--text-primary);
  margin: 40px 0 14px;
  padding-top: 32px;
  border-top: 1px solid var(--border);
}
.legal h2:first-of-type { border-top: none; padding-top: 0; }

.legal h3 {
  font-size: 1rem;
  color: var(--text-primary);
  margin: 26px 0 10px;
}

.legal p { margin-bottom: 12px; }
.legal strong { color: var(--text-primary); font-weight: 700; }
.legal a { color: var(--primary-dark); font-weight: 600; text-decoration: underline; }

.legal ul { margin: 8px 0 16px; padding-left: 22px; }
.legal li { margin-bottom: 8px; }
.legal li::marker { color: var(--primary); }

/* Callouts for the disclaimers that genuinely matter (medical advice, AI
   accuracy). The app renders these in a tinted box; matching that here keeps
   the emphasis identical across web and app. */
.legal-note {
  background: var(--surface-subtle);
  border-left: 3px solid var(--primary);
  border-radius: var(--r-sm);
  padding: 16px 18px;
  margin: 20px 0;
}
.legal-note p:last-child { margin-bottom: 0; }
.legal-note ul:last-child { margin-bottom: 0; }

/* The medical-advice warning and the all-caps warranty/liability clauses. The
   app renders these on warningSoft / dangerSoft; these are the web equivalents,
   sharing the palette already used by .alert--error. */
.legal-note--warn {
  background: #FFFBEB;
  border-left-color: #F59E0B;
}
.legal-note--danger {
  background: #FEF2F2;
  border-left-color: #B91C1C;
  color: #B91C1C;
  font-weight: 600;
}
.legal-note--danger strong { color: #B91C1C; }

/* ===== Footer ===== */
.footer {
  position: relative;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: clamp(48px, 7vw, 72px) 0 32px;
  margin-top: 0;
  overflow: hidden;
}
/* A single brand wash rising out of the bottom-left corner, so the last band on
   the page is not the only flat one. */
.footer::before {
  content: '';
  position: absolute;
  left: -8%; bottom: -60%;
  width: 720px; height: 720px;
  background: radial-gradient(circle, rgba(22, 199, 132, .09) 0%, transparent 62%);
  pointer-events: none;
}
.footer > * { position: relative; }

.footer-grid { display: grid; grid-template-columns: 1.7fr 1fr 1fr; gap: 44px; margin-bottom: 44px; }
.footer p { color: var(--text-secondary); font-size: 14px; max-width: 34ch; margin-top: 14px; }
.footer h4 { font-size: 12px; text-transform: uppercase; letter-spacing: .12em; color: var(--text-tertiary); margin-bottom: 16px; font-weight: 700; }
.footer-links { display: grid; gap: 11px; }
.footer-links a {
  font-size: 14.5px;
  font-weight: 500;
  color: var(--text-secondary);
  transition: color .16s ease, transform .22s var(--ease-spring);
  justify-self: start;
}
.footer-links a:hover { color: var(--primary-dark); transform: translateX(3px); }

/* Store button in the footer: the page's last chance to hand someone the app,
   and the only place below the hero that repeats the primary action. */
.footer-cta {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  margin-top: 22px;
  padding: 12px 20px 12px 16px;
  border-radius: var(--r-pill);
  background: var(--text-primary);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  transition: transform .24s var(--ease-spring), box-shadow .24s var(--ease-out);
  box-shadow: 0 6px 18px rgba(var(--shadow-color), .18);
}
.footer-cta:hover { transform: translateY(-2px); box-shadow: 0 12px 26px rgba(var(--shadow-color), .24); }
.footer-cta svg { width: 17px; height: 19px; flex-shrink: 0; }
.footer-cta small { display: block; font-size: 10.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; opacity: .68; }

/* Its browser twin, directly under the store button: same pill, outlined. */
.footer-web-cta {
  display: flex;
  width: fit-content;
  align-items: center;
  gap: 11px;
  margin-top: 10px;
  padding: 11px 20px 11px 16px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--text-primary);
  color: var(--text-primary);
  font-size: 14px;
  font-weight: 700;
  transition: transform .24s var(--ease-spring), background .18s ease, color .18s ease;
}
.footer-web-cta:hover { transform: translateY(-2px); background: var(--text-primary); color: #fff; }
.footer-web-cta svg { width: 18px; height: 18px; flex-shrink: 0; }
.footer-web-cta small { display: block; font-size: 10.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; opacity: .68; }

.footer-bottom {
  border-top: 1px solid var(--border);
  padding-top: 26px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  font-size: 13px; color: var(--text-tertiary);
}
.footer-social {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-weight: 600;
  color: var(--text-secondary);
  transition: color .16s ease;
}
.footer-social:hover { color: var(--primary-dark); }
.footer-social svg { width: 17px; height: 17px; }

/* ===== Forms ===== */
.field { text-align: left; margin-bottom: 16px; }
.field label {
  display: block;
  font-size: 13px; font-weight: 700;
  color: var(--text-secondary);
  margin-bottom: 6px;
}
.input {
  width: 100%;
  padding: 13px 16px;
  font-size: 15px;
  font-family: inherit;
  color: var(--text-primary);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.input:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(22, 199, 132, .15);
}
.input::placeholder { color: var(--text-tertiary); }

.tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  padding: 4px;
  background: var(--surface-subtle);
  border-radius: var(--r-pill);
  margin-bottom: 28px;
}
.tab {
  padding: 10px;
  border-radius: var(--r-pill);
  font-size: 15px; font-weight: 700;
  color: var(--text-secondary);
  transition: background .18s ease, color .18s ease;
}
.tab.active { background: var(--surface); color: var(--text-primary); box-shadow: var(--shadow-sm); }

.divider-or {
  display: flex; align-items: center; gap: 14px;
  margin: 24px 0;
  color: var(--text-tertiary); font-size: 13px; font-weight: 600;
}
.divider-or::before, .divider-or::after {
  content: ''; flex: 1; height: 1px; background: var(--border);
}

.alert {
  padding: 12px 16px;
  border-radius: var(--r-sm);
  font-size: 14px; font-weight: 600;
  text-align: left;
  margin-bottom: 16px;
}
.alert--error { background: #FEF2F2; color: #B91C1C; }
.alert--ok { background: var(--primary-light); color: var(--primary-dark); }

.link-btn {
  color: var(--primary-dark);
  font-weight: 700; font-size: 14px;
  text-decoration: underline;
}

/* ===== Dashboard ===== */
.dash-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  margin-bottom: 32px;
}
.dash-hello h2 { font-size: 1.75rem; margin-bottom: 4px; }
.dash-hello p { color: var(--text-secondary); font-size: 15px; }

.avatar {
  width: 46px; height: 46px; border-radius: var(--r-pill);
  background: var(--primary-light); color: var(--primary-dark);
  display: grid; place-items: center;
  font-weight: 800; font-size: 18px;
  flex-shrink: 0;
}

.dash-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 24px; align-items: start; }

.tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 28px;
  box-shadow: var(--shadow-sm);
}
.tile-head {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 20px;
}
.tile-head .card-icon { width: 40px; height: 40px; font-size: 19px; margin: 0; border-radius: 10px; }
.tile-title {
  font-size: 12px; font-weight: 800; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text-secondary);
}
.tile-big { font-size: 2.25rem; font-weight: 800; letter-spacing: -.03em; line-height: 1.1; }
.tile-big span { font-size: 1rem; font-weight: 600; color: var(--text-tertiary); }

/* CSS bar chart, same read as the app's weekly view */
.chart {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 10px;
  align-items: end;
  height: 190px;
  margin-top: 24px;
}
.bar-col { display: flex; flex-direction: column; align-items: center; gap: 8px; height: 100%; justify-content: flex-end; min-width: 0; }
.bar-val { font-size: 11px; font-weight: 700; color: var(--text-secondary); white-space: nowrap; }
.bar {
  width: 100%;
  max-width: 40px;
  min-height: 4px;
  border-radius: 8px 8px 4px 4px;
  background: linear-gradient(180deg, var(--primary) 0%, var(--brand-sweep-end) 100%);
  transition: height .5s cubic-bezier(.22,1,.36,1);
}
.bar--empty { background: var(--surface-subtle); }
.bar--today { box-shadow: 0 0 0 2px rgba(22,199,132,.35); }
.bar-day { font-size: 12px; font-weight: 700; color: var(--text-tertiary); }
.bar-col.is-today .bar-day { color: var(--primary-dark); }

/* Macro progress rows */
.macro { margin-bottom: 18px; }
.macro:last-child { margin-bottom: 0; }
.macro-top {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 14px; margin-bottom: 7px;
}
.macro-name { display: flex; align-items: center; gap: 8px; font-weight: 700; }
.macro-dot { width: 9px; height: 9px; border-radius: var(--r-pill); }
.macro-val { color: var(--text-secondary); font-weight: 600; }
.macro-track { height: 8px; background: var(--surface-subtle); border-radius: var(--r-pill); overflow: hidden; }
.macro-fill { height: 100%; border-radius: var(--r-pill); transition: width .5s cubic-bezier(.22,1,.36,1); }

/* Premium status tile */
.tile--pro {
  background: linear-gradient(135deg, var(--pro-start), var(--pro-end));
  border-color: transparent;
  color: #fff;
  position: relative;
  overflow: hidden;
}
.tile--pro::before {
  content: '';
  position: absolute; top: -50%; right: -20%;
  width: 320px; height: 320px;
  background: radial-gradient(circle, rgba(22,199,132,.3) 0%, transparent 65%);
  pointer-events: none;
}
.tile--pro > * { position: relative; }
.tile--pro .tile-title { color: rgba(255,255,255,.72); }
.tile--pro p { color: rgba(255,255,255,.82); font-size: 14px; }

.empty-note {
  text-align: center;
  padding: 32px 20px;
  color: var(--text-tertiary);
  font-size: 14px;
}

/* ===== Scroll reveal (progressive enhancement) ===== */
/* Scroll reveal. --d staggers siblings so a grid cascades in instead of
   snapping in as one block; it defaults to 0ms, so anything that does not set
   it behaves exactly as before. Only opacity and transform animate. */
.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .6s var(--ease-out), transform .7s var(--ease-out);
  transition-delay: var(--d, 0ms);
  will-change: opacity, transform;
}
.reveal.in { opacity: 1; transform: none; }

/* ===== Utilities ===== */
.center { text-align: center; }
.mt-32 { margin-top: 32px; }
.hidden { display: none !important; }

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

   Four steps, largest first, each one earning its place:

     <= 1024px  desktop still, but the two-column hero and the nav link row are
                near their comfortable minimum.
     <=  959px  the nav collapses to a drawer and every two-column block stacks.
                Measured, not guessed: the widest nav state (signed in, so five
                links plus Account / Sign out / Get Premium) needs ~910px, so
                960 is the first round width that always holds it.
     <=  620px  small tablet / large phone: single column everywhere, decorative
                hero furniture retired.
     <=  400px  small phone: tighter padding, smaller labels, stacked key/value
                rows. Everything must still work at 320px.
   ========================================================================== */

@media (max-width: 1024px) {
  .hero-grid { grid-template-columns: 1fr .9fr; gap: 40px; }
  .pro-band { padding: 56px 40px; }
  .tour { gap: 32px; }
  .tour-tab { padding: 13px 15px; }
  .tour-tab small { font-size: 13px; }

  /* Two columns. The hero tile goes full width and turns side-on: copy left,
     scan right, so it keeps its extra weight without becoming a tower. The
     last compact tile widens to fill the row it would otherwise half-occupy,
     because five tiles across two columns always orphans one. */
  .bento { grid-template-columns: repeat(2, 1fr); }
  .bento-card--hero { grid-column: span 2; grid-row: span 1; }
  .bento-card--wide,
  .bento-card--third { grid-column: span 1; }
  .bento-card--third:last-child { grid-column: span 2; }

  .bento-card--hero {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, .92fr);
    gap: 32px;
    align-items: center;
  }
  .bento-card--hero .bento-art { margin-top: 0; padding-top: 0; }
  .bento-card--hero .scan { aspect-ratio: 4 / 3; }
}

/* ---------- 899px: nav becomes a drawer, two-column blocks stack ---------- */
@media (max-width: 959px) {

  /* Nav.
     .nav-cta is display:contents so its three children (auth slot, "Get
     Premium", hamburger) become direct flex items of .nav-inner. That lets the
     hamburger stay in the bar while the other two drop into the drawer as
     full-width rows, without duplicating the markup in eleven HTML files. */
  .nav-inner { flex-wrap: wrap; height: auto; row-gap: 0; }
  .brand, .menu-toggle { min-height: var(--nav-h); }
  .nav-cta { display: contents; }
  .menu-toggle { display: flex; order: 3; margin-left: auto; }

  .nav-links,
  .nav-cta > .btn,
  #navAuth { display: none; }

  /* Open state. The drawer is in flow rather than absolutely positioned, so a
     long menu pushes the page down instead of overlaying it, and the
     max-height keeps it usable on a 360px-tall landscape phone. */
  .nav.is-open .nav-inner {
    max-height: 100dvh;
    overflow-y: auto;
    padding-bottom: 12px;
  }
  .nav.is-open .nav-links {
    display: flex;
    order: 4;
    flex: 0 0 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin-top: 4px;
    padding-top: 4px;
    border-top: 1px solid var(--border);
  }
  .nav-links a { padding: 14px 2px; }
  .nav.is-open #navAuth {
    display: flex;
    order: 5;
    flex: 0 0 100%;
    gap: 10px;
    margin-top: 10px;
  }
  /* Full-size tap targets once they are drawer rows rather than bar chips. */
  .nav .btn { padding: 13px 20px; font-size: 15px; }
  .nav.is-open #navAuth .btn { flex: 1; }
  .nav.is-open .nav-cta > .btn {
    display: inline-flex;
    order: 6;
    flex: 0 0 100%;
    margin-top: 10px;
  }

  /* Hamburger -> X. The 7px shift is half the 2px bar plus the 5px gap. */
  .nav.is-open .menu-toggle span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav.is-open .menu-toggle span:nth-child(2) { opacity: 0; }
  .nav.is-open .menu-toggle span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  /* Layout */
  .section-head--split { grid-template-columns: 1fr; align-items: start; }
  .section-head--split h2,
  .section-head--split .lead { grid-column: 1; }
  .section-head--split .lead { padding-bottom: 0; }

  /* Device above, chooser below: the phone is the thing worth seeing first on
     a small screen, and a five-row list pushed it entirely off-screen. */
  .tour { grid-template-columns: 1fr; gap: 30px; }
  .tour-stage { order: 1; }
  .tour-list { order: 2; }

  .hero-grid { grid-template-columns: 1fr; gap: 48px; }
  .hero .lead { max-width: none; }
  .hero-actions { justify-content: flex-start; }
  .pro-inner { grid-template-columns: 1fr; gap: 32px; }
  .stats { grid-template-columns: 1fr 1fr; gap: 28px; }
  .dash-grid { grid-template-columns: 1fr; }
  .float-card--a { left: 2%; }
  .float-card--b { right: 2%; }

  /* Brand blurb spans the full width, the two link columns sit side by side.
     A plain 1fr 1fr would have orphaned the third column onto its own row. */
  .footer-grid { grid-template-columns: 1fr 1fr; gap: 36px; }
  .footer-grid > :first-child { grid-column: 1 / -1; }
  .footer p { max-width: 46ch; }
}

/* A phone held sideways has ~360px of height to spend. Tighten the drawer so
   the whole menu is still visible at once instead of becoming a scroller. */
@media (max-width: 959px) and (max-height: 460px) {
  .nav-links a { padding: 9px 2px; }
  .nav.is-open #navAuth,
  .nav.is-open .nav-cta > .btn { margin-top: 8px; }
  .nav .btn { padding: 10px 18px; }
}

/* ---------- 700px: three-up rows that would orphan an item ----------
   Held at three across through the tablet range (768, 834) because a 2+1 wrap
   reads as a mistake, then straight to one column. Never two. */
@media (max-width: 700px) {
  .steps, .grid-trio {
    grid-template-columns: 1fr;
    gap: 32px;
    max-width: 480px;
    margin-inline: auto;
  }
}

/* ---------- 620px: phone ---------- */
@media (max-width: 620px) {
  .menu-toggle { margin-right: -8px; }   /* optical alignment with the gutter */

  .grid-2, .plans { grid-template-columns: 1fr; }
  .pro-band { padding: 44px 26px; }

  /* One column, and the hero tile drops back to a stack. */
  .bento { grid-template-columns: 1fr; gap: 14px; }
  .bento-card--hero,
  .bento-card--wide,
  .bento-card--third,
  .bento-card--third:last-child { grid-column: span 1; }
  .bento-card--hero { display: flex; }
  .bento-card--hero .bento-art { margin-top: auto; padding-top: 20px; }
  .bento-card--hero .scan { aspect-ratio: 5 / 4; }
  .bento-card { padding: 26px 22px; border-radius: var(--r-lg); }
  .bento-card--third { padding: 24px 22px; }

  /* Three columns of stat became two, so the vertical rules would now cut
     across the middle of the block rather than between figures. */
  .stat + .stat::before { display: none; }
  .stats { gap: 26px 20px; }

  /* The connector only makes sense while the steps are in a row. */
  .steps::before { display: none; }
  .stats { grid-template-columns: 1fr 1fr; padding: 28px 20px; }
  .phone--back, .float-card { display: none; }
  .phone-stage { min-height: 0; }

  /* Let the screenshot rail scroll edge to edge rather than stopping short
     inside the gutter, so it reads as a swipeable strip. */
  .shots {
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scroll-padding-inline: var(--gutter);
  }

  /* Below this the descriptions cost more room than they earn; the titles
     alone still say which screen you are picking. */
  .tour-list {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    scrollbar-width: none;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scroll-snap-type: x proximity;
  }
  .tour-list::-webkit-scrollbar { display: none; }
  .tour-tab {
    flex: 0 0 auto;
    width: auto;
    align-items: center;
    padding: 10px 16px 10px 12px;
    border-radius: var(--r-pill);
    border-color: var(--border);
    background: var(--surface);
    scroll-snap-align: center;
  }
  .tour-tab[aria-selected="true"] { transform: none; }
  .tour-tab small { display: none; }
  .tour-tab b { font-size: .95rem; white-space: nowrap; }
  .tour-tab-icon { width: 30px; height: 30px; border-radius: 9px; margin-top: 0; }
  .tour-tab-icon svg { width: 16px; height: 16px; }

  .step::before { font-size: 6rem; top: 22px; }

  .panel { padding: 32px 24px; }
  .tile { padding: 22px 18px; }
  .chart { height: 150px; gap: 6px; }
  .bar-val { font-size: 10px; }
}

/* ---------- 400px: small phone, down to 320px ---------- */
@media (max-width: 400px) {
  .footer-grid { grid-template-columns: 1fr; gap: 28px; }
  .pro-band { padding: 36px 20px; }
  .panel { padding: 26px 18px; }
  .card { padding: 26px 22px; }
  .bento-card, .bento-card--third { padding: 22px 18px; }
  .menu-row { gap: 8px; padding: 9px 11px; font-size: 12.5px; }
  .menu-row .slot { flex-basis: 52px; font-size: 9.5px; }
  .ring { width: 58px; }
  .plan { padding: 30px 22px; }
  .btn { padding: 13px 22px; }
  .btn--lg { padding: 16px 26px; font-size: 15px; }
  .hero-actions--stack { max-width: none; }
  .web-cta { padding: 11px 14px 11px 11px; gap: 12px; }
  .web-cta-text strong { font-size: 15px; }
  .tab { font-size: 14px; padding: 10px 6px; }

  /* Seven labelled bars in ~250px: shrink the label rather than let the
     numbers collide. */
  .chart { gap: 4px; }
  .bar-val { font-size: 9px; letter-spacing: -.03em; }
  .bar-day { font-size: 11px; }

  /* A label and a long value no longer fit on one line. */
  .kv-row { flex-direction: column; gap: 2px; }
  .kv-row dd { text-align: left; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
  /* The sweep is the one purely decorative loop on the page. Park it in the
     middle of the reticle rather than leaving a stray band at the top edge. */
  .scan-beam::before { animation: none; opacity: 1; transform: none; }
  .bento-card:hover, .card:hover, .btn:hover { transform: none; }
}

/* ==========================================================================
   AFFILIATE / PROMOTER DASHBOARD
   ==========================================================================
   Built entirely on the tokens above, no new brand hue, no new font, no
   framework. The one genuinely new thing is a chart palette, and it is not a
   matter of taste: the three series colours were run through a colour-vision
   validator (lightness band, chroma floor, adjacent-pair separation under
   protanopia, deuteranopia and tritanopia). Purple / amber / deep-green passes
   with a worst adjacent separation of 12.9. The obvious "brand green plus the
   navy from the Pro gradient" does NOT, #1E3A5F falls outside the lightness
   band and under the chroma floor, which means it reads as grey to a good
   number of people. Do not swap these for prettier ones without re-running
   that check.

   Amber sits below 3:1 against white. That is permitted here only because
   identity is never carried by colour alone: every series is named in the
   legend, and the same numbers exist in a table underneath. Delete the table
   and this palette stops being accessible.
   ========================================================================== */

:root {
  --series-clicks: #8B5CF6;
  --series-signups: #F59E0B;
  /* Trials. Validated as a fourth slot alongside the other three: CVD
     separation 18.0, normal-vision 19.0. The obvious choice, the app's own
     --steps orange, FAILED both against the amber and the green, which is
     exactly why this gets run through the validator instead of picked. */
  --series-trials: #0EA5E9;
  --series-earned: #0D9668;

  /* Status is a reserved scale. Never reused as a fourth series colour. */
  --state-pending: #6B7280;
  --state-good: #0D9668;
  --state-paid: #1E3A5F;
  --state-bad: #B91C1C;
}

.af-page { padding: clamp(24px, 4vw, 44px) 0 clamp(64px, 9vw, 104px); }

/* --------------------------------------------------------------------------
   Double bezel.

   A card sitting flat on the page background reads as a rectangle of colour.
   Nesting a lighter core inside a slightly darker tray, each with its own
   hairline and its own radius, reads as a physical object. The radii are
   concentric (inner = outer − padding) because two mismatched curves is
   exactly what makes a nested box look like a mistake instead of a component.
   -------------------------------------------------------------------------- */
.af-shell {
  background: linear-gradient(180deg, rgba(255, 255, 255, .6), rgba(248, 249, 250, .9));
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 6px;
  box-shadow: var(--shadow-md);
}
.af-core {
  background: var(--surface);
  border: 1px solid rgba(232, 236, 240, .9);
  border-radius: calc(var(--r-lg) - 6px);
  padding: clamp(20px, 3vw, 28px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9);
  height: 100%;
}

.af-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 10px;
}
.af-head h2 { font-size: 1.05rem; margin: 0; letter-spacing: -.01em; }
.af-sub { color: var(--text-secondary); font-size: 13.5px; margin: 0; }

/* --------------------------------------------------------------------------
   Asymmetric bento. A uniform 3-up grid of identical cards is the layout that
   makes every dashboard look like every other dashboard. The chart earns twice
   the width because it carries several times the information.
   -------------------------------------------------------------------------- */
.af-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: clamp(14px, 1.8vw, 20px);
  margin-top: clamp(18px, 2.5vw, 26px);
}
.af-col-2 { grid-column: span 2; }
.af-col-3 { grid-column: span 3; }
.af-col-4 { grid-column: span 4; }
.af-col-6 { grid-column: span 6; }

/* --------------------------------------------------------------------------
   Stat tile. A number on its own is trivia; a number with its movement against
   the previous equal-length period is a decision. The sparkline is the shape
   behind that movement, and it is what makes the delta trustworthy rather than
   merely readable, +40% off two data points is noise, and the line shows it.
   -------------------------------------------------------------------------- */
.af-stat { display: flex; flex-direction: column; gap: 6px; height: 100%; }
.af-stat-label {
  font-size: 11.5px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-tertiary);
}
.af-stat-val {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(26px, 3.4vw, 34px); line-height: 1.05;
  letter-spacing: -.025em; color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.af-stat-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: auto; padding-top: 8px;
}
.af-delta {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums;
  padding: 3px 9px; border-radius: var(--r-pill);
}
.af-delta--up { background: rgba(13, 150, 104, .10); color: var(--primary-dark); }
.af-delta--down { background: rgba(185, 28, 28, .09); color: var(--state-bad); }
.af-delta--flat { background: var(--surface-subtle); color: var(--text-tertiary); }
.af-spark { width: 74px; height: 26px; flex-shrink: 0; overflow: visible; }

/* --------------------------------------------------------------------------
   Funnel. The single view that says whether the audience or the pitch is the
   problem: clicks but no signups is a landing-page problem, signups but no
   payers is an audience problem. Neither is visible from a total.
   -------------------------------------------------------------------------- */
.af-funnel { display: flex; align-items: stretch; gap: 8px; margin-top: 18px; flex-wrap: wrap; }
.af-fstep { flex: 1 1 130px; min-width: 0; }
.af-fbar {
  height: 8px; border-radius: var(--r-pill); background: var(--surface-subtle);
  overflow: hidden; margin-bottom: 10px;
}
.af-fbar i {
  display: block; height: 100%; width: 100%; border-radius: var(--r-pill);
  transform-origin: left center; transform: scaleX(0);
  transition: transform .9s var(--ease-spring);
}
.af-fstep b {
  display: block; font-family: var(--font-display); font-size: 22px;
  line-height: 1.1; letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.af-fstep span { font-size: 12.5px; color: var(--text-secondary); }
.af-frate {
  align-self: center; flex: 0 0 auto; padding: 0 2px 18px;
  font-size: 12px; font-weight: 700; color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   Chart. Hand-drawn inline SVG, for the same reason dashboard.html draws its
   weekly bars by hand: no CDN request, no dependency, no library theme to
   fight. Grid and axes stay recessive; the data is the only thing on the page
   allowed real contrast.
   -------------------------------------------------------------------------- */
.af-chart-wrap { position: relative; margin-top: 16px; }
/* Width-driven, with the 3:1 viewBox setting the height. Pinning a fixed height
   instead would force the SVG to stretch, and a non-uniform scale distorts the
   axis text and makes a 2px stroke thicker in one direction than the other. */
.af-chart { width: 100%; height: auto; display: block; overflow: visible; }
.af-chart .grid line { stroke: var(--border); stroke-width: 1; }
.af-chart .axis text { fill: var(--text-tertiary); font-size: 11px; font-family: var(--font-sans); }
.af-chart .line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.af-chart .area { opacity: .13; }
.af-chart .crosshair { stroke: var(--border-strong); stroke-width: 1; stroke-dasharray: 3 3; }
/* A 2px surface ring, so a marker sitting on top of another series still reads
   as two marks rather than one muddy blob. */
.af-chart .dot { stroke: var(--surface); stroke-width: 2; }
.af-chart .hit { fill: transparent; cursor: crosshair; }

.af-legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px; }
.af-legend span {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; color: var(--text-secondary); font-weight: 600;
}
.af-legend i { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }

.af-tip {
  position: absolute; pointer-events: none; opacity: 0; left: 0; top: 0;
  background: var(--text-primary); color: #fff;
  padding: 9px 12px; border-radius: var(--r-sm);
  font-size: 12.5px; line-height: 1.6; white-space: nowrap;
  box-shadow: var(--shadow-lg); z-index: var(--z-raised);
  transition: opacity .18s var(--ease-out);
  font-variant-numeric: tabular-nums;
}
.af-tip.on { opacity: 1; }
.af-tip em { font-style: normal; opacity: .65; display: block; margin-bottom: 4px; }
.af-tip i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; margin-right: 6px; }

/* --------------------------------------------------------------------------
   Date range. Presets first, because "last 30 days" is what is wanted nine
   times out of ten and typing two dates to get it is a tax.
   -------------------------------------------------------------------------- */
.af-range { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.af-preset {
  border: 1px solid var(--border-strong); background: var(--surface);
  border-radius: var(--r-pill); padding: 7px 14px;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--text-secondary);
  cursor: pointer; transition: color .28s var(--ease-out),
    border-color .28s var(--ease-out), background .28s var(--ease-out),
    transform .28s var(--ease-spring);
}
.af-preset:hover { color: var(--text-primary); border-color: var(--text-tertiary); transform: translateY(-1px); }
.af-preset[aria-pressed="true"] {
  background: var(--primary-light); border-color: var(--primary); color: var(--primary-dark);
}
.af-range input[type="date"] {
  border: 1px solid var(--border-strong); background: var(--surface);
  border-radius: var(--r-sm); padding: 7px 11px; font: inherit; font-size: 13px;
  color: var(--text-primary); min-width: 0;
}

/* --------------------------------------------------------------------------
   Share link, with a button-in-button copy control
   -------------------------------------------------------------------------- */
.af-copy {
  display: flex; align-items: center; gap: 10px;
  background: var(--surface-subtle); border: 1px solid var(--border);
  border-radius: var(--r-pill); padding: 6px 6px 6px 18px;
}
.af-copy code {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: 14.5px; font-weight: 600;
  color: var(--text-primary); background: none; font-family: var(--font-sans);
}
.af-copy button {
  flex-shrink: 0; display: inline-flex; align-items: center; gap: 8px;
  background: var(--text-primary); color: #fff; border: 0;
  border-radius: var(--r-pill); padding: 8px 8px 8px 18px;
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: transform .3s var(--ease-spring), background .3s var(--ease-out);
}
.af-copy button:hover { transform: translateY(-1px); }
.af-copy button:active { transform: scale(.975); }
.af-copy button.done { background: var(--primary-dark); }
/* The icon never sits naked beside the label. It gets its own well and moves
   inside it on hover, which is what makes the control feel machined rather
   than drawn. */
.af-copy button i {
  width: 28px; height: 28px; border-radius: 50%;
  background: rgba(255, 255, 255, .16);
  display: inline-flex; align-items: center; justify-content: center;
  transition: transform .35s var(--ease-spring);
}
.af-copy button:hover i { transform: translate(2px, -1px) scale(1.06); }
.af-copy button svg { width: 15px; height: 15px; }

.af-qr {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 12px; width: max-content;
  box-shadow: var(--shadow-sm);
}
.af-qr svg { display: block; width: 128px; height: 128px; shape-rendering: crispEdges; }

/* --------------------------------------------------------------------------
   Status pills. A reserved semantic scale, always paired with the word. Never
   colour alone.
   -------------------------------------------------------------------------- */
.af-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: var(--r-pill);
  font-size: 12px; font-weight: 700; white-space: nowrap;
}
.af-pill::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.af-pill--estimated { background: var(--surface-subtle); color: var(--state-pending); }
.af-pill--confirmed { background: rgba(13, 150, 104, .11); color: var(--state-good); }
.af-pill--paid { background: rgba(30, 58, 95, .10); color: var(--state-paid); }
.af-pill--reversed { background: rgba(185, 28, 28, .09); color: var(--state-bad); }

/* --------------------------------------------------------------------------
   Tables
   -------------------------------------------------------------------------- */
.af-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; margin-top: 16px; }
.af-table { width: 100%; min-width: 560px; border-collapse: collapse; font-size: 14px; }
.af-table th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-tertiary); font-weight: 700; padding: 9px 12px;
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
.af-table td { padding: 12px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.af-table tbody tr { transition: background .2s var(--ease-out); }
.af-table tbody tr:hover { background: var(--surface-subtle); }
.af-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* --------------------------------------------------------------------------
   Empty and loading.

   An empty state that says "No data" wastes the one screen where a promoter is
   most receptive to being told what to do next. Skeletons rather than a
   spinner, because the layout must not jump when the real numbers land.
   -------------------------------------------------------------------------- */
.af-empty { text-align: center; padding: 34px 20px; }
.af-empty h3 { font-size: 1rem; margin-bottom: 6px; }
.af-empty p { color: var(--text-secondary); font-size: 14px; max-width: 44ch; margin: 0 auto 18px; }

.af-skel {
  background: linear-gradient(90deg, var(--surface-subtle) 25%, #EDEFF2 37%, var(--surface-subtle) 63%);
  background-size: 400% 100%;
  animation: afShimmer 1.4s ease infinite;
  border-radius: var(--r-xs);
  color: transparent;
}
@keyframes afShimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
.af-skel--val { height: 34px; width: 70%; }
/* Same 3:1 box the real chart resolves to, so nothing shifts when it lands. */
.af-skel--chart { aspect-ratio: 3 / 1; width: 100%; border-radius: var(--r-md); }

/* --------------------------------------------------------------------------
   How-it-works explainer
   -------------------------------------------------------------------------- */
.af-explain { background: var(--primary-light); border-color: rgba(22, 199, 132, .22); }
.af-math {
  font-variant-numeric: tabular-nums;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 16px 18px; margin: 16px 0;
  font-size: 14px; line-height: 1.9;
}
.af-math .row { display: flex; justify-content: space-between; gap: 20px; }
.af-math .row--sum { border-top: 1px solid var(--border); margin-top: 6px; padding-top: 8px; font-weight: 700; }
.af-math .row span:first-child { color: var(--text-secondary); }
.af-math .row b { color: var(--primary-dark); }

/* The payout line is the only number on this card anybody is reading for, so
   it is sized like a headline rather than like a table cell. */
.af-math .row--hero { align-items: baseline; margin-top: 8px; }
.af-math .row--hero b { font-size: 1.7rem; line-height: 1.2; letter-spacing: -0.02em; }

/* Footnote inside the sum box: the same customer on the other billing period,
   so the big number is never mistaken for the only number. */
.af-math__alt {
  border-top: 1px dashed var(--border); margin-top: 12px; padding-top: 10px;
  font-size: 13.5px; line-height: 1.6; color: var(--text-secondary);
}
.af-math__alt b { color: var(--text-primary); font-weight: 700; }

/* The "now multiply it" box.

   One sale is a number a promoter shrugs at. The same number against an
   audience they already have is the one that makes them apply, so it gets the
   largest type on the card. */
.af-scale {
  background: linear-gradient(180deg, rgba(22, 199, 132, .06), rgba(22, 199, 132, .02));
  border: 1px solid rgba(22, 199, 132, .3);
  border-radius: var(--r-md); padding: 16px 18px; margin: 16px 0;
}
.af-scale h3 {
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-tertiary); margin: 0 0 12px; font-weight: 700;
}
.af-scale .line { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.af-scale .line + .line { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border); }
.af-scale .who { font-size: 14px; color: var(--text-secondary); }
.af-scale .amt {
  font-family: var(--font-display); font-weight: 800; letter-spacing: -.02em;
  color: var(--primary-dark); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.af-scale .amt--big { font-size: clamp(1.5rem, 4.5vw, 1.9rem); line-height: 1.15; }
.af-scale .amt--sm { font-size: 1.1rem; }
.af-scale p { font-size: 13px; color: var(--text-secondary); margin: 12px 0 0; line-height: 1.6; }

.af-steps { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; align-items: center; }
.af-steps .arrow { color: var(--text-tertiary); font-size: 13px; }

@media (max-width: 900px) {
  .af-col-2, .af-col-3, .af-col-4 { grid-column: span 3; }
}
@media (max-width: 620px) {
  /* Asymmetry is a desktop luxury. Below this everything is one column with
     real vertical breathing room, and nothing overlaps a touch target. */
  .af-grid { grid-template-columns: 1fr; }
  .af-col-2, .af-col-3, .af-col-4, .af-col-6 { grid-column: span 1; }
  .af-shell { padding: 5px; border-radius: var(--r-md); }
  .af-core { border-radius: calc(var(--r-md) - 5px); padding: 18px 16px; }
  .af-copy { flex-direction: column; align-items: stretch; border-radius: var(--r-md); padding: 14px; }
  .af-copy code { white-space: normal; overflow-wrap: anywhere; text-align: center; }
  .af-copy button { justify-content: center; }
  .af-frate { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .af-skel { animation: none; }
  /* Only the movement is dropped, never the value. Forcing scaleX(1) here
     would draw every funnel bar full width, a reduced-motion preference must
     not change what the data says. */
  .af-fbar i { transition: none; }
  .af-copy button:hover, .af-preset:hover { transform: none; }
}

/* A hidden button stays hidden: .btn sets display, which beats the attribute. */
.btn[hidden] { display: none; }
