/* ============================================================
   EASING LAB V2  —  /easing-lab-v2
   A fully independent, self-contained design system. Nothing here reads
   from /styles.css or /theme.js — every color, font, and spacing value is
   defined locally. Light-only by design: one confident look, no toggle,
   no inverted sections (Page Theme Lock).

   DESIGN READ: standalone dev-tool page getting a deliberate, bright,
   heavily-rounded bento-card reskin — the owner's own words were "rounded
   corners, brighter colors, more fun" as an explicit break from the
   portfolio's quiet editorial system. Dials: VARIANCE 8 (bold color-
   blocking, numbered panel cards, big frame), MOTION 6 (the tool's own
   spring/curve engine is already highly physical; chrome motion is a
   ticker marquee + tactile button presses, nothing more), DENSITY 4
   (four clearly separated control panels, not a cockpit).

   Fonts: Fredoka (rounded, chunky, friendly — display headlines, big
   numbers, panel number badges) + Manrope (geometric, confident — all UI
   labels, buttons, body text). Neither is Fraunces or Instrument Serif;
   neither is Inter. Code stays in a real monospace stack.
   ============================================================ */

:root {
  /* ---- page-level color blocking ---- */
  --v2-bg: #FFBB3B;              /* bold orange field behind the card */
  --v2-bg-deep: #E44E1D;
  --v2-card: #FFF8EC;            /* cream mega-card */
  --v2-card-soft: #FFF1DC;
  --v2-ink: #1A1512;             /* near-black warm ink, never pure #000 */
  --v2-ink-soft: #6B5F55;

  --v2-purple: #7C5CFC;
  --v2-purple-ink: #FFF8EC;
  --v2-blue: #2F6FED;
  --v2-yellow: #FFD23F;
  --v2-yellow-ink: #1A1512;
  --v2-green: #17B978;
  --v2-green-ink: #FFF8EC;
  --v2-orange: #FF7A3D;

  --v2-line: #1A1512;            /* the one outline color, used everywhere */

  /* ---- shape scale (documented three-tier system) ----
     1. PILL   — every interactive control: buttons, tabs, segmented
        controls, selects, badges.
     2. SOFT   — panel cards, the code block, the phone's own frame
        (each panel picks its own soft radius, but always "soft", never 0).
     3. TIGHT  — small inline chips (panel number badge, numeric inputs). */
  --v2-r-pill: 999px;
  --v2-r-soft: 26px;
  --v2-r-tight: 12px;

  --v2-gutter: 40px;
  --v2-nav: 76px;

  /* The page's own chrome finally gets some easing too — a confident
     ease-out-expo for things opening/appearing, a gentle back-out for
     playful confirmations (checked/toggled state). */
  --v2-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --v2-ease-back: cubic-bezier(0.34, 1.56, 0.64, 1);

  --wf-1: #F3A65A;   /* warm apricot — title bars */
  --wf-2: #FFD9A6;   /* lighter warm — secondary bars */
  --wf-3: #FFE9CC;   /* faintest warm — background bars/dividers */
  --wf-accent: #7C5CFC;
  --wf-accent-ink: #FFF8EC;
  --wf-surface: #FFFDF8;
  --wf-device: #1A1512;
}

* { box-sizing: border-box; }

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

body.v2-body {
  margin: 0;
  background: var(--v2-bg);
  color: var(--v2-ink);
  font-family: 'Manrope', 'Segoe UI', sans-serif;
  min-height: 100dvh;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--v2-ink);
  color: var(--v2-card);
  padding: 10px 16px;
  border-radius: 0 0 var(--v2-r-tight) 0;
  z-index: 100;
}
.skip-link:focus { left: 0; }

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

/* ---------------- graph-paper field behind everything ---------------- */

.v2-field {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(26, 21, 18, 0.07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(26, 21, 18, 0.07) 1px, transparent 1px);
  background-size: 34px 34px;
}

/* ---------------- header ---------------- */

