@tangle-network/brand 1.15.7 → 1.17.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/CHANGELOG.md +12 -0
- package/README.md +3 -1
- package/dist/index.d.ts +172 -1
- package/dist/index.js +138 -1
- package/package.json +4 -3
- package/src/index.ts +1 -0
- package/src/palette.generated.ts +174 -0
- package/src/palette.test.ts +25 -0
- package/src/styles/legacy-light.css +108 -108
- package/src/styles/named-themes.css +14 -14
- package/src/styles/tokens.css +92 -92
- package/src/styles/tokens.test.ts +50 -26
package/src/styles/tokens.css
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* an indigo page, and the eye stops finding them. Neutral planes give the brand
|
|
23
23
|
* a field to sit on, so a single accent does all the signalling.
|
|
24
24
|
*
|
|
25
|
-
* The canvas stays above pure black (#
|
|
25
|
+
* The canvas stays above pure black (#0a0a14, lifted toward indigo). Near-white ink on it is the
|
|
26
26
|
* glare end of the range, so its luminance leaves room for distinct text tiers.
|
|
27
27
|
*
|
|
28
28
|
* Elevation is carried by surface lightness plus a hairline that can actually be
|
|
@@ -56,20 +56,20 @@
|
|
|
56
56
|
.light,
|
|
57
57
|
.dark {
|
|
58
58
|
color-scheme: dark;
|
|
59
|
-
--md3-surface: #
|
|
60
|
-
--md3-surface-dim: #
|
|
61
|
-
--md3-surface-bright: #
|
|
59
|
+
--md3-surface: #0a0a14; /* L0 page canvas — indigo-lifted off black */
|
|
60
|
+
--md3-surface-dim: #060610; /* recessed page */
|
|
61
|
+
--md3-surface-bright: #232238;
|
|
62
62
|
/* Same value as --md3-surface-dim by design, not by drift: both name the floor
|
|
63
63
|
* of the ladder, and dark has no plane below it to give them. They stay two
|
|
64
64
|
* tokens because the roles diverge in light, where the well is paper (#ffffff)
|
|
65
|
-
* and the recessed page is grey (#
|
|
65
|
+
* and the recessed page is grey (#dcdee6) — a consumer binding to the wrong one
|
|
66
66
|
* there gets the wrong surface. */
|
|
67
|
-
--md3-surface-container-lowest: #
|
|
68
|
-
--md3-surface-container-low: #
|
|
69
|
-
--md3-surface-container: #
|
|
70
|
-
--md3-surface-container-high: #
|
|
71
|
-
--md3-surface-container-highest: #
|
|
72
|
-
--md3-surface-variant: #
|
|
67
|
+
--md3-surface-container-lowest: #050509; /* L-1 terminal/code well */
|
|
68
|
+
--md3-surface-container-low: #12111f; /* L1 chrome, field wells */
|
|
69
|
+
--md3-surface-container: #191826; /* L2 resting card */
|
|
70
|
+
--md3-surface-container-high: #221f33; /* L3 nested / hover */
|
|
71
|
+
--md3-surface-container-highest: #2c2942; /* L4 overlay */
|
|
72
|
+
--md3-surface-variant: #221f33;
|
|
73
73
|
--md3-on-surface: #e6e6e6;
|
|
74
74
|
--md3-on-surface-variant: #b5b5b5;
|
|
75
75
|
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
--md3-tertiary: #10B981;
|
|
91
91
|
--md3-tertiary-container: var(--surface-success-bg);
|
|
92
92
|
--md3-outline: #8e8e8e;
|
|
93
|
-
--md3-outline-variant: #
|
|
93
|
+
--md3-outline-variant: #2a293d;
|
|
94
94
|
|
|
95
95
|
--md3-error: #ff5277;
|
|
96
96
|
--md3-error-dim: #eb2855;
|
|
@@ -103,15 +103,15 @@
|
|
|
103
103
|
--md3-inverse-on-surface: #000000;
|
|
104
104
|
--md3-surface-tint: #A5B4FC;
|
|
105
105
|
|
|
106
|
-
/* Semantic HSL bridge — the
|
|
107
|
-
--hsl-background:
|
|
106
|
+
/* Semantic HSL bridge — the indigo-lifted surface ladder (GTM's, now every product's) */
|
|
107
|
+
--hsl-background: 240 33% 6%; /* L0 canvas #0a0a14 */
|
|
108
108
|
/* Near-white ink on the canvas is the glare end of the range, and the tiers
|
|
109
109
|
* below it need room to separate. This neutral ink keeps the original 10.6:1
|
|
110
110
|
* contrast on the card and leaves four steps the eye can tell apart. */
|
|
111
111
|
--hsl-foreground: 0 0% 90.1961%; /* #e6e6e6 */
|
|
112
|
-
--hsl-card:
|
|
112
|
+
--hsl-card: 244 23% 12%; /* L2 card #191826 */
|
|
113
113
|
--hsl-card-foreground: 0 0% 90.1961%;
|
|
114
|
-
--hsl-popover:
|
|
114
|
+
--hsl-popover: 247 23% 21%; /* #2c2942 — the NESTED plane despite
|
|
115
115
|
the name; the overlay plane is --md3-surface-container-highest. A popover
|
|
116
116
|
carries body text, and the ladder above L3 is reserved for planes that lift
|
|
117
117
|
by shadow, so it sits on L3 and the ink ramp reaches it. */
|
|
@@ -121,20 +121,20 @@
|
|
|
121
121
|
* bright indigo stays available for accent TEXT via --accent-text. */
|
|
122
122
|
--hsl-primary: 246 54% 51%; /* #4c3fc6 */
|
|
123
123
|
--hsl-primary-foreground: 0 0% 100%;
|
|
124
|
-
--hsl-secondary:
|
|
124
|
+
--hsl-secondary: 249 24% 16%; /* L3 #221f33 */
|
|
125
125
|
--hsl-secondary-foreground: 0 0% 90.1961%;
|
|
126
|
-
--hsl-muted:
|
|
127
|
-
--hsl-muted-foreground:
|
|
126
|
+
--hsl-muted: 249 24% 16%; /* L3 hover well */
|
|
127
|
+
--hsl-muted-foreground: 240 5% 68%; /* #a9a9b1 — high enough that the
|
|
128
128
|
reduced-opacity caption derivatives (/70, /60) still clear the large-text
|
|
129
129
|
contrast floor against the L2 card. */
|
|
130
|
-
--hsl-accent:
|
|
130
|
+
--hsl-accent: 249 24% 16%; /* neutral hover (indigo lives in accent-surface-strong) */
|
|
131
131
|
--hsl-accent-foreground: 0 0% 90.1961%;
|
|
132
132
|
--hsl-destructive: 348 74% 48%;
|
|
133
133
|
--hsl-destructive-foreground: 0 0% 100%;
|
|
134
134
|
/* Quiet on purpose: 1.5:1 against the resting card. Shadow does no work on a
|
|
135
135
|
* dark canvas, so the hairline is what states where a plane ends. */
|
|
136
|
-
--hsl-border:
|
|
137
|
-
--hsl-input:
|
|
136
|
+
--hsl-border: 243 20% 20%; /* #2a293d = --md3-outline-variant */
|
|
137
|
+
--hsl-input: 244 29% 9%; /* recessed field well #11101e */
|
|
138
138
|
/* Brighter than --primary on purpose: the ring is NON-TEXT and has to clear
|
|
139
139
|
* 3:1 against BOTH canvas and card. The fill indigo is tuned to carry white
|
|
140
140
|
* text instead, so the two cannot be the same value. */
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
|
|
176
176
|
/* Sidebar tokens — L1 chrome: one step BELOW the canvas here, so the nav reads
|
|
177
177
|
* as chrome the content sits in front of rather than as another card. */
|
|
178
|
-
--sidebar-background:
|
|
178
|
+
--sidebar-background: 244 29% 9%; /* L1 chrome #11101e */
|
|
179
179
|
--sidebar-foreground: 0 0% 90.1961%;
|
|
180
180
|
--sidebar-primary: 246 54% 51%;
|
|
181
181
|
--sidebar-primary-foreground: 0 0% 100%;
|
|
@@ -185,10 +185,10 @@
|
|
|
185
185
|
--sidebar-ring: 233 100% 87%;
|
|
186
186
|
|
|
187
187
|
/* Depth scale — tracks the MD3 ladder (canvas / card / nested / overlay) */
|
|
188
|
-
--depth-1: #
|
|
189
|
-
--depth-2: #
|
|
190
|
-
--depth-3: #
|
|
191
|
-
--depth-4: #
|
|
188
|
+
--depth-1: #0a0a14;
|
|
189
|
+
--depth-2: #191826;
|
|
190
|
+
--depth-3: #221f33;
|
|
191
|
+
--depth-4: #2c2942;
|
|
192
192
|
|
|
193
193
|
/* Status colors */
|
|
194
194
|
--status-running: #10B981;
|
|
@@ -213,69 +213,69 @@
|
|
|
213
213
|
--surface-info-border: #3d7292;
|
|
214
214
|
--surface-info-text: #67cdff;
|
|
215
215
|
/* tones:begin — generated by scripts/gen-ladders.mjs; edit its CATEGORY map, not these lines. */
|
|
216
|
-
--tone-violet-bg: #
|
|
217
|
-
--tone-violet-bg-hover: #
|
|
218
|
-
--tone-violet-bg-selected: #
|
|
216
|
+
--tone-violet-bg: #392751;
|
|
217
|
+
--tone-violet-bg-hover: #432b60;
|
|
218
|
+
--tone-violet-bg-selected: #4c306c;
|
|
219
219
|
--tone-violet-border: #8457aa;
|
|
220
220
|
--tone-violet-border-selected: #d19dff;
|
|
221
221
|
--tone-violet-text: #ecd9fa;
|
|
222
222
|
--tone-violet-icon: #d19dff;
|
|
223
|
-
--tone-orange-bg: #
|
|
224
|
-
--tone-orange-bg-hover: #
|
|
225
|
-
--tone-orange-bg-selected: #
|
|
223
|
+
--tone-orange-bg: #432823;
|
|
224
|
+
--tone-orange-bg-hover: #512d22;
|
|
225
|
+
--tone-orange-bg-selected: #5e3221;
|
|
226
226
|
--tone-orange-border: #a35829;
|
|
227
|
-
--tone-orange-border-selected: #
|
|
227
|
+
--tone-orange-border-selected: #f76b15;
|
|
228
228
|
--tone-orange-text: #ffe0c2;
|
|
229
|
-
--tone-orange-icon: #
|
|
230
|
-
--tone-teal-bg: #
|
|
231
|
-
--tone-teal-bg-hover: #
|
|
232
|
-
--tone-teal-bg-selected: #
|
|
229
|
+
--tone-orange-icon: #f76b15;
|
|
230
|
+
--tone-teal-bg: #18333b;
|
|
231
|
+
--tone-teal-bg-hover: #173b42;
|
|
232
|
+
--tone-teal-bg-selected: #174448;
|
|
233
233
|
--tone-teal-border: #207e73;
|
|
234
|
-
--tone-teal-border-selected: #
|
|
234
|
+
--tone-teal-border-selected: #12a594;
|
|
235
235
|
--tone-teal-text: #adf0dd;
|
|
236
|
-
--tone-teal-icon: #
|
|
237
|
-
--tone-blue-bg: #
|
|
238
|
-
--tone-blue-bg-hover: #
|
|
239
|
-
--tone-blue-bg-selected: #
|
|
236
|
+
--tone-teal-icon: #12a594;
|
|
237
|
+
--tone-blue-bg: #143051;
|
|
238
|
+
--tone-blue-bg-hover: #123861;
|
|
239
|
+
--tone-blue-bg-selected: #113e6b;
|
|
240
240
|
--tone-blue-border: #2870bd;
|
|
241
|
-
--tone-blue-border-selected: #
|
|
241
|
+
--tone-blue-border-selected: #0090ff;
|
|
242
242
|
--tone-blue-text: #c2e6ff;
|
|
243
|
-
--tone-blue-icon: #
|
|
244
|
-
--tone-pink-bg: #
|
|
245
|
-
--tone-pink-bg-hover: #
|
|
246
|
-
--tone-pink-bg-selected: #
|
|
243
|
+
--tone-blue-icon: #0090ff;
|
|
244
|
+
--tone-pink-bg: #462243;
|
|
245
|
+
--tone-pink-bg-hover: #55254d;
|
|
246
|
+
--tone-pink-bg-selected: #612754;
|
|
247
247
|
--tone-pink-border: #a84885;
|
|
248
|
-
--tone-pink-border-selected: #
|
|
248
|
+
--tone-pink-border-selected: #de51a8;
|
|
249
249
|
--tone-pink-text: #fdd1ea;
|
|
250
|
-
--tone-pink-icon: #
|
|
251
|
-
--tone-brown-bg: #
|
|
252
|
-
--tone-brown-bg-hover: #
|
|
253
|
-
--tone-brown-bg-selected: #
|
|
250
|
+
--tone-pink-icon: #de51a8;
|
|
251
|
+
--tone-brown-bg: #372d30;
|
|
252
|
+
--tone-brown-bg-hover: #413434;
|
|
253
|
+
--tone-brown-bg-selected: #4a3a37;
|
|
254
254
|
--tone-brown-border: #7c5f46;
|
|
255
|
-
--tone-brown-border-selected: #
|
|
255
|
+
--tone-brown-border-selected: #ad7f58;
|
|
256
256
|
--tone-brown-text: #f2e1ca;
|
|
257
|
-
--tone-brown-icon: #
|
|
258
|
-
--tone-cyan-bg: #
|
|
259
|
-
--tone-cyan-bg-hover: #
|
|
260
|
-
--tone-cyan-bg-selected: #
|
|
257
|
+
--tone-brown-icon: #ad7f58;
|
|
258
|
+
--tone-cyan-bg: #143245;
|
|
259
|
+
--tone-cyan-bg-hover: #133b4e;
|
|
260
|
+
--tone-cyan-bg-selected: #114358;
|
|
261
261
|
--tone-cyan-border: #11809c;
|
|
262
|
-
--tone-cyan-border-selected: #
|
|
262
|
+
--tone-cyan-border-selected: #00a2c7;
|
|
263
263
|
--tone-cyan-text: #b6ecf7;
|
|
264
|
-
--tone-cyan-icon: #
|
|
265
|
-
--tone-lime-bg: #
|
|
266
|
-
--tone-lime-bg-hover: #
|
|
267
|
-
--tone-lime-bg-selected: #
|
|
264
|
+
--tone-cyan-icon: #00a2c7;
|
|
265
|
+
--tone-lime-bg: #2b302d;
|
|
266
|
+
--tone-lime-bg-hover: #32382f;
|
|
267
|
+
--tone-lime-bg-selected: #384132;
|
|
268
268
|
--tone-lime-border: #577538;
|
|
269
269
|
--tone-lime-border-selected: #bdee63;
|
|
270
270
|
--tone-lime-text: #e3f7ba;
|
|
271
271
|
--tone-lime-icon: #bdee63;
|
|
272
|
-
--surface-violet-bg: #
|
|
272
|
+
--surface-violet-bg: #392751;
|
|
273
273
|
--surface-violet-border: #8457aa;
|
|
274
274
|
--surface-violet-text: #d19dff;
|
|
275
|
-
--surface-orange-bg: #
|
|
275
|
+
--surface-orange-bg: #432823;
|
|
276
276
|
--surface-orange-border: #a35829;
|
|
277
277
|
--surface-orange-text: #ffa057;
|
|
278
|
-
--surface-teal-bg: #
|
|
278
|
+
--surface-teal-bg: #18333b;
|
|
279
279
|
--surface-teal-border: #207e73;
|
|
280
280
|
--surface-teal-text: #0bd8b6;
|
|
281
281
|
/* tones:end */
|
|
@@ -332,7 +332,7 @@
|
|
|
332
332
|
--accent-text: #A5B4FC;
|
|
333
333
|
|
|
334
334
|
/* Surfaces — solid fills off the ladder (no radial mesh) */
|
|
335
|
-
--bg-root: #
|
|
335
|
+
--bg-root: #0a0a14;
|
|
336
336
|
--bg-dark: var(--depth-1);
|
|
337
337
|
--bg-card: var(--depth-2);
|
|
338
338
|
--bg-elevated: var(--depth-4);
|
|
@@ -549,15 +549,15 @@
|
|
|
549
549
|
color-scheme: light;
|
|
550
550
|
/* Light theme — neutral surfaces with purple accents */
|
|
551
551
|
--code-header-bg: color-mix(in srgb, hsl(var(--hsl-muted)) 50%, transparent);
|
|
552
|
-
--syntax-foreground: #
|
|
553
|
-
--syntax-comment: #
|
|
554
|
-
--syntax-keyword: #
|
|
552
|
+
--syntax-foreground: #24243a;
|
|
553
|
+
--syntax-comment: #6b7280;
|
|
554
|
+
--syntax-keyword: #7c3aed;
|
|
555
555
|
--syntax-string: #047857;
|
|
556
|
-
--syntax-function: #
|
|
557
|
-
--syntax-number: #
|
|
558
|
-
--syntax-meta: #
|
|
559
|
-
--syntax-variable: #
|
|
560
|
-
--syntax-error: #
|
|
556
|
+
--syntax-function: #2563eb;
|
|
557
|
+
--syntax-number: #b45309;
|
|
558
|
+
--syntax-meta: #6d28d9;
|
|
559
|
+
--syntax-variable: #334155;
|
|
560
|
+
--syntax-error: #dc2626;
|
|
561
561
|
|
|
562
562
|
/* MD3 surface hierarchy */
|
|
563
563
|
/* The light ladder ALTERNATES; it is not monotonic, and that is deliberate.
|
|
@@ -566,15 +566,15 @@
|
|
|
566
566
|
* by shadow and ring, not by fill — which is how light-mode elevation actually
|
|
567
567
|
* reads. Forcing a strictly-darkening ladder here is what produced the
|
|
568
568
|
* white-on-white flatness this palette replaced. Pinned in tokens.test.ts. */
|
|
569
|
-
--md3-surface: #
|
|
570
|
-
--md3-surface-dim: #
|
|
569
|
+
--md3-surface: #eceef3; /* L0 page canvas — cool grey, BELOW the wells */
|
|
570
|
+
--md3-surface-dim: #dcdee6;
|
|
571
571
|
--md3-surface-bright: #ffffff;
|
|
572
572
|
--md3-surface-container-lowest: #ffffff; /* insets read as paper */
|
|
573
|
-
--md3-surface-container-low: #
|
|
573
|
+
--md3-surface-container-low: #f6f7fa; /* L1 chrome, field wells — well */
|
|
574
574
|
--md3-surface-container: #ffffff; /* L2 resting card — paper */
|
|
575
|
-
--md3-surface-container-high: #
|
|
575
|
+
--md3-surface-container-high: #f1f2f7; /* L3 nested / hover — well */
|
|
576
576
|
--md3-surface-container-highest: #ffffff; /* L4 overlay — paper + shadow */
|
|
577
|
-
--md3-surface-variant: #
|
|
577
|
+
--md3-surface-variant: #f1f2f7;
|
|
578
578
|
--md3-on-surface: #2d2d2d;
|
|
579
579
|
--md3-on-surface-variant: #4c4c4c;
|
|
580
580
|
|
|
@@ -595,7 +595,7 @@
|
|
|
595
595
|
--md3-tertiary: #065F46;
|
|
596
596
|
--md3-tertiary-container: #D1FAE5;
|
|
597
597
|
--md3-outline: #787878;
|
|
598
|
-
--md3-outline-variant: #
|
|
598
|
+
--md3-outline-variant: #c7c6d6;
|
|
599
599
|
|
|
600
600
|
--md3-error: #ba1a1a;
|
|
601
601
|
--md3-error-dim: #93000a;
|
|
@@ -609,7 +609,7 @@
|
|
|
609
609
|
--md3-surface-tint: #6366F1;
|
|
610
610
|
|
|
611
611
|
/* Semantic HSL bridge — white paper on an achromatic canvas */
|
|
612
|
-
--hsl-background:
|
|
612
|
+
--hsl-background: 223 23% 94%; /* L0 canvas #eceef3 */
|
|
613
613
|
/* Neutral ink rather than pure black keeps the original 13.7:1 contrast on
|
|
614
614
|
* white paper and 11.4:1 on the canvas: AAA with headroom in both places. */
|
|
615
615
|
--hsl-foreground: 0 0% 17.6471%; /* #2d2d2d */
|
|
@@ -619,19 +619,19 @@
|
|
|
619
619
|
--hsl-popover-foreground: 0 0% 17.6471%;
|
|
620
620
|
--hsl-primary: 246 54% 51%; /* #4c3fc6 — carries white at 7.4:1 */
|
|
621
621
|
--hsl-primary-foreground: 0 0% 100%;
|
|
622
|
-
--hsl-secondary:
|
|
622
|
+
--hsl-secondary: 230 27% 96%; /* L3 #f2f3f8 */
|
|
623
623
|
--hsl-secondary-foreground: 0 0% 17.6471%;
|
|
624
|
-
--hsl-muted:
|
|
624
|
+
--hsl-muted: 230 27% 96%; /* L3 hover well */
|
|
625
625
|
/* The shared components fade captions with /70 and /60, so a translucent
|
|
626
626
|
* foreground renders composited over the surface behind it. This neutral value
|
|
627
627
|
* keeps 3.0:1 at /60 and 8.6:1 at full strength on paper. */
|
|
628
628
|
--hsl-muted-foreground: 0 0% 29.8039%; /* #4c4c4c text-muted */
|
|
629
|
-
--hsl-accent:
|
|
629
|
+
--hsl-accent: 230 27% 96%; /* neutral hover */
|
|
630
630
|
--hsl-accent-foreground: 0 0% 17.6471%;
|
|
631
631
|
--hsl-destructive: 0 72% 41%;
|
|
632
632
|
--hsl-destructive-foreground: 0 0% 100%;
|
|
633
|
-
--hsl-border:
|
|
634
|
-
--hsl-input:
|
|
633
|
+
--hsl-border: 244 16% 81%; /* #c8c7d6, the --md3-outline-variant hairline */
|
|
634
|
+
--hsl-input: 225 29% 97%; /* field well #f5f6fa */
|
|
635
635
|
--hsl-ring: 246 54% 51%;
|
|
636
636
|
--hsl-success: 160 84% 30%;
|
|
637
637
|
--hsl-warning: 41 96% 50%;
|
|
@@ -640,9 +640,9 @@
|
|
|
640
640
|
/* depth-2 (card) and depth-4 (elevated) are BOTH paper, on purpose: in light,
|
|
641
641
|
* a popover does not out-lighten a white card — it lifts by shadow. See the
|
|
642
642
|
* ladder note above. */
|
|
643
|
-
--depth-1: #
|
|
643
|
+
--depth-1: #eceef3; /* canvas */
|
|
644
644
|
--depth-2: #ffffff; /* card — paper */
|
|
645
|
-
--depth-3: #
|
|
645
|
+
--depth-3: #f1f2f7; /* nested well */
|
|
646
646
|
--depth-4: #ffffff; /* elevated — paper, separated by shadow */
|
|
647
647
|
|
|
648
648
|
--status-running: #059669;
|
|
@@ -782,7 +782,7 @@
|
|
|
782
782
|
--mesh-blue: transparent;
|
|
783
783
|
|
|
784
784
|
/* Surfaces — flat single-neutral light */
|
|
785
|
-
--bg-root: #
|
|
785
|
+
--bg-root: #eceef3;
|
|
786
786
|
--bg-dark: var(--depth-1);
|
|
787
787
|
--bg-card: var(--depth-2);
|
|
788
788
|
/* Paper, exactly like --bg-card: in light you cannot raise a plane by making it
|
|
@@ -799,7 +799,7 @@
|
|
|
799
799
|
/* Mirrors the dark ramp's spacing against white paper. The ink uses neutral
|
|
800
800
|
* grays so purple stays reserved for interaction accents.
|
|
801
801
|
*
|
|
802
|
-
* --text-dim is pinned from the CANVAS (#
|
|
802
|
+
* --text-dim is pinned from the CANVAS (#eceef3), not the white card: it is
|
|
803
803
|
* the faintest tier, so the lighter L0 is the surface that binds it. Its 4.6:1
|
|
804
804
|
* canvas and 5.6:1 card contrasts clear the body-text floor, as asserted in
|
|
805
805
|
* tokens.test.ts, because a ratio quoted in a comment drifts the moment a
|
|
@@ -924,7 +924,7 @@
|
|
|
924
924
|
* draws as a --chart-muted outline, the same mark as an unsolved attempt. No
|
|
925
925
|
* categorical palette ships: identity is carried by direct labels.
|
|
926
926
|
*
|
|
927
|
-
* Contrast against the dark canvas (#
|
|
927
|
+
* Contrast against the dark canvas (#0a0a14) and white paper, checked with the
|
|
928
928
|
* dataviz palette validator and WCAG ratios:
|
|
929
929
|
* ink 14.45 / 13.70, ink-muted 7.34 / 5.56, accent 6.05 / 7.41,
|
|
930
930
|
* muted 5.54 / 4.41, grid 1.46 / 1.12 (recessive by design).
|
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
/**
|
|
15
15
|
* The canonical spine's structural contract.
|
|
16
16
|
*
|
|
17
|
-
* The canonical
|
|
17
|
+
* The canonical ink is neutral and the surfaces carry one faint indigo cast (the
|
|
18
|
+
* ladder GTM proved out), kept low in chroma so the saturated accent still
|
|
19
|
+
* signals interaction.
|
|
18
20
|
* Each surface remains distinct by fill and elevation. These assertions pin that
|
|
19
21
|
* structure without fixing every shade, so the ladder can be retuned safely.
|
|
20
22
|
*/
|
|
@@ -29,29 +31,31 @@ const tokens = readFileSync(
|
|
|
29
31
|
const DARK = blockIn(tokens, ".dark");
|
|
30
32
|
const LIGHT = blockIn(tokens, ".light");
|
|
31
33
|
|
|
32
|
-
describe("canonical surfaces
|
|
33
|
-
const
|
|
34
|
-
"hsl-background",
|
|
34
|
+
describe("canonical ink is neutral; surfaces share one faint indigo cast", () => {
|
|
35
|
+
const inkHslRoles = [
|
|
35
36
|
"hsl-foreground",
|
|
36
|
-
"hsl-card",
|
|
37
37
|
"hsl-card-foreground",
|
|
38
|
-
"hsl-popover",
|
|
39
38
|
"hsl-popover-foreground",
|
|
40
|
-
"hsl-secondary",
|
|
41
39
|
"hsl-secondary-foreground",
|
|
40
|
+
"hsl-accent-foreground",
|
|
41
|
+
"sidebar-foreground",
|
|
42
|
+
"sidebar-accent-foreground",
|
|
43
|
+
];
|
|
44
|
+
const surfaceHslRoles = [
|
|
45
|
+
"hsl-background",
|
|
46
|
+
"hsl-card",
|
|
47
|
+
"hsl-popover",
|
|
48
|
+
"hsl-secondary",
|
|
42
49
|
"hsl-muted",
|
|
43
50
|
"hsl-muted-foreground",
|
|
44
51
|
"hsl-accent",
|
|
45
|
-
"hsl-accent-foreground",
|
|
46
52
|
"hsl-border",
|
|
47
53
|
"hsl-input",
|
|
48
54
|
"sidebar-background",
|
|
49
|
-
"sidebar-foreground",
|
|
50
55
|
"sidebar-accent",
|
|
51
|
-
"sidebar-accent-foreground",
|
|
52
56
|
"sidebar-border",
|
|
53
57
|
];
|
|
54
|
-
const
|
|
58
|
+
const surfaceHexRoles = [
|
|
55
59
|
"md3-surface",
|
|
56
60
|
"md3-surface-dim",
|
|
57
61
|
"md3-surface-bright",
|
|
@@ -61,8 +65,6 @@ describe("canonical surfaces, ink, and chrome are neutral", () => {
|
|
|
61
65
|
"md3-surface-container-high",
|
|
62
66
|
"md3-surface-container-highest",
|
|
63
67
|
"md3-surface-variant",
|
|
64
|
-
"md3-on-surface",
|
|
65
|
-
"md3-on-surface-variant",
|
|
66
68
|
"md3-outline",
|
|
67
69
|
"md3-outline-variant",
|
|
68
70
|
"depth-1",
|
|
@@ -70,26 +72,48 @@ describe("canonical surfaces, ink, and chrome are neutral", () => {
|
|
|
70
72
|
"depth-3",
|
|
71
73
|
"depth-4",
|
|
72
74
|
];
|
|
75
|
+
const inkHexRoles = ["md3-on-surface", "md3-on-surface-variant"];
|
|
76
|
+
// A tinted plane stays in the indigo band and below 0.1 chroma (0..1), so it
|
|
77
|
+
// reads as a cool neutral beside the accent rather than as a coloured fill.
|
|
78
|
+
const MAX_CHROMA = 0.1;
|
|
79
|
+
const inIndigoBand = (h: number) => h >= 220 && h <= 255;
|
|
80
|
+
const hexHueChroma = (hex: string) => {
|
|
81
|
+
const [r, g, b] = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
|
|
82
|
+
const max = Math.max(r, g, b);
|
|
83
|
+
const c = max - Math.min(r, g, b);
|
|
84
|
+
if (c === 0) return { h: 0, c };
|
|
85
|
+
const h = max === r ? ((g - b) / c) % 6 : max === g ? (b - r) / c + 2 : (r - g) / c + 4;
|
|
86
|
+
return { h: (h * 60 + 360) % 360, c };
|
|
87
|
+
};
|
|
73
88
|
|
|
74
89
|
for (const [theme, spine] of [
|
|
75
90
|
["dark", DARK],
|
|
76
91
|
["light", LIGHT],
|
|
77
92
|
] as const) {
|
|
78
|
-
it(`${theme}:
|
|
79
|
-
for (const token of
|
|
93
|
+
it(`${theme}: ink roles are achromatic`, () => {
|
|
94
|
+
for (const token of inkHslRoles) {
|
|
80
95
|
expect(hslIn(spine, token).s, `--${token}`).toBe(0);
|
|
81
96
|
}
|
|
82
|
-
for (const token of
|
|
83
|
-
const hex = spine.match(
|
|
84
|
-
|
|
85
|
-
)
|
|
97
|
+
for (const token of inkHexRoles) {
|
|
98
|
+
const hex = spine.match(new RegExp(`--${token}:\\s*(#[0-9a-fA-F]{6})`))?.[1];
|
|
99
|
+
expect(hex, `--${token} must be defined`).toBeDefined();
|
|
100
|
+
expect(hexHueChroma(hex!).c, `--${token} is achromatic`).toBe(0);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it(`${theme}: surface roles are neutral or a faint indigo`, () => {
|
|
105
|
+
for (const token of surfaceHslRoles) {
|
|
106
|
+
const { h, s: sat, l } = hslIn(spine, token);
|
|
107
|
+
const chroma = (sat / 100) * (1 - Math.abs((2 * l) / 100 - 1));
|
|
108
|
+
expect(chroma, `--${token} chroma`).toBeLessThanOrEqual(MAX_CHROMA);
|
|
109
|
+
if (sat > 0) expect(inIndigoBand(h), `--${token} hue ${h}`).toBe(true);
|
|
110
|
+
}
|
|
111
|
+
for (const token of surfaceHexRoles) {
|
|
112
|
+
const hex = spine.match(new RegExp(`--${token}:\\s*(#[0-9a-fA-F]{6})`))?.[1];
|
|
86
113
|
expect(hex, `--${token} must be defined`).toBeDefined();
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
);
|
|
90
|
-
expect(hex?.slice(3, 5), `--${token} green equals blue`).toBe(
|
|
91
|
-
hex?.slice(5, 7),
|
|
92
|
-
);
|
|
114
|
+
const { h, c } = hexHueChroma(hex!);
|
|
115
|
+
expect(c, `--${token} chroma`).toBeLessThanOrEqual(MAX_CHROMA);
|
|
116
|
+
if (c > 0.02) expect(inIndigoBand(h), `--${token} hue ${h}`).toBe(true);
|
|
93
117
|
}
|
|
94
118
|
});
|
|
95
119
|
}
|
|
@@ -432,7 +456,7 @@ describe("input tokens carry two DIFFERENT roles and must not be conflated", ()
|
|
|
432
456
|
});
|
|
433
457
|
|
|
434
458
|
describe("canonical light spine", () => {
|
|
435
|
-
it("is white paper on a
|
|
459
|
+
it("is white paper on a cool canvas, never white-on-white", () => {
|
|
436
460
|
const canvas = hslIn(LIGHT, "hsl-background");
|
|
437
461
|
const card = hslIn(LIGHT, "hsl-card");
|
|
438
462
|
expect(card.l, "the card is paper").toBe(100);
|