@astryxdesign/theme-neutral 0.0.0-bootstrap.0 → 0.0.15

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/dist/source.js ADDED
@@ -0,0 +1,602 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/source.ts
21
+ var source_exports = {};
22
+ __export(source_exports, {
23
+ neutralIconRegistry: () => neutralIconRegistry,
24
+ neutralTheme: () => neutralTheme
25
+ });
26
+ module.exports = __toCommonJS(source_exports);
27
+
28
+ // src/neutralTheme.ts
29
+ var import_theme = require("@astryxdesign/core/theme");
30
+
31
+ // src/icons.tsx
32
+ var import_lucide_react = require("lucide-react");
33
+ var import_jsx_runtime = require("react/jsx-runtime");
34
+ var iconProps = {
35
+ size: "1em",
36
+ "aria-hidden": true
37
+ };
38
+ var neutralIconRegistry = {
39
+ close: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { ...iconProps }),
40
+ chevronDown: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ChevronDown, { ...iconProps }),
41
+ chevronLeft: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ChevronLeft, { ...iconProps }),
42
+ chevronRight: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ChevronRight, { ...iconProps }),
43
+ check: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Check, { ...iconProps }),
44
+ success: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.CheckCircle, { ...iconProps }),
45
+ error: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.XCircle, { ...iconProps }),
46
+ warning: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.AlertTriangle, { ...iconProps }),
47
+ info: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Info, { ...iconProps }),
48
+ calendar: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Calendar, { ...iconProps }),
49
+ clock: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Clock, { ...iconProps }),
50
+ externalLink: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ExternalLink, { ...iconProps }),
51
+ menu: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Menu, { ...iconProps }),
52
+ moreHorizontal: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.MoreHorizontal, { ...iconProps }),
53
+ search: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Search, { ...iconProps }),
54
+ arrowUp: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowUp, { ...iconProps }),
55
+ arrowDown: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowDown, { ...iconProps }),
56
+ arrowsUpDown: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowUpDown, { ...iconProps }),
57
+ funnel: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Filter, { ...iconProps }),
58
+ eyeSlash: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.EyeOff, { ...iconProps }),
59
+ viewColumns: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Columns, { ...iconProps }),
60
+ copy: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Copy, { ...iconProps }),
61
+ checkDouble: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.CheckCheck, { ...iconProps }),
62
+ wrench: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Wrench, { ...iconProps }),
63
+ stop: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Square, { ...iconProps }),
64
+ microphone: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Mic, { ...iconProps })
65
+ };
66
+
67
+ // src/neutralTheme.ts
68
+ var neutralSyntax = (0, import_theme.defineSyntaxTheme)({
69
+ name: "xds-neutral",
70
+ tokens: {
71
+ keyword: ["#700084", "#efa8ff"],
72
+ // purple T30/T80
73
+ string: ["#005600", "#a6d2a2"],
74
+ // green (sat T30 / pastel T80)
75
+ comment: ["#737373", "#a3a3a3"],
76
+ // neutral
77
+ number: ["#6e3500", "#ffb37f"],
78
+ // orange
79
+ function: ["#00458c", "#a0caff"],
80
+ // blue T30/T80 H=255
81
+ type: ["#700084", "#efa8ff"],
82
+ // purple
83
+ variable: ["#171717", "#e5e5e5"],
84
+ // near-black / near-white
85
+ operator: ["#737373", "#a3a3a3"],
86
+ // neutral
87
+ constant: ["#6e3500", "#ffb37f"],
88
+ // orange
89
+ tag: ["#89001a", "#ffaeaa"],
90
+ // red
91
+ attribute: ["#584400", "#eec12f"],
92
+ // yellow
93
+ property: ["#005348", "#83dac9"],
94
+ // teal
95
+ punctuation: ["#a3a3a3", "#525252"],
96
+ // neutral
97
+ background: ["#fafafa", "#0a0a0a"]
98
+ }
99
+ });
100
+ var neutralTheme = (0, import_theme.defineTheme)({
101
+ name: "neutral",
102
+ // Typography: Figtree across body, heading, and display sizes (display
103
+ // size tokens inherit from heading.family). Monospace stays as the
104
+ // platform default for code.
105
+ // Scale: base=14, ratio=1.2. Bold weights on h3/h4 for subsection hierarchy.
106
+ typography: {
107
+ scale: { base: 14, ratio: 1.2 },
108
+ body: {
109
+ family: "Figtree",
110
+ fallbacks: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
111
+ },
112
+ heading: {
113
+ family: "Figtree",
114
+ fallbacks: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
115
+ weights: { 3: "bold", 4: "bold" }
116
+ },
117
+ code: {
118
+ family: "ui-monospace",
119
+ fallbacks: '"SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
120
+ }
121
+ },
122
+ // Motion: snappier than default to match shadcn/Tailwind conventions.
123
+ // Produces: fast-min=95ms, fast=125ms, fast-max=165ms,
124
+ // medium-min=225ms, medium=300ms, medium-max=400ms.
125
+ motion: { fast: 125, medium: 300, slow: 700, ratio: 0.75 },
126
+ syntax: neutralSyntax,
127
+ tokens: {
128
+ // =========================================================================
129
+ // Core — pure grayscale spine (Tailwind neutral)
130
+ // 50:#fafafa 100:#f5f5f5 200:#e5e5e5 300:#d4d4d4 400:#a3a3a3
131
+ // 500:#737373 600:#525252 700:#404040 800:#262626 900:#171717 950:#0a0a0a
132
+ // =========================================================================
133
+ // =========================================================================
134
+ // Backgrounds — Figma-style flat with a single lifted surface.
135
+ //
136
+ // Dark mode collapses card / popover / muted to body T10. Cards and
137
+ // popovers lift purely via shadow + inset highlight (see --shadow-*
138
+ // below) — they don't need a distinct tone.
139
+ //
140
+ // Surface is the exception: it's tonally LIGHTER than body (T15) so
141
+ // interactive components that sit on top of body have a clear,
142
+ // differentiated foreground. Real consumers of --color-background-surface
143
+ // are: switches, radios, checkboxes, multi-selectors, dialogs, app
144
+ // shells, sections — all things that need to lift above the canvas.
145
+ //
146
+ // surface T15 #262626 — interactive surfaces lifted above body
147
+ // body T10 #1b1b1b — main canvas
148
+ // card T10 #1b1b1b — same as body, lifts via --shadow-low
149
+ // popover T10 #1b1b1b — same as body, lifts via --shadow-med
150
+ // muted T10 #1b1b1b — same as body
151
+ //
152
+ // Light mode keeps the standard ladder (white surfaces float on tinted
153
+ // body; shadows do most of the lifting):
154
+ // surface T100 #ffffff
155
+ // body T95 #f1f1f1
156
+ // card T100 #ffffff
157
+ // popover T100 #ffffff
158
+ // muted T95 #f1f1f1
159
+ //
160
+ // All values use the OKLCH Neutral tonal palette (chroma=0).
161
+ // =========================================================================
162
+ "--color-background-surface": ["#ffffff", "#262626"],
163
+ "--color-background-body": ["#f1f1f1", "#1b1b1b"],
164
+ "--color-background-card": ["#ffffff", "#1b1b1b"],
165
+ "--color-background-popover": ["#ffffff", "#1b1b1b"],
166
+ "--color-background-muted": ["#f1f1f1", "#1b1b1b"],
167
+ // Accent + neutral surface tints (sit alongside backgrounds)
168
+ "--color-accent": ["#262626", "#ebebeb"],
169
+ "--color-accent-muted": ["#f1f1f1", "#262626"],
170
+ "--color-neutral": ["#0000000F", "#FFFFFF1A"],
171
+ // Overlays (modal scrims, hover/pressed tints)
172
+ "--color-overlay": ["#00000080", "#000000CC"],
173
+ "--color-overlay-hover": ["#0000000D", "#FFFFFF0D"],
174
+ "--color-overlay-pressed": ["#0000001A", "#FFFFFF1A"],
175
+ // Text
176
+ "--color-text-primary": ["#171717", "#fafafa"],
177
+ "--color-text-secondary": ["#737373", "#a3a3a3"],
178
+ "--color-text-disabled": ["#a3a3a3", "#525252"],
179
+ "--color-text-accent": ["#262626", "#ebebeb"],
180
+ "--color-on-dark": "#ffffff",
181
+ "--color-on-light": "#171717",
182
+ // Contrast: neutral accent is near-black (L) / near-white (D)
183
+ "--color-on-accent": ["#ffffff", "#171717"],
184
+ "--color-on-success": ["#ffffff", "#171717"],
185
+ "--color-on-error": ["#ffffff", "#171717"],
186
+ "--color-on-warning": "#171717",
187
+ // Icon
188
+ "--color-icon-accent": ["#262626", "#ebebeb"],
189
+ "--color-icon-primary": ["#171717", "#fafafa"],
190
+ "--color-icon-secondary": ["#737373", "#a3a3a3"],
191
+ "--color-icon-disabled": ["#a3a3a3", "#525252"],
192
+ // Status / Sentiment — dark mode follows the issue #2150 rubric:
193
+ //
194
+ // Light mode: pastel T90 banner bg + dark T30/T40 text/icon. Locked
195
+ // light values for cards/banners/inputs/destructive btn.
196
+ // Dark mode : tinted-dark T20 bg + light pastel T80 text. INVERTED
197
+ // from light. Avoids the §5 "pastel-in-both-modes"
198
+ // anti-pattern (locked pastels glow against a dark body).
199
+ //
200
+ // --color-X = "saturated text/icon stop":
201
+ // light = T40 dark colored (sits on light pastel)
202
+ // dark = T80 light pastel (sits on dark tinted bg)
203
+ // Used by destructive button text, input border/icon
204
+ // (in light), banner-status-* text overrides.
205
+ // --color-X-muted = "muted bg stop":
206
+ // light = T90 light pastel
207
+ // dark = hue-tinted alpha overlay (T70 stop @ 24%)
208
+ // Used by banner bg, status-input message bg,
209
+ // destructive button bg. Dark mode uses an alpha
210
+ // overlay rather than a solid T20 tinted bg so
211
+ // the surface composes onto whatever sits behind
212
+ // it (body, card, popover) rather than reading
213
+ // as a hard colored panel.
214
+ //
215
+ // 24% alpha = '3D' suffix. Hue values match --color-icon-{X} dark
216
+ // slots (palette T70). Composited onto body #1b1b1b, the effective
217
+ // bg luminance hits ~1.65-1.70:1 vs body — visible colored surface
218
+ // without the heaviness of a solid T20 panel.
219
+ "--color-success": ["#007004", "#9fe59b"],
220
+ "--color-error": ["#a50c25", "#ffc6c1"],
221
+ "--color-warning": ["#745b00", "#fdcf4f"],
222
+ "--color-success-muted": ["#c5e5c0", "#84c9803D"],
223
+ "--color-error-muted": ["#facecb", "#ff9e973D"],
224
+ "--color-warning-muted": ["#f8da9d", "#deb4333D"],
225
+ // Border
226
+ "--color-border": ["#ebebeb", "#FFFFFF1A"],
227
+ "--color-border-emphasized": ["#d4d4d4", "#525252"],
228
+ // Effects
229
+ "--color-skeleton": ["#ebebeb", "#525252"],
230
+ "--color-shadow": ["#0000001A", "#0000004D"],
231
+ "--color-tint-hover": ["black", "white"],
232
+ // =========================================================================
233
+ // Categorical — light mode uses pastel surfaces + dark colored text;
234
+ // dark mode INVERTS to a hue-tinted alpha overlay surface +
235
+ // light pastel text (per #2150 rubric §3 — pick the tone
236
+ // that satisfies required contrast against every surface
237
+ // the token touches).
238
+ //
239
+ // Per-token tone choice (CIELab L*):
240
+ // bg light=T87-T90 pastel dark=T70 hue @ 24% alpha overlay
241
+ // (composites onto body to ~1.65:1
242
+ // vs body — colored surface that
243
+ // feels lighter than a solid T20
244
+ // panel; same hue as --color-icon-X
245
+ // dark slot, just at lower opacity)
246
+ // border light=T80 pastel dark=T60 mid-bright (>=5.8:1 vs body)
247
+ // icon light=T30 dark colored dark=T70 light pastel
248
+ // text light=T30 dark colored dark=T80 light pastel (>=7:1 on bg)
249
+ //
250
+ // Light pastels still use the per-hue chroma table (red/blue C=0.05,
251
+ // orange/green/purple/pink C=0.06, teal/cyan C=0.07, yellow H=85 C=0.10)
252
+ // for equal PERCEIVED saturation. Dark stops (T60/T70/T80) come from
253
+ // the dark-mode tonal palette (chroma×0.85, +5 tone lift tapering 80-95).
254
+ // =========================================================================
255
+ // Each row's dark slots are HCT-derived from the source hex listed in
256
+ // apps/sandbox/src/app/(fullscreen)/pages/neutral-palette/page.tsx via
257
+ // the canonical dark-ramp transform (chroma×0.85, +5 tone-lift taper)
258
+ // — same algorithm the Tonal Palettes preview renders. Border=T60,
259
+ // icon=T70, text=T80. Background uses the T70 hue at 24% alpha so the
260
+ // overlay surface composites onto body to ~1.65:1 luminance.
261
+ // Red H=22 — source #eb183a
262
+ "--color-background-red": ["#facecb", "#ff9e973D"],
263
+ "--color-border-red": ["#e6bab8", "#ff6f6c"],
264
+ "--color-icon-red": ["#89001a", "#ff9e97"],
265
+ "--color-text-red": ["#89001a", "#ffc6c1"],
266
+ // Orange H=55 — source #d57113
267
+ "--color-background-orange": ["#fad0b5", "#ffa2583D"],
268
+ "--color-border-orange": ["#e6bda2", "#e2883e"],
269
+ "--color-icon-orange": ["#6e3500", "#ffa258"],
270
+ "--color-text-orange": ["#6e3500", "#ffc9a2"],
271
+ // Yellow H=90 — source #f8c723
272
+ // Light-mode butter-yellow pastel at H=85 C=0.085 L=0.90 — yellow
273
+ // sits at the green-cyan luminance peak so it feels louder than the
274
+ // other status hues at the same canonical L. Picker decision: pull
275
+ // L down one step (0.91→0.90) and C down to its identity floor
276
+ // (0.10→0.085, just above the bronze threshold) so it sits closer
277
+ // to red/blue's perceived brightness without losing yellow identity.
278
+ // Dark-mode comes from the canonical H=90 ramp for tonal-palette
279
+ // consistency.
280
+ "--color-background-yellow": ["#f8da9d", "#deb4333D"],
281
+ "--color-border-yellow": ["#e4c279", "#c0990e"],
282
+ "--color-icon-yellow": ["#584400", "#deb433"],
283
+ "--color-text-yellow": ["#584400", "#fdcf4f"],
284
+ // Green H=144 — source #358a3a
285
+ "--color-background-green": ["#c5e5c0", "#84c9803D"],
286
+ "--color-border-green": ["#b2d1ac", "#69ad67"],
287
+ "--color-icon-green": ["#0c5700", "#84c980"],
288
+ "--color-text-green": ["#0c5700", "#9fe59b"],
289
+ // Teal H=180 — source #0c7365
290
+ // Light pastel uses L=0.87 C=0.065 (a step darker + less chroma than
291
+ // the L=0.888 C=0.07 used by other hues) to compensate for the
292
+ // green-cyan luminance overshoot — at the same OKLCH L, teal/cyan read
293
+ // ~5% brighter than red/blue because the eye's luminance response
294
+ // peaks in this band. Dropping L+C brings perceived brightness in
295
+ // line with the rest of the palette without losing hue identity.
296
+ "--color-background-teal": ["#a5e3d6", "#7ec6b83D"],
297
+ "--color-border-teal": ["#94d6c8", "#63ab9d"],
298
+ "--color-icon-teal": ["#005348", "#7ec6b8"],
299
+ "--color-text-teal": ["#005348", "#99e2d3"],
300
+ // Cyan H=215 — source #0c6f82
301
+ // Same L=0.87 C=0.065 pastel as teal (luminance overshoot compensation).
302
+ "--color-background-cyan": ["#a3e0ef", "#83c2d43D"],
303
+ "--color-border-cyan": ["#91d3e3", "#67a7b8"],
304
+ "--color-icon-cyan": ["#00505f", "#83c2d4"],
305
+ "--color-text-cyan": ["#00505f", "#9edef0"],
306
+ // Blue H=255 — source #0074e2
307
+ // T50 #0074e2 reserved for filled Info badge / progressbar / inset hover.
308
+ "--color-background-blue": ["#c4ddfb", "#9eb7ff3D"],
309
+ "--color-border-blue": ["#b1c9e7", "#6d9cfe"],
310
+ "--color-icon-blue": ["#00458c", "#9eb7ff"],
311
+ "--color-text-blue": ["#00458c", "#c7d3ff"],
312
+ // Purple H=320 — source #980fb2
313
+ "--color-background-purple": ["#eccef3", "#f297ff3D"],
314
+ "--color-border-purple": ["#d8bbdf", "#dd74f0"],
315
+ "--color-icon-purple": ["#700084", "#f297ff"],
316
+ "--color-text-purple": ["#700084", "#fac1ff"],
317
+ // Pink H=355 — source #b10e69
318
+ "--color-background-pink": ["#fccadc", "#ff99c33D"],
319
+ "--color-border-pink": ["#e7b7c8", "#f273aa"],
320
+ "--color-icon-pink": ["#83004b", "#ff99c3"],
321
+ "--color-text-pink": ["#83004b", "#ffc3da"],
322
+ // Gray (categorical neutral, chroma 0)
323
+ // Light: #e5e5e5 (Neutral 200) so it's visibly distinct from the
324
+ // lighter body / muted surface (both #f5f5f5).
325
+ // Dark : var(--color-neutral) — semi-transparent white wash
326
+ // (#FFFFFF1A, 10%). Matches the same treatment the gray
327
+ // badge uses; clearly distinct from the body T10 #1b1b1b
328
+ // while staying chroma-0 neutral. Solid T15 #1c1c1c was
329
+ // indistinguishable from --color-background-muted.
330
+ "--color-background-gray": ["#e5e5e5", "var(--color-neutral)"],
331
+ "--color-border-gray": ["#d4d4d4", "#262626"],
332
+ "--color-icon-gray": ["#525252", "#a3a3a3"],
333
+ "--color-text-gray": ["#262626", "#e5e5e5"],
334
+ // =========================================================================
335
+ // Radius — slightly larger than default (kept as-is)
336
+ // =========================================================================
337
+ "--radius-none": "0.25rem",
338
+ "--radius-inner": "0.375rem",
339
+ "--radius-element": "0.625rem",
340
+ "--radius-container": "0.75rem",
341
+ "--radius-page": "1.75rem",
342
+ "--radius-full": "9999px",
343
+ // =========================================================================
344
+ // Shadows
345
+ //
346
+ // Light mode: matches origin/main exactly (5%/10% low+med, 10%/15% high).
347
+ // Subtle drops; light surfaces don't need rim highlights.
348
+ //
349
+ // Dark mode: deepened drops + an all-around 1px white inset that wraps
350
+ // every edge ("Figma-style bezel"). The inset mimics ambient light
351
+ // catching the surface's rim on every side, giving cards/popovers/modals
352
+ // a substantial "lit from above" feel that drop shadows alone can't
353
+ // achieve against a dark canvas.
354
+ // low : drops 25%/40% + 8% white all-around inset
355
+ // med : drops 35%/50% + 12% white all-around inset
356
+ // high : drops 50%/70% + 15% white all-around inset
357
+ //
358
+ // The inset layer uses light-dark(transparent, ...) so light mode is
359
+ // unaffected — main's exact light values are preserved.
360
+ // =========================================================================
361
+ "--shadow-low": "0 2px 4px light-dark(oklch(0 0 0 / 5%), oklch(0 0 0 / 25%)), 0 4px 8px light-dark(oklch(0 0 0 / 10%), oklch(0 0 0 / 40%)), inset 0 0 0 1px light-dark(transparent, oklch(1 0 0 / 8%))",
362
+ "--shadow-med": "0 2px 4px light-dark(oklch(0 0 0 / 5%), oklch(0 0 0 / 35%)), 0 4px 12px light-dark(oklch(0 0 0 / 10%), oklch(0 0 0 / 50%)), inset 0 0 0 1px light-dark(transparent, oklch(1 0 0 / 12%))",
363
+ "--shadow-high": "0 4px 6px light-dark(oklch(0 0 0 / 10%), oklch(0 0 0 / 50%)), 0 12px 24px light-dark(oklch(0 0 0 / 15%), oklch(0 0 0 / 70%)), inset 0 0 0 1px light-dark(transparent, oklch(1 0 0 / 15%))",
364
+ "--shadow-inset-hover": "inset 0px 0px 0px 2px #0074e24D",
365
+ "--shadow-inset-selected": "inset 0px 0px 0px 2px #0074e280",
366
+ "--shadow-inset-success": "inset 0px 0px 0px 2px #1981004D",
367
+ "--shadow-inset-warning": "inset 0px 0px 0px 2px #ffce2f4D",
368
+ "--shadow-inset-error": "inset 0px 0px 0px 2px #e33f4a4D"
369
+ },
370
+ components: {
371
+ // =========================================================================
372
+ // Button — primary gets white text, secondary gets a border, destructive
373
+ // uses the OKLCH red filled treatment.
374
+ // =========================================================================
375
+ button: {
376
+ "variant:destructive": {
377
+ backgroundColor: "var(--color-error-muted)",
378
+ // locked pastel red bg
379
+ color: "var(--color-error)"
380
+ // locked T30 red — matches banner/input error text
381
+ }
382
+ },
383
+ // =========================================================================
384
+ // Badge —
385
+ // Semantic (info/success/warning/error): filled saturated T50 + contrasting
386
+ // text (white, or dark on yellow). The filled-button rule from #2150
387
+ // §3 — text contrast locks the bg tone, so this stays at T50 in
388
+ // BOTH modes, unlike pastel surfaces which invert by mode.
389
+ // Categorical (blue/green/red/orange/etc.): pastel-tinted hue surface +
390
+ // colored text — light mode = soft T87-T90 + dark T30 text; dark mode
391
+ // = T20 tinted + T80 light pastel text (sources: --color-background-X
392
+ // and --color-text-X tokens).
393
+ // Neutral: light gray bg + dark text (or inverted in dark mode).
394
+ // =========================================================================
395
+ badge: {
396
+ // Semantic — filled saturated bg + contrasting text.
397
+ // Light: vivid T45-T55 from the OKLCH palette + white text
398
+ // (~4.5-5:1 — Material/Linear/Vercel pop).
399
+ // Dark : T60 stop from the dark-mode tonal palette (chroma×0.85,
400
+ // +5 tone-lift taper from issue #2150 §4) + DARK text.
401
+ // T60+white fails AA-large (~2.7:1); T60+dark hits 6.6-7:1
402
+ // and tames the §4 vibration. Same dark-text-on-bright-bg
403
+ // treatment that warning yellow uses in both modes.
404
+ "variant:info": {
405
+ // Light: T50 #0074e2 (palette saturated stop)
406
+ // Dark : T60 stop from dark-mode tonal palette of source #0074e2
407
+ backgroundColor: "light-dark(#0074e2, #6d9cfe)",
408
+ color: "light-dark(#ffffff, #171717)"
409
+ },
410
+ "variant:neutral": {
411
+ // Mirrors the gray categorical badge — same neutral chip treatment
412
+ // (Neutral 200 light / semi-transparent white wash dark) sourced
413
+ // from the gray hue tokens, so a single change at the token layer
414
+ // updates both variants.
415
+ backgroundColor: "var(--color-background-gray)",
416
+ color: "var(--color-text-gray)"
417
+ },
418
+ "variant:success": {
419
+ // Light: T45 #198100 (palette saturated stop)
420
+ // Dark : T60 stop from dark-mode tonal palette of source #198100
421
+ backgroundColor: "light-dark(#198100, #64af4c)",
422
+ color: "light-dark(#ffffff, #171717)"
423
+ },
424
+ "variant:warning": {
425
+ // Yellow stays at the same hex in both modes — chroma reduction
426
+ // is barely visible at T85, and dark text on yellow doesn't
427
+ // suffer from the §4 vibration concern.
428
+ backgroundColor: "#ffce2f",
429
+ color: "#171717"
430
+ },
431
+ "variant:error": {
432
+ // Light: T55 #e33f4a (palette saturated stop)
433
+ // Dark : T60 stop from dark-mode tonal palette of Tailwind red-600
434
+ // source #dc2626 (kept on H=27 alarm-red rather than coral)
435
+ backgroundColor: "light-dark(#e33f4a, #ff705d)",
436
+ color: "light-dark(#ffffff, #171717)"
437
+ },
438
+ // Categorical — bg + text reference the per-hue tokens, so behavior
439
+ // tracks the categorical palette automatically:
440
+ // Light: pastel T87-T90 bg + dark T30 colored text (low-key chip)
441
+ // Dark : tinted T20 bg + light T80 colored text (per #2150 §5,
442
+ // inverted from light to avoid the "pastel-in-both-modes"
443
+ // anti-pattern that makes locked light pastels glow on a
444
+ // dark body)
445
+ "variant:red": {
446
+ backgroundColor: "var(--color-background-red)",
447
+ color: "var(--color-text-red)"
448
+ },
449
+ "variant:orange": {
450
+ backgroundColor: "var(--color-background-orange)",
451
+ color: "var(--color-text-orange)"
452
+ },
453
+ "variant:yellow": {
454
+ backgroundColor: "var(--color-background-yellow)",
455
+ color: "var(--color-text-yellow)"
456
+ },
457
+ "variant:green": {
458
+ backgroundColor: "var(--color-background-green)",
459
+ color: "var(--color-text-green)"
460
+ },
461
+ "variant:teal": {
462
+ backgroundColor: "var(--color-background-teal)",
463
+ color: "var(--color-text-teal)"
464
+ },
465
+ "variant:cyan": {
466
+ backgroundColor: "var(--color-background-cyan)",
467
+ color: "var(--color-text-cyan)"
468
+ },
469
+ "variant:blue": {
470
+ backgroundColor: "var(--color-background-blue)",
471
+ color: "var(--color-text-blue)"
472
+ },
473
+ "variant:purple": {
474
+ backgroundColor: "var(--color-background-purple)",
475
+ color: "var(--color-text-purple)"
476
+ },
477
+ "variant:pink": {
478
+ backgroundColor: "var(--color-background-pink)",
479
+ color: "var(--color-text-pink)"
480
+ },
481
+ "variant:gray": {
482
+ backgroundColor: "var(--color-background-gray)",
483
+ color: "var(--color-text-gray)"
484
+ }
485
+ },
486
+ // =========================================================================
487
+ // Banner — sits on a hue-tinted surface with colored text/icon:
488
+ // Light: pastel T90 bg (pulled from --color-{X}-muted / --color-background-blue)
489
+ // + dark T30 colored text (--color-text-{hue}).
490
+ // Dark : tinted T20 bg (same tokens, dark slot) + light T80 colored text.
491
+ // Per #2150 §5 — large hue-tinted surfaces in dark mode invert
492
+ // to a deep tinted bg + light text rather than locking the
493
+ // light-mode pastel.
494
+ //
495
+ // The inner-header *-muted token is forced transparent so the outer
496
+ // tinted background shows through cleanly.
497
+ //
498
+ // Status overrides reference --color-text-{hue} so text/icon colors
499
+ // stay in sync with the palette anchors automatically.
500
+ banner: {
501
+ "status:info": {
502
+ backgroundColor: "var(--color-background-blue)",
503
+ "--color-accent-muted": "transparent",
504
+ "--color-text-primary": "var(--color-text-blue)",
505
+ "--color-text-secondary": "var(--color-text-blue)",
506
+ "--color-accent": "var(--color-text-blue)"
507
+ },
508
+ // success/warning/error banner bgs come from --color-{X}-muted, which
509
+ // already carries the correct light/dark tinted values. We only need
510
+ // to redirect the text/icon to the palette colored stop.
511
+ "status:success": {
512
+ "--color-text-primary": "var(--color-text-green)",
513
+ "--color-text-secondary": "var(--color-text-green)",
514
+ "--color-success": "var(--color-text-green)"
515
+ },
516
+ "status:warning": {
517
+ "--color-text-primary": "var(--color-text-yellow)",
518
+ "--color-text-secondary": "var(--color-text-yellow)",
519
+ "--color-warning": "var(--color-text-yellow)"
520
+ },
521
+ "status:error": {
522
+ "--color-text-primary": "var(--color-text-red)",
523
+ "--color-text-secondary": "var(--color-text-red)",
524
+ "--color-error": "var(--color-text-red)"
525
+ }
526
+ },
527
+ // =========================================================================
528
+ // TextInput — no per-status overrides needed. The global tokens
529
+ // --color-{success,error,warning} carry the correct values in both
530
+ // modes (light=T40 dark colored, dark=T80 light pastel) for both
531
+ // surfaces the input border/icon touches: the input surface
532
+ // (white/T15-dark) and the status message bubble (light pastel T90 /
533
+ // dark T20). Verified all six combinations clear AA non-text 3:1.
534
+ // =========================================================================
535
+ // =========================================================================
536
+ // Switch — off-state track uses the same lifted-neutral surface as the
537
+ // ProgressBar track (--color-border-emphasized). Aligns the two
538
+ // "channel-on-body" components so their off-states share one visual
539
+ // language: light T85 #d4d4d4 sits one step darker than the body T95
540
+ // bg, dark T35 #525252 sits one step lighter than the body T10. Each
541
+ // is a defined channel, not a wash that blends in.
542
+ // =========================================================================
543
+ switch: {
544
+ base: {
545
+ "--color-background-gray": "var(--color-border-emphasized)"
546
+ }
547
+ },
548
+ progressbar: {
549
+ base: {
550
+ // Track uses --color-background-muted; override it to
551
+ // --color-border-emphasized (Neutral T85 #d4d4d4 in light mode) so
552
+ // the track is clearly darker than the body bg (Neutral T95 #f1f1f1)
553
+ // and reads as a defined channel rather than blending in. Dark
554
+ // mode inherits T35 #525252 — same one-step-lighter behavior.
555
+ "--color-background-muted": "var(--color-border-emphasized)"
556
+ },
557
+ // Vivid stops match the filled semantic badge colors (info/success/
558
+ // warning/error variants in the badge override above). Same hex
559
+ // values; documented per role with palette provenance.
560
+ "variant:accent": {
561
+ // Blue T50 saturated stop (= variant:info badge bg)
562
+ "--color-accent": "#0074e2"
563
+ },
564
+ "variant:success": {
565
+ // Green T45 saturated stop (= variant:success badge bg)
566
+ "--color-success": "#198100"
567
+ },
568
+ "variant:warning": {
569
+ // Yellow T85 saturated stop (= variant:warning badge bg)
570
+ "--color-warning": "#ffce2f"
571
+ },
572
+ "variant:error": {
573
+ // Red T55 saturated stop (= variant:error badge bg)
574
+ "--color-error": "#e33f4a"
575
+ }
576
+ },
577
+ // =========================================================================
578
+ // Card — tighter padding via public card padding token
579
+ // =========================================================================
580
+ card: {
581
+ base: {
582
+ padding: "var(--spacing-3)"
583
+ }
584
+ },
585
+ // =========================================================================
586
+ // Section — tighter padding via public section padding token
587
+ // =========================================================================
588
+ section: {
589
+ base: {
590
+ padding: "var(--spacing-3)"
591
+ }
592
+ }
593
+ // Heading and text component overrides are auto-generated by typography.scale.
594
+ // h3/h4 bold weights come from typography.heading.weights above.
595
+ },
596
+ icons: neutralIconRegistry
597
+ });
598
+ // Annotate the CommonJS export names for ESM import in node:
599
+ 0 && (module.exports = {
600
+ neutralIconRegistry,
601
+ neutralTheme
602
+ });