.v2-header {
  position: relative;
  z-index: 2;
  height: var(--v2-nav);
  display: flex;
  align-items: center;
}
.v2-header-inner {
  width: 100%;
  max-width: 1360px;
  margin: 0 auto;
  padding: 0 var(--v2-gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.v2-mark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: 'Fredoka', sans-serif;
  font-weight: 600;
  font-size: 30px;
  color: var(--v2-ink);
  letter-spacing: -0.01em;
}
.v2-mark svg { flex: none; }
.v2-back {
  display: inline-flex;
  align-items: center;
  height: 40px;
  padding: 0 18px;
  border-radius: var(--v2-r-pill);
  background: var(--v2-ink);
  color: var(--v2-card);
  font-family: 'Manrope', sans-serif;
  font-weight: 700;
  font-size: 13px;
  border: 2px solid var(--v2-ink);
  transition: transform 0.15s var(--v2-ease-out);
}
.v2-back:hover { transform: translateY(-1px); }
.v2-back:active { transform: translateY(0) scale(0.98); }

/* ---------------- ticker ---------------- */

/* ---------------- the mega card ---------------- */

.v2-card {
  position: relative;
  z-index: 1;
  max-width: 1360px;
  margin: 0 auto 56px;
  padding: 30px var(--v2-gutter) 36px;
  background: var(--v2-card);
  border: 6px solid var(--v2-line);
  border-radius: clamp(24px, 4vw, 44px);
  box-shadow: 10px 10px 0 var(--v2-ink);
}

/* Named areas, not just grid-column/row on each item, specifically so the
   mobile breakpoint can freely reflow which row things sit in (see
   responsive section) without moving anything in the DOM. This also fixes
   a real sticky-positioning bug: position:sticky only holds a box in place
   for as long as scrolling stays within that box's OWN PARENT — earlier,
   .ease-preview-block was nested a level deeper inside .ease-stage-col
   alongside the Duration panel, so its "room to stick" was capped at
   stage-col's own (short) height and it released the moment you scrolled
   into the rail below, which is exactly the control area you're trying to
   watch the preview WHILE using. Making .ease-preview-block a DIRECT child
   of .ease-app (a tall grid spanning the whole page) instead gives it the
   entire page's height to stick within. */
.ease-app {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 372px;
  grid-template-areas:
    "head  rail"
    "stage rail";
  grid-template-rows: auto minmax(0, 1fr);
  column-gap: 34px;
  row-gap: 14px;
  /* stretch, not start — the stage fills the same height as the rail
     column. The rail's own box height is locked to its initial
     (Controls-tab) height by JS once on boot (see initRailTabs()), so
     switching to the shorter Output tab changes only the sidebar's
     content, never the stage's size. */
  align-items: stretch;
}

.ease-stage-head { grid-area: head; }
.ease-preview-block { grid-area: stage; min-width: 0; }
.ease-rail { grid-area: rail; }

/* Component + Direction sit side by side within their panel so toggling
   a direction-having component (drawer, push, toast) never changes the
   panel's own height — Direction just occupies its own column instead
   of stacking a new row. Both are stacked full-width instead now that
   this panel lives in the narrow rail (372px) rather than the wide
   below-stage slot it used to — 300px+220px side by side doesn't fit a
   336px-wide interior. */
#ease-component-group,
#ease-dir-group { width: 100%; }

/* ---------------- heading ---------------- */

.ease-stage-head {
  padding-top: 4px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px 16px;
}

.v2-eyebrow-badge {
  display: inline-flex;
  align-items: center;
  flex: none;
  height: 28px;
  padding: 0 14px;
  border-radius: var(--v2-r-pill);
  background: var(--v2-yellow);
  border: 2px solid var(--v2-line);
  color: var(--v2-ink);
  font-family: 'Manrope', sans-serif;
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.v2-title {
  font-family: 'Fredoka', sans-serif;
  font-weight: 700;
  font-size: 42px;
  line-height: 1;
  letter-spacing: -0.01em;
  margin: 0;
  color: var(--v2-ink);
}

.ease-sub {
  margin: 0;
  font-size: 15px;
  line-height: 1.5;
  color: #3E332B;
  max-width: 46ch;
  font-weight: 600;
}

.v2-panel-green .ease-sub {
  font-size: 12.5px;
  line-height: 1.45;
  color: #3E332B;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* ---------------- the stage: a phone ---------------- */

.ease-stage {
  /* Fills .ease-preview-block, which the grid stretches to match the
     rail column's own height (align-items:stretch on .ease-app). The
     rail's box height is locked by JS to its initial Controls-tab
     height (see initRailTabs()), so this stays constant across
     Controls/Output tab switches even though it's not a fixed number
     here. */
  height: 100%;
  min-height: 340px;
  position: relative;
  display: grid;
  padding: 26px;
  overflow: hidden;
  background: #FFDA82;
  background-image:
    radial-gradient(rgb(34 28 20 / 9%) 1.4px, transparent 1.4px);
  background-size: 22px 22px;
  border: 4px solid var(--v2-line);
  border-radius: var(--v2-r-soft);
}

/* THE MEASUREMENT FIX — read before touching this file.
   .ease-phone establishes container-type:size for its own children's --pt
   units (see .ease-viewport below), but its OWN border-radius/padding must
   never consume a cqw unit derived from that same container — that self-
   reference is the exact bug documented in easing-lab-v2.js's
   syncFramePoints() comment. --phone-w here is a plain px custom property
   written by JS via getBoundingClientRect() + style.setProperty(), not a
   query unit. v2 deliberately uses a much bigger ratio (0.19) than the
   quiet /easing-lab page (0.102) — a big, friendly, heavily-rounded frame
   is the point of this reskin — but the MECHANISM is identical. */
.ease-phone {
  height: min(100%, 760px);
  aspect-ratio: 402 / 874;
  justify-self: center;
  align-self: center;
  --phone-w: 280px;
  padding: calc(var(--phone-w) * 0.024);
  background: var(--wf-device);
  border-radius: calc(var(--phone-w) * 0.19);
}

/* Reference width: 402pt, the iPhone 17, per Apple's HIG device table
   (developer.apple.com/design/human-interface-guidelines/layout,
   "Specifications" -> device screen dimensions), fetched 2026-08-24.
   Interior radius = outer radius ratio minus padding ratio, same
   concentric-corners relationship the quiet page uses (HIG Toolbars,
   "Best practices": radii should be "concentric with the bar's corners"). */
.ease-viewport {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--wf-surface);
  container-type: size;
  --pt: calc(100cqw / 402);
  border-radius: calc(var(--phone-w) * 0.166);
}

/* ---------------- wireframe atoms (colored, not grey) ---------------- */

.wf-bar {
  display: block;
  height: calc(17 * var(--pt));
  border-radius: calc(8 * var(--pt));
  background: var(--wf-2);
  flex: none;
}
.wf-bar.wf-faint { background: var(--wf-3); }
.wf-bar.wf-title { height: calc(21 * var(--pt)); border-radius: calc(9 * var(--pt)); background: var(--wf-1); }
.wf-bar.wf-hero-title { height: calc(32 * var(--pt)); border-radius: calc(11 * var(--pt)); background: var(--wf-1); }

.wf-dot {
  display: block;
  width: calc(44 * var(--pt)); height: calc(44 * var(--pt));
  border-radius: 50%;
  background: var(--wf-2);
  flex: none;
}
.wf-dot-lg { width: calc(50 * var(--pt)); height: calc(50 * var(--pt)); }

.wf-btn {
  display: block;
  height: calc(46 * var(--pt));
  border-radius: var(--v2-r-pill);
  background: var(--wf-accent);
  flex: none;
}
.wf-btn-ghost { background: var(--wf-3); }

.wf-lines { flex: 1; display: flex; flex-direction: column; gap: calc(11 * var(--pt)); min-width: 0; }

.wf-ic {
  flex: none;
  width: calc(34 * var(--pt)); height: calc(34 * var(--pt));
  border-radius: calc(11 * var(--pt));
  background: var(--wf-2);
}
.wf-ic-ok { border-radius: 50%; background: var(--v2-green); }

.wf-tab {
  display: block;
  width: calc(32 * var(--pt)); height: calc(32 * var(--pt));
  border-radius: calc(11 * var(--pt));
  background: var(--wf-2);
  flex: none;
}
.wf-tab.is-on { background: var(--wf-accent); }

/* ---------------- base screen ---------------- */

.ease-base,
.ease-detail {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--wf-surface);
  will-change: transform;
  backface-visibility: hidden;
}

.ease-detail { display: none; z-index: 3; }
.ease-viewport[data-kind="push"] .ease-detail { display: flex; }
.ease-viewport[data-kind="push"] .ease-comp { display: none; }

.wf-header {
  display: flex; align-items: center;
  padding: calc(28 * var(--pt)) calc(22 * var(--pt)) calc(18 * var(--pt));
  flex: none;
}
.wf-header-back { gap: calc(16 * var(--pt)); }
.wf-back {
  display: block;
  width: calc(13 * var(--pt)); height: calc(13 * var(--pt));
  border-left: calc(3 * var(--pt)) solid var(--wf-1);
  border-bottom: calc(3 * var(--pt)) solid var(--wf-1);
  transform: rotate(45deg);
  flex: none;
}

.wf-list { flex: 1; overflow: hidden; }

.wf-row {
  display: flex; align-items: center;
  gap: calc(18 * var(--pt));
  padding: calc(20 * var(--pt)) calc(22 * var(--pt));
}
.wf-list .wf-row { border-bottom: 1px solid var(--wf-3); }
.wf-row span {
  flex: 1; display: flex; flex-direction: column;
  gap: calc(11 * var(--pt)); min-width: 0;
}

.wf-tabs {
  display: flex; justify-content: space-around; align-items: center;
  padding: calc(18 * var(--pt)) calc(28 * var(--pt)) calc(24 * var(--pt));
  border-top: 1px solid var(--wf-3);
  flex: none;
}

.wf-detail-body {
  flex: 1;
  display: flex; flex-direction: column; gap: calc(18 * var(--pt));
  padding: calc(28 * var(--pt)) calc(22 * var(--pt)) calc(22 * var(--pt));
}
.wf-hero { display: block; height: 28%; border-radius: calc(14 * var(--pt)); background: var(--wf-3); flex: none; }
.wf-detail-body .wf-btn { margin-top: auto; }

.wf-nav { display: flex; align-items: center; gap: calc(18 * var(--pt)); padding: calc(18 * var(--pt)) calc(22 * var(--pt)); }

/* ---------------- the moving component ---------------- */

.ease-scrim {
  position: absolute;
  inset: 0;
  background: #1A1512;
  opacity: 0;
  z-index: 1;
}

.ease-comp {
  position: absolute;
  z-index: 2;
  background: var(--v2-card);
  will-change: transform, opacity;
  backface-visibility: hidden;
}

.ease-viewport[data-kind="sheet"] .ease-comp {
  left: 0; right: 0; bottom: 0;
  height: 58%;
  padding: calc(14 * var(--pt)) calc(10 * var(--pt)) calc(22 * var(--pt));
  display: flex; flex-direction: column; gap: calc(10 * var(--pt));
  border-radius: calc(30 * var(--pt)) calc(30 * var(--pt)) 0 0;
  box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.18);
}
.ease-viewport[data-kind="sheet"] .ease-comp > .wf-btn { margin-top: auto; height: calc(70 * var(--pt)); }

