@import "tailwindcss";
@import "@gradeui/core/tokens.css"; /* primitive ramps + semantic aliases + type scale (layers 1-2) */

/* v4 NATIVE THEME — the v3 config bridge (tailwind.config.ts →
   tailwind-preset.ts) is retired (THEME-MIGRATION.md Phase A). Everything
   the preset defined lives here as native @theme declarations.

   Source scanning: v4's automatic detection covers everything inside this
   package (components/, lib/, styles/ — INCLUDING the .md sidecars, whose
   class mentions the old @config build was already picking up via
   auto-detection; the v3 `content` globs were additive, not exclusive).
   Only out-of-package sources need explicit @source directives. */

/* Playground scaffolds — siloed dev-only set rendered by Studio's
   Playground tab. Scanning them here means arbitrary Tailwind values
   (`h-[600px]`, `md:grid-cols-[minmax(0,440px)_1fr]`, etc.) get compiled
   into dist/styles.css — the bundle Fast Frame loads in its iframe.
   Without this, arbitrary classes would be present in the JSX but
   generate no CSS rules. (Path is relative to this file.) */
@source "../../studio/src/playbook/layouts/scaffolds-playground/*.{jsx,tsx}";

/* v3 `darkMode: "class"` — same selector the @config compat layer
   generated, so dark: variants are unchanged. */
@custom-variant dark (&:is(.dark *));

@plugin "tailwindcss-animate";

/* The theme bridge. `inline` because every value here references a runtime
   CSS variable (the GradeThemeProvider contract) — utilities must carry
   the var() reference itself, not a second-hop --color-* theme var.
   `reference` so none of these are re-emitted into :root (the runtime
   vars are authored in @gradeui/core/tokens.css and the :root blocks
   below; emitting e.g. `--shadow-lift: var(--shadow-lift)` would be
   circular). Net effect: byte-compatible with the old @config output.

   Fonts are deliberately ABSENT: the runtime vars are literally named
   --font-sans / --font-mono (same as v4's theme namespace), so preflight's
   `--default-font-family: var(--font-sans)` already lands on them and the
   font-sans/font-mono utilities resolve correctly with no declaration —
   declaring them here would be the one truly circular case. */
/* -- Type scale extension — `2xs` (11px) is the dense tool-panel step
   below `xs`, used by the Studio inspector and other Figma-density
   surfaces. Declared in a PLAIN @theme block (not inline/reference) so
   the utility references var(--text-2xs) — that's what lets the theme
   generator re-pitch the whole named ladder at runtime (Phase B2): a
   modular scale writes --text-2xs…--text-7xl to :root and every text-*
   utility follows. -- */
@theme {
  --text-2xs: 0.6875rem;
  --text-2xs--line-height: 1rem;
  /* Accent font role — gives a `font-accent` utility that references the
     runtime --font-accent var (set by the theme generator / apply.ts).
     Plain @theme (not inline) so the utility stays var-referencing and
     re-skins live; falls back to the display/sans stack until a theme
     sets one. */
  --font-accent: var(--font-display, var(--font-sans));
}

