/* ═══════════════════════════════════════════════════════════════════════════
   Moldcell presentation environment — 1:1 port of Velvet Workspace.

   Source of truth:
     velvet-workspace/app/src/app/globals.css
     velvet-workspace/app/src/app/(workspace)/layout.tsx
     velvet-workspace/app/src/components/workspace/primary-nav.tsx
     velvet-workspace/app/src/app/(workspace)/brand/[slug]/page.tsx
     velvet-workspace/app/src/components/workspace/color-swatch.tsx

   The original is Next.js + Tailwind v4. Here the Tailwind utility classes that
   the original markup used are hand-written as semantic classes with the SAME
   computed values. Every block names the original class list it replaces, so it
   can be diffed against the React source line by line.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Preflight ────────────────────────────────────────────────────────────
   Tailwind v4 preflight, trimmed to the elements this page actually uses.
   Verbatim values from node_modules/tailwindcss/preflight.css. */
*, ::after, ::before, ::backdrop, ::file-selector-button {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  border: 0 solid;
}

html, :host {
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
  tab-size: 4;
  font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
    "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  -webkit-tap-highlight-color: transparent;
}

h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }
a { color: inherit; -webkit-text-decoration: inherit; text-decoration: inherit; }
b, strong { font-weight: bolder; }
code, kbd, samp, pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    "Liberation Mono", "Courier New", monospace;
  font-size: 1em;
}
ol, ul, menu { list-style: none; }
img, svg, video, canvas, audio, iframe, embed, object {
  display: block;
  vertical-align: middle;
}
img, video { max-width: 100%; height: auto; }
button, input, select, optgroup, textarea, ::file-selector-button {
  font: inherit;
  font-feature-settings: inherit;
  font-variation-settings: inherit;
  letter-spacing: inherit;
  color: inherit;
  border-radius: 0;
  background-color: transparent;
  opacity: 1;
}
button, input:where([type="button"], [type="reset"], [type="submit"]),
::file-selector-button { appearance: button; }
:-moz-focusring { outline: auto; }
[hidden]:where(:not([hidden="until-found"])) { display: none !important; }

/* ── Design tokens ───────────────────────────────────────────────────────
   globals.css :root, ported in full.

   NOTE — the brand colour tokens below still hold the original values.
   The brief states the Moldcell colour system arrives later, together with the
   content; until then the only substitution in this build is the logo. Replace
   this block when the palette lands. The structural ramp (--n-0 … --n-100) and
   --nav-w stay whatever happens to the brand colours: the nav and layout
   sit on them. */
