/* Design Layer landing page. One file, no build step. */

@font-face {
  font-family: "Inter";
  src: url("assets/fonts/InterVariable-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Headlines in Instrument Sans; the body stays in Inter. */
@font-face {
  font-family: "Instrument Sans";
  src: url("assets/fonts/InstrumentSans-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
}

/* Direct edit's live retype. Block: a fallback would wrap differently. */
@font-face {
  font-family: "Hedvig Letters Serif";
  src: url("assets/fonts/HedvigLettersSerif-latin.woff2") format("woff2");
  font-display: block;
}

:root {
  color-scheme: dark;

  /* Pure black, so the headline and the product carry the page; the editor
     chrome (#171717) still reads as a surface on it. */
  --bg: #000;
  --raised: #111;
  --line: rgb(255 255 255 / 0.08);
  --line-strong: rgb(255 255 255 / 0.14);
  --text: #fafafa;
  --text-2: #a1a1aa;
  --text-3: #8a8a93;

  /* The editor's own indigo: ink #798cff, word #90a3ff, fill #4a5df9. */
  --accent: #798cff;
  --accent-text: #90a3ff;
  --accent-fill: #4a5df9;
  --accent-glow: rgb(121 140 255 / 0.18);

  --add: #7ee2a8;

  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --display: "Instrument Sans", var(--font);
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  --wrap: 1120px;
  --wrap-wide: 1240px;
  --gutter: clamp(20px, 4vw, 40px);
  --section: clamp(96px, 12vw, 152px);

  --radius-control: 10px;
  --radius-shot: 14px;
  --frame-inset: 6px;
  --radius-frame: calc(var(--radius-shot) + var(--frame-inset));

  /* The frosted glass of the nav and of the footer's links row. */
  --frost: blur(18px) saturate(1.6);

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-hover: cubic-bezier(0.4, 0, 0.2, 1);
}

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 72px;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.625;
  font-feature-settings: "cv11", "ss01";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

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

img {
  height: auto;
}

a {
  color: inherit;
  text-decoration-color: rgb(255 255 255 / 0.3);
  text-decoration-thickness: from-font;
  text-underline-position: from-font;
  text-decoration-skip-ink: auto;
}

a:hover {
  text-decoration-color: currentColor;
}

::selection {
  background: color-mix(in srgb, var(--accent) 40%, transparent);
  color: #fff;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 6px;
}

h1,
h2,
h3,
p,
ol,
ul,
dl,
dd,
figure,
pre {
  margin: 0;
}

ol,
ul {
  padding: 0;
  list-style: none;
}

code {
  font-family: var(--mono);
}

kbd {
  display: inline-block;
  min-width: 1.6em;
  padding: 0 0.38em;
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: 6px;
  background: rgb(255 255 255 / 0.05);
  color: var(--text);
  font: 500 0.8em/1.55 var(--font);
  text-align: center;
  vertical-align: 0.1em;
}

/* Sprite glyphs inherit their paint from here: <use> does not carry the
   sprite root's own fill and stroke. Filled glyphs set fill on their paths. */
.icon {
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.dim {
  color: var(--text-3);
}

.skip {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 100;
  padding: 10px 14px;
  border-radius: var(--radius-control);
  background: var(--text);
  color: var(--bg);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-200%);
  transition: transform 200ms var(--ease-out);
}

.skip:focus-visible {
  transform: none;
}

.wrap,
.wrap-wide {
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.wrap {
  max-width: calc(var(--wrap) + 2 * var(--gutter));
}

.wrap-wide {
  max-width: calc(var(--wrap-wide) + 2 * var(--gutter));
}

/* ---------- Buttons ----------
   Every button answers the pointer. Hovered, it rises a pixel, a band of
   light crosses it once, and the GitHub mark waves. Pressed, it sinks to 96%
   at once and springs back on release. Touch gets the press; reduced motion
   keeps only the colour changes. */

.btn {
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --sheen: rgb(255 255 255 / 0.13);
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  font: 500 15px/1 var(--font);
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color 150ms var(--ease-hover),
    border-color 150ms var(--ease-hover),
    color 150ms var(--ease-hover),
    box-shadow 240ms var(--ease-hover),
    transform 420ms var(--spring);
}

/* The band of light: over the fill, under the label. */
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(105deg, transparent 30%, var(--sheen) 50%, transparent 70%);
  transform: translateX(-100%);
  pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    transform: translateY(-1px);
  }

  /* An even pace, so the band is seen travelling rather than flashing. */
  .btn:hover::after {
    transform: translateX(100%);
    transition: transform 650ms var(--ease-hover);
  }

  .btn:hover .gh {
    animation: btn-wave 640ms var(--ease-out);
  }
}

.btn:active {
  transform: scale(0.96);
  transition-duration: 150ms, 150ms, 150ms, 240ms, 90ms;
  transition-timing-function: var(--ease-hover), var(--ease-hover), var(--ease-hover), var(--ease-hover), ease-out;
}

@keyframes btn-wave {
  30% {
    transform: rotate(-14deg) scale(1.12);
  }

  65% {
    transform: rotate(8deg) scale(1.04);
  }
}

/* On white the band would vanish, so the primary glows instead. */
.btn-primary {
  --sheen: rgb(255 255 255 / 0);
  background: var(--text);
  color: #0b0b0b;
}

.btn-primary:hover {
  background: #fff;
  box-shadow: 0 8px 28px -10px rgb(255 255 255 / 0.5);
}

.btn-secondary {
  border-color: var(--line-strong);
  background: rgb(255 255 255 / 0.03);
  color: var(--text);
}

.btn-secondary:hover {
  border-color: rgb(255 255 255 / 0.24);
  background: rgb(255 255 255 / 0.07);
}

.star-count {
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}

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

/* No rule and no shadow under the nav: the frosted fill alone separates it from
   what passes beneath, and it stops at the bar's edge, since a fade below it
   read as a drop shadow. The hero has no nav: it slides down once the second
   section pins (main.js adds .is-shown). */
.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 50;
}

.js .nav {
  transition: translate 420ms var(--ease-out), opacity 300ms var(--ease-out);
}

.js .nav:not(.is-shown) {
  opacity: 0;
  translate: 0 -100%;
  pointer-events: none;
}

.nav::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgb(0 0 0 / 0.74);
  -webkit-backdrop-filter: var(--frost);
  backdrop-filter: var(--frost);
  pointer-events: none;
  transition: opacity 120ms linear;
}

.nav-inner {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: calc(var(--wrap-wide) + 2 * var(--gutter));
  height: 64px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.brand img {
  border-radius: 6px;
}

/* Get started as plain text, the film as the secondary button, and GitHub as
   the primary one at the far right. Phones drop the brand's name; the
   narrowest keep only the GitHub mark in its button. */
.nav-start {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-left: auto;
  padding: 0 8px;
  color: var(--text-2);
  font-size: 14px;
  white-space: nowrap;
  text-decoration: none;
  transition: color 150ms var(--ease-hover);
}

.nav-start:hover {
  color: var(--text);
}

.nav-film,
.nav-gh {
  min-height: 36px;
  padding: 0 14px;
  font-size: 14px;
}

.nav-gh {
  padding-left: 12px;
}

.nav .btn:hover {
  box-shadow: none;
}

.nav-gh .star-count {
  color: rgb(11 11 11 / 0.6);
}

.brand {
  flex: none;
  white-space: nowrap;
}

@media (max-width: 480px) {
  .nav .brand span {
    display: none;
  }
}

@media (max-width: 360px) {
  .nav-inner {
    gap: 8px;
  }

  .nav-gh {
    width: 36px;
    padding: 0;
  }

  .nav-gh-label,
  .nav-gh .star-count {
    display: none;
  }
}

/* ---------- Hero: a pinned scene ----------
   The track is tall and the stage sticks, so scrolling plays the scene:
   the window rises and flattens, the editor slides in around the app, and
   the selection draws itself. main.js maps scroll to each layer's transform. */

.hero {
  position: relative;
  isolation: isolate;
}

.hero::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  z-index: -1;
  height: 900px;
  background: radial-gradient(60% 40% at 50% 0%, var(--accent-glow), transparent 70%);
  pointer-events: none;
}

/* With WebGL, the blinds take the glow's place (backgrounds.js). */
.js:not(.no-webgl) .hero::before {
  display: none;
}

/* The blinds behind the mockup, from the top of the viewport. */
.hero-blinds {
  position: absolute;
  inset: 0 0 auto;
  z-index: -1;
  height: 100vh;
  height: 100svh;
  opacity: 0;
  pointer-events: none;
  transition: opacity 1200ms var(--ease-out);
}

