/* ─────────────────────────────────────────────────────────────────────────
   move-tokens.css — LOCKED 2026-08-19T19:22:05.246Z, re-synced 2026-08-20
   Source: Move Design System.dc.html, confirmed by founder. The 2026-08-20
   pass (design_handoff_platform_wide/) retuned --mv-bg/--mv-hover/
   --mv-border-strong to the cool off-white canvas and neutralized the
   [data-immersive="on"] chrome-accent override; every other value already
   matched. Re-sync from the approved source rather than hand-tuning further.
   Install: app/globals.css, first line: @import "./move-tokens.css";
   Keep imported platform tokens here and app aliases or contextual rules there.
   ───────────────────────────────────────────────────────────────────────── */


/* ══════════ fonts.css ══════════ */
/* THE TWO FACES, plus one for numbers.
   Founder, 2026-08-13: "I like how 'Athletic' the Inter ExtraBold font looks.
   And I like how it contrasts with Inter Light." One family, opposite ends of
   its weight axis — that is where the contrast comes from.

   SUBSTITUTION FLAG: the production app self-hosts Inter, Source Serif 4 and
   IBM Plex Mono through next/font. No binaries were available to this project,
   so these load from Google's CDN. Drop real .woff2 files into assets/fonts/
   and replace this import with @font-face rules pointing at them. */

:root {
  --font-inter: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-serif: "Source Serif 4", Georgia, "Times New Roman", serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Ten stylesheets and the Tailwind `display` key read --font-barlow-condensed
     by name. Repointing the variable switched every one of them at once and left
     nothing behind to drift; the alias keeps its old name only so no consumer
     has to change. Rename to --font-display in a later sweep. */
  --font-barlow-condensed: var(--font-inter);
  --font-display: var(--font-inter);

  --font-ui: var(--font-inter);
  --font-reading: var(--font-serif);
  --font-numeric: var(--font-mono);
}


