/* ============================================================================
   TrophyShelf marketing site
   Merged from Claude Design/website/ (SEO build) and
   Claude Design/TrophyShelf Landing Website/ (product build).

   Rules read SEMANTIC ALIASES, never raw ink steps. That indirection is the
   only reason the light theme costs nothing per rule — the same discipline
   trophyshelf_app/CLAUDE.md enforces on the Flutter side.

   Theme resolution, in order of precedence:
     1. :root                                  -> dark  (the designed default)
     2. @media (prefers-color-scheme: light)   -> light ramp
     3. :root[data-theme="..."]                -> explicit choice wins either way
   ========================================================================== */

/* ---------------------------------------------------------------- tokens -- */

:root {
  /* Brand — identical in both themes. */
  --brand: #ff8d28;
  --brand-deep: #e07b20;
  --brand-light: #ffa056;
  --amber: #f5a623;
  --success: #4caf50;
  --on-brand: #1c1714;
  --grad-brand: linear-gradient(135deg, #ff8d28 0%, #e07b20 100%);
  --grad-progress: linear-gradient(90deg, #f5a623 0%, #e07b20 100%);

  /* Photography stays dark in both themes. public.css states the reason: a
     collectible shot against a light frame reads as a product listing rather
     than a shelf. */
  --photo-bg: #0c0e16;

  /* Surfaces — dark ramp. */
  --surface-app: #070809;
  --surface-tint: #0d0f14;
  --surface-card: #171a21;
  --surface-raised: #1c1f24;
  --surface-track: #252830;
  --surface-glass: rgba(255, 255, 255, 0.05);
  --surface-glass-hover: rgba(255, 255, 255, 0.09);

  /* Text — dark ramp. */
  --text-strong: #ffffff;
  --text-primary: #f0ede8;
  --text-muted: #8a8f9e;
  /* ⚠️ Lifted 2026-09-08 for WCAG AA. Was #676d75, which measured 3.33:1 on
     `--surface-card` and 3.84:1 on the page — both under the 4.5:1 that normal
     text needs, and this token carries the footer line, the locked-badge state,
     rank-card labels and every figure caption. #7c838c clears 4.55:1 on the
     worst surface while keeping the hue. */
  --text-faint: #808790;

  /* Borders — dark ramp. */
  --border: rgba(255, 255, 255, 0.08);
  --border-strong: rgba(255, 255, 255, 0.14);
  --border-accent: rgba(255, 141, 40, 0.20);

  /* Brand-tinted chrome — dark ramp. */
  --tint-brand: rgba(245, 166, 35, 0.12);
  --tint-brand-ink: var(--amber);
  --link: var(--brand);
  --link-hover: var(--brand-light);
  --glow-core: rgba(255, 141, 40, 0.22);
  --glow-mid: rgba(255, 141, 40, 0.05);
  --nav-bg: rgba(7, 8, 9, 0.72);
  --shadow-card: 0 24px 60px rgba(0, 0, 0, 0.5);
  --shadow-brand: 0 8px 24px rgba(255, 141, 40, 0.32);
  --bezel-face: linear-gradient(160deg, #26282e, #0c0d10);
  --bezel-ring: rgba(255, 255, 255, 0.07);

  --font: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --nav-h: 64px;
}

/* Light ramp — warm paper. Values transcribed from
   trophyshelf_app/web_public/public.css, itself transcribed from
   docs/design.md, so the marketing site and the public shelf pages agree. */
@media (prefers-color-scheme: light) {
  :root {
    --surface-app: #f7f3ee;
    --surface-tint: #f1ebe3;
    --surface-card: #ffffff;
    --surface-raised: #ede8e1;
    --surface-track: #ded7cd;
    --surface-glass: rgba(28, 23, 20, 0.04);
    --surface-glass-hover: rgba(28, 23, 20, 0.08);

    --text-strong: #14100e;
    --text-primary: #1c1714;
    --text-muted: #6b6058;
    /* ⚠️ Lifted with its dark counterpart. #8d8279 measured 3.08:1 on
       `--surface-raised` — the light theme was the worse of the two, and would
       have been missed by testing only the default. */
    --text-faint: #6e655e;

    --border: rgba(28, 23, 20, 0.10);
    --border-strong: rgba(28, 23, 20, 0.18);
    --border-accent: rgba(255, 141, 40, 0.42);

    --tint-brand: rgba(255, 141, 40, 0.14);
    --tint-brand-ink: #9c5209;
    --link: #b4590c;
    --link-hover: #8f460a;
    --glow-core: rgba(255, 141, 40, 0.13);
    --glow-mid: rgba(255, 141, 40, 0.03);
    --nav-bg: rgba(247, 243, 238, 0.78);
    --shadow-card: 0 18px 40px rgba(28, 23, 20, 0.10);
    --shadow-brand: 0 8px 24px rgba(255, 141, 40, 0.28);
    --bezel-ring: rgba(28, 23, 20, 0.16);
  }
}

/* An explicit choice overrides the OS in BOTH directions. */
:root[data-theme="dark"] {
  --surface-app: #070809;
  --surface-tint: #0d0f14;
  --surface-card: #171a21;
  --surface-raised: #1c1f24;
  --surface-track: #252830;
  --surface-glass: rgba(255, 255, 255, 0.05);
  --surface-glass-hover: rgba(255, 255, 255, 0.09);
  --text-strong: #ffffff;
  --text-primary: #f0ede8;
  --text-muted: #8a8f9e;
  --text-faint: #808790;
  --border: rgba(255, 255, 255, 0.08);
  --border-strong: rgba(255, 255, 255, 0.14);
  --border-accent: rgba(255, 141, 40, 0.20);
  --tint-brand: rgba(245, 166, 35, 0.12);
  --tint-brand-ink: #f5a623;
  --link: #ff8d28;
  --link-hover: #ffa056;
  --glow-core: rgba(255, 141, 40, 0.22);
  --glow-mid: rgba(255, 141, 40, 0.05);
  --nav-bg: rgba(7, 8, 9, 0.72);
  --shadow-card: 0 24px 60px rgba(0, 0, 0, 0.5);
  --shadow-brand: 0 8px 24px rgba(255, 141, 40, 0.32);
  --bezel-ring: rgba(255, 255, 255, 0.07);
}

:root[data-theme="light"] {
  --surface-app: #f7f3ee;
  --surface-tint: #f1ebe3;
  --surface-card: #ffffff;
  --surface-raised: #ede8e1;
  --surface-track: #ded7cd;
  --surface-glass: rgba(28, 23, 20, 0.04);
  --surface-glass-hover: rgba(28, 23, 20, 0.08);
  --text-strong: #14100e;
  --text-primary: #1c1714;
  --text-muted: #6b6058;
  --text-faint: #6e655e;
  --border: rgba(28, 23, 20, 0.10);
  --border-strong: rgba(28, 23, 20, 0.18);
  --border-accent: rgba(255, 141, 40, 0.42);
  --tint-brand: rgba(255, 141, 40, 0.14);
  --tint-brand-ink: #9c5209;
  --link: #b4590c;
  --link-hover: #8f460a;
  --glow-core: rgba(255, 141, 40, 0.13);
  --glow-mid: rgba(255, 141, 40, 0.03);
  --nav-bg: rgba(247, 243, 238, 0.78);
  --shadow-card: 0 18px 40px rgba(28, 23, 20, 0.10);
  --shadow-brand: 0 8px 24px rgba(255, 141, 40, 0.28);
  --bezel-ring: rgba(28, 23, 20, 0.16);
}

/* ------------------------------------------------------------------ base -- */

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

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--surface-app);
  color: var(--text-primary);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  font-synthesis-weight: none;
  /* No `overflow-x: hidden` here on purpose: it masks overflow rather than
     fixing it, and it breaks position:sticky in some engines. The glows are
     contained by `overflow: clip` on their own sections instead. */
}

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

/* ⚠️ **Links inside running text carry an underline, and it is not decoration.**
   WCAG 1.4.1 wants a link distinguishable from its surrounding text by something
   other than colour, and the brand link against body text measured 1.39:1 — well
   under the 3:1 a colour-only difference needs.

   Scoped to prose deliberately: nav items, buttons, cards and the guide grid are
   all distinguishable by position and shape, so underlining them would add noise
   without adding information. */
.article p a, .article li a,
.legal-section p a, .legal-section li a,
.faq-list a, .guide-faq a,
.footer-legal a, .tip a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
img { max-width: 100%; height: auto; }

/* ⚠️ `hidden` is a UA `display: none`, so ANY author `display` rule beats it and
   the element renders anyway. Three components here ship `hidden` and set their
   own display — the theme toggle, the back-to-top button and the store badges —
   and all three were visible despite the attribute. The toggle is the one that
   mattered: it is marked hidden precisely so a visitor without JS never sees a
   control that cannot work, and that promise was not being kept.
   `!important` is not decoration here; without it the author rules win again. */
[hidden] { display: none !important; }
::selection { background: rgba(255, 141, 40, 0.28); color: var(--text-strong); }

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: 6px;
}

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