/* The footer's fade turned over: full at the top, gone at the bottom. */
.hero-blinds {
  --rise: 100%;
  --fade-to: 0deg;
}

.hero-blinds.is-on,
.footer-blinds.is-on {
  opacity: 1;
}

.hero-blinds canvas,
.footer-blinds canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.hero-track {
  position: relative;
  /* The stage and one screen of landing, whose last 60vh is the next
     track's lead-in (data-lead). */
  height: 200vh;
}

/* Design like Figma starts under the hero's last 160vh: its stage pins while
   the hero's mock lands in its frame (main.js), hidden until then. */
.js #edit {
  margin-top: -160vh;
  padding-top: 0;
}

.js #edit .tabs-track {
  height: calc(160vh + var(--tabs, 2) * 55vh);
}

.js #edit .tabs-track.is-waiting .tabs-stage {
  visibility: hidden;
}

/* Until then it lies over the hero's lower half; clicks go through to it. */
.js #edit:has(.is-waiting) {
  pointer-events: none;
}

.hero-stage {
  --caption-h: 40px;
  --film-w: min(var(--wrap), calc(100vw - 2 * var(--gutter)), calc((100svh - 92px - var(--caption-h) - 12px) * 1.6 + 12px));
  --film-h: calc((var(--film-w) - 12px) / 1.6 + 12px);
  /* The window and its caption sit centred in the space under the nav. */
  --film-top: max(84px, calc(64px + (100svh - 64px - var(--film-h) - var(--caption-h)) / 2));
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

/* Above the film: the tilted stack reaches up behind the buttons and would
   take their clicks. */
.hero-copy {
  position: absolute;
  top: clamp(56px, 13vh, 150px);
  left: 0;
  right: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  will-change: transform, opacity;
}

h1 {
  max-width: 18em;
  font-family: var(--display);
  font-size: clamp(34px, 5.4vw, 68px);
  font-weight: 500;
  line-height: 1.36;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

/* Hard breaks keep the cycling word from rewrapping the lines above it: the
   first line ends at "to", so only the second line moves. A phone breaks
   after "the" and "your" instead, so only the last line moves. */
.br-sm {
  display: none;
}

@media (max-width: 640px) {
  .br-lg {
    display: none;
  }

  .br-sm {
    display: inline;
  }

  .hero-copy {
    top: max(56px, 17vh);
  }

  /* Its widest line, "design layer to your" with the pill, is about 10em:
     the size that keeps it on one line between the gutters. */
  h1 {
    font-size: min(34px, (100vw - 2 * var(--gutter)) / 10.2);
  }
}

/* Words as objects: a solid pill behind a word, and a sticker, die-cut in
   white, at the pill's start. The pill is opaque so it reads as one object,
   not a tint over the page. It is a ::before, so the word keeps the heading's
   own line box; the button one cycles the kind of app (main.js), its old and
   new words stacked in one clipped cell.
   The sticker is 0.8em with the brand mark's corners (7 of its 32), ringed in
   white. The pill clears the ring by the same gap above, below and before
   it, and its corners are the ring's plus that gap, so the two are
   concentric. */
.token {
  --pill: color-mix(in oklab, var(--accent-fill) 36%, #050505);
  --art: 0.8em;
  --art-radius: 0.175em;
  --ring: 0.05em;
  --gap: 0.17em;
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: baseline;
  margin-inline: 0.02em;
  padding: 0 0.42em 0 calc(var(--ring) + var(--gap));
  border: 0;
  border-radius: calc(var(--art-radius) + var(--ring) + var(--gap));
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  white-space: nowrap;
  vertical-align: baseline;
}

.token::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 50%;
  z-index: -1;
  height: calc(var(--art) + 2 * (var(--ring) + var(--gap)));
  border-radius: inherit;
  background: var(--pill);
  transform: translateY(-50%);
  transition: background-color 180ms var(--ease-hover);
}

.token-art {
  display: inline-grid;
  place-items: center;
  flex: none;
  align-self: center;
  width: var(--art);
  height: var(--art);
  margin-right: 0.24em;
  border-radius: var(--art-radius);
  box-shadow: 0 0 0 var(--ring) #fff, 0 0.1em 0.22em rgb(0 0 0 / 0.55);
}

/* The app stickers wear the brand mark's colors (assets/brand/mark.svg): its
   dark ground and its layer's indigo, fixed like the mark's, so all three
   stickers read as one family. */
span.token-art {
  background: linear-gradient(#1e222d, #0c1014);
  color: #7c8eff;
}

.token-art .icon {
  width: 0.58em;
  height: 0.58em;
  stroke-width: 2.4;
}

.token-words {
  display: inline-grid;
  overflow-x: clip;
}

.token-word {
  grid-area: 1 / 1;
  justify-self: start;
}

button.token {
  cursor: pointer;
}

button.token:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

@media (hover: hover) and (pointer: fine) {
  button.token:hover {
    --pill: color-mix(in oklab, var(--accent-fill) 46%, #050505);
  }
}

button.token:active {
  --pill: color-mix(in oklab, var(--accent-fill) 54%, #050505);
}

/* The staged entrance: the copy arrives in reading order, 100ms apart, once. */
.hero-copy > * {
  animation: hero-rise 800ms var(--ease-out) both;
}

.hero-copy > :nth-child(2) { animation-delay: 100ms; }
.hero-copy > :nth-child(3) { animation-delay: 200ms; }
.hero-copy > :nth-child(4) { animation-delay: 300ms; }
.hero-copy > :nth-child(5) { animation-delay: 380ms; }

@keyframes hero-rise {
  from {
    opacity: 0;
    transform: translateY(12px);
    filter: blur(6px);
  }
}

/* Three planes in one 3D stack: the app at the bottom, Design Layer's panels
   on a sheet of glass above it, and the toolbar on top. main.js holds them
   apart in a gentle 3/4 view, then lands them one after another on scroll,
   where they line up pixel for pixel into the real screenshot. */
.hero-film {
  position: absolute;
  top: var(--film-top);
  left: 50%;
  width: var(--film-w);
  margin-left: calc(var(--film-w) / -2);
  perspective: 2200px;
  animation: film-arrive 900ms var(--ease-out) 300ms both;
}

@keyframes film-arrive {
  from {
    opacity: 0;
  }
}

/* No will-change on the stack or its planes: a layer promoted at the small
   opening scale keeps that raster, and the flat frame would come out soft.
   The 3D transforms promote them anyway, re-rastered at each scale. */
.stack {
  position: relative;
  transform-style: preserve-3d;
  transform-origin: 50% 50%;
}

/* Edges of turned planes: a transparent outline and a hidden back face make
   the browser antialias them instead of stepping them. */
.plane-app,
.plane {
  outline: 1px solid transparent;
  backface-visibility: hidden;
}

/* The app dims while Design Layer hovers over it, so the eye goes to the layer. */
.plane-app .frame-view::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: #0a0a0b;
  opacity: var(--dim, 0);
  pointer-events: none;
}

.plane {
  position: absolute;
  inset: calc(var(--frame-inset) + 1px);
  border-radius: var(--radius-shot);
  pointer-events: none;
}

.plane-chrome {
  --lift: 0;
  --seam-l: 0.1559;
  --seam-r: 0.8188;
  overflow: hidden;
  box-shadow: 0 32px 64px -32px rgb(0 0 0 / calc(0.55 * var(--lift)));
}

/* The layer itself: a sheet of clear glass the panels sit on, lit along its
   top edge. */
.plane-chrome::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.22);
  opacity: var(--lift);
}

.plane-tools {
  overflow: visible;
}

/* Lifted, the toolbar casts a deeper shadow; landed, it is the editor's own
   float shadow (lift 0), ring and rim included. */
.plane-tools .tb {
  box-shadow:
    0 calc(18px + 6px * var(--lift, 0)) 56px rgb(0 0 0 / calc(0.42 + 0.13 * var(--lift, 0))),
    0 0 0 1px rgb(255 255 255 / 0.08),
    inset 0 1px 0 rgb(255 255 255 / 0.1);
}

/* A coordinate space the size of the captures (1600x1000), scaled to the
   frame, so drawings laid over a screenshot land on its pixels at any width. */
.shot-space {
  position: absolute;
  top: 0;
  left: 0;
  width: 1600px;
  height: 1000px;
  transform: scale(var(--k, 0.75));
  transform-origin: 0 0;
  pointer-events: none;
}

/* ---------- The editor's toolbar, redrawn at its real size ----------
   Every number is the running editor's computed style (1600x1000 viewport):
   a 328x40 pill 12px off the bottom, nine 32px squares 4px apart. */
.tb {
  position: absolute;
  left: 636px;
  top: 948px;
  display: flex;
  gap: 4px;
  padding: 3px;
  border: 1px solid rgb(255 255 255 / 0.1);
  border-radius: 16px;
  corner-shape: superellipse(2); /* the editor's squircle, on every corner in the bar */
  background: #171717;
  box-shadow:
    0 18px 56px rgb(0 0 0 / 0.42),
    0 0 0 1px rgb(255 255 255 / 0.08),
    inset 0 1px 0 rgb(255 255 255 / 0.1);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  transform-origin: 50% 100%;
  pointer-events: auto;
}

.tb-chip {
  --chip: 0;
  position: absolute;
  top: 3px;
  left: 3px;
  width: 32px;
  height: 32px;
  border-radius: 12px;
  corner-shape: superellipse(2);
  background: #4a5df9;
  transform: translateX(calc(var(--chip) * 36px));
}

.tb.is-touring .tb-chip {
  transition: transform 520ms var(--ease-out);
}

.tb-tool {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 12px;
  corner-shape: superellipse(2);
  color: #fafafa;
  transition:
    color 200ms var(--ease-out),
    background-color 150ms var(--ease-out);
}

/* The editor's hover square (bgHoverQuiet), shown with the tip it arms. */
.tb-tool:hover:not(.is-on),
.tb-tool.show-tip:not(.is-on) {
  background: #2e2e2e;
}

.tb-tool.is-dim {
  color: rgb(255 255 255 / 0.62);
}

.tb-tool svg {
  grid-area: 1 / 1;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: opacity 200ms var(--ease-out);
}

/* The editor's optical correction: the arrow sits 6.3% of its box down-right
   (css/icons.ts), 1.008px at 16px. */
.tb-tool[data-tool="inspect"] svg {
  translate: 6.3% 6.3%;
}

.tb-tool .tb-on,
.tb-tool.is-on .tb-off {
  opacity: 0;
}

.tb-tool.is-on .tb-on {
  opacity: 1;
}

.tb-tool.is-on {
  color: #fff;
}

/* A pressed glyph draws half a unit heavier (css/icons.ts). */
.tb-tool.is-on svg {
  stroke-width: 2.5;
}

.tb-tool::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  width: var(--tip-w, auto); /* whole pixels, as the editor sizes its tip (main.js) */
  padding: 6px 12px;
  text-align: center;
  border-radius: 12px;
  corner-shape: superellipse(2);
  background: #171717;
  box-shadow:
    0 14px 44px rgb(0 0 0 / 0.36),
    0 0 0 1px rgb(255 255 255 / 0.08),
    inset 0 1px 0 rgb(255 255 255 / 0.1);
  color: #fafafa;
  font: 400 12px/16px -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "SF Pro", ui-sans-serif, system-ui, sans-serif;
  white-space: nowrap;
  opacity: 0;
  transform: translate(-50%, 4px) scale(0.96);
  transform-origin: 50% 100%;
  transition:
    opacity 150ms ease,
    transform 220ms var(--ease-out);
  pointer-events: none;
}

.tb-tool:hover::after,
.tb-tool.show-tip::after {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Measured: centred on the tool and snapped to a whole pixel, as the editor
   places it (Math.round of the left edge). */
.tb-tool.is-sized::after {
  left: round(50% - var(--tip-w) / 2, 1px);
  transform: translateY(4px) scale(0.96);
}

.tb-tool.is-sized:hover::after,
.tb-tool.is-sized.show-tip::after {
  transform: none;
}

/* Where a note lands: ripples drawn around the pin in the screenshot, never
   over it, so each pin keeps its own number and shape (a note is round, an
   edit is square). */
.pin {
  position: absolute;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  pointer-events: none;
}

.pin::before,
.pin::after {
  content: "";
  position: absolute;
  inset: -3px;
  border: 2px solid #90a3ff;
  border-radius: 50%;
}

.pin::before {
  opacity: var(--ring, 0);
  transform: scale(calc(1 + (1 - var(--ring, 0)) * 1.8));
}

.pin::after {
  opacity: calc(var(--ring2, 0) * 0.6);
  transform: scale(calc(1 + (1 - var(--ring2, 0)) * 2.6));
}

.send-ring,
.option-ring {
  position: absolute;
  border-radius: 10px;
  box-shadow: 0 0 0 2px #90a3ff, 0 0 28px 4px rgb(121 140 255 / 0.65);
  opacity: var(--o, 0);
  transform: scale(var(--s, 1));
}

/* Pick: a note on the option to keep, drawn as the editor draws one. */
.pick-note {
  position: absolute;
  left: 560px;
  top: 700px;
  opacity: var(--o, 0);
  transform: translateY(calc((1 - var(--o, 0)) * 8px));
}

.pick-note::before {
  content: "";
  position: absolute;
  inset: -12px auto auto -12px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent-fill);
  box-shadow: 0 0 0 2px #fff, 0 6px 16px -4px rgb(0 0 0 / 0.5);
}

.pick-bubble {
  position: absolute;
  left: 22px;
  top: -8px;
  width: 300px;
  margin: 0;
  padding: 12px 14px;
  border: 1px solid rgb(255 255 255 / 0.12);
  border-radius: 12px;
  background: #181818;
  box-shadow: 0 18px 40px -12px rgb(0 0 0 / 0.7);
  color: #fafafa;
  font: 500 15px/1.45 var(--font);
}

/* ---------- Frames around real screenshots ---------- */

.frame {
  --x: 50%;
  --y: 0%;
  position: relative;
  padding: var(--frame-inset);
  border: 1px solid var(--line);
  border-radius: var(--radius-frame);
  background: rgb(255 255 255 / 0.03);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.1),
    0 24px 80px -24px rgb(0 0 0 / 0.7),
    0 0 0 1px rgb(0 0 0 / 0.6);
}

