/* Studio Plus — web design tokens.
   Ported from the app's design system (Sources/StudioPlus/DesignSystem/Theme.swift)
   so the site and the editor read as one product. Apple SF typography only. */

:root {
  /* Typography — Apple SF only. No web fonts, no third-party families. */
  --font-studio-display: -apple-system, BlinkMacSystemFont, "SF Pro Display",
    "SF Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-studio-text: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro",
    "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-studio-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;

  /* Canvas */
  --bg: #08080b;
  --surface: #101116;

  /* Text tiers — white at opacity, same ladder as Theme.text(_:) */
  --text-primary: rgba(255, 255, 255, 0.92);
  --text-secondary: rgba(255, 255, 255, 0.55);
  --text-tertiary: rgba(255, 255, 255, 0.4);

  /* Hairlines + glass fills */
  --stroke-soft: rgba(255, 255, 255, 0.08);
  --stroke: rgba(255, 255, 255, 0.14);
  --fill-soft: rgba(255, 255, 255, 0.05);
  --fill: rgba(255, 255, 255, 0.1);

  /* Accent — the primary-action gradient */
  --accent-a: #a855f7;
  --accent-b: #7c3aed;
  --accent-c: #db2777;
  --accent-gradient: linear-gradient(135deg, var(--accent-a), var(--accent-b), var(--accent-c));

  /* Content column — the nav row, hero text, preview panel and footer all use
     these two values, so everything shares one left/right edge. The gutter is
     fluid, so a new page never needs a per-breakpoint override to stop hugging
     a phone's edge. */
  --content-max: 1280px;
  --gutter: clamp(16px, 3.6vw, 24px);

  /* Shape language */
  --radius-control: 16px;
  --radius-card: 20px;
  --radius-panel: 34px;
  --radius-pill: 999px;

  color-scheme: dark;
}

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

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

/* An in-page link lands below the sticky bar instead of under it. */
[id] {
  scroll-margin-top: 84px;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  margin: 0;
  min-height: 100vh;
  /* Phone browsers shrink the viewport as the URL bar retracts; dvh tracks it,
     100vh does not. */
  min-height: 100dvh;
  /* Top glow, anchored to the top of the page — mirrors the app canvas.
     Scrolls with the content, so it never trails the viewport. */
  background-color: var(--bg);
  background-image: radial-gradient(
    1100px 620px at 50% -160px,
    rgba(128, 150, 210, 0.17),
    rgba(128, 150, 210, 0.05) 45%,
    transparent 100%
  );
  background-repeat: no-repeat;
  color: var(--text-primary);
  /* One SF face for all UI text, exactly like the app — every label there goes
     through Theme.Font.display. --font-studio-text/mono stay for dense copy
     and technical readouts. */
  font-family: var(--font-studio-display);
  font-size: 17px;
  line-height: 1.5;
  letter-spacing: -0.01em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* A long unbroken string (a URL, a model id) breaks rather than pushing the
     page wider than the phone. Applies to whatever a new page adds. */
  overflow-wrap: break-word;
}

/* Grid and flex children default to min-width:auto, which lets a wide child
   (a long word, a table, a code block) blow past its track and give the whole
   page a horizontal scrollbar. Opting every layout child out of that once is
   what keeps a page nobody has written yet from overflowing. */
.shell > *,
.plans > *,
.footer-top > *,
.footer-nav > * {
  min-width: 0;
}

/* Anything embedded scales down with its column instead of setting the page
   width. img is handled below; these are the other usual offenders. */
svg,
video,
iframe,
canvas,
table {
  max-width: 100%;
}

::selection {
  background: rgba(168, 85, 247, 0.35);
  color: #fff;
}

/* Two flashes a touch device paints on tap that a control should never show:
   the grey tap-highlight rectangle, and — when a second tap lands quickly, as
   it does stepping Starter → Pro → Max — the selection rectangle over the
   label. Controls are for pressing, so their text isn't selectable; body copy
   stays selectable. */
a,
button,
[role="button"] {
  -webkit-tap-highlight-color: transparent;
}

button {
  -webkit-user-select: none;
  user-select: none;
}

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

a {
  color: inherit;
  text-decoration: none;
}

:focus-visible {
  outline: 2px solid rgba(168, 85, 247, 0.75);
  outline-offset: 3px;
  border-radius: var(--radius-pill);
}

/* ---------- Layout ---------- */

/* The gutter also clears a notched phone's rounded corners in landscape, where
   the safe area is wider than the gutter. */
.shell {
  position: relative;
  z-index: 1;
  max-width: var(--content-max);
  margin: 0 auto;
  padding-left: max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
}

/* ---------- Nav ---------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 10;
  /* Top-weighted so the row rides high in the bar. */
  padding: 4px 0 12px;
}

/* Same width and gutter as .shell, so the wordmark lines up with the preview
   panel below instead of hugging the window edge. */
.nav-inner {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 20px;
  max-width: var(--content-max);
  margin: 0 auto;
  padding-left: max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
}

.nav-links {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 28px;
  font-size: 15px;
  color: var(--text-secondary);
}

.nav-links a {
  color: var(--text-secondary);
  transition: color 0.18s ease;
  white-space: nowrap;
}


/* Every hover rule on the site is scoped this way: a touch device has no
   hover, so an unscoped one sticks after a tap and leaves the control lit. */
@media (hover: hover) {
  .nav-links a:hover {
    color: var(--text-primary);
  }
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: 18px;
  justify-self: end;
}

/* Menu button — after the download pill, never before it: the download is the
   one thing the site is for, so it keeps the end of the row. Hidden until the
   links collapse into it (see Responsive). */
/* Bare glyph — no capsule, no hairline. The 34px box is hit area only, so the
   mark keeps a finger-sized target while reading as marks on the bar. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: none;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-primary);
  cursor: pointer;
  touch-action: manipulation;
  transition: opacity 0.18s ease;
}

.nav-toggle:active {
  opacity: 0.55;
}

/* Three identical bars that fold into a cross: the outer two travel to the
   middle and rotate, the middle one fades. Laid out by the flex box rather than
   by three separate offsets — whole-pixel bars in even gaps, so no bar renders
   a half-pixel lighter or shorter than its neighbours. */
.nav-toggle-bars {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 16px;
  height: 12px;
}