/* ══════════ colors.css ══════════ */
/* ── COLOR ────────────────────────────────────────────────────────────────
   ONE ENCODING DECISION, made here so it never has to be made again.

   Base neutrals are space-separated RGB triplets (no #). That is not a style
   preference: production Tailwind resolves them as `rgb(var(--mv-text) / <alpha>)`,
   so the triplet is what makes opacity modifiers work at all. Changing it to hex
   would break every `/15` and `/30` utility in the app.

   Authors who just want a color use the SEMANTIC aliases below, which wrap the
   same triplets in rgb(). One source of truth, two accessors — never two values.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  /* Neutral ramp (light). Flipping .dark retunes every surface at once. */
  --mv-bg: 242 243 243;          /* the locked cool off-white canvas (retuned 2026-08-20, was warm cream #EFEEE8) */
  --mv-surface: 255 255 255;     /* raised surface / card */
  --mv-surface-2: 252 252 251;   /* card (alt) / field */
  --mv-hover: 236 237 237;       /* hover / input (retuned 2026-08-20 with --mv-bg) */
  --mv-border-strong: 224 225 225; /* retuned 2026-08-20 with --mv-bg */

  --mv-text: 21 22 26;           /* primary ink */
  --mv-text-muted: 71 71 80;     /* secondary — darkened for AA on small body */
  --mv-text-dim: 90 90 100;      /* tertiary / labels — never gray-on-gray */

  /* The slab: rail, media, spotlight cards. */
  --mv-slab: 11 11 14;
  --mv-slab-ink: 245 245 244;
  --mv-slab-muted: 150 152 155;
  /* THE RAISED SLAB (2026-08-21). Chips, wells and rows sitting ON slab ground.
     #161619 is used constantly across the shell and the media deck and had no
     token, so every author typed the hex. Named by the Claude Design handoff's
     ARCHETYPES.md as one of exactly two gaps in the system. */
  --mv-slab-raised: 22 22 25;

  /* Hairlines carry baked-in alpha. Lifted from 0.08 because at that value they
     all but vanished on the cool off-white canvas and card edges read as one flat field. */
  --mv-line-color: rgba(17, 17, 20, 0.12);
  --mv-line-strong-color: rgba(17, 17, 20, 0.2);
  /* The softer hairline BETWEEN rows inside a surface. A row separator at full
     hairline weight competes with the card's own edge. */
  --mv-line-soft-color: rgba(17, 17, 20, 0.08);

  /* ── THE ACCENT ─────────────────────────────────────────────────────────
     Electric lime. A FILL — never type on the light canvas, never a card
     border, never a background flood. One accent fill per view: the single
     most important action. */
  --mv-accent: 200 255 46;
  --mv-accent-soft: 212 255 92;
  --mv-accent-ink: 11 11 14;

  /* A ramp is not a text color. --mv-lime-* feeds every *-lime-600 utility:
     checkbox fills, score buttons, rings, status dots. Neutralising lime TEXT
     here instead of at the text utility painted 48 FILLS charcoal as well and
     left `bg-lime-600 text-ink-900` labels charcoal on charcoal. */
  --mv-lime-text: 92 116 0;
  --mv-lime-text-deep: 78 98 0;
  --mv-text-lime-override: #26282c;

  /* Semantics. Note the deliberate fill/text split, twice. */
  --mv-good: 21 145 91;
  --mv-warn: 183 121 31;
  --mv-ember: 224 112 14;
  --mv-bad: 220 38 38;
  /* --mv-bad is a FILL: at 15% over a white card it leaves its own label at
     3.82:1, under the 4.5 AA floor — which is how the destructive Button and
     "Delete everything" shipped unreadable. A fill tuned to look right and a
     label tuned to be read cannot be one value. */
  --mv-bad-text: 190 18 18;

  /* Lane accents. AA-deep on the light canvas. Never hardcode a lane hex as text. */
  --mv-scout: 29 78 216;
  --mv-growth: 124 58 237;

  /* ── SEMANTIC ALIASES, THE SLAB HALF ────────────────────────────────────
     These four name the SLAB explicitly. The slab is the slab on any theme and
     inside any Space, so they are correct resolved once, here. */
  --surface-slab: rgb(var(--mv-slab));
  --surface-slab-raised: rgb(var(--mv-slab-raised));
  --text-on-slab: rgb(var(--mv-slab-ink));
  --text-muted-on-slab: rgb(var(--mv-slab-muted));

  /* ── THE FOUR ACCENT CHANNELS ────────────────────────────────────────────
     Contract from lib/design/accent.ts. An identity meets two grounds that
     INVERT — every identity that reads on one fails on the other:

         Lipscomb  #331E54   11.0:1 on canvas    1.36:1 on slab
         BDA       #2E3140    9.9:1 on canvas    1.40:1 on slab
         KaiCon    #F58220    2.2:1 on canvas    6.97:1 on slab
         Move      #C8FF2E    1.4:1 on canvas   15.32:1 on slab

     So there are two FILLS. Anything under 3:1 falls back — to CHALK on the
     slab, to INK on the canvas — and NEVER to platform lime, which is the
     founder's original complaint wearing a different hat.

     AND TWO INKS, which cost a live defect on a partner's demo. A fill needs ink
     computed against ITSELF. Baseball Dads' navy keeps its fill on the warm
     canvas (chalk reads on it) but fails on the near-black slab, so the slab fill
     falls back to chalk — one shared ink token then painted chalk on chalk and
     the primary "Make The Move" button shipped with an invisible label.
     --mv-accent-ink pairs with --mv-accent; --mv-accent-slab-ink pairs with
     --mv-accent-slab. Pairing them across is the bug, and
     scripts/accent-two-token.test.ts asserts it.

     Set all four together from components/identity/accent.js accentTokens(),
     on ONE shell root. A screen that reaches for its own hex is the defect this
     replaces.

     ON ENCODING, because these four are not all the same shape. --mv-accent is
     declared above as an RGB TRIPLET, because Tailwind resolves it through
     `rgb(var(--mv-accent) / <alpha>)` and every accent tint depends on that. The
     SLAB channels are consumed directly as colors (MediaDeck.tsx reads
     `var(--mv-accent-slab, #C8FF2E)` raw, not wrapped), so they are hex.
     accentTokens() writes hex into all four as a runtime override; the triplet
     here is the base the utility layer needs. Do not "unify" them — the mismatch
     is load-bearing in one direction only. */
  --mv-accent-slab: #c8ff2e;
  --mv-accent-slab-ink: #0b0b0e;

  /* The two constants the resolver falls back to. */
  --mv-chalk: #f5f2ec;
  --mv-ink: #0b0b0e;

  /* Space / immersive channel. Defaults hold Move-neutral so any surface reading
     --space-accent outside a Space stays house lime. */
  --space-accent: #c8ff2e;
  --space-accent-fg: #0b0b0e;
  --space-accent-tint: rgb(var(--mv-accent) / 0.15);
  --space-accent-ring: rgb(var(--mv-accent) / 0.3);

  /* CHROME ACCENT. Neutral on a platform page so it never competes with the one
     lime CTA; becomes the brand inside a Space. Net effect: lime in the chrome
     now reads as "the focal action." */
  --chrome-accent: rgb(var(--mv-text-dim));
  --chrome-accent-tint: rgb(var(--mv-text-dim) / 0.14);
  --chrome-accent-ring: rgb(var(--mv-text-dim) / 0.3);

  /* Known identities, for reference and for the specimen cards. */
  --identity-kaicon: #f58220;
  --identity-lipscomb: #331e54;
  --identity-pitching-angel: #f238a2;
  --identity-chat: #7c5cff;
}

