/* Ridio marketing landing page — shared styles for all locales. */

:root {
  --bg: #f6f1e6;
  --bg-alt: #ece2d1;
  --card: #fffdf9;
  --ink: #241d17;
  --ink-soft: #5c5148;
  --ink-faint: #8a7d6d;
  --border: rgba(36, 29, 23, 0.12);
  --accent: #b06547;
  --accent-dark: #8a4d34;
  --accent-soft: #f2ddd0;
  --accent-ink: #fffaf5;
  --shadow-sm: 0 1px 2px rgba(36, 29, 23, 0.08);
  --shadow-md: 0 8px 24px rgba(36, 29, 23, 0.1);
  --shadow-lg: 0 20px 60px rgba(36, 29, 23, 0.16);
  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 26px;
  --max-w: 1160px;
  --header-h: 64px;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1512;
    --bg-alt: #221b16;
    --card: #241d18;
    --ink: #f4ece0;
    --ink-soft: #cabaa5;
    --ink-faint: #8f8071;
    --border: rgba(244, 236, 224, 0.12);
    --accent: #d98262;
    --accent-dark: #e79877;
    --accent-soft: #3a2a22;
    --accent-ink: #241108;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
    --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.55);
    color-scheme: dark;
  }
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  * {
    animation-duration: 0.001ms !important;
    animation-delay: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, .display {
  font-family: "Literata", Georgia, "Times New Roman", serif;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin: 0 0 0.5em;
}

p {
  margin: 0 0 1em;
  color: var(--ink-soft);
}

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

a {
  color: inherit;
}

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

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent-dark);
  background: var(--accent-soft);
  border-radius: 999px;
  padding: 8px 16px;
  margin-bottom: 18px;
  max-width: 260px;
}

.eyebrow svg {
  flex: none;
  width: 14px;
  height: 14px;
}

@media (min-width: 460px) {
  .eyebrow {
    font-size: 13px;
    letter-spacing: 0.06em;
    max-width: none;
    white-space: nowrap;
  }
}

.section {
  padding: 64px 0;
}

.section-alt {
  background: var(--bg-alt);
}

.section-head {
  max-width: 640px;
  margin: 0 0 40px;
}

.section-head.center {
  margin-inline: auto;
  text-align: center;
}

.section-head h2 {
  font-size: clamp(28px, 4.5vw, 38px);
}

.section-head p {
  font-size: 17px;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 52px;
  padding: 0 26px;
  border-radius: 999px;
  font-size: 16px;
  font-weight: 600;
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
  white-space: nowrap;
}

.btn:active {
  transform: scale(0.97);
}

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--shadow-md);
}

.btn-primary:hover {
  background: var(--accent-dark);
}

.btn-ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--border);
}

.btn-ghost:hover {
  border-color: var(--accent);
  color: var(--accent-dark);
}

.btn-block {
  width: 100%;
}

.btn-lg {
  min-height: 58px;
  padding: 0 32px;
  font-size: 17px;
}

/* Header */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(140%) blur(10px);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--border);
}

.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  font-family: "Literata", Georgia, serif;
  font-weight: 600;
  font-size: 20px;
  color: var(--ink);
}

.brand img {
  width: 32px;
  height: 32px;
  border-radius: 8px;
}

.nav-links {
  display: none;
  align-items: center;
  gap: 28px;
  font-size: 15px;
  font-weight: 500;
}

.nav-links a {
  text-decoration: none;
  color: var(--ink-soft);
}

.nav-links a:hover {
  color: var(--accent-dark);
}

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

.lang-switcher {
  position: relative;
}

.lang-switcher summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  font-weight: 600;
  padding: 8px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--ink-soft);
}

.lang-switcher summary::-webkit-details-marker {
  display: none;
}

.lang-switcher[open] summary {
  border-color: var(--accent);
  color: var(--accent-dark);
}

.lang-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  padding: 6px;
  min-width: 160px;
}

.lang-menu a {
  display: block;
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 14px;
  text-decoration: none;
  color: var(--ink-soft);
}

.lang-menu a:hover {
  background: var(--accent-soft);
  color: var(--accent-dark);
}

.lang-menu a[aria-current="true"] {
  color: var(--accent-dark);
  font-weight: 700;
}

.header-actions .btn-primary {
  display: none;
  min-height: 40px;
  padding: 0 18px;
  font-size: 14px;
}

/* Hero — the demo IS the cover: headline + lede first (what most visitors
   catch before their first scroll), the animated visual right under them so
   scrolling lands on it, and the CTA below. Everything centered. */
.hero {
  padding: 32px 0 56px;
  overflow: hidden;
}

.hero .container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
}

.hero-intro,
.hero-actions {
  text-align: center;
  max-width: 680px;
}

.hero-intro .lede {
  margin-bottom: 0;
}

.hero h1 {
  font-size: clamp(32px, 6.5vw, 54px);
}

.hero .lede {
  font-size: 18px;
  max-width: 46ch;
  margin-inline: auto;
  margin-bottom: 28px;
}

.hero-cta-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-bottom: 18px;
}

.hero-note {
  font-size: 13px;
  color: var(--ink-faint);
}

.trust-bullets {
  list-style: none;
  padding: 0;
  margin: 28px 0 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px 24px;
}

.trust-bullets li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 14px;
  color: var(--ink-soft);
}

.trust-bullets svg {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  color: var(--accent);
}

.hero-visual {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1.02;
  max-width: 480px;
  margin: 0 auto;
}

.hero-visual .frame {
  position: absolute;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  border: 6px solid var(--card);
}

