/* SeqLog's site-wide base: tokens, typography, nav, buttons, footer, landing sections. Design context and
   the reasoning behind token values live in .impeccable.md; only rules live here. Every page loads it; the
   landing page stacks faq.css, long-form pages doc.css, /vs/* pages compare.css — split off because this
   file is past the project's per-file line ceiling, not because those rules belong elsewhere. */
/* Display family. Self-hosted, content-hashed filename so a font swap is a new URL.
   Latin + punctuation subset only; opsz is baked in so wght stays the one live axis. */
@font-face {
  font-family: 'Newsreader';
  src: url('newsreader-roman-a1b801c9.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Newsreader';
  src: url('newsreader-italic-658e7d90.woff2') format('woff2');
  font-weight: 300 700;
  font-style: italic;
  font-display: swap;
}

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

:root {
  --bg: #1F1F1F;
  --surface: #252526;
  --surface-raised: #2D2D2D;
  --border: #3C3C3C;
  --border-subtle: #2E2E2E;
  --primary-text: #CCCCCC;
  /* Two tiers below primary, split by job rather than by taste: --body-text is for prose someone
     actually reads, --secondary-text only for chrome and metadata. The app's secondary tone is
     measured against the flat editor background; on a lifted card it lands on the AA floor and
     reads muddy. Ratios and the full rule: .impeccable.md § Color Tokens. */
  --body-text: #ADB6C0;
  --secondary-text: #8B949E;
  --active-text: #FFFFFF;
  --accent: #569CD6;
  --accent-dim: color-mix(in srgb, var(--accent) 16%, transparent);
  --green: #4EC9B0;
  --orange: #CE9178;

  --display: 'Newsreader', 'Iowan Old Style', Palatino, Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', sans-serif;
  --mono: 'SF Mono', ui-monospace, Menlo, monospace;

  --content-max: 1120px;
  /* Half a section gap: every section pays it top and bottom, so the seam between two
     sections is one full gap and no seam is special-cased. */
  --section-pad: 66px;
  /* How much of a surface token a lifted panel keeps. Enough grain reads through to hold the
     panel inside the page; below ~70% it dissolves into #1F1F1F entirely. */
  --veil: 80%;
}

html { scroll-behavior: smooth; }

body {
  background: var(--bg);
  color: var(--primary-text);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* Atmosphere: one dim accent wash at the fold plus fine grain. Deliberately near the
   threshold of visibility — the app is flat dark, and the page must not out-decorate it.
   z-index: -1 puts both layers under all content but still over the canvas (body's background
   propagates there), so no content block has to opt out of being buried. */
body::before,
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
body::before {
  background:
    radial-gradient(1200px 640px at 72% -12%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 62%),
    radial-gradient(900px 500px at -8% 4%, color-mix(in srgb, var(--green) 4.5%, transparent), transparent 58%);
}
body::after {
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* No a:hover underline: every anchor on the page is a nav link, a button or an already-underlined
   meta link, so a global rule would only exist to be cancelled five times. */
a { color: var(--accent); text-decoration: none; }
code, kbd { font-family: var(--mono); }

.container {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 0 28px;
}

/* --- Nav --- */
nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  padding: 15px 0;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s, background 0.3s;
}
nav.scrolled {
  border-bottom-color: var(--border-subtle);
  background: color-mix(in srgb, var(--bg) 90%, transparent);
}
nav .container { display: flex; align-items: center; justify-content: space-between; }
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--display);
  font-weight: 500;
  font-size: 22px;
  letter-spacing: -0.01em;
  color: var(--active-text);
}
.nav-links { display: flex; gap: 30px; align-items: center; }
.nav-links a:not(.nav-cta) {
  color: var(--secondary-text);
  font-size: 14px;
  font-weight: 500;
  transition: color 0.18s;
}
.nav-links a:not(.nav-cta):hover { color: var(--active-text); }
.nav-cta {
  background: var(--active-text);
  color: var(--bg);
  padding: 8px 17px;
  border-radius: 9px;
  font-weight: 600;
  font-size: 13.5px;
  transition: opacity 0.18s;
}
.nav-cta:hover { opacity: 0.86; }

/* --- Shared typography --- */
/* One pill, two sizes: the hero badge and the long-tail chips are the same object, so the shared
   half lives here and each variant only states its padding. */