@theme inline reference {
  /* -- Brand color ramps — values live in @gradeui/core as --gds-* vars. -- */
  --color-gds-green: var(--gds-green);
  --color-gds-green-50: var(--gds-green-50);
  --color-gds-green-100: var(--gds-green-100);
  --color-gds-green-200: var(--gds-green-200);
  --color-gds-green-300: var(--gds-green-300);
  --color-gds-green-400: var(--gds-green-400);
  --color-gds-green-500: var(--gds-green-500);
  --color-gds-green-600: var(--gds-green-600);
  --color-gds-green-700: var(--gds-green-700);
  --color-gds-green-800: var(--gds-green-800);
  --color-gds-green-900: var(--gds-green-900);
  --color-gds-green-950: var(--gds-green-950);
  --color-gds-yellow: var(--gds-yellow);
  --color-gds-yellow-50: var(--gds-yellow-50);
  --color-gds-yellow-100: var(--gds-yellow-100);
  --color-gds-yellow-200: var(--gds-yellow-200);
  --color-gds-yellow-300: var(--gds-yellow-300);
  --color-gds-yellow-400: var(--gds-yellow-400);
  --color-gds-yellow-500: var(--gds-yellow-500);
  --color-gds-yellow-600: var(--gds-yellow-600);
  --color-gds-yellow-700: var(--gds-yellow-700);
  --color-gds-yellow-800: var(--gds-yellow-800);
  --color-gds-yellow-900: var(--gds-yellow-900);
  --color-gds-orange: var(--gds-orange);
  --color-gds-orange-50: var(--gds-orange-50);
  --color-gds-orange-100: var(--gds-orange-100);
  --color-gds-orange-200: var(--gds-orange-200);
  --color-gds-orange-300: var(--gds-orange-300);
  --color-gds-orange-400: var(--gds-orange-400);
  --color-gds-orange-500: var(--gds-orange-500);
  --color-gds-orange-600: var(--gds-orange-600);
  --color-gds-orange-700: var(--gds-orange-700);
  --color-gds-orange-800: var(--gds-orange-800);
  --color-gds-orange-900: var(--gds-orange-900);
  --color-gds-red: var(--gds-red);
  --color-gds-red-50: var(--gds-red-50);
  --color-gds-red-100: var(--gds-red-100);
  --color-gds-red-200: var(--gds-red-200);
  --color-gds-red-300: var(--gds-red-300);
  --color-gds-red-400: var(--gds-red-400);
  --color-gds-red-500: var(--gds-red-500);
  --color-gds-red-600: var(--gds-red-600);
  --color-gds-red-700: var(--gds-red-700);
  --color-gds-red-800: var(--gds-red-800);
  --color-gds-red-900: var(--gds-red-900);
  --color-gds-teal: var(--gds-teal);
  --color-gds-teal-50: var(--gds-teal-50);
  --color-gds-teal-100: var(--gds-teal-100);
  --color-gds-teal-200: var(--gds-teal-200);
  --color-gds-teal-300: var(--gds-teal-300);
  --color-gds-teal-400: var(--gds-teal-400);
  --color-gds-teal-500: var(--gds-teal-500);
  --color-gds-teal-600: var(--gds-teal-600);
  --color-gds-teal-700: var(--gds-teal-700);
  --color-gds-teal-800: var(--gds-teal-800);
  --color-gds-teal-900: var(--gds-teal-900);
  --color-gds-teal-950: var(--gds-teal-950);
  --color-gds-navy: var(--gds-navy);
  --color-gds-navy-50: var(--gds-navy-50);
  --color-gds-navy-100: var(--gds-navy-100);
  --color-gds-navy-200: var(--gds-navy-200);
  --color-gds-navy-300: var(--gds-navy-300);
  --color-gds-navy-400: var(--gds-navy-400);
  --color-gds-navy-500: var(--gds-navy-500);
  --color-gds-navy-600: var(--gds-navy-600);
  --color-gds-navy-700: var(--gds-navy-700);
  --color-gds-navy-800: var(--gds-navy-800);
  --color-gds-navy-900: var(--gds-navy-900);
  --color-gds-blue: var(--gds-blue);
  --color-gds-blue-50: var(--gds-blue-50);
  --color-gds-blue-100: var(--gds-blue-100);
  --color-gds-blue-200: var(--gds-blue-200);
  --color-gds-blue-300: var(--gds-blue-300);
  --color-gds-blue-400: var(--gds-blue-400);
  --color-gds-blue-500: var(--gds-blue-500);
  --color-gds-blue-600: var(--gds-blue-600);
  --color-gds-blue-700: var(--gds-blue-700);
  --color-gds-blue-800: var(--gds-blue-800);
  --color-gds-blue-900: var(--gds-blue-900);
  --color-gds-gray-50: var(--gds-gray-50);
  --color-gds-gray-100: var(--gds-gray-100);
  --color-gds-gray-200: var(--gds-gray-200);
  --color-gds-gray-300: var(--gds-gray-300);
  --color-gds-gray-400: var(--gds-gray-400);
  --color-gds-gray-500: var(--gds-gray-500);
  --color-gds-gray-600: var(--gds-gray-600);
  --color-gds-gray-700: var(--gds-gray-700);
  --color-gds-gray-800: var(--gds-gray-800);
  --color-gds-gray-900: var(--gds-gray-900);
  --color-gds-gray-950: var(--gds-gray-950);
  --color-gds-black: var(--gds-black);
  --color-gds-white: var(--gds-white);

  /* -- Semantic roles — runtime vars hold bare "L C H" OKLCH triplets,
     wrapped with oklch() at use. Opacity shortcuts (bg-primary/50) come
     out as color-mix() under v4 — visually identical to the old
     <alpha-value> substitution. -- */
  --color-border: oklch(var(--border));
  --color-input: oklch(var(--input));
  --color-ring: oklch(var(--ring));
  --color-background: oklch(var(--background));
  --color-foreground: oklch(var(--foreground));
  --color-primary: oklch(var(--primary));
  --color-primary-foreground: oklch(var(--primary-foreground));
  --color-secondary: oklch(var(--secondary));
  --color-secondary-foreground: oklch(var(--secondary-foreground));
  --color-destructive: oklch(var(--destructive));
  --color-destructive-foreground: oklch(var(--destructive-foreground));
  /* Alert surface pair — paler tinted bg + deeper on-surface text. */
  --color-destructive-soft: oklch(var(--destructive-soft));
  --color-destructive-deep: oklch(var(--destructive-deep));
  --color-muted: oklch(var(--muted));
  --color-muted-foreground: oklch(var(--muted-foreground));
  --color-super-muted-foreground: oklch(var(--super-muted-foreground));
  --color-accent: oklch(var(--accent));
  --color-accent-foreground: oklch(var(--accent-foreground));
  --color-popover: oklch(var(--popover));
  --color-popover-foreground: oklch(var(--popover-foreground));
  --color-card: oklch(var(--card));
  --color-card-foreground: oklch(var(--card-foreground));
  /* Status colours — each exposes `-soft` / `-deep` siblings, mapping to
     the tinted alert surface + readable text tokens generated by the
     theme pipeline (lib/themes/oklch.ts#deriveAlertPair). */
  --color-success: oklch(var(--success));
  --color-success-soft: oklch(var(--success-soft));
  --color-success-deep: oklch(var(--success-deep));
  --color-warning: oklch(var(--warning));
  --color-warning-soft: oklch(var(--warning-soft));
  --color-warning-deep: oklch(var(--warning-deep));
  --color-info: oklch(var(--info));
  --color-info-soft: oklch(var(--info-soft));
  --color-info-deep: oklch(var(--info-deep));
  --color-highlight: oklch(var(--highlight));
  --color-highlight-soft: oklch(var(--highlight-soft));
  --color-highlight-deep: oklch(var(--highlight-deep));
  /* Selection pair — solid fill, on-fill text, ambient glow halo. */
  --color-selected: oklch(var(--selected));
  --color-selected-foreground: oklch(var(--selected-foreground));
  --color-selected-glow: oklch(var(--selected-glow));
  /* Chart series palette — derived from theme hues by the generator. */
  --color-chart-1: oklch(var(--chart-1));
  --color-chart-2: oklch(var(--chart-2));
  --color-chart-3: oklch(var(--chart-3));
  --color-chart-4: oklch(var(--chart-4));
  --color-chart-5: oklch(var(--chart-5));

  /* -- Role ramp families (THEME-MIGRATION.md B4) — every semantic alias
     is a whole ramp: status displays many ways (soft 100 bg, solid 600
     fill, 800 text). The generator writes --gds-<role>-<step> OKLCH
     triplets to :root (applyThemeToRoot); each step falls back to the
     flat role triplet so e.g. bg-success-600 degrades to bg-success when
     no generated theme is active. `neutral` is deliberately ABSENT from
     the utility layer — --color-neutral-* would shadow Tailwind's
     default neutral palette; its family ships as runtime vars only. -- */
  --color-primary-50: oklch(var(--gds-primary-50, var(--primary)));
  --color-primary-100: oklch(var(--gds-primary-100, var(--primary)));
  --color-primary-200: oklch(var(--gds-primary-200, var(--primary)));
  --color-primary-300: oklch(var(--gds-primary-300, var(--primary)));
  --color-primary-400: oklch(var(--gds-primary-400, var(--primary)));
  --color-primary-500: oklch(var(--gds-primary-500, var(--primary)));
  --color-primary-600: oklch(var(--gds-primary-600, var(--primary)));
  --color-primary-700: oklch(var(--gds-primary-700, var(--primary)));
  --color-primary-800: oklch(var(--gds-primary-800, var(--primary)));
  --color-primary-900: oklch(var(--gds-primary-900, var(--primary)));
  --color-primary-950: oklch(var(--gds-primary-950, var(--primary)));
  --color-accent-50: oklch(var(--gds-accent-50, var(--accent)));
  --color-accent-100: oklch(var(--gds-accent-100, var(--accent)));
  --color-accent-200: oklch(var(--gds-accent-200, var(--accent)));
  --color-accent-300: oklch(var(--gds-accent-300, var(--accent)));
  --color-accent-400: oklch(var(--gds-accent-400, var(--accent)));
  --color-accent-500: oklch(var(--gds-accent-500, var(--accent)));
  --color-accent-600: oklch(var(--gds-accent-600, var(--accent)));
  --color-accent-700: oklch(var(--gds-accent-700, var(--accent)));
  --color-accent-800: oklch(var(--gds-accent-800, var(--accent)));
  --color-accent-900: oklch(var(--gds-accent-900, var(--accent)));
  --color-accent-950: oklch(var(--gds-accent-950, var(--accent)));
  --color-success-50: oklch(var(--gds-success-50, var(--success)));
  --color-success-100: oklch(var(--gds-success-100, var(--success)));
  --color-success-200: oklch(var(--gds-success-200, var(--success)));
  --color-success-300: oklch(var(--gds-success-300, var(--success)));
  --color-success-400: oklch(var(--gds-success-400, var(--success)));
  --color-success-500: oklch(var(--gds-success-500, var(--success)));
  --color-success-600: oklch(var(--gds-success-600, var(--success)));
  --color-success-700: oklch(var(--gds-success-700, var(--success)));
  --color-success-800: oklch(var(--gds-success-800, var(--success)));
  --color-success-900: oklch(var(--gds-success-900, var(--success)));
  --color-success-950: oklch(var(--gds-success-950, var(--success)));
  --color-warning-50: oklch(var(--gds-warning-50, var(--warning)));
  --color-warning-100: oklch(var(--gds-warning-100, var(--warning)));
  --color-warning-200: oklch(var(--gds-warning-200, var(--warning)));
  --color-warning-300: oklch(var(--gds-warning-300, var(--warning)));
  --color-warning-400: oklch(var(--gds-warning-400, var(--warning)));
  --color-warning-500: oklch(var(--gds-warning-500, var(--warning)));
  --color-warning-600: oklch(var(--gds-warning-600, var(--warning)));
  --color-warning-700: oklch(var(--gds-warning-700, var(--warning)));
  --color-warning-800: oklch(var(--gds-warning-800, var(--warning)));
  --color-warning-900: oklch(var(--gds-warning-900, var(--warning)));
  --color-warning-950: oklch(var(--gds-warning-950, var(--warning)));
  --color-info-50: oklch(var(--gds-info-50, var(--info)));
  --color-info-100: oklch(var(--gds-info-100, var(--info)));
  --color-info-200: oklch(var(--gds-info-200, var(--info)));
  --color-info-300: oklch(var(--gds-info-300, var(--info)));
  --color-info-400: oklch(var(--gds-info-400, var(--info)));
  --color-info-500: oklch(var(--gds-info-500, var(--info)));
  --color-info-600: oklch(var(--gds-info-600, var(--info)));
  --color-info-700: oklch(var(--gds-info-700, var(--info)));
  --color-info-800: oklch(var(--gds-info-800, var(--info)));
  --color-info-900: oklch(var(--gds-info-900, var(--info)));
  --color-info-950: oklch(var(--gds-info-950, var(--info)));
  --color-highlight-50: oklch(var(--gds-highlight-50, var(--highlight)));
  --color-highlight-100: oklch(var(--gds-highlight-100, var(--highlight)));
  --color-highlight-200: oklch(var(--gds-highlight-200, var(--highlight)));
  --color-highlight-300: oklch(var(--gds-highlight-300, var(--highlight)));
  --color-highlight-400: oklch(var(--gds-highlight-400, var(--highlight)));
  --color-highlight-500: oklch(var(--gds-highlight-500, var(--highlight)));
  --color-highlight-600: oklch(var(--gds-highlight-600, var(--highlight)));
  --color-highlight-700: oklch(var(--gds-highlight-700, var(--highlight)));
  --color-highlight-800: oklch(var(--gds-highlight-800, var(--highlight)));
  --color-highlight-900: oklch(var(--gds-highlight-900, var(--highlight)));
  --color-highlight-950: oklch(var(--gds-highlight-950, var(--highlight)));
  --color-destructive-50: oklch(var(--gds-destructive-50, var(--destructive)));
  --color-destructive-100: oklch(var(--gds-destructive-100, var(--destructive)));
  --color-destructive-200: oklch(var(--gds-destructive-200, var(--destructive)));
  --color-destructive-300: oklch(var(--gds-destructive-300, var(--destructive)));
  --color-destructive-400: oklch(var(--gds-destructive-400, var(--destructive)));
  --color-destructive-500: oklch(var(--gds-destructive-500, var(--destructive)));
  --color-destructive-600: oklch(var(--gds-destructive-600, var(--destructive)));
  --color-destructive-700: oklch(var(--gds-destructive-700, var(--destructive)));
  --color-destructive-800: oklch(var(--gds-destructive-800, var(--destructive)));
  --color-destructive-900: oklch(var(--gds-destructive-900, var(--destructive)));
  --color-destructive-950: oklch(var(--gds-destructive-950, var(--destructive)));

  /* Surface backgrounds — `bg-surface-glass` etc. work alongside the
     `.gds-surface-*` classes (those add backdrop-filter). */
  --color-surface-solid: var(--surface-solid);
  --color-surface-translucent: var(--surface-translucent);
  --color-surface-glass: var(--surface-glass);
  --color-surface-glass-strong: var(--surface-glass-strong);

  /* -- Radius — pinned to the theme's --radius base. -- */
  /* xl was the one step NOT tied to the theme's radius, so a rounder
     or sharper theme moved every corner except the largest. */
  --radius-xl: calc(var(--radius) + 4px);
  --radius-lg: var(--radius);
  --radius-md: calc(var(--radius) - 2px);
  --radius-sm: calc(var(--radius) - 4px);

  /* -- Presence — elevation tokens exposed both as semantic
     `shadow-elevation-N` utilities AND as overrides of Tailwind's default
     `shadow-sm/md/lg/xl/2xl` scale so existing call sites inherit the new
     system without code changes. See PRESENCE.md.
     (`shadow-none` stays v4's static `0 0 #0000` — that's what the old
     build shipped too; the preset's `none: var(--elevation-0)` never won.) */
  --shadow: var(--elevation-2);
  --shadow-sm: var(--elevation-1);
  --shadow-md: var(--elevation-4);
  --shadow-lg: var(--elevation-5);
  --shadow-xl: var(--elevation-5);
  --shadow-2xl: var(--elevation-5);
  --shadow-inner: var(--shadow-pressed-bevel);
  /* Explicit elevation levels (preferred for new code) */
  --shadow-elevation-0: var(--elevation-0);
  --shadow-elevation-1: var(--elevation-1);
  --shadow-elevation-2: var(--elevation-2);
  --shadow-elevation-3: var(--elevation-3);
  --shadow-elevation-4: var(--elevation-4);
  --shadow-elevation-5: var(--elevation-5);
  /* State variants for raised/tactile surfaces */
  --shadow-raised: var(--elevation-3);
  --shadow-hot: var(--elevation-hot);
  --shadow-pressed: var(--elevation-pressed);
  /* Single-layer atoms (when you need bevel-only / lift-only) */
  --shadow-bevel-hi: var(--shadow-bevel-hi);
  --shadow-bevel-lo: var(--shadow-bevel-lo);
  --shadow-contact: var(--shadow-contact);
  --shadow-lift: var(--shadow-lift);
  --shadow-lift-deep: var(--shadow-lift-deep);
  --shadow-heat-inner: var(--shadow-heat-inner);
  --shadow-heat-outer: var(--shadow-heat-outer);

  /* -- Backdrop blur (glass surfaces) — v4 shares the --blur-* namespace
     between blur-* and backdrop-blur-*; only the backdrop forms are part
     of the public API. -- */
  --blur-glass: var(--surface-blur-glass);
  --blur-glass-strong: var(--surface-blur-strong);
  --blur-subtle: var(--surface-blur-subtle);

  /* -- Motion -- */
  --animate-accordion-down: accordion-down 0.2s ease-out;
  --animate-accordion-up: accordion-up 0.2s ease-out;
  --animate-fade-in: fade-in 0.2s ease-out;
  --animate-fade-out: fade-out 0.2s ease-in;
  --animate-scale-in: scale-in 0.15s ease-out;
  --animate-slide-in-from-top: slide-in-from-top 0.2s ease-out;
  --animate-slide-in-from-bottom: slide-in-from-bottom 0.2s ease-out;
  --animate-slide-in-from-left: slide-in-from-left 0.2s ease-out;
  --animate-slide-in-from-right: slide-in-from-right 0.2s ease-out;
  --animate-energy-pulse: energy-pulse 2s ease-in-out infinite;
  --animate-shimmer: shimmer 2s linear infinite;

  @keyframes accordion-down {
    from {
      height: 0;
    }
    to {
      height: var(--radix-accordion-content-height);
    }
  }
  @keyframes accordion-up {
    from {
      height: var(--radix-accordion-content-height);
    }
    to {
      height: 0;
    }
  }
  @keyframes fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
  @keyframes fade-out {
    from {
      opacity: 1;
    }
    to {
      opacity: 0;
    }
  }
  @keyframes scale-in {
    from {
      opacity: 0;
      transform: scale(0.95);
    }
    to {
      opacity: 1;
      transform: scale(1);
    }
  }
  @keyframes slide-in-from-top {
    from {
      opacity: 0;
      transform: translateY(-10px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
  @keyframes slide-in-from-bottom {
    from {
      opacity: 0;
      transform: translateY(10px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
  @keyframes slide-in-from-left {
    from {
      opacity: 0;
      transform: translateX(-10px);
    }
    to {
      opacity: 1;
      transform: translateX(0);
    }
  }
  @keyframes slide-in-from-right {
    from {
      opacity: 0;
      transform: translateX(10px);
    }
    to {
      opacity: 1;
      transform: translateX(0);
    }
  }
  @keyframes energy-pulse {
    0%,
    100% {
      opacity: 1;
      box-shadow: 0 0 0 0 oklch(var(--accent) / 0.4);
    }
    50% {
      opacity: 0.8;
      box-shadow: 0 0 0 8px oklch(var(--accent) / 0);
    }
  }
  @keyframes shimmer {
    0% {
      background-position: 200% 0;
    }
    100% {
      background-position: -200% 0;
    }
  }
}

/* Elevation/presence shadow utilities are typed at runtime (Studio output,
   generated screens), so the scanner never sees them in source. v4's
   `@source inline(...)` force-emits them — this replaces the JS `safelist`
   that v4 removed. */
@source inline("shadow-{elevation-0,elevation-1,elevation-2,elevation-3,elevation-4,elevation-5,raised,hot,pressed,bevel-hi,bevel-lo,contact,lift,lift-deep,heat-inner,heat-outer}");

/* The Studio inspector's WHOLE token vocabulary — every class the
   TokenField controls can mint at runtime. None of these literals appear
   in scanned source (they're assembled from registry scales while
   editing), so without this block they'd produce no CSS in Fast Frame —
   the "blend modes don't do anything" bug, one family at a time. Finite
   sets, so safelisting is cheap and correct. */
@source inline("mix-blend-{normal,multiply,screen,overlay,darken,lighten,color-dodge,color-burn,difference,exclusion,hue,saturation,color,luminosity}");
@source inline("opacity-{0,5,10,20,30,40,50,60,70,80,90,100}");
@source inline("text-{2xs,xs,sm,base,lg,xl,2xl,3xl,4xl,5xl}");
@source inline("font-{thin,extralight,light,normal,medium,semibold,bold,extrabold,black}");
@source inline("rounded{,-none,-sm,-md,-lg,-xl,-2xl,-3xl,-full}");
@source inline("rounded-{tl,tr,br,bl,t,r,b,l}{,-none,-sm,-md,-lg,-xl,-2xl,-3xl,-full}");
@source inline("{p,m}{,t,r,b,l,x,y}-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,14,16,20,24}");
@source inline("gap-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,8,10,12}");
@source inline("grid-cols-{1,2,3,4,5,6,8,12}");
@source inline("shadow{,-none,-sm,-md,-lg,-xl,-2xl,-inner}");
@source inline("bg-{background,card,muted,secondary,accent,primary,destructive,transparent}");
/* Border family — the inspector's BorderGroup mints every one of these at
   runtime (side-aware widths, styles, colours, and ring-based inside/outside
   positions). Without the force-emit, anything the scanner hasn't seen in
   source (border-4, border-t-8, ring-inset…) silently renders as no CSS in
   Fast Frame — the ">2px borders don't work" bug. */
@source inline("border{,-t,-r,-b,-l,-x,-y}{,-0,-2,-4,-8}");
@source inline("border-{solid,dashed,dotted,double}");
@source inline("border{,-t,-r,-b,-l}-{border,foreground,primary,muted-foreground,destructive,ring}");
@source inline("ring{,-0,-1,-2,-4,-8}");
@source inline("ring-inset");
@source inline("ring-{border,foreground,primary,muted-foreground,destructive,ring}");
/* Typography family — the inspector's TypographyGroup mints these at
   runtime (line-height, letter-spacing, text-align). Same force-emit
   rationale as the border/blend families above. */
@source inline("leading-{none,tight,snug,normal,relaxed,loose}");
@source inline("tracking-{tighter,tight,normal,wide,wider,widest}");
@source inline("text-{left,center,right,justify}");
/* Display sizes + responsive variants — generated heroes lean on
   `text-6xl`/`md:text-8xl`-style ladders that never appear in scanned
   source, so the responsive variants silently no-oped in Fast Frame.
   sm/md/lg cover BOTH model output and everything the inspector's
   BreakpointOverridesEditor can mint (size, weight, line-height,
   tracking, align — keep in sync with EDITABLE_BREAKPOINTS). */
@source inline("{,sm:,md:,lg:}text-{6xl,7xl,8xl,9xl}");
@source inline("{sm:,md:,lg:}text-{2xs,xs,sm,base,lg,xl,2xl,3xl,4xl,5xl}");
@source inline("{sm:,md:,lg:}font-{thin,extralight,light,normal,medium,semibold,bold,extrabold,black}");
@source inline("{sm:,md:,lg:}leading-{none,tight,snug,normal,relaxed,loose}");
@source inline("{sm:,md:,lg:}tracking-{tighter,tight,normal,wide,wider,widest}");
@source inline("{sm:,md:,lg:}text-{left,center,right,justify}");
/* Gradient-text + gradient-stop recipe — `bg-clip-text text-transparent
   bg-gradient-to-b from-foreground to-foreground/75` is the canonical
   generated-hero headline. All of it is runtime-typed (model output),
   none of it appears in scanned source. Stops cover the semantic tokens
   at full strength plus a sensible opacity ladder. */
@source inline("bg-clip-text");
@source inline("text-transparent");
@source inline("text-super-muted-foreground");
@source inline("bg-gradient-to-{t,tr,r,br,b,bl,l,tl}");
@source inline("{from,via,to}-{background,foreground,card,card-foreground,muted,muted-foreground,primary,primary-foreground,secondary,accent,destructive,border,transparent,white,black}");
@source inline("{from,via,to}-{background,foreground,muted,primary,secondary,accent,white,black}/{10,20,25,30,40,50,60,70,75,80,90}");
/* Animate-in vocabulary (tailwindcss-animate enter/exit modifiers) +
   easing — generated screens compose `animate-in fade-in
   slide-in-from-top`-style entrances at runtime, so the scanner never
   sees the pieces in source. Until the v4 native-@theme migration these
   leaked into the build by accident (the scanner picked them out of
   tailwind-preset.ts comment/value text); now they're force-emitted
   deliberately. */
@source inline("fade-{in,out}");
@source inline("slide-in-from-{top,bottom,left,right}");
@source inline("ease-{in,out,in-out}");
/* Role ramp family vocabulary (THEME-MIGRATION.md B4) — bg/text/border
   at every step for every themed role. Runtime-typed (model output +
   future inspector token picker), so force-emit like the other
   families. The --color-<role>-<step> @theme entries below define the
   values; the generator's --gds-<role>-<step> vars make them live. */
@source inline("bg-{primary,accent,success,warning,info,highlight,destructive}-{50,100,200,300,400,500,600,700,800,900,950}");
@source inline("text-{primary,accent,success,warning,info,highlight,destructive}-{50,100,200,300,400,500,600,700,800,900,950}");
@source inline("border-{primary,accent,success,warning,info,highlight,destructive}-{50,100,200,300,400,500,600,700,800,900,950}");
/* Parity holdovers from the same accidental scan — shipped in every
   previous dist/styles.css, kept so no runtime-typed class loses its
   rules. TODO(follow-up): widen bg-surface-glass to the full
   bg-surface-{solid,translucent,glass,glass-strong} family as a
   deliberate (additive) API decision, and decide whether the two
   playground measurements below should live in a scaffold instead. */
@source inline("bg-surface-glass");
@source inline("h-[600px]");
@source inline("md:grid-cols-[minmax(0,440px)_1fr]");

/* ============================================
   GRADE DESIGN SYSTEM - COMPONENT-LAYER TOKENS
   ============================================ */

:root {
  /* Brand color ramps, neutral grays, semantic aliases (--gds-success …),
     spacing scale, and border radii moved to @gradeui/core — see the
     `@import "@gradeui/core/tokens.css"` at the top of this file and
     packages/core/styles/tokens.css. Component tokens stay below. */
  /* ============================================
     ELEVATION SYSTEM
     ============================================
     A single vocabulary for every box-shadow in the DS. Three layers:

     1. ATOMIC SUB-TOKENS — `-y` (y-offset), `-blur`, `-spread`, `-alpha`.
        These are the bare numbers a theme tweaks to tune "depth" globally
        without touching component CSS. e.g. lowering `--shadow-lift-alpha`
        across the board gives the whole system a "lighter" feel.

     2. COMPOSED LAYER TOKENS — full box-shadow expressions for one layer
        each (`--shadow-bevel-hi`, `--shadow-contact`, `--shadow-lift`,
        etc.). Components compose layers into a complete shadow stack
        without having to know the atomic numbers.

     3. ELEVATION PRESETS — `--elevation-0` through `--elevation-5` plus
        state variants (`--elevation-hot`, `--elevation-pressed`). The
        default contract: components read these, not the layers, unless
        they're doing something custom (Button raised, custom cards).

     Light source convention: light comes from above. Bevel-hi sits at
     the top edge, bevel-lo at the bottom, drops fall downward (positive
     y). Flipping this in a future "inverse light" theme = changing the
     atomic y-offsets to negative; no component CSS changes.

     Color convention:
       bevel-hi   — white (the highlight)
       bevel-lo   — black (the undercut)
       contact    — black (tight close drop)
       lift       — black (diffuse ambient drop)
       heat-inner — var(--btn-glow) (tonal hover bloom, inset from bottom)
       heat-outer — var(--btn-glow) (tonal hover bloom, outer drop)
     -------------------------------------------- */

  /* -- Layer 1: bevel-hi (top inset highlight) -- */
  --shadow-bevel-hi-y: 1px;
  --shadow-bevel-hi-alpha: 0.12;
  --shadow-bevel-hi: inset 0 var(--shadow-bevel-hi-y) 0 oklch(1 0 0 / var(--shadow-bevel-hi-alpha));

  /* -- Layer 2: bevel-lo (bottom inset shadow) -- */
  --shadow-bevel-lo-y: 1px;
  --shadow-bevel-lo-alpha: 0.30;
  --shadow-bevel-lo: inset 0 calc(-1 * var(--shadow-bevel-lo-y)) 0 oklch(0 0 0 / var(--shadow-bevel-lo-alpha));

  /* -- Layer 3: contact (tight outer drop, the "sitting on a surface" feel) -- */
  --shadow-contact-y: 1px;
  --shadow-contact-blur: 2px;
  --shadow-contact-alpha: 0.10;
  --shadow-contact: 0 var(--shadow-contact-y) var(--shadow-contact-blur) oklch(0 0 0 / var(--shadow-contact-alpha));

  /* -- Layer 4: lift (diffuse outer drop, the "floating" feel) -- */
  --shadow-lift-y: 4px;
  --shadow-lift-blur: 12px;
  --shadow-lift-alpha: 0.18;
  --shadow-lift: 0 var(--shadow-lift-y) var(--shadow-lift-blur) oklch(0 0 0 / var(--shadow-lift-alpha));

  /* -- Layer 5: lift-deep (deeper diffuse drop for dialogs/sheets) -- */
  --shadow-lift-deep-y: 16px;
  --shadow-lift-deep-blur: 40px;
  --shadow-lift-deep-alpha: 0.28;
  --shadow-lift-deep: 0 var(--shadow-lift-deep-y) var(--shadow-lift-deep-blur) oklch(0 0 0 / var(--shadow-lift-deep-alpha));

  /* -- Layer 6: heat-inner (tonal hover bloom, bleeds in from bottom) -- */
  --shadow-heat-inner-y: 10px;
  --shadow-heat-inner-blur: 24px;
  --shadow-heat-inner-spread: 4px;
  --shadow-heat-inner-alpha: 0.55;
  --shadow-heat-inner: inset 0 calc(-1 * var(--shadow-heat-inner-y)) var(--shadow-heat-inner-blur) calc(-1 * var(--shadow-heat-inner-spread)) oklch(var(--btn-glow, var(--selected-glow)) / var(--shadow-heat-inner-alpha));

  /* -- Layer 7: heat-outer (tonal hover bloom, escapes outward) -- */
  --shadow-heat-outer-y: 6px;
  --shadow-heat-outer-blur: 18px;
  --shadow-heat-outer-alpha: 0.30;
  --shadow-heat-outer: 0 var(--shadow-heat-outer-y) var(--shadow-heat-outer-blur) oklch(var(--btn-glow, var(--selected-glow)) / var(--shadow-heat-outer-alpha));

  /* -- Layer 8: pressed (inset shadow simulating a key push) -- */
  --shadow-pressed-y: 2px;
  --shadow-pressed-blur: 4px;
  --shadow-pressed-alpha: 0.45;
  --shadow-pressed-bevel: inset 0 var(--shadow-pressed-y) var(--shadow-pressed-blur) oklch(0 0 0 / var(--shadow-pressed-alpha));

  /* -- Composed elevation presets --
     0 = flat, 1 = minimal contact, 2 = interactive surface (bevel + contact),
     3 = raised key (bevel + contact + lift), 4 = popover/dropdown,
     5 = dialog/sheet/modal. Components reach for these by default. */
  --elevation-0: 0 0 0 0 transparent;
  --elevation-1: var(--shadow-contact);
  --elevation-2: var(--shadow-bevel-hi), var(--shadow-bevel-lo), var(--shadow-contact);
  --elevation-3: var(--shadow-bevel-hi), var(--shadow-bevel-lo), var(--shadow-contact), var(--shadow-lift);
  --elevation-4: var(--shadow-contact), var(--shadow-lift);
  --elevation-5: var(--shadow-contact), var(--shadow-lift-deep);

  /* State variants for interactive surfaces (Button raised, Toggle, Tab) */
  --elevation-hot: var(--shadow-bevel-hi), var(--shadow-bevel-lo), var(--shadow-heat-inner), var(--shadow-contact), var(--shadow-heat-outer), var(--shadow-lift);
  --elevation-pressed: var(--shadow-pressed-bevel), inset 0 -1px 0 oklch(1 0 0 / 0.04), var(--shadow-contact);

  /* ----------------------------------------
     Legacy shadow tokens — repointed onto the elevation system so call
     sites that read `--gds-shadow-*` directly inherit the new look
     without a code change. Kept here as a compatibility shim; new code
     should prefer the elevation tokens above.
     ---------------------------------------- */
  --gds-shadow-sm: var(--elevation-1);
  --gds-shadow-md: var(--elevation-4);
  --gds-shadow-lg: var(--elevation-5);
  --gds-shadow-xl: var(--elevation-5);
  --gds-shadow-2xl: var(--elevation-5);
  --gds-shadow-inner: var(--shadow-pressed-bevel);

  /* ============================================
     SURFACE SYSTEM
     ============================================
     What the surface is *made of*. Independent from elevation (how
     high it sits) and from aura (what it's radiating). A glass card
     can sit at elevation-4 with a calm aura, or at elevation-2 with
     no aura — these axes don't entangle.

     Atomic alpha + blur sub-tokens make the look tunable per theme
     (frosted dark vs. light glass want different opacity floors).
     The composed `--surface-*` tokens read from `--card` so they
     stay theme-aware — switching themes changes the glass tint.
     -------------------------------------------- */

  /* -- Atomic alphas -- */
  --surface-alpha-solid: 1;
  --surface-alpha-translucent: 0.8;
  --surface-alpha-glass: 0.6;
  --surface-alpha-glass-strong: 0.4;
  --surface-alpha-frosted: 0.9; /* near-opaque, just a whisper of the page behind */

  /* -- Atomic blurs -- */
  --surface-blur-subtle: 6px;
  --surface-blur-glass: 14px;
  --surface-blur-strong: 24px;

  /* -- Atomic inner-edge highlight (the "wet" line on glass surfaces) -- */
  --surface-edge-alpha: 0.10;
  --surface-edge: inset 0 1px 0 oklch(1 0 0 / var(--surface-edge-alpha));

  /* -- Composed surface backgrounds — theme-aware via --card -- */
  --surface-solid: oklch(var(--card) / var(--surface-alpha-solid));
  --surface-translucent: oklch(var(--card) / var(--surface-alpha-translucent));
  --surface-glass: oklch(var(--card) / var(--surface-alpha-glass));
  --surface-glass-strong: oklch(var(--card) / var(--surface-alpha-glass-strong));
  --surface-frosted: oklch(var(--card) / var(--surface-alpha-frosted));

  /* ============================================
     AURA SYSTEM
     ============================================
     What's actively *radiating* from an element. State signals — AI
     attention, generation-in-progress, selection focus. Three
     composable styles: ring (pulsing outer shadow), gradient
     (rotating conic border), shimmer (diagonal sweep). They stack —
     `.gds-aura-ring.gds-aura-shimmer` runs both passes.

     Per-style timing & easing vars are overridable at the consumer
     level, so a heavyweight button can slow its pulse without
     rewriting keyframes:

       <Button className="gds-aura-ring"
               style={{ '--aura-pulse-duration': '3.2s' }} />

     Color defaults to --selected-glow (blue, themed). Override
     per-instance via `--aura-color` for danger / success / brand
     attention.
     -------------------------------------------- */

  /* -- Color -- */
  --aura-color: var(--selected-glow);

  /* -- Ring (pulsing outer halo via animated box-shadow) -- */
  --aura-ring-spread: 2px;
  --aura-ring-blur: 14px;
  --aura-ring-alpha-min: 0.00;
  --aura-ring-alpha-max: 0.65;
  --aura-pulse-duration: 2400ms;
  --aura-pulse-ease: cubic-bezier(0.4, 0, 0.2, 1);

  /* -- Gradient (rotating conic-gradient border) -- */
  --aura-gradient-thickness: 1.5px;
  --aura-gradient-duration: 6000ms;
  --aura-gradient-ease: linear;

  /* -- Shimmer (diagonal sweep) -- */
  --aura-shimmer-width: 28%;
  --aura-shimmer-alpha: 0.18;
  --aura-shimmer-duration: 2000ms;
  --aura-shimmer-ease: cubic-bezier(0.4, 0, 0.2, 1);
  --aura-shimmer-delay-between: 1200ms;

  /* Font stacks (--font-sans / --font-mono) and the type scale
     (--text-display … --text-overline-*) moved to @gradeui/core/tokens.css. */
  /* ----------------------------------------
     Animation
     ---------------------------------------- */
  --gds-transition-fast: 150ms;
  --gds-transition-base: 200ms;
  --gds-transition-slow: 300ms;
  --gds-transition-slower: 500ms;

  --gds-ease-in: cubic-bezier(0.4, 0, 1, 1);
  --gds-ease-out: cubic-bezier(0, 0, 0.2, 1);
  --gds-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);

  /* ----------------------------------------
     Semantic Tokens (OKLCH) — STUDIO DEFAULTS.
     Values are bare "L C H" triplets, wrapped at call-time with
     oklch(var(--x)) via the @theme inline reference block above.
     These defaults mirror the current Studio look (warm-cream
     neutrals at hue 85, near-black primary, Inter/Geist type) and
     are SYNCED FROM apps/docs/app/globals.css — the canonical copy —
     by the A7.1 pass (2026-06-11; the previous values here were the
     pre-redesign terracotta theme, which leaked into any surface that
     loaded both stylesheets — the MCP panel "pink progress bar" bug).
     If the docs palette changes, re-sync this block. The
     GradeThemeProvider overrides all of it at runtime.
     ---------------------------------------- */
  --background: 0.985 0.0018 85;          /* neutral 50 */
  --foreground: 0.170 0.0048 85;          /* neutral 950 */
  --bg-base: 0.985 0.0018 85;             /* stable mirror for scope-inverse */
  --fg-base: 0.170 0.0048 85;
  --card: 1 0 0;                            /* pure white */
  --card-foreground: 0.170 0.0048 85;
  --popover: 1 0 0;
  --popover-foreground: 0.170 0.0048 85;
  --primary: 0.245 0.0096 85;                /* near-black — Studio buttons */
  --primary-foreground: 0.985 0.0018 85;
  --secondary: 0.955 0.0048 85;              /* neutral 100 */
  --secondary-foreground: 0.415 0.0180 85;  /* neutral 700 */
  --muted: 0.955 0.0048 85;
  --muted-foreground: 0.510 0.0204 85;      /* neutral 600 (firmed 8 Aug) */
  --super-muted-foreground: 0.610 0.0204 85; /* neutral 500 — quietest text tier */
  --accent: 0.610 0.0306 85;                 /* warm cream accent */
  --accent-foreground: 0.985 0.0027 85;
  --destructive: 0.560 0.220 27;
  --destructive-foreground: 0.990 0.005 27;
  --border: 0.895 0.0090 85;               /* neutral 200 */
  --input: 0.895 0.0090 85;
  --ring: 0.415 0.0180 85;
  --radius: 0.250rem;                       /* Studio's compact radius base */

  /* Typography defaults — Inter body, Geist display (Studio). */
  --font-sans: var(--font-inter), system-ui, -apple-system, sans-serif;
  --font-display: var(--font-geist), system-ui, -apple-system, sans-serif;

  /* Fixed semantic colors (not hue-derived, for accessibility consistency) */
  --success: 0.610 0.180 145;
  --warning: 0.720 0.180 60;
  --info: 0.580 0.200 240;
  --highlight: 0.860 0.180 95;

  /* Selection token pair — solid fill behind a selected option (radio
     row, segmented control) and the ambient halo around it on hover /
     focus. Blue by default; theme-overridable. Distinct from --primary
     so a brand can keep its primary cta colour while selection stays
     consistent, and distinct from --highlight (amber status). */
  --selected: 0.55 0.22 258;
  --selected-foreground: 0.985 0.0018 85;
  --selected-glow: 0.66 0.20 258;

  /* Accent glow — tonal halo for raised/tactile chrome. Defaults to
     --primary so raised buttons read as branded by default, not as
     selected. Themes can retune independently (e.g. lift lightness for
     softer halos) by setting --accent-glow explicitly. Per-button
     overrides still flow through `style={{ "--btn-glow": ... }}` for
     "traffic light" semantics (warning iterate, success ship, etc.). */
  --accent-glow: var(--primary);

  /* Media placeholder pair — used by MediaSurface when it has no children
     (image-not-yet-loaded slots, scaffolds without real photos, layouts
     waiting on the image-generation pipeline). Defined here as a token
     pair so themes can override per-brand and consumers can roll their
     own placeholder UI with `background: var(--gds-media-placeholder-bg)`.
     Defaults to the secondary surface so it reads visibly distinct from
     `bg-card` and `bg-background` while staying neutral (not brand-loud).
     Resolves dynamically against the active mode because var(--secondary)
     itself is mode-scoped — no `.dark` duplicate needed.

     Both bg and fg are SOLID (no alpha) — earlier `secondary-foreground / 0.4`
     produced a "half-cooked" look where the icon and caption faded into the
     surface. Solid muted-foreground reads as "intentional placeholder
     colour", not "loading half-way". */
  --gds-media-placeholder-bg: oklch(var(--muted));
  --gds-media-placeholder-fg: oklch(var(--muted-foreground));

  /* Fidelity fade — how long a MediaSurface takes to cross-fade between
     full imagery and the wireframe placeholder when `data-fidelity`
     flips on an ancestor (Studio's toggle, an embed's `fidelity`
     parameter). One knob, read by the "MediaSurface fidelity" rules
     further down. */
  --gds-media-fidelity-fade: 280ms;

  /* Transparency checkerboard — the standard alpha backdrop drawn behind
     image previews (Studio's inspector image well; anywhere else that
     needs to say "these pixels are transparent"). Consumed as
     `repeating-conic-gradient(var(--gds-media-checker-color) 0% 25%,
     transparent 0% 50%)` sized by the size token. Mode-aware for free:
     the colour rides on --muted-foreground at low alpha, so it reads as
     grey/white in light mode and grey/dark in dark without a `.dark`
     override. */
  --gds-media-checker-color: oklch(var(--muted-foreground) / 0.16);
  --gds-media-checker-size: 14px;

  /* Canvas fill — the standard backdrop behind a screen when it doesn't fill
     its frame: the letterbox bars in an embed/share, and the stage a
     `<ScreenAnimator>` reveals when it flies in or pulls below 1× zoom. One
     token so every "canvas" surface (embed, share, animator) matches and a
     theme/brand can restyle it in one place. Mode-aware: a soft neutral in
     light mode and a deep near-black in dark (see `.dark` below) — a neutral
     cinematic backdrop that flatters a screen sitting on top of it in either
     mode. Set it to `transparent` to let the host page show through. */
  --gds-canvas-fill: #e8e8ec;

  /* Map marker chrome — the baseline "lift" every MapMarker child gets
     so pin labels never melt into the tiles (see the map-marker rule
     near the fidelity block). Map tiles are EXTERNAL imagery — they
     don't ride the theme's surface tokens — so marker content needs
     its own guaranteed separation in both modes: a 1px border + a soft
     ambient shadow. Light mode: a slightly-darker-than---border line
     reads against pale tiles. Dark mode override below flips to a
     light hairline (a dark border on dark tiles lifts nothing). */
  --gds-map-marker-border: oklch(var(--border));
  --gds-map-marker-shadow: 0 1px 3px oklch(0 0 0 / 0.22),
    0 0 0 1px oklch(0 0 0 / 0.05);
  /* Halo behind floating map labels (the text-stroke that separates a
     label from busy tiles). Mode-aware: a white halo reads on pale
     light tiles; the dark-mode override flips it to near-black so a
     light label on dark tiles isn't a white-on-white wash-out. Applied
     via the `.gds-map-label` helper. */
  --gds-map-label-halo: oklch(1 0 0);
  /* Map canvas — the backing colour BEHIND the raster tiles. Leaflet's
     default is `#dddddd`, so subpixel gaps between tiles flash light
     "seams" over a dark basemap. Defaulting this to the tile base
     colour (here, the CARTO voyager land tone) makes those gaps blend
     away. Neutral on purpose: tint it and the seams come back as
     coloured lines unless your tiles match. */
  --gds-map-canvas: oklch(0.97 0.004 95);

  /* Sidebar knobs — the width pair drives the collapsed/expanded animation
     on `<Sidebar>` (compound nav primitive). Header height + section
     padding + per-section gap are exposed so consumers can retune density
     without prop drilling. */
  --gds-sidebar-width: 16rem;
  --gds-sidebar-collapsed-width: 4rem;
  --gds-sidebar-header-height: 3.25rem;
  --gds-sidebar-content-py: 0.5rem;
  --gds-sidebar-section-px: 0.5rem;
  --gds-sidebar-section-gap: 0.125rem;

  /* Property list knobs — <PropertyList> renders a single record as
     label / value rows (a Table row transposed). The label column width
     and the row/column rhythm are exposed so a dense inspector and an
     airy settings page share one primitive. Read as
     `var(--gds-property-list-* , <fallback>)` in the component, so a
     missing stanza degrades gracefully. */
  --gds-property-list-label-width: 8.5rem;
  --gds-property-list-col-gap: 1rem;
  --gds-property-list-row-gap: 0.625rem;
  --gds-property-list-row-gap-compact: 0.375rem;
  --gds-property-list-row-gap-relaxed: 1rem;
  --gds-property-list-icon-size: 0.875rem;
  --gds-property-list-label-color: oklch(var(--muted-foreground));
  --gds-property-list-divider-color: oklch(var(--border));

  /* Data view knobs — <DataView> draws one dataset as a table, cards, or a
     grid. The card / grid tiles auto-fill, so these min column widths set
     how many tiles sit per row at a given container width; the pinned and
     header backgrounds back the sticky column / header so scrolled content
     doesn't bleed through. */
  --gds-data-view-card-min: 18rem;
  --gds-data-view-grid-min: 11rem;
  --gds-data-view-gap: 0.75rem;
  --gds-data-view-pinned-bg: oklch(var(--background));
  --gds-data-view-header-bg: oklch(var(--background));
  --gds-data-view-table-max-h: 28rem; /* scroll height when stickyHeader is on */

  /* Carousel knobs — every visual dimension of <Carousel> is driven from
     this stanza so consumers can re-skin the slideshow (dot shape, arrow
     chrome, transition feel) without prop-drilling. The component reads
     these as `var(--gds-carousel-* , <fallback>)` so missing values are
     safe. */
  --gds-carousel-gap: 0;
  --gds-carousel-radius: var(--gds-media-radius, 0.5rem);
  --gds-carousel-fade-ms: 200ms;
  --gds-carousel-slide-basis: 100%;
  /* Dots */
  --gds-carousel-dot-size: 0.5rem;
  --gds-carousel-dot-active-width: 1.25rem;
  --gds-carousel-dot-color: oklch(var(--muted-foreground) / 0.4);
  --gds-carousel-dot-active-color: oklch(var(--primary));
  --gds-carousel-dots-gap: 0.75rem;        /* between viewport and below-dots */
  --gds-carousel-dots-spacing: 0.5rem;     /* between adjacent dots */
  --gds-carousel-dots-inset: 0.75rem;      /* overlay position from bottom */
  /* Arrows */
  --gds-carousel-arrow-size: 2.25rem;
  --gds-carousel-arrow-inset: 0.75rem;
  --gds-carousel-arrow-bg: oklch(var(--background) / 0.85);
  --gds-carousel-arrow-hover-bg: oklch(var(--background));
  --gds-carousel-arrow-fg: oklch(var(--foreground));
  --gds-carousel-arrow-backdrop: blur(6px);
  --gds-carousel-arrow-shadow: 0 2px 8px oklch(0 0 0 / 0.12);

  /* Alert surface pairs — paler tinted surface + readable deep text for each
     status colour. Pre-hydration fallbacks; the JS theme pipeline overwrites
     these via lib/themes/oklch.ts#deriveAlertPair. Kept here so the Alert
     component has the right tokens before the theme provider mounts. */
  --destructive-soft: 0.965 0.045 27;
  --destructive-deep: 0.380 0.220 27;
  --success-soft: 0.965 0.040 145;
  --success-deep: 0.380 0.180 145;
  --warning-soft: 0.965 0.040 60;
  --warning-deep: 0.380 0.180 60;
  --info-soft: 0.965 0.044 240;
  --info-deep: 0.380 0.200 240;
  --highlight-soft: 0.965 0.040 95;
  --highlight-deep: 0.380 0.180 95;

  /* Code surface tokens — used by `<Code>` (marketing/docs code blocks).
     Token roles map to prism's tag types; the component reads them as
     `color: var(--gds-code-<role>)` so the palette inverts with the
     theme without us swapping prism themes at runtime. Light values
     are tuned against a near-paper surface; the .dark block below
     mirrors them for dark mode.

     diff-added / diff-removed pairs are intentionally desaturated
     surfaces with strong text — same pattern as the destructive-soft /
     -deep alert pairs. Line-highlight is a quieter accent (no green/
     red implication, just "look here"). */
  --gds-code-bg: oklch(0.985 0.0018 85);
  --gds-code-fg: oklch(0.220 0.012 250);
  --gds-code-comment: oklch(0.620 0.030 250);
  --gds-code-punctuation: oklch(0.420 0.020 250);
  --gds-code-property: oklch(0.550 0.180 25);
  --gds-code-number: oklch(0.560 0.180 35);
  --gds-code-string: oklch(0.500 0.140 145);
  --gds-code-operator: oklch(0.480 0.140 280);
  --gds-code-keyword: oklch(0.490 0.220 285);
  --gds-code-function: oklch(0.500 0.200 250);
  --gds-code-variable: oklch(0.530 0.180 25);
  --gds-code-tag: oklch(0.520 0.200 15);
  --gds-code-attr-name: oklch(0.510 0.150 60);
  --gds-code-attr-value: oklch(0.500 0.140 145);
  --gds-code-line-highlight-bg: oklch(var(--selected-glow) / 0.10);
  --gds-code-line-highlight-marker: oklch(var(--selected-glow) / 0.65);
  --gds-code-diff-added-bg: oklch(var(--success) / 0.12);
  --gds-code-diff-added-fg: oklch(0.300 0.140 145);
  --gds-code-diff-removed-bg: oklch(var(--destructive) / 0.10);
  --gds-code-diff-removed-fg: oklch(0.360 0.180 27);
}

.dark {
  /* ----------------------------------------
     Semantic Tokens — Studio dark defaults (synced from
     apps/docs/app/globals.css, A7.1 — see the :root note).
     ---------------------------------------- */
  --background: 0.170 0.0048 85;
  --foreground: 0.985 0.0018 85;
  --bg-base: 0.170 0.0048 85;
  --fg-base: 0.985 0.0018 85;
  --card: 0.245 0.0096 85;                  /* neutral 900 */
  --card-foreground: 0.985 0.0018 85;
  --popover: 0.245 0.0096 85;
  --popover-foreground: 0.985 0.0018 85;
  --primary: 0.955 0.0048 85;                /* primary 400 */
  --primary-foreground: 0.170 0.0048 85;
  --secondary: 0.325 0.0144 85;             /* neutral 800 */
  --secondary-foreground: 0.895 0.0090 85;
  --muted: 0.325 0.0144 85;
  --muted-foreground: 0.820 0.0132 85;      /* neutral 300 (firmed 8 Aug) */
  --super-muted-foreground: 0.720 0.0168 85; /* neutral 400 — quietest text tier */
  --accent: 0.720 0.0252 85;
  --accent-foreground: 0.170 0.0072 85;
  --destructive: 0.680 0.220 27;
  --destructive-foreground: 0.990 0.005 27;
  --border: 0.325 0.0144 85;
  --input: 0.325 0.0144 85;
  --ring: 0.820 0.0132 85;

  --success: 0.720 0.180 145;
  --warning: 0.800 0.180 60;
  --info: 0.700 0.200 240;
  --highlight: 0.880 0.180 95;

  /* Selection pair — dark mode. Solid fill stays vivid; halo lifts in
     lightness so it can glow against the dark canvas. */
  --selected: 0.62 0.22 258;
  --selected-foreground: 0.985 0.0018 85;
  --selected-glow: 0.74 0.20 258;

  /* Accent glow — dark mode. Same theme-driven default (primary), but
     the dark variants of --primary already carry the appropriate lift
     for a glow against the dark canvas. */
  --accent-glow: var(--primary);

  /* Canvas fill — dark mode. Deep near-black cinematic backdrop (the light
     default in :root is a soft neutral). One token, mode-scoped: letterbox
     bars in embed/share/preview and the ScreenAnimator stage all match. */
  --gds-canvas-fill: #0b0b0e;

  /* Map marker chrome — dark mode: a light hairline + deeper shadow.
     Dark-mode pin surfaces (bg-card etc.) sit on dark tiles, so the
     border has to be the bright element; the alpha keeps it a lift,
     not a frame. */
  --gds-map-marker-border: oklch(1 0 0 / 0.3);
  --gds-map-marker-shadow: 0 1px 3px oklch(0 0 0 / 0.5),
    0 0 0 1px oklch(0 0 0 / 0.4);
  /* Dark-mode label halo — near-black so light labels stay legible on
     dark tiles (mirror of the light-mode white halo above). */
  --gds-map-label-halo: oklch(0.18 0 0);
  /* Dark-mode map canvas — near-black to match the CARTO dark_all base,
     so tile seams vanish on the dark basemap. */
  --gds-map-canvas: oklch(0.17 0 0);

  /* Alert surface pairs — dark-mode variant: tint sits just above the dark
     background without going gray; deep text stays bright and readable. */
  --destructive-soft: 0.220 0.075 27;
  --destructive-deep: 0.820 0.198 27;
  --success-soft: 0.220 0.075 145;
  --success-deep: 0.820 0.162 145;
  --warning-soft: 0.220 0.075 60;
  --warning-deep: 0.820 0.162 60;
  --info-soft: 0.220 0.075 240;
  --info-deep: 0.820 0.180 240;
  --highlight-soft: 0.220 0.075 95;
  --highlight-deep: 0.820 0.162 95;

  /* -- Elevation: bump alphas in dark mode. Shadows fade against
        dark surfaces; without this the lift looks anemic. -- */
  --shadow-bevel-hi-alpha: 0.14;
  --shadow-bevel-lo-alpha: 0.40;
  --shadow-contact-alpha: 0.45;
  --shadow-lift-alpha: 0.50;
  --shadow-lift-deep-alpha: 0.60;

  /* -- Surface: glass needs higher opacity in dark mode or the card
        loses its boundary against the canvas. -- */
  --surface-alpha-translucent: 0.74;
  --surface-alpha-glass: 0.48;
  --surface-alpha-glass-strong: 0.32;
  --surface-alpha-frosted: 0.88;
  --surface-edge-alpha: 0.06;

  /* Code surface tokens — dark mode mirror. Background sits a touch
     above the page bg so the code block reads as a distinct surface
     even on a dark hero. Tokens lift in lightness to stay readable. */
  --gds-code-bg: oklch(0.205 0.010 250);
  --gds-code-fg: oklch(0.920 0.012 250);
  --gds-code-comment: oklch(0.580 0.024 250);
  --gds-code-punctuation: oklch(0.760 0.018 250);
  --gds-code-property: oklch(0.780 0.150 25);
  --gds-code-number: oklch(0.790 0.150 60);
  --gds-code-string: oklch(0.780 0.140 145);
  --gds-code-operator: oklch(0.770 0.130 280);
  --gds-code-keyword: oklch(0.760 0.180 285);
  --gds-code-function: oklch(0.770 0.150 250);
  --gds-code-variable: oklch(0.790 0.160 25);
  --gds-code-tag: oklch(0.780 0.180 15);
  --gds-code-attr-name: oklch(0.800 0.140 60);
  --gds-code-attr-value: oklch(0.780 0.140 145);
  --gds-code-line-highlight-bg: oklch(var(--selected-glow) / 0.16);
  --gds-code-line-highlight-marker: oklch(var(--selected-glow) / 0.85);
  --gds-code-diff-added-bg: oklch(var(--success) / 0.18);
  --gds-code-diff-added-fg: oklch(0.870 0.160 145);
  --gds-code-diff-removed-bg: oklch(var(--destructive) / 0.18);
  --gds-code-diff-removed-fg: oklch(0.870 0.180 27);
}

/* ============================================
   ENERGY THEME — scoped overrides.
   Engaged when data-grade-theme="energy" is set on :root (either by the
   pre-hydration inline script or GradeThemeProvider). Mirrors the Energy
   built-in (hues all at 175, teal primary, Geist sans).
   ============================================ */
:root[data-grade-theme="energy"] {
  --background: 0.985 0.0012 175;           /* neutral 50 */
  --foreground: 0.170 0.0032 175;           /* neutral 950 */
  --bg-base: 0.985 0.0012 175;
  --fg-base: 0.170 0.0032 175;
  --card: 1 0 0;
  --card-foreground: 0.170 0.0032 175;
  --popover: 1 0 0;
  --popover-foreground: 0.170 0.0032 175;
  --primary: 0.610 0.170 175;               /* primary 500 — teal */
  --primary-foreground: 0.985 0.015 175;
  --secondary: 0.955 0.0032 175;            /* neutral 100 */
  --secondary-foreground: 0.415 0.012 175;  /* neutral 700 */
  --muted: 0.955 0.0032 175;
  --muted-foreground: 0.510 0.0136 175;     /* neutral 600 (firmed 8 Aug) */
  --super-muted-foreground: 0.610 0.0136 175; /* neutral 500 — quietest text tier */
  --accent: 0.610 0.170 175;
  --accent-foreground: 0.985 0.015 175;
  --border: 0.895 0.006 175;
  --input: 0.895 0.006 175;
  --ring: 0.610 0.170 175;
  --radius: 0.5rem;

  --font-sans: var(--font-geist), system-ui, sans-serif;
  --font-display: var(--font-geist), system-ui, sans-serif;
}

.dark[data-grade-theme="energy"] {
  --background: 0.170 0.0032 175;
  --foreground: 0.985 0.0012 175;
  --bg-base: 0.170 0.0032 175;
  --fg-base: 0.985 0.0012 175;
  --card: 0.245 0.0064 175;
  --card-foreground: 0.985 0.0012 175;
  --popover: 0.245 0.0064 175;
  --popover-foreground: 0.985 0.0012 175;
  --primary: 0.720 0.140 175;
  --primary-foreground: 0.170 0.040 175;
  --secondary: 0.325 0.0096 175;
  --secondary-foreground: 0.895 0.006 175;
  --muted: 0.325 0.0096 175;
  --muted-foreground: 0.820 0.0088 175;     /* neutral 300 (firmed 8 Aug) */
  --super-muted-foreground: 0.720 0.0112 175; /* neutral 400 — quietest text tier */
  --accent: 0.720 0.140 175;
  --accent-foreground: 0.170 0.040 175;
  --border: 0.325 0.0096 175;
  --input: 0.325 0.0096 175;
  --ring: 0.720 0.140 175;
}

@layer base {
  * {
    @apply border-border;
  }

  /* BROWSER AUTOFILL (Ali, 11 Aug 2026: "looks horrid").
     Chrome paints an autofilled field with its own pale blue
     (rgb(232,240,254)) and its own near-black text, through the UA
     stylesheet's :-webkit-autofill rule. On a dark theme that lands as a
     lavender block with dark text in the middle of a dark form, and it
     hits exactly the fields a browser knows how to fill (password,
     phone, name) while leaving selects and checkboxes alone, so a form
     ends up half one palette and half the other.

     `background-color` cannot override it: the UA rule wins. The fix is
     -webkit-background-clip: text, which clips that background to the
     text glyphs, so the block fill disappears and the element's own
     background shows through. That matters here because Grade inputs are
     `bg-transparent` and sit on whatever surface hosts them, a card or
     the page: painting an opaque colour over the autofill (the usual
     `box-shadow: 0 0 0 1000px … inset` trick) would be wrong the moment
     the field is on a card.

     -webkit-text-fill-color is the other half: the UA sets it, so the
     text stays dark unless it is reset to the theme's foreground. */
  input:-webkit-autofill,
  input:-webkit-autofill:hover,
  input:-webkit-autofill:focus,
  input:-webkit-autofill:active,
  textarea:-webkit-autofill,
  select:-webkit-autofill {
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: oklch(var(--foreground));
    caret-color: oklch(var(--foreground));
  }

  /* Smooth theme transitions */
  html {
    transition-property: background-color, border-color;
    transition-duration: 200ms;
    transition-timing-function: ease-in-out;
  }

  body {
    @apply bg-background text-foreground;
    font-feature-settings: "rlig" 1, "calt" 1;
    font-size: var(--text-body);
    line-height: var(--text-body-line);
    /* Theme-driven width cut (variable-font wdth axis). Inherits into
       every span/component, so a theme set to e.g. 90% re-cuts ALL text;
       per-element font-stretch-[…] utilities still override. */
    font-stretch: var(--font-body-stretch, normal);
  }

  /* Theme-driven heading font.
     Naked <h1>–<h4> elements pick up the active theme's display font
     (falls back to sans if the theme doesn't set one). Utility classes
     like .text-h1 still win if they set their own font, since classes
     outrank tag selectors. */
  h1, h2, h3, h4 {
    font-family: var(--font-display, var(--font-sans));
    font-weight: var(--font-heading-weight, 600);
    letter-spacing: var(--font-heading-tracking, -0.01em);
    font-stretch: var(--font-display-stretch, var(--font-body-stretch, normal));
  }

  /* Link styles */
  a:not([class]) {
    @apply text-primary underline-offset-4 hover:underline;
  }
}

/* ============================================
   APP SHELL GRID
   Five slots — Header, Nav, Aside, Main, Footer — placed via CSS-grid
   template areas keyed off [data-nav] on the shell root. Slot order in
   JSX doesn't matter; each slot has a fixed grid-area class assignment.
   ============================================ */

.gds-app-shell-header { grid-area: header; }
.gds-app-shell-nav    { grid-area: nav; }
.gds-app-shell-aside  { grid-area: aside; }
.gds-app-shell-main   { grid-area: main; }
.gds-app-shell-footer { grid-area: footer; }

/* nav="none" — Header / Main / Footer stacked vertically. */
.gds-app-shell[data-nav="none"] {
  grid-template-areas: "header" "main" "footer";
  grid-template-rows: auto 1fr auto;
}

/* nav="top" — Header (site chrome), then in-app top nav, then Main. */
.gds-app-shell[data-nav="top"] {
  grid-template-areas: "header" "nav" "main" "footer";
  grid-template-rows: auto auto 1fr auto;
}

/* nav="side" — Header full width, Nav rail + Main below, Footer full width. */
.gds-app-shell[data-nav="side"] {
  grid-template-areas:
    "header header"
    "nav    main"
    "footer footer";
  grid-template-rows: auto 1fr auto;
  grid-template-columns: auto 1fr;
}

/* nav="three-pane" — Header full width, Nav rail + fixed Aside + flex Main,
   Footer full width. Aside width is set by --gds-app-shell-aside (default
   320px) so callers can override per-screen via inline style or a parent
   class without forking the component. */
.gds-app-shell[data-nav="three-pane"] {
  grid-template-areas:
    "header header header"
    "nav    aside  main"
    "footer footer footer";
  grid-template-rows: auto 1fr auto;
  grid-template-columns: auto var(--gds-app-shell-aside, 320px) 1fr;
}

/* ============================================
   THEME-DRIVEN COMPONENT SHAPES
   Keyed off data attributes that GradeThemeProvider sets on <html>.
   Each rule is an additive opt-in — themes that don't set a style get
   the component's default look.
   ============================================ */

/* Card styles */
[data-card-style="outlined"] .gds-card {
  box-shadow: none;
}

[data-card-style="flat"] .gds-card {
  box-shadow: none;
  border-color: transparent;
  background-color: oklch(var(--muted));
}

[data-card-style="elevated"] .gds-card {
  box-shadow: var(--gds-shadow-lg);
  border-color: transparent;
}

/* ── Interactive card ───────────────────────────────────────────────
   The whole card is the click target (Card `interactive`). On dark
   surfaces a drop shadow barely registers against a near-black page,
   so the hover cue LEADS with a surface lift — card -> muted is one
   ramp step, the same distance the theme puts between any two stacked
   surfaces — plus a brighter border, and carries the shadow only as a
   secondary depth hint. The lift is what reads; the shadow is garnish.

   The trailing action lights at the same moment, so a card and its
   chevron read as ONE affordance instead of two competing ones. That
   button should be a `Button asChild` wrapping a span: styled by the
   design system, but not a nested control inside a clickable region. */
.gds-card[data-interactive]:hover {
  background-color: oklch(var(--muted));
  border-color: oklch(var(--border));
  box-shadow: var(--gds-shadow-lg);
}

.gds-card[data-interactive]:hover [data-gds-part="button"] {
  background-color: oklch(var(--accent));
  color: oklch(var(--accent-foreground));
  border-color: oklch(var(--accent));
}

/* Pressing the card settles it back down — the lift is the hover
   signal, so removing it is the press signal. */
.gds-card[data-interactive]:active {
  box-shadow: var(--gds-shadow-sm);
}

[data-card-style="glass"] .gds-card {
  background-color: oklch(var(--card) / 0.6);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-color: oklch(var(--border) / 0.5);
  box-shadow: var(--gds-shadow-md);
}

/* Button shapes — themes can set buttonShape: "pill" | "square" | "sharp".
   The Button component's radius comes from --radius (shadcn), so we override
   border-radius directly on the gds-button class when a shape is set. */
[data-button-shape="pill"] .gds-button {
  border-radius: 9999px;
}

[data-button-shape="square"] .gds-button,
[data-button-shape="sharp"] .gds-button {
  border-radius: 0;
}

/* ============================================
   BUTTON — RAISED VARIANT
   ============================================
   Tactile "physical key" treatment composed from elevation tokens.
   Tone is driven by `--btn-glow` (defaults to --selected-glow); the
   heat-inner / heat-outer layers in the elevation system already read
   from --btn-glow so per-button overrides flow through automatically.

   Tabs and Toggle pick this up too via the same data-state / aria
   selectors below — TabsTrigger emits `data-state="active"`, Toggle
   emits `data-state="on"`, both listed. */
.gds-button-raised {
  /* Cascade: per-element style override > --accent-glow (theme accent,
     defaults to --primary) > --selected-glow (system selection blue).
     Was hardcoded to --selected-glow before May 2026 — the user
     flagged "should pull from theme accent, not selection blue", which
     this fallback chain solves without breaking existing per-button
     style overrides like `style={{ "--btn-glow": "var(--warning)" }}`. */
  --btn-glow: var(--accent-glow, var(--selected-glow));

  box-shadow: var(--elevation-3);

  transition:
    box-shadow var(--gds-transition-base) var(--gds-ease-out),
    transform var(--gds-transition-fast) var(--gds-ease-out),
    background-color var(--gds-transition-base) var(--gds-ease-out);
}

/* Surface for the STANDALONE raised look. June 2026: raised became a
   TRAIT (the `raised` prop on Button adds .gds-button-raised only, so
   elevation + glow compose with ANY colour variant — raised primary,
   raised outline). This companion class supplies the classic neutral
   "key" surface and is applied only by the variant="raised" alias,
   which is kept for back-compat. */
.gds-button-raised-surface {
  background-color: oklch(var(--secondary));
  color: oklch(var(--secondary-foreground));
}

.gds-button-raised:hover {
  box-shadow: var(--elevation-hot);
}

.gds-button-raised:active {
  transform: translateY(1px);
  box-shadow: var(--elevation-pressed);
}

.gds-button-raised[data-state="on"],
.gds-button-raised[data-state="active"],
.gds-button-raised[aria-pressed="true"],
.gds-button-raised[data-selected="true"] {
  background-color: oklch(var(--selected) / 0.18);
  color: oklch(var(--foreground));
  box-shadow:
    var(--shadow-bevel-hi),
    inset 0 0 0 1px oklch(var(--selected) / 0.55),
    var(--shadow-heat-inner),
    0 0 0 1px oklch(var(--selected) / 0.20),
    var(--shadow-heat-outer),
    var(--shadow-lift);
}

.gds-button-raised:focus-visible {
  outline: none;
  box-shadow:
    var(--shadow-bevel-hi),
    var(--shadow-bevel-lo),
    0 0 0 2px oklch(var(--background)),
    0 0 0 4px oklch(var(--btn-glow) / 0.70),
    var(--shadow-lift);
}

/* ============================================
   CODE CURSOR — blinking caret for `<Code>` typewriter / terminal demos
   ============================================
   Rendered as a 1ch-wide block at the tail of the active line. Uses
   currentColor so it inherits the surrounding token colour (which is
   `--gds-code-fg` by default but can be set per-line by the consumer).
   The 1.05s cycle is the canonical iOS/macOS caret blink rate. */
@keyframes gds-code-caret-blink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}
.gds-code-cursor {
  display: inline-block;
  width: 0.55em;
  height: 1.05em;
  vertical-align: text-bottom;
  margin-left: 0.05em;
  background-color: currentColor;
  border-radius: 1px;
  animation: gds-code-caret-blink 1.05s steps(1, end) infinite;
}
@media (prefers-reduced-motion: reduce) {
  .gds-code-cursor { animation: none; opacity: 1; }
}

/* ============================================
   DEMO CURSOR — generic blinking caret used by lib/demo/ consumers
   ============================================
   The shared caret used wherever a gradeui component scripts typing
   (Code's terminal lines, Composer's inline cursor, future surfaces).
   Parameterised via CSS variables so each host can dial dimensions
   and colour without forking the class.

     --gds-demo-cursor-color    defaults to currentColor
     --gds-demo-cursor-width    defaults to 0.55em
     --gds-demo-cursor-height   defaults to 1.05em
     --gds-demo-cursor-radius   defaults to 1px

   Two variants — `inline` (default; thin caret for text) and `block`
   (square cell for terminal demos). The block variant swaps width to
   match height. */
.gds-demo-cursor {
  display: inline-block;
  width: var(--gds-demo-cursor-width, 0.55em);
  height: var(--gds-demo-cursor-height, 1.05em);
  vertical-align: text-bottom;
  margin-left: 0.05em;
  background-color: var(--gds-demo-cursor-color, currentColor);
  border-radius: var(--gds-demo-cursor-radius, 1px);
  animation: gds-code-caret-blink 1.05s steps(1, end) infinite;
}
.gds-demo-cursor[data-gds-variant="block"] {
  width: var(--gds-demo-cursor-height, 1.05em);
  border-radius: 0;
}
@media (prefers-reduced-motion: reduce) {
  .gds-demo-cursor { animation: none; opacity: 1; }
}

/* ============================================
   COMPOSER — token palette for <Composer>
   ============================================
   The generic text composition surface (chat / comments / post body).
   Tokens here drive the editor card, toolbar, mention pills, and
   attachment chips. Theme via consumer CSS to retune any surface
   without forking the component.

   Two themes shipped inline: light + dark via .dark scoping. */
:root {
  --gds-composer-bg: var(--background);
  --gds-composer-fg: var(--foreground);
  --gds-composer-muted-fg: var(--muted-foreground);
  --gds-composer-border: var(--border);
  --gds-composer-action-fg: var(--muted-foreground);
  --gds-composer-toolbar-fg: var(--muted-foreground);
  --gds-composer-toolbar-hover-bg: var(--muted);
  --gds-composer-toolbar-active-bg: var(--accent);
  --gds-composer-toolbar-active-fg: var(--accent-foreground);
  --gds-composer-chip-remove-bg: oklch(0.15 0 0);
  --gds-composer-chip-remove-fg: #fff;
  --gds-composer-mention-bg: oklch(var(--primary) / 0.12);
  --gds-composer-mention-fg: oklch(var(--primary));
  --gds-composer-mention-slash-bg: oklch(0.6 0.12 280 / 0.12);
  --gds-composer-mention-slash-fg: oklch(0.6 0.12 280);
}
.dark {
  --gds-composer-chip-remove-bg: #fff;
  --gds-composer-chip-remove-fg: oklch(0.15 0 0);
}

/* Mention pill — applied via Lexical theme.beautifulMentions.
   Renders as an inline rounded chip with a subtle background and
   the trigger char + value. */
.gds-composer-mention {
  display: inline-block;
  padding: 0 0.4em;
  margin: 0 0.05em;
  border-radius: 0.4em;
  background: var(--gds-composer-mention-bg);
  color: var(--gds-composer-mention-fg);
  font-weight: 500;
  cursor: default;
}
.gds-composer-mention-slash {
  background: var(--gds-composer-mention-slash-bg);
  color: var(--gds-composer-mention-slash-fg);
}

/* Pullquote — styled variant of blockquote. The Lexical node
   carries a __pullquote sentinel; this rule applies whenever the
   blockquote DOM is tagged with data-gds-pullquote (set via a
   future custom node) OR carries the .gds-composer-pullquote class. */
.gds-composer-pullquote,
[data-gds-pullquote="true"] {
  border-left: 3px solid var(--primary);
  padding-left: 1rem;
  font-size: 1.125rem;
  font-style: normal;
  font-weight: 500;
}

/* ============================================
   SURFACE CLASSES
   ============================================
   Apply one to any container. Composes with elevation classes —
   `<div class="gds-surface-glass shadow-elevation-4">` is a glass
   popover sitting at popover elevation, no conflict.

   --card is theme-aware, so each theme automatically gets its own
   "frosted X" tint without per-theme overrides. */
.gds-surface-solid {
  background-color: var(--surface-solid);
}

.gds-surface-translucent {
  background-color: var(--surface-translucent);
}

.gds-surface-glass {
  background-color: var(--surface-glass);
  backdrop-filter: blur(var(--surface-blur-glass));
  -webkit-backdrop-filter: blur(var(--surface-blur-glass));
  border: 1px solid oklch(var(--border) / 0.5);
  box-shadow: var(--surface-edge);
}

.gds-surface-glass-strong {
  background-color: var(--surface-glass-strong);
  backdrop-filter: blur(var(--surface-blur-strong));
  -webkit-backdrop-filter: blur(var(--surface-blur-strong));
  border: 1px solid oklch(var(--border) / 0.4);
  box-shadow: var(--surface-edge);
}

/* Frosted — near-opaque (≈0.92) but keeps the strong blur, so it reads almost
   solid with just a halo of the page behind. The most solid of the blurred
   surfaces; above this, reach for `solid`. */
.gds-surface-frosted {
  background-color: var(--surface-frosted);
  backdrop-filter: blur(var(--surface-blur-strong));
  -webkit-backdrop-filter: blur(var(--surface-blur-strong));
  border: 1px solid oklch(var(--border) / 0.4);
  box-shadow: var(--surface-edge);
}

/* ============================================
   AURA — RING (pulsing outer halo)
   ============================================
   Apply `.gds-aura-ring` to any element. Animation duration & easing
   are CSS vars (`--aura-pulse-duration`, `--aura-pulse-ease`) — set
   them at the consumer level to retune without rewriting keyframes.
   Tone defaults to --aura-color (= --selected-glow); override per
   element for danger / success attention. */
@keyframes gds-aura-pulse {
  0%, 100% {
    box-shadow:
      0 0 0 0 oklch(var(--aura-color) / var(--aura-ring-alpha-min)),
      0 0 0 0 oklch(var(--aura-color) / 0);
  }
  50% {
    box-shadow:
      0 0 0 var(--aura-ring-spread) oklch(var(--aura-color) / var(--aura-ring-alpha-max)),
      0 0 var(--aura-ring-blur) 0 oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.6));
  }
}

.gds-aura-ring {
  animation: gds-aura-pulse var(--aura-pulse-duration) var(--aura-pulse-ease) infinite;
}

/* ============================================
   AURA — GRADIENT (rotating conic border)
   ============================================
   Uses a ::before pseudo with a conic-gradient masked to just the
   border ring (via the standard mask-composite trick). The angle is
   animated via @property so the gradient rotates rather than redrawing.
   Requires position:relative on the host element; class adds it. */
@property --aura-gradient-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

@keyframes gds-aura-gradient-spin {
  to { --aura-gradient-angle: 360deg; }
}

.gds-aura-gradient {
  position: relative;
  isolation: isolate;
}

.gds-aura-gradient::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: var(--aura-gradient-thickness);
  background: conic-gradient(
    from var(--aura-gradient-angle),
    oklch(var(--aura-color) / 0) 0deg,
    oklch(var(--aura-color) / 0.9) 90deg,
    oklch(var(--aura-color) / 0) 180deg,
    oklch(var(--aura-color) / 0.9) 270deg,
    oklch(var(--aura-color) / 0) 360deg
  );
  /* mask trick: paint only the border ring, not the body */
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
          mask-composite: exclude;
  pointer-events: none;
  animation: gds-aura-gradient-spin var(--aura-gradient-duration) var(--aura-gradient-ease) infinite;
}

/* ============================================
   AURA — SHIMMER (diagonal sweep)
   ============================================
   Highlight sweeps across the element periodically. Lives on a
   ::after pseudo with `overflow: hidden` applied to the host so the
   sweep clips to the element's shape. Uses background-position rather
   than transform so it composites cheaply with the gradient + ring
   layers. */
@keyframes gds-aura-shimmer-sweep {
  0%   { background-position: -150% 0; opacity: 0; }
  8%   { opacity: var(--aura-shimmer-alpha); }
  60%  { background-position: 250% 0; opacity: var(--aura-shimmer-alpha); }
  62%  { opacity: 0; }
  100% { background-position: 250% 0; opacity: 0; }
}

.gds-aura-shimmer {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.gds-aura-shimmer::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-image: linear-gradient(
    105deg,
    transparent 30%,
    oklch(var(--aura-color) / 1) 50%,
    transparent 70%
  );
  background-size: var(--aura-shimmer-width) 200%;
  background-repeat: no-repeat;
  pointer-events: none;
  animation: gds-aura-shimmer-sweep
    calc(var(--aura-shimmer-duration) + var(--aura-shimmer-delay-between))
    var(--aura-shimmer-ease) infinite;
}

/* Reduce-motion: aura animations are decorative. Honor the user's
   pref by holding at the "peak" frame instead of animating. */
@media (prefers-reduced-motion: reduce) {
  .gds-aura-ring,
  .gds-aura-gradient::before,
  .gds-aura-shimmer::after {
    animation: none;
  }
  .gds-aura-ring {
    box-shadow:
      0 0 0 var(--aura-ring-spread) oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.6)),
      0 0 var(--aura-ring-blur) 0 oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.4));
  }
}

