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