:root {
  --background: #ffffff;
  --foreground: #070709;

  /* Color — brand slot, awaiting the Moldcell palette */
  --color-text-primary:   #60606c;
  --color-text-secondary: #070709;
  --color-text-inverse:   #ffffff;
  --color-surface-base:   #000000;
  --color-surface-strong: #efeff1;

  /* Radius */
  --radius-xs: 10px;
  --radius-sm: 18px;
  --radius-md: 36px;

  /* ── Structural tokens (base.org-derived) ──────────────────────────────
     215px nav, large editorial type scale, neutral ramp, generous whitespace. */
  --nav-w: 215px;

  /* Section grid — 9 columns, gutter tracks the viewport up to 32px.
     Measured off brand.base.org, which is where --nav-w and the neutral ramp
     already came from. Label column = 3, content column = 6. */
  --grid-cols: 9;
  --grid-gap: min(2.25vw, 32px);

  /* Muted body colour. Same value as --n-60; named separately because it is a
     role (secondary text), not a step on the ramp. */
  --muted: #717886;

  /* Tints — the light backgrounds that hold a specimen without drawing a box
     around it. Three steps is the whole system:
       --tint-panel  the surface a specimen sits on
       --tint-chip   a tag, one step darker so it reads against the panel
       --tint-rule   the hairline that separates stacked blocks
     Measured off brand.base.org (#FAFAFA / #F2F2F2 / #D9D9E3). --tint-rule is
     within a hair of --n-30, so the ramp already had it. */
  --tint-panel: #FAFAFA;
  --tint-chip:  #F2F2F2;
  --tint-rule:  #D9D9E3;

  /* Eyebrow colour — the muted line above a heading. Same value as --n-50. */
  --eyebrow: #B1B7C3;

  /* Neutral ramp (base.org gray scale) */
  --n-0:   #ffffff;
  --n-10:  #f7f7f7;
  --n-15:  #eef0f3;
  --n-30:  #dee1e7;
  --n-50:  #b1b7c3;
  --n-60:  #717886;
  --n-70:  #5b616e;
  --n-90:  #32353d;
  --n-100: #0a0b0d;

  /* Editorial type scale (base.org: light weight, tight tracking) */
  --t-display: 80px;
  --t-h1:      40px;
  --t-h2:      24px;
  --t-lead:    24px;
  --t-h3:      14px;

  /* Shadows */
  --shadow-1:
    rgba(36, 36, 40, 0.10) 0px 1px 2px 0px,
    rgba(36, 36, 40, 0.09) 0px 3px 3px 0px,
    rgba(36, 36, 40, 0.05) 0px 6px 4px 0px,
    rgba(36, 36, 40, 0.01) 0px 11px 4px 0px,
    rgba(36, 36, 40, 0.00) 0px 17px 5px 0px;
  --shadow-2:
    rgba(16, 55, 132, 0.03) 0px 17px 37px 0px,
    rgba(16, 55, 132, 0.03) 0px 67px 67px 0px,
    rgba(16, 55, 132, 0.02) 0px 150px 90px 0px;

  /* Was globals.css @theme inline — plain custom properties here. */
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: "Open Runde", ui-sans-serif, system-ui, sans-serif;
  /* Was var(--font-geist-mono), which nothing defined — so every `font-mono`
     in the original silently fell back to the sans body face. The intent was
     always mono (hex values, px sizes, tags), so it is a real stack now.
     This visibly changes swatch values, type-specimen sizes and placeholder
     labels from sans to mono. */
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    "Liberation Mono", "Courier New", monospace;

  --color-text-p:    var(--color-text-primary);
  --color-text-s:    var(--color-text-secondary);
  --color-surface-s: var(--color-surface-strong);

  --shadow-card:  var(--shadow-1);
  --shadow-float: var(--shadow-2);

  /* Tailwind v4 neutral palette — the values behind the original's
     text-neutral-* / border-neutral-* / bg-neutral-* classes.
     Verbatim from node_modules/tailwindcss/theme.css. */
  --neutral-50:  oklch(98.5% 0 0);
  --neutral-100: oklch(97% 0 0);
  --neutral-200: oklch(92.2% 0 0);
  --neutral-300: oklch(87% 0 0);
  --neutral-400: oklch(70.8% 0 0);
  --neutral-500: oklch(55.6% 0 0);
  --neutral-600: oklch(43.9% 0 0);
  --neutral-700: oklch(37.1% 0 0);
  --neutral-800: oklch(26.9% 0 0);
  --neutral-900: oklch(20.5% 0 0);
  --neutral-950: oklch(14.5% 0 0);

  /* Tailwind's default transition timing, used by transition-colors. */
  --tw-ease: cubic-bezier(0.4, 0, 0.2, 1);
}

body {
  background: var(--background);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  line-height: 21px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

html { height: 100%; }
body { min-height: 100%; }

/* ════════════════════════════════════════════════════════════════════════
   MOTION & POLISH FOUNDATION (transitions.dev + Emil Kowalski principles)
   These are project-wide defaults — every element inherits them.
   ════════════════════════════════════════════════════════════════════════ */
:root {
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);   /* signature ease */
  --ease-io:  cubic-bezier(0.4, 0, 0.2, 1);
  --dur-fast: 150ms;
  --dur:      250ms;
  --dur-slow: 400ms;
}

/* Flat aesthetic — no border-radius anywhere. Enforced globally so future
   elements stay flat. (The original also carried a Clerk exemption; Clerk is
   gone from this build, so the selector is just `*`.) */
* { border-radius: 0 !important; }

/* Text wrapping — balance headings, avoid orphans in body (Emil #10) */
h1, h2, h3, h4 { text-wrap: balance; }
p { text-wrap: pretty; }

/* Tabular numbers for any dynamically-updating figure (Emil #9) */
.tnum { font-variant-numeric: tabular-nums; }

/* Scale-on-press — tactile feedback, always 0.96 (Emil #12). */
.press {
  transition: scale var(--dur-fast) var(--ease-out);
}
.press:active { scale: 0.96; }

/* Subtle 1px outline on images for consistent depth (Emil #11) */
.img-outline { box-shadow: inset 0 0 0 1px rgba(10, 11, 13, 0.06); }

/* Smooth in-page anchor scrolling (jump-links in the sidebar → sections). */
#main-scroll { scroll-behavior: smooth; }

