@stnd/styles 0.5.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.
@@ -0,0 +1,683 @@
1
+ @use "standard-00-variables" as *;
2
+
3
+ /* ==========================================================================
4
+ STANDARD COLOR SYSTEM — Seed-First Architecture
5
+
6
+ Two seeds → two palettes → one semantic layer
7
+
8
+ The system works in four stages:
9
+ 1. SEEDS: Light & dark foreground/background (set in tokens or themes)
10
+ 2. PALETTES: Generate full light & dark color spectrums via relative color syntax
11
+ 3. SEMANTICS: Map active palette to semantic tokens based on scheme
12
+ 4. COMPUTED: Derive surfaces, shadows, borders from scheme-aware tokens
13
+
14
+ Override strategy (pick your depth):
15
+ ┌─────────────────────────────────────────────────────────────────────┐
16
+ │ Minimal: Set --color-light-foreground → everything else follows │
17
+ │ Per-scheme: Set --color-light-* and/or --color-dark-* tokens │
18
+ │ Full: Theme via [data-theme] selectors (highest specificity) │
19
+ └─────────────────────────────────────────────────────────────────────┘
20
+
21
+ Hue wheel (offsets from foreground hue):
22
+ Red +25° · Orange +55° · Yellow +85° · Green +145°
23
+ Cyan +190° · Blue +240° · Magenta +300°
24
+
25
+ How it works:
26
+ Each palette color is a single oklch(from <seed> ...) call.
27
+ Channel keywords l, c, h are resolved INSIDE the function — no
28
+ intermediate extraction needed. Safety clamps are applied inline:
29
+ • clamp(0.35, l, 0.65) — lightness for WCAG AA on light bg
30
+ • max(0.12, c) — chroma floor prevents gray alerts
31
+ • calc(h + offset) — hue rotation from seed
32
+
33
+ For achromatic seeds (h = none), CSS Color Level 5 §4.3 says
34
+ "none" is treated as 0 inside calc(), so the palette maps to
35
+ standard color positions (red ≈ 25°, blue ≈ 240°, etc.).
36
+
37
+ @since 0.2.0
38
+ @see AGENTS.md § OKLCH Color System
39
+ ========================================================================== */
40
+
41
+ #{$stnd-theme-scope} {
42
+ /* Tell browser we support both color schemes */
43
+ color-scheme: light dark;
44
+
45
+ /* ===================================================================
46
+ STAGE 1: DARK SEED DERIVATION
47
+ The ink's soul carries into darkness.
48
+ Both dark seeds inherit hue from the light foreground — warm brown
49
+ ink begets a warm dark background and a warm soft foreground.
50
+ Themes override --color-dark-background / --color-dark-foreground
51
+ at [data-theme] specificity for a distinct dark mood.
52
+
53
+ Light fg #442f1c → oklch(0.30, 0.07, H≈60) warm brown ink
54
+ Dark bg derived → oklch(0.20, 0.02, H≈60) same hue, very dark, muted
55
+ Dark fg derived → oklch(0.85, 0.04, H≈60) same hue, soft reading brightness
56
+ =================================================================== */
57
+
58
+ /* Dark background: foreground's hue at very low lightness, muted chroma */
59
+ --color-dark-background: oklch(
60
+ from var(--color-light-foreground) 0.2
61
+ /* deep dark — not void-black, just dark enough */ min(c, 0.02)
62
+ /* muted — a whisper of the ink's chroma */ h
63
+ /* same hue — warm ink → warm dark */
64
+ );
65
+
66
+ /* Dark foreground: foreground's hue at comfortable reading brightness */
67
+ --color-dark-foreground: oklch(
68
+ from var(--color-light-foreground) 0.85
69
+ /* soft — easy on the eyes, not blinding white */ min(c, 0.04)
70
+ /* gentle — enough color to feel the warmth */ h
71
+ /* same hue — the vibe persists */
72
+ );
73
+
74
+ /* ===================================================================
75
+ STAGE 2: LIGHT PALETTE
76
+ Full spectrum generated from light foreground seed via inline
77
+ relative color syntax. Each color is a self-contained transform:
78
+ oklch(from <seed> <safe-L> <safe-C> calc(h + offset))
79
+
80
+ Channel keywords l, c, h resolve inside the function scope.
81
+ No extraction into intermediate custom properties needed.
82
+ Safety: clamp(0.65, l, 0.70) for bright vibrant tones, max(0.12, c) for saturation.
83
+ Achromatic seeds (h = none) → none treated as 0 in calc() per spec.
84
+ =================================================================== */
85
+
86
+ /* Error / Red — foreground hue + 25° */
87
+ --color-light-red: oklch(
88
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
89
+ calc(h + 25)
90
+ );
91
+ /* Attention / Orange — foreground hue + 55° */
92
+ --color-light-orange: oklch(
93
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
94
+ calc(h + 55)
95
+ );
96
+ /* Warning / Yellow — foreground hue + 85° */
97
+ --color-light-yellow: oklch(
98
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
99
+ calc(h + 85)
100
+ );
101
+ /* Success / Green — foreground hue + 145° */
102
+ --color-light-green: oklch(
103
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
104
+ calc(h + 145)
105
+ );
106
+ /* Info / Cyan — foreground hue + 190° */
107
+ --color-light-cyan: oklch(
108
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
109
+ calc(h + 190)
110
+ );
111
+ /* Link / Blue — foreground hue + 240° */
112
+ --color-light-blue: oklch(
113
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
114
+ calc(h + 240)
115
+ );
116
+ /* Magic / Magenta — foreground hue + 300° */
117
+ --color-light-magenta: oklch(
118
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
119
+ calc(h + 300)
120
+ );
121
+ /* Purple — foreground hue + 270° */
122
+ --color-light-purple: oklch(
123
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
124
+ calc(h + 270)
125
+ );
126
+ /* Pink — foreground hue + 350° */
127
+ --color-light-pink: oklch(
128
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
129
+ calc(h + 350)
130
+ );
131
+ /* Brown — foreground hue - 15° */
132
+ --color-light-brown: oklch(
133
+ from var(--color-light-foreground) clamp(0.65, l, 0.7) max(0.12, c)
134
+ calc(h - 15)
135
+ );
136
+
137
+ /* Link color defaults to blue */
138
+ --color-light-link: var(--color-light-accent);
139
+
140
+ /* Auto-accent strategies */
141
+ /* 1. Complementary (Opposite side of color wheel, +180°) - High contrast */
142
+ --color-light-accent-complementary: oklch(
143
+ from var(--color-light-foreground) calc(l + (0.65 - l) * min(1, c * 10))
144
+ min(0.2, c * 2) calc(h + 180)
145
+ );
146
+
147
+ /* 2. Analogous (Next to the seed color, +30°) - Harmonious, less jarring */
148
+ --color-light-accent-analogous: oklch(
149
+ from var(--color-light-foreground) calc(l + (0.65 - l) * min(1, c * 10))
150
+ min(0.2, c * 2) calc(h + 30)
151
+ );
152
+
153
+ /* 3. Triadic (Third of the way around, +120°) - Balanced contrast */
154
+ --color-light-accent-triadic: oklch(
155
+ from var(--color-light-foreground) calc(l + (0.65 - l) * min(1, c * 10))
156
+ min(0.2, c * 2) calc(h + 120)
157
+ );
158
+
159
+ /* Default to a slightly shifted analogous/triadic blend instead of pure 180 opposite
160
+ which can be jarring if the foreground is highly saturated.
161
+ +120 gives a strong but often more pleasing result than +180. */
162
+ --color-light-accent-auto: var(--color-light-accent-triadic);
163
+ --color-light-accent: var(--color-light-accent-auto);
164
+
165
+ /* ===================================================================
166
+ STAGE 2B: DARK PALETTE
167
+ Full spectrum generated from dark foreground seed.
168
+ NOT a simple lightening of the light palette — independent vibe.
169
+ A warm dark-foreground seed gives warm darks. A cool one gives
170
+ cool darks. Two different seeds, two different moods.
171
+ Safety: clamp(0.55, l, 0.70) for softer tones on dark surfaces.
172
+ =================================================================== */
173
+
174
+ /* Error / Red — dark foreground hue + 25° */
175
+ --color-dark-red: oklch(
176
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
177
+ calc(h + 25)
178
+ );
179
+ /* Attention / Orange — dark foreground hue + 55° */
180
+ --color-dark-orange: oklch(
181
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
182
+ calc(h + 55)
183
+ );
184
+ /* Warning / Yellow — dark foreground hue + 85° */
185
+ --color-dark-yellow: oklch(
186
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
187
+ calc(h + 85)
188
+ );
189
+ /* Success / Green — dark foreground hue + 145° */
190
+ --color-dark-green: oklch(
191
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
192
+ calc(h + 145)
193
+ );
194
+ /* Info / Cyan — dark foreground hue + 190° */
195
+ --color-dark-cyan: oklch(
196
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
197
+ calc(h + 190)
198
+ );
199
+ /* Link / Blue — dark foreground hue + 240° */
200
+ --color-dark-blue: oklch(
201
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
202
+ calc(h + 240)
203
+ );
204
+ /* Magic / Magenta — dark foreground hue + 300° */
205
+ --color-dark-magenta: oklch(
206
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
207
+ calc(h + 300)
208
+ );
209
+ /* Purple — dark foreground hue + 270° */
210
+ --color-dark-purple: oklch(
211
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
212
+ calc(h + 270)
213
+ );
214
+ /* Pink — dark foreground hue + 350° */
215
+ --color-dark-pink: oklch(
216
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
217
+ calc(h + 350)
218
+ );
219
+ /* Brown — dark foreground hue - 15° */
220
+ --color-dark-brown: oklch(
221
+ from var(--color-dark-foreground) clamp(0.55, l, 0.7) max(0.12, c)
222
+ calc(h - 15)
223
+ );
224
+
225
+ /* Link color defaults to blue */
226
+ --color-dark-link: var(--color-dark-accent);
227
+
228
+ /* Auto-accent strategies for dark mode */
229
+ --color-dark-accent-complementary: oklch(
230
+ from var(--color-dark-foreground) calc(l + (0.7 - l) * min(1, c * 10))
231
+ min(0.2, c * 2) calc(h + 180)
232
+ );
233
+
234
+ --color-dark-accent-analogous: oklch(
235
+ from var(--color-dark-foreground) calc(l + (0.7 - l) * min(1, c * 10))
236
+ min(0.2, c * 2) calc(h + 30)
237
+ );
238
+
239
+ --color-dark-accent-triadic: oklch(
240
+ from var(--color-dark-foreground) calc(l + (0.7 - l) * min(1, c * 10))
241
+ min(0.2, c * 2) calc(h + 120)
242
+ );
243
+
244
+ --color-dark-accent-auto: var(--color-dark-accent-triadic);
245
+ --color-dark-accent: var(--color-dark-accent-auto);
246
+ /* On-accent (text drawn on accent surfaces) */
247
+ --color-dark-on-accent: var(--color-dark-background);
248
+
249
+ /* ===================================================================
250
+ STAGE 2C: COMPUTED PALETTES
251
+ Always available for explicit "light-in-dark" or "dark-in-light" UI.
252
+ =================================================================== */
253
+
254
+ /* Light Computed Palette */
255
+ --color-light-muted: oklch(from var(--color-light-foreground) l c h / 0.6);
256
+ --color-light-subtle: oklch(from var(--color-light-foreground) l c h / 0.4);
257
+ --color-light-border: oklch(from var(--color-light-foreground) l c h / 0.1);
258
+ --color-light-surface: color-mix(
259
+ in srgb,
260
+ var(--color-light-foreground) 3%,
261
+ var(--color-light-background)
262
+ );
263
+ --color-light-on-accent: var(--color-light-background);
264
+
265
+ /* Dark Computed Palette */
266
+ --color-dark-muted: oklch(from var(--color-dark-foreground) l c h / 0.65);
267
+ --color-dark-subtle: oklch(from var(--color-dark-foreground) l c h / 0.45);
268
+ --color-dark-border: oklch(
269
+ from var(--color-dark-background) calc(l * 0.25) calc(c * 0.5)
270
+ calc(h - 15) / 0.25
271
+ );
272
+ --color-dark-surface: color-mix(
273
+ in srgb,
274
+ var(--color-dark-foreground) 3%,
275
+ var(--color-dark-background)
276
+ );
277
+
278
+ /* Light Shadow Palette */
279
+ --color-light-shadow-base: oklch(
280
+ from var(--color-light-foreground) calc(l * 0.25) calc(c * 0.5)
281
+ calc(h - 15) / 0.075
282
+ );
283
+ --color-light-highlight: oklch(
284
+ from var(--color-light-background) min(1, calc(l + 0.05)) c h / 0.6
285
+ );
286
+
287
+ /* Dark Shadow Palette */
288
+ --color-dark-shadow-base: oklch(
289
+ from var(--color-dark-background) calc(l * 0.25) calc(c * 0.5)
290
+ calc(h - 15) / 0.5
291
+ );
292
+ --color-dark-highlight: oklch(
293
+ from var(--color-dark-foreground) min(1, calc(l + 0.05)) c h / 0.11
294
+ );
295
+
296
+ /* ===================================================================
297
+ STAGE 3: SEMANTIC TOKENS (Default → Light Palette)
298
+ These are the tokens components actually consume.
299
+ Never reference --color-light-* or --color-dark-* in components.
300
+ Always use --color-red, --color-background, etc.
301
+ =================================================================== */
302
+ --color-background: var(--color-light-background);
303
+ --color-foreground: var(--color-light-foreground);
304
+ --color-accent: var(--color-light-accent);
305
+
306
+ --color-header: var(--color-light-header, var(--color-foreground));
307
+
308
+ --color-red: var(--color-light-red);
309
+ --color-orange: var(--color-light-orange);
310
+ --color-yellow: var(--color-light-yellow);
311
+ --color-green: var(--color-light-green);
312
+ --color-cyan: var(--color-light-cyan);
313
+ --color-blue: var(--color-light-blue);
314
+ --color-magenta: var(--color-light-magenta);
315
+ --color-purple: var(--color-light-purple);
316
+ --color-pink: var(--color-light-pink);
317
+ --color-brown: var(--color-light-brown);
318
+
319
+ /* Semantic status aliases */
320
+ --color-success: var(--color-green);
321
+ --color-warning: var(--color-orange);
322
+ --color-error: var(--color-red);
323
+ --color-info: var(--color-blue);
324
+
325
+ --color-link: var(--color-light-link);
326
+ --color-italic: var(--color-light-italic, var(--color-foreground));
327
+ --color-bold: var(--color-light-bold, var(--color-foreground));
328
+ --color-on-accent: var(--color-background);
329
+
330
+ /* ===================================================================
331
+ STAGE 4: COMPUTED SEMANTIC COLORS
332
+ Derived from the scheme-aware --color-foreground/--color-background.
333
+ auto-adapt when the scheme switches.
334
+ =================================================================== */
335
+ --color-muted: var(--color-light-muted);
336
+ --color-subtle: var(--color-light-subtle);
337
+ --color-border: var(--color-light-border);
338
+ --color-surface: var(--color-light-surface);
339
+
340
+ /* Light/dark polarity aliases — always "the lighter" and "the darker" */
341
+ --color-light: var(--color-light-background);
342
+ --color-dark: var(--color-light-foreground);
343
+ --color-darker: oklch(from var(--color-dark) l c h / 0.15);
344
+
345
+ --color-glass: oklch(from var(--color-background) l c h / 0.8);
346
+
347
+ /* ===================================================================
348
+ STAGE 5: SHADOWS & SURFACES
349
+ OKLCH-based for perceptually uniform elevation.
350
+ All derived from semantic pointers — auto-adapt to dark mode.
351
+ =================================================================== */
352
+
353
+ /* Semantic Shadow Seeds */
354
+ --color-shadow: var(--color-light-shadow-base);
355
+ --color-highlight: var(--color-light-highlight);
356
+
357
+ /* Shadow building blocks — compose freely */
358
+ --shadow-ambient: 0 1px 1px var(--color-shadow); /* Felt more than seen */
359
+
360
+ /* This one pop a bit */
361
+ --shadow-lift:
362
+ 0 4px 6px -1px var(--color-shadow), 0 2px 4px -2px var(--color-shadow);
363
+ /* Organicaly grows */
364
+ --shadow-glow: 0 4px var(--space) oklch(from var(--color-shadow) l c h / 0.15);
365
+ --shadow-inset:
366
+ inset 0 1px 3px var(--color-shadow), inset 0 -1px 0 0 var(--color-highlight);
367
+ --shadow-ring:
368
+ inset 0px 1px 1px var(--color-border),
369
+ inset 1px 0px 1px var(--color-border),
370
+ inset -1px 0px 1px var(--color-border),
371
+ inset 0px -1px 1px var(--color-border);
372
+
373
+ /* shadcn-style raised effect: inner highlight + border + shadow */
374
+ --shadow-raised:
375
+ inset 0 1px 0 0 var(--color-highlight), 0 0 0 1px var(--color-border),
376
+ var(--shadow-ambient);
377
+
378
+ /* Composed presets */
379
+ --shadow: var(--shadow-ambient), var(--shadow-ambient);
380
+ --shadow-lg: var(--shadow-ambient), var(--shadow-lift);
381
+ --shadow-xl: var(--shadow-ambient), var(--shadow-lift), var(--shadow-glow);
382
+
383
+ /* Surface elevation scale */
384
+ --color-surface-low: color-mix(
385
+ in srgb,
386
+ var(--color-foreground) 3%,
387
+ var(--color-surface)
388
+ );
389
+ --color-surface-lowest: color-mix(
390
+ in srgb,
391
+ var(--color-foreground) 3%,
392
+ var(--color-surface-low)
393
+ );
394
+ --color-surface-lower: color-mix(
395
+ in srgb,
396
+ var(--color-foreground) 3%,
397
+ transparent
398
+ );
399
+
400
+ --color-surface-high: color-mix(in srgb, white 50%, var(--color-surface));
401
+ --color-surface-highest: color-mix(
402
+ in srgb,
403
+ white 50%,
404
+ var(--color-surface-high)
405
+ );
406
+ --color-surface-higher: color-mix(in srgb, white 50%, transparent);
407
+ /* ===================================================================
408
+ STAGE 6: DESIGN TOKENS (Border Shorthands)
409
+ =================================================================== */
410
+ --border: 0 solid transparent;
411
+ --border-transparent: var(--stroke-width) solid transparent;
412
+ --border-accent: var(--stroke-width) solid
413
+ oklch(from var(--color-accent) l c h / 0.07);
414
+ --border: none;
415
+ }
416
+
417
+ /* =====================================================================
418
+ STAGE 7: DARK MODE SWITCH
419
+ System preference flips semantic tokens to dark palette.
420
+ Computed semantics (muted, subtle, border, surfaces, shadows)
421
+ auto-adapt because they reference scheme-aware --color-foreground
422
+ and --color-background.
423
+ ===================================================================== */
424
+ @media (prefers-color-scheme: dark) {
425
+ #{$stnd-theme-scope} {
426
+ /* Core semantic tokens → dark palette */
427
+ --color-background: var(--color-dark-background);
428
+ --color-foreground: var(--color-dark-foreground);
429
+ --color-accent: var(--color-dark-accent);
430
+
431
+ /* Polarity flip: in dark mode, "light" = foreground, "dark" = background */
432
+ --color-light: var(--color-dark-foreground);
433
+ --color-dark: var(--color-dark-background);
434
+
435
+ --color-header: var(--color-dark-header, var(--color-foreground));
436
+ --color-on-accent: var(--color-background);
437
+
438
+ /* Hue tokens → dark palette */
439
+ --color-red: var(--color-dark-red);
440
+ --color-orange: var(--color-dark-orange);
441
+ --color-yellow: var(--color-dark-yellow);
442
+ --color-green: var(--color-dark-green);
443
+ --color-cyan: var(--color-dark-cyan);
444
+ --color-blue: var(--color-dark-blue);
445
+ --color-magenta: var(--color-dark-magenta);
446
+
447
+ --color-link: var(--color-dark-link);
448
+ --color-italic: var(--color-dark-italic, var(--color-foreground));
449
+ --color-bold: var(--color-dark-bold, var(--color-foreground));
450
+
451
+ /* Adjusted opacity for dark backgrounds */
452
+ --color-subtle: var(--color-dark-subtle);
453
+ --color-muted: var(--color-dark-muted);
454
+ --color-border: var(--color-dark-border);
455
+ --color-surface: var(--color-dark-surface);
456
+
457
+ --color-shadow: var(--color-dark-shadow-base);
458
+ --color-highlight: var(--color-dark-highlight);
459
+
460
+ --shadow-xl: var(--shadow-glow);
461
+
462
+ /* Surface elevation scale */
463
+
464
+ --color-surface-low: color-mix(in srgb, black 3%, var(--color-surface));
465
+ --color-surface-lowest: color-mix(
466
+ in srgb,
467
+ black 7%,
468
+ var(--color-surface-low)
469
+ );
470
+ --color-surface-lower: color-mix(in srgb, black 7%, transparent);
471
+
472
+ --color-surface-high: color-mix(
473
+ in srgb,
474
+ var(--color-dark-foreground) 3%,
475
+ var(--color-surface)
476
+ );
477
+ --color-surface-highest: color-mix(
478
+ in srgb,
479
+ var(--color-dark-foreground) 3%,
480
+ var(--color-surface-high)
481
+ );
482
+ --color-surface-higher: color-mix(
483
+ in srgb,
484
+ var(--color-dark-foreground) 3%,
485
+ transparent
486
+ );
487
+ }
488
+ }
489
+
490
+ /* =====================================================================
491
+ STAGE 7B: THEME UTILITIES (.inverse, .theme-dark, .theme-light)
492
+ Provides explicit control and local context switching.
493
+ ===================================================================== */
494
+
495
+ @mixin theme-light-tokens {
496
+ --color-background: var(--color-light-background);
497
+ --color-foreground: var(--color-light-foreground);
498
+ --color-accent: var(--color-light-accent);
499
+ --color-muted: var(--color-light-muted);
500
+ --color-subtle: var(--color-light-subtle);
501
+ --color-border: var(--color-light-border);
502
+ --color-surface: var(--color-light-surface);
503
+ --color-shadow: var(--color-light-shadow-base);
504
+ --color-highlight: var(--color-light-highlight);
505
+ --color-light: var(--color-light-background);
506
+ --color-dark: var(--color-light-foreground);
507
+ color-scheme: light;
508
+ }
509
+
510
+ @mixin theme-dark-tokens {
511
+ --color-background: var(--color-dark-background);
512
+ --color-foreground: var(--color-dark-foreground);
513
+ --color-accent: var(--color-dark-accent);
514
+ --color-muted: var(--color-dark-muted);
515
+ --color-subtle: var(--color-dark-subtle);
516
+ --color-border: var(--color-dark-border);
517
+ --color-surface: var(--color-dark-surface);
518
+ --color-shadow: var(--color-dark-shadow-base);
519
+ --color-highlight: var(--color-dark-highlight);
520
+ --color-light: var(--color-dark-foreground);
521
+ --color-dark: var(--color-dark-background);
522
+ color-scheme: dark;
523
+ }
524
+
525
+ .theme-light {
526
+ @include theme-light-tokens;
527
+ }
528
+
529
+ .theme-dark {
530
+ @include theme-dark-tokens;
531
+ }
532
+
533
+ /* =====================================================================
534
+ STAGE 7C: CONTAINER THEMING
535
+ Re-resolves semantic tokens at element level so any element with
536
+ [data-theme] gets full color resolution — not just <html>.
537
+ Enables: themed cards, themed panels, side-by-side theme comparisons,
538
+ and scoped theme previews without changing the page chrome.
539
+ ===================================================================== */
540
+
541
+ [data-theme] {
542
+ @include theme-light-tokens;
543
+
544
+ /* Hue tokens (not in the mixin) */
545
+ --color-header: var(--color-light-header, var(--color-foreground));
546
+ --color-red: var(--color-light-red);
547
+ --color-orange: var(--color-light-orange);
548
+ --color-yellow: var(--color-light-yellow);
549
+ --color-green: var(--color-light-green);
550
+ --color-cyan: var(--color-light-cyan);
551
+ --color-blue: var(--color-light-blue);
552
+ --color-magenta: var(--color-light-magenta);
553
+ --color-purple: var(--color-light-purple);
554
+ --color-pink: var(--color-light-pink);
555
+ --color-brown: var(--color-light-brown);
556
+ --color-link: var(--color-light-link);
557
+ --color-italic: var(--color-light-italic, var(--color-foreground));
558
+ --color-bold: var(--color-light-bold, var(--color-foreground));
559
+ }
560
+
561
+ @media (prefers-color-scheme: dark) {
562
+ [data-theme]:not([data-color-mode="light"]) {
563
+ @include theme-dark-tokens;
564
+
565
+ --color-header: var(--color-dark-header, var(--color-foreground));
566
+ --color-red: var(--color-dark-red);
567
+ --color-orange: var(--color-dark-orange);
568
+ --color-yellow: var(--color-dark-yellow);
569
+ --color-green: var(--color-dark-green);
570
+ --color-cyan: var(--color-dark-cyan);
571
+ --color-blue: var(--color-dark-blue);
572
+ --color-magenta: var(--color-dark-magenta);
573
+ --color-link: var(--color-dark-link);
574
+ --color-italic: var(--color-dark-italic, var(--color-foreground));
575
+ --color-bold: var(--color-dark-bold, var(--color-foreground));
576
+ }
577
+ }
578
+
579
+ [data-color-mode="dark"] [data-theme],
580
+ [data-color-mode="dark"][data-theme] {
581
+ @include theme-dark-tokens;
582
+
583
+ --color-header: var(--color-dark-header, var(--color-foreground));
584
+ --color-red: var(--color-dark-red);
585
+ --color-orange: var(--color-dark-orange);
586
+ --color-yellow: var(--color-dark-yellow);
587
+ --color-green: var(--color-dark-green);
588
+ --color-cyan: var(--color-dark-cyan);
589
+ --color-blue: var(--color-dark-blue);
590
+ --color-magenta: var(--color-dark-magenta);
591
+ --color-link: var(--color-dark-link);
592
+ --color-italic: var(--color-dark-italic, var(--color-foreground));
593
+ --color-bold: var(--color-dark-bold, var(--color-foreground));
594
+ }
595
+
596
+ [data-color-mode="light"] [data-theme],
597
+ [data-color-mode="light"][data-theme] {
598
+ @include theme-light-tokens;
599
+
600
+ --color-header: var(--color-light-header, var(--color-foreground));
601
+ --color-red: var(--color-light-red);
602
+ --color-orange: var(--color-light-orange);
603
+ --color-yellow: var(--color-light-yellow);
604
+ --color-green: var(--color-light-green);
605
+ --color-cyan: var(--color-light-cyan);
606
+ --color-blue: var(--color-light-blue);
607
+ --color-magenta: var(--color-light-magenta);
608
+ --color-link: var(--color-light-link);
609
+ --color-italic: var(--color-light-italic, var(--color-foreground));
610
+ --color-bold: var(--color-light-bold, var(--color-foreground));
611
+ }
612
+
613
+ /* =====================================================================
614
+ STAGE 8: FALLBACKS — Graceful Degradation
615
+ Browsers without OKLCH support (Safari <16.4, Chrome <111, FF <113)
616
+ get concrete color values so the UI remains usable.
617
+ ===================================================================== */
618
+ @supports not (color: oklch(50% 0.2 180)) {
619
+ #{$stnd-theme-scope} {
620
+ /* Concrete dark seeds */
621
+ --color-dark-background: #232323;
622
+ --color-dark-foreground: #d0d0d0;
623
+
624
+ /* Light palette fallbacks — standard hue positions */
625
+ --color-light-red: #c44040;
626
+ --color-light-orange: #b87020;
627
+ --color-light-yellow: #9a8520;
628
+ --color-light-green: #3a8a50;
629
+ --color-light-cyan: #2a8a8a;
630
+ --color-light-blue: #3a60b0;
631
+ --color-light-magenta: #9040a0;
632
+ --color-light-accent: #2a8a8a;
633
+
634
+ /* Dark palette fallbacks */
635
+ --color-dark-red: #e06060;
636
+ --color-dark-orange: #d09040;
637
+ --color-dark-yellow: #c0a840;
638
+ --color-dark-green: #60b070;
639
+ --color-dark-cyan: #50b0b0;
640
+ --color-dark-blue: #6080d0;
641
+ --color-dark-magenta: #b060c0;
642
+ --color-dark-accent: #50b0b0;
643
+
644
+ /* Computed fallbacks */
645
+ --color-muted: rgba(0, 0, 0, 0.6);
646
+ --color-subtle: rgba(0, 0, 0, 0.4);
647
+ --color-border: rgba(0, 0, 0, 0.1);
648
+ --color-surface: rgba(0, 0, 0, 0.03);
649
+ --color-shadow: rgba(0, 0, 0, 0.25);
650
+ --color-highlight: rgba(255, 255, 255, 0.6);
651
+ }
652
+ }
653
+
654
+ /* =====================================================================
655
+ STAGE 9: HIGH CONTRAST — Windows / forced-colors mode
656
+ ===================================================================== */
657
+ @media (forced-colors: active) {
658
+ #{$stnd-theme-scope} {
659
+ --color-background: Canvas;
660
+ --color-foreground: CanvasText;
661
+ --color-accent: Highlight;
662
+ --color-border: CanvasText;
663
+ --color-muted: GrayText;
664
+ }
665
+ }
666
+
667
+ /* The Inverse Logic: Context-aware flipping */
668
+ .inverse {
669
+ /* Default: if root is light (either by system or choice), inverse is dark */
670
+ @include theme-dark-tokens;
671
+
672
+ /* If system is dark AND root hasn't forced light mode, root is dark, so inverse must be light */
673
+ @media (prefers-color-scheme: dark) {
674
+ #{$stnd-theme-scope}:not([data-color-mode="light"]) & {
675
+ @include theme-light-tokens;
676
+ }
677
+ }
678
+
679
+ /* If root explicitly forced dark mode, inverse must be light */
680
+ #{$stnd-theme-scope}[data-color-mode="dark"] & {
681
+ @include theme-light-tokens;
682
+ }
683
+ }