/* ── SEMANTIC ALIASES — what authors should actually reach for ─────────────
   AND THEY RESOLVE PER ELEMENT, NOT ONCE AT :root. This is the whole point of
   the rule below, and getting it wrong cost a founder report.

   A custom property's `var()` is substituted on the element that DECLARES it.
   Declared on `:root`, `--text-body: rgb(var(--mv-text))` computes ONE time
   against the light canvas and then inherits downward as a frozen literal. Every
   scope that retunes the primitives — `[data-move-stage="dark"]`, the four
   immersive palettes, the two scoped blocks in globals.css, the interview and
   creator module palettes, a Space setting its own `--mv-accent`, and the
   runtime override layer — retunes nothing any of these names can see.

   MEASURED IN CHROMIUM on one element carrying [data-move-stage="dark"], before
   this change:

       --mv-surface = 21 23 28      but  --surface-card = rgb(255 255 255)
       --mv-text    = 242 243 245   but  --text-body    = rgb(21 22 26)
       --mv-hover   = 34 38 44      but  --surface-hover = rgb(236 237 237)

   Two names for one decision returning opposite colours on one element. On the
   logged-out homepage that measured 73 of 233 visible text nodes failing AA,
   59 of them from this alone: a white box on a black band, near-black type on a
   near-black panel, a form field that vanishes into its own slab.

   It is worse than an ordinary bug because this block is labelled "what authors
   should actually reach for". The trap was laid exactly where the law points.

   `*, ::before, ::after` makes each element substitute against ITS OWN inherited
   primitives, so every scope above retunes the aliases with it and there is no
   second list to keep in sync. Same shape Tailwind uses for its own `--tw-*`
   ring and shadow variables, for the same reason.

   The accent aliases are here too, deliberately: a Space sets `--mv-accent` on
   its shell root, and frozen at `:root` its identity could never have reached
   `--accent-fill` either. */
*,
::before,
::after {
  --surface-canvas: rgb(var(--mv-bg));
  --surface-card: rgb(var(--mv-surface));
  --surface-field: rgb(var(--mv-surface-2));
  --surface-hover: rgb(var(--mv-hover));

  --text-body: rgb(var(--mv-text));
  --text-secondary: rgb(var(--mv-text-muted));
  --text-meta: rgb(var(--mv-text-dim));

  --line: var(--mv-line-color);
  --line-strong: var(--mv-line-strong-color);
  --line-soft: var(--mv-line-soft-color);

  --accent-fill: rgb(var(--mv-accent));
  --accent-ink: rgb(var(--mv-accent-ink));
  --accent-tint: rgb(var(--mv-accent) / 0.15);
  --accent-ring: rgb(var(--mv-accent) / 0.3);
}