/* A light that follows the pointer across the frame's rim. */
.frame::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(400px circle at var(--x) var(--y), rgb(255 255 255 / 0.07), transparent 60%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease;
}

.frame.is-lit::after {
  opacity: 1;
}

.frame-view {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: var(--radius-shot);
  background: #171717;
  outline: 1px solid rgb(255 255 255 / 0.1);
  outline-offset: -1px;
  isolation: isolate;
}

.frame-view:focus {
  outline: 1px solid rgb(255 255 255 / 0.1);
}

.frame-view:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The hero's app sits bare, with no rim, matte or hairline around the shot:
   only its cast shadow. The planes above it cover the shot edge to edge. */
.frame.frame-hero {
  padding: 0;
  border: 0;
  border-radius: var(--radius-shot);
  background: none;
  box-shadow: 0 24px 80px -24px rgb(0 0 0 / 0.7);
}

.frame-hero .frame-view {
  outline: 0;
}

.stack > .plane {
  inset: 0;
}

/* The camera: scroll pushes in and pans across this layer, never the frame. */
.frame-zoom {
  position: absolute;
  inset: 0;
  transform-origin: 50% 50%;
  will-change: transform;
}

.frame-zoom > img,
.strip > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Every screenshot set stacks its shots and crossfades between them. */
.shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.012);
  transition:
    opacity 480ms ease,
    transform 800ms var(--ease-out);
}

.shot.is-active {
  opacity: 1;
  transform: none;
}

/* The editor, cut into the three columns it really has: the left panel, the
   app, and the inspector (measured seams: 15.59% and 81.88% of the shot).
   Each strip is the whole shot clipped to its column, so the pixels line up
   exactly at rest, and the panels can slide in from the edges they dock to. */
.strip {
  position: absolute;
  inset: 0;
  will-change: transform, opacity;
}

.frame-zoom {
  --seam-l: 0.1559;
  --seam-r: 0.8188;
}

.strip-left {
  clip-path: inset(0 calc((1 - var(--seam-l)) * 100%) 0 0);
}

.strip-center {
  clip-path: inset(0 calc((1 - var(--seam-r)) * 100%) 0 calc(var(--seam-l) * 100%));
}

.strip-right {
  clip-path: inset(0 0 0 calc(var(--seam-r) * 100%));
}

