/* contracts/TOKENS.md is the source of truth — the only colours Nightshade uses.
   Light is the bare :root so it is never undefined; dark is layered on top and must win
   both by system preference and by explicit toggle.

   Doctrine notes that are load-bearing and must not be "tidied away":
   - Every colour here appears in contracts/TOKENS.md. A colour that is not in that file
     does not exist (TOKENS.md §Core palette). --on-accent is the one
     addition and it is #FFFFFF, which is the `text` value in dark mode; it exists so the
     CTA does not resolve to black in light mode. See the .cta contrast note below.
   - Spacing is an 8pt rhythm. 02-brand/mobile-ui-layout.md permits design-system spacing
     tokens and bans device-shaped magic numbers; ad-hoc rem values were neither.
   - Tap targets are 44px minimum (mobile-ui-layout.md §No device-shaped magic numbers),
     which on the web means nav and footer links get real padding, not bare text.
   - Safe areas: the web equivalent of .safeAreaInset is env(safe-area-inset-*) plus
     viewport-fit=cover (mobile-ui-layout.md §Reserve chrome with .safeAreaInset). The
     gutter reserves the band; nothing hand-computes an inset. */
:root {
  color-scheme: light dark;

  --bg: #ffffff;
  --card: #f2f2f7;
  --card2: #e5e5ea;
  --text: #000000;
  --text2: #6e6e73;   /* light mode: #8e8e93 is only 3.3:1 on white — fails AA */
  --accent: #ff2400;   /* Scarlet — interactive, Stage 2-3 */
  --accent2: #c25e00;  /* Dark Orange Crush — warnings only, NEVER interactive */
  --ok: #1b4d2e;       /* Dark Forest — positive states */
  --on-accent: #ffffff; /* fixed in both modes: the .cta contrast maths assumes white */

  /* 8pt rhythm. Nothing outside this scale. */
  --s1: 0.25rem;   /* 4  */
  --s2: 0.5rem;    /* 8  */
  --s3: 0.75rem;   /* 12 */
  --s4: 1rem;      /* 16 */
  --s5: 1.5rem;    /* 24 */
  --s6: 2rem;      /* 32 */
  --s7: 3rem;      /* 48 */
  --s8: 4rem;      /* 64 */

  /* Type scale. Body is 16px/1.6; every other size is a step off it. */
  --fs-xs: 0.8125rem;  /* 13 */
  --fs-sm: 0.875rem;   /* 14 */
  --fs-md: 1rem;       /* 16 */
  --fs-lg: 1.125rem;   /* 18 */
  --fs-xl: 1.5rem;     /* 24 */
  --fs-h1: clamp(2.25rem, 6vw, 3.5rem);
  /* 20px. Its own step on purpose — see .cta: this one is a WCAG floor, not a taste
     decision, and must never be folded back into --fs-lg (18px, which is UNDER the
     18.66px large-text threshold the CTA's 3.82:1 contrast depends on). */
  --fs-cta: 1.25rem;

  --maxw: 68rem;
  --radius: 14px;
  --tap: 2.75rem;      /* 44px — the platform minimum, not a device-shaped number */

  /* One gutter, computed once, respecting the safe area on notched phones in landscape.
     The 0px fallbacks are load-bearing: a bare env() on a browser that does not know the
     keyword makes the whole custom property invalid at computed-value time, which would
     silently drop the gutter to zero rather than fall back to --s5. */
  --gutter-l: max(var(--s5), env(safe-area-inset-left, 0px));
  --gutter-r: max(var(--s5), env(safe-area-inset-right, 0px));
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #000000;
    --card: #1c1c1e;
    --card2: #2c2c2e;
    --text: #ffffff;
    --text2: #8e8e93;  /* 6.4:1 on black — passes */
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #000000;
  --card: #1c1c1e;
  --card2: #2c2c2e;
  --text: #ffffff;
  --text2: #8e8e93;
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: var(--fs-md)/1.6 -apple-system, BlinkMacSystemFont, system-ui, "Segoe UI",
        Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* TOKENS.md ships no bundled font on any platform, so the web surface uses the same
   system stack the apps do. Do not swap in a webfont: doctrine bans third-party CDNs
   and the site's CSP would block the request anyway. */

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding-left: var(--gutter-l);
  padding-right: var(--gutter-r);
}

/* No anchor may fall through to the user agent's default link blue. #0000EE (and #9E9EFF
   in dark mode) is not a palette colour — TOKENS.md is explicit that a colour not in it
   does not exist — and it is what made the "Status and known issues" / "Privacy" footer
   links on every lander render as an unstyled page. Components below override this; the
   point is that nothing is left to the UA. */
a { color: var(--text); }

/* Keyboard users get the same affordance mouse users get from :hover. */
:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--s1);
}

/* The skip link is a control like any other, and it was the one chrome control that never
   got the 44px floor: padding alone left it 41.6px tall in every viewport and both schemes.
   It is only ever reached by keyboard or switch control, which is exactly the population the
   minimum exists for, so it gets the same min-height every other target has rather than an
   exemption. inline-flex + center is the same construction .store and `td a` use. */
