/* shikki.io stylesheet — v1.1.40
   v1.1.40: header-logo swapped from transparent PNG to header-logo-crt.svg
            (sumi-blue + CRT scanline pattern behind the flame). Aligns the
            always-visible header mark with the hero terminal aesthetic.
            Pattern reused from /tmp/logo-square/ until shi anim brand-asset
            preset ships per spec PR #197 W2 follow-up. */
/* ── Reset & Base ─────────────────────────────────── */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

/* ── Dracula Tokens + DSKintsugi accent ──────────── */
:root {
  --bg: #282a36;
  --bg-dark: #1e1f29;
  --bg-card: #44475a;
  --fg: #f8f8f2;
  --dim: #6272a4;
  --purple: #bd93f9;
  --pink: #ff79c6;
  --green: #50fa7b;
  --cyan: #8be9fd;
  --orange: #ffb86c;
  --red: #ff5555;
  --yellow: #f1fa8c;
  --glow: rgba(189, 147, 249, 0.4);
  --radius: 8px;
  --mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', ui-monospace, monospace;
  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, sans-serif;
  /* v1.1 brand-purple/green tokens for install-snippet, contact-form, popins.
     Replaces the kintsugi-gold --accent that shipped briefly with v1.1. */
  --terminal-bg: var(--bg-dark);
  --terminal-fg: var(--fg);
  --terminal-border: var(--bg-card);
  --terminal-glow: 0 24px 80px rgba(0, 0, 0, 0.5),
                   0 0 40px rgba(189, 147, 249, 0.18);
}

html { scroll-behavior: smooth; font-size: 16px; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  line-height: 1.6;
  overflow-x: hidden;
}

a { color: var(--purple); text-decoration: none; transition: opacity 0.2s; }
a:hover { opacity: 0.8; }

/* ── Header ──────────────────────────────────────── */
.header {
  position: fixed; top: 0; left: 0; right: 0;
  z-index: 100;
  display: flex; align-items: center; justify-content: space-between;
  padding: 1.25rem 2rem;
  background: rgba(40, 42, 54, 0.85);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.header-logo {
  /* v1.1.9: header-logo is the flame image only, hidden by default.
     Operator pattern (sigma-analytics): img fades in on scroll once the
     hero-badge leaves the viewport. No text wordmark — logo OR nothing. */
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 48px;
  text-decoration: none;
  line-height: 1;
  /* Reserve width so the nav doesn't reflow when the img fades in. */
  width: 130px;
}
.header-logo-img {
  display: block;
  height: 48px;
  width: auto;
  opacity: 0;
  transform: translateX(-12px);
  transition:
    opacity 0.3s ease-in-out,
    transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  pointer-events: none;
}
.header-logo.is-scrolled .header-logo-img {
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .header-logo-img { transition: none; }
}
/* v1.1.7: header logo reverted to text wordmark — image moved into the
   hero-badge. Old `.header-logo-img` rule deleted. */
.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
}
.hero-badge-logo {
  display: block;
  height: 28px;
  width: auto;
}
.hero-badge-text {
  display: inline-block;
}

.header-nav { display: flex; gap: 1.5rem; align-items: center; }
.header-nav a {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--dim);
  text-decoration: none;
  transition: color 0.2s;
}
.header-nav a:hover { color: var(--fg); opacity: 1; }
.header-nav .nav-cta {
  color: var(--bg);
  background: var(--purple);
  padding: 0.4rem 1rem;
  border-radius: var(--radius);
  font-weight: 500;
}
.header-nav .nav-cta:hover { opacity: 0.9; }

/* ── Main ────────────────────────────────────────── */
main {
  max-width: 960px;
  margin: 0 auto;
  padding: 0 2rem;
}

main > section { margin-bottom: 6rem; }
main > section:last-child { margin-bottom: 4rem; }

.section-title {
  font-family: var(--sans);
  font-size: clamp(1.4rem, 3vw, 2rem);
  font-weight: 300;
  text-align: center;
  margin-bottom: 3rem;
  letter-spacing: -0.01em;
}

/* ── Hero ────────────────────────────────────────── */
.hero {
  min-height: 100vh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  padding: 6rem 0 5rem;
  /* v1.1.39: position:relative anchors the absolute scroll-cue to the
     bottom of the first viewport (wabisabi / sigma-analytics pattern). */
  position: relative;
}

.hero-badge {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.15em;
  color: var(--purple);
  text-transform: uppercase;
  margin-bottom: 1.5rem;
  padding: 0.3rem 1rem;
  border: 1px solid rgba(189, 147, 249, 0.3);
  border-radius: 20px;
}

.hero h1 {
  font-family: var(--sans);
  font-size: clamp(2rem, 6vw, 3.6rem);
  font-weight: 300;
  letter-spacing: -0.02em;
  line-height: 1.2;
  margin-bottom: 1.2rem;
}

.hero-tagline {
  font-size: clamp(0.95rem, 2vw, 1.15rem);
  color: var(--dim);
  line-height: 1.6;
  max-width: 540px;
  margin-bottom: 2rem;
}
.hero-tagline strong { color: var(--fg); font-weight: 600; }

.hero-cta {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--bg);
  background: var(--purple);
  padding: 0.7rem 2rem;
  border-radius: var(--radius);
  text-decoration: none;
  transition: box-shadow 0.3s, transform 0.2s;
  margin-bottom: 3rem;
}
.hero-cta:hover {
  box-shadow: 0 0 24px var(--glow);
  transform: translateY(-1px);
  opacity: 1;
}

/* ── Terminal ────────────────────────────────────── */
.hero-terminal {
  width: 100%;
  max-width: 600px;
  background: var(--bg-dark);
  border: 1px solid var(--bg-card);
  border-radius: var(--radius);
  box-shadow: var(--terminal-glow);
  overflow: hidden;
  text-align: left;
}

