@nebutra/design-tokens 0.1.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -127,7 +127,7 @@ The 70% parity floor is enforced; below it, the script exits non-zero.
127
127
  1. **Display-P3 wide-gamut overrides** — the legacy SSOT defines `--nebutra-brand-blue` / `--nebutra-brand-cyan` aliases inside `@supports (color: color(display-p3 ...))`. Those are not yet modeled in DTCG; today the generated values are the sRGB hex fallback. Next step: add a P3 wide-gamut variant in `core.json` and emit it under a SD `mediaQuery` wrapper.
128
128
  2. **`oklch()` overrides for `--ds-gray-*`** — the legacy file ships sRGB `hsla()` baseline values and overrides them under `@supports (color: oklch(...))`. The generated CSS currently keeps only the sRGB fallback. Next step: add a separate `themes/ds-gray-oklch.json` and a SD format that wraps it in the `@supports` block.
129
129
  3. **Compound shorthand tokens not yet emitted**:
130
- - `--transition` (combines `--transition-duration` + `--transition-easing`)
130
+ - `--transition` (emitted today as a hardcoded `150ms ease-out` literal in `buildExtras()`; the `--transition-duration` / `--transition-easing` component tokens were removed from `semantic.json` — they had zero consumers)
131
131
  - `--focus-ring` (compound box-shadow recipe)
132
132
  These can be modeled as DTCG `composite` tokens; deferred to follow-up.
133
133
  4. **`--brand-primary` / `--brand-accent` in `.dark` mode** — the legacy SSOT keeps these mapped to the sRGB primitives, but in dark mode the visual brand is `var(--blue-9)` which itself maps to `--nebutra-blue-400`. Today the DTCG model resolves to the *core* primitive (always blue-500/cyan-500). Next step: add per-mode brand alias overrides in the dark theme file.
@@ -26,17 +26,17 @@
26
26
  --nebutra-cyan-800: #057963;
27
27
  --nebutra-cyan-900: #035143;
28
28
  --nebutra-cyan-950: #012923;
29
- --nebutra-neutral-50: #f8fafc;
30
- --nebutra-neutral-100: #f1f5f9;
31
- --nebutra-neutral-200: #e2e8f0;
32
- --nebutra-neutral-300: #cbd5e1;
33
- --nebutra-neutral-400: #94a3b8;
34
- --nebutra-neutral-500: #64748b;
35
- --nebutra-neutral-600: #475569;
36
- --nebutra-neutral-700: #334155;
37
- --nebutra-neutral-800: #1e293b;
38
- --nebutra-neutral-900: #0f172a;
39
- --nebutra-neutral-950: #020617;
29
+ --nebutra-neutral-50: #f9fafb;
30
+ --nebutra-neutral-100: #f3f4f6;
31
+ --nebutra-neutral-200: #e4e5e8;
32
+ --nebutra-neutral-300: #d2d4d8;
33
+ --nebutra-neutral-400: #9fa1a5;
34
+ --nebutra-neutral-500: #717377;
35
+ --nebutra-neutral-600: #515357;
36
+ --nebutra-neutral-700: #3f4044;
37
+ --nebutra-neutral-800: #26272a;
38
+ --nebutra-neutral-900: #18191c;
39
+ --nebutra-neutral-950: #0b0c0d;
40
40
  --nebutra-gray-50: #fafafa;
41
41
  --nebutra-gray-100: #f5f5f5;
42
42
  --nebutra-gray-200: #e5e5e5;
@@ -52,41 +52,111 @@
52
52
  --breakpoint-tight: 67.5rem; /** 1080px — dense dashboard header horizontal */
53
53
  --breakpoint-shell: 73.75rem; /** 1180px — three-pane shell threshold */
54
54
  --breakpoint-3xl: 112.5rem; /** 1800px — 4K / Studio Display friendliness */