/* In the hero the panels lift off the app, where the editor's 1px divider at
   each seam would read as a light rim on the panel. Its strips stop 3 shot
   pixels short of the seam (the divider plus its resampling blur); landed,
   the gap shows the frame's #171717, the panel's own fill. */
.plane-chrome .strip-left {
  clip-path: inset(0 calc((1 - var(--seam-l)) * 100% + 0.1875%) 0 0);
}

.plane-chrome .strip-right {
  clip-path: inset(0 0 0 calc(var(--seam-r) * 100% + 0.1875%));
}

@media (max-width: 760px) {
  .hero-stage {
    --caption-h: 28px;
  }
}


/* ---------- Sections ----------
   One lockup everywhere: a centred two-tone heading on one line where it
   fits, then the section's visual below it at one width, the same distance
   away. */

:root {
  --visual: min(var(--wrap), 100vw - 2 * var(--gutter));
  --head-gap: clamp(32px, 4vw, 48px);
  /* In a tabbed section the title, the tabs and the mock sit one step apart. */
  --tabs-gap: clamp(20px, 2.2vw, 28px);
}

.section {
  position: relative;
  padding-top: var(--section);
}

.section:last-child {
  padding-bottom: var(--section);
}

.section-head {
  max-width: var(--wrap);
  margin-inline: auto;
  text-align: center;
}

h2 {
  font-family: var(--display);
  font-size: clamp(30px, 3.6vw, 50px);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.visual {
  width: var(--visual);
  max-width: 100%;
  margin: var(--head-gap) auto 0;
}

/* ---------- Switcher: a segmented control over a set of shots ---------- */

.switcher {
  margin-top: var(--tabs-gap);
}

.switcher-tabs {
  display: flex;
  justify-content: center;
  gap: 2px;
  width: max-content;
  max-width: 100%;
  margin: 0 auto var(--tabs-gap);
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: rgb(255 255 255 / 0.03);
  overflow-x: auto;
  scrollbar-width: none;
}

.switcher-tabs::-webkit-scrollbar {
  display: none;
}

.switcher-tabs button {
  flex: none;
  min-height: 36px;
  padding: 0 14px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--text-2);
  font: 500 14px/1 var(--font);
  letter-spacing: -0.01em;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 150ms var(--ease-hover), color 150ms var(--ease-hover);
}

.switcher-tabs button:hover {
  color: var(--text);
}

.switcher-tabs button[aria-selected="true"] {
  background: rgb(255 255 255 / 0.1);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.08);
}

.switcher-frame {
  width: var(--visual);
  max-width: 100%;
  margin-inline: auto;
}

/* On a phone the control spans the frame under it and the tabs share it
   equally, so the row lines up with the shot. Five or more tabs don't fit:
   they stay on one row that scrolls sideways, and main.js slides it along
   with the page scroll so the chosen tab is always in view. */
@media (max-width: 640px) {
  .switcher-tabs {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    width: 100%;
    overflow: visible;
  }

  .switcher-tabs button {
    min-width: 0;
    min-height: 40px;
    padding: 0 6px;
  }

  .switcher-tabs:has(> button:nth-of-type(5)) {
    display: flex;
    justify-content: flex-start;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--fade-l, 0px), #000 calc(100% - var(--fade-r, 24px)), transparent);
    mask-image: linear-gradient(90deg, transparent, #000 var(--fade-l, 0px), #000 calc(100% - var(--fade-r, 24px)), transparent);
  }

  .switcher-tabs:has(> button:nth-of-type(5)) button {
    flex: none;
    padding: 0 14px;
  }
}

@media (max-width: 360px) {
  .switcher-tabs button {
    font-size: 13px;
    letter-spacing: -0.015em;
  }
}

/* ---------- Terminal ---------- */

.terminal {
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-frame);
  background: #0d0d0d;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.06),
    0 24px 80px -24px rgb(0 0 0 / 0.7);
}

.terminal-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 0 6px 0 16px;
  border-bottom: 1px solid var(--line);
  color: var(--text-3);
  font-size: 13px;
}

.terminal-body {
  overflow: auto;
  padding: 14px 0;
  color: var(--text-2);
  font: 12.5px/1.75 var(--mono);
  tab-size: 2;
  scrollbar-color: rgb(255 255 255 / 0.18) transparent;
  scrollbar-width: thin;
}

.terminal-body code {
  display: block;
  min-width: max-content;
}

.terminal-body code[hidden] {
  display: none;
}

.terminal-body {
  padding-inline: 18px;
  white-space: pre;
}

.t-dim {
  color: var(--text-3);
}

.t-key {
  color: var(--accent-text);
}

.t-str {
  color: #e6c07b;
}

.t-ok {
  color: var(--add);
}

.terminal {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.terminal-dots {
  display: inline-flex;
  gap: 6px;
}

.terminal-bar .copy {
  margin-left: auto;
}

.terminal-dots i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: rgb(255 255 255 / 0.14);
}

.terminal-body {
  flex: 1;
  min-height: 320px;
  max-height: 560px;
}

@media (max-width: 920px) {
  .terminal-body {
    max-height: 420px;
  }
}

.copy {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
  transition: background-color 150ms var(--ease-hover), color 150ms var(--ease-hover);
}

.copy:hover {
  background: rgb(255 255 255 / 0.07);
  color: var(--text);
}

.copy .icon {
  grid-area: 1 / 1;
  transition-property: opacity, scale, filter;
  transition-duration: 200ms;
  transition-timing-function: cubic-bezier(0.2, 0, 0, 1);
}

.copy .icon-check {
  color: var(--add);
  opacity: 0;
  scale: 0.25;
  filter: blur(4px);
}

.copy.is-copied .icon-copy {
  opacity: 0;
  scale: 0.25;
  filter: blur(4px);
}

.copy.is-copied .icon-check {
  opacity: 1;
  scale: 1;
  filter: blur(0);
}

/* ---------- Private ---------- */

.facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-frame);
  background: var(--line);
}

.facts > div {
  padding: 24px;
  background: var(--bg);
}