.pill,
.also span:not(.also-label) {
  border: 1px solid var(--border);
  border-radius: 100px;
  background: color-mix(in srgb, var(--surface-raised) var(--veil), transparent);
  font-size: 13px;
  color: var(--body-text);
}
.pill {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 6px 15px;
}
.pill .pulse {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 60%, transparent);
  animation: pulse 2.4s infinite;
}
@keyframes pulse {
  70% { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
/* The unshipped-platform badge is the page's one deliberate exception to flat-and-restrained
   (.impeccable.md § Aesthetic Direction names rainbow gradients and glass as anti-references):
   frosted lozenge over a turning colour bloom. It has to carry an announcement across the fold,
   and there is exactly one of them. The dot stays a hollow ring — same "not yet" tell as the
   shipped badge's filled, pulsing dot, so the pair still reads as one object in two states.
   Gap is sized off the bloom, not the other way round: at 20px the blur stops short of the
   badge beside it. */
.pill-row { display: flex; flex-wrap: wrap; gap: 20px; }
.pill-soon {
  position: relative;
  color: var(--active-text);
  border-color: rgba(255, 255, 255, 0.3);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.19), rgba(255, 255, 255, 0.06));
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45), 0 8px 26px rgba(0, 0, 0, 0.5);
}
/* The bloom needs its own element rather than a pseudo on the pill: a positioned ::before paints
   above its own element's background, and pushing it to z-index -1 would put it above that
   background too (negative descendants still paint after the parent's background box). Sitting it
   on a wrapper makes paint order do the work — bloom first, glass second, no z-index anywhere. */
.pill-bloom { position: relative; display: inline-flex; }
/* Turning the gradient by its `from` angle regenerates the conic bitmap and re-runs the blur every
   frame, both at paint time — measured, that cost as much as the whole journal animation beside
   it. A turning gradient is a still gradient on a turning element, and that repaints once, but it
   needs a carrier whose shape survives rotation: hence a circle, radially symmetric once blurred,
   so spinning it moves nothing but colour. The static scaleY flattens the disc back into the wide
   wash the old rectangle gave. scaleY must sit inside transform, not use the standalone scale
   property — standalone translate/rotate/scale compose in a fixed order that scales first, swinging
   the ellipse's long axis around; transform functions apply right to left, so scaleY(s) rotate(a)
   is turn-then-flatten. translate stays standalone because it composes after transform. */
.pill-bloom::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 98%;
  aspect-ratio: 1;
  border-radius: 50%;
  translate: -50% -50%;
  background: conic-gradient(#FF5F57, #FEBC2E, var(--green), var(--accent), #C586C0, #FF5F57);
  filter: blur(17px);
  opacity: 0.78;
  transform: scaleY(0.33) rotate(0deg);
  animation: bloom 5s linear infinite;
}
@keyframes bloom { to { transform: scaleY(0.33) rotate(360deg); } }
.pill-soon .pulse {
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.92);
  box-shadow: none;
  animation: none;
}

h1 {
  font-family: var(--display);
  font-weight: 380;
  font-size: clamp(44px, 5.4vw, 66px);
  line-height: 1.04;
  letter-spacing: -0.02em;
  color: var(--active-text);
  margin: 26px 0 0;
}
h1 em { font-style: italic; font-weight: 420; color: var(--accent); }

.lede {
  font-size: 18px;
  color: var(--body-text);
  max-width: 30em;
  margin: 22px 0 34px;
}

.kicker {
  font-family: var(--display);
  font-style: italic;
  font-weight: 400;
  font-size: 20px;
  color: var(--accent);
}
h2 {
  font-family: var(--display);
  font-weight: 420;
  font-size: clamp(30px, 3.6vw, 42px);
  line-height: 1.1;
  letter-spacing: -0.015em;
  color: var(--active-text);
  margin: 8px 0 0;
}
.section-head { max-width: 34em; margin-bottom: 48px; }
.section-head p { color: var(--body-text); font-size: 17px; margin-top: 14px; text-wrap: pretty; }

section { padding: var(--section-pad) 0; }

/* --- Buttons --- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 13px 25px;
  border-radius: 11px;
  font-weight: 600;
  font-size: 15.5px;
  transition: transform 0.18s ease, border-color 0.18s, color 0.18s;
}
/* Icon geometry lives here rather than on each <svg>'s presentation attributes, so a stroke or
   size change is one edit instead of eight. */
.btn svg,
.feature-icon svg {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.btn svg { width: 17px; height: 17px; stroke-width: 2; }
.feature-icon svg { width: 21px; height: 21px; stroke-width: 1.9; }
.btn-primary {
  background: var(--active-text);
  color: var(--bg);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.4);
}
.btn-primary:hover { transform: translateY(-2px); }
.btn-ghost {
  color: var(--primary-text);
  border: 1px solid var(--border);
  font-weight: 500;
}
.btn-ghost:hover { border-color: var(--secondary-text); color: var(--active-text); }
.cta-row { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; }
.cta-note { margin-top: 20px; font-size: 13.5px; color: var(--secondary-text); }
.cta-note a { color: var(--secondary-text); text-decoration: underline; text-underline-offset: 3px; }
.cta-note a:hover { color: var(--active-text); }