55
- --radius-none: 0;
56
- --radius-sm: 0.25rem;
57
- --radius-md: 0.375rem;
58
- --radius-lg: 0.5rem;
59
- --radius-xl: 0.75rem;
60
- --radius-2xl: 1rem;
61
- --radius-3xl: 1.5rem;
62
- --radius-full: 9999px;
63
- --radius-button: 0.5rem; /** 2026 button radius — Vercel/Linear/Cursor consensus */
64
- --radius-card: 0.75rem; /** 2026 card radius */
65
- --radius-panel: 1rem; /** 2026 bento/hero panel radius */
55
+ --radius-none: 0; /** Job: flush. Edge-to-edge surfaces — a full-bleed table row, a segment welded to its neighbour, an inset that must not round twice. */
56
+ --radius-xs: 0.125rem; /** Job: swatch. The smallest corner that still reads as intentional at 8-12px: chart legend keys, mark highlights, progress and sparkline bar caps. Added because 18 call sites had hand-written 1px, 2px and 3px corners with no step to reach for. Matches the bottom of the Tailwind scale, so rounded-xs and var(--radius-xs) agree. */
57
+ --radius-sm: 0.25rem; /** Job: inner. The corner of something nested inside a control — a thumbnail inside a chip, a swatch inside a tile, a code span inside a paragraph. One step inside md. */
58
+ --radius-md: 0.375rem; /** Job: control. The default for anything you can click or type into at text height: input, select trigger, small button, menu item, badge with square corners. The most-used step on the ladder. */
59
+ --radius-lg: 0.5rem; /** Job: container. Things that hold controls rather than being one: popover, dropdown surface, tooltip, toolbar, inline code block. Aliased as radius-button. */
60
+ --radius-xl: 0.75rem; /** Job: card. A discrete surface in a list or grid that carries its own padding and elevation. Aliased as radius-card. */
61
+ --radius-2xl: 1rem; /** Job: panel. A region that holds cards — bento cell, dialog, sheet, dashboard section. Aliased as radius-panel. */
62
+ --radius-3xl: 1.5rem; /** Job: frame. The outermost shell of a marketing or docs composition — mockup window, hero frame, screenshot chrome. The top of the ladder. */
63
+ --radius-full: 9999px; /** Job: pill. Fully round by intent, not by size: avatar, dot, toggle track, tag. Kept as one step, not a pair, so there is exactly one way to say round. */
64
+ --radius-button: 0.5rem; /** Intent alias for the container step. Reach for this in product code instead of lg so the shape can be re-tuned in one place. */
65
+ --radius-card: 0.75rem; /** Intent alias for the card step. Reach for this in product code instead of xl. */
66
+ --radius-panel: 1rem; /** Intent alias for the panel step. Reach for this in product code instead of 2xl. */
66
67
  --duration-micro: 100ms; /** Micro-feedback: hover, focus, toggle, button press */
67
68
  --duration-flow: 200ms; /** State flow: modal open, dropdown reveal, tab switch, default page transition */
68
69
  --duration-reveal: 300ms; /** Content unveil: slide-in, expand, accordion, drawer */
69
70
  --duration-cinematic: 500ms; /** Hero-grade entrance: landing reveal, large delight moments */
70
71
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
71
- --ease-out: cubic-bezier(0, 0, 0.2, 1);
72
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
72
73
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
73
74
  --ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
74
75
  --tracking-display: -0.04em; /** Hero H1 */
75
76
  --tracking-heading: -0.03em; /** Section H2/H3 */
76
77
  --tracking-tight: -0.015em; /** Body emphasis */
78
+ --tracking-label: 0.07em; /** Uppercase micro-labels. 0.07em is Vercel's measured eyebrow value (0.071em); open tracking keeps small caps legible. */
77
79
  --leading-display: 1.05;
78
80
  --leading-heading: 1.15;
