/* ── Typefaces ─────────────────────────────────────────────────────────────────────────────────
   Self-hosted, and they have to be: the site's CSP is font-src 'self', so a Google Fonts URL is
   blocked outright — silently, with the page simply rendering in a fallback.

   Which is what this page was doing. It named all three families below and declared none of them,
   so every heading fell back to the system UI font. The files were already in the repo for the
   editor; buildSite.mjs copies them to /fonts at the root for this page.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
@font-face {
  font-family: 'Space Grotesk';
  src: url('/fonts/space-grotesk-300.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Space Grotesk';
  src: url('/fonts/space-grotesk-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Space Grotesk';
  src: url('/fonts/space-grotesk-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Space Grotesk';
  src: url('/fonts/space-grotesk-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('/fonts/plus-jakarta-sans-300.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('/fonts/plus-jakarta-sans-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('/fonts/plus-jakarta-sans-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('/fonts/plus-jakarta-sans-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: url('/fonts/plus-jakarta-sans-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/fonts/jetbrains-mono-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/fonts/jetbrains-mono-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/fonts/jetbrains-mono-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/fonts/jetbrains-mono-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* =====================================================================
   Decimation Lab — Landing Page Stylesheet
   Self-contained. No build step. No framework dependencies.
   Matches the app's carbon-dark + neon-green aesthetic.
   ===================================================================== */

/* ── Fonts ──
   Space Grotesk, Plus Jakarta Sans and JetBrains Mono are declared as @font-face above, served from
   /fonts on this origin. There used to be an @import of the same three families from
   fonts.googleapis.com here. It never did anything on the live site: the CSP is `style-src 'self'`,
   so the request was blocked, and the only visible result was a console error on every page load.
   The local faces are the real ones - if a weight is missing, add it to /fonts and declare it. */

/* ── Design Tokens ── */
:root {
  /* Palette */
  --neon:        #00ff66;
  --neon-dim:    #00ff6633;
  --neon-glow:   #00ff6618;
  --coral:       #f43f5e;
  --cyan:        #22d3ee;
  --violet:      #a78bfa;
  --amber:       #f59e0b;

  --carbon-950:  #0f0f11;
  --carbon-900:  #131315;
  --carbon-850:  #161618;
  --carbon-800:  #1a1a1c;
  --carbon-700:  #222224;
  --carbon-600:  #2a2a2d;
  --carbon-500:  #3f3f46;
  --carbon-400:  #52525b;
  --carbon-300:  #71717a;
  --carbon-200:  #a1a1aa;
  --carbon-100:  #d4d4d8;
  --carbon-50:   #e4e4e7;

  /* Typography */
  --font-display: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  --font-body:    'Plus Jakarta Sans', ui-sans-serif, system-ui, sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;

  /* Spacing */
  --section-pad-y: clamp(80px, 10vw, 140px);
  --section-pad-x: clamp(20px, 5vw, 80px);
  --container-max: 1200px;

  /* Animation */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
}

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

html {
  scroll-behavior: smooth;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  background-color: var(--carbon-950);
  color: var(--carbon-50);
  font-family: var(--font-body);
  line-height: 1.6;
  overflow-x: hidden;
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

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

/* ── Utility ── */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 var(--section-pad-x);
}

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

/* ── Scroll Reveal ── */
/* Both rules are scoped to `html.js`, which boot.js adds before first paint.

   WHY: the hidden state is opacity 0, and the ONLY thing that ever reveals it is the
   IntersectionObserver in script.js. If that one file fails to load — a network error, a CSP change,
   a syntax error anywhere above the observer — every element carrying `reveal` stays invisible and
   the marketing page renders as a hero above a blank void. Gating the hidden state on JavaScript
   having run means the page degrades to un-animated rather than to empty.

   The `.visible` rule has to be scoped too, and not for symmetry: `html.js .reveal` is one class
   more specific than a bare `.reveal.visible`, so it would win, and the observer would add a class
   that changed nothing. Qualifying both keeps `.visible` the more specific of the two. */
html.js .reveal {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.8s var(--ease-out-expo),
              transform 0.8s var(--ease-out-expo);
}
html.js .reveal.visible {
  opacity: 1;
  transform: translateY(0);
}
.reveal-delay-1 { transition-delay: 0.1s; }
.reveal-delay-2 { transition-delay: 0.2s; }
.reveal-delay-3 { transition-delay: 0.3s; }
.reveal-delay-4 { transition-delay: 0.4s; }
.reveal-delay-5 { transition-delay: 0.5s; }

/* ══════════════════════════════════════════════════════════════════════
   SECTION 1 — HERO
   ══════════════════════════════════════════════════════════════════════ */

.hero {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  overflow: hidden;
  padding: 60px var(--section-pad-x) 80px;
}

/* Background layers */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  /* Grid pattern: BLACK lines, inverted to white. Drawn white directly, Dark Reader treats the lines
     as a background color and darkens them below the page, so the grid vanishes. It leaves `filter`
     alone, so black-inverted survives it. Same trick and reasoning as .btn-primary's text. Measured
     with Dark Reader's engine: lines sit +4 above the cells both with it on and off. */
  background:
    linear-gradient(to right, rgba(0,0,0,0.02) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0,0,0,0.02) 1px, transparent 1px);
  background-size: 40px 40px;
  filter: invert(1);
  pointer-events: none;
  z-index: 1;
}

