@bsuite/theme 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/css/vars.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * @bsuite/theme — CSS custom properties (:root + .dark)
3
- * Version 0.3.0
3
+ * Version 0.6.0
4
4
  *
5
5
  * Architecture — 5 layers (inner → outer):
6
6
  * 1. Palette — frozen oklch primitives (--neon-electric-*, --braden-*)
@@ -12,13 +12,27 @@
12
12
  * Consumers should bind to Layer 3 (roles) or Layer 4 (shadcn) — never
13
13
  * directly to palette primitives. Roles make white-labelling safe.
14
14
  *
15
- * Colourblind policy (WCAG 1.4.1 + audit constraint):
16
- * --role-error / --role-destructive = PURPLE, never red-vs-green.
17
- * Only flag where colour is the SOLE signifier of state.
15
+ * Colourblind policy (WCAG 1.4.1) REWRITTEN 2026-08-02, contract 0.7.0:
16
+ * The invariant is SEPARATION, not a fixed hue. --role-error must be
17
+ * maximally separated from --role-primary under deuteranopia and
18
+ * protanopia. D2C's primary is blue, so error is RED.
19
+ * The previous "error = purple" rule was actively harmful: under
20
+ * protanopia purple measured DeltaE 0.006 against primary blue — the
21
+ * destructive colour and the primary action colour were the SAME swatch.
22
+ * Purple and indigo are therefore quarantined from semantics entirely.
23
+ * Warm states (red/amber) separate by LIGHTNESS, not hue — under
24
+ * deuteranopia everything warm collapses to yellow, so error is the dark
25
+ * one and warning the light one. Never put two cool hues in opposition.
26
+ * Colour is never the sole signifier: shape + label always.
27
+ * TENANT OVERRIDE BLOCKED on --role-error / --role-destructive.
18
28
  *