.skip {
  position: absolute; left: -9999px; top: var(--s2);
  display: inline-flex; align-items: center; min-height: var(--tap);
  background: var(--card); color: var(--text);
  padding: var(--s2) var(--s4); border-radius: var(--radius);
  text-decoration: none; z-index: 10;
}
.skip:focus { left: var(--s4); }

header.site { border-bottom: 1px solid var(--card2); }
header.site .bar {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--s2);
  padding-top: var(--s2); padding-bottom: var(--s2);
}
header.site a.brand {
  font-weight: 700; letter-spacing: -0.02em; color: var(--text);
  text-decoration: none; font-size: var(--fs-lg);
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: var(--tap);
}
header.site a.brand img { width: 1.5rem; height: 1.5rem; border-radius: var(--s1); }
header.site nav { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s4); }
header.site nav a {
  color: var(--text2); text-decoration: none; font-size: var(--fs-sm);
  display: inline-flex; align-items: center; min-height: var(--tap);
}
header.site nav a:hover { color: var(--text); }
/* The current page is never signalled by colour alone (TOKENS.md §3.9): the underline
   and aria-current carry it too. */
header.site nav a[aria-current="page"] {
  color: var(--text); text-decoration: underline; text-underline-offset: 0.35em;
}

.hero { padding: var(--s8) 0 var(--s7); }
.hero h1 {
  font-size: var(--fs-h1); line-height: 1.08;
  letter-spacing: -0.03em; margin: 0 0 var(--s4);
}
.hero p.lede {
  font-size: var(--fs-lg); color: var(--text2); max-width: 40rem;
  margin: 0 0 var(--s6);
}
.hero .badge + h1 { margin-top: var(--s4); }

.applead { display: flex; align-items: center; gap: var(--s4); margin-bottom: var(--s5); }
.applead img { width: 4rem; height: 4rem; border-radius: 22.5%; flex: none; }
.applead .names { display: flex; flex-direction: column; gap: var(--s1); }
.applead .names strong { font-size: var(--fs-lg); letter-spacing: -0.01em; }
/* This badge quotes the literal Home Screen label ("Drive NS", the Nightshade initials), so
   it keeps its real casing — uppercasing it would print a string the icon grid never shows. */
.applead .names .badge { text-transform: none; letter-spacing: 0; align-self: flex-start; }

.badge {
  display: inline-block; padding: var(--s1) var(--s3); border-radius: 999px;
  background: var(--card); color: var(--text2);
  font-size: var(--fs-xs); letter-spacing: 0.06em; text-transform: uppercase;
}

/* White on Scarlet is 3.82:1 — under the 4.5:1 AA floor for normal text, but over the
   3:1 floor for large text. So the CTA must STAY large and bold: >=18.66px at weight 700.
   Measured, not assumed: at var(--fs-lg) (18px) a headless-Chrome contrast pass flags this
   button as a real AA failure, because 18px does not reach the large-text threshold and it
   is then judged against 4.5:1. --fs-cta exists solely to keep it above that line. */
.cta {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--on-accent);
  padding: var(--s3) var(--s5); border-radius: var(--radius);
  min-height: var(--tap);
  text-decoration: none; font-weight: 700; font-size: var(--fs-cta);
}
.cta:hover { filter: brightness(1.08); }

/* Store buttons. Secondary to the CTA, still a 44px target. */
.stores { display: flex; flex-wrap: wrap; gap: var(--s3); margin: var(--s5) 0 0; }
.store {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: var(--tap); padding: var(--s2) var(--s4);
  border: 1px solid var(--card2); border-radius: var(--radius);
  background: var(--card); color: var(--text); text-decoration: none;
  font-size: var(--fs-sm); font-weight: 600;
}
.store:hover { border-color: var(--accent); }

/* Hero media. The 10s product videos land later; until then this slot holds the poster
   still (growth/branding/videos/out/<v>/hero.jpg) or, failing that, the app icon.
   Never autoplays: an autoplaying hero is a reduced-motion violation and a Lighthouse
   performance cost, and this site's whole pitch is that it does not surprise you. */
figure.hero-media { margin: var(--s6) 0 0; padding: 0; }
.hero-media .frame {
  background: var(--card); border-radius: var(--radius); overflow: hidden;
  aspect-ratio: 16 / 9; display: flex; align-items: center; justify-content: center;
}
.hero-media video, .hero-media img.fill { width: 100%; height: 100%; object-fit: cover; }
.hero-media img.icon { width: 6rem; height: 6rem; border-radius: 22.5%; }
/* Used by the icon fallback ("Product video coming soon") when a variant has no final film.
   Preview cuts are never published, so a caption never has to disclaim one. */
.hero-media figcaption { color: var(--text2); font-size: var(--fs-xs); margin-top: var(--s2); }

/* Home Screen icon labels ("Drive NS" etc) diverge from the in-prose brand name
   ("Nightshade: Drive") since the 2026-08-12 springboard rename — this is a locate-it
   hint, not a rename, so it stays small and quiet rather than competing with the CTA. */
.home-screen-hint { color: var(--text2); font-size: var(--fs-sm); margin: var(--s3) 0 0; }