/* Accessibility guard — respect reduced motion everywhere */
@media (prefers-reduced-motion: reduce) {
  #main-scroll { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ── Shared helpers ──────────────────────────────────────────────────────── */
/* Tailwind `truncate` */
.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Tailwind `font-mono`. --font-mono resolves to var(--font-geist-mono), which
   the original never defines — so the declaration is invalid at computed-value
   time and font-family falls back to inherit. Ported as-is: this is what the
   original renders. */
.mono { font-family: var(--font-mono); }

/* ═══ layout.tsx ══════════════════════════════════════════════════════════ */

/* <div className="h-screen flex"> */
.app { height: 100vh; display: flex; }

/* right column: <div className="flex-1 flex flex-col min-w-0">
   (the h-14 topbar it used to hold — member.role, Settings, UserButton — is
   removed in this build) */
.main-col { flex: 1 1 0%; display: flex; flex-direction: column; min-width: 0; }

/* <div className="flex-1 flex overflow-hidden min-h-0"> */
.main-area { flex: 1 1 0%; display: flex; overflow: hidden; min-height: 0; }

/* ═══ primary-nav.tsx ═════════════════════════════════════════════════════ */

/* <aside className="shrink-0 bg-white flex flex-col" style={{width:"var(--nav-w)"}}> */
.nav {
  flex-shrink: 0;
  width: var(--nav-w);
  background: #fff;
  display: flex;
  flex-direction: column;
}

/* <div className="px-5 h-14 flex items-center shrink-0"> */
.nav-logo {
  padding-inline: 1.25rem;
  height: 3.5rem;
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
/* The logo link. No .press: scaling a 129px-wide wordmark on mousedown reads
   as a glitch, not as feedback. */
.nav-logo a { display: inline-flex; align-items: center; }
/* <img className="h-[26px] w-auto" /> */
.nav-logo img { height: 26px; width: auto; }

/* <div className="flex-1 overflow-y-auto px-2 pb-3 flex flex-col"> */
.nav-body {
  flex: 1 1 0%;
  overflow-y: auto;
  padding-inline: 0.5rem;
  padding-bottom: 0.75rem;
  display: flex;
  flex-direction: column;
}

/* <nav className="flex flex-col gap-0.5"> */
.nav-list { display: flex; flex-direction: column; gap: 0.125rem; }

/* Topic row — one link, chevron is an indicator not a control.

   .press is deliberately NOT on this row. Scale-on-press is feedback for a
   compact control; on a 199px-wide row it shrinks the background with the
   label inside it, and every click reads as a flinch. A row this size gives
   feedback by darkening instead — the geometry never moves.

   The original's markup asked for `transition-[background-color,color]
   duration-150` but never got it, because unlayered `.press` beat the layered
   utility and left only `scale`. With .press gone the intended transition is
   finally what runs. */
.topic-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.625rem 0.75rem;
  font-size: 15px;
  line-height: 1.14;
  color: var(--neutral-800);
  transition: background-color var(--dur-fast) var(--tw-ease),
              color var(--dur-fast) var(--tw-ease);
}
.topic-link:hover { background: #F5F5F7; color: var(--neutral-950); }
/* Press: one step past hover, and instant — feedback that lags is not
   feedback. The release fades back out over --dur-fast. */
.topic-link:active {
  background: #E8E8EC;
  transition-duration: 0s;
}
.topic-link.is-active {
  background: #EEEEF0;
  color: var(--neutral-900);
  font-weight: 500;
}
.topic-link.is-active:active { background: #E4E4E9; }

/* ── Submenu opening ──────────────────────────────────────────────────────
   In the original this was a SPA: clicking a topic transitioned the previous
   list closed and the new one open, using grid-template-rows 0fr → 1fr. This
   build is static — every click is a page load — so the open state arrived
   already finished, and the submenu simply appeared. That is what made the nav
   feel abrupt.

   So the open is replayed as a load-time animation. Same technique, same
   duration and easing as the original transition; it just runs once when the
   page arrives instead of on a state change.

   The resting state of an expanded topic is OPEN. The animation only supplies
   the frames leading up to it, so if animations never run — reduced motion,
   an old engine — the submenu is simply there. Nothing depends on motion. */
.topic-chev {
  flex-shrink: 0;
  font-size: 12px;
  color: var(--neutral-400);
  rotate: 0deg;
}
.topic.is-expanded .topic-chev {
  rotate: 90deg;
  animation: nav-chev var(--dur) var(--ease-out) both;
}

.topic-sections {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
}
.topic.is-expanded .topic-sections {
  grid-template-rows: 1fr;
  opacity: 1;
  animation: nav-open var(--dur) var(--ease-out) both;
}

/* The rows arrive just behind the container, 25ms apart — but the stagger is
   capped at 150ms total. A topic with three anchors reads as one movement; a
   topic with twelve would otherwise take 410ms to finish arriving, which is
   slower than the page it is describing. Past the sixth row they land
   together, and nobody counts them. */
.topic.is-expanded .anchor-link {
  animation: nav-anchor var(--dur) var(--ease-out) both;
  animation-delay: calc(80ms + min(var(--i, 0) * 25ms, 150ms));
}

@keyframes nav-open {
  from { grid-template-rows: 0fr; opacity: 0; }
  to   { grid-template-rows: 1fr; opacity: 1; }
}
@keyframes nav-chev {
  from { rotate: 0deg; }
  to   { rotate: 90deg; }
}
@keyframes nav-anchor {
  from { opacity: 0; translate: 0 -4px; }
  to   { opacity: 1; translate: 0 0; }
}

/* <div className="overflow-hidden min-h-0"> */
.topic-sections-clip { overflow: hidden; min-height: 0; }

/* <div className="ml-3 mt-0.5 mb-1 border-l border-neutral-100 flex flex-col"> */
.topic-anchors {
  margin-left: 0.75rem;
  margin-top: 0.125rem;
  margin-bottom: 0.25rem;
  border-left: 1px solid var(--neutral-100);
  display: flex;
  flex-direction: column;
}

/* "pl-3 pr-2 py-1 text-[12px] text-neutral-500 hover:text-neutral-900
    transition-colors duration-150 truncate" */
.anchor-link {
  padding: 0.375rem 0.5rem 0.375rem 0.75rem;
  font-size: 14px;
  line-height: 1.2;
  color: var(--neutral-500);
  transition: color 150ms var(--tw-ease),
              background-color 150ms var(--tw-ease),
              border-color 150ms var(--tw-ease);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.anchor-link:hover { color: var(--neutral-900); }

/* <p className="px-3 py-1.5 text-[12px] text-neutral-400"> */
.nav-empty {
  padding: 0.375rem 0.75rem;
  font-size: 14px;
  color: var(--neutral-400);
}

/* <div className="px-4 py-4 border-t border-neutral-100 shrink-0">
   The "Download assets" button is removed; the org row keeps text-[10px].
   (Its original mt-3 was spacing to that button, so it goes with it.) */
.nav-footer {
  padding: 1rem;
  border-top: 1px solid var(--neutral-100);
  flex-shrink: 0;
}
.nav-footer p {
  font-size: 12px;
  color: var(--neutral-400);
  line-height: 1.25;
}

/* ═══ brand/[slug]/page.tsx ═══════════════════════════════════════════════ */

/* <div className="flex-1 overflow-auto" id="main-scroll"> */
.page { flex: 1 1 0%; overflow: auto; }

/* <div className="px-10 py-12 mx-auto" style={{maxWidth:1136}}> */
.page-inner {
  padding: 3rem 2.5rem;
  margin-inline: auto;
  max-width: 1136px;
}

/* <header className="mb-16"> */
.page-header { margin-bottom: 4rem; }

/* "font-medium tracking-[-0.03em] text-neutral-950 leading-[1.0] mb-8"
   style fontSize: clamp(44px, 6vw, 76px) */
.page-title {
  font-weight: 500;
  letter-spacing: -0.03em;
  color: var(--neutral-950);
  line-height: 1;
  margin-bottom: 2rem;
  font-size: clamp(44px, 6vw, 76px);
}

/* <div className="relative w-full overflow-hidden mb-8" style={{height:340}}> */
.hero {
  position: relative;
  width: 100%;
  overflow: hidden;
  margin-bottom: 2rem;
  height: 340px;
}
/* <img className="absolute inset-0 w-full h-full object-cover" /> */
.hero > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* gradient fallback */
.hero-fallback {
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, #f7f7f7 0%, #eef0f3 60%, #e2e6ec 100%);
}
/* <span className="absolute bottom-4 right-5 text-[11px] font-mono text-neutral-400/70"> */
.hero-label {
  position: absolute;
  bottom: 1rem;
  right: 1.25rem;
  font-size: 12px;
  color: color-mix(in oklab, var(--neutral-400) 70%, transparent);
}

/* <p className="text-[22px] leading-snug text-neutral-800 max-w-[760px]"> */
.page-intro {
  font-size: 24px;
  line-height: 1.2;
  letter-spacing: -0.025em;
  color: var(--neutral-800);
  max-width: 760px;
}

/* <div className="flex flex-col"> */
.sections { display: flex; flex-direction: column; }

/* ── Section shell ───────────────────────────────────────────────────────
   Was: flex, 300px label + 1fr content, gap 48px, py-12, border-t.
   Now: a 9-column grid with a min(2.25vw,32px) gutter — label spans 3,
   content spans 6 — and the roomier vertical rhythm that goes with it
   (32px padding inside the section, 80px margin after it, so two sections
   are 144px apart instead of 96px).

   Placement is set by a modifier, so one shell serves every layout:
     .section--split   label (3) + content (6)   ← default
     .section--wide    heading row on top, content across all 9
     .section--full    no label, content across all 9 */
.section {
  scroll-margin-top: 2rem;
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  column-gap: var(--grid-gap);
  row-gap: 1.5rem;
  padding-block: 2rem;
  margin-bottom: 5rem;
  border-top: 1px solid var(--neutral-100);
}
.section:first-child { border-top-width: 0; }
.section:last-child  { margin-bottom: 0; }

/* Below lg every part spans the full grid — the layout stacks. */
.section > * { grid-column: 1 / -1; }

.section-heading {
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.2;
  color: var(--neutral-950);
}

/* Was 14px/neutral-600. 16px is the floor here, and --muted is the role
   colour for secondary text. */
.section-desc {
  font-size: 16px;
  line-height: 1.3;
  color: var(--muted);
}

/* The heading + description stack. Sticky lives on this inner wrapper, not on
   the grid cell, so the cell can stretch to the row height and the label can
   travel down it. */
.section-side-inner {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.section-body { min-width: 0; }

/* Tailwind `lg:` = 64rem */
@media (min-width: 64rem) {
  .section--split > .section-side { grid-column: span 3; }
  .section--split > .section-body { grid-column: span 6; }

  .section--wide > .section-side { grid-column: 1 / -1; }
  .section--wide > .section-body { grid-column: 1 / -1; }

  .section--full > .section-body { grid-column: 1 / -1; }

  .section--split > .section-side .section-side-inner {
    position: sticky;
    top: 2rem;
    align-self: flex-start;
    padding-bottom: 2rem;
  }
}

/* <p className="text-sm text-neutral-400"> — empty state */
.sections-empty {
  font-size: 0.875rem;
  line-height: calc(1.25 / 0.875);
  color: var(--neutral-400);
}

/* ── PROSE (the `group` type's shared prose class list) ─────────────────── */
.prose {
  max-width: none;
  font-size: 16px;
  line-height: 1.625;
  color: var(--neutral-700);
}
.prose p { margin-bottom: 0.75rem; }
.prose p:last-child { margin-bottom: 0; }
.prose strong { color: var(--neutral-950); font-weight: 500; }
.prose ul { list-style: disc; padding-left: 1.25rem; margin-bottom: 0.75rem; }
.prose ol { list-style: decimal; padding-left: 1.25rem; margin-bottom: 0.75rem; }
.prose li { margin-bottom: 0.25rem; }
.prose a { color: var(--neutral-900); text-decoration: underline; text-underline-offset: 2px; }
.prose a:hover { opacity: 0.7; }
.prose blockquote {
  border-left: 2px solid var(--neutral-200);
  padding-left: 1rem;
  font-style: italic;
  color: var(--neutral-500);
  margin-block: 1rem;
}

/* ── editorial type — its own class list, deliberately not PROSE ────────── */
.prose-editorial {
  max-width: none;
  font-size: 16px;
  line-height: 1.625;
  color: var(--neutral-700);
}
.prose-editorial p { margin-bottom: 1rem; }
.prose-editorial p:last-child { margin-bottom: 0; }
.prose-editorial strong { color: var(--neutral-900); font-weight: 500; }
.prose-editorial h3 {
  font-size: 17px;
  font-weight: 500;
  color: var(--neutral-900);
  margin-top: 2rem;
  margin-bottom: 0.5rem;
}
.prose-editorial ul { list-style: disc; padding-left: 1.25rem; margin-bottom: 1rem; }
.prose-editorial ol { list-style: decimal; padding-left: 1.25rem; margin-bottom: 1rem; }
.prose-editorial li { margin-bottom: 0.375rem; }
.prose-editorial a { color: var(--neutral-900); text-decoration: underline; text-underline-offset: 2px; }
.prose-editorial a:hover { opacity: 0.7; }
.prose-editorial blockquote {
  border-left: 2px solid var(--neutral-200);
  padding-left: 1rem;
  font-style: italic;
  color: var(--neutral-500);
  margin-block: 1rem;
}
.prose-editorial cite {
  display: block;
  font-style: normal;
  font-size: 13px;
  color: var(--neutral-400);
  margin-top: 0.25rem;
}

/* ── group ──────────────────────────────────────────────────────────────── */
/* <div className={`${PROSE} mb-8`}> */
.group-intro { margin-bottom: 2rem; }
/* <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-10 gap-y-8"> */
.group-grid {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  column-gap: 2.5rem;
  row-gap: 2rem;
}
/* <div className="flex flex-col"> */
.group-item { display: flex; flex-direction: column; }
/* <h3 className="text-[16px] font-medium text-neutral-950 mb-2 leading-snug"> */
.group-item-heading {
  font-size: 16px;
  font-weight: 500;
  color: var(--neutral-950);
  margin-bottom: 0.5rem;
  line-height: 1.375;
}

/* ── specimen:color ─────────────────────────────────────────────────────── */
/* Was "grid grid-cols-2 sm:grid-cols-3 gap-3".
   The chips now butt together into one continuous band of colour — no gutter
   between them, so the palette reads as a field rather than as a set of cards.

   Only the COLUMN gap goes. Rows keep theirs: each swatch carries its name and
   hex below the chip, and with no row gap the next row's chip would start
   straight off that text. */
.swatch-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 0;
  row-gap: 1.5rem;
}
/* <button className="group flex flex-col text-left"> */
.swatch { display: flex; flex-direction: column; text-align: left; }
/* <div className="relative h-28 img-outline group-active:scale-[0.96]"> */
.swatch-chip {
  position: relative;
  height: 7rem;
  transition: scale var(--dur-fast) var(--ease-out);
}
.swatch:active .swatch-chip { scale: 0.96; }
/* <span className="absolute inset-0 flex items-center justify-center text-[12px] font-medium"> */
.swatch-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 500;
  background: rgba(255, 255, 255, 0.88);
  color: #0a0b0d;
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out);
}
.swatch:hover .swatch-overlay,
.swatch.is-copied .swatch-overlay { opacity: 1; }
/* The labels keep a gutter of their own — the chips touch, the words must not.
   padding-right only, so every label still starts flush with its chip's left
   edge and the column of names stays aligned. */
.swatch-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--neutral-900);
  margin-top: 0.5rem;
  padding-right: 0.75rem;
}
.swatch-value {
  font-size: 13px;
  color: var(--neutral-400);
  text-transform: uppercase;
  padding-right: 0.75rem;
}

