/* LockIn site styles.

   One stylesheet for every page. The legal pages (privacy, delete-account, terms) were written with
   an inline copy of the tokens and classes in the first block below, so those class names are load
   bearing: keep .wrap, .mark, .brand, .eff, .lede, h2 .n and .foot working or those pages lose
   their formatting. Everything from "marketing components" down is additive.

   The look is deliberate: a 2001-era game promotional site. Brushed metal, HUD framing, scanlines,
   chrome lettering, hazard tape, hard angular cuts. That means a few things that would be wrong on
   an ordinary marketing page are right here, and a few that would be tempting are still wrong:

   - Type is system-only. The CSP sets font-src 'self' and there is no licensed display face in the
     repo, so the era's voice comes from weight, tracking, skew and uppercase rather than from a
     webfont. Impact and Arial Black do the heavy lifting where they exist, with a condensed
     fallback chain behind them.
   - Every texture is generated. A data-URI SVG for grain, gradients for metal and hazard tape, and
     clip-path for the notched panels. Nothing is a bitmap, so none of it costs a request and all of
     it scales to any density.
   - Motion is loud but it is all decorative. Everything still reads with animation off, which the
     reduced-motion block at the top enforces globally rather than per-component, because a
     per-component opt-out is one someone forgets to write. */

:root {
  /* Base palette. Colder and more industrial than a green-tinted dark theme: this wants to look
     like painted steel under a work light, not like a wellness app at night. */
  --bg: #06080a;
  --card: #0e1216;
  --card-2: #141a20;
  --ink: #e9f2ee;
  --muted: #93a4a0;
  --mint: #7ef0c8;
  --line: #23303a;
  --line-soft: #1a242c;

  /* Surface ramp, dark to light, for stacking metal panels. */
  --surface-0: #06080a;
  --surface-1: #0c1116;
  --surface-2: #141b22;
  --surface-3: #1e2830;

  /* The app's category accents, same hues as the nav bar in the product. */
  --coral: #ff8a75;
  --amber: #ffb865;
  --violet: #cba9ff;
  --blue: #89a6ff;

  /* Era-specific additions. Hazard is the warning-tape yellow; steel drives every bevel. */
  --hazard: #ffcc33;
  --steel-hi: #cfdae2;
  /* Bevels, borders and rules only. Never text: it is 2.81:1 against --bg, so any copy set in it
     fails WCAG AA silently, and it is exactly the kind of muted grey that looks correct while
     being unreadable. Use --muted (7.70:1) for de-emphasised text instead. */
  --steel-lo: #4a5a66;

  --maxw: 1080px;
  --radius: 4px;

  /* The corner cut used on panels and buttons. One number so every notch matches. */
  --notch: 14px;

  --display: Impact, "Haettenschweiler", "Arial Narrow Bold", "Arial Black", system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Everything the two themes disagree about is a token, and only a token.

     This is not tidiness for its own sake. The first cut wrote the light theme as component rules
     hung off [data-theme="light"], which silently did nothing for the much larger group who never
     touch the toggle and simply have a light OS: they matched the light token block through the
     media query and then got every dark component rule on top. A washed-out page with invisible
     headlines, and no selector was "wrong" anywhere. Keeping the difference in tokens means the
     media query and the attribute each need to override exactly one block, and there is no second
     place to forget. */
  --chrome-grad: linear-gradient(180deg, #ffffff 0%, #dfeae6 26%, #8fa39c 47%, #6b7d78 53%, #cfdcd7 72%, #ffffff 100%);
  --chrome-shadow: drop-shadow(0 2px 0 rgba(0, 0, 0, 0.55)) drop-shadow(0 0 22px rgba(126, 240, 200, 0.18));
  --shine-strength: rgba(255, 255, 255, 0.85);

  --grain-opacity: 0.05;
  --scan-color: rgba(0, 0, 0, 0.28);
  --vignette: rgba(0, 0, 0, 0.62);
  --vignette-start: 55%;
  --sweep-color: rgba(126, 240, 200, 0.11);
  --grid-color: rgba(126, 240, 200, 0.055);

  --topbar-bg: linear-gradient(180deg, rgba(8, 11, 14, 0.94), rgba(8, 11, 14, 0.78));
  --topbar-bg-scrolled: linear-gradient(180deg, rgba(6, 8, 10, 0.97), rgba(6, 8, 10, 0.9));
  --topbar-edge: rgba(126, 240, 200, 0.14);
  --topbar-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);

  --btn-bg: linear-gradient(180deg, #a9ffe2 0%, var(--mint) 42%, #46c79c 100%);
  --btn-bg-hover: linear-gradient(180deg, #c8ffee 0%, #93f7d5 42%, #52dcae 100%);
  --btn-ink: #04140e;
  --btn-inset: rgba(255, 255, 255, 0.7);

  --input-bg: var(--surface-0);
  --shot-shadow: 0 0 0 1px rgba(126, 240, 200, 0.16), 0 30px 70px rgba(0, 0, 0, 0.7);
  --panel-edge: rgba(126, 240, 200, 0.5);
  --keyhole: #050a08;
  --lock-hi: #46564e;
  --lock-lo: #1b2420;
  --lock-glow: rgba(126, 240, 200, 0.55);
  --stat-glow: 0 0 24px rgba(126, 240, 200, 0.4);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

/* One global switch. Everything below is free to animate without writing its own opt-out, which is
   the opt-out that gets forgotten. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation: none !important; transition: none !important; }
}

body {
  margin: 0;
  background-color: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  line-height: 1.6;
  /* Painted steel: a cold top light, a warmer bounce at the bottom, over near-black. Anchored to
     the document rather than the viewport so it does not repaint the whole gradient every scroll
     frame, which is real battery on a page this long. */
  background-image:
    radial-gradient(120% 70% at 50% -10%, #16222b 0%, transparent 60%),
    radial-gradient(90% 50% at 100% 0%, rgba(126, 240, 200, 0.07) 0%, transparent 55%);
  background-repeat: no-repeat;
  background-size: 100% 1400px;
  overflow-x: hidden;
}

/* ---------- document pages ---------- */
/* The legal pages keep the original narrow measure. They get the chrome treatment on headings and
   nothing else: a privacy policy that is hard to read is a worse privacy policy. */

.wrap { max-width: 760px; margin: 0 auto; padding: 40px 22px 80px; position: relative; z-index: 2; }
.brand { font-weight: 900; letter-spacing: 3px; font-size: 15px; font-family: var(--display); }
.mark {
  width: 46px; height: 46px;
  background: var(--mint); color: #06120d;
  display: grid; place-items: center; font-weight: 900; font-size: 22px;
  letter-spacing: -1px;
  clip-path: polygon(0 0, 100% 0, 100% 72%, 72% 100%, 0 100%);
}
h1 {
  font-size: clamp(30px, 6vw, 46px); line-height: 1.05; margin: 22px 0 4px;
  letter-spacing: 0.5px; text-transform: uppercase; font-family: var(--display);
  font-weight: 900;
}
.eff {
  color: var(--mint); font-weight: 700; font-size: 12px; letter-spacing: 2.4px;
  margin-bottom: 26px; text-transform: uppercase; font-family: var(--mono);
}
.lede {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--line); border-left: 3px solid var(--mint);
  padding: 18px 20px; color: var(--ink); font-size: 17px; margin-bottom: 30px;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
}
.lede b { color: var(--mint); }
h2 {
  font-size: 22px; margin: 34px 0 8px; text-transform: uppercase;
  letter-spacing: 1px; font-family: var(--display); font-weight: 900;
}
h2 .n { color: var(--mint); font-variant-numeric: tabular-nums; margin-right: 10px; font-family: var(--mono); }
p, li { color: var(--muted); }
li { margin: 4px 0; }
a { color: var(--mint); }
strong { color: var(--ink); }
.foot { margin-top: 46px; padding-top: 20px; border-top: 1px solid var(--line); color: var(--muted); font-size: 14px; }

/* ---------- full-screen overlays ---------- */
/* Three stacked layers sell the CRT: static grain, scanlines, and a corner vignette. All of them
   are pointer-events:none and aria-hidden by construction, so nothing here can intercept a click or
   reach a screen reader. */

body::before,
body::after {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 9000;
}

/* Grain. feTurbulence rendered once by the browser and tiled, so it costs no request. */
body::before {
  opacity: var(--grain-opacity);
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain 900ms steps(3) infinite;
}
@keyframes grain {
  0%   { transform: translate(0, 0); }
  33%  { transform: translate(-3px, 2px); }
  66%  { transform: translate(2px, -3px); }
  100% { transform: translate(0, 0); }
}

/* Scanlines plus a vignette, and a single bright line that sweeps down the page forever. */
body::after {
  background:
    linear-gradient(transparent 50%, var(--scan-color) 50%),
    radial-gradient(130% 90% at 50% 50%, transparent var(--vignette-start), var(--vignette) 100%);
  background-size: 100% 3px, 100% 100%;
}

.sweep {
  position: fixed; left: 0; right: 0; height: 180px; top: -180px;
  pointer-events: none; z-index: 9001;
  background: linear-gradient(180deg, transparent, var(--sweep-color) 82%, transparent);
  animation: sweep 7s linear infinite;
}
@keyframes sweep {
  0%   { transform: translateY(0); }
  100% { transform: translateY(calc(100vh + 360px)); }
}

/* ---------- boot sequence ---------- */
/* A loading screen, because every game site in 2003 had one. It is added and driven by JS, so with
   JS off it never exists at all and the page is simply there. The markup is injected rather than
   authored into each page so no document ships a full-screen black div that JS has to remember to
   remove. */

.boot {
  position: fixed; inset: 0; z-index: 9500;
  background: var(--surface-0);
  display: grid; place-content: center; justify-items: center; gap: 18px;
  font-family: var(--mono);
  transition: opacity 420ms ease, visibility 420ms;
}
.boot[hidden] { display: none; }
.boot.is-done { opacity: 0; visibility: hidden; }
.boot-word {
  font-family: var(--display); font-size: clamp(38px, 11vw, 88px); font-weight: 900;
  letter-spacing: 6px; text-transform: uppercase; line-height: 1;
  color: var(--ink);
  transform: skewX(-8deg);
}
.boot-bar {
  width: min(58vw, 320px); height: 6px; background: var(--surface-3);
  border: 1px solid var(--line); overflow: hidden;
}
.boot-fill {
  display: block; height: 100%; width: 0%;
  background: linear-gradient(90deg, var(--mint), #b9ffe8);
  box-shadow: 0 0 14px rgba(126, 240, 200, 0.75);
  transition: width 140ms linear;
}
.boot-status {
  font-size: 11px; letter-spacing: 2.6px; text-transform: uppercase; color: var(--muted);
  min-height: 1.2em;
}

/* ---------- chrome lettering ---------- */
/* The era's signature: brushed metal type with a highlight band running through it.

   The @supports guard matters. Without background-clip:text the fill would be transparent on top of
   nothing, which is invisible text, so the colour is only surrendered once the clip is known to
   work.

   The shimmer animates the fill's own background-position rather than sliding a lit overlay across
   the top. An overlay was the first attempt and it was wrong in an obvious way once rendered: the
   element's box is a rectangle, the letters are not, and mix-blend-mode happily painted a grey
   parallelogram across the whole headline. Moving the gradient underneath keeps every lit pixel
   inside a glyph, because the clip is doing the masking. */
.chrome { color: var(--ink); }
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .chrome {
    background-image: var(--chrome-grad);
    background-size: 100% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    /* A drop-shadow filter rather than text-shadow: a shadow drawn on clipped text paints inside
       the glyphs and turns the metal muddy. */
    filter: var(--chrome-shadow);
  }

  /* The shine is a duplicate of the text stacked on top, clipped to its OWN glyphs, rather than a
     second gradient layer on the base text.

     Two reasons, and the first one is a bug that shipped. Safari rasterises an element that has both
     a filter and -webkit-background-clip: text into a filter layer, and then stops invalidating it
     when only background-position changes. The sweep runs for a while and then freezes permanently.
     Splitting them means the filtered element never animates and the animated element never has a
     filter, so there is nothing to cache wrongly.

     The second is that this cannot repeat the earlier mistake of lighting the element's rectangle
     instead of its letters: the overlay is text, so its own clip is doing the masking.

     `content: attr(data-text) / ""` supplies empty alternative text, so assistive technology does
     not read the headline a second time. */
  /* No filter here: the h1 above carries the drop shadow now, and leaving it on the span too
     would stack the same shadow twice. */
  .chrome-shine { position: relative; filter: none; }
  .chrome-shine::after {
    content: attr(data-text) / "";
    position: absolute;
    left: 0; top: 0;
    width: 100%;
    pointer-events: none;
    background-image: linear-gradient(100deg, transparent 42%, var(--shine-strength) 50%, transparent 58%);
    background-size: 300% 100%;
    background-position: 190% 0;
    background-repeat: no-repeat;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    filter: none;
    will-change: background-position;
    animation: shine 6s ease-in-out infinite;
  }
}

/* The drop shadow lives on the h1, not on the clipped span, for the reason above. */
.hero-title { filter: var(--chrome-shadow); }

@keyframes shine {
  0%, 55%  { background-position: 190% 0; }
  100%     { background-position: -90% 0; }
}

/* ---------- hazard tape ---------- */

.hazard {
  height: 10px;
  background: repeating-linear-gradient(
    -45deg, var(--hazard) 0 12px, #14181c 12px 24px
  );
  opacity: 0.85;
}
.hazard-thin { height: 4px; }

/* ---------- shared chrome ---------- */

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--mint); color: #06120d; padding: 10px 16px;
  font-weight: 800; z-index: 9600;
}
.skip:focus { left: 0; }