/* Animated scan line */
.hero-scanline {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 100%;
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
}
.hero-scanline::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 120px;
  background: linear-gradient(180deg, transparent, rgba(0,255,102,0.04) 50%, transparent);
  will-change: transform;
  animation: scanDown 4s ease-in-out infinite;
}

/*
 * TRANSFORM, NOT `top`. This animated `top` from -120px to calc(100% + 120px), and `top` is a
 * layout property: every frame of it is reported as a layout shift, so an infinite 4s animation was
 * an infinite CLS generator. Measured at 0.1318 CLS on the landing page, of which 0.1312 was this
 * one pseudo-element across 209 shifts that were still arriving 7.6 seconds in.
 *
 * That is worse than a number over a threshold. CLS accumulates for the whole page view, so it had
 * no ceiling at all: the longer somebody read the page, the worse the score got, and it is a Core
 * Web Vitals ranking factor on the page search results now land on.
 *
 * `transform` is composited — no layout, no shift, and pixel-identical. The travel is written
 * against the viewport because a percentage in translateY resolves against the ELEMENT's own height
 * (120px here), not the parent's, which is the trap that makes the obvious translation wrong.
 */
@keyframes scanDown {
  0%   { transform: translateY(-120px); }
  100% { transform: translateY(calc(100vh + 120px)); }
}

/* Content */
.hero-content {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
}

/* Brand title */
.hero-title {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(36px, 7vw, 80px);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--carbon-50);
  line-height: 1.1;
}
.hero-title .accent {
  color: var(--neon);
  font-weight: 600;
  text-shadow: 0 0 40px rgba(0,255,102,0.3);
}

.hero-description {
  font-size: clamp(14px, 1.5vw, 17px);
  color: var(--carbon-300);
  max-width: 560px;
  line-height: 1.7;
}
/* Two stacked lines: what it is, then what you do with it. */
.hero-description-lead,
.hero-description-sub {
  display: block;
}
.hero-description {
  margin-top: 10px;
}
.hero-description-lead {
  font-size: clamp(16px, 1.7vw, 20px);
  color: var(--carbon-50);
}
.hero-description-sub {
  margin-top: 6px;
  color: var(--carbon-300);
}

/* Hero showcase image */
.hero-showcase {
  position: relative;
  margin-top: 20px;
  /* Grows with screen HEIGHT so the whole hero, clip included, lands on the first screen: 800px
     on tall displays, shrinking toward 560px on short laptops. 1.78 is the clip's width/height
     ratio (16:9, down from the old screenshot's 1.95); 460px is roughly the title and text above
     it. Wrong here and the hero is sized for an aspect it no longer has. */
  max-width: clamp(560px, calc((100vh - 460px) * 1.78), 800px);
  width: 100%;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--carbon-700);
  box-shadow:
    0 0 0 1px var(--carbon-700),
    0 25px 80px rgba(0,0,0,0.6);
}
.hero-showcase img {
  width: 100%;
  display: block;
}
/* The bottom-fade overlay that used to live here is GONE (2026-09-18). It ran a gradient to the page
   background over the lower 40% of the frame, which bled a still screenshot into the page nicely and
   does the opposite to a clip: the motion is still visible underneath it, so instead of an edge that
   dissolves you get a moving picture with a dirty smear across the bottom of it. A video has to be
   allowed to be a rectangle. */

/* Byline */

/* Scroll indicator */
.scroll-indicator {
  position: absolute;
  bottom: 32px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: var(--carbon-500);
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  animation: scrollBounce 2s ease-in-out infinite;
}
.scroll-indicator .chevron {
  width: 20px;
  height: 20px;
  border-right: 1.5px solid var(--carbon-500);
  border-bottom: 1.5px solid var(--carbon-500);
  transform: rotate(45deg);
}

@keyframes scrollBounce {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50%      { transform: translateX(-50%) translateY(8px); }
}


/* ══════════════════════════════════════════════════════════════════════
   SECTION 2 — FEATURES
   ══════════════════════════════════════════════════════════════════════ */

.features {
  padding: var(--section-pad-y) 0;
  position: relative;
}

.features::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent 0%, var(--carbon-700) 20%, var(--carbon-700) 80%, transparent 100%);
}

.section-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--neon);
  margin-bottom: 16px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.section-label::before {
  content: '';
  width: 20px;
  height: 1px;
  background: var(--neon);
}

.section-title {
  font-family: var(--font-display);
  font-size: clamp(28px, 4vw, 48px);
  font-weight: 500;
  color: var(--carbon-50);
  margin-bottom: 16px;
  line-height: 1.2;
}

.section-description {
  font-size: clamp(14px, 1.5vw, 16px);
  color: var(--carbon-300);
  max-width: 600px;
  line-height: 1.7;
  margin-bottom: 56px;
}

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

.feature-card {
  position: relative;
  padding: 32px 28px;
  background: var(--carbon-900);
  border: 1px solid var(--carbon-700);
  border-radius: 12px;
  transition: border-color 0.4s var(--ease-out-expo),
              box-shadow 0.4s var(--ease-out-expo),
              transform 0.4s var(--ease-out-expo);
  overflow: hidden;
}
.feature-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--neon-dim), transparent);
  opacity: 0;
  transition: opacity 0.4s ease;
}
.feature-card:hover {
  border-color: var(--carbon-600);
  transform: translateY(-4px);
  box-shadow: 0 20px 60px rgba(0,0,0,0.4), 0 0 40px rgba(0,255,102,0.03);
}
.feature-card:hover::before {
  opacity: 1;
}