/* Inside an active Space the chrome wayfinding wears the Space brand. */
/* CHROME STAYS NEUTRAL, ALWAYS — even inside a branded Space (locked
   2026-08-20). This selector used to swap --chrome-accent to --space-accent;
   kept as a no-op so a future edit doesn't silently reintroduce it. */
[data-immersive="on"] {
}

.dark,
[data-move-stage="dark"] {
  --mv-bg: 0 0 0;              /* true black canvas */
  --mv-surface: 13 13 15;
  --mv-surface-2: 19 19 22;
  --mv-hover: 28 28 32;
  --mv-border-strong: 58 62 71;

  --mv-text: 242 243 245;
  --mv-text-muted: 178 178 188; /* lifted for AA on small body */
  --mv-text-dim: 148 150 159;

  --mv-line-color: rgba(255, 255, 255, 0.1);
  --mv-line-strong-color: rgba(255, 255, 255, 0.16);
}


/* ══════════ typography.css ══════════ */
/* ── TYPE ─────────────────────────────────────────────────────────────────
   Headings take a LEVEL and never set their own size, weight or tracking.
   These approved values drive lib/design/Type.tsx (HEADING and TEXT_SIZE).

   WEIGHT AND TRACKING ARE TUNED FOR INTER (founder, 2026-08-13): "The letters
   are too spaced out, and I think this might be extra bold when it's supposed to
   be black." Barlow Condensed's narrow glyphs read tight at zero tracking;
   Inter's wider glyphs read airy at the same value. Tracking is a property of the
   face, so swapping the face without retuning it turns a correct value wrong.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  /* THE HEADING LADDER — four levels, Black through 3.
     1 page/hero · 2 section (the workhorse) · 3 card/group · 4 small label-weight.
     Level 4 stays ExtraBold: Black there would out-shout the level-3 titles. */
  --heading-1-size: clamp(2.5rem, 4.5vw, 4rem);
  --heading-1-leading: 0.95;
  --heading-1-weight: 900;
  --heading-1-tracking: -0.035em;

  --heading-2-size: clamp(1.75rem, 3vw, 2.5rem);
  --heading-2-leading: 1;
  --heading-2-weight: 900;
  --heading-2-tracking: -0.03em;

  /* 20px, 23px from the sm breakpoint. NOT 18px — a copy of this scale inside
     CardTitle drifted to 18px while its docstring claimed it was level 3, and
     authors who could see the difference hand-rolled <Heading level={3}> inside
     their cards 260 times across 104 files. Two copies of one scale is two scales. */
  --heading-3-size: 20px;
  --heading-3-size-sm: 23px;
  --heading-3-leading: 1.08;
  --heading-3-weight: 900;
  --heading-3-tracking: -0.022em;

  --heading-4-size: 17px;
  --heading-4-leading: 1.35;
  --heading-4-weight: 800;
  --heading-4-tracking: -0.015em;

  /* THE BODY SCALE. 13px is the floor and it is the founder's own number
     (2026-08-02, asked to pick: "13"). scripts/body-floor.test.ts pins it.
     A card whose copy is too small is a CALL SITE reaching for sm where it
     wanted the default — fix it there, not here.

     LEADING CARRIES THE DENSITY, NOT SIZE (2026-08-13): 1.5 → 1.45 is about a
     line recovered every twenty, and it reads tighter rather than smaller. */
  --text-sm-size: 13px;
  --text-base-size: 15px;
  --text-lg-size: 18px;
  --text-leading: 1.45;

  /* The eyebrow's own 12px interface floor — the number nobody is under
     (enforced by scripts/verify-text-floor.mjs). */
  --eyebrow-size: 12px;
  --eyebrow-weight: 800;
  --eyebrow-tracking: 0.06em;

  /* The editorial lead: light weight = premium contrast. */
  --lead-size: 17px;
  --lead-weight: 300;
  --lead-leading: 1.55;

  /* Long-form reader prose is NOT the body scale. It sets its own measure and
     leading, because that is a genuinely different job. */
  --rank-reader-size: 17px;
  --rank-reader-leading: 1.5;

  --mono-size: 12px;
  --mono-weight: 500;

  /* ── THE ONE MEASURE POLICY ─────────────────────────────────────────────
     A measure cap is a CEILING, never a fixed width. Spell every cap
     `max-inline-size: min(var(--mv-measure), 100%)` so a narrow container gets
     the full column and only a very wide one is trimmed back.

     Three rules, and they are LAW:
       - DISPLAY text takes NO measure cap. It is short and balanced; capping it
         at 14ch produced a two-word line with a dead half-screen beside it.
       - CHROME COPY takes no cap. A section description, a card's explanatory
         line, a tile's blurb — none of that is reading. It belongs to the box it
         sits in, which already sets the width.
       - READING PROSE, and only reading prose, takes the cap via .mv-read.

     The utility is named "read" and not "measure" on purpose: 64 of 67 call
     sites of the old name were chrome, and `max-w-2xl` (672px) was twice swapped
     in for 92ch (~690px) and the note closed as fixed. A cap swapped for a
     slightly larger cap is not a fix when the complaint is that there is a cap. */
  --mv-measure: 92ch;
  --mv-measure-tight: 72ch;
}

