/* pyke.fit marketing site.
   Design tokens mirror the real app (src/index.css): near-black ground, one
   accent (Cold, #3a86ff), the same system-font stack, the same light-theme
   pairing. Nothing here is a second visual language - it is the app's palette
   applied to a page instead of a screen. Default is dark; a light scheme is
   offered to whoever's system prefers it, same as the app. */

:root {
  --bg: #0d0d0d;
  --gutter: #000;
  --surface: #161616;
  --surface-2: #202020;
  --surface-3: #2c2c2c;
  --ink: #ffffff;
  --ink-2: #a3a9b5;
  /* Raised from #6b7280, which measured 4.02:1 on --bg and failed AA at the
     13px this is used at. It carries the hero's development-status note, so it
     is disclosure rather than decoration and has to be readable. */
  --ink-3: #858c99;
  --line: #262626;
  --accent: #3a86ff;
  --accent-dim: #2e6bcc;
  --accent-ink: #000000;
  --accent-text: #3a86ff;
  --radius: 14px;
  /* Accent used as STRUCTURE, not just as text colour. Kept as separate tokens
     so the light theme can weaken them independently: the same alpha that reads
     as a whisper on near-black reads as a smudge on near-white. */
  --accent-rule: rgba(58, 134, 255, 0.55);
  --accent-veil: rgba(58, 134, 255, 0.13);
  --accent-haze: rgba(58, 134, 255, 0.07);
  --font: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif;
  --ease: cubic-bezier(0.22, 0.9, 0.3, 1);
  color-scheme: dark;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f2f4f7;
    --gutter: #e2e6eb;
    --surface: #ffffff;
    --surface-2: #edf0f4;
    --surface-3: #dee2e8;
    --ink: #181a1e;
    --ink-2: #5f656e;
    /* Was #868d97, which measures 3.04:1 on --bg: a clear AA failure, not a
       near miss. This token carries the hero's development-status note, which is
       disclosure rather than chrome, so it has to be readable. Now 4.79:1. */
    --ink-3: #666c76;
    --line: #dee2e8;
    --accent: #3a86ff;
    --accent-dim: #2e6bcc;
    --accent-ink: #000000;
    --accent-text: #2e6bcc;
    --accent-rule: rgba(46, 107, 204, 0.42);
    --accent-veil: rgba(46, 107, 204, 0.10);
    --accent-haze: rgba(46, 107, 204, 0.05);
    color-scheme: light;
  }
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { background: var(--gutter); }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  line-height: 1.5;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }

.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
.wrap-narrow { max-width: 720px; margin: 0 auto; padding: 0 24px; }

main { display: block; }

.sec { padding: 96px 0; }
.sec-tight { padding: 64px 0; }

h1, h2, h3 { margin: 0; font-weight: 700; letter-spacing: -0.02em; color: var(--ink); }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-text);
}
.eyebrow::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

.display {
  /* The single largest lever available without a webfont. -apple-system
     resolves to SF Pro, which carries a true display optical size, so it holds
     tracking this tight at this scale where a UI-optimised face would not. */
  font-size: clamp(44px, 9vw, 108px);
  /* ⚠️ 1.02, NOT the 0.96 this started at. Measured with canvas TextMetrics on
     the resolved face: descender ink falls 7/32em below the baseline and
     ascender ink rises 24/32em above it, so two consecutive lines need a ratio
     above (7+24)/32 = 0.969 or the glyphs physically OVERLAP. Both ratios are
     independent of font-size, so 0.96 collided at EVERY size, not just small
     ones: measured -0.28px on /contact at 375px, where "Talk to a person," sits
     a descender directly over "not a bot."
     The hero escaped only because "Train alone." contains no descenders, which
     made the layout depend on the copy. 1.02 clears by 1.6px at 32px and 5.5px
     at 108px, and still reads as tight display leading. */
  line-height: 1.02;
  font-weight: 800;
  letter-spacing: -0.045em;
  color: var(--ink);
  text-wrap: balance;
}
.display em {
  font-style: normal;
  color: var(--accent-text);
}

.lede {
  font-size: clamp(17px, 2vw, 20px);
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 46ch;
}