.feature-icon {
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  margin-bottom: 20px;
  font-size: 20px;
  background: var(--carbon-800);
  border: 1px solid var(--carbon-700);
  color: var(--neon);
  transition: all 0.4s var(--ease-out-expo);
}
.feature-card:hover .feature-icon {
  background: var(--neon-glow);
  border-color: var(--neon-dim);
  box-shadow: 0 0 20px rgba(0,255,102,0.1);
}

.feature-card h3 {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 500;
  color: var(--carbon-50);
  margin-bottom: 10px;
}

.feature-card p {
  font-size: 13px;
  color: var(--carbon-300);
  line-height: 1.65;
}

.feature-stat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 16px;
  padding: 4px 10px;
  background: var(--carbon-800);
  border: 1px solid var(--carbon-700);
  border-radius: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--neon);
}
.feature-stat .num {
  font-weight: 700;
}

/* ══════════════════════════════════════════════════════════════════════
   SECTION 3b — EDITOR PREVIEW
   ══════════════════════════════════════════════════════════════════════ */

/* The step number, now carried by the gallery caption rather than by a section of its own. */
.preview-step-num {
  display: inline-block;
  min-width: 15px;
  margin-right: 4px;
  color: var(--neon);
  font-weight: 700;
}

.editor-preview {
  padding: var(--section-pad-y) 0;
  position: relative;
}

.editor-preview::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent 0%, var(--carbon-700) 20%, var(--carbon-700) 80%, transparent 100%);
}

.preview-gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

.preview-card {
  background: var(--carbon-900);
  border: 1px solid var(--carbon-700);
  border-radius: 12px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Explicit property list rather than `all`: `all` puts every animatable
     property in play and invites the browser to re-rasterize the whole card
     mid-transition, screenshot included. */
  transition: border-color 0.4s var(--ease-out-expo),
              box-shadow 0.4s var(--ease-out-expo),
              transform 0.4s var(--ease-out-expo);
}

.preview-card:hover {
  border-color: var(--carbon-600);
  transform: translateY(-4px);
  box-shadow: 0 20px 50px rgba(0,0,0,0.5), 0 0 30px rgba(0,255,102,0.04);
}

.preview-image {
  position: relative;
  overflow: hidden;
  background: var(--carbon-950);
  aspect-ratio: 16 / 9;
}

/* No hover zoom here on purpose. These are 1920x991 UI screenshots displayed
   ~320px wide, so the browser is already downscaling them ~6x. Scaling them
   again with a transform makes the compositor resample that downscale from a
   stretched texture, then re-rasterize crisply when the transition lands —
   which reads as "goes low quality for a second, then snaps back". Every 1px
   border and 10px label inside the screenshot shimmers through it. The card
   lift, border and glow carry the hover instead; none of them touch the
   image's pixel grid. */
.preview-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.preview-caption {
  padding: 16px 20px;
  font-size: 13px;
  color: var(--carbon-300);
  display: flex;
  align-items: center;
  gap: 12px;
  border-top: 1px solid var(--carbon-800);
  background: var(--carbon-850);
}

.preview-step {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--neon);
  padding: 3px 8px;
  background: var(--carbon-800);
  border: 1px solid var(--neon-dim);
  border-radius: 4px;
  /* `shrink: 0` was written here, which is not a CSS property and so did nothing. The badge was
     therefore shrinkable, and once a step number was added beside the word it wrapped onto two
     lines inside its own box. */
  flex-shrink: 0;
  white-space: nowrap;
}


/* ══════════════════════════════════════════════════════════════════════
   SECTION 4 — EFFECT LIBRARY & BEFORE/AFTER SLIDER (SPLIT ACCORDION)
   ══════════════════════════════════════════════════════════════════════ */

.effect-showcase {
  padding: var(--section-pad-y) 0;
  position: relative;
}

.effect-showcase::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent 0%, var(--carbon-700) 20%, var(--carbon-700) 80%, transparent 100%);
}

.showcase-split-layout {
  display: grid;
  grid-template-columns: 1fr 1.15fr;
  gap: 48px;
  align-items: start;
}

.showcase-left {
  display: flex;
  flex-direction: column;
}

.showcase-left .section-description {
  margin-bottom: 20px;
}

/* Accordion List */
.accordion-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 10px;
}

.accordion-item {
  background: var(--carbon-900);
  border: 1px solid var(--carbon-700);
  border-radius: 10px;
  overflow: hidden;
  transition: border-color 0.3s ease, background 0.3s ease;
}

.accordion-item:hover {
  border-color: var(--carbon-600);
}

.accordion-item.active {
  border-color: var(--neon-dim);
  background: var(--carbon-850);
}

.accordion-header {
  width: 100%;
  padding: 14px 18px;
  display: flex;
  align-items: center;
  gap: 12px;
  background: transparent;
  border: none;
  color: var(--carbon-100);
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
  transition: color 0.2s ease;
}

.accordion-header:hover {
  color: #ffffff;
}

.accordion-item.active .accordion-header {
  color: var(--neon);
}