.hero-visual .frame-main {
  inset: 32% 0 0 24%;
  z-index: 2;
}

/* The text "page" card: kept clearly visible (not just a sliver peeking
   out) so the text -> illustration pairing actually reads at a glance. */
.hero-visual .frame-secondary {
  inset: 0 26% 40% 0;
  z-index: 1;
  transform: rotate(-3deg);
}

.hero-visual .frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The "before" half of the hero's magic-moment pairing: a page of text with
   the phrase that gets illustrated highlighted, sitting behind the "after"
   illustration (frame-main) so visitors see text -> picture at a glance. */
.hero-visual .page-mock {
  width: 100%;
  height: 100%;
  background: var(--card);
  display: flex;
  align-items: center;
  padding: 18px 16px;
}

.hero-visual .page-mock p {
  margin: 0;
  font-family: "Literata", Georgia, serif;
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--ink-soft);
  text-align: left;
}

/* The sentence Ridio "reads" and turns into the picture beside it. Rendered
   as a highlighter fill plus a drawn underline, both painted by background
   gradients so landing.js can replay a left-to-right sweep (.is-scanning)
   right before each regeneration. The resting state is fully swept, so
   no-JS and reduced-motion visitors simply see a highlighted sentence. */
.hero-visual .page-mock mark {
  background-color: transparent;
  background-image: linear-gradient(var(--accent-soft), var(--accent-soft)),
    linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-size: 100% 100%, 100% 2px;
  background-position: 0 0, 0 100%;
  color: var(--accent-dark);
  font-weight: 600;
  padding: 1px 1px 2px;
  border-radius: 2px;
  /* Each wrapped line paints its own complete fill + underline, so the
     sweep animation behaves identically across browsers and fragments. */
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* Character-by-character sweep. landing.js splits the sentence into word
   wrappers (.w, unbreakable) holding one span per character (.ch, indexed
   via --i); adding .is-sweeping lights every character after its own
   staggered delay, so the highlighter travels the whole sentence serially —
   across line wraps — like a pen following the words. Removing the class
   clears all characters at once (delay only applies on the way in). */
.hero-visual .page-mock mark.is-split {
  background-image: none;
  padding: 0;
  border-radius: 0;
}

.hero-visual .page-mock mark .w {
  white-space: nowrap;
}

.hero-visual .page-mock mark .ch {
  transition:
    background-color 0.13s ease,
    box-shadow 0.13s ease;
}

html.js .hero-visual .page-mock mark.is-sweeping .ch {
  background-color: var(--accent-soft);
  box-shadow: 0 2px 0 var(--accent);
  /* Keep the 12ms step in sync with CHAR_MS in landing.js. */
  transition-delay: calc(var(--i, 0) * 12ms);
}

/* First act of the cycle: only the character's name (the sentence's first
   word) lights up — Ridio spotting "Elena" before reading the scene. */
html.js .hero-visual .page-mock mark.is-naming .w:first-child .ch {
  background-color: var(--accent-soft);
  box-shadow: 0 2px 0 var(--accent);
  transition-delay: calc(var(--i, 0) * 40ms);
}

/* Floating character chip: springs from the detected name, flies to hover
   over the illustration frame, pulses while "building" the character's
   identity, then Elena's portrait materializes over the colored initial —
   the exact cast-chip flow the app (and the feature mocks below) use. At
   generation time it fuses with the incoming style chip. */
.cast-float {
  position: absolute;
  z-index: 4;
  width: 34px;
  height: 34px;
  opacity: 0;
  pointer-events: none;
  --fx: 0px;
  --fy: 0px;
}

.cast-float-core {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: #b06547; /* Elena's cast color (MOCK_CAST "E") */
  border: 2px solid var(--card);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

.cast-float-initial {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fffaf5;
  font-weight: 700;
  font-size: 15px;
  font-family: "Literata", Georgia, serif;
}

.cast-float img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.15);
  transition:
    opacity 0.45s ease,
    transform 0.45s ease;
}

.cast-float.is-ready img {
  opacity: 1;
  transform: none;
}

.cast-float.show {
  animation: cast-in 0.35s cubic-bezier(0.2, 0.7, 0.3, 1.4) both;
}

.cast-float.is-flying {
  animation: chip-fly-x 0.75s cubic-bezier(0.55, 0.08, 0.65, 0.9) both;
}

.cast-float.is-flying .cast-float-core {
  animation: cast-fly-y 0.75s cubic-bezier(0.5, -0.38, 0.75, 0.5) both;
}

.cast-float.is-hovering {
  opacity: 1;
  animation: pick-float 2.6s ease-in-out infinite alternate;
}

/* Identity-building pulse (ring lives on the outer element so the core's
   overflow clipping doesn't cut it). */
.cast-float.is-loading::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  animation: cast-ring 0.85s ease-out infinite;
}

.cast-float.is-fusing {
  opacity: 1;
  animation: cast-fuse 0.55s cubic-bezier(0.4, 0, 0.6, 1) both;
}

.cast-float.is-fusing::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  animation: cast-ring 0.5s ease-out;
}