/* --- Hero --- */
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
  gap: 56px;
  align-items: center;
  /* Block-axis only: .hero is also a .container and must keep its horizontal padding, which the
     shorthand would silently zero (invisible above 1120px, flush to the edge below it). */
  padding-top: 152px;
  padding-bottom: var(--section-pad);
}
/* Anchors the perf badge; the mockup itself clips to its own rounded corners, so the badge that
   straddles its top edge has to hang off the wrapper instead. */
.hero-visual { position: relative; }

/* The page's second rainbow, deliberately the opposite shape to .pill-soon's frosted lozenge: bare
   3D-extruded letters, no fill. .impeccable.md § Aesthetic Direction holds the rule — two rainbows,
   forms must not collide. Kept clear of the right edge rather than hung off the corner, because
   .hero-visual bleeds past the viewport between roughly 1120px and 1360px wide, where an
   overhanging badge would be cut off by body's overflow-x: hidden. */
.badge-blaze {
  position: absolute;
  /* Tilted down to the right, so it reads as slapped on at an angle rather than set straight.
     top is measured to the raised left end, which is why it clears the window by more than the
     rotation alone would suggest. */
  top: -26px;
  right: 26px;
  z-index: 1;
  display: flex;
  font-weight: 900;
  font-size: 28px;
  letter-spacing: -0.005em;
  line-height: 1;
  /* For the letters. The badge's own swivel needs perspective() inside its transform, since the
     property only reaches descendants. */
  perspective: 480px;
  transform: perspective(860px) rotate(8deg) rotateY(-12deg);
  animation: blaze-swivel 7s ease-in-out infinite;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
.badge-blaze span {
  /* Hue walks the spectrum letter by letter; the three darker steps below are the extrusion, so
     each letter's side wall keeps its own colour instead of going uniformly grey. */
  --hue: calc(4 + var(--i) * 23);
  color: hsl(var(--hue) 95% 68%);
  text-shadow:
    0 1px 0 hsl(var(--hue) 55% 27%),
    0 2px 0 hsl(var(--hue) 55% 21%),
    0 3px 0 hsl(var(--hue) 55% 15%),
    0 5px 14px rgba(0, 0, 0, 0.7);
  transform-origin: 50% 100%;
  /* Same cycle for every letter, staggered start: the flip runs left to right as a wave and
     still repeats cleanly. Idle for the first two thirds of the cycle — the brief is an
     intermittent shake, not a permanent one.
     The -1s phase-shifts that idle stretch so the first shake lands at 66% × 3s - 1s ≈ 1s, right
     after .hero-visual's .rise/.d4 entrance ends (0.62s + 0.3s) — at delay 0 the badge holds still
     for two seconds and reads as static. Retune it alongside the duration, the 66% keyframe, or
     that entrance. */
  animation: blaze-flip 3s cubic-bezier(0.3, 1.5, 0.4, 1) infinite;
  animation-delay: calc(var(--i) * 42ms - 1s);
}
/* No 0%/100% keyframes: the implicit ones are the element's own declared transform, so the resting
   pose lives in exactly one place. */
@keyframes blaze-swivel {
  50% { transform: perspective(860px) rotate(7deg) rotateY(12deg); }
}
@keyframes blaze-flip {
  66% { transform: none; }
  76% { transform: translateY(-8px) rotateX(52deg) scale(1.16); }
  86% { transform: translateY(2px) rotateX(-24deg) scale(0.97); }
  94% { transform: rotateX(10deg); }
}

/* The window runs off the right edge: it is a screen, not a card, and cropping it says so.
   The clamp lands the bleed on the viewport edge and caps it there, instead of letting it grow
   without limit on a wide display; once the viewport is narrower than the content column plus the
   gutter the calc turns positive and the bleed switches itself off, so no breakpoint repeats it.
   body has overflow-x: hidden, so it never opens a horizontal scrollbar. */
.hero-visual { margin-right: clamp(-160px, calc((var(--content-max) - 100vw) / 2 - 40px), 0px); }

/* --- App mockup --- */
.app-mockup {
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg);
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.03),
    0 30px 90px rgba(0, 0, 0, 0.55),
    0 0 140px color-mix(in srgb, var(--accent) 5%, transparent);
}
.mockup-titlebar {
  height: 36px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  padding: 0 14px;
  gap: 8px;
}
.traffic-dot { width: 11px; height: 11px; border-radius: 50%; }
.traffic-dot.red { background: #FF5F57; }
.traffic-dot.yellow { background: #FEBC2E; }
.traffic-dot.green { background: #28C840; }
.mockup-tabs {
  display: flex;
  height: 33px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 0 10px;
  gap: 2px;
  align-items: flex-end;
}
.mock-tab {
  padding: 6px 14px;
  font-size: 11.5px;
  color: var(--secondary-text);
  border-radius: 6px 6px 0 0;
  white-space: nowrap;
}
.mock-tab.active {
  background: var(--bg);
  color: var(--primary-text);
  border: 1px solid var(--border);
  border-bottom-color: var(--bg);
}
.mockup-content { padding: 24px 30px 30px; position: relative; }
/* Painted, not laid out — the rationale is in mockup.js. .mock-fallback keeps its box, so it stays
   the one place the panel's height is decided, but stops painting: an opaque canvas over it is not
   enough, since .mock-checkbox is position: relative and .mock-cursor animates opacity, and both
   join layers that paint after an earlier sibling — they bled through as loose checkboxes and a
   stray caret. Hiding one named wrapper, rather than every sibling the canvas isn't, keeps that
   from recurring the next time something is added beside them.
   width/height are not redundant next to inset: canvas is a replaced element, so an auto width
   resolves to its 300x150 intrinsic size and the far insets are dropped on the floor. */
.mock-canvas { display: none; }
.js .mock-canvas { display: block; position: absolute; inset: 0; width: 100%; height: 100%; }
.js .mock-fallback { visibility: hidden; }
/* Set by mockup.js when it bails: JS is on but nothing will be drawn, so hand the panel back. */
.js .canvas-dead .mock-fallback { visibility: visible; }
.mock-date {
  font-size: 19px;
  font-weight: 600;
  color: var(--active-text);
  margin-bottom: 16px;
  display: flex;
  align-items: baseline;
  gap: 9px;
}
.mock-weekday { font-size: 13px; color: var(--secondary-text); font-weight: 400; }
.mock-block {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 3px 0;
  font-size: 13.5px;
  line-height: 1.55;
}
.mock-bullet {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--secondary-text);
  margin-top: 8px;
  flex-shrink: 0;
}
.mock-block.focused {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  border-radius: 4px;
  padding: 3px 8px;
  margin: 0 -8px;
}
.mock-block.focused .mock-bullet { background: var(--accent); }
.mock-indent { margin-left: 22px; }
.mock-wikilink { color: var(--accent); border-bottom: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); }
.mock-tag { color: var(--green); }
.mock-code {
  font-family: var(--mono);
  font-size: 12px;
  background: var(--surface);
  padding: 1px 5px;
  border-radius: 3px;
  color: var(--orange);
}
.mock-cursor {
  display: inline-block;
  width: 2px;
  height: 15px;
  background: var(--accent);
  vertical-align: text-bottom;
  animation: blink 1.1s step-end infinite;
}
@keyframes blink { 50% { opacity: 0; } }
.mock-checkbox {
  width: 13px; height: 13px;
  border: 1.5px solid var(--secondary-text);
  border-radius: 3px;
  margin-top: 4px;
  flex-shrink: 0;
  position: relative;
}
.mock-checkbox.checked { background: var(--accent); border-color: var(--accent); }
.mock-checkbox.checked::after {
  content: '';
  position: absolute;
  left: 3px; top: 0.5px;
  width: 4px; height: 7px;
  border: solid var(--bg);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}

/* --- Features --- */
.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.feature-card {
  padding: 28px 26px;
  border-radius: 16px;
  border: 1px solid var(--border);
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--surface-raised) var(--veil), transparent),
    color-mix(in srgb, var(--surface) var(--veil), transparent)
  );
  transition: transform 0.2s ease, border-color 0.2s, box-shadow 0.2s ease;
}
.feature-card:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.32);
}
.feature-icon {
  width: 42px; height: 42px;
  border-radius: 12px;
  background: var(--accent-dim);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  display: grid;
  place-items: center;
  margin-bottom: 20px;
  color: var(--accent);
}
.feature-card h3 {
  font-family: var(--display);
  font-weight: 500;
  font-size: 21px;
  letter-spacing: -0.005em;
  color: var(--active-text);
  margin-bottom: 9px;
}
/* Mono sits taller than the serif at the same size, so the shortcut heading is stepped down
   to keep the six card titles on one optical line. */