/* ------------------------------------------------------- layout & type -- */

.wrap {
  max-width: 1200px;
  margin: 0 auto;
  padding-left: clamp(20px, 5vw, 40px);
  padding-right: clamp(20px, 5vw, 40px);
}

.section { position: relative; scroll-margin-top: calc(var(--nav-h) + 16px); }
.section--pad { padding-top: clamp(64px, 9vw, 130px); padding-bottom: clamp(64px, 9vw, 130px); }
.section--tint { background: var(--surface-tint); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.section--bordered { border-top: 1px solid var(--border); }

/* ⚠️ The proof bar is itself tinted and already draws a bottom rule, so a tinted
   section following it would stack two 1px borders into a visible 2px seam
   between two same-coloured bands. Dropping the top border here leaves proof's
   own rule as the single divider. Written as an adjacency rule rather than a
   class on the section, so it stops applying by itself if the two are ever
   separated again — which they have been twice already. */
.proof + .section--tint { border-top: 0; }
.section--clip { overflow: clip; }

.center { text-align: center; }
.center .lede { margin-left: auto; margin-right: auto; }

h1, h2, h3 { color: var(--text-strong); font-weight: 800; text-wrap: balance; margin: 0; }
h1 { font-size: clamp(38px, 5.6vw, 64px); line-height: 1.04; letter-spacing: -1.4px; }
h2 { font-size: clamp(28px, 3.6vw, 42px); line-height: 1.1; letter-spacing: -0.8px; }
h3 { font-size: 19px; letter-spacing: -0.3px; }

.h2 { margin: 0; font-weight: 800; font-size: clamp(30px, 4.6vw, 52px); line-height: 1.05; letter-spacing: -1px; color: var(--text-strong); text-wrap: balance; }
.lead { font-size: clamp(15px, 1.6vw, 18px); color: var(--text-muted); line-height: 1.55; text-wrap: pretty; }
.lede { font-size: clamp(16px, 1.7vw, 19px); color: var(--text-muted); max-width: 620px; text-wrap: pretty; margin: 18px 0 0; }
.overline { display: inline-block; font-size: 12px; font-weight: 800; letter-spacing: 1.5px; text-transform: uppercase; color: var(--link); margin-bottom: 14px; }
.grad-text { background: var(--grad-brand); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }

.glow {
  position: absolute; left: 50%; transform: translateX(-50%); pointer-events: none;
  background: radial-gradient(ellipse at center, var(--glow-core) 0%, var(--glow-mid) 40%, transparent 70%);
  filter: blur(20px);
}

/* ------------------------------------------------------------ components -- */

.ts-badge {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 22px; padding: 3px 11px; border-radius: 20px;
  font-size: 11px; font-weight: 800; letter-spacing: -0.1px; line-height: 1.3;
}
.ts-badge--brand { background: var(--tint-brand); color: var(--tint-brand-ink); border: 1px solid var(--border-accent); }
.ts-badge--solid { background: var(--grad-brand); color: var(--on-brand); }

.card { background: var(--surface-card); border: 1px solid var(--border); border-radius: 18px; padding: 24px; }
.card.accent { border-color: var(--border-accent); position: relative; overflow: hidden; }
.card.accent::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--grad-progress); }

.btn-primary {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  height: 56px; padding: 0 30px; border-radius: 16px;
  background: var(--grad-brand); color: var(--on-brand);
  font-weight: 800; font-size: 16px; letter-spacing: -0.1px;
  box-shadow: var(--shadow-brand);
  transition: transform 0.14s ease, filter 0.14s ease;
}
.btn-primary:hover { color: var(--on-brand); filter: brightness(1.07); transform: translateY(-2px); }
.btn-primary--sm { height: 44px; padding: 0 20px; border-radius: 12px; font-size: 14px; }

/* ----------------------------------------------------------- store badge -- */

/* ⚠️ The two badge files are NOT in this repo and must not be redrawn. They are
   licensed trademark artwork; both companies require the supplied file be used
   unmodified. Expected as:

       img/badge-app-store.svg     Apple  — "Download on the App Store"
       img/badge-google-play.png   Google — "Get it on Google Play"

   Rules below are taken from the two guideline pages, not assumed:
     developer.apple.com/app-store/marketing/guidelines/
     partnermarketinghub.withgoogle.com/brands/google-play/google-play/lockups-icons-badges/

   ⚠️ **Apple's BLACK badge is required here, and the theme does not get a vote.**
   Black is the preferred badge, and Apple's rule is that it "must be used
   whenever one or more badges for other app platforms appear in the layout" —
   the white alternative is only for a lone badge. A Play badge sits beside it,
   so black it is on both ramps. Google separately forbids recolouring its badge
   at all ("Don't change the badge color"), so neither mark is theme-dependent.

   ⚠️ **Apple must stay first in the lineup** — also their rule, not a layout
   preference. Do not reorder these two.

   Minimum heights: Apple 40px on screen, Google 28px. 44px clears both.
   Clear space: both ask for a quarter of the badge height on every side, so the
   14px `gap` covers the 13px that the taller 52px badge needs. */
.store-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; }
/* The final CTA centres its column and the download band does not, so centring
   is scoped here rather than baked into .store-badges. The margin restores the
   breathing room the old .final__chips rule carried, which went with the chips. */
.final__inner .store-badges { justify-content: center; margin-top: 22px; }
.store-badge { display: inline-flex; border-radius: 8px; }

/* Height drives it and width follows, so each badge keeps its own aspect ratio
   rather than being stretched to a shared box.

   ⚠️ `max-width: none` is load-bearing. The base `img { max-width: 100% }` rule
   resolves against the flex parent, whose own width comes from this image — a
   circular constraint that both badges collapsed to 0px wide under, while still
   reporting the right height. */
.store-badge img { display: block; height: 44px; width: auto; max-width: none; }

/* ⚠️ Both badges render at the SAME height, and that is measured, not assumed.
   An earlier version made the Play badge taller on the widespread belief that
   Google bakes clear space into its artwork. Rasterising both supplied files and
   scanning for the ink bounding box says otherwise: each fills its viewBox edge
   to edge, padding 0 on all four sides. Apple is 119.66x40 (2.99:1), Google is
   238.96x70.87 (3.37:1), so at equal height the Play badge is simply wider —
   that is its longer lockup, not a size mismatch. Do not "fix" it. */