/* ============================================
   MOTION TOGGLE — data-motion="off" on <html>
   ============================================
   The manual motion switch (lib/motion). Mirrors the OS reduced-motion
   behaviour but driven by an attribute, so it can be flipped from a toolbar
   and posted into Studio's Fast Frame / embed iframes (grade:set-motion).
   Reduce-only: this never forces motion ON, it only suppresses it.

   Broad reset first — neutralises animation/transition on arbitrary
   (generated-screen) content, the same shape the canonical
   prefers-reduced-motion reset uses. */
[data-motion="off"] *,
[data-motion="off"] *::before,
[data-motion="off"] *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}

/* Aura keeps its bespoke static fallback (the broad reset can't supply the
   resting box-shadow). */
[data-motion="off"] .gds-aura-ring {
  box-shadow:
    0 0 0 var(--aura-ring-spread) oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.6)),
    0 0 var(--aura-ring-blur) 0 oklch(var(--aura-color) / calc(var(--aura-ring-alpha-max) * 0.4));
}

/* ============================================
   MEDIASURFACE FIDELITY — data-fidelity on any ancestor
   ============================================
   The pure-CSS half of MediaSurface's filled/wireframe model. The
   component keeps its tiered placeholder MOUNTED beneath a filled
   image and stamps `data-filled` on it; these rules decide what's
   visible. Flipping `data-fidelity="wireframe"` on ANY ancestor
   (Studio's iframe root, an embed wrapper, a docs demo) cross-fades
   imagery out and the placeholder back in — no React, so the toggle
   works in static embeds and respects the data-motion="off" reset
   above (transition collapses to 0.01ms).

   Default (full fidelity): a filled slot hides its placeholder with
   `visibility: hidden` — NOT unmount — so transparent imagery (logos
   on alpha) never shows the glyph/`--gds-media-placeholder-bg`
   through its pixels, while the layer stays in the DOM for this very
   toggle. The `visibility` transition is delayed by the fade length
   (the standard fade-then-hide pattern) so the placeholder finishes
   fading before it stops hit-testing/painting. */