.nav-toggle-bars span {
  width: 100%;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1),
    opacity 0.16s ease;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(1) {
  transform: translateY(5px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(2) {
  opacity: 0;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(3) {
  transform: translateY(-5px) rotate(-45deg);
}

/* Glass layer, faded in only once the page has scrolled. Sits behind the row
   and masks off toward the bottom so the blur dissolves instead of ending on a
   hard edge. */
.nav::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(8, 8, 11, 0.55);
  backdrop-filter: blur(22px) saturate(160%);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  mask-image: linear-gradient(to bottom, #000 62%, transparent);
  -webkit-mask-image: linear-gradient(to bottom, #000 62%, transparent);
  opacity: 0;
  transition: opacity 0.28s ease;
  pointer-events: none;
}

.nav[data-scrolled="true"]::before {
  opacity: 1;
}

.wordmark {
  display: flex;
  align-items: center;
}

.wordmark img {
  height: 46px;
  width: auto;
  opacity: 0.92;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  padding: 0 20px;
  border: 0;
  border-radius: var(--radius-pill);
  font-family: var(--font-studio-display);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.01em;
  cursor: pointer;
  /* Drops the ~300ms tap delay mobile Safari holds for double-tap zoom. */
  touch-action: manipulation;
  transition: transform 0.16s ease, opacity 0.16s ease, background 0.16s ease;
}

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

/* Mirrors the app's primary "Start new project" pill (StartNewButton in
   Screens/LaunchScreen.swift): solid white, canvas-dark label. Flat on purpose —
   no white glow behind it. Kept slimmer than the app's 12pt vertical padding. */
.btn-app {
  height: 34px;
  padding: 0 20px;
  gap: 7px;
  background: #fff;
  color: var(--bg);
  font-size: 14px;
  font-weight: 600;
}

/* Drawn rather than the U+F8FF Apple glyph — that character is private-use and
   renders as tofu off Apple platforms. Nudged up to sit optically centred. */
.btn-glyph {
  width: 15px;
  height: 15px;
  flex: none;
  transform: translateY(-1px);
}

/* No hover scale — a growing pill reads as bouncy at this size. A faint dim
   carries the state instead. */
@media (hover: hover) {
  .btn-app:hover {
    background: #f1f1f3;
  }
}

/* ---------- Hero ---------- */

.hero {
  padding: 52px 0 44px;
  text-align: left;
}

.hero h1 {
  margin: 0;
  font-family: var(--font-studio-display);
  font-size: clamp(40px, 6.4vw, 68px);
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -0.035em;
  color: var(--text-primary);
}

.hero-sub {
  margin: 16px 0 0;
  font-family: var(--font-studio-text);
  font-size: clamp(15px, 1.7vw, 17px);
  line-height: 1.5;
  color: var(--text-secondary);
}

/* Phones lead with the headline and the video alone (owner, 2026-08-02);
   the long subline is desktop copy. Same breakpoint as the hero's type
   ramp. */
@media (max-width: 640px) {
  .hero-sub {
    display: none;
  }
}


/* ---------- Page head (inner pages) ---------- */

.page-head {
  padding: 52px 0 40px;
}

.page-head h1 {
  margin: 0;
  font-size: clamp(36px, 4.6vw, 52px);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: var(--text-primary);
}

.page-head p {
  margin: 16px 0 0;
  max-width: 560px;
  font-size: 16px;
  color: var(--text-secondary);
}

/* ---------- Pricing ---------- */

.plans {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  align-items: stretch;
}

/* Card chrome ported from SettingsScreen's planBoxSurface: #1B1E27 with a
   blue wash falling off by the middle, on a hairline. */
.plan {
  display: flex;
  flex-direction: column;
  padding: 22px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 18px;
  background: linear-gradient(
      to bottom,
      rgba(128, 150, 210, 0.11),
      transparent 50%
    ),
    #1b1e27;
}

.plan-featured {
  border-color: rgba(255, 255, 255, 0.2);
}

.plan-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--text-primary);
}

/* The saving badge, matching planBoxTitle in Screens/SettingsScreen.swift:
   the same green as a refund, on a 12% wash of itself. */
.plan-badge {
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: rgba(74, 204, 120, 0.12);
  font-size: 12px;
  font-weight: 600;
  color: #4acc78;
}

/* The app gives it .transition(.opacity) with an 0.18s ease-out. */
.plan-badge.value-in {
  animation-duration: 0.18s;
}

.plan-sub {
  margin: 4px 0 0;
  font-size: 13px;
  color: var(--text-tertiary);
}

.plan-price {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 18px 0 0;
}

.plan-price span {
  font-size: 34px;
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--text-primary);
}

.plan-price em {
  font-style: normal;
  font-size: 14px;
  color: var(--text-tertiary);
}

/* Matches creditLine in SettingsScreen.swift — 18pt semibold at 0.88. */
.plan-credits {
  margin: 8px 0 0;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: rgba(255, 255, 255, 0.88);
}

.plan-tagline {
  margin: 5px 0 0;
  font-size: 13.5px;
  color: rgba(255, 255, 255, 0.55);
}

.plan-rule {
  margin: 20px 0;
  border: 0;
  border-top: 1px solid var(--stroke-soft);
}

.plan-perks {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: 14px;
  color: var(--text-secondary);
}

.plan-perks li {
  display: flex;
  align-items: flex-start;
  gap: 9px;
}

.plan-perks li::before {
  content: "";
  flex: none;
  width: 15px;
  height: 15px;
  margin-top: 3px;
  background-color: #8096d2;
  mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M3 8.4l3.3 3.3L13 5" fill="none" stroke="black" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg>')
    no-repeat center / contain;
  -webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M3 8.4l3.3 3.3L13 5" fill="none" stroke="black" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/></svg>')
    no-repeat center / contain;
}

/* margin-top:auto pushes every card's button to the same baseline regardless of
   how long its perk list is; the list's own margin keeps a minimum gap. */
.plan-perks {
  margin-bottom: 24px;
}

.btn-plan {
  width: 100%;
  height: 40px;
  margin-top: auto;
}

/* Where a card has no button: the same bottom baseline as .btn-plan, but stated
   quietly — it points at the app, it isn't a control. */
.plan-note {
  margin: auto 0 0;
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 40px;
  font-size: 13.5px;
  color: rgba(255, 255, 255, 0.5);
}

/* The Apple mark, so the line reads as "in the Mac app" at a glance. */
.plan-note::before {
  content: "";
  flex: none;
  width: 13px;
  height: 13px;
  background-color: currentColor;
  mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"/></svg>')
    no-repeat center / contain;
  -webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"/></svg>')
    no-repeat center / contain;
}

.btn-filled {
  background: #fff;
  color: var(--bg);
  font-weight: 600;
}

.btn-glass {
  background: var(--fill-soft);
  border: 1px solid var(--stroke-soft);
  color: var(--text-primary);
}

@media (hover: hover) {
  .btn-filled:hover {
    background: #f1f1f3;
  }

  .btn-glass:hover {
    background: var(--fill);
    border-color: var(--stroke);
  }
}

/* Tier segmented control — one pill that slides between segments rather than a
   background switching on and off. Mirrors PlanTierSwitch in
   Screens/SettingsScreen.swift: capsules throughout, and a glide that settles
   without overshooting (the app's interpolatingSpring, stiffness 220 /
   damping 30, is critically damped and lands in a little under half a second). */
.tier-toggle {
  position: relative;
  display: flex;
  margin-top: 14px;
  padding: 3px;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-pill);
  background: var(--fill-soft);
}

.tier-thumb {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc((100% - 6px) / 3);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.13);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12),
    0 1px 4px rgba(0, 0, 0, 0.32);
  transform: translateX(calc(var(--tier-index, 0) * 100%));
  transition: transform 0.44s cubic-bezier(0.32, 0.72, 0, 1);
}

.tier-toggle:active .tier-thumb {
  background: rgba(255, 255, 255, 0.16);
}

.tier-toggle button {
  position: relative;
  z-index: 1;
  flex: 1;
  /* 32 + 3px padding either side + the hairline = the 40px of the credits field
     opposite it, so both cards' prices sit on one line. */
  height: 32px;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
  touch-action: manipulation;
  transition: color 0.16s ease-in-out;
}

@media (hover: hover) {
  .tier-toggle button:hover {
    color: var(--text-primary);
  }
}

.tier-toggle button[aria-pressed="true"] {
  color: var(--text-primary);
  font-weight: 600;
}

/* Text that swaps outright fades rather than snapping. Numbers don't use this —
   they count from the old value to the new one in app.js. */
@keyframes valueIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.value-in {
  animation: valueIn 0.24s ease-out;
}

/* Top-up field + slider */
.topup {
  margin-top: 14px;
}

/* The one thing on this page you actually type into, so it sits a little larger
   than a plain field. */
.topup-field {
  width: 100%;
  height: 40px;
  padding: 0 13px;
  border: 1px solid var(--stroke-soft);
  border-radius: 12px;
  background: var(--surface);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 15.5px;
}

.topup-field:focus {
  outline: none;
  border-color: rgba(128, 150, 210, 0.7);
}

/* Tinted fill up to the thumb, like CreditSlider in the app. --pct is set by
   app.js on every input. */
.topup-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  margin-top: 18px;
  border-radius: var(--radius-pill);
  background: linear-gradient(
    to right,
    #8096d2 0%,
    #8096d2 var(--pct, 0%),
    rgba(255, 255, 255, 0.14) var(--pct, 0%),
    rgba(255, 255, 255, 0.14) 100%
  );
  cursor: pointer;
}

.topup-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 0;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
  transition: transform 0.18s cubic-bezier(0.32, 0.72, 0, 1),
    box-shadow 0.18s ease;
}

@media (hover: hover) {
  .topup-slider:hover::-webkit-slider-thumb {
    transform: scale(1.12);
  }
}

.topup-slider:active::-webkit-slider-thumb {
  transform: scale(1.22);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45), 0 0 0 6px rgba(128, 150, 210, 0.22);
}

.topup-slider::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 0;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
  transition: transform 0.18s cubic-bezier(0.32, 0.72, 0, 1);
}

.topup-slider:active::-moz-range-thumb {
  transform: scale(1.22);
}

.topup-slider:focus-visible {
  outline: none;
}

.topup-slider:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45), 0 0 0 5px rgba(128, 150, 210, 0.35);
}

