@motion-proto/live-tokens 0.53.0 → 0.54.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-adjust-shape-space/SKILL.md +21 -2
- package/.claude/skills/live-tokens-generate-theme/SKILL.md +16 -1
- package/CHANGELOG.md +26 -0
- package/bin/generate-theme.mjs +1 -0
- package/dist-plugin/adjust/index.cjs +10 -6
- package/dist-plugin/adjust/index.js +10 -6
- package/dist-plugin/generateColorsAndType/index.cjs +63 -0
- package/dist-plugin/generateColorsAndType/index.d.cts +4 -1
- package/dist-plugin/generateColorsAndType/index.d.ts +4 -1
- package/dist-plugin/generateColorsAndType/index.js +63 -0
- package/package.json +1 -1
- package/src/editor/core/components/adjustAliases.ts +23 -7
- package/src/editor/core/themes/generateColorsAndType.ts +50 -1
- package/src/editor/core/themes/parsers/shadow.ts +52 -0
- package/src/editor/core/themes/slices/shadows.ts +2 -38
- package/src/live-tokens/data/colors-and-type/autumn.json +5 -5
- package/src/live-tokens/data/colors-and-type/ocean.json +5 -5
- package/src/live-tokens/data/colors-and-type/spring-meadow.json +5 -5
- package/src/live-tokens/data/themes/autumn.json +5 -5
- package/src/live-tokens/data/themes/midnight-study.json +39 -39
- package/src/live-tokens/data/themes/ocean.json +5 -5
- package/src/live-tokens/data/themes/spring-meadow.json +5 -5
- package/src/system/components/FloatingTokenTags.css +6 -4
- package/src/system/internal/backgroundContrast.ts +2 -1
|
@@ -11,7 +11,7 @@ You translate the request into a small ops file; the CLI resolves each matching
|
|
|
11
11
|
|
|
12
12
|
1. Write the ops file to a temp path (not the project tree), e.g. `/tmp/adjust-ops.json`.
|
|
13
13
|
2. Run `npx live-tokens adjust /tmp/adjust-ops.json`. It writes `component-configs/<id>/_working.json` for every component the ops change, which is the buffer the page already runs. `--dry-run` prints the report without writing.
|
|
14
|
-
3. Read the report card: every changed alias old → new, plus skips (raw value, off the ladder, already at the ladder end, pill preserved). Exit 1 means the run was rejected; the message names the offending op or the missing input, so fix it and re-run.
|
|
14
|
+
3. Read the report card: every changed alias old → new, plus skips (raw value, off the ladder, already at the ladder end, pill preserved). Exit 1 means the run was rejected; the message names the offending op or the missing input, so fix it and re-run. Read where the controls landed, not only that the run succeeded: a button, badge, input, or tab padding sitting at `--space-6` is on its floor, and one that also carries `--radius-full` wants a targeted lift.
|
|
15
15
|
4. Tell the user to reload the app and look. Offer the inverse op as the undo, and say the edit is unsaved until they save the open theme.
|
|
16
16
|
|
|
17
17
|
Each run reads the LIVE config (buffer, else the open theme, else the shipped default), so "a bit more" and "back one" compound naturally.
|
|
@@ -40,7 +40,7 @@ Targeted, absolute:
|
|
|
40
40
|
|
|
41
41
|
| Request | Ops |
|
|
42
42
|
|---|---|
|
|
43
|
-
| pill, capsule | radius `set: "--radius-full"
|
|
43
|
+
| pill, capsule | radius `set: "--radius-full"`, plus the padding the pill needs (see below) |
|
|
44
44
|
| sharp, square corners | radius `set: "--radius-none"`, or `--radius-sm` for "mostly sharp" |
|
|
45
45
|
| rounded (a named component) | radius `shift: 2` |
|
|
46
46
|
| softer, rounder (global) | radius `shift: 1` to `2`, no `full` |
|
|
@@ -52,12 +52,30 @@ Targeted, absolute:
|
|
|
52
52
|
|
|
53
53
|
Magnitude words: "slightly" or "a bit" is 1 step, unqualified is 1 to 2, "much", "way", or "really" is 2 to 3. Mood words often mean both axes: "softer" is rounder plus airier, "compact" is tighter padding plus smaller gaps.
|
|
54
54
|
|
|
55
|
+
## Controls squeeze before containers
|
|
56
|
+
|
|
57
|
+
A global op spends the same number of steps everywhere, but a step costs a control far more than a container. `padding shift: -2` takes a card from a 16px inset to 10px and it is still a card. It takes a button from 8 to 4, doubled to 8px at each end, around an 18px line. The button stops reading as a button.
|
|
58
|
+
|
|
59
|
+
So a global compaction is `shift: -1`. When the brief wants more, spend the extra steps on the containers by name (`card`, `dialog`, `panel`, `sidenavigation`, `table`, `codesnippet`) and leave the controls alone. Loosening is not symmetric: airier is safe globally, because nothing breaks by growing.
|
|
60
|
+
|
|
61
|
+
A pill needs the room most. `--radius-full` bends the corner in over the first and last glyph, so a capsule wants more horizontal inset than a square-cornered control, never less. `--space-8` is the floor for a large-text pill, which is where compact Midnight Study sits; the roomier pill presets (Ocean, Sunset, Royal Velvet) run `--space-10` to `--space-12`. Pair the radius op with a padding `set` on the same target, placed after any global compaction so it wins outright:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{ "ops": [
|
|
65
|
+
{ "kind": "padding", "shift": -1 },
|
|
66
|
+
{ "target": "button", "kind": "radius", "set": "--radius-full" },
|
|
67
|
+
{ "target": "button", "kind": "padding", "set": "--space-10" }
|
|
68
|
+
] }
|
|
69
|
+
```
|
|
70
|
+
|
|
55
71
|
## Ladders
|
|
56
72
|
|
|
57
73
|
Radius runs `none, sm, md, lg, xl, 2xl, 3xl, 4xl`, with `full` as the gated ninth rung. Space (padding and gap) is the editor picker's subset: `0, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32, 48`, so every written value stays re-editable by hand. Border width is the full `--border-width-*` scale. `set` values must be on the ladder (`--space-64` is rejected).
|
|
58
74
|
|
|
59
75
|
Content insets stop at `--space-4`. Below it the text sits against its own edge, so `--space-0` and `--space-2` are destinations a person picks on purpose, not ones a relative "tighter" hands you. Both stay available through the editor picker and through `set`. An alias already below the floor still moves up, and a shift that would push one under `--space-4` reports as clamped and writes nothing.
|
|
60
76
|
|
|
77
|
+
Padding that wraps a line of type stops a rung higher, at `--space-6`. The engine spots it in the config itself: a variant that also declares a `-text-font-size` is holding text, and the components that hold text double their padding horizontally, so `--space-4` there is 4px over an 18px line and 8px at each end. No shipped default puts text below `--space-6`.
|
|
78
|
+
|
|
61
79
|
The floor guards `-padding` only. Outer space is exempt, because a 2px gap between an icon and its label, or a 2px margin under a bar, is ordinary design rather than a mistake. Note that `-margin` rides the `padding` kind, so a padding op moves margins too; it just does not floor them.
|
|
62
80
|
|
|
63
81
|
An alias sitting off the subset spends its first step reaching the rung the shift points at, so `--space-2` with `shift: 1` lands on `--space-4` rather than jumping past it.
|
|
@@ -70,5 +88,6 @@ Every value written is an existing token; nothing new is minted. `tokens.css`, s
|
|
|
70
88
|
|
|
71
89
|
- The CLI exits 0 and the report card lists the changes you expected, with no surprising skips.
|
|
72
90
|
- The app (dev server running) shows the new shape on each changed component after a reload.
|
|
91
|
+
- Buttons still read as buttons: the label has room at both ends, and a pill has more of it than a square-cornered control had.
|
|
73
92
|
- `component-configs/<id>/_working.json` exists for every component the report listed. That buffer is the whole change: it stays until the open theme is saved or another theme is loaded.
|
|
74
93
|
- To revert, run the inverse ops, or load a theme in the Theme panel to discard every unsaved edit.
|
|
@@ -130,6 +130,21 @@ A sky needs a committed canvas: when the Canvas seed anchors at the ramp edge
|
|
|
130
130
|
side and the engine skips the sky, saying so in the report. An atmospheric
|
|
131
131
|
brief that wants one should already be at canvas commitment level 2–3.
|
|
132
132
|
|
|
133
|
+
## Shadow weight follows the canvas
|
|
134
|
+
|
|
135
|
+
The engine sets the opacity of the `--shadow-*` scale from the Canvas seed's
|
|
136
|
+
lightness and carries each shadow's geometry and color through untouched. A
|
|
137
|
+
near-black shadow at 0.9 opacity is what a dark ground needs to show any
|
|
138
|
+
shadow at all, and it punches a hole in paper, so opacity holds at 0.9 up to
|
|
139
|
+
canvas L 0.5 and eases to 0.2 by L 0.9. There is nothing to choose: the report
|
|
140
|
+
prints the derived value, and every regeneration re-derives it, so a brief
|
|
141
|
+
iterated from a light canvas to a dark one gets its weight back.
|
|
142
|
+
|
|
143
|
+
When the user says the shadows read heavy, muddy, or dirty, the canvas is the
|
|
144
|
+
lever. Raise the Canvas seed's lightness and the shadows lighten with it. A
|
|
145
|
+
one-off override lives in the editor's Shadows tab and survives regeneration
|
|
146
|
+
in everything except opacity.
|
|
147
|
+
|
|
133
148
|
## Named themes (canonical OKLCH anchors)
|
|
134
149
|
|
|
135
150
|
Holiday briefs are statement briefs — default to commitment level 2–3 above, not cream. The named colors go on the *ground*, not just the buttons.
|
|
@@ -143,7 +158,7 @@ Holiday briefs are statement briefs — default to commitment level 2–3 above,
|
|
|
143
158
|
|
|
144
159
|
## Scope
|
|
145
160
|
|
|
146
|
-
Fonts are never touched (they carry forward from the live look, as do
|
|
161
|
+
Fonts are never touched (they carry forward from the live look, as do component aliases and shadow geometry; shadow opacity is derived, see above). Swatch gradients (`--gradient-1..4`) carry forward when user-tuned; if they are absent or still stock, the engine rebuilds them from the new theme's families — you never author them. Radius is out of scope for generation. Shipping the theme stays a human action: Adopt, in the editor.
|
|
147
162
|
|
|
148
163
|
## Verify
|
|
149
164
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.54.0 — Shadow weight and text insets follow their context
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Padding that holds text stops at `--space-6`.** One floor for every inset
|
|
8
|
+
treated a card and a button alike, but the components that hold text double
|
|
9
|
+
their padding horizontally, so `--space-4` on a button is 4px over an 18px
|
|
10
|
+
line and 8px at each end. `adjust` now reads which floor applies off the
|
|
11
|
+
config itself: a variant that also declares a `-text-font-size` is holding
|
|
12
|
+
type. Containers keep the `--space-4` floor, `-margin` and `-gap` stay
|
|
13
|
+
exempt, and both rungs stay reachable by `set` and by the editor picker.
|
|
14
|
+
Midnight Study shipped with every text inset on the old floor, its pill
|
|
15
|
+
buttons worst of all; its controls move up, and the six full-size button
|
|
16
|
+
variants to `--space-8` so the capsule has room for its label.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Shadow weight follows the canvas.** The `--shadow-*` scale was one fixed
|
|
21
|
+
near-black at 0.9 opacity in every theme, which is what a dark ground needs
|
|
22
|
+
and what puts a smudge under every card on a light one. `generate-theme` now
|
|
23
|
+
derives the opacity from the Canvas seed's lightness: 0.9 holds up to L 0.5
|
|
24
|
+
and eases to 0.2 by L 0.9. Geometry and color carry forward untouched, so a
|
|
25
|
+
hand-tuned elevation ramp survives regeneration. The three light presets
|
|
26
|
+
(Ocean, Spring Meadow, Autumn) ship with the derived values; the dark presets
|
|
27
|
+
and the default theme are unchanged.
|
|
28
|
+
|
|
3
29
|
## 0.53.0 — Themes are complete documents
|
|
4
30
|
|
|
5
31
|
### Fixed
|
package/bin/generate-theme.mjs
CHANGED
|
@@ -241,6 +241,7 @@ export function formatGenerateThemeResult(result) {
|
|
|
241
241
|
? '\nGradients: kept your tuned swatch gradients.'
|
|
242
242
|
: '\nGradients: swatch tokens rebuilt from the theme families.',
|
|
243
243
|
);
|
|
244
|
+
lines.push(`Shadows: ${result.report.shadows}; carried geometry kept.`);
|
|
244
245
|
if (result.report.canvasGradient) {
|
|
245
246
|
lines.push(`Canvas sky: ${result.report.canvasGradient}.`);
|
|
246
247
|
}
|
|
@@ -102,6 +102,7 @@ var SPACE_SETTABLE = [
|
|
|
102
102
|
];
|
|
103
103
|
var SPACE_RUNGS = SPACE_SETTABLE;
|
|
104
104
|
var INSET_RUNGS = SPACE_SETTABLE.slice(SPACE_SETTABLE.indexOf("--space-4"));
|
|
105
|
+
var TEXT_INSET_RUNGS = SPACE_SETTABLE.slice(SPACE_SETTABLE.indexOf("--space-6"));
|
|
105
106
|
var SPACE_FAMILY = [...SPACE_SETTABLE, "--space-40", "--space-64", "--space-96", "--space-128"];
|
|
106
107
|
var BORDER_WIDTH_RUNGS = [
|
|
107
108
|
"--border-width-0",
|
|
@@ -133,10 +134,13 @@ var LADDERS = {
|
|
|
133
134
|
}
|
|
134
135
|
};
|
|
135
136
|
var TOKEN_NAME = /^--[a-z0-9-]+$/;
|
|
136
|
-
function rungsFor(op, variable) {
|
|
137
|
+
function rungsFor(op, variable, aliases) {
|
|
137
138
|
if (op.kind === "radius") return op.full ? [...RADIUS_RUNGS, RADIUS_FULL] : RADIUS_RUNGS;
|
|
138
|
-
if (op.kind
|
|
139
|
-
|
|
139
|
+
if (op.kind !== "padding") return LADDERS[op.kind].rungs;
|
|
140
|
+
const base = stripSide(variable);
|
|
141
|
+
if (!base.endsWith("-padding")) return LADDERS.padding.rungs;
|
|
142
|
+
const variant = base.slice(0, -"-padding".length);
|
|
143
|
+
return `${variant}-text-font-size` in aliases ? TEXT_INSET_RUNGS : INSET_RUNGS;
|
|
140
144
|
}
|
|
141
145
|
function spaceStep(token) {
|
|
142
146
|
return Number(token.slice("--space-".length));
|
|
@@ -149,11 +153,11 @@ function snapRung(token, rungs, direction) {
|
|
|
149
153
|
}
|
|
150
154
|
return -1;
|
|
151
155
|
}
|
|
152
|
-
function resolve(op, variable, value) {
|
|
156
|
+
function resolve(op, variable, value, aliases) {
|
|
153
157
|
if (typeof value !== "string" || !TOKEN_NAME.test(value)) return { skip: "raw-value" };
|
|
154
158
|
if (!LADDERS[op.kind].family.includes(value)) return { skip: "off-ladder" };
|
|
155
159
|
if (op.set !== void 0) return { from: value, to: op.set };
|
|
156
|
-
const rungs = rungsFor(op, variable);
|
|
160
|
+
const rungs = rungsFor(op, variable, aliases);
|
|
157
161
|
let index = rungs.indexOf(value);
|
|
158
162
|
let shift = op.shift;
|
|
159
163
|
if (index < 0) {
|
|
@@ -206,7 +210,7 @@ function adjustAliases(configs, ops, now) {
|
|
|
206
210
|
let changed = false;
|
|
207
211
|
for (const [variable, value] of Object.entries(config.aliases)) {
|
|
208
212
|
if (!matchesKind(variable, op.kind)) continue;
|
|
209
|
-
const outcome = resolve(op, variable, value);
|
|
213
|
+
const outcome = resolve(op, variable, value, config.aliases);
|
|
210
214
|
if ("skip" in outcome) {
|
|
211
215
|
reportFor(component).skips.push({ variable, value, reason: outcome.skip });
|
|
212
216
|
continue;
|
|
@@ -70,6 +70,7 @@ var SPACE_SETTABLE = [
|
|
|
70
70
|
];
|
|
71
71
|
var SPACE_RUNGS = SPACE_SETTABLE;
|
|
72
72
|
var INSET_RUNGS = SPACE_SETTABLE.slice(SPACE_SETTABLE.indexOf("--space-4"));
|
|
73
|
+
var TEXT_INSET_RUNGS = SPACE_SETTABLE.slice(SPACE_SETTABLE.indexOf("--space-6"));
|
|
73
74
|
var SPACE_FAMILY = [...SPACE_SETTABLE, "--space-40", "--space-64", "--space-96", "--space-128"];
|
|
74
75
|
var BORDER_WIDTH_RUNGS = [
|
|
75
76
|
"--border-width-0",
|
|
@@ -101,10 +102,13 @@ var LADDERS = {
|
|
|
101
102
|
}
|
|
102
103
|
};
|
|
103
104
|
var TOKEN_NAME = /^--[a-z0-9-]+$/;
|
|
104
|
-
function rungsFor(op, variable) {
|
|
105
|
+
function rungsFor(op, variable, aliases) {
|
|
105
106
|
if (op.kind === "radius") return op.full ? [...RADIUS_RUNGS, RADIUS_FULL] : RADIUS_RUNGS;
|
|
106
|
-
if (op.kind
|
|
107
|
-
|
|
107
|
+
if (op.kind !== "padding") return LADDERS[op.kind].rungs;
|
|
108
|
+
const base = stripSide(variable);
|
|
109
|
+
if (!base.endsWith("-padding")) return LADDERS.padding.rungs;
|
|
110
|
+
const variant = base.slice(0, -"-padding".length);
|
|
111
|
+
return `${variant}-text-font-size` in aliases ? TEXT_INSET_RUNGS : INSET_RUNGS;
|
|
108
112
|
}
|
|
109
113
|
function spaceStep(token) {
|
|
110
114
|
return Number(token.slice("--space-".length));
|
|
@@ -117,11 +121,11 @@ function snapRung(token, rungs, direction) {
|
|
|
117
121
|
}
|
|
118
122
|
return -1;
|
|
119
123
|
}
|
|
120
|
-
function resolve(op, variable, value) {
|
|
124
|
+
function resolve(op, variable, value, aliases) {
|
|
121
125
|
if (typeof value !== "string" || !TOKEN_NAME.test(value)) return { skip: "raw-value" };
|
|
122
126
|
if (!LADDERS[op.kind].family.includes(value)) return { skip: "off-ladder" };
|
|
123
127
|
if (op.set !== void 0) return { from: value, to: op.set };
|
|
124
|
-
const rungs = rungsFor(op, variable);
|
|
128
|
+
const rungs = rungsFor(op, variable, aliases);
|
|
125
129
|
let index = rungs.indexOf(value);
|
|
126
130
|
let shift = op.shift;
|
|
127
131
|
if (index < 0) {
|
|
@@ -174,7 +178,7 @@ function adjustAliases(configs, ops, now) {
|
|
|
174
178
|
let changed = false;
|
|
175
179
|
for (const [variable, value] of Object.entries(config.aliases)) {
|
|
176
180
|
if (!matchesKind(variable, op.kind)) continue;
|
|
177
|
-
const outcome = resolve(op, variable, value);
|
|
181
|
+
const outcome = resolve(op, variable, value, config.aliases);
|
|
178
182
|
if ("skip" in outcome) {
|
|
179
183
|
reportFor(component).skips.push({ variable, value, reason: outcome.skip });
|
|
180
184
|
continue;
|
|
@@ -691,6 +691,39 @@ function makeDefaultGradients() {
|
|
|
691
691
|
];
|
|
692
692
|
}
|
|
693
693
|
|
|
694
|
+
// src/editor/core/themes/parsers/shadow.ts
|
|
695
|
+
var SHADOW_VAR_NAMES = [
|
|
696
|
+
"--shadow-sm",
|
|
697
|
+
"--shadow-md",
|
|
698
|
+
"--shadow-lg",
|
|
699
|
+
"--shadow-xl",
|
|
700
|
+
"--shadow-2xl"
|
|
701
|
+
];
|
|
702
|
+
function computeAngleDistance(x, y) {
|
|
703
|
+
const distance = Math.round(Math.sqrt(x * x + y * y));
|
|
704
|
+
if (distance === 0) return { angle: 135, distance: 0 };
|
|
705
|
+
let angle = Math.atan2(y, -x) * (180 / Math.PI);
|
|
706
|
+
if (angle < 0) angle += 360;
|
|
707
|
+
return { angle: Math.round(angle), distance };
|
|
708
|
+
}
|
|
709
|
+
function shadowTokenCss(t) {
|
|
710
|
+
return `${t.x}px ${t.y}px ${t.blur}px ${t.spread}px hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
|
|
711
|
+
}
|
|
712
|
+
function parseShadowCss(variable, raw) {
|
|
713
|
+
const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(-?\d+)px\s+hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
|
|
714
|
+
if (!m) return null;
|
|
715
|
+
const x = parseInt(m[1], 10);
|
|
716
|
+
const y = parseInt(m[2], 10);
|
|
717
|
+
const blur = parseInt(m[3], 10);
|
|
718
|
+
const spread = parseInt(m[4], 10);
|
|
719
|
+
const hue = Math.round(parseFloat(m[5]));
|
|
720
|
+
const saturation = Math.round(parseFloat(m[6]));
|
|
721
|
+
const lightness = Math.round(parseFloat(m[7]));
|
|
722
|
+
const opacity = parseFloat(m[8]);
|
|
723
|
+
const { angle, distance } = computeAngleDistance(x, y);
|
|
724
|
+
return { variable, x, y, blur, spread, opacity, hue, saturation, lightness, angle, distance };
|
|
725
|
+
}
|
|
726
|
+
|
|
694
727
|
// src/editor/core/palettes/contrast.ts
|
|
695
728
|
var AA_BODY = 4.5;
|
|
696
729
|
var AA_LARGE = 3;
|
|
@@ -1760,6 +1793,34 @@ function applyCanvasGradient(canvas, scheme) {
|
|
|
1760
1793
|
canvas.gradientSize = "window";
|
|
1761
1794
|
return `on, ${labels[sky]} \u2192 ${labels[anchor]}`;
|
|
1762
1795
|
}
|
|
1796
|
+
var STOCK_SHADOW_GEOMETRY = {
|
|
1797
|
+
"--shadow-sm": [1, 1, 2],
|
|
1798
|
+
"--shadow-md": [3, 3, 6],
|
|
1799
|
+
"--shadow-lg": [5, 5, 9],
|
|
1800
|
+
"--shadow-xl": [6, 7, 13],
|
|
1801
|
+
"--shadow-2xl": [8, 9, 16]
|
|
1802
|
+
};
|
|
1803
|
+
var STOCK_SHADOW_COLOR = { spread: 0, hue: 237, saturation: 18, lightness: 3, opacity: 0.9 };
|
|
1804
|
+
var SHADOW_OPACITY_DEEP = 0.9;
|
|
1805
|
+
var SHADOW_OPACITY_SOFT = 0.2;
|
|
1806
|
+
var SHADOW_GROUND_DEEP = 0.5;
|
|
1807
|
+
var SHADOW_GROUND_SOFT = 0.9;
|
|
1808
|
+
function shadowOpacityForCanvas(canvasL) {
|
|
1809
|
+
const t = (canvasL - SHADOW_GROUND_DEEP) / (SHADOW_GROUND_SOFT - SHADOW_GROUND_DEEP);
|
|
1810
|
+
const ramp = Math.max(0, Math.min(1, t));
|
|
1811
|
+
const opacity = SHADOW_OPACITY_DEEP + ramp * (SHADOW_OPACITY_SOFT - SHADOW_OPACITY_DEEP);
|
|
1812
|
+
return Math.round(opacity * 100) / 100;
|
|
1813
|
+
}
|
|
1814
|
+
function shadowVars(carried, canvas) {
|
|
1815
|
+
const opacity = shadowOpacityForCanvas(canvas.l);
|
|
1816
|
+
const out = {};
|
|
1817
|
+
for (const variable of SHADOW_VAR_NAMES) {
|
|
1818
|
+
const [x, y, blur] = STOCK_SHADOW_GEOMETRY[variable];
|
|
1819
|
+
const base = parseShadowCss(variable, carried[variable] ?? "") ?? { x, y, blur, ...STOCK_SHADOW_COLOR };
|
|
1820
|
+
out[variable] = shadowTokenCss({ ...base, opacity });
|
|
1821
|
+
}
|
|
1822
|
+
return out;
|
|
1823
|
+
}
|
|
1763
1824
|
function buildColorsAndTypeFromSeeds(brief, carry = {}, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1764
1825
|
const { seeds, slug } = validateBrief(brief);
|
|
1765
1826
|
const configs = {};
|
|
@@ -1784,6 +1845,7 @@ function buildColorsAndTypeFromSeeds(brief, carry = {}, now = (/* @__PURE__ */ n
|
|
|
1784
1845
|
Object.entries(carry.cssVariables ?? {}).filter(([k]) => !owned.has(k))
|
|
1785
1846
|
);
|
|
1786
1847
|
for (const t of gradients) cssVariables[t.variable] = formatGradientValue(t);
|
|
1848
|
+
Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, seeds.Canvas));
|
|
1787
1849
|
const colorsAndType = {
|
|
1788
1850
|
name: brief.name.trim(),
|
|
1789
1851
|
createdAt: now,
|
|
@@ -1802,6 +1864,7 @@ function buildColorsAndTypeFromSeeds(brief, carry = {}, now = (/* @__PURE__ */ n
|
|
|
1802
1864
|
report: {
|
|
1803
1865
|
...report,
|
|
1804
1866
|
gradients: gradients === carry.gradients ? "carried" : "recipes",
|
|
1867
|
+
shadows: `opacity ${shadowOpacityForCanvas(seeds.Canvas.l)} for a canvas at L ${seeds.Canvas.l.toFixed(2)}`,
|
|
1805
1868
|
...canvasGradient ? { canvasGradient } : {}
|
|
1806
1869
|
}
|
|
1807
1870
|
};
|
|
@@ -41,7 +41,8 @@ interface ColorsAndTypeBrief {
|
|
|
41
41
|
canvasGradient?: boolean;
|
|
42
42
|
}
|
|
43
43
|
/** Non-color content carried forward from the currently active colors and type
|
|
44
|
-
* so gradients,
|
|
44
|
+
* so gradients, shadow geometry, component aliases, and fonts survive
|
|
45
|
+
* regeneration. Shadow opacity is derived, not carried — see `shadowVars`. */
|
|
45
46
|
interface CarryForward {
|
|
46
47
|
cssVariables?: Record<string, string>;
|
|
47
48
|
fontSources?: FontSource[];
|
|
@@ -68,6 +69,8 @@ interface GenerateColorsAndTypeReport {
|
|
|
68
69
|
* 'on, <sky> → <anchor>' or a 'skipped — …' explanation when the Canvas
|
|
69
70
|
* anchors at the ramp edge and leaves no room. */
|
|
70
71
|
canvasGradient?: string;
|
|
72
|
+
/** The derived shadow opacity and the canvas lightness it came from. */
|
|
73
|
+
shadows: string;
|
|
71
74
|
}
|
|
72
75
|
interface GenerateColorsAndTypeResult {
|
|
73
76
|
colorsAndType: ColorsAndType;
|
|
@@ -41,7 +41,8 @@ interface ColorsAndTypeBrief {
|
|
|
41
41
|
canvasGradient?: boolean;
|
|
42
42
|
}
|
|
43
43
|
/** Non-color content carried forward from the currently active colors and type
|
|
44
|
-
* so gradients,
|
|
44
|
+
* so gradients, shadow geometry, component aliases, and fonts survive
|
|
45
|
+
* regeneration. Shadow opacity is derived, not carried — see `shadowVars`. */
|
|
45
46
|
interface CarryForward {
|
|
46
47
|
cssVariables?: Record<string, string>;
|
|
47
48
|
fontSources?: FontSource[];
|
|
@@ -68,6 +69,8 @@ interface GenerateColorsAndTypeReport {
|
|
|
68
69
|
* 'on, <sky> → <anchor>' or a 'skipped — …' explanation when the Canvas
|
|
69
70
|
* anchors at the ramp edge and leaves no room. */
|
|
70
71
|
canvasGradient?: string;
|
|
72
|
+
/** The derived shadow opacity and the canvas lightness it came from. */
|
|
73
|
+
shadows: string;
|
|
71
74
|
}
|
|
72
75
|
interface GenerateColorsAndTypeResult {
|
|
73
76
|
colorsAndType: ColorsAndType;
|
|
@@ -86,6 +86,39 @@ function makeDefaultGradients() {
|
|
|
86
86
|
];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// src/editor/core/themes/parsers/shadow.ts
|
|
90
|
+
var SHADOW_VAR_NAMES = [
|
|
91
|
+
"--shadow-sm",
|
|
92
|
+
"--shadow-md",
|
|
93
|
+
"--shadow-lg",
|
|
94
|
+
"--shadow-xl",
|
|
95
|
+
"--shadow-2xl"
|
|
96
|
+
];
|
|
97
|
+
function computeAngleDistance(x, y) {
|
|
98
|
+
const distance = Math.round(Math.sqrt(x * x + y * y));
|
|
99
|
+
if (distance === 0) return { angle: 135, distance: 0 };
|
|
100
|
+
let angle = Math.atan2(y, -x) * (180 / Math.PI);
|
|
101
|
+
if (angle < 0) angle += 360;
|
|
102
|
+
return { angle: Math.round(angle), distance };
|
|
103
|
+
}
|
|
104
|
+
function shadowTokenCss(t) {
|
|
105
|
+
return `${t.x}px ${t.y}px ${t.blur}px ${t.spread}px hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
|
|
106
|
+
}
|
|
107
|
+
function parseShadowCss(variable, raw) {
|
|
108
|
+
const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(-?\d+)px\s+hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
|
|
109
|
+
if (!m) return null;
|
|
110
|
+
const x = parseInt(m[1], 10);
|
|
111
|
+
const y = parseInt(m[2], 10);
|
|
112
|
+
const blur = parseInt(m[3], 10);
|
|
113
|
+
const spread = parseInt(m[4], 10);
|
|
114
|
+
const hue = Math.round(parseFloat(m[5]));
|
|
115
|
+
const saturation = Math.round(parseFloat(m[6]));
|
|
116
|
+
const lightness = Math.round(parseFloat(m[7]));
|
|
117
|
+
const opacity = parseFloat(m[8]);
|
|
118
|
+
const { angle, distance } = computeAngleDistance(x, y);
|
|
119
|
+
return { variable, x, y, blur, spread, opacity, hue, saturation, lightness, angle, distance };
|
|
120
|
+
}
|
|
121
|
+
|
|
89
122
|
// src/editor/core/palettes/contrast.ts
|
|
90
123
|
var AA_BODY = 4.5;
|
|
91
124
|
var AA_LARGE = 3;
|
|
@@ -410,6 +443,34 @@ function applyCanvasGradient(canvas, scheme) {
|
|
|
410
443
|
canvas.gradientSize = "window";
|
|
411
444
|
return `on, ${labels[sky]} \u2192 ${labels[anchor]}`;
|
|
412
445
|
}
|
|
446
|
+
var STOCK_SHADOW_GEOMETRY = {
|
|
447
|
+
"--shadow-sm": [1, 1, 2],
|
|
448
|
+
"--shadow-md": [3, 3, 6],
|
|
449
|
+
"--shadow-lg": [5, 5, 9],
|
|
450
|
+
"--shadow-xl": [6, 7, 13],
|
|
451
|
+
"--shadow-2xl": [8, 9, 16]
|
|
452
|
+
};
|
|
453
|
+
var STOCK_SHADOW_COLOR = { spread: 0, hue: 237, saturation: 18, lightness: 3, opacity: 0.9 };
|
|
454
|
+
var SHADOW_OPACITY_DEEP = 0.9;
|
|
455
|
+
var SHADOW_OPACITY_SOFT = 0.2;
|
|
456
|
+
var SHADOW_GROUND_DEEP = 0.5;
|
|
457
|
+
var SHADOW_GROUND_SOFT = 0.9;
|
|
458
|
+
function shadowOpacityForCanvas(canvasL) {
|
|
459
|
+
const t = (canvasL - SHADOW_GROUND_DEEP) / (SHADOW_GROUND_SOFT - SHADOW_GROUND_DEEP);
|
|
460
|
+
const ramp = Math.max(0, Math.min(1, t));
|
|
461
|
+
const opacity = SHADOW_OPACITY_DEEP + ramp * (SHADOW_OPACITY_SOFT - SHADOW_OPACITY_DEEP);
|
|
462
|
+
return Math.round(opacity * 100) / 100;
|
|
463
|
+
}
|
|
464
|
+
function shadowVars(carried, canvas) {
|
|
465
|
+
const opacity = shadowOpacityForCanvas(canvas.l);
|
|
466
|
+
const out = {};
|
|
467
|
+
for (const variable of SHADOW_VAR_NAMES) {
|
|
468
|
+
const [x, y, blur] = STOCK_SHADOW_GEOMETRY[variable];
|
|
469
|
+
const base = parseShadowCss(variable, carried[variable] ?? "") ?? { x, y, blur, ...STOCK_SHADOW_COLOR };
|
|
470
|
+
out[variable] = shadowTokenCss({ ...base, opacity });
|
|
471
|
+
}
|
|
472
|
+
return out;
|
|
473
|
+
}
|
|
413
474
|
function buildColorsAndTypeFromSeeds(brief, carry = {}, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
414
475
|
const { seeds, slug } = validateBrief(brief);
|
|
415
476
|
const configs = {};
|
|
@@ -434,6 +495,7 @@ function buildColorsAndTypeFromSeeds(brief, carry = {}, now = (/* @__PURE__ */ n
|
|
|
434
495
|
Object.entries(carry.cssVariables ?? {}).filter(([k]) => !owned.has(k))
|
|
435
496
|
);
|
|
436
497
|
for (const t of gradients) cssVariables[t.variable] = formatGradientValue(t);
|
|
498
|
+
Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, seeds.Canvas));
|
|
437
499
|
const colorsAndType = {
|
|
438
500
|
name: brief.name.trim(),
|
|
439
501
|
createdAt: now,
|
|
@@ -452,6 +514,7 @@ function buildColorsAndTypeFromSeeds(brief, carry = {}, now = (/* @__PURE__ */ n
|
|
|
452
514
|
report: {
|
|
453
515
|
...report,
|
|
454
516
|
gradients: gradients === carry.gradients ? "carried" : "recipes",
|
|
517
|
+
shadows: `opacity ${shadowOpacityForCanvas(seeds.Canvas.l)} for a canvas at L ${seeds.Canvas.l.toFixed(2)}`,
|
|
455
518
|
...canvasGradient ? { canvasGradient } : {}
|
|
456
519
|
}
|
|
457
520
|
};
|
package/package.json
CHANGED
|
@@ -66,6 +66,12 @@ const SPACE_RUNGS = SPACE_SETTABLE;
|
|
|
66
66
|
rungs stay pickable by hand and settable by name. */
|
|
67
67
|
const INSET_RUNGS = SPACE_SETTABLE.slice(SPACE_SETTABLE.indexOf('--space-4'));
|
|
68
68
|
|
|
69
|
+
/** Padding that wraps a line of type stops a rung higher. Those components
|
|
70
|
+
double it horizontally (`themed-padding($h: 2)`), so `--space-4` is 4px
|
|
71
|
+
over an 18px line and 8px inside a pill's own corner: squeezed rather than
|
|
72
|
+
compact. No shipped default puts text below `--space-6`. */
|
|
73
|
+
const TEXT_INSET_RUNGS = SPACE_SETTABLE.slice(SPACE_SETTABLE.indexOf('--space-6'));
|
|
74
|
+
|
|
69
75
|
const SPACE_FAMILY = [...SPACE_SETTABLE, '--space-40', '--space-64', '--space-96', '--space-128'];
|
|
70
76
|
|
|
71
77
|
const BORDER_WIDTH_RUNGS = [
|
|
@@ -95,11 +101,16 @@ const TOKEN_NAME = /^--[a-z0-9-]+$/;
|
|
|
95
101
|
type Outcome = { from: string; to: string } | { skip: SkipReason };
|
|
96
102
|
|
|
97
103
|
/** `-margin` shares the padding kind, so the inset floor keys off the variable
|
|
98
|
-
rather than the kind.
|
|
99
|
-
|
|
104
|
+
rather than the kind. Which floor depends on what the padding surrounds,
|
|
105
|
+
and the config says so itself: a variant that also declares a
|
|
106
|
+
`-text-font-size` is holding type. */
|
|
107
|
+
function rungsFor(op: AdjustOp, variable: string, aliases: ComponentConfig['aliases']): string[] {
|
|
100
108
|
if (op.kind === 'radius') return op.full ? [...RADIUS_RUNGS, RADIUS_FULL] : RADIUS_RUNGS;
|
|
101
|
-
if (op.kind
|
|
102
|
-
|
|
109
|
+
if (op.kind !== 'padding') return LADDERS[op.kind].rungs;
|
|
110
|
+
const base = stripSide(variable);
|
|
111
|
+
if (!base.endsWith('-padding')) return LADDERS.padding.rungs;
|
|
112
|
+
const variant = base.slice(0, -'-padding'.length);
|
|
113
|
+
return `${variant}-text-font-size` in aliases ? TEXT_INSET_RUNGS : INSET_RUNGS;
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
function spaceStep(token: string): number {
|
|
@@ -119,12 +130,17 @@ function snapRung(token: string, rungs: string[], direction: number): number {
|
|
|
119
130
|
return -1;
|
|
120
131
|
}
|
|
121
132
|
|
|
122
|
-
function resolve(
|
|
133
|
+
function resolve(
|
|
134
|
+
op: AdjustOp,
|
|
135
|
+
variable: string,
|
|
136
|
+
value: AliasDiskValue,
|
|
137
|
+
aliases: ComponentConfig['aliases'],
|
|
138
|
+
): Outcome {
|
|
123
139
|
if (typeof value !== 'string' || !TOKEN_NAME.test(value)) return { skip: 'raw-value' };
|
|
124
140
|
if (!LADDERS[op.kind].family.includes(value)) return { skip: 'off-ladder' };
|
|
125
141
|
if (op.set !== undefined) return { from: value, to: op.set };
|
|
126
142
|
|
|
127
|
-
const rungs = rungsFor(op, variable);
|
|
143
|
+
const rungs = rungsFor(op, variable, aliases);
|
|
128
144
|
let index = rungs.indexOf(value);
|
|
129
145
|
let shift = op.shift!;
|
|
130
146
|
if (index < 0) {
|
|
@@ -188,7 +204,7 @@ export function adjustAliases(
|
|
|
188
204
|
|
|
189
205
|
for (const [variable, value] of Object.entries(config.aliases)) {
|
|
190
206
|
if (!matchesKind(variable, op.kind)) continue;
|
|
191
|
-
const outcome = resolve(op, variable, value);
|
|
207
|
+
const outcome = resolve(op, variable, value, config.aliases);
|
|
192
208
|
if ('skip' in outcome) {
|
|
193
209
|
reportFor(component).skips.push({ variable, value, reason: outcome.skip });
|
|
194
210
|
continue;
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
setCurveAnchor,
|
|
19
19
|
} from '../palettes/paletteDerivation';
|
|
20
20
|
import { formatGradientValue, makeDefaultGradients } from './slices/gradients';
|
|
21
|
+
import { SHADOW_VAR_NAMES, parseShadowCss, shadowTokenCss } from './parsers/shadow';
|
|
21
22
|
import { AA_BODY, AA_LARGE, contrastRatio, findLForContrast } from '../palettes/contrast';
|
|
22
23
|
import { type HarmonyAxis, type HarmonyMode } from '../palettes/colorHarmony';
|
|
23
24
|
import { defaultPaletteConfig } from '../../ui/palette/paletteMath';
|
|
@@ -39,7 +40,8 @@ export interface ColorsAndTypeBrief {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/** Non-color content carried forward from the currently active colors and type
|
|
42
|
-
* so gradients,
|
|
43
|
+
* so gradients, shadow geometry, component aliases, and fonts survive
|
|
44
|
+
* regeneration. Shadow opacity is derived, not carried — see `shadowVars`. */
|
|
43
45
|
export interface CarryForward {
|
|
44
46
|
cssVariables?: Record<string, string>;
|
|
45
47
|
fontSources?: FontSource[];
|
|
@@ -68,6 +70,8 @@ export interface GenerateColorsAndTypeReport {
|
|
|
68
70
|
* 'on, <sky> → <anchor>' or a 'skipped — …' explanation when the Canvas
|
|
69
71
|
* anchors at the ramp edge and leaves no room. */
|
|
70
72
|
canvasGradient?: string;
|
|
73
|
+
/** The derived shadow opacity and the canvas lightness it came from. */
|
|
74
|
+
shadows: string;
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
export interface GenerateColorsAndTypeResult {
|
|
@@ -377,6 +381,49 @@ function applyCanvasGradient(canvas: PaletteConfig, scheme: SchemeDirection): st
|
|
|
377
381
|
return `on, ${labels[sky]} → ${labels[anchor]}`;
|
|
378
382
|
}
|
|
379
383
|
|
|
384
|
+
/** The tokens.css scale, the starting point when a theme carries no shadows.
|
|
385
|
+
* Geometry per step; every step shares one color. */
|
|
386
|
+
const STOCK_SHADOW_GEOMETRY: Record<(typeof SHADOW_VAR_NAMES)[number], readonly [number, number, number]> = {
|
|
387
|
+
'--shadow-sm': [1, 1, 2],
|
|
388
|
+
'--shadow-md': [3, 3, 6],
|
|
389
|
+
'--shadow-lg': [5, 5, 9],
|
|
390
|
+
'--shadow-xl': [6, 7, 13],
|
|
391
|
+
'--shadow-2xl': [8, 9, 16],
|
|
392
|
+
};
|
|
393
|
+
const STOCK_SHADOW_COLOR = { spread: 0, hue: 237, saturation: 18, lightness: 3, opacity: 0.9 };
|
|
394
|
+
|
|
395
|
+
/** A near-black shadow needs 0.9 to read at all against a dark ground, and at
|
|
396
|
+
* 0.9 it punches a hole in a light one. Opacity therefore rides the canvas:
|
|
397
|
+
* full weight up to DEEP, easing to SOFT by the time the page is paper. */
|
|
398
|
+
const SHADOW_OPACITY_DEEP = 0.9;
|
|
399
|
+
const SHADOW_OPACITY_SOFT = 0.2;
|
|
400
|
+
const SHADOW_GROUND_DEEP = 0.5;
|
|
401
|
+
const SHADOW_GROUND_SOFT = 0.9;
|
|
402
|
+
|
|
403
|
+
export function shadowOpacityForCanvas(canvasL: number): number {
|
|
404
|
+
const t = (canvasL - SHADOW_GROUND_DEEP) / (SHADOW_GROUND_SOFT - SHADOW_GROUND_DEEP);
|
|
405
|
+
const ramp = Math.max(0, Math.min(1, t));
|
|
406
|
+
const opacity = SHADOW_OPACITY_DEEP + ramp * (SHADOW_OPACITY_SOFT - SHADOW_OPACITY_DEEP);
|
|
407
|
+
return Math.round(opacity * 100) / 100;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Recolor the scale for this canvas: carried geometry and color ride through
|
|
411
|
+
* (elevation shape belongs to the shape pass), opacity is replaced. Replaced
|
|
412
|
+
* rather than kept-when-untouched, because a brief iterated from a light
|
|
413
|
+
* canvas to a dark one would otherwise hold a shadow too faint to see. The
|
|
414
|
+
* Canvas seed is the page background verbatim, so it is the ground the
|
|
415
|
+
* shadow falls on. */
|
|
416
|
+
function shadowVars(carried: Record<string, string>, canvas: Oklch): Record<string, string> {
|
|
417
|
+
const opacity = shadowOpacityForCanvas(canvas.l);
|
|
418
|
+
const out: Record<string, string> = {};
|
|
419
|
+
for (const variable of SHADOW_VAR_NAMES) {
|
|
420
|
+
const [x, y, blur] = STOCK_SHADOW_GEOMETRY[variable];
|
|
421
|
+
const base = parseShadowCss(variable, carried[variable] ?? '') ?? { x, y, blur, ...STOCK_SHADOW_COLOR };
|
|
422
|
+
out[variable] = shadowTokenCss({ ...base, opacity });
|
|
423
|
+
}
|
|
424
|
+
return out;
|
|
425
|
+
}
|
|
426
|
+
|
|
380
427
|
export function buildColorsAndTypeFromSeeds(
|
|
381
428
|
brief: ColorsAndTypeBrief,
|
|
382
429
|
carry: CarryForward = {},
|
|
@@ -417,6 +464,7 @@ export function buildColorsAndTypeFromSeeds(
|
|
|
417
464
|
);
|
|
418
465
|
// Rendered projections of the structured gradients, kept for production CSS.
|
|
419
466
|
for (const t of gradients) cssVariables[t.variable] = formatGradientValue(t);
|
|
467
|
+
Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, seeds.Canvas));
|
|
420
468
|
|
|
421
469
|
const colorsAndType: ColorsAndType = {
|
|
422
470
|
name: brief.name.trim(),
|
|
@@ -437,6 +485,7 @@ export function buildColorsAndTypeFromSeeds(
|
|
|
437
485
|
report: {
|
|
438
486
|
...report,
|
|
439
487
|
gradients: gradients === carry.gradients ? 'carried' : 'recipes',
|
|
488
|
+
shadows: `opacity ${shadowOpacityForCanvas(seeds.Canvas.l)} for a canvas at L ${seeds.Canvas.l.toFixed(2)}`,
|
|
440
489
|
...(canvasGradient ? { canvasGradient } : {}),
|
|
441
490
|
},
|
|
442
491
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single source of truth for the CSS form of a shadow scale token:
|
|
3
|
+
*
|
|
4
|
+
* <x>px <y>px <blur>px <spread>px hsla(<h>, <s>%, <l>%, <a>)
|
|
5
|
+
*
|
|
6
|
+
* Everything that reads or writes that form goes through `parseShadowCss` /
|
|
7
|
+
* `shadowTokenCss`, so the theme generator (Node, no store) and the editor
|
|
8
|
+
* (store, DOM) agree on the shape without sharing a graph.
|
|
9
|
+
*/
|
|
10
|
+
import type { ShadowToken } from '../../store/editorTypes';
|
|
11
|
+
|
|
12
|
+
export const SHADOW_VAR_NAMES = [
|
|
13
|
+
'--shadow-sm', '--shadow-md', '--shadow-lg', '--shadow-xl', '--shadow-2xl',
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
/** What the CSS form needs; `ShadowToken` adds the editor's polar controls. */
|
|
17
|
+
export type ShadowValue = Omit<ShadowToken, 'variable' | 'angle' | 'distance'>;
|
|
18
|
+
|
|
19
|
+
export function computeShadowXY(angle: number, distance: number): { x: number; y: number } {
|
|
20
|
+
const rad = angle * (Math.PI / 180);
|
|
21
|
+
return {
|
|
22
|
+
x: Math.round(-distance * Math.cos(rad)),
|
|
23
|
+
y: Math.round(distance * Math.sin(rad)),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function computeAngleDistance(x: number, y: number): { angle: number; distance: number } {
|
|
28
|
+
const distance = Math.round(Math.sqrt(x * x + y * y));
|
|
29
|
+
if (distance === 0) return { angle: 135, distance: 0 };
|
|
30
|
+
let angle = Math.atan2(y, -x) * (180 / Math.PI);
|
|
31
|
+
if (angle < 0) angle += 360;
|
|
32
|
+
return { angle: Math.round(angle), distance };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function shadowTokenCss(t: ShadowValue): string {
|
|
36
|
+
return `${t.x}px ${t.y}px ${t.blur}px ${t.spread}px hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function parseShadowCss(variable: string, raw: string): ShadowToken | null {
|
|
40
|
+
const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(-?\d+)px\s+hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
|
|
41
|
+
if (!m) return null;
|
|
42
|
+
const x = parseInt(m[1], 10);
|
|
43
|
+
const y = parseInt(m[2], 10);
|
|
44
|
+
const blur = parseInt(m[3], 10);
|
|
45
|
+
const spread = parseInt(m[4], 10);
|
|
46
|
+
const hue = Math.round(parseFloat(m[5]));
|
|
47
|
+
const saturation = Math.round(parseFloat(m[6]));
|
|
48
|
+
const lightness = Math.round(parseFloat(m[7]));
|
|
49
|
+
const opacity = parseFloat(m[8]);
|
|
50
|
+
const { angle, distance } = computeAngleDistance(x, y);
|
|
51
|
+
return { variable, x, y, blur, spread, opacity, hue, saturation, lightness, angle, distance };
|
|
52
|
+
}
|
|
@@ -10,53 +10,17 @@
|
|
|
10
10
|
import { get } from 'svelte/store';
|
|
11
11
|
import type { EditorState, ShadowToken } from '../../store/editorTypes';
|
|
12
12
|
import { store, persist } from '../../store/editorCore';
|
|
13
|
+
import { SHADOW_VAR_NAMES, computeShadowXY, parseShadowCss, shadowTokenCss } from '../parsers/shadow';
|
|
13
14
|
|
|
14
|
-
export
|
|
15
|
-
'--shadow-sm', '--shadow-md', '--shadow-lg', '--shadow-xl', '--shadow-2xl',
|
|
16
|
-
] as const;
|
|
15
|
+
export { SHADOW_VAR_NAMES, computeShadowXY, parseShadowCss, shadowTokenCss };
|
|
17
16
|
|
|
18
17
|
// Identical literal set as SHADOW_VAR_NAMES — derived to avoid drift.
|
|
19
18
|
export const SCALE_SHADOW_VARIABLES: ReadonlySet<string> = new Set(SHADOW_VAR_NAMES);
|
|
20
19
|
|
|
21
|
-
export function computeShadowXY(angle: number, distance: number): { x: number; y: number } {
|
|
22
|
-
const rad = angle * (Math.PI / 180);
|
|
23
|
-
return {
|
|
24
|
-
x: Math.round(-distance * Math.cos(rad)),
|
|
25
|
-
y: Math.round(distance * Math.sin(rad)),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function computeAngleDistance(x: number, y: number): { angle: number; distance: number } {
|
|
30
|
-
const distance = Math.round(Math.sqrt(x * x + y * y));
|
|
31
|
-
if (distance === 0) return { angle: 135, distance: 0 };
|
|
32
|
-
let angle = Math.atan2(y, -x) * (180 / Math.PI);
|
|
33
|
-
if (angle < 0) angle += 360;
|
|
34
|
-
return { angle: Math.round(angle), distance };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function shadowTokenCss(t: ShadowToken): string {
|
|
38
|
-
return `${t.x}px ${t.y}px ${t.blur}px ${t.spread}px hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
20
|
export function defaultShadowOverride(): import('../../store/editorTypes').ShadowOverrideFlags {
|
|
42
21
|
return { angle: false, opacity: false, color: false, distance: false, blur: false, size: false };
|
|
43
22
|
}
|
|
44
23
|
|
|
45
|
-
export function parseShadowCss(variable: string, raw: string): ShadowToken | null {
|
|
46
|
-
const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(-?\d+)px\s+hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
|
|
47
|
-
if (!m) return null;
|
|
48
|
-
const x = parseInt(m[1], 10);
|
|
49
|
-
const y = parseInt(m[2], 10);
|
|
50
|
-
const blur = parseInt(m[3], 10);
|
|
51
|
-
const spread = parseInt(m[4], 10);
|
|
52
|
-
const hue = Math.round(parseFloat(m[5]));
|
|
53
|
-
const saturation = Math.round(parseFloat(m[6]));
|
|
54
|
-
const lightness = Math.round(parseFloat(m[7]));
|
|
55
|
-
const opacity = parseFloat(m[8]);
|
|
56
|
-
const { angle, distance } = computeAngleDistance(x, y);
|
|
57
|
-
return { variable, x, y, blur, spread, opacity, hue, saturation, lightness, angle, distance };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
24
|
export function shadowsToVars(shadows: EditorState['shadows']): Record<string, string> {
|
|
61
25
|
const out: Record<string, string> = {};
|
|
62
26
|
for (const t of shadows.tokens) out[t.variable] = shadowTokenCss(t);
|
|
@@ -2265,11 +2265,11 @@
|
|
|
2265
2265
|
"--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
|
|
2266
2266
|
"--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
|
|
2267
2267
|
"--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
|
|
2268
|
-
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.
|
|
2269
|
-
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.
|
|
2270
|
-
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.
|
|
2271
|
-
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.
|
|
2272
|
-
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.
|
|
2268
|
+
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.25)",
|
|
2269
|
+
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.25)",
|
|
2270
|
+
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.25)",
|
|
2271
|
+
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.25)",
|
|
2272
|
+
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.25)"
|
|
2273
2273
|
},
|
|
2274
2274
|
"fontSources": [
|
|
2275
2275
|
{
|
|
@@ -2265,11 +2265,11 @@
|
|
|
2265
2265
|
"--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
|
|
2266
2266
|
"--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
|
|
2267
2267
|
"--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
|
|
2268
|
-
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.
|
|
2269
|
-
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.
|
|
2270
|
-
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.
|
|
2271
|
-
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.
|
|
2272
|
-
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.
|
|
2268
|
+
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2269
|
+
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2270
|
+
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2271
|
+
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2272
|
+
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.2)"
|
|
2273
2273
|
},
|
|
2274
2274
|
"fontSources": [
|
|
2275
2275
|
{
|
|
@@ -2251,11 +2251,11 @@
|
|
|
2251
2251
|
"--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
|
|
2252
2252
|
"--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
|
|
2253
2253
|
"--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
|
|
2254
|
-
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.
|
|
2255
|
-
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.
|
|
2256
|
-
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.
|
|
2257
|
-
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.
|
|
2258
|
-
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.
|
|
2254
|
+
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2255
|
+
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2256
|
+
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2257
|
+
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2258
|
+
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.2)"
|
|
2259
2259
|
},
|
|
2260
2260
|
"fontSources": [
|
|
2261
2261
|
{
|
|
@@ -2270,11 +2270,11 @@
|
|
|
2270
2270
|
"--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
|
|
2271
2271
|
"--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
|
|
2272
2272
|
"--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
|
|
2273
|
-
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.
|
|
2274
|
-
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.
|
|
2275
|
-
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.
|
|
2276
|
-
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.
|
|
2277
|
-
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.
|
|
2273
|
+
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.25)",
|
|
2274
|
+
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.25)",
|
|
2275
|
+
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.25)",
|
|
2276
|
+
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.25)",
|
|
2277
|
+
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.25)"
|
|
2278
2278
|
},
|
|
2279
2279
|
"fontSources": [
|
|
2280
2280
|
{
|
|
@@ -2544,7 +2544,7 @@
|
|
|
2544
2544
|
"name": "midnight-study",
|
|
2545
2545
|
"component": "badge",
|
|
2546
2546
|
"createdAt": "2026-05-01T08:28:00.345Z",
|
|
2547
|
-
"updatedAt": "2026-08-
|
|
2547
|
+
"updatedAt": "2026-08-20T00:00:00.000Z",
|
|
2548
2548
|
"aliases": {
|
|
2549
2549
|
"--badge-primary-surface": "--surface-brand",
|
|
2550
2550
|
"--badge-primary-text": "--text-brand",
|
|
@@ -2555,7 +2555,7 @@
|
|
|
2555
2555
|
"--badge-primary-text-line-height": "--line-height-none",
|
|
2556
2556
|
"--badge-primary-border-width": "--border-width-1",
|
|
2557
2557
|
"--badge-primary-radius": "--radius-full",
|
|
2558
|
-
"--badge-primary-padding": "--space-
|
|
2558
|
+
"--badge-primary-padding": "--space-6",
|
|
2559
2559
|
"--badge-primary-shadow": "--shadow-none",
|
|
2560
2560
|
"--badge-primary-blur": "--blur-none",
|
|
2561
2561
|
"--badge-primary-icon-size": "--icon-size-sm",
|
|
@@ -2568,7 +2568,7 @@
|
|
|
2568
2568
|
"--badge-accent-text-line-height": "--line-height-none",
|
|
2569
2569
|
"--badge-accent-border-width": "--border-width-1",
|
|
2570
2570
|
"--badge-accent-radius": "--radius-full",
|
|
2571
|
-
"--badge-accent-padding": "--space-
|
|
2571
|
+
"--badge-accent-padding": "--space-6",
|
|
2572
2572
|
"--badge-accent-shadow": "--shadow-none",
|
|
2573
2573
|
"--badge-accent-blur": "--blur-none",
|
|
2574
2574
|
"--badge-accent-icon-size": "--icon-size-sm",
|
|
@@ -2581,7 +2581,7 @@
|
|
|
2581
2581
|
"--badge-neutral-text-line-height": "--line-height-none",
|
|
2582
2582
|
"--badge-neutral-border-width": "--border-width-1",
|
|
2583
2583
|
"--badge-neutral-radius": "--radius-full",
|
|
2584
|
-
"--badge-neutral-padding": "--space-
|
|
2584
|
+
"--badge-neutral-padding": "--space-6",
|
|
2585
2585
|
"--badge-neutral-shadow": "--shadow-none",
|
|
2586
2586
|
"--badge-neutral-blur": "--blur-none",
|
|
2587
2587
|
"--badge-neutral-icon-size": "--icon-size-sm",
|
|
@@ -2594,7 +2594,7 @@
|
|
|
2594
2594
|
"--badge-alternate-text-line-height": "--line-height-none",
|
|
2595
2595
|
"--badge-alternate-border-width": "--border-width-1",
|
|
2596
2596
|
"--badge-alternate-radius": "--radius-full",
|
|
2597
|
-
"--badge-alternate-padding": "--space-
|
|
2597
|
+
"--badge-alternate-padding": "--space-6",
|
|
2598
2598
|
"--badge-alternate-shadow": "--shadow-none",
|
|
2599
2599
|
"--badge-alternate-blur": "--blur-none",
|
|
2600
2600
|
"--badge-alternate-icon-size": "--icon-size-sm",
|
|
@@ -2607,7 +2607,7 @@
|
|
|
2607
2607
|
"--badge-canvas-text-line-height": "--line-height-none",
|
|
2608
2608
|
"--badge-canvas-border-width": "--border-width-1",
|
|
2609
2609
|
"--badge-canvas-radius": "--radius-full",
|
|
2610
|
-
"--badge-canvas-padding": "--space-
|
|
2610
|
+
"--badge-canvas-padding": "--space-6",
|
|
2611
2611
|
"--badge-canvas-shadow": "--shadow-none",
|
|
2612
2612
|
"--badge-canvas-blur": "--blur-none",
|
|
2613
2613
|
"--badge-canvas-icon-size": "--icon-size-sm",
|
|
@@ -2620,7 +2620,7 @@
|
|
|
2620
2620
|
"--badge-special-text-line-height": "--line-height-none",
|
|
2621
2621
|
"--badge-special-border-width": "--border-width-1",
|
|
2622
2622
|
"--badge-special-radius": "--radius-full",
|
|
2623
|
-
"--badge-special-padding": "--space-
|
|
2623
|
+
"--badge-special-padding": "--space-6",
|
|
2624
2624
|
"--badge-special-shadow": "--shadow-none",
|
|
2625
2625
|
"--badge-special-blur": "--blur-none",
|
|
2626
2626
|
"--badge-special-icon-size": "--icon-size-sm",
|
|
@@ -2633,7 +2633,7 @@
|
|
|
2633
2633
|
"--badge-success-text-line-height": "--line-height-none",
|
|
2634
2634
|
"--badge-success-border-width": "--border-width-1",
|
|
2635
2635
|
"--badge-success-radius": "--radius-full",
|
|
2636
|
-
"--badge-success-padding": "--space-
|
|
2636
|
+
"--badge-success-padding": "--space-6",
|
|
2637
2637
|
"--badge-success-shadow": "--shadow-none",
|
|
2638
2638
|
"--badge-success-blur": "--blur-none",
|
|
2639
2639
|
"--badge-success-icon-size": "--icon-size-sm",
|
|
@@ -2646,7 +2646,7 @@
|
|
|
2646
2646
|
"--badge-warning-text-line-height": "--line-height-none",
|
|
2647
2647
|
"--badge-warning-border-width": "--border-width-1",
|
|
2648
2648
|
"--badge-warning-radius": "--radius-full",
|
|
2649
|
-
"--badge-warning-padding": "--space-
|
|
2649
|
+
"--badge-warning-padding": "--space-6",
|
|
2650
2650
|
"--badge-warning-shadow": "--shadow-none",
|
|
2651
2651
|
"--badge-warning-blur": "--blur-none",
|
|
2652
2652
|
"--badge-warning-icon-size": "--icon-size-sm",
|
|
@@ -2659,7 +2659,7 @@
|
|
|
2659
2659
|
"--badge-danger-text-line-height": "--line-height-none",
|
|
2660
2660
|
"--badge-danger-border-width": "--border-width-1",
|
|
2661
2661
|
"--badge-danger-radius": "--radius-full",
|
|
2662
|
-
"--badge-danger-padding": "--space-
|
|
2662
|
+
"--badge-danger-padding": "--space-6",
|
|
2663
2663
|
"--badge-danger-shadow": "--shadow-none",
|
|
2664
2664
|
"--badge-danger-blur": "--blur-none",
|
|
2665
2665
|
"--badge-danger-icon-size": "--icon-size-sm",
|
|
@@ -2672,7 +2672,7 @@
|
|
|
2672
2672
|
"--badge-info-text-line-height": "--line-height-none",
|
|
2673
2673
|
"--badge-info-border-width": "--border-width-1",
|
|
2674
2674
|
"--badge-info-radius": "--radius-full",
|
|
2675
|
-
"--badge-info-padding": "--space-
|
|
2675
|
+
"--badge-info-padding": "--space-6",
|
|
2676
2676
|
"--badge-info-shadow": "--shadow-none",
|
|
2677
2677
|
"--badge-info-blur": "--blur-none",
|
|
2678
2678
|
"--badge-info-icon-size": "--icon-size-sm"
|
|
@@ -2682,7 +2682,7 @@
|
|
|
2682
2682
|
"name": "midnight-study",
|
|
2683
2683
|
"component": "button",
|
|
2684
2684
|
"createdAt": "2026-05-10T19:28:06.893Z",
|
|
2685
|
-
"updatedAt": "2026-08-
|
|
2685
|
+
"updatedAt": "2026-08-20T00:00:00.000Z",
|
|
2686
2686
|
"aliases": {
|
|
2687
2687
|
"--button-primary-surface": "--surface-brand-high",
|
|
2688
2688
|
"--button-primary-text": "--text-primary",
|
|
@@ -2693,7 +2693,7 @@
|
|
|
2693
2693
|
"--button-primary-border": "--border-brand",
|
|
2694
2694
|
"--button-primary-border-width": "--border-width-1",
|
|
2695
2695
|
"--button-primary-radius": "--radius-full",
|
|
2696
|
-
"--button-primary-padding": "--space-
|
|
2696
|
+
"--button-primary-padding": "--space-8",
|
|
2697
2697
|
"--button-primary-hover-surface": "--surface-brand-higher",
|
|
2698
2698
|
"--button-primary-hover-text": "--text-primary",
|
|
2699
2699
|
"--button-primary-hover-border": "--border-brand-strong",
|
|
@@ -2710,7 +2710,7 @@
|
|
|
2710
2710
|
"--button-secondary-border": "--border-neutral",
|
|
2711
2711
|
"--button-secondary-border-width": "--border-width-1",
|
|
2712
2712
|
"--button-secondary-radius": "--radius-full",
|
|
2713
|
-
"--button-secondary-padding": "--space-
|
|
2713
|
+
"--button-secondary-padding": "--space-8",
|
|
2714
2714
|
"--button-secondary-hover-surface": "--surface-neutral-higher",
|
|
2715
2715
|
"--button-secondary-hover-text": "--text-primary",
|
|
2716
2716
|
"--button-secondary-hover-border": "--border-neutral-strong",
|
|
@@ -2727,7 +2727,7 @@
|
|
|
2727
2727
|
"--button-outline-border": "--border-neutral",
|
|
2728
2728
|
"--button-outline-border-width": "--border-width-1",
|
|
2729
2729
|
"--button-outline-radius": "--radius-full",
|
|
2730
|
-
"--button-outline-padding": "--space-
|
|
2730
|
+
"--button-outline-padding": "--space-8",
|
|
2731
2731
|
"--button-outline-hover-surface": "--surface-neutral-lower",
|
|
2732
2732
|
"--button-outline-hover-text": "--text-primary",
|
|
2733
2733
|
"--button-outline-hover-border": "--border-neutral-strong",
|
|
@@ -2745,7 +2745,7 @@
|
|
|
2745
2745
|
"--button-success-border": "--border-success",
|
|
2746
2746
|
"--button-success-border-width": "--border-width-1",
|
|
2747
2747
|
"--button-success-radius": "--radius-full",
|
|
2748
|
-
"--button-success-padding": "--space-
|
|
2748
|
+
"--button-success-padding": "--space-8",
|
|
2749
2749
|
"--button-success-hover-surface": "--surface-success-higher",
|
|
2750
2750
|
"--button-success-hover-text": "--text-primary",
|
|
2751
2751
|
"--button-success-hover-border": "--border-success-strong",
|
|
@@ -2762,7 +2762,7 @@
|
|
|
2762
2762
|
"--button-danger-border": "--border-danger",
|
|
2763
2763
|
"--button-danger-border-width": "--border-width-1",
|
|
2764
2764
|
"--button-danger-radius": "--radius-full",
|
|
2765
|
-
"--button-danger-padding": "--space-
|
|
2765
|
+
"--button-danger-padding": "--space-8",
|
|
2766
2766
|
"--button-danger-hover-surface": "--surface-danger-high",
|
|
2767
2767
|
"--button-danger-hover-text": "--text-primary",
|
|
2768
2768
|
"--button-danger-hover-border": "--border-danger-medium",
|
|
@@ -2779,7 +2779,7 @@
|
|
|
2779
2779
|
"--button-warning-border": "--border-warning",
|
|
2780
2780
|
"--button-warning-border-width": "--border-width-1",
|
|
2781
2781
|
"--button-warning-radius": "--radius-full",
|
|
2782
|
-
"--button-warning-padding": "--space-
|
|
2782
|
+
"--button-warning-padding": "--space-8",
|
|
2783
2783
|
"--button-warning-hover-surface": "--surface-warning-high",
|
|
2784
2784
|
"--button-warning-hover-text": "--text-primary",
|
|
2785
2785
|
"--button-warning-hover-border": "--border-warning-medium",
|
|
@@ -2787,7 +2787,7 @@
|
|
|
2787
2787
|
"--button-warning-disabled-text": "--text-tertiary",
|
|
2788
2788
|
"--button-warning-disabled-border": "--border-neutral-faint",
|
|
2789
2789
|
"--button-warning-icon-size": "--icon-size-md",
|
|
2790
|
-
"--button-small-padding": "--space-
|
|
2790
|
+
"--button-small-padding": "--space-6",
|
|
2791
2791
|
"--button-small-text-font-size": "--font-size-sm",
|
|
2792
2792
|
"--button-small-text-font-weight": "--font-weight-normal",
|
|
2793
2793
|
"--button-small-text-line-height": "--line-height-tighter",
|
|
@@ -3008,14 +3008,14 @@
|
|
|
3008
3008
|
"name": "midnight-study",
|
|
3009
3009
|
"component": "cornerbadge",
|
|
3010
3010
|
"createdAt": "2026-05-12T15:31:06.313Z",
|
|
3011
|
-
"updatedAt": "2026-08-
|
|
3011
|
+
"updatedAt": "2026-08-20T00:00:00.000Z",
|
|
3012
3012
|
"aliases": {
|
|
3013
3013
|
"--corner-badge-margin": "--space-0",
|
|
3014
3014
|
"--corner-badge-outer-radius": "--radius-none",
|
|
3015
3015
|
"--corner-badge-inner-radius": "--radius-none",
|
|
3016
3016
|
"--corner-badge-h-axis-radius": "--radius-none",
|
|
3017
3017
|
"--corner-badge-v-axis-radius": "--radius-none",
|
|
3018
|
-
"--corner-badge-padding": "--space-
|
|
3018
|
+
"--corner-badge-padding": "--space-6",
|
|
3019
3019
|
"--corner-badge-text-font-family": "--font-sans",
|
|
3020
3020
|
"--corner-badge-text-font-size": "--font-size-sm",
|
|
3021
3021
|
"--corner-badge-text-font-weight": "--font-weight-medium",
|
|
@@ -3624,7 +3624,7 @@
|
|
|
3624
3624
|
"name": "midnight-study",
|
|
3625
3625
|
"component": "segmentedcontrol",
|
|
3626
3626
|
"createdAt": "2026-04-28T21:13:17.669Z",
|
|
3627
|
-
"updatedAt": "2026-08-
|
|
3627
|
+
"updatedAt": "2026-08-20T00:00:00.000Z",
|
|
3628
3628
|
"aliases": {
|
|
3629
3629
|
"--segmentedcontrol-bar-surface": "color-mix(in srgb, var(--surface-neutral-lowest) 75%, transparent)",
|
|
3630
3630
|
"--segmentedcontrol-bar-border": "--border-neutral",
|
|
@@ -3635,7 +3635,7 @@
|
|
|
3635
3635
|
"--segmentedcontrol-divider-color": "--border-neutral",
|
|
3636
3636
|
"--segmentedcontrol-divider-thickness": "--border-width-1",
|
|
3637
3637
|
"--segmentedcontrol-divider-inset": "--space-6",
|
|
3638
|
-
"--segmentedcontrol-option-padding": "--space-
|
|
3638
|
+
"--segmentedcontrol-option-padding": "--space-6",
|
|
3639
3639
|
"--segmentedcontrol-option-gap": "--space-6",
|
|
3640
3640
|
"--segmentedcontrol-option-text": "--text-primary",
|
|
3641
3641
|
"--segmentedcontrol-option-text-font-family": "--font-sans",
|
|
@@ -3672,7 +3672,7 @@
|
|
|
3672
3672
|
"--segmentedcontrol-bar-small-radius": "--radius-md",
|
|
3673
3673
|
"--segmentedcontrol-small-divider-inset": "--space-4",
|
|
3674
3674
|
"--segmentedcontrol-small-divider-thickness": "--border-width-1",
|
|
3675
|
-
"--segmentedcontrol-option-small-padding": "--space-
|
|
3675
|
+
"--segmentedcontrol-option-small-padding": "--space-6",
|
|
3676
3676
|
"--segmentedcontrol-option-small-gap": "--space-4",
|
|
3677
3677
|
"--segmentedcontrol-option-small-icon-size": "--icon-size-sm",
|
|
3678
3678
|
"--segmentedcontrol-option-small-text-font-size": "--font-size-sm",
|
|
@@ -3684,7 +3684,7 @@
|
|
|
3684
3684
|
"name": "midnight-study",
|
|
3685
3685
|
"component": "sidenavigation",
|
|
3686
3686
|
"createdAt": "2026-05-25T19:34:09.771Z",
|
|
3687
|
-
"updatedAt": "2026-08-
|
|
3687
|
+
"updatedAt": "2026-08-20T00:00:00.000Z",
|
|
3688
3688
|
"aliases": {
|
|
3689
3689
|
"--sidenavigation-panel-surface": "--surface-canvas-lower",
|
|
3690
3690
|
"--sidenavigation-panel-border": "--border-canvas-faint",
|
|
@@ -3776,7 +3776,7 @@
|
|
|
3776
3776
|
"--sidenavigation-section-active-text-font-weight": "--font-weight-medium",
|
|
3777
3777
|
"--sidenavigation-section-active-text-line-height": "--line-height-normal",
|
|
3778
3778
|
"--sidenavigation-item-default-surface": "--color-transparent",
|
|
3779
|
-
"--sidenavigation-item-default-padding": "--space-
|
|
3779
|
+
"--sidenavigation-item-default-padding": "--space-6",
|
|
3780
3780
|
"--sidenavigation-item-default-accent": "--color-transparent",
|
|
3781
3781
|
"--sidenavigation-item-default-accent-width": "--border-width-3",
|
|
3782
3782
|
"--sidenavigation-item-default-text": "--text-tertiary",
|
|
@@ -3785,7 +3785,7 @@
|
|
|
3785
3785
|
"--sidenavigation-item-default-text-font-weight": "--font-weight-semibold",
|
|
3786
3786
|
"--sidenavigation-item-default-text-line-height": "--line-height-normal",
|
|
3787
3787
|
"--sidenavigation-item-hover-surface": "--surface-canvas",
|
|
3788
|
-
"--sidenavigation-item-hover-padding": "--space-
|
|
3788
|
+
"--sidenavigation-item-hover-padding": "--space-6",
|
|
3789
3789
|
"--sidenavigation-item-hover-accent": "--color-transparent",
|
|
3790
3790
|
"--sidenavigation-item-hover-accent-width": "--border-width-3",
|
|
3791
3791
|
"--sidenavigation-item-hover-text": "--text-secondary",
|
|
@@ -3794,7 +3794,7 @@
|
|
|
3794
3794
|
"--sidenavigation-item-hover-text-font-weight": "--font-weight-light",
|
|
3795
3795
|
"--sidenavigation-item-hover-text-line-height": "--line-height-normal",
|
|
3796
3796
|
"--sidenavigation-item-active-surface": "--surface-canvas-low",
|
|
3797
|
-
"--sidenavigation-item-active-padding": "--space-
|
|
3797
|
+
"--sidenavigation-item-active-padding": "--space-6",
|
|
3798
3798
|
"--sidenavigation-item-active-accent": "--border-brand-medium",
|
|
3799
3799
|
"--sidenavigation-item-active-accent-width": "--border-width-3",
|
|
3800
3800
|
"--sidenavigation-item-active-text": "--text-primary",
|
|
@@ -3803,7 +3803,7 @@
|
|
|
3803
3803
|
"--sidenavigation-item-active-text-font-weight": "--font-weight-normal",
|
|
3804
3804
|
"--sidenavigation-item-active-text-line-height": "--line-height-normal",
|
|
3805
3805
|
"--sidenavigation-footer-default-surface": "--color-transparent",
|
|
3806
|
-
"--sidenavigation-footer-default-padding": "--space-
|
|
3806
|
+
"--sidenavigation-footer-default-padding": "--space-6",
|
|
3807
3807
|
"--sidenavigation-footer-default-gap": "--space-6",
|
|
3808
3808
|
"--sidenavigation-footer-default-accent": "--color-transparent",
|
|
3809
3809
|
"--sidenavigation-footer-default-accent-width": "--border-width-3",
|
|
@@ -3815,7 +3815,7 @@
|
|
|
3815
3815
|
"--sidenavigation-footer-default-text-font-weight": "--font-weight-light",
|
|
3816
3816
|
"--sidenavigation-footer-default-text-line-height": "--line-height-normal",
|
|
3817
3817
|
"--sidenavigation-footer-hover-surface": "--surface-canvas",
|
|
3818
|
-
"--sidenavigation-footer-hover-padding": "--space-
|
|
3818
|
+
"--sidenavigation-footer-hover-padding": "--space-6",
|
|
3819
3819
|
"--sidenavigation-footer-hover-gap": "--space-6",
|
|
3820
3820
|
"--sidenavigation-footer-hover-accent": "--color-transparent",
|
|
3821
3821
|
"--sidenavigation-footer-hover-accent-width": "--border-width-3",
|
|
@@ -3827,7 +3827,7 @@
|
|
|
3827
3827
|
"--sidenavigation-footer-hover-text-font-weight": "--font-weight-light",
|
|
3828
3828
|
"--sidenavigation-footer-hover-text-line-height": "--line-height-normal",
|
|
3829
3829
|
"--sidenavigation-footer-active-surface": "--surface-canvas-low",
|
|
3830
|
-
"--sidenavigation-footer-active-padding": "--space-
|
|
3830
|
+
"--sidenavigation-footer-active-padding": "--space-6",
|
|
3831
3831
|
"--sidenavigation-footer-active-gap": "--space-6",
|
|
3832
3832
|
"--sidenavigation-footer-active-accent": "--border-brand-medium",
|
|
3833
3833
|
"--sidenavigation-footer-active-accent-width": "--border-width-3",
|
|
@@ -3844,7 +3844,7 @@
|
|
|
3844
3844
|
"name": "midnight-study",
|
|
3845
3845
|
"component": "tabbar",
|
|
3846
3846
|
"createdAt": "2026-05-12T01:57:26.390Z",
|
|
3847
|
-
"updatedAt": "2026-08-
|
|
3847
|
+
"updatedAt": "2026-08-20T00:00:00.000Z",
|
|
3848
3848
|
"aliases": {
|
|
3849
3849
|
"--tabbar-bar-divider": "--border-neutral-subtle",
|
|
3850
3850
|
"--tabbar-bar-divider-thickness": "--border-width-1",
|
|
@@ -3858,7 +3858,7 @@
|
|
|
3858
3858
|
"--tabbar-default-text-font-weight": "--font-weight-light",
|
|
3859
3859
|
"--tabbar-default-text-line-height": "--line-height-normal",
|
|
3860
3860
|
"--tabbar-default-icon-size": "--icon-size-md",
|
|
3861
|
-
"--tabbar-default-padding": "--space-
|
|
3861
|
+
"--tabbar-default-padding": "--space-6",
|
|
3862
3862
|
"--tabbar-default-border": "--color-transparent",
|
|
3863
3863
|
"--tabbar-default-surface": "--color-transparent",
|
|
3864
3864
|
"--tabbar-default-tab-border-color": "--color-transparent",
|
|
@@ -3873,7 +3873,7 @@
|
|
|
3873
3873
|
"--tabbar-hover-text-font-weight": "--font-weight-light",
|
|
3874
3874
|
"--tabbar-hover-text-line-height": "--line-height-normal",
|
|
3875
3875
|
"--tabbar-hover-icon-size": "--icon-size-md",
|
|
3876
|
-
"--tabbar-hover-padding": "--space-
|
|
3876
|
+
"--tabbar-hover-padding": "--space-6",
|
|
3877
3877
|
"--tabbar-hover-border": "--color-transparent",
|
|
3878
3878
|
"--tabbar-hover-tab-border-color": "--color-transparent",
|
|
3879
3879
|
"--tabbar-hover-tab-border-width": "--border-width-0",
|
|
@@ -3888,7 +3888,7 @@
|
|
|
3888
3888
|
"--tabbar-active-text-font-weight": "--font-weight-light",
|
|
3889
3889
|
"--tabbar-active-text-line-height": "--line-height-normal",
|
|
3890
3890
|
"--tabbar-active-icon-size": "--icon-size-md",
|
|
3891
|
-
"--tabbar-active-padding": "--space-
|
|
3891
|
+
"--tabbar-active-padding": "--space-6",
|
|
3892
3892
|
"--tabbar-active-tab-border-color": "--color-transparent",
|
|
3893
3893
|
"--tabbar-active-tab-border-width": "--border-width-0",
|
|
3894
3894
|
"--tabbar-active-tab-top-radius": "--radius-none",
|
|
@@ -3900,7 +3900,7 @@
|
|
|
3900
3900
|
"--tabbar-disabled-text-font-weight": "--font-weight-light",
|
|
3901
3901
|
"--tabbar-disabled-text-line-height": "--line-height-normal",
|
|
3902
3902
|
"--tabbar-disabled-icon-size": "--icon-size-md",
|
|
3903
|
-
"--tabbar-disabled-padding": "--space-
|
|
3903
|
+
"--tabbar-disabled-padding": "--space-6",
|
|
3904
3904
|
"--tabbar-disabled-border": "--color-transparent",
|
|
3905
3905
|
"--tabbar-disabled-surface": "--color-transparent",
|
|
3906
3906
|
"--tabbar-disabled-tab-border-color": "--color-transparent",
|
|
@@ -3980,7 +3980,7 @@
|
|
|
3980
3980
|
"name": "midnight-study",
|
|
3981
3981
|
"component": "tooltip",
|
|
3982
3982
|
"createdAt": "2026-04-18T02:43:11.333Z",
|
|
3983
|
-
"updatedAt": "2026-08-
|
|
3983
|
+
"updatedAt": "2026-08-20T00:00:00.000Z",
|
|
3984
3984
|
"aliases": {
|
|
3985
3985
|
"--tooltip-surface": "color-mix(in srgb, var(--surface-canvas-lowest) 75%, transparent)",
|
|
3986
3986
|
"--tooltip-text": "--text-primary",
|
|
@@ -3991,10 +3991,10 @@
|
|
|
3991
3991
|
"--tooltip-border": "--border-neutral",
|
|
3992
3992
|
"--tooltip-border-width": "--border-width-1",
|
|
3993
3993
|
"--tooltip-radius": "--radius-md",
|
|
3994
|
-
"--tooltip-padding": "--space-
|
|
3994
|
+
"--tooltip-padding": "--space-6",
|
|
3995
3995
|
"--tooltip-shadow": "--shadow-md"
|
|
3996
3996
|
}
|
|
3997
3997
|
}
|
|
3998
3998
|
},
|
|
3999
3999
|
"componentSchemaVersion": 21
|
|
4000
|
-
}
|
|
4000
|
+
}
|
|
@@ -2270,11 +2270,11 @@
|
|
|
2270
2270
|
"--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
|
|
2271
2271
|
"--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
|
|
2272
2272
|
"--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
|
|
2273
|
-
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.
|
|
2274
|
-
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.
|
|
2275
|
-
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.
|
|
2276
|
-
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.
|
|
2277
|
-
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.
|
|
2273
|
+
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2274
|
+
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2275
|
+
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2276
|
+
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2277
|
+
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.2)"
|
|
2278
2278
|
},
|
|
2279
2279
|
"fontSources": [
|
|
2280
2280
|
{
|
|
@@ -2256,11 +2256,11 @@
|
|
|
2256
2256
|
"--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
|
|
2257
2257
|
"--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
|
|
2258
2258
|
"--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
|
|
2259
|
-
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.
|
|
2260
|
-
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.
|
|
2261
|
-
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.
|
|
2262
|
-
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.
|
|
2263
|
-
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.
|
|
2259
|
+
"--shadow-sm": "1px 1px 2px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2260
|
+
"--shadow-md": "3px 3px 6px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2261
|
+
"--shadow-lg": "5px 5px 9px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2262
|
+
"--shadow-xl": "6px 7px 13px 0px hsla(237, 18%, 3%, 0.2)",
|
|
2263
|
+
"--shadow-2xl": "8px 9px 16px 0px hsla(237, 18%, 3%, 0.2)"
|
|
2264
2264
|
},
|
|
2265
2265
|
"fontSources": [
|
|
2266
2266
|
{
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Decoupled from the design system: pills, knots, strings, balls, and
|
|
3
3
|
* animations are hardcoded so token edits don't repaint our chrome. The
|
|
4
|
-
*
|
|
5
|
-
* surface/radius/border (set inline)
|
|
6
|
-
* <MenuSelect>)
|
|
4
|
+
* exceptions are intentional and ARE the demo: the central box's
|
|
5
|
+
* surface/radius/border (set inline), the dropdown (rendered through
|
|
6
|
+
* <MenuSelect>), and elevation, which reads from the --shadow-* scale.
|
|
7
|
+
* File is global; keep selectors `ftt-` prefixed.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
.ftt-stage {
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
border-color: #52726e;
|
|
35
36
|
border-width: 2px;
|
|
36
37
|
border-radius: 0.25rem;
|
|
37
|
-
box-shadow:
|
|
38
|
+
box-shadow: var(--shadow-md);
|
|
38
39
|
transition:
|
|
39
40
|
background 300ms ease,
|
|
40
41
|
border-color 300ms ease,
|
|
@@ -188,6 +189,7 @@
|
|
|
188
189
|
color: #000;
|
|
189
190
|
border: 1px solid rgba(255, 255, 255, 0.55);
|
|
190
191
|
border-radius: 9999px;
|
|
192
|
+
box-shadow: var(--shadow-md);
|
|
191
193
|
font-family: Manrope, system-ui, -apple-system, sans-serif;
|
|
192
194
|
font-size: 0.875rem;
|
|
193
195
|
font-weight: 600;
|
|
@@ -48,7 +48,8 @@ function luminance({ r, g, b }: Rgb): number {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Pick the invariant black/white token with the stronger minimum contrast
|
|
51
|
-
* against every explicit colour in a solid or gradient page background
|
|
51
|
+
* against every explicit colour in a solid or gradient page background —
|
|
52
|
+
* the rule FloatingTokenTags uses for its kite strings.
|
|
52
53
|
*/
|
|
53
54
|
export function contrastTokenForBackground(background: string): ContrastColorToken {
|
|
54
55
|
const colors = colorsIn(background);
|