.facts dt {
  font-family: var(--display);
  font-size: clamp(30px, 3.6vw, 44px);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.facts dd {
  margin-top: 8px;
  color: var(--text-2);
  font-size: 14px;
  line-height: 1.5;
}

@media (max-width: 900px) {
  .facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 420px) {
  .facts > div {
    padding: 20px 16px;
  }
}

/* ---------- Install ---------- */

.paths {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
}

.path {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 28px;
  border: 1px solid var(--line);
  border-radius: var(--radius-frame);
  background: rgb(255 255 255 / 0.02);
}

.path h3 {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
}

.path-scene {
  display: grid;
  place-items: center;
  height: 112px;
  margin-bottom: 24px;
}

/* One width for both: your app in a browser tab, and the Dock. */
.mini-window,
.mini-dock {
  width: 250px;
  border: 1px solid var(--line-strong);
  border-radius: 14px;
}

.mini-window {
  overflow: hidden;
  background: #111;
}

.mini-tabs {
  display: flex;
  align-items: end;
  gap: 4px;
  height: 22px;
  padding: 0 8px;
  background: #050505;
}

.mini-tabs > i {
  align-self: center;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #ff5f57;
}

.mini-tabs > i + i {
  background: #febc2e;
}

.mini-tabs > i + i + i {
  background: #28c840;
}

.mini-tab {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-left: 6px;
  padding: 4px 10px;
  border-radius: 6px 6px 0 0;
  background: #1c1c1c;
  color: var(--text-2);
  font: 9px/1 var(--mono);
}

/* Your app is left blank: a placeholder Design Layer outlines as it arrives. */
.mini-canvas {
  height: 68px;
  padding: 10px;
  background: #121212;
}

.mini-canvas span {
  display: grid;
  place-items: center;
  height: 100%;
  border: 1.5px dashed rgb(255 255 255 / 0.2);
  border-radius: 8px;
  color: var(--text-2);
  font: 500 12px var(--font);
  transition: 500ms var(--ease-out) 400ms allow-discrete;
  transition-property: border-color, border-style, color;
}

.scene-browser.is-in .mini-canvas span {
  border-style: solid;
  border-color: var(--accent);
  color: var(--text);
}

.mini-dock {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  background: rgb(255 255 255 / 0.06);
}

.dock-icon,
.dock-app img {
  display: block;
  width: 52px;
  height: 52px;
}

.dock-app {
  position: relative;
}

.dock-app img {
  filter: drop-shadow(0 8px 16px rgb(74 93 249 / 0.35));
}

.dock-dot {
  position: absolute;
  bottom: -6px;
  left: 50%;
  width: 4px;
  height: 4px;
  margin-left: -2px;
  border-radius: 50%;
  background: var(--text);
}

.js .scene-mac .dock-dot {
  opacity: 0;
  transition: opacity 300ms var(--ease-out) 1300ms;
}

.scene-mac.is-in .dock-app img {
  animation: dock-bounce 1000ms var(--ease-out) 300ms both;
}

.scene-mac.is-in .dock-dot {
  opacity: 1;
}

@keyframes dock-bounce {
  0%, 100% { translate: 0 0; }
  25% { translate: 0 -18px; }
  50% { translate: 0 0; }
  70% { translate: 0 -8px; }
  85% { translate: 0 0; }
}

/* Commands wrap, never scroll; a wrapped line hangs clear of the prompt. */
.path-cmd {
  position: relative;
  margin-top: auto;
  padding: 14px 48px 14px 16px;
  border-radius: 12px;
  background: rgb(255 255 255 / 0.04);
  color: var(--text);
  font: 13px/1.8 var(--mono);
}

.path-cmd pre {
  font: inherit;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.path-cmd .copy {
  position: absolute;
  top: 6px;
  right: 6px;
}

.t-cmd {
  display: block;
  padding-left: 2ch;
  text-indent: -2ch;
}

.t-cmd::before {
  content: "$ ";
  color: var(--text-3);
}

.path-lead {
  margin: 6px 0 24px;
  color: var(--text-2);
  font-size: 15px;
  line-height: 1.5;
}

.paths-note {
  margin-top: 20px;
  color: var(--text-3);
  font-size: 14px;
  text-align: center;
}

@media (max-width: 860px) {
  .paths {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 420px) {
  .path {
    padding: 20px;
  }
}

/* ---------- Contribute ---------- */

/* Three ways in, numbered with the editor's own note pins. */
.ways {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.way {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 24px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-frame);
  background: rgb(255 255 255 / 0.025);
  color: var(--text);
  text-decoration: none;
  transition:
    border-color 150ms var(--ease-hover),
    background-color 150ms var(--ease-hover),
    transform 300ms var(--ease-out);
}

.way-pin {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  margin-bottom: 20px;
  border-radius: 50%;
  background: var(--accent-fill);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--accent-fill) 45%, transparent);
  color: #fff;
  font: 600 13px/1 var(--font);
  transition: transform 300ms var(--ease-out);
}

.way h3 {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.015em;
}

.way p {
  margin-top: 8px;
  color: var(--text-2);
  font-size: 15px;
  line-height: 1.55;
}

.way-cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  padding-top: 20px;
  color: var(--accent-text);
  font-size: 14px;
  font-weight: 500;
}

.way-cta .icon {
  transition: transform 200ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .way:hover {
    border-color: color-mix(in srgb, var(--accent-text) 40%, transparent);
    background: color-mix(in srgb, var(--accent) 6%, transparent);
    transform: translateY(-2px);
  }

  .way:hover .way-pin {
    transform: scale(1.12);
  }

  .way:hover .way-cta .icon {
    transform: translateX(3px);
  }
}

@media (max-width: 860px) {
  .ways {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------- Footer ----------
   One section: gradient blinds for its background, rising out of the section
   above and running to the bottom of the page, with the end card's name and
   the links on them. */

/* The last screen: the wordmark and its inspector centered in it, as the
   focal point, and the small print along the bottom. */
.footer {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh;
  color: var(--text-2);
  font-size: 13px;
}

/* Room for the blinds to rise in, which on a phone is mostly above the links. */
.js:not(.no-webgl) .footer {
  padding-top: clamp(100px, 8vw, 120px);
}

/* Lit by a spotlight that follows the pointer (backgrounds.js). The blinds
   start where the cards above end, behind them, and run on to the bottom edge,
   under the links. */
.footer-blinds {
  --rise: clamp(160px, 25vw, 360px);
  position: absolute;
  inset: calc(-1 * var(--section)) 0 0;
  z-index: -1;
  opacity: 0;
  pointer-events: none;
  transition: opacity 1200ms var(--ease-out);
}

/* Both blinds fade in over --rise toward --fade-to, on a smoothstep curve, so
   there is no line where they begin: the footer's down from its top edge, the
   hero's up from its bottom edge. */
.hero-blinds,
.footer-blinds {
  --fade: var(--fade-to, 180deg), transparent, rgb(0 0 0 / 0.04) calc(var(--rise) * 0.125), rgb(0 0 0 / 0.16) calc(var(--rise) * 0.25), rgb(0 0 0 / 0.32) calc(var(--rise) * 0.375), rgb(0 0 0 / 0.5) calc(var(--rise) * 0.5), rgb(0 0 0 / 0.68) calc(var(--rise) * 0.625), rgb(0 0 0 / 0.84) calc(var(--rise) * 0.75), rgb(0 0 0 / 0.96) calc(var(--rise) * 0.875), #000 var(--rise);
  -webkit-mask-image: linear-gradient(var(--fade));
  mask-image: linear-gradient(var(--fade));
}

.footer-inner {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 24px;
  padding-block: 24px;
}

.footer .brand {
  color: var(--text);
  font-size: 14px;
  text-decoration: none;
}

.footer-note {
  color: var(--text-2);
}

.footer-note a {
  color: var(--text-2);
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0 4px;
  margin-left: auto;
}

/* Once the row no longer fits, the small print stacks on one left edge: the
   links pull back by their own padding so their text lines up with the note. */
@media (max-width: 1000px) {
  .footer-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
  }

  .footer-links {
    margin-left: -8px;
  }
}

.footer-links a {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 0 8px;
  color: var(--text-2);
  text-decoration: none;
  transition: color 150ms var(--ease-hover);
}

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

/* On the blinds the small print turns white. The spotlight stays above it
   (backgrounds.js), so the blinds under it hold at their floor, and it keeps
   4.5:1 wherever the pointer goes. */
.js:not(.no-webgl) .footer-note,
.js:not(.no-webgl) .footer-note a,
.js:not(.no-webgl) .footer-links a {
  color: rgb(250 250 250 / 0.82);
}

.js:not(.no-webgl) .footer-links a:hover {
  color: var(--text);
}

/* ---------- Motion: the parts main.js does not drive frame by frame ---------- */

/* Elements that rise once when they arrive. */
.js [data-rise] {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 700ms var(--ease-out),
    transform 900ms var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}

.js [data-rise].is-in {
  opacity: 1;
  transform: none;
}

/* Lists and section heads: their parts arrive in reading order. */
.js [data-reveal] > :not(.words) {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity 700ms var(--ease-out),
    transform 900ms var(--ease-out);
}

.js [data-reveal] > :nth-child(2) { transition-delay: 70ms; }
.js [data-reveal] > :nth-child(3) { transition-delay: 140ms; }
.js [data-reveal] > :nth-child(4) { transition-delay: 210ms; }
.js [data-reveal] > :nth-child(5) { transition-delay: 280ms; }
.js [data-reveal] > :nth-child(n + 6) { transition-delay: 340ms; }

.js [data-reveal].is-in > * {
  opacity: 1;
  transform: none;
}

/* Headings brighten a word at a time as they travel up the screen. main.js
   sets --lit (0 to 1) on each word from scroll; each word ends at its own
   colour, so the dim half settles dim. */
.js .words .w {
  opacity: calc(0.16 + 0.84 * var(--lit, 1));
}

/* Switchers: a pill that travels to the chosen tab. */
.switcher-tabs {
  position: relative;
}

.switcher-tabs button {
  position: relative;
  z-index: 1;
}

/* Without script the pill never moves, so the selected tab keeps its own plate. */
.js .switcher-tabs button[aria-selected="true"] {
  background: transparent;
  box-shadow: none;
}

html:not(.js) .switcher-pill {
  display: none;
}

.switcher-pill {
  position: absolute;
  top: 0;
  left: 0;
  width: 100px;
  height: 36px;
  border-radius: 9px;
  background: rgb(255 255 255 / 0.1);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.08);
  transform-origin: 0 50%;
  transition: transform 320ms var(--ease-out);
}

.switcher-pill.no-motion {
  transition: none;
}

/* While scroll walks the tabs, the chosen one fills as its stretch of scroll
   runs out, so it is clear that scrolling moves to the next. */
.switcher-tabs button::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: 4px;
  height: 2px;
  border-radius: 1px;
  background: var(--accent);
  opacity: 0;
  transform: scaleX(var(--fill, 0));
  transform-origin: 0 50%;
}