.plan-legend {
  margin: 22px 0 96px;
  padding: 0 4px;
}

.plan-legend p {
  margin: 0;
  max-width: 780px;
  font-size: 14px;
  color: var(--text-tertiary);
}

.plan-legend-fine {
  margin-top: 4px !important;
  font-size: 13px !important;
  color: rgba(255, 255, 255, 0.32) !important;
}

.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;
}

/* ---------- Tutorials ---------- */

.tutorials {
  padding-bottom: 96px;
}

/* Search field. Held to a readable measure rather than the content column: a
   single-line field spanning 1280px looks like a mistake, and nobody types a
   query that long. */
.search {
  position: relative;
  max-width: 420px;
  margin-bottom: 34px;
}

.search-glyph {
  position: absolute;
  top: 50%;
  left: 14px;
  width: 17px;
  height: 17px;
  transform: translateY(-50%);
  fill: none;
  stroke: var(--text-tertiary);
  stroke-width: 1.8;
  pointer-events: none;
  transition: stroke 0.16s ease;
}

.search:focus-within .search-glyph {
  stroke: var(--text-secondary);
}

/* Same field chrome as the pricing top-up box — a stable --surface base, never
   translucent material, so the value never sits on whatever is behind it.
   The right padding clears the shortcut chip, and stays clear on touch where
   the chip is absent so the field does not change shape between devices. */
.search-field {
  width: 100%;
  height: 40px;
  padding: 0 52px 0 40px;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 15.5px;
  letter-spacing: -0.01em;
  -webkit-appearance: none;
  appearance: none;
}

/* Quieter than an entered value, but still legible — never the disabled tier. */
.search-field::placeholder {
  color: var(--text-tertiary);
}

.search-field:focus {
  outline: none;
  border-color: rgba(128, 150, 210, 0.7);
}

/* Safari and Chrome draw their own clear button; it is the right control, just
   too bright for this chrome by default. Stroked to match the magnifier — and
   stroked is also what makes it legible at all: a mask reads the artwork's
   ALPHA, so a filled disc with a white cross painted on it is opaque
   throughout and stamps out as a solid dot. */
.search-field::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  background: var(--text-tertiary);
  mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="black" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M5.9 5.9l4.2 4.2M10.1 5.9l-4.2 4.2" stroke-linecap="round"/></svg>')
    no-repeat center / contain;
  -webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="black" stroke-width="1.5"><circle cx="8" cy="8" r="6.5"/><path d="M5.9 5.9l4.2 4.2M10.1 5.9l-4.2 4.2" stroke-linecap="round"/></svg>')
    no-repeat center / contain;
  cursor: pointer;
}

@media (hover: hover) {
  .search-field::-webkit-search-cancel-button:hover {
    background: var(--text-secondary);
  }
}

/* The ⌘K chip. Quiet, and only ever a hint — the shortcut works whether or not
   this is on screen. It steps aside the moment the field is in use, so it can
   never sit under a typed value or collide with the clear button. */
.search-hint {
  position: absolute;
  top: 50%;
  right: 10px;
  padding: 2px 7px;
  border: 1px solid var(--stroke-soft);
  border-radius: 7px;
  background: var(--fill-soft);
  font-family: inherit;
  font-size: 11.5px;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--text-tertiary);
  transform: translateY(-50%);
  pointer-events: none;
  transition: opacity 0.16s ease;
}

.search:focus-within .search-hint,
.search[data-filled="true"] .search-hint {
  opacity: 0;
}

/* Nothing to press on a touch device, so the hint would be a lie. */
@media (pointer: coarse) {
  .search-hint {
    display: none;
  }
}

/* The list. No dividers and no numbering: rows are told apart by their
   thumbnails, and an unnumbered list can be reordered — newest first — without
   any label going stale. */
.tutorial-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 780px;
}

/* [hidden] is how the search filter removes a row, so nothing here may set a
   display that would outrank it. */
.tutorial[hidden] {
  display: none;
}

/* Titles start at the TOP of their thumbnail, not centred on it: with rows of
   one, two and three lines in one list, centring makes every title start at a
   different height and the column stops reading as a column. */
.tutorial-link {
  display: flex;
  align-items: flex-start;
  gap: 18px;
  padding: 10px;
  border-radius: var(--radius-card);
  transition: background 0.18s ease;
}

@media (hover: hover) {
  .tutorial-link:hover {
    background: var(--fill-soft);
  }
}

.tutorial-link:active {
  background: var(--fill-soft);
}

/* Fixed 16:9 box, so a row keeps its height whether the image is loaded, still
   loading, or missing — the list never reflows under the pointer. */
.tutorial-thumb {
  position: relative;
  flex: none;
  width: 264px;
  aspect-ratio: 16 / 9;
  border-radius: 14px;
  border: 1px solid var(--stroke-soft);
  background: var(--surface);
  overflow: hidden;
}

/* <picture> is only a wrapper for the format choice; it must not become a box
   of its own between the thumbnail and the image it sizes. */
.tutorial-thumb picture {
  display: contents;
}

/* YouTube's own frame, untouched — no tint, no crop beyond the 16:9 box it is
   already in. */
.tutorial-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Our play mark, not YouTube's red one: the thumbnail is theirs, the chrome
   around it is ours — so it is the site's liquid glass, the same language as
   the nav bar and the app's panels.

   What makes it read as glass is the BLUR AND THE LIT EDGE, not a weak tint:
   a top-down white sheen for the specular curve, an inset top hairline for the
   lit rim, and a blurred, saturated backdrop showing the frame through it.
   The tint stays at the nav bar's own strength on purpose — this disc sits on
   a picture we do not control, and its triangle is white, so a lighter glass
   loses the mark completely over a bright shot (checked against a pure white
   frame: at 0.34 the triangle is barely there, at 0.50 it holds). Darkening
   costs nothing, because the blur is doing the work. */
.tutorial-play {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.26);
  background: linear-gradient(
      to bottom,
      rgba(255, 255, 255, 0.18),
      rgba(255, 255, 255, 0.04)
    ),
    rgba(8, 8, 11, 0.5);
  backdrop-filter: blur(18px) saturate(170%);
  -webkit-backdrop-filter: blur(18px) saturate(170%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24),
    0 6px 18px -6px rgba(0, 0, 0, 0.65);
  transform: translate(-50%, -50%);
  transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1),
    background 0.18s ease, border-color 0.18s ease;
}

/* No blur support: the sheen over a half-transparent tint is a flat wash with
   nothing to justify it, so drop to a plain dark disc. Glass degrades to
   something deliberate rather than to a smear. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tutorial-play {
    background: rgba(8, 8, 11, 0.62);
  }
}

.tutorial-play svg {
  width: 18px;
  height: 18px;
  /* Optical centring: a triangle's mass sits left of its bounding box. */
  margin-left: 2px;
  fill: #fff;
  /* Holds the mark against whatever the frame is doing underneath it. */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
}

@media (hover: hover) {
  /* Glass catches more light on hover — it does NOT darken. Dimming a pane of
     glass reads as switching it off, which is the opposite of the affordance. */
  .tutorial-link:hover .tutorial-play {
    background: linear-gradient(
        to bottom,
        rgba(255, 255, 255, 0.28),
        rgba(255, 255, 255, 0.09)
      ),
      rgba(8, 8, 11, 0.5);
    border-color: rgba(255, 255, 255, 0.4);
    transform: translate(-50%, -50%) scale(1.08);
  }
}

/* Pulled up by the first line's half-leading, so what lines up with the top of
   the thumbnail is the top of the LETTERS — the line box above them is empty
   space, and aligning that leaves the title visibly sitting low. Derived from
   the line height in em rather than typed as a pixel value, so every size below
   stays aligned without its own correction. */
.tutorial-title {
  --title-leading: 1.35;
  margin-top: calc((1em - 1em * var(--title-leading)) / 2);
  font-size: 19px;
  font-weight: 500;
  line-height: var(--title-leading);
  letter-spacing: -0.015em;
  color: var(--text-primary);
}

/* Matched words inside a title. Tinted rather than boxed — the same accent as
   a text selection, so a hit reads as "this part" without turning the row into
   a highlighter drawing. Colour is never the only cue: the row is there
   because it matched, the mark only says where. */
.tutorial-title mark {
  padding: 0 1px;
  margin: 0 -1px;
  border-radius: 4px;
  background: rgba(168, 85, 247, 0.32);
  color: inherit;
}