@keyframes cast-in {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes cast-fly-y {
  to {
    transform: translateY(var(--fy));
  }
}

@keyframes cast-ring {
  from {
    transform: scale(0.85);
    opacity: 1;
  }
  to {
    transform: scale(1.6);
    opacity: 0;
  }
}

@keyframes cast-fuse {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  35% {
    transform: scale(1.4);
  }
  100% {
    transform: scale(0.15);
    opacity: 0;
  }
}

/* Floating style picker: three chips pop in beside the text page once the
   sentence is swept, one gets "pressed" and flies into the illustration
   frame (the arc comes from animating X on the chip and Y on its core with
   different easings; the deltas --fx/--fy are measured by landing.js). */
.style-picker {
  position: absolute;
  top: 4%;
  right: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 9px;
  pointer-events: none;
}

.style-picker .pick-chip {
  opacity: 0;
  --fx: 0px;
  --fy: 0px;
}

.style-picker.show .pick-chip {
  animation:
    pick-in 0.45s cubic-bezier(0.2, 0.7, 0.3, 1.35) both,
    pick-float 2.4s ease-in-out infinite alternate;
  animation-delay: var(--d, 0s), calc(0.45s + var(--d, 0s));
}

.pick-chip-core {
  display: inline-block;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
  color: var(--ink);
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1;
  padding: 8px 13px;
  border-radius: 999px;
  transition:
    background 0.25s ease,
    color 0.25s ease,
    box-shadow 0.25s ease;
}

.style-picker .pick-chip.is-pressed .pick-chip-core {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 25%, transparent);
  animation: pick-press 0.38s cubic-bezier(0.3, 0.1, 0.3, 1);
}

.style-picker.show .pick-chip.is-out {
  animation: pick-out 0.45s ease both;
}

.style-picker.show .pick-chip.is-flying {
  animation: chip-fly-x 0.85s cubic-bezier(0.55, 0.08, 0.65, 0.9) both;
}

.style-picker .pick-chip.is-flying .pick-chip-core {
  animation: chip-fly-y 0.85s cubic-bezier(0.5, -0.38, 0.75, 0.5) both;
}

@keyframes pick-in {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.75);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes pick-float {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-5px);
  }
}

@keyframes pick-press {
  40% {
    transform: scale(0.8);
  }
}

@keyframes pick-out {
  to {
    opacity: 0;
    transform: translateY(8px) scale(0.85);
  }
}

@keyframes chip-fly-x {
  from {
    opacity: 1;
  }
  to {
    opacity: 1;
    transform: translateX(var(--fx));
  }
}

@keyframes chip-fly-y {
  0% {
    transform: translateY(0) scale(1) rotate(0deg);
  }
  60% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translateY(var(--fy)) scale(0.45) rotate(-8deg);
  }
}

.hero-visual .badge-float {
  position: absolute;
  right: -6px;
  bottom: 6%;
  z-index: 5;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
  /* It's a real <button> now (it opens the style wheel). */
  font-family: inherit;
  color: var(--ink);
  cursor: pointer;
  transition:
    border-color 0.2s ease,
    transform 0.2s ease;
}

.hero-visual .badge-float:hover,
.hero-visual .badge-float[aria-expanded="true"] {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.hero-visual .badge-float:active {
  transform: scale(0.97);
}

/* Style wheel: tag pills fanning out above the badge. Picking one
   interrupts the demo loop and regenerates the illustration in that style
   (the loop resumes on its own after a pause). */
.style-wheel {
  position: absolute;
  right: -6px;
  bottom: calc(6% + 52px);
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  pointer-events: none;
  /* Closed = fully out of the page (no tab stops); the delay lets the
     tags' fade-out play before the wheel vanishes. */
  visibility: hidden;
  transition: visibility 0s linear 0.4s;
}

.style-wheel.open {
  visibility: visible;
  transition-delay: 0s;
}

/* Without JS the wheel markup stays [hidden]; make sure that wins over the
   display:flex above. */
.style-wheel[hidden] {
  display: none;
}

.style-wheel .wheel-tag {
  opacity: 0;
  transform: translateY(12px) scale(0.85);
  pointer-events: none;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--ink);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1;
  padding: 8px 14px;
  border-radius: 999px;
  box-shadow: var(--shadow-md);
  cursor: pointer;
  /* Fan the column slightly to the left as it rises — a quarter-wheel. */
  margin-right: calc(var(--wi, 0) * var(--wi, 0) * 2.5px);
  transition:
    opacity 0.22s ease,
    transform 0.3s cubic-bezier(0.2, 0.7, 0.3, 1.35),
    border-color 0.2s ease,
    color 0.2s ease;
  transition-delay: calc(var(--wi, 0) * 40ms);
}