[data-gds-part="media-surface-placeholder"][data-filled] {
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--gds-media-fidelity-fade, 280ms) ease,
    visibility 0s linear var(--gds-media-fidelity-fade, 280ms);
}
[data-gds-part="media-surface-content"] {
  transition: opacity var(--gds-media-fidelity-fade, 280ms) ease;
}

/* Wireframe: imagery fades out, placeholder fades back in. Content is
   opacity-faded (not display-toggled) so the swap reads as a cross-fade;
   pointer-events off so the invisible imagery can't intercept clicks. */
[data-fidelity="wireframe"] [data-gds-part="media-surface-placeholder"][data-filled] {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s, 0s;
}
[data-fidelity="wireframe"] [data-gds-part="media-surface-content"] {
  opacity: 0;
  pointer-events: none;
}

/* ============================================
   MAP MARKER — intentionally unstyled
   ============================================
   Marker content (Badge price pins, numbered markers, avatars, custom pin
   types) is the CONSUMER's design. The DS used to force a 1px border + ambient
   shadow on every direct child of a marker as a legibility "floor", but that's
   too opinionated for a primitive — it imposed a look on content you supply.
   Removed. The mode-aware `--gds-map-marker-border` / `--gds-map-marker-shadow`
   tokens remain defined, and `.gds-map-label` (below) still gives text a halo,
   so legibility on busy tiles is OPT-IN, not mandatory. */