@media (min-width: 640px) {
  :root { --heading-3-size: var(--heading-3-size-sm); }
}


/* ══════════ space.css ══════════ */
/* ── SPACE + GEOMETRY ─────────────────────────────────────────────────────
   Padding is a property of the SURFACE, not a per-screen decision.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;

  --pad-card: var(--space-5);       /* 20px, on CardBody — never on both boxes */
  --pad-card-dense: var(--space-3);
  --gap-section: var(--space-3);
  --gutter-page: var(--space-6);

  /* 18px, because that is exactly what rounded-2xl is in tailwind.config.ts.
     It was 1rem, which silently re-rounded every card the moment it shipped.
     Then it was 1.125rem, which is 18px only where the root font size is 16px:
     the desktop density knob (html { font-size: 90% }) made the same card paint
     a 16.2px corner at 1440 and an 18px corner at 768. Stated in px now, with
     the rest of the radius scale, because a corner is a shape and not a
     density. See tailwind.config.ts -> borderRadius. */
  --mv-radius: 18px;       /* 18px — cards */
  --radius-tile: 14px;
  --radius-field: 10px;
  --radius-pill: 999px;

  /* The ONE chrome corner radius: left rail, right agent rail, every Space
     content surface. Change it here, it changes everywhere. */
  --chrome-radius: 14px;
  /* The gap floating chrome keeps from the screen edges, so the rails read as
     elevated cards rather than edge-to-edge panels. One knob for the layout. */
  --chrome-gap: 10px;
  --safe-top: calc(env(safe-area-inset-top, 0px) + var(--chrome-gap));
  --safe-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--chrome-gap));

  --rail-expanded: 238px;
  --rail-collapsed: 64px;
  --topbar-height: 56px;

  /* BUTTON TARGETS. The 44px floor protects a THUMB, not a mouse — holding every
     size to it gave the scale four names and three heights. sm is 38px on a fine
     pointer and returns to 44 under pointer:coarse. */
  --target-sm: 38px;
  --target-md: 44px;
  --target-lg: 48px;
  --target-xl: 56px;
  /* The minimum thumb target for every T3 control. */
  --t3-target: 46px;
  --target-field: 40px;
  --target-nav: 34px;

  /* THE HAND: 68px WIDE, 56px TALL, 28px disc, 11px/800 label
     (MEDIA-HANDOFF §2, MediaDeck.tsx). The row is wider than it is tall, and
     swapping the two clips "Opportunity". */
  --target-hand-w: 68px;
  --target-hand-h: 56px;

  --mv-density: 1;
  --mv-card-border-width: 1px;
}

/* The pointer:coarse escalation of --target-sm to 44px was removed 2026-08-20
   (founder: "get rid of the 44px min-height stuff... none of it"). --target-*
   above is otherwise unused (no var(--target-...) consumer in the codebase). */