.style-wheel.open .wheel-tag {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.style-wheel .wheel-tag:hover {
  border-color: var(--accent);
  color: var(--accent-dark);
}

.style-wheel .wheel-tag.is-current {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.hero-visual .badge-float svg {
  width: 18px;
  height: 18px;
  color: var(--accent);
}

/* Steps */
.steps {
  display: grid;
  gap: 24px;
  counter-reset: step;
}

.step {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 28px 24px;
  position: relative;
}

.step::before {
  counter-increment: step;
  content: counter(step);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 700;
  font-size: 15px;
  margin-bottom: 16px;
}

.step h3 {
  font-size: 19px;
  margin-bottom: 6px;
}

.step p {
  font-size: 15px;
  margin: 0;
}

/* Feature grid */
.feature-grid {
  display: grid;
  gap: 20px;
}

.feature-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.feature-card-body {
  padding: 18px 20px 22px;
}

.feature-card-body h3 {
  font-size: 17.5px;
  margin-bottom: 6px;
}

.feature-card-body p {
  font-size: 14px;
  margin: 0;
}

/* Product mockups (real component styling, sample data — see feature cards) */
.mock {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 150px;
  background: var(--bg-alt);
}

.mock-chips,
.mock-graph {
  padding: 20px;
}

.mock-chip-row {
  display: flex;
}

.mock-chip {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fffaf5;
  font-weight: 700;
  font-family: "Literata", Georgia, serif;
  font-size: 15px;
  border: 2px solid var(--card);
  box-shadow: var(--shadow-sm);
  margin-left: -10px;
  flex: none;
}

.mock-chip:first-child {
  margin-left: 0;
}

/* AI-generated cast portrait filling the chip; the inline background color
   underneath stays visible while it loads (or if it fails). The extra
   .mock-chip-row ancestor outweighs later broad rules like .mock-media img,
   which would otherwise stretch the chips inside the scene card. */
.mock-chip-row .mock-chip img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

.mock-chip.ready {
  transform: scale(1.16);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent);
}

.mock-chip.small {
  width: 26px;
  height: 26px;
  font-size: 11px;
  margin-left: -7px;
  border-width: 1.5px;
}

.mock-graph-svg {
  width: 100%;
  height: 150px;
  overflow: visible;
}

.mock-graph-svg .edge {
  stroke: #cbbb9e;
  stroke-width: 1.5;
}

.mock-graph-svg .edge.live {
  stroke: var(--accent);
  stroke-width: 2.2;
}

.mock-graph-svg .edge-label {
  font-size: 9px;
  font-style: italic;
  fill: var(--ink-faint);
  text-anchor: middle;
}

.mock-graph-svg .edge-label.live {
  fill: var(--accent-dark);
  font-weight: 600;
}

.mock-graph-svg .node circle {
  stroke: var(--card);
  stroke-width: 2;
}

.mock-graph-svg .node text {
  fill: #fffaf5;
  font-size: 13px;
  font-weight: 700;
  font-family: "Literata", Georgia, serif;
  text-anchor: middle;
}

.mock-media {
  flex-direction: column;
  align-items: stretch;
}

.mock-media img {
  width: 100%;
  height: 130px;
  object-fit: cover;
}

.mock-media-caption {
  padding: 10px 16px 14px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.mock-media-caption strong {
  font-family: "Literata", Georgia, serif;
  font-size: 14px;
}

.mock-media-caption span {
  font-size: 11px;
  color: var(--ink-faint);
}

.mock-scene-frame {
  position: relative;
}

.mock-scene-chips {
  position: absolute;
  top: 8px;
  right: 8px;
  display: flex;
}

.mock-quote {
  padding: 10px 16px 14px;
  font-size: 11.5px;
  font-style: italic;
  color: var(--ink-faint);
  margin: 0;
}

.mock-storyboard {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  padding: 10px;
  width: 100%;
}

.mock-panel {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
  aspect-ratio: 4 / 3;
}

.mock-panel img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.mock-panel span {
  position: absolute;
  left: 6px;
  bottom: 6px;
  background: rgba(36, 29, 23, 0.72);
  color: #fdfaf4;
  font-size: 9px;
  padding: 2px 7px;
  border-radius: 999px;
}

.mock-recap {
  flex-direction: column;
  align-items: stretch;
  padding: 0;
}

.mock-recap-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 16px 16px 10px;
}

.mock-recap-ornament {
  font-size: 20px;
  color: var(--accent);
}

.mock-recap-figure img {
  width: 100%;
  height: 100px;
  object-fit: cover;
  display: block;
}

.mock-recap-summary {
  font-size: 11.5px;
  color: var(--ink-soft);
  text-align: left;
  margin: 0;
  line-height: 1.6;
  padding: 0 16px 14px;
}

/* Style gallery */
/* The scroller bleeds to the viewport edges via `margin: 0 -20px`, so its box
   is 40px wider than the section and pokes ~20px past the right edge. Clip
   that overflow at the section (x-axis only, so vertical reveals/sticky are
   untouched) — this kills the faint horizontal page scroll on mobile without
   putting overflow-x on <html>/<body>, which would break the sticky header. */
#styles {
  overflow-x: clip;
}

.style-scroller {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  padding: 4px 20px 20px;
  margin: 0 -20px;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  /* Soft edge fades hint that the gallery keeps going (it also auto-drifts —
     see landing.js). */
  -webkit-mask-image: linear-gradient(
    to right,
    transparent,
    #000 28px,
    #000 calc(100% - 28px),
    transparent
  );
  mask-image: linear-gradient(
    to right,
    transparent,
    #000 28px,
    #000 calc(100% - 28px),
    transparent
  );
}

.style-scroller::-webkit-scrollbar {
  height: 6px;
}

.style-scroller::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 999px;
}

.style-card {
  flex: none;
  width: 200px;
  scroll-snap-align: start;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
}

.style-card .thumb {
  aspect-ratio: 4 / 5;
  overflow: hidden;
}

.style-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}

.style-card:hover img {
  transform: scale(1.05);
}

.style-card .label {
  padding: 12px 14px;
  font-size: 13.5px;
  font-weight: 600;
}

/* Soon / waitlist section */
.soon-card {
  background: var(--ink);
  color: var(--bg);
  border-radius: var(--radius-lg);
  padding: 44px 26px;
  text-align: center;
}

.soon-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: var(--bg);
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  margin-bottom: 16px;
}

.soon-badge svg {
  width: 13px;
  height: 13px;
  color: var(--accent-dark);
}

.soon-card h2 {
  color: var(--bg);
  font-size: clamp(26px, 5vw, 34px);
}

.soon-card p {
  color: color-mix(in srgb, var(--bg) 75%, transparent);
  max-width: 52ch;
  margin: 0 auto 28px;
}

.store-badges {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-bottom: 28px;
}