/* ── specimen:type ──────────────────────────────────────────────────────── */
/* <div className="flex flex-col divide-y divide-neutral-100"> */
.type-list { display: flex; flex-direction: column; }
.type-list > * + * { border-top: 1px solid var(--neutral-100); }

/* Optional tint. A type sample on white has nothing holding it — the letters
   float. A light ground gives the specimen an edge without drawing a box. */
.type-list.is-tint {
  background: var(--tint-panel);
  padding: 1rem;
}
.type-list.is-tint > * + * { border-top-color: var(--tint-rule); }
/* <div className="flex items-baseline justify-between gap-6 py-4 first:pt-0"> */
.type-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: 1rem;
}
.type-row:first-child { padding-top: 0; }
/* <span className="text-neutral-900 font-medium tracking-tight truncate"> */
.type-sample {
  color: var(--neutral-900);
  font-weight: 500;
  letter-spacing: -0.025em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1;
}
/* <span className="shrink-0 text-right"> */
.type-meta { flex-shrink: 0; text-align: right; }
.type-name {
  display: block;
  font-size: 13px;
  font-weight: 500;
  color: var(--neutral-700);
}
.type-size {
  display: block;
  font-size: 13px;
  color: var(--neutral-400);
}

/* ── gallery ────────────────────────────────────────────────────────────── */
/* <div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: 1rem;
}
/* <figure className="flex flex-col"> */
.gallery-item { display: flex; flex-direction: column; }
/* <div className="w-full bg-white img-outline flex items-center justify-center overflow-hidden"> */
.gallery-frame {
  width: 100%;
  background: var(--n-0);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
/* contain (default) vs. cover */
.gallery-frame img { max-width: 70%; max-height: 60%; object-fit: contain; }
.gallery-frame img.is-cover { max-width: 100%; width: 100%; height: 100%; object-fit: cover; }
/* <div className="w-full bg-[#EEF0F3] flex items-center justify-center"> */
.gallery-placeholder {
  width: 100%;
  background: var(--n-15);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* <span className="text-[11px] font-mono text-neutral-400"> */
.gallery-ph-label { font-size: 12px; color: var(--neutral-400); }
/* <figcaption className="text-[12px] text-neutral-400 mt-2"> */
.gallery-caption { font-size: 13px; color: var(--muted); margin-top: 0.5rem; }

/* Tailwind `sm:` = 40rem */
@media (min-width: 40rem) {
  .group-grid   { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .swatch-grid  { grid-template-columns: repeat(var(--swatch-cols, 3), minmax(0, 1fr)); }
  .gallery-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LAYOUT VARIANTS
   Section-body types beyond the original four. Each one exists because a
   real pattern needed it, and each is driven by content JSON like the rest.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── statement ──────────────────────────────────────────────────────────────
   A lead paragraph set one step below the page title. Use with layout "full"
   so it runs the whole measure — it is a pause between sections, not a
   section with something to say on the left. */
.statement {
  font-size: 32px;
  line-height: 1.05;
  letter-spacing: -0.024em;
  color: var(--neutral-950);
  text-wrap: balance;
}
.statement em { font-style: normal; color: var(--muted); }

/* ── media ──────────────────────────────────────────────────────────────────
   One panel at a fixed aspect ratio. The workhorse for a single specimen,
   diagram or screen. aspect-ratio comes from the content JSON. */
.media-panel {
  width: 100%;
  background: var(--n-15);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.media-panel > img { width: 100%; height: 100%; object-fit: cover; }
.media-panel > img.is-contain { width: auto; height: auto; max-width: 70%; max-height: 60%; object-fit: contain; }
.media-label { font-size: 12px; color: var(--neutral-400); }
.media-caption { font-size: 13px; color: var(--muted); margin-top: 0.5rem; }

/* ── tiles ──────────────────────────────────────────────────────────────────
   A spanned grid. `columns` sets the track count, each item's `span` sets how
   many it takes. One primitive covers a palette grid, a 3x3 matrix and a
   mixed-span gallery — which is three separate components elsewhere. */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
}
.tile {
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
  padding: 0.75rem;
  background: var(--n-15);
  min-height: 0;
}
.tile-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--neutral-900);
}
.tile.is-dark .tile-label { color: var(--n-0); }

@media (min-width: 40rem) {
  .tile-grid { grid-template-columns: repeat(var(--tile-cols, 3), minmax(0, 1fr)); gap: 2rem; }
  .tile-grid.is-tight { gap: 0.75rem; }
}

/* ── compare ────────────────────────────────────────────────────────────────
   Two panels under one label, each carrying a verdict. The tone colours are
   the only place in the system where colour means something rather than
   belongs to something, so they are named by role. */
:root {
  --tone-right: #66C800;
  --tone-wrong: #FC401F;
}
.compare {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: 1.5rem;
}
@media (min-width: 40rem) {
  .compare { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2rem; }
}
.compare-panel { display: flex; flex-direction: column; }
.compare-verdict {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 13px;
  font-weight: 500;
  margin-bottom: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.compare-verdict::before {
  content: "";
  width: 8px;
  height: 8px;
  background: currentColor;
  flex-shrink: 0;
}
.compare-panel.is-right .compare-verdict { color: var(--tone-right); }
.compare-panel.is-wrong .compare-verdict { color: var(--tone-wrong); }
.compare-body { font-size: 16px; line-height: 1.3; color: var(--neutral-700); }
.compare-body p + p { margin-top: 0.75rem; }

/* ── cards ──────────────────────────────────────────────────────────────────
   The end-of-page pair that points at what to read next. A tall panel with a
   pill under it. */
.cards {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: 1.5rem 2rem;
}
@media (min-width: 40rem) {
  .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2.5rem 2rem; }
}
.card { display: flex; flex-direction: column; }
.card-panel {
  width: 100%;
  aspect-ratio: 16 / 11;
  background: var(--n-15);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color var(--dur) var(--ease-out);
}
.card:hover .card-panel { background: var(--n-30); }
.card-pill {
  align-self: flex-start;
  margin-top: 1rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--neutral-200);
  font-size: 15px;
  color: var(--neutral-900);
  transition: background-color var(--dur-fast) var(--ease-io),
              border-color var(--dur-fast) var(--ease-io);
}
.card:hover .card-pill { background: var(--n-10); border-color: var(--neutral-300); }

/* ── heading row (layout "wide") ────────────────────────────────────────────
   When the content runs full width, the heading sits above it rather than
   beside it, and the description moves to the right so the row still reads as
   two columns. */
@media (min-width: 64rem) {
  .section--wide > .section-side .section-side-inner {
    display: grid;
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
    column-gap: var(--grid-gap);
    gap: 0;
  }
  .section--wide .section-heading { grid-column: span 3; }
  .section--wide .section-desc { grid-column: 4 / span 6; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   EYEBROW, TAG, PANEL STACK
   Three small patterns measured off brand.base.org/typography. They are what
   makes a specimen page readable without adding a single box or shadow.

   Note on radius: base puts 8px (`rounded-lg`) on the chips and panels. This
   system is flat by rule (`border-radius: 0 !important` above), so they are
   square here. That is a Velvet decision, not an oversight.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── eyebrow ────────────────────────────────────────────────────────────────
   The muted line ABOVE a heading — a category, not a sub-heading. 16px, same
   size as body: it is quiet because of colour, not because it is small.

   The negative margin cancels half of .section-side-inner's 16px gap, so the
   eyebrow sits 8px off its heading while the description keeps the full 16px.
   The pair reads as one unit that way. */
.eyebrow {
  font-size: 16px;
  line-height: 1.3;
  font-weight: 400;
  color: var(--eyebrow);
  margin-bottom: -0.5rem;
}

/* ── tag ────────────────────────────────────────────────────────────────────
   A chip that names the specimen below it. Shrink-wrapped, never full width. */
.tag {
  display: inline-grid;
  place-items: center;
  width: fit-content;
  padding: 0.25rem 0.5rem;
  background: var(--tint-chip);
  margin-bottom: 1rem;
}
.tag-label {
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--neutral-950);
}

/* ── panel stack ────────────────────────────────────────────────────────────
   Hairline rule, tag, tinted panel — repeated. The pattern behind a specimen
   page: each block is separated by a rule rather than a border around a card,
   so the page stays flat and the eye still knows where a block ends. */
.panel-stack { display: flex; flex-direction: column; gap: 2rem; }

.panel-block {
  border-top: 1px solid var(--tint-rule);
  padding-top: 1rem;
}

.tint-panel {
  background: var(--tint-panel);
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
@media (min-width: 64rem) {
  .tint-panel { gap: 0.5rem; }
}
.tint-panel > * { min-width: 0; }

/* A specimen line inside a tint panel — one sample per row. */
.panel-line {
  font-size: 32px;
  line-height: 1.5;
  letter-spacing: -0.024em;
  color: var(--neutral-950);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.panel-line.is-italic { font-style: italic; }
.panel-line.is-mono { font-family: var(--font-mono); letter-spacing: 0; }


/* ═══════════════════════════════════════════════════════════════════════════
   DARK PAGE THEME
   A page can be set on Dark purple instead of Base. It is one class, and it
   works by moving the tokens — not by restating every component in a dark
   variant. Anything already built on the ramp flips for free; anything that
   does not flip was carrying a hardcoded colour, which is a bug in that
   component rather than a gap in the theme.

   The light ramp below is hue-locked to the ground (306.6°) with chroma kept
   low, so it reads as light rather than as more purple. Measured contrast on
   #420068: headings 13.0:1, intro 10.9:1, body 8.9:1, muted 5.9:1 — all clear
   of AA. The two rule steps sit below AA on purpose; they are hairlines, not
   text.
   ═══════════════════════════════════════════════════════════════════════════ */
.page.is-dark {
  background: #420068;

  --neutral-950: #F4EFFB;   /* headings, statement */
  --neutral-900: #F4EFFB;
  --neutral-800: #E2DBEA;   /* intro */
  --neutral-700: #CFC6D8;   /* body */
  --neutral-600: #A9A0B4;
  --neutral-500: #A9A0B4;
  --neutral-400: #857C90;   /* captions, placeholder labels */
  --neutral-300: #584D64;
  --neutral-200: #584D64;   /* blockquote rule */
  --neutral-100: #483D54;   /* section hairline */

  --muted:   #A9A0B4;
  --eyebrow: #857C90;

  /* Surfaces step UP from the ground rather than down. */
  --tint-panel: #501A77;
  --tint-chip:  #602F88;
  --tint-rule:  #584D64;

  --n-0:  #420068;
  --n-10: #4A0F70;
  --n-15: #501A77;
  --n-30: #602F88;
}

/* The hero watermark sits on the image, so it keeps its own light treatment. */
.page.is-dark .hero-fallback {
  background: linear-gradient(135deg, #4A0F70 0%, #501A77 60%, #602F88 100%);
}