.wf-sheet-icon {
  align-self: center;
  flex: none;
  width: calc(64 * var(--pt)); height: calc(64 * var(--pt));
  border-radius: 50%;
  background: var(--v2-purple);
  margin: calc(4 * var(--pt)) 0 calc(2 * var(--pt));
}

.wf-sheet-copy {
  flex: 1 1 auto;
  min-height: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: calc(10 * var(--pt));
  padding: 0 calc(22 * var(--pt));
  overflow: hidden;
}
.wf-sheet-copy .wf-bar { align-self: center; }

.wf-sheet-tertiary {
  align-self: center;
  flex: none;
  height: calc(13 * var(--pt));
  background: var(--wf-2);
  margin-top: calc(-2 * var(--pt));
}

.wf-grab {
  display: block;
  width: calc(40 * var(--pt)); height: calc(5 * var(--pt));
  border-radius: calc(2.5 * var(--pt));
  background: var(--wf-2);
  align-self: center;
  margin-bottom: calc(10 * var(--pt));
  flex: none;
}

.ease-viewport[data-kind="drawer"] .ease-comp {
  top: 0; bottom: 0;
  width: 76%;
  padding: calc(24 * var(--pt)) calc(10 * var(--pt));
  display: flex; flex-direction: column;
  box-shadow: 0 0 34px rgba(0, 0, 0, 0.24);
}
.ease-viewport[data-kind="drawer"][data-edge="left"] .ease-comp { left: 0; }
.ease-viewport[data-kind="drawer"][data-edge="right"] .ease-comp { right: 0; }
.wf-drawer-top {
  display: flex; align-items: center; gap: calc(16 * var(--pt));
  padding: 0 calc(12 * var(--pt)) calc(20 * var(--pt));
  margin-bottom: calc(6 * var(--pt));
  border-bottom: 1px solid var(--wf-3);
}

