@motion-proto/live-tokens 0.60.1 → 0.61.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 +34 -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-OIOXU7FR.js} +33 -2
- package/dist-plugin/generateColorsAndType/index.js +2 -2
- package/dist-plugin/index.cjs +135 -1
- package/dist-plugin/index.js +99 -4
- package/dist-plugin/tokensCssMigrations/index.cjs +32 -1
- package/dist-plugin/tokensCssMigrations/index.js +2 -2
- package/package.json +6 -1
- package/src/editor/component-editor/ImageLightboxEditor.svelte +2 -1
- 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.generated.ts +1 -0
- package/src/live-tokens/data/themes/autumn.json +1 -0
- package/src/live-tokens/data/themes/halloween.json +1 -0
- package/src/live-tokens/data/themes/midnight-study.json +1 -0
- package/src/live-tokens/data/themes/ocean.json +1 -0
- package/src/live-tokens/data/themes/royal-velvet.json +1 -0
- package/src/live-tokens/data/themes/sketchy.json +1 -0
- package/src/live-tokens/data/themes/spring-meadow.json +1 -0
- package/src/live-tokens/data/themes/sunset.json +1 -0
- 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/FloatingTokenTags.svelte +1 -1
- package/src/system/components/ImageLightbox.svelte +4 -3
- package/src/system/styles/tokens.css +20 -0
- package/src/system/internal/backgroundContrast.ts +0 -62
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.61.0 — A surface says which way it leans
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **One answer to "is this surface light or dark".** A wordmark drawn in white
|
|
8
|
+
disappears on a pale theme, ink that multiplies onto paper vanishes on a dark
|
|
9
|
+
one, and a headline over a photograph is on a dark ground whatever the palette
|
|
10
|
+
says. Every one of those needs the same fact, and every one of them was
|
|
11
|
+
answering it for itself: a luminance probe here, a `--page-bg` poll there, a
|
|
12
|
+
hand-picked token pair somewhere else. `@motion-proto/live-tokens/backdrop`
|
|
13
|
+
answers it once. `data-backdrop` is the single channel — state it in markup
|
|
14
|
+
where the surface knows its own tone, or let `use:backdrop` measure what
|
|
15
|
+
actually paints and stamp it — and `tokens.css` turns the attribute into
|
|
16
|
+
`color-scheme`, so `light-dark()` under it resolves the half that reads. The
|
|
17
|
+
module also answers in JavaScript, for a canvas, a WebGL uniform, or an image
|
|
18
|
+
that comes in two versions: `isLightBackdrop`, `watchBackdrop`, `polarityOf`,
|
|
19
|
+
`contrastTokenFor`, and `cssColorToHex`. A new guide chapter, **Light and
|
|
20
|
+
dark**, covers the whole of it.
|
|
21
|
+
- **A theme's own polarity is baked, not probed.** Nothing in a saved theme
|
|
22
|
+
recorded whether it was light or dark — the generator takes `scheme` as a
|
|
23
|
+
brief and throws it away — so anything that needed to know had to measure at
|
|
24
|
+
runtime and repaint. `tokens.generated.css` now carries a `color-scheme`
|
|
25
|
+
measured from the production theme's `--page-bg`, at zero specificity: it is
|
|
26
|
+
the answer at first paint, and any `data-backdrop` stamp — the editor
|
|
27
|
+
switching themes live, a section stating its own tone — outranks it.
|
|
28
|
+
|
|
29
|
+
- **`ImageLightbox` has a tile fill.** A diagram drawn in light ink, or any
|
|
30
|
+
cut-out with a transparent ground, disappeared into a light page: the tile is
|
|
31
|
+
exactly the image's own box, so nothing stood behind the art to separate it
|
|
32
|
+
from the section. `--imagelightbox-tile-surface` paints behind the image on
|
|
33
|
+
the closed tile and the opened stage alike, inside the radius and border the
|
|
34
|
+
tile already carries. It takes any fill the editor offers, solid or gradient,
|
|
35
|
+
and defaults to none, so every existing lightbox renders as it did.
|
|
36
|
+
|
|
3
37
|
## 0.60.1 — A drawn heading is type, not an icon
|
|
4
38
|
|
|
5
39
|
### 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) {
|
|
@@ -430,6 +430,36 @@ var tokensCssMigration_2026_08_26_gradientStops = {
|
|
|
430
430
|
}
|
|
431
431
|
};
|
|
432
432
|
|
|
433
|
+
// vite-plugin/tokensCssMigrations/migrations/2026-08-27-backdrop-polarity.ts
|
|
434
|
+
var MARKER = "[data-backdrop='dark']";
|
|
435
|
+
var RULES = `/* BACKDROP POLARITY \u2014 one attribute says which way a surface leans. State it
|
|
436
|
+
in markup (\`<section data-backdrop="dark">\`) or let \`use:backdrop\` measure
|
|
437
|
+
and stamp it. Either way \`light-dark()\` inside resolves the right half, and
|
|
438
|
+
a rule can key on the attribute for what colour alone cannot carry. */
|
|
439
|
+
|
|
440
|
+
[data-backdrop='light'] {
|
|
441
|
+
color-scheme: light;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
[data-backdrop='dark'] {
|
|
445
|
+
color-scheme: dark;
|
|
446
|
+
}
|
|
447
|
+
`;
|
|
448
|
+
var tokensCssMigration_2026_08_27_backdropPolarity = {
|
|
449
|
+
id: "2026-08-27-backdrop-polarity",
|
|
450
|
+
kind: "additive",
|
|
451
|
+
description: "Add the [data-backdrop] \u2192 color-scheme rules",
|
|
452
|
+
apply(css) {
|
|
453
|
+
if (css.includes(MARKER)) return css;
|
|
454
|
+
const at = css.indexOf(":root");
|
|
455
|
+
if (at === -1) return `${css.trimEnd()}
|
|
456
|
+
|
|
457
|
+
${RULES}`;
|
|
458
|
+
return `${css.slice(0, at)}${RULES}
|
|
459
|
+
${css.slice(at)}`;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
433
463
|
// vite-plugin/tokensCssMigrations/index.ts
|
|
434
464
|
var TOKENS_CSS_MIGRATIONS = [
|
|
435
465
|
tokensCssMigration_2026_05_29_typographyScaleAdditions,
|
|
@@ -441,7 +471,8 @@ var TOKENS_CSS_MIGRATIONS = [
|
|
|
441
471
|
tokensCssMigration_2026_07_20_semanticTextStyles,
|
|
442
472
|
tokensCssMigration_2026_08_19_oklchColorValues,
|
|
443
473
|
tokensCssMigration_2026_08_25_editorialTypeRole,
|
|
444
|
-
tokensCssMigration_2026_08_26_gradientStops
|
|
474
|
+
tokensCssMigration_2026_08_26_gradientStops,
|
|
475
|
+
tokensCssMigration_2026_08_27_backdropPolarity
|
|
445
476
|
];
|
|
446
477
|
function runTokensCssMigrations(css) {
|
|
447
478
|
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);
|
|
@@ -2472,6 +2563,36 @@ var tokensCssMigration_2026_08_26_gradientStops = {
|
|
|
2472
2563
|
}
|
|
2473
2564
|
};
|
|
2474
2565
|
|
|
2566
|
+
// vite-plugin/tokensCssMigrations/migrations/2026-08-27-backdrop-polarity.ts
|
|
2567
|
+
var MARKER = "[data-backdrop='dark']";
|
|
2568
|
+
var RULES = `/* BACKDROP POLARITY \u2014 one attribute says which way a surface leans. State it
|
|
2569
|
+
in markup (\`<section data-backdrop="dark">\`) or let \`use:backdrop\` measure
|
|
2570
|
+
and stamp it. Either way \`light-dark()\` inside resolves the right half, and
|
|
2571
|
+
a rule can key on the attribute for what colour alone cannot carry. */
|
|
2572
|
+
|
|
2573
|
+
[data-backdrop='light'] {
|
|
2574
|
+
color-scheme: light;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
[data-backdrop='dark'] {
|
|
2578
|
+
color-scheme: dark;
|
|
2579
|
+
}
|
|
2580
|
+
`;
|
|
2581
|
+
var tokensCssMigration_2026_08_27_backdropPolarity = {
|
|
2582
|
+
id: "2026-08-27-backdrop-polarity",
|
|
2583
|
+
kind: "additive",
|
|
2584
|
+
description: "Add the [data-backdrop] \u2192 color-scheme rules",
|
|
2585
|
+
apply(css) {
|
|
2586
|
+
if (css.includes(MARKER)) return css;
|
|
2587
|
+
const at = css.indexOf(":root");
|
|
2588
|
+
if (at === -1) return `${css.trimEnd()}
|
|
2589
|
+
|
|
2590
|
+
${RULES}`;
|
|
2591
|
+
return `${css.slice(0, at)}${RULES}
|
|
2592
|
+
${css.slice(at)}`;
|
|
2593
|
+
}
|
|
2594
|
+
};
|
|
2595
|
+
|
|
2475
2596
|
// vite-plugin/tokensCssMigrations/index.ts
|
|
2476
2597
|
var TOKENS_CSS_MIGRATIONS = [
|
|
2477
2598
|
tokensCssMigration_2026_05_29_typographyScaleAdditions,
|
|
@@ -2483,7 +2604,8 @@ var TOKENS_CSS_MIGRATIONS = [
|
|
|
2483
2604
|
tokensCssMigration_2026_07_20_semanticTextStyles,
|
|
2484
2605
|
tokensCssMigration_2026_08_19_oklchColorValues,
|
|
2485
2606
|
tokensCssMigration_2026_08_25_editorialTypeRole,
|
|
2486
|
-
tokensCssMigration_2026_08_26_gradientStops
|
|
2607
|
+
tokensCssMigration_2026_08_26_gradientStops,
|
|
2608
|
+
tokensCssMigration_2026_08_27_backdropPolarity
|
|
2487
2609
|
];
|
|
2488
2610
|
function runTokensCssMigrations(css) {
|
|
2489
2611
|
return foldMigrations(css, () => true);
|
|
@@ -2724,6 +2846,7 @@ function themeFileApi(opts) {
|
|
|
2724
2846
|
}
|
|
2725
2847
|
const colorsAndTypeData = productionTheme?.colorsAndType;
|
|
2726
2848
|
let colorsAndTypeVarCount = 0;
|
|
2849
|
+
let pageBackground = "";
|
|
2727
2850
|
if (colorsAndTypeData) {
|
|
2728
2851
|
const cssVars = { ...colorsAndTypeData.cssVariables || {} };
|
|
2729
2852
|
Object.assign(cssVars, palettesToVars(migratePaletteColorsToOklch(colorsAndTypeData.editorConfigs ?? {})));
|
|
@@ -2731,6 +2854,7 @@ function themeFileApi(opts) {
|
|
|
2731
2854
|
for (const [name, value] of Object.entries(resolvedFontVars)) {
|
|
2732
2855
|
cssVars[name] = value;
|
|
2733
2856
|
}
|
|
2857
|
+
pageBackground = cssVars["--page-bg"] ?? "";
|
|
2734
2858
|
colorsAndTypeVarCount = Object.keys(cssVars).length;
|
|
2735
2859
|
if (colorsAndTypeVarCount > 0) {
|
|
2736
2860
|
lines.push(`/* Production theme: ${productionThemeName} */`);
|
|
@@ -2742,6 +2866,16 @@ function themeFileApi(opts) {
|
|
|
2742
2866
|
lines.push("");
|
|
2743
2867
|
}
|
|
2744
2868
|
}
|
|
2869
|
+
{
|
|
2870
|
+
const polarity = pageBackground ? polarityOfBackground(pageBackground) : null;
|
|
2871
|
+
if (polarity) {
|
|
2872
|
+
lines.push(`/* Page polarity, from --page-bg */`);
|
|
2873
|
+
lines.push(":where(:root) {");
|
|
2874
|
+
lines.push(` color-scheme: ${polarity};`);
|
|
2875
|
+
lines.push("}");
|
|
2876
|
+
lines.push("");
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2745
2879
|
let componentOverrideCount = 0;
|
|
2746
2880
|
if (import_fs4.default.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
2747
2881
|
const blocks = [];
|
package/dist-plugin/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
parseGradientValue,
|
|
6
6
|
reconcilePalettesFromCssVars,
|
|
7
7
|
sanitizeFileName
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-O6GQ6GBH.js";
|
|
9
9
|
import {
|
|
10
10
|
THEME_SCHEMA_VERSION,
|
|
11
11
|
detectLegacyLayout,
|
|
@@ -23,10 +23,12 @@ import {
|
|
|
23
23
|
runAdditiveTokensCssMigrations,
|
|
24
24
|
runTokensCssMigrations,
|
|
25
25
|
validateTokensCss
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-OIOXU7FR.js";
|
|
27
27
|
import {
|
|
28
|
-
hexToOklch
|
|
29
|
-
|
|
28
|
+
hexToOklch,
|
|
29
|
+
oklchToRgb255,
|
|
30
|
+
parseOklchCss
|
|
31
|
+
} from "./chunk-7TQQTHI6.js";
|
|
30
32
|
import {
|
|
31
33
|
resolveDataDirs
|
|
32
34
|
} from "./chunk-Y5CNFSSV.js";
|
|
@@ -55,6 +57,87 @@ function migratePaletteColorsToOklch(editorConfigs) {
|
|
|
55
57
|
return out;
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
// src/system/backdrop/backdrop.ts
|
|
61
|
+
var LIGHT_BACKDROP = 0.2;
|
|
62
|
+
var OPAQUE = 0.98;
|
|
63
|
+
var HEX_RE = /#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/gi;
|
|
64
|
+
var OKLCH_RE = /oklch\(\s*[\d.]+%?\s+[\d.]+%?\s+[\d.]+(?:deg)?\s*(?:\/\s*([\d.]+%?)\s*)?\)/gi;
|
|
65
|
+
var RGB_RE = /rgba?\(\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)(?:\s*[,/]\s*([\d.]+%?))?\s*\)/gi;
|
|
66
|
+
var EXOTIC_RE = /\b(?:color-mix|hwb|lab|lch|oklab|color)\([^()]*\)/gi;
|
|
67
|
+
function channel(value) {
|
|
68
|
+
const parsed = Number.parseFloat(value);
|
|
69
|
+
return value.endsWith("%") ? parsed * 2.55 : parsed;
|
|
70
|
+
}
|
|
71
|
+
function alpha(value) {
|
|
72
|
+
if (value === void 0) return 1;
|
|
73
|
+
const parsed = Number.parseFloat(value);
|
|
74
|
+
return value.endsWith("%") ? parsed / 100 : parsed;
|
|
75
|
+
}
|
|
76
|
+
function parseHex(hex) {
|
|
77
|
+
const digits = hex.slice(1);
|
|
78
|
+
const full = digits.length <= 4 ? digits.split("").map((part) => part + part).join("") : digits;
|
|
79
|
+
return {
|
|
80
|
+
r: Number.parseInt(full.slice(0, 2), 16),
|
|
81
|
+
g: Number.parseInt(full.slice(2, 4), 16),
|
|
82
|
+
b: Number.parseInt(full.slice(4, 6), 16),
|
|
83
|
+
a: full.length >= 8 ? Number.parseInt(full.slice(6, 8), 16) / 255 : 1
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function colorsIn(background) {
|
|
87
|
+
const colors = [];
|
|
88
|
+
for (const match of background.matchAll(HEX_RE)) colors.push(parseHex(match[0]));
|
|
89
|
+
for (const match of background.matchAll(OKLCH_RE)) {
|
|
90
|
+
const parsed = parseOklchCss(match[0]);
|
|
91
|
+
if (parsed) colors.push({ ...oklchToRgb255(parsed.l, parsed.c, parsed.h), a: alpha(match[1]) });
|
|
92
|
+
}
|
|
93
|
+
for (const match of background.matchAll(RGB_RE)) {
|
|
94
|
+
colors.push({
|
|
95
|
+
r: channel(match[1]),
|
|
96
|
+
g: channel(match[2]),
|
|
97
|
+
b: channel(match[3]),
|
|
98
|
+
a: alpha(match[4])
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
for (const match of background.matchAll(EXOTIC_RE)) {
|
|
102
|
+
const resolved = viaCanvas(match[0]);
|
|
103
|
+
if (resolved) colors.push(resolved);
|
|
104
|
+
}
|
|
105
|
+
return colors;
|
|
106
|
+
}
|
|
107
|
+
function linearChannel(value) {
|
|
108
|
+
const srgb = Math.max(0, Math.min(255, value)) / 255;
|
|
109
|
+
return srgb <= 0.04045 ? srgb / 12.92 : ((srgb + 0.055) / 1.055) ** 2.4;
|
|
110
|
+
}
|
|
111
|
+
function luminance({ r, g, b }) {
|
|
112
|
+
return 0.2126 * linearChannel(r) + 0.7152 * linearChannel(g) + 0.0722 * linearChannel(b);
|
|
113
|
+
}
|
|
114
|
+
var SEED = "#010203";
|
|
115
|
+
var probe;
|
|
116
|
+
function viaCanvas(value) {
|
|
117
|
+
if (typeof document === "undefined") return null;
|
|
118
|
+
if (probe === void 0) {
|
|
119
|
+
probe = document.createElement("canvas").getContext?.("2d", { willReadFrequently: true }) ?? null;
|
|
120
|
+
}
|
|
121
|
+
if (!probe) return null;
|
|
122
|
+
probe.fillStyle = SEED;
|
|
123
|
+
probe.fillStyle = value;
|
|
124
|
+
if (probe.fillStyle === SEED) return null;
|
|
125
|
+
probe.clearRect(0, 0, 1, 1);
|
|
126
|
+
probe.fillRect(0, 0, 1, 1);
|
|
127
|
+
const [r, g, b, a] = probe.getImageData(0, 0, 1, 1).data;
|
|
128
|
+
return { r, g, b, a: a / 255 };
|
|
129
|
+
}
|
|
130
|
+
function backgroundLuminance(background) {
|
|
131
|
+
const opaque = colorsIn(background).filter((color) => color.a >= OPAQUE);
|
|
132
|
+
if (!opaque.length) return null;
|
|
133
|
+
return opaque.reduce((total, color) => total + luminance(color), 0) / opaque.length;
|
|
134
|
+
}
|
|
135
|
+
function polarityOfBackground(background) {
|
|
136
|
+
const measured = backgroundLuminance(background);
|
|
137
|
+
if (measured === null) return null;
|
|
138
|
+
return measured >= LIGHT_BACKDROP ? "light" : "dark";
|
|
139
|
+
}
|
|
140
|
+
|
|
58
141
|
// vite-plugin/files/routeTable.ts
|
|
59
142
|
async function dispatch(req, res, routes) {
|
|
60
143
|
const url = req.url || "";
|
|
@@ -277,6 +360,7 @@ function themeFileApi(opts) {
|
|
|
277
360
|
}
|
|
278
361
|
const colorsAndTypeData = productionTheme?.colorsAndType;
|
|
279
362
|
let colorsAndTypeVarCount = 0;
|
|
363
|
+
let pageBackground = "";
|
|
280
364
|
if (colorsAndTypeData) {
|
|
281
365
|
const cssVars = { ...colorsAndTypeData.cssVariables || {} };
|
|
282
366
|
Object.assign(cssVars, palettesToVars(migratePaletteColorsToOklch(colorsAndTypeData.editorConfigs ?? {})));
|
|
@@ -284,6 +368,7 @@ function themeFileApi(opts) {
|
|
|
284
368
|
for (const [name, value] of Object.entries(resolvedFontVars)) {
|
|
285
369
|
cssVars[name] = value;
|
|
286
370
|
}
|
|
371
|
+
pageBackground = cssVars["--page-bg"] ?? "";
|
|
287
372
|
colorsAndTypeVarCount = Object.keys(cssVars).length;
|
|
288
373
|
if (colorsAndTypeVarCount > 0) {
|
|
289
374
|
lines.push(`/* Production theme: ${productionThemeName} */`);
|
|
@@ -295,6 +380,16 @@ function themeFileApi(opts) {
|
|
|
295
380
|
lines.push("");
|
|
296
381
|
}
|
|
297
382
|
}
|
|
383
|
+
{
|
|
384
|
+
const polarity = pageBackground ? polarityOfBackground(pageBackground) : null;
|
|
385
|
+
if (polarity) {
|
|
386
|
+
lines.push(`/* Page polarity, from --page-bg */`);
|
|
387
|
+
lines.push(":where(:root) {");
|
|
388
|
+
lines.push(` color-scheme: ${polarity};`);
|
|
389
|
+
lines.push("}");
|
|
390
|
+
lines.push("");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
298
393
|
let componentOverrideCount = 0;
|
|
299
394
|
if (fs.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
300
395
|
const blocks = [];
|
|
@@ -517,6 +517,36 @@ var tokensCssMigration_2026_08_26_gradientStops = {
|
|
|
517
517
|
}
|
|
518
518
|
};
|
|
519
519
|
|
|
520
|
+
// vite-plugin/tokensCssMigrations/migrations/2026-08-27-backdrop-polarity.ts
|
|
521
|
+
var MARKER = "[data-backdrop='dark']";
|
|
522
|
+
var RULES = `/* BACKDROP POLARITY \u2014 one attribute says which way a surface leans. State it
|
|
523
|
+
in markup (\`<section data-backdrop="dark">\`) or let \`use:backdrop\` measure
|
|
524
|
+
and stamp it. Either way \`light-dark()\` inside resolves the right half, and
|
|
525
|
+
a rule can key on the attribute for what colour alone cannot carry. */
|
|
526
|
+
|
|
527
|
+
[data-backdrop='light'] {
|
|
528
|
+
color-scheme: light;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
[data-backdrop='dark'] {
|
|
532
|
+
color-scheme: dark;
|
|
533
|
+
}
|
|
534
|
+
`;
|
|
535
|
+
var tokensCssMigration_2026_08_27_backdropPolarity = {
|
|
536
|
+
id: "2026-08-27-backdrop-polarity",
|
|
537
|
+
kind: "additive",
|
|
538
|
+
description: "Add the [data-backdrop] \u2192 color-scheme rules",
|
|
539
|
+
apply(css) {
|
|
540
|
+
if (css.includes(MARKER)) return css;
|
|
541
|
+
const at = css.indexOf(":root");
|
|
542
|
+
if (at === -1) return `${css.trimEnd()}
|
|
543
|
+
|
|
544
|
+
${RULES}`;
|
|
545
|
+
return `${css.slice(0, at)}${RULES}
|
|
546
|
+
${css.slice(at)}`;
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
520
550
|
// vite-plugin/files/dataPaths.ts
|
|
521
551
|
var import_fs = __toESM(require("fs"), 1);
|
|
522
552
|
var import_path = __toESM(require("path"), 1);
|
|
@@ -562,7 +592,8 @@ var TOKENS_CSS_MIGRATIONS = [
|
|
|
562
592
|
tokensCssMigration_2026_07_20_semanticTextStyles,
|
|
563
593
|
tokensCssMigration_2026_08_19_oklchColorValues,
|
|
564
594
|
tokensCssMigration_2026_08_25_editorialTypeRole,
|
|
565
|
-
tokensCssMigration_2026_08_26_gradientStops
|
|
595
|
+
tokensCssMigration_2026_08_26_gradientStops,
|
|
596
|
+
tokensCssMigration_2026_08_27_backdropPolarity
|
|
566
597
|
];
|
|
567
598
|
function runTokensCssMigrations(css) {
|
|
568
599
|
return foldMigrations(css, () => true);
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
runTokensCssMigrations,
|
|
14
14
|
semverBumpType,
|
|
15
15
|
validateTokensCss
|
|
16
|
-
} from "../chunk-
|
|
17
|
-
import "../chunk-
|
|
16
|
+
} from "../chunk-OIOXU7FR.js";
|
|
17
|
+
import "../chunk-7TQQTHI6.js";
|
|
18
18
|
import {
|
|
19
19
|
readLiveTokensConfig
|
|
20
20
|
} from "../chunk-Y5CNFSSV.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motion-proto/live-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
|
|
6
6
|
"keywords": [
|
|
@@ -90,6 +90,11 @@
|
|
|
90
90
|
"svelte": "./src/editor/docs/Docs.svelte",
|
|
91
91
|
"default": "./src/editor/docs/Docs.svelte"
|
|
92
92
|
},
|
|
93
|
+
"./backdrop": {
|
|
94
|
+
"svelte": "./src/system/backdrop/index.ts",
|
|
95
|
+
"types": "./src/system/backdrop/index.ts",
|
|
96
|
+
"default": "./src/system/backdrop/index.ts"
|
|
97
|
+
},
|
|
93
98
|
"./components/*": {
|
|
94
99
|
"svelte": "./src/system/components/*",
|
|
95
100
|
"default": "./src/system/components/*"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// for the interactive controls. No second variant.
|
|
9
9
|
const states: Record<string, Token[]> = {
|
|
10
10
|
tile: [
|
|
11
|
+
{ label: 'fill color', groupKey: 'surface', variable: '--imagelightbox-tile-surface' },
|
|
11
12
|
{ label: 'corner radius', groupKey: 'radius', variable: '--imagelightbox-tile-radius' },
|
|
12
13
|
{ label: 'border color', groupKey: 'border', variable: '--imagelightbox-tile-border' },
|
|
13
14
|
{ label: 'border width', groupKey: 'width', variable: '--imagelightbox-tile-border-width' },
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
<ComponentEditorBase
|
|
51
52
|
{component}
|
|
52
53
|
title="Image Lightbox"
|
|
53
|
-
description="Click an inline image to expand it into a centered modal with a backdrop. Pass multiple images for a gallery (chevrons + counter). Extended mode adds zoom controls and drag panning. `shadow` picks where the one tile-shadow token falls: `box` casts it from the tile rectangle, `content` from the image's own alpha, or `none`."
|
|
54
|
+
description="Click an inline image to expand it into a centered modal with a backdrop. Pass multiple images for a gallery (chevrons + counter). Extended mode adds zoom controls and drag panning. `shadow` picks where the one tile-shadow token falls: `box` casts it from the tile rectangle, `content` from the image's own alpha, or `none`. The tile fill sits behind the image in both states: none by default, so cut-out art floats on the page, and any fill you set frames it instead."
|
|
54
55
|
tokens={allTokens}
|
|
55
56
|
>
|
|
56
57
|
<VariantGroup name="imagelightbox" title="Image Lightbox" {states} {component}>
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
{ path: 'sketch-mode', title: 'Sketch mode' },
|
|
102
102
|
{ path: 'themes-workflow', title: 'Themes' },
|
|
103
103
|
{ path: 'where-themes-live', title: 'Where themes live' },
|
|
104
|
+
{ path: 'light-and-dark', title: 'Light and dark' },
|
|
104
105
|
{ path: 'creating-components', title: 'Creating components' },
|
|
105
106
|
],
|
|
106
107
|
},
|
|
@@ -16,6 +16,7 @@ export const chapters: Chapter[] = [
|
|
|
16
16
|
{ id: 'sketch-mode', title: 'Sketch mode' },
|
|
17
17
|
{ id: 'themes-workflow', title: 'Themes' },
|
|
18
18
|
{ id: 'where-themes-live', title: 'Where themes live' },
|
|
19
|
+
{ id: 'light-and-dark', title: 'Light and dark' },
|
|
19
20
|
{ id: 'creating-components', title: 'Creating components' },
|
|
20
21
|
];
|
|
21
22
|
|