@astryxdesign/theme-neutral 0.5.2 → 0.5.3

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 CHANGED
@@ -66,43 +66,108 @@ var neutralIconRegistry = {
66
66
  microphone: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Mic, { ...iconProps })
67
67
  };
68
68
 
69
+ // src/neutralPaletteRefs.generated.ts
70
+ var neutralPaletteRefs = {
71
+ purple: {
72
+ light: { 30: "#6b187c", 70: "#d885eb", 75: "#e496f6", 80: "#efa8ff", 85: "#f3bfff", 90: "#f7d5ff" },
73
+ dark: { 25: "#4a2f51", 80: "#eaacf8" }
74
+ },
75
+ green: {
76
+ light: { 30: "#0b5615", 40: "#237028", 45: "#2f7d33", 65: "#6ab26b", 75: "#90ca90", 80: "#a4d6a3" },
77
+ dark: { 25: "#2b422b", 80: "#aad4a9", 85: "#bce0bb" }
78
+ },
79
+ neutral: {
80
+ light: { 0: "#000000", 5: "#111111", 15: "#262626", 35: "#525252", 45: "#6a6a6a", 50: "#777777", 65: "#9e9e9e", 85: "#d4d4d4", 90: "#e2e2e2", 95: "#f1f1f1", 100: "#ffffff" },
81
+ dark: { 0: "#000000", 5: "#111111", 10: "#1b1b1b", 15: "#262626", 20: "#303030", 35: "#525252", 65: "#9e9e9e", 85: "#d4d4d4", 90: "#e2e2e2", 95: "#f1f1f1", 100: "#ffffff" }
82
+ },
83
+ orange: {
84
+ light: { 30: "#733100", 75: "#ff9e55", 85: "#ffc7a1" },
85
+ dark: { 25: "#503424", 65: "#df843f", 80: "#ffb37e" }
86
+ },
87
+ blue: {
88
+ light: { 30: "#00458c", 50: "#0074e2", 80: "#a1caff", 85: "#b9d7ff" },
89
+ dark: { 25: "#253c5a", 65: "#5aa0f8", 75: "#88bcff", 80: "#a1caff" }
90
+ },
91
+ red: {
92
+ light: { 30: "#8a0011", 35: "#9e0015", 55: "#de4745", 65: "#fa6762", 70: "#ff7f77", 80: "#ffaea7", 85: "#ffc4be" },
93
+ dark: { 25: "#5b2b28", 65: "#ee736c", 75: "#ff9890", 80: "#ffaea7", 85: "#ffc4be" }
94
+ },
95
+ yellow: {
96
+ light: { 30: "#584400", 40: "#745b00", 65: "#c29900", 75: "#e2b623", 80: "#eec448", 85: "#f8d36a" },
97
+ dark: { 25: "#453a1c", 80: "#e3c36c", 90: "#fae19e" }
98
+ },
99
+ teal: {
100
+ light: { 30: "#005348", 80: "#90d7c8", 85: "#a9e2d6" },
101
+ dark: { 25: "#28413c", 65: "#4fb1a0", 75: "#81c9bb" }
102
+ },
103
+ cyan: {
104
+ dark: { 25: "#274046", 65: "#49adc4", 75: "#71c7dd", 80: "#85d5e9", 85: "#9ae2f4" },
105
+ light: { 30: "#00505f" }
106
+ },
107
+ pink: {
108
+ light: { 30: "#83004b", 70: "#fc78b1", 85: "#ffc0d7" },
109
+ dark: { 25: "#572b3d", 75: "#fd92bd", 80: "#ffa9ca" }
110
+ }
111
+ };
112
+
69
113
  // src/neutralTheme.ts
114
+ var { blue, cyan, green, neutral, orange, pink, purple, red, teal, yellow } = neutralPaletteRefs;
115
+ var withAlpha = (color, alpha) => `${color}${alpha}`;
70
116
  var neutralSyntax = (0, import_theme.defineSyntaxTheme)({
71
- name: "xds-neutral",
117
+ name: "astryx-neutral",
72
118
  tokens: {
73
- keyword: ["#700084", "#efa8ff"],
74
- // purple T30/T80
75
- string: ["#005600", "#a6d2a2"],
76
- // green (sat T30 / pastel T80)
77
- comment: ["#737373", "#a3a3a3"],
78
- // neutral
79
- number: ["#6e3500", "#ffb37f"],
80
- // orange
81
- function: ["#00458c", "#a0caff"],
82
- // blue T30/T80 H=255
83
- type: ["#700084", "#efa8ff"],
84
- // purple
85
- variable: ["#171717", "#e5e5e5"],
86
- // near-black / near-white
87
- operator: ["#737373", "#a3a3a3"],
88
- // neutral
89
- constant: ["#6e3500", "#ffb37f"],
90
- // orange
91
- tag: ["#89001a", "#ffaeaa"],
92
- // red
93
- attribute: ["#584400", "#eec12f"],
94
- // yellow
95
- property: ["#005348", "#83dac9"],
96
- // teal
97
- // #a3a3a3/#525252 (this pair's own disabled-text tone) failed WCAG AA
98
- // against the syntax background: 2.42:1 light, 2.53:1 dark. #5386.
99
- punctuation: ["#6e6e6e", "#a0a0a0"],
100
- // neutral, 4.89:1 / 7.57:1
101
- background: ["#fafafa", "#0a0a0a"]
119
+ keyword: [purple.light[30], purple.light[80]],
120
+ string: [green.light[30], green.light[80]],
121
+ comment: [neutral.light[45], neutral.dark[65]],
122
+ number: [orange.light[30], orange.dark[80]],
123
+ function: [blue.light[30], blue.dark[80]],
124
+ type: [purple.light[30], purple.light[80]],
125
+ variable: [neutral.light[5], neutral.dark[90]],
126
+ operator: [neutral.light[45], neutral.dark[65]],
127
+ constant: [orange.light[30], orange.dark[80]],
128
+ tag: [red.light[30], red.dark[80]],
129
+ attribute: [yellow.light[30], yellow.light[80]],
130
+ property: [teal.light[30], teal.light[80]],
131
+ punctuation: [neutral.light[45], neutral.dark[65]],
132
+ background: [neutral.light[100], neutral.dark[5]]
102
133
  }
103
134
  });
