/* Spotter.fit marketing site
   Apple-proportioned layout, Spotter app design system. System-adaptive:
   light is the default token block, dark comes only from prefers-color-scheme
   — deliberately no toggle.

   Since the 2026-07-25 flat rework the page is ONE background with large
   rounded solid tiles on it (Apple "Get to know Mac" proportions): zinc-100
   tiles on the white page, zinc-800 tiles on the zinc-900 dark page — no
   borders, no blur, no drop shadows on blocks, and no ambient wash behind
   the page. Glass survives in exactly one place: the nav, and only once the
   page has scrolled under it — at rest the bar is the page background.
   Screenshots are cropped against their tile's rounded edge rather than
   framed whole.

   Tokens still mirror the app (apps/web/src/shared/styles/tailwind.css +
   shared/ui/components/button.tsx): zinc palette (dark page = zinc-900, not
   near-black), lime #7ddb0f as the one brand accent with ink #091605 text on
   it, --radius 8px for controls. Buttons ARE the app's buttons: 36px visual
   box (32px `sm` in the nav), rounded-lg, semibold text-sm/6, opaque primary
   with the white/25 inset highlight — not pills. The app's glass `outline`
   variant is flattened here to a quiet border. */

:root {
  color-scheme: light dark;

  --bg:        #ffffff;
  --bg-2:      #f4f4f5;              /* zinc-100 — app --background-muted */
  --fg:        #09090b;              /* zinc-950 */
  --muted:     #71717a;              /* zinc-500 — app --muted-foreground */
  --faint:     #a1a1aa;              /* zinc-400 */
  --line:      rgba(9,9,11,.10);     /* app --border */
  --line-2:    rgba(9,9,11,.14);     /* app --input */

  --lime:      #7ddb0f;
  --ink:       #091605;
  --accent:    #4d7c0f;              /* text-safe lime on light (eyebrows) */

  /* Tiles: the one block surface. --tile-muted is body text ON a tile —
     zinc-500 on zinc-100 is only ~4.4:1, so tiles step down to zinc-600. */
  --tile:       #f4f4f5;             /* zinc-100 */
  --tile-muted: #52525b;             /* zinc-600 */
  --r-tile:     clamp(20px, 2.5vw, 28px);
  --tile-gap:   clamp(.75rem, 1.6vw, 1.25rem);

  /* Nav glass — the only translucent surface left, shown once scrolled. */
  --nav-bg:    rgba(255,255,255,.72);
  --nav-blur:  16px;

  --hover:     rgba(9,9,11,.05);
  --shadow-sm: 0 1px 2px rgba(9,9,11,.05);

  --sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --display: Archivo, var(--sans);

  --wrap: 1200px;
  --nav-wrap: 1024px;
  --pad: clamp(1.25rem, 4vw, 2.5rem);
  --sec-y: clamp(3rem, 6vw, 5rem);
  --radius: 8px;                     /* app --radius: rounded-lg */
  --r-xl: 12px;

  --ease: cubic-bezier(.16,1,.3,1);  /* ease-out-expo-ish */

  --z-nav: 100;
  --z-menu: 200;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:        #18181b;            /* zinc-900 — the app's dark page bg */
    --bg-2:      #09090b;            /* zinc-950 — app --background-muted */
    --fg:        #ffffff;
    --muted:     #a1a1aa;            /* zinc-400 */
    --faint:     #71717a;            /* zinc-500 */
    --line:      rgba(255,255,255,.10);
    --line-2:    rgba(255,255,255,.16);

    --accent:    #7ddb0f;

    --tile:       #27272a;           /* zinc-800 — a step LIGHTER than the page */
    --tile-muted: #a1a1aa;           /* zinc-400 — 5.8:1 on zinc-800 */

    --nav-bg:    rgba(39,39,42,.62); /* zinc-800 @62% */
    --nav-blur:  20px;

    --hover:     rgba(255,255,255,.06);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.30);
  }
}

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