.store-badge:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex-shrink: 0;
  border-radius: 12px; border: 1px solid var(--border);
  background: var(--surface-glass); color: var(--text-muted);
  cursor: pointer; padding: 0;
  transition: color 0.14s ease, background 0.14s ease, border-color 0.14s ease;
}
.icon-btn:hover { color: var(--brand); border-color: var(--border-accent); background: var(--surface-glass-hover); }
.icon-btn svg { width: 20px; height: 20px; display: block; }

/* Sun shows in dark (tap for light); moon shows in light. */
.icon-btn .i-moon { display: none; }
:root[data-theme="light"] .icon-btn .i-sun { display: none; }
:root[data-theme="light"] .icon-btn .i-moon { display: block; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .icon-btn .i-sun { display: none; }
  :root:not([data-theme]) .icon-btn .i-moon { display: block; }
}

/* ---------------------------------------------------------------- to top -- */

/* ⚠️ `z-index: 40` sits below the mobile drawer's scrim (50) and the sticky nav
   (60) on purpose, so it can never float over an open menu.

   The show/hide copies the drawer's visibility trick rather than using opacity
   alone: an element left at `opacity: 0` is still focusable and still announced,
   so a keyboard visitor would tab into a button they cannot see. `visibility`
   transitions at `0s linear 0.18s` closed and `0s linear 0s` open so it leaves
   the tab order only after the fade has finished.

   The scroll itself inherits `scroll-behavior` from `html`, which the reduced
   motion block at the foot of this file already flips to `auto` — which is why
   site.js has no motion branch of its own. */
.to-top {
  position: fixed; right: clamp(16px, 3vw, 28px); bottom: clamp(16px, 3vw, 28px);
  z-index: 40;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 14px; padding: 0;
  background: var(--surface-glass); color: var(--text-primary);
  border: 1px solid var(--border-strong);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  cursor: pointer;
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0.18s,
              color 0.14s ease, background 0.14s ease, border-color 0.14s ease;
}
.to-top.is-visible {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0s,
              color 0.14s ease, background 0.14s ease, border-color 0.14s ease;
}
.to-top:hover { color: var(--brand); border-color: var(--border-accent); background: var(--surface-glass-hover); }
.to-top svg { width: 20px; height: 20px; display: block; }

/* --------------------------------------------------------------- app mark -- */

/* The app icon is raster art with a panel baked in behind the trophy — black in
   the dark file, white in the light one — so one asset cannot serve both ramps,
   and no filter turns either into the other without eating the orange with it.
   That is why this is an element with a background rather than an <img>.

   ⚠️ Swapped in CSS, never by JS on the toggle. The inline <head> script sets
   `data-theme` before first paint; a script that rewrote `src` would run after
   it and show one frame of the wrong panel — the same reason the theme itself
   is not loaded from site.js.

   ⚠️ The unsuffixed file is the DARK art. `app-icon.png` is the name the
   favicon, the og:image and the JSON-LD all point at, so it cannot be renamed
   to `-dark` without breaking references that live outside this repo.

   These URLs resolve against site.css, not the document, so `guides/` gets the
   same file as the root with no `../` variant to keep in sync. */
.appmark {
  display: block;
  background: url(img/app-icon.png) center / contain no-repeat;
}
.appmark--pro { background-image: url(img/app-icon-pro.png); }

:root[data-theme="light"] .appmark { background-image: url(img/app-icon-light.png); }
:root[data-theme="light"] .appmark--pro { background-image: url(img/app-icon-pro-light.png); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .appmark { background-image: url(img/app-icon-light.png); }
  :root:not([data-theme]) .appmark--pro { background-image: url(img/app-icon-pro-light.png); }
}

/* -------------------------------------------------------------------- nav -- */

.nav {
  position: sticky; top: 0; z-index: 60;
  background: var(--nav-bg);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
}
.nav__inner { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: var(--nav-h); }

.brand { display: inline-flex; align-items: center; gap: 10px; }
.brand:hover { color: inherit; }
.brand .appmark { width: 32px; height: 32px; border-radius: 8px; flex: none; }
/* The two footer lockups are smaller, and differ from each other: index has a
   brand column, the guides and legal pages have a single legal strip. */
.footer-brand .brand .appmark { width: 28px; height: 28px; }
.footer-legal .brand .appmark { width: 24px; height: 24px; border-radius: 6px; }
.brand__word { font-weight: 800; font-size: 18px; letter-spacing: -0.3px; color: var(--text-strong); }
.brand__word span { color: var(--brand); }

.nav__actions { display: flex; align-items: center; gap: clamp(10px, 1.6vw, 18px); }
.nav__links { display: flex; align-items: center; gap: clamp(14px, 2.4vw, 30px); }
.nav__link { color: var(--text-muted); font-size: 14px; font-weight: 600; }
.nav__link:hover { color: var(--text-primary); }

.nav__cta {
  display: inline-flex; align-items: center; height: 40px; padding: 0 18px;
  border-radius: 12px; background: var(--grad-brand); color: var(--on-brand);
  font-weight: 800; font-size: 14px; letter-spacing: -0.1px;
  box-shadow: var(--shadow-brand); transition: filter 0.14s ease;
  white-space: nowrap;
}
.nav__cta:hover { color: var(--on-brand); filter: brightness(1.07); }
.nav__cta--drawer { display: none; }

.nav__toggle { display: none; }
.nav__scrim { display: none; }

/* ------------------------------------------------------------------ hero -- */

.hero { position: relative; overflow: clip; scroll-margin-top: var(--nav-h); }
.hero__glow { top: -120px; width: min(900px, 120vw); height: 640px; }
.hero__inner {
  position: relative; display: flex; flex-wrap: wrap; align-items: center;
  gap: clamp(40px, 6vw, 72px);
  /* Less top than a `.section--pad` (64/9vw/130px) on purpose: those separate
     two content blocks, while this one sits under a 64px sticky nav that has
     already done that job. Note the copy column also picks up ~60px at desktop
     from `align-items: center` resolving it against the taller phone, so the
     gap under the nav reads larger than this number alone. */
  padding-top: clamp(24px, 3.5vw, 48px); padding-bottom: clamp(48px, 7vw, 90px);
}
.hero__copy { flex: 1 1 440px; min-width: 290px; }
.hero__title { margin: 0; font-weight: 800; font-size: clamp(38px, 6.4vw, 74px); line-height: 1.02; letter-spacing: -1.5px; color: var(--text-strong); text-wrap: balance; }
.hero__lead { margin: 22px 0 0; font-size: clamp(16px, 1.7vw, 20px); line-height: 1.5; max-width: 520px; color: var(--text-muted); }
.hero__actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 34px; }
.hero__facts { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 18px; margin-top: 26px; font-size: 13px; font-weight: 600; color: var(--text-muted); }
.hero__facts .rule { width: 1px; height: 16px; background: var(--border-strong); }
.hero__phone { flex: 1 1 320px; min-width: 260px; display: flex; justify-content: center; }
/* ⚠️ The width is capped by the height the viewport can spare, not by width
   alone. These captures are 554 × 1243 (ratio 0.446) where the previous set was
   0.547, so the old `clamp(260px, 34vw, 390px)` resolved to a phone 875px tall —
   cut off by the fold on any window shorter than about 980px.

   Multiplying the spare height by the aspect ratio turns it into a *width* cap,
   which leaves `height: auto` doing the scaling. ⚠️ **Do not write this as
   `max-height` instead.** Height is already `auto` but width is not, and a
   replaced element with one axis fixed and the other clamped squashes rather
   than scaling down.

   The 150px is the sticky nav plus the hero's own top padding plus a little air,
   so the phone lands inside the first screen with its base visible.

   `svh`, not `vh`: on a phone the URL bar makes `vh` the *largest* viewport,
   which is the one measurement guaranteed to overflow. The `vh` line above is
   the fallback for browsers without the newer units. */