81
+ --type-2xs-size: 0.6875rem;
82
+ --type-2xs-leading: 1.45;
83
+ --type-2xs-tracking: 0.01em;
84
+ --type-xs-size: 0.75rem;
85
+ --type-xs-leading: 1.4;
86
+ --type-xs-tracking: 0.005em;
87
+ --type-ui-size: 0.8125rem;
88
+ --type-ui-leading: 1.45;
89
+ --type-ui-tracking: 0em;
90
+ --type-sm-size: 0.875rem;
91
+ --type-sm-leading: 1.5;
92
+ --type-sm-tracking: 0em;
93
+ --type-base-size: 1rem;
94
+ --type-base-leading: 1.6;
95
+ --type-base-tracking: -0.005em;
96
+ --type-lg-size: 1.125rem;
97
+ --type-lg-leading: 1.55;
98
+ --type-lg-tracking: -0.01em;
99
+ --type-xl-size: 1.25rem;
100
+ --type-xl-leading: 1.45;
101
+ --type-xl-tracking: -0.012em;
102
+ --type-2xl-size: 1.5rem;
103
+ --type-2xl-leading: 1.33;
104
+ --type-2xl-tracking: -0.015em;
105
+ --type-3xl-size: 1.875rem;
106
+ --type-3xl-leading: 1.25;
107
+ --type-3xl-tracking: -0.02em;
108
+ --type-4xl-size: 2.25rem;
109
+ --type-4xl-leading: 1.15;
110
+ --type-4xl-tracking: -0.022em;
111
+ --type-5xl-size: 3rem;
112
+ --type-5xl-leading: 1.08;
113
+ --type-5xl-tracking: -0.028em;
114
+ --type-6xl-size: 3.75rem;
115
+ --type-6xl-leading: 1.04;
116
+ --type-6xl-tracking: -0.032em;
117
+ --type-7xl-size: 4.5rem;
118
+ --type-7xl-leading: 1;
119
+ --type-7xl-tracking: -0.035em;
120
+ --type-8xl-size: 6rem;
121
+ --type-8xl-leading: 1;
122
+ --type-8xl-tracking: -0.04em;
123
+ --type-9xl-size: 8rem;
124
+ --type-9xl-leading: 1;
125
+ --type-9xl-tracking: -0.045em;
126
+ --type-display-size: 1.25rem;
127
+ --type-display-leading: 1.45;
128
+ --type-display-tracking: -0.012em;
129
+ --type-body-size: 0.875rem;
130
+ --type-body-leading: 1.5;
131
+ --type-body-tracking: 0em;
132
+ --type-label-size: 0.75rem;
133
+ --type-label-leading: 1.4;
134
+ --type-label-tracking: 0.005em;
135
+ --type-meta-size: 0.6875rem;
136
+ --type-meta-leading: 1.45;
137
+ --type-meta-tracking: 0.01em;
138
+ --layer-raised: 10; /** Within a component: an element lifted over its siblings. */
139
+ --layer-sticky: 20; /** Sticky headers and table heads. */
140
+ --layer-floating: 30; /** Floating toolbars and canvas chrome anchored to content. */
141
+ --layer-nav: 40; /** App navigation and the shell's top bar. */
142
+ --layer-drawer: 60; /** Mobile navigation drawers and their toggles. */
143
+ --layer-banner: 90; /** Page-level banners: cookie consent, announcements. */
144
+ --layer-skip-link: 100; /** The skip link — above page chrome whenever it is focused. */
145
+ --layer-panel: 200; /** App-owned floating panels that are not overlays (locale switcher, search flyouts). */
146
+ --layer-backdrop: 1040; /** Overlay tier. Mirrors overlayZIndex in @nebutra/ui; a test keeps them equal. */
147
+ --layer-modal: 1050; /** Dialogs and sheets. */
148
+ --layer-popover: 1060; /** Popovers, menus, selects. */
149
+ --layer-tooltip: 1070; /** Tooltips. */
150
+ --layer-devtools: 9999; /** Developer and debug surfaces only — never product UI. */
79
151
  --ring-hairline: inset 0 0 0 1px rgb(255 255 255 / 0.06); /** Hairline override — more visible on dark background */