.tutorial-empty {
  margin: 0;
  padding: 6px 10px;
  font-size: 15px;
  color: var(--text-tertiary);
}

/* ---------- Preview panel ---------- */

.preview {
  position: relative;
  /* No top margin — the hero's padding-bottom is the whole gap. */
  margin: 0 0 96px;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-panel);
  background: linear-gradient(180deg, var(--fill-soft), rgba(16, 17, 22, 0.6));
  backdrop-filter: blur(30px);
  -webkit-backdrop-filter: blur(30px);
  box-shadow: 0 40px 90px -40px rgba(0, 0, 0, 0.9);
  overflow: hidden;
}

/* 16:9 is the video's own shape; the ratio holds the panel's height before a
   single byte of media arrives, so the page never reflows around it. */
.preview-frame {
  position: relative;
  aspect-ratio: 16 / 9;
}

.preview-frame img,
.preview-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Same liquid glass as the tutorials play badge: smoked tint under a top-down
   sheen, blur doing the glass work, inset hairline for the lit rim. The discs
   sit on our own footage here, but the footage has white-card moments, so the
   0.5 tint is still what keeps the white glyph readable. */
.preview-control {
  position: absolute;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.26);
  background: linear-gradient(
      to bottom,
      rgba(255, 255, 255, 0.18),
      rgba(255, 255, 255, 0.04)
    ),
    rgba(8, 8, 11, 0.5);
  backdrop-filter: blur(18px) saturate(170%);
  -webkit-backdrop-filter: blur(18px) saturate(170%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24),
    0 6px 18px -6px rgba(0, 0, 0, 0.65);
  cursor: pointer;
  padding: 0;
  transition: background 0.18s ease, border-color 0.18s ease;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .preview-control {
    background: rgba(8, 8, 11, 0.62);
  }
}

@media (hover: hover) {
  /* Hover brightens the sheen — glass catches light, it never switches off. */
  .preview-control:hover {
    background: linear-gradient(
        to bottom,
        rgba(255, 255, 255, 0.28),
        rgba(255, 255, 255, 0.08)
      ),
      rgba(8, 8, 11, 0.5);
    border-color: rgba(255, 255, 255, 0.36);
  }
}

.preview-control svg {
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
}

/* Sound lives top-right, replay bottom-right — one disc per corner. */
.preview-replay {
  right: 14px;
  bottom: 14px;
  width: 40px;
  height: 40px;
}

.preview-replay svg {
  width: 18px;
  height: 18px;
  fill: #fff;
}

.preview-sound {
  right: 14px;
  top: 14px;
  width: 40px;
  height: 40px;
}

.preview-sound svg {
  width: 19px;
  height: 19px;
}

.preview-sound .sound-body {
  fill: #fff;
}

.preview-sound .sound-waves path,
.preview-sound .sound-slash {
  fill: none;
  stroke: #fff;
  stroke-width: 1.7;
  stroke-linecap: round;
}

/* One icon, two states: waves while sound is on, a cross while muted. The
   state rides on the frame so the JS flips a single attribute. */
[data-muted="true"] .sound-waves {
  display: none;
}

[data-muted="false"] .sound-slash {
  display: none;
}

/* ---------- Footer ---------- */

.footer {
  padding: 56px 0 40px;
  border-top: 1px solid var(--stroke-soft);
}

.footer-top {
  display: grid;
  grid-template-columns: 1.6fr 3fr;
  gap: 48px;
}

.footer-brand img {
  height: 40px;
  width: auto;
  opacity: 0.85;
}

.footer-brand p {
  margin: 14px 0 0;
  max-width: 240px;
  font-size: 14px;
  color: var(--text-tertiary);
}

/* Bare glyphs — no enclosing circle. */
.footer-social {
  display: flex;
  gap: 20px;
  margin: 26px 0 0;
  padding: 0;
  list-style: none;
}

.footer-social a {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--text-tertiary);
  transition: color 0.18s ease;
}

@media (hover: hover) {
  .footer-social a:hover {
    color: var(--text-primary);
  }
}

.footer-social svg {
  width: 19px;
  height: 19px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
}

/* X is a solid mark, so it needs trimming to sit level with the stroked ones. */
.footer-social svg:has(.solid:only-child) {
  width: 16px;
  height: 16px;
}

/* Filled details inside an otherwise stroked mark — the Instagram dot, the
   YouTube play triangle, the LinkedIn cap. */
.footer-social svg .solid {
  fill: currentColor;
  stroke: none;
}

.footer-nav {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
}

.footer-col h2 {
  margin: 0 0 14px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--text-primary);
}

.footer-col ul,
.footer-legal {
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-col li + li {
  margin-top: 10px;
}

.footer a {
  font-size: 14px;
  color: var(--text-tertiary);
  transition: color 0.18s ease;
}

@media (hover: hover) {
  .footer a:hover {
    color: var(--text-primary);
  }
}

.footer-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 56px;
  padding-top: 24px;
  border-top: 1px solid var(--stroke-soft);
  font-size: 13px;
  color: var(--text-tertiary);
}

.footer-bottom small {
  font-size: 13px;
}

.footer-legal {
  display: flex;
  gap: 24px;
}

.footer-legal a {
  font-size: 13px;
}

/* ---------- Responsive ---------- */

/* Three plan cards stop being readable well before phone width, so they step
   down twice: two-up on a tablet, then one column with a readable measure. */
@media (max-width: 900px) {
  .plans {
    grid-template-columns: repeat(2, 1fr);
  }

  /* An odd last card takes the whole row rather than leaving a hole beside it.
     Written off :last-child, so it holds however many cards a page lists. */
  .plans > :last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

/* The band just above the collapse. Five links plus the full download pill do
   not fit a ~800px window — the pill wrapped its label onto a second line — so
   the row tightens and the button drops its platform suffix, exactly the trade
   the phone header already makes below. Keep this in step with the link count:
   a sixth link needs this band widened, not a narrower gap. */
@media (min-width: 781px) and (max-width: 940px) {
  .nav-links {
    gap: 18px;
  }

  .btn-long {
    display: none;
  }
}

/* Phones and small tablets: the three links collapse behind the menu button.
   Everything here is keyed off the header markup itself, so a new page that
   copies the header collapses identically with no page-specific CSS. Keep this
   width in step with the matching query in app.js. */
@media (max-width: 780px) {
  .nav {
    padding: 6px 0 10px;
  }

  /* Two columns now: wordmark, then Download + menu button. */
  .nav-inner {
    grid-template-columns: 1fr auto;
  }

  .nav-actions {
    gap: 10px;
  }

  .nav-toggle {
    display: inline-flex;
  }

  /* The same three links, re-laid as the dropped panel — one list to keep in
     step rather than a duplicate mobile menu. */
  .nav-links {
    position: absolute;
    top: calc(100% + 8px);
    left: max(var(--gutter), env(safe-area-inset-left));
    right: max(var(--gutter), env(safe-area-inset-right));
    z-index: 20;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 2px;
    padding: 8px;
    border: 1px solid var(--stroke-soft);
    border-radius: var(--radius-control);
    background: rgba(16, 17, 22, 0.95);
    backdrop-filter: blur(24px) saturate(160%);
    -webkit-backdrop-filter: blur(24px) saturate(160%);
    box-shadow: 0 26px 60px -24px rgba(0, 0, 0, 0.9);
    font-size: 16px;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px) scale(0.98);
    transform-origin: top right;
    pointer-events: none;
    transition: opacity 0.2s ease,
      transform 0.26s cubic-bezier(0.32, 0.72, 0, 1),
      visibility 0s linear 0.26s;
  }

  .nav[data-menu="open"] .nav-links {
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
    transition: opacity 0.2s ease,
      transform 0.26s cubic-bezier(0.32, 0.72, 0, 1), visibility 0s;
  }

  /* Full-width rows at a comfortable tap height. */
  .nav-links a {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0 12px;
    border-radius: 11px;
    color: var(--text-primary);
  }


  .nav-links a:active {
    background: var(--fill-soft);
  }

  .nav-links a[aria-current="page"] {
    background: var(--fill-soft);
  }

  /* Apple mark plus "Download" only — the platform is obvious on a page this
     narrow, and the short label keeps the row from crowding the wordmark. */
  .btn-long {
    display: none;
  }

  .wordmark img {
    height: 38px;
  }

  .hero {
    padding: 40px 0 34px;
  }


  .page-head {
    padding: 40px 0 32px;
  }

  .preview {
    border-radius: var(--radius-card);
    margin-bottom: 64px;
  }

  .plan-legend {
    margin-bottom: 64px;
  }

  .tutorials {
    padding-bottom: 64px;
  }
}