/* ══════════ motion.css ══════════ */
/* ── MOTION ───────────────────────────────────────────────────────────────
   Founder standing order, 2026-08-14: "Move cannot feel like software sitting
   still. Every screen must communicate movement through at least one honest
   signal." And, in the same breath: "Do not create constant decorative
   animation. Do not fake activity."

   WHY TOKENS. Before this the platform had ~30 distinct durations — 140ms,
   150ms, 180ms, 240ms, 480ms, 520ms, 620ms, 860ms, 2.1s, 2.4s, 5.5s — each fine
   alone. Together they are not a system, they are thirty opinions, and no author
   could tell which to copy. Five bands replace them.

   THE VALUE IS NOT THE POINT — THE BAND IS. Reach for the token whose JOB
   matches, never the number that looks right.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  --mv-motion-instant: 100ms;   /* a control acknowledging a press */
  --mv-motion-micro: 160ms;     /* hover, selection, a chip changing state */
  --mv-motion-panel: 260ms;     /* a panel, a section, a row advancing */
  --mv-motion-narrative: 420ms; /* something arriving, something completing */
  --mv-motion-working: 1.8s;    /* the quiet loop that says work is happening */
  /* SWEEP REVEAL — how generated text arrives, everywhere in Move.
     From the approved Claude Design handoff "Sweep Reveal (generated-text
     entrance)", 2026-08-26, adopted after a review of ~10 alternatives (line
     cascades, per-word fades, a blinking-cursor token stream, an
     outline-to-content panel, gradient sweeps). Its own note: "this is a motion
     component, and the numbers ARE the design." */
  --mv-motion-sweep-cinematic: 1100ms; /* @kind other */ /* per-line sweep, reading pages and notes at rest */
  --mv-stagger-sweep-cinematic: 650ms;                   /* delay per subsequent line, cinematic */
  --mv-motion-sweep-brisk: 520ms; /* @kind other */      /* per-line sweep, short rows and dense lists */
  --mv-stagger-sweep-brisk: 300ms;                       /* delay per subsequent line, brisk */

  --mv-ease-out: cubic-bezier(0.16, 1, 0.3, 1);   /* enters and settles — the house curve */
  --mv-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); /* loops, both directions equal */
  --mv-ease-standard: cubic-bezier(0.2, 0, 0, 1); /* between two known states */

  /* Small on purpose: travel reads as intent, not spectacle. */
  --mv-travel: 6px;
  --mv-travel-lg: 12px;
  --mv-press: 0.97;
}

@keyframes mv-rise {
  from { opacity: 0; transform: translateY(var(--mv-travel)); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Tailwind's animate-pulse curve, which Skeleton wears upstream. */
@keyframes mv-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.5; }
}
@keyframes mv-working {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}

/* ── SESSION PRIMITIVES · four keyframes ──────────────────────────────────
   Added by the Meet & Move promotion pass (2026-08-24). The reduced-motion
   block below already collapses all four — do not add another. */

/* SPEECH. The eq bars: real mic activity only, never a timer. Separate from
   mv-working because speech and machine work are different causes and sharing
   one animation makes the pulse unreadable. */
@keyframes mv-eq {
  0%, 100% { transform: scaleY(0.28); }
  50%      { transform: scaleY(1); }
}

/* RECORDING. Breathes rather than blinks — a blink reads as an alarm, and
   recording is a fact, not an alert. Never lime. */
@keyframes mv-rec {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.28; }
}

/* THE MET STAMP. The ONLY element in the system allowed to scale in, because it
   is the only moment that is genuinely an arrival rather than a transition.
   Spend it once per session. */
@keyframes mv-stamp {
  from { opacity: 0; transform: scale(1.08); }
  to   { opacity: 1; transform: scale(1); }
}

/* FADE, NO TRAVEL. For a row arriving inside a FIXED-HEIGHT container. This
   exists because mv-rise was used there first and 6px of travel inside a clipped
   card is 6px of clipped text. Rule: travel only where there is room to travel. */
@keyframes mv-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* THE DRAWER SLIDE. Added with components/overlays/Drawer.jsx — the edge tab's
   drawer motion, made reusable instead of re-authored per screen. */
@keyframes mv-drawer-right {
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
}
@keyframes mv-drawer-left {
  from { transform: translateX(-100%); }
  to   { transform: translateX(0); }
}