.hero__phone img {
  animation: tsFloat 6s ease-in-out infinite;
  width: min(clamp(260px, 30vw, 350px), calc((100vh - 150px) * 0.446));
  width: min(clamp(260px, 30vw, 350px), calc((100svh - 150px) * 0.446));
  height: auto; display: block; border-radius: 26px;
}

/* ------------------------------------------------------------- proof bar -- */

.proof { background: var(--surface-tint); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.proof__inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: clamp(20px, 4vw, 56px); padding-top: 26px; padding-bottom: 26px; }
.proof__stat { display: flex; align-items: center; gap: 10px; }
.proof__num { font-size: 24px; font-weight: 800; color: var(--text-strong); letter-spacing: -0.4px; }
.proof__lbl { font-size: 13px; color: var(--text-muted); line-height: 1.2; }
.proof__div { width: 1px; height: 30px; background: var(--border-strong); }
.proof__glyphs { display: flex; align-items: center; gap: 12px; font-size: 24px; }

/* ----------------------------------------------------------- AI autofill -- */

.ai__flow { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: clamp(20px, 3vw, 44px); margin-top: clamp(40px, 6vw, 72px); text-align: left; }
.ai__shot { flex: 0 1 340px; min-width: 240px; position: relative; border-radius: 22px; overflow: hidden; border: 1px solid var(--border); box-shadow: var(--shadow-card); background: var(--photo-bg); }
.ai__shot img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; display: block; }
.ai__shot::after { content: ""; position: absolute; inset: 0; border: 2px solid rgba(255, 141, 40, 0.7); border-radius: 22px; pointer-events: none; box-shadow: inset 0 0 40px rgba(255, 141, 40, 0.2); }
.ai__scan { position: absolute; left: 14px; top: 14px; display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 800; color: var(--on-brand); background: var(--grad-brand); padding: 5px 10px; border-radius: 20px; z-index: 1; }
.ai__arrow { font-size: 28px; color: var(--brand); font-weight: 800; }
.ai__panel { flex: 0 1 360px; min-width: 260px; border-radius: 22px; padding: 20px; background: var(--surface-card); border: 1px solid var(--border-accent); position: relative; overflow: hidden; }
.ai__panel::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--grad-progress); }
.ai__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.ai__head b { font-size: 13px; font-weight: 800; color: var(--text-strong); }
.ai__rows { display: flex; flex-direction: column; gap: 12px; }
.ai__row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
.ai__row:last-child { padding-bottom: 0; border-bottom: none; }
.ai__row dt { font-size: 12px; color: var(--text-faint); font-weight: 600; margin: 0; }
.ai__row dd { font-size: 13px; color: var(--text-strong); font-weight: 700; margin: 0; text-align: right; }
.ai__note { margin: 28px auto 0; font-size: 13px; color: var(--text-faint); }

/* -------------------------------------------------------------- features -- */

.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.feature { border-radius: 20px; padding: 26px; background: var(--surface-card); border: 1px solid var(--border); }
.feature__icon { width: 50px; height: 50px; border-radius: 14px; background: var(--tint-brand); display: flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 18px; }
.feature h3 { margin: 0 0 8px; font-size: 19px; font-weight: 800; color: var(--text-strong); letter-spacing: -0.3px; }
.feature p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--text-muted); }

/* --------------------------------------------------------- how it works -- */

.step__num { font-size: 13px; font-weight: 800; color: var(--link); letter-spacing: 1px; }
.step h3 { margin-top: 10px; }
.step p { margin: 10px 0 0; font-size: 15px; color: var(--text-muted); }

/* ------------------------------------------------------- guides index -- */

/* ⚠️ The hub does NOT reuse `.related-grid`. That grid is `repeat(4, 1fr)` for
   four short titles, and its `a span` is the orange "Read the guide →" cue — a
   real sentence of description inherits both and comes out narrow and shouting.
   The hub needs cards that hold a line of prose, so it gets its own pair. */
.guide-index {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));
  gap: 14px; margin-top: 16px;
}
.guide-index a {
  display: block; background: var(--surface-card); border: 1px solid var(--border);
  border-radius: 16px; padding: 20px;
  font-weight: 700; font-size: 16px; color: var(--text-strong); line-height: 1.32;
  transition: border-color 0.14s ease;
}
.guide-index a:hover { border-color: var(--border-accent); }
.guide-index a span {
  display: block; margin-top: 9px;
  font-size: 13.5px; font-weight: 400; line-height: 1.5; color: var(--text-muted);
}

/* ⚠️ `.article h2` is 0,1,1 and would win against a bare class, which is how the
   first version of this shipped at 30px. Scoped to match it. */
.article h2.guide-group {
  font-size: 12px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-faint); margin: 36px 0 0;
}
.article h2.guide-group:first-of-type { margin-top: 30px; }

/* ------------------------------------------------------------- consent -- */

/* The analytics banner. ⚠️ It ships `hidden`, and `site.js` clears that only
   when no choice is stored — the `[hidden]` rule near the top of this file is
   what makes the attribute bite here.

   ⚠️ **A bottom strip, not a bottom-left card.** The card version covered the
   hero's "Start your shelf" button on any window around 800px tall: a fixed
   element sits on top of whatever the fold happens to land on, and on the
   landing page that is the primary call to action. Flush to the bottom edge it
   covers a ~60px band of whatever is already scrolled past. */
.consent {
  position: fixed; z-index: 60;
  left: 0; right: 0; bottom: 0;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 10px 22px;
  padding: 12px clamp(16px, 4vw, 28px);
  padding-bottom: max(12px, env(safe-area-inset-bottom));  /* home indicator */
  background: var(--surface-raised);
  border-top: 1px solid var(--border-strong);
  box-shadow: 0 -12px 34px rgba(0, 0, 0, 0.34);
}
.consent__text {
  margin: 0; font-size: 13px; line-height: 1.45; color: var(--text-muted);
  max-width: 68ch;
}
.consent__text a { color: var(--text-strong); font-weight: 600; }
.consent__actions { display: flex; gap: 10px; flex-shrink: 0; }
.consent__btn {
  font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  padding: 9px 18px; border-radius: 999px;
  min-height: 40px;                    /* a real target on a phone */
  background: transparent; color: var(--text-muted);
  border: 1px solid var(--border-strong);
  transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
}
.consent__btn:hover { background: var(--surface-glass-hover); color: var(--text-strong); }
.consent__btn--yes {
  background: var(--brand); color: var(--on-brand);
  border-color: transparent; box-shadow: var(--shadow-brand);
}
.consent__btn--yes:hover { filter: brightness(1.06); background: var(--brand); color: var(--on-brand); }

/* ⚠️ `.to-top` is bottom-right and the strip is flush to the bottom, so while
   the banner is up the button has to step over it. `:has()` keeps this in CSS
   rather than adding a class for `site.js` to remember to remove; a browser
   without it simply shows the two overlapping until the visitor answers. */
body:has(.consent:not([hidden])) .to-top { bottom: calc(clamp(16px, 3vw, 28px) + 76px); }

@media (max-width: 620px) {
  /* Stacked, with the buttons on their own row and reachable by thumb. */
  .consent { flex-direction: column; align-items: stretch; gap: 12px; }
  .consent__actions { justify-content: flex-end; }
}

/* ----------------------------------------------------------- screenshots -- */