.body-md { font-size: 16px; line-height: 1.6; color: var(--ink-2); }
.mono { font-variant-numeric: tabular-nums; }

/* ── Buttons ── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 48px;
  padding: 0 22px;
  border-radius: 999px;
  font-size: 15px;
  font-weight: 700;
  border: 1px solid transparent;
  transition: transform 150ms var(--ease), opacity 150ms var(--ease), background 150ms var(--ease), border-color 150ms var(--ease);
  cursor: pointer;
}
.btn:active { transform: scale(0.97); }
/* Scoped: a bare link must not inherit the pill radius. It is invisible today
   because no plain link has a background, but it would silently deform the
   first one that does. */
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 999px; }
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.btn-lg { height: 54px; padding: 0 28px; font-size: 16px; }
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-dim); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn-ghost:hover { border-color: var(--ink-3); }

/* ── Nav ── */
#nav {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 24px;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color 200ms var(--ease), background 200ms var(--ease);
}
#nav.scrolled { border-bottom-color: var(--line); }

.nav-logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 800;
  font-size: 17px;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.nav-logo svg { width: 22px; height: 22px; }
.nav-links {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 28px;
  margin: 0;
  padding: 0;
}
.nav-links a {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
  transition: color 150ms var(--ease);
}
.nav-links a:hover, .nav-links a[aria-current="page"] { color: var(--ink); }

/* Nav CTA. Shorter than a page button so it does not inflate the bar.
   ⚠️ SELECTOR SCOPED ON PURPOSE. `.nav-links a` is (0,0,1,1) and outranks both
   `.btn-primary` and a bare `.nav-cta`, both (0,0,1,0). Written the obvious way
   this button rendered --ink-2 grey on the accent fill at 1.47:1, which is not
   readable, and nothing in the markup looked wrong. Keep the `.nav-links`
   prefix on BOTH rules; the hover rule needs it for the same reason. */
.nav-links a.nav-cta {
  height: 38px;
  padding: 0 16px;
  font-size: 14px;
  color: var(--accent-ink);
}
.nav-links a.nav-cta:hover { color: var(--accent-ink); background: var(--accent-dim); }

@media (max-width: 720px) {
  .nav-links { gap: 16px; }
  .nav-links a { font-size: 13px; }
  .nav-logo span.wordmark { display: none; }
  /* The bar now carries five items. Privacy and Terms are one tap away in the
     footer of every page, so they yield first and the CTA survives. Store
     review reaches them from the footer, which is where reviewers look. */
  .nav-links li.nav-legal { display: none; }
  /* 40px, not 36: the repo's own touch-target floor is 44 and this is the one
     control in the bar. The bar grows by 4px, which is the whole cost. */
  .nav-links a.nav-cta { height: 40px; padding: 0 15px; font-size: 13.5px; }
}
/* No 400px rule. One was written here to hide "Home" on the smallest phones and
   it was NOT earned: measured at 320px with Home shown, the bar needs 299.6px of
   320 and the CTA still sets on one line, so there is 20.4px spare. Hiding a
   link that fits is a loss for nothing. */

/* ── Footer ── */
footer { border-top: 1px solid var(--line); padding: 56px 0 32px; }
.ft-top {
  display: flex;
  flex-wrap: wrap;
  gap: 32px;
  justify-content: space-between;
  align-items: flex-start;
}
.ft-brand { display: flex; align-items: center; gap: 10px; font-weight: 800; font-size: 16px; }
.ft-brand svg { width: 20px; height: 20px; }
.ft-tag { margin: 10px 0 0; font-size: 14px; color: var(--ink-2); max-width: 32ch; }
.ft-links { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 20px; }
.ft-links a { font-size: 14px; color: var(--ink-2); }
.ft-links a:hover { color: var(--ink); }
.ft-bottom {
  margin-top: 40px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 10px 20px;
  justify-content: space-between;
  font-size: 13px;
  color: var(--ink-3);
}

