/* ==========================================================================
   RatingEcho marketing site
   --------------------------------------------------------------------------
   Static HTML/CSS, deliberately NOT Flutter. The app paints into a canvas,
   which means zero text in the DOM for crawlers and screen readers — the old
   index.html even said so in a comment ("a separate statically-rendered
   marketing page would rank considerably better"). This is that page.

   Effects are hand-ported from the three libraries this was modelled on,
   because none of them can run inside a Flutter canvas app:
     - lenis            → real library, vendored at /lenis.min.js
     - inspira-ui       → aurora background, spotlight card, border beam, marquee
     - animate-ui       → number ticker, gradient/shimmer text, motion buttons

   Everything animated below is transform / opacity / background-position so
   it stays on the compositor thread. Anything that would animate layout is
   deliberately absent.
   ========================================================================== */

/* Self-hosted Poppins (latin subset only — this site is English-only), swapped
   in for the Google Fonts CDN link. That link was render-blocking: the browser
   had to fetch fonts.googleapis.com's CSS, then chase it to fonts.gstatic.com
   for the actual files, before any text could paint. Lighthouse measured
   ~2.5s of the page's LCP going to that round trip alone. Serving the four
   weights from this origin removes the extra DNS/TLS hops and the CSS fetch
   entirely. font-display: swap keeps text visible with a fallback font while
   these load, same behavior as the old &display=swap query param. */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/poppins-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/poppins-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/poppins-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/poppins-700.woff2') format('woff2');
}

:root {
  --brand: #4f46e5;
  --brand-deep: #4338ca;
  --brand-press: #3730a3;
  --brand-soft: #eef2ff;
  --brand-line: #c7d2fe;
  /* Two golds, because one colour cannot do both jobs.
     --gold is the star glyph: amber is what a rating star is, and darkening it
     to satisfy a contrast rule would make it read as brown. It sits on the
     white phone mockup as an icon whose meaning comes from filled-vs-unfilled,
     not from the hue alone.
     --gold-text is for the same idea rendered as words or figures, where it is
     text and 1.4.3 applies. #f59e0b measures 2.15:1 on white — less than half
     the 4.5:1 floor — so the dashboard's gold stat was failing outright. This
     is amber-700 at 5.02:1, still unmistakably amber. */
  --gold: #f59e0b;
  --gold-text: #b45309;

  --ink: #0b0f1c;
  --ink-2: #111827;
  --text: #1f2430;
  --muted: #6b7280;
  --line: #e5e7eb;
  --line-soft: #f3f4f6;
  --fill: #f9fafb;
  --canvas: #ffffff;

  --radius-sm: 10px;
  --radius: 16px;
  --radius-lg: 22px;
  --radius-xl: 30px;

  --shadow-sm: 0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .1);
  --shadow: 0 10px 30px -12px rgba(16, 24, 40, .18);
  --shadow-lg: 0 30px 70px -25px rgba(16, 24, 40, .35);
  --glow: 0 18px 45px -12px rgba(79, 70, 229, .55);

  /* 1160 left ~380px of empty page either side of the content on a 1920
     monitor, which read as the site being narrower than the window rather than
     as generous margins. Widening this is safe in a way it would not be on a
     text layout: the one thing that must not grow with it is line length, and
     nothing here takes its measure from the wrap — .section-head is capped at
     42rem, .hero-sub at 33rem, .foot-brand p at 22rem. What actually widens is
     the grids: guide tiles, plan cards, the bento and the hero's two columns. */
  --maxw: 1320px;
  --ease: cubic-bezier(.22, 1, .36, 1);
}

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

html.mode-site, html.mode-site body { margin: 0; padding: 0; }

html.mode-site {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: auto; /* Lenis owns scrolling; native smooth would fight it */
}

.mode-site body {
  font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  color: var(--text);
  background: var(--canvas);
  line-height: 1.6;
  overflow-x: hidden;
}

/* Lenis drives scroll off rAF; these two are from its own stylesheet. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }

.mode-site img { max-width: 100%; display: block; }
/* :not(.btn) matters. As a plain `.mode-site a` this rule (0,1,1) outranked
   every `.btn*` colour rule (0,1,0), so buttons inherited their parent's
   colour instead — which painted the white CTA button's label white on white
   and made it read as an empty pill. */
.mode-site a:not(.btn) { color: inherit; }
.mode-site a { text-decoration: none; }
.mode-site h1, .mode-site h2, .mode-site h3, .mode-site h4 {
  margin: 0; line-height: 1.12; letter-spacing: -.022em; font-weight: 700; color: var(--ink);
}
.mode-site p { margin: 0; }
.mode-site ul { margin: 0; padding: 0; list-style: none; }

.wrap { width: 100%; max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }

/* ── Type scale ─────────────────────────────────────────────────────────── */
.h-display { font-size: clamp(2.5rem, 6.2vw, 4.4rem); font-weight: 700; letter-spacing: -.034em; }
.h-section { font-size: clamp(1.9rem, 3.6vw, 2.9rem); letter-spacing: -.028em; }
.h-card    { font-size: 1.16rem; letter-spacing: -.014em; }
.lead      { font-size: clamp(1.02rem, 1.5vw, 1.18rem); color: var(--muted); }
.eyebrow {
  font-size: .78rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--brand);
}