.is-scrolling .switcher-tabs button[aria-selected="true"]::after {
  opacity: 1;
}

.is-scrolling .switcher-tabs button::before {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: 4px;
  height: 2px;
  border-radius: 1px;
  background: rgb(255 255 255 / 0.1);
  opacity: 0;
  transition: opacity 200ms ease;
}

.is-scrolling .switcher-tabs button[aria-selected="true"]::before {
  opacity: 1;
}

/* Pinned tabs: the track is tall and the stage sticks; each tab owns an equal
   stretch of the scroll. Without script or with reduced motion, the stage
   sits in the page and the tabs are plain tabs. */
.js .tabs-track {
  position: relative;
  height: calc(100vh + var(--tabs, 2) * 55vh);
}

.js .tabs-stage {
  --film-w: min(var(--wrap), calc(100vw - 2 * var(--gutter)), calc((100svh - 290px) * 1.6 + 12px));
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: 100vh;
  height: 100svh;
  padding-top: 64px;
  overflow: hidden;
}

.js .tabs-stage .switcher-frame {
  width: var(--film-w);
}

.js .section-pinned {
  padding-top: calc(var(--section) - 15vh);
}

.cursor {
  display: inline-block;
  width: 0.55em;
  height: 1.1em;
  margin-left: 2px;
  background: var(--text);
  vertical-align: -0.2em;
  animation: blink 1s steps(1) 4;
}

@keyframes blink {
  50% {
    opacity: 0;
  }
}

/* ---------- Cues: pointer, clicks and keys drawn on a shot ----------
   One clock, --t (0 to 1 over the shot's scroll, from main.js; else data-rest
   or 1), plays them. Moments: --a in, --z out, --c click, --d travel, --swap. */
.frame-view {
  --t: 1;
}

/* --s is the design-system camera's zoom; cues keep their size through it. */
.shot-space {
  --cue: calc(max(1, calc(0.55 / var(--k, 0.75))) / var(--s, 1));
}

div.shot > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cue-ring {
  position: absolute;
  border-radius: 8px;
  box-shadow: 0 0 0 calc(2px / var(--s, 1)) #90a3ff, 0 0 calc(24px / var(--s, 1)) rgb(121 140 255 / 0.55);
  opacity: clamp(0, (var(--t) - var(--a)) * 10, 1);
}

.cue-type {
  position: absolute;
  left: var(--x);
  top: var(--y);
  padding: 0 3px;
  background: #252525;
  color: #ededed;
  font: 12.5px/19px var(--mono);
  white-space: nowrap;
  clip-path: inset(0 calc((1 - clamp(0, (var(--t) - var(--a)) / 0.14, 1)) * 100%) 0 0);
}

.cue-after {
  opacity: clamp(0, (var(--t) - var(--swap, 0.5)) * 400, 1);
}

/* Direct edit, typed live (main.js) in the app's own face and metrics, so it
   rewraps as the app did; the shot under it swaps on the rewrap (--swap). */
.te {
  position: absolute;
  left: 416px;
  top: 246px;
  width: 768px;
  background: #faf8f4; /* the demo app's paper, sampled from the shot */
  box-shadow: 0 0 0 14px #faf8f4;
  color: #121212;
  font: 400 60px/60px "Hedvig Letters Serif", serif;
  letter-spacing: -1.5px;
  text-align: center;
  text-wrap: balance;
  opacity: calc(clamp(0, (var(--t) - 0.3) * 400, 1) * clamp(0, (0.76 - var(--t)) * 400, 1));
}

/* The editor's text-edit outline, measured on the capture. */
.te-span {
  outline: 2px solid #8a72ee;
}

.te .is-sel {
  background: rgb(121 140 255 / 0.32);
}

.te-caret {
  display: inline-block;
  width: 2px;
  height: 54px;
  margin: 0 -1px;
  vertical-align: -10px;
  background: #121212;
  animation: caret 1s steps(1) infinite;
}

.is-sel + .te-caret {
  visibility: hidden;
}

@keyframes caret {
  50% {
    opacity: 0;
  }
}

.cue-hand,
.cue-key,
.cue-ripple,
.cue-pulse {
  position: absolute;
  left: var(--x);
  top: var(--y);
}

.cue-hand {
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-1 -1 22 28'%3E%3Cpath d='M2.5 2v18.2l4.6-4.3 2.9 6.8 3.3-1.4-2.9-6.7H16.6Z' fill='%230b0b0c' stroke='%23fff' stroke-width='1.4' stroke-linejoin='round'/%3E%3C/svg%3E") 0 0 / 100% 100% no-repeat;
  --go: clamp(0, (var(--t) - var(--a, 0)) / var(--d, 0.14), 1);
  --ease: calc(var(--go) * var(--go) * (3 - 2 * var(--go)));
  width: 26px;
  height: 34px;
  margin: -2.6px 0 0 -3.25px; /* the arrow's tip is the point */
  opacity: min(clamp(0, (var(--t) - var(--a, 0)) * 14, 1), clamp(0, (var(--z, 2) - var(--t)) * 14, 1));
  translate: calc((var(--fx) - var(--x)) * (1 - var(--ease))) calc((var(--fy) - var(--y)) * (1 - var(--ease)));
  scale: var(--cue);
  transform-origin: 12.5% 7.7%;
  filter: drop-shadow(0 2px 3px rgb(0 0 0 / 0.4));
}

.cue-ripple {
  --r: clamp(0, (var(--t) - var(--c)) * 9, 1);
  width: 44px;
  height: 44px;
  margin: -22px 0 0 -22px;
  border: 3px solid #798cff;
  border-radius: 50%;
  opacity: calc((1 - var(--r)) * clamp(0, (var(--t) - var(--c)) * 400, 1) * 0.85);
  scale: calc((0.35 + var(--r) * 1.1) * var(--cue));
}

.cue-pulse {
  --r: clamp(0, (var(--t) - var(--c)) * 7, 1);
  width: 52px;
  height: 52px;
  margin: -26px 0 0 -26px;
  border: 3px solid #90a3ff;
  border-radius: 50%;
  opacity: calc((1 - var(--r)) * clamp(0, (var(--t) - var(--c)) * 400, 1));
  scale: calc(1 + var(--r) * 0.9);
}

/* A key or gesture where it happens, in the editor's own hint pill. */
.cue-key {
  --in: clamp(0, (var(--t) - var(--a, 0)) * 10, 1);
  display: flex;
  align-items: center;
  gap: 9px;
  height: 40px;
  padding: 0 14px;
  border: 1.5px solid rgb(255 255 255 / 0.14);
  border-radius: 13px;
  background: rgb(23 23 23 / 0.94);
  box-shadow: 0 12px 32px rgb(0 0 0 / 0.38), inset 0 1px 0 rgb(255 255 255 / 0.08);
  color: #e5e5e5;
  font: 500 18px/1 var(--font);
  white-space: nowrap;
  opacity: min(var(--in), clamp(0, (var(--z, 2) - var(--t)) * 10, 1));
  translate: 0 calc((1 - var(--in)) * 8px);
  scale: var(--cue);
  transform-origin: 0 0;
}

.cue-key kbd {
  display: grid;
  place-items: center;
  min-width: 28px;
  height: 28px;
  padding: 0 6px;
  border: 0;
  border-radius: 7px;
  background: #333;
  box-shadow: inset 0 -1.5px 0 rgb(0 0 0 / 0.5), inset 0 1.5px 0 rgb(255 255 255 / 0.1);
  color: #fafafa;
  font: 600 16px/1 var(--font);
}

.cue-key kbd:first-child {
  margin-left: -8px;
}

.cue-key kbd + kbd {
  margin-left: -5px;
}

.cue-key-center {
  translate: -50% calc((1 - var(--in)) * 8px);
  transform-origin: 50% 0;
}

.cue-tag {
  border-color: rgb(144 163 255 / 0.5);
  color: #fff;
}

.cue-tag .icon {
  color: var(--accent-text);
}

/* Hide tab: the panels slide to their edges, leaving the round button, and
   return; main.js scrubs --out. At rest it is the hidden editor. */
.shot-collapse {
  --out: 1;
}

.shot-collapse .strip-left {
  transform: translateX(calc(var(--seam-l) * -100% * var(--out)));
}

.shot-collapse .strip-right {
  transform: translateX(calc((1 - var(--seam-r)) * 100% * var(--out)));
}

/* The two layouts trade places in sequence: the outgoing one dips first. */
.shot-collapse > img {
  opacity: clamp(0, calc(var(--out) * 3 - 1.2), 1);
}