.terminal-bar {
  display: flex; align-items: center; gap: 0.4rem;
  padding: 0.6rem 1rem;
  background: rgba(68, 71, 90, 0.5);
}
.terminal-dot {
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--bg-card);
}
.terminal-dot:first-child { background: var(--red); }
.terminal-dot:nth-child(2) { background: var(--yellow); }
.terminal-dot:nth-child(3) { background: var(--green); }
.terminal-title {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--dim);
}

.terminal-body {
  padding: 1rem 1.2rem;
  position: relative;
  /* v1.1.32: stack-grid so logo + content share the same cell. Cell
     height = max(logo, content) → terminal-body keeps stable height
     across the logo→content handoff (no shrink-then-grow pop). */
  display: grid;
  grid-template-areas: "stack";
  align-items: stretch;
}
.terminal-body > .terminal-logo-intro,
.terminal-body > .terminal-content {
  grid-area: stack;
}
/* v1.1.30: CRT scanline overlay on the terminal body — visible during the
   terminal entrance + logo intro cycle, fades out when the demo lines start
   typing. JS toggles `.is-crt-fading` on `.terminal-body`. */
.terminal-body::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0, transparent 2px,
    rgba(0, 0, 0, 0.18) 2px, rgba(0, 0, 0, 0.18) 3px);
  mix-blend-mode: multiply;
  z-index: 5;
  opacity: 1;
  /* v1.1.31: longer + gentler fade-out so the scanline disappearance
     doesn't read as a luminance pop. ease-in-out smooths the start
     where the eye is most sensitive to onset. */
  transition: opacity 800ms ease-in-out;
  will-change: opacity;
}
.terminal-body.is-crt-fading::before { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .terminal-body::before { display: none; }
}

.terminal-line {
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1.8;
  white-space: nowrap;
  overflow: hidden;
}

.t-prompt { color: var(--green); margin-right: 0.5rem; }
.t-muted { color: var(--dim); }
.t-green { color: var(--green); }
.t-purple { color: var(--purple); }

.terminal-line.typing {
  overflow: hidden;
  border-right: 2px solid var(--green);
  white-space: nowrap;
  animation: typing 1.5s steps(40, end) 2s forwards,
             blink-caret 0.6s step-end 6 2s;
  width: 0;
}

@keyframes typing {
  from { width: 0; }
  to { width: 100%; }
}
@keyframes blink-caret {
  from, to { border-color: transparent; }
  50% { border-color: var(--green); }
}

/* ── Features ────────────────────────────────────── */
.features { padding-top: 2rem; }

.features-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}

.feature-card {
  background: var(--bg-dark);
  border: 1px solid var(--bg-card);
  border-radius: var(--radius);
  padding: 2rem 1.5rem;
  transition: border-color 0.3s, box-shadow 0.3s;
}
.feature-card:hover {
  border-color: var(--purple);
  box-shadow: 0 0 20px rgba(189, 147, 249, 0.06);
}

.feature-icon {
  font-size: 1.4rem;
  color: var(--purple);
  margin-bottom: 0.8rem;
}

.feature-card h3 {
  font-family: var(--mono);
  font-size: 0.9rem;
  font-weight: 500;
  margin-bottom: 0.5rem;
}

.feature-card p {
  font-size: 0.85rem;
  color: var(--dim);
  line-height: 1.55;
}

/* ── Architecture ────────────────────────────────── */
.arch-flow {
  display: flex;
  align-items: flex-start;
  gap: 0;
}

.arch-step {
  flex: 1;
  text-align: center;
  padding: 0 1rem;
}

.arch-num {
  font-family: var(--mono);
  font-size: 2rem;
  font-weight: 700;
  color: var(--purple);
  opacity: 0.3;
  margin-bottom: 0.8rem;
}

.arch-step h3 {
  font-family: var(--mono);
  font-size: 1rem;
  font-weight: 500;
  margin-bottom: 0.5rem;
}

.arch-step p {
  font-size: 0.85rem;
  color: var(--dim);
  line-height: 1.55;
}

.arch-connector {
  width: 60px;
  min-width: 60px;
  height: 1px;
  background: var(--bg-card);
  margin-top: 2.2rem;
  position: relative;
}
.arch-connector::after {
  /* v1.1.14: switched from font-glyph (`›`) to a pure-CSS triangle.
     Glyph approach was unreliable — fonts render `›` with non-uniform
     leading/baseline offsets, so even with line-height:1 the optical
     center never matched the 1px line's center. A border-triangle has
     no glyph metrics; its geometric center IS its visual center. */
  content: '';
  position: absolute;
  right: -1px;
  top: 50%;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 4px 0 4px 6px;
  border-color: transparent transparent transparent var(--purple);
  transform: translateY(-50%);
}

/* v1.1.19: scroll-margin-top on every section — fallback for any anchor
   navigation NOT routed through the JS handler (browser-native hash links,
   bookmarks landing on a hash). Matches the live header offset. */
main > section { scroll-margin-top: 96px; }

/* ── v1.1.13: Download — own section, sits between hero and shikki-flow.
   Generous top margin for breathing room from the hero-terminal. */
.download {
  text-align: center;
  padding: 4rem 0 2rem;
}
.download .section-title {
  margin-bottom: 0.5rem;
}
.download-sub {
  color: var(--dim);
  font-size: 0.95rem;
  margin: 0 auto 1.75rem;
  max-width: 480px;
}
.download .install-snippet {
  margin-left: auto;
  margin-right: auto;
}

/* Generic section subhead used by download + shikki-flow. */
.section-sub {
  color: var(--dim);
  font-size: 0.95rem;
  text-align: center;
  margin: 0 auto 2.5rem;
  max-width: 600px;
}

/* ── Waitlist ────────────────────────────────────── */
.waitlist {
  text-align: center;
  padding: 4rem 0;
}

.waitlist-sub {
  color: var(--dim);
  font-size: 0.95rem;
  margin-top: -2rem;
  margin-bottom: 2rem;
  max-width: 480px;
  margin-left: auto;
  margin-right: auto;
}

.waitlist-form {
  display: flex;
  gap: 0.75rem;
  max-width: 440px;
  margin: 0 auto;
}