/* Every animation collapses under reduced motion. Not optional. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}


/* ══════════ media.css ══════════ */
/* ── MEDIA PLAYER STATE + BREAKPOINTS ─────────────────────────────────────
   The handful of rules inline styles genuinely cannot express: keyframes, a
   PARENT state driving a child, and a breakpoint reordering a grid. Everything
   else about the media composition is inline in MediaExperience.jsx.

   Lives in tokens/ so any page that links styles.css gets it — the composition
   must never need a second stylesheet remembered per consumer.
   ───────────────────────────────────────────────────────────────────────── */

/* The moving stand-in under the frame, so a media surface never opens on a dead
   poster even when no source resolves. */
@keyframes mv-pan {
  0%   { background-position: 0 0; }
  100% { background-position: 240px 240px; }
}

/* The play affordance's two chevrons, offset by 0.15s. */
@keyframes mv-slide {
  0%, 100% { transform: translateX(0); opacity: 1; }
  50%      { transform: translateX(4px); opacity: 0.6; }
}

/* A reaction leaving the deck. Rises and fades — it does not linger. */
@keyframes mv-burst {
  0%   { opacity: 0; transform: translate(-50%, -20%) scale(0.8); }
  25%  { opacity: 1; transform: translate(-50%, -60%) scale(1.15); }
  100% { opacity: 0; transform: translate(-50%, -180%) scale(1); }
}

@keyframes mv-ring {
  0%        { transform: scale(1); opacity: 0.55; }
  70%, 100% { transform: scale(1.9); opacity: 0; }
}

@keyframes mv-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.05); }
}

/* CHROME RECEDES WHILE THE FRAME PLAYS and returns on hover or focus. A parent
   state driving a child is the one thing an inline style cannot say. */
.mv-center,
.mv-deck-ui {
  transition: opacity var(--mv-motion-panel) var(--mv-ease-out);
}
.mv-player.is-playing .mv-center,
.mv-player.is-playing .mv-deck-ui {
  opacity: 0;
}
.mv-player.is-playing:hover .mv-center,
.mv-player.is-playing:focus-within .mv-center,
.mv-player.is-playing:hover .mv-deck-ui,
.mv-player.is-playing:focus-within .mv-deck-ui {
  opacity: 1;
}

/* Snap-scrolled sliders, with the scrollbar kept out of the layout. */
.mv-slider {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}
.mv-slider > * {
  scroll-snap-align: start;
}
.mv-tile-pan {
  animation: mv-pan 18s linear infinite;
}

/* MOBILE IS ITS OWN ORDER, and it is a breakpoint, not a prop: the player
   sticks to the top on phones and releases from lg up, where it already sits
   beside a sticky rail. */