.shot-collapse .strip-center {
  opacity: clamp(0, calc(1.8 - var(--out) * 3), 1);
}

/* Canvas: a pinned pull-back from the live page to the whole board. */
.canvas-track {
  position: relative;
  height: 240vh;
}

.canvas-stage {
  --film-w: min(var(--wrap), calc(100vw - 2 * var(--gutter)), calc((100svh - 230px) * 1.6 + 12px));
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--head-gap);
  height: 100vh;
  height: 100svh;
  padding-top: 64px;
  overflow: hidden;
}

.canvas-film {
  width: var(--film-w);
  margin: 0 auto;
}

.canvas-live,
.canvas-page {
  opacity: 0;
}

/* The board's Live button (src/core/css/board.ts). */
.live-badge {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 24px;
  padding: 0 9px 0 7px;
  border-radius: 12px;
  background: rgb(18 22 26 / 0.6);
  color: #fff;
  font: 500 12px/1 var(--font);
  opacity: var(--o, 1);
  translate: -100% 0;
  scale: calc(var(--cue) * 1.3);
  transform-origin: 100% 0;
}

.live-badge svg {
  fill: currentColor;
}

.board-hover {
  position: absolute;
  border-radius: 4px;
  box-shadow: 0 0 0 2px var(--accent);
  opacity: var(--o, 1);
}

/* ---------- Comment: a pinned story in four beats ----------
   The beats are tabs, like every other tabbed section. Without script (and
   with reduced motion) the stage sits in the page and the tabs switch the
   shot. With script the stage sticks and scroll walks the tabs: the toolbar
   switches to Notes, pins drop, Send to agent lights up, and the agent's
   brief streams in. */
.comment-stage {
  display: grid;
}

.comment-film {
  position: relative;
  width: var(--visual);
  margin: 0 auto;
}

.comment-stage .shot[data-beat="1"] {
  opacity: 1;
  transform: none;
}

.terminal-float {
  margin-top: 20px;
}

.terminal-float .terminal-body {
  min-height: 0;
  max-height: 360px;
}

.js .comment-track {
  position: relative;
  height: 420vh;
}

.js .comment-stage {
  --film-w: min(var(--wrap), calc(100vw - 2 * var(--gutter)), calc((100svh - 310px) * 1.6 + 12px));
  position: sticky;
  top: 0;
  align-content: center;
  height: 100vh;
  height: 100svh;
  padding-top: 64px;
  overflow: hidden;
}

.js .comment-film {
  width: var(--film-w);
}

.js .comment-stage .shot[data-beat="1"] {
  opacity: 0;
}

.js .comment-stage .shot.is-active {
  opacity: 1;
}

.js .terminal-float {
  position: absolute;
  right: -3%;
  bottom: -5%;
  z-index: 3;
  width: min(460px, 44%);
  height: 74%;
  margin: 0;
  opacity: 0;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.06),
    0 40px 120px -20px rgb(0 0 0 / 0.95),
    0 0 0 1px rgb(0 0 0 / 0.6);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
  background: rgb(13 13 13 / 0.9);
  will-change: transform, opacity;
}

.js .terminal-float .terminal-body {
  flex: 1;
  max-height: none;
  overflow: hidden;
}

.js .terminal-float code {
  will-change: transform;
}

.js .terminal-float .tl {
  opacity: clamp(0, calc(var(--reveal, 999) - var(--i)), 1);
}

@media (max-width: 760px) {
  .js .comment-track {
    height: 340vh;
  }

  .js .terminal-float {
    left: 4%;
    right: 4%;
    top: 6%;
    bottom: 6%;
    width: auto;
    height: auto;
  }
}

/* A phone fits the agent's longest line without clipping it. */
@media (max-width: 420px) {
  .js .terminal-float .terminal-body {
    padding-inline: 12px;
    font-size: 11px;
  }
}

@media (max-width: 360px) {
  .js .terminal-float .terminal-body {
    font-size: 10px;
  }
}

/* The film. */
.film {
  width: min(1200px, 100vw - 32px);
  max-width: none;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text);
  overflow: visible;
}

.film::backdrop {
  background: rgb(5 5 5 / 0.82);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.film[open] {
  animation: film-in 320ms var(--ease-out);
}

.film[open]::backdrop {
  animation: fade-in 320ms ease;
}

@keyframes film-in {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

.film-box {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-frame);
  background: #000;
  box-shadow: 0 40px 120px -20px rgb(0 0 0 / 0.9), 0 0 120px -40px rgb(121 140 255 / 0.5);
}

.film-video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  background: #000;
}

.film-close {
  position: absolute;
  top: 12px;
  right: 12px;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: rgb(0 0 0 / 0.7);
  color: var(--text);
  cursor: pointer;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.film-close .icon {
  rotate: 45deg;
}

.film-close:hover {
  background: rgb(30 30 33 / 0.9);
}

/* ---------- Review picker (only with ?review) ---------- */

.review {
  position: fixed;
  left: 50%;
  bottom: 20px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 2px;
  max-width: calc(100vw - 24px);
  padding: 4px;
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  background: rgb(20 20 22 / 0.86);
  box-shadow: 0 20px 50px -10px rgb(0 0 0 / 0.8);
  transform: translateX(-50%);
  overflow-x: auto;
  -webkit-backdrop-filter: blur(16px) saturate(1.5);
  backdrop-filter: blur(16px) saturate(1.5);
}

.review-label {
  padding: 0 10px;
  color: var(--text-2);
  font: 500 12px/1 var(--mono);
  white-space: nowrap;
}

.review button {
  min-height: 36px;
  padding: 0 14px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--text-2);
  font: 500 13px/1 var(--font);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 150ms var(--ease-hover), color 150ms var(--ease-hover);
}

.review button:hover {
  background: rgb(255 255 255 / 0.06);
  color: var(--text);
}

.review button[aria-checked="true"] {
  background: var(--text);
  color: #0b0b0b;
}

/* ---------- The end card ---------- */

/* Grain (NOISE in backgrounds.js) masked onto the CSS glow the hero falls
   back to without WebGL. */
.hero::before {
  -webkit-mask: var(--grain) 0 0 / 256px 256px;
  mask: var(--grain) 0 0 / 256px 256px;
}

/* The end card: the wordmark, selected as the editor selects a layer, and an
   inspector beside it that restyles it, and the page's accent with it. */
.endcard {
  position: relative;
  flex: 1 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(24px, 3vw, 48px);
  padding: clamp(24px, 6vw, 64px) var(--gutter);
}

.specimen {
  position: relative;
  display: inline-block;
  user-select: none;
}

.endcard-word {
  display: block;
  padding: 0 0.04em;
  font-family: var(--display);
  /* The width the wide wrap leaves beside the inspector, over the word's
     5.8em at its widest weight and spacing. */
  font-size: clamp(40px, calc((min(100vw - 2 * var(--gutter), var(--wrap-wide)) - 300px) / 5.8), 200px);
  font-weight: var(--wm-weight, 620);
  line-height: 0.92;
  letter-spacing: var(--wm-spacing, -0.06em);
  white-space: nowrap;
  --wm: color-mix(in oklab, var(--accent) 35%, rgb(255 255 255 / 0.12));
  background: linear-gradient(100deg, var(--wm) 0%, var(--wm) 40%, rgb(255 255 255 / 0.5) 50%, var(--wm) 60%, var(--wm) 100%) 100% 0 / 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  transform: translateY(18%);
  transition: transform 1400ms var(--ease-out), font-weight 300ms var(--ease-out), letter-spacing 300ms var(--ease-out);
}

.js:not(.no-webgl) .endcard-word {
  --wm: color-mix(in oklab, var(--accent) 45%, rgb(255 255 255 / 0.4));
}

.endcard.is-in .endcard-word {
  transform: none;
  animation: sweep 2600ms var(--ease-in-out) 200ms both;
}

@keyframes sweep {
  from {
    background-position: 100% 0;
  }

  to {
    background-position: 0% 0;
  }
}

/* The selection: an accent outline, four handles and the layer's name tag. */
.specimen > i,
.specimen::after,
.specimen-tag {
  position: absolute;
  opacity: 0;
  transition: opacity 400ms var(--ease-out) 900ms;
}

.specimen::after {
  content: "";
  inset: 0;
  border: 1.5px solid var(--accent);
  pointer-events: none;
}

.specimen > i {
  width: 9px;
  height: 9px;
  margin: -4px;
  border: 1.5px solid var(--accent);
  background: #fff;
}