/* ── Hero ── */
.hero { padding: 136px 0 84px; position: relative; overflow: hidden; }
/* STATIC field. Deliberately a plain CSS gradient and not an animated canvas:
   it is identical in a screenshot, needs no requestAnimationFrame, and cannot
   fail the way an animated reveal can in a document that never gets a frame.
   That failure mode blanked the previous product's onboarding for three weeks. */
.hero::before {
  content: '';
  position: absolute;
  inset: -30% -10% auto -10%;
  height: 150%;
  background:
    radial-gradient(52% 46% at 74% 16%, var(--accent-veil) 0%, transparent 68%),
    radial-gradient(40% 38% at 8% 4%, var(--accent-haze) 0%, transparent 70%);
  pointer-events: none;
  z-index: 0;
}
.hero > .wrap { position: relative; z-index: 1; }
.hero-mark { position: absolute; top: -70px; right: -50px; width: 380px; height: 380px; opacity: 0.07; pointer-events: none; z-index: 0; }
.hero-mark svg { width: 100%; height: 100%; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 34px; }
.hero-note { margin-top: 20px; font-size: 13px; color: var(--ink-3); }

/* ── Stat strip ── */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.stat { background: var(--surface); padding: 26px 22px; }
.stat b {
  display: block;
  font-size: clamp(38px, 5.4vw, 62px);
  line-height: 1;
  font-weight: 800;
  letter-spacing: -0.04em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* The one figure that is a claim about privacy rather than about volume. It is
   the only accent numeral on the page, so it cannot compete with itself. */
.stat.is-key b { color: var(--accent-text); }
.stat span { display: block; margin-top: 12px; font-size: 13.5px; line-height: 1.45; color: var(--ink-2); }

/* ── Feature rows (spec-sheet style, not icon cards) ── */
.rows { border-top: 1px solid var(--line); }
.row {
  position: relative;
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) minmax(0, 1.3fr);
  gap: 24px;
  align-items: baseline;
  padding: 32px 0 32px 20px;
  border-bottom: 1px solid var(--line);
  /* No background transition here on purpose. `.js-rv-armed .rv` is (0,0,2,0)
     and every .row carries .rv, so any `transition` declared on `.row` is
     overridden the moment JS arms the reveal, and the hover would snap while
     the spine faded. The spine's transition sits on ::before, which nothing
     overrides, so the motion that matters survives. */
}
/* The spine. Scales from the baseline on hover rather than fading in, so it
   reads as a rule being drawn down the row. Purely decorative: it carries no
   information the text does not already carry. Where there is no hover to
   trigger it, it is SHOWN AT REST instead (see the touch and reduced-motion
   blocks below) rather than dropped, so the row never looks unfinished. */
.row::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 2px;
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: 50% 100%;
  transition: transform 320ms var(--ease);
}
.row:hover::before { transform: scaleY(1); }
.row:hover { background: var(--accent-haze); }
.row-idx { font-size: 12px; font-weight: 700; color: var(--accent-text); font-variant-numeric: tabular-nums; letter-spacing: 0.04em; }
.row-title { font-size: 20px; font-weight: 700; letter-spacing: -0.02em; color: var(--ink); }
.row-desc { font-size: 15px; line-height: 1.6; color: var(--ink-2); }

@media (max-width: 720px) {
  .row { grid-template-columns: 1fr; gap: 7px; padding: 24px 0 24px 18px; }
  .row-idx { order: -1; }
  /* No hover on touch, so the spine would never appear. Draw it at rest. */
  .row::before { transform: scaleY(1); opacity: 0.45; }
}

/* ── Accent hairline. Structure, not decoration: it marks where a section
      begins, which the eyebrow alone was doing on its own. ── */
.rule {
  height: 1px;
  border: 0;
  margin: 0 0 40px;
  background: linear-gradient(90deg, var(--accent-rule), transparent 62%);
}

/* ── Section header ── */
.sec-head { max-width: 720px; }
.sec-head h2 { font-size: clamp(28px, 4.4vw, 44px); margin-top: 14px; letter-spacing: -0.03em; line-height: 1.08; text-wrap: balance; }
.sec-head p { margin-top: 12px; }

/* ── Note / callout ── */
.note {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px 22px;
  background: var(--surface);
  font-size: 14px;
  color: var(--ink-2);
}
.note b { color: var(--ink); }