80
152
  --overlay-faint: rgb(255 255 255 / 0.06); /** Dark-mode faint overlay (6% white) — inverted from light */
81
- --font-sans: var(--font-geist-sans), "Geist", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
82
- --font-cn: "Noto Sans SC", "PingFang SC", "Microsoft YaHei", "vivo Sans", sans-serif;
83
- --font-display: var(--font-geist-sans), "Geist", "Noto Sans SC", sans-serif;
84
- --font-heading: var(--font-geist-sans), "Geist", "Noto Sans SC", sans-serif;
153
+ --font-sans: var(--font-geist-sans), "Geist", var(--font-misans, "MiSans"), "PingFang SC", "Microsoft YaHei", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
154
+ --font-cn: var(--font-geist-sans), "Geist", var(--font-misans, "MiSans"), "PingFang SC", "Microsoft YaHei", sans-serif; /** CJK-locale stack — same order as sans, minus the Latin-only system faces */
155
+ --font-display: var(--font-dm-sans), "DM Sans", var(--font-misans, "MiSans"), "PingFang SC", sans-serif;
156
+ --font-heading: var(--font-dm-sans), "DM Sans", var(--font-misans, "MiSans"), "PingFang SC", sans-serif;
85
157
  --font-mono: var(--font-geist-mono), "Geist Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
86
- --transition-duration: 200ms; /** Default transition duration — 200ms flow rail. */
87
- --transition-easing: ease-out; /** ease-out keyword form (CSS-friendly). Equivalent to cubic-bezier(0,0,0.2,1). */
88
- --brand-primary: #5c7cfa; /** Dark-mode brand primary — visible against dark surfaces (uses --blue-9 = nebutra-blue-400). */
89
- --brand-accent: #1af7c8; /** Dark-mode brand accent — visible against dark surfaces (uses --cyan-9 = nebutra-cyan-400). */
158
+ --brand-primary: #5c7cfa; /** Dark-mode brand primary — the identity blue lifted to nebutra-blue-400 so it stays visible on dark surfaces. This is the identity alias, not the functional fill: the dark solid fill is --blue-9 (#396ae2, white label 4.85:1). The two are deliberately separate now that the VI hex is no longer pinned into ramp slot 9. */
159
+ --brand-accent: #1af7c8; /** Dark-mode brand accent — nebutra-cyan-400 (L* 87.1). The functional bright fill is --cyan-9 (#0bf1c3, L* 85.41), the same hex in both modes; dark ink for it is --cyan-contrast at 10.35:1. */
90
160
  --brand-tertiary: #8b5cf6;
91
161
  --brand-gradient-start: hsl(var(--primary)); /** Legacy alias of --primary (shadcn product blue). Prefer bg-primary / Button default. */
92
162
  --brand-gradient-end: hsl(var(--primary)); /** Legacy alias of --primary. Prefer hover:bg-primary/90. */
@@ -109,75 +179,80 @@
109
179
  --motion-duration-reveal: 300ms;
110
180
  --motion-duration-cinematic: 500ms;
111
181
  --focus-ring: 0 0 0 2px hsl(var(--ring) / 0.4); /** Single-layer translucent focus ring (2026 pattern). Legacy box-shadow consumers stay compatible; modern surfaces should prefer the global :focus-visible outline rule in static/base.css. */