:focus-visible { outline: 2px solid var(--mint); outline-offset: 3px; }

.shell { max-width: var(--maxw); margin: 0 auto; padding: 0 22px; position: relative; }

/* Ambient backdrop: a perspective floor grid, masked so it fades out before the horizon, and
   parallaxed a little by site.js so it reads as depth rather than wallpaper.

   Five blurred colour orbs used to sit in front of it, and the comment here argued they read as
   distant hangar lights rather than decorative gradient blobs. That was a rationalisation. Five
   absolutely positioned circles at 70px blur in five brand colours are the "abstract floating
   blobs" tell however they are narrated, so they are gone. The grid does the depth work on its
   own, and it is doing something a flat background cannot: it establishes the horizon the whole
   layout hangs from. Do not put the blobs back. */
.ambient {
  position: fixed; inset: 0; pointer-events: none; z-index: 0; overflow: hidden;
  /* A floor grid in perspective, fading out before it reaches the horizon. */
  background-image:
    linear-gradient(var(--grid-color) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
  background-size: 64px 64px, 64px 64px;
  mask-image: radial-gradient(120% 90% at 50% 0%, #000 0%, transparent 72%);
  -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 0%, transparent 72%);
}

/* Site header. */
.topbar {
  position: sticky; top: 0; z-index: 60;
  background: var(--topbar-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--topbar-edge), var(--topbar-shadow);
}
.topbar .shell { display: flex; align-items: center; gap: 18px; height: 66px; }
.logo { display: flex; align-items: center; gap: 11px; text-decoration: none; color: var(--ink); min-height: 44px; }
.logo svg { display: block; }
.logo .brand { color: var(--ink); }
.navlinks { display: flex; gap: 22px; margin-left: auto; align-items: center; }
/* Scoped to plain links, not buttons. `.navlinks a` is 0,1,1 and both `.btn` and `.btn-sm` are
   0,1,0, so an unscoped rule here silently beats them: the header CTA was inheriting this rule's
   `padding: 4px 0` and 12px size and ignoring its own button padding completely. Two separate
   attempts to widen `.btn-sm` changed nothing at all, because the declaration never applied.
   The file already carried a comment about this exact trap for `color`, one rule below. */
.navlinks a:not(.btn) {
  color: var(--muted); font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 1.8px; font-family: var(--mono);
  position: relative; padding: 4px 0;
}
.navlinks a { text-decoration: none; }
.navlinks a:not(.btn)::after {
  content: ""; position: absolute; left: 0; right: 100%; bottom: 0; height: 1px;
  background: var(--mint); transition: right 180ms ease;
}
.navlinks a:not(.btn):hover { color: var(--mint); }
.navlinks a:not(.btn):hover::after { right: 0; }
/* `.navlinks a` is 0,1,1 and `.btn-primary` is 0,1,0, so without this the nav CTA inherits the
   muted grey meant for plain nav links and renders grey-on-mint, which is unreadable. */
.navlinks a.btn-primary { color: var(--btn-ink); }
.navlinks a.btn-ghost { color: var(--ink); }
.navlinks a.btn-ghost:hover { color: var(--mint); }
/* The header is the tightest row on the site and the uppercase mono treatment costs real width:
   tracking at 1.8px over short words, plus a 44px icon button, added about 90px to a bar that
   already fit exactly. It has to shed items in order of value rather than shrink everything into
   illegibility, so plain links go first, the brand word next, and the CTA never goes at all. */
@media (max-width: 760px) {
  .navlinks .hide-sm { display: none; }
  .navlinks { gap: 16px; }
}
@media (max-width: 620px) {
  /* Every plain link is reachable from the footer; the CTA is not reachable from anywhere else. */
  .navlinks a:not(.btn) { display: none; }
  .navlinks { gap: 12px; }
}

/* ---------- buttons ---------- */
/* Beveled and notched, and they physically depress. A flat pill would undo the whole look. */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 15px 30px;
  font-family: var(--mono);
  font-weight: 700; font-size: 13px; letter-spacing: 2px; text-transform: uppercase;
  text-decoration: none; border: 1px solid transparent; cursor: pointer;
  white-space: nowrap;
  position: relative;
  clip-path: polygon(var(--notch) 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%, 0 var(--notch));
  transition: transform 90ms ease, background 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
.btn:active { transform: translateY(2px); }

.btn-primary {
  background: var(--btn-bg);
  color: var(--btn-ink);
  box-shadow: inset 0 1px 0 var(--btn-inset), 0 0 0 1px rgba(126, 240, 200, 0.4), 0 8px 22px rgba(126, 240, 200, 0.22);
}
.btn-primary:hover {
  background: var(--btn-bg-hover);
  box-shadow: inset 0 1px 0 var(--btn-inset), 0 0 0 1px rgba(126, 240, 200, 0.7), 0 0 30px rgba(126, 240, 200, 0.45);
}
.btn-ghost {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  color: var(--ink); border-color: var(--line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.btn-ghost:hover { border-color: var(--mint); color: var(--mint); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 0 22px rgba(126, 240, 200, 0.18); }
.btn-sm { padding: 14px 34px; font-size: 13px; letter-spacing: 1.8px; --notch: 10px; }

/* ---------- section rhythm ---------- */

.section { padding: 92px 0; position: relative; z-index: 2; }
.section::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--line) 12%, var(--line) 88%, transparent);
}
.section:first-of-type::before { display: none; }