/* ── Process steps (used on delete-account) ── */
.steps { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; margin-top: 22px; }
.step { background: var(--surface); padding: 20px 22px; display: flex; gap: 16px; align-items: flex-start; }
.step-n {
  flex: none;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--surface-2);
  border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; color: var(--ink);
}
.step-body b { display: block; font-size: 15px; color: var(--ink); }
.step-body span { display: block; margin-top: 3px; font-size: 14px; color: var(--ink-2); }

.check-list { list-style: none; margin: 20px 0 0; padding: 0; display: grid; gap: 12px; }
.check-list li { display: flex; gap: 12px; align-items: flex-start; font-size: 15px; color: var(--ink-2); }
.check-list svg { flex: none; width: 18px; height: 18px; margin-top: 2px; color: var(--accent-text); }

/* ── FAQ ── */
.faq-item { border-bottom: 1px solid var(--line); padding: 22px 0; }
.faq-item h3 { font-size: 16px; font-weight: 700; }
.faq-item p { margin: 10px 0 0; font-size: 15px; color: var(--ink-2); line-height: 1.6; }

/* ── Contact card ── */
.contact-card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 30px 28px;
  background: var(--surface);
}
.contact-card a.mail { color: var(--accent-text); font-weight: 700; font-size: 18px; }

/* ── 404 ── */
.err-code { font-size: 13px; font-weight: 700; letter-spacing: 0.1em; color: var(--ink-3); text-transform: uppercase; }

/* ── Reveal (progressive enhancement only - never hides content by default) ── */
.rv { opacity: 1; transform: none; }
.js-rv-armed .rv {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 560ms var(--ease), transform 560ms var(--ease);
}
.js-rv-armed .rv.in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .btn, .rv, #nav, .row, .row::before { transition: none !important; }
  .row::before { transform: scaleY(1); opacity: 0.45; }
}

/* ════════════════════════════════════════════════════════════════════════════
   HERO: one quiet field, one loud subject.
   The field is a backdrop and is built to be ignored. The device in front of
   it is what the eye lands on. Getting that hierarchy backwards is what turns
   a hero into a screensaver.
   ════════════════════════════════════════════════════════════════════════════ */

.hero {
  position: relative;
  overflow: hidden;
  padding: 118px 0 96px;
  /* ⚠️ FALLBACK FIRST. This gradient is what shows when WebGL is absent,
     blocked, reduced-motion, or has lost its context. The canvas paints OVER
     it and only when it has proven it works, so the worst case is a still
     image rather than a black rectangle. */
  background:
    radial-gradient(58% 48% at 76% 14%, rgba(58,134,255,0.10) 0%, transparent 68%),
    radial-gradient(44% 40% at 6% 2%,   rgba(58,134,255,0.06) 0%, transparent 72%);
}

#field {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
  pointer-events: none;
  z-index: 0;
  /* Raised ONLY by JS after a successful init. See hero.js. */
  opacity: 0;
  transition: opacity 900ms var(--ease);
}
#field.is-on { opacity: 1; }

/* Everything readable sits above the field, and the field never intercepts a
   pointer event, so text stays selectable and links stay clickable. */
.hero > .wrap { position: relative; z-index: 2; }

/* A soft floor so the field does not end on a hard edge against the section
   below it. */
.hero::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 34%;
  background: linear-gradient(180deg, transparent, var(--bg));
  pointer-events: none;
  z-index: 1;
}

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: 48px;
  align-items: center;
}
@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; gap: 44px; }
}

/* ── The device ──────────────────────────────────────────────────────────────
   Real CSS 3D, not a flat mockup: the perspective is what makes it read as an
   object in the room rather than a picture pasted on the page. */