112
- --neutral-1: #020617;
113
- --neutral-2: #0f172a;
114
- --neutral-3: #1e293b;
115
- --neutral-4: #334155;
116
- --neutral-5: #475569;
117
- --neutral-6: #171717;
118
- --neutral-7: #262626;
119
- --neutral-8: #404040;
120
- --neutral-9: #94a3b8;
121
- --neutral-10: #cbd5e1;
122
- --neutral-11: #e2e8f0;
123
- --neutral-12: #f8fafc;
124
- --blue-1: #000830;
125
- --blue-2: #000f59;
126
- --blue-3: #001882;
127
- --blue-4: #0021ab;
128
- --blue-5: #002ad4;
129
- --blue-6: #001882;
130
- --blue-7: #0021ab;
131
- --blue-8: #002ad4;
132
- --blue-9: #5c7cfa;
133
- --blue-10: #91a7ff;
134
- --blue-11: #bac8ff;
135
- --blue-12: #dbe4ff;
136
- --cyan-1: #012923;
137
- --cyan-2: #035143;
138
- --cyan-3: #057963;
139
- --cyan-4: #07a183;
140
- --cyan-5: #09c9a3;
141
- --cyan-6: #057963;
142
- --cyan-7: #07a183;
143
- --cyan-8: #09c9a3;
144
- --cyan-9: #1af7c8;
145
- --cyan-10: #4dfcd4;
146
- --cyan-11: #80ffe0;
147
- --cyan-12: #b3ffec;
148
- --background: 222 14% 9%;
149
- --foreground: 210 18% 96%;
150
- --card: 222 12% 13%;
151
- --card-foreground: 210 18% 96%;
152
- --popover: 222 12% 13%;
153
- --popover-foreground: 210 18% 96%;
154
- --primary: 228 90% 72%;
155
- --primary-foreground: 222 14% 9%;
156
- --secondary: 222 10% 18%;
157
- --secondary-foreground: 210 18% 96%;
158
- --muted: 222 10% 17%;
159
- --muted-foreground: 220 8% 65%;
160
- --accent: 228 38% 22%;
161
- --accent-foreground: 228 95% 80%;
182
+ --neutral-1: #0b0c0d;
183
+ --neutral-2: #18191c;
184
+ --neutral-3: #26272a;
185
+ --neutral-4: #3f4044;
186
+ --neutral-5: #515357;
187
+ --neutral-6: #666666; /** subtle border — DERIVED: OKLab lerp step5 → step9 at t=0.25, chroma forced to 0. The achromatic result is deliberate and preserves the documented intent of color.nebutra-gray ('used where blue undertone clashes — primarily dark-mode borders, dividers, elevation hairlines'). What was broken was the LIGHTNESS: gray-900/800/700 are darker than the slate background tier they sit above, so the border read as a hole rather than a hairline. */
188
+ --neutral-7: #797979; /** default border — DERIVED: OKLab lerp step5 → step9 at t=0.50, chroma 0. */
189
+ --neutral-8: #8d8d8d; /** hovered border — DERIVED: OKLab lerp step5 → step9 at t=0.75, chroma 0. */
190
+ --neutral-9: #9fa1a5;
191
+ --neutral-10: #d2d4d8;
192
+ --neutral-11: #e4e5e8;
193
+ --neutral-12: #f9fafb;
194
+ --blue-1: #030815; /** app tint — L* 13.59, C 0.031, hue 264. */
195
+ --blue-2: #060f25; /** subtle background — L* 17.43, C 0.048. */
196
+ --blue-3: #09183b; /** component background default — L* 22.04, C 0.071. */
197
+ --blue-4: #0d2254; /** component background hover — L* 26.99, C 0.095. */
198
+ --blue-5: #132e6f; /** component background active — L* 32.46, C 0.118. Anchor for the derived border tier. */
199
+ --blue-6: #1c3c8a; /** subtle border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (deep scale, span 23.5 L*) at t=0.25. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 38.25, C 0.136. */
200
+ --blue-7: #254ba7; /** default border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (deep scale, span 23.5 L*) at t=0.50. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 44.21, C 0.155. */
201
+ --blue-8: #2f5ac4; /** hovered border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (deep scale, span 23.5 L*) at t=0.75. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 50.01, C 0.173. */
202
+ --blue-9: #396ae2; /** solid fill — L* 55.92, C 0.191, hue 264.3. White label 4.85:1. Same value as the dark shadcn --primary, so light and dark both take their deep fill from one source. Chroma ceiling for dark is 0.190; this is 0.191. */
203
+ --blue-10: #3f70e3; /** solid fill hover — L* 57.44, C 0.183. White label 4.53:1. The hover has to move UP in a dark ramp, which costs contrast, so the step is deliberately small (1.52 L*) to keep white above 4.5:1. */
204
+ --blue-11: #89aaee; /** secondary text — L* 74.01, C 0.106. 8.62:1 on step 1. */
205
+ --blue-12: #d2e0fd; /** primary text — L* 90.52, C 0.043. 15.08:1 on step 1. */
206
+ --blue-contrast: #ffffff; /** Label ink for the solid fill tier. Absolute value, identical in both modes. 4.85:1 on step 9, 4.53:1 on step 10. */
207
+ --cyan-1: #04100c; /** app tint — L* 15.90, C 0.021, hue 172. */
208
+ --cyan-2: #091e18; /** subtle background — L* 21.54, C 0.030. */
209
+ --cyan-3: #0f2e25; /** component background default — L* 27.46, C 0.041. */
210
+ --cyan-4: #1c4035; /** component background hover — L* 34.10, C 0.046. */
211
+ --cyan-5: #2b5347; /** component background active — L* 40.88, C 0.050. Anchor for the derived border tier. */
212
+ --cyan-6: #307864; /** subtle border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (span 44.5 L*) at t=0.25. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 52.12, C 0.079. */
213
+ --cyan-7: #309e82; /** default border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (span 44.5 L*) at t=0.50. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 63.04, C 0.107. */
214
+ --cyan-8: #28c7a2; /** hovered border — DERIVED at build time (scripts/derive-border-tier.mjs): OKLab lerp of step 5 toward step9 (span 44.5 L*) at t=0.75. The value here is a placeholder anchor only; the preprocessor overwrites it. Lands at L* 74.30, C 0.136. */
215
+ --cyan-9: #0bf1c3; /** solid fill — same bright value as light mode, the way Radix ships one hex for mint-9 in both modes: L* 85.41, C 0.164. Ink 10.35:1 against --cyan-contrast. */
216
+ --cyan-10: #5af8ce; /** solid fill hover — L* 88.53, C 0.145, 3.12 L* above step 9 (Radix mint dark moves up 4.60 L*). Ink 11.34:1. */
217
+ --cyan-11: #90fbdb; /** secondary text — L* 91.46, C 0.110. 15.68:1 on step 1. */
218
+ --cyan-12: #bdfde8; /** primary text — L* 94.54, C 0.070. 16.95:1 on step 1. */
219
+ --cyan-contrast: #0d2b25; /** Label ink for the bright fill tier. Absolute near-black, identical in both modes. 10.35:1 on step 9, 11.34:1 on step 10. */
220
+ --background: 220 11.1% 5.3%; /** House canvas. Light is gray-50 under white cards (two-surface layering, as Vercel #fafafa/#fff); dark is OKLCH L 0.16, inside the near-black band every reference measures (Linear L 0.14, Raycast #040506). */
221
+ --foreground: 228 23.8% 95.9%;
222
+ --card: 220 7.3% 8%;
223
+ --card-foreground: 228 23.8% 95.9%;
224
+ --popover: 225 7.7% 10.2%;
225
+ --popover-foreground: 228 23.8% 95.9%;
226
+ --primary: 228 23.8% 95.9%; /** House action fill = ink, not blue (owner decision 2026-09-24). Every measured reference ships a solid near-black primary (Vercel #171717, Figma #000) and keeps its one saturated hue for a single signal. Blue now lives on --ring (focus, selection) only. */
227
+ --primary-foreground: 220 11.1% 5.3%; /** White, matching light mode and matching --blue-contrast. It was near-black (222 14% 9%) when --primary was a LIGHT fill at L* 68.3, where dark ink read 6.20:1. Once --primary became a deep fill at L* 55.9 the pairing inverted and measured 3.74:1, below AA — the fill moved and the ink did not. The deep-fill tier takes white in both modes; --blue-contrast says the same thing, and they must agree because --primary and --blue-9 are now the same value. */
228
+ --secondary: 225 6.2% 12.5%;
229
+ --secondary-foreground: 228 23.8% 95.9%;
230
+ --muted: 225 6.2% 12.5%;
231
+ --muted-foreground: 228 2.6% 62.5%;
232
+ --accent: 225 6.2% 12.5%;
233
+ --accent-foreground: 228 23.8% 95.9%;
162
234
  --destructive: 0 63% 38%;