.store-badge {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 18px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: var(--bg);
  text-decoration: none;
  font-size: 13px;
  min-width: 168px;
  position: relative;
  cursor: pointer;
}

/* Not a real store link yet — decorative "coming soon" indicator, not
   clickable, so it never reads as a broken/misleading download button. */
.store-badge.is-inert {
  cursor: default;
  opacity: 0.85;
}

.store-badge svg {
  width: 24px;
  height: 24px;
  flex: none;
}

.store-badge .store-badge-text {
  text-align: left;
  line-height: 1.2;
}

.store-badge .store-badge-text small {
  display: block;
  font-size: 10.5px;
  opacity: 0.75;
}

.store-badge .store-badge-text strong {
  font-size: 15px;
  font-family: "Literata", Georgia, serif;
}

.store-badge .soon-pill {
  position: absolute;
  top: -9px;
  right: -8px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
}

.waitlist-form {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  max-width: 440px;
  margin: 0 auto;
}

.waitlist-form input[type="email"] {
  flex: 1 1 220px;
  min-height: 52px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.06);
  color: var(--bg);
  padding: 0 20px;
  font-size: 15px;
}

.waitlist-form input[type="email"]::placeholder {
  color: color-mix(in srgb, var(--bg) 55%, transparent);
}

.waitlist-form input[type="email"]:focus {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.waitlist-note {
  font-size: 12.5px;
  color: color-mix(in srgb, var(--bg) 60%, transparent);
  margin-top: 14px;
}

.waitlist-status {
  font-size: 13.5px;
  margin-top: 12px;
  min-height: 1.2em;
}

/* FAQ */
.faq-list {
  border-top: 1px solid var(--border);
}

.faq-item {
  border-bottom: 1px solid var(--border);
}

.faq-item summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 4px;
  font-size: 16.5px;
  font-weight: 600;
}

.faq-item summary::-webkit-details-marker {
  display: none;
}

.faq-item .chevron {
  flex: none;
  width: 20px;
  height: 20px;
  transition: transform 0.2s ease;
  color: var(--accent);
}

.faq-item[open] .chevron {
  transform: rotate(180deg);
}

.faq-item .faq-answer {
  padding: 0 4px 20px;
  max-width: 68ch;
}

.faq-item .faq-answer p {
  font-size: 15px;
  margin: 0;
}

/* Final CTA band */
.final-cta {
  text-align: center;
  padding: 72px 0;
}

.final-cta h2 {
  font-size: clamp(28px, 5vw, 40px);
}

.final-cta p {
  max-width: 50ch;
  margin: 0 auto 28px;
  font-size: 17px;
}

/* Footer */
.site-footer {
  border-top: 1px solid var(--border);
  padding: 40px 0 calc(40px + env(safe-area-inset-bottom));
  font-size: 14px;
  color: var(--ink-faint);
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 24px;
}

.footer-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: "Literata", Georgia, serif;
  font-weight: 600;
  font-size: 17px;
  color: var(--ink);
  text-decoration: none;
}

.footer-brand img {
  width: 26px;
  height: 26px;
  border-radius: 7px;
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 18px 26px;
}

.footer-links a {
  text-decoration: none;
  color: var(--ink-soft);
}

.footer-links a:hover {
  color: var(--accent-dark);
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: space-between;
  align-items: center;
  border-top: 1px solid var(--border);
  padding-top: 20px;
}

/* Sticky mobile CTA bar (thumb zone) */
.mobile-cta-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  display: flex;
  gap: 10px;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-top: 1px solid var(--border);
  transform: translateY(100%);
  transition: transform 0.25s ease;
}

.mobile-cta-bar.visible {
  transform: translateY(0);
}

.mobile-cta-bar .btn {
  flex: 1;
}

@media (min-width: 860px) {
  .mobile-cta-bar {
    display: none;
  }
}

/* Responsive */
@media (min-width: 700px) {
  .steps {
    grid-template-columns: repeat(3, 1fr);
    position: relative;
  }

  /* Dashed thread linking the three steps, visible in the gaps between the
     cards — page to picture as one continuous journey. */
  .steps::before {
    content: "";
    position: absolute;
    top: 45px;
    left: 8%;
    right: 8%;
    border-top: 2px dashed color-mix(in srgb, var(--accent) 45%, transparent);
  }

  .feature-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 860px) {
  .nav-links {
    display: flex;
  }

  .header-actions .btn-primary {
    display: inline-flex;
  }

  .section {
    padding: 96px 0;
  }

  .feature-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 640px) {
  .waitlist-form {
    flex-wrap: nowrap;
  }
}

/* Skip link */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: var(--accent-ink);
  padding: 12px 18px;
  border-radius: 0 0 8px 0;
  z-index: 100;
}

.skip-link:focus {
  left: 0;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Motion & emotion layer
   The page's whole pitch is a transformation — plain text becoming pictures —
   so the motion design keeps restating it: the hero illustration crossfades
   through art styles, the highlighted phrase in the text page pulses, and
   content settles softly into view as you scroll. Everything here is
   progressive enhancement: scroll reveals only hide content under html.js
   (set by an inline script), and the global prefers-reduced-motion rule at
   the top of this file freezes every animation/transition below. */

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes frame-in {
  from {
    opacity: 0;
    transform: translateY(32px) scale(0.96);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes frame-in-tilt {
  from {
    opacity: 0;
    transform: rotate(-3deg) translateY(-22px);
  }
  to {
    opacity: 1;
    transform: rotate(-3deg);
  }
}

@keyframes badge-pop {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.9);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes floaty {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-7px);
  }
}

@keyframes kenburns {
  from {
    transform: scale(1);
  }
  to {
    transform: scale(1.07);
  }
}

@keyframes cta-ring {
  0% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent);
  }
  60%,
  100% {
    box-shadow: 0 0 0 16px transparent;
  }
}