.feature-card h3 kbd { font-size: 18px; }
.feature-card code { font-size: 13px; color: var(--orange); }
.feature-card p,
.principle-item p { font-size: 14.5px; color: var(--body-text); line-height: 1.55; }

/* The long tail of shipped features: real, but none of them earns a card. */
.also {
  margin-top: 34px;
  padding-top: 28px;
  border-top: 1px dashed var(--border);
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
  align-items: center;
}
.also .also-label {
  font-family: var(--display);
  font-style: italic;
  font-size: 17px;
  color: var(--body-text);
  margin-right: 6px;
}
.also span:not(.also-label) { padding: 5px 13px; }

/* --- Philosophy --- */
.principle-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 38px 60px;
}
.principle-item { display: flex; gap: 20px; align-items: flex-start; }
.principle-num {
  font-family: var(--display);
  font-weight: 400;
  font-size: 26px;
  line-height: 1;
  color: var(--accent);
  opacity: 0.7;
  flex-shrink: 0;
  width: 40px;
  padding-top: 2px;
}
.principle-item h3 {
  font-family: var(--display);
  font-weight: 500;
  font-size: 20px;
  color: var(--active-text);
  margin-bottom: 7px;
}

/* --- Spec sheet --- */
.spec {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0 56px;
}
.spec div {
  display: grid;
  grid-template-columns: 128px 1fr;
  gap: 16px;
  align-items: baseline;
  padding: 14px 0;
  border-top: 1px solid var(--border);
  font-size: 14.5px;
}
.spec dt {
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--body-text);
}
.spec dd { color: var(--primary-text); }
.spec dd b { font-weight: 600; color: var(--active-text); }