163
235
  --destructive-foreground: 210 18% 96%;
236
+ --destructive-strong: 0 80% 66%; /** Red as FOREGROUND on a dark surface. --destructive (0 63% 38%) is 2.15:1 on --card here — a fill only. Lightened to 5.18:1 on --card / 5.61:1 on --neutral-1, which sits alongside the light-mode 5.42:1 so red reads with the same weight in both themes. Use --destructive for fills with --destructive-foreground on top; use this for red text and icons. */
164
237
  --success: 142 60% 42%;
165
238
  --success-foreground: 222 14% 9%;
239
+ --success-strong: 142 60% 42%; /** Green as foreground on a dark surface. Needs no adjustment — 142 60% 42% is already 4.83:1 as ink on its own 10% tint — so it deliberately equals --success, matching how --warning-strong behaves in this theme. */
166
240
  --warning: 38 80% 52%;
167
241
  --warning-foreground: 222 14% 9%;
242
+ --warning-strong: 38 80% 52%; /** Amber as foreground on a dark surface. Unlike light mode this needs no adjustment — 38 80% 52% is already 7.98:1 against --neutral-1 — so it deliberately equals --warning. The token exists in both themes so consumers can reference one name. */
168
243
  --info: 228 95% 72%;
169
244
  --info-foreground: 222 14% 9%;