/* Floating map label halo — put this class on a text element inside a
   MapMarker to give it a mode-aware outline (white on light tiles,
   near-black on dark) instead of hard-coding a white text-stroke that
   washes out in dark mode. Pairs with the `--gds-map-label-halo` token. */
.gds-map-label {
  -webkit-text-stroke: 1.5px var(--gds-map-label-halo);
  paint-order: stroke;
}

/* Vendor font reset — Leaflet (`.leaflet-container`) and MapLibre
   (`.maplibregl-map`) both hard-set `font-family` on their map
   containers, which beats inheritance and strands marker content on
   the vendor stack ("pins don't carry the custom font" — they can,
   the vendor CSS was just in the way). Re-assert the theme stack at
   the marker-content boundary so Badges/Cards inside pins type-match
   the rest of the screen, including custom uploaded faces riding
   `--font-sans`. */
[data-gds-part="map-marker-content"] {
  font-family: var(--font-sans);
}

/* Vendor z-index neutralizer — Leaflet's stylesheet sets `z-index: 200`
   on every `<svg>` inside the map. That hijacks an inline SVG used as
   marker content (e.g. a pin shield) and paints it ABOVE later sibling
   DOM — so a count/label sitting on top of the shield vanishes behind
   it. The Mapbox/MapLibre/Google adapters have no such rule, so the
   same marker markup renders fine there; this made pins look provider-
   dependent ("numbers show on Mapbox, not Leaflet"). Reset marker SVGs
   to `z-index: auto` so author paint order (DOM order) holds on every
   provider. `!important` to beat Leaflet's vendor selector. */