/* --- Closing CTA --- */
.close-panel {
  position: relative;
  overflow: hidden;
  text-align: center;
  border: 1px solid var(--border);
  border-radius: 24px;
  padding: 76px 40px;
  background:
    radial-gradient(700px 320px at 50% 0%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%),
    var(--surface);
}
.close-panel h2 { margin-bottom: 14px; }
.close-panel p { color: var(--body-text); font-size: 17px; margin-bottom: 32px; }
.close-panel .cta-row { justify-content: center; }

/* --- Footer --- */
footer { padding: 34px 0 44px; border-top: 1px solid var(--border-subtle); }
footer .container { display: flex; justify-content: space-between; align-items: center; gap: 20px; flex-wrap: wrap; }
footer p, footer a { font-size: 13px; color: var(--secondary-text); }
footer .links { display: flex; gap: 22px; }
footer a:hover { color: var(--primary-text); text-decoration: none; }

/* --- Motion. Hero staggers on load (CSS only), everything below waits for the observer. --- */
.js .rise { opacity: 0; transform: translateY(16px); animation: rise 0.62s cubic-bezier(0.2, 0.7, 0.2, 1) forwards; }
@keyframes rise { to { opacity: 1; transform: none; } }
.d1 { animation-delay: 0.04s; }
.d2 { animation-delay: 0.12s; }
.d3 { animation-delay: 0.2s; }
.d4 { animation-delay: 0.3s; }

.js .reveal { opacity: 0; transform: translateY(20px); }
.js .reveal.in { opacity: 1; transform: none; transition: opacity 0.55s ease, transform 0.55s ease; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .rise, .js .reveal { animation: none; transition: none; opacity: 1; transform: none; }
  /* Colour and 3D pose survive, motion does not — both badges still announce themselves, and the
     mockup keeps its journal (mockup.js draws the resting frame and never starts the loop). */
  .pill .pulse, .mock-cursor, .pill-bloom::before,
  .badge-blaze, .badge-blaze span { animation: none; }
  .btn-primary:hover, .feature-card:hover { transform: none; }
}

/* --- Responsive --- */
@media (max-width: 980px) {
  :root { --section-pad: 48px; }
  .hero {
    grid-template-columns: 1fr;
    gap: 48px;
    padding-top: 116px;
  }
  .lede { max-width: none; }
  .feature-grid { grid-template-columns: repeat(2, 1fr); }
  .principle-grid { grid-template-columns: 1fr; gap: 30px; }
  .spec { grid-template-columns: 1fr; }
}
@media (max-width: 680px) {
  body { font-size: 16px; }
  .nav-links a:not(.nav-cta) { display: none; }
  .feature-grid { grid-template-columns: 1fr; }
  .badge-blaze { font-size: 21px; top: -21px; right: 18px; }
  .mockup-content { padding: 20px 20px 24px; }
  .spec div { grid-template-columns: 1fr; gap: 4px; }
  .close-panel { padding: 56px 24px; }
}
@media (max-width: 480px) {
  .cta-row { flex-direction: column; align-items: stretch; }
  .btn { justify-content: center; }
}