/* The eyebrow becomes a HUD readout with a leading rule and a blinking cursor.

   Use it sparingly. There were 42 of these, one above nearly every heading on the site, and a
   label above every single headline is one of the named tells of a generated page: they stack up
   like receipts and stop meaning anything. Half were cut, and the test was whether the heading
   beneath still stands up alone. "Who" above "Robert Tolbert. That is the whole team." adds
   nothing. "Circles" above "Owning one is paid. Being in one never is." is load bearing, because
   without it "one" has no antecedent. The ones left are dates, scopes and group labels, which are
   information. Before adding another, cover the eyebrow and read the heading: if it still works,
   do not add it. */
.eyebrow {
  color: var(--mint); font-weight: 700; font-size: 11px;
  letter-spacing: 3px; text-transform: uppercase; margin: 0 0 14px;
  font-family: var(--mono);
  display: flex; align-items: center; gap: 10px;
}
.eyebrow::before {
  content: ""; width: 26px; height: 1px; background: var(--mint); flex: none;
  box-shadow: 0 0 8px var(--mint);
}
.eyebrow::after {
  content: "_"; animation: blink 1.1s steps(1) infinite; margin-left: -4px;
}
@keyframes blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

.section h2 {
  font-size: clamp(28px, 5.2vw, 46px); line-height: 1.05; letter-spacing: 0.5px;
  margin: 0 0 16px; text-transform: uppercase; font-family: var(--display); font-weight: 900;
  transform: skewX(-6deg); transform-origin: left;
}
.section .sub { font-size: 17px; max-width: 62ch; margin: 0 0 36px; }