@media (max-width: 640px) {
  /* The headline keeps its hand-placed break at every width — the type shrinks
     to hold "Create and edit videos" on one line rather than rewrapping. The
     ramp continues the wide clamp (both land on 40px around 640px) and is sized
     to leave ~15% slack on the longest line, so it still holds where SF isn't
     available and the fallback face sets wider. */
  .hero h1 {
    font-size: clamp(26px, 9.2vw - 3px, 40px);
  }

  /* One column, held to a readable measure and centred in what's left. */
  .plans {
    grid-template-columns: 1fr;
    max-width: 460px;
    margin-inline: auto;
  }

  .footer-col li + li {
    margin-top: 4px;
  }

  .footer-col a {
    display: inline-block;
    padding: 5px 0;
  }

  /* Brand stacks above the link columns, which pair up two-by-two. */
  .footer-top {
    grid-template-columns: 1fr;
    gap: 36px;
  }

  .footer-nav {
    grid-template-columns: repeat(2, 1fr);
    gap: 28px 20px;
  }

  .footer-bottom {
    margin-top: 40px;
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
  }

  /* The row stays side by side at every width — that IS the shape of the list.
     Only the thumbnail shrinks, so the title always has a column to wrap in. */
  .search {
    max-width: none;
  }

  .tutorial-link {
    gap: 14px;
    padding: 8px;
  }

  .tutorial-thumb {
    width: 168px;
    border-radius: 11px;
  }

  .tutorial-play {
    width: 34px;
    height: 34px;
  }

  .tutorial-play svg {
    width: 15px;
    height: 15px;
  }

  .tutorial-title {
    font-size: 15.5px;
  }
}

/* Small phones (iPhone SE / mini / 13 mini). */
@media (max-width: 390px) {
  .wordmark img {
    height: 32px;
  }

  .btn-app {
    padding: 0 14px;
  }

  .plan {
    padding: 18px;
  }

  .footer-legal {
    gap: 18px;
  }

  .tutorial-thumb {
    width: 138px;
  }

  .tutorial-title {
    font-size: 15px;
  }
}

/* Touch input, whatever the width: controls sized for a fingertip rather than a
   cursor. */
@media (pointer: coarse) {
  .tier-toggle button {
    height: 34px;
  }

  /* 16px is the threshold below which iOS Safari zooms the page in on focus. */
  .topup-field {
    height: 40px;
    font-size: 16px;
  }

  .topup-slider {
    height: 6px;
  }

  .topup-slider::-webkit-slider-thumb {
    width: 22px;
    height: 22px;
  }

  .topup-slider::-moz-range-thumb {
    width: 22px;
    height: 22px;
  }

  /* 16px is the threshold below which iOS Safari zooms the page in on focus. */
  .search-field {
    font-size: 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    animation: none !important;
  }
}

/* ---------- Legal pages (refunds, and anything like it) ---------- */

.legal {
  max-width: 680px;
  padding-bottom: 96px;
}

.legal h2 {
  margin: 40px 0 12px;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.legal h2:first-child {
  margin-top: 8px;
}

.legal p {
  margin: 0 0 14px;
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--text-secondary);
}

.legal ul {
  margin: 0 0 14px;
  padding-left: 0;
  list-style: none;
}

.legal li {
  position: relative;
  margin-bottom: 8px;
  padding-left: 22px;
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--text-secondary);
}

/* Same checked-off rhythm as the plan cards, in the accent blue. */
.legal li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 0.62em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #8096d2;
}

.legal a {
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(255, 255, 255, 0.3);
}

.legal a:hover {
  text-decoration-color: rgba(255, 255, 255, 0.7);
}

.legal strong {
  color: var(--text-primary);
  font-weight: 600;
}

.legal-updated {
  margin-top: 36px;
  padding-top: 18px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 13.5px;
  color: var(--text-tertiary, rgba(255, 255, 255, 0.45));
}

/* ---------- Coming-soon toast ---------- */

/* Raised by app.js when a dead-end link (Originals) is clicked: a small
   liquid-glass card under the nav, auto-dismissing, click to dismiss early.
   Same glass recipe as the nav bar. */
.toast {
  position: fixed;
  top: 60px;
  left: 50%;
  z-index: 40;
  display: flex;
  gap: 12px;
  width: min(380px, calc(100vw - 32px));
  padding: 14px 38px 14px 16px;
  border: 1px solid var(--stroke);
  border-radius: 16px;
  background: rgba(16, 17, 22, 0.72);
  backdrop-filter: blur(24px) saturate(1.4);
  -webkit-backdrop-filter: blur(24px) saturate(1.4);
  box-shadow: 0 24px 60px -24px rgba(0, 0, 0, 0.85);
  opacity: 0;
  transform: translate(-50%, -8px);
  transition: opacity 0.3s ease, transform 0.3s ease;
  pointer-events: none;
  cursor: pointer;
}

.toast[data-open="true"] {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
}

.toast svg {
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  color: var(--accent-a);
}

.toast-title {
  margin: 0;
  font-family: var(--font-studio-display);
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
}

.toast-body {
  margin: 3px 0 0;
  font-family: var(--font-studio-text);
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-secondary);
}

.toast-close {
  position: absolute;
  top: 8px;
  right: 8px;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

@media (hover: hover) {
  .toast-close:hover {
    color: var(--text-primary);
    background: var(--fill-soft);
  }
}

.toast-close svg {
  width: 12px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

/* ---------- Homepage story sections ---------- */

/* Text-only feature story below the launch video. Entrance motion is
   scroll-driven CSS only — no JS, no libraries. Everything animated is gated
   behind @supports (animation-timeline: view()) + prefers-reduced-motion, so
   a browser without either simply paints the finished state. */

.eyebrow {
  margin: 0 0 14px;
  font-family: var(--font-studio-text);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.statement {
  position: relative;
  padding: 20px 0 clamp(64px, 12vw, 120px);
  max-width: 1080px;
}

/* One soft accent glow behind the statement — hero and CTA only carry glows,
   never every card (effect-stacking reads as template, restraint as premium). */
.statement::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: -140px;
  left: -12%;
  width: 70%;
  height: 420px;
  background: radial-gradient(
    closest-side,
    rgba(124, 58, 237, 0.14),
    transparent 70%
  );
  filter: blur(60px);
  pointer-events: none;
}

.statement-text {
  margin: 0;
  font-family: var(--font-studio-display);
  font-size: clamp(27px, 4.1vw, 48px);
  font-weight: 600;
  line-height: 1.16;
  letter-spacing: -0.03em;
  color: var(--text-primary);
}

.feature {
  padding: 0 0 clamp(64px, 12vw, 110px);
  max-width: 1080px;
}

/* A feature section that owns the element right below it (the Generate
   section and its model strip) keeps a related-content gap, not a full
   section break. */
.feature-tight {
  padding-bottom: clamp(28px, 5vw, 40px);
}

.feature h2,
.trio h2,
.cta h2,
.faq h2 {
  margin: 0;
  font-family: var(--font-studio-display);
  font-size: clamp(32px, 4.4vw, 48px);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: var(--text-primary);
}

.feature-copy {
  margin: 16px 0 0;
  /* Wide enough that the Production Bible paragraph sits on two lines at
     full width (owner request); narrower windows wrap naturally. */
  max-width: 1040px;
  font-family: var(--font-studio-text);
  font-size: clamp(16px, 2vw, 19px);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* ---------- Model marquee ---------- */

/* Contained in the content column with faded edges — never full-bleed, so it
   can't hand the page a horizontal scrollbar. The gap lives on the <li> (not
   the flex containers) so the -50% loop point lands exactly between pills. */
.models {
  /* Short gap: the media tiles below are part of the same Generate cluster;
     the full section break lives on .media-cards. */
  padding: 0 0 clamp(28px, 5vw, 44px);
  overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent,
    #000 12%,
    #000 88%,
    transparent
  );
}

.models-track {
  display: flex;
  width: max-content;
  animation: models-scroll 60s linear infinite;
}

.models-row {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}

.models-row li {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-right: 10px;
  padding: 9px 18px 9px 14px;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-pill);
  background: var(--fill-soft);
  font-family: var(--font-studio-text);
  font-size: 15px;
  white-space: nowrap;
  color: var(--text-secondary);
}

/* Real provider marks (lobehub icon set, committed under assets/model-icons).
   The monochrome ones carry a baked-in near-white fill because an <img> can't
   inherit currentColor. */
.models-row li img {
  width: 16px;
  height: 16px;
  flex: none;
}

@keyframes models-scroll {
  to {
    transform: translateX(-50%);
  }
}

/* Deliberately no pause-on-hover: the strip runs continuously (owner
   2026-08-02). Reduced motion below remains the one thing that stops it. */

/* Reduced motion: the copy row disappears and the real row wraps in place —
   every name still readable, nothing moves. */
@media (prefers-reduced-motion: reduce) {
  .models {
    mask-image: none;
    -webkit-mask-image: none;
  }

  .models-track {
    animation: none;
    width: auto;
  }

  .models-row {
    flex-wrap: wrap;
    row-gap: 10px;
  }

  .models-row[aria-hidden="true"] {
    display: none;
  }
}

/* ---------- Generation media tiles ---------- */

/* Artlist-style stages for the three generation kinds. Each card is a frame
   for owner-provided media (image / looping video / audio treatment) layered
   behind the label; until those land, the bare glass IS the design — never
   stand-in artwork. The label sits bottom-left like a film slate. */
.media-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  padding: 0 0 clamp(64px, 12vw, 110px);
}


