@tangle-network/brand 1.27.0 → 1.28.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 +37 -0
- package/dist/index.d.ts +18 -22
- package/dist/index.js +20 -22
- package/dist/knot.d.ts +1 -3
- package/dist/knot.js +1 -2
- package/dist/social.d.ts +1 -3
- package/dist/social.js +1 -2
- package/package.json +11 -4
- package/src/palette.generated.ts +18 -18
- package/src/styles/css-test-utils.test.ts +10 -7
- package/src/styles/css-test-utils.ts +38 -13
- package/src/styles/focus.test.ts +4 -15
- package/src/styles/legacy-light.css +193 -337
- package/src/styles/named-themes.css +16 -24
- package/src/styles/named-themes.test.ts +35 -98
- package/src/styles/surface-ladder.test.ts +1 -1
- package/src/styles/theme-scopes.ts +4 -2
- package/src/styles/theme.css +12 -10
- package/src/styles/tokens.css +259 -430
- package/src/styles/tokens.test.ts +35 -12
|
@@ -28,6 +28,15 @@
|
|
|
28
28
|
--spacing-control-md: var(--control-height);
|
|
29
29
|
--spacing-control-lg: var(--control-height-lg);
|
|
30
30
|
--spacing-shell-header: var(--shell-header-height);
|
|
31
|
+
--color-brand: var(--brand-primary);
|
|
32
|
+
--color-brand-dim: var(--btn-primary-bg);
|
|
33
|
+
--color-brand-cool: var(--brand-cool);
|
|
34
|
+
--color-brand-glow: var(--brand-glow);
|
|
35
|
+
--color-brand-emerald: var(--brand-emerald);
|
|
36
|
+
--color-status-running: var(--status-running);
|
|
37
|
+
--color-status-creating: var(--status-creating);
|
|
38
|
+
--color-status-stopped: var(--status-stopped);
|
|
39
|
+
--color-status-error: var(--status-error);
|
|
31
40
|
--color-scrim: var(--scrim);
|
|
32
41
|
--color-scrim-strong: var(--scrim-strong);
|
|
33
42
|
--color-on-media: var(--on-media);
|
|
@@ -230,31 +239,14 @@
|
|
|
230
239
|
}
|
|
231
240
|
|
|
232
241
|
/* ───────────────────────── Tangle — canonical dark spine ───────────────────
|
|
233
|
-
* tangle-dark names the default :root/.dark spine explicitly
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
* the dark-tuned root palette.
|
|
242
|
+
* tangle-dark names the default :root/.dark spine explicitly, for a dark island
|
|
243
|
+
* inside a light-named wrapper or a picker's explicit choice. It is the
|
|
244
|
+
* canonical spine by construction, never a copy of it.
|
|
237
245
|
*/
|
|
238
246
|
[data-theme="tangle-dark"] {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
--hsl-primary: 246 54% 51%; --hsl-primary-foreground: 0 0% 100%;
|
|
243
|
-
--hsl-secondary: 249 24% 16%; --hsl-secondary-foreground: 0 0% 90.1961%;
|
|
244
|
-
--hsl-muted: 249 24% 16%; --hsl-muted-foreground: 240 5% 68%;
|
|
245
|
-
--hsl-accent: 249 24% 16%; --hsl-accent-foreground: 0 0% 90.1961%;
|
|
246
|
-
--hsl-destructive: 0 90% 77%; --hsl-destructive-foreground: 0 60% 10%;
|
|
247
|
-
--hsl-border: 243 20% 20%; --hsl-input: 244 29% 9%;
|
|
248
|
-
--hsl-ring: 233 100% 87%;
|
|
249
|
-
--depth-1: #0a0a14; --depth-2: #191826; --depth-3: #221f33; --depth-4: #2c2942;
|
|
250
|
-
--bg-root: #0a0a14; --bg-card: var(--depth-2); --bg-input: hsl(var(--hsl-input));
|
|
251
|
-
--md3-surface-container-lowest: #040408; --md3-surface-container-low: #12111f;
|
|
252
|
-
--md3-surface-container: #191826; --md3-surface-container-high: #221f33;
|
|
253
|
-
--md3-surface-container-highest: #2c2942; --md3-outline-variant: #2a293d;
|
|
254
|
-
--border-subtle: rgba(255, 255, 255, 0.10); --border-default: rgba(255, 255, 255, 0.16);
|
|
255
|
-
--border-hover: rgba(255, 255, 255, 0.28); --accent-text: #A5B4FC;
|
|
256
|
-
--brand-primary: #7C6CF6; --brand-glow: #A5AAFC;
|
|
257
|
-
--text-primary: #e6e6e6; --text-secondary: #c7c7c7; --text-muted: #b5b5b5; --text-dim: #a5a5a5;
|
|
247
|
+
/* Nothing to declare: tokens.css seeds the complete dark spine on this
|
|
248
|
+
* selector, so a tangle-dark island carries every value itself and inherits
|
|
249
|
+
* nothing from a light wrapper. */
|
|
258
250
|
}
|
|
259
251
|
/* Its light mode. */
|
|
260
252
|
[data-theme="tangle-light"] {
|
|
@@ -378,7 +370,7 @@
|
|
|
378
370
|
--hsl-accent: 133 38.4% 88%; --hsl-accent-foreground: 143 25% 12%;
|
|
379
371
|
--hsl-border: 107 8.7% 79.8%; --hsl-input: 0 0% 100%;
|
|
380
372
|
--hsl-ring: 148 28% 43%;
|
|
381
|
-
--hsl-destructive: 0
|
|
373
|
+
--hsl-destructive: 0 73.7% 41.8%; /* the canonical light red, restated so the dark block below mirrors it */
|
|
382
374
|
--hsl-raised: 110 18.7% 93.7%; --hsl-raised-2: 0 0% 100%;
|
|
383
375
|
--sidebar-background: 108 21.7% 95.5%; --sidebar-foreground: 143 25% 12%;
|
|
384
376
|
--sidebar-primary: 153 38% 25%; --sidebar-primary-foreground: 0 0% 100%;
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
hexRelativeLuminanceIn,
|
|
12
12
|
hslIn,
|
|
13
13
|
hslToRgb,
|
|
14
|
+
resolveIn,
|
|
14
15
|
} from "./css-test-utils";
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -100,71 +101,15 @@ describe('named theme: [data-theme="tangle-dark"]', () => {
|
|
|
100
101
|
);
|
|
101
102
|
if (!light) throw new Error('missing theme block: [data-theme="tangle-light"]');
|
|
102
103
|
|
|
103
|
-
it("
|
|
104
|
+
it("declares nothing: the attribute names the spine tokens.css seeds on it", () => {
|
|
104
105
|
// tangle-dark exists so the attribute can NAME the default spine (a dark
|
|
105
|
-
// island in a light-named wrapper, a picker's explicit selection).
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"hsl-popover",
|
|
113
|
-
"hsl-popover-foreground",
|
|
114
|
-
"hsl-primary",
|
|
115
|
-
"hsl-primary-foreground",
|
|
116
|
-
"hsl-secondary",
|
|
117
|
-
"hsl-secondary-foreground",
|
|
118
|
-
"hsl-muted",
|
|
119
|
-
"hsl-muted-foreground",
|
|
120
|
-
"hsl-accent",
|
|
121
|
-
"hsl-accent-foreground",
|
|
122
|
-
"hsl-destructive",
|
|
123
|
-
"hsl-destructive-foreground",
|
|
124
|
-
"hsl-border",
|
|
125
|
-
"hsl-input",
|
|
126
|
-
"hsl-ring",
|
|
127
|
-
];
|
|
128
|
-
for (const token of hslTokens) {
|
|
129
|
-
expect(hslIn(css, token), `--${token} must equal the :root dark spine`).toEqual(
|
|
130
|
-
hslIn(CANON, token),
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
const hexTokens = [
|
|
134
|
-
"depth-1",
|
|
135
|
-
"depth-2",
|
|
136
|
-
"depth-3",
|
|
137
|
-
"depth-4",
|
|
138
|
-
"md3-surface-container-lowest",
|
|
139
|
-
"md3-surface-container-low",
|
|
140
|
-
"md3-surface-container",
|
|
141
|
-
"md3-surface-container-high",
|
|
142
|
-
"md3-surface-container-highest",
|
|
143
|
-
"md3-outline-variant",
|
|
144
|
-
"accent-text",
|
|
145
|
-
"brand-primary",
|
|
146
|
-
"brand-glow",
|
|
147
|
-
"text-primary",
|
|
148
|
-
"text-secondary",
|
|
149
|
-
"text-muted",
|
|
150
|
-
"text-dim",
|
|
151
|
-
];
|
|
152
|
-
for (const token of hexTokens) {
|
|
153
|
-
expect(hexIn(css, token), `--${token} must equal the :root dark spine`).toEqual(
|
|
154
|
-
hexIn(CANON, token),
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
// Aliased and rgba declarations don't fit hslIn/hexIn — compare them raw.
|
|
158
|
-
for (const token of [
|
|
159
|
-
"bg-root",
|
|
160
|
-
"bg-card",
|
|
161
|
-
"bg-input",
|
|
162
|
-
"border-subtle",
|
|
163
|
-
"border-default",
|
|
164
|
-
"border-hover",
|
|
165
|
-
]) {
|
|
166
|
-
expect(declIn(css, token), `--${token} must equal the :root dark spine`).toBe(
|
|
167
|
-
declIn(CANON, token),
|
|
106
|
+
// island in a light-named wrapper, a picker's explicit selection). It is
|
|
107
|
+
// the canonical spine by construction: tokens.css declares every dark token
|
|
108
|
+
// on this selector, so a copy here could only drift from it.
|
|
109
|
+
expect(css.replace(/^[^{]*\{/, "").trim()).toBe("");
|
|
110
|
+
for (const token of ["hsl-background", "hsl-destructive", "accent-text", "text-dim"]) {
|
|
111
|
+
expect(resolveIn(css, token), `--${token} resolves to the :root dark spine`).toBe(
|
|
112
|
+
resolveIn(CANON, token),
|
|
168
113
|
);
|
|
169
114
|
}
|
|
170
115
|
});
|
|
@@ -238,27 +183,14 @@ describe('named theme: [data-theme="tangle-dark"]', () => {
|
|
|
238
183
|
|
|
239
184
|
it("is self-sufficient inside a light-named wrapper — the island it exists for", () => {
|
|
240
185
|
// The feature's stated scenario: tangle-dark nested in a tangle-light
|
|
241
|
-
// wrapper.
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
const effective = (token: string) =>
|
|
249
|
-
const read = (source: string) => {
|
|
250
|
-
try {
|
|
251
|
-
return hslIn(source, token);
|
|
252
|
-
} catch {
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
const value = read(css) ?? read(wrapper);
|
|
257
|
-
if (!value) {
|
|
258
|
-
throw new Error(`--${token} resolves to nothing inside the island`);
|
|
259
|
-
}
|
|
260
|
-
return value;
|
|
261
|
-
};
|
|
186
|
+
// wrapper. A token the island omitted would inherit the WRAPPER's light
|
|
187
|
+
// value (its near-black secondary-foreground on the dark secondary measured
|
|
188
|
+
// ~1.5:1 once), which is why tokens.css seeds the whole spine on the
|
|
189
|
+
// island's own selector.
|
|
190
|
+
// The island declares every dark token on itself (the baseline tokens.css
|
|
191
|
+
// seeds on its selector), so nothing falls through to the wrapper: the
|
|
192
|
+
// chain is the island's own block over the canonical dark spine.
|
|
193
|
+
const effective = (token: string) => hslIn(css, token);
|
|
262
194
|
for (const [fgToken, bgToken] of [
|
|
263
195
|
["hsl-secondary-foreground", "hsl-secondary"],
|
|
264
196
|
["hsl-accent-foreground", "hsl-accent"],
|
|
@@ -304,14 +236,12 @@ describe("named themes: secondary text tiers are readable text", () => {
|
|
|
304
236
|
];
|
|
305
237
|
for (const scope of scopes) {
|
|
306
238
|
it(`[data-theme="${scope}"] holds AA for --text-muted and --text-dim`, () => {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
return ref ? resolve(ref[1]) : hexIn(css!, token);
|
|
314
|
-
};
|
|
239
|
+
// The theme block over the spine it inherits; tangle-dark declares nothing
|
|
240
|
+
// and resolves to the canonical dark spine.
|
|
241
|
+
const blocks = blocksIn(themes, `[data-theme="${scope}"]`);
|
|
242
|
+
const css = blocks.find((b) => /--hsl-background:/.test(b)) ?? blocks[0];
|
|
243
|
+
const chain = scope.endsWith("-light") ? [css, CANON_LIGHT] : [css];
|
|
244
|
+
const resolve = (token: string) => hexIn(chain, token);
|
|
315
245
|
const planes = {
|
|
316
246
|
canvas: resolve("bg-root"),
|
|
317
247
|
card: resolve("bg-card"),
|
|
@@ -450,13 +380,16 @@ describe('named theme: [data-theme="hospitality"]', () => {
|
|
|
450
380
|
// sage planes (surface-ladder.test.ts holds their hue and contrast), and the
|
|
451
381
|
// dark destructive red is lightened to read on the lighter popover; it stays
|
|
452
382
|
// red. The other status hues, info and every category stay canonical.
|
|
453
|
-
for (const css of [
|
|
383
|
+
for (const [css, spine] of [
|
|
384
|
+
[light, CANON_LIGHT],
|
|
385
|
+
[dark, CANON],
|
|
386
|
+
] as const) {
|
|
454
387
|
expect(css).not.toMatch(
|
|
455
388
|
/--(hsl-)?(success|warning|info|error)\b|--destructive|--hsl-destructive-foreground|--surface-info-|--status-|--tone-|--run-mix-|--syntax-/,
|
|
456
389
|
);
|
|
457
|
-
expect(hslIn(css, "hsl-destructive").h, "destructive stays red").toBe(0);
|
|
390
|
+
expect(hslIn([css, spine], "hsl-destructive").h, "destructive stays red").toBe(0);
|
|
458
391
|
}
|
|
459
|
-
expect(
|
|
392
|
+
expect(hslIn(light, "hsl-destructive")).toEqual(hslIn(CANON_LIGHT, "hsl-destructive"));
|
|
460
393
|
});
|
|
461
394
|
|
|
462
395
|
it("keeps the canonical ladder roles: paper cards on a tinted canvas in light, rising planes in dark", () => {
|
|
@@ -680,8 +613,12 @@ describe("dark themes: destructive reads as text", () => {
|
|
|
680
613
|
'[data-theme="agents"]',
|
|
681
614
|
'.dark[data-theme="website"]',
|
|
682
615
|
].map((selector): [string, string] => {
|
|
683
|
-
|
|
684
|
-
|
|
616
|
+
// A selector can head two rules (tangle-light's shared status group and
|
|
617
|
+
// its theme block); the theme block is the one with a canvas. tangle-dark
|
|
618
|
+
// declares nothing and resolves to the spine it is seeded with.
|
|
619
|
+
const blocks = blocksIn(themes, selector);
|
|
620
|
+
const css = blocks.find((b) => b.includes("--hsl-background")) ?? (blocks.length === 1 ? blocks[0] : undefined);
|
|
621
|
+
if (css === undefined) throw new Error(`missing dark theme block: ${selector}`);
|
|
685
622
|
return [selector, css];
|
|
686
623
|
}),
|
|
687
624
|
];
|
|
@@ -305,6 +305,6 @@ describe("Inset workspace backdrop (canonical themes)", () => {
|
|
|
305
305
|
expect(Math.abs(panel - backdrop)).toBeGreaterThanOrEqual(3);
|
|
306
306
|
});
|
|
307
307
|
it("keeps the dark floor tokens equal, as their comment says", () => {
|
|
308
|
-
expect(
|
|
308
|
+
expect(hexIn(DARK, "md3-surface-dim")).toEqual(hexIn(DARK, "md3-surface-container-lowest"));
|
|
309
309
|
});
|
|
310
310
|
});
|
|
@@ -18,8 +18,10 @@ const ladders = read("ladders.css");
|
|
|
18
18
|
|
|
19
19
|
/** The spine block of a selector: the rule that declares the surface ladder. */
|
|
20
20
|
function spine(source: string, selector: string): string {
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const blocks = blocksIn(source, selector);
|
|
22
|
+
// A theme that declares nothing (tangle-dark) is the baseline its chain continues to.
|
|
23
|
+
const block = blocks.find((b) => b.includes("--hsl-background:")) ?? (blocks.length === 1 ? blocks[0] : undefined);
|
|
24
|
+
if (block === undefined) throw new Error(`missing spine block: ${selector}`);
|
|
23
25
|
return block;
|
|
24
26
|
}
|
|
25
27
|
|
package/src/styles/theme.css
CHANGED
|
@@ -36,16 +36,6 @@
|
|
|
36
36
|
--radius-xl: 12px;
|
|
37
37
|
--radius-full: 999px;
|
|
38
38
|
|
|
39
|
-
--color-brand: #6366F1;
|
|
40
|
-
--color-brand-dim: #4F46E5;
|
|
41
|
-
--color-brand-cool: #818CF8;
|
|
42
|
-
--color-brand-glow: #A5AAFC;
|
|
43
|
-
--color-brand-emerald: #10B981;
|
|
44
|
-
--color-status-running: #10B981;
|
|
45
|
-
--color-status-creating: #9B93F0;
|
|
46
|
-
--color-status-stopped: #FFB800;
|
|
47
|
-
--color-status-error: #FF4D6D;
|
|
48
|
-
|
|
49
39
|
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
50
40
|
--shadow-dropdown: 0 8px 30px rgba(0, 0, 0, 0.45);
|
|
51
41
|
--shadow-glow: none;
|
|
@@ -60,6 +50,18 @@
|
|
|
60
50
|
* Public HSL aliases remain HSL channel triples, never full CSS colors.
|
|
61
51
|
*/
|
|
62
52
|
@theme inline {
|
|
53
|
+
/* Brand and status colours follow the runtime tokens, so bg-brand and
|
|
54
|
+
* text-status-running change with the mode and the named theme. */
|
|
55
|
+
--color-brand: var(--brand-primary);
|
|
56
|
+
--color-brand-dim: var(--btn-primary-bg);
|
|
57
|
+
--color-brand-cool: var(--brand-cool);
|
|
58
|
+
--color-brand-glow: var(--brand-glow);
|
|
59
|
+
--color-brand-emerald: var(--brand-emerald);
|
|
60
|
+
--color-status-running: var(--status-running);
|
|
61
|
+
--color-status-creating: var(--status-creating);
|
|
62
|
+
--color-status-stopped: var(--status-stopped);
|
|
63
|
+
--color-status-error: var(--status-error);
|
|
64
|
+
|
|
63
65
|
--text-display: var(--font-size-display);
|
|
64
66
|
--text-hero: var(--font-size-hero);
|
|
65
67
|
--text-page: var(--font-size-3xl);
|