.shot-rail {
  display: grid; grid-auto-flow: column; grid-auto-columns: min(260px, 68vw);
  gap: 22px; overflow-x: auto; padding: 10px 4px 22px;
  scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
  scrollbar-color: var(--surface-track) transparent;
}
.shot-rail::-webkit-scrollbar { height: 8px; }
.shot-rail::-webkit-scrollbar-thumb { background: var(--surface-track); border-radius: 4px; }
.shot-rail figure { margin: 0; scroll-snap-align: start; }
.shot-rail figcaption { margin-top: 8px; font-size: 13px; font-weight: 600; color: var(--text-muted); text-align: center; }
/* ⚠️ The screenshots are LIGHT-theme captures, so they stay light on the dark
   ramp — the inverse of what stood here while the shots were dark captures.
   That is correct: device art is a photograph of a screen, and a screen does
   not repaint itself because the page around it changed. They still need the
   frame below, or a bright rectangle floats off the dark background. */
.phone-shot { display: block; border-radius: 24px; background: var(--photo-bg); box-shadow: 0 0 0 1px var(--border-strong), var(--shadow-card); }
.bezel { border-radius: 42px; padding: 11px; background: var(--bezel-face); box-shadow: 0 40px 90px rgba(0, 0, 0, 0.45), inset 0 0 0 1px var(--bezel-ring); }
.bezel img { border-radius: 32px; display: block; width: 100%; height: auto; }

/* -------------------------------------------------------------- discover -- */

.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.feed-card { border-radius: 18px; overflow: hidden; border: 1px solid var(--border); background: var(--surface-card); }
.feed-card__img { aspect-ratio: 1 / 1; overflow: hidden; background: var(--photo-bg); }
.feed-card__img img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The diecast shots are product cutouts with a white background baked into the
   pixels (verified: opaque, no alpha). Sitting them at 86% on the dark photo
   plate leaves a white box floating inside a dark tile; a full-bleed white
   plate reads as a deliberate product shot instead. Theme-independent. */
.feed-card__img--pad { background: #ffffff; display: flex; align-items: center; justify-content: center; }
.feed-card__img--pad img { width: 100%; height: 100%; object-fit: contain; }
.feed-card__body { padding: 14px 16px 16px; }
.feed-card__title { font-size: 14.5px; font-weight: 800; color: var(--text-strong); letter-spacing: -0.2px; }
.feed-card__meta { margin-top: 4px; font-size: 12.5px; color: var(--text-muted); }

/* ----------------------------------------------------------------- ranks -- */

.ranks__inner { display: flex; flex-wrap: wrap; align-items: center; gap: clamp(36px, 6vw, 72px); }
.ranks__copy { flex: 1 1 380px; min-width: 280px; }
.ranks__panel { flex: 1 1 320px; min-width: 270px; display: flex; flex-direction: column; gap: 12px; }
.rank-chips { display: flex; gap: 12px; margin-top: 26px; flex-wrap: wrap; }
.rank-chip { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; padding: 8px 12px; border-radius: 12px; }
.rank-chip--idle { color: var(--text-primary); background: var(--surface-raised); border: 1px solid var(--border); }
.rank-chip--brand { color: var(--on-brand); background: var(--grad-brand); }
.rank-card { border-radius: 18px; padding: 18px; background: var(--surface-card); border: 1px solid var(--border-accent); position: relative; overflow: hidden; }
.rank-card::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--grad-progress); }
.rank-card__head { display: flex; align-items: center; gap: 14px; }
.rank-card__glyph { width: 52px; height: 52px; border-radius: 16px; background: var(--tint-brand); display: flex; align-items: center; justify-content: center; font-size: 26px; flex-shrink: 0; }
.rank-card__label { font-size: 10px; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-faint); font-weight: 700; }
.rank-card__name { font-size: 18px; font-weight: 800; color: var(--text-strong); }
.rank-card__track { height: 8px; border-radius: 8px; background: var(--surface-track); margin-top: 16px; overflow: hidden; }
.rank-card__fill { height: 100%; background: var(--grad-progress); border-radius: 8px; }
.rank-card__meta { display: flex; justify-content: space-between; gap: 12px; margin-top: 8px; font-size: 12px; color: var(--text-muted); }
.rank-card__desc { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }
/* The app puts "Progress to X · N% complete" ABOVE the bar and the "Reach X to
   rank up" line below it, so the label reads before the thing it labels. */
.rank-card__meta--top { margin-top: 18px; font-size: 12.5px; font-weight: 600; color: var(--text-primary); }
.rank-card__foot { margin-top: 9px; font-size: 12px; color: var(--text-faint); }
.badge-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.badge-tile { border-radius: 16px; padding: 16px 8px; background: var(--surface-card); border: 1px solid var(--border); text-align: center; }
.badge-tile__glyph { font-size: 26px; line-height: 1; }
.badge-tile__name { font-size: 11px; font-weight: 700; color: var(--text-primary); margin-top: 6px; }
.badge-tile__state { font-size: 10px; margin-top: 2px; color: var(--success); }
/* ⚠️ **No blanket `opacity` here, and that is the whole point.** It used to be
   `opacity: 0.55`, which dims the entire subtree — so the state label computed to
   roughly #51565e against the card and measured 2.28:1, and *no colour token
   could have fixed it*, because opacity is applied after the colour resolves.
   Lighthouse caught this where a contrast sweep over computed `color` cannot.

   Locked still reads as locked: the glyph is greyscale, the surface is flatter
   than an earned tile, and the label is the faint token — which is 4.8:1 on this
   card rather than 2.28. */
.badge-tile--locked { background: var(--surface-app); border-color: var(--border); }
.badge-tile--locked .badge-tile__glyph { filter: grayscale(1); opacity: 0.5; }
.badge-tile--locked .badge-tile__name { color: var(--text-muted); }
.badge-tile--locked .badge-tile__state { color: var(--text-faint); }

/* The full rank ladder was removed from the Shelf Rank section on 2026-08-09
   (product owner). tool/sync_ranks.py no longer emits it, so the `.rank-ladder`
   and `.rung` rules that lived here have nothing to style. */

/* --------------------------------------------------------------- pricing -- */

.billing { display: inline-flex; gap: 4px; margin-top: 26px; padding: 5px; border-radius: 14px; background: var(--surface-raised); border: 1px solid var(--border); width: 268px; max-width: 90vw; }
.billing button { flex: 1; min-height: 40px; border: none; border-radius: 11px; cursor: pointer; font-family: var(--font); font-weight: 700; font-size: 13px; background: transparent; color: var(--text-muted); transition: background 0.2s ease, color 0.2s ease; }
.billing button.is-active { font-weight: 800; background: var(--grad-brand); color: var(--on-brand); }

.price-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; align-items: start; }
/* One plan, one column — TASK-368. A two-column grid holding a single child
   leaves an empty half beside it, which reads as a card that failed to load;
   the app repo paid for exactly this once already (`.ts-stage--one`, a public
   collection page drawing half a plate of empty shelf beside one tile). The
   mobile rule below is `1fr` and is correct for both cases. */