.media-card {
  position: relative;
  display: flex;
  align-items: flex-end;
  aspect-ratio: 1 / 1;
  padding: 22px 24px;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-card);
  background: linear-gradient(180deg, var(--fill-soft), rgba(16, 17, 22, 0.7));
  overflow: hidden;
  transition: transform 0.22s ease, border-color 0.22s ease;
}

@media (hover: hover) {
  .media-card:hover {
    transform: translateY(-3px);
    border-color: var(--stroke);
  }
}

/* The media layer fills the card behind the label. Only .media-fill — the
   player's album art is an <img> too and must NOT be stretched. */
.media-fill {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Legibility scrim under the label, over the footage. */
.media-scrim {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 46%;
  background: linear-gradient(180deg, transparent, rgba(5, 5, 8, 0.78));
  pointer-events: none;
}

.media-meta {
  position: relative;
  width: 100%;
}

/* Smaller sibling of the hero's corner disc, top-right of the video card —
   same smoked glass, same speaker glyph states driven by data-muted. */
.media-sound {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.26);
  border-radius: 50%;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.22),
    rgba(255, 255, 255, 0.05) 55%,
    rgba(0, 0, 0, 0) 100%
  ),
  rgba(8, 8, 11, 0.5);
  color: #fff;
  cursor: pointer;
  transition: border-color 0.18s ease, transform 0.18s ease;
}

@media (hover: hover) {
  .media-sound:hover {
    border-color: rgba(255, 255, 255, 0.45);
  }
}

.media-sound:active {
  transform: scale(0.95);
}

.media-sound svg {
  width: 19px;
  height: 19px;
}

.media-sound .sound-body {
  fill: currentColor;
}

.media-sound .sound-waves path,
.media-sound .sound-slash {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
}

.media-sound[data-muted="false"] .sound-slash { display: none; }
.media-sound[data-muted="true"] .sound-waves { display: none; }

.media-card h3 {
  margin: 0;
  font-family: var(--font-studio-display);
  font-size: clamp(24px, 2.6vw, 32px);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}

/* Prompt + model, revealed under the label. max-height (not display) so the
   label glides up as it opens; on touch devices it is simply always open —
   there is no hover to find it with. */
.media-sub {
  margin: 0;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transform: translateY(6px);
  transition: max-height 0.4s ease, opacity 0.35s ease, transform 0.4s ease;
  font-family: var(--font-studio-text);
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.75);
}

/* Model pills sit on their own row above the quote. Inline pills inflated
   the first line's height and left an ugly gap under it (owner, 2026-08-02);
   a flex row is block-level, so the quote wraps evenly below. */
.media-models {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 6px;
}

.media-model {
  padding: 2px 8px;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-pill);
  background: rgba(8, 8, 11, 0.6);
  font-size: 11px;
  color: var(--text-secondary);
  white-space: nowrap;
}

@media (hover: hover) {
  .media-card:hover .media-sub {
    max-height: 180px;
    opacity: 1;
    transform: none;
  }
}

@media (hover: none) {
  .media-sub {
    max-height: 180px;
    opacity: 1;
    transform: none;
  }
}

/* --- Mini music player --- */

/* The player card stacks player-over-meta in NORMAL FLOW, so when the hover
   reveal expands the meta, the player compresses smoothly instead of being
   overlapped (the absolute-inset version put the title through the progress
   bar — owner screenshot, 2026-08-02). */
.media-card-player {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-end;
}

.player {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 4px 0 16px;
}

/* One glass widget, not floating pieces (the scattered version read as
   template filler — owner, 2026-08-02). */
.player-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(88%, 340px);
  padding: 14px 14px 16px;
  border: 1px solid var(--stroke-soft);
  border-radius: 16px;
  background: linear-gradient(180deg, var(--fill-soft), rgba(16, 17, 22, 0.72));
}

.player-art {
  flex: none;
  width: 46px;
  height: 46px;
  border: 1px solid var(--stroke-soft);
  border-radius: 10px;
  object-fit: cover;
}

.player-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.player-titles {
  flex: 1;
  min-width: 0;
}

.player-caption {
  margin: 2px 0 0;
  font-family: var(--font-studio-text);
  font-size: 12px;
  color: var(--text-tertiary);
}

/* Same language as the site's primary pill: solid white, canvas-dark glyph. */
.player-btn {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #fff;
  color: var(--bg);
  cursor: pointer;
  transition: background 0.16s ease, transform 0.16s ease;
}

@media (hover: hover) {
  .player-btn:hover {
    background: #f1f1f3;
  }
}

.player-btn:active {
  transform: scale(0.96);
}

.player-btn svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
}

/* Play shows while paused, pause while playing. */
.player .p-pause { display: none; }
.player[data-playing="true"] .p-play { display: none; }
.player[data-playing="true"] .p-pause { display: block; }

.player-title {
  margin: 0;
  font-family: var(--font-studio-display);
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The waveform IS the progress bar: heights live from the analyser (app.js;
   resting wave when paused), played bars tinted accent, click to seek. */
.player-wave {
  display: flex;
  align-items: center;
  gap: 2px;
  width: 100%;
  height: 30px;
  cursor: pointer;
}

.player-wave span {
  flex: 1;
  min-width: 1px;
  height: 12%;
  border-radius: 1px;
  background: rgba(255, 255, 255, 0.18);
  transition: height 0.08s linear, background 0.2s ease;
}

.player-wave span.on {
  background: var(--accent-a);
}

@media (max-width: 760px) {
  .media-cards {
    grid-template-columns: 1fr;
  }
}

/* ---------- Production Bible screenshots ---------- */

/* Film strip: snap-scrolling row, one slide leading and the next peeking in
   from the right (owner direction 2026-08-02 — the earlier grid mosaics read
   as too small, then too in-your-face). Chevrons scroll it for mouse users;
   trackpads and touch swipe natively. The scrollbar is hidden because the
   chevrons + peek already say "there's more". */
.bible-gallery {
  position: relative;
  margin-top: 36px;
}

.bible-nav {
  position: absolute;
  top: -54px;
  right: 0;
  display: flex;
  gap: 8px;
}

.bible-nav button {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--stroke);
  border-radius: 50%;
  background: var(--fill-soft);
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, transform 0.18s ease;
}

@media (hover: hover) {
  .bible-nav button:hover {
    background: var(--fill);
    color: var(--text-primary);
  }
}

.bible-nav button:active {
  transform: scale(0.94);
}

.bible-nav svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.bible-strip {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  /* Hairline of breathing room so focus rings aren't clipped. */
  padding: 4px;
  margin: -4px;
}

.bible-strip::-webkit-scrollbar {
  display: none;
}

/* Flat on the canvas — no drop shadow, no scrim over the screenshot: dark
   shadow treatments on dark app UI read as smudge (owner, 2026-08-02). The
   hairline border alone carries the edge, Linear-style. */