/* Section tones tint the top rule and the eyebrow, so each block still has its own identity. */
.tone-mint   { --tone: var(--mint); }
.tone-coral  { --tone: var(--coral); }
.tone-blue   { --tone: var(--blue); }
.tone-violet { --tone: var(--violet); }
.tone-amber  { --tone: var(--amber); }
.section[class*="tone-"] .eyebrow { color: var(--tone, var(--mint)); }
.section[class*="tone-"] .eyebrow::before { background: var(--tone, var(--mint)); box-shadow: 0 0 8px var(--tone, var(--mint)); }
.section[class*="tone-"]::before { background: linear-gradient(90deg, transparent, var(--tone, var(--line)) 50%, transparent); opacity: 0.4; }

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

.hero { padding: 84px 0 68px; overflow-x: clip; position: relative; z-index: 2; }
.hero-grid { display: grid; grid-template-columns: 1.15fr 0.85fr; gap: 54px; align-items: center; }
.hero h1, .hero-title {
  font-size: clamp(42px, 9vw, 92px); line-height: 0.92; letter-spacing: 0px; margin: 0 0 22px;
  text-transform: uppercase; font-family: var(--display); font-weight: 900;
  transform: skewX(-7deg); transform-origin: left;
}
.hero .sub { font-size: 19px; max-width: 46ch; margin: 0 0 32px; }
.hero .cta { display: flex; gap: 14px; flex-wrap: wrap; }

/* The product shot sits inside a HUD frame with drawn corner brackets. */
/* Which platform, and what it costs, in the one place everybody sees.

   The hero used to carry a status pill saying "Closed testing on Android and Wear OS". Deleting it
   was right, but it took the only above-the-fold statement of platform with it, and below 900px the
   grid stacks so the feature chips that repeat it fall well below the fold. An iPhone user could
   reach "Become a tester" without ever learning it is Android only. This is that information as a
   sentence rather than a badge, which is the distinction that mattered about the pill. */
.hero-note { margin: 16px 0 0; font-size: 14px; color: var(--muted); }

.hero-shot { justify-self: center; max-width: 300px; width: 100%; position: relative; }
.hero-shot img {
  width: 100%; height: auto; display: block;
  border: 1px solid var(--line);
  clip-path: polygon(22px 0, 100% 0, 100% calc(100% - 22px), calc(100% - 22px) 100%, 0 100%, 0 22px);
  box-shadow: var(--shot-shadow);
}
.hero-shot::before,
.hero-shot::after {
  content: ""; position: absolute; width: 30px; height: 30px; pointer-events: none;
  border-color: var(--mint); border-style: solid; border-width: 0;
}
.hero-shot::before { top: -9px; right: -9px; border-top-width: 2px; border-right-width: 2px; }
.hero-shot::after  { bottom: -9px; left: -9px; border-bottom-width: 2px; border-left-width: 2px; }

@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; gap: 40px; }
  .hero-shot { max-width: 260px; }
}
/* A phone showing a browser address bar has roughly 700 usable CSS pixels, and an in-app browser
   less. Every one spent on padding is one the product screenshot does not get. */
@media (max-width: 560px) {
  .hero { padding: 30px 0 40px; }
  .hero h1 { font-size: clamp(36px, 12vw, 54px); margin-bottom: 14px; }
  .hero .sub { font-size: 16px; margin-bottom: 22px; }
  .hero-grid { gap: 26px; }
  .section { padding: 60px 0; }
  .section .sub { margin-bottom: 26px; }
  .hero .cta { gap: 10px; }
  /* Scoped away from .btn-sm. Both selectors are 0,1,0 and this block sits after .btn-sm in the
     file, so an unscoped `.btn` here quietly overrode the small button's own sizing at every width
     below 560. */
  .btn:not(.btn-sm) { padding: 13px 22px; font-size: 12px; letter-spacing: 1.5px; }
}

/* Narrowest last. Equal-specificity media blocks resolve by source order, not by how tight the
   query is, so a 430px block written above a 560px block loses on every phone it was written for.
   This one has to stay below. */
@media (max-width: 430px) {
  .logo .brand { display: none; }
  .btn-sm { padding: 12px 20px; font-size: 12px; letter-spacing: 1.2px; }
  .topbar .shell { gap: 10px; padding: 0 14px; }
}

/* The status pill and its breathing dot were removed deliberately. A capsule above the headline
   announcing "Closed testing on Android and Wear OS" next to a pulsing dot is the single most
   recognisable tell of a generated landing page, and every one of the six said something the page
   already said better a paragraph later. Do not reintroduce it. If a status genuinely needs stating,
   state it in a sentence somebody would actually write. */