.dev-stage {
  perspective: 1400px;
  display: flex;
  justify-content: center;
}
.dev {
  width: 300px;
  max-width: 100%;
  border-radius: 42px;
  padding: 10px;
  background: linear-gradient(160deg, #2c2f36, #14161a 42%, #26292f);
  box-shadow:
    0 46px 90px -28px rgba(0,0,0,0.9),
    0 0 0 1px rgba(255,255,255,0.06),
    0 0 70px -14px rgba(58,134,255,0.3);
  transform: rotateY(-13deg) rotateX(5deg) rotateZ(-1deg);
  transform-style: preserve-3d;
  transition: transform 700ms var(--ease);
}
@media (hover: hover) {
  .dev:hover { transform: rotateY(-7deg) rotateX(3deg) rotateZ(0deg); }
}
@media (max-width: 900px) {
  .dev { transform: rotateY(-6deg) rotateX(3deg); }
}
.dev-screen {
  position: relative;
  border-radius: 33px;
  background: #0b0b0c;
  overflow: hidden;
  padding: 16px 15px 18px;
  min-height: 386px;
}
/* The pill. Cheap, and its absence is instantly legible as "not a phone". */
.dev-screen::before {
  content: '';
  position: absolute;
  top: 9px; left: 50%;
  transform: translateX(-50%);
  width: 74px; height: 20px;
  border-radius: 999px;
  background: #000;
  z-index: 3;
}

.dev-top {
  display: flex; align-items: center; justify-content: space-between;
  margin: 26px 2px 16px;
}
.dev-ex { font-size: 15px; font-weight: 700; color: var(--ink); letter-spacing: -0.02em; }
.dev-meta { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }

.dev-head {
  display: grid;
  grid-template-columns: 26px 1fr 1fr 30px;
  gap: 8px;
  padding: 0 4px 8px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  border-bottom: 1px solid var(--line);
}
.set {
  display: grid;
  grid-template-columns: 26px 1fr 1fr 30px;
  gap: 8px;
  align-items: center;
  padding: 11px 4px;
  border-bottom: 1px solid var(--line);
}
.set-n { font-size: 12px; font-weight: 700; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.set-box {
  height: 30px;
  border-radius: 8px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
  transition: background 260ms var(--ease), border-color 260ms var(--ease), color 260ms var(--ease);
}
.set-tick {
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  transition: background 260ms var(--ease), border-color 260ms var(--ease), transform 260ms var(--ease);
}
.set-tick svg { width: 12px; height: 12px; opacity: 0; transition: opacity 200ms var(--ease); }
.set.is-done .set-box { border-color: rgba(58,134,255,0.42); background: var(--accent-haze); }
.set.is-done .set-tick { background: var(--accent); border-color: var(--accent); transform: scale(1.06); }
.set.is-done .set-tick svg { opacity: 1; }

/* The payoff line. It appears only on the set that beat the last one, because
   that single moment IS the product. */
.dev-hint {
  margin-top: 14px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--accent-haze);
  border: 1px solid rgba(58,134,255,0.34);
  font-size: 12px;
  font-weight: 600;
  color: var(--accent-text);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}
.dev-hint.is-on { opacity: 1; transform: none; }

/* ── Oversized ghost numeral behind a section head ───────────────────────────
   Structure, not decoration: it marks the section's ordinal. Pure background,
   aria-hidden, and never sized so it can push layout. */
.ghost {
  position: absolute;
  top: -34px; left: -14px;
  font-size: clamp(150px, 22vw, 300px);
  font-weight: 800;
  line-height: 0.8;
  letter-spacing: -0.06em;
  color: transparent;
  -webkit-text-stroke: 1px var(--line);
  pointer-events: none;
  user-select: none;
  z-index: 0;
}
.has-ghost { position: relative; }
.has-ghost > *:not(.ghost) { position: relative; z-index: 1; }

/* ── Marquee ─────────────────────────────────────────────────────────────────
   Duplicated track so the loop is seamless. Pauses under reduced motion. */
.marquee {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 18px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.marquee-track { display: flex; width: max-content; animation: mq 34s linear infinite; }
.marquee-track span {
  display: inline-flex; align-items: center;
  padding: 0 26px;
  font-size: 12.5px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
.marquee-track span::after {
  content: '';
  width: 4px; height: 4px; margin-left: 26px;
  border-radius: 50%; background: var(--accent);
  opacity: 0.5;
}
@keyframes mq { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; }
  .dev { transition: none; }
}