.price-grid--single { grid-template-columns: minmax(0, 420px); justify-content: center; }
.plan { border-radius: 22px; padding: 28px; background: var(--surface-card); border: 1px solid var(--border); }
.plan--pro { border: 1px solid var(--brand); position: relative; overflow: hidden; box-shadow: 0 24px 60px rgba(255, 141, 40, 0.14); }
.plan--pro::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--grad-brand); }
.plan__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.plan__name { font-size: 15px; font-weight: 800; color: var(--text-strong); }
.plan__price { margin: 12px 0 4px; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.plan__price b { font-size: clamp(34px, 4.6vw, 44px); font-weight: 800; color: var(--text-strong); letter-spacing: -1px; }
.plan__price span { font-size: 14px; color: var(--text-muted); }
.plan__desc { margin: 0 0 20px; font-size: 13px; color: var(--text-muted); }
.plan__cta { display: flex; align-items: center; justify-content: center; min-height: 48px; border-radius: 14px; font-weight: 800; font-size: 14px; }
.plan__cta--ghost { background: var(--surface-glass); color: var(--text-primary); border: 1px solid var(--border-strong); }
.plan__cta--ghost:hover { color: var(--text-primary); background: var(--surface-glass-hover); }
.plan__cta--brand { background: var(--grad-brand); color: var(--on-brand); box-shadow: var(--shadow-brand); }
.plan__cta--brand:hover { color: var(--on-brand); filter: brightness(1.07); }
.plan__rule { height: 1px; background: var(--border); margin: 22px 0; }
.plan__feats { display: flex; flex-direction: column; gap: 12px; }
.plan__feat { display: flex; gap: 10px; font-size: 13.5px; line-height: 1.45; color: var(--text-primary); }
.plan__feat i { font-style: normal; flex-shrink: 0; }
.plan__feat--free i { color: var(--success); }
.plan__feat--pro i { color: var(--brand); }

/* ---------------------------------------------------------------- guides -- */

.guide-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.guide-card { display: block; transition: border-color 0.14s ease, transform 0.14s ease; }
.guide-card:hover { border-color: var(--border-accent); transform: translateY(-2px); }
.guide-card h3 { font-size: 16.5px; line-height: 1.3; }
.guide-card p { margin: 8px 0 14px; font-size: 14px; color: var(--text-muted); }
.guide-card__more { font-size: 13px; font-weight: 800; color: var(--link); }

/* ------------------------------------------------------------------- FAQ -- */
/* <details name="faq"> gives native one-open-at-a-time and needs no JS, so the
   answers are in the DOM for the FAQPage JSON-LD to be honest about. */

.faq-list { display: flex; flex-direction: column; gap: 12px; }
.faq-item { border-radius: 16px; background: var(--surface-card); border: 1px solid var(--border); overflow: hidden; }
.faq-item > summary {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 18px 20px; min-height: 56px; cursor: pointer;
  font-size: 15px; font-weight: 700; color: var(--text-strong);
  list-style: none;
}
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary::after {
  content: ""; flex-shrink: 0; width: 11px; height: 11px;
  border-right: 2px solid var(--brand); border-bottom: 2px solid var(--brand);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.2s ease;
}
.faq-item[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.faq-item p { margin: 0; padding: 0 20px 20px; font-size: 14.5px; line-height: 1.6; color: var(--text-muted); }

/* -------------------------------------------------------------- download -- */

.download__inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 24px; }
.download__id { display: flex; align-items: center; gap: 16px; }
.download__id .appmark { width: 56px; height: 56px; border-radius: 14px; flex: none; }
.download__id h2 { font-size: 22px; letter-spacing: -0.4px; }
.download__id p { margin: 4px 0 0; font-size: 14px; color: var(--text-muted); }
.download__chips { display: flex; flex-wrap: wrap; gap: 12px; }

/* ------------------------------------------------------------- final CTA -- */

.final { position: relative; overflow: clip; border-top: 1px solid var(--border); scroll-margin-top: var(--nav-h); }
.final__glow { bottom: -200px; width: min(800px, 120vw); height: 500px; filter: blur(10px); }
.final__inner { max-width: 760px; margin: 0 auto; padding: clamp(64px, 10vw, 150px) clamp(20px, 5vw, 40px); text-align: center; position: relative; }
.final__icon { width: 72px; height: 72px; border-radius: 18px; margin: 0 auto 24px; display: block; box-shadow: 0 16px 40px rgba(255, 141, 40, 0.3); }
.final__inner .h2 { font-size: clamp(32px, 5.5vw, 64px); line-height: 1.02; letter-spacing: -1.4px; }
.final__inner .lead { margin: 18px auto 0; max-width: 480px; font-size: clamp(16px, 1.8vw, 20px); line-height: 1.5; }
.final__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px; margin-top: 34px; }
.final__chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px; margin-top: 20px; }

/* ---------------------------------------------------------------- footer -- */

.site-footer { background: var(--surface-tint); border-top: 1px solid var(--border); padding: 48px 0 32px; }
/* Three children since the "More Guides" column was dropped. The last track is
   the widest because "Share Your Collection Online" is the longest label in the
   footer and must not wrap. */
/* Four columns since the Support one was added for the stores' required
   Support URL. The narrow-width rule below already collapses this to two with
   the brand spanning, so it needs no second breakpoint. */
.footer-cols { display: grid; grid-template-columns: 1.4fr 0.9fr 1.2fr 1.1fr; gap: 32px; }
.footer-brand p { margin: 12px 0 0; font-size: 13px; color: var(--text-faint); max-width: 260px; }
.site-footer h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-faint); margin-bottom: 12px; }
.site-footer ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.site-footer li a { font-size: 13.5px; color: var(--text-muted); font-weight: 600; }
.site-footer li a:hover { color: var(--text-primary); }
.footer-legal {
  margin-top: 34px; padding-top: 20px; border-top: 1px solid var(--border);
  display: flex; flex-wrap: wrap; gap: 14px; align-items: center; justify-content: space-between;
  font-size: 12.5px; color: var(--text-faint);
}
.footer-legal nav { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; }
.footer-legal a { color: var(--text-muted); font-weight: 600; }

/* ------------------------------------------------------- guide articles -- */

.article { max-width: 760px; margin: 0 auto; padding: clamp(36px, 6vw, 72px) clamp(20px, 5vw, 40px) 24px; }

/* --- guide hero: the home page's treatment, at article scale --- */
.guide-hero { position: relative; overflow: clip; margin: 0 -12px; padding: 4px 12px clamp(18px, 3vw, 26px); }
/* :not(.glow) matters — `.guide-hero > *` is more specific than `.glow`, so a
   blanket rule here overrides its `position: absolute` and the glow becomes a
   460px-tall block in normal flow. */
.guide-hero > :not(.glow) { position: relative; }
.guide-hero .glow { top: -300px; width: min(680px, 130vw); height: 460px; }
.guide-hero .lede { margin-top: 20px; }

/* --- key takeaways --- */
.takeaways { margin-top: clamp(26px, 4vw, 36px); padding: 22px 24px; }
/* `.article h2` is declared later at equal specificity, so this needs to
   out-specify it rather than rely on order. */
.article .takeaways h2 { margin: 0 0 14px; font-size: 11px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; color: var(--text-faint); }
.article .takeaways ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; font-size: 15.5px; }
.article .takeaways li { display: flex; gap: 13px; align-items: flex-start; line-height: 1.5; color: var(--text-primary); }
.takeaways li::before {
  content: ""; flex: none; width: 7px; height: 7px; margin-top: 9px;
  border-radius: 50%; background: var(--grad-brand);
}

/* --- pro tips as a card grid --- */
.tip-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 22px; counter-reset: tip; }
.tip {
  counter-increment: tip; position: relative;
  background: var(--surface-card); border: 1px solid var(--border); border-radius: 16px;
  padding: 42px 18px 18px; font-size: 14.5px; line-height: 1.55; color: var(--text-muted);
}
.tip::before {
  content: counter(tip); position: absolute; top: 16px; left: 18px;
  width: 22px; height: 22px; border-radius: 7px;
  background: var(--tint-brand); border: 1px solid var(--border-accent);
  color: var(--tint-brand-ink); font-size: 11px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}

/* ============================================================
   LEGAL DOCUMENTS  (privacy.html, terms.html)
   Generated by tool/sync_legal.py from trophyshelf_app/web_public/.
   ============================================================ */

.legal { max-width: 780px; }

/* The on-this-page index card was removed from both legal pages on 2026-08-09
   (product owner). `tool/sync_legal.py` strips it at generation time, so there
   is nothing left for a `.legal-toc` rule to style. */

.legal-section { margin-top: clamp(34px, 5vw, 48px); }
/* The small uppercase label above each heading — its own line, not a chip. */
.legal-eyebrow { font-size: 11px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; color: var(--brand); }
.article .legal-section h2 { margin-top: 8px; }