.ease-viewport[data-kind="menu"] .ease-comp {
  top: calc(70 * var(--pt)); right: calc(14 * var(--pt));
  width: 54%;
  padding: calc(4 * var(--pt)) 0;
  display: flex; flex-direction: column;
  transform-origin: top right;
  border-radius: calc(18 * var(--pt));
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.22);
}
.ease-comp .wf-nav { padding: calc(16 * var(--pt)) calc(12 * var(--pt)); }
.ease-viewport[data-kind="menu"] .wf-nav {
  gap: calc(12 * var(--pt));
  padding: calc(13 * var(--pt)) calc(16 * var(--pt));
}
.ease-viewport[data-kind="menu"] .wf-ic {
  width: calc(22 * var(--pt)); height: calc(22 * var(--pt));
  border-radius: calc(7 * var(--pt));
}
.ease-viewport[data-kind="menu"] .wf-nav + .wf-nav,
.ease-viewport[data-kind="drawer"] .wf-nav + .wf-nav,
.ease-viewport[data-kind="sheet"] .wf-nav + .wf-nav { border-top: 1px solid var(--wf-3); }

.ease-viewport[data-kind="modal"] .ease-comp {
  inset: 0;
  padding: 0 calc(24 * var(--pt));
  display: flex; align-items: center; justify-content: center;
  background: transparent;
}
.wf-card {
  width: 100%;
  background: var(--v2-card);
  padding: calc(30 * var(--pt)) calc(24 * var(--pt));
  display: flex; flex-direction: column; align-items: center; gap: calc(16 * var(--pt));
  border-radius: calc(24 * var(--pt));
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.28);
}
.wf-card .wf-title { margin-bottom: calc(2 * var(--pt)); }
.wf-card .wf-actions { width: 100%; }
.wf-actions { display: flex; gap: calc(12 * var(--pt)); margin-top: calc(8 * var(--pt)); }
.wf-actions .wf-btn { flex: 1; }