170
- --border: 220 8% 22%;
171
- --input: 220 8% 22%;
172
- --ring: 0 0% 88%; /** Focus ring (dark mode) — neutral light gray. Mirrors light-mode neutral foreground choice. */
173
- --sidebar: 222 16% 8%;
174
- --sidebar-foreground: 210 18% 94%;
175
- --sidebar-primary: 228 95% 72%;
176
- --sidebar-primary-foreground: 222 14% 9%;
177
- --sidebar-accent: 228 38% 22%;
178
- --sidebar-accent-foreground: 228 95% 80%;
179
- --sidebar-border: 220 8% 18%;
180
- --sidebar-ring: 228 95% 72%;
245
+ --border: 220 3.6% 16.3%;
246
+ --input: 220 3.6% 16.3%;
247
+ --ring: 222.6 87.3% 69%; /** The House blue. Focus rings, selection and links — the one saturated hue on a product screen. #2e65ee light / #6b93f5 dark. */
248
+ --sidebar: 220 11.1% 5.3%;
249
+ --sidebar-foreground: 228 23.8% 95.9%;
250
+ --sidebar-primary: 228 23.8% 95.9%; /** Current-page accent in the sidebar. = blue-11 (#89aaee, L* 74.0, C 0.106), the ramp's measured secondary-text tier on dark (8.62:1 on step 1). Was 228 95% 72% — 95% chroma sat outside every measured band and matched no ramp step. */
251
+ --sidebar-primary-foreground: 220 11.1% 5.3%;
252
+ --sidebar-accent: 225 6.2% 12.5%;
253
+ --sidebar-accent-foreground: 228 23.8% 95.9%;
254
+ --sidebar-border: 225 6.2% 12.5%;
255
+ --sidebar-ring: 222.6 87.3% 69%;
181
256
  --chart-1: 228 95% 67%;