/* The processors table. Wide content scrolls inside its own box — the page body
   must never scroll horizontally. */
.legal-scroll { overflow-x: auto; margin-top: 20px; -webkit-overflow-scrolling: touch; }
.legal-scroll table { width: 100%; min-width: 460px; border-collapse: collapse; font-size: 14.5px; }
.legal-scroll th, .legal-scroll td { text-align: left; vertical-align: top; padding: 14px 16px; border-bottom: 1px solid var(--border); }
.legal-scroll tr:first-child th { font-size: 11px; font-weight: 800; letter-spacing: 1.2px; text-transform: uppercase; color: var(--text-faint); }
.legal-scroll th { color: var(--text-strong); font-weight: 700; white-space: nowrap; }
.legal-scroll td { color: var(--text-muted); }
.legal-why { display: block; margin-top: 6px; font-size: 13px; color: var(--text-faint); }

/* The footer's social row. `rel="me"` is the half that makes `sameAs` mean
   something: the schema claims the profiles are ours, and this is the link a
   verifier follows back. Text rather than brand marks — the app's About screen
   holds real Instagram and YouTube SVGs, and a footer does not earn the
   trademark risk of a redrawn one. */
.footer-social { display: flex; gap: 14px; margin-top: 14px; }
.footer-social a { font-size: 14px; color: var(--text-muted); text-decoration: none; }
.footer-social a:hover, .footer-social a:focus-visible { color: var(--brand); text-decoration: underline; }

/* --- Comparison tables and lists (alternatives/) ---
   Wide content scrolls inside its own box, the rule .legal-scroll already
   follows: the page body must never scroll horizontally. The caption is
   visually hidden rather than absent — a screen reader announcing "CLZ
   compared with TrophyShelf" is what makes a three-column grid of bare
   cells navigable, and sighted readers already have the h2 above it. */