19
- * Eye-strain policy:
20
- * Dark-mode text capped at L=0.94. Pure white (L=1.0) is banned as a
21
- * text token. 5-tier scale verified WCAG AA minimum throughout.
29
+ * Eye-strain / pure-endpoint policy:
30
+ * Pure white (L=1.0) and pure black (L=0) are BANNED in EVERY role —
31
+ * text, surface, border, ring and shadow alike (operator ruling
32
+ * 2026-08-02). The prior text-only scoping of this rule is what let
33
+ * --light-bg-accent ship as oklch(1 0 0.5) and render every shadcn card
34
+ * and popover pure white. "White" here means oklch(0.982 0.002 248)
35
+ * (#f8f9fa); the lightest surface is bg-panel at L=0.994.
22
36
  *
23
37
  * OKLCH format is MANDATORY for all new tokens. No hex/rgb/hsl.
24
38
  * Syntax: oklch(L C H) — L=lightness 0–1, C=chroma 0–0.4, H=hue 0–360.
@@ -41,6 +55,17 @@
41
55
  --neon-electric-green: oklch(0.723 0.192 149.6);
42
56
  --neon-electric-lavender: oklch(0.736 0.141 285.6);
43
57
 
58
+ /* Semantic hues — added 2026-08-02 (contract 0.7.0). The 11 above were
59
+ chosen for looks; these four were chosen for SEPARATION, measured via
60
+ Vienot-Brettel dichromat simulation. Worst-case DeltaE(OKLab) across
61
+ normal/deuteranopia/protanopia for every semantic pair is >= 0.156;
62
+ the pairing these replace scored 0.006. Never bind a component to
63
+ these directly — bind to the --role-* alias. */
64
+ --neon-electric-red: oklch(0.580 0.230 25); /* error / destructive */
65
+ --neon-electric-amber: oklch(0.800 0.150 75); /* warning */
66
+ --neon-electric-teal: oklch(0.600 0.130 195); /* success */
67
+ --neon-electric-deep: oklch(0.380 0.140 270); /* secondary action */
68
+
44
69
  /* ============================================================
45
70
  LAYER 2 — SURFACE PRIMITIVES (light mode)
46
71
  ============================================================ */
@@ -49,18 +74,23 @@
49
74
  --light-bg-primary: oklch(0.961 0 0.5);
50
75
  --light-bg-secondary: oklch(0.982 0.002 248);
51
76
  --light-bg-tertiary: oklch(0.963 0.003 228.9);
52
- --light-bg-accent: oklch(1 0 0.5);
77
+ /* bg-panel. Was oklch(1 0 0.5) — pure white, and since --card and
78
+ --popover bridge to it, every shadcn card and popover in all five D2C
79
+ apps rendered pure white in light mode from this one line. */
80
+ --light-bg-accent: oklch(0.994 0.002 260);
53
81
 
54
82
  /* D2C light text scale — hue 260 (cool blue-grey), capped L=0.22.
55
- Verified WCAG AA/AAA on --light-bg-primary oklch(0.961 0 0.5). */
56
- --light-text-primary: oklch(0.22 0.015 260); /* 13.2:1 AAA */
57
- --light-text-secondary: oklch(0.38 0.018 260); /* 8.1:1 ✓ AAA */
58
- --light-text-muted: oklch(0.52 0.018 260); /* 4.9:1 AA */
59
- --light-text-subtle: oklch(0.60 0.012 260); /* 3.8:1 AA large */
60
- --light-text-disabled: oklch(0.72 0.010 260); /* pair with icon cue */
61
- /* Legacy aliases (kept for consumers that haven't migrated) */
62
- --light-text-tertiary: var(--light-text-muted);
63
- --light-text-quaternary: var(--light-text-subtle);
83
+ Measured WCAG contrast vs --light-bg-primary oklch(0.961 0 0.5), OKLCH ->
84
+ linear-sRGB -> WCAG relative-luminance pipeline, round-trip-verified
85
+ against 6 known hex references (packages/theme/docs/TOKEN-MAPPING.md §8).
86
+ Ratios re-measured 2026-07-17 (W3 §3.1) supersede prior approximate
87
+ inline values; vs --light-bg-accent (bg-panel, pure white) is always
88
+ HIGHER, so bg-body is the conservative floor for every tier below. */
89
+ --light-text-primary: oklch(0.22 0.015 260); /* 15.46:1 AAA (large+normal) */
90
+ --light-text-secondary: oklch(0.38 0.018 260); /* 8.94:1 ✓ AAA (large+normal) */
91
+ --light-text-muted: oklch(0.52 0.018 260); /* 4.92:1 ✓ AA (normal, 4.5:1 floor) */
92
+ --light-text-subtle: oklch(0.60 0.012 260); /* 3.52:1 ✓ AA large only (3:1) — ✗ AA normal */
93
+ --light-text-disabled: oklch(0.72 0.010 260); /* 2.21:1 ✗ fails AA at any size — MUST pair with icon cue */
64
94
 
65
95
  --light-border: oklch(0.942 0.005 247.9);
66
96
  --light-hover: oklch(0.963 0.003 228.9);
@@ -78,49 +108,62 @@
78
108
 
79
109
  /* Action colours */
80
110
  --role-primary: var(--neon-electric-blue); /* oklch(0.546 0.215 262.9) */
81
- --role-secondary: var(--neon-electric-indigo); /* oklch(0.511 0.23 277) */
111
+ --role-secondary: var(--neon-electric-deep); /* was indigo — 0.037 from primary */
82
112
  --role-accent: var(--neon-electric-cyan); /* oklch(0.769 0.132 191.7) */
83
113
 
84
- /* ⚠️ COLOURBLIND POLICY: error = PURPLE never red/coral.
85
- Red-vs-green is the most common colour-vision deficiency (~8% M, ~0.5% F).
86
- Purple (hue 283) is unambiguous to deuteranopes and protanopes.
114
+ /* ⚠️ SEPARATION POLICY (contract 0.7.0). The invariant is NOT a fixed hue —
115
+ it is that error must be maximally separated from PRIMARY under
116
+ deuteranopia and protanopia. D2C's primary is blue, so error is RED.
117
+ Purple scored DeltaE 0.006 against primary under protanopia: the
118
+ destructive colour and the primary action colour were the same swatch.
119
+ Purple and indigo are quarantined from semantics entirely.
120
+ Warm states separate by LIGHTNESS (under deuteranopia red and amber
121
+ both read yellow — error is the dark one, warning the light one).
87
122
  TENANT OVERRIDE BLOCKED: BrandingProvider rejects --role-error changes. */
88
- --role-error: var(--neon-electric-purple); /* oklch(0.568 0.202 283.1) */
89
- --role-destructive: var(--neon-electric-purple); /* synonym — same value */
90
- --role-warning: var(--neon-electric-orange); /* oklch(0.728 0.168 22.5) */
91
- --role-success: var(--neon-electric-green); /* oklch(0.723 0.192 149.6) */
123
+ --role-error: var(--neon-electric-red); /* was purple — 0.006 from primary */
124
+ --role-destructive: var(--neon-electric-red); /* synonym — same value */
125
+ --role-warning: var(--neon-electric-amber); /* was orange h22.5 0.059 from red */
126
+ --role-success: var(--neon-electric-teal); /* was green — 0.043 from red */
92
127
  --role-info: var(--neon-electric-cyan); /* oklch(0.769 0.132 191.7) */
93
128
 
94
- /* Text roles */
95
- --role-text-body: var(--light-text-primary);
96
- --role-text-secondary: var(--light-text-secondary);
97
- --role-text-muted: var(--light-text-muted);
98
- --role-text-subtle: var(--light-text-subtle);
99
- --role-text-disabled: var(--light-text-disabled);
100
- --role-text-heading: oklch(0.20 0.018 263); /* slightly darker + bluer — 14.5:1 ✓ AAA */
129
+ /* AA-safe text renderings of the saturated roles on light surfaces.
130
+ A saturated fill colour is not a legible text colour; these are the
131
+ text variants and are the ONLY correct token for coloured type. */
132
+ --role-primary-text: oklch(0.541 0.215 262.9); /* 4.71:1 ✓ AA */
133
+ --role-accent-text: oklch(0.499 0.132 191.7); /* 4.70:1 ✓ AA */
134
+ --role-error-text: oklch(0.556 0.230 25); /* 4.69:1 ✓ AA */
135
+ --role-warning-text: oklch(0.535 0.150 75); /* 4.70:1 ✓ AA */
136
+ --role-success-text: oklch(0.501 0.130 195); /* 4.70:1 ✓ AA */
137
+
138
+ /* Text roles — six-tier scale (W3 §3.1 contract). Every tier below is
139
+ measured against --role-bg-body (the conservative floor); all tiers
140
+ score HIGHER against --role-bg-panel/--role-bg-surface. */
141
+ --role-text-heading: oklch(0.20 0.018 263); /* 16.17:1 ✓ AAA (slightly darker + bluer than body) */
142
+ --role-text-body: var(--light-text-primary); /* 15.46:1 ✓ AAA */
143
+ --role-text-secondary: var(--light-text-secondary); /* 8.94:1 ✓ AAA */
144
+ --role-text-muted: var(--light-text-muted); /* 4.92:1 ✓ AA normal */
145
+ --role-text-subtle: var(--light-text-subtle); /* 3.52:1 ✓ AA large only */
146
+ --role-text-disabled: var(--light-text-disabled); /* 2.21:1 ✗ icon cue required */
101
147
 
102
148
  /* Surface roles */
103
- --role-bg-body: var(--light-bg-primary);
104
- --role-bg-surface: var(--light-bg-secondary);
105
- --role-bg-panel: var(--light-bg-accent);
106
- --role-bg-input: var(--light-bg-tertiary);
107
- --role-border: var(--light-border);
108
-
109
- /* Inverse text — safe on coloured fills (mid-lightness L≈0.5–0.7) */
110
- --text-on-primary: oklch(0.99 0 0); /* on blue L≈0.55 — 5.9:1 ✓ AA */
111
- --text-on-accent: oklch(0.17 0.02 260); /* on cyan L≈0.77 — 6.1:1 ✓ AA */
112
- --text-on-error: oklch(0.99 0 0); /* on purple L≈0.57 */
113
- --text-on-success: oklch(0.17 0.02 260); /* on green L≈0.72 — dark text */
114
- --text-on-warning: oklch(0.17 0.02 260); /* on amber L≈0.82 — dark text */
149
+ --role-bg-body: var(--light-bg-primary);
150
+ --role-bg-surface: var(--light-bg-secondary);
151
+ --role-bg-panel: var(--light-bg-accent);
152
+ --role-bg-input: var(--light-bg-tertiary);
153
+ --role-bg-sunken: oklch(0.938 0.004 250);
154
+ --role-border: var(--light-border);
155
+ --role-border-strong: oklch(0.880 0.008 250);
115
156
 
116
- /* ============================================================
117
- LAYER 3 SEMANTIC STATUS (legacy: keeps --status-* names)
118
- Deprecated: prefer --role-* going forward.
119
- ============================================================ */
120
- --status-success: var(--role-success);
121
- --status-warning: var(--role-warning);
122
- --status-error: var(--role-error); /* was coral; now purple per colourblind policy */
123
- --status-info: var(--role-info);
157
+ /* Inverse text on coloured fills. Two values only — the fill is either
158
+ light enough to need dark text, or dark enough to need light text.
159
+ Neither is a pure endpoint. */
160
+ --text-on-light-fill: oklch(0.98 0.006 260);
161
+ --text-on-dark-fill: oklch(0.19 0.020 255);
162
+ --text-on-primary: var(--text-on-light-fill); /* on blue L≈0.55 — 4.88:1 ✓ AA */
163
+ --text-on-error: var(--text-on-light-fill); /* on red L≈0.58 4.55:1 AA */
164
+ --text-on-accent: var(--text-on-dark-fill); /* on cyan L≈0.77 — 9.38:1 ✓ AAA */
165
+ --text-on-warning: var(--text-on-dark-fill); /* on amber L≈0.80 — 9.68:1 ✓ AAA */
166
+ --text-on-success: var(--text-on-dark-fill); /* on teal L≈0.60 — 5.17:1 ✓ AA */
124
167
 
125
168
  /* ============================================================
126
169
  LAYER 4 — SHADCN/UI BRIDGE (light mode)
@@ -148,11 +191,15 @@
148
191
  --ring: var(--role-primary);
149
192
 
150
193
  /* ============================================================
151
- WCAG AA compliant text pairs for raw neon on light bg
152
- (neon cyan L=0.77 on light bg is only 1.76:1 fails 4.5:1)
194
+ CANONICAL ACCENT GRADIENT (W3 §3.1) exactly ONE gradient
195
+ exists suite-wide. Marketing/pre-auth heroes ONLYnever on
196
+ functional text (labels, body copy, buttons). Consumed via the
197
+ `.bsuite-accent-gradient` utility (utilities.css). App-local
198
+ decorative gradients (e.g. crm7's `.crm7-gradient`) are
199
+ quarantined and must not be used for new work — see
200
+ docs/TOKEN-MAPPING.md §8.
153
201
  ============================================================ */
154
- --color-accent-text: oklch(0.486 0.084 191.5); /* Dark cyan — AA on light bg */
155
- --color-primary-text: oklch(0.485 0.243 263.6); /* Saturated blue — AA on light bg */
202
+ --gradient-accent: linear-gradient(135deg, var(--role-primary), var(--role-accent));
156
203
 
157
204
  /* ============================================================
158
205
  GLOW + SHADOW MULTIPLIERS
@@ -169,29 +216,75 @@
169
216
  --card-glow-source: var(--app-accent, var(--role-accent));
170
217
  --glow-card: none;
171
218
  --glow-card-hover: none;
219
+ }
172
220
 
173
- /* ============================================================
174
- SRGB FALLBACKS (for browsers without oklch support)
175
- Tested against Chrome 105-. Values are closest sRGB equivalents.
176
- ============================================================ */
177
- @supports not (color: oklch(0 0 0)) {
178
- --neon-electric-blue: #3b82f6;
179
- --neon-electric-cyan: #06b6d4;
180
- --neon-electric-indigo: #6366f1;
181
- --neon-electric-purple: #a855f7;
182
- --neon-electric-magenta: #f472b6;
183
- --neon-electric-pink: #ec4899;
184
- --neon-electric-coral: #f87171;
185
- --neon-electric-orange: #f97316;
186
- --neon-electric-yellow: #fbbf24;
187
- --neon-electric-green: #22c55e;
188
- --neon-electric-lavender: #c084fc;
189
- --role-error: #a855f7;
190
- --role-destructive: #a855f7;
191
- --light-text-primary: #1e1f2e;
192
- --dark-text-primary: #e8eaf6;
193
- }
221
+ /* ============================================================
222
+ LAYER 5 PER-APP ACCENT
223
+ Added 2026-08-02: the 0.7.0 contract specifies these blocks but the
224
+ package had never shipped them — only a `var(--app-accent, …)` fallback
225
+ reference, so every app resolved to the shared cyan and the per-app
226
+ identity existed on paper only.
227
+
228
+ Set `data-app` on <body>. The FILL is the brand hue; the TEXT variants
229
+ are solved per mode, because a saturated fill colour is not a legible
230
+ text colour and the accent must never render below AA.
231
+ ============================================================ */
232
+ [data-app="crm7"] {
233
+ --app-accent: oklch(0.769 0.132 191.7);
234
+ --app-accent-text-l: oklch(0.505 0.132 191.7);
235
+ --app-accent-text-d: oklch(0.769 0.132 191.7);
236
+ }
237
+ [data-app="bsu"] {
238
+ --app-accent: oklch(0.541 0.247 293);
239
+ --app-accent-text-l: oklch(0.564 0.247 293);
240
+ --app-accent-text-d: oklch(0.745 0.247 293);
194
241
  }
242
+ [data-app="conduit"] {
243
+ --app-accent: oklch(0.723 0.192 149.6);
244
+ --app-accent-text-l: oklch(0.505 0.192 149.6);
245
+ --app-accent-text-d: oklch(0.723 0.192 149.6);
246
+ }
247
+ [data-app="r80"] {
248
+ --app-accent: oklch(0.666 0.157 58.3);
249
+ --app-accent-text-l: oklch(0.546 0.157 58.3);
250
+ --app-accent-text-d: oklch(0.703 0.157 58.3);
251
+ }
252
+ /* R8.04 — the rates engine replacing R80.3. Deliberately REUSES r80's
253
+ orange: it serves the same users doing the same job, and a rates engine
254
+ is not where people should be re-learning which app they are in.
255
+ Requested by the r804 lane 2026-08-02. */
256
+ [data-app="r804"] {
257
+ --app-accent: oklch(0.666 0.157 58.3);
258
+ --app-accent-text-l: oklch(0.546 0.157 58.3);
259
+ --app-accent-text-d: oklch(0.703 0.157 58.3);
260
+ }
261
+ [data-app="throughput"] {
262
+ --app-accent: oklch(0.656 0.212 354.3);
263
+ --app-accent-text-l: oklch(0.564 0.212 354.3);
264
+ --app-accent-text-d: oklch(0.731 0.212 354.3);
265
+ }
266
+ /* Light/dark rebinding of the text variant. */
267
+ :root { --app-accent-text: var(--app-accent-text-l); }
268
+ .dark { --app-accent-text: var(--app-accent-text-d); }
269
+
270
+ /* ============================================================
271
+ NO sRGB FALLBACK — DELIBERATELY REMOVED 2026-08-02.
272
+ Precedent: precedent__bsuite__20260802__theme_conformance_is_absolute.
273
+
274
+ The `@supports not (color: oklch(0 0 0))` block that stood here shipped
275
+ a parallel hex palette. It is gone, and must not be reinstated:
276
+
277
+ · OKLCH has been Baseline-available in every evergreen engine since
278
+ Firefox 113 (May 2023), after Safari 15.4 (2022) and Chrome 111
279
+ (Mar 2023). It guarded a population that no longer exists.
280
+ · It was a second source of truth for the same colours, so it drifted:
281
+ it still bound --role-error to purple after the semantic set moved
282
+ to red. A fallback that disagrees with the tokens is worse than none.
283
+
284
+ If a colour needs to exist in hex (email HTML, PDF, canvas, manifest),
285
+ derive it at that call site from the oklch token — do not re-introduce a
286
+ second palette here.
287
+ ============================================================ */
195
288
 
196
289
  /* ============================================================
197
290
  DARK MODE — Layer 2 surface overrides + Layer 3 role rebind
@@ -207,37 +300,41 @@
207
300
  /* D2C dark text scale — hue 260, capped L=0.94.
208
301
  Pure white (L=1.0) is BANNED as a text token.
209
302
  Eye-strain: 16:1 → ~14:1; perceived glare drops significantly.
210
- Verified WCAG AA on --dark-bg-primary oklch(0.166 0.026 269.4). */
211
- --dark-text-primary: oklch(0.94 0.012 260); /* body — ~13.9:1 AAA */
212
- --dark-text-secondary: oklch(0.82 0.015 260); /* labels — ~9.8:1 AAA */
213
- --dark-text-muted: oklch(0.68 0.018 260); /* metadata — ~5.6:1 AA */
214
- --dark-text-subtle: oklch(0.56 0.015 260); /* placeholders — ~3.6:1 ✓ AA large */
215
- --dark-text-disabled: oklch(0.44 0.010 260); /* disabled pair with icon cue */
216
- /* Legacy aliases */
217
- --dark-text-tertiary: var(--dark-text-muted);
218
- --dark-text-quaternary: var(--dark-text-disabled);
303
+ Measured WCAG contrast vs --dark-bg-primary oklch(0.166 0.026 269.4)
304
+ via the same OKLCH -> linear-sRGB -> WCAG pipeline as the light scale
305
+ above (re-measured 2026-07-17, W3 §3.1 supersedes prior approximate
306
+ inline values). vs --role-bg-surface/--role-bg-panel is always HIGHER,
307
+ so bg-body is the conservative floor. */
308
+ --dark-text-primary: oklch(0.94 0.012 260); /* body 16.16:1 AAA */
309
+ --dark-text-secondary: oklch(0.82 0.015 260); /* labels — 11.04:1 ✓ AAA */
310
+ --dark-text-muted: oklch(0.68 0.018 260); /* metadata — 6.69:1 ✓ AA (normal) */
311
+ --dark-text-subtle: oklch(0.56 0.015 260); /* placeholders — 4.14:1 ✓ AA large only — ✗ AA normal */
312
+ --dark-text-disabled: oklch(0.44 0.010 260); /* disabled — 2.48:1 ✗ fails AA at any size — MUST pair with icon cue */
219
313
 
220
314
  --dark-border: oklch(0.428 0.015 248.2);
221
315
  --dark-hover: oklch(0.39 0.035 265);
222
316
 
223
- /* Rebind roles to dark-mode surface values */
224
- --role-text-body: var(--dark-text-primary);
225
- --role-text-secondary: var(--dark-text-secondary);
226
- --role-text-muted: var(--dark-text-muted);
227
- --role-text-subtle: var(--dark-text-subtle);
228
- --role-text-disabled: var(--dark-text-disabled);
229
- --role-text-heading: oklch(0.92 0.014 260); /* ~12.8:1 AAA */
317
+ /* Rebind roles to dark-mode surface values — six-tier scale (W3 §3.1). */
318
+ --role-text-heading: oklch(0.92 0.014 260); /* 15.21:1 ✓ AAA */
319
+ --role-text-body: var(--dark-text-primary); /* 16.16:1 ✓ AAA */
320
+ --role-text-secondary: var(--dark-text-secondary); /* 11.04:1 ✓ AAA */
321
+ --role-text-muted: var(--dark-text-muted); /* 6.69:1 ✓ AA normal */
322
+ --role-text-subtle: var(--dark-text-subtle); /* 4.14:1 ✓ AA large only */
323
+ --role-text-disabled: var(--dark-text-disabled); /* 2.48:1 icon cue required */
230
324
 
231
325
  --role-bg-body: var(--dark-bg-primary);
232
326
  --role-bg-surface: var(--dark-bg-secondary);
233
- --role-bg-panel: oklch(0.19 0.02 260 / 0.8);
234
- --role-bg-input: oklch(0.24 0.02 260 / 0.6);
327
+ --role-bg-panel: oklch(0.190 0.020 260);
328
+ --role-bg-input: oklch(0.240 0.020 260);
329
+ --role-bg-sunken: oklch(0.145 0.022 268);
235
330
  --role-border: var(--dark-border);
236
331
 
237
- /* Accent/primary text tokens flip to raw neons in dark mode
238
- contrast on navy bg is already ≥ 9:1 for these values. */
239
- --color-accent-text: oklch(0.769 0.132 191.7);
240
- --color-primary-text: oklch(0.623 0.188 259.8);
332
+ /* Role text variants rebind for the dark surface all AAA on navy. */
333
+ --role-primary-text: oklch(0.721 0.215 262.9); /* 7.01:1 AAA */
334
+ --role-accent-text: var(--neon-electric-cyan); /* 9.78:1 ✓ AAA */
335
+ --role-error-text: oklch(0.786 0.230 25); /* 7.00:1 ✓ AAA */
336
+ --role-warning-text: var(--neon-electric-amber);/* 10.10:1 ✓ AAA */
337
+ --role-success-text: oklch(0.671 0.130 195); /* 6.99:1 ✓ AAA */
241
338
 
242
339
  /* Glow/shadow scale up in dark mode */
243
340
  --glow-strength: 0.6;