/* scroll-padding clears the sticky header when an in-page anchor lands —
   the hero's scroll cue and the nav both jump straight to a section top. */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 60px; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.65;
  font-feature-settings: 'cv11';
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 700;
  font-stretch: 118%;
  letter-spacing: -.03em;
  line-height: 1.05;
  text-wrap: balance;
  margin: 0;
}
p { text-wrap: pretty; }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--pad); }

.sr, .skip:not(:focus) {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.skip:focus {
  position: fixed; z-index: 999; top: .75rem; left: .75rem;
  background: var(--lime); color: var(--ink); padding: .6rem 1rem;
  border-radius: var(--radius); font-weight: 600;
}

/* The app's focus treatment: a 3px ring at 50% lime (ring/50). */
:where(a, button, [tabindex]):focus-visible {
  outline: 3px solid rgba(125,219,15,.5);
  outline-offset: 2px;
  border-radius: var(--radius);
}

/* ── Tiles ────────────────────────────────────────────────
   The one block surface: solid fill, big radius, nothing else. The
   overflow:hidden is load-bearing — it is what crops the screenshots
   against the tile edge. */
.tile {
  background: var(--tile);
  border-radius: var(--r-tile);
  overflow: hidden;
}

/* ── Buttons (the app's shadcn button, sizes default + sm) ── */
/* Geometry from button.tsx: text-sm/6 semibold, py 5px + px 13px + 1px border
   = a 36px visual box; `sm` is py 3px + px 9px = 32px. Radius is rounded-lg
   (8px). No pills. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: 5px 13px;
  border-radius: var(--radius); border: 1px solid transparent;
  font-family: var(--sans); font-size: .875rem; line-height: 1.5rem; font-weight: 600;
  text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: background-color .15s var(--ease), border-color .15s var(--ease),
              color .15s var(--ease), filter .15s var(--ease);
}
.btn-primary {
  background: var(--lime); color: var(--ink);
  border-color: color-mix(in oklab, var(--lime) 90%, transparent);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.25), var(--shadow-sm);
}
.btn-primary:hover { filter: brightness(.95); }
/* The app's `outline` variant, flattened: quiet border, no fill, no blur. */
.btn-ghost {
  background: transparent;
  border-color: var(--line-2);
  color: var(--fg);
}
.btn-ghost:hover { background: var(--hover); }
.btn-sm { padding: 3px 9px; }

/* ── Language offer bar ──────────────────────────────────── */
/* The FIRST element in the document, above the header — main.js inserts it
   when the reader's saved choice (or browser language) differs from the
   page. Since the picker is gone, this bar is the only language switch. */
.lang-offer {
  display: flex; align-items: center; justify-content: center; gap: 1rem;
  padding: .55rem var(--pad); font-size: .8125rem;
  background: var(--bg-2); border-bottom: 1px solid var(--line);
}
.lang-offer a { color: var(--fg); }
.lang-offer button {
  background: none; border: 0; padding: 0 .25rem;
  color: var(--muted); font: inherit; line-height: 1; cursor: pointer;
}
.lang-offer button:hover { color: var(--fg); }

/* ── Nav ─────────────────────────────────────────────────── */
/* One thin bar, Apple-globalnav proportions: 48px tall, a narrower
   max-width than the page, 12px link type, no border. At rest it is the
   page background — indistinguishable from the page. Once content scrolls
   under it (main.js toggles .scrolled) the background thins to the
   translucent nav fill and the always-on backdrop blur becomes visible.
   Everything is ONE flex row — no three-column grid pinning the brand and
   the CTA to the frame edges, so a longer translated label just takes
   shared slack instead of colliding with a centred pill. */
.nav {
  position: sticky; top: 0; z-index: var(--z-nav);
  background: var(--bg);
  backdrop-filter: blur(var(--nav-blur)) saturate(180%);
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(180%);
  transition: background-color .3s var(--ease);
}
.nav.scrolled { background: var(--nav-bg); }
.nav-in {
  max-width: var(--nav-wrap); margin-inline: auto; padding-inline: var(--pad);
  display: flex; align-items: center; gap: clamp(1rem, 3vw, 2.5rem);
  min-height: 48px;
}

.brand { display: inline-flex; align-items: center; gap: .55rem; flex: none; text-decoration: none; }
.brand img { width: 26px; height: 26px; display: block; }

.nav-menu { flex: 1; min-width: 0; display: flex; align-items: center; gap: 1rem; }

.nav-links {
  margin-inline: auto; min-width: 0;
  display: flex; align-items: center; gap: clamp(0px, 1vw, 8px);
}
.nav-links a {
  display: inline-flex; align-items: center; padding: .4rem .7rem; border-radius: var(--radius);
  color: var(--muted); font-size: .75rem; font-weight: 500; white-space: nowrap;
  text-decoration: none; transition: color .15s var(--ease), background-color .15s var(--ease);
}
.nav-links a:hover { color: var(--fg); background: var(--hover); }

.nav-actions { display: flex; align-items: center; gap: .9rem; flex: none; }
.nav-actions > a:not(.btn) {
  color: var(--muted); font-size: .75rem; font-weight: 500; white-space: nowrap;
  text-decoration: none; transition: color .15s var(--ease);
}
.nav-actions > a:not(.btn):hover { color: var(--fg); }

.nav-toggle {
  margin-left: auto; display: none; align-items: center; justify-content: center;
  width: 40px; height: 40px; background: none; border: 0;
  border-radius: var(--radius); cursor: pointer;
}
.bars { display: grid; gap: 5px; width: 18px; }
.bars i { display: block; height: 1.5px; background: var(--fg); transition: transform .22s var(--ease), opacity .22s var(--ease); }
.nav-toggle[aria-expanded="true"] .bars i:first-child { transform: translateY(3.25px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .bars i:last-child  { transform: translateY(-3.25px) rotate(-45deg); }

@media (max-width: 899px) {
  .nav-toggle { display: inline-flex; }

  /* Anchored to the sticky bar itself (a sticky box is a containing block),
     not to the viewport — so the language bar above the header can push
     everything down without misaligning the sheet. */
  .nav-menu {
    position: absolute; top: 100%; left: 0; right: 0; z-index: var(--z-menu);
    flex: none; flex-direction: column; align-items: stretch; gap: 0;
    background: var(--bg); border-bottom: 1px solid var(--line);
    padding: .5rem var(--pad) 1.25rem;
    display: none;
  }
  .nav-menu.open { display: flex; }

  /* Both groups become plain stacked columns — one flat list. Deliberately
     not display:contents: .nav-links is the <nav> landmark, and keeping its
     box keeps that landmark independent of how an engine treats
     display:contents. */
  .nav-links, .nav-actions {
    display: flex; flex-direction: column; align-items: stretch; gap: 0;
    margin-inline: 0;
  }
  .nav-links a { border-radius: 0; font-size: 1rem; }

  .nav-menu a { padding: .85rem 0; border-bottom: 1px solid var(--line); color: var(--fg); }
  .nav-menu a.btn {
    margin-top: 1rem; border-bottom: none; justify-content: center;
    padding: 9px 13px; /* lg-ish: comfortable tap target in the sheet */
  }
}

/* ── Hero ─────────────────────────────────────────────────
   One statement, one screen. --vp-h is the JS-measured viewport height
   (main.js): iOS Safari can report `100svh` as the LARGE viewport when the
   page opens with the toolbar already collapsed, which pushed the scroll
   cue below the fold — `innerHeight` is what is actually visible. `100svh`
   is the no-JS fallback (`vh` overflows under the iOS toolbar, `dvh`
   resizes as it hides).

   The subtraction is because the header is STICKY, not fixed, so it takes
   flow height above this box. --nav-h is set by main.js, which is also the
   thing that prepends the language-offer bar above the header — so the
   fallback here is exact at first paint and both change in the same frame. */
.hero {
  min-height: calc(var(--vp-h, 100svh) - var(--nav-h, 48px));
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  position: relative;
  padding-block: clamp(2rem, 6vh, 4rem);
  text-align: center;
}
/* The nav (and the language bar) sit above the hero, so the hero box's own
   centre is below the screen's centre by half that stack. Shift the
   headline up by the same half so it lands on the optical centre of the
   whole first screen. Transform only — no layout or typewriter impact. */
.hero .wrap { transform: translateY(calc(var(--nav-h, 48px) / -2)); }
/* text-wrap: initial defeats the global `balance` on headings. Balance splits
   this three-word line as "Surpass" / "your limits."; greedy wrapping gives
   "Surpass your" / "limits.", which lands the green word on its own line. */
.hero h1 {
  font-size: clamp(2.75rem, 11vw, 8rem);
  font-weight: 800; font-stretch: 122%;
  text-wrap: initial;
}

/* The brand green used as TEXT, so --accent and not --lime: #7ddb0f on white
   is about 1.5:1, under even the 3:1 large-text floor. --accent is the token
   already carrying the text-safe value, and it flips back to full lime in
   dark mode where lime is fine. */
.hl { color: var(--accent); }

/* Typewriter reveal, ported from the app's sign-in page
   (apps/web/src/features/auth/components/AuthTagline.tsx) — same tagline,
   same 60ms a character, same caret. The two layers share one grid cell:
   .hero-sizer carries the full wrapped headline and is only made INVISIBLE,
   never display:none, so the cell keeps the finished box and typing cannot
   reflow a headline that is vertically centred in a full-screen section.
   Without JS .hero-typed stays hidden and the sizer is just the headline. */
.hero h1 { display: grid; }
.hero h1 > .hero-sizer,
.hero h1 > .hero-typed { grid-area: 1 / 1; }
.hero h1 > .hero-typed { display: none; }
.hero h1.typing > .hero-sizer { visibility: hidden; }
.hero h1.typing > .hero-typed { display: block; }

.caret {
  display: inline-block; vertical-align: baseline;
  width: .06em; height: .8em; margin-left: .05em;
  transform: translateY(.06em);
  background: currentColor;
  animation: caret 2s cubic-bezier(.4, 0, .6, 1) infinite;
}
@keyframes caret { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }
@media (prefers-reduced-motion: reduce) { .caret { animation: none; } }

/* A bare full-screen headline gives no sign there is a page under it. */
.scroll-cue {
  position: absolute; bottom: clamp(1.25rem, 4vh, 2.75rem);
  display: inline-flex; flex-direction: column; align-items: center; gap: .3rem;
  color: var(--muted); text-decoration: none;
  font-size: .6875rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  transition: color .15s var(--ease);
}
.scroll-cue:hover { color: var(--fg); }
.anim.ready .scroll-cue svg { animation: bob 2.6s var(--ease) infinite; }
@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(4px); } }

.cta { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 2rem; justify-content: center; }

/* ── Section furniture ───────────────────────────────────── */
/* Sections separate by whitespace alone — no hairlines between bands.
   Each section's header sits on the page background, ABOVE its tiles,
   wrapped in .sec-head so it reveals as one unit. */
.band { padding-block: var(--sec-y); }
.eyebrow {
  color: var(--accent); font-size: .8125rem; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; margin: 0 0 .85rem;
}
.sec-title { font-size: clamp(1.9rem, 3.6vw, 2.9rem); }
.sec-lede { color: var(--muted); max-width: 42rem; margin: 1rem 0 0; font-size: 1.0625rem; }

/* ── How it works: step tiles with cropped screenshots ────
   Step 01 is a full-width tile (copy left, shot bleeding off the right and
   bottom); steps 02/03 sit side by side (copy on top, shot bleeding off the
   bottom). Under 900px everything stacks and every step reads like a half
   tile. The screenshot is OVERSIZED (118%, max-width unset) and pinned by
   its top-left corner; the tile's overflow:hidden crops its right and
   bottom edges, so it reads as a window onto the app, not a framed screen.
   Only the visible corner gets a radius. The top/left offsets are also the
   parallax safety margin — the drift (±14px, see Motion) must stay smaller
   than the smallest offset so an image edge can never surface. */
.steps { list-style: none; margin: clamp(1.75rem, 3.5vw, 2.75rem) 0 0; padding: 0; display: grid; gap: var(--tile-gap); }
.step { display: grid; }
.step-copy { padding: clamp(1.75rem, 3.5vw, 3rem); }
.step-media { position: relative; min-height: clamp(13rem, 30vw, 20rem); }
.step-media img {
  position: absolute; top: clamp(1rem, 2vw, 2rem); left: clamp(1.75rem, 3.5vw, 3rem);
  width: 118%; max-width: none; height: auto;
  border-radius: var(--r-xl) 0 0 0;
}
@media (min-width: 900px) {
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .step-wide {
    grid-column: 1 / -1;
    grid-template-columns: minmax(0, 26rem) minmax(0, 1fr);
    align-items: center;
  }
  .step-wide .step-media { min-height: clamp(16rem, 30vw, 26rem); align-self: stretch; }
  .step-wide .step-media img { left: 0; top: clamp(1.5rem, 3vw, 3rem); }
}
.step-n {
  display: inline-flex; align-items: center; justify-content: center;
  height: 34px; padding: 0 14px; border-radius: 9999px;
  background: var(--lime); color: var(--ink);
  font-family: var(--sans); font-size: .875rem; font-weight: 700; letter-spacing: .02em;
  margin-bottom: 1.1rem;
}
.step h3 { font-size: clamp(1.5rem, 2.6vw, 2rem); }
.step p { color: var(--tile-muted); margin: .9rem 0 0; }
.step em { color: var(--fg); font-style: normal; font-weight: 600; }

/* ── Features: bento grid ─────────────────────────────────
   feat_1 (the MCP connector — the flagship) is the 2×2 hero tile; grid
   auto-placement stacks feat_2/feat_3 into the third column and flows the
   rest. Tablet: hero becomes a full-width banner over a 2-col grid.
   Mobile: one column, natural order. */
.feats { margin-top: clamp(1.75rem, 3.5vw, 2.75rem); display: grid; gap: var(--tile-gap); }
.feat { padding: clamp(1.5rem, 2.5vw, 2rem); }
.feat h3 { font-size: 1.075rem; font-stretch: 110%; line-height: 1.25; }
.feat p { color: var(--tile-muted); margin: .7rem 0 0; font-size: .95rem; }

.feat-hero {
  padding: clamp(2rem, 3.5vw, 3rem);
  display: flex; flex-direction: column; justify-content: center;
}
.feat-hero h3 { font-size: clamp(1.7rem, 2.6vw, 2.4rem); font-stretch: 118%; line-height: 1.1; }
.feat-hero p { font-size: 1.0625rem; max-width: 34rem; }

@media (min-width: 640px) and (max-width: 1023px) {
  .feats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .feat-hero { grid-column: span 2; }
}
@media (min-width: 1024px) {
  .feats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .feat-hero { grid-column: span 2; grid-row: span 2; }
}

/* ── Insiders: the closing tile ──────────────────────────
   The whole section lives inside one centred tile — header included; a
   header floating above a near-empty tile reads broken. The tile is LIME,
   the one brand-colored block on the page, so the close pops. Ink text on
   lime is the btn-primary pairing (~12:1); the buttons invert to ink. Lime
   is scheme-invariant, so the same colors work in light and dark. */
.insiders-tile {
  padding: clamp(2.5rem, 6vw, 5rem) clamp(1.5rem, 5vw, 4rem);
  text-align: center;
  background: var(--lime);
  color: var(--ink);
}
.insiders-tile .eyebrow { color: var(--ink); }
.insiders-tile .sec-lede { margin-inline: auto; color: var(--ink); opacity: .85; }
.insiders-tile .btn-primary {
  background: var(--ink); color: #fff;
  border-color: transparent; box-shadow: none;
}
.insiders-tile .btn-primary:hover { filter: brightness(1.6); }
.insiders-tile .btn-ghost { border-color: rgba(9,22,5,.4); color: var(--ink); }
.insiders-tile .btn-ghost:hover { background: rgba(9,22,5,.08); }
/* The lime focus ring is invisible on the lime tile. */
.insiders-tile :where(a, button):focus-visible { outline-color: rgba(9,22,5,.55); }

/* ── Footer ──────────────────────────────────────────────── */
.foot { border-top: 1px solid var(--line); padding-block: 2.5rem; }
.foot-in { display: flex; flex-wrap: wrap; gap: 1.25rem 2rem; align-items: center; }
.foot .brand img { width: 24px; height: 24px; opacity: .92; }
.foot .brand span { font-weight: 600; font-size: .95rem; }
.foot-links { display: flex; flex-wrap: wrap; gap: 1.25rem; font-size: .925rem; }
.foot-links a { color: var(--muted); text-decoration: none; }
.foot-links a:hover { color: var(--fg); }
.foot-c { margin: 0 0 0 auto; color: var(--faint); font-size: .875rem; }

/* ── Motion ───────────────────────────────────────────────
   A handful of deliberate moments, not a fade on every element.
   Everything is visible by default; the hidden start state only exists once
   the page has opted in (.anim), and the hero never depends on scrolling. */

/* 1. Hero entrance, staggered. Fires on load. */
.anim .hero .r { opacity: 0; transform: translateY(14px); }
.anim.ready .hero .r {
  opacity: 1; transform: none;
  transition: opacity .7s var(--ease), transform .7s var(--ease);
  transition-delay: calc(var(--d, 0) * 90ms);
}

/* 2. One reveal per unit as it scrolls in: each section header, each tile.
   Feature tiles stagger across their grid; a step or the insiders tile
   reveals as a single unit (--d defaults to 0). */
.anim :is(.sec-head, .tile) { opacity: 0; transform: translateY(12px); }
.anim :is(.sec-head, .tile).in {
  opacity: 1; transform: none;
  transition: opacity .6s var(--ease), transform .6s var(--ease);
  transition-delay: calc(var(--d, 0) * 55ms);
}

/* 3. Parallax: the cropped screenshot drifts inside its tile as the tile
   crosses the viewport. CSS scroll-driven, zero JS — progressive
   enhancement behind @supports (Chrome/Edge, Safari 26+; anyone else gets
   the static crop). Animates the standalone `translate` property so it can
   never clobber the reveal transform, and ±14px stays inside the crop's
   smallest top offset (16px) so no image edge ever surfaces. */
@supports (animation-timeline: view()) {
  .anim .step-media img {
    animation: drift linear both;
    animation-timeline: view();
  }
}
@keyframes drift {
  from { translate: 0 14px; }
  to   { translate: 0 -14px; }
}

/* Never let a renderer that does not scroll or animate ship a blank section. */
@media print {
  .anim .hero .r, .anim .sec-head, .anim .tile {
    opacity: 1 !important; transform: none !important;
  }
  .anim .step-media img { animation: none !important; translate: none !important; }
}
@media (prefers-reduced-motion: reduce) {
  .anim .hero .r, .anim.ready .hero .r,
  .anim .sec-head, .anim .sec-head.in,
  .anim .tile, .anim .tile.in {
    opacity: 1; transform: none; transition: none;
  }
  .anim.ready .scroll-cue svg { animation: none; }
  .anim .step-media img { animation: none; translate: none; }
}