.grid {
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.card {
  background: var(--card); border-radius: var(--radius); padding: var(--s5);
  text-decoration: none; color: var(--text);
  display: flex; flex-direction: column;
  border: 1px solid transparent;
}
a.card:hover, a.card:focus-visible { border-color: var(--accent); }
.card h3 { margin: 0 0 var(--s1); font-size: var(--fs-md); }
.card p { margin: 0; color: var(--text2); font-size: var(--fs-sm); }
.card .cardhead { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s3); }
.card .cardhead img { width: 2.5rem; height: 2.5rem; border-radius: 22.5%; flex: none; }
/* Pinned to the bottom so the eight cards share one baseline instead of drifting with
   each card's copy length. */
.card .short {
  display: block; color: var(--text2); font-size: var(--fs-xs);
  margin-top: auto; padding-top: var(--s3);
}

section { padding: var(--s7) 0; }
section > h2:first-child { margin-top: 0; }
section h2 { font-size: var(--fs-xl); letter-spacing: -0.02em; margin: 0 0 var(--s5); }

.privacy-note {
  background: var(--card); border-left: 3px solid var(--ok);
  padding: var(--s5); border-radius: 0 var(--radius) var(--radius) 0;
  color: var(--text2);
}
/* Safety disclaimer. TOKENS.md: accent2 is the warning colour but is NEVER interactive,
   and where a warning surface needs more contrast the answer is accent — never a
   lightened accent2. This surface is not interactive, so it takes accent2, and it is
   never colour-only: it carries the word "Warning" and a glyph too (§3.9). */
.privacy-note.warn { border-left-color: var(--accent2); }
.privacy-note .warnhead {
  display: flex; align-items: center; gap: var(--s2);
  color: var(--text); font-weight: 700; margin: 0 0 var(--s2);
  text-transform: uppercase; letter-spacing: 0.06em; font-size: var(--fs-xs);
}
.privacy-note .warnhead svg { width: 1rem; height: 1rem; fill: var(--accent2); flex: none; }
.privacy-note p { margin: 0 0 var(--s3); }
.privacy-note p:last-child { margin-bottom: 0; }
.privacy-note strong { color: var(--text); }

table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm);
        font-variant-numeric: tabular-nums; }
th, td { text-align: left; padding: var(--s3) var(--s2); border-bottom: 1px solid var(--card2); }
th { color: var(--text2); font-weight: 600; font-size: var(--fs-xs);
     text-transform: uppercase; letter-spacing: 0.05em; }
.tablewrap { overflow-x: auto; }
/* A link in a table cell is still a tap target: as bare inline text these measured 39px
   at 390px wide, under the 44px platform minimum. */
td a { color: var(--text); display: inline-flex; align-items: center; min-height: var(--tap); }

/* Chrome status dots (02-brand/app-chrome-status.md): green only for a state that is
   genuinely live, grey for "not asserted yet". Never green for an app that has not
   shipped — a green dot next to "In development" is a false positive, and the colour is
   read before the word is. Every dot is paired with its label, so no state is
   colour-only. */
.status-dot {
  display: inline-block; width: var(--s2); height: var(--s2); border-radius: 50%;
  margin-right: var(--s2); vertical-align: baseline;
}
.status-dot.ok { background: var(--ok); }
.status-dot.pending { background: var(--text2); }
.status-dot.warn { background: var(--accent2); }
.status-dot.bad { background: var(--accent); }

ul.issues { margin: 0; padding-left: var(--s5); }
ul.issues li { margin-bottom: var(--s3); color: var(--text2); }
ul.issues strong { color: var(--text); }

article.legal { padding: var(--s7) 0; max-width: 46rem; }
article.legal h2 { font-size: var(--fs-lg); margin: var(--s6) 0 var(--s2); }
article.legal p, article.legal li { color: var(--text2); }
article.legal strong { color: var(--text); }
article.legal a { color: var(--text); }

.pagelinks { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s5); }
.pagelinks a {
  display: inline-flex; align-items: center; min-height: var(--tap);
  color: var(--text); text-decoration: underline; text-underline-offset: 0.25em;
}
.pagelinks a:hover { color: var(--accent); }

footer.site {
  border-top: 1px solid var(--card2); margin-top: var(--s7);
  padding-top: var(--s6);
  /* Reserve the home-indicator band on iOS rather than letting the last row sit in it. */
  padding-bottom: calc(var(--s7) + env(safe-area-inset-bottom, 0px));
  color: var(--text2); font-size: var(--fs-sm);
}
footer.site nav { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s5); }
footer.site a {
  color: var(--text2); text-decoration: none;
  display: inline-flex; align-items: center; min-height: var(--tap);
}
footer.site a:hover { color: var(--text); }
footer.site p { margin: var(--s3) 0 0; }

img, video { max-width: 100%; height: auto; }

@media (max-width: 30rem) {
  .hero { padding: var(--s6) 0 var(--s5); }
  section { padding: var(--s6) 0; }
  .applead img { width: 3rem; height: 3rem; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important; scroll-behavior: auto !important;
  }
}