@keyframes aurora {
  to {
    transform: rotate(1turn);
  }
}

@keyframes fade-up-sm {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
}

/* Hero entrance: kicker and headline rise first, the cover assembles under
   them — text page, canvas, badge — then the CTA. Runs once on load. */
.hero .eyebrow {
  animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

.hero h1 {
  animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.3, 1) 0.08s both;
}

.hero .lede {
  animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.3, 1) 0.16s both;
}

.hero-visual .frame-secondary {
  animation: frame-in-tilt 0.8s cubic-bezier(0.2, 0.7, 0.3, 1) 0.3s both;
}

.hero-visual .frame-main {
  animation: frame-in 0.9s cubic-bezier(0.2, 0.7, 0.3, 1) 0.48s both;
}

.hero-visual .badge-float {
  animation:
    badge-pop 0.6s cubic-bezier(0.2, 0.7, 0.3, 1) 1s both,
    floaty 5.5s ease-in-out 1.7s infinite;
}

/* Hold still while the wheel is open — it anchors the tag column. */
.hero-visual .badge-float[aria-expanded="true"] {
  animation: badge-pop 0.6s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

.hero .hero-cta-row {
  animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.3, 1) 0.62s both;
}

.hero .hero-note {
  animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.3, 1) 0.68s both;
}

.hero .trust-bullets {
  animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.3, 1) 0.76s both;
}


/* Hero style rotator: the same scene crossfading through art styles, with a
   slow Ken Burns drift so it feels alive rather than a slideshow. The first
   image ships with .is-active so no-JS visitors still get the full hero.
   Each incoming style resolves from a blurred, washed-out state into full
   sharpness — the visual language of an image being generated. */
.frame-main .rotator {
  position: relative;
  width: 100%;
  height: 100%;
  /* Blank canvas behind the slides: what shows before the first generation
     (JS deactivates the server-rendered image at boot; without JS it stays
     and this background is never seen). */
  background: radial-gradient(
      120% 90% at 50% 40%,
      color-mix(in srgb, var(--accent) 7%, transparent),
      transparent 70%
    ),
    var(--card);
}

/* Waiting-canvas hint: a faint dashed frame while the illustration hasn't
   been "generated" yet. Only ever visible when the choreography is running
   (JS adds .is-blank and removes it on the first generation). */
.frame-main .rotator::before {
  content: "";
  position: absolute;
  inset: 12px;
  border: 2px dashed color-mix(in srgb, var(--ink) 20%, transparent);
  border-radius: 14px;
  opacity: 0;
  transition: opacity 0.8s ease;
}

html.js .frame-main.is-blank .rotator::before {
  opacity: 1;
}

/* No style label while the canvas is still blank. */
.frame-main .frame-style-chip {
  transition: opacity 0.35s ease;
}

html.js .frame-main.is-blank .frame-style-chip {
  opacity: 0;
}

.frame-main .rotator img {
  position: absolute;
  inset: 0;
  opacity: 0;
  filter: blur(14px) saturate(0.6);
  /* Ink-bloom reveal: a soft radial mask grows from the center of the frame
     while the image sharpens, so each style visibly "paints in" after the
     sentence sweep instead of just crossfading. */
  -webkit-mask-image: radial-gradient(circle at 50% 44%, #000 58%, transparent 100%);
  mask-image: radial-gradient(circle at 50% 44%, #000 58%, transparent 100%);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: 52% 52%;
  mask-size: 52% 52%;
  transition:
    opacity 1.1s ease,
    filter 1.3s ease,
    -webkit-mask-size 1.35s cubic-bezier(0.2, 0.5, 0.4, 1),
    mask-size 1.35s cubic-bezier(0.2, 0.5, 0.4, 1);
  animation: kenburns 9s ease-in-out infinite alternate;
}

.frame-main .rotator img.is-active {
  opacity: 1;
  filter: blur(0) saturate(1);
  -webkit-mask-size: 320% 320%;
  mask-size: 320% 320%;
}

/* Pulsing dot shown while the style chip reads "Generating…" (the rotator
   swaps the label in two beats: generating -> style name; see landing.js). */
.frame-style-chip.generating::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  margin-right: 7px;
  vertical-align: 1px;
  animation: gen-pulse 0.9s ease-in-out infinite;
}

@keyframes gen-pulse {
  0%,
  100% {
    opacity: 0.35;
    transform: scale(0.8);
  }
  50% {
    opacity: 1;
    transform: scale(1.15);
  }
}

/* A soft sheen sweeps across the illustration each time the style changes,
   like light catching a fresh page. Triggered by landing.js adding .sweep. */
.frame-main::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(
    105deg,
    transparent 42%,
    rgba(255, 253, 249, 0.35) 50%,
    transparent 58%
  );
  transform: translateX(-140%);
}

@keyframes sheen {
  to {
    transform: translateX(140%);
  }
}

.frame-main.sweep::after {
  animation: sheen 0.9s ease 0.2s;
}

/* Depth parallax targets (transformed by landing.js on fine pointers only):
   the text page and the illustration drift at different rates, selling the
   two-layer "text in front, picture behind it" depth. */
.hero-visual .page-mock,
.frame-main .rotator {
  transition: transform 0.3s ease-out;
  will-change: transform;
}