.bible-slide {
  position: relative;
  flex: 0 0 auto;
  margin: 0;
  scroll-snap-align: start;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-card);
  background: var(--surface);
  overflow: hidden;
}

/* Slides are sized by HEIGHT, widths follow each capture's own ratio —
   equal-height strip, flush top and bottom edges, no cropping and no
   letterboxing even though Locations is 16:9 and the rest are taller
   (mismatched bottoms read as broken — owner, 2026-08-02). 54vw keeps a
   phone's slide near the old 86%-width size. */
.bible-slide img {
  display: block;
  height: min(377px, 54vw);
  width: auto;
}

.bible-meta {
  position: absolute;
  left: 20px;
  right: 20px;
  bottom: 14px;
}

.bible-meta h3 {
  margin: 0;
  font-family: var(--font-studio-display);
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

/* Same reveal mechanics as the generation cards: glides open on hover,
   always open where there is no hover. */
.bible-sub {
  margin: 0;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transform: translateY(6px);
  transition: max-height 0.4s ease, opacity 0.35s ease, transform 0.4s ease;
  font-family: var(--font-studio-text);
  font-size: 14px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.78);
}

@media (hover: hover) {
  .bible-slide:hover .bible-sub {
    max-height: 90px;
    opacity: 1;
    transform: none;
  }
}

@media (hover: none) {
  .bible-sub {
    max-height: 90px;
    opacity: 1;
    transform: none;
  }
}

/* ---------- Trio cards ---------- */

.trio {
  padding: 0 0 clamp(64px, 12vw, 110px);
}

/* Deliberately a size down from the other section headlines — this is the
   "and more" tail, not a headline act. */
.trio h2 {
  font-size: clamp(26px, 3.4vw, 36px);
}

.trio-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-top: 26px;
}

.trio-card {
  padding: 20px 20px 22px;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-card);
  background: linear-gradient(180deg, var(--fill-soft), rgba(16, 17, 22, 0.6));
  transition: transform 0.22s ease, border-color 0.22s ease;
}

@media (hover: hover) {
  .trio-card:hover {
    transform: translateY(-3px);
    border-color: var(--stroke);
  }
}

/* --- Card motion graphics --- */

/* Each art panel is a miniature app surface: canvas-dark fill on a hairline,
   one drawing inside. Resting styles are the finished states — the keyframes
   below only run under prefers-reduced-motion: no-preference, so a still
   frame is always complete. */
.trio-art {
  position: relative;
  height: 128px;
  margin-bottom: 18px;
  border: 1px solid var(--stroke-soft);
  border-radius: 12px;
  background: rgba(8, 8, 11, 0.55);
  overflow: hidden;
}

/* Color grade: a real still, flat copy under a graded copy, the graded side
   swept by a wipe line — before/after with the work visibly happening. The
   graded layer is clipped (clip-path), never resized, so the picture can't
   squish as the wipe travels. */
.art-grade {
  display: grid;
  place-items: center;
}

/* Sized from the panel's HEIGHT, never a width percentage: at wide card
   widths a 74%-wide 16:9 box grows taller than the 128px panel and its top —
   with the AI chip pinned to it — gets clipped off (bit Safari first,
   2026-08-02). Height-derived, it always fits with margin. */
.grade-shot {
  position: relative;
  height: calc(100% - 20px);
  aspect-ratio: 16 / 9;
  border: 1px solid var(--stroke-soft);
  border-radius: 8px;
  background: #141821;
  overflow: hidden;
}

.grade-before,
.grade-after {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.grade-before {
  filter: saturate(0.22) contrast(0.9) brightness(0.85);
}

.grade-after {
  filter: saturate(1.3) contrast(1.08) brightness(1.02);
  clip-path: inset(0 38% 0 0);
}

.grade-line {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 62%;
  width: 2px;
  background: rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.55);
}

/* Solid fill, no backdrop-filter: Safari lets a backdrop-filtered child
   escape its ancestor's overflow clip, which hung this chip off the photo's
   corner. */
.grade-chip {
  position: absolute;
  top: 8px;
  left: 8px;
  padding: 2px 8px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: var(--radius-pill);
  background: rgba(8, 8, 11, 0.72);
  font-family: var(--font-studio-text);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.85);
}

/* Beat sync: the timeline's own furniture — beat dots (downbeats brighter),
   a waveform, a playhead sweeping through. */
.art-beats {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 12px;
  padding: 18px;
}

.beat-dots {
  display: flex;
  justify-content: space-between;
  padding: 0 3px;
}

.beat-dots span {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.38);
}

.beat-dots .downbeat {
  background: var(--accent-a);
}

.beat-bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 52px;
}

.beat-bars span {
  flex: 1;
  height: var(--h);
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.28);
}

.beat-head {
  position: absolute;
  top: 14px;
  bottom: 14px;
  left: 18px;
  width: 2px;
  border-radius: 1px;
  background: rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 12px rgba(168, 85, 247, 0.7);
}

/* Upscale: a viewfinder around "4K". Resting state is full size and sharp;
   in motion it breathes up from small and soft — the upscale itself. The
   roster tags drift slowly around it. */
.art-upscale {
  display: grid;
  place-items: center;
}

.upscale-tag {
  position: absolute;
  padding: 3px 10px;
  border: 1px solid var(--stroke-soft);
  border-radius: var(--radius-pill);
  background: var(--fill-soft);
  font-family: var(--font-studio-text);
  font-size: 11px;
  white-space: nowrap;
  color: var(--text-tertiary);
}

.upscale-tag.t1 { top: 12px; left: 14px; }
.upscale-tag.t2 { top: 16px; right: 14px; }
.upscale-tag.t3 { bottom: 14px; right: 20px; }

.upscale-frame {
  position: relative;
  display: grid;
  place-items: center;
  width: 104px;
  height: 72px;
}

.upscale-frame .corner {
  position: absolute;
  width: 16px;
  height: 16px;
  border: 2px solid rgba(255, 255, 255, 0.75);
}

.c-tl { top: 0; left: 0; border-right: 0; border-bottom: 0; border-top-left-radius: 6px; }
.c-tr { top: 0; right: 0; border-left: 0; border-bottom: 0; border-top-right-radius: 6px; }
.c-bl { bottom: 0; left: 0; border-right: 0; border-top: 0; border-bottom-left-radius: 6px; }
.c-br { bottom: 0; right: 0; border-left: 0; border-top: 0; border-bottom-right-radius: 6px; }

.upscale-label {
  font-family: var(--font-studio-display);
  font-size: 21px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text-primary);
}

@media (prefers-reduced-motion: no-preference) {
  /* Wipe and line share one duration/easing so they stay glued together. */
  .grade-after {
    animation: grade-clip 5.5s ease-in-out infinite alternate;
  }

  .grade-line {
    animation: grade-travel 5.5s ease-in-out infinite alternate;
  }

  .grade-chip {
    animation: chip-pulse 2.4s ease-in-out infinite;
  }

  /* Slow wandering loops, not a shiver: each tag walks its own soft circuit
     on its own clock, so the trio never moves in lockstep. */
  .upscale-tag.t1 { animation: tag-float-a 13s ease-in-out infinite; }
  .upscale-tag.t2 { animation: tag-float-b 16s ease-in-out infinite; }
  .upscale-tag.t3 { animation: tag-float-c 14.5s ease-in-out infinite; }

  .beat-head {
    animation: beat-head 4.2s linear infinite;
  }

  .beat-dots span {
    animation: dot-pulse 2.1s ease-in-out infinite;
  }

  /* Staggered so the pulse walks the row like a count-in. */
  .beat-dots span:nth-child(2) { animation-delay: 0.35s; }
  .beat-dots span:nth-child(3) { animation-delay: 0.7s; }
  .beat-dots span:nth-child(4) { animation-delay: 1.05s; }
  .beat-dots span:nth-child(5) { animation-delay: 1.4s; }
  .beat-dots span:nth-child(6) { animation-delay: 1.75s; }

  .upscale-frame {
    animation: upscale-breathe 3.4s ease-in-out infinite alternate;
  }
}

@keyframes grade-clip {
  from { clip-path: inset(0 76% 0 0); }
  to { clip-path: inset(0 16% 0 0); }
}

@keyframes grade-travel {
  from { left: 24%; }
  to { left: 84%; }
}