.waitlist-form input {
  flex: 1;
  font-family: var(--mono);
  font-size: 0.85rem;
  padding: 0.75rem 1rem;
  background: var(--bg-dark);
  border: 1px solid var(--bg-card);
  border-radius: var(--radius);
  color: var(--fg);
  outline: none;
  transition: border-color 0.2s;
}
.waitlist-form input::placeholder { color: var(--dim); }
.waitlist-form input:focus { border-color: var(--purple); }

.waitlist-form button {
  font-family: var(--mono);
  font-size: 0.85rem;
  font-weight: 500;
  padding: 0.75rem 1.5rem;
  background: var(--purple);
  color: var(--bg);
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  transition: box-shadow 0.3s, transform 0.2s;
  white-space: nowrap;
}
.waitlist-form button:hover {
  box-shadow: 0 0 20px var(--glow);
  transform: translateY(-1px);
}
.waitlist-form button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.waitlist-msg {
  font-family: var(--mono);
  font-size: 0.8rem;
  margin-top: 1rem;
  min-height: 1.2em;
}
.waitlist-msg.success { color: var(--green); }
.waitlist-msg.error { color: var(--red); }

/* ── Footer ──────────────────────────────────────── */
.footer {
  border-top: 1px solid var(--bg-card);
  padding: 2rem;
}

.footer-inner {
  max-width: 960px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--dim);
}

.footer-inner a { color: var(--dim); }
.footer-inner a:hover { color: var(--purple); }

/* ── Responsive ──────────────────────────────────── */
@media (max-width: 900px) {
  .features-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 768px) {
  main { padding: 0 1.5rem; }
  main > section { margin-bottom: 4rem; }

  .header { padding: 1rem 1.5rem; }

  .arch-flow { flex-direction: column; align-items: center; gap: 1.5rem; }
  .arch-connector {
    width: 1px; height: 40px; min-width: 1px;
    margin-top: 0;
  }
  .arch-connector::after {
    /* v1.1.14: mobile (vertical 1px line) — downward-pointing triangle.
       Same border-triangle trick, just orientation flipped (top border
       solid, others transparent). */
    content: '';
    top: auto;
    bottom: -1px;
    right: 50%;
    border-width: 6px 4px 0 4px;
    border-color: var(--purple) transparent transparent transparent;
    transform: translateX(50%);
  }

  .footer-inner { flex-direction: column; gap: 0.5rem; text-align: center; }
}

@media (max-width: 480px) {
  main { padding: 0 1rem; }
  .header { padding: 1rem; }

  .features-grid { grid-template-columns: 1fr; }
  .feature-card { padding: 1.5rem 1.2rem; }

  .hero h1 { font-size: clamp(1.6rem, 7vw, 2rem); }
  .hero-tagline br { display: none; }

  .waitlist-form { flex-direction: column; }
  .waitlist-form button { width: 100%; }

  /* v1.1.42: terminal text + animation cut by viewport on iPhone-class
     screens (operator screenshot 2026-05-06). The longest line is the
     install command (~60 mono chars). Use clamp(min, vw, max) so the
     font scales DOWN with viewport width and stops shrinking at 0.55rem
     — keeping the line legible while making it fit a 360px-wide screen
     without horizontal cut. terminal-body padding is also tightened on
     this breakpoint to claw back a few px on the smallest devices. */
  .terminal-line { font-size: clamp(0.55rem, 2.4vw, 0.75rem); }
  .terminal-body { padding: 0.8rem 0.7rem; }
}

/* ── Utilities ───────────────────────────────────── */
::selection { background: var(--purple); color: var(--bg); }

