@motion-proto/live-tokens 0.60.1 → 0.62.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/.claude/skills/live-tokens-build-page/SKILL.md +1 -1
- package/.claude/skills/live-tokens-pair-fonts/SKILL.md +1 -1
- package/CHANGELOG.md +107 -0
- package/dist-plugin/{chunk-XWXIMTWZ.js → chunk-7TQQTHI6.js} +10 -1
- package/dist-plugin/{chunk-LW4SR7AZ.js → chunk-O6GQ6GBH.js} +1 -1
- package/dist-plugin/{chunk-E5QYON4L.js → chunk-OPYOK2CA.js} +77 -26
- package/dist-plugin/generateColorsAndType/index.js +2 -2
- package/dist-plugin/index.cjs +179 -25
- package/dist-plugin/index.js +99 -4
- package/dist-plugin/tokensCssMigrations/index.cjs +76 -25
- package/dist-plugin/tokensCssMigrations/index.js +2 -2
- package/package.json +6 -1
- package/src/app/site.css +16 -0
- package/src/editor/component-editor/CardEditor.svelte +7 -1
- package/src/editor/component-editor/ImageLightboxEditor.svelte +2 -1
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +25 -2
- package/src/editor/core/sketch/maskField.ts +70 -34
- package/src/editor/core/sketch/sketchLayer.ts +24 -3
- package/src/editor/core/sketch/sketchPresets.ts +10 -11
- package/src/editor/core/sketch/sketchStore.ts +89 -18
- package/src/editor/docs/Docs.svelte +1 -0
- package/src/editor/docs/chapters.ts +1 -0
- package/src/editor/docs/content/light-and-dark.md +90 -0
- package/src/editor/docs/content/sketch-mode.md +7 -2
- package/src/editor/docs/content.generated.ts +2 -1
- package/src/editor/overlay/LiveTokensRouter.svelte +8 -0
- package/src/editor/ui/sections/textStyles.ts +15 -1
- package/src/editor/ui/sketch/SketchTab.svelte +33 -81
- package/src/live-tokens/data/colors-and-type/midnight-study.json +40 -30
- package/src/live-tokens/data/themes/autumn.json +4 -3
- package/src/live-tokens/data/themes/halloween.json +4 -3
- package/src/live-tokens/data/themes/midnight-study.json +62 -49
- package/src/live-tokens/data/themes/ocean.json +4 -3
- package/src/live-tokens/data/themes/royal-velvet.json +4 -3
- package/src/live-tokens/data/themes/sketchy.json +4 -3
- package/src/live-tokens/data/themes/spring-meadow.json +4 -3
- package/src/live-tokens/data/themes/sunset.json +4 -3
- package/src/live-tokens/data/tokens.generated.css +5 -0
- package/src/system/backdrop/backdrop.ts +197 -0
- package/src/system/backdrop/index.ts +16 -0
- package/src/system/backdrop/watch.ts +139 -0
- package/src/system/components/Card.svelte +27 -9
- package/src/system/components/FloatingTokenTags.svelte +1 -1
- package/src/system/components/ImageLightbox.svelte +8 -4
- package/src/system/components/SectionDivider.svelte +3 -3
- package/src/system/components/SegmentedControl.svelte +11 -9
- package/src/system/styles/tokens.css +33 -5
- package/template/src/pages/Home.svelte +1 -11
- package/src/system/internal/backgroundContrast.ts +0 -62
|
@@ -10,7 +10,7 @@ Two rules above all else:
|
|
|
10
10
|
1. **Use a shipped component if one fits.** Import from `@motion-proto/live-tokens/components/<Name>.svelte`. See [[live-tokens-pick-component]] for the catalogue and the confusing-pair decisions. Author custom markup only when nothing fits, and then consider [[live-tokens-create-component]] so the new piece is editable too.
|
|
11
11
|
2. **Use theme tokens for every value.** Every color, spacing, radius, font-size, and font-family in page CSS is a `var(--token-*)`. No hex literals. No pixel literals. A change in `/live-tokens/editor` should repaint your page.
|
|
12
12
|
|
|
13
|
-
For text, reach for a whole text style rather than assembling one: `--heading-xl` through `--heading-sm`, `--body-md`, `--body-sm`, `--eyebrow`, and `--code` each carry a `-font-family`, `-font-size`, `-font-weight`, `-line-height`, and `-letter-spacing`. A heading set from `--heading-lg-*` retypes when the theme's fonts change; one set from a raw `font-size` does not.
|
|
13
|
+
For text, reach for a whole text style rather than assembling one: `--heading-xl` through `--heading-sm`, `--body-md`, `--body-sm`, `--editorial-md`, `--editorial-sm`, `--eyebrow`, and `--code` each carry a `-font-family`, `-font-size`, `-font-weight`, `-line-height`, and `-letter-spacing`. A heading set from `--heading-lg-*` retypes when the theme's fonts change; one set from a raw `font-size` does not.
|
|
14
14
|
|
|
15
15
|
## Layout
|
|
16
16
|
|
|
@@ -24,7 +24,7 @@ Flags: `--dry-run` reports without writing. `--no-verify` skips the network and
|
|
|
24
24
|
{ "display": "Fraunces", "body": "Nunito Sans" }
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Every slot is optional and an omitted slot is left exactly as it is. `display` is `--font-display`, `body` is `--font-sans`; `serif`, `mono` and `editorial` exist when a theme needs them. `editorial` is `--font-editorial`, the long-reading face behind the `--editorial-*` text
|
|
27
|
+
Every slot is optional and an omitted slot is left exactly as it is. `display` is `--font-display`, `body` is `--font-sans`; `serif`, `mono` and `editorial` exist when a theme needs them. `editorial` is `--font-editorial`, the long-reading face behind the `--editorial-md-*` and `--editorial-sm-*` text styles: it tracks the body face until a theme repoints it, so set it only when essays and articles should not carry the body face. A slot may be `{ "name": "...", "url": "..." }` to pin an exact URL. Spell families as Google does; the CLI reports the canonical spelling back.
|
|
28
28
|
|
|
29
29
|
## Choose the body face first
|
|
30
30
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,112 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.62.0 — Editorial type reads as a pair
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **The editorial role has its own sizes.** The role shipped with a single
|
|
8
|
+
unsized bundle, so an editorial surface could only take the body size. It now
|
|
9
|
+
reads as a pair, matching body: `--editorial-md-*` and `--editorial-sm-*`,
|
|
10
|
+
both editable in the editor's Text Styles section beside the other styles, and
|
|
11
|
+
both carried by an `.editorial-md` / `.editorial-sm` class in `site.css`. Each
|
|
12
|
+
step mirrors its body counterpart apart from the family, so a project that
|
|
13
|
+
never repoints `--font-editorial` renders exactly as it did.
|
|
14
|
+
- **`Card` takes `variant="bare"`.** A card with no icon and no title dropped
|
|
15
|
+
its header silently, which read the same as a card whose title had gone
|
|
16
|
+
missing. `bare` says the headerless card is the intent; the frame and body
|
|
17
|
+
keep every token the default variant uses, so padding, stroke and fill stay
|
|
18
|
+
editable. A default-variant card with nothing to put in its header now warns
|
|
19
|
+
in dev.
|
|
20
|
+
- **Sketch mode reaches the component editor.** A component demo draws itself
|
|
21
|
+
the way the page will, sketch included, while the tab strips and property rows
|
|
22
|
+
around it stay chrome: they run on `--ui-*` tokens the layer knows nothing
|
|
23
|
+
about. The router hands the layer the root to paint, so a sketch style never
|
|
24
|
+
bleeds onto the editor's own routes.
|
|
25
|
+
|
|
26
|
+
### Changed (breaking)
|
|
27
|
+
|
|
28
|
+
- **`--editorial-*` is now `--editorial-md-*`.** The unsized bundle became the
|
|
29
|
+
medium step. A `breaking` tokens.css migration
|
|
30
|
+
(`2026-08-27-editorial-size-steps`) renames it and backfills both steps,
|
|
31
|
+
carrying any value already tuned. It never auto-applies: run
|
|
32
|
+
`npx live-tokens migrate`.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **Ink coverage is a levels control.** The dials read as a pile of unrelated
|
|
37
|
+
knobs because each one meant something different depending on the grain and
|
|
38
|
+
octave count under it. The field is now normalized to run black to white
|
|
39
|
+
whatever the noise beneath, so **Steps** flattens it into tones and **Output**
|
|
40
|
+
squeezes the whole of it into the range the ink covers, from palest to
|
|
41
|
+
densest. The field is squeezed into that gap, never clipped at it, so both
|
|
42
|
+
ends mean the same thing at every setting. The seven shipped presets are
|
|
43
|
+
retuned against the new response.
|
|
44
|
+
- **Menus and tooltips are drawn solid whatever the coverage dials say.** They
|
|
45
|
+
float over arbitrary page content, so a fill worn through in patches let the
|
|
46
|
+
page show through them and they stopped reading as a surface the pointer can
|
|
47
|
+
land on.
|
|
48
|
+
- **`SectionDivider` titles take no outline by default.** The three sizes each
|
|
49
|
+
carried a themed outline color, which drew a halo around every subsection
|
|
50
|
+
title on a page that never asked for one. All three default to transparent;
|
|
51
|
+
a theme that wants the halo sets the color.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **Per-side padding no longer strands the base token.** `themed-padding`
|
|
56
|
+
emits `var(--x-side, <base>)` for each side, so a per-side token is an
|
|
57
|
+
override slot the editor fills when the user splits the padding. `Card` and
|
|
58
|
+
`SegmentedControl` declared sides that named a literal step instead, which
|
|
59
|
+
always beat that fallback and left the base inert: merging split padding back
|
|
60
|
+
to one value cleared the side aliases, the fallback never took over, and the
|
|
61
|
+
padding control moved nothing. Both now track their own base, and
|
|
62
|
+
`paddingBaseStaysLive` holds every component that uses `themed-padding` to
|
|
63
|
+
the same rule. `Card`'s header and body padding are symmetric as a result;
|
|
64
|
+
a theme that wants the asymmetry back sets the per-side aliases.
|
|
65
|
+
- **`ImageLightbox`'s tile fill stays on the closed tile.** The fill also
|
|
66
|
+
painted the opened stage, where it had nothing to do: the stage rides a
|
|
67
|
+
near-opaque overlay that already backs transparent art, so the fill only
|
|
68
|
+
showed as a slab of theme color in the gap the image's aspect left inside the
|
|
69
|
+
stage box. The open stage now takes no fill of its own, matching the tile
|
|
70
|
+
shadow, which already stopped at the same edge.
|
|
71
|
+
- **Sketch settings no longer accumulate a trace on `globalThis`.** Debug
|
|
72
|
+
instrumentation left in `sketchStore` pushed an entry onto an unbounded
|
|
73
|
+
global array on every settings write, every share and every cross-frame
|
|
74
|
+
adopt. It is gone.
|
|
75
|
+
|
|
76
|
+
## 0.61.0 — A surface says which way it leans
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
|
|
80
|
+
- **One answer to "is this surface light or dark".** A wordmark drawn in white
|
|
81
|
+
disappears on a pale theme, ink that multiplies onto paper vanishes on a dark
|
|
82
|
+
one, and a headline over a photograph is on a dark ground whatever the palette
|
|
83
|
+
says. Every one of those needs the same fact, and every one of them was
|
|
84
|
+
answering it for itself: a luminance probe here, a `--page-bg` poll there, a
|
|
85
|
+
hand-picked token pair somewhere else. `@motion-proto/live-tokens/backdrop`
|
|
86
|
+
answers it once. `data-backdrop` is the single channel — state it in markup
|
|
87
|
+
where the surface knows its own tone, or let `use:backdrop` measure what
|
|
88
|
+
actually paints and stamp it — and `tokens.css` turns the attribute into
|
|
89
|
+
`color-scheme`, so `light-dark()` under it resolves the half that reads. The
|
|
90
|
+
module also answers in JavaScript, for a canvas, a WebGL uniform, or an image
|
|
91
|
+
that comes in two versions: `isLightBackdrop`, `watchBackdrop`, `polarityOf`,
|
|
92
|
+
`contrastTokenFor`, and `cssColorToHex`. A new guide chapter, **Light and
|
|
93
|
+
dark**, covers the whole of it.
|
|
94
|
+
- **A theme's own polarity is baked, not probed.** Nothing in a saved theme
|
|
95
|
+
recorded whether it was light or dark — the generator takes `scheme` as a
|
|
96
|
+
brief and throws it away — so anything that needed to know had to measure at
|
|
97
|
+
runtime and repaint. `tokens.generated.css` now carries a `color-scheme`
|
|
98
|
+
measured from the production theme's `--page-bg`, at zero specificity: it is
|
|
99
|
+
the answer at first paint, and any `data-backdrop` stamp — the editor
|
|
100
|
+
switching themes live, a section stating its own tone — outranks it.
|
|
101
|
+
|
|
102
|
+
- **`ImageLightbox` has a tile fill.** A diagram drawn in light ink, or any
|
|
103
|
+
cut-out with a transparent ground, disappeared into a light page: the tile is
|
|
104
|
+
exactly the image's own box, so nothing stood behind the art to separate it
|
|
105
|
+
from the section. `--imagelightbox-tile-surface` paints behind the image on
|
|
106
|
+
the closed tile and the opened stage alike, inside the radius and border the
|
|
107
|
+
tile already carries. It takes any fill the editor offers, solid or gradient,
|
|
108
|
+
and defaults to none, so every existing lightbox renders as it did.
|
|
109
|
+
|
|
3
110
|
## 0.60.1 — A drawn heading is type, not an icon
|
|
4
111
|
|
|
5
112
|
### Fixed
|
|
@@ -113,6 +113,13 @@ function cssColorToOklch(value) {
|
|
|
113
113
|
if (HEX6_RE.test(raw)) return hexToOklch(raw);
|
|
114
114
|
return parseOklchCss(raw);
|
|
115
115
|
}
|
|
116
|
+
function oklchToRgb255(l, c, h) {
|
|
117
|
+
const clamped = gamutClamp(l, c, h);
|
|
118
|
+
const [L, a, bVal] = oklchToOklab(clamped.l, clamped.c, clamped.h);
|
|
119
|
+
const [r, g, b] = oklabToLinearRgb(L, a, bVal);
|
|
120
|
+
const to255 = (channel) => Math.round(Math.max(0, Math.min(1, linearToSrgb(channel))) * 255);
|
|
121
|
+
return { r: to255(r), g: to255(g), b: to255(b) };
|
|
122
|
+
}
|
|
116
123
|
|
|
117
124
|
export {
|
|
118
125
|
hexToOklch,
|
|
@@ -120,5 +127,7 @@ export {
|
|
|
120
127
|
oklchToHexClamped,
|
|
121
128
|
gamutClamp,
|
|
122
129
|
oklchToCss,
|
|
123
|
-
|
|
130
|
+
parseOklchCss,
|
|
131
|
+
cssColorToOklch,
|
|
132
|
+
oklchToRgb255
|
|
124
133
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hexToOklch,
|
|
3
3
|
oklchToCss
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7TQQTHI6.js";
|
|
5
5
|
|
|
6
6
|
// src/editor/core/themes/parsers/globalRootBlock.ts
|
|
7
7
|
function extractGlobalRootBody(source) {
|
|
@@ -377,37 +377,15 @@ var tokensCssMigration_2026_08_19_oklchColorValues = {
|
|
|
377
377
|
|
|
378
378
|
// vite-plugin/tokensCssMigrations/migrations/2026-08-25-editorial-type-role.ts
|
|
379
379
|
var STACK = [{ name: "--font-editorial", value: "var(--font-sans)" }];
|
|
380
|
-
var BUNDLE2 = [
|
|
381
|
-
{ name: "--editorial-font-family", value: "var(--font-editorial)" },
|
|
382
|
-
{ name: "--editorial-font-size", value: "var(--font-size-md)" },
|
|
383
|
-
{ name: "--editorial-font-weight", value: "var(--font-weight-normal)" },
|
|
384
|
-
{ name: "--editorial-line-height", value: "var(--line-height-normal)" },
|
|
385
|
-
{ name: "--editorial-letter-spacing", value: "var(--letter-spacing-normal)" }
|
|
386
|
-
];
|
|
387
380
|
var tokensCssMigration_2026_08_25_editorialTypeRole = {
|
|
388
381
|
id: "2026-08-25-editorial-type-role",
|
|
389
382
|
kind: "additive",
|
|
390
|
-
description: "Add the editorial type role (--font-editorial
|
|
383
|
+
description: "Add the editorial type role (--font-editorial)",
|
|
391
384
|
apply(css) {
|
|
392
|
-
|
|
385
|
+
return ensureScale(css, {
|
|
393
386
|
anchorPrefixes: ["--font-mono", "--font-serif", "--font-sans", "--font-display"],
|
|
394
387
|
entries: STACK
|
|
395
388
|
});
|
|
396
|
-
return ensureScale(withStack, {
|
|
397
|
-
sectionComment: "Editorial \u2014 the long-reading role, body face until repointed",
|
|
398
|
-
anchorPrefixes: [
|
|
399
|
-
"--eyebrow-",
|
|
400
|
-
"--code-",
|
|
401
|
-
"--body-",
|
|
402
|
-
"--heading-",
|
|
403
|
-
"--letter-spacing-",
|
|
404
|
-
"--line-height-",
|
|
405
|
-
"--font-weight-",
|
|
406
|
-
"--font-size-",
|
|
407
|
-
"--font-"
|
|
408
|
-
],
|
|
409
|
-
entries: BUNDLE2
|
|
410
|
-
});
|
|
411
389
|
}
|
|
412
390
|
};
|
|
413
391
|
|
|
@@ -430,6 +408,77 @@ var tokensCssMigration_2026_08_26_gradientStops = {
|
|
|
430
408
|
}
|
|
431
409
|
};
|
|
432
410
|
|
|
411
|
+
// vite-plugin/tokensCssMigrations/migrations/2026-08-27-backdrop-polarity.ts
|
|
412
|
+
var MARKER = "[data-backdrop='dark']";
|
|
413
|
+
var RULES = `/* BACKDROP POLARITY \u2014 one attribute says which way a surface leans. State it
|
|
414
|
+
in markup (\`<section data-backdrop="dark">\`) or let \`use:backdrop\` measure
|
|
415
|
+
and stamp it. Either way \`light-dark()\` inside resolves the right half, and
|
|
416
|
+
a rule can key on the attribute for what colour alone cannot carry. */
|
|
417
|
+
|
|
418
|
+
[data-backdrop='light'] {
|
|
419
|
+
color-scheme: light;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
[data-backdrop='dark'] {
|
|
423
|
+
color-scheme: dark;
|
|
424
|
+
}
|
|
425
|
+
`;
|
|
426
|
+
var tokensCssMigration_2026_08_27_backdropPolarity = {
|
|
427
|
+
id: "2026-08-27-backdrop-polarity",
|
|
428
|
+
kind: "additive",
|
|
429
|
+
description: "Add the [data-backdrop] \u2192 color-scheme rules",
|
|
430
|
+
apply(css) {
|
|
431
|
+
if (css.includes(MARKER)) return css;
|
|
432
|
+
const at = css.indexOf(":root");
|
|
433
|
+
if (at === -1) return `${css.trimEnd()}
|
|
434
|
+
|
|
435
|
+
${RULES}`;
|
|
436
|
+
return `${css.slice(0, at)}${RULES}
|
|
437
|
+
${css.slice(at)}`;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// vite-plugin/tokensCssMigrations/migrations/2026-08-27-editorial-size-steps.ts
|
|
442
|
+
var AXES = ["font-family", "font-size", "font-weight", "line-height", "letter-spacing"];
|
|
443
|
+
var BUNDLE2 = [
|
|
444
|
+
{ name: "--editorial-md-font-family", value: "var(--font-editorial)" },
|
|
445
|
+
{ name: "--editorial-md-font-size", value: "var(--font-size-md)" },
|
|
446
|
+
{ name: "--editorial-md-font-weight", value: "var(--font-weight-normal)" },
|
|
447
|
+
{ name: "--editorial-md-line-height", value: "var(--line-height-normal)" },
|
|
448
|
+
{ name: "--editorial-md-letter-spacing", value: "var(--letter-spacing-normal)" },
|
|
449
|
+
{ name: "--editorial-sm-font-family", value: "var(--font-editorial)" },
|
|
450
|
+
{ name: "--editorial-sm-font-size", value: "var(--font-size-sm)" },
|
|
451
|
+
{ name: "--editorial-sm-font-weight", value: "var(--font-weight-normal)" },
|
|
452
|
+
{ name: "--editorial-sm-line-height", value: "var(--line-height-tight)" },
|
|
453
|
+
{ name: "--editorial-sm-letter-spacing", value: "var(--letter-spacing-normal)" }
|
|
454
|
+
];
|
|
455
|
+
var tokensCssMigration_2026_08_27_editorialSizeSteps = {
|
|
456
|
+
id: "2026-08-27-editorial-size-steps",
|
|
457
|
+
kind: "breaking",
|
|
458
|
+
description: "Split the editorial bundle into --editorial-md-* and --editorial-sm-* steps",
|
|
459
|
+
apply(css) {
|
|
460
|
+
let out = css;
|
|
461
|
+
for (const axis of AXES) {
|
|
462
|
+
out = renameToken(out, `--editorial-${axis}`, `--editorial-md-${axis}`);
|
|
463
|
+
}
|
|
464
|
+
return ensureScale(out, {
|
|
465
|
+
sectionComment: "Editorial \u2014 the long-reading role, body face until repointed",
|
|
466
|
+
anchorPrefixes: [
|
|
467
|
+
"--eyebrow-",
|
|
468
|
+
"--code-",
|
|
469
|
+
"--body-",
|
|
470
|
+
"--heading-",
|
|
471
|
+
"--letter-spacing-",
|
|
472
|
+
"--line-height-",
|
|
473
|
+
"--font-weight-",
|
|
474
|
+
"--font-size-",
|
|
475
|
+
"--font-"
|
|
476
|
+
],
|
|
477
|
+
entries: BUNDLE2
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
433
482
|
// vite-plugin/tokensCssMigrations/index.ts
|
|
434
483
|
var TOKENS_CSS_MIGRATIONS = [
|
|
435
484
|
tokensCssMigration_2026_05_29_typographyScaleAdditions,
|
|
@@ -441,7 +490,9 @@ var TOKENS_CSS_MIGRATIONS = [
|
|
|
441
490
|
tokensCssMigration_2026_07_20_semanticTextStyles,
|
|
442
491
|
tokensCssMigration_2026_08_19_oklchColorValues,
|
|
443
492
|
tokensCssMigration_2026_08_25_editorialTypeRole,
|
|
444
|
-
tokensCssMigration_2026_08_26_gradientStops
|
|
493
|
+
tokensCssMigration_2026_08_26_gradientStops,
|
|
494
|
+
tokensCssMigration_2026_08_27_backdropPolarity,
|
|
495
|
+
tokensCssMigration_2026_08_27_editorialSizeSteps
|
|
445
496
|
];
|
|
446
497
|
function runTokensCssMigrations(css) {
|
|
447
498
|
return foldMigrations(css, () => true);
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
scaleToCssVar,
|
|
16
16
|
setCurveAnchor,
|
|
17
17
|
syncBaseAnchor
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-O6GQ6GBH.js";
|
|
19
19
|
import {
|
|
20
20
|
CURRENT_COLORS_AND_TYPE_SCHEMA_VERSION,
|
|
21
21
|
CURRENT_COMPONENT_SCHEMA_VERSION
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
hexToOklch,
|
|
27
27
|
oklchToHex,
|
|
28
28
|
oklchToHexClamped
|
|
29
|
-
} from "../chunk-
|
|
29
|
+
} from "../chunk-7TQQTHI6.js";
|
|
30
30
|
import {
|
|
31
31
|
readLiveTokensConfig,
|
|
32
32
|
resolveDataDirs
|
package/dist-plugin/index.cjs
CHANGED
|
@@ -203,6 +203,9 @@ function sanitizeFileName(name) {
|
|
|
203
203
|
function srgbToLinear(c) {
|
|
204
204
|
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
205
205
|
}
|
|
206
|
+
function linearToSrgb(c) {
|
|
207
|
+
return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
208
|
+
}
|
|
206
209
|
function hexToLinearRgb(hex) {
|
|
207
210
|
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
208
211
|
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
@@ -295,6 +298,13 @@ function cssColorToOklch(value) {
|
|
|
295
298
|
if (HEX6_RE.test(raw)) return hexToOklch(raw);
|
|
296
299
|
return parseOklchCss(raw);
|
|
297
300
|
}
|
|
301
|
+
function oklchToRgb255(l, c, h) {
|
|
302
|
+
const clamped = gamutClamp(l, c, h);
|
|
303
|
+
const [L, a, bVal] = oklchToOklab(clamped.l, clamped.c, clamped.h);
|
|
304
|
+
const [r, g, b] = oklabToLinearRgb(L, a, bVal);
|
|
305
|
+
const to255 = (channel2) => Math.round(Math.max(0, Math.min(1, linearToSrgb(channel2))) * 255);
|
|
306
|
+
return { r: to255(r), g: to255(g), b: to255(b) };
|
|
307
|
+
}
|
|
298
308
|
|
|
299
309
|
// src/editor/ui/curveEngine.ts
|
|
300
310
|
function makeAnchor(x, y, tangentLen = 15) {
|
|
@@ -1520,6 +1530,87 @@ function runMigrations(kind, fileVersion, rawVars, meta = {}) {
|
|
|
1520
1530
|
return out;
|
|
1521
1531
|
}
|
|
1522
1532
|
|
|
1533
|
+
// src/system/backdrop/backdrop.ts
|
|
1534
|
+
var LIGHT_BACKDROP = 0.2;
|
|
1535
|
+
var OPAQUE = 0.98;
|
|
1536
|
+
var HEX_RE = /#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/gi;
|
|
1537
|
+
var OKLCH_RE2 = /oklch\(\s*[\d.]+%?\s+[\d.]+%?\s+[\d.]+(?:deg)?\s*(?:\/\s*([\d.]+%?)\s*)?\)/gi;
|
|
1538
|
+
var RGB_RE = /rgba?\(\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)(?:\s*[,/]\s*([\d.]+%?))?\s*\)/gi;
|
|
1539
|
+
var EXOTIC_RE = /\b(?:color-mix|hwb|lab|lch|oklab|color)\([^()]*\)/gi;
|
|
1540
|
+
function channel(value) {
|
|
1541
|
+
const parsed = Number.parseFloat(value);
|
|
1542
|
+
return value.endsWith("%") ? parsed * 2.55 : parsed;
|
|
1543
|
+
}
|
|
1544
|
+
function alpha(value) {
|
|
1545
|
+
if (value === void 0) return 1;
|
|
1546
|
+
const parsed = Number.parseFloat(value);
|
|
1547
|
+
return value.endsWith("%") ? parsed / 100 : parsed;
|
|
1548
|
+
}
|
|
1549
|
+
function parseHex(hex) {
|
|
1550
|
+
const digits = hex.slice(1);
|
|
1551
|
+
const full = digits.length <= 4 ? digits.split("").map((part) => part + part).join("") : digits;
|
|
1552
|
+
return {
|
|
1553
|
+
r: Number.parseInt(full.slice(0, 2), 16),
|
|
1554
|
+
g: Number.parseInt(full.slice(2, 4), 16),
|
|
1555
|
+
b: Number.parseInt(full.slice(4, 6), 16),
|
|
1556
|
+
a: full.length >= 8 ? Number.parseInt(full.slice(6, 8), 16) / 255 : 1
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
function colorsIn(background) {
|
|
1560
|
+
const colors = [];
|
|
1561
|
+
for (const match of background.matchAll(HEX_RE)) colors.push(parseHex(match[0]));
|
|
1562
|
+
for (const match of background.matchAll(OKLCH_RE2)) {
|
|
1563
|
+
const parsed = parseOklchCss(match[0]);
|
|
1564
|
+
if (parsed) colors.push({ ...oklchToRgb255(parsed.l, parsed.c, parsed.h), a: alpha(match[1]) });
|
|
1565
|
+
}
|
|
1566
|
+
for (const match of background.matchAll(RGB_RE)) {
|
|
1567
|
+
colors.push({
|
|
1568
|
+
r: channel(match[1]),
|
|
1569
|
+
g: channel(match[2]),
|
|
1570
|
+
b: channel(match[3]),
|
|
1571
|
+
a: alpha(match[4])
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
for (const match of background.matchAll(EXOTIC_RE)) {
|
|
1575
|
+
const resolved = viaCanvas(match[0]);
|
|
1576
|
+
if (resolved) colors.push(resolved);
|
|
1577
|
+
}
|
|
1578
|
+
return colors;
|
|
1579
|
+
}
|
|
1580
|
+
function linearChannel(value) {
|
|
1581
|
+
const srgb = Math.max(0, Math.min(255, value)) / 255;
|
|
1582
|
+
return srgb <= 0.04045 ? srgb / 12.92 : ((srgb + 0.055) / 1.055) ** 2.4;
|
|
1583
|
+
}
|
|
1584
|
+
function luminance({ r, g, b }) {
|
|
1585
|
+
return 0.2126 * linearChannel(r) + 0.7152 * linearChannel(g) + 0.0722 * linearChannel(b);
|
|
1586
|
+
}
|
|
1587
|
+
var SEED = "#010203";
|
|
1588
|
+
var probe;
|
|
1589
|
+
function viaCanvas(value) {
|
|
1590
|
+
if (typeof document === "undefined") return null;
|
|
1591
|
+
if (probe === void 0) {
|
|
1592
|
+
probe = document.createElement("canvas").getContext?.("2d", { willReadFrequently: true }) ?? null;
|
|
1593
|
+
}
|
|
1594
|
+
if (!probe) return null;
|
|
1595
|
+
probe.fillStyle = SEED;
|
|
1596
|
+
probe.fillStyle = value;
|
|
1597
|
+
if (probe.fillStyle === SEED) return null;
|
|
1598
|
+
probe.clearRect(0, 0, 1, 1);
|
|
1599
|
+
probe.fillRect(0, 0, 1, 1);
|
|
1600
|
+
const [r, g, b, a] = probe.getImageData(0, 0, 1, 1).data;
|
|
1601
|
+
return { r, g, b, a: a / 255 };
|
|
1602
|
+
}
|
|
1603
|
+
function backgroundLuminance(background) {
|
|
1604
|
+
const opaque = colorsIn(background).filter((color) => color.a >= OPAQUE);
|
|
1605
|
+
if (!opaque.length) return null;
|
|
1606
|
+
return opaque.reduce((total, color) => total + luminance(color), 0) / opaque.length;
|
|
1607
|
+
}
|
|
1608
|
+
function polarityOfBackground(background) {
|
|
1609
|
+
const measured = backgroundLuminance(background);
|
|
1610
|
+
if (measured === null) return null;
|
|
1611
|
+
return measured >= LIGHT_BACKDROP ? "light" : "dark";
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1523
1614
|
// vite-plugin/files/versionedFileResourceServer.ts
|
|
1524
1615
|
var import_fs = __toESM(require("fs"), 1);
|
|
1525
1616
|
var import_path = __toESM(require("path"), 1);
|
|
@@ -2419,37 +2510,15 @@ var tokensCssMigration_2026_08_19_oklchColorValues = {
|
|
|
2419
2510
|
|
|
2420
2511
|
// vite-plugin/tokensCssMigrations/migrations/2026-08-25-editorial-type-role.ts
|
|
2421
2512
|
var STACK = [{ name: "--font-editorial", value: "var(--font-sans)" }];
|
|
2422
|
-
var BUNDLE2 = [
|
|
2423
|
-
{ name: "--editorial-font-family", value: "var(--font-editorial)" },
|
|
2424
|
-
{ name: "--editorial-font-size", value: "var(--font-size-md)" },
|
|
2425
|
-
{ name: "--editorial-font-weight", value: "var(--font-weight-normal)" },
|
|
2426
|
-
{ name: "--editorial-line-height", value: "var(--line-height-normal)" },
|
|
2427
|
-
{ name: "--editorial-letter-spacing", value: "var(--letter-spacing-normal)" }
|
|
2428
|
-
];
|
|
2429
2513
|
var tokensCssMigration_2026_08_25_editorialTypeRole = {
|
|
2430
2514
|
id: "2026-08-25-editorial-type-role",
|
|
2431
2515
|
kind: "additive",
|
|
2432
|
-
description: "Add the editorial type role (--font-editorial
|
|
2516
|
+
description: "Add the editorial type role (--font-editorial)",
|
|
2433
2517
|
apply(css) {
|
|
2434
|
-
|
|
2518
|
+
return ensureScale(css, {
|
|
2435
2519
|
anchorPrefixes: ["--font-mono", "--font-serif", "--font-sans", "--font-display"],
|
|
2436
2520
|
entries: STACK
|
|
2437
2521
|
});
|
|
2438
|
-
return ensureScale(withStack, {
|
|
2439
|
-
sectionComment: "Editorial \u2014 the long-reading role, body face until repointed",
|
|
2440
|
-
anchorPrefixes: [
|
|
2441
|
-
"--eyebrow-",
|
|
2442
|
-
"--code-",
|
|
2443
|
-
"--body-",
|
|
2444
|
-
"--heading-",
|
|
2445
|
-
"--letter-spacing-",
|
|
2446
|
-
"--line-height-",
|
|
2447
|
-
"--font-weight-",
|
|
2448
|
-
"--font-size-",
|
|
2449
|
-
"--font-"
|
|
2450
|
-
],
|
|
2451
|
-
entries: BUNDLE2
|
|
2452
|
-
});
|
|
2453
2522
|
}
|
|
2454
2523
|
};
|
|
2455
2524
|
|
|
@@ -2472,6 +2541,77 @@ var tokensCssMigration_2026_08_26_gradientStops = {
|
|
|
2472
2541
|
}
|
|
2473
2542
|
};
|
|
2474
2543
|
|
|
2544
|
+
// vite-plugin/tokensCssMigrations/migrations/2026-08-27-backdrop-polarity.ts
|
|
2545
|
+
var MARKER = "[data-backdrop='dark']";
|
|
2546
|
+
var RULES = `/* BACKDROP POLARITY \u2014 one attribute says which way a surface leans. State it
|
|
2547
|
+
in markup (\`<section data-backdrop="dark">\`) or let \`use:backdrop\` measure
|
|
2548
|
+
and stamp it. Either way \`light-dark()\` inside resolves the right half, and
|
|
2549
|
+
a rule can key on the attribute for what colour alone cannot carry. */
|
|
2550
|
+
|
|
2551
|
+
[data-backdrop='light'] {
|
|
2552
|
+
color-scheme: light;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
[data-backdrop='dark'] {
|
|
2556
|
+
color-scheme: dark;
|
|
2557
|
+
}
|
|
2558
|
+
`;
|
|
2559
|
+
var tokensCssMigration_2026_08_27_backdropPolarity = {
|
|
2560
|
+
id: "2026-08-27-backdrop-polarity",
|
|
2561
|
+
kind: "additive",
|
|
2562
|
+
description: "Add the [data-backdrop] \u2192 color-scheme rules",
|
|
2563
|
+
apply(css) {
|
|
2564
|
+
if (css.includes(MARKER)) return css;
|
|
2565
|
+
const at = css.indexOf(":root");
|
|
2566
|
+
if (at === -1) return `${css.trimEnd()}
|
|
2567
|
+
|
|
2568
|
+
${RULES}`;
|
|
2569
|
+
return `${css.slice(0, at)}${RULES}
|
|
2570
|
+
${css.slice(at)}`;
|
|
2571
|
+
}
|
|
2572
|
+
};
|
|
2573
|
+
|
|
2574
|
+
// vite-plugin/tokensCssMigrations/migrations/2026-08-27-editorial-size-steps.ts
|
|
2575
|
+
var AXES = ["font-family", "font-size", "font-weight", "line-height", "letter-spacing"];
|
|
2576
|
+
var BUNDLE2 = [
|
|
2577
|
+
{ name: "--editorial-md-font-family", value: "var(--font-editorial)" },
|
|
2578
|
+
{ name: "--editorial-md-font-size", value: "var(--font-size-md)" },
|
|
2579
|
+
{ name: "--editorial-md-font-weight", value: "var(--font-weight-normal)" },
|
|
2580
|
+
{ name: "--editorial-md-line-height", value: "var(--line-height-normal)" },
|
|
2581
|
+
{ name: "--editorial-md-letter-spacing", value: "var(--letter-spacing-normal)" },
|
|
2582
|
+
{ name: "--editorial-sm-font-family", value: "var(--font-editorial)" },
|
|
2583
|
+
{ name: "--editorial-sm-font-size", value: "var(--font-size-sm)" },
|
|
2584
|
+
{ name: "--editorial-sm-font-weight", value: "var(--font-weight-normal)" },
|
|
2585
|
+
{ name: "--editorial-sm-line-height", value: "var(--line-height-tight)" },
|
|
2586
|
+
{ name: "--editorial-sm-letter-spacing", value: "var(--letter-spacing-normal)" }
|
|
2587
|
+
];
|
|
2588
|
+
var tokensCssMigration_2026_08_27_editorialSizeSteps = {
|
|
2589
|
+
id: "2026-08-27-editorial-size-steps",
|
|
2590
|
+
kind: "breaking",
|
|
2591
|
+
description: "Split the editorial bundle into --editorial-md-* and --editorial-sm-* steps",
|
|
2592
|
+
apply(css) {
|
|
2593
|
+
let out = css;
|
|
2594
|
+
for (const axis of AXES) {
|
|
2595
|
+
out = renameToken2(out, `--editorial-${axis}`, `--editorial-md-${axis}`);
|
|
2596
|
+
}
|
|
2597
|
+
return ensureScale(out, {
|
|
2598
|
+
sectionComment: "Editorial \u2014 the long-reading role, body face until repointed",
|
|
2599
|
+
anchorPrefixes: [
|
|
2600
|
+
"--eyebrow-",
|
|
2601
|
+
"--code-",
|
|
2602
|
+
"--body-",
|
|
2603
|
+
"--heading-",
|
|
2604
|
+
"--letter-spacing-",
|
|
2605
|
+
"--line-height-",
|
|
2606
|
+
"--font-weight-",
|
|
2607
|
+
"--font-size-",
|
|
2608
|
+
"--font-"
|
|
2609
|
+
],
|
|
2610
|
+
entries: BUNDLE2
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
};
|
|
2614
|
+
|
|
2475
2615
|
// vite-plugin/tokensCssMigrations/index.ts
|
|
2476
2616
|
var TOKENS_CSS_MIGRATIONS = [
|
|
2477
2617
|
tokensCssMigration_2026_05_29_typographyScaleAdditions,
|
|
@@ -2483,7 +2623,9 @@ var TOKENS_CSS_MIGRATIONS = [
|
|
|
2483
2623
|
tokensCssMigration_2026_07_20_semanticTextStyles,
|
|
2484
2624
|
tokensCssMigration_2026_08_19_oklchColorValues,
|
|
2485
2625
|
tokensCssMigration_2026_08_25_editorialTypeRole,
|
|
2486
|
-
tokensCssMigration_2026_08_26_gradientStops
|
|
2626
|
+
tokensCssMigration_2026_08_26_gradientStops,
|
|
2627
|
+
tokensCssMigration_2026_08_27_backdropPolarity,
|
|
2628
|
+
tokensCssMigration_2026_08_27_editorialSizeSteps
|
|
2487
2629
|
];
|
|
2488
2630
|
function runTokensCssMigrations(css) {
|
|
2489
2631
|
return foldMigrations(css, () => true);
|
|
@@ -2724,6 +2866,7 @@ function themeFileApi(opts) {
|
|
|
2724
2866
|
}
|
|
2725
2867
|
const colorsAndTypeData = productionTheme?.colorsAndType;
|
|
2726
2868
|
let colorsAndTypeVarCount = 0;
|
|
2869
|
+
let pageBackground = "";
|
|
2727
2870
|
if (colorsAndTypeData) {
|
|
2728
2871
|
const cssVars = { ...colorsAndTypeData.cssVariables || {} };
|
|
2729
2872
|
Object.assign(cssVars, palettesToVars(migratePaletteColorsToOklch(colorsAndTypeData.editorConfigs ?? {})));
|
|
@@ -2731,6 +2874,7 @@ function themeFileApi(opts) {
|
|
|
2731
2874
|
for (const [name, value] of Object.entries(resolvedFontVars)) {
|
|
2732
2875
|
cssVars[name] = value;
|
|
2733
2876
|
}
|
|
2877
|
+
pageBackground = cssVars["--page-bg"] ?? "";
|
|
2734
2878
|
colorsAndTypeVarCount = Object.keys(cssVars).length;
|
|
2735
2879
|
if (colorsAndTypeVarCount > 0) {
|
|
2736
2880
|
lines.push(`/* Production theme: ${productionThemeName} */`);
|
|
@@ -2742,6 +2886,16 @@ function themeFileApi(opts) {
|
|
|
2742
2886
|
lines.push("");
|
|
2743
2887
|
}
|
|
2744
2888
|
}
|
|
2889
|
+
{
|
|
2890
|
+
const polarity = pageBackground ? polarityOfBackground(pageBackground) : null;
|
|
2891
|
+
if (polarity) {
|
|
2892
|
+
lines.push(`/* Page polarity, from --page-bg */`);
|
|
2893
|
+
lines.push(":where(:root) {");
|
|
2894
|
+
lines.push(` color-scheme: ${polarity};`);
|
|
2895
|
+
lines.push("}");
|
|
2896
|
+
lines.push("");
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2745
2899
|
let componentOverrideCount = 0;
|
|
2746
2900
|
if (import_fs4.default.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
2747
2901
|
const blocks = [];
|