.ease-viewport[data-kind="toast"] .ease-comp {
  left: 0; right: 0;
  background: transparent;
  padding: 0 calc(14 * var(--pt));
  display: flex; justify-content: center;
}
.ease-viewport[data-kind="toast"][data-edge="top"] .ease-comp { top: 0; padding-top: calc(96 * var(--pt)); }
.ease-viewport[data-kind="toast"][data-edge="bottom"] .ease-comp { bottom: 0; padding-bottom: calc(78 * var(--pt)); }
.wf-toast {
  display: flex; align-items: center; gap: calc(12 * var(--pt));
  padding: calc(16 * var(--pt)) calc(20 * var(--pt));
  width: 76%;
  background: var(--v2-card);
  border-radius: var(--v2-r-pill);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.2);
}
.wf-toast .wf-ic { width: calc(26 * var(--pt)); height: calc(26 * var(--pt)); flex: none; }
.wf-toast .wf-bar { height: calc(19 * var(--pt)); flex: 1 1 auto; }

/* ---------------- segmented controls ---------------- */

.ease-seg {
  display: inline-flex;
  border-radius: var(--v2-r-pill);
  border: 2px solid var(--v2-line);
  overflow: hidden;
  background: var(--v2-card);
}
.ease-seg button {
  appearance: none;
  border: 0;
  border-left: 2px solid var(--v2-line);
  background: transparent;
  color: var(--v2-ink);
  font-family: 'Manrope', sans-serif;
  font-weight: 700;
  font-size: 13px;
  line-height: 1;
  height: 38px;
  min-width: 46px;
  padding: 0 10px;
  cursor: pointer;
  transition: background 0.15s var(--v2-ease-out), color 0.15s var(--v2-ease-out);
}
.ease-seg button:first-child { border-left: 0; }
.ease-seg button:hover { background: var(--v2-card-soft); }
.ease-seg button.is-on {
  background: var(--v2-ink);
  color: var(--v2-card);
}
.ease-seg-dir { width: 100%; }
.ease-seg-dir button { flex: 1; font-size: 16px; }

/* ---------------- transport: floats inside the stage ---------------- */

.ease-transport {
  position: absolute;
  right: 16px;
  bottom: 16px;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 8px;
}

.ease-icon-btn {
  appearance: none;
  position: relative;
  width: 38px;
  height: 38px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  border-radius: var(--v2-r-tight);
  border: 2.5px solid var(--v2-line);
  background: var(--v2-card);
  color: var(--v2-ink);
  cursor: pointer;
  box-shadow: 3px 3px 0 var(--v2-ink);
  transition: transform 0.15s var(--v2-ease-back), background 0.15s var(--v2-ease-out), color 0.15s var(--v2-ease-out);
}
.ease-icon-btn:hover { transform: translate(-1px, -1px); }
.ease-icon-btn:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 var(--v2-ink); }
.ease-icon-btn[aria-pressed="true"],
.ease-icon-btn.is-playing {
  background: var(--v2-ink);
  color: var(--v2-card);
}

.ease-icon-btn[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 5px 10px;
  border-radius: 7px;
  background: var(--v2-ink);
  color: var(--v2-card);
  font-family: 'Manrope', sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.01em;
  opacity: 0;
  transform: translateX(-50%) translateY(3px);
  pointer-events: none;
  transition: opacity 0.12s var(--v2-ease-out), transform 0.12s var(--v2-ease-out);
  z-index: 10;
}
.ease-icon-btn:last-child[data-tip]::after { left: auto; right: -2px; transform: translateY(3px); }
.ease-icon-btn:last-child[data-tip]:hover::after,
.ease-icon-btn:last-child[data-tip]:focus-visible::after { transform: translateY(0); }
.ease-icon-btn[data-tip]:hover::after,
.ease-icon-btn[data-tip]:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.ease-play-icon,
.ease-pause-icon { display: block; }
.ease-pause-icon { display: none; }
.ease-icon-btn.is-playing .ease-play-icon { display: none; }
.ease-icon-btn.is-playing .ease-pause-icon { display: block; }

/* ---------------- control rail: numbered color panels ---------------- */