[data-gds-part="map-marker-content"] svg {
  z-index: auto !important;
}

/* Map canvas backing — sits behind the tiles so Leaflet's default
   `#dddddd` no longer flashes through the subpixel gaps between raster
   tiles ("seams"), most visible on a dark basemap. Harmless on the GL
   providers (their opaque canvas covers it). Mode-aware via the token.
   NB the selector is compounded with `[data-gds-part="map"]` on purpose:
   Leaflet stamps `.leaflet-container` (which sets `background:#ddd`) onto
   the SAME element, and its stylesheet loads after ours — a bare
   `.gds-map` ties on specificity and loses. The data-part attribute
   (present on every provider's root) lifts us above Leaflet's default. */
.gds-map[data-gds-part="map"] {
  background-color: var(--gds-map-canvas);
}

/* ============================================
   STREAM-IN — data-gds-streaming on <html>
   ============================================
   Live-draw treatment for streaming renders: while the attribute is
   stamped on <html>, every element ENTERING the DOM fades + rises in.

   CURRENTLY DORMANT — the Fast Frame sandbox no longer stamps the
   attribute. Speculative drafts moved to double-buffered full
   remounts (every compile re-inserts EVERY element, so this rule made
   the whole frame pulse on each draft tick rather than easing in just
   the new content). Kept because the treatment is right for a future
   diff-aware renderer that only inserts the appended elements.

   While stamped, every element ENTERING the DOM fades + rises in.
   Implemented with @starting-style + transition rather than a
   keyframe animation on purpose:
     - `@starting-style` only fires on first render of an element, so
       React-preserved nodes don't re-animate on every draft compile —
       only the newly streamed-in elements move.
     - It composes via `transition`, leaving each component's own
       `animation` (pulse, spin, aura) untouched. The universal
       transition does temporarily override component transitions while
       streaming, which is acceptable: the screen is read-only while it
       draws.
   Tunable via the --gds-stream-in-* tokens. Respects both the OS
   reduced-motion preference and the manual data-motion="off" toggle
   (the broad reset above zeroes transition-duration). */
:root {
  --gds-stream-in-duration: 240ms;
  --gds-stream-in-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --gds-stream-in-rise: 6px;
}

html[data-gds-streaming] body * {
  transition:
    opacity var(--gds-stream-in-duration) var(--gds-stream-in-ease),
    translate var(--gds-stream-in-duration) var(--gds-stream-in-ease);
}

html[data-gds-streaming] body * {
  @starting-style {
    opacity: 0;
    translate: 0 var(--gds-stream-in-rise);
  }
}

@media (prefers-reduced-motion: reduce) {
  html[data-gds-streaming] body * {
    transition: none;
  }
}

/* ============================================
   EDIT FLASH — data-gds-flash on changed nodes
   ============================================
   "Show me what the AI touched": when an edit turn (or any sealed
   re-render) commits, the Fast Frame sandbox diffs the old and new
   frames by data-gds-source-id and stamps `data-gds-flash` on the
   innermost changed nodes. One short pulse — outline ring + a faint
   primary wash — then the attribute is removed on animationend so the
   next edit re-triggers cleanly. Tunable via the --gds-edit-flash-*
   tokens. Honours reduced motion + the manual data-motion="off" toggle
   (the broad animation reset above zeroes it). */
:root {
  --gds-edit-flash-duration: 900ms;
  --gds-edit-flash-color: var(--primary);
}

[data-gds-flash] {
  animation: gds-edit-flash var(--gds-edit-flash-duration) ease-out;
}

@keyframes gds-edit-flash {
  0% {
    outline: 2px solid oklch(var(--gds-edit-flash-color) / 0.9);
    outline-offset: 2px;
    background-color: oklch(var(--gds-edit-flash-color) / 0.1);
  }
  60% {
    outline: 2px solid oklch(var(--gds-edit-flash-color) / 0.45);
    outline-offset: 2px;
    background-color: oklch(var(--gds-edit-flash-color) / 0.04);
  }
  100% {
    outline: 2px solid transparent;
    outline-offset: 2px;
    background-color: transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-gds-flash] {
    animation: none;
  }
}

/* ============================================
   TYPOGRAPHY UTILITIES
   ============================================ */

@layer utilities {
  /* Display */
  .text-display {
    font-size: var(--text-display);
    line-height: var(--text-display-line);
    letter-spacing: var(--text-display-tracking);
    font-weight: 700;
  }

  /* Headings */
  .text-h1 {
    font-size: var(--text-h1);
    line-height: var(--text-h1-line);
    letter-spacing: var(--text-h1-tracking);
    font-weight: 700;
  }

  .text-h2 {
    font-size: var(--text-h2);
    line-height: var(--text-h2-line);
    letter-spacing: var(--text-h2-tracking);
    font-weight: 600;
  }

  .text-h3 {
    font-size: var(--text-h3);
    line-height: var(--text-h3-line);
    letter-spacing: var(--text-h3-tracking);
    font-weight: 600;
  }

  .text-h4 {
    font-size: var(--text-h4);
    line-height: var(--text-h4-line);
    letter-spacing: var(--text-h4-tracking);
    font-weight: 600;
  }

  .text-h5 {
    font-size: var(--text-h5);
    line-height: var(--text-h5-line);
    letter-spacing: var(--text-h5-tracking);
    font-weight: 500;
  }

  .text-h6 {
    font-size: var(--text-h6);
    line-height: var(--text-h6-line);
    letter-spacing: var(--text-h6-tracking);
    font-weight: 500;
  }

  /* Body */
  .text-body-lg {
    font-size: var(--text-body-lg);
    line-height: var(--text-body-line);
  }

  .text-body {
    font-size: var(--text-body);
    line-height: var(--text-body-line);
  }

  .text-body-sm {
    font-size: var(--text-body-sm);
    line-height: var(--text-body-line);
  }

  /* Label */
  .text-label-lg {
    font-size: var(--text-label-lg);
    line-height: var(--text-label-line);
    letter-spacing: var(--text-label-tracking);
    font-weight: 500;
  }

  .text-label {
    font-size: var(--text-label);
    line-height: var(--text-label-line);
    letter-spacing: var(--text-label-tracking);
    font-weight: 500;
  }

  /* Caption */
  .text-caption {
    font-size: var(--text-caption);
    line-height: var(--text-caption-line);
    color: oklch(var(--muted-foreground));
  }

  /* Overline */
  .text-overline {
    font-size: var(--text-overline);
    letter-spacing: var(--text-overline-tracking);
    text-transform: uppercase;
    font-weight: 600;
  }

  /* Code */
  .text-code {
    font-family: var(--font-mono);
    font-size: 0.875em;
    background-color: oklch(var(--muted));
    padding: 0.125rem 0.375rem;
    border-radius: 0.25rem;
  }

  /* Lead paragraph */
  .text-lead {
    font-size: var(--text-body-lg);
    line-height: var(--text-body-line);
    color: oklch(var(--muted-foreground));
  }

  /* Muted text */
  .text-muted {
    color: oklch(var(--muted-foreground));
  }
}

/* ============================================
   ANIMATION UTILITIES
   ============================================ */

@layer utilities {
  /* Hide scrollbar */
  .scrollbar-none {
    -ms-overflow-style: none;  /* IE and Edge */
    scrollbar-width: none;  /* Firefox */
  }
  .scrollbar-none::-webkit-scrollbar {
    display: none;  /* Chrome, Safari, Opera */
  }

  /* Fade animations */
  .animate-fade-in {
    animation: fadeIn 0.2s ease-out;
  }

  .animate-fade-out {
    animation: fadeOut 0.2s ease-in;
  }

  /* Scale animations */
  .animate-scale-in {
    animation: scaleIn 0.15s ease-out;
  }

  /* Slide animations */
  .animate-slide-in-from-top {
    animation: slideInFromTop 0.2s ease-out;
  }

  .animate-slide-in-from-bottom {
    animation: slideInFromBottom 0.2s ease-out;
  }

  .animate-slide-in-from-left {
    animation: slideInFromLeft 0.2s ease-out;
  }

  .animate-slide-in-from-right {
    animation: slideInFromRight 0.2s ease-out;
  }

  /* Pulse animation for energy indicators */
  .animate-energy-pulse {
    animation: energyPulse 2s ease-in-out infinite;
  }

  /* Shimmer effect for loading states */
  .animate-shimmer {
    animation: shimmer 2s linear infinite;
    background: linear-gradient(
      90deg,
      oklch(var(--muted)) 0%,
      oklch(var(--muted-foreground) / 0.1) 50%,
      oklch(var(--muted)) 100%
    );
    background-size: 200% 100%;
  }
}

@layer utilities {
  /* Colour SCOPES (STUDIO-COLOR.md) — a local colour mode, the same idea as
     light/dark but scoped to a wrapper and focused on surfaces.
     Apply `scope-inverse` (etc.) to any element and it becomes a mini themed
     region: it re-points the SURFACE family of tokens — page background, card,
     popover, muted, border, and every foreground — at the named pair, so the
     whole subtree re-tones (a dark band with light text, cards that sit on the
     band instead of glaring white). Descendants keep using the ordinary tokens
     (bg-background / text-foreground / bg-card); only what they resolve to
     changes. It deliberately leaves the ACTION colours (--primary / --accent /
     --secondary / --destructive) untouched, so a CTA stays branded and vivid
     on top of the band.
     `inverse` reads --fg-base / --bg-base (stable literal mirrors of the page
     ink/paper that the generator emits) so the fg/bg swap can't form a custom-
     property cycle; the other scopes read the theme's own tokens directly. */
  .scope-default,
  .scope-inverse,
  .scope-brand,
  .scope-accent,
  .scope-muted,
  .scope-card {
    background-color: oklch(var(--background));
    color: oklch(var(--foreground));
  }
  /* default = identity: paints the page tokens as-is, no overrides. */
  .scope-inverse {
    --background: var(--fg-base);
    --foreground: var(--bg-base);
    --card: var(--fg-base);
    --card-foreground: var(--bg-base);
    --popover: var(--fg-base);
    --popover-foreground: var(--bg-base);
    --muted: var(--fg-base);
    --muted-foreground: var(--bg-base);
    --super-muted-foreground: var(--bg-base);
    --border: var(--bg-base);
    --input: var(--bg-base);
  }
  .scope-brand {
    --background: var(--primary);
    --foreground: var(--primary-foreground);
    --card: var(--primary);
    --card-foreground: var(--primary-foreground);
    --popover: var(--primary);
    --popover-foreground: var(--primary-foreground);
    --muted: var(--primary);
    --muted-foreground: var(--primary-foreground);
    --super-muted-foreground: var(--primary-foreground);
    --border: var(--primary-foreground);
    --input: var(--primary-foreground);
  }
  .scope-accent {
    --background: var(--accent);
    --foreground: var(--accent-foreground);
    --card: var(--accent);
    --card-foreground: var(--accent-foreground);
    --popover: var(--accent);
    --popover-foreground: var(--accent-foreground);
    --muted: var(--accent);
    --muted-foreground: var(--accent-foreground);
    --super-muted-foreground: var(--accent-foreground);
    --border: var(--accent-foreground);
    --input: var(--accent-foreground);
  }
  .scope-muted {
    --background: var(--muted);
    --card: var(--muted);
    --popover: var(--muted);
    /* foreground + muted-foreground stay the page's: already tuned to read on
       a muted surface. */
  }
  .scope-card {
    --background: var(--card);
    --foreground: var(--card-foreground);
    --popover: var(--card);
    --popover-foreground: var(--card-foreground);
    --muted-foreground: var(--card-foreground);
    --super-muted-foreground: var(--card-foreground);
  }
}

/* ============================================
   EXPRESSIVE colour layer — see STUDIO-EXPRESSIVE.md
   A highlight layer for painting whole SECTIONS, deliberately louder than the
   neutral product chrome (marketing bands, feature cards, promo strips).
   Orthogonal to the surface scopes above: it does NOT touch --primary/--accent,
   so it stacks on top of any theme. Two attributes resolve a section's colour:
     data-expressive="accent1..5"   picks one of 5 accent slots
     data-expressive-tier="…"       picks one of 4 tiers (default: light)
   …landing an accessible pair on --gds-expressive-bg / --gds-expressive-fg,
   which the `.expressive` helper paints. Values mirror the Figma "Expressive"
   collection (accent1..5 = rose, orange, amber, yellow, olive). The accents are
   fixed ramps today; the generator will later write them from one hue per slot
   — the token shape stays identical. Tiers use ramp steps 100/300/700/900. */
:root {
  --gds-expressive-accent1-100: #ffe9e6; --gds-expressive-accent1-300: #ffaaa2; --gds-expressive-accent1-700: #8b181d; --gds-expressive-accent1-900: #3f0b0b;
  --gds-expressive-accent2-100: #ffeae0; --gds-expressive-accent2-300: #ffae89; --gds-expressive-accent2-700: #822c00; --gds-expressive-accent2-900: #3c1100;
  --gds-expressive-accent3-100: #ffecd8; --gds-expressive-accent3-300: #f6b575; --gds-expressive-accent3-700: #743b00; --gds-expressive-accent3-900: #331900;
  --gds-expressive-accent4-100: #fdefd2; --gds-expressive-accent4-300: #e5bf6d; --gds-expressive-accent4-700: #634600; --gds-expressive-accent4-900: #2d1d00;
  --gds-expressive-accent5-100: #f4f2d4; --gds-expressive-accent5-300: #cfc871; --gds-expressive-accent5-700: #544e00; --gds-expressive-accent5-900: #252100;
}

/* accent slot → generic step vars */
[data-expressive="accent1"] { --exp-100: var(--gds-expressive-accent1-100); --exp-300: var(--gds-expressive-accent1-300); --exp-700: var(--gds-expressive-accent1-700); --exp-900: var(--gds-expressive-accent1-900); }
[data-expressive="accent2"] { --exp-100: var(--gds-expressive-accent2-100); --exp-300: var(--gds-expressive-accent2-300); --exp-700: var(--gds-expressive-accent2-700); --exp-900: var(--gds-expressive-accent2-900); }
[data-expressive="accent3"] { --exp-100: var(--gds-expressive-accent3-100); --exp-300: var(--gds-expressive-accent3-300); --exp-700: var(--gds-expressive-accent3-700); --exp-900: var(--gds-expressive-accent3-900); }
[data-expressive="accent4"] { --exp-100: var(--gds-expressive-accent4-100); --exp-300: var(--gds-expressive-accent4-300); --exp-700: var(--gds-expressive-accent4-700); --exp-900: var(--gds-expressive-accent4-900); }
[data-expressive="accent5"] { --exp-100: var(--gds-expressive-accent5-100); --exp-300: var(--gds-expressive-accent5-300); --exp-700: var(--gds-expressive-accent5-700); --exp-900: var(--gds-expressive-accent5-900); }

/* tier → which steps become bg / fg (default = light) */
[data-expressive] { --gds-expressive-bg: var(--exp-300); --gds-expressive-fg: var(--exp-900); }
[data-expressive][data-expressive-tier="superlight"] { --gds-expressive-bg: var(--exp-100); --gds-expressive-fg: var(--exp-900); }
[data-expressive][data-expressive-tier="light"]      { --gds-expressive-bg: var(--exp-300); --gds-expressive-fg: var(--exp-900); }
[data-expressive][data-expressive-tier="dark"]       { --gds-expressive-bg: var(--exp-700); --gds-expressive-fg: var(--exp-100); }
[data-expressive][data-expressive-tier="superdark"]  { --gds-expressive-bg: var(--exp-900); --gds-expressive-fg: var(--exp-100); }

/* the surface helper a section wears: paints the resolved expressive pair */
.expressive { background-color: var(--gds-expressive-bg); color: var(--gds-expressive-fg); }

/* ============================================
   REVEAL — scroll-in motion for sections (and any element). Token-driven:
   reuses the DS transition durations + eases (above), adds a reveal distance.
   An element with `data-reveal` starts hidden/offset; an IntersectionObserver
   in the consumer adds `.in-view` to animate it in. Entirely disabled under
   prefers-reduced-motion (the hidden state lives inside the media query, so
   content is always visible if motion is off or JS never runs). */
:root {
  --gds-reveal-distance: 1.25rem;
  --gds-reveal-duration: var(--gds-transition-slower); /* 500ms */
  --gds-reveal-ease: var(--gds-ease-out);
}