182
257
  --chart-2: 168 96% 54%;
183
258
  --chart-3: 228 100% 82%;
@@ -223,6 +298,18 @@
223
298
  --elevation-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.8);
224
299
  --elevation-brand: 0 0 0 1px rgb(92 124 250 / 0.25), 0 4px 20px -2px rgb(92 124 250 / 0.3);
225
300
  --elevation-brand-lg: 0 0 0 1px rgb(92 124 250 / 0.3), 0 8px 40px -4px rgb(92 124 250 / 0.4);
301
+ --elevation-ambient-sm: 0 1px 2px -1px rgb(0 0 0 / 0.4), 0 6px 16px -6px rgb(0 0 0 / 0.5); /** Ambient elevation, small. Contact layer plus a wide soft pool — the marketing-surface counterpart to the tight xs..2xl product ramp. */
302
+ --elevation-ambient-md: 0 2px 4px -2px rgb(0 0 0 / 0.4), 0 12px 32px -10px rgb(0 0 0 / 0.55); /** Ambient elevation, medium. Default resting depth for a floating marketing card. */
303
+ --elevation-ambient-lg: 0 4px 8px -4px rgb(0 0 0 / 0.4), 0 24px 56px -18px rgb(0 0 0 / 0.6); /** Ambient elevation, large. One step up from md; use for hover of a md surface, not as a resting default. */
304
+ --elevation-glass-sm: inset 0 1px 0 rgb(255 255 255 / 0.08), 0 1px 2px -1px rgb(0 0 0 / 0.4), 0 6px 16px -6px rgb(0 0 0 / 0.5); /** Ambient-sm plus a lit top edge, for translucent panels over a blurred backdrop. */
305
+ --elevation-glass-md: inset 0 1px 0 rgb(255 255 255 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.4), 0 12px 32px -10px rgb(0 0 0 / 0.55); /** Ambient-md plus a lit top edge. */
306
+ --elevation-glass-lg: inset 0 1px 0 rgb(255 255 255 / 0.08), 0 4px 8px -4px rgb(0 0 0 / 0.4), 0 24px 56px -18px rgb(0 0 0 / 0.6); /** Ambient-lg plus a lit top edge. */
307
+ --elevation-sheen: inset 0 1px 0 rgb(0 0 0 / 0.12); /** Top-edge sheen for solid inverted fills (bg-foreground). Lifts with light on a dark fill, with shade on a light one. */
308
+ --elevation-ambient-glow: 0 0 80px rgb(255 255 255 / 0.06); /** Centred offsetless halo — the only non-directional step on the ramp. Inverted from the light value: a black halo has nothing to darken against a dark surround, so the halo becomes light. Keep the alpha low; this is separation, not a bloom. */
309
+ --elevation-glow-accent: 0 8px 32px rgb(0 0 0 / 0.4), 0 0 60px color-mix(in srgb, var(--brand-accent) 8%, transparent); /** Identical to the light value by design. The pool is theme-invariant black because glow-accent surfaces are fixed dark glass in both themes; only the accent halo shifts, via --brand-accent. */
310
+ --elevation-glow-accent-lg: 0 12px 48px rgb(0 0 0 / 0.5), 0 0 80px color-mix(in srgb, var(--brand-accent) 12%, transparent); /** Hover escalation of glow-accent. Identical to the light value by design. */
311
+ --elevation-glow-accent-sm: 0 0 20px color-mix(in srgb, var(--brand-accent) 10%, transparent); /** Accent halo alone, no pool. Identical to the light value by design. */
312
+ --elevation-glow-primary: 0 20px 25px -5px hsl(var(--primary) / 0.05), 0 8px 10px -6px hsl(var(--primary) / 0.05); /** xl geometry tinted with the action fill at 5 percent. Same reference as light; --primary carries the dark channels. */
226
313
  }
227
314
 
228
315
  .dark {