.ease-rail {
  display: flex;
  flex-direction: column;
  gap: 24px;
  /* Fallback for whatever doesn't fit after the trims above: the rail
     scrolls internally rather than the whole page, so the phone stays
     visible instead of scrolling off with everything else — the same
     pattern the quieter sibling page already uses. Bounded to the
     viewport height minus the header/card chrome above it, PLUS the
     14px padding-top added below — otherwise that padding eats into the
     real content budget and the rail becomes scrollable by exactly 14px
     even when nothing inside it actually overflows, which reads as a
     scrollbar with nothing to scroll. */
  max-height: calc(100vh - var(--v2-nav) - 76px + 20px);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* .v2-panel-num pokes up -14px above its panel, and overflow-y:auto
     clips anything above this box's own top edge. margin-top cancels
     padding-top visually (net zero — no visible gap, panels sit exactly
     where they would with neither set) while still giving the scrollable
     box's internal coordinate space room above the content for the badge
     to render into instead of being clipped. */
  margin-top: -14px;
  padding-top: 14px;
}

.v2-panel {
  position: relative;
  padding: 18px 18px 16px;
  border-radius: var(--v2-r-soft);
  border: 4px solid var(--v2-line);
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.v2-panel-num {
  position: absolute;
  top: -14px;
  left: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--v2-ink);
  color: var(--v2-card);
  font-family: 'Fredoka', sans-serif;
  font-weight: 600;
  font-size: 13px;
  box-shadow: 0 0 0 2px var(--v2-line);
}

.v2-panel-orange { background: var(--v2-orange); }
.v2-panel-purple { background: var(--v2-purple); color: var(--v2-purple-ink); }
.v2-panel-yellow { background: var(--v2-yellow); }
/* Component is the only panel using this color now (after the
   orange/green swap) — ink text throughout, not the cream that suited
   green back when it was the darker Output panel. */
.v2-panel-green  { background: var(--v2-green); color: var(--v2-ink); }

.v2-panel-orange .ease-label,
.v2-panel-yellow .ease-label,
.v2-panel-green .ease-label { color: var(--v2-ink); }
.v2-panel-purple .ease-label { color: var(--v2-card); }

.v2-panel-orange .ease-sub-title,
.v2-panel-purple .ease-label output,
.v2-panel-yellow .ease-label output { color: inherit; }

.ease-group { display: flex; flex-direction: column; gap: 7px; }

.ease-panel { display: flex; flex-direction: column; gap: 12px; }

.ease-app [hidden] { display: none !important; }

.ease-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--v2-ink-soft);
}

.ease-panel-title { font-size: 14px; }

.ease-label output {
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--v2-ink);
}
.v2-panel-purple .ease-label output,
.v2-panel-green .ease-label output { color: var(--v2-card); }

/* selects */
.ease-select { position: relative; display: block; }
.ease-select select {
  appearance: none;
  width: 100%;
  height: 40px;
  padding: 0 36px 0 14px;
  border-radius: var(--v2-r-pill);
  border: 3px solid var(--v2-line);
  background: var(--v2-card);
  color: var(--v2-ink);
  font-family: 'Manrope', sans-serif;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
}
.ease-select svg {
  position: absolute;
  right: 14px;
  top: 50%;
  margin-top: -7px;
  pointer-events: none;
  color: var(--v2-ink);
}

/* custom dropdown: button replaces the select visually, select stays as
   the hidden source of truth (see enhanceSelect() in the JS) */
.ease-select-btn {
  appearance: none;
  display: block;
  width: 100%;
  height: 40px;
  padding: 0 36px 0 14px;
  border-radius: var(--v2-r-pill);
  border: 3px solid var(--v2-line);
  background: var(--v2-card);
  color: var(--v2-ink);
  font-family: 'Manrope', sans-serif;
  font-weight: 700;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s var(--v2-ease-out), opacity 0.15s var(--v2-ease-out);
}
.ease-select-btn:focus-visible { outline: 2px solid var(--v2-purple); outline-offset: 2px; }
.ease-select.is-open .ease-select-btn { border-color: var(--v2-purple); }
.ease-select-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.ease-select-menu {
  /* position:fixed + appended to <body> (see enhanceSelect() in the JS),
     not absolute inside .ease-select — the rail this can live in scrolls
     internally via overflow-y:auto, which clips any normal in-flow
     popup that extends past its box. Left/top/width are set inline by
     positionMenu() from the button's own live boundingClientRect. */
  position: fixed;
  z-index: 100;
  max-height: 264px;
  overflow-y: auto;
  padding: 6px;
  border-radius: var(--v2-r-tight);
  border: 3px solid var(--v2-line);
  background: var(--v2-card);
  box-shadow: 4px 4px 0 var(--v2-ink);
  opacity: 0;
  transform: translateY(-4px) scale(0.98);
  pointer-events: none;
  transition: opacity 0.18s var(--v2-ease-out), transform 0.18s var(--v2-ease-out);
}
.ease-select-menu.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}
.ease-select-option {
  padding: 9px 10px;
  border-radius: calc(var(--v2-r-tight) - 4px);
  font-family: 'Manrope', sans-serif;
  font-weight: 700;
  font-size: 13.5px;
  color: var(--v2-ink);
  cursor: pointer;
  transition: background 0.12s var(--v2-ease-out);
}
/* :hover is plain CSS so it's instant and never depends on the JS
   mouseenter listener (which drives keyboard arrow-key highlighting via
   the same .is-active class) — belt and suspenders for the same visual. */
