@tangle-network/brand 1.23.1 → 1.24.1
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/package.json +1 -1
- package/src/styles/legacy-light.css +12 -8
- package/src/styles/named-themes.css +3 -3
- package/src/styles/surface-ladder.test.ts +73 -8
- package/src/styles/tokens.css +23 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.24.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ea4b052: Hospitality dark field border #868f87 → #939c94: it measured 2.99:1 on the raised plane (a Select in a card in a card on Connections). Now 4.6:1 on the card and 3.5:1 on raised.
|
|
8
|
+
|
|
9
|
+
## 1.24.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 33cfa62: Canonical surface ladder: every theme without its own ladder now derives its field wells and field border from its own page, card, hairline and ink. Dark wells recess 4.6 L under the page (they sat 3.1 above it), light wells take the opposite plane (5 L either way; the old well sat 2.6 under the white card), and the field border clears 4:1 on the card in both modes (it was the 1.5:1 hairline). Hospitality keeps its tuned ladder.
|
|
14
|
+
|
|
3
15
|
## 1.23.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* GENERATED by scripts/gen-compat.mjs. Do not edit.
|
|
2
2
|
* Source: tokens.css + named-themes.css
|
|
3
|
-
* SHA-256:
|
|
3
|
+
* SHA-256: 6316c513b7feb4a21006744fa33c56971c00ba6b0d971b1e4b95b0343b497fcc
|
|
4
4
|
* Light-default compatibility; all explicit modes remain canonical.
|
|
5
5
|
*/
|
|
6
6
|
:where(:root),
|
|
@@ -647,12 +647,12 @@
|
|
|
647
647
|
[data-theme] {
|
|
648
648
|
--hsl-raised: var(--hsl-card);
|
|
649
649
|
--hsl-raised-2: var(--hsl-card);
|
|
650
|
-
--well-page: var(--
|
|
651
|
-
--well-card: var(--
|
|
652
|
-
--well-raised: var(--
|
|
653
|
-
--well-raised-2: var(--
|
|
650
|
+
--well-page: color-mix(in oklab, hsl(var(--hsl-background)) 70%, black);
|
|
651
|
+
--well-card: var(--well-page);
|
|
652
|
+
--well-raised: var(--well-page);
|
|
653
|
+
--well-raised-2: var(--well-page);
|
|
654
654
|
--well: var(--well-page);
|
|
655
|
-
--field-border: hsl(var(--hsl-border));
|
|
655
|
+
--field-border: color-mix(in oklab, hsl(var(--hsl-border)), hsl(var(--hsl-foreground)) 45%);
|
|
656
656
|
--shadow-raised: var(--shadow-card);
|
|
657
657
|
--shadow-well: 0 0 0 0 transparent;
|
|
658
658
|
--edge-highlight: 0 0 0 0 transparent;
|
|
@@ -674,6 +674,10 @@
|
|
|
674
674
|
:where(:root) {
|
|
675
675
|
--ix-sel-bg: hsl(var(--hsl-ring) / 0.13);
|
|
676
676
|
--ix-hover-bg: hsl(var(--hsl-ring) / 0.07);
|
|
677
|
+
--well-page: hsl(var(--hsl-card));
|
|
678
|
+
--well-card: hsl(var(--hsl-background));
|
|
679
|
+
--well-raised: hsl(var(--hsl-background));
|
|
680
|
+
--well-raised-2: hsl(var(--hsl-background));
|
|
677
681
|
}
|
|
678
682
|
[data-slot="card"] :where(*) {
|
|
679
683
|
--card: var(--hsl-raised);
|
|
@@ -1472,8 +1476,8 @@
|
|
|
1472
1476
|
--border-subtle: rgba(227, 233, 229, 0.10);
|
|
1473
1477
|
--border-default: rgba(227, 233, 229, 0.16);
|
|
1474
1478
|
--border-hover: rgba(227, 233, 229, 0.28);
|
|
1475
|
-
--border-strong: #
|
|
1476
|
-
--field-border: #
|
|
1479
|
+
--border-strong: #939c94;
|
|
1480
|
+
--field-border: #939c94;
|
|
1477
1481
|
--border-accent: rgba(118, 183, 145, 0.35);
|
|
1478
1482
|
--border-accent-hover: rgba(118, 183, 145, 0.50);
|
|
1479
1483
|
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.45), 0 10px 24px -14px rgba(0, 0, 0, 0.6);
|
|
@@ -349,7 +349,7 @@
|
|
|
349
349
|
* well (field fill) on page / in card / in raised / in raised-2:
|
|
350
350
|
* 100 / 94.9 / 100 / 94.9 12.4 / 12.4 / 22.1 / 22.1
|
|
351
351
|
* hairline 85.0 #c9d0c7 42.0 #494f4a
|
|
352
|
-
* field border 60.0 #798479
|
|
352
|
+
* field border 60.0 #798479 68.3 #939c94 (light 3.9:1 on the card; dark 4.6:1 on the card, 3.5:1 on raised)
|
|
353
353
|
* A well is always the opposite step from its parent in light and always
|
|
354
354
|
* deeper in dark; tokens.css carries the `--well` context that picks one.
|
|
355
355
|
*
|
|
@@ -448,8 +448,8 @@
|
|
|
448
448
|
--well-page: #050705; --well-card: #050705; --well-raised: #181c18; --well-raised-2: #181c18;
|
|
449
449
|
--bg-hover: rgba(118, 183, 145, 0.07); --bg-selection: rgba(118, 183, 145, 0.28);
|
|
450
450
|
--border-subtle: rgba(227, 233, 229, 0.10); --border-default: rgba(227, 233, 229, 0.16);
|
|
451
|
-
--border-hover: rgba(227, 233, 229, 0.28); --border-strong: #
|
|
452
|
-
--field-border: #
|
|
451
|
+
--border-hover: rgba(227, 233, 229, 0.28); --border-strong: #939c94;
|
|
452
|
+
--field-border: #939c94;
|
|
453
453
|
--border-accent: rgba(118, 183, 145, 0.35); --border-accent-hover: rgba(118, 183, 145, 0.50);
|
|
454
454
|
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.45), 0 10px 24px -14px rgba(0, 0, 0, 0.6);
|
|
455
455
|
--shadow-raised: 0 1px 2px rgba(0, 0, 0, 0.35);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
|
-
import { blockIn, contrastRatio, declIn, hexIn, hslIn, hslToRgb, oklchLightness } from "./css-test-utils";
|
|
4
|
+
import { blockIn, blocksIn, contrastRatio, declIn, hexIn, hslIn, hslToRgb, oklchLightness } from "./css-test-utils";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The surface ladder and the interaction palette.
|
|
@@ -39,7 +39,7 @@ const ladder: Array<[role: string, token: string, light: number, dark: number]>
|
|
|
39
39
|
["well in raised", "well-raised", 100, 22.1],
|
|
40
40
|
["well in raised-2", "well-raised-2", 94.9, 22.1],
|
|
41
41
|
["hairline", "hsl-border", 85.0, 42.0],
|
|
42
|
-
["field border", "field-border", 60.0,
|
|
42
|
+
["field border", "field-border", 60.0, 68.3],
|
|
43
43
|
];
|
|
44
44
|
|
|
45
45
|
describe("Hospitality surface ladder", () => {
|
|
@@ -88,6 +88,9 @@ describe("Hospitality surface ladder", () => {
|
|
|
88
88
|
const border = rgbOf(css, "field-border");
|
|
89
89
|
expect(contrastRatio(border, rgbOf(css, "hsl-card")), "on the card").toBeGreaterThanOrEqual(3);
|
|
90
90
|
expect(contrastRatio(border, rgbOf(css, "well-card")), "on the well in a card").toBeGreaterThanOrEqual(3);
|
|
91
|
+
// A field in a card in a card sits on the raised plane: production measured
|
|
92
|
+
// the dark Select edge at 2.99:1 there before #939c94.
|
|
93
|
+
expect(contrastRatio(border, rgbOf(css, "hsl-raised")), "on the raised plane").toBeGreaterThanOrEqual(3);
|
|
91
94
|
expect(declIn(css, "border-strong")).toBe(declIn(css, "field-border"));
|
|
92
95
|
});
|
|
93
96
|
|
|
@@ -168,14 +171,10 @@ describe("shared surface context and interaction tokens (tokens.css)", () => {
|
|
|
168
171
|
}
|
|
169
172
|
});
|
|
170
173
|
|
|
171
|
-
it("defaults
|
|
174
|
+
it("defaults the raised planes and shadows to the plain spine (wells: the canonical suite below)", () => {
|
|
172
175
|
expect(declIn(body, "hsl-raised")).toBe("var(--hsl-card)");
|
|
173
176
|
expect(declIn(body, "hsl-raised-2")).toBe("var(--hsl-card)");
|
|
174
|
-
for (const well of ["well-page", "well-card", "well-raised", "well-raised-2"]) {
|
|
175
|
-
expect(declIn(body, well)).toBe("var(--bg-input)");
|
|
176
|
-
}
|
|
177
177
|
expect(declIn(body, "well")).toBe("var(--well-page)");
|
|
178
|
-
expect(declIn(body, "field-border")).toBe("hsl(var(--hsl-border))");
|
|
179
178
|
expect(declIn(body, "shadow-raised")).toBe("var(--shadow-card)");
|
|
180
179
|
// A shadow token joins a comma list; "none" would void the list.
|
|
181
180
|
expect(declIn(body, "shadow-well")).toBe("0 0 0 0 transparent");
|
|
@@ -190,7 +189,7 @@ describe("shared surface context and interaction tokens (tokens.css)", () => {
|
|
|
190
189
|
expect(declIn(body, "ix-sel-ring")).toBe("hsl(var(--hsl-ring) / 0.85)");
|
|
191
190
|
expect(declIn(body, "ix-sel-bg")).toBe("hsl(var(--hsl-ring) / 0.18)");
|
|
192
191
|
expect(declIn(body, "ix-hover-bg")).toBe("hsl(var(--hsl-ring) / 0.08)");
|
|
193
|
-
const lightTint = css.match(/([^{}]*)\{\s*--ix-sel-bg:\s*hsl\(var\(--hsl-ring\) \/ 0\.13\);\s*--ix-hover-bg:\s*hsl\(var\(--hsl-ring\) \/ 0\.07\)
|
|
192
|
+
const lightTint = css.match(/([^{}]*)\{\s*--ix-sel-bg:\s*hsl\(var\(--hsl-ring\) \/ 0\.13\);\s*--ix-hover-bg:\s*hsl\(var\(--hsl-ring\) \/ 0\.07\);[^{}]*\}/);
|
|
194
193
|
expect(lightTint, "light tint override").not.toBeNull();
|
|
195
194
|
expect(lightTint![1]).toContain('[data-theme="hospitality"]:where(:not(.dark))');
|
|
196
195
|
expect(lightTint![1]).toContain(".light");
|
|
@@ -223,3 +222,69 @@ describe("shared surface context and interaction tokens (tokens.css)", () => {
|
|
|
223
222
|
}
|
|
224
223
|
});
|
|
225
224
|
});
|
|
225
|
+
|
|
226
|
+
describe("Canonical surface ladder (every theme without its own ladder)", () => {
|
|
227
|
+
// The spine's shared --bg-input sat +3.1 L above the dark page and -2.6 L under
|
|
228
|
+
// the white card, so a field read as the plane it was on. The defaults derive
|
|
229
|
+
// wells and the field border from each theme's own page, card, hairline and ink.
|
|
230
|
+
const withWells = (selector: string) => {
|
|
231
|
+
const block = blocksIn(tokens, selector).find((b) => /--well-page:/.test(b));
|
|
232
|
+
if (!block) throw new Error(`no well defaults under ${selector}`);
|
|
233
|
+
return block;
|
|
234
|
+
};
|
|
235
|
+
const defaults = withWells("[data-theme]");
|
|
236
|
+
const lightDefaults = withWells('[data-theme$="-light"]');
|
|
237
|
+
const DARK = blockIn(tokens, ".dark");
|
|
238
|
+
const LIGHT = blockIn(tokens, ".light");
|
|
239
|
+
|
|
240
|
+
type Rgb = [number, number, number];
|
|
241
|
+
const toLab = (rgb: Rgb) => {
|
|
242
|
+
const lin = (c: number) => ((c /= 255) <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
|
|
243
|
+
const [r, g, b] = rgb.map(lin);
|
|
244
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
245
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
246
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
247
|
+
return [0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s, 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s, 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s];
|
|
248
|
+
};
|
|
249
|
+
const fromLab = ([L, a, b]: number[]): Rgb => {
|
|
250
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
251
|
+
const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
252
|
+
const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
253
|
+
const enc = (c: number) => 255 * Math.min(1, Math.max(0, c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055));
|
|
254
|
+
return [enc(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s), enc(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s), enc(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)];
|
|
255
|
+
};
|
|
256
|
+
/** `color-mix(in oklab, a, b <t>)`. */
|
|
257
|
+
const mix = (a: Rgb, b: Rgb, t: number) => fromLab(toLab(a).map((v, i) => v * (1 - t) + toLab(b)[i] * t));
|
|
258
|
+
const spine = (css: string, token: string) => hslToRgb(hslIn(css, token)) as Rgb;
|
|
259
|
+
|
|
260
|
+
it("declares the derivations this suite measures", () => {
|
|
261
|
+
expect(declIn(defaults, "well-page")).toBe("color-mix(in oklab, hsl(var(--hsl-background)) 70%, black)");
|
|
262
|
+
expect(declIn(defaults, "field-border")).toBe("color-mix(in oklab, hsl(var(--hsl-border)), hsl(var(--hsl-foreground)) 45%)");
|
|
263
|
+
expect(declIn(lightDefaults, "well-page")).toBe("hsl(var(--hsl-card))");
|
|
264
|
+
for (const token of ["well-card", "well-raised", "well-raised-2"]) {
|
|
265
|
+
expect(declIn(defaults, token)).toBe("var(--well-page)");
|
|
266
|
+
expect(declIn(lightDefaults, token)).toBe("hsl(var(--hsl-background))");
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("recesses the dark well at least 4 L below the page and the card", () => {
|
|
271
|
+
const well = mix(spine(DARK, "hsl-background"), [0, 0, 0], 0.3);
|
|
272
|
+
expect(oklchLightness(spine(DARK, "hsl-background")) - oklchLightness(well)).toBeGreaterThanOrEqual(4);
|
|
273
|
+
expect(oklchLightness(spine(DARK, "hsl-card")) - oklchLightness(well)).toBeGreaterThanOrEqual(10);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("steps the light well 4 L or more off the page and the card", () => {
|
|
277
|
+
const page = oklchLightness(spine(LIGHT, "hsl-background"));
|
|
278
|
+
const card = oklchLightness(spine(LIGHT, "hsl-card"));
|
|
279
|
+
expect(card - page).toBeGreaterThanOrEqual(4);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it.each([
|
|
283
|
+
["dark", () => DARK],
|
|
284
|
+
["light", () => LIGHT],
|
|
285
|
+
])("draws a field border that clears 3:1 on the card and the page in %s (WCAG 1.4.11)", (_mode, css) => {
|
|
286
|
+
const border = mix(spine(css(), "hsl-border"), spine(css(), "hsl-foreground"), 0.45);
|
|
287
|
+
expect(contrastRatio(border, spine(css(), "hsl-card")), "on the card").toBeGreaterThanOrEqual(3);
|
|
288
|
+
expect(contrastRatio(border, spine(css(), "hsl-background")), "on the page").toBeGreaterThanOrEqual(3);
|
|
289
|
+
});
|
|
290
|
+
});
|
package/src/styles/tokens.css
CHANGED
|
@@ -950,9 +950,15 @@
|
|
|
950
950
|
* level in, --hsl-raised-2 two levels in.
|
|
951
951
|
* --well the field fill: --well-page on the page, --well-card in a card,
|
|
952
952
|
* --well-raised in a card in a card, --well-raised-2 deeper still.
|
|
953
|
-
* A theme that tunes its ladder (Hospitality) sets these per mode.
|
|
954
|
-
*
|
|
955
|
-
*
|
|
953
|
+
* A theme that tunes its ladder (Hospitality) sets these per mode. Every other
|
|
954
|
+
* theme gets wells derived from its own page and card, so a field always sits
|
|
955
|
+
* at least 3 OKLCH L points off the plane it is on (agent-app-signoff
|
|
956
|
+
* surface-ladder): dark wells recess below the page (page + 30% black: -4.6 L on
|
|
957
|
+
* the page, -10.9 L in a card on the spine); light wells take the opposite plane
|
|
958
|
+
* (the card on the page, the page in a card: 5 L either way). The old shared
|
|
959
|
+
* --bg-input sat +3.1 above the dark page and -2.6 under the white card. Raised
|
|
960
|
+
* levels still paint the card here: --hsl-raised is an HSL triplet and cannot
|
|
961
|
+
* be derived per theme.
|
|
956
962
|
*
|
|
957
963
|
* INTERACTION. Four states, all from the focus ring hue (--hsl-ring), so one
|
|
958
964
|
* green or one indigo marks every interactive thing in a theme:
|
|
@@ -972,12 +978,14 @@
|
|
|
972
978
|
[data-theme] {
|
|
973
979
|
--hsl-raised: var(--hsl-card);
|
|
974
980
|
--hsl-raised-2: var(--hsl-card);
|
|
975
|
-
--well-page: var(--
|
|
976
|
-
--well-card: var(--
|
|
977
|
-
--well-raised: var(--
|
|
978
|
-
--well-raised-2: var(--
|
|
981
|
+
--well-page: color-mix(in oklab, hsl(var(--hsl-background)) 70%, black);
|
|
982
|
+
--well-card: var(--well-page);
|
|
983
|
+
--well-raised: var(--well-page);
|
|
984
|
+
--well-raised-2: var(--well-page);
|
|
979
985
|
--well: var(--well-page);
|
|
980
|
-
|
|
986
|
+
/* The hairline is 1.5:1 by design; a field edge is non-text UI and needs 3:1.
|
|
987
|
+
* 45% toward the ink measures 4.0:1 on the spine card in both modes. */
|
|
988
|
+
--field-border: color-mix(in oklab, hsl(var(--hsl-border)), hsl(var(--hsl-foreground)) 45%);
|
|
981
989
|
--shadow-raised: var(--shadow-card);
|
|
982
990
|
/* A shadow token joins a comma list, so "none" would void the whole list. */
|
|
983
991
|
--shadow-well: 0 0 0 0 transparent;
|
|
@@ -991,8 +999,9 @@
|
|
|
991
999
|
--ix-hover-bg: hsl(var(--hsl-ring) / 0.08);
|
|
992
1000
|
}
|
|
993
1001
|
|
|
994
|
-
/* Light planes need less tint for the same step
|
|
995
|
-
* light rule below; dark is the
|
|
1002
|
+
/* Light planes need less tint for the same step, and take their wells from the
|
|
1003
|
+
* opposite plane. Same scope list as the chart light rule below; dark is the
|
|
1004
|
+
* default. */
|
|
996
1005
|
[data-sandbox-theme="vault"]:where(:not(.dark)),
|
|
997
1006
|
[data-sandbox-theme="dawn"]:where(:not(.dark)),
|
|
998
1007
|
[data-theme="hospitality"]:where(:not(.dark)),
|
|
@@ -1002,6 +1011,10 @@
|
|
|
1002
1011
|
[data-theme$="-light"] {
|
|
1003
1012
|
--ix-sel-bg: hsl(var(--hsl-ring) / 0.13);
|
|
1004
1013
|
--ix-hover-bg: hsl(var(--hsl-ring) / 0.07);
|
|
1014
|
+
--well-page: hsl(var(--hsl-card));
|
|
1015
|
+
--well-card: hsl(var(--hsl-background));
|
|
1016
|
+
--well-raised: hsl(var(--hsl-background));
|
|
1017
|
+
--well-raised-2: hsl(var(--hsl-background));
|
|
1005
1018
|
}
|
|
1006
1019
|
|
|
1007
1020
|
/* Surface context: keyed only on the Card primitive's data-slot, never on app
|