/* ── v1.1: terminal-internal logo intro (additive, no v1 overrides) ── */
.terminal-logo-intro {
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem 0;
  /* v1.1.30: logo starts HIDDEN; the .is-appearing class drives the
     combo TV-off-reverse + phosphor-flash entrance from gallery cell #13. */
  opacity: 0;
  transform: scale(0);
  will-change: transform, opacity, filter;
}
.terminal-logo-intro.is-appearing {
  animation: anim-hero-logo-in 520ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.terminal-logo-intro.is-bursting {
  /* Stays visible at full size; only filter/transform jitter momentarily.
     Equivalent to gallery cell #5 (CRT degauss). */
  animation: anim-hero-logo-burst 700ms steps(14, end) forwards;
  opacity: 1;
  transform: scale(1);
}
.terminal-logo-intro.is-disappearing {
  /* Pixel-glitch dissolve — gallery cell #7. */
  animation: anim-hero-logo-out 720ms steps(18, end) forwards;
}

/* APPEAR — TV-off reverse seeded by phosphor flash. */
@keyframes anim-hero-logo-in {
  0%   { opacity:0; transform:scale(0); filter:brightness(1) saturate(0) drop-shadow(0 0 0 transparent) }
  10%  { opacity:0.85; transform:scale(0.012); filter:brightness(5) saturate(0) drop-shadow(0 0 12px white) }
  22%  { opacity:1; transform:scaleY(0.025) scaleX(1.12); filter:brightness(3.5) saturate(0.4) drop-shadow(0 0 28px white) }
  35%  { opacity:1; transform:scaleY(0.06) scaleX(1.08); filter:brightness(3) saturate(0.6) drop-shadow(0 0 24px white) }
  55%  { opacity:1; transform:scaleY(0.6) scaleX(1.04); filter:brightness(2.2) saturate(0.9) drop-shadow(0 0 18px white) }
  72%  { opacity:1; transform:scale(1.06); filter:brightness(2.4) saturate(1.1) drop-shadow(0 0 22px white) }
  85%  { opacity:1; transform:scale(1.02); filter:brightness(1.6) saturate(1.1) drop-shadow(0 0 10px var(--purple)) }
  100% { opacity:1; transform:scale(1); filter:brightness(1) saturate(1) drop-shadow(0 0 0 transparent) }
}

/* BURST — CRT degauss-style RGB split + jitter pulse, returns to clean. */
@keyframes anim-hero-logo-burst {
  0%, 100% { transform:translate(0,0) scale(1); filter:hue-rotate(0deg) saturate(1) drop-shadow(0 0 0 transparent) drop-shadow(0 0 0 transparent); opacity:1 }
  10% { transform:translate(-3px,1px) scale(1); filter:hue-rotate(60deg) saturate(1.5) drop-shadow(4px 0 0 #ff5050) drop-shadow(-4px 0 0 #50c8ff) }
  20% { transform:translate(4px,-2px) scale(1); filter:hue-rotate(-30deg) saturate(1.4) drop-shadow(-4px 0 0 #ff5050) drop-shadow(4px 0 0 #50c8ff) }
  30% { transform:translate(-2px,3px) scale(1); filter:hue-rotate(120deg) saturate(1.3) drop-shadow(3px 1px 0 #ff5050) drop-shadow(-3px -1px 0 #50c8ff) }
  45% { transform:translate(3px,0) scale(1); filter:hue-rotate(180deg) brightness(1.4) drop-shadow(-5px 0 0 #ff5050) drop-shadow(5px 0 0 #50c8ff) }
  60% { transform:translate(-4px,-1px) scale(1); filter:hue-rotate(45deg) saturate(0.6) drop-shadow(2px 2px 0 #ff5050) drop-shadow(-2px -2px 0 #50c8ff) }
  80% { transform:translate(0,2px) scale(1); filter:hue-rotate(-90deg) saturate(0.5) }
}

/* DISAPPEAR — pixel-glitch slice dissolve. */
@keyframes anim-hero-logo-out {
  0%   { clip-path:inset(0 0 0 0); transform:translate(0,0); opacity:1; filter:hue-rotate(0deg) }
  12%  { clip-path:inset(0 0 0 0); transform:translate(-4px,2px); filter:hue-rotate(45deg) saturate(1.4); opacity:1 }
  24%  { clip-path:inset(20% 0 30% 0); transform:translate(4px,-2px); filter:hue-rotate(-30deg); opacity:1 }
  36%  { clip-path:inset(40% 60% 30% 0); transform:translate(-3px,1px); filter:hue-rotate(90deg) brightness(1.4); opacity:1 }
  48%  { clip-path:inset(10% 0 70% 30%); transform:translate(3px,-3px); filter:hue-rotate(180deg); opacity:1 }
  60%  { clip-path:inset(50% 20% 30% 50%); transform:translate(-2px,0); filter:hue-rotate(-90deg) saturate(0.5); opacity:1 }
  72%  { clip-path:inset(70% 0 0 80%); transform:translate(0,1px); filter:hue-rotate(270deg) brightness(1.6); opacity:1 }
  84%  { clip-path:inset(85% 50% 0 0); opacity:0.4; transform:translate(2px,0) }
  100% { clip-path:inset(100% 0 0 0); opacity:0; transform:translate(0,0) }
}

/* Hero-terminal frame entrance: TV-off reverse instead of fade-up. */
.hero-terminal.reveal-up { opacity: 0; transform: scale(0); }
.hero-terminal.reveal-up.is-entered {
  animation: anim-hero-frame-on 520ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
  opacity: 1;
  transform: none;
  transition: none;
}
@keyframes anim-hero-frame-on {
  0%   { opacity:0; transform:scale(0); filter:brightness(1) }
  10%  { opacity:0.9; transform:scale(0.012); filter:brightness(4) saturate(0) }
  22%  { opacity:1; transform:scaleY(0.025) scaleX(1.06); filter:brightness(2.5) saturate(0.4) }
  35%  { opacity:1; transform:scaleY(0.06) scaleX(1.04); filter:brightness(2) saturate(0.6) }
  55%  { opacity:1; transform:scaleY(0.6) scaleX(1.02); filter:brightness(1.5) saturate(0.9) }
  72%  { opacity:1; transform:scale(1.03); filter:brightness(1.3) saturate(1.05) }
  100% { opacity:1; transform:scale(1); filter:brightness(1) saturate(1) }
}

.terminal-logo-intro.is-fading-out {
  /* v1.1.25: 90s CRT TV-power-off — vertical squish to a bright scanline,
     then horizontal collapse to a point, then a final fade. No transition;
     this runs as a keyframe so the multi-stage curve is preserved. */
  animation: hero-logo-tv-off 520ms step-end forwards;
  /* step-end ensures the final state holds; the keyframes use cubic-bezier
     easings on each stage internally via discrete percent stops. */
  animation-timing-function: linear;
}
@keyframes hero-logo-tv-off {
  /* Stage 1 — full → thin bright horizontal band (vertical squish + bloom). */
  0% {
    opacity: 1;
    transform: scaleY(1) scaleX(1);
    filter: brightness(1) contrast(1) saturate(1);
  }
  18% {
    opacity: 1;
    transform: scaleY(0.6) scaleX(1.02);
    filter: brightness(1.4) contrast(1.15) saturate(1.2);
  }
  32% {
    opacity: 1;
    transform: scaleY(0.06) scaleX(1.08);
    filter: brightness(2.5) contrast(1.4) saturate(0.6);
  }
  /* Stage 2 — bright scanline holds + slightly widens (CRT bloom). */
  40% {
    opacity: 1;
    transform: scaleY(0.025) scaleX(1.12);
    filter: brightness(3) contrast(1.5) saturate(0.4);
  }
  /* Stage 3 — horizontal collapse to a point (the "phosphor dot"). */
  68% {
    opacity: 1;
    transform: scaleY(0.02) scaleX(0.04);
    filter: brightness(3.5) contrast(1.3) saturate(0.3);
  }
  82% {
    opacity: 1;
    transform: scaleY(0.012) scaleX(0.012);
    filter: brightness(4) contrast(1) saturate(0.2);
  }
  /* Stage 4 — flash white, then vanish. */
  90% {
    opacity: 0.85;
    transform: scale(0.008);
    filter: brightness(5) contrast(0.6) saturate(0);
  }
  100% {
    opacity: 0;
    transform: scale(0);
    filter: brightness(1);
  }
}
@media (prefers-reduced-motion: reduce) {
  .terminal-logo-intro.is-fading-out {
    animation: none;
    opacity: 0;
    transform: scale(0);
    transition: opacity 200ms ease-out;
  }
}
.terminal-logo-intro.is-done { display: none; }
.terminal-logo-img {
  max-width: 100%;
  height: auto;
  display: block;
  margin: 0 auto;
}

/* v1.1.32: terminal-content is ALWAYS in flow (so its height contributes
   to the terminal-body cell from page load on). Override `[hidden]` to
   not collapse layout; lines themselves are invisible by default. */
.terminal-content[hidden] {
  display: block !important;
  pointer-events: none;
}
.terminal-content .terminal-line {
  opacity: 0;
}
/* v1.1.34: char-by-char typing for prompt lines (operator-tagged with
   `.typed` + inline `--chars` + `--typing-dur`). Uses clip-path + steps so
   the visible region grows char-by-char left-to-right; browser slices the
   already-laid-out text without width-math gymnastics. */
.terminal-content.is-revealed .terminal-line.typed {
  opacity: 1;
  clip-path: inset(0 100% 0 0);
  animation: typing-line var(--typing-dur, 1.5s) steps(var(--chars, 36), end) forwards;
}
@keyframes typing-line {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
.terminal-content.is-revealed .terminal-line {
  opacity: 0;
  animation: terminal-line-in 480ms ease-out forwards;
}
/* v1.1.38: typing slowed (operator: "a little too fast" + fade also).
   Typing 600/650/1000 → 950/1000/1500. Fade 320 → 480. Cadence between
   line starts kept at 220ms ("timing between each line is good"). */
.terminal-content.is-revealed .terminal-line:nth-child(1)  { animation-delay:   80ms; } /* $ spawn TYPED 950 → 1030 */
.terminal-content.is-revealed .terminal-line:nth-child(2)  { animation-delay: 1130ms; } /* +100 breath; ▸ agent-0 → 1610 */
.terminal-content.is-revealed .terminal-line:nth-child(3)  { animation-delay: 1350ms; } /* ▸ agent-1 → 1830 */
.terminal-content.is-revealed .terminal-line:nth-child(4)  { animation-delay: 1570ms; } /* ▸ agent-2 → 2050 */
.terminal-content.is-revealed .terminal-line:nth-child(5)  { animation-delay: 1790ms; } /* ✓ mesh ready → 2270 */
.terminal-content.is-revealed .terminal-line:nth-child(6)  { animation-delay: 2470ms; } /* +200 group gap; $ dispatch TYPED 1000 → 3470 */
.terminal-content.is-revealed .terminal-line:nth-child(7)  { animation-delay: 3570ms; } /* +100 breath; ▸ dispatching 0 → 4050 */
.terminal-content.is-revealed .terminal-line:nth-child(8)  { animation-delay: 3790ms; } /* ▸ dispatching 1 → 4270 */
.terminal-content.is-revealed .terminal-line:nth-child(9)  { animation-delay: 4010ms; } /* ▸ dispatching 2 → 4490 */
.terminal-content.is-revealed .terminal-line:nth-child(10) { animation-delay: 4230ms; } /* ✓ task completed → 4710 */
.terminal-content.is-revealed .terminal-line:nth-child(11) { animation-delay: 4450ms; } /* ▸ shikki idle → 4930 */
.terminal-content.is-revealed .terminal-line:nth-child(12) { animation-delay: 4670ms; } /* (blank) → 5150 */
.terminal-content.is-revealed .terminal-line:nth-child(13) { animation-delay: 5250ms; } /* +100 breath; $ install TYPED 1500 → 6750 */
.terminal-content.is-revealed .terminal-line:nth-child(14) { animation-delay: 6850ms; } /* +100 breath; cursor → 7330 */

/* v1.1.15: blank line is just visual breathing room — no leading glyph,
   no min-content. Force a single-line height via &nbsp; and prevent it
   from collapsing. */
.terminal-line-blank {
  /* nbsp inside keeps the line from being zero-height */
  user-select: none;
}

/* v1.1.16: easter-egg glitch — triggered by 2+ clicks on .terminal-title.
   Pure-CSS chromatic-aberration + jitter + clip-displacement, ~900ms.
   Reduced-motion skips entirely. */
/* v1.1.34: glitch rule needs higher specificity than `.reveal-up.is-entered`
   (which sets `animation: anim-hero-frame-on ...`) — otherwise the entrance
   animation rule wins and the glitch never plays. Match the same triple-class
   selector so the cascade picks the glitch when `.is-glitching` is added. */
.hero-terminal.reveal-up.is-glitching,
.hero-terminal.is-glitching {
  animation: hero-terminal-glitch 0.9s steps(18, end) 1 !important;
}
@keyframes hero-terminal-glitch {
  0%, 100% {
    transform: translate(0, 0);
    filter: none;
  }
  6% {
    transform: translate(-3px, 2px);
    filter: hue-rotate(60deg) saturate(1.4);
    box-shadow:
      -4px 0 0 rgba(255, 80, 80, 0.5),
       4px 0 0 rgba(80, 200, 255, 0.5),
       0 24px 80px rgba(0, 0, 0, 0.5);
  }
  14% {
    transform: translate(3px, -1px) skewX(0.5deg);
    filter: hue-rotate(-30deg) contrast(1.3);
    box-shadow:
      3px -2px 0 rgba(255, 80, 80, 0.45),
     -3px  2px 0 rgba(80, 200, 255, 0.45);
  }
  22% {
    transform: translate(-2px, 0);
    filter: hue-rotate(180deg) saturate(0.7);
    clip-path: polygon(0 0, 100% 0, 100% 28%, 78% 28%, 78% 48%, 100% 48%, 100% 100%, 0 100%);
  }
  30% {
    transform: translate(0, 3px);
    filter: hue-rotate(-90deg);
    clip-path: none;
  }
  38% {
    transform: translate(2px, -2px);
    filter: hue-rotate(45deg) brightness(1.15);
    box-shadow:
      -2px 0 0 rgba(255, 80, 80, 0.6),
       2px 0 0 rgba(80, 200, 255, 0.6);
  }
  46% {
    transform: translate(-3px, 1px) skewX(2deg);
    filter: hue-rotate(0deg) contrast(1.5);
    clip-path: polygon(0 0, 100% 0, 100% 60%, 35% 60%, 35% 80%, 100% 80%, 100% 100%, 0 100%);
  }
  54% {
    transform: translate(0, 0);
    filter: invert(0.08);
    clip-path: none;
  }
  62% {
    transform: translate(2px, 0);
    filter: hue-rotate(120deg);
    box-shadow:
       5px 0 0 rgba(255, 80, 80, 0.4),
      -5px 0 0 rgba(80, 200, 255, 0.4);
  }
  70% {
    transform: translate(-1px, -2px) skewY(0.5deg);
    filter: hue-rotate(0deg) saturate(1.3);
  }
  78% {
    transform: translate(0, 1px);
    filter: contrast(1.4);
    clip-path: polygon(0 0, 100% 0, 100% 70%, 60% 70%, 60% 90%, 100% 90%, 100% 100%, 0 100%);
  }
  86% {
    transform: translate(1px, 0);
    filter: none;
    clip-path: none;
  }
  94% {
    transform: translate(0, -1px);
    filter: hue-rotate(20deg);
  }
}
/* v1.1.33: trigger area = whole .terminal-bar. Pointer cursor + tap-friendly
   touch-action + a faint hover tint to suggest interactivity without
   spoiling the easter-egg surprise. */
.terminal-bar {
  cursor: pointer;
  user-select: none;
  touch-action: manipulation;   /* removes the 300ms tap delay on mobile */
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.2s ease-out;
}
.terminal-bar:hover { background-color: rgba(189, 147, 249, 0.06); }
.terminal-title { user-select: none; }
.terminal-title:hover { color: var(--purple); transition: color 0.2s; }
@media (prefers-reduced-motion: reduce) {
  .hero-terminal.is-glitching { animation: none; }
}

/* v1.1.14: in-terminal install line — the integrated install snippet
   inside the demo. Compact: prompt + monospaced cmd + tiny copy icon +
   blinking cursor at the end. Stays in the same vertical rhythm as the
   other terminal-line elements. */
.terminal-install-line {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: nowrap;
}
.terminal-install-cmd {
  background: none;
  padding: 0;
  font: inherit;
  color: var(--purple);
  white-space: nowrap;
  overflow-x: auto;
  scrollbar-width: thin;
  flex: 1;
  min-width: 0;
}
.terminal-install-copy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px 6px;
  background: transparent;
  color: var(--purple);
  border: 1px solid var(--purple);
  border-radius: 3px;
  font-family: inherit;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 150ms, color 150ms;
}
.terminal-install-copy:hover,
.terminal-install-copy.is-copied {
  background: var(--purple);
  color: var(--bg);
}
.terminal-install-copy.is-copied {
  background: var(--green);
  border-color: var(--green);
}

/* v1.1.13: blinking caret on the final cursor line — the carriage-return
   visual after the demo finishes. Replaces the old `.typing` class which
   was buggy (overrode the line-in opacity animation, leaving the line
   invisible). */
.cursor-blink {
  display: inline-block;
  width: 0.55em;
  height: 1em;
  background: var(--green);
  vertical-align: text-bottom;
  margin-left: 0.15em;
  animation: cursor-blink-keys 0.9s step-end infinite;
}
@keyframes cursor-blink-keys {
  0%, 50%   { opacity: 1; }
  51%, 100% { opacity: 0; }
}

/* v1.1.21: dynamic echo lines added when the user types in the in-terminal
   input + Enter. Each line animates max-height 0 → 1 line so the terminal
   visibly grows by one row per Enter, until MAX_EXTRA_LINES is reached. */
.terminal-line-history {
  opacity: 0;
  max-height: 0;
  overflow: hidden;
  transform: translateY(2px);
  transition:
    max-height 0.32s ease-out,
    opacity 0.32s ease-out 0.08s,
    transform 0.32s ease-out 0.08s;
}
.terminal-line-history.is-entered {
  opacity: 1;
  max-height: 1.6em;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .terminal-line-history { transition: none; }
}

/* v1.1.17: hidden inline input on the cursor line — easter-egg trigger.
   No UI: transparent bg, no border/outline, inherits terminal text style.
   Native caret takes over once the input has focus. */
.terminal-line-cursor {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
}
.terminal-input {
  /* v1.1.18: drop `width: 100%` which combined with flex:1 was extending
     the input's hit-area beyond the visible cursor row, intercepting
     clicks on elements below (the scroll-cue link). flex:1 1 0 is enough
     to grow with content while staying within parent bounds. */
  flex: 1 1 0;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
  background: transparent;
  border: 0;
  outline: 0;
  color: var(--fg);
  font: inherit;
  caret-color: var(--green);
  padding: 0;
  margin: 0;
}
.terminal-input::placeholder { color: transparent; }
.terminal-input::selection { background: var(--purple); color: var(--bg); }
/* Hide the fake cursor-blink while the input has focus — the browser's
   native caret takes over. */
.terminal-line-cursor:focus-within .cursor-blink { display: none; }
.terminal-line-cursor.has-value .cursor-blink { display: none; }

/* v1.1.23: as soon as the user interacts with the input (focus or first
   keystroke), short-circuit the staggered terminal-line-in fade so typed
   characters never appear "delayed". The echo line that gets appended on
   Enter still keeps its own .terminal-line-history reveal animation. */
.terminal-line-cursor.is-typed {
  opacity: 1 !important;
  animation: none !important;
  transform: none !important;
}

@keyframes terminal-line-in {
  from { opacity: 0; transform: translateY(2px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── v1.1: brew-style install snippet, sibling-card to .hero-terminal ── */
.install-snippet {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  max-width: 600px;
  margin: 1.5rem auto 0;
  padding: 0.875rem 1rem;
  background: var(--terminal-bg);
  border: 1px solid var(--bg-card);
  border-radius: var(--radius);
  box-shadow: var(--terminal-glow);
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--terminal-fg);
  overflow: hidden;
  position: relative;
  text-align: left;
}
.install-prompt {
  color: var(--purple);
  flex-shrink: 0;
  user-select: none;
}
.install-snippet code {
  flex: 1;
  white-space: nowrap;
  overflow-x: auto;
  scrollbar-width: thin;
  background: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
/* v1.1.2: download-focus mode — when /download is active, dim every
   sibling in the hero, soften the terminal glow, scale the
   install-snippet to 1.2×, AND dim sections below the hero (e.g. the
   beginning of #features visible on desktop). Toggled by main.js
   via `.is-download-focused` on both `.hero` and `body`. */
.install-snippet {
  transition:
    transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1),
    box-shadow 0.35s ease-out,
    opacity 0.35s ease-out;
  transform-origin: center center;
  will-change: transform;
}
/* v1.1.29: ONE class for focus state — `.is-focus-target` on the focused
   section. body-level rules use `:has(.is-focus-target)` to detect that
   any section is focused. Removes the dual `is-section-focused` /
   `is-focused-section` (similar names, easy to confuse).
   Easing asymmetric — ease-out IN, ease-in OUT, both 0.3s. */
body main > section,
body .hero > *,
body .hero-terminal,
body .install-snippet {
  transition:
    opacity 0.3s ease-in,
    transform 0.3s ease-in,
    box-shadow 0.3s ease-in;
}

body:has(.is-focus-target) main > section:not(.is-focus-target),
body:has(.is-focus-target) .hero:not(.is-focus-target) > *,
body:has(.is-focus-target) .hero:not(.is-focus-target) .hero-terminal,
body:has(.is-focus-target) #download.is-focus-target .install-snippet {
  transition:
    opacity 0.3s ease-out,
    transform 0.3s ease-out,
    box-shadow 0.3s ease-out;
}

body:has(.is-focus-target) main > section:not(.is-focus-target)        { opacity: 0.25; }
body:has(.is-focus-target) .hero:not(.is-focus-target) > *             { opacity: 0.25; }
body:has(.is-focus-target) .hero:not(.is-focus-target) .hero-terminal  { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); }
body:has(.is-focus-target) #download.is-focus-target .install-snippet  {
  transform: scale(1.2);
  position: relative;
  z-index: 10;
}

@media (prefers-reduced-motion: reduce) {
  main > section,
  .hero > *,
  .hero-terminal,
  .install-snippet,
  body:has(.is-focus-target) main > section:not(.is-focus-target),
  body:has(.is-focus-target) .hero:not(.is-focus-target) > *,
  body:has(.is-focus-target) .hero:not(.is-focus-target) .hero-terminal,
  body:has(.is-focus-target) #download.is-focus-target .install-snippet {
    transition: none;
  }
  body:has(.is-focus-target) #download.is-focus-target .install-snippet {
    transform: none;
  }
}

/* v1.1.4: Maya.fit-style staggered entrance. Each .reveal-up element
   starts hidden (opacity 0, translated 20px below) and reveals on
   `.is-entered`. Stagger delays are wired in main.js (not via
   nth-child) so we keep the per-element semantic order stable. */
.reveal-up {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 0.55s ease-out,
    transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: opacity, transform;
}
.reveal-up.is-entered {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .reveal-up {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* v1.1.3: scroll-cue hides permanently after Copy click (user has the install
   command — no need to nudge them down anymore). */
.scroll-cue.is-hidden {
  opacity: 0;
  pointer-events: none;
  transition: opacity 300ms ease-out;
}
/* v1.1.42: smart-fit hide — JS sets `.is-redundant` when (i) the hero
   already overflows the viewport (mobile / short screen — cue would
   appear mid-content, not at the visible bottom), or (ii) the next
   section already peeks in by >= 75% on a tall (>1080px) viewport
   (4K — content invites the scroll on its own). Display:none rather
   than opacity:0 because we don't want it to take grid/flex space
   either. */
.scroll-cue.is-redundant {
  display: none !important;
}
/* v1.1.6 superseded by v1.1.13 consolidated rule below — see line ~707. */
@media (prefers-reduced-motion: reduce) {
  .scroll-cue { transition: none; }
}

.install-copy-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.375rem 0.75rem;
  background: transparent;
  color: var(--purple);
  border: 1px solid var(--purple);
  border-radius: 4px;
  font: inherit;
  font-size: 0.75rem;
  cursor: pointer;
  transition: background 150ms, color 150ms;
  flex-shrink: 0;
}
.install-copy-btn:hover {
  background: var(--purple);
  color: var(--terminal-bg);
}
.install-copy-btn.is-copied {
  background: var(--green);
  color: var(--terminal-bg);
  border-color: var(--green);
}
.install-copy-btn svg { flex-shrink: 0; }
@media (max-width: 640px) {
  .install-snippet { flex-wrap: wrap; font-size: 0.75rem; }
  .install-copy-label { display: none; }
}

/* ── v1.1: scroll cue (gentle bob) ─────────────── */
.scroll-cue {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  /* v1.1.39: pinned to bottom of the hero (first viewport) — wabisabi /
     sigma-analytics pattern. translateX(-50%) handles centering; the bob
     keyframes compose translateY on top of that. */
  position: absolute;
  left: 50%;
  bottom: 2rem;
  transform: translateX(-50%);
  z-index: 1;
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--dim);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  text-decoration: none;
  /* v1.1.13: opacity 0 by default. Only `.is-revealed` (set by JS after
     terminal text finishes + 2s) brings it to 0.7. The earlier duplicate
     `.scroll-cue` rule was overriding this with opacity 0.7 from page-load
     — consolidated here. */
  opacity: 0;
  transition: opacity 0.55s ease-out, color 0.2s;
  animation: scroll-cue-bob 1.6s ease-in-out infinite paused;
}
.scroll-cue.is-revealed { opacity: 0.7; }
.scroll-cue.is-revealed.is-onscreen { animation-play-state: running; }
.scroll-cue:hover {
  opacity: 1;
  color: var(--purple);
}
.scroll-cue-arrow {
  font-size: 1.1rem;
  line-height: 1;
}
@keyframes scroll-cue-bob {
  /* v1.1.39: keep translateX(-50%) for absolute centering; bob composes Y. */
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50%      { transform: translateX(-50%) translateY(6px); }
}

/* ── v1.1: a11y — reduced motion bypasses intro + bob ── */
@media (prefers-reduced-motion: reduce) {
  .terminal-logo-intro { display: none; }
  .terminal-content[hidden] { display: block !important; }
  .terminal-content .terminal-line {
    opacity: 1 !important;
    animation: none !important;
  }
  .header-logo-img {
    opacity: 1;
    transform: translateX(0);
    transition: none;
  }
  .scroll-cue { animation: none; }
  .terminal-line.typing { animation: none; width: auto; border-right: none; }
}

/* ──────────────────────────────────────────────────────────────────────
   v1.2 — mobile-adaptive: splash-fade-essential + 3-CTA stack
   /🔥 ballot 2026-05-06: B1 ★ A · B2 ★ C · B3 ★ A · B4 ★ B/A · B5 ★ A
   Desktop default: both blocks display:none — no layout impact at all.
   ────────────────────────────────────────────────────────────────────── */

.mobile-splash,
.mobile-essential {
  display: none;
}

@media (max-width: 480px) {
  /* Hide the desktop hero on mobile (DOM stays intact for SEO + a11y;
     CSS gates visibility only). #shikki-flow / #features / #download /
     #waitlist remain scrollable below the 3-CTA stack (B4 hybrid). */
  main > section.hero { display: none; }

  /* Hide the fixed header on mobile — the 3-CTA stack already surfaces
     install + iPhone Companion + Beta as primary actions. The nav links
     duplicate the page sections that "Read more on the full page" lands
     on, so the header adds noise rather than signal at this size. The
     full <header> stays in DOM (SEO + a11y) — just visually hidden. */
  .header { display: none; }

  /* 3-CTA essential block lives where .hero used to sit. Visible at
     first paint underneath the splash; once splash fades, this is the
     view the user sees (B1 ★ A — splash-fade-essential). */
  .mobile-essential {
    display: block;
    padding: 1.75rem 1rem 2rem;  /* no fixed header → tight top */
    text-align: center;
  }

  .mobile-h1 {
    font-family: var(--sans);
    font-size: 1.75rem;
    font-weight: 300;
    line-height: 1.2;
    letter-spacing: -0.02em;
    margin-bottom: 0.6rem;
  }
  .mobile-tagline {
    font-size: 0.95rem;
    color: var(--dim);
    line-height: 1.5;
    margin-bottom: 1.75rem;
  }

  .mobile-cta {
    display: block;
    text-align: left;
    margin: 0 auto 1rem;
    padding: 1rem;
    max-width: 100%;
    background: var(--bg-dark);
    border: 1px solid var(--bg-card);
    border-radius: var(--radius);
    text-decoration: none;
    color: var(--fg);
    transition: border-color 0.2s, box-shadow 0.2s;
  }
  .mobile-cta:hover,
  .mobile-cta:focus-visible {
    border-color: var(--purple);
    box-shadow: 0 0 16px rgba(189, 147, 249, 0.18);
    opacity: 1;
  }
  .mobile-cta-eyebrow {
    display: block;
    font-family: var(--mono);
    font-size: 0.65rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--purple);
    margin-bottom: 0.5rem;
  }
  .mobile-cta-title {
    display: block;
    font-family: var(--sans);
    font-size: 1.05rem;
    font-weight: 500;
    color: var(--fg);
    margin-bottom: 0.35rem;
  }
  .mobile-cta-hint {
    display: block;
    font-size: 0.8rem;
    color: var(--dim);
    line-height: 1.45;
    margin-top: 0.5rem;
  }

  /* CTA 1 — install: the snippet card already styles itself (it's the
     existing .install-snippet rule). We just nest it inside .mobile-cta
     and tighten margins so the visual hierarchy stays primary. */
  .mobile-cta-install { padding: 1rem 0.85rem; }
  .mobile-cta-install .install-snippet {
    margin: 0.5rem 0 0.25rem;
    box-shadow: none;
  }
  .mobile-cta-install .install-copy-label { display: inline; }

  .mobile-more-link {
    display: inline-block;
    margin: 1.5rem auto 0;
    font-family: var(--mono);
    font-size: 0.7rem;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--dim);
    text-decoration: none;
  }
  .mobile-more-link:hover { color: var(--purple); opacity: 1; }

  /* Splash overlay — full-viewport, fixed-position, fades 0.3s.
     Z-index above header. v1.2.1: <video> plays the splash-portrait-v1
     animator preset (5.5s); the overlay container itself no longer pulses
     (the animation IS the splash). is-fading still triggers 0.3s opacity
     ramp on dismissal. */
  .mobile-splash {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 9999;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    cursor: pointer;             /* signals "tap to dismiss" */
    -webkit-tap-highlight-color: transparent;
    opacity: 1;
  }
  .mobile-splash-video,
  .mobile-splash-logo {
    max-width: 90vw;
    max-height: 90vh;
    width: auto;
    height: auto;
    object-fit: contain;
    user-select: none;
    -webkit-user-drag: none;
    background: transparent;
  }
  .mobile-splash.is-fading {
    animation: mobile-splash-fade-out 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
  }
}

/* Fade-out keyframe lives OUTSIDE the media query so the JS controller
   can rely on it existing whenever it adds .is-fading. v1.2.1: the
   splash itself is now a video — no container pulse keyframe needed. */
@keyframes mobile-splash-fade-out {
  to { opacity: 0; visibility: hidden; }
}

/* a11y override (F15): user opted out of motion → splash never shows,
   3-CTA-essential renders immediately. !important beats the @media
   rule above since both share the same specificity. */
@media (max-width: 480px) and (prefers-reduced-motion: reduce) {
  .mobile-splash {
    display: none !important;
    animation: none !important;
  }
}