@keyframes chip-pulse {
  0%, 100% { opacity: 0.7; }
  50% { opacity: 1; }
}

@keyframes tag-float-a {
  0%, 100% { transform: translate(0, 0); }
  30% { transform: translate(22px, 12px); }
  62% { transform: translate(8px, 26px); }
}

@keyframes tag-float-b {
  0%, 100% { transform: translate(0, 0); }
  34% { transform: translate(-26px, 14px); }
  68% { transform: translate(-10px, 30px); }
}

@keyframes tag-float-c {
  0%, 100% { transform: translate(0, 0); }
  32% { transform: translate(-18px, -16px); }
  66% { transform: translate(-34px, -4px); }
}

@keyframes beat-head {
  from { left: 18px; }
  to { left: calc(100% - 20px); }
}

@keyframes dot-pulse {
  0%, 24%, 100% { transform: scale(1); }
  12% { transform: scale(1.7); }
}

@keyframes upscale-breathe {
  from {
    transform: scale(0.76);
    filter: blur(1.2px);
    opacity: 0.75;
  }

  to {
    transform: scale(1);
    filter: blur(0);
    opacity: 1;
  }
}

.trio-card h3 {
  margin: 0 0 6px;
  font-family: var(--font-studio-display);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.trio-card p {
  margin: 0;
  font-family: var(--font-studio-text);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-secondary);
}

@media (max-width: 760px) {
  .trio-grid {
    grid-template-columns: 1fr;
  }
}

/* ---------- Closing CTA ---------- */

.cta {
  position: relative;
  padding: 10px 0 clamp(84px, 13vw, 130px);
  text-align: center;
}

.cta::before {
  content: "";
  position: absolute;
  z-index: -1;
  bottom: -40px;
  left: 50%;
  transform: translateX(-50%);
  width: 60%;
  height: 340px;
  background: radial-gradient(
    closest-side,
    rgba(124, 58, 237, 0.13),
    transparent 70%
  );
  filter: blur(60px);
  pointer-events: none;
}

.cta .btn-big {
  margin-top: 28px;
  height: 44px;
  padding: 0 26px;
  font-size: 16px;
}

.cta-note {
  margin: 14px 0 0;
  font-family: var(--font-studio-text);
  font-size: 14px;
  color: var(--text-tertiary);
}

/* ---------- FAQ ----------

   The page's one reference panel, so it is deliberately the plainest thing on
   it: hairline rows, text, no card, no glow, no art. The heading sits in its
   own column and STAYS PUT there (owner, 2026-08-03) — it was sticky and rode
   the scroll down beside the answers, which reads as the page chasing you.
   Never reintroduce `position: sticky` here.

   The rows are native <details>. The open/close animation is progressive
   enhancement in the strictest sense — where ::details-content is unsupported
   the row simply snaps open, which is the correct behaviour, not a fallback. */

.faq {
  display: grid;
  gap: clamp(20px, 3vw, 48px);
  padding: clamp(48px, 8vw, 80px) 0 clamp(80px, 13vw, 130px);
  border-top: 1px solid var(--stroke-soft);
}

.faq-head h2 {
  /* Smaller than a story-section headline: this closes the page, it doesn't
     open a chapter. Shares the family/leading rule above. */
  font-size: clamp(28px, 3.4vw, 40px);
}

.faq-list {
  border-top: 1px solid var(--stroke-soft);
}

.faq-item {
  border-bottom: 1px solid var(--stroke-soft);
  /* Inherited by ::details-content, so the row can transition from 0 to its
     natural height. Scoped here rather than :root — nothing else on the site
     animates to a size keyword. */
  interpolate-size: allow-keywords;
}

.faq-item summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  padding: 19px 0;
  cursor: pointer;
  font-family: var(--font-studio-text);
  font-size: clamp(16px, 1.7vw, 18px);
  font-weight: 500;
  line-height: 1.45;
  color: var(--text-primary);
  /* Both spellings: the pseudo-element is what older WebKit reads, list-style
     is what everything current reads. Without them the row carries a triangle
     the design never accounts for. */
  list-style: none;
  -webkit-tap-highlight-color: transparent;
}

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

.faq-item summary:focus-visible {
  /* The global focus ring is a pill, which looks wrong wrapped around a
     full-width row. */
  border-radius: var(--radius-control);
  outline-offset: 2px;
}

/* The mark is a plus that loses its upright stroke when the row opens. Drawn
   rather than shipped as an icon so it inherits the text color exactly. */
.faq-mark {
  position: relative;
  flex: none;
  width: 13px;
  height: 13px;
  margin-top: 0.4em;
  color: var(--text-tertiary);
}

.faq-mark::before,
.faq-mark::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 1.5px;
  border-radius: 1px;
  background: currentColor;
  transform: translateY(-50%);
  transition: transform 0.28s ease;
}

/* Rotated first, so scaling it away collapses it along its own length. */
.faq-mark::after {
  transform: translateY(-50%) rotate(90deg);
}

.faq-item[open] .faq-mark::after {
  transform: translateY(-50%) rotate(90deg) scaleX(0);
}

@media (hover: hover) {
  .faq-item summary:hover {
    color: #fff;
  }

  .faq-item summary:hover .faq-mark {
    color: var(--text-secondary);
  }
}

.faq-item[open] summary {
  color: #fff;
}

.faq-answer {
  margin: 0;
  /* Clears the mark's column so an answer never runs under the plus. */
  padding: 0 37px 22px 0;
  max-width: 64ch;
  font-family: var(--font-studio-text);
  font-size: clamp(15px, 1.6vw, 16px);
  line-height: 1.62;
  color: var(--text-secondary);
}

.faq-answer a {
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--stroke);
  transition: text-decoration-color 0.2s ease;
}

@media (hover: hover) {
  .faq-answer a:hover {
    text-decoration-color: currentColor;
  }
}

@media (min-width: 900px) {
  .faq {
    grid-template-columns: minmax(200px, 0.75fr) minmax(0, 2fr);
    align-items: start;
  }
}

@media (prefers-reduced-motion: no-preference) {
  @supports selector(details::details-content) {
    .faq-item::details-content {
      block-size: 0;
      opacity: 0;
      overflow: clip;
      transition: block-size 0.32s ease, opacity 0.26s ease,
        content-visibility 0.32s;
      transition-behavior: allow-discrete;
    }

    .faq-item[open]::details-content {
      block-size: auto;
      opacity: 1;
    }
  }
}

/* ---------- Entrance reveals ---------- */

/* Play-once rises driven by IntersectionObserver (app.js) + transitions.
   Deliberately NOT CSS scroll-driven animation (animation-timeline: view()):
   Safari passes that @supports gate but intermittently parks both-fill
   scrubbed animations on their first frame — whole sections and the model
   strip rendered invisible (owner report, 2026-08-02). Do not migrate back
   without testing Safari. The hidden state exists only under html.js (added
   by the script that also observes, so it can never strand content), and
   only under no-preference, so reduced-motion readers get still, visible
   content. */
@media (prefers-reduced-motion: no-preference) {
  html.js .reveal {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity 0.65s ease, transform 0.65s ease;
    /* Cards pass their place in the row through --i for a small wave. */
    transition-delay: calc(var(--i, 0) * 90ms);
  }

  html.js .reveal.in {
    opacity: 1;
    transform: none;
  }

  /* The statement lights with ONE smooth gradient sweep once in view —
     stepped per-word delays were tried at 36ms (a flash) and 130ms
     (mechanical); a soft edge gliding across in ~2s is the version that
     reads as light, not machinery. Gated on background-clip support; without
     it the text is simply bright. */
  @supports ((-webkit-background-clip: text) or (background-clip: text)) {
    html.js .statement-text {
      color: transparent;
      -webkit-text-fill-color: transparent;
      background-image: linear-gradient(
        100deg,
        rgba(255, 255, 255, 0.92) 0%,
        rgba(255, 255, 255, 0.92) 42%,
        rgba(255, 255, 255, 0.13) 58%,
        rgba(255, 255, 255, 0.13) 100%
      );
      background-size: 240% 100%;
      background-position: 100% 0;
      -webkit-background-clip: text;
      background-clip: text;
      transition: background-position 2.2s ease-out;
    }

    html.js .statement.in .statement-text {
      background-position: 0% 0;
    }
  }
}