.accordion-header .cat-icon {
  font-size: 14px;
}
.cat-pixel-icon   { color: #00ff66; }
.cat-color-icon   { color: #a855f7; }
.cat-glitch-icon  { color: #f59e0b; }
.cat-warp-icon    { color: #f43f5e; }
.cat-overlay-icon { color: #22d3ee; }
.cat-format-icon  { color: #3b82f6; }
.cat-outline-icon { color: #10b981; }
.cat-spectral-icon { color: #6366f1; }
.cat-artistic-icon { color: #ec4899; }

.accordion-header .cat-title {
  flex: 1;
}

.accordion-header .cat-badge {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--carbon-400);
  padding: 2px 8px;
  background: var(--carbon-800);
  border-radius: 100px;
}

.accordion-header .cat-arrow {
  font-size: 9px;
  color: var(--carbon-400);
  transition: transform 0.3s var(--ease-out-expo);
}

.accordion-item.active .cat-arrow {
  transform: rotate(180deg);
  color: var(--neon);
}

/* Accordion Collapsible Content */
.accordion-content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.4s var(--ease-out-expo), padding 0.3s ease;
  padding: 0 18px;
}

.accordion-item.active .accordion-content {
  max-height: 340px;
  overflow-y: auto;
  padding: 4px 18px 16px;
  border-top: 1px solid var(--carbon-800);
}

.accordion-content::-webkit-scrollbar {
  width: 4px;
}
.accordion-content::-webkit-scrollbar-track {
  background: var(--carbon-900);
}
.accordion-content::-webkit-scrollbar-thumb {
  background: var(--carbon-700);
  border-radius: 4px;
}
.accordion-content::-webkit-scrollbar-thumb:hover {
  background: var(--neon-dim);
}

.effect-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}

.effect-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-radius: 6px;
  background: var(--carbon-900);
  border: 1px solid var(--carbon-750);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--carbon-300);
  cursor: pointer;
  text-align: left;
  transition: all 0.2s ease;
}

.effect-item:hover {
  background: var(--carbon-800);
  color: var(--carbon-50);
  border-color: var(--carbon-600);
}

.effect-item.active-effect {
  background: var(--neon-glow);
  border-color: var(--neon);
  color: var(--neon);
  font-weight: 500;
  box-shadow: 0 0 12px rgba(0,255,102,0.1);
}

.effect-item .effect-bullet {
  font-size: 10px;
  opacity: 0.6;
}
.effect-item.active-effect .effect-bullet {
  color: var(--neon);
  opacity: 1;
}

/* Right Column (Sticky Before & After Slider) */
.showcase-right {
  position: sticky;
  top: 90px;
  top: max(90px, calc((100dvh - var(--preview-height, 100dvh)) / 2));
  display: flex;
  flex-direction: column;
  align-items: center;
}

.ba-slider-container {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 11;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid var(--carbon-700);
  box-shadow:
    0 25px 80px rgba(0,0,0,0.6),
    0 0 80px rgba(0,255,102,0.04);
  background: var(--carbon-950);
  user-select: none;
  touch-action: none;
}

.ba-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.ba-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.ba-after {
  z-index: 2;
  width: 50%;
  border-right: 1.5px solid var(--neon);
}

.ba-after img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
}

.ba-badge {
  position: absolute;
  top: 14px;
  padding: 6px 14px;
  border-radius: 100px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  z-index: 5;
  pointer-events: none;
  backdrop-filter: blur(8px);
  /* Each badge owns its half of the container, so the longest effect names
     ("Dead Channel Reconstruction") truncate instead of colliding. */
  max-width: calc(50% - 20px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.badge-left {
  left: 14px;
  background: rgba(15, 15, 17, 0.85);
  border: 1px solid var(--carbon-700);
  color: var(--carbon-300);
}

.badge-right {
  right: 14px;
  background: rgba(0, 255, 102, 0.15);
  border: 1px solid var(--neon-dim);
  color: var(--neon);
}

/* A preview is fetching. The label already reads the new effect name, so this
   pulse is what says the picture behind it hasn't caught up yet. Warmed
   previews resolve inside a frame and never show it. */
.ba-slider-container.is-loading .badge-right {
  animation: ba-badge-pulse 1s ease-in-out infinite;
}

@keyframes ba-badge-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
  .ba-slider-container.is-loading .badge-right { animation: none; }
  /* The hero scanline runs forever. Decoration that never stops is exactly what this preference is
     asking about, and hiding it costs the page nothing. */
  .hero-scanline::after { animation: none; display: none; }
}

/* Split Line & Knob */
.ba-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  background: var(--neon);
  z-index: 4;
  transform: translateX(-50%);
  pointer-events: none;
  box-shadow: 0 0 10px var(--neon);
}

.ba-handle-button {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--carbon-900);
  border: 2px solid var(--neon);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  color: var(--neon);
  font-size: 9px;
  box-shadow: 0 0 20px rgba(0,255,102,0.4), 0 4px 12px rgba(0,0,0,0.6);
}

.ba-slider-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: ew-resize;
  z-index: 10;
  margin: 0;
}

.ba-instructions {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--carbon-400);
  margin-top: 14px;
  text-align: center;
  letter-spacing: 0.05em;
}
.category-tag .cat-icon {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 5px;
  font-size: 13px;
}
.category-tag .cat-name {
  flex: 1;
  font-weight: 500;
  color: var(--carbon-100);
}
.category-tag .cat-count {
  font-size: 10px;
  color: var(--carbon-400);
  padding: 2px 8px;
  background: var(--carbon-800);
  border-radius: 100px;
}