.ease-select-option:hover,
.ease-select-option.is-active { background: rgba(124, 92, 252, 0.16); }
.ease-select-option.is-selected { background: var(--v2-ink); color: var(--v2-card); }
.ease-select-option.is-selected:hover,
.ease-select-option.is-selected.is-active { background: var(--v2-ink); }

/* tabs */
.ease-tabs {
  display: flex;
  border-radius: var(--v2-r-pill);
  border: 3px solid var(--v2-line);
  overflow: hidden;
  background: var(--v2-card);
}
.ease-tabs button {
  appearance: none;
  flex: 1;
  border: 0;
  border-left: 3px solid var(--v2-line);
  background: transparent;
  color: var(--v2-ink);
  font-family: 'Manrope', sans-serif;
  font-weight: 800;
  font-size: 13px;
  height: 38px;
  cursor: pointer;
  transition: background 0.15s var(--v2-ease-out), color 0.15s var(--v2-ease-out);
}
.ease-tabs button:first-child { border-left: 0; }
.ease-tabs button.is-on { background: var(--v2-ink); color: var(--v2-card); }

/* ---------------- rail: Controls / Output switcher ---------------- */

/* Filled-segment toggle, the same pattern as .ease-tabs (Curve/Spring)
   elsewhere on the page — a stacked base-line-plus-underline treatment
   here read as two competing lines rather than one clear indicator. */
.ease-rail-tabs {
  flex: none;
  display: flex;
  border-radius: var(--v2-r-pill);
  border: 3px solid var(--v2-line);
  overflow: hidden;
  background: var(--v2-card);
}
.ease-rail-tabs button {
  appearance: none;
  flex: 1 1 50%;
  border: 0;
  border-left: 3px solid var(--v2-line);
  background: transparent;
  height: 46px;
  text-align: center;
  font-family: 'Manrope', sans-serif;
  font-weight: 800;
  font-size: 15px;
  letter-spacing: 0.01em;
  color: var(--v2-ink);
  cursor: pointer;
  transition: background 0.15s var(--v2-ease-out), color 0.15s var(--v2-ease-out);
}
.ease-rail-tabs button:first-child { border-left: 0; }
.ease-rail-tabs button.is-on { background: var(--v2-ink); color: var(--v2-card); }