/* ---------- ticker ---------- */
/* An infinite marquee. The track holds the strip twice and translates by exactly -50%, so the
   restart lands on an identical frame and there is no visible jump. JS clones the second copy, so
   with JS off this is simply one static strip that does not move. */

.ticker {
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, var(--surface-1), var(--surface-0));
  overflow: hidden; position: relative; z-index: 2;
}
.ticker-track { display: flex; width: max-content; animation: marquee 34s linear infinite; }
.ticker-strip { display: flex; flex: none; }
.ticker span {
  font-family: var(--mono); font-size: 11px; letter-spacing: 3px; text-transform: uppercase;
  color: var(--muted); padding: 13px 26px; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 26px;
}
.ticker span::after { content: "◆"; color: var(--mint); font-size: 8px; opacity: 0.7; }
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ---------- cards ---------- */

.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px; }
.card {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--line);
  padding: 24px 22px 26px;
  position: relative;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
  transition: transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
}
/* A thin lit edge along the top, the way a metal panel catches light. */
.card::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--panel-edge), transparent);
}
.card:hover {
  transform: translateY(-4px);
  border-color: rgba(126, 240, 200, 0.4);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(126, 240, 200, 0.16);
}
/* The headline is the line somebody repeats to themselves, so it is set like a statement rather
   than like a label: bigger than the body by a clear step, skewed with the rest of the display
   type, and given room above it. */
.card h3 {
  margin: 12px 0 12px; font-size: clamp(22px, 2.4vw, 27px); line-height: 1.02; color: var(--ink);
  text-transform: uppercase; letter-spacing: 0.4px; font-family: var(--display); font-weight: 900;
  transform: skewX(-6deg); transform-origin: left;
}

/* A ghosted index numeral in the corner. Chapter numbering on panels is one of the era's most
   reliable tells, and unlike most of them it is quiet: at this opacity it reads as texture until
   you look for it. Hidden from assistive technology, since it carries no information. */
.card .idx {
  position: absolute; top: 2px; right: 12px;
  font-family: var(--display); font-weight: 900; font-size: 62px; line-height: 1;
  color: var(--ink); opacity: 0.06; pointer-events: none;
  transform: skewX(-6deg);
}
.card p { margin: 0; font-size: 15px; }
.tag {
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: 2.4px; text-transform: uppercase;
}

/* ---------- feature rows ---------- */

.feature {
  display: grid; grid-template-columns: 1fr 1fr; gap: 46px; align-items: center;
  margin-top: 44px;
}
.feature img {
  width: 100%; height: auto; display: block; border: 1px solid var(--line);
  clip-path: polygon(18px 0, 100% 0, 100% calc(100% - 18px), calc(100% - 18px) 100%, 0 100%, 0 18px);
}
.feature h3 {
  font-size: 22px; margin: 0 0 10px; text-transform: uppercase;
  font-family: var(--display); font-weight: 900; letter-spacing: 0.5px;
}
@media (max-width: 820px) { .feature { grid-template-columns: 1fr; gap: 26px; } }

/* ---------- watch row ---------- */

.watch-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 22px; margin-top: 38px; }
.watch-shot { text-align: center; }
.watch-shot img {
  width: 100%; max-width: 200px; height: auto; display: block; margin: 0 auto 12px;
  border-radius: 50%; border: 1px solid var(--line);
  box-shadow: 0 0 0 6px rgba(20, 27, 34, 0.8), 0 0 30px rgba(126, 240, 200, 0.12);
}
.watch-shot p { font-size: 14px; margin: 0; }

/* ---------- promise list ---------- */

.promise { list-style: none; padding: 0; margin: 32px 0 0; display: grid; gap: 12px; }
.promise li {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--line); border-left: 3px solid var(--mint);
  padding: 15px 18px; color: var(--muted); font-size: 15px;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%);
}
.promise li strong { color: var(--ink); }

/* ---------- stats ---------- */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 18px; margin-top: 36px; }
.stat {
  border: 1px solid var(--line); padding: 20px 18px;
  background: linear-gradient(180deg, var(--surface-1), var(--surface-0));
  clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
}
.stat b {
  display: block; font-family: var(--display); font-weight: 900;
  font-size: clamp(30px, 5vw, 44px); line-height: 1; color: var(--mint);
  text-shadow: var(--stat-glow);
  font-variant-numeric: tabular-nums;
}
.stat span {
  display: block; margin-top: 8px; font-family: var(--mono); font-size: 10px;
  letter-spacing: 2.2px; text-transform: uppercase; color: var(--muted);
}

/* ---------- FAQ ---------- */

.faq details {
  border: 1px solid var(--line); margin-bottom: 12px;
  background: linear-gradient(180deg, var(--surface-1), var(--surface-0));
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%);
}
.faq summary {
  cursor: pointer; padding: 17px 20px; font-weight: 700; color: var(--ink);
  list-style: none; display: flex; align-items: center; gap: 12px;
  font-family: var(--mono); font-size: 13px; letter-spacing: 1px; text-transform: uppercase;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::before {
  content: "+"; color: var(--mint); font-size: 18px; line-height: 1; flex: none;
  transition: transform 160ms ease;
}
.faq details[open] summary::before { content: "\2212"; }
.faq details[open] summary { color: var(--mint); }
.faq p { margin: 0; padding: 0 20px 18px; font-size: 15px; }

/* ---------- CTA band and signup ---------- */

.cta-band {
  border: 1px solid var(--line);
  background:
    linear-gradient(180deg, var(--surface-2), var(--surface-0));
  padding: 42px 34px; text-align: center; position: relative; overflow: hidden;
  clip-path: polygon(24px 0, 100% 0, 100% calc(100% - 24px), calc(100% - 24px) 100%, 0 100%, 0 24px);
}
.cta-band::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(70% 120% at 50% 0%, rgba(126, 240, 200, 0.14), transparent 70%);
  pointer-events: none;
}
.cta-band > * { position: relative; }

.signup {
  border: 1px solid var(--line); padding: 26px 24px; margin-top: 26px;
  background: linear-gradient(180deg, var(--surface-1), var(--surface-0));
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%, 0 100%);
}
.signup-row { display: flex; gap: 12px; flex-wrap: wrap; }
.signup input[type="email"], .signup input[type="text"], .signup input:not([type="checkbox"]) {
  flex: 1 1 240px; min-width: 0;
  background: var(--input-bg); border: 1px solid var(--line); color: var(--ink);
  padding: 14px 16px; font-size: 15px; font-family: var(--body);
}
.signup input:focus { outline: none; border-color: var(--mint); box-shadow: 0 0 0 1px var(--mint), 0 0 20px rgba(126, 240, 200, 0.2); }