/* Category icon colors */
.cat-pixel   .cat-icon { background: rgba(0,255,102,0.1);  color: #00ff66; }
.cat-color   .cat-icon { background: rgba(168,85,247,0.1); color: #a855f7; }
.cat-format  .cat-icon { background: rgba(59,130,246,0.1); color: #3b82f6; }
.cat-overlay .cat-icon { background: rgba(34,211,238,0.1); color: #22d3ee; }
.cat-warp    .cat-icon { background: rgba(244,63,94,0.1);  color: #f43f5e; }
.cat-glitch  .cat-icon { background: rgba(245,158,11,0.1); color: #f59e0b; }
.cat-outline .cat-icon { background: rgba(16,185,129,0.1); color: #10b981; }
.cat-spectral .cat-icon { background: rgba(99,102,241,0.1); color: #6366f1; }
.cat-artistic .cat-icon { background: rgba(236,72,153,0.1); color: #ec4899; }

.showcase-visual {
  position: relative;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid var(--carbon-700);
  box-shadow:
    0 25px 80px rgba(0,0,0,0.5),
    0 0 80px rgba(0,255,102,0.03);
}
.showcase-visual img {
  width: 100%;
  display: block;
}
.showcase-visual::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 16px;
  background: linear-gradient(135deg, transparent 50%, rgba(0,255,102,0.03) 100%);
  pointer-events: none;
  z-index: 1;
}

/* Total effects counter */
.effects-counter {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 8px;
}
.effects-counter .count-wrap {
  display: inline-flex;
  align-items: baseline;
}
/* The "+" is part of the claim, not decoration: the number is a floor, and checkLandingPage asserts
   the page still lists at least that many. Slightly smaller and dimmer so it reads as a qualifier
   rather than as another digit. */
.effects-counter .count-plus {
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 700;
  color: var(--neon);
  line-height: 1;
  opacity: 0.75;
  margin-left: 2px;
}
.effects-counter .count {
  font-family: var(--font-display);
  font-size: 48px;
  font-weight: 700;
  color: var(--neon);
  line-height: 1;
  text-shadow: 0 0 30px rgba(0,255,102,0.3);
}
.effects-counter .label {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--carbon-400);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* Link out to the generated category pages under /effects/ (scripts/buildEffectPages.mjs). Carries
   no `reveal` class on purpose — it is a crawl path into those pages as well as a link for the
   reader, and a `.reveal` element is opacity:0 until script.js reveals it. */
.showcase-browse {
  margin-top: 18px;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.02em;
}
.showcase-browse a {
  color: var(--neon);
}


/* ══════════════════════════════════════════════════════════════════════
   SECTION 5 — PRIVACY
   ══════════════════════════════════════════════════════════════════════ */

.privacy {
  padding: var(--section-pad-y) 0;
  position: relative;
}

.privacy::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent 0%, var(--carbon-700) 20%, var(--carbon-700) 80%, transparent 100%);
}

/* Closing call to action */
.final-cta {
  padding: var(--section-pad-y) 0;
  position: relative;
  text-align: center;
}
.final-cta::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent 0%, var(--carbon-700) 20%, var(--carbon-700) 80%, transparent 100%);
}
.final-cta .section-label {
  justify-content: center;
}
.final-cta .section-description {
  margin: 0 auto 32px;
}
.final-cta .offer-panel {
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
}

.privacy-card {
  position: relative;
  padding: 60px;
  background: var(--carbon-900);
  border: 1px solid var(--carbon-700);
  border-radius: 16px;
  text-align: center;
  overflow: hidden;
}
.privacy-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--neon), transparent);
  opacity: 0.4;
}
.privacy-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 50% 80% at 50% 0%, rgba(0,255,102,0.03) 0%, transparent 70%);
  pointer-events: none;
}

.privacy-icon {
  font-size: 48px;
  margin-bottom: 24px;
  position: relative;
  z-index: 1;
}

.privacy-headline {
  font-family: var(--font-display);
  font-size: clamp(24px, 3.5vw, 36px);
  font-weight: 500;
  color: var(--carbon-50);
  margin-bottom: 20px;
  position: relative;
  z-index: 1;
}
.privacy-headline em {
  font-style: normal;
  color: var(--neon);
}

.privacy-body {
  font-size: 15px;
  color: var(--carbon-300);
  max-width: 520px;
  margin: 0 auto 40px;
  line-height: 1.8;
  position: relative;
  z-index: 1;
}

.privacy-features {
  display: flex;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  position: relative;
  z-index: 1;
}

.privacy-feature {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 18px;
  background: var(--carbon-800);
  border: 1px solid var(--carbon-700);
  border-radius: 100px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  color: var(--carbon-200);
}
.privacy-feature .pf-icon {
  color: var(--neon);
  font-size: 14px;
}


/* ══════════════════════════════════════════════════════════════════════
   SECTION 6 — FOOTER
   ══════════════════════════════════════════════════════════════════════ */

.site-footer {
  padding: 48px 0;
  position: relative;
}

.site-footer::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent 0%, var(--carbon-700) 20%, var(--carbon-700) 80%, transparent 100%);
}

.footer-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
}

.footer-brand {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 300;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--carbon-300);
}
.footer-brand .accent {
  color: var(--neon);
  font-weight: 600;
}