.ease-rail-view {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* ---------------- curve graph ---------------- */

.ease-graph-wrap { display: flex; flex-direction: column; gap: 6px; }

.ease-graph {
  width: 100%;
  aspect-ratio: 1;
  height: auto;
  display: block;
  background: var(--v2-card);
  border-radius: var(--v2-r-tight);
  border: 3px solid var(--v2-line);
  touch-action: none;
}

.ease-dotgrid circle { fill: rgb(34 28 20 / 9%); }
.ease-dots { pointer-events: none; }

.ease-grid line { stroke: rgba(26, 21, 18, 0.18); stroke-width: 1; }
.ease-graph-unit { fill: none; stroke: none; }

.ease-curve { fill: none; stroke: var(--v2-ink); stroke-width: 2.6; stroke-linecap: round; }

.ease-handle-arm { stroke: var(--v2-orange); stroke-width: 1.6; opacity: 1; }
.ease-handle { fill: var(--v2-orange); stroke: var(--v2-card); stroke-width: 2; cursor: grab; }
.ease-handle:active { cursor: grabbing; }
.ease-handle:focus-visible {
  outline: 2px solid var(--v2-ink);
  outline-offset: 3px;
}
.ease-handles[hidden] { display: none; }

.ease-playhead { fill: var(--v2-ink); stroke: var(--v2-card); stroke-width: 2; }

.ease-graph-note { font-size: 11px; font-weight: 700; color: rgba(255,248,236,0.85); }

/* ---------------- numeric bezier fields ---------------- */

.ease-numrow {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.ease-num {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ease-num span {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255,248,236,0.8);
}
.ease-num input {
  appearance: textfield;
  width: 100%;
  height: 32px;
  padding: 0 8px;
  border-radius: var(--v2-r-tight);
  border: 3px solid var(--v2-line);
  background: var(--v2-card);
  color: var(--v2-ink);
  font-family: 'Manrope', sans-serif;
  font-weight: 700;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
.ease-num input::-webkit-outer-spin-button,
.ease-num input::-webkit-inner-spin-button { margin: 0; }

/* ---------------- sliders ---------------- */

.ease-slider-row input[type="range"] {
  appearance: none;
  width: 100%;
  height: 24px;
  background: transparent;
  cursor: pointer;
}
.ease-slider-row input[type="range"]::-webkit-slider-runnable-track {
  height: 5px; border-radius: var(--v2-r-pill); background: rgba(26, 21, 18, 0.22);
}
.v2-panel-purple .ease-slider-row input[type="range"]::-webkit-slider-runnable-track,
.v2-panel-green .ease-slider-row input[type="range"]::-webkit-slider-runnable-track {
  background: rgba(255, 248, 236, 0.3);
}
.ease-slider-row input[type="range"]::-moz-range-track { height: 5px; border-radius: var(--v2-r-pill); background: rgba(26, 21, 18, 0.22); }
.ease-slider-row input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  width: 18px; height: 18px; margin-top: -7px;
  border: 3px solid var(--v2-ink); border-radius: 50%;
  background: var(--v2-card);
}
.ease-slider-row input[type="range"]::-moz-range-thumb {
  width: 18px; height: 18px; border: 3px solid var(--v2-ink); border-radius: 50%; background: var(--v2-card);
}

.ease-derived-val { margin: 0; font-size: 13px; font-weight: 700; color: rgba(26,21,18,0.72); line-height: 1.4; }
.ease-derived-val strong {
  display: block;
  font-family: 'Fredoka', sans-serif;
  font-weight: 700;
  font-size: 30px;
  color: var(--v2-ink);
  font-variant-numeric: tabular-nums;
}

/* ---------------- output ---------------- */

.ease-out { gap: 10px; }

.ease-code {
  margin: 0;
  padding: 12px 14px;
  border-radius: 16px;
  border: 2px solid var(--v2-line);
  background: var(--v2-ink);
  color: #C9F7E1;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
}

.ease-out-actions { display: flex; gap: 8px; flex-wrap: wrap; }

.ease-copy {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 38px;
  padding: 0 16px;
  border-radius: var(--v2-r-pill);
  border: 2px solid var(--v2-line);
  background: var(--v2-card);
  color: var(--v2-ink);
  font-family: 'Manrope', sans-serif;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  transition: transform 0.15s var(--v2-ease-back);
}
.ease-copy:hover { transform: translateY(-1px); }
.ease-copy:active { transform: scale(0.97); }
.ease-copy-primary {
  background: var(--v2-ink);
  color: var(--v2-card);
  border-color: var(--v2-ink);
}

/* ---------------- responsive ---------------- */

@media (max-width: 999px) {
  .v2-card { margin: 0 20px 40px; padding: 26px 22px 28px; }

  /* Re-flow into one column: head, then the (sticky) preview, then the
     rail (its own Controls/Output tabs on top). .ease-preview-block
     being a DIRECT child of this grid (not nested inside another
     element) is what makes the sticky fix below actually work — see the
     long comment on .ease-app above for why that nesting mattered. */
  .ease-app {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "head"
      "stage"
      "rail";
    grid-template-rows: none;
    row-gap: 0;
  }
  .ease-rail { margin-top: 22px; }

  /* The actual mobile complaint: adjusting a control meant scrolling the
     phone preview off-screen, so you couldn't see what you'd just changed.
     .v2-header isn't sticky itself (it's fine to let it scroll away), so
     the preview block pins flush to the very top of the viewport once you
     scroll past it, and stays there while Duration and the rail's controls
     scroll underneath. */
  .ease-preview-block {
    position: sticky;
    top: 0;
    z-index: 5;
    margin: 0 -22px;
    padding: 14px 22px 16px;
    background: var(--v2-card);
    box-shadow: 0 18px 22px -16px rgba(26, 21, 18, 0.35);
  }
  /* A bounded height, not just a floor — this box is pinned now, so it must
     not be free to grow into however much vertical room a tall phone
     offers. Small enough to leave real room to scroll to below it. */
  .ease-stage { height: clamp(240px, 44vh, 400px); min-height: 0; padding: 16px; }

  .v2-header-inner { padding-left: 20px; padding-right: 20px; }
}

@media (max-width: 620px) {
  .v2-title { font-size: 32px; }
  /* Still a bounded height, not min-height — a bare floor would let this
     grow unchecked again on a small-but-tall phone, right where the pinned
     preview most needs to stay compact. */
  .ease-stage { height: clamp(220px, 38vh, 340px); padding: 14px; }
  .ease-preview-block { margin: 0 -18px; padding: 12px 18px 14px; }
  .v2-header-inner { padding: 0 18px; }
  .v2-back span { display: none; }
}

/* Reduced motion: the tool is *about* motion, so playback itself is never
   disabled — only the chrome's own decorative loop (the ticker) and the
   engine's autoplay-on-load are gated. See easing-lab-v2.js: `reduced`
   short-circuits autoplay and looping, an explicit Play press still runs. */