.cmp-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 22px 0 4px; border: 1px solid var(--border); border-radius: 14px; background: var(--surface-card); }
.cmp { width: 100%; min-width: 540px; border-collapse: collapse; font-size: 14.5px; }
.cmp__cap { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
.cmp th, .cmp td { text-align: left; vertical-align: top; padding: 13px 16px; border-bottom: 1px solid var(--border); }
.cmp thead th { font-size: 11px; font-weight: 800; letter-spacing: 1.2px; text-transform: uppercase; color: var(--text-muted); background: var(--surface-raised); }
.cmp thead th.is-us { color: var(--brand); }
.cmp tbody th { color: var(--text-strong); font-weight: 700; width: 27%; }
.cmp tbody td { color: var(--text-muted); }
.cmp tbody td.is-us { color: var(--text-primary); }
.cmp tbody tr:last-child th, .cmp tbody tr:last-child td { border-bottom: none; }

.article .cmp-list { list-style: none; margin: 18px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.article .cmp-list li { position: relative; padding: 13px 16px 13px 40px; background: var(--surface-card); border: 1px solid var(--border); border-radius: 12px; color: var(--text-muted); }
.article .cmp-list li::before { content: ""; position: absolute; left: 17px; top: 20px; width: 7px; height: 7px; border-radius: 2px; background: var(--brand); }
.article .cmp-list li b { color: var(--text-strong); }

.article .card.cmp-verdict { margin: 26px 0 8px; }
.article .card.cmp-verdict h2 { margin-top: 0; font-size: 19px; }
.article .card.cmp-verdict p + p { margin-top: 10px; }

/* --- Community Guidelines: the Do / Don't lists --- */
.article .rule-list { list-style: none; margin: 18px 0 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.article .rule {
  display: flex; align-items: flex-start; gap: 12px; margin: 0;
  padding: 16px 18px; border-radius: 16px;
  background: var(--surface-card); border: 1px solid var(--border);
}
.rule__mark {
  flex: none; width: 22px; height: 22px; margin-top: 1px; border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 800;
}
.rules--do .rule__mark { background: rgba(76, 175, 80, 0.14); color: var(--success); }
.rules--do .rule { border-color: rgba(76, 175, 80, 0.28); }
/* No --danger token exists on this site, so the Don't tint is declared here
   rather than invented as a global. */
.rules--dont .rule__mark { background: rgba(226, 88, 66, 0.14); color: #e25842; }
.rules--dont .rule { border-color: rgba(226, 88, 66, 0.26); }
.rule__name { display: block; font-size: 14.5px; font-weight: 800; color: var(--text-strong); }
.article .rule__desc { margin: 4px 0 0; font-size: 13.5px; line-height: 1.45; color: var(--text-muted); }

.legal-report { margin-top: clamp(30px, 4vw, 42px); padding: 22px 24px; }
.article .legal-report h2 { margin: 0 0 8px; font-size: 17px; }
.article .legal-report p { margin: 0; font-size: 14.5px; color: var(--text-muted); }

@media (max-width: 640px) {
  .article .rule-list { grid-template-columns: 1fr; }
}

/* An unresolved {{TODO}} must be impossible to miss — these are facts only the
   product owner can supply, and a page that ships with one is a page with a
   blank where its legal entity should be. */
.legal-todo {
  background: var(--tint-warn, rgba(240, 158, 60, 0.16));
  color: var(--text-strong);
  border: 1px dashed var(--border-accent);
  border-radius: 6px; padding: 1px 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.86em;
}


/* --- per-guide FAQ: the home page's accordion, no JS --- */
.guide-faq { margin-top: clamp(36px, 5vw, 52px); }
.guide-faq .faq-list { margin-top: 18px; }
.guide-faq .faq-item > summary { font-size: 15px; }
/* Same trap again: `.article p` is declared later at equal specificity, so
   without the extra class the answers inherit its 16.5px and 16px top margin. */
.article .faq-item p { margin: 0; padding: 0 20px 20px; font-size: 14.5px; line-height: 1.6; color: var(--text-muted); }
.breadcrumbs { font-size: 13px; font-weight: 600; color: var(--text-faint); display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px; }
.breadcrumbs a { color: var(--text-muted); }
.article .meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 13px; font-weight: 600; color: var(--text-faint); margin-top: 16px; }
.article .lede { max-width: none; }
.article h2 { font-size: clamp(23px, 3vw, 30px); margin-top: 44px; }
.article h3 { margin-top: 28px; }
.article p { color: var(--text-muted); font-size: 16.5px; margin: 16px 0 0; text-wrap: pretty; }
.article p strong, .article li strong { color: var(--text-primary); }
.article ul { color: var(--text-muted); font-size: 16.5px; margin: 16px 0 0; padding-left: 22px; display: flex; flex-direction: column; gap: 8px; }
.steps { list-style: none; counter-reset: step; margin: 24px 0 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.steps li { counter-increment: step; background: var(--surface-card); border: 1px solid var(--border); border-radius: 16px; padding: 18px 20px 18px 62px; position: relative; color: var(--text-muted); font-size: 15.5px; }
/* The source markup is `<li><b>Title</b>Body</li>` with nothing between them,
   so the two run together as one sentence unless the title is made a block. */
.steps li b { display: block; margin-bottom: 5px; color: var(--text-strong); font-size: 15px; }
.steps li::before {
  content: counter(step); position: absolute; left: 18px; top: 17px;
  width: 28px; height: 28px; border-radius: 9px; background: var(--grad-brand);
  color: var(--on-brand); font-weight: 800; font-size: 14px;
  display: flex; align-items: center; justify-content: center;
}
.guide-figure { margin: 34px auto 0; text-align: center; }
/* The bezel is a block, so it has to be sized here — left to the article width
   it frames a 280px screenshot inside a 760px surround. */
.guide-figure .bezel { width: min(300px, 78vw); margin: 0 auto; padding: 9px; border-radius: 34px; }
.guide-figure .bezel img { width: 100%; border-radius: 26px; }
.guide-figure img { width: min(280px, 76vw); }
.guide-figure figcaption { font-size: 13px; color: var(--text-faint); font-weight: 600; margin-top: 8px; }
.cta-box { margin-top: 44px; text-align: center; padding: 36px 26px; }
.cta-box p { margin: 10px auto 22px; max-width: 440px; color: var(--text-muted); }
.related { max-width: 760px; margin: 0 auto; padding: 20px clamp(20px, 5vw, 40px) clamp(56px, 7vw, 90px); }
.related-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 18px; }
.related-grid a { display: block; background: var(--surface-card); border: 1px solid var(--border); border-radius: 16px; padding: 18px; font-weight: 700; font-size: 14.5px; color: var(--text-primary); line-height: 1.4; transition: border-color 0.14s ease; }
.related-grid a:hover { border-color: var(--border-accent); color: var(--text-strong); }
.related-grid a span { display: block; margin-top: 8px; font-size: 12.5px; font-weight: 700; color: var(--link); }

/* ============================================================================
   RESPONSIVE
   Neither source design had a ladder — website/site.css carried one query and
   the landing build carried none. Everything below is new.
   ========================================================================== */

/* ---- laptop ---- */
@media (max-width: 1024px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .guide-grid { grid-template-columns: repeat(2, 1fr); }
  .related-grid { grid-template-columns: repeat(2, 1fr); }
}

/* The article column is 760px, so its tip grid crowds well before the page
   grids do — it needs its own step down rather than the shared ladder. */
@media (max-width: 820px) {
  .tip-grid { grid-template-columns: repeat(2, 1fr); }
  .tip-grid > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  .tip-grid { grid-template-columns: 1fr; }
  .takeaways { padding: 18px; }
}

/* ---- small laptop / large tablet ---- */
@media (max-width: 900px) {
  /* A 3-up grid at 2 columns strands its third card; span it instead. */
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  .footer-cols { grid-template-columns: repeat(2, 1fr); gap: 28px; }
  .footer-brand { grid-column: 1 / -1; }
}

/* ---- pricing goes single-column early: two 280px columns leave ~235px of
        content and the Pro feature rows wrap badly ---- */
@media (max-width: 720px) {
  .price-grid { grid-template-columns: 1fr; }
  .plan--pro { order: -1; }
}

/* ---- tablet & below: the mobile nav ---- */
@media (max-width: 768px) {
  .nav__toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 48px; height: 48px; border-radius: 12px; padding: 0;
    border: 1px solid var(--border); background: var(--surface-glass);
    color: var(--text-primary); cursor: pointer;
  }
  /* 48 is the app's own hit target (TsLayout.hitTarget). */
  .icon-btn { width: 48px; height: 48px; }
  .nav__toggle svg { width: 22px; height: 22px; display: block; }
  .nav__toggle .i-close { display: none; }
  .nav.is-open .nav__toggle .i-menu { display: none; }
  .nav.is-open .nav__toggle .i-close { display: block; }

  .nav__inner > .nav__actions > .nav__cta { display: none; }
  .nav__cta--drawer { display: inline-flex; justify-content: center; height: 52px; margin-top: 10px; }

  /* `position: absolute` inside .nav, not fixed: .nav carries a backdrop-filter,
     which establishes a containing block for fixed descendants — a fixed drawer
     would anchor to the bar instead of the viewport. Absolute against the sticky
     bar gives the same result and cannot be broken by that. */
  .nav__links {
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 8px clamp(20px, 5vw, 40px) 24px;
    background: var(--surface-tint);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.28);
    transform: translateY(-10px); opacity: 0; visibility: hidden;
    /* visibility flips instantly on open and is DELAYED on close. A plain
       `visibility 0.18s` makes it still-hidden for the first frame after the
       class lands, and a visibility:hidden element cannot take focus — so the
       drawer would open with focus left behind on the page. */
    transition: transform 0.18s ease, opacity 0.18s ease, visibility 0s linear 0.18s;
    max-height: calc(100dvh - var(--nav-h)); overflow-y: auto;
  }
  .nav.is-open .nav__links {
    transform: none; opacity: 1; visibility: visible;
    transition: transform 0.18s ease, opacity 0.18s ease, visibility 0s linear 0s;
  }
  .nav__link { display: flex; align-items: center; min-height: 52px; font-size: 16px; border-bottom: 1px solid var(--border); }

  /* Sibling of .nav, so it is outside that containing block. */
  .nav__scrim {
    display: block; position: fixed; inset: var(--nav-h) 0 0; z-index: 50;
    background: rgba(0, 0, 0, 0.45); opacity: 0; visibility: hidden;
    transition: opacity 0.18s ease, visibility 0s linear 0.18s;
  }
  .nav.is-open + .nav__scrim { opacity: 1; visibility: visible; transition: opacity 0.18s ease, visibility 0s linear 0s; }

  /* Touch targets. The app's own standard is 48 (TsLayout.hitTarget). */
  .site-footer li a,
  .footer-legal a { display: inline-flex; align-items: center; min-height: 48px; }
  .guide-card { padding: 20px; }
  .faq-item > summary { min-height: 60px; }
  .billing button { min-height: 48px; }

  /* The hero phone shrinks and stops floating — it is also the reduced-motion
     answer on the surface where the animation is most obvious. */
  .hero__phone img { animation: none; width: min(300px, 74vw); }

  /* The dividers are separate elements in a wrapping row: once it wraps they
     land at row starts and ends. Grid the stats instead. */
  .proof__div { display: none; }
  .proof__inner { display: grid; grid-template-columns: repeat(2, auto); justify-content: center; gap: 20px 36px; }
  .proof__glyphs { grid-column: 1 / -1; justify-content: center; font-size: 22px; }

  .ai__arrow { transform: rotate(90deg); }
  .download__inner { justify-content: flex-start; }
}

/* ---- phone ---- */
@media (max-width: 600px) {
  .grid-3 { grid-template-columns: 1fr; }
  .guide-grid { grid-template-columns: 1fr; }
  .related-grid { grid-template-columns: 1fr; }
  .footer-cols { grid-template-columns: 1fr; gap: 26px; }
  .footer-brand { grid-column: auto; }
  .footer-brand p { max-width: none; }
  .footer-legal { justify-content: flex-start; gap: 8px 14px; }
  .shot-rail { grid-auto-columns: min(240px, 72vw); gap: 16px; }
}

@media (max-width: 480px) {
  .grid-4 { grid-template-columns: 1fr; }
  /* clamp()'s 38px floor is still tight in a ~335px box at -1.5px tracking. */
  .hero__title { font-size: clamp(30px, 8.6vw, 40px); letter-spacing: -1px; }
  .hero__actions .btn-primary { flex: 1 1 100%; }
  /* Same defect the proof bar had: separators are elements in a wrapping row,
     so once it wraps one is left dangling at a line end. */
  .hero__facts .rule { display: none; }
  .hero__facts { gap: 6px 18px; }
  .final__actions .btn-primary { width: 100%; }
  .plan { padding: 22px; }
  .badge-tiles { gap: 8px; }
  .badge-tile { padding: 14px 4px; }
  .badge-tile__name { font-size: 10px; }
  .ai__panel, .ai__shot { min-width: 0; width: 100%; }
}

@media (max-width: 380px) {
  .proof__inner { grid-template-columns: 1fr; }
  .badge-tile__glyph { font-size: 22px; }
  .download__id .appmark { width: 44px; height: 44px; }
}

/* ---- large desktop: nothing grows past the clamp ceilings, but keep the
        stage from looking stranded in a very wide viewport ---- */
@media (min-width: 1600px) {
  .wrap { max-width: 1280px; }
}

/* ---- motion ---- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .hero__phone img { animation: none; }
}