135
+ var neutralLocalTokens = {
136
+ "--astryx-theme-neutral-color-status-fill-accent": ["#0074e2", "#6d9cfe"],
137
+ "--astryx-theme-neutral-color-status-fill-success": ["#198100", "#64af4c"],
138
+ "--astryx-theme-neutral-color-status-fill-warning": "#ffce2f",
139
+ "--astryx-theme-neutral-color-status-fill-error": ["#c9303a", "#ff705d"],
140
+ "--astryx-theme-neutral-color-status-muted-accent": [
141
+ blue.light[85],
142
+ withAlpha(blue.dark[75], "3D")
143
+ ],
144
+ "--astryx-theme-neutral-color-on-tint-neutral": ["#fafafa4D", "#0a0a0a4D"],
145
+ "--astryx-theme-neutral-color-on-tint-overlay-hover": [
146
+ "#fafafa1A",
147
+ "#0a0a0a1A"
148
+ ],
149
+ "--astryx-theme-neutral-color-on-tint-overlay-pressed": [
150
+ "#fafafa33",
151
+ "#0a0a0a33"
152
+ ],
153
+ "--astryx-theme-neutral-color-destructive-overlay-hover": [
154
+ withAlpha(red.light[70], "0D"),
155
+ withAlpha(red.dark[65], "0D")
156
+ ],
157
+ "--astryx-theme-neutral-color-destructive-overlay-pressed": [
158
+ withAlpha(red.light[70], "1A"),
159
+ withAlpha(red.dark[65], "1A")
160
+ ]
161
+ };
162
+ var statusFill = {
163
+ accent: "var(--astryx-theme-neutral-color-status-fill-accent)",
164
+ success: "var(--astryx-theme-neutral-color-status-fill-success)",
165
+ warning: "var(--astryx-theme-neutral-color-status-fill-warning)",
166
+ error: "var(--astryx-theme-neutral-color-status-fill-error)"
167
+ };
104
168
  var neutralTheme = (0, import_theme.defineTheme)({
105
169
  name: "neutral",
170
+ localTokens: neutralLocalTokens,
106
171
  // Typography: Figtree across body, heading, and display sizes (display
107
172
  // size tokens inherit from heading.family). Monospace stays as the
108
173
  // platform default for code.
@@ -129,215 +194,107 @@ var neutralTheme = (0, import_theme.defineTheme)({
129
194
  motion: { fast: 125, medium: 300, slow: 700, ratio: 0.75 },
130
195
  syntax: neutralSyntax,
131
196
  tokens: {
132
- // =========================================================================
133
- // Core — pure grayscale spine (Tailwind neutral)
134
- // 50:#fafafa 100:#f5f5f5 200:#e5e5e5 300:#d4d4d4 400:#a3a3a3
135
- // 500:#737373 600:#525252 700:#404040 800:#262626 900:#171717 950:#0a0a0a
136
- // =========================================================================
137
- // =========================================================================
138
- // Backgrounds — Figma-style flat with a single lifted surface.
139
- //
140
- // Dark mode collapses card / popover / muted to body T10. Cards and
141
- // popovers lift purely via shadow + inset highlight (see --shadow-*
142
- // below) — they don't need a distinct tone.
143
- //
144
- // Surface is the exception: it's tonally LIGHTER than body (T15) so
145
- // interactive components that sit on top of body have a clear,
146
- // differentiated foreground. Real consumers of --color-background-surface
147
- // are: switches, radios, checkboxes, multi-selectors, dialogs, app
148
- // shells, sections — all things that need to lift above the canvas.
149
- //
150
- // surface T15 #262626 — interactive surfaces lifted above body
151
- // body T10 #1b1b1b — main canvas
152
- // card T10 #1b1b1b — same as body, lifts via --shadow-low
153
- // popover T10 #1b1b1b — same as body, lifts via --shadow-med
154
- // muted T10 #1b1b1b — same as body
155
- //
156
- // Light mode keeps the standard ladder (white surfaces float on tinted
157
- // body; shadows do most of the lifting):
158
- // surface T100 #ffffff
159
- // body T95 #f1f1f1
160
- // card T100 #ffffff
161
- // popover T100 #ffffff
162
- // muted T95 #f1f1f1
163
- //
164
- // All values use the OKLCH Neutral tonal palette (chroma=0).
165
- // =========================================================================
166
- "--color-background-surface": ["#ffffff", "#262626"],
167
- "--color-background-body": ["#f1f1f1", "#1b1b1b"],
168
- "--color-background-card": ["#ffffff", "#1b1b1b"],
169
- "--color-background-popover": ["#ffffff", "#1b1b1b"],
170
- "--color-background-muted": ["#f1f1f1", "#1b1b1b"],
171
- // Accent + neutral surface tints (sit alongside backgrounds)
172
- "--color-accent": ["#262626", "#ebebeb"],
173
- "--color-accent-muted": ["#f1f1f1", "#262626"],
174
- "--color-neutral": ["#0000000F", "#FFFFFF1A"],
197
+ "--color-background-surface": [neutral.light[100], neutral.dark[15]],
198
+ "--color-background-body": [neutral.light[95], neutral.dark[10]],
199
+ "--color-background-card": [neutral.light[100], neutral.dark[10]],
200
+ "--color-background-popover": [neutral.light[100], neutral.dark[10]],
201
+ "--color-background-muted": [neutral.light[95], neutral.dark[10]],
202
+ "--color-accent": [neutral.light[15], neutral.dark[95]],
203
+ "--color-accent-muted": [neutral.light[95], neutral.dark[15]],
204
+ "--color-neutral": [
205
+ withAlpha(neutral.light[0], "0F"),
206
+ withAlpha(neutral.dark[100], "1A")
207
+ ],
175
208
  // Overlays (modal scrims, hover/pressed tints)
176
- "--color-overlay": ["#00000080", "#000000CC"],
177
- "--color-overlay-hover": ["#0000000D", "#FFFFFF0D"],
178
- "--color-overlay-pressed": ["#0000001A", "#FFFFFF1A"],
209
+ "--color-overlay": [
210
+ withAlpha(neutral.light[0], "80"),
211
+ withAlpha(neutral.dark[0], "CC")
212
+ ],
213
+ "--color-overlay-hover": [
214
+ withAlpha(neutral.light[0], "0D"),
215
+ withAlpha(neutral.dark[100], "0D")
216
+ ],
217
+ "--color-overlay-pressed": [
218
+ withAlpha(neutral.light[0], "1A"),
219
+ withAlpha(neutral.dark[100], "1A")
220
+ ],
179
221
  // Text
180
- "--color-text-primary": ["#171717", "#fafafa"],
181
- // Light secondary is neutral-600 (#525252), not 500 (#737373): 500 only
182
- // reaches 4.19:1 on the T95 body (#f1f1f1), just under WCAG AA 4.5:1.
183
- // 600 clears it (6.9:1 on body, 7.8:1 on card). Dark stays neutral-400.
184
- "--color-text-secondary": ["#525252", "#a3a3a3"],
185
- "--color-text-disabled": ["#a3a3a3", "#525252"],
186
- "--color-text-accent": ["#262626", "#ebebeb"],
187
- "--color-on-dark": "#ffffff",
188
- "--color-on-light": "#171717",
189
- // Contrast: neutral accent is near-black (L) / near-white (D)
190
- "--color-on-accent": ["#ffffff", "#171717"],
191
- "--color-on-success": ["#ffffff", "#171717"],
192
- "--color-on-error": ["#ffffff", "#171717"],
193
- "--color-on-warning": "#171717",
222
+ "--color-text-primary": [neutral.light[5], neutral.dark[100]],
223
+ "--color-text-secondary": [neutral.light[35], neutral.dark[65]],
224
+ "--color-text-disabled": [neutral.light[65], neutral.dark[35]],
225
+ "--color-text-accent": [neutral.light[15], neutral.dark[95]],
226
+ "--color-on-dark": neutral.light[100],
227
+ "--color-on-light": neutral.light[5],
228
+ "--color-on-accent": [neutral.light[100], neutral.dark[5]],
229
+ "--color-on-success": [neutral.light[100], neutral.dark[5]],
230
+ "--color-on-error": [neutral.light[100], neutral.dark[5]],
231
+ "--color-on-warning": neutral.light[5],
194
232
  // Icon
195
- "--color-icon-accent": ["#262626", "#ebebeb"],
196
- "--color-icon-primary": ["#171717", "#fafafa"],
197
- "--color-icon-secondary": ["#737373", "#a3a3a3"],
198
- "--color-icon-disabled": ["#a3a3a3", "#525252"],
199
- // Status / Sentiment — dark mode follows the issue #2150 rubric:
200
- //
201
- // Light mode: pastel T90 banner bg + dark T30/T40 text/icon. Locked
202
- // light values for cards/banners/inputs/destructive btn.
203
- // Dark mode : tinted-dark T20 bg + light pastel T80 text. INVERTED
204
- // from light. Avoids the §5 "pastel-in-both-modes"
205
- // anti-pattern (locked pastels glow against a dark body).
206
- //
207
- // --color-X = "saturated text/icon stop":
208
- // light = T40 dark colored (sits on light pastel)
209
- // dark = T80 light pastel (sits on dark tinted bg)
210
- // Used by destructive button text, input border/icon
211
- // (in light), banner-status-* text overrides.
212
- // --color-X-muted = "muted bg stop":
213
- // light = T90 light pastel
214
- // dark = hue-tinted alpha overlay (T70 stop @ 24%)
215
- // Used by banner bg, status-input message bg,
216
- // destructive button bg. Dark mode uses an alpha
217
- // overlay rather than a solid T20 tinted bg so
218
- // the surface composes onto whatever sits behind
219
- // it (body, card, popover) rather than reading
220
- // as a hard colored panel.
221
- //
222
- // 24% alpha = '3D' suffix. Hue values match --color-icon-{X} dark
223
- // slots (palette T70). Composited onto body #1b1b1b, the effective
224
- // bg luminance hits ~1.65-1.70:1 vs body — visible colored surface
225
- // without the heaviness of a solid T20 panel.
226
- "--color-success": ["#007004", "#9fe59b"],
227
- "--color-error": ["#a50c25", "#ffc6c1"],
228
- "--color-warning": ["#745b00", "#fdcf4f"],
229
- "--color-success-muted": ["#c5e5c0", "#84c9803D"],
230
- "--color-error-muted": ["#facecb", "#ff9e973D"],
231
- "--color-warning-muted": ["#f8da9d", "#deb4333D"],
232
- // Border
233
- "--color-border": ["#00000014", "#FFFFFF1A"],
234
- "--color-border-emphasized": ["#d4d4d4", "#525252"],
233
+ "--color-icon-accent": [neutral.light[15], neutral.dark[95]],
234
+ "--color-icon-primary": [neutral.light[5], neutral.dark[100]],
235
+ "--color-icon-secondary": [neutral.light[50], neutral.dark[65]],
236
+ "--color-icon-disabled": [neutral.light[65], neutral.dark[35]],
237
+ "--color-success": [green.light[40], green.light[80]],
238
+ "--color-error": [red.light[35], red.dark[85]],
239
+ "--color-warning": [yellow.light[40], yellow.light[85]],
240
+ "--color-success-muted": [green.dark[85], withAlpha(green.light[75], "3D")],
241
+ "--color-error-muted": [red.light[85], withAlpha(red.dark[75], "3D")],
242
+ "--color-warning-muted": [
243
+ yellow.dark[90],
244
+ withAlpha(yellow.light[75], "3D")
245
+ ],
246
+ "--color-border": [
247
+ withAlpha(neutral.light[0], "14"),
248
+ withAlpha(neutral.dark[100], "1A")
249
+ ],
250
+ "--color-border-emphasized": [neutral.light[85], neutral.dark[35]],
235
251
  // Effects
236
- "--color-skeleton": ["#ebebeb", "#525252"],
237
- "--color-shadow": ["#0000001A", "#0000004D"],
252
+ "--color-skeleton": [neutral.light[95], neutral.dark[35]],
253
+ "--color-shadow": [
254
+ withAlpha(neutral.light[0], "1A"),
255
+ withAlpha(neutral.dark[0], "4D")
256
+ ],
238
257
  "--color-tint-hover": ["black", "white"],
239
- // =========================================================================
240
- // Categorical light mode uses pastel surfaces + dark colored text;
241
- // dark mode INVERTS to a hue-tinted alpha overlay surface +
242
- // light pastel text (per #2150 rubric §3 — pick the tone
243
- // that satisfies required contrast against every surface
244
- // the token touches).
245
- //
246
- // Per-token tone choice (CIELab L*):
247
- // bg light=T87-T90 pastel dark=T70 hue @ 24% alpha overlay
248
- // (composites onto body to ~1.65:1
249
- // vs body — colored surface that
250
- // feels lighter than a solid T20
251
- // panel; same hue as --color-icon-X
252
- // dark slot, just at lower opacity)
253
- // border light=T80 pastel dark=T60 mid-bright (>=5.8:1 vs body)
254
- // icon light=T30 dark colored dark=T70 light pastel
255
- // text light=T30 dark colored dark=T80 light pastel (>=7:1 on bg)
256
- //
257
- // Light pastels still use the per-hue chroma table (red/blue C=0.05,
258
- // orange/green/purple/pink C=0.06, teal/cyan C=0.07, yellow H=85 C=0.10)
259
- // for equal PERCEIVED saturation. Dark stops (T60/T70/T80) come from
260
- // the dark-mode tonal palette (chroma×0.85, +5 tone lift tapering 80-95).
261
- // =========================================================================
262
- // Each row's dark slots are HCT-derived from the source hex listed in
263
- // apps/sandbox/src/app/(fullscreen)/pages/neutral-palette/page.tsx via
264
- // the canonical dark-ramp transform (chroma×0.85, +5 tone-lift taper)
265
- // same algorithm the Tonal Palettes preview renders. Border=T60,
266
- // icon=T70, text=T80. Background uses the T70 hue at 24% alpha so the
267
- // overlay surface composites onto body to ~1.65:1 luminance.
268
- // Red H=22 — source #eb183a
269
- "--color-background-red": ["#facecb", "#ff9e973D"],
270
- "--color-border-red": ["#e6bab8", "#ff6f6c"],
271
- "--color-icon-red": ["#89001a", "#ff9e97"],
272
- "--color-text-red": ["#89001a", "#ffc6c1"],
273
- // Orange H=55 — source #d57113
274
- "--color-background-orange": ["#fad0b5", "#ffa2583D"],
275
- "--color-border-orange": ["#e6bda2", "#e2883e"],
276
- "--color-icon-orange": ["#6e3500", "#ffa258"],
277
- "--color-text-orange": ["#6e3500", "#ffc9a2"],
278
- // Yellow H=90 — source #f8c723
279
- // Light-mode butter-yellow pastel at H=85 C=0.085 L=0.90 — yellow
280
- // sits at the green-cyan luminance peak so it feels louder than the
281
- // other status hues at the same canonical L. Picker decision: pull
282
- // L down one step (0.91→0.90) and C down to its identity floor
283
- // (0.10→0.085, just above the bronze threshold) so it sits closer
284
- // to red/blue's perceived brightness without losing yellow identity.
285
- // Dark-mode comes from the canonical H=90 ramp for tonal-palette
286
- // consistency.
287
- "--color-background-yellow": ["#f8da9d", "#deb4333D"],
288
- "--color-border-yellow": ["#e4c279", "#c0990e"],
289
- "--color-icon-yellow": ["#584400", "#deb433"],
290
- "--color-text-yellow": ["#584400", "#fdcf4f"],
291
- // Green H=144 — source #358a3a
292
- "--color-background-green": ["#c5e5c0", "#84c9803D"],
293
- "--color-border-green": ["#b2d1ac", "#69ad67"],
294
- "--color-icon-green": ["#0c5700", "#84c980"],
295
- "--color-text-green": ["#0c5700", "#9fe59b"],
296
- // Teal H=180 — source #0c7365
297
- // Light pastel uses L=0.87 C=0.065 (a step darker + less chroma than
298
- // the L=0.888 C=0.07 used by other hues) to compensate for the
299
- // green-cyan luminance overshoot — at the same OKLCH L, teal/cyan read
300
- // ~5% brighter than red/blue because the eye's luminance response
301
- // peaks in this band. Dropping L+C brings perceived brightness in
302
- // line with the rest of the palette without losing hue identity.
303
- "--color-background-teal": ["#a5e3d6", "#7ec6b83D"],
304
- "--color-border-teal": ["#94d6c8", "#63ab9d"],
305
- "--color-icon-teal": ["#005348", "#7ec6b8"],
306
- "--color-text-teal": ["#005348", "#99e2d3"],
307
- // Cyan H=215 — source #0c6f82
308
- // Same L=0.87 C=0.065 pastel as teal (luminance overshoot compensation).
309
- "--color-background-cyan": ["#a3e0ef", "#83c2d43D"],
310
- "--color-border-cyan": ["#91d3e3", "#67a7b8"],
311
- "--color-icon-cyan": ["#00505f", "#83c2d4"],
312
- "--color-text-cyan": ["#00505f", "#9edef0"],
313
- // Blue H=255 — source #0074e2
314
- // T50 #0074e2 reserved for filled Info badge / progressbar / inset hover.
315
- "--color-background-blue": ["#c4ddfb", "#9eb7ff3D"],
316
- "--color-border-blue": ["#b1c9e7", "#6d9cfe"],
317
- "--color-icon-blue": ["#00458c", "#9eb7ff"],
318
- "--color-text-blue": ["#00458c", "#c7d3ff"],
319
- // Purple H=320 — source #980fb2
320
- "--color-background-purple": ["#eccef3", "#f297ff3D"],
321
- "--color-border-purple": ["#d8bbdf", "#dd74f0"],
322
- "--color-icon-purple": ["#700084", "#f297ff"],
323
- "--color-text-purple": ["#700084", "#fac1ff"],
324
- // Pink H=355 — source #b10e69
325
- "--color-background-pink": ["#fccadc", "#ff99c33D"],
326
- "--color-border-pink": ["#e7b7c8", "#f273aa"],
327
- "--color-icon-pink": ["#83004b", "#ff99c3"],
328
- "--color-text-pink": ["#83004b", "#ffc3da"],
329
- // Gray (categorical neutral, chroma 0)
330
- // Light: #e5e5e5 (Neutral 200) so it's visibly distinct from the
331
- // lighter body / muted surface (both #f5f5f5).
332
- // Dark : var(--color-neutral) — semi-transparent white wash
333
- // (#FFFFFF1A, 10%). Matches the same treatment the gray
334
- // badge uses; clearly distinct from the body T10 #1b1b1b
335
- // while staying chroma-0 neutral. Solid T15 #1c1c1c was
336
- // indistinguishable from --color-background-muted.
337
- "--color-background-gray": ["#e5e5e5", "var(--color-neutral)"],
338
- "--color-border-gray": ["#d4d4d4", "#262626"],
339
- "--color-icon-gray": ["#525252", "#a3a3a3"],
340
- "--color-text-gray": ["#262626", "#e5e5e5"],
258
+ "--color-background-red": [red.light[85], red.dark[25]],
259
+ "--color-border-red": [red.light[80], red.light[65]],
260
+ "--color-icon-red": [red.light[30], red.dark[75]],
261
+ "--color-text-red": [red.light[30], red.dark[80]],
262
+ "--color-background-orange": [orange.light[85], orange.dark[25]],
263
+ "--color-border-orange": [orange.light[85], orange.dark[65]],
264
+ "--color-icon-orange": [orange.light[30], orange.light[75]],
265
+ "--color-text-orange": [orange.light[30], orange.dark[80]],
266
+ "--color-background-yellow": [yellow.dark[90], yellow.dark[25]],
267
+ "--color-border-yellow": [yellow.dark[80], yellow.light[65]],
268
+ "--color-icon-yellow": [yellow.light[30], yellow.light[75]],
269
+ "--color-text-yellow": [yellow.light[30], yellow.light[80]],
270
+ "--color-background-green": [green.dark[85], green.dark[25]],
271
+ "--color-border-green": [green.dark[80], green.light[65]],
272
+ "--color-icon-green": [green.light[30], green.light[75]],
273
+ "--color-text-green": [green.light[30], green.light[75]],
274
+ "--color-background-teal": [teal.light[85], teal.dark[25]],
275
+ "--color-border-teal": [teal.light[80], teal.dark[65]],
276
+ "--color-icon-teal": [teal.light[30], teal.dark[75]],
277
+ "--color-text-teal": [teal.light[30], teal.light[80]],
278
+ "--color-background-cyan": [cyan.dark[85], cyan.dark[25]],
279
+ "--color-border-cyan": [cyan.dark[80], cyan.dark[65]],
280
+ "--color-icon-cyan": [cyan.light[30], cyan.dark[75]],
281
+ "--color-text-cyan": [cyan.light[30], cyan.dark[80]],
282
+ "--color-background-blue": [blue.light[85], blue.dark[25]],
283
+ "--color-border-blue": [blue.light[80], blue.dark[65]],
284
+ "--color-icon-blue": [blue.light[30], blue.dark[75]],
285
+ "--color-text-blue": [blue.light[30], blue.dark[80]],
286
+ "--color-background-purple": [purple.light[90], purple.dark[25]],
287
+ "--color-border-purple": [purple.light[85], purple.light[70]],
288
+ "--color-icon-purple": [purple.light[30], purple.light[75]],
289
+ "--color-text-purple": [purple.light[30], purple.dark[80]],
290
+ "--color-background-pink": [pink.light[85], pink.dark[25]],
291
+ "--color-border-pink": [pink.light[85], pink.light[70]],
292
+ "--color-icon-pink": [pink.light[30], pink.dark[75]],
293
+ "--color-text-pink": [pink.light[30], pink.dark[80]],
294
+ "--color-background-gray": [neutral.light[90], neutral.dark[20]],
295
+ "--color-border-gray": [neutral.light[85], neutral.dark[15]],
296
+ "--color-icon-gray": [neutral.light[35], neutral.dark[65]],
297
+ "--color-text-gray": [neutral.light[15], neutral.dark[85]],
341
298
  // =========================================================================
342
299
  // Radius — slightly larger than default (kept as-is)
343
300
  // --radius-none and --radius-full are always fixed and must never be
@@ -371,91 +328,42 @@ var neutralTheme = (0, import_theme.defineTheme)({
371
328
  "--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%))",
372
329
  "--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%))",
373
330
  "--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%))",
374
- "--shadow-inset-hover": "inset 0px 0px 0px 2px #0074e24D",
375
- "--shadow-inset-selected": "inset 0px 0px 0px 2px #0074e280",
376
- "--shadow-inset-success": "inset 0px 0px 0px 2px #1981004D",
377
- "--shadow-inset-warning": "inset 0px 0px 0px 2px #ffce2f4D",
378
- "--shadow-inset-error": "inset 0px 0px 0px 2px #e33f4a4D"
331
+ "--shadow-inset-hover": `inset 0px 0px 0px 2px ${withAlpha(blue.light[50], "4D")}`,
332
+ "--shadow-inset-selected": `inset 0px 0px 0px 2px ${withAlpha(blue.light[50], "80")}`,
333
+ "--shadow-inset-success": `inset 0px 0px 0px 2px ${withAlpha(green.light[45], "4D")}`,
334
+ "--shadow-inset-warning": `inset 0px 0px 0px 2px ${withAlpha(yellow.light[85], "4D")}`,
335
+ "--shadow-inset-error": `inset 0px 0px 0px 2px ${withAlpha(red.light[55], "4D")}`
379
336
  },
380
337
  components: {
381
- // =========================================================================
382
- // Button — primary gets white text, secondary gets a border, destructive
383
- // uses the OKLCH red filled treatment.
384
- // =========================================================================
385
338
  button: {
386
339
  "variant:destructive": {
387
340
  backgroundColor: "var(--color-error-muted)",
388
- // locked pastel red bg
389
- color: "var(--color-error)"
390
- // locked T30 red — matches banner/input error text
341
+ color: "var(--color-error)",
342
+ "--color-overlay-hover": "var(--astryx-theme-neutral-color-destructive-overlay-hover)",
343
+ "--color-overlay-pressed": "var(--astryx-theme-neutral-color-destructive-overlay-pressed)"
391
344
  }
392
345
  },
393
- // =========================================================================
394
- // Badge —
395
- // Semantic (info/success/warning/error): filled saturated T50 + contrasting
396
- // text (white, or dark on yellow). The filled-button rule from #2150
397
- // §3 — text contrast locks the bg tone, so this stays at T50 in
398
- // BOTH modes, unlike pastel surfaces which invert by mode.
399
- // Categorical (blue/green/red/orange/etc.): pastel-tinted hue surface +
400
- // colored text — light mode = soft T87-T90 + dark T30 text; dark mode
401
- // = T20 tinted + T80 light pastel text (sources: --color-background-X
402
- // and --color-text-X tokens).
403
- // Neutral: light gray bg + dark text (or inverted in dark mode).
404
- // =========================================================================
405
346
  badge: {
406
- // Semantic — filled saturated bg + contrasting text.
407
- // Light: vivid T45-T55 from the OKLCH palette + white text
408
- // (~4.5-5:1 — Material/Linear/Vercel pop).
409
- // Dark : T60 stop from the dark-mode tonal palette (chroma×0.85,
410
- // +5 tone-lift taper from issue #2150 §4) + DARK text.
411
- // T60+white fails AA-large (~2.7:1); T60+dark hits 6.6-7:1
412
- // and tames the §4 vibration. Same dark-text-on-bright-bg
413
- // treatment that warning yellow uses in both modes.
414
347
  "variant:info": {
415
- // Light: T50 #0074e2 (palette saturated stop)
416
- // Dark : T60 stop from dark-mode tonal palette of source #0074e2
417
- backgroundColor: "light-dark(#0074e2, #6d9cfe)",
418
- color: "light-dark(#ffffff, #171717)"
348
+ backgroundColor: statusFill.accent,
349
+ color: "var(--color-on-accent)"
419
350
  },
420
351
  "variant:neutral": {
421
- // Mirrors the gray categorical badge — same neutral chip treatment
422
- // (Neutral 200 light / semi-transparent white wash dark) sourced
423
- // from the gray hue tokens, so a single change at the token layer
424
- // updates both variants.
425
352
  backgroundColor: "var(--color-background-gray)",
426
353
  color: "var(--color-text-gray)"
427
354
  },
428
355
  "variant:success": {
429
- // Light: T45 #198100 (palette saturated stop)
430
- // Dark : T60 stop from dark-mode tonal palette of source #198100
431
- backgroundColor: "light-dark(#198100, #64af4c)",
432
- color: "light-dark(#ffffff, #171717)"
356
+ backgroundColor: statusFill.success,
357
+ color: "var(--color-on-success)"
433
358
  },
434
359
  "variant:warning": {
435
- // Yellow stays at the same hex in both modes — chroma reduction
436
- // is barely visible at T85, and dark text on yellow doesn't
437
- // suffer from the §4 vibration concern.
438
- backgroundColor: "#ffce2f",
439
- color: "#171717"
360
+ backgroundColor: statusFill.warning,
361
+ color: "var(--color-on-warning)"
440
362
  },
441
363
  "variant:error": {
442
- // Light: T58 #c9303a. The T55 stop #e33f4a pairs with white at only
443
- // 4.14:1 — the label is 12px/500, so AA wants 4.5, not the 3:1
444
- // large-text allowance. One tonal step down holds the hue
445
- // (OKLCH H 21.9 -> 22.8, C 0.200 -> 0.189) and reaches 5.29:1.
446
- // Dark : T60 stop from dark-mode tonal palette of Tailwind red-600
447
- // source #dc2626 (kept on H=27 alarm-red rather than coral).
448
- // Dark text on it is 6.60:1 and unchanged.
449
- backgroundColor: "light-dark(#c9303a, #ff705d)",
450
- color: "light-dark(#ffffff, #171717)"
364
+ backgroundColor: statusFill.error,
365
+ color: "var(--color-on-error)"
451
366
  },
452
- // Categorical — bg + text reference the per-hue tokens, so behavior
453
- // tracks the categorical palette automatically:
454
- // Light: pastel T87-T90 bg + dark T30 colored text (low-key chip)
455
- // Dark : tinted T20 bg + light T80 colored text (per #2150 §5,
456
- // inverted from light to avoid the "pastel-in-both-modes"
457
- // anti-pattern that makes locked light pastels glow on a
458
- // dark body)
459
367
  "variant:red": {
460
368
  backgroundColor: "var(--color-background-red)",
461
369
  color: "var(--color-text-red)"
@@ -497,63 +405,49 @@ var neutralTheme = (0, import_theme.defineTheme)({
497
405
  color: "var(--color-text-gray)"
498
406
  }
499
407
  },
500
- // =========================================================================
501
- // StatusDot — fill uses the SAME vivid stops as the filled semantic Badge
502
- // (and ProgressBar), so a dot and its badge read as one status language.
503
- //
504
- // The default component maps each variant to a raw semantic token
505
- // (--color-success / --color-error / --color-warning / --color-icon-
506
- // secondary), which in light mode are the dark T30/T40 stops meant to
507
- // sit as TEXT on a pastel surface — as a solid dot they read muddy
508
- // (dark green / maroon / brown). Redirect them to the badge fills.
509
- //
510
- // success → badge success bg (green T45 / dark-ramp T60)
511
- // warning → badge warning bg (yellow T85, same hex both modes)
512
- // error → badge error bg (red T58 / dark-ramp T60)
513
- // accent → badge info bg (blue T50 / dark-ramp T60) — the
514
- // StatusDot "accent" is the info/attention color, so it
515
- // pairs with the info badge rather than --color-accent
516
- // (near-black #262626, the darkest offender).
517
- //
518
- // `neutral` is intentionally NOT overridden: the neutral badge bg is a
519
- // near-invisible light gray (--color-background-gray #e5e5e5 / 10% white
520
- // wash), fine as a large pill but unreadable as an 8px dot. It keeps the
521
- // component default's visible mid-gray (--color-icon-secondary), which is
522
- // not among the "too dark" cases.
523
- // =========================================================================
524
408
  statusdot: {
525
- "variant:success": { backgroundColor: "light-dark(#198100, #64af4c)" },
526
- "variant:warning": { backgroundColor: "#ffce2f" },
527
- "variant:error": { backgroundColor: "light-dark(#c9303a, #ff705d)" },
528
- "variant:accent": { backgroundColor: "light-dark(#0074e2, #6d9cfe)" }
409
+ "variant:success": { backgroundColor: statusFill.success },
410
+ "variant:warning": { backgroundColor: statusFill.warning },
411
+ "variant:error": { backgroundColor: statusFill.error },
412
+ "variant:accent": { backgroundColor: statusFill.accent }
413
+ },
414
+ "avatar-status-dot": {
415
+ "variant:success": { backgroundColor: statusFill.success },
416
+ "variant:error": { backgroundColor: statusFill.error }
417
+ },
418
+ // Give the Neutral segmented control a roomier inset without changing its
419
+ // outside height. The selected item stays flat against the tinted track.
420
+ "segmented-control": {
421
+ base: {
422
+ padding: "var(--spacing-1)"
423
+ }
424
+ },
425
+ "segmented-control-item": {
426
+ "size:sm": {
427
+ height: "calc(var(--size-element-sm) - 8px)"
428
+ },
429
+ "size:md": {
430
+ height: "calc(var(--size-element-md) - 8px)"
431
+ },
432
+ "size:lg": {
433
+ height: "calc(var(--size-element-lg) - 8px)"
434
+ },
435
+ selected: {
436
+ boxShadow: "none"
437
+ }
529
438
  },
530
- // =========================================================================
531
- // Banner — sits on a hue-tinted surface with colored text/icon:
532
- // Light: pastel T90 bg (pulled from --color-{X}-muted / --color-background-blue)
533
- // + dark T30 colored text (--color-text-{hue}).
534
- // Dark : tinted T20 bg (same tokens, dark slot) + light T80 colored text.
535
- // Per #2150 §5 — large hue-tinted surfaces in dark mode invert
536
- // to a deep tinted bg + light text rather than locking the
537
- // light-mode pastel.
538
- //
539
- // The inner-header *-muted token carries the tinted background for every
540
- // status, info included. A theme override that sets a plain CSS property
541
- // instead lands in @layer astryx-theme, which StyleX's @layer priority4
542
- // outranks, so `backgroundColor` here would silently do nothing and the
543
- // info banner would paint no background at all.
544
- //
545
- // Status overrides reference --color-text-{hue} so text/icon colors
546
- // stay in sync with the palette anchors automatically.
547
439
  banner: {
440
+ base: {
441
+ "--color-neutral": "var(--astryx-theme-neutral-color-on-tint-neutral)",
442
+ "--color-overlay-hover": "var(--astryx-theme-neutral-color-on-tint-overlay-hover)",
443
+ "--color-overlay-pressed": "var(--astryx-theme-neutral-color-on-tint-overlay-pressed)"
444
+ },
548
445
  "status:info": {
549
- "--color-accent-muted": "var(--color-background-blue)",
446
+ "--color-accent-muted": "var(--astryx-theme-neutral-color-status-muted-accent)",
550
447
  "--color-text-primary": "var(--color-text-blue)",
551
448
  "--color-text-secondary": "var(--color-text-blue)",
552
449
  "--color-accent": "var(--color-text-blue)"
553
450
  },
554
- // success/warning/error banner bgs come from --color-{X}-muted, which
555
- // already carries the correct light/dark tinted values. We only need
556
- // to redirect the text/icon to the palette colored stop.
557
451
  "status:success": {
558
452
  "--color-text-primary": "var(--color-text-green)",
559
453
  "--color-text-secondary": "var(--color-text-green)",
@@ -570,22 +464,12 @@ var neutralTheme = (0, import_theme.defineTheme)({
570
464
  "--color-error": "var(--color-text-red)"
571
465
  }
572
466
  },
573
- // =========================================================================
574
- // TextInput — no per-status overrides needed. The global tokens
575
- // --color-{success,error,warning} carry the correct values in both
576
- // modes (light=T40 dark colored, dark=T80 light pastel) for both
577
- // surfaces the input border/icon touches: the input surface
578
- // (white/T15-dark) and the status message bubble (light pastel T90 /
579
- // dark T20). Verified all six combinations clear AA non-text 3:1.
580
- // =========================================================================
581
- // =========================================================================
582
- // Switch — off-state track uses the same lifted-neutral surface as the
583
- // ProgressBar track (--color-border-emphasized). Aligns the two
584
- // "channel-on-body" components so their off-states share one visual
585
- // language: light T85 #d4d4d4 sits one step darker than the body T95
586
- // bg, dark T35 #525252 sits one step lighter than the body T10. Each
587
- // is a defined channel, not a wash that blends in.
588
- // =========================================================================
467
+ "step-indicator": {
468
+ "status:accent": { "--color-accent": statusFill.accent },
469
+ "status:success": { "--color-success": statusFill.success },
470
+ "status:warning": { "--color-warning": statusFill.warning },
471
+ "status:error": { "--color-error": statusFill.error }
472
+ },
589
473
  switch: {
590
474
  base: {
591
475
  "--color-background-gray": "var(--color-border-emphasized)"
@@ -593,44 +477,26 @@ var neutralTheme = (0, import_theme.defineTheme)({
593
477
  },
594
478
  progressbar: {
595
479
  base: {
596
- // Track uses --color-background-muted; override it to
597
- // --color-border-emphasized (Neutral T85 #d4d4d4 in light mode) so
598
- // the track is clearly darker than the body bg (Neutral T95 #f1f1f1)
599
- // and reads as a defined channel rather than blending in. Dark
600
- // mode inherits T35 #525252 — same one-step-lighter behavior.
601
480
  "--color-background-muted": "var(--color-border-emphasized)"
602
481
  },
603
- // Vivid stops match the filled semantic badge colors (info/success/
604
- // warning/error variants in the badge override above). Same hex
605
- // values; documented per role with palette provenance.
606
482
  "variant:accent": {
607
- // Blue T50 saturated stop (= variant:info badge bg)
608
- "--color-accent": "#0074e2"
483
+ "--color-accent": statusFill.accent
609
484
  },
610
485
  "variant:success": {
611
- // Green T45 saturated stop (= variant:success badge bg)
612
- "--color-success": "#198100"
486
+ "--color-success": statusFill.success
613
487
  },
614
488
  "variant:warning": {
615
- // Yellow T85 saturated stop (= variant:warning badge bg)
616
- "--color-warning": "#ffce2f"
489
+ "--color-warning": statusFill.warning
617
490
  },
618
491
  "variant:error": {
619
- // Red T58 saturated stop (= variant:error badge bg)
620
- "--color-error": "#c9303a"
492
+ "--color-error": statusFill.error
621
493
  }
622
494
  },
623
- // =========================================================================
624
- // Card — tighter padding via public card padding token
625
- // =========================================================================
626
495
  card: {
627
496
  base: {
628
497
  padding: "var(--spacing-3)"
629
498
  }
630
499
  },
631
- // =========================================================================
632
- // Section — tighter padding via public section padding token
633
- // =========================================================================
634
500
  section: {
635
501
  base: {
636
502
  padding: "var(--spacing-3)"