@media (prefers-reduced-motion: no-preference) {
  [data-reveal] {
    opacity: 0;
    transform: translateY(var(--gds-reveal-distance));
    transition:
      opacity var(--gds-reveal-duration) var(--gds-reveal-ease),
      transform var(--gds-reveal-duration) var(--gds-reveal-ease);
    will-change: opacity, transform;
  }
  [data-reveal="fade"] { transform: none; }
  [data-reveal="fade-down"] { transform: translateY(calc(-1 * var(--gds-reveal-distance))); }
  [data-reveal="fade-left"] { transform: translateX(var(--gds-reveal-distance)); }
  [data-reveal="fade-right"] { transform: translateX(calc(-1 * var(--gds-reveal-distance))); }
  [data-reveal].in-view {
    opacity: 1;
    transform: none;
  }
}

/* ============================================
   TYPE SCALE — scoped, switchable, fluid (mobile → desktop)
   ============================================
   Two scales, one fluid ramp. Pick a mobile + a desktop scale and the whole
   --text-* ramp (Tailwind 2xs…7xl + semantic display/h1…body) interpolates
   between them by viewport, CLAMPED to the mobile floor and desktop ceiling
   (the clamp lives on --ts-fluid: 0→1, so values never exceed either end):
     data-type-scale-mobile="minor-third"  data-type-scale-desktop="perfect-fifth"
   Shorthand (flat): data-type-scale="major-third" (sets both). Assign to :root,
   a page, or any element — composes via the cascade like the colour scopes.

   ASYMMETRIC by design: steps go UP at the chosen ratio but DOWN capped at
   major-third, so a dramatic display scale keeps sm/xs/2xs readable instead of
   shrinking body/caption text into the floor. Generated from @gradeui/core. */

[data-type-scale-mobile="minor-second"] {
  --tsm-2xs: 0.823rem;
  --tsm-xs: 0.878rem;
  --tsm-sm: 0.937rem;
  --tsm-base: 1rem;
  --tsm-lg: 1.067rem;
  --tsm-xl: 1.138rem;
  --tsm-2xl: 1.215rem;
  --tsm-3xl: 1.296rem;
  --tsm-4xl: 1.383rem;
  --tsm-5xl: 1.476rem;
  --tsm-6xl: 1.575rem;
  --tsm-7xl: 1.68rem;
}
[data-type-scale-desktop="minor-second"] {
  --tsd-2xs: 0.823rem;
  --tsd-xs: 0.878rem;
  --tsd-sm: 0.937rem;
  --tsd-base: 1rem;
  --tsd-lg: 1.067rem;
  --tsd-xl: 1.138rem;
  --tsd-2xl: 1.215rem;
  --tsd-3xl: 1.296rem;
  --tsd-4xl: 1.383rem;
  --tsd-5xl: 1.476rem;
  --tsd-6xl: 1.575rem;
  --tsd-7xl: 1.68rem;
}
[data-type-scale="minor-second"] {
  --tsm-2xs: 0.823rem; --tsd-2xs: 0.823rem;
  --tsm-xs: 0.878rem; --tsd-xs: 0.878rem;
  --tsm-sm: 0.937rem; --tsd-sm: 0.937rem;
  --tsm-base: 1rem; --tsd-base: 1rem;
  --tsm-lg: 1.067rem; --tsd-lg: 1.067rem;
  --tsm-xl: 1.138rem; --tsd-xl: 1.138rem;
  --tsm-2xl: 1.215rem; --tsd-2xl: 1.215rem;
  --tsm-3xl: 1.296rem; --tsd-3xl: 1.296rem;
  --tsm-4xl: 1.383rem; --tsd-4xl: 1.383rem;
  --tsm-5xl: 1.476rem; --tsd-5xl: 1.476rem;
  --tsm-6xl: 1.575rem; --tsd-6xl: 1.575rem;
  --tsm-7xl: 1.68rem; --tsd-7xl: 1.68rem;
}

[data-type-scale-mobile="major-second"] {
  --tsm-2xs: 0.702rem;
  --tsm-xs: 0.79rem;
  --tsm-sm: 0.889rem;
  --tsm-base: 1rem;
  --tsm-lg: 1.125rem;
  --tsm-xl: 1.266rem;
  --tsm-2xl: 1.424rem;
  --tsm-3xl: 1.602rem;
  --tsm-4xl: 1.802rem;
  --tsm-5xl: 2.027rem;
  --tsm-6xl: 2.281rem;
  --tsm-7xl: 2.566rem;
}
[data-type-scale-desktop="major-second"] {
  --tsd-2xs: 0.702rem;
  --tsd-xs: 0.79rem;
  --tsd-sm: 0.889rem;
  --tsd-base: 1rem;
  --tsd-lg: 1.125rem;
  --tsd-xl: 1.266rem;
  --tsd-2xl: 1.424rem;
  --tsd-3xl: 1.602rem;
  --tsd-4xl: 1.802rem;
  --tsd-5xl: 2.027rem;
  --tsd-6xl: 2.281rem;
  --tsd-7xl: 2.566rem;
}
[data-type-scale="major-second"] {
  --tsm-2xs: 0.702rem; --tsd-2xs: 0.702rem;
  --tsm-xs: 0.79rem; --tsd-xs: 0.79rem;
  --tsm-sm: 0.889rem; --tsd-sm: 0.889rem;
  --tsm-base: 1rem; --tsd-base: 1rem;
  --tsm-lg: 1.125rem; --tsd-lg: 1.125rem;
  --tsm-xl: 1.266rem; --tsd-xl: 1.266rem;
  --tsm-2xl: 1.424rem; --tsd-2xl: 1.424rem;
  --tsm-3xl: 1.602rem; --tsd-3xl: 1.602rem;
  --tsm-4xl: 1.802rem; --tsd-4xl: 1.802rem;
  --tsm-5xl: 2.027rem; --tsd-5xl: 2.027rem;
  --tsm-6xl: 2.281rem; --tsd-6xl: 2.281rem;
  --tsm-7xl: 2.566rem; --tsd-7xl: 2.566rem;
}

[data-type-scale-mobile="minor-third"] {
  --tsm-2xs: 0.625rem;
  --tsm-xs: 0.694rem;
  --tsm-sm: 0.833rem;
  --tsm-base: 1rem;
  --tsm-lg: 1.2rem;
  --tsm-xl: 1.44rem;
  --tsm-2xl: 1.728rem;
  --tsm-3xl: 2.074rem;
  --tsm-4xl: 2.488rem;
  --tsm-5xl: 2.986rem;
  --tsm-6xl: 3.583rem;
  --tsm-7xl: 4.3rem;
}
[data-type-scale-desktop="minor-third"] {
  --tsd-2xs: 0.625rem;
  --tsd-xs: 0.694rem;
  --tsd-sm: 0.833rem;
  --tsd-base: 1rem;
  --tsd-lg: 1.2rem;
  --tsd-xl: 1.44rem;
  --tsd-2xl: 1.728rem;
  --tsd-3xl: 2.074rem;
  --tsd-4xl: 2.488rem;
  --tsd-5xl: 2.986rem;
  --tsd-6xl: 3.583rem;
  --tsd-7xl: 4.3rem;
}
[data-type-scale="minor-third"] {
  --tsm-2xs: 0.625rem; --tsd-2xs: 0.625rem;
  --tsm-xs: 0.694rem; --tsd-xs: 0.694rem;
  --tsm-sm: 0.833rem; --tsd-sm: 0.833rem;
  --tsm-base: 1rem; --tsd-base: 1rem;
  --tsm-lg: 1.2rem; --tsd-lg: 1.2rem;
  --tsm-xl: 1.44rem; --tsd-xl: 1.44rem;
  --tsm-2xl: 1.728rem; --tsd-2xl: 1.728rem;
  --tsm-3xl: 2.074rem; --tsd-3xl: 2.074rem;
  --tsm-4xl: 2.488rem; --tsd-4xl: 2.488rem;
  --tsm-5xl: 2.986rem; --tsd-5xl: 2.986rem;
  --tsm-6xl: 3.583rem; --tsd-6xl: 3.583rem;
  --tsm-7xl: 4.3rem; --tsd-7xl: 4.3rem;
}

[data-type-scale-mobile="major-third"] {
  --tsm-2xs: 0.625rem;
  --tsm-xs: 0.64rem;
  --tsm-sm: 0.8rem;
  --tsm-base: 1rem;
  --tsm-lg: 1.25rem;
  --tsm-xl: 1.563rem;
  --tsm-2xl: 1.953rem;
  --tsm-3xl: 2.441rem;
  --tsm-4xl: 3.052rem;
  --tsm-5xl: 3.815rem;
  --tsm-6xl: 4.768rem;
  --tsm-7xl: 5.96rem;
}
[data-type-scale-desktop="major-third"] {
  --tsd-2xs: 0.625rem;
  --tsd-xs: 0.64rem;
  --tsd-sm: 0.8rem;
  --tsd-base: 1rem;
  --tsd-lg: 1.25rem;
  --tsd-xl: 1.563rem;
  --tsd-2xl: 1.953rem;
  --tsd-3xl: 2.441rem;
  --tsd-4xl: 3.052rem;
  --tsd-5xl: 3.815rem;
  --tsd-6xl: 4.768rem;
  --tsd-7xl: 5.96rem;
}
[data-type-scale="major-third"] {
  --tsm-2xs: 0.625rem; --tsd-2xs: 0.625rem;
  --tsm-xs: 0.64rem; --tsd-xs: 0.64rem;
  --tsm-sm: 0.8rem; --tsd-sm: 0.8rem;
  --tsm-base: 1rem; --tsd-base: 1rem;
  --tsm-lg: 1.25rem; --tsd-lg: 1.25rem;
  --tsm-xl: 1.563rem; --tsd-xl: 1.563rem;
  --tsm-2xl: 1.953rem; --tsd-2xl: 1.953rem;
  --tsm-3xl: 2.441rem; --tsd-3xl: 2.441rem;
  --tsm-4xl: 3.052rem; --tsd-4xl: 3.052rem;
  --tsm-5xl: 3.815rem; --tsd-5xl: 3.815rem;
  --tsm-6xl: 4.768rem; --tsd-6xl: 4.768rem;
  --tsm-7xl: 5.96rem; --tsd-7xl: 5.96rem;
}

[data-type-scale-mobile="perfect-fourth"] {
  --tsm-2xs: 0.625rem;
  --tsm-xs: 0.64rem;
  --tsm-sm: 0.8rem;
  --tsm-base: 1rem;
  --tsm-lg: 1.333rem;
  --tsm-xl: 1.777rem;
  --tsm-2xl: 2.369rem;
  --tsm-3xl: 3.157rem;
  --tsm-4xl: 4.209rem;
  --tsm-5xl: 5.61rem;
  --tsm-6xl: 7.478rem;
  --tsm-7xl: 9.969rem;
}
[data-type-scale-desktop="perfect-fourth"] {
  --tsd-2xs: 0.625rem;
  --tsd-xs: 0.64rem;
  --tsd-sm: 0.8rem;
  --tsd-base: 1rem;
  --tsd-lg: 1.333rem;
  --tsd-xl: 1.777rem;
  --tsd-2xl: 2.369rem;
  --tsd-3xl: 3.157rem;
  --tsd-4xl: 4.209rem;
  --tsd-5xl: 5.61rem;
  --tsd-6xl: 7.478rem;
  --tsd-7xl: 9.969rem;
}
[data-type-scale="perfect-fourth"] {
  --tsm-2xs: 0.625rem; --tsd-2xs: 0.625rem;
  --tsm-xs: 0.64rem; --tsd-xs: 0.64rem;
  --tsm-sm: 0.8rem; --tsd-sm: 0.8rem;
  --tsm-base: 1rem; --tsd-base: 1rem;
  --tsm-lg: 1.333rem; --tsd-lg: 1.333rem;
  --tsm-xl: 1.777rem; --tsd-xl: 1.777rem;
  --tsm-2xl: 2.369rem; --tsd-2xl: 2.369rem;
  --tsm-3xl: 3.157rem; --tsd-3xl: 3.157rem;
  --tsm-4xl: 4.209rem; --tsd-4xl: 4.209rem;
  --tsm-5xl: 5.61rem; --tsd-5xl: 5.61rem;
  --tsm-6xl: 7.478rem; --tsd-6xl: 7.478rem;
  --tsm-7xl: 9.969rem; --tsd-7xl: 9.969rem;
}

[data-type-scale-mobile="augmented-fourth"] {
  --tsm-2xs: 0.625rem;
  --tsm-xs: 0.64rem;
  --tsm-sm: 0.8rem;
  --tsm-base: 1rem;
  --tsm-lg: 1.414rem;
  --tsm-xl: 1.999rem;
  --tsm-2xl: 2.827rem;
  --tsm-3xl: 3.998rem;
  --tsm-4xl: 5.653rem;
  --tsm-5xl: 7.993rem;
  --tsm-6xl: 11.302rem;
  --tsm-7xl: 15.981rem;
}
[data-type-scale-desktop="augmented-fourth"] {
  --tsd-2xs: 0.625rem;
  --tsd-xs: 0.64rem;
  --tsd-sm: 0.8rem;
  --tsd-base: 1rem;
  --tsd-lg: 1.414rem;
  --tsd-xl: 1.999rem;
  --tsd-2xl: 2.827rem;
  --tsd-3xl: 3.998rem;
  --tsd-4xl: 5.653rem;
  --tsd-5xl: 7.993rem;
  --tsd-6xl: 11.302rem;
  --tsd-7xl: 15.981rem;
}
[data-type-scale="augmented-fourth"] {
  --tsm-2xs: 0.625rem; --tsd-2xs: 0.625rem;
  --tsm-xs: 0.64rem; --tsd-xs: 0.64rem;
  --tsm-sm: 0.8rem; --tsd-sm: 0.8rem;
  --tsm-base: 1rem; --tsd-base: 1rem;
  --tsm-lg: 1.414rem; --tsd-lg: 1.414rem;
  --tsm-xl: 1.999rem; --tsd-xl: 1.999rem;
  --tsm-2xl: 2.827rem; --tsd-2xl: 2.827rem;
  --tsm-3xl: 3.998rem; --tsd-3xl: 3.998rem;
  --tsm-4xl: 5.653rem; --tsd-4xl: 5.653rem;
  --tsm-5xl: 7.993rem; --tsd-5xl: 7.993rem;
  --tsm-6xl: 11.302rem; --tsd-6xl: 11.302rem;
  --tsm-7xl: 15.981rem; --tsd-7xl: 15.981rem;
}

[data-type-scale-mobile="perfect-fifth"] {
  --tsm-2xs: 0.625rem;
  --tsm-xs: 0.64rem;
  --tsm-sm: 0.8rem;
  --tsm-base: 1rem;
  --tsm-lg: 1.5rem;
  --tsm-xl: 2.25rem;
  --tsm-2xl: 3.375rem;
  --tsm-3xl: 5.063rem;
  --tsm-4xl: 7.594rem;
  --tsm-5xl: 11.391rem;
  --tsm-6xl: 17.086rem;
  --tsm-7xl: 25.629rem;
}
[data-type-scale-desktop="perfect-fifth"] {
  --tsd-2xs: 0.625rem;
  --tsd-xs: 0.64rem;
  --tsd-sm: 0.8rem;
  --tsd-base: 1rem;
  --tsd-lg: 1.5rem;
  --tsd-xl: 2.25rem;
  --tsd-2xl: 3.375rem;
  --tsd-3xl: 5.063rem;
  --tsd-4xl: 7.594rem;
  --tsd-5xl: 11.391rem;
  --tsd-6xl: 17.086rem;
  --tsd-7xl: 25.629rem;
}
[data-type-scale="perfect-fifth"] {
  --tsm-2xs: 0.625rem; --tsd-2xs: 0.625rem;
  --tsm-xs: 0.64rem; --tsd-xs: 0.64rem;
  --tsm-sm: 0.8rem; --tsd-sm: 0.8rem;
  --tsm-base: 1rem; --tsd-base: 1rem;
  --tsm-lg: 1.5rem; --tsd-lg: 1.5rem;
  --tsm-xl: 2.25rem; --tsd-xl: 2.25rem;
  --tsm-2xl: 3.375rem; --tsd-2xl: 3.375rem;
  --tsm-3xl: 5.063rem; --tsd-3xl: 5.063rem;
  --tsm-4xl: 7.594rem; --tsd-4xl: 7.594rem;
  --tsm-5xl: 11.391rem; --tsd-5xl: 11.391rem;
  --tsm-6xl: 17.086rem; --tsd-6xl: 17.086rem;
  --tsm-7xl: 25.629rem; --tsd-7xl: 25.629rem;
}