/* Gradient text — animate-ui's gradient-text, as a background sweep. */
.grad {
  background: linear-gradient(100deg, #818cf8 0%, #ffffff 32%, #c7d2fe 55%, #ffffff 78%, #a5b4fc 100%);
  background-size: 220% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  animation: sweep 7s linear infinite;
}
.grad-ink {
  background: linear-gradient(100deg, var(--brand) 0%, #7c3aed 45%, var(--brand) 100%);
  background-size: 200% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  animation: sweep 8s linear infinite;
}
@keyframes sweep { to { background-position: -220% 0; } }

/* ── Buttons ────────────────────────────────────────────────────────────── */
.btn {
  --btn-bg: var(--brand);
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 14px 26px; border-radius: 999px; border: 0;
  font: inherit; font-weight: 600; font-size: .97rem; cursor: pointer;
  background: var(--btn-bg); color: #fff; white-space: nowrap;
  transition: transform .3s var(--ease), box-shadow .3s var(--ease), background .2s;
  box-shadow: var(--glow);
  will-change: transform;
}
.btn:hover { transform: translateY(-2px); background: var(--brand-deep); }
.btn:active { transform: translateY(0); background: var(--brand-press); }
.btn svg { width: 17px; height: 17px; }

.btn-ghost {
  background: transparent; color: var(--ink); box-shadow: none;
  border: 1px solid var(--line);
}
.btn-ghost:hover { background: var(--fill); border-color: var(--brand-line); }

.btn-on-dark {
  background: #fff; color: var(--ink); box-shadow: 0 18px 45px -14px rgba(0, 0, 0, .6);
}
.btn-on-dark:hover { background: #fff; }
.btn-outline-dark {
  background: rgba(255, 255, 255, .06); color: #fff; box-shadow: none;
  border: 1px solid rgba(255, 255, 255, .22); backdrop-filter: blur(6px);
}
.btn-outline-dark:hover { background: rgba(255, 255, 255, .13); }
.btn-lg { padding: 16px 32px; font-size: 1.02rem; }

/* ── Nav ────────────────────────────────────────────────────────────────── */
/* Transparent while the hero is still under it, frosted white the moment you
   scroll. The earlier all-transparent version failed for a concrete reason:
   with nothing behind it, page content slid under the bar and the hero's
   floating cards overlapped the links outright — "FAQ" was unreadable behind
   a review card. A bar has to be opaque to be a bar.
   z-index sits above the hero's floating cards for the same reason. */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 200;
  background: transparent;
  transition: background .35s var(--ease), box-shadow .35s var(--ease),
              border-color .35s var(--ease), backdrop-filter .35s var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.is-stuck {
  background: rgba(255, 255, 255, .88);
  backdrop-filter: saturate(180%) blur(18px);
  -webkit-backdrop-filter: saturate(180%) blur(18px);
  border-bottom-color: rgba(16, 24, 40, .08);
  box-shadow: 0 10px 30px -22px rgba(16, 24, 40, .55);
}
.nav-inner {
  display: flex; align-items: center; gap: 28px;
  height: 74px; transition: height .35s var(--ease);
}
.nav.is-stuck .nav-inner { height: 64px; }

.nav .brand,
.nav .nav-links a,
.nav .nav-signin,
.nav .btn,
.nav .nav-burger,
.nav .nav-burger span {
  transition: color .4s var(--ease), background-color .4s var(--ease),
              border-color .4s var(--ease), box-shadow .4s var(--ease);
}

/* Over dark sections */
.nav.on-dark .brand { color: #fff; }
.nav.on-dark .nav-links a { color: rgba(255, 255, 255, .78); }
.nav.on-dark .nav-links a:hover { color: #fff; }
.nav.on-dark .nav-signin { color: rgba(255, 255, 255, .92); }
.nav.on-dark .re-mark { --echo: rgba(255, 255, 255, .85); }
/* Inverted CTA: a white pill reads as the brightest thing on a dark field. */
.nav.on-dark .btn { background: #fff; color: var(--ink); box-shadow: 0 10px 26px -14px rgba(0, 0, 0, .9); }
.nav.on-dark .btn:hover { background: #fff; }
.nav.on-dark .nav-burger { border-color: rgba(255, 255, 255, .25); background: rgba(255, 255, 255, .08); }
.nav.on-dark .nav-burger span { background: #fff; }

/* Over light sections */
.nav.on-light .brand { color: var(--ink); }
.nav.on-light .nav-links a { color: var(--muted); }
.nav.on-light .nav-links a:hover { color: var(--brand); }
.nav.on-light .nav-signin { color: var(--ink); }
.nav.on-light .re-mark { --echo: var(--brand); }
.nav.on-light .btn { background: var(--brand); color: #fff; box-shadow: 0 12px 26px -14px rgba(79, 70, 229, .9); }
.nav.on-light .btn:hover { background: var(--brand-deep); }
.nav.on-light .nav-burger { border-color: var(--line); background: rgba(255, 255, 255, .7); }
.nav.on-light .nav-burger span { background: var(--ink); }

.brand { display: flex; align-items: center; gap: 11px; font-weight: 700; font-size: 1.12rem; color: #fff; letter-spacing: -.02em; }
.nav.is-stuck .brand { color: var(--ink); }

/* ── Brand mark ─────────────────────────────────────────────────────────
   The RatingEcho speech bubble — indigo circle, "REcho" set in the same
   serif as web/favicon.svg, a tail pointing down, wrapped in the "amplified"
   echo ripple.

   The ripple is a direct port of _EchoPainter in
   lib/widgets/ratingecho_mark.dart so the web and the app pulse identically:
   3 rings, one 2800ms cycle, radius 0.98 → 1.45, opacity peaking at 0.5
   mid-cycle. Ring thickness is left constant rather than animated (the
   Flutter painter thins it from 2.6 to 1.1) because animating border-width
   would relayout every frame; transform+opacity stay on the compositor.
   ───────────────────────────────────────────────────────────────────────── */
/* display:inline-block matters — this is a <span>, and an inline box ignores
   width/height entirely, which collapses the circle to nothing and leaves the
   letters spilling out as bare text. */
.re-mark {
  position: relative; display: inline-block; flex: none;
  width: var(--m, 32px); height: var(--m, 32px);
}
/* The lettering is an inline SVG carrying BrandGeometry's own coordinates
   (lib/widgets/brand_mark.dart) — letters at their own x on one shared
   baseline of 713, in the 240 unit box the circle occupies.

   It used to be three text spans in a centred flexbox, which is what made
   this bubble read differently from the app's: align-items:center centres
   every letter independently, so the small "cho" floated at mid-height
   instead of sitting on the R's baseline, justify-content:center ignored the
   x positions entirely, and a letter-spacing the app does not have was
   applied to the lavender letters only. Coordinates cannot drift the way
   flex centring did.

   Poppins comes from the page's own webfont, which is why this works here
   and not in web/favicon.svg — an SVG favicon cannot load a font. */
.re-bubble {
  position: absolute; inset: 0; border-radius: 50%; z-index: 2;
  background: var(--brand);
  box-shadow: 0 calc(var(--m) * .13) calc(var(--m) * .29) rgba(79, 70, 229, .3);
}
.re-bubble svg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  font-family: 'Poppins', -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-weight: 700;
}
/* The tail belongs to the splash animation alone — see BrandGeometry. Kept
   hidden rather than stripped from the markup so the generated MARK string
   and gen_home.py do not need touching. */
.re-tail { display: none; }
.re-echo {
  position: absolute; inset: 0; border-radius: 50%; z-index: 0;
  border: 2px solid var(--echo, var(--brand));
  opacity: 0; transform: scale(.98);
  animation: re-echo 2800ms cubic-bezier(.5, 1, .89, 1) infinite;
  will-change: transform, opacity;
}
.re-echo:nth-of-type(2) { animation-delay: 933ms; }
.re-echo:nth-of-type(3) { animation-delay: 1866ms; }
@keyframes re-echo {
  0%   { transform: scale(.98); opacity: 0; }
  50%  { opacity: .5; }
  100% { transform: scale(1.45); opacity: 0; }
}
/* Over the dark hero and footer the indigo ring disappears; go white there. */
.hero .re-mark, footer .re-mark, .mobile-menu .re-mark, .cta .re-mark { --echo: rgba(255, 255, 255, .85); }
.nav.is-stuck .re-mark { --echo: var(--brand); }
.nav-links { display: flex; gap: 26px; margin-left: 12px; }
.nav-links a {
  font-size: .93rem; font-weight: 500; color: rgba(255, 255, 255, .78);
  position: relative; padding: 4px 0; transition: color .2s;
}
.nav.is-stuck .nav-links a { color: var(--muted); }
.nav-links a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1.5px;
  background: currentColor; transform: scaleX(0); transform-origin: right;
  transition: transform .35s var(--ease);
}
.nav-links a:hover { color: #fff; }
.nav.is-stuck .nav-links a:hover { color: var(--brand); }
.nav-links a:hover::after { transform: scaleX(1); transform-origin: left; }
.nav-cta { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.nav-signin { font-size: .93rem; font-weight: 600; color: rgba(255, 255, 255, .9); padding: 9px 4px; }
.nav.is-stuck .nav-signin { color: var(--ink); }
.nav .btn { padding: 11px 22px; font-size: .92rem; box-shadow: none; }
/* 44px, not the 34px this was. It only appears below 980px, which means it is
   the entire navigation on a phone — and 34px is under both Apple's 44pt and
   Android's 48dp minimum for something a thumb has to hit. The glyph inside
   is unchanged; only the tap area grew, so it looks the same and misses less. */
.nav-burger {
  display: none; margin-left: auto; width: 44px; height: 44px; border-radius: 11px;
  border: 1px solid rgba(255, 255, 255, .25); background: rgba(255, 255, 255, .08);
  cursor: pointer; padding: 0; place-items: center;
}
.nav.is-stuck .nav-burger { border-color: var(--line); background: #fff; }
/* 14px lines with 2.6px of margin either side leaves 5.2px of air between
   them — wider than the lines are long is tall, so the mark read as three
   loose strokes rather than one icon. Every stock hamburger sits at roughly a
   2:1 gap-to-stroke ratio; this is 16px lines at 1.6px with 4px between. */
.nav-burger span { display: block; width: 16px; height: 1.6px; background: #fff; margin: 2px auto; border-radius: 2px; transition: .3s var(--ease); }
.nav.is-stuck .nav-burger span { background: var(--ink); }

.mobile-menu {
  position: fixed; inset: 0; z-index: 55; background: rgba(11, 15, 28, .97);
  backdrop-filter: blur(14px); padding: 84px 22px 28px;
  display: flex; flex-direction: column; gap: 6px;
  opacity: 0; pointer-events: none; transform: translateY(-8px);
  /* visibility, not just opacity. opacity:0 hides a thing from eyes and from
     nothing else: the seven links in here stayed in the tab order at every
     width, so a keyboard user tabbing the desktop page — where the burger
     that opens this is display:none — walked through seven invisible links
     before reaching the hero. visibility:hidden takes them out of the tab
     order, and delaying it until the fade finishes keeps the animation.
     pointer-events alone never fixed this; it only ever governed the mouse. */
  visibility: hidden;
  transition: opacity .3s var(--ease), transform .3s var(--ease), visibility 0s linear .3s;
}
.mobile-menu.is-open {
  opacity: 1; pointer-events: auto; transform: none;
  visibility: visible;
  transition: opacity .3s var(--ease), transform .3s var(--ease), visibility 0s;
}
/* .mode-site prefix is load-bearing: `.mode-site a:not(.btn)` (0,2,1)
   outranks a bare `.mobile-menu a` (0,1,1), so these links inherited the
   body's dark text and rendered near-invisible on the dark panel. Same
   cascade trap as the footer headings and the CTA button. */
.mode-site .mobile-menu a {
  color: #fff; font-size: 1.06rem; font-weight: 600;
  padding: 13px 2px; border-bottom: 1px solid rgba(255, 255, 255, .09);
}
.mode-site .mobile-menu a:last-of-type { border-bottom: 0; }
.mode-site .mobile-menu a.btn { color: #fff; }
.mobile-menu .btn { margin-top: 18px; padding: 13px 22px; font-size: .95rem; }

/* ── Hero ───────────────────────────────────────────────────────────────── */
.hero {
  position: relative; overflow: hidden; isolation: isolate;
  background: var(--ink); color: #fff;
  padding: 152px 0 108px;
}
/* Fade into the white body below. A straight linear ramp of white over a
   near-black ground goes grey almost immediately and reads as a hard band
   across the hero — the stops below hold the wash back until the last third,
   so the transition lands as atmosphere rather than a seam. */
.hero::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 220px;
  background: linear-gradient(to bottom,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, .03) 34%,
    rgba(255, 255, 255, .14) 56%,
    rgba(255, 255, 255, .46) 76%,
    rgba(255, 255, 255, .85) 91%,
    #fff 100%);
  z-index: 3; pointer-events: none;
}

/* inspira-ui Aurora Background: slow drifting radial blooms. */
/* Opacity held well down — at .85 with near-opaque blooms this read as a
   saturated purple wall rather than atmosphere. */
.aurora { position: absolute; inset: -30% -10%; z-index: 0; filter: blur(96px); opacity: .55; }
.aurora i {
  position: absolute; display: block; border-radius: 50%; mix-blend-mode: screen;
  will-change: transform;
}
.aurora i:nth-child(1) { width: 46vw; height: 46vw; left: 4%;  top: 6%;  background: radial-gradient(circle, rgba(79, 70, 229, .62), transparent 64%); animation: drift1 28s ease-in-out infinite; }
.aurora i:nth-child(2) { width: 40vw; height: 40vw; right: 2%; top: 0;   background: radial-gradient(circle, rgba(109, 91, 214, .42), transparent 64%);  animation: drift2 34s ease-in-out infinite; }
.aurora i:nth-child(3) { width: 34vw; height: 34vw; left: 34%; bottom: 0; background: radial-gradient(circle, rgba(62, 127, 184, .34), transparent 64%); animation: drift3 40s ease-in-out infinite; }
/* Frozen below 900px. The blooms are 34-46vw circles under a 96px blur that
   drift on 28-40s loops, so the hero is still visually changing long after it
   has finished loading. Lighthouse scores Speed Index on how quickly the
   viewport becomes visually stable, and against a 40s cycle it never does:
   mobile measured 16.5s against a 1.1s First Contentful Paint, while desktop
   — unthrottled, absorbing the compositing — measured 0.9s. Holding them still
   keeps the exact colours and composition and drops only the motion, which is
   the part least perceptible on a phone-sized hero anyway. Desktop keeps it. */
@media (max-width: 900px) {
  .aurora i { animation: none; }
}

@keyframes drift1 { 0%, 100% { transform: translate3d(0, 0, 0) scale(1); } 50% { transform: translate3d(7vw, 4vh, 0) scale(1.14); } }
@keyframes drift2 { 0%, 100% { transform: translate3d(0, 0, 0) scale(1.06); } 50% { transform: translate3d(-6vw, 6vh, 0) scale(.92); } }
@keyframes drift3 { 0%, 100% { transform: translate3d(0, 0, 0) scale(.95); } 50% { transform: translate3d(4vw, -5vh, 0) scale(1.16); } }

/* The grid lines and film grain that used to sit here are gone on purpose:
   at any real viewport they read as visible square blocks over the gradient
   rather than as texture. A deep vignette does the same job — stopping the
   gradient looking flat — without putting hard edges on screen. */
.hero-grid {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    radial-gradient(ellipse 90% 70% at 50% 0%, rgba(129, 140, 248, .18), transparent 60%),
    radial-gradient(ellipse 120% 80% at 50% 100%, rgba(11, 15, 28, .75), transparent 70%);
}
.grain { display: none; }
.hero .wrap { position: relative; z-index: 4; }

.hero-grid-2col {
  display: grid; grid-template-columns: minmax(0, 1.06fr) minmax(0, .94fr);
  gap: 56px; align-items: center;
}

/* A pill that wraps stops being a pill: the badge sits alone on line one and
   the tail hangs under it as loose text. So it never breaks — the type shrinks
   to fit the line instead. Every internal length is in em for that reason, so
   padding, gap and badge shrink with the label and the shape stays a shape. */
.pill {
  display: inline-flex; align-items: center; gap: .686em; white-space: nowrap;
  padding: .534em 1.144em .534em .61em; border-radius: 999px; margin-bottom: 26px;
  background: rgba(255, 255, 255, .07); border: 1px solid rgba(255, 255, 255, .16);
  font-size: .82rem; font-weight: 500; color: rgba(255, 255, 255, .9);
  backdrop-filter: blur(8px);
}
.pill b {
  background: linear-gradient(120deg, var(--brand), #a78bfa); color: #fff;
  padding: .229em .762em; border-radius: 999px; font-size: .878em; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase;
}
/* The hero's copy column is the pill's real constraint, not the viewport:
   between 561 and 980 the mock takes 44% of the row, so a vw-based size would
   still wrap the line there while claiming there was room. Hence a container.
   The whole pill measures ~28.7× its font-size, so 3.35cqi keeps it inside the
   column with a few percent to spare, and min() means it only ever shrinks —
   at 428px and up it is the .82rem it always was. */
.hero-grid-2col > div:first-child { container-type: inline-size; }
.hero-grid-2col .pill { font-size: min(.82rem, 3.35cqi); }
/* The H1 carries the app name and what the product does, ahead of the
   slogan — Google's OAuth review reads the headline for exactly that, and a
   slogan on its own satisfies neither half. Two spans so the descriptive
   line can be typeset small without leaving the heading. */
.hero-h1 { margin: 0 0 22px; }
/* Quiet enough not to compete with the display line under it — at 600 weight
   and .82 alpha it read as a second headline and the eye had nowhere to land
   first. */
.h1-lead {
  display: block; font-size: .86rem; font-weight: 500; line-height: 1.45;
  letter-spacing: 0; color: rgba(255, 255, 255, .58); margin-bottom: 14px;
  max-width: 32rem;
}
.h1-display {
  display: block;
  font-size: clamp(2.1rem, 4.9vw, 3.5rem); font-weight: 700;
  letter-spacing: -.03em; line-height: 1.08; color: #fff;
}
.policy-links { margin-top: 18px; font-size: .86rem; color: rgba(255, 255, 255, .5); }
.policy-links a { color: rgba(255, 255, 255, .8); text-decoration: underline; text-underline-offset: 3px; }
.policy-links a:hover { color: #fff; }
.policy-links span { margin: 0 8px; }
.inline-link { color: var(--brand); text-decoration: underline; text-underline-offset: 3px; font-weight: 500; }
/* Plain, selectable text stating what the product is — Google's OAuth
   review reads the homepage for exactly this, and the stylised wordmark in
   the nav does not extract as "RatingEcho". */
.purpose {
  font-size: .92rem; color: rgba(255, 255, 255, .76); max-width: 33rem;
  margin: -8px 0 18px; line-height: 1.55;
}
.hero h1 { color: #fff; }
.mode-site .hero-sub { font-size: .92rem; color: rgba(255, 255, 255, .7); max-width: 33rem; margin-bottom: 30px; line-height: 1.55; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 13px; align-items: center; }
.hero-note { margin-top: 20px; font-size: .87rem; color: rgba(255, 255, 255, .64); display: flex; align-items: flex-start; gap: 8px; }
.hero-note svg { width: 15px; height: 15px; color: #6ee7b7; flex: none; margin-top: 3px; }

/* ── Hero device mock ───────────────────────────────────────────────────── */
/* 496px is not a taste call — it is the width this column had when --maxw was
   1160, and every float-card offset below is a percentage of it. The cards are
   positioned against .mock while the phone inside is a flat min(310px, 78%),
   so widening the page widened the stage without moving the device: the column
   went to 571px, left:-8% became 46px instead of 40, and the cards slid out
   from under the phone until they were merely next to it. Pinning the stage to
   the geometry the offsets were tuned against keeps the overlap identical at
   any page width, and the extra column width becomes air around it. Both
   narrow bands reset this — see the ≤980 and ≤560 blocks. */
/* width, not max-width: auto margins in a grid item's inline axis override the
   default stretch, so max-width + auto margins let the box fall back to
   shrink-to-fit — 422px here, which overshot the other way and buried more of
   the phone than the cards ever did. An explicit width holds the stage at 496
   and lets min() hand the column back whenever it is narrower than that. */
.mock { position: relative; perspective: 1400px; width: min(100%, 496px); margin: 0 auto; }
/* A pool of light under the device, so the phone sits in the gradient rather
   than on top of it. Behind everything in the mock (z-index 0). */
.mock::before {
  content: ''; position: absolute; z-index: 0; pointer-events: none;
  left: 50%; top: 50%; width: 132%; aspect-ratio: 1; transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(129, 140, 248, .22), transparent 62%);
  filter: blur(28px);
}
.mock-tilt { transform-style: preserve-3d; transition: transform .5s var(--ease); will-change: transform; }
.phone {
  position: relative; z-index: 2; width: min(310px, 78%); margin: 0 auto;
  background: #fff; border-radius: 38px; padding: 11px;
  box-shadow: 0 50px 90px -30px rgba(0, 0, 0, .75), 0 0 0 1px rgba(255, 255, 255, .1);
}
.phone-screen { background: #fff; border-radius: 28px; overflow: hidden; }
.phone-top { height: 24px; display: grid; place-items: center; }
.phone-notch { width: 78px; height: 5px; border-radius: 99px; background: #e5e7eb; }
.phone-body { padding: 6px 18px 22px; text-align: center; }
.phone-logo-wrap { display: flex; justify-content: center; margin: 4px auto 16px; }
.phone-q { font-size: 1.02rem; font-weight: 700; color: var(--ink); line-height: 1.3; }
.phone-hint { font-size: .74rem; color: var(--muted); margin: 5px 0 15px; }
.stars { display: flex; justify-content: center; gap: 5px; margin-bottom: 16px; }
.stars svg { width: 27px; height: 27px; color: #e5e7eb; transition: color .25s var(--ease), transform .35s var(--ease); }
.stars svg.on { color: var(--gold); transform: scale(1.08); }
.rowlet { display: flex; align-items: center; justify-content: space-between; padding: 9px 0; border-top: 1px solid var(--line-soft); font-size: .78rem; color: var(--text); }
.rowlet .mini { display: flex; gap: 2.5px; }
.rowlet .mini svg { width: 12px; height: 12px; color: var(--gold); }
.phone-cta { margin-top: 15px; background: var(--brand); color: #fff; border-radius: 12px; padding: 11px; font-size: .84rem; font-weight: 600; }

.float-card {
  position: absolute; z-index: 5; background: rgba(255, 255, 255, .97);
  border-radius: 15px; padding: 12px 14px; width: 216px;
  /* A hairline ring in ink, not white — against the white phone behind them
     the old white border vanished and the two surfaces fused into one shape. */
  box-shadow: 0 28px 56px -20px rgba(0, 0, 0, .62), 0 0 0 1px rgba(15, 23, 42, .06);
  border: 0;
  animation: bob 7s ease-in-out infinite;
}
.float-card .fc-top { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; }
.float-card .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.float-card .fc-label { font-size: .66rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.float-card .fc-text { font-size: .8rem; color: var(--text); line-height: 1.45; }
.float-card .fc-text b { color: var(--ink); }
/* The cards frame the phone from its corners rather than covering its
   screen: fc-1 peeks off the top-left, above the "How was your experience"
   header; fc-2 sits off the bottom-right near the submit button. Positions
   verified against real screenshots at 1180px and 1440px, not guessed —
   earlier values put fc-1 over the header text and pushed fc-2 past the
   viewport edge (clipped by .hero's overflow:hidden).
   pointer-events:none because they are decoration and must never eat a click
   meant for the CTA behind them. */
/* The layered/overlapping look: cards float over the phone's edges rather
   than tucked to the corners, which is the depth effect that reads as a
   living product shot. Positions verified against screenshots so they
   overlap the phone but stay inside the viewport and don't bury the header. */
/* Three cards floating around the phone: review top-left, live order on the
   right, loyalty bottom-left — a loose triangle that reads as a live product
   shot. Positions verified against screenshots so all three overlap the
   phone's edges but stay in the viewport and clear the header + submit
   button. fc-3 is a touch smaller so the bottom-left corner doesn't crowd. */
/* Positions are the difference between a product shot and a pile-up. Each card
   overlaps the phone's ROUNDED EDGE only — never the screen content. The
   earlier values put fc-2 across the Food/Service rating rows and left fc-3
   half-buried under the phone body, which is what made the hero look broken.
   Checked against 1440 / 1180 / 900 screenshots, not guessed. */
/* Positions are the difference between a product shot and a pile-up.
   The phone is only 310px wide inside a 496px column, so a 216px card can
   never sit fully clear of it horizontally — which means the placement has to
   be chosen VERTICALLY, against the two bands of the screen that carry no
   text: the header strip above the question (y 161–260 at 1440) and the
   padding below the submit button (y 510–542). Both cards overlap the phone's
   rounded edge there and occlude nothing.
   The measured screen bands are, top to bottom: logo 210–256, question
   265–305, hint 311–330, stars 330–357, the two rating rows 373–451, CTA
   466–510. Anything landing in that range covers content — which is exactly
   what the previous `bottom: 30%` did to the Food and Service rows. */
.float-card { pointer-events: none; }
.fc-1 { top: 1%;  left: -17%;  animation-delay: -1s; }
.fc-2 { top: 92%; right: -13%; animation-delay: -3.5s; }
.fc-3 { bottom: 1%; left: -17%; width: 194px; animation-delay: -5s; }

/* Between the mobile breakpoint and ~1260 the wrap has stopped growing but the
   viewport has not: the outward offsets above run past the edge and .hero's
   overflow:hidden slices the cards. Tuck them in. Overlapping the phone more
   is safe here — the vertical bands they sit in are empty either way. */
@media (max-width: 1260px) {
  .float-card { width: 196px; }
  .fc-1 { left: -8%; }
  .fc-2 { right: -4%; }
  .fc-3 { left: -8%; width: 182px; }
}
/* Cards stay visible down to phones — the per-breakpoint rules below size
   and reposition them so they overlap the (now centred) phone without
   clipping. */
@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-13px); } }

/* ── Marquee (inspira-ui) ───────────────────────────────────────────────── */
.strip { border-bottom: 1px solid var(--line); background: #fff; padding: 30px 0; }
/* A wider fade at both ends — at 12% the items were still near full opacity
   when they hit the edge, so words got sliced mid-letter instead of dissolving. */
.marquee { position: relative; overflow: hidden; mask-image: linear-gradient(90deg, transparent 0, #000 18%, #000 82%, transparent 100%); -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 18%, #000 82%, transparent 100%); }
.marquee-track { display: flex; gap: 46px; width: max-content; animation: slide 34s linear infinite; }
.marquee:hover .marquee-track { animation-play-state: paused; }
.marquee-item { display: flex; align-items: center; gap: 10px; font-size: .92rem; font-weight: 500; color: var(--muted); white-space: nowrap; }
.marquee-item svg { width: 17px; height: 17px; color: var(--brand); flex: none; }
@keyframes slide { to { transform: translateX(-50%); } }

/* ── Sections ───────────────────────────────────────────────────────────── */
.section { padding: 104px 0; }
.section-head { max-width: 42rem; margin: 0 auto 56px; text-align: center; }
.section-head .eyebrow { display: block; margin-bottom: 14px; }
.section-head p { margin-top: 16px; }

/* ── Stats ──────────────────────────────────────────────────────────────── */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.stat {
  text-align: center; padding: 30px 16px; border-radius: var(--radius);
  background: linear-gradient(180deg, #fff, var(--fill)); border: 1px solid var(--line);
  transition: transform .45s var(--ease), box-shadow .45s var(--ease), border-color .45s;
}
.stat:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--brand-line); }
.stat-n { font-size: clamp(1.9rem, 3.4vw, 2.6rem); font-weight: 700; color: var(--ink); letter-spacing: -.03em; }
.stat-n .suf { color: var(--brand); }
.stat-l { font-size: .84rem; color: var(--muted); margin-top: 4px; }

/* ── Bento + spotlight (inspira-ui) ─────────────────────────────────────── */
/* Six columns, so every card's span must sum to a multiple of six or the last
   row orphans. It did: 4+2 / 2+2+2 / 3+3 / 2 came to twenty, leaving the final
   card alone against four columns of dead space. Now 4+2 / 2+2+2 / 2+2+2 —
   eighteen, three full rows. Adding a card here means re-checking that sum. */
.bento { display: grid; grid-template-columns: repeat(6, 1fr); gap: 20px; }
.card {
  position: relative; overflow: hidden; background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 30px;
  transition: transform .45s var(--ease), box-shadow .45s var(--ease), border-color .45s;
  will-change: transform;
}
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); border-color: var(--brand-line); }
/* Mouse-follow spotlight; --mx/--my are written by site.js. */
.card::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0;
  transition: opacity .4s var(--ease);
  background: radial-gradient(360px circle at var(--mx, 50%) var(--my, 50%), rgba(79, 70, 229, .1), transparent 62%);
}
.card:hover::before { opacity: 1; }
.card-icon {
  width: 46px; height: 46px; border-radius: 13px; display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand); margin-bottom: 18px;
}
.card-icon svg { width: 22px; height: 22px; }
.card h3 { margin-bottom: 9px; }
.card p { font-size: .93rem; color: var(--muted); }
.c-4 { grid-column: span 4; }
.c-3 { grid-column: span 3; }
.c-2 { grid-column: span 2; }

/* Featured card: dark, with inspira-ui's rotating border beam. */
.card-dark { background: var(--ink); border-color: transparent; color: #fff; }
.card-dark h3 { color: #fff; }
.card-dark p { color: rgba(255, 255, 255, .68); }
.card-dark .card-icon { background: rgba(255, 255, 255, .1); color: #a5b4fc; }
.card-dark::before { background: radial-gradient(400px circle at var(--mx, 50%) var(--my, 50%), rgba(129, 140, 248, .22), transparent 60%); }
.beam { position: relative; }
/* A long, soft sweep. The old 62%→78% window was a short hard-edged segment
   that parked on one side of the card and read as a stray blue line rather
   than as light travelling round the border. */
.beam::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; padding: 1.25px;
  background: conic-gradient(from var(--angle, 0deg),
    transparent 40%, rgba(129, 140, 248, .35) 58%, #a5b4fc 72%, #c4b5fd 78%,
    rgba(129, 140, 248, .35) 88%, transparent 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  animation: spin-beam 5.5s linear infinite; pointer-events: none;
}
@property --angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
@keyframes spin-beam { to { --angle: 360deg; } }

/* ── Language showcase ──────────────────────────────────────────────────── */
.langs { background: var(--fill); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.lang-grid { display: grid; grid-template-columns: minmax(0, .92fr) minmax(0, 1.08fr); gap: 54px; align-items: center; }
.lang-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 26px; }
.chip {
  padding: 7px 14px; border-radius: 999px; background: #fff; border: 1px solid var(--line);
  font-size: .84rem; font-weight: 500; color: var(--muted); cursor: default;
  transition: all .3s var(--ease);
}
.chip.on { background: var(--brand); border-color: var(--brand); color: #fff; box-shadow: 0 8px 20px -10px rgba(79, 70, 229, .9); }
.lang-demo { background: #fff; border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 28px; box-shadow: var(--shadow); }
.demo-row { display: flex; gap: 13px; align-items: flex-start; padding: 15px 0; }
.demo-row + .demo-row { border-top: 1px solid var(--line-soft); }
.avatar { width: 36px; height: 36px; border-radius: 50%; flex: none; display: grid; place-items: center; font-size: .78rem; font-weight: 700; color: #fff; background: linear-gradient(140deg, var(--brand), #7c3aed); }
.avatar.rest { background: linear-gradient(140deg, #059669, #10b981); }
.demo-who { font-size: .74rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-bottom: 4px; }
.demo-txt { font-size: .98rem; color: var(--ink); line-height: 1.5; min-height: 1.5em; }
.demo-txt.typing::after { content: '▌'; color: var(--brand); animation: caret 1s steps(2) infinite; }
@keyframes caret { 50% { opacity: 0; } }
.demo-foot { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line-soft); font-size: .78rem; color: var(--muted); display: flex; align-items: center; gap: 7px; }
.demo-foot svg { width: 14px; height: 14px; color: var(--brand); }

/* ── Steps ──────────────────────────────────────────────────────────────── */
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; position: relative; }
/* A rule through the centre of the three markers, so the steps read as one
   sequence instead of three unrelated paragraphs. Sits behind the (opaque)
   markers and fades out at both ends rather than butting into the column edge.
   Centre line = .step padding-top (8) + half the 44px marker. */
.steps::before {
  content: ''; position: absolute; z-index: 0; top: 30px; left: 8%; right: 8%; height: 1px;
  background: linear-gradient(90deg, transparent, var(--line) 12%, var(--line) 88%, transparent);
}
.step { position: relative; z-index: 1; padding-top: 8px; }
.step-n {
  position: relative; width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center;
  background: linear-gradient(145deg, #1e1b3a, var(--ink));
  color: #fff; font-weight: 700; font-size: .96rem; margin-bottom: 18px;
  box-shadow: 0 10px 24px -12px rgba(0, 0, 0, .8), 0 0 0 6px #fff;
}
.step h3 { margin-bottom: 8px; }
.step p { font-size: .94rem; color: var(--muted); }

/* ── Pricing ────────────────────────────────────────────────────────────── */
.toggle { display: inline-flex; padding: 4px; background: var(--fill); border: 1px solid var(--line); border-radius: 999px; margin: 0 auto 44px; }
.toggle button {
  border: 0; background: transparent; font: inherit; font-size: .88rem; font-weight: 600;
  color: var(--muted); padding: 9px 20px; border-radius: 999px; cursor: pointer; transition: .3s var(--ease);
}
.toggle button.on { background: #fff; color: var(--ink); box-shadow: var(--shadow-sm); }
.toggle .save { color: var(--brand); font-size: .76rem; margin-left: 5px; }

/* stretch, not start — with align-items:start each card sized to its own
   feature list and the three bottom edges landed at three different heights. */
/* ── Pricing watermark ──────────────────────────────────────────────────
   Thermal slips drifting behind the plans, because this is the section where
   a reader is deciding whether to pay for a thing that prints them.

   overflow:hidden on the section is what makes the scatter safe: slips are
   placed past the edges on purpose so they read as a pattern continuing off
   the page rather than eight objects arranged inside a box, and without the
   clip they would widen the document and produce a horizontal scrollbar.

   The stacking is explicit. The section gets no z-index of its own — giving
   it one would put the whole thing in front of the sticky nav — so the layer
   sits at z-index 0 and the .wrap that holds the cards is lifted to 1.
   ───────────────────────────────────────────────────────────────────────── */
#pricing { position: relative; overflow: hidden; }
#pricing > .wrap { position: relative; z-index: 1; }
.slips { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.slips .slip {
  position: absolute; color: var(--ink);
  opacity: .055;
  width: 118px; height: auto;
}
/* Hand-placed, not random: each one sits in a margin or a gap between cards,
   since a slip behind an opaque white card is a slip nobody sees. The
   rotations are all different and none is a round number — a scatter with
   four things at 15deg reads as a grid that slipped. */
.slips .s1 { top: 4%;   left: -2%;  transform: rotate(-13deg); }
.slips .s2 { top: 26%;  left: 6%;   transform: rotate(9deg);   width: 92px; opacity: .04; }
.slips .s3 { top: 62%;  left: -3%;  transform: rotate(21deg);  width: 134px; }
.slips .s4 { top: 8%;   right: 3%;  transform: rotate(17deg); }
.slips .s5 { top: 44%;  right: -2%; transform: rotate(-8deg);  width: 146px; opacity: .05; }
.slips .s6 { bottom: 3%; right: 8%; transform: rotate(-24deg); width: 96px;  opacity: .04; }
.slips .s7 { bottom: -4%; left: 28%; transform: rotate(6deg);  width: 104px; opacity: .035; }
.slips .s8 { top: -3%;  left: 46%;  transform: rotate(-19deg); width: 88px;  opacity: .035; }
/* Below 900 the cards go full width and there are no margins left to put a
   slip in, so every one of them would be behind a card doing nothing. */
@media (max-width: 900px) { .slips { display: none; } }
@media (prefers-reduced-motion: no-preference) { .slips .slip { will-change: auto; } }

.plans { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; align-items: stretch; }
.plan { background: #fff; border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 32px; position: relative; transition: transform .45s var(--ease), box-shadow .45s var(--ease); }
.plan:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.plan.featured { border-color: var(--brand); box-shadow: var(--shadow-lg); }
.plan-tag { position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: var(--brand); color: #fff; font-size: .7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; padding: 5px 14px; border-radius: 999px; white-space: nowrap; }
.plan-name { font-size: 1.06rem; font-weight: 700; color: var(--ink); }
.plan-for { font-size: .84rem; color: var(--muted); margin-top: 3px; min-height: 2.4em; }
/* Fixed height and line-height, so the three CTAs land on one line. "Free"
   has no rupee glyph and no descender, so left to itself it set a shorter box
   than "₹2,499" and pushed the Basic column's button 6px out of step with the
   other two. */
.plan-price { display: flex; align-items: baseline; gap: 5px; margin: 20px 0 3px; min-height: 2.9rem; }
.plan-price .amt { font-size: 2.5rem; line-height: 1.1; font-weight: 700; color: var(--ink); letter-spacing: -.035em; }
.plan-price .per { font-size: .88rem; color: var(--muted); }
/* min-height must match the height one line actually occupies, or the plans
   that have a line here (Basic's "During our launch period") sit taller than
   the ones that don't and the row of CTAs steps down. */
.plan-inr { font-size: .8rem; color: var(--muted); line-height: 1.5; min-height: 1.5em; }
.plan .btn { width: 100%; margin: 22px 0; }
.plan ul li { display: flex; gap: 10px; font-size: .9rem; color: var(--text); padding: 7px 0; }
.plan ul svg { width: 17px; height: 17px; color: #059669; flex: none; margin-top: 3px; }
.plan-foot { font-size: .78rem; color: var(--muted); text-align: center; margin-top: 52px; }
/* The reset `.mode-site p { margin: 0 }` is specificity 0,1,1 and outranks a
   single class, so the margin above never applied on any page that carries
   mode-site — which at runtime is all of them, since site.js adds the class to
   the landing page too. The footnote has been sitting flush against the
   pricing cards whatever number was written here. */
.mode-site .plan-foot { margin-top: 52px; }

/* ── FAQ ────────────────────────────────────────────────────────────────── */
.faq { max-width: 47rem; margin: 0 auto; }
.qa { border-bottom: 1px solid var(--line); }
.qa button {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 20px;
  background: none; border: 0; font: inherit; text-align: left; cursor: pointer;
  padding: 22px 0; font-weight: 600; font-size: 1.02rem; color: var(--ink);
}
.qa button:hover { color: var(--brand); }
.qa .ic { width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--line); display: grid; place-items: center; flex: none; transition: .35s var(--ease); position: relative; }
.qa .ic::before, .qa .ic::after { content: ''; position: absolute; background: var(--ink); border-radius: 2px; transition: .35s var(--ease); }
.qa .ic::before { width: 11px; height: 1.6px; }
.qa .ic::after { width: 1.6px; height: 11px; }
.qa.open .ic { background: var(--brand); border-color: var(--brand); transform: rotate(135deg); }
.qa.open .ic::before, .qa.open .ic::after { background: #fff; }
.qa .a { overflow: hidden; height: 0; transition: height .4s var(--ease); }
.qa .a p { padding: 0 0 22px; font-size: .96rem; color: var(--muted); max-width: 40rem; }

/* ── CTA + footer ───────────────────────────────────────────────────────── */
.cta { position: relative; overflow: hidden; background: var(--ink); color: #fff; text-align: center; padding: 104px 0; isolation: isolate; }
.cta h2 { color: #fff; margin-bottom: 16px; }
.cta p { color: rgba(255, 255, 255, .7); max-width: 34rem; margin: 0 auto 32px; }
.cta .hero-actions { justify-content: center; }

footer { background: var(--ink-2); color: rgba(255, 255, 255, .62); padding: 62px 0 34px; font-size: .9rem; }
.foot-grid { display: grid; grid-template-columns: 1.6fr repeat(3, 1fr); gap: 40px; margin-bottom: 44px; }
/* .mode-site prefix is load-bearing: as a bare `footer h4` (0,0,2) this lost
   to `.mode-site h4` (0,1,1) up top, which paints headings ink — invisible on
   the dark footer. */
.mode-site footer h4 { color: #fff; font-size: .82rem; letter-spacing: .1em; text-transform: uppercase; margin-bottom: 15px; font-weight: 600; }
footer a { display: block; padding: 5px 0; color: rgba(255, 255, 255, .62); transition: color .2s; }
footer a:hover { color: #fff; }
.foot-brand { color: #fff; }
.foot-brand p { margin-top: 12px; max-width: 22rem; color: rgba(255, 255, 255, .55); font-size: .88rem; }
/* .45 alpha computed to 4.47:1 against the footer's #111827, just under the
   4.5:1 WCAG AA floor for text this size. .55 clears it with room to spare. */
.foot-bottom { border-top: 1px solid rgba(255, 255, 255, .1); padding-top: 24px; display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; font-size: .84rem; color: rgba(255, 255, 255, .55); }

/* ── Scroll reveal ──────────────────────────────────────────────────────── */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity .75s var(--ease), transform .75s var(--ease); }
.reveal.in { opacity: 1; transform: none; }

/* ── Focus ──────────────────────────────────────────────────────────────────
   The page had no focus styles at all, so anyone navigating by keyboard had
   no idea where they were — and because nothing here suppressed the default
   outline, what they actually got was the browser's, drawn in whatever colour
   it fancied against a dark hero it was never designed for.

   :focus-visible rather than :focus, so a clicked button does not keep a ring
   the mouse user never asked for. Every browser this site supports has it.

   The ring colour follows the section's own tone. Sections already declare
   data-tone for the nav's colour-inversion observer, so the same attribute
   drives this: indigo reads 8.6:1 on white, and would nearly vanish against
   the near-black hero, where white reads 19:1. Both clear the 3:1 that WCAG
   2.4.11 asks of a focus indicator against what sits next to it.

   Outline, not box-shadow. Half the interactive elements here already carry a
   shadow for depth (.btn, .plan) and a focus box-shadow would replace it,
   making the control jump at the moment attention lands on it. Outline is
   painted outside the border box, follows border-radius in every current
   browser, and takes part in no layout.
   ────────────────────────────────────────────────────────────────────────── */
.mode-site {
  --focus-ring: var(--brand);
}
/* The dark half of the page: hero, dashboard showcase, CTA, footer, the nav
   before it sticks, and the mobile menu — which carries no data-tone because
   it is a fixed overlay rather than a section. */
.mode-site [data-tone="dark"],
.mode-site .mobile-menu,
.mode-site .nav:not(.is-stuck) {
  --focus-ring: #fff;
}

.mode-site a:focus-visible,
.mode-site button:focus-visible,
.mode-site summary:focus-visible,
.mode-site input:focus-visible,
/* Negative tabindex excluded: those are programmatic focus targets — the hero
   the skip link lands on — and a ring drawn round a whole section is noise,
   not guidance. The user is looking at where they arrived, not hunting for it. */
.mode-site [tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Underline bars and lifts are hover affordances; a keyboard user arriving at
   the same link should get the same affordance, not just the ring. */
.mode-site .nav-links a:focus-visible::after { transform: scaleX(1); transform-origin: left; }

/* Skip link. The nav, the hero and its floating cards sit ahead of the real
   content, so reaching the page by keyboard meant tabbing past all of it on
   every single load. Off-screen until focused, which is the one time it is
   needed — and it must not be display:none, or it would never be reachable
   to focus in the first place. */
.skip-link {
  position: absolute; left: 12px; top: -100px; z-index: 200;
  background: var(--brand); color: #fff;
  padding: 12px 18px; border-radius: var(--radius-sm);
  font-weight: 600; font-size: .95rem; text-decoration: none;
  transition: top .2s var(--ease);
}
.skip-link:focus-visible { top: 12px; outline: 3px solid #fff; outline-offset: 2px; }

/* ── Responsive ─────────────────────────────────────────────────────────── */
/* ── On your phone ──────────────────────────────────────────────────────── */
/* Namespaced pk-* rather than reusing the hero's .phone: that one is tilted,
   carries floating cards and is sized against the hero grid. Sharing the class
   would tie two mockups with different jobs to one set of rules. */
.pocket-grid { display: grid; grid-template-columns: minmax(0, 1.04fr) minmax(0, .96fr); gap: 54px; align-items: center; }
/* A notch smaller than the site's shared .lead. This column runs long — a
   paragraph plus three bullets plus a footnote — and at the full lead size it
   set the height of the whole section. Scoped rather than changing .lead,
   which the languages and dashboard sections also use at their own lengths. */
.pocket .lead { font-size: clamp(.95rem, 1.3vw, 1.06rem); }
.pocket-list { list-style: none; margin: 22px 0 0; padding: 0; display: grid; gap: 13px; }
.pocket-list li { position: relative; padding-left: 24px; color: var(--muted); line-height: 1.58; font-size: .89rem; }
.pocket-list li::before { content: ''; position: absolute; left: 0; top: .55em; width: 7px; height: 7px; border-radius: 50%; background: var(--brand); }
.pocket-list b { color: var(--ink); font-weight: 650; }
.pocket-foot { margin-top: 22px; padding-top: 15px; border-top: 1px solid var(--line-soft); font-size: .77rem; color: var(--muted); display: flex; align-items: center; gap: 8px; }
.pocket-foot svg { width: 15px; height: 15px; color: var(--brand); flex: none; }

/* A soft brand wash behind the device. Without it the phone is a narrow object
   marooned in a wide column, which is what makes a mockup look pasted on. */
.pocket-stage { position: relative; display: grid; place-items: center; }
.pocket-stage::before {
  content: ''; position: absolute; width: 82%; aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle, rgba(79, 70, 229, .14), rgba(79, 70, 229, 0) 68%);
}
.pk-phone {
  position: relative; width: min(268px, 100%); aspect-ratio: 9 / 18.6;
  padding: 9px; border-radius: 42px;
  background: linear-gradient(155deg, #303a5c, #0e1320);
  box-shadow: 0 36px 72px -30px rgba(15, 20, 34, .6), inset 0 1px 0 rgba(255, 255, 255, .22);
}
.pk-island { position: absolute; z-index: 4; top: 21px; left: 50%; transform: translateX(-50%); width: 64px; height: 18px; border-radius: 999px; background: #0b0f1c; }
/* The app's own canvas colour, so the white modules read as cards sitting on a
   page rather than as panels floating on nothing. */
.pk-screen { position: relative; height: 100%; border-radius: 34px; overflow: hidden; background: var(--fill); }

/* ── Splash ──
   The page's own boot splash, reused rather than reinvented: same .rp-logo
   lockup as #rp-splash, same classes, so the phone shows what the app
   actually shows. Those sub-classes are global, not scoped to the id, which
   is the only reason this works — scale is the one thing overridden, since
   the real one is sized for a full screen.

   Hidden at rest and only animated once the stage scrolls into view. With
   scripting off .in never lands and the phone simply shows the dashboard,
   rather than a logo sitting there forever. */
.pk-splash {
  position: absolute; inset: 0; z-index: 3; opacity: 0; visibility: hidden;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 50% 40%, #fff 0%, #f4f5fa 78%, #eceef6 100%);
}
.pk-splash .rp-logo { transform: scale(.62); }

/* The lockup's own rules, ported from index.html's inline <style>.
   They have to live here because gen_home.py strips the boot splash — and its
   stylesheet with it — out of home.html, so on /home the markup arrived with
   no rules at all and rendered as the words "RatıREchongEcho" in a row.
   Scoped under .pk-splash rather than copied loose, so the real #rp-splash
   keeps using its own inline copy and nothing here can interfere with the
   boot sequence. */
.pk-splash .rp-logo { display: flex; flex-direction: column; align-items: center; padding-top: 128px; font-family: 'Poppins', -apple-system, 'Segoe UI', Roboto, sans-serif; }
.pk-splash .rp-word { position: relative; font-size: 40px; line-height: 1; color: #1e1e2e; letter-spacing: -1px; white-space: nowrap; }
.pk-splash .rp-word .bold { font-weight: 700; }
.pk-splash .rp-word .med { font-weight: 500; }
.pk-splash .i-slot { position: relative; display: inline-block; }
.pk-splash .rp-badge { position: absolute; left: 50%; bottom: 100%; transform: translateX(-50%); margin-bottom: 18px; width: 104px; height: 104px; }
.pk-splash .rp-bubble { position: absolute; inset: 0; border-radius: 50%; background: #4f46e5; box-shadow: 0 14px 30px rgba(79, 70, 229, .30); }
/* Same inline-SVG lettering as the boot splash — see the note in
   web/index.html. Centring two spans in a flexbox is what let this one drift. */
.pk-splash .rp-letters { position: absolute; inset: 0; width: 100%; height: 100%; font-family: 'Poppins', -apple-system, 'Segoe UI', Roboto, sans-serif; font-weight: 700; }
.pk-splash .rp-tail { position: absolute; left: 50%; bottom: -13px; transform: translateX(-50%); width: 0; height: 0; border-left: 13px solid transparent; border-right: 13px solid transparent; border-top: 20px solid #4f46e5; }
.pk-splash .rp-echo { position: absolute; inset: 0; border-radius: 50%; border: 3px solid #4f46e5; opacity: 0; animation: rp-echo 2s ease-out infinite; }
.pk-splash .rp-echo:nth-child(1) { animation-delay: 1s; }
.pk-splash .rp-echo:nth-child(2) { animation-delay: 1.66s; }
.pk-splash .rp-echo:nth-child(3) { animation-delay: 2.33s; }
.pk-splash .rp-tag { margin-top: 16px; font-size: 15px; font-weight: 500; color: #6b6b7b; }
@keyframes rp-echo { 0% { opacity: .5; transform: scale(1); } 70% { opacity: 0; } 100% { opacity: 0; transform: scale(2.3); } }

/* ── The loop ──
   Every element runs the same 9s period so nothing can drift out of step; the
   phase differences live in the keyframe percentages, not in the durations.
   The stagger is the one exception and uses a delay of a few hundredths, small
   enough that the offset never accumulates into a visible desync.

   Timeline: splash 0-33%, hand-off 33-40%, dashboard 40-93%, reset 93-100%. */
.pocket-stage.in .pk-splash { animation: pk-splash 9s linear infinite; }
@keyframes pk-splash {
  0%   { opacity: 0; visibility: visible; }
  4%   { opacity: 1; }
  33%  { opacity: 1; }
  40%  { opacity: 0; visibility: hidden; }
  96%  { opacity: 0; visibility: hidden; }
  100% { opacity: 0; visibility: visible; }
}

/* rp-pop and rp-rise are one-shot in the real splash, which is right there —
   it plays once and hands over to Flutter. Here it has to come back every
   lap, so they are re-timed onto the shared period. translateX(-50%) is
   carried through every frame because the badge's own centring lives in the
   same property. */
.pocket-stage.in .pk-splash .rp-bubble { animation: pk-pop 9s cubic-bezier(.34, 1.56, .64, 1) infinite; }
@keyframes pk-pop {
  0%   { opacity: 0; transform: translateY(-14px) scale(.5); }
  8%   { opacity: 1; transform: none; }
  33%  { opacity: 1; transform: none; }
  40%  { opacity: 0; transform: none; }
  100% { opacity: 0; transform: translateY(-14px) scale(.5); }
}
.pocket-stage.in .pk-splash .rp-word { animation: pk-rise 9s ease-out infinite; }
.pocket-stage.in .pk-splash .rp-tag { animation: pk-rise-late 9s ease-out infinite; }
@keyframes pk-rise {
  0%   { opacity: 0; transform: translateY(12px); }
  10%  { opacity: 1; transform: none; }
  33%  { opacity: 1; transform: none; }
  40%  { opacity: 0; transform: none; }
  100% { opacity: 0; transform: translateY(12px); }
}
@keyframes pk-rise-late {
  0%   { opacity: 0; transform: translateY(12px); }
  8%   { opacity: 0; transform: translateY(12px); }
  16%  { opacity: 1; transform: none; }
  33%  { opacity: 1; transform: none; }
  40%  { opacity: 0; transform: none; }
  100% { opacity: 0; transform: translateY(12px); }
}

/* ── App ── */
.pk-app { height: 100%; padding: 40px 9px 9px; display: flex; flex-direction: column; gap: 7px; }
.pocket-stage.in .pk-app > * { animation: pk-mod 9s var(--ease) infinite; animation-delay: calc(var(--i) * .07s); }
@keyframes pk-mod {
  0%   { opacity: 0; transform: translateY(11px); }
  38%  { opacity: 0; transform: translateY(11px); }
  47%  { opacity: 1; transform: none; }
  93%  { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(6px); }
}

.pk-bar { display: flex; align-items: center; justify-content: space-between; padding: 0 3px 2px; }
.pk-bar b { font-size: .62rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.pk-bar s { width: 18px; height: 18px; border-radius: 50%; background: linear-gradient(140deg, var(--brand), #7c3aed); }

.pk-head { background: linear-gradient(140deg, #4f46e5, #4338ca); border-radius: 14px; padding: 9px 11px; color: #fff; }
.pk-greet { display: block; font-size: .55rem; font-weight: 600; color: rgba(255, 255, 255, .8); }
.pk-head b { display: block; margin: 1px 0 5px; font-size: .88rem; font-weight: 700; letter-spacing: -.02em; }
.pk-tag { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: .53rem; font-weight: 600; color: #fff; background: rgba(255, 255, 255, .18); border: 1px solid rgba(255, 255, 255, .3); }

.pk-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.pk-stats div { background: #fff; border: 1px solid var(--line); border-radius: 11px; padding: 6px 8px; }
.pk-stats i { display: block; font-style: normal; font-size: .78rem; font-weight: 700; color: var(--ink); letter-spacing: -.02em; }
.pk-stats i.good { color: #059669; }
.pk-stats span { display: block; margin-top: 1px; font-size: .53rem; color: var(--muted); }

.pk-mod { background: #fff; border: 1px solid var(--line); border-radius: 13px; padding: 8px 10px; }
.pk-mh { display: flex; align-items: center; justify-content: space-between; margin-bottom: 7px; font-size: .55rem; color: var(--muted); font-weight: 600; }
.pk-mh b { font-size: .58rem; color: var(--ink); }
.pk-mh b.good { color: #059669; }

.pk-graph { display: block; width: 100%; height: auto; }
.pk-graph .b { fill: #c7d2fe; }
.pk-graph .b.on { fill: var(--brand); }
/* fill-box so the origin is the bar's own foot rather than the SVG's. */
.pocket-stage.in .pk-graph .b { transform-box: fill-box; transform-origin: bottom; animation: pk-bar 9s var(--ease) infinite; animation-delay: calc(var(--d)); }
@keyframes pk-bar {
  0%   { transform: scaleY(0); }
  46%  { transform: scaleY(0); }
  57%  { transform: scaleY(1); }
  93%  { transform: scaleY(1); }
  100% { transform: scaleY(0); }
}
.pk-days { display: grid; grid-template-columns: repeat(7, 1fr); margin-top: 3px; }
.pk-days i { font-style: normal; text-align: center; font-size: .48rem; color: var(--muted); }

.pk-row { display: flex; align-items: center; justify-content: space-between; gap: 9px; padding: 5px 0; font-size: .6rem; }
.pk-row + .pk-row { border-top: 1px solid var(--line-soft); }
.pk-row span { display: flex; align-items: center; gap: 6px; color: var(--muted); min-width: 0; }
.pk-row b { color: var(--ink); font-weight: 650; flex: none; }
.pk-row s { width: 6px; height: 6px; border-radius: 50%; background: var(--line); flex: none; }
.pk-row s.on { background: #059669; }
.pk-row s.warn { background: #d97706; }
.pk-row s.star { background: #f59e0b; }

@media (max-width: 980px) {
  .nav-links, .nav-cta { display: none; }
  .nav-burger { display: grid; }
  .hero { padding: 122px 0 84px; }
  /* The mock stays BESIDE the copy at every width rather than dropping under
     it. Stacking pushed the phone below the fold on a handset, where the one
     thing it exists to show — what a diner actually sees — never got seen.
     It gives up width instead, all the way down; the ≤560 block below scales
     the screen's contents to match so it reads as a small phone rather than a
     clipped big one. */
  .hero-grid-2col { grid-template-columns: minmax(0, 1fr) minmax(0, 44%); gap: 26px; }
  .mock { max-width: none; margin: 0; }
  .phone { width: min(268px, 100%); }
  .float-card { width: 152px; }
  /* Same two empty bands as on desktop — see the note by the base rules.
     fc-2 hangs out by only 2%: the mock column now ends a hair inside the
     viewport edge, and at -8% the card's last word fell off the page. */
  .fc-1 { top: 0; left: -6%; }
  .fc-2 { top: 93%; right: -2%; }
  /* Pushed below the frame rather than sitting on it: the phone is 268px here
     instead of 310, and at bottom:0 the card reached up into the Food/Service
     rows — the exact overlap the base rules exist to prevent. */
  .fc-3 { display: block; bottom: -5%; left: -6%; width: 128px; }
  .lang-grid { grid-template-columns: 1fr; gap: 36px; }
  /* Stacks, unlike the hero mock. That one has to stay beside the copy because
     it is the proof above the fold; this one is two thirds down the page and a
     reader who has got this far will scroll. Centred so it does not sit off to
     one side under a full-width paragraph. */
  .pocket-grid { grid-template-columns: 1fr; gap: 36px; }
  .pocket-stage { order: 2; }
  .bento { grid-template-columns: repeat(2, 1fr); }
  .c-4, .c-3, .c-2 { grid-column: span 1; }
  /* Three cards in a 2-up grid is a pair and an orphan, and the orphan leaves
     a card-sized hole of white beside it — the section reads as though a
     fourth card failed to load. The features bento above has seven and wraps
     without anyone noticing; three does not have that luxury, so it goes one
     per row until there is width for all three. */
  #data .bento { grid-template-columns: 1fr; }
  .plans, .steps { grid-template-columns: 1fr; }
  /* One column: the connecting rule would run across a single marker and stop. */
  .steps::before { display: none; }
  .steps { gap: 34px; }
  .stats { grid-template-columns: repeat(2, 1fr); }
  /* 1.6fr + three 1fr columns does not survive being halved. Two columns put
     the brand blurb beside Product and pushed Company and Legal into a second
     row, and because the two rows are set by different content — six lines of
     prose against a five-link list — nothing lined up down either edge. The
     blurb is prose and the other three are lists: the prose takes a row of its
     own and the three lists share the row below, which is the same split the
     handset block already used for the brand, one breakpoint earlier. */
  .foot-grid { grid-template-columns: repeat(3, 1fr); gap: 26px 24px; }
  .foot-brand { grid-column: 1 / -1; }
  .foot-brand p { max-width: 40rem; }
  .section { padding: 78px 0; }
}
/* The narrow end of the two-column band. The mock is a 44% track of a ~600px
   page here — about 225px — while fc-2 and fc-3 are still sized for the wide
   end of the band, so the two of them crossed over each other by ~36px at the
   bottom of the phone. Same answer as on a handset: the third card goes up the
   left side instead of sitting beside the second. */
@media (min-width: 561px) and (max-width: 670px) {
  .fc-3 { top: 64%; bottom: auto; left: -6%; }
}
@media (max-width: 560px) {
  .wrap { padding: 0 18px; }
  .bento { grid-template-columns: 1fr; }
  .card, .plan { padding: 22px; }

  /* Type steps down rather than staying desktop-sized in a 360px column. */
  .h-display { font-size: clamp(1.72rem, 8.2vw, 2.15rem); letter-spacing: -.028em; }
  .h-section { font-size: clamp(1.34rem, 5.6vw, 1.6rem); }
  .hero-sub   { font-size: .92rem; }
  .lead       { font-size: .9rem; }
  .h-card     { font-size: .99rem; }
  .h1-lead    { font-size: .9rem; margin-bottom: 12px; }
  .h1-display { font-size: clamp(1.72rem, 8.2vw, 2.15rem); letter-spacing: -.028em; }
  .policy-links { font-size: .8rem; }
  .purpose    { font-size: .9rem; margin: -10px 0 22px; }
  .card p, .plan ul li, .step p { font-size: .88rem; }
  .eyebrow    { font-size: .72rem; }
  .qa button  { font-size: .95rem; padding: 18px 0; }
  .qa .a p    { font-size: .89rem; }
  .stat-n     { font-size: 1.75rem; }
  .plan-price .amt { font-size: 2rem; }
  .marquee-item { font-size: .85rem; }
  .section-head { margin-bottom: 38px; }
  .section { padding: 62px 0; }
  .hero { padding: 104px 0 72px; }

  /* Buttons: 16/32 padding at 1.02rem is a desktop button on a phone. */
  .btn { padding: 12px 20px; font-size: .93rem; }
  .btn-lg { padding: 13px 22px; font-size: .95rem; }
  .hero-actions { gap: 10px; }
  .hero-actions .btn { flex: 1 1 auto; }

  /* Phones stack; tablets don't. The 561–980 block above puts the mock beside
     the copy, which works while there's width for it — but on a handset the
     mock can only fit there by shrinking to ~150px, and a phone screenshot
     that small stops being readable. So below 560 the hero goes back to one
     column with the mock at full size underneath.
     The three resets are load-bearing: grid-template-columns, the gap, and
     .mock's margin/max-width all cascade down from that block, and without
     undoing them here the layout stays side-by-side. */
  .hero-grid-2col { grid-template-columns: 1fr; gap: 60px; }
  .mock { max-width: 420px; margin: 0 auto; }

  /* Keep the floating cards — smaller, pulled inside the frame so nothing
     is clipped, and still bobbing. They are the hero's animation. */
  .float-card { width: 152px; padding: 9px 11px; border-radius: 13px; }
  .float-card .fc-text { font-size: .72rem; line-height: 1.4; }
  .float-card .fc-label { font-size: .58rem; }
  .fc-1 { top: -1%; left: 0%; }
  .fc-2 { top: 94%; right: 0%; width: min(152px, 48%); }
  /* All three cards, on a phone too. The third one was display:none here, so
     the handset got a different hero from the one everyone else sees.
     It goes mid-left rather than bottom-left as on desktop: at this size the
     mock is ~300px against a ~200px phone, so any card overlaps the screen
     somewhere, and bottom-left would have put it beside fc-2 with the two of
     them burying the Submit button between them. The Food row is the cheapest
     thing on that screen to cover. Widths are capped as a share of the mock so
     the pair at the bottom never collide on a 320px screen. */
  .fc-3 { display: block; top: 64%; bottom: auto; left: 0; width: min(134px, 43%); }
  /* Clear breathing room between the paragraph and the buttons — margins
     collapse between these blocks, so this is what actually sets the gap. */
  .mode-site .hero-sub { margin-bottom: 44px; }
  .hero-note { margin-top: 22px; }
  .phone { width: min(238px, 68%); padding: 8px; border-radius: 30px; }
  .phone-screen { border-radius: 22px; }
  .phone-body { padding: 4px 14px 16px; }
  .stars svg { width: 22px; height: 22px; }
}

/* Respect the OS setting — every effect above degrades to a static page. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    /* Delay too, not just duration. The closed mobile menu defers its
       visibility change until the fade ends, and with the fade reduced to
       nothing that delay would leave seven invisible links focusable for
       300ms after every close. */
    transition-delay: 0s !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ── Dashboard showcase ─────────────────────────────────────────────────────
   The owner's half of the product. The hero shows the diner's phone; this
   shows what the phone produces.

   A dark section, so the headings need explicit light colours — the site's
   defaults are ink-on-white and simply vanish here, which is exactly what
   happened the first time this shipped. */
.dash-sec { background: var(--ink); border-top: 1px solid rgba(255, 255, 255, .08); overflow: hidden; }
.dash-sec .h-section { color: #fff; }
.dash-sec .lede { color: rgba(255, 255, 255, .66); max-width: 40rem; }
.dash-sec .eyebrow { color: #a5b4fc; }

/* One drawing, one set of proportions.
   The dashboard is a product shot, not a component someone uses — so it is
   authored once at 880px and every length inside it is written as a multiple
   of --u, one pixel of that drawing. Narrow the viewport and the container
   narrows, --u narrows with it, and a phone gets the SAME picture at a smaller
   size: sidebar still on the left, four stats still across, three rail cards
   still in a row. The alternative — reflowing into a stack — hands the phone a
   different product than the one the copy beside it describes.

   Container units rather than transform: scale, so the box still takes its
   real height in flow (no reserved-height arithmetic) and the text is still
   text: selectable, and rendered at its own size rather than resampled. */
/* The two cards hang 30 units past the frame's edges, and 30 units is 3.4% of
   whatever width the stage has. The wrap's 24px gutter covers that on a phone
   but not around 930px, where the stage is near its full 880 and the gutter is
   still 24 — .dash-sec's overflow:hidden would shave the card. Holding the
   stage 9px inside the gutter keeps the hang in view at every width, and the
   min() leaves the 880px desktop case untouched. */
.dash { position: relative; max-width: min(880px, 100% - 18px); margin: 44px auto 0; container-type: inline-size; }
/* --u has to live on the children. Container query units used inside the query
   container's own declarations resolve against its ANCESTOR container, not
   against itself — set it on .dash and every length here would size off the
   viewport instead. */
.dash > * { --u: calc(100cqw / 880); }
.dash-frame {
  position: relative; z-index: 2; display: grid; grid-template-columns: calc(172 * var(--u)) 1fr;
  background: #fff; border-radius: calc(18 * var(--u)); overflow: hidden;
  box-shadow: 0 calc(50 * var(--u)) calc(90 * var(--u)) calc(-30 * var(--u)) rgba(0, 0, 0, .7),
              0 0 0 1px rgba(255, 255, 255, .08);
}

.dash-side { background: #fafafb; border-right: 1px solid var(--line-soft); padding: calc(16 * var(--u)) calc(12 * var(--u)); }
.ds-brand { display: flex; align-items: center; gap: calc(8 * var(--u)); margin-bottom: calc(16 * var(--u)); padding: 0 calc(6 * var(--u)); }
.ds-brand s { width: calc(20 * var(--u)); height: calc(20 * var(--u)); border-radius: calc(6 * var(--u)); background: var(--brand); flex: none; }
.ds-brand span { font-size: calc(12.5 * var(--u)); font-weight: 700; color: var(--ink); }
.dash-side b {
  display: flex; align-items: center; gap: calc(9 * var(--u)); padding: calc(7 * var(--u)) calc(8 * var(--u)); border-radius: calc(8 * var(--u));
  font-size: calc(11.8 * var(--u)); font-weight: 500; color: var(--muted);
}
.dash-side b s { width: calc(13 * var(--u)); height: calc(13 * var(--u)); border-radius: calc(4 * var(--u)); background: #e5e7eb; flex: none; }
.dash-side b.on { background: rgba(79, 70, 229, .09); color: var(--brand); font-weight: 700; }
.dash-side b.on s { background: var(--brand); }

.dash-main { padding: calc(18 * var(--u)) calc(20 * var(--u)) calc(20 * var(--u)); min-width: 0; }
.dash-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: calc(12 * var(--u)); margin-bottom: calc(16 * var(--u)); }
.dash-stat .k { font-size: calc(9.3 * var(--u)); text-transform: uppercase; letter-spacing: .08em; color: var(--muted); font-weight: 700; }
.dash-stat .v { font-size: calc(18.6 * var(--u)); font-weight: 800; color: var(--ink); line-height: 1.25; margin-top: calc(2 * var(--u)); white-space: nowrap; }
.dash-stat .v.brand { color: var(--brand); }
/* Text, not a glyph — so the accessible amber. See the note on --gold-text. */
.dash-stat .v.gold { color: var(--gold-text); }

.dash-panel { border: 1px solid var(--line-soft); border-radius: calc(12 * var(--u)); padding: calc(12 * var(--u)) calc(12 * var(--u)) calc(6 * var(--u)); }
.dp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: calc(9 * var(--u)); }
.dp-head > span:first-child { font-size: calc(11.8 * var(--u)); font-weight: 700; color: var(--ink); }
.dp-range { display: flex; gap: calc(3 * var(--u)); }
.dp-range i {
  font-style: normal; font-size: calc(9.6 * var(--u)); font-weight: 600; color: var(--muted);
  padding: calc(3 * var(--u)) calc(7 * var(--u)); border-radius: calc(6 * var(--u)); border: 1px solid var(--line);
}
.dp-range i.on { color: var(--brand); border-color: var(--brand); background: rgba(79, 70, 229, .08); }

.dash-legend { display: flex; flex-wrap: wrap; gap: calc(12 * var(--u)); margin-bottom: calc(6 * var(--u)); }
.dash-legend b { display: flex; align-items: center; gap: calc(5 * var(--u)); font-size: calc(10.2 * var(--u)); font-weight: 600; color: var(--muted); }
.dash-legend b s { width: calc(8 * var(--u)); height: calc(8 * var(--u)); border-radius: calc(3 * var(--u)); }

/* The chart is a composition, not a line: stacked bars carrying the rating
   mix, with invites drawn over them. Both are counts, so they share one axis —
   a second scale is how two unrelated series get made to look related, and it
   is the single most common way a chart lies.

   The bands are a DIVERGING encoding, because they have polarity: two hues
   either side of a neutral middle. Five and four stars in indigo, three in
   grey, two and one in amber. Never a rainbow, and never a hue at the
   midpoint. */
.dash-chart { width: 100%; height: calc(210 * var(--u)); display: block; overflow: visible; }
.dash-chart .grid { stroke: #eef0f4; stroke-width: 1; }
.dash-chart .ax { font-size: 8px; fill: #9ca3af; text-anchor: end; font-weight: 600; }
.dash-chart .xl { text-anchor: middle; }

.dash-chart .bar { transform-box: fill-box; transform-origin: bottom; animation: bar-rise .7s var(--ease) forwards; transform: scaleY(0); }
@keyframes bar-rise { to { transform: scaleY(1); } }
.dash-chart .bar { animation-delay: var(--d); }
/* A 1px surface gap between stacked segments, so the bands stay countable
   where they meet instead of fusing into one column. */
.dash-chart .bar { stroke: #fff; stroke-width: 1; }
.dash-chart .s5 { fill: #3730a3; }
.dash-chart .s4 { fill: #818cf8; }
.dash-chart .s3 { fill: #9ca3af; }
.dash-chart .s2 { fill: #fcd34d; }
.dash-chart .s1 { fill: #d97706; }

.dash-chart .ivln {
  fill: none; stroke: #0891b2; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 900; stroke-dashoffset: 900; animation: dash-draw 1.5s var(--ease) .5s forwards;
}
.dash-chart .ivdot { fill: #0891b2; stroke: #fff; stroke-width: 1.6; opacity: 0; animation: dash-pop .5s var(--ease) 1.5s forwards; }
@keyframes dash-draw { to { stroke-dashoffset: 0; } }
@keyframes dash-pop { to { opacity: 1; } }

.dash-legend b s.k5 { background: #3730a3; }
.dash-legend b s.k4 { background: #818cf8; }
.dash-legend b s.k3 { background: #9ca3af; }
.dash-legend b s.k2 { background: #fcd34d; }
.dash-legend b s.k1 { background: #d97706; }
/* The line's swatch is a line, not a block — the mark in the legend matches
   the mark on the plot, which is how a reader knows which is which. */
.dash-legend b.lg-line s.kl { width: calc(14 * var(--u)); height: calc(2.5 * var(--u)); border-radius: calc(2 * var(--u)); background: #0891b2; }

/* Side rail: the numbers that do not belong on a time axis. */
.dash-rail { display: grid; grid-template-columns: repeat(3, 1fr); gap: calc(10 * var(--u)); margin-top: calc(12 * var(--u)); }
.dr-card { border: 1px solid var(--line-soft); border-radius: calc(10 * var(--u)); padding: calc(9 * var(--u)) calc(10 * var(--u)); }
.dr-k { font-size: calc(9.3 * var(--u)); text-transform: uppercase; letter-spacing: .07em; color: var(--muted); font-weight: 700; }
.dr-v { font-size: calc(18.4 * var(--u)); font-weight: 800; color: var(--ink); margin-top: calc(2 * var(--u)); }
.dr-v i { font-style: normal; font-size: calc(12.5 * var(--u)); color: var(--muted); margin-left: calc(1 * var(--u)); }
.spark { width: 100%; height: calc(22 * var(--u)); margin-top: calc(4 * var(--u)); display: block; }
.spark path { fill: none; stroke: #4f46e5; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 200; stroke-dashoffset: 200; animation: dash-draw 1.2s var(--ease) .9s forwards; }

.dz-donut { width: 100%; height: calc(74 * var(--u)); display: block; margin-top: calc(2 * var(--u)); }
.dz-donut .seg { stroke-linecap: butt; opacity: 0; animation: dash-pop .5s var(--ease) forwards; }
.dz-donut .s5 { stroke: #3730a3; animation-delay: .7s; }
.dz-donut .s4 { stroke: #818cf8; animation-delay: .8s; }
.dz-donut .s3 { stroke: #9ca3af; animation-delay: .9s; }
.dz-donut .s2 { stroke: #fcd34d; animation-delay: 1s; }
.dz-donut .s1 { stroke: #d97706; animation-delay: 1.1s; }
.dz-donut .dn-v { font-size: 17px; font-weight: 800; fill: var(--ink); text-anchor: middle; }
.dz-donut .dn-k { font-size: 8px; fill: #9ca3af; text-anchor: middle; font-weight: 600; }

/* The pull-out. Overlaps the dashboard's lower-right corner, tilted slightly
   so it reads as lifted off the surface rather than pasted onto it. */
.dash-zoom {
  /* Hangs off the frame's bottom-right corner. Pulled further down and made
     narrower than it was: at 292px / -46px it covered the reply-rate tile in
     the rail behind it instead of overlapping the corner. */
  position: absolute; z-index: 4; right: calc(-30 * var(--u)); bottom: calc(-72 * var(--u)); width: calc(268 * var(--u));
  background: #fff; border-radius: calc(16 * var(--u)); padding: calc(13 * var(--u)) calc(14 * var(--u)) calc(11 * var(--u));
  box-shadow: 0 calc(34 * var(--u)) calc(70 * var(--u)) calc(-20 * var(--u)) rgba(0, 0, 0, .6);
  border: 1px solid rgba(255, 255, 255, .7);
  transform: rotate(-2.2deg);
  animation: bob 8s ease-in-out infinite;
}
.dz-head { display: flex; align-items: center; gap: calc(7 * var(--u)); margin-bottom: calc(8 * var(--u)); }
.dz-head .dot { width: calc(7 * var(--u)); height: calc(7 * var(--u)); border-radius: 50%; background: #34d399; flex: none; }
.dz-head span:last-child { font-size: calc(10.2 * var(--u)); font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
.dz-chart { width: 100%; height: calc(96 * var(--u)); display: block; }
.dz-ln { fill: none; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 420; stroke-dashoffset: 420; animation: dash-draw 1.4s var(--ease) .5s forwards; }
.dz-ln.dz-b { stroke-width: 2; animation-delay: .72s; }
.dz-area { opacity: 0; animation: dash-pop .8s var(--ease) 1.1s forwards; }
@keyframes dash-pop { to { opacity: 1; } }
.dz-live { animation: dz-pulse 2.4s ease-in-out infinite 1.4s; transform-origin: center; }
@keyframes dz-pulse { 0%, 100% { r: 5; opacity: 1; } 50% { r: 8; opacity: .4; } }
.dz-foot { display: flex; align-items: baseline; gap: calc(6 * var(--u)); margin-top: calc(6 * var(--u)); }
.dz-foot b { font-size: calc(16.8 * var(--u)); font-weight: 800; color: #059669; }
.dz-foot span { font-size: calc(10.9 * var(--u)); color: var(--muted); }

/* The float card is shared with the hero, where it stays a real responsive
   element and shrinks at 980 and 560. Inside the dashboard it is part of the
   drawing instead, so every one of those lengths gets re-stated in --u — the
   higher specificity here is what keeps the hero's breakpoints from reaching
   in and resizing one card inside an otherwise uniformly scaled picture. */
.dash .dc-1 {
  top: calc(-30 * var(--u)); left: calc(-30 * var(--u)); width: calc(210 * var(--u));
  padding: calc(12 * var(--u)) calc(14 * var(--u)); border-radius: calc(15 * var(--u));
  box-shadow: 0 calc(28 * var(--u)) calc(56 * var(--u)) calc(-20 * var(--u)) rgba(0, 0, 0, .62),
              0 0 0 1px rgba(15, 23, 42, .06);
}
.dash .dc-1 .fc-top { gap: calc(7 * var(--u)); margin-bottom: calc(6 * var(--u)); }
.dash .dc-1 .dot { width: calc(7 * var(--u)); height: calc(7 * var(--u)); }
.dash .dc-1 .fc-label { font-size: calc(10.6 * var(--u)); }
.dash .dc-1 .fc-text { font-size: calc(12.8 * var(--u)); line-height: 1.45; }

@media (prefers-reduced-motion: reduce) {
  .dash-chart .ivln, .dz-ln, .spark path { animation: none; stroke-dashoffset: 0; }
  /* Each lap of the phone's loop ends faded out so the next can begin. The
     rule above collapses every animation to one 0.001ms run, which lands on
     exactly that end state — an empty phone. Show the dashboard outright and
     drop the splash instead. */
  .pk-splash { display: none; }
  .pk-app > *, .pk-graph .b {
    animation: none !important; opacity: 1 !important; transform: none !important;
  }
  .dash-chart .bar { animation: none; transform: scaleY(1); }
  .dash-chart .ivdot, .dz-donut .seg { animation: none; opacity: 1; }
  .dz-area { animation: none; opacity: 1; }
  .dz-live, .dash-zoom { animation: none; }
}

/* Only the space around the mock changes with the viewport now. The mock's own
   proportions do not: no column count, padding or type size is restated below,
   because the whole point is that a phone gets the desktop picture. The two
   layout reflows that used to live here — sidebar to a top strip at 900, stats
   to 2-up and the rail to 1-up at 620 — are gone deliberately. */
@media (max-width: 900px) {
  .dash { margin-top: 56px; }
}
@media (max-width: 620px) {
  .dash { margin-top: 30px; }
}

/* ==========================================================================
   Book a demo
   --------------------------------------------------------------------------
   The dialog behind the hero's primary CTA. Dark like the hero it opens from,
   so the transition reads as the same surface lifting rather than a white box
   dropped over it.

   Everything here is scoped under .demo-* and nothing is scoped under
   .mode-site, because this markup also lives in index.html — which is the
   Flutter shell and carries no .mode-site class until the marketing DOM is
   the thing on screen.
   ========================================================================== */
.demo-overlay {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(6, 8, 20, .72);
  backdrop-filter: blur(6px);
  opacity: 0; transition: opacity .28s var(--ease);
  overflow-y: auto;
}
.demo-overlay[hidden] { display: none; }
.demo-overlay.open { opacity: 1; }

.demo-panel {
  position: relative;
  display: grid; grid-template-columns: 300px 1fr;
  width: min(980px, 100%); max-height: min(92vh, 760px);
  border-radius: var(--radius-lg);
  background: #0f1424;
  border: 1px solid rgba(255, 255, 255, .1);
  box-shadow: 0 50px 120px -30px rgba(0, 0, 0, .9);
  color: rgba(255, 255, 255, .88);
  overflow: hidden;
  transform: translateY(14px) scale(.985);
  transition: transform .34s var(--ease);
}
.demo-overlay.open .demo-panel { transform: none; }

.demo-close {
  position: absolute; top: 12px; right: 12px; z-index: 2;
  width: 36px; height: 36px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; cursor: pointer;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .14);
  color: rgba(255, 255, 255, .8);
  transition: background .2s var(--ease), color .2s var(--ease);
}
.demo-close:hover { background: rgba(255, 255, 255, .16); color: #fff; }
.demo-close svg { width: 17px; height: 17px; }

/* ── Left column ─────────────────────────────────────────────────────────── */
.demo-aside {
  padding: 34px 28px;
  background:
    radial-gradient(120% 80% at 0% 0%, rgba(79, 70, 229, .38), transparent 60%),
    linear-gradient(180deg, #151a30, #0d1120);
  border-right: 1px solid rgba(255, 255, 255, .08);
}
.demo-aside .pill { margin-bottom: 18px; }
.demo-aside h2 {
  margin: 0 0 12px; font-size: 1.7rem; line-height: 1.15;
  font-weight: 700; color: #fff; letter-spacing: -.02em;
}
.demo-aside p { margin: 0; font-size: .92rem; line-height: 1.6; color: rgba(255, 255, 255, .62); }
.demo-points { list-style: none; margin: 20px 0 0; padding: 0; }
.demo-points li {
  display: flex; align-items: flex-start; gap: 9px;
  margin-bottom: 10px; font-size: .88rem; color: rgba(255, 255, 255, .78);
}
.demo-points svg { width: 15px; height: 15px; flex: none; margin-top: 4px; color: #34d399; }
/* Scoped through .demo-panel throughout this block wherever the element is a
   <p>. The site sets `.mode-site p { margin: 0 }`, which at (0,1,1) quietly
   outranks any single-class rule of mine — so every margin below read as zero
   on home.html while working on index.html, which carries no .mode-site class.
   The extra class is what buys the rules back, and it beats reaching for
   !important, which this rule used to do. */
.demo-panel .demo-aside-foot { margin-top: 22px; font-size: .82rem; }
.demo-aside-foot a { color: #a5b4fc; text-decoration: none; }
.demo-aside-foot a:hover { text-decoration: underline; }

/* ── Right column ────────────────────────────────────────────────────────── */
.demo-form {
  display: grid; grid-template-columns: 1fr 1fr; gap: 0;
  overflow-y: auto;
  /* A grid item's min-height is auto, which means "as tall as my content" and
     makes overflow-y above do nothing — the item grows instead of scrolling and
     pushes the submit button out of the panel. Worst on a phone, where the
     panel is the full screen height and cannot grow to hide the mistake. */
  min-height: 0;
  /* iOS keeps the page behind a scrollable modal from scrolling with it. */
  -webkit-overflow-scrolling: touch;
}
.demo-cal { padding: 30px 22px 26px; border-right: 1px solid rgba(255, 255, 255, .08); }
.demo-fields { padding: 30px 26px 26px; }

.demo-cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.demo-cal-head strong { font-size: .95rem; font-weight: 600; color: #fff; }
.demo-cal-nav {
  width: 30px; height: 30px; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 8px; background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .12); color: rgba(255, 255, 255, .82);
  transition: background .2s var(--ease);
}
.demo-cal-nav:hover:not(:disabled) { background: rgba(255, 255, 255, .14); }
.demo-cal-nav:disabled { opacity: .3; cursor: default; }
.demo-cal-nav svg { width: 15px; height: 15px; }

.demo-dow, .demo-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.demo-dow {
  margin-bottom: 5px; font-size: .68rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  color: rgba(255, 255, 255, .38); text-align: center;
}
.demo-day {
  aspect-ratio: 1; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 9px; border: 1px solid transparent;
  background: rgba(255, 255, 255, .05);
  color: rgba(255, 255, 255, .85);
  font: inherit; font-size: .84rem; font-weight: 500;
  transition: background .18s var(--ease), transform .18s var(--ease);
}
.demo-day:hover:not(:disabled) { background: rgba(255, 255, 255, .14); transform: translateY(-1px); }
.demo-day:disabled { background: none; color: rgba(255, 255, 255, .2); cursor: default; }
/* A blank cell before the 1st. Not a button, so it is skipped by the keyboard
   and announced as nothing. */
.demo-day-pad { visibility: hidden; }
/* The :hover rule above is (0,3,0) and a bare `.demo-day.on` only (0,2,0), so
   the selected day lost its fill the moment the pointer was over it — which is
   most of the time, since the pointer is there because it just clicked. The
   hover form is repeated here to outrank it rather than to change anything. */
.demo-day.on,
.demo-day.on:hover:not(:disabled) {
  background: var(--brand); border-color: #a5b4fc; color: #fff; font-weight: 600;
  box-shadow: 0 8px 20px -8px rgba(79, 70, 229, .9);
}
/* A ring, not a fill — today should be findable without looking selected. */
.demo-day.today:not(.on) { border-color: rgba(255, 255, 255, .35); }

.demo-panel .demo-slot-label {
  margin: 20px 0 8px; font-size: .8rem; font-weight: 600;
  letter-spacing: .05em; text-transform: uppercase; color: rgba(255, 255, 255, .5);
}
.demo-slots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.demo-slot {
  padding: 8px 4px; cursor: pointer;
  border-radius: 9px; border: 1px solid rgba(255, 255, 255, .12);
  background: rgba(255, 255, 255, .05); color: rgba(255, 255, 255, .85);
  font: inherit; font-size: .78rem; font-weight: 500;
  transition: background .18s var(--ease);
}
.demo-slot:hover:not(:disabled) { background: rgba(255, 255, 255, .14); }
.demo-slot:disabled { opacity: .32; cursor: default; }
/* The "+1d" / "−1d" marker under a slot that lands on a different date in the
   visitor's timezone. Small and dim: it matters, but only to the minority far
   enough from IST for it to appear at all. */
.demo-slot i {
  display: block; font-style: normal;
  font-size: .62rem; font-weight: 500; letter-spacing: .04em; opacity: .62;
}
/* Same specificity fix as .demo-day.on above. */
.demo-slot.on,
.demo-slot.on:hover:not(:disabled) {
  background: var(--brand); border-color: #a5b4fc; color: #fff; font-weight: 600;
}

.demo-field { display: block; margin-bottom: 13px; }
.demo-field > span {
  display: block; margin-bottom: 5px;
  font-size: .78rem; font-weight: 600; color: rgba(255, 255, 255, .62);
}
.demo-field i { font-style: normal; color: #f87171; }
.demo-field i.opt { color: rgba(255, 255, 255, .35); font-weight: 500; }
.demo-field input, .demo-field textarea {
  width: 100%; padding: 10px 13px;
  border-radius: 10px; border: 1px solid rgba(255, 255, 255, .14);
  background: rgba(255, 255, 255, .06); color: #fff;
  font: inherit; font-size: .9rem; resize: vertical;
  transition: border-color .2s var(--ease), background .2s var(--ease);
}
.demo-field input:focus, .demo-field textarea:focus {
  outline: none; border-color: #a5b4fc; background: rgba(255, 255, 255, .1);
}
.demo-field input.bad, .demo-field textarea.bad { border-color: #f87171; }

/* Off-screen rather than display:none — some bots skip hidden inputs, and one
   that fills this is exactly the one worth catching. */
.demo-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.demo-panel .demo-picked {
  margin: 4px 0 22px; padding: 9px 12px; border-radius: 10px;
  background: rgba(255, 255, 255, .05); border: 1px dashed rgba(255, 255, 255, .16);
  font-size: .82rem; color: rgba(255, 255, 255, .6);
}
.demo-picked.set {
  border-style: solid; border-color: rgba(165, 180, 252, .5);
  background: rgba(79, 70, 229, .18); color: #fff; font-weight: 500;
}
.demo-panel .demo-error { margin: 0 0 10px; font-size: .82rem; color: #fca5a5; }
.demo-error[hidden] { display: none; }
/* Deliberately not full width and not .btn-lg. At that size it was the loudest
   thing in the panel, competing with the calendar for the eye when the calendar
   is the part that needs the attention. Centred, because the fine print under
   it is centred too. */
.demo-submit {
  display: flex; width: 100%; max-width: 232px; margin: 0 auto;
  padding: 11px 22px; font-size: .9rem;
}
.demo-submit svg { width: 15px; height: 15px; }
.demo-submit[disabled] { opacity: .6; cursor: default; transform: none; }
.demo-panel .demo-fine { margin: 14px 0 0; font-size: .74rem; color: rgba(255, 255, 255, .38); text-align: center; }

/* ── Confirmation ────────────────────────────────────────────────────────── */
.demo-done {
  grid-column: 1 / -1;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 60px 30px; text-align: center;
}
.demo-done[hidden] { display: none; }
.demo-tick {
  width: 58px; height: 58px; margin-bottom: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: rgba(52, 211, 153, .16);
  border: 1px solid rgba(52, 211, 153, .4); color: #34d399;
}
.demo-tick svg { width: 27px; height: 27px; }
.demo-done h2 { margin: 0; font-size: 1.5rem; font-weight: 700; color: #fff; }
.demo-done > p { margin: 0; font-size: .95rem; color: rgba(255, 255, 255, .72); }
.demo-done .demo-fine { max-width: 380px; margin: 6px 0 20px; }
/* The panel collapses to one column while the confirmation is up, so the
   left rail does not sit beside a message that has nothing to do with it. */
.demo-panel.done { grid-template-columns: 1fr; }
.demo-panel.done .demo-aside, .demo-panel.done .demo-form { display: none; }

/* Focus rings — the site sets its own elsewhere and these controls are new. */
.demo-panel :is(button, input, textarea):focus-visible {
  outline: 2px solid #a5b4fc; outline-offset: 2px;
}

@media (max-width: 900px) {
  .demo-panel { grid-template-columns: 1fr; max-height: 94vh; }
  .demo-aside { padding: 26px 22px 22px; border-right: none; border-bottom: 1px solid rgba(255, 255, 255, .08); }
  .demo-aside h2 { font-size: 1.4rem; }
  /* The bullet list is the first thing to go: on a phone it costs a screenful
     of scrolling before the calendar, which is what the dialog is for. */
  .demo-points, .demo-aside-foot { display: none; }
}
@media (max-width: 640px) {
  .demo-overlay { padding: 0; }
  .demo-panel { border-radius: 0; width: 100%; max-height: 100vh; height: 100%; }
  .demo-form { grid-template-columns: 1fr; }
  .demo-cal { border-right: none; border-bottom: 1px solid rgba(255, 255, 255, .08); padding: 22px 18px; }
  .demo-fields { padding: 22px 18px 28px; }
  .demo-slots { grid-template-columns: repeat(2, 1fr); }
}

/* ==========================================================================
   Guides
   --------------------------------------------------------------------------
   The homepage's links into the blog. Same white-card language as .bento, but
   a plain auto-fit grid rather than the bento's 6-column spans: these eight
   entries are peers with no hierarchy between them, and giving one a wider
   span would imply an importance none of them has over the others.

   Each card is a single <a>. Wrapping the whole card rather than putting a
   link at the bottom means the anchor text a crawler reads is the guide's own
   title, and means a person can hit any part of the card — the bottom "Read
   the guide" line is decoration on a link that already covers it.
   ========================================================================== */
/* Two rows, never three. Eight tiles in a 3-up grid is 3+3+2 — around a
   screen and a half of cards, sitting between the FAQ and the closing CTA,
   which is the worst place on the page to ask someone to read a list.
   auto-fit had to go to promise that: it decides the column count from the
   available width, so nothing in CSS knows how many tiles make a row, and the
   count of tiles to show has to follow the count of columns. Explicit tracks
   per band, and the tail of the list is hidden rather than wrapped — the
   tinted "All guides" tile is the 8th child and is never hidden, so the rest
   is always one click away, and /blog.html still links every guide for
   crawlers. Handsets stack, where rows are cards and hiding buys nothing, so
   the full list comes back at ≤640. */
.guide-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(4, 1fr);
}
/* Sized as teasers, not as articles. The row is only ever as short as its
   tallest card — margin-top:auto on the go-link stretches every other card to
   match — so the height of the whole block is set by the one guide with a
   three-line title and a five-line summary, and every millimetre of padding
   and leading in here is paid three or six times over. 26/22 of padding at
   1.04rem/0.9rem was card-sized for one card; across a grid of them it made a
   wall. This is the same card 40-odd pixels shorter. */
.guide {
  display: flex; flex-direction: column;
  background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 19px 19px 16px;
  text-decoration: none; color: inherit;
  transition: transform .45s var(--ease), box-shadow .45s var(--ease), border-color .45s;
}
.guide:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); border-color: var(--brand-line); }
.guide h3 {
  margin: 0 0 7px; font-size: .95rem; line-height: 1.3;
  font-weight: 600; color: var(--ink);
}
/* .mode-site p is (0,1,1) and beats a bare .guide p — same trap the demo
   dialog's paragraphs hit. The extra class is what makes this rule apply. */
.guide-grid .guide p {
  margin: 0 0 13px; font-size: .85rem; line-height: 1.52; color: var(--muted);
}
/* Pushed to the bottom so the arrow lines up across a row of cards whose
   descriptions run to different lengths. */
.guide-go {
  display: inline-flex; align-items: center; gap: 7px; margin-top: auto;
  font-size: .82rem; font-weight: 600; color: var(--brand);
}
.guide-go svg { width: 14px; height: 14px; transition: transform .3s var(--ease); }
.guide:hover .guide-go svg { transform: translateX(3px); }
/* The one card that is a destination rather than an article. Tinted so it
   reads as the end of the list instead of an eighth guide. */
.guide-all { background: var(--brand-soft); border-color: var(--brand-line); }

/* 4 columns need ~240px a card to stay readable: 4 × 240 + 3 gaps + the
   wrap's 48px of padding is 1062. Below that, 3 up and six tiles. */
@media (max-width: 1061px) {
  .guide-grid { grid-template-columns: repeat(3, 1fr); }
  .guide-grid > a:nth-child(n+6):not(.guide-all) { display: none; }
}
/* 3 × 240 + 2 gaps + 48 = 804. */
@media (max-width: 803px) {
  .guide-grid { grid-template-columns: repeat(2, 1fr); }
  .guide-grid > a:nth-child(n+4):not(.guide-all) { display: none; }
}
@media (max-width: 640px) {
  .guide-grid { grid-template-columns: 1fr; gap: 14px; }
  .guide { padding: 22px 20px 18px; }
  /* Written with the same nth-child shape as the two rules above, not a plain
     `.guide-grid > a`: those are (0,3,1) and a bare one would be (0,2,1) and
     lose to both regardless of order, leaving the tail hidden on a phone. */
  .guide-grid > a:nth-child(n+4):not(.guide-all) { display: flex; }
}

/* ==========================================================================
   Mobile polish
   --------------------------------------------------------------------------
   Cosmetic only, and only below the handset breakpoints. Everything here was
   found by rendering the page at 390px rather than by reasoning about it —
   which is how the nested scroll in the booking dialog surfaced, since at any
   width wide enough to read the CSS comfortably the bug does not exist.
   ========================================================================== */
@media (max-width: 640px) {
  /* The dialog was TWO scrolling regions stacked on a phone: .demo-panel
     clips its own overflow at 100vh, and .demo-form scrolls inside it. So the
     intro pane stayed pinned to the top taking a third of the screen, and the
     month grid sat above the top of an inner window with no way to scroll up
     to it — you landed in the middle of the time slots and the calendar was
     simply gone. One scrolling surface fixes it: the panel scrolls, the form
     is content again. min-height has to be released with it, or the grid item
     keeps the height that made the inner scroller necessary. */
  .demo-panel { overflow-y: auto; overscroll-behavior: contain; }
  .demo-form { overflow: visible; min-height: auto; }
  /* Which would scroll the close button away with the intro. The panel is the
     full viewport at this width, so fixed puts it exactly where it already
     looked pinned — and keeps it under the thumb the whole way down. */
  .demo-close { position: fixed; }
}

@media (max-width: 560px) {
  /* The brand blurb was sharing the footer's 2-column grid with the link
     lists, which left it 162px wide: six lines averaging three words. It is
     prose and they are lists, so it takes the full width and they keep the
     columns. max-width has to go too — the 22rem cap from the desktop rule
     is wider than the column anyway, but leaving it is a trap for whoever
     changes this next. */
  .foot-brand { grid-column: 1 / -1; }
  .foot-brand p { max-width: none; }
  /* Back to two columns: the tablet block above puts the three lists three
     across, and three lists across a 360px screen is ~100px a column, which
     wraps "How it works" onto two lines. */
  .foot-grid { grid-template-columns: 1fr 1fr; }

  /* 104px top and bottom is 208px of empty near-black on an 844px screen.
     Enough of it lands between the buttons and the footer that the page
     reads as having ended before the footer arrives. */
  .cta { padding: 76px 0; }

  /* Two of the four labels wrap to a second line and two don't, so the grid
     came out with a 132px row above a 154px row and the block looked
     accidental. A floor evens them without stretching the text away from
     its number. */
  .stat { min-height: 152px; display: flex; flex-direction: column; justify-content: center; }

  /* The burger's weight was never the glyph — it was the chrome around it. A
     44px rounded box with a border and a fill, holding a 14px mark, put a
     button in the corner heavier than the logo opposite it. Taking it down in
     stages (44 → 36) kept shrinking a thing whose size was not the problem, so
     the box goes entirely: three lines and nothing else, which is what the
     control actually is.

     Listed against every nav state because .nav.on-dark/.on-light/.is-stuck
     each set the border and background at (0,3,0) — a bare .nav-burger here
     would lose to all three and the box would survive on exactly the pages it
     was most visible on. The span colour rules are deliberately NOT overridden:
     they are what keeps the lines white over the hero and ink once the nav goes
     light, and without the box they are now the only thing doing it. */
  .nav .nav-burger,
  .nav.on-dark .nav-burger,
  .nav.on-light .nav-burger,
  .nav.is-stuck .nav-burger {
    width: 26px; height: 26px; position: relative;
    border: 0; background: none; border-radius: 0;
  }
  /* The glyph itself is the base one — same 16px lines at the same 4px spacing
     as the tablet burger. Only the box around it differs by width. */
  /* A 26px glyph is well under the 44px minimum touch target, and there is no
     longer a box edge to aim at. The hit area is pushed back out with a
     transparent inset — 48px to a thumb, 26px to an eye. Absolutely
     positioned, so the button's own grid never lays it out. */
  .nav-burger::after { content: ''; position: absolute; inset: -11px; }

  /* 74/64 was set for a desktop bar carrying five links and a CTA pill. On a
     phone the row holds a 32px mark, one word, and three lines — the rest of
     that height was air, and because the bar is fixed it is air sitting over
     the page for the entire scroll. 66/56 still clears the mark at both edges
     (12px above and below it once stuck) and gives 8px back to the content. */
  .nav-inner { height: 66px; }
  .nav.is-stuck .nav-inner { height: 56px; }
  /* 1.12rem next to a 32px circle read as two competing sizes rather than one
     lockup; the tighter gap closes the pair up. */
  .nav .brand { font-size: 1.04rem; gap: 9px; }

  /* The menu behind it read small at 17px, so the rows get bigger.
     It is NOT centred vertically. Centring was tried and reverted: the panel
     carries 84px of top padding to clear the fixed nav, and justify-content
     centres the free space that is left *after* that padding — so the block
     did not come to rest in the middle, it got shoved down, opening a 117px
     hole between the nav and the first link. Empty space below a short menu
     is ordinary and nobody reads it as a fault; a gap under the header is the
     first thing you see. Top-aligned, and the space falls where it always
     did. */
  .mode-site .mobile-menu a { font-size: 1.2rem; padding: 16px 2px; }
  /* Tracks the shorter bar above. The 84px exists to clear the fixed nav, so
     leaving it while the nav loses 8px would open exactly the gap under the
     header that the note above says is the first thing you see. */
  .mobile-menu { padding-top: 76px; }
}

/* ==========================================================================
   Deal popup
   --------------------------------------------------------------------------
   Same overlay/panel/close shape as .demo-*, deliberately not sharing its
   classes: the demo dialog is a booking form that needs to be findable and
   reusable everywhere, this is a scroll-triggered promo that fires once and
   is gone, and giving them separate names means a future change to one
   can't silently reach the other.

   Plain by design: a message and a button. Two earlier passes added a lit
   red ribbon banner and a seal badge for the headline figure, and both
   moved it further from "this site's popup" toward "a discount site's
   popup" — the flourish was doing the opposite of what it was for. What's
   left is the white rounded card, the neon-green CTA and the confetti
   burst on open; nothing else needed removing to get back to simple.
   ========================================================================== */
.deal-overlay {
  position: fixed; inset: 0; z-index: 210;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(10, 14, 12, .6);
  backdrop-filter: blur(4px);
  opacity: 0; transition: opacity .28s var(--ease);
}
.deal-overlay[hidden] { display: none; }
.deal-overlay.open { opacity: 1; }

.deal-panel {
  /* Neon green, not forest green — #16a34a read as the site's ordinary
     "success" green. #00e05a is bright enough to need a real glow around it
     to sell the "neon" read; the hover step goes brighter still. */
  --deal-green: #00e05a;
  --deal-green-bright: #17ff79;
  position: relative;
  width: 100%; max-width: 400px;
  border-radius: var(--radius-lg);
  text-align: center;
  color: var(--ink);
  background: #fff;
  box-shadow: var(--shadow-lg);
  transform: translateY(10px) scale(.98);
  transition: transform .3s var(--ease);
}
.deal-overlay.open .deal-panel { transform: none; }
/* The panel itself takes programmatic focus on open (site.js) so a keyboard
   or screen-reader user lands inside the dialog — but it is not a control,
   and the browser's default blue ring looks stray against a plain white
   card. The card's own shadow already marks it clearly as the focused
   surface without one. */
.deal-panel:focus { outline: none; }

.deal-close {
  position: absolute; top: 12px; right: 12px; z-index: 2;
  width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: none; color: var(--muted);
  cursor: pointer; transition: background .2s, color .2s;
}
.deal-close:hover { background: var(--fill); color: var(--ink); }
.deal-close svg { width: 15px; height: 15px; }

.deal-body { padding: 40px 32px 30px; }

.deal-panel h2 {
  margin: 0 0 10px;
  font-size: 1.7rem; font-weight: 800; letter-spacing: -.02em;
  color: var(--ink);
}
.deal-panel p {
  margin: 0 0 24px;
  font-size: .94rem; line-height: 1.5;
  color: var(--muted);
}

.deal-cta {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%;
  border-radius: var(--radius-sm);
  background: var(--deal-green);
  /* Dark text, not white — a green this bright fails contrast badly under
     white (under 2:1). Dark text on a neon fill is also just what neon signs
     and neon-branded buttons actually look like; white-on-neon-green reads
     as a mistake, not as a style. */
  color: var(--ink);
  box-shadow: 0 0 0 0 rgba(0, 224, 90, 0), 0 10px 24px -10px rgba(0, 224, 90, .6);
  transition: background .2s, box-shadow .2s, transform .2s var(--ease);
}
.deal-cta svg { width: 17px; height: 17px; transition: transform .2s var(--ease); }
.deal-cta:hover {
  background: var(--deal-green-bright);
  box-shadow: 0 0 24px 2px rgba(23, 255, 121, .55), 0 12px 28px -10px rgba(0, 224, 90, .65);
  transform: translateY(-1px);
}
.deal-cta:hover svg { transform: translateX(3px); }
.deal-cta:active { background: #00c452; transform: translateY(0); }

.deal-skip {
  display: block; width: 100%;
  margin-top: 10px; padding: 8px;
  border: 0; background: none;
  font-size: .82rem; color: var(--muted);
  cursor: pointer; transition: color .2s;
}
.deal-skip:hover { color: var(--ink); }

.deal-panel :is(.deal-cta, .deal-close, .deal-skip):focus-visible {
  outline: 2px solid var(--deal-green); outline-offset: 2px;
}

/* Party-popper burst. Pieces are plain spans, positioned and coloured by
   site.js, animated by this one keyframe — rotate while falling and drifting
   sideways by each piece's own --x, fading out near the end so nothing is
   left sitting visibly on the panel once the burst is over. Not stacked
   above the ribbon/seal/body — plain DOM order already keeps them on top —
   and allowed to overflow the panel on purpose, since a popper that stayed
   inside its own card wouldn't read as one. */
.deal-confetti {
  position: absolute; top: 0; left: 50%;
  width: 0; height: 0;
  pointer-events: none;
}
.deal-confetti span {
  position: absolute; top: 0; left: 0;
  width: var(--w, 8px); height: var(--h, 8px);
  background: var(--c, var(--deal-green));
  opacity: 0;
  animation: deal-confetti-fall var(--dur, 1.1s) var(--delay, 0s) cubic-bezier(.21, .68, .34, 1) forwards;
}
@keyframes deal-confetti-fall {
  0%   { opacity: 1; transform: translate(0, 0) rotate(0deg); }
  100% { opacity: 0; transform: translate(var(--x, 0), var(--y, 220px)) rotate(var(--r, 300deg)); }
}

@media (max-width: 480px) {
  .deal-body { padding: 32px 24px 26px; }
  .deal-panel h2 { font-size: 1.4rem; }
}

@media (prefers-reduced-motion: reduce) {
  .deal-overlay, .deal-panel, .deal-cta svg { transition: none; }
  .deal-confetti { display: none; }
}

/* ==========================================================================
   Ethical-pitch section (#ethics)
   --------------------------------------------------------------------------
   One boxed panel rather than a full-width section — this is a short aside
   on the way to Pricing, not a fourth hero, and a contained card is what
   tells the eye "read this, then keep scrolling" instead of asking for the
   same attention Features or How it works got. Info on the left, the two
   verdict cards stacked on the right — the mirror of .pocket-grid's
   text-left/visual-right split, at a much smaller scale.
   ========================================================================== */
.trust-hero { padding: 40px 0; }

.trust-panel {
  position: relative;
  overflow: hidden;
  padding: 40px;
  border-radius: var(--radius-xl);
  background: linear-gradient(155deg, #f0fdf4 0%, #fff 46%);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
}

/* The big G. Decoration, not a second logo — real size and full colour
   would compete with the small one in the kicker above; this is a shape in
   the background, the way a watermark is meant to read. Clipped by the
   panel's own overflow:hidden rather than sized to fit.

   Dead centre of the whole panel — behind the seam between the text column
   and the cards, not tucked into either one. A corner placement kept
   reading as "misplaced asset" rather than "watermark"; centred and this
   large, it reads as the panel's own background mark instead of a logo that
   wandered off to one side. Low opacity is what keeps it from fighting the
   text on the left or the card edges on the right that cross over it. */
.trust-watermark {
  position: absolute; top: 50%; left: 50%;
  width: 360px; height: 360px;
  transform: translate(-50%, -50%);
  opacity: .06;
  pointer-events: none;
}

.trust-grid {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  gap: 36px; align-items: center;
}

.trust-kicker { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.trust-icon {
  display: grid; place-items: center;
  width: 26px; height: 26px;
  border-radius: 8px;
  background: #fff;
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  flex: none;
}
.trust-info .eyebrow { margin: 0; }
.trust-info .h-section { font-size: clamp(1.28rem, 2vw, 1.6rem); margin: 0 0 10px; }
.trust-info .lead { font-size: .88rem; margin: 0; }

.trust-foot {
  margin: 16px 0 0;
  font-size: .8rem; color: var(--muted);
}

.trust-cards { display: flex; flex-direction: column; gap: 12px; }
.trust-cards .card { padding: 16px 18px; border-radius: var(--radius); }
.trust-cards .card-icon { width: 30px; height: 30px; margin-bottom: 8px; border-radius: 8px; }
.trust-cards .card-icon svg { width: 15px; height: 15px; }
.trust-cards .h-card { font-size: .93rem; margin-bottom: 3px; }
.trust-cards .card p { font-size: .81rem; line-height: 1.42; }

.trust-bad .card-icon { background: #fef2f2; color: #dc2626; }
.trust-bad h3 { color: #991b1b; }
.trust-good .card-icon { background: #f0fdf4; color: #16a34a; }
.trust-good h3 { color: #166534; }
/* A quieter border than the brand-line hover every other .card gets, so the
   red card doesn't borrow the same "this is good, look here" affordance the
   rest of the page uses for things worth clicking toward. */
.trust-bad:hover { border-color: #fecaca; }
.trust-good:hover { border-color: #bbf7d0; }
/* .card::before is the sitewide mouse-follow spotlight, and it is always
   the brand indigo — right on every other card on the page, wrong here: a
   red verdict card and a green verdict card lighting up indigo on hover
   contradicts the red/green story this section is telling. Killed outright
   rather than recoloured to match, since a coloured glow on hover reads as
   "this card does something when you interact with it" and neither of
   these opens, expands, or links anywhere. */
.trust-bad::before, .trust-good::before { content: none; }

@media (max-width: 860px) {
  .trust-panel { padding: 28px 24px; }
  .trust-grid { grid-template-columns: 1fr; gap: 24px; }
  .trust-watermark { width: 260px; height: 260px; opacity: .05; }
}