.footer-tagline {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--carbon-500);
  letter-spacing: 0.05em;
}

.footer-credit {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--carbon-400);
}
.footer-credit a {
  color: var(--neon);
  text-decoration: none;
}

/* Decimation Lab's social accounts, as icons under the byline. The marks are inline SVG (no
   third-party images under the CSP) and take their color from here. */
.footer-social {
  display: flex;
  justify-content: center;
  gap: 16px;
  margin-top: 4px;
}
.footer-social a {
  display: inline-flex;
  color: var(--carbon-400);
  transition: color 0.2s ease;
}
.footer-social a:hover,
.footer-social a:focus-visible {
  color: var(--neon);
}
.footer-social svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
}


/* ══════════════════════════════════════════════════════════════════════
   RESPONSIVE
   ══════════════════════════════════════════════════════════════════════ */

@media (max-width: 968px) {
  .features-grid,
  .preview-gallery {
    grid-template-columns: repeat(2, 1fr);
  }
  .showcase-split-layout {
    grid-template-columns: 1fr;
    gap: 40px;
  }

  .showcase-right {
    position: static;
  }

  .privacy-card {
    padding: 40px 28px;
  }
}

@media (max-width: 640px) {
  .features-grid,
  .preview-gallery,
  .category-grid {
    grid-template-columns: 1fr;
  }

  .hero-showcase {
    max-width: 100%;
    border-radius: 8px;
  }

  .privacy-features {
    flex-direction: column;
    align-items: center;
    gap: 12px;
  }

  .scroll-indicator {
    display: none;
  }

  .effects-drawer {
    padding: 16px;
  }

  .ba-badge {
    font-size: 9px;
    padding: 4px 10px;
  }
}


/* ── Sign-in bar and purchase CTAs ─────────────────────────────────────────────────────────────
   Added when the landing page went live at `/` with the editor at `/app`. Before this the page had
   no route into the product at all and no price on it.

   The top bar is fixed and always shows "Sign in", because somebody who already owns this may
   arrive on a new machine, in a private window, or with site data cleared — the returning-visitor
   redirect in script.js cannot help any of them, so a visible way in is the actual mechanism.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px clamp(16px, 4vw, 40px);
  background: color-mix(in srgb, var(--carbon-950) 82%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--carbon-700);
}

/* THE WORDMARK. One lockup, in four places: this bar, the hero title, the footer, and the app's
   own header (src/components/panels/AppHeader.tsx). The footer and the app already agreed — light
   weight, wide tracking, uppercase, LAB in neon — and this bar had drifted off on its own: semibold,
   almost no tracking, and NO rule for `.accent` at all, so the one green word in the whole lockup
   rendered white. It is the piece that sits next to the app's header in a visitor's memory, because
   it is the last thing they see before clicking into the editor and the first thing they see there.
   Match the app; the hero title is the only one allowed to differ, being display type at 60px. */
.topbar-brand {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 300;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--carbon-50);
  text-decoration: none;
  white-space: nowrap;
}
.topbar-brand .accent {
  color: var(--neon);
  font-weight: 600;
}

.topbar-actions { display: flex; align-items: center; gap: 10px; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 9px 16px;
  border-radius: 6px;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.btn-primary { background: var(--neon); color: #000; border: 1px solid var(--neon); font-weight: 600; }
.btn-primary:hover { background: #4dff94; border-color: #4dff94; }

/* Black text that survives Dark Reader. Dark Reader turns dark text light and can't be told to skip
   one element (every color property is rewritten, color-mix() included), but it leaves light text
   light and never touches `filter`. So the text is WHITE, inverted to black: black with Dark Reader
   off, and still near-black with it on. The rest of the page stays Dark Reader's to recolor.
   Verified against Dark Reader's own engine (npm `darkreader`). */
.btn-primary .btn-label,
.btn-primary .btn-price { color: #fff; filter: invert(1); }

.btn-ghost { background: transparent; color: var(--carbon-100); border: 1px solid var(--carbon-500); }
.btn-ghost:hover { color: var(--carbon-50); border-color: var(--carbon-300); }

.btn-lg { padding: 14px 26px; font-size: 15px; }

/* Full-strength black. Faded text on neon green was hard to read, and the struck-through price
   faded twice (here and in .price-was) nearly disappeared. */
.btn-price { font-family: var(--font-mono); font-size: 0.9em; }

/* The offer panel (hero and closing section), using the same card treatment as the
   rest of the page (see .preview-card): solid surface, carbon-700 hairline, 12px radius. */
.offer-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 24px 32px 20px;
  border: 1px solid var(--carbon-700);
  border-radius: 12px;
  background: var(--carbon-900);
}

.hero-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin: 0;
}

.hero-cta-note {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--carbon-300);
  text-align: center;
  margin: 0;
}

/* Keeps the fixed bar off the hero title on short viewports, and holds open the
   breathing room the (now removed) hero badge used to provide. */
.hero { padding-top: 112px; }

/* The Explore arrow is decoration. On screens too short to fit it under the screenshot it
   collides with the image, so it steps aside there. */
@media (max-height: 959px) {
  .scroll-indicator { display: none; }
}

.footer-legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 18px;
  margin-top: 18px;
  font-family: var(--font-mono);
  font-size: 11px;
}