.specimen > i:nth-of-type(1) { top: 0; left: 0; }
.specimen > i:nth-of-type(2) { top: 0; right: 0; }
.specimen > i:nth-of-type(3) { bottom: 0; left: 0; }
.specimen > i:nth-of-type(4) { bottom: 0; right: 0; }

.specimen-tag {
  bottom: 100%;
  left: -1px;
  margin-bottom: 6px;
  padding: 2px 6px;
  border-radius: 4px;
  background: var(--accent-fill);
  color: #fff;
  font: 500 11px/16px var(--font);
}

.js .endcard.is-in .specimen > *,
.js .endcard.is-in .specimen::after {
  opacity: 1;
}

/* The inspector, in the editor's dark glass, beside the wordmark. */
.tweak {
  position: relative;
  z-index: 1;
  flex: none;
  display: grid;
  gap: 18px;
  width: 248px;
  padding: 12px;
  border: 1px solid var(--line-strong);
  border-radius: 20px;
  background: rgb(23 23 23 / 0.72);
  -webkit-backdrop-filter: var(--frost);
  backdrop-filter: var(--frost);
  box-shadow: 0 24px 60px rgb(0 0 0 / 0.5);
  color: var(--text);
  font-size: 13px;
  text-align: left;
}

html:not(.js) .tweak {
  display: none;
}

.tweak-title {
  font-weight: 600;
}

.tweak-field {
  display: grid;
  gap: 8px;
}

.tweak-label {
  display: flex;
  justify-content: space-between;
  color: var(--text-2);
}

.tweak-label output {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* The color wheel, after graphicalui.com's: touching 24px swatches that slide
   under a fixed frame in the middle, a white 3px ring with the chosen color
   showing through it. main.js places each swatch and wraps them around. */
.wheel {
  position: relative;
  height: 40px;
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.wheel[data-dragging] {
  cursor: grabbing;
}

.wheel-window {
  position: relative;
  height: 100%;
  overflow: hidden;
  border-radius: 8px;
}

.wheel-window span {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 24px;
}

.wheel-frame {
  position: absolute;
  top: 0;
  left: calc(50% - 12px);
  width: 24px;
  height: 100%;
  border-radius: 6px;
  background: var(--wheel, var(--accent));
  box-shadow: 0 0 0 3px #fff, 0 2px 10px rgb(0 0 0 / 0.5);
  pointer-events: none;
  transition: scale 200ms var(--ease-out);
}

.wheel[data-dragging] .wheel-frame {
  scale: 1.06;
}

.wheel input,
.tweak-track input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.wheel:has(:focus-visible),
.tweak-track:not([data-pointer]):has(:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: 8px;
}

/* The sliders share the wheel's height and corners: a 40px track with an 8px
   radius, the fill up to the thumb, and a 24 by 32 thumb inset 4px, so its
   6px corners run parallel to the track's. main.js reads --inset and --knob-w
   to put the value under the pointer. */
.tweak-track {
  --p: 0.5;
  --inset: 4px;
  --knob-w: 24px;
  --knob-h: 32px;
  --travel: calc(100% - var(--knob-w) - 2 * var(--inset));
  position: relative;
  display: block;
  height: calc(var(--knob-h) + 2 * var(--inset));
  border-radius: 8px;
  background: rgb(255 255 255 / 0.07);
  cursor: ew-resize;
}

.tweak-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--knob-w) + 2 * var(--inset) + var(--travel) * var(--p));
  border-radius: inherit;
  background: linear-gradient(90deg, var(--accent-fill), var(--accent));
}

.tweak-ticks {
  position: absolute;
  inset: 0 calc(var(--inset) + var(--knob-w) / 2);
  display: flex;
  align-items: center;
  justify-content: space-between;
  pointer-events: none;
}

.tweak-ticks i {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: rgb(255 255 255 / 0.28);
}

.tweak-thumb {
  position: absolute;
  top: var(--inset);
  left: calc(var(--inset) + var(--travel) * var(--p));
  width: var(--knob-w);
  height: var(--knob-h);
  border-radius: calc(8px - var(--inset));
  background: #fff;
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 4px 10px rgb(0 0 0 / 0.25);
  pointer-events: none;
  transition: scale 150ms var(--ease-out);
}

.tweak-track[data-dragging] .tweak-thumb {
  scale: 1.06;
}

.tweak-track {
  touch-action: none;
  user-select: none;
}

@media (max-width: 860px) {
  .endcard {
    flex-direction: column-reverse;
    align-items: center;
  }

  .endcard-word {
    font-size: 15vw;
  }
}

/* ---------- Without script ----------
   The scenes are laid out for main.js to play. Without it, they rest in
   their final frame, stacked like the rest of the page. */

html:not(.js) .hero-track,
html:not(.js) .canvas-track {
  height: auto;
}

html:not(.js) .hero-stage,
html:not(.js) .canvas-stage {
  position: relative;
  height: auto;
  overflow: visible;
}

html:not(.js) .hero-copy {
  position: relative;
  top: auto;
  padding-top: calc(64px + clamp(48px, 8vw, 96px));
}

html:not(.js) .hero-film {
  position: relative;
  top: auto;
  left: auto;
  margin: clamp(48px, 7vw, 72px) auto 0;
}

html:not(.js) .canvas-live {
  display: none;
}

html:not(.js) .canvas-stage {
  padding-top: 0;
}

/* A phone on its side: little height, so the heading, tabs and gaps shrink
   and the shot takes the height they give back. */
@media (max-height: 520px) and (min-width: 560px) {
  .section-pinned {
    --tabs-gap: 12px;
    --head-gap: 16px;
  }

  .section-pinned h2 {
    font-size: 22px;
  }

  .section-pinned .switcher-tabs button {
    min-height: 32px;
    font-size: 13px;
  }

  .js .tabs-stage,
  .js .comment-stage,
  .canvas-stage {
    padding-top: 56px;
  }

  .js .tabs-stage {
    --film-w: min(var(--wrap), calc(100vw - 2 * var(--gutter)), calc((100svh - 170px) * 1.6 + 12px));
  }

  .js .comment-stage {
    --film-w: min(var(--wrap), calc(100vw - 2 * var(--gutter)), calc((100svh - 170px) * 1.6 + 12px));
  }

  .canvas-stage {
    --film-w: min(var(--wrap), calc(100vw - 2 * var(--gutter)), calc((100svh - 130px) * 1.6 + 12px));
  }
}

/* ---------- Motion preferences ----------
   Reduced motion keeps the story and drops the travel: no pinned scenes, no
   camera; every scene rests in its final frame, and changes still
   crossfade so nothing teleports. */

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

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }

  .hero-track,
  .canvas-track {
    height: auto;
  }

  .hero-stage,
  .canvas-stage {
    position: relative;
    height: auto;
    overflow: visible;
  }

  .hero-copy {
    position: relative;
    top: auto;
    padding-top: clamp(64px, 10vw, 112px);
  }

  .hero-film {
    position: relative;
    top: auto;
    left: auto;
    margin: clamp(48px, 7vw, 72px) auto 0;
  }

  .canvas-stage {
    padding-top: 0;
  }

  .js .section-pinned {
    padding-top: var(--section);
  }

  .js .tabs-track,
  .js #edit .tabs-track {
    height: auto;
  }

  .js #edit {
    margin-top: 0;
    padding-top: var(--section);
  }

  .js .tabs-stage {
    position: relative;
    display: block;
    height: auto;
    padding-top: 0;
    overflow: visible;
  }

  .js .tabs-stage .switcher-frame {
    width: var(--visual);
  }

  .js [data-rise],
  .js [data-reveal] > *,
  .js .words .w {
    opacity: 1;
    transform: none;
    filter: none;
  }

  .hero-film,
  .stack,
  .plane {
    transform: none !important;
  }

  .shot {
    transform: none;
  }

  .canvas-live {
    display: none;
  }

  .js .comment-track {
    height: auto;
  }

  .js .comment-stage {
    position: relative;
    height: auto;
    padding-top: 0;
    overflow: visible;
  }

  .js .comment-film {
    width: var(--visual);
  }

  .js .terminal-float {
    position: relative;
    inset: auto;
    width: auto;
    height: auto;
    margin-top: 20px;
    opacity: 1;
    transform: none;
  }

  .js .terminal-float .terminal-body {
    max-height: 360px;
    overflow: auto;
  }

  .js .terminal-float .tl {
    opacity: 1;
  }
}