/* Stacked fields, for the feedback form. The signup form is one row and needs none of this; a
   form with six controls needs every label on its own line or the labels stop being readable. */
.field { margin-bottom: 20px; }
.field label { display: block; margin-bottom: 8px; font-size: 14px; font-weight: 600; color: var(--ink); }
/* "optional" reads as part of the label rather than a second thing to parse. */
.field .opt { font-weight: 400; font-size: 13px; color: var(--muted); }
.field .hint { margin: 6px 0 0; }
/* Beats the flex-basis on the shared .signup input rule, which is sized for the one-row form. */
.signup .field input, .signup textarea, .signup select {
  display: block; width: 100%; flex: none; box-sizing: border-box;
  background: var(--input-bg); border: 1px solid var(--line); color: var(--ink);
  padding: 14px 16px; font-size: 15px; font-family: var(--body);
}
/* Vertical only. Horizontal resize inside a clipped panel drags the control out of its own box. */
.signup textarea { resize: vertical; min-height: 150px; line-height: 1.55; }
.signup select { appearance: none; cursor: pointer; padding-right: 44px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-size: 6px 6px, 6px 6px;
  background-position: right 20px center, right 14px center;
  background-repeat: no-repeat;
}
.signup textarea:focus, .signup select:focus {
  outline: none; border-color: var(--mint);
  box-shadow: 0 0 0 1px var(--mint), 0 0 20px rgba(126, 240, 200, 0.2);
}
.signup ::placeholder { color: var(--muted); opacity: 1; }
/* Only speaks up near the limit. A character count sitting there from the first keystroke reads as
   a budget to spend rather than a ceiling to avoid. */
.counter { min-height: 1em; }
.counter.near { color: var(--amber); }
.counter.over { color: var(--coral); }

.signup-msg { margin-top: 14px; font-size: 14px; }
.signup-done { color: var(--mint); }
.hint, .disclaimer { font-size: 13px; color: var(--muted); }
.trap { position: absolute; left: -9999px; }

/* Invite page code block. */
.invite-code {
  font-family: var(--mono); font-size: clamp(22px, 6vw, 34px); letter-spacing: 6px;
  color: var(--mint); background: var(--surface-0); border: 1px solid var(--line);
  padding: 18px 20px; text-align: center; margin: 18px 0;
  text-shadow: 0 0 20px rgba(126, 240, 200, 0.5);
}

/* Press page odds and ends. */
.cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 18px; }
.yes { color: var(--mint); }
.no { color: var(--coral); }
.already, .shown-email { color: var(--muted); font-size: 14px; }

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