.footer-legal a { color: var(--carbon-300); text-decoration: none; }
.footer-legal a:hover { color: var(--neon); }

@media (max-width: 520px) {
  .topbar-brand { font-size: 12px; }
  .btn { padding: 8px 12px; font-size: 12px; }
  .hero-cta { flex-direction: column; align-items: stretch; }
}

.footer-copyright {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--carbon-400);
  margin: 14px 0 0;
}

/* ── Pricing and sales ─────────────────────────────────────────────────────────────────────────
   Driven by pricing.js from GET /api/price. The HTML carries the list price and a sale is revealed
   only once the server confirms it, so the struck-through price and the badge start hidden. The
   [hidden] rules are explicit because these elements set their own display, which would otherwise
   beat the attribute's user-agent style and show an empty badge.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.price-was[hidden],
.sale-badge[hidden],
.sale-badge-ends[hidden] { display: none !important; }

.price-was {
  margin-right: 0.4em;
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  opacity: 0.7;
}

.sale-badge {
  display: flex;
  width: fit-content;
  align-items: center;
  gap: 10px;
  margin: 0 auto 18px;
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--coral) 45%, transparent);
  background: color-mix(in srgb, var(--coral) 10%, transparent);
  color: var(--coral);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.sale-badge-ends { color: var(--carbon-200); letter-spacing: 0.04em; }
.sale-badge-ends::before {
  content: '\00B7';
  margin-right: 10px;
  color: color-mix(in srgb, var(--coral) 60%, transparent);
}

/* No room for both prices in the top bar on a phone. The badge further down still says it is a sale. */
@media (max-width: 520px) {
  .topbar .price-was { display: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   HERO PROMISE, TRIAL ACTIONS, AND THE SHOWCASE TRIO
   Added 2026-09-18 with the trial. The feature grid already DESCRIBED AI Generate, Create
   Variations and video; none of them were shown. These are the styles for showing them.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* The one promise, in place of a free-vs-paid table — see the note in index.html for why there is
   no table. */
.hero-promise {
  margin: 28px auto 0;
  font-family: var(--font-mono);
  font-size: clamp(12px, 1.5vw, 14px);
  letter-spacing: 0.02em;
  color: var(--carbon-200);
}

.hero-promise-price {
  color: var(--neon);
  font-weight: 600;
  white-space: nowrap;
}

/* `.price-was` is shown by pricing.js only when the server confirms a sale is running, so this can
   never invent a discount — it only styles one the server already reported. */
.hero-promise-price .price-was {
  color: var(--carbon-400);
  font-weight: 400;
  margin-right: 6px;
}

.hero-actions {
  margin-top: 22px;
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
}

/* ══════════════════════════════════════════════════════════════════════
   SCREENSHOT LIGHTBOX

   The UI screenshots are 1018-1920px wide and render about 320-560px wide, so the thing they are
   there to show - what the editor actually looks like - is not legible at the size it is shown.

   A HOVER ZOOM WOULD NOT HAVE FIXED THAT, and the note above `.preview-image img` says why it would
   have made things worse: scaling an already 6x-downscaled screenshot resamples a stretched texture,
   so every 1px border and 10px label inside it shimmers for the length of the transition. It also
   only ever buys a few percent, which is nowhere near enough to read a sidebar. Opening the image is
   the honest answer to "I cannot see it", and it costs nothing to download: the full-resolution file
   is already in the page, being displayed small.
   ══════════════════════════════════════════════════════════════════════ */

.zoomable {
  cursor: zoom-in;
}
.zoomable:focus-visible {
  outline: 2px solid var(--neon);
  outline-offset: 3px;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 5vmin 4vmin;
  background: rgba(8, 8, 10, 0.94);
  backdrop-filter: blur(3px);
}
/* `display: flex` beats the `hidden` attribute's UA `display: none`, so it has to be said again. */
.lightbox[hidden] { display: none !important; }

.lightbox img {
  /* No width/height of its own: an image smaller than the viewport shows at its true size rather
     than being blown up past it, which for a screenshot is the whole point. */
  max-width: min(96vw, 1680px);
  max-height: 82vh;
  width: auto;
  height: auto;
  display: block;
  border: 1px solid var(--carbon-700);
  border-radius: 8px;
  background: var(--carbon-950);
  box-shadow: 0 40px 110px rgba(0, 0, 0, 0.75);
}

/* The alt text, reused. It already describes the picture for someone who cannot see it, which is the
   same sentence someone looking at it enlarged wants as a label. */
.lightbox-hint {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--carbon-500);
}

.lightbox-caption {
  max-width: min(94vw, 820px);
  text-align: center;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.7;
  letter-spacing: 0.02em;
  color: var(--carbon-300);
}

/* THE WAY OUT, and it has to be seen to be one. The first version was a 38px dark-gray square on a
   near-black backdrop: present, and invisible, which for an exit is the same as absent. Clicking
   the backdrop and pressing Escape both work, but neither announces itself, so the button is the
   only affordance a first-time viewer can actually find — it gets real contrast, a 44px touch
   target, and the word ESC under it so the faster way out is learned rather than guessed. */
.lightbox-close {
  position: absolute;
  top: calc(16px + env(safe-area-inset-top, 0px));
  right: 16px;
  width: 44px;
  height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  font-family: var(--font-mono);
  line-height: 1;
  color: var(--carbon-50);
  background: var(--carbon-800);
  border: 1px solid var(--carbon-500);
  border-radius: 10px;
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s, background-color 0.2s;
}
.lightbox-close .x { font-size: 17px; }
.lightbox-close .esc { font-size: 7px; letter-spacing: 0.12em; color: var(--carbon-300); }
.lightbox-close:hover,
.lightbox-close:focus-visible {
  color: var(--neon);
  background: var(--carbon-750, #232327);
  border-color: var(--neon);
}
.lightbox-close:hover .esc,
.lightbox-close:focus-visible .esc { color: var(--neon); }

@media (max-width: 640px) {
  .lightbox img { max-height: 74vh; }
  .lightbox-caption { font-size: 10px; }
}


/* A pair of clips that cross-fade into each other, forever. See `video-cycle` in script.js.
   `aspect-ratio` on the FRAME is what keeps this off the CLS score: the clips are absolutely
   positioned and so contribute no height of their own, and a <video> whose source arrives a second
   after layout would otherwise resize the hero under the reader. Both clips are stacked in the same
   box and swapped by opacity, because anything that changed their POSITION would be animating a
   layout property - the defect that cost this page 0.13 CLS on the scanline. */
.video-cycle {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--carbon-900);
  overflow: hidden;
}
.video-cycle .cycle-clip {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 700ms ease;
}
.video-cycle .cycle-clip.is-active { opacity: 1; }

/* The portrait pair, shot on a phone. Held to a sane height and centered in the panel rather than
   filling the column: a 9:16 clip across half a 1200px row would be a thousand pixels tall. */
.video-cycle--portrait {
  aspect-ratio: 406 / 720;
  width: min(100%, 300px);
  margin: 0 auto;
  border-radius: 6px;
}
.trio-media--portrait {
  display: flex;
  justify-content: center;
  padding: 22px;
}

/* A decoration that never stops is exactly what this preference is asking about. The poster stays,
   which is a complete picture rather than a gap - and script.js never attaches a source at all, so
   this is a genuine saving and not just a hidden download. */
@media (prefers-reduced-motion: reduce) {
  .video-cycle .cycle-clip { transition: none; }
}

.showcase-trio {
  padding: var(--section-pad-y) var(--section-pad-x);
  background: var(--carbon-950);
}

.trio-row {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: clamp(28px, 5vw, 64px);
  align-items: center;
}

.trio-row + .trio-row {
  margin-top: clamp(56px, 8vw, 110px);
}

/* Alternating sides. `direction` would flip the text too, so the media is moved by column order —
   and the copy is pinned back to column 1 rather than left to reflow, because `order` on a grid
   child does not reorder for a screen reader. */
.trio-row--reverse .trio-media { grid-column: 2; grid-row: 1; }
.trio-row--reverse .trio-copy  { grid-column: 1; grid-row: 1; }

.trio-media {
  border: 1px solid var(--carbon-700);
  border-radius: 10px;
  overflow: hidden;
  background: var(--carbon-900);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.55);
}