[data-type-scale-mobile="golden-ratio"] {
  --tsm-2xs: 0.625rem;
  --tsm-xs: 0.64rem;
  --tsm-sm: 0.8rem;
  --tsm-base: 1rem;
  --tsm-lg: 1.618rem;
  --tsm-xl: 2.618rem;
  --tsm-2xl: 4.236rem;
  --tsm-3xl: 6.854rem;
  --tsm-4xl: 11.089rem;
  --tsm-5xl: 17.942rem;
  --tsm-6xl: 29.03rem;
  --tsm-7xl: 46.971rem;
}
[data-type-scale-desktop="golden-ratio"] {
  --tsd-2xs: 0.625rem;
  --tsd-xs: 0.64rem;
  --tsd-sm: 0.8rem;
  --tsd-base: 1rem;
  --tsd-lg: 1.618rem;
  --tsd-xl: 2.618rem;
  --tsd-2xl: 4.236rem;
  --tsd-3xl: 6.854rem;
  --tsd-4xl: 11.089rem;
  --tsd-5xl: 17.942rem;
  --tsd-6xl: 29.03rem;
  --tsd-7xl: 46.971rem;
}
[data-type-scale="golden-ratio"] {
  --tsm-2xs: 0.625rem; --tsd-2xs: 0.625rem;
  --tsm-xs: 0.64rem; --tsd-xs: 0.64rem;
  --tsm-sm: 0.8rem; --tsd-sm: 0.8rem;
  --tsm-base: 1rem; --tsd-base: 1rem;
  --tsm-lg: 1.618rem; --tsd-lg: 1.618rem;
  --tsm-xl: 2.618rem; --tsd-xl: 2.618rem;
  --tsm-2xl: 4.236rem; --tsd-2xl: 4.236rem;
  --tsm-3xl: 6.854rem; --tsd-3xl: 6.854rem;
  --tsm-4xl: 11.089rem; --tsd-4xl: 11.089rem;
  --tsm-5xl: 17.942rem; --tsd-5xl: 17.942rem;
  --tsm-6xl: 29.03rem; --tsd-6xl: 29.03rem;
  --tsm-7xl: 46.971rem; --tsd-7xl: 46.971rem;
}

[data-type-scale], [data-type-scale-mobile], [data-type-scale-desktop] {
  --ts-fluid: clamp(0, calc((100vw - 20rem) / 60rem), 1);
  --text-2xs: calc(var(--tsm-2xs, var(--tsd-2xs)) + (var(--tsd-2xs, var(--tsm-2xs)) - var(--tsm-2xs, var(--tsd-2xs))) * var(--ts-fluid));
  --text-xs: calc(var(--tsm-xs, var(--tsd-xs)) + (var(--tsd-xs, var(--tsm-xs)) - var(--tsm-xs, var(--tsd-xs))) * var(--ts-fluid));
  --text-sm: calc(var(--tsm-sm, var(--tsd-sm)) + (var(--tsd-sm, var(--tsm-sm)) - var(--tsm-sm, var(--tsd-sm))) * var(--ts-fluid));
  --text-base: calc(var(--tsm-base, var(--tsd-base)) + (var(--tsd-base, var(--tsm-base)) - var(--tsm-base, var(--tsd-base))) * var(--ts-fluid));
  --text-lg: calc(var(--tsm-lg, var(--tsd-lg)) + (var(--tsd-lg, var(--tsm-lg)) - var(--tsm-lg, var(--tsd-lg))) * var(--ts-fluid));
  --text-xl: calc(var(--tsm-xl, var(--tsd-xl)) + (var(--tsd-xl, var(--tsm-xl)) - var(--tsm-xl, var(--tsd-xl))) * var(--ts-fluid));
  --text-2xl: calc(var(--tsm-2xl, var(--tsd-2xl)) + (var(--tsd-2xl, var(--tsm-2xl)) - var(--tsm-2xl, var(--tsd-2xl))) * var(--ts-fluid));
  --text-3xl: calc(var(--tsm-3xl, var(--tsd-3xl)) + (var(--tsd-3xl, var(--tsm-3xl)) - var(--tsm-3xl, var(--tsd-3xl))) * var(--ts-fluid));
  --text-4xl: calc(var(--tsm-4xl, var(--tsd-4xl)) + (var(--tsd-4xl, var(--tsm-4xl)) - var(--tsm-4xl, var(--tsd-4xl))) * var(--ts-fluid));
  --text-5xl: calc(var(--tsm-5xl, var(--tsd-5xl)) + (var(--tsd-5xl, var(--tsm-5xl)) - var(--tsm-5xl, var(--tsd-5xl))) * var(--ts-fluid));
  --text-6xl: calc(var(--tsm-6xl, var(--tsd-6xl)) + (var(--tsd-6xl, var(--tsm-6xl)) - var(--tsm-6xl, var(--tsd-6xl))) * var(--ts-fluid));
  --text-7xl: calc(var(--tsm-7xl, var(--tsd-7xl)) + (var(--tsd-7xl, var(--tsm-7xl)) - var(--tsm-7xl, var(--tsd-7xl))) * var(--ts-fluid));
  --text-display: var(--text-6xl);
  --text-h1: var(--text-4xl);
  --text-h2: var(--text-3xl);
  --text-h3: var(--text-2xl);
  --text-h4: var(--text-xl);
  --text-h5: var(--text-lg);
  --text-body-lg: var(--text-lg);
  --text-h6: var(--text-base);
  --text-body: var(--text-base);
  --text-body-sm: var(--text-sm);
}

/* ============================================
   SIZE SCALE — scoped, switchable, fluid (mobile → desktop)
   ============================================
   Same engine as the type scale, for SPACING. Re-points Tailwind's --spacing
   base, so EVERY spacing utility (gap-*, p-*, m-*, size-*) re-pitches at once —
   one knob for a page or section's whole density. Pick a mobile density + a
   desktop density; --spacing interpolates between them by viewport, clamped.
   Shorthand data-size-scale="comfortable" sets both (flat). Levels are density
   presets (the --spacing base unit), not modular ratios — spacing is a linear
   grid. Composes via the cascade like the type scale; drop data-size-scale=
   "compact" on an embedded table/UI mockup to tighten just that region. */
[data-size-scale-mobile="compact"]      { --ssm: 0.2rem; }
[data-size-scale-mobile="cozy"]         { --ssm: 0.225rem; }
[data-size-scale-mobile="comfortable"]  { --ssm: 0.25rem; }
[data-size-scale-mobile="spacious"]     { --ssm: 0.3rem; }
[data-size-scale-mobile="expansive"]    { --ssm: 0.35rem; }
[data-size-scale-desktop="compact"]     { --ssd: 0.2rem; }
[data-size-scale-desktop="cozy"]        { --ssd: 0.225rem; }
[data-size-scale-desktop="comfortable"] { --ssd: 0.25rem; }
[data-size-scale-desktop="spacious"]    { --ssd: 0.3rem; }
[data-size-scale-desktop="expansive"]   { --ssd: 0.35rem; }
[data-size-scale="compact"]      { --ssm: 0.2rem;   --ssd: 0.2rem; }
[data-size-scale="cozy"]         { --ssm: 0.225rem; --ssd: 0.225rem; }
[data-size-scale="comfortable"]  { --ssm: 0.25rem;  --ssd: 0.25rem; }
[data-size-scale="spacious"]     { --ssm: 0.3rem;   --ssd: 0.3rem; }
[data-size-scale="expansive"]    { --ssm: 0.35rem;  --ssd: 0.35rem; }
[data-size-scale], [data-size-scale-mobile], [data-size-scale-desktop] {
  --ss-fluid: clamp(0, calc((100vw - 20rem) / 60rem), 1);
  --spacing: calc(var(--ssm, var(--ssd)) + (var(--ssd, var(--ssm)) - var(--ssm, var(--ssd))) * var(--ss-fluid));
}

/* Keyframes */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes fadeOut {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0.95);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes slideInFromTop {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideInFromBottom {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideInFromLeft {
  from {
    opacity: 0;
    transform: translateX(-10px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideInFromRight {
  from {
    opacity: 0;
    transform: translateX(10px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes energyPulse {
  0%, 100% {
    opacity: 1;
    box-shadow: 0 0 0 0 oklch(var(--accent) / 0.4);
  }
  50% {
    opacity: 0.8;
    box-shadow: 0 0 0 8px oklch(var(--accent) / 0);
  }
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

@keyframes pulse-glow {
  0%, 100% {
    box-shadow: 0 0 4px 1px oklch(var(--primary) / 0.2);
  }
  50% {
    box-shadow: 0 0 8px 2px oklch(var(--primary) / 0.35);
  }
}

.animate-pulse-glow {
  animation: pulse-glow 2s ease-in-out infinite;
}

/* CSS Houdini property for animating conic-gradient angle */
@property --angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@keyframes rotate-border {
  0% {
    --angle: 0deg;
  }
  100% {
    --angle: 360deg;
  }
}

/* Border travel animation for energy flow nodes */
.border-travel-animation {
  position: relative;
  border: 2px solid transparent;
  background:
    linear-gradient(white, white) padding-box,
    conic-gradient(
      from var(--angle, 0deg),
      rgba(34, 197, 94, 0.15) 0deg,
      rgba(34, 197, 94, 0.15) 350deg,
      #22c55e 353deg,
      #16a34a 356deg,
      #22c55e 359deg,
      rgba(34, 197, 94, 0.15) 360deg
    ) border-box;
  animation: rotate-border 3s linear infinite;
  border-radius: 0.75rem;
}

.border-travel-animation.rounded-full {
  border-radius: 9999px;
}

.dark .border-travel-animation {
  background:
    linear-gradient(#141414, #141414) padding-box,
    conic-gradient(
      from var(--angle, 0deg),
      rgba(34, 197, 94, 0.1) 0deg,
      rgba(34, 197, 94, 0.1) 350deg,
      #22c55e 353deg,
      #16a34a 356deg,
      #22c55e 359deg,
      rgba(34, 197, 94, 0.1) 360deg
    ) border-box;
}

/* React Flow controls positioning */
.react-flow__controls {
  bottom: 10px !important;
  top: auto !important;
  left: 10px !important;
}

/* React Flow dark mode styles */
.dark .react-flow__controls {
  background: #141414;
  border: 1px solid #262626;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}

.dark .react-flow__controls-button {
  background: #141414;
  border-bottom: 1px solid #262626;
  fill: #a3a3a3;
}

.dark .react-flow__controls-button:hover {
  background: #262626;
}

.dark .react-flow__controls-button svg {
  fill: #a3a3a3;
}

.dark .react-flow__controls-button:hover svg {
  fill: #ffffff;
}

/* Dotted background pattern */
.bg-dot-pattern {
  background-image: radial-gradient(circle, rgba(0, 0, 0, 0.08) 1px, transparent 1px);
  background-size: 24px 24px;
}

.dark .bg-dot-pattern {
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
}

/* Gradient fade for hero */
.hero-gradient {
  background: linear-gradient(to bottom, transparent 0%, oklch(var(--background)) 70%, oklch(var(--background)) 100%);
}

/* ============================================
   INFINITE SCROLL ANIMATION (Partner Logos)
   ============================================ */

@keyframes scrollLeft {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%);
  }
}

@keyframes scrollRight {
  0% {
    transform: translateX(-50%);
  }
  100% {
    transform: translateX(0);
  }
}

.animate-scroll-left {
  animation: scrollLeft linear infinite;
}

.animate-scroll-right {
  animation: scrollRight linear infinite;
}

/* Pause animation on hover */
.animate-scroll-left:hover,
.animate-scroll-right:hover {
  animation-play-state: paused;
}

/* ─────────────────────────────────────────────────────────────────────────
   Selection cards — RadioCard / CheckboxCard / SwitchCard
   ---------------------------------------------------------------------------
   The whole card is the control, so focus / hover / checked all live on the
   parent surface. Every visual reads from a `--gds-selection-card-*` token
   with a semantic fallback, so a project can re-skin the cards via the
   per-project override layer (set the token at theme scope) without touching
   the component. Per-instance overrides still flow through `style={{ … }}`.
   ───────────────────────────────────────────────────────────────────────── */
.gds-selection-card {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--gds-selection-card-gap, 0.75rem);
  width: 100%;
  text-align: left;
  cursor: pointer;
  padding: var(--gds-selection-card-padding, 1rem);
  border-radius: var(--gds-selection-card-radius, var(--radius));
  border: var(--gds-selection-card-border-width, 1px) solid
    oklch(var(--gds-selection-card-border, var(--border)));
  background: oklch(var(--gds-selection-card-bg, var(--card)));
  color: oklch(var(--foreground));
  transition:
    border-color 150ms ease,
    background-color 150ms ease,
    box-shadow 150ms ease;
}
.gds-selection-card:hover {
  background: var(--gds-selection-card-hover-bg, oklch(var(--muted) / 0.55));
}
.gds-selection-card:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 2px oklch(var(--background)),
    0 0 0 4px oklch(var(--gds-selection-card-ring, var(--ring)) / 0.85);
}
.gds-selection-card[data-state="checked"] {
  border-color: oklch(
    var(--gds-selection-card-selected-border, var(--foreground))
  );
  background: var(
    --gds-selection-card-selected-bg,
    oklch(var(--muted) / 0.4)
  );
}
.gds-selection-card:disabled,
.gds-selection-card[data-disabled] {
  cursor: not-allowed;
  opacity: 0.5;
}

/* Content blocks */
.gds-selection-card__content {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  flex: 1 1 auto;
  min-width: 0;
}
.gds-selection-card__slot {
  flex: 1 1 auto;
  min-width: 0;
}
.gds-selection-card__title {
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.4;
  color: oklch(var(--foreground));
}
.gds-selection-card__desc {
  font-size: 0.875rem;
  line-height: 1.4;
  color: oklch(var(--muted-foreground));
}

/* ── Indicators (the small glyph; differs by control type) ─────────────── */
.gds-selection-indicator {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  margin-top: 0.0625rem;
}

/* Radio — empty ring → filled circle with a light inner dot when checked */
.gds-selection-indicator--radio {
  height: 1.25rem;
  width: 1.25rem;
  border-radius: 9999px;
  border: 1.5px solid oklch(var(--gds-selection-card-control, var(--super-muted-foreground)));
  background: oklch(var(--background));
  transition:
    border-color 150ms ease,
    background-color 150ms ease;
}
.gds-selection-card[data-state="checked"] .gds-selection-indicator--radio {
  border-color: oklch(var(--gds-selection-card-control-checked, var(--primary)));
  background: oklch(var(--gds-selection-card-control-checked, var(--primary)));
}
.gds-selection-indicator__dot {
  display: block;
  height: 0.5rem;
  width: 0.5rem;
  border-radius: 9999px;
  background: oklch(var(--background));
}

/* Checkbox — rounded square, check glyph when checked */
.gds-selection-indicator--checkbox {
  height: 1.2rem;
  width: 1.2rem;
  border-radius: 0.3rem;
  border: 1.5px solid oklch(var(--gds-selection-card-control, var(--super-muted-foreground)));
  background: oklch(var(--background));
  color: oklch(var(--background));
  transition:
    border-color 150ms ease,
    background-color 150ms ease;
}
.gds-selection-card[data-state="checked"] .gds-selection-indicator--checkbox {
  border-color: oklch(var(--gds-selection-card-control-checked, var(--primary)));
  background: oklch(var(--gds-selection-card-control-checked, var(--primary)));
}
.gds-selection-indicator__check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: oklch(var(--background));
}

/* Switch — track + thumb that slides on checked */
.gds-selection-indicator--switch {
  height: 1.25rem;
  width: 2.25rem;
  padding: 2px;
  border-radius: 9999px;
  background: oklch(var(--input));
  justify-content: flex-start;
  transition: background-color 150ms ease;
}
.gds-selection-card[data-state="checked"] .gds-selection-indicator--switch {
  background: oklch(var(--gds-selection-card-control-checked, var(--primary)));
}
.gds-selection-indicator__thumb {
  display: block;
  height: 1rem;
  width: 1rem;
  border-radius: 9999px;
  background: oklch(var(--background));
  box-shadow: 0 1px 2px oklch(0 0 0 / 0.25);
  transition: transform 150ms ease;
}
.gds-selection-card[data-state="checked"] .gds-selection-indicator__thumb {
  transform: translateX(1rem);
}

/* ─────────────────────────────────────────────────────────────────────────
   Elevation — real classes (JIT-proof, always in the bundle).
   ---------------------------------------------------------------------------
   The `shadow-elevation-N` Tailwind utilities (declared in tailwind-preset.ts)
   only compile when a scanned source file actually uses the class string, so
   typing `shadow-elevation-3` in Studio output or a consumer screen produced
   no CSS. These plain classes always exist in the shipped stylesheet, exactly
   like `.gds-surface-*`, so elevation works everywhere regardless of the JIT
   content scan. Prefer these for runtime/generated UI; the Tailwind utilities
   remain available for component-internal use.
   ───────────────────────────────────────────────────────────────────────── */
.gds-elevation-0 { box-shadow: var(--elevation-0); }
.gds-elevation-1 { box-shadow: var(--elevation-1); }
.gds-elevation-2 { box-shadow: var(--elevation-2); }
.gds-elevation-3 { box-shadow: var(--elevation-3); }
.gds-elevation-4 { box-shadow: var(--elevation-4); }
.gds-elevation-5 { box-shadow: var(--elevation-5); }
.gds-elevation-hot { box-shadow: var(--elevation-hot); }
.gds-elevation-pressed { box-shadow: var(--elevation-pressed); }
/* Single-layer atoms, for composing a custom stack */
.gds-shadow-bevel-hi { box-shadow: var(--shadow-bevel-hi); }
.gds-shadow-bevel-lo { box-shadow: var(--shadow-bevel-lo); }
.gds-shadow-contact { box-shadow: var(--shadow-contact); }
.gds-shadow-lift { box-shadow: var(--shadow-lift); }
.gds-shadow-lift-deep { box-shadow: var(--shadow-lift-deep); }
.gds-shadow-heat-inner { box-shadow: var(--shadow-heat-inner); }
.gds-shadow-heat-outer { box-shadow: var(--shadow-heat-outer); }