.frame-style-chip {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 2;
  background: rgba(36, 29, 23, 0.72);
  color: #fdfaf4;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 4px 11px;
  border-radius: 999px;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  transition: opacity 0.35s ease;
}

/* Primary CTAs breathe a soft ring so the eye keeps returning to them. */
.btn-pulse {
  position: relative;
}

.btn-pulse::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 999px;
  pointer-events: none;
  animation: cta-ring 3s cubic-bezier(0.2, 0.6, 0.4, 1) 2.2s infinite;
}

.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg);
}

/* Scroll reveals: only ever hide content when JS is confirmed present
   (html.js), so crawlers and no-JS visitors see everything. Stagger delays
   arrive via --reveal-delay (set by landing.js and cleared after the reveal
   finishes) so pseudo-element flourishes below can sync with them. */
html.js [data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition:
    opacity 0.65s ease,
    transform 0.65s cubic-bezier(0.2, 0.7, 0.3, 1);
  transition-delay: var(--reveal-delay, 0ms);
}

html.js [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

/* Reveal flourishes: small story beats that play as each mock scrolls in —
   the step number pops, relationship edges draw themselves, character
   avatars hop into the scene one by one. */
@keyframes step-pop {
  from {
    transform: scale(0.4);
  }
}

html.js .step[data-reveal].is-visible::before {
  animation: step-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  animation-delay: calc(var(--reveal-delay, 0ms) + 150ms);
}

/* Relations graph generation sequence: nodes pop in one by one as Ridio
   "discovers" each character, every edge draws itself once both of its
   endpoints exist, and the relation label fades in as its edge completes.
   Markup order (see build-landing.mjs): edges E–M (live), E–N, M–I; nodes
   E, M, N, I. Timeline: E(250) M(550) → E–M(800) → N(850) → E–N(1100) →
   I(1150) → M–I(1400); labels trail their edges by ~450ms. Everything ends
   by ~2.3s, inside the reveal-stagger cleanup window (see landing.js). */
@keyframes node-pop {
  from {
    opacity: 0;
    transform: scale(0);
  }
  70% {
    opacity: 1;
    transform: scale(1.12);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.mock-graph-svg .node {
  transform-box: fill-box;
  transform-origin: center;
}

html.js .feature-card[data-reveal]:not(.is-visible) .mock-graph-svg .node {
  opacity: 0;
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg .node {
  animation: node-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  animation-delay: calc(var(--reveal-delay, 0ms) + 250ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg g:nth-of-type(2).node {
  animation-delay: calc(var(--reveal-delay, 0ms) + 550ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg g:nth-of-type(3).node {
  animation-delay: calc(var(--reveal-delay, 0ms) + 850ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg g:nth-of-type(4).node {
  animation-delay: calc(var(--reveal-delay, 0ms) + 1150ms);
}

.mock-graph-svg .edge {
  stroke-dasharray: 220 220;
  stroke-dashoffset: 0;
}

html.js .feature-card[data-reveal]:not(.is-visible) .mock-graph-svg .edge {
  stroke-dashoffset: 220;
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg .edge {
  transition: stroke-dashoffset 0.55s ease-out;
  transition-delay: calc(var(--reveal-delay, 0ms) + 800ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg .edge:nth-of-type(2) {
  transition-delay: calc(var(--reveal-delay, 0ms) + 1100ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg .edge:nth-of-type(3) {
  transition-delay: calc(var(--reveal-delay, 0ms) + 1400ms);
}

html.js .feature-card[data-reveal]:not(.is-visible) .mock-graph-svg .edge-label {
  opacity: 0;
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg .edge-label {
  transition: opacity 0.4s ease;
  transition-delay: calc(var(--reveal-delay, 0ms) + 1250ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg .edge-label:nth-of-type(2) {
  transition-delay: calc(var(--reveal-delay, 0ms) + 1550ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg .edge-label:nth-of-type(3) {
  transition-delay: calc(var(--reveal-delay, 0ms) + 1850ms);
}

.mock-chip {
  transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.mock-chip:nth-child(2) {
  transition-delay: 0.07s;
}

.mock-chip:nth-child(3) {
  transition-delay: 0.14s;
}

.mock-chip:nth-child(4) {
  transition-delay: 0.21s;
}

html.js .feature-card[data-reveal]:not(.is-visible) .mock-chip {
  transform: scale(0);
}

/* ── Generation simulation ──────────────────────────────────────────────
   As each mock scrolls in, its artwork doesn't just appear — it resolves
   from a blurred, washed-out state into full sharpness, the way the app
   actually materializes an illustration. Portraits land one by one after
   their colored placeholder chips; storyboard panels resolve in sequence. */
@keyframes materialize {
  0% {
    opacity: 0;
    filter: blur(16px) saturate(0.4) brightness(1.12);
    transform: scale(1.05);
  }
  35% {
    opacity: 1;
  }
  70% {
    filter: blur(4px) saturate(0.85) brightness(1.04);
  }
  100% {
    opacity: 1;
    filter: blur(0) saturate(1) brightness(1);
    transform: none;
  }
}

@keyframes portrait-in {
  from {
    opacity: 0;
    filter: blur(6px);
  }
  to {
    opacity: 1;
    filter: none;
  }
}

/* Scene, place, storyboard and recap artwork. */
html.js .feature-card[data-reveal]:not(.is-visible) .mock-media img,
html.js .feature-card[data-reveal]:not(.is-visible) .mock-panel img,
html.js .feature-card[data-reveal]:not(.is-visible) .mock-recap-figure img {
  opacity: 0;
}

html.js .feature-card[data-reveal].is-visible .mock-media img,
html.js .feature-card[data-reveal].is-visible .mock-panel img,
html.js .feature-card[data-reveal].is-visible .mock-recap-figure img {
  animation: materialize 1.3s ease both;
  animation-delay: calc(var(--reveal-delay, 0ms) + 250ms);
}

/* One storyboard panel after another, like pages being processed. */
html.js .feature-card[data-reveal].is-visible .mock-panel:nth-child(2) img {
  animation-delay: calc(var(--reveal-delay, 0ms) + 650ms);
}

html.js .feature-card[data-reveal].is-visible .mock-panel:nth-child(3) img {
  animation-delay: calc(var(--reveal-delay, 0ms) + 1050ms);
}

html.js .feature-card[data-reveal].is-visible .mock-panel:nth-child(4) img {
  animation-delay: calc(var(--reveal-delay, 0ms) + 1450ms);
}

/* Cast portraits: colored chips pop in first, then each face materializes. */
html.js .feature-card[data-reveal]:not(.is-visible) .mock-chip-row .mock-chip img {
  opacity: 0;
}

html.js .feature-card[data-reveal].is-visible .mock-chip-row .mock-chip img {
  animation: portrait-in 0.7s ease both;
  animation-delay: calc(var(--reveal-delay, 0ms) + 450ms);
}

html.js .feature-card[data-reveal].is-visible .mock-chip-row .mock-chip:nth-child(2) img {
  animation-delay: calc(var(--reveal-delay, 0ms) + 650ms);
}

html.js .feature-card[data-reveal].is-visible .mock-chip-row .mock-chip:nth-child(3) img {
  animation-delay: calc(var(--reveal-delay, 0ms) + 850ms);
}

html.js .feature-card[data-reveal].is-visible .mock-chip-row .mock-chip:nth-child(4) img {
  animation-delay: calc(var(--reveal-delay, 0ms) + 1050ms);
}

/* Relations graph: faces fade onto the colored nodes while edges draw. */
html.js .feature-card[data-reveal]:not(.is-visible) .mock-graph-svg image {
  opacity: 0;
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg image {
  animation: portrait-in 0.7s ease both;
  animation-delay: calc(var(--reveal-delay, 0ms) + 400ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg g:nth-of-type(2) image {
  animation-delay: calc(var(--reveal-delay, 0ms) + 700ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg g:nth-of-type(3) image {
  animation-delay: calc(var(--reveal-delay, 0ms) + 1000ms);
}

html.js .feature-card[data-reveal].is-visible .mock-graph-svg g:nth-of-type(4) image {
  animation-delay: calc(var(--reveal-delay, 0ms) + 1300ms);
}

/* Micro-interactions: cards lift toward the cursor. */
.feature-card,
.step,
.style-card {
  transition:
    transform 0.35s ease,
    box-shadow 0.35s ease;
}

.feature-card:hover,
.style-card:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-md);
}

.step:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-sm);
}

.faq-item[open] .faq-answer {
  animation: fade-up-sm 0.3s ease;
}

/* Early-access card: a slow warm sweep of light behind the content. */
.soon-card {
  position: relative;
  overflow: hidden;
}

.soon-card::before {
  content: "";
  position: absolute;
  inset: -45%;
  background: conic-gradient(
    from 90deg at 50% 50%,
    transparent 0deg,
    rgba(217, 130, 98, 0.28) 70deg,
    transparent 160deg,
    rgba(217, 130, 98, 0.16) 260deg,
    transparent 360deg
  );
  animation: aurora 16s linear infinite;
  pointer-events: none;
}

.soon-card > * {
  position: relative;
  z-index: 1;
}

/* Final CTA band: a soft glow that frames the closing ask. */
.final-cta {
  position: relative;
}

.final-cta::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    600px 300px at 50% 25%,
    color-mix(in srgb, var(--accent) 12%, transparent),
    transparent 70%
  );
  pointer-events: none;
}

/* ---------------------------------------------------------------------------
   3D layer (Three.js, lazy — see landing-3d.js). The [data-3d] mounts are
   empty decorative boxes until the bundle loads and appends a canvas; with
   JS off, reduced motion or no WebGL nothing here ever shows. */

[data-3d] {
  pointer-events: none;
}

[data-3d] canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Lighthouse band: the canvas is the section's full background. The section
   only turns to dusk (has-3d) once the scene is really going to render, so
   no-WebGL visitors keep the plain light band. The CSS gradient behind the
   canvas approximates the scene's sky, making the fade-in seamless. */
.final-cta .cta-3d {
  position: absolute;
  inset: 0;
  overflow: hidden;
  opacity: 0;
  transition: opacity 1.6s ease;
}

.final-cta .cta-3d.is-live {
  opacity: 1;
}

.final-cta.has-3d {
  background: linear-gradient(#10162a 0%, #33243c 58%, #4c3030 100%);
  padding: 108px 0 132px;
  overflow: hidden;
}

.final-cta.has-3d::before {
  /* Readability scrim over the scene, replacing the light-theme glow. */
  background: radial-gradient(
    640px 340px at 50% 45%,
    rgba(10, 12, 24, 0.52),
    transparent 72%
  );
  z-index: 1;
}

.final-cta.has-3d .container {
  position: relative;
  z-index: 2;
}

.final-cta.has-3d h2 {
  color: #f4ece0;
}

.final-cta.has-3d p {
  color: rgba(244, 236, 224, 0.82);
}