.sitefoot {
  border-top: 1px solid var(--line); padding: 52px 0 40px; margin-top: 20px;
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.5));
  position: relative; z-index: 2;
}
.sitefoot .cols { gap: 26px; }
.foot-head {
  font-size: 11px; text-transform: uppercase; letter-spacing: 2.6px; color: var(--mint);
  margin: 0 0 12px; font-family: var(--mono); font-weight: 700;
}
.sitefoot a { color: var(--muted); text-decoration: none; display: block; padding: 5px 0; font-size: 14px; }
.sitefoot a:hover { color: var(--mint); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------- scroll reveals: removed ---------- */
/* Everything on the page used to start at opacity 0, translated down 22px, and slide up as it
   crossed the viewport. "A generic fade-in scroll animation on every element" is one of the
   named tells of a generated site, and it was applied here to eleven selector groups, which is
   to say all of the content. Motion that fires on everything communicates nothing: it is not a
   hierarchy, it is a tax paid on every scroll.

   The deliberate motion on this site stays, because each piece of it means something: the
   shackle seats itself when you reach the section that explains it, the header gains weight once
   you have left the top, the ticker moves because a ticker moves. Add motion here only when
   there is an answer to what it is telling the reader.

   The `js` class is still set by the inline script in each head. Nothing styles it now, but it
   stays a valid progressive-enhancement hook, and leaving that script byte-identical keeps its
   CSP hash in firebase.json valid. */

/* ---------- shackle motif ---------- */
/* The product's one primitive, so it stays the centrepiece of the section that explains it.

   The mechanism is the point and it is easy to lose: the leg is drawn at full length and squashed
   to scaleY(0.413), so seating it means scaling it back to 1 and the shackle visibly slides down
   into the body. A restyle replaced that with a translate on the whole group, which moved the arch
   without ever closing anything, and left the leg a different colour from the bow it is supposed to
   be part of. That reads as a broken drawing rather than a lock, which is exactly what it looked
   like.

   The bow, the leg and the glow are all one colour for the same reason: they are one object. */

.shackle-wrap { display: grid; place-items: center; margin: 10px 0 38px; }
.shackle { width: min(164px, 42vw); height: auto; overflow: visible; }

.shackle .bow { fill: none; stroke: var(--mint); stroke-width: 11.51; stroke-linecap: round; }

/* The glow is a filter on the whole group, not a fatter stroke under the bow.
   The bow path draws the arch and the LEFT leg; the right leg is a separate rect, because it is the
   part that slides. So a glow stroked along that path lit three sides of the shackle and stopped
   dead at the right shoulder, which read as a notch cut out of the lock. A filter on the group
   covers whatever the group contains, including the leg, at whatever length it is currently scaled
   to. */
.shackle .bow-group { filter: none; transition: filter 500ms ease; }
/* There is deliberately no specular arc on the bow. It was a second stroke drawn inside the
   shackle, and because it is an arc with round caps it terminated mid-curve at both ends, so at any
   size above thumbnail the lock looked double-outlined rather than lit. */
.shackle .leg {
  fill: var(--mint);
  transform-origin: 83.03px 44.19px;
  transform: scaleY(0.413);
  transition: transform 620ms cubic-bezier(0.3, 1.6, 0.5, 1);
}
.shackle .body      { fill: url(#steel); }
.shackle .body-tint { fill: var(--mint); opacity: 0; transition: opacity 400ms ease; }
.shackle .bevel     { stroke: #fff; stroke-width: 1.61; stroke-linecap: round; opacity: 0.3; }
.shackle .keyhole   { fill: var(--keyhole); }
.shackle .halo      { fill: var(--mint); opacity: 0; transition: opacity 500ms ease; }

/* The steel body is an SVG gradient, so its stops are driven by tokens rather than baked into the
   markup. Without this the body stayed near-black on a light page: a dark blob under a mint arch. */
.shackle .steel-hi { stop-color: var(--lock-hi); }
.shackle .steel-lo { stop-color: var(--lock-lo); }

.shackle.is-shut .leg       { transform: scaleY(1); }
.shackle.is-shut .bow-group { filter: drop-shadow(0 0 5px var(--lock-glow)) drop-shadow(0 0 13px var(--lock-glow)); }
.shackle.is-shut .body-tint { opacity: 0.14; }
.shackle.is-shut .halo      { opacity: 0.09; animation: haloPulse 3.4s ease-in-out 700ms infinite; }
.shackle.is-shut            { animation: clunk 460ms cubic-bezier(0.3, 1.5, 0.4, 1) 420ms; }

@keyframes clunk {
  0%   { transform: translateY(0); }
  38%  { transform: translateY(3px); }
  100% { transform: translateY(0); }
}
@keyframes haloPulse { 0%, 100% { opacity: 0.06; } 50% { opacity: 0.13; } }

/* With motion off the lock is simply already shut, rather than never shutting. */
@media (prefers-reduced-motion: reduce) {
  .shackle .leg { transform: scaleY(1); }
  .shackle .bow-group { filter: drop-shadow(0 0 5px var(--lock-glow)) drop-shadow(0 0 13px var(--lock-glow)); }
  .shackle .body-tint { opacity: 0.14; }
  .shackle .halo { opacity: 0.13; }
}

/* Forced colours: the lock has to survive losing every fill it was given. */
@media (forced-colors: active) {
  .shackle .bow, .shackle .leg { stroke: CanvasText; fill: CanvasText; }
}

/* ---------- glitch ---------- */
/* Used on the hero headline once, on load. Two offset copies in the accent colours, clipped to
   alternating bands. Cheap, and unmistakably of the period. */

.glitch { position: relative; }
.glitch.is-glitching::before,
.glitch.is-glitching::after {
  content: attr(data-text); position: absolute; inset: 0; pointer-events: none;
  background: none; -webkit-text-fill-color: currentColor;
}
.glitch.is-glitching::before { color: var(--coral); animation: glitch-a 620ms steps(2) 2; }
.glitch.is-glitching::after  { color: var(--blue);  animation: glitch-b 620ms steps(2) 2; }
@keyframes glitch-a {
  0%   { transform: translate(0); clip-path: inset(0 0 82% 0); }
  25%  { transform: translate(-4px, 1px); clip-path: inset(38% 0 40% 0); }
  50%  { transform: translate(3px, -2px); clip-path: inset(72% 0 8% 0); }
  75%  { transform: translate(-2px, 2px); clip-path: inset(12% 0 66% 0); }
  100% { transform: translate(0); clip-path: inset(0 0 100% 0); }
}
@keyframes glitch-b {
  0%   { transform: translate(0); clip-path: inset(66% 0 10% 0); }
  25%  { transform: translate(4px, -1px); clip-path: inset(14% 0 70% 0); }
  50%  { transform: translate(-3px, 2px); clip-path: inset(44% 0 34% 0); }
  75%  { transform: translate(2px, -2px); clip-path: inset(78% 0 4% 0); }
  100% { transform: translate(0); clip-path: inset(0 0 100% 0); }
}

/* ---------- pieces the marketing pages rely on ---------- */

/* Feature rows: a copy column and a shot column, with .flip swapping their order. On one column the
   flip must be undone or every other row reads image-then-text while its neighbours read the other
   way, which looks like a bug rather than a rhythm. */
.feature .shot { justify-self: center; max-width: 244px; width: 100%; }
.feature .shot img {
  width: 100%; height: auto; display: block; border: 1px solid var(--line);
  clip-path: polygon(18px 0, 100% 0, 100% calc(100% - 18px), calc(100% - 18px) 100%, 0 100%, 0 18px);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(126, 240, 200, 0.12);
}
.feature.flip .copy { order: 2; }
@media (max-width: 820px) {
  .feature .shot { max-width: 240px; }
  .feature.flip .copy { order: 0; }
}

/* The header gains weight once you leave the top. */
.topbar.scrolled {
  background: var(--topbar-bg-scrolled);
  box-shadow: 0 1px 0 var(--topbar-edge), var(--topbar-shadow);
}

/* Signup states. */
.signup-msg { display: none; }
.signup-msg.show { display: block; }
.signup-msg.err { color: var(--coral); }
.signup-msg.ok { color: var(--mint); }
.signup-done { display: none; }
.signup.done .signup-form { display: none; }
.signup.done .signup-done { display: block; animation: riseIn 460ms cubic-bezier(0.2, 0.8, 0.2, 1); }
@keyframes riseIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

.sitefoot .legal { margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--line); font-size: 14px; color: var(--muted); }
.sitefoot .legal p { margin: 0 0 8px; }


/* The theme button, injected by site.js so it exists on every page without eight edits.
   44px, not the 34 it started at: this is a bare icon with no label, so its hit area is exactly its
   box, and small icon buttons are the single most missed control on a phone. */
.theme-toggle {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--line);
  color: var(--muted);
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center; cursor: pointer;
  clip-path: polygon(9px 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%, 0 9px);
  transition: color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
.theme-toggle svg { width: 19px; height: 19px; fill: currentColor; display: block; }
.theme-toggle:hover { color: var(--mint); border-color: var(--mint); box-shadow: 0 0 18px rgba(126, 240, 200, 0.2); }

/* ---------- light theme ---------- */
/* The OS preference decides by default; a recorded choice on <html data-theme> overrides it in both
   directions. Because every difference between the themes is a token, these are the only two blocks
   that exist: there are no light-mode component rules to keep in sync, and therefore none to forget.

   This is deliberately not a white page. A brushed-metal site lit from above is still made of metal
   in daylight, so the surfaces become concrete and steel rather than paper. Inverting to white would
   have thrown away the identity the rest of this file exists to build. */

html[data-theme="light"] {
  --bg: #c9d2d6;
  --surface-0: #c9d2d6;
  --surface-1: #dfe6e9;
  --surface-2: #eef3f5;
  --surface-3: #ffffff;
  --card: #eaf0f2;
  --card-2: #dde5e8;
  --ink: #0a1013;
  --muted: #3e484d;
  --line: #9aa8af;
  --line-soft: #b4c0c6;
  --mint: #005c45;
  --coral: #9c2f19;
  --amber: #6f4800;
  --violet: #472f83;
  --blue: #203163;
  --hazard: #b98a00;
  --steel-hi: #ffffff;
  --steel-lo: #74848d;

  /* Dark metal on a light ground. The white stops that make the dark theme's headline gleam would
     vanish here, so the ramp inverts: the bright band becomes the shadow and the body of each
     letter stays near-black, which is what keeps a 90px headline legible on concrete. */
  --chrome-grad: linear-gradient(180deg, #55646c 0%, #0a1013 28%, #4e5f68 50%, #0a1013 72%, #46555e 100%);
  --chrome-shadow: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.75));
  --shine-strength: rgba(255, 255, 255, 0.55);

  /* Scanlines and grain have to back off by roughly a factor of five. At dark-theme strength they
     read as dirt on a light page rather than as a CRT. */
  --grain-opacity: 0.022;
  --scan-color: rgba(0, 0, 0, 0.045);
  --vignette: rgba(20, 32, 38, 0.22);
  --vignette-start: 66%;
  --sweep-color: rgba(0, 92, 69, 0.05);
  --grid-color: rgba(0, 92, 69, 0.07);

  --topbar-bg: linear-gradient(180deg, rgba(223, 230, 233, 0.95), rgba(223, 230, 233, 0.8));
  --topbar-bg-scrolled: linear-gradient(180deg, rgba(223, 230, 233, 0.99), rgba(223, 230, 233, 0.93));
  --topbar-edge: rgba(0, 92, 69, 0.22);
  --topbar-shadow: 0 10px 26px rgba(0, 0, 0, 0.14);

  --btn-bg: linear-gradient(180deg, #0a8365 0%, #016248 45%, #01412f 100%);
  --btn-bg-hover: linear-gradient(180deg, #0d9a77 0%, #037a5c 45%, #02563f 100%);
  --btn-ink: #ffffff;
  --btn-inset: rgba(255, 255, 255, 0.32);

  --input-bg: #ffffff;
  --shot-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.55), 0 0 0 1px var(--line);
  --panel-edge: rgba(0, 92, 69, 0.4);
  --keyhole: #0a1013;
  --lock-hi: #8b9aa2;
  --lock-lo: #4d5b63;
  --lock-glow: rgba(0, 92, 69, 0.3);
  --stat-glow: none;
}

@media (prefers-color-scheme: light) {
  html:not([data-theme="dark"]) {
    --bg: #c9d2d6;
    --surface-0: #c9d2d6;
    --surface-1: #dfe6e9;
    --surface-2: #eef3f5;
    --surface-3: #ffffff;
    --card: #eaf0f2;
    --card-2: #dde5e8;
    --ink: #0a1013;
    --muted: #3e484d;
    --line: #9aa8af;
    --line-soft: #b4c0c6;
    --mint: #005c45;
    --coral: #9c2f19;
    --amber: #6f4800;
    --violet: #472f83;
    --blue: #203163;
    --hazard: #b98a00;
    --steel-hi: #ffffff;
    --steel-lo: #74848d;

    /* Dark metal on a light ground. The white stops that make the dark theme's headline gleam would
       vanish here, so the ramp inverts: the bright band becomes the shadow and the body of each
       letter stays near-black, which is what keeps a 90px headline legible on concrete. */
    --chrome-grad: linear-gradient(180deg, #55646c 0%, #0a1013 28%, #4e5f68 50%, #0a1013 72%, #46555e 100%);
    --chrome-shadow: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.75));
    --shine-strength: rgba(255, 255, 255, 0.55);

    /* Scanlines and grain have to back off by roughly a factor of five. At dark-theme strength they
       read as dirt on a light page rather than as a CRT. */
    --grain-opacity: 0.022;
    --scan-color: rgba(0, 0, 0, 0.045);
    --vignette: rgba(20, 32, 38, 0.22);
    --vignette-start: 66%;
    --sweep-color: rgba(0, 92, 69, 0.05);
    --grid-color: rgba(0, 92, 69, 0.07);

    --topbar-bg: linear-gradient(180deg, rgba(223, 230, 233, 0.95), rgba(223, 230, 233, 0.8));
    --topbar-bg-scrolled: linear-gradient(180deg, rgba(223, 230, 233, 0.99), rgba(223, 230, 233, 0.93));
    --topbar-edge: rgba(0, 92, 69, 0.22);
    --topbar-shadow: 0 10px 26px rgba(0, 0, 0, 0.14);

    --btn-bg: linear-gradient(180deg, #0a8365 0%, #016248 45%, #01412f 100%);
    --btn-bg-hover: linear-gradient(180deg, #0d9a77 0%, #037a5c 45%, #02563f 100%);
    --btn-ink: #ffffff;
    --btn-inset: rgba(255, 255, 255, 0.32);

    --input-bg: #ffffff;
    --shot-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.55), 0 0 0 1px var(--line);
    --panel-edge: rgba(0, 92, 69, 0.4);
    --keyhole: #0a1013;
    --lock-hi: #8b9aa2;
  --lock-lo: #4d5b63;
  --lock-glow: rgba(0, 92, 69, 0.3);
    --stat-glow: none;
  }
}

/* Stats inside the closing band. The band centres its text, but .cta is a flex row and flex does
   not inherit text-align, so the buttons need centring explicitly or they sit left under centred
   copy, which reads as a mistake rather than a choice. */
.cta-band .stats { margin: 30px 0 34px; }
.cta-band .cta { justify-content: center; }
.cta-band .sub { margin-left: auto; margin-right: auto; }
.cta-band .cta { display: flex; gap: 14px; flex-wrap: wrap; }

/* The `hidden` attribute is only a UA-stylesheet `display: none`, so ANY author rule that sets
   display beats it. `.hero .cta { display: flex }` did exactly that: the invite page set
   android-cta.hidden = true, the DOM agreed it was hidden, and the buttons stayed on screen for
   every iPhone visitor. A screenshot caught it; an assertion on el.hidden did not.

   This rule is last in the file and deliberately blunt. It was dropped once during a restyle and
   the same bug came straight back, which is the argument for it being a one-liner nobody has to
   reason about. */
[hidden] { display: none !important; }