.trio-media img {
  display: block;
  width: 100%;
  height: auto;
}

.trio-copy h3 {
  margin: 14px 0 12px;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(22px, 3vw, 32px);
  line-height: 1.18;
  color: var(--carbon-50);
}

.trio-copy p {
  font-size: clamp(14px, 1.6vw, 16px);
  line-height: 1.75;
  color: var(--carbon-200);
}

.trio-copy p + p { margin-top: 14px; }

/* The qualification that has to travel with the claim (Decision 5). Deliberately not hidden behind
   a smaller, grayer treatment than the claim above it — it is dimmer, but it is the same size. */
.trio-note {
  color: var(--carbon-300);
  border-left: 2px solid var(--carbon-600);
  padding-left: 14px;
}

/* One column on a phone, media first in every row so the page alternates copy/media/copy/media
   rather than stacking two blocks of text together. */
@media (max-width: 860px) {
  .trio-row { grid-template-columns: 1fr; gap: 22px; }
  .trio-row--reverse .trio-media,
  .trio-row--reverse .trio-copy { grid-column: 1; }
  .trio-row--reverse .trio-media { grid-row: 1; }
  .trio-row--reverse .trio-copy  { grid-row: 2; }
}

/* The free route in the topbar. On a narrow screen the topbar is already tight, so Sign in gives way
   first — someone who has an account can still reach it from /app, but a first-time visitor losing
   the free route loses the whole point of it. */
.btn-try {
  border-color: var(--neon-dim);
  color: var(--neon);
}

.btn-try:hover {
  border-color: var(--neon);
  color: var(--neon);
}

@media (max-width: 560px) {
  .topbar-signin { display: none; }
}

.cat-ai-icon { color: var(--violet); }

/* The AI row shows a pair. The border and shadow move to the figures so the two read as two frames
   rather than one panel split down the middle. */
.trio-media--pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  border: 0;
  background: none;
  box-shadow: none;
  overflow: visible;
}

.trio-media--pair figure {
  margin: 0;
  border: 1px solid var(--carbon-700);
  border-radius: 8px;
  overflow: hidden;
  background: var(--carbon-900);
  box-shadow: 0 20px 44px rgba(0, 0, 0, 0.5);
}

.trio-media--pair img { display: block; width: 100%; height: auto; }

.trio-media--pair figcaption {
  padding: 7px 10px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--carbon-300);
  border-top: 1px solid var(--carbon-700);
}