@media (max-width: 1023px) {
  .mv-grid {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  .mv-aside {
    position: static !important;
    top: auto !important;
  }
  .mv-player {
    position: sticky !important;
    top: 0;
    z-index: 20;
  }
}


/* ══════════ surfaces.css ══════════ */
/* ── SURFACES ─────────────────────────────────────────────────────────────
   Lift is ONE shadow. Depth budget is two: section, then card, then rows.
   No card inside a card — every internal separation is a Well.
   ───────────────────────────────────────────────────────────────────────── */

:root {
  --mv-shadow-card:
    0 1px 2px 0 rgba(17, 17, 20, 0.04), 0 8px 24px -16px rgba(17, 17, 20, 0.18);
  --mv-shadow-float:
    0 1px 2px rgba(17, 17, 20, 0.04), 0 14px 40px -20px rgba(17, 17, 20, 0.28);
  --mv-card-shadow: var(--mv-shadow-card);

  --mv-glass-bg: rgba(255, 255, 255, 0.72);
  --glass-blur: blur(20px);
  --topbar-bg: rgba(247, 248, 242, 0.96);

  --mv-scrollbar: rgba(17, 17, 20, 0.16);
  --mv-scrollbar-hover: rgba(17, 17, 20, 0.28);

  /* The focal CTA fill. Lime sheen on a platform page; the Space brand on any
     immersive surface, so the one hero action wears the surface identity. */
  --cta-fill: linear-gradient(135deg, #d4ff5c 0%, #c8ff2e 55%, #a9e000 100%);
  --cta-fg: var(--space-accent-fg);
  --cta-glow: rgb(var(--mv-accent) / 0.5);
}

/* Reading prose only. Never a per-page max-w literal. */
.mv-read {
  max-inline-size: min(var(--mv-measure), 100%);
}
.mv-read-tight {
  max-inline-size: min(var(--mv-measure-tight), 100%);
}


/* ══════════ entry.css ══════════ */
/* TOKENS ONLY. `design/source/claude-design/current/tokens/entry.css` also
   carries the Entry composition's own classes, keyframes and breakpoints
   (`.mv-entry-*`). Those are component CSS, not tokens, and nothing in this repo
   renders them yet — they ship with `components/entry/*` when that composition
   lands. This bundle carries the variables so the parity gate has its source of
   truth and any consumer can read them today. */

:root {
  /* THE CONTAINER IS ONE WIDTH, everywhere, forever. 620px is wide enough for a
     four-column role picker and narrow enough that a two-field login does not
     look abandoned inside it. A cap is a ceiling, never a fixed width. */
  --mv-entry-container: 620px;

  /* The hero. svh not vh: mobile browser chrome collapses vh mid-scroll and the
     headline jumps. */
  --mv-entry-hero-h: 680px;
  --mv-entry-hero-h-sm: 88svh; /* @kind spacing */

  /* HOW FAR THE CONTAINER HANGS OVER THE SEAM. The overlap is the family's
     signature — it is what makes the card read as arriving out of the dark
     rather than sitting in a section below it. */
  --mv-entry-overlap: -96px;
  --mv-entry-overlap-sm: -48px;

  /* The scrim. Three stops, not two: the middle stop opens back up so the
     photograph is still legible behind the headline, then closes hard at the
     bottom so the container's white edge lands on near-black. */
  --mv-entry-scrim: linear-gradient(
    180deg,
    rgba(11, 11, 14, 0.55) 0%,
    rgba(11, 11, 14, 0.25) 32%,
    rgba(11, 11, 14, 0.96) 100%
  ); /* @kind other */

  /* PHOTOGRAPHY IS ALWAYS GRAYSCALE HERE, and slightly hardened. Color
     photography behind lime reads as two accents fighting; grayscale makes the
     lime the only chromatic thing on the surface, which is the Lime Law doing
     its job at the scale of a whole page. */
  --mv-entry-photo-filter: grayscale(1) contrast(1.08); /* @kind other */

  /* The header before and after it solidifies. */
  --mv-entry-header-h: 76px;
  --mv-entry-header-solid: #0B0B0E;
  --mv-entry-header-hairline: 0 1px 0 rgba(255, 255, 255, 0.06);
  --mv-entry-header-trigger: 40px; /* scrollY past which it solidifies */
}

/* ── LOADING & TRANSITION SYSTEM ──────────────────────────────────────────
   Founder handoff, 2026-08-25: "Move loading and transition system", built and
   approved in Loading.dc.html. His instruction on delivery: *"This should
   override anything else that was used previously."* That answers the handoff's
   own decision #1 — it REPLACES the shape-mirroring Skeleton pulse for
   route-level waits and retires the top-of-viewport trickle bar. Two
   simultaneous "something is loading" signals on one navigation was one too
   many, and the handoff says so itself.

   Additive tokens, per its decision #2: 320ms and 10px are deliberately NOT
   --mv-motion-narrative (420ms) or --mv-travel (6px). Bending a shared band
   would silently retune every other consumer.

   IT SPENDS ZERO LIME, on purpose. The outline is --text-meta. Lime is the one
   activation signal per view, and a wait is not the view's action. */
:root {
  --mv-motion-page-enter: 320ms;
  --mv-travel-page: 10px;
  /* Panel-level, bigger than --mv-radius (18px cards) and --chrome-radius. */
  --mv-radius-panel: 28px;
}

@keyframes mv-page-enter {
  from { opacity: 0; transform: translateY(var(--mv-travel-page)); }
  to   { opacity: 1; transform: translateY(0); }
}

/* `pathLength="100"` on the SVG rect makes this dash math independent of the
   panel's real perimeter, so one dasharray works at every size. */
@keyframes mv-fetch-outline-travel {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: -100; }
}

@keyframes mv-fetch-outline-dissolve {
  from { opacity: 1; }
  to   { opacity: 0; }
}
