@daydreamui/tokens 0.0.0-stage → 0.4.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/dist/index.js ADDED
@@ -0,0 +1,1658 @@
1
+ import { className, createComponent, delegateEvents, effect, insert, readShallow, ref, setAttribute, setStyleProperty, style, template } from "@solidjs/web";
2
+ import { For, Show, createEffect, createSignal, onCleanup, onSettled, untrack } from "solid-js";
3
+ function valueMapper(before, after, rootSize) {
4
+ const old = new Map(before.map((t) => [t.name.slice(2), t.value]));
5
+ const available = new Set(after.map((t) => t.name.slice(2)));
6
+ const parser = document.createElement("span").style;
7
+ const colors = new Map();
8
+ const color = (value) => {
9
+ const cached = colors.get(value);
10
+ if (cached !== void 0) return cached;
11
+ const probe = document.createElement("span");
12
+ probe.style.color = value;
13
+ if (!probe.style.color || /^(transparent|currentcolor|inherit|initial|unset|revert|revert-layer)$/.test(probe.style.color)) return "";
14
+ probe.style.position = "fixed";
15
+ probe.style.visibility = "hidden";
16
+ document.documentElement.append(probe);
17
+ try {
18
+ const normalized = getComputedStyle(probe).color;
19
+ colors.set(value, normalized);
20
+ return normalized;
21
+ } finally {
22
+ probe.remove();
23
+ }
24
+ };
25
+ const oldColors = new Map([...old].map(([name, value]) => [name, color(value)]));
26
+ const variable = (name) => available.has(name) ? `var(--tokens-${name})` : null;
27
+ const colorValue = (value, names) => {
28
+ const normalized = color(value);
29
+ if (!normalized || [
30
+ "transparent",
31
+ "currentcolor",
32
+ "inherit",
33
+ "initial",
34
+ "unset"
35
+ ].includes(normalized)) return null;
36
+ const name = names.find((name) => available.has(name) && oldColors.get(name) === normalized);
37
+ return name ? variable(name) : null;
38
+ };
39
+ const scaled = (value, role) => {
40
+ if (!available.has(role)) return null;
41
+ const source = old.get(role) ?? "";
42
+ if (!/^\d+(?:\.\d+)?px$/.test(source)) return null;
43
+ const baseline = parseFloat(source);
44
+ if (!(baseline > 0)) return null;
45
+ const converted = value.split(/\s+/).map((piece) => {
46
+ const length = /^([\d.]+)(px|rem)$/.exec(piece);
47
+ if (!length || Number(length[1]) === 0) return piece;
48
+ const px = Number(length[1]) * (length[2] === "rem" ? rootSize : 1);
49
+ const ratio = Math.round(px / baseline * 1e4) / 1e4;
50
+ return ratio === 1 ? `var(--tokens-${role})` : `calc(var(--tokens-${role}) * ${ratio})`;
51
+ });
52
+ return converted.join(" ") === value ? null : converted.join(" ");
53
+ };
54
+ return (property, value, use) => {
55
+ if (value.includes("var(") || value.includes("env(") || /^(inherit|initial|unset|revert|revert-layer|normal|none)$/.test(value)) return null;
56
+ if (property.startsWith("--")) {
57
+ if (!use.customProperties || property.startsWith("--tokens-")) return null;
58
+ return colorValue(value, [
59
+ "primary",
60
+ "background",
61
+ "foreground",
62
+ "card",
63
+ "muted",
64
+ "muted-foreground",
65
+ "border",
66
+ "primary-foreground"
67
+ ]);
68
+ }
69
+ if (property === "color") return colorValue(value, use.control ? [
70
+ "primary-foreground",
71
+ "foreground",
72
+ "muted-foreground",
73
+ "primary"
74
+ ] : [
75
+ "foreground",
76
+ "muted-foreground",
77
+ "primary",
78
+ "primary-foreground"
79
+ ]);
80
+ if (property === "background-color" || property === "background") {
81
+ const names = use.control ? [
82
+ "primary",
83
+ "card",
84
+ "background",
85
+ "muted"
86
+ ] : [
87
+ "background",
88
+ "card",
89
+ "muted",
90
+ "primary"
91
+ ];
92
+ return colorValue(value, names);
93
+ }
94
+ if (/^border(-(?:top|right|bottom|left))?-color$/.test(property) || property === "outline-color") return colorValue(value, [
95
+ "border",
96
+ "primary",
97
+ "foreground"
98
+ ]);
99
+ if (/^(border(-(?:top|right|bottom|left))?|outline)$/.test(property)) {
100
+ parser.cssText = "";
101
+ parser.setProperty(property, value);
102
+ const part = parser.getPropertyValue(`${property}-color`);
103
+ const mapped = colorValue(part, [
104
+ "border",
105
+ "primary",
106
+ "foreground"
107
+ ]);
108
+ const serialized = parser.getPropertyValue(property);
109
+ return mapped && part && serialized.includes(part) ? serialized.replace(part, mapped) : null;
110
+ }
111
+ const themeFont = (family) => {
112
+ parser.fontFamily = "";
113
+ parser.fontFamily = old.get(use.heading ? "font-heading" : "font-sans") ?? old.get("font-sans") ?? "";
114
+ const baseline = parser.fontFamily;
115
+ parser.fontFamily = "";
116
+ parser.fontFamily = family;
117
+ return baseline && baseline === parser.fontFamily ? variable(use.heading ? "font-heading" : "font-sans") ?? variable("font-sans") : null;
118
+ };
119
+ if (property === "font-family") return themeFont(value);
120
+ if (property === "font") {
121
+ parser.cssText = "";
122
+ parser.font = value;
123
+ const family = parser.fontFamily;
124
+ const font = parser.font;
125
+ const mapped = themeFont(family);
126
+ if (!family || !mapped || !font.includes(family)) return null;
127
+ const weight = use.heading ? variable("heading-weight") : null;
128
+ if (!weight) return font.replace(family, mapped);
129
+ parser.font = font;
130
+ parser.fontWeight = "normal";
131
+ return `${weight} ${parser.font.replace(family, mapped)}`;
132
+ }
133
+ if (use.heading && property === "font-weight") return variable("heading-weight");
134
+ if (use.heading && property === "letter-spacing") return variable("heading-tracking");
135
+ if (/^border(-(?:top|bottom)-(?:left|right))?-radius$/.test(property)) return scaled(value, "radius");
136
+ if (/^(padding(-(?:top|right|bottom|left|block|inline|block-start|block-end|inline-start|inline-end))?|gap|row-gap|column-gap)$/.test(property)) return scaled(value, "spacing");
137
+ return null;
138
+ };
139
+ }
140
+ function adoptDeclarations(text, core, map, use) {
141
+ let result = text;
142
+ for (const declaration of core.cssDeclarations(text).reverse()) {
143
+ const value = map(declaration.property, declaration.value, use);
144
+ if (!value) continue;
145
+ const searchable = text.slice(...declaration.range).replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\/\*[\s\S]*?\*\//g, (part) => part.startsWith("/*") ? " ".repeat(part.length) : part);
146
+ const at = searchable.indexOf(declaration.value, searchable.indexOf(":") + 1);
147
+ if (at < 0) continue;
148
+ const start = declaration.range[0] + at;
149
+ result = result.slice(0, start) + value + result.slice(start + declaration.value.length);
150
+ }
151
+ return result;
152
+ }
153
+ function extractionViewports(dd, context) {
154
+ const ids = new Set(context.itemSelection());
155
+ const selected = context.selection();
156
+ if (selected) ids.add(dd.pageElement(selected)?.viewportId ?? selected);
157
+ return dd.core.viewportItems(dd.document()).filter((v) => ids.has(v.id));
158
+ }
159
+ function ranked(bucket) {
160
+ return [...bucket.values()].sort((a, b) => b.uses.size - a.uses.size);
161
+ }
162
+ function visibleColor(value) {
163
+ if (value === "transparent") return false;
164
+ const alpha = value.includes("/") ? value.slice(value.lastIndexOf("/") + 1) : /^rgba\(/i.test(value) ? value.split(",")[3] : void 0;
165
+ return alpha === void 0 || parseFloat(alpha) !== 0;
166
+ }
167
+ function extractTheme(dd, context) {
168
+ const viewports = extractionViewports(dd, context);
169
+ if (!viewports.length) throw new Error("Select one or more page viewports first.");
170
+ const buckets = new Map();
171
+ const pages = new Set();
172
+ let truncated = false;
173
+ for (const viewport of viewports) {
174
+ const rootId = dd.pageFind(viewport.id, "html");
175
+ const root = rootId ? dd.geometry.node(rootId) : null;
176
+ if (!root) throw new Error("A selected page is still mounting. Try extracting again.");
177
+ const path = viewport.payload.page;
178
+ pages.add(path);
179
+ const add = (name, value, node, property) => {
180
+ if (!value || value.includes("<")) return;
181
+ const selector = dd.core.uniqueSelector(node);
182
+ const key = JSON.stringify([
183
+ path,
184
+ selector,
185
+ property
186
+ ]);
187
+ const bucket = buckets.get(name) ?? new Map();
188
+ const observation = bucket.get(value) ?? {
189
+ value,
190
+ uses: new Map()
191
+ };
192
+ observation.uses.set(key, `${path} · ${selector} · ${property} · ${viewport.frame?.width ?? 1024}px`);
193
+ bucket.set(value, observation);
194
+ buckets.set(name, bucket);
195
+ };
196
+ const body = root.querySelector("body") ?? root;
197
+ const rootStyle = getComputedStyle(root);
198
+ const bodyStyle = getComputedStyle(body);
199
+ const background = visibleColor(bodyStyle.backgroundColor) ? bodyStyle.backgroundColor : rootStyle.backgroundColor;
200
+ if (visibleColor(background)) add("background", background, visibleColor(bodyStyle.backgroundColor) ? body : root, "background-color");
201
+ add("foreground", bodyStyle.color, body, "color");
202
+ add("font-sans", bodyStyle.fontFamily, body, "font-family");
203
+ const visit = (node) => {
204
+ const style = getComputedStyle(node);
205
+ if (style.display === "none" || style.contentVisibility === "hidden" || Number(style.opacity) === 0) return;
206
+ const rects = node.getClientRects();
207
+ if (style.visibility === "visible" && [...rects].some((r) => r.width > 0 && r.height > 0)) {
208
+ const control = node.matches("button, [role=\"button\"], input[type=\"submit\"], input[type=\"button\"]") && !node.matches(":disabled, [aria-disabled=\"true\"]");
209
+ const hasText = [...node.childNodes].some((child) => child.nodeType === Node.TEXT_NODE && child.textContent?.trim()) || node.matches("input, textarea, select");
210
+ if (visibleColor(style.backgroundColor)) {
211
+ add("surface", style.backgroundColor, node, "background-color");
212
+ if (control && style.backgroundColor !== background) {
213
+ add("primary", style.backgroundColor, node, "background-color");
214
+ add(`action-text:${style.backgroundColor}`, style.color, node, "color");
215
+ } else if (node !== body && node !== root && style.backgroundColor !== background) add("card", style.backgroundColor, node, "background-color");
216
+ }
217
+ if (node.matches("h1, h2, h3, h4, h5, h6")) add("font-heading", style.fontFamily, node, "font-family");
218
+ if (hasText) {
219
+ add("color", style.color, node, "color");
220
+ if (!control && style.color !== bodyStyle.color) add("muted-foreground", style.color, node, "color");
221
+ for (const property of [
222
+ "font-family",
223
+ "font-size",
224
+ "font-weight",
225
+ "line-height"
226
+ ]) add(property, style.getPropertyValue(property), node, property);
227
+ }
228
+ for (const side of [
229
+ "top",
230
+ "right",
231
+ "bottom",
232
+ "left"
233
+ ]) {
234
+ const width = style.getPropertyValue(`border-${side}-width`);
235
+ const color = style.getPropertyValue(`border-${side}-color`);
236
+ if (parseFloat(width) > 0 && visibleColor(color)) {
237
+ add("border", color, node, `border-${side}-color`);
238
+ add("border-width", width, node, `border-${side}-width`);
239
+ }
240
+ const padding = style.getPropertyValue(`padding-${side}`);
241
+ if (parseFloat(padding) > 0) add("spacing", padding, node, `padding-${side}`);
242
+ }
243
+ for (const property of ["row-gap", "column-gap"]) {
244
+ const value = style.getPropertyValue(property);
245
+ if (parseFloat(value) > 0) add("spacing", value, node, property);
246
+ }
247
+ for (const corner of [
248
+ "top-left",
249
+ "top-right",
250
+ "bottom-right",
251
+ "bottom-left"
252
+ ]) {
253
+ const property = `border-${corner}-radius`;
254
+ const value = style.getPropertyValue(property);
255
+ if (parseFloat(value) > 0) add("radius", value, node, property);
256
+ }
257
+ if (style.boxShadow !== "none") add("shadow", style.boxShadow, node, "box-shadow");
258
+ }
259
+ for (const child of node.children) walk(child);
260
+ };
261
+ let visited = 0;
262
+ const walk = (node) => {
263
+ if (++visited > 2e3) {
264
+ truncated = true;
265
+ return;
266
+ }
267
+ visit(node);
268
+ };
269
+ walk(body);
270
+ }
271
+ const tokens = [];
272
+ const addToken = (name, observation) => {
273
+ if (!observation) return;
274
+ const evidence = [...observation.uses.values()];
275
+ tokens.push({
276
+ name: `--${name}`,
277
+ value: observation.value,
278
+ source: `${evidence.length} observations · ${evidence.slice(0, 3).join("; ")}${evidence.length > 3 ? "; …" : ""}`
279
+ });
280
+ };
281
+ const top = (name) => ranked(buckets.get(name) ?? new Map())[0];
282
+ for (const name of [
283
+ "background",
284
+ "foreground",
285
+ "card",
286
+ "muted-foreground",
287
+ "primary",
288
+ "border",
289
+ "font-sans",
290
+ "font-heading",
291
+ "radius",
292
+ "spacing"
293
+ ]) addToken(name, top(name));
294
+ const primary = top("primary");
295
+ if (primary) addToken("primary-foreground", top(`action-text:${primary.value}`));
296
+ for (const name of [
297
+ "background",
298
+ "foreground",
299
+ "card",
300
+ "primary",
301
+ "surface",
302
+ "color",
303
+ "font-family",
304
+ "font-size",
305
+ "font-weight",
306
+ "line-height",
307
+ "border-width",
308
+ "radius",
309
+ "spacing",
310
+ "shadow"
311
+ ]) {
312
+ const values = ranked(buckets.get(name) ?? new Map());
313
+ if (values.length > 5) truncated = true;
314
+ values.slice(0, 5).forEach((value, i) => {
315
+ if (!tokens.some((t) => t.name === `--${name}` && t.value === value.value)) addToken(`${name}-${i + 1}`, value);
316
+ });
317
+ }
318
+ if (!tokens.length) throw new Error("No visible page styles were found in this selection.");
319
+ return {
320
+ tokens,
321
+ pages: pages.size,
322
+ viewports: viewports.length,
323
+ truncated
324
+ };
325
+ }
326
+ function readTokens(css, core) {
327
+ if (css.length > 64e3) throw new Error("Use a token block under 64 KB.");
328
+ if (css.includes("<")) throw new Error("Use CSS values without HTML tags.");
329
+ const blocks = core.cssBlocks(css);
330
+ const sheet = new CSSStyleSheet();
331
+ sheet.replaceSync(css);
332
+ if (!blocks.length || sheet.cssRules.length !== blocks.length) throw new Error("Theme tokens must form a complete :root block.");
333
+ const tokens = new Map();
334
+ for (const [index, block] of blocks.entries()) {
335
+ const rule = sheet.cssRules[index];
336
+ if (!(rule instanceof CSSStyleRule) || rule.selectorText !== ":root" || block.children.length || block.statement || css[block.range[1] - 1] !== "}") throw new Error("Only :root token blocks are supported, without dark-mode, component or @theme rules.");
337
+ for (const declaration of block.declarations) {
338
+ if (!declaration.property.startsWith("--") || declaration.important) throw new Error("Use custom properties without !important in the token block.");
339
+ const value = rule.style.getPropertyValue(declaration.property).trim();
340
+ if (!value) throw new Error(`Check the value of ${declaration.property}.`);
341
+ tokens.set(declaration.property, value);
342
+ }
343
+ if (rule.style.length !== new Set(block.declarations.map((d) => d.property)).size) throw new Error("Some declarations could not be read. Check the token block.");
344
+ }
345
+ if (!tokens.size || tokens.size > 100) throw new Error("A theme must contain between 1 and 100 tokens.");
346
+ return [...tokens].map(([name, value]) => ({
347
+ name,
348
+ value
349
+ }));
350
+ }
351
+ function declarations(tokens) {
352
+ return tokens.map(({ name, value }) => `${CSS.escape(name)}: ${value};`).join("\n");
353
+ }
354
+ function tokenCss(tokens) {
355
+ return `:root {\n${declarations(tokens)}\n}\n`;
356
+ }
357
+ function changedToken(token, value, core) {
358
+ const probe = new CSSStyleSheet();
359
+ probe.replaceSync(`:root { ${CSS.escape(token.name)}: ${value};\n--tokens-validation-sentinel: valid; }`);
360
+ const rule = probe.cssRules[0];
361
+ if (!(rule instanceof CSSStyleRule) || rule.style.getPropertyValue("--tokens-validation-sentinel").trim() !== "valid") throw new Error("Enter a complete CSS value.");
362
+ const parsed = readTokens(`:root { ${CSS.escape(token.name)}: ${value}; }`, core);
363
+ if (parsed.length !== 1 || parsed[0].name !== token.name || value.includes(";")) throw new Error("Enter one CSS value, without a semicolon.");
364
+ return parsed[0];
365
+ }
366
+ function managedTokens(tokens) {
367
+ const names = new Set(tokens.map((t) => t.name));
368
+ return tokens.map((t) => ({
369
+ name: `--tokens-${t.name.slice(2)}`,
370
+ value: t.value.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|var\(\s*(--[\w-]+)(?=[\s,)])/g, (part, name) => name && names.has(name) ? part.replace(name, `--tokens-${name.slice(2)}`) : part)
371
+ }));
372
+ }
373
+ var headings = "h1, h2, h3, h4, h5, h6";
374
+ var actions = "button, [role=\"button\"], input[type=\"submit\"], input[type=\"button\"]";
375
+ var fields = "input:not([type]), input[type=\"text\"], input[type=\"email\"], input[type=\"password\"], input[type=\"search\"], input[type=\"number\"], textarea, select";
376
+ var themeSelector = ":root:where(html)";
377
+ var themeSelectors = [themeSelector, ...[
378
+ headings,
379
+ actions,
380
+ fields
381
+ ].map((s) => `:where(html) :where(${s})`)];
382
+ function themeRules(tokens, defaults = true) {
383
+ const names = new Set(tokens.map((t) => t.name));
384
+ const written = managedTokens(tokens);
385
+ const properties = (pairs) => pairs.filter(([, name]) => names.has(`--${name}`)).map(([property, name]) => `${property}: var(--tokens-${name});`).join("\n");
386
+ return [
387
+ {
388
+ selector: themeSelector,
389
+ match: "html",
390
+ body: declarations(written) + "\n" + (defaults ? properties([
391
+ ["background-color", "background"],
392
+ ["color", "foreground"],
393
+ ["font-family", "font-sans"]
394
+ ]) : "")
395
+ },
396
+ {
397
+ selector: themeSelectors[1],
398
+ match: headings,
399
+ body: properties([
400
+ ["font-family", "font-heading"],
401
+ ["font-weight", "heading-weight"],
402
+ ["letter-spacing", "heading-tracking"]
403
+ ])
404
+ },
405
+ {
406
+ selector: themeSelectors[2],
407
+ match: actions,
408
+ body: properties([
409
+ ["background-color", "primary"],
410
+ ["color", "primary-foreground"],
411
+ ["border-color", "border"],
412
+ ["border-radius", "radius"],
413
+ ["font-family", "font-sans"]
414
+ ])
415
+ },
416
+ {
417
+ selector: themeSelectors[3],
418
+ match: fields,
419
+ body: properties([
420
+ ["background-color", "card"],
421
+ ["color", "foreground"],
422
+ ["border-color", "border"],
423
+ ["border-radius", "radius"],
424
+ ["font-family", "font-sans"]
425
+ ])
426
+ }
427
+ ];
428
+ }
429
+ var sheetIdentity = (path, source) => "file" in source ? source.file : `${path}:${JSON.stringify(source)}`;
430
+ function planAdoption(dd, viewportId, path, tokens) {
431
+ const rootId = dd.pageFind(viewportId, "html");
432
+ const root = rootId ? dd.geometry.node(rootId) : null;
433
+ if (!root) throw new Error("A selected page is still mounting. Try Apply again.");
434
+ const nodes = [root, ...root.querySelectorAll("*")];
435
+ if (nodes.length > 2e3) throw new Error("Theme adoption supports up to 2,000 elements per page.");
436
+ const map = valueMapper(extractTheme(dd, {
437
+ selection: () => viewportId,
438
+ itemSelection: () => [viewportId]
439
+ }).tokens, tokens, parseFloat(getComputedStyle(root).fontSize));
440
+ const rules = new Map();
441
+ const edits = [];
442
+ for (const node of nodes) {
443
+ if (node.matches("head, head *, script, style, template")) continue;
444
+ const selector = dd.core.uniqueSelector(node);
445
+ const id = dd.pageFind(viewportId, selector);
446
+ if (!id) continue;
447
+ const stack = dd.pageStack(id);
448
+ if (!stack) continue;
449
+ const use = {
450
+ heading: node.matches("h1, h2, h3, h4, h5, h6"),
451
+ control: node.matches("button, [role=\"button\"], input[type=\"submit\"], input[type=\"button\"]")
452
+ };
453
+ if (stack.own) {
454
+ const next = adoptDeclarations(stack.own, dd.core, map, use);
455
+ if (next !== stack.own) edits.push({
456
+ file: path,
457
+ selector,
458
+ edit: {
459
+ kind: "style",
460
+ elementId: id,
461
+ expected: stack.own,
462
+ declarations: next
463
+ }
464
+ });
465
+ }
466
+ for (const rule of stack.rules) {
467
+ if (themeSelectors.includes(rule.prelude)) continue;
468
+ const prior = rules.get(rule.key);
469
+ if (prior) {
470
+ prior.use.heading &&= use.heading;
471
+ prior.use.control &&= use.control;
472
+ } else rules.set(rule.key, {
473
+ rule,
474
+ selector,
475
+ use: {
476
+ ...use,
477
+ customProperties: node === root && rule.active && !rule.conditions.length && !rule.parents.length
478
+ }
479
+ });
480
+ }
481
+ }
482
+ for (const { rule, use, selector } of rules.values()) {
483
+ const next = adoptDeclarations(rule.declarations, dd.core, map, use);
484
+ if (next !== rule.declarations && !rule.editable) throw new Error(`Cannot adopt the theme because ${rule.prelude} is in a read-only or unsupported rule. Make that stylesheet editable first.`);
485
+ if (next !== rule.declarations) edits.push({
486
+ file: sheetIdentity(path, rule.sheet),
487
+ sheet: rule.sheet,
488
+ selector,
489
+ edit: {
490
+ kind: "rule",
491
+ viewportId,
492
+ key: rule.key,
493
+ expected: rule.declarations,
494
+ declarations: next
495
+ }
496
+ });
497
+ }
498
+ return edits.sort((a, b) => Number(a.edit.kind === "style") - Number(b.edit.kind === "style"));
499
+ }
500
+ var sameSource = (a, b) => JSON.stringify(a) === JSON.stringify(b);
501
+ function hasAuthoredLayers(page) {
502
+ const containsLayer = (rules) => [...rules].some((rule) => rule instanceof CSSLayerBlockRule || rule instanceof CSSLayerStatementRule || "cssRules" in rule && containsLayer(rule.cssRules));
503
+ return page.sheets.some((source) => {
504
+ const sheet = new CSSStyleSheet();
505
+ sheet.replaceSync(source.text);
506
+ return containsLayer(sheet.cssRules);
507
+ });
508
+ }
509
+ function unconditionalSheet(page, source) {
510
+ const doc = new DOMParser().parseFromString(page.html, "text/html");
511
+ const stored = page.sheets.find((s) => sameSource(s.source, source));
512
+ const base = new URL(page.path.split("/").map(encodeURIComponent).join("/"), "https://tokens.invalid/");
513
+ const elements = [...doc.querySelectorAll("link, style")].filter((el) => {
514
+ if ("style" in source) return el.localName === "style" && el.textContent === stored?.text.replace(/\r\n?/g, "\n");
515
+ if (!("file" in source) || el.localName !== "link") return false;
516
+ try {
517
+ const url = new URL(el.getAttribute("href") ?? "", base);
518
+ return url.origin === base.origin && decodeURIComponent(url.pathname.slice(1)) === source.file;
519
+ } catch {
520
+ return false;
521
+ }
522
+ });
523
+ if (!elements.length) return "file" in source;
524
+ return elements.every((el) => {
525
+ const media = new CSSStyleSheet({ media: el.getAttribute("media") ?? "" }).media.mediaText;
526
+ return media === "" || media === "all";
527
+ });
528
+ }
529
+ function mergeDeclarations(core, before, after, tokensOnly = false) {
530
+ const replacing = new Set(core.cssDeclarations(after).map((d) => d.property));
531
+ let kept = before;
532
+ for (const d of core.cssDeclarations(before).reverse()) if (replacing.has(d.property) || tokensOnly && !d.property.startsWith("--")) kept = kept.slice(0, d.range[0]) + kept.slice(d.range[1]);
533
+ else if (d.property.startsWith("--") && replacing.has(`--tokens-${d.property.slice(2)}`)) {
534
+ const alias = `${CSS.escape(d.property)}: var(--tokens-${d.property.slice(2)})${d.important ? " !important" : ""};`;
535
+ kept = kept.slice(0, d.range[0]) + alias + kept.slice(d.range[1]);
536
+ }
537
+ return `${kept.trim()}\n${after}`.trim();
538
+ }
539
+ function hasTheme(dd, target) {
540
+ const id = dd.pageFind(target.viewportId, "html");
541
+ return id && dd.pageStack(id)?.rules.some((r) => r.prelude === ":root:where(html)" && sameSource(r.sheet, target.source));
542
+ }
543
+ function writeThemeSheet(dd, transaction, target, tokens, defaults, consumers, suppressDefaults) {
544
+ for (const { selector, match, body } of themeRules(tokens, defaults && !suppressDefaults)) {
545
+ if (!suppressDefaults && (!body || !defaults && selector !== ":root:where(html)")) continue;
546
+ let viewportId = target.viewportId;
547
+ let id = null;
548
+ for (const consumer of consumers) {
549
+ const rootId = dd.pageFind(consumer.viewportId, "html");
550
+ const root = rootId ? dd.geometry.node(rootId) : null;
551
+ const node = match === "html" ? root : root?.querySelector(match);
552
+ if (!node) continue;
553
+ viewportId = consumer.viewportId;
554
+ id = dd.pageFind(viewportId, dd.core.uniqueSelector(node));
555
+ if (id) break;
556
+ }
557
+ if (!id) continue;
558
+ const rule = id && dd.pageStack(id)?.rules.find((r) => r.prelude === selector && r.editable && !r.parents.length && sameSource(r.sheet, target.source));
559
+ const next = suppressDefaults && selector !== ":root:where(html)" ? "" : body;
560
+ if (!rule && !next) continue;
561
+ const result = transaction.write(rule ? {
562
+ kind: "rule",
563
+ viewportId,
564
+ key: rule.key,
565
+ expected: rule.declarations,
566
+ declarations: suppressDefaults && selector !== ":root:where(html)" ? "" : mergeDeclarations(dd.core, rule.declarations, next, suppressDefaults)
567
+ } : {
568
+ kind: "add",
569
+ viewportId,
570
+ sheet: target.source,
571
+ selector,
572
+ declarations: next
573
+ });
574
+ if (typeof result === "string") throw new Error(result);
575
+ }
576
+ }
577
+ function selectedPages(dd, context) {
578
+ const targets = [];
579
+ const pages = new Set();
580
+ for (const viewport of extractionViewports(dd, context)) {
581
+ const root = dd.pageFind(viewport.id, "html");
582
+ const stack = root ? dd.pageStack(root) : null;
583
+ if (!stack) return [];
584
+ if (pages.has(viewport.payload.page)) continue;
585
+ pages.add(viewport.payload.page);
586
+ targets.push({
587
+ viewportId: viewport.id,
588
+ path: viewport.payload.page,
589
+ stack
590
+ });
591
+ }
592
+ return targets;
593
+ }
594
+ function applyTokens(dd, context, tokens) {
595
+ const targets = selectedPages(dd, context);
596
+ if (!targets.length) throw new Error("Select editable page viewports on the canvas first.");
597
+ readTokens(tokenCss(tokens), dd.core);
598
+ const planned = targets.flatMap((target) => planAdoption(dd, target.viewportId, target.path, tokens));
599
+ const sheets = new Map();
600
+ const include = (target, defaults) => {
601
+ const file = sheetIdentity(target.path, target.source);
602
+ const prior = sheets.get(file);
603
+ sheets.set(file, {
604
+ target: defaults ? target : prior?.target ?? target,
605
+ defaults: defaults || !!prior?.defaults
606
+ });
607
+ };
608
+ for (const target of targets) {
609
+ const page = dd.page(target.path);
610
+ if (!page) throw new Error("A selected page changed. Try Apply again.");
611
+ const landing = target.stack.sheets.filter((s) => s.editable && (s.absent || unconditionalSheet(page, s.source))).at(-1);
612
+ if (landing) include({
613
+ ...target,
614
+ source: landing.source
615
+ }, true);
616
+ else throw new Error(`${target.path} needs an editable stylesheet without a media restriction before applying a theme.`);
617
+ for (const sheet of target.stack.sheets) {
618
+ const entry = {
619
+ ...target,
620
+ source: sheet.source
621
+ };
622
+ if (sheet.editable && hasTheme(dd, entry)) include(entry, false);
623
+ }
624
+ for (const entry of planned) if (entry.edit.kind === "rule" && entry.edit.viewportId === target.viewportId && entry.sheet) include({
625
+ ...target,
626
+ source: entry.sheet
627
+ }, false);
628
+ }
629
+ const layeredSheets = new Set();
630
+ const mountedConsumers = [...targets];
631
+ const selectedIds = new Set(targets.map((target) => target.viewportId));
632
+ for (const viewport of dd.core.viewportItems(dd.document())) {
633
+ if (!selectedIds.has(viewport.id)) {
634
+ const root = dd.pageFind(viewport.id, "html");
635
+ const stack = root ? dd.pageStack(root) : null;
636
+ if (stack) mountedConsumers.push({
637
+ viewportId: viewport.id,
638
+ path: viewport.payload.page,
639
+ stack
640
+ });
641
+ }
642
+ const page = dd.page(viewport.payload.page);
643
+ if (page && hasAuthoredLayers(page)) for (const sheet of page.sheets) layeredSheets.add(sheetIdentity(page.path, sheet.source));
644
+ }
645
+ const transaction = dd.beginItemTransaction();
646
+ try {
647
+ const equivalent = (a, b) => JSON.stringify(dd.core.cssDeclarations(a).map(({ property, value, important }) => [
648
+ property,
649
+ value,
650
+ important
651
+ ])) === JSON.stringify(dd.core.cssDeclarations(b).map(({ property, value, important }) => [
652
+ property,
653
+ value,
654
+ important
655
+ ]));
656
+ for (const entry of planned) {
657
+ const edit = entry.edit;
658
+ if (edit.kind !== "rule") continue;
659
+ const id = dd.pageFind(edit.viewportId, entry.selector);
660
+ const rule = id && dd.pageStack(id)?.rules.find((r) => r.key === edit.key);
661
+ if (!rule) throw new Error("A selected page changed. Try Apply again.");
662
+ if (equivalent(rule.declarations, edit.declarations)) continue;
663
+ if (!equivalent(rule.declarations, edit.expected)) throw new Error("Selected pages assign different theme roles to a shared rule. Apply to one page at a time.");
664
+ const result = transaction.write({
665
+ ...edit,
666
+ expected: rule.declarations
667
+ });
668
+ if (typeof result === "string") throw new Error(result);
669
+ for (const remaining of planned) if (remaining.edit.kind === "rule" && remaining.edit.viewportId === edit.viewportId) remaining.edit.key = result.moved[remaining.edit.key] ?? remaining.edit.key;
670
+ }
671
+ for (const { target, defaults } of sheets.values()) writeThemeSheet(dd, transaction, target, tokens, defaults, (layeredSheets.has(sheetIdentity(target.path, target.source)) ? mountedConsumers : targets).filter((consumer) => consumer.stack.sheets.some((s) => sheetIdentity(consumer.path, s.source) === sheetIdentity(target.path, target.source))).map((consumer) => ({
672
+ ...consumer,
673
+ source: target.source
674
+ })), layeredSheets.has(sheetIdentity(target.path, target.source)));
675
+ for (const entry of planned) {
676
+ if (entry.edit.kind !== "style") continue;
677
+ const viewportId = targets.find((t) => t.path === entry.file).viewportId;
678
+ const id = dd.pageFind(viewportId, entry.selector);
679
+ if (!id) throw new Error("A selected page changed. Try Apply again.");
680
+ const result = transaction.write({
681
+ ...entry.edit,
682
+ elementId: id
683
+ });
684
+ if (typeof result === "string") throw new Error(result);
685
+ }
686
+ transaction.commit();
687
+ } catch (cause) {
688
+ transaction.cancel();
689
+ throw cause;
690
+ }
691
+ return targets.map((target) => target.path).join(", ");
692
+ }
693
+ var specimenStyles = `
694
+ html { background-color: var(--tokens-background, #f6f3ec); color: var(--tokens-foreground, #242a25); font-family: var(--tokens-font-sans, system-ui); }
695
+ * { box-sizing: border-box; }
696
+ body { margin: 0; padding: clamp(24px, 5vw, 56px); }
697
+ main { max-width: 960px; margin: auto; }
698
+ header { display: flex; gap: 24px; justify-content: space-between; align-items: baseline; border-bottom: 1px solid var(--tokens-border, #d5d7cc); padding-bottom: 24px; }
699
+ h1, h2 { font-family: var(--tokens-font-heading, var(--tokens-font-sans, system-ui)); letter-spacing: var(--tokens-heading-tracking, normal); }
700
+ h1 { margin: 0; font-size: clamp(32px, 5vw, 52px); font-weight: var(--tokens-heading-weight, 500); line-height: 1.1; }
701
+ h2 { margin: 0 0 16px; font-size: 22px; font-weight: var(--tokens-heading-weight, 500); }
702
+ p { line-height: 1.6; margin: 8px 0 20px; }
703
+ small, .muted { color: var(--tokens-muted-foreground, #62685f); }
704
+ .layout { display: grid; grid-template-columns: 1.2fr 1fr; gap: calc(var(--tokens-spacing, 16px) * 2); margin-top: 32px; }
705
+ section { min-width: 0; }
706
+ .card { padding: calc(var(--tokens-spacing, 16px) * 1.5); border: 1px solid var(--tokens-border, #d5d7cc); background-color: var(--tokens-card, #fffdf7); border-radius: var(--tokens-radius, 8px); }
707
+ .row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--tokens-spacing, 16px); }
708
+ button, input, select { font: inherit; border-radius: var(--tokens-radius, 8px); padding: 10px 16px; border: 1px solid var(--tokens-border, #d5d7cc); }
709
+ button { background-color: var(--tokens-primary, #355c48); color: var(--tokens-primary-foreground, white); cursor: pointer; }
710
+ button.secondary { background-color: var(--tokens-card, #fffdf7); color: var(--tokens-foreground, #242a25); }
711
+ button:disabled { opacity: .5; cursor: default; }
712
+ button:hover:not(:disabled) { filter: brightness(.92); }
713
+ :focus-visible { outline: 2px solid var(--tokens-primary, #355c48); outline-offset: 3px; }
714
+ label { display: grid; gap: 8px; margin: 20px 0; }
715
+ input, select { min-width: 0; width: 100%; background-color: var(--tokens-background, #f6f3ec); color: var(--tokens-foreground, #242a25); }
716
+ .tag { display: inline-block; padding: 4px 10px; border-radius: var(--tokens-radius, 8px); background-color: var(--tokens-muted, #eae6dc); font-size: 13px; }
717
+ .palette { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 16px 0 32px; }
718
+ .swatch { height: 64px; border: 1px solid var(--tokens-border, #d5d7cc); border-radius: var(--tokens-radius, 8px); }
719
+ .swatch:nth-child(1) { background-color: var(--tokens-primary, #355c48); }
720
+ .swatch:nth-child(2) { background-color: var(--tokens-foreground, #242a25); }
721
+ .swatch:nth-child(3) { background-color: var(--tokens-muted, #eae6dc); }
722
+ .swatch:nth-child(4) { background-color: var(--tokens-card, #fffdf7); }
723
+ footer { margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--tokens-border, #d5d7cc); font-size: 13px; }
724
+ @media (max-width: 640px) { .layout { grid-template-columns: 1fr; } header { flex-direction: column; gap: 12px; } }
725
+ `;
726
+ function specimen(tokens) {
727
+ return `<!doctype html>
728
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
729
+ <title>Theme specimen</title><style>${themeSelector} { ${declarations(managedTokens(tokens))} }</style><style>${specimenStyles}</style></head>
730
+ <body><main>
731
+ <header><h1>A little more character.</h1><small>Theme specimen · Daydream</small></header>
732
+ <div class="layout">
733
+ <section><div class="card"><span class="tag">Field notes</span><h2 style="margin-top: 24px">Make room for good ideas.</h2>
734
+ <p>A place to collect the details, share a thought, and find the next thing worth making.</p>
735
+ <div class="row"><button type="button">Start a collection</button><button type="button" class="secondary">Explore</button></div></div>
736
+ <h2 style="margin-top: 32px">Color and contrast</h2><div class="palette" aria-label="Theme colors"><div class="swatch"></div><div class="swatch"></div><div class="swatch"></div><div class="swatch"></div></div>
737
+ <h2>Words with a point of view.</h2><p class="muted">A typeface changes the voice. Space changes the pace. Small decisions make the whole feel like yours.</p></section>
738
+ <section class="card"><h2>The everyday details</h2><p class="muted">Inputs, actions, and the space between.</p>
739
+ <label>Collection name<input value="Things worth keeping"></label>
740
+ <label>Visibility<select><option>Only me</option><option>Everyone on the team</option></select></label>
741
+ <div class="row"><button type="button">Save collection</button><button type="button" disabled>Saving…</button></div>
742
+ <footer>Saved locally. Ready for your next idea.</footer></section>
743
+ </div><footer class="muted">Real HTML and CSS. Resize this viewport to compare the same theme on a smaller screen.</footer>
744
+ </main></body></html>`;
745
+ }
746
+ function createProbe({ ask, answer, target, debounceMs = 400 }) {
747
+ let running = null;
748
+ let again = false;
749
+ let timer;
750
+ let gone = false;
751
+ const run = async () => {
752
+ let reachable;
753
+ try {
754
+ reachable = await ask() === true;
755
+ } catch {
756
+ reachable = false;
757
+ }
758
+ if (!gone) answer(reachable);
759
+ };
760
+ const probe = (soon = false) => {
761
+ if (gone) return;
762
+ if (soon) {
763
+ clearTimeout(timer);
764
+ timer = setTimeout(() => probe(), debounceMs);
765
+ return;
766
+ }
767
+ if (running !== null) {
768
+ again = true;
769
+ return;
770
+ }
771
+ running = run().finally(() => {
772
+ running = null;
773
+ if (again) {
774
+ again = false;
775
+ probe();
776
+ }
777
+ });
778
+ };
779
+ const focused = () => probe(true);
780
+ target?.addEventListener("focus", focused);
781
+ return {
782
+ probe,
783
+ async settled() {
784
+ while (running !== null) await running;
785
+ },
786
+ dispose() {
787
+ gone = true;
788
+ clearTimeout(timer);
789
+ target?.removeEventListener("focus", focused);
790
+ }
791
+ };
792
+ }
793
+ var COLOR_KINDS = ["color"];
794
+ var SIZE_KINDS = ["number", "dimension"];
795
+ var FONT_KINDS = ["font"];
796
+ var ROLES = [
797
+ [
798
+ "background",
799
+ [
800
+ "background",
801
+ "bg",
802
+ "canvas",
803
+ "page",
804
+ "surface-0"
805
+ ],
806
+ COLOR_KINDS
807
+ ],
808
+ [
809
+ "foreground",
810
+ [
811
+ "foreground",
812
+ "fg",
813
+ "text",
814
+ "text-primary",
815
+ "text-default",
816
+ "on-background",
817
+ "ink"
818
+ ],
819
+ COLOR_KINDS
820
+ ],
821
+ [
822
+ "card",
823
+ [
824
+ "card",
825
+ "surface",
826
+ "elevated",
827
+ "panel"
828
+ ],
829
+ COLOR_KINDS
830
+ ],
831
+ [
832
+ "muted",
833
+ [
834
+ "muted",
835
+ "subtle",
836
+ "secondary-surface"
837
+ ],
838
+ COLOR_KINDS
839
+ ],
840
+ [
841
+ "muted-foreground",
842
+ [
843
+ "muted-foreground",
844
+ "text-secondary",
845
+ "secondary",
846
+ "hint",
847
+ "placeholder"
848
+ ],
849
+ COLOR_KINDS
850
+ ],
851
+ [
852
+ "primary",
853
+ [
854
+ "primary",
855
+ "brand",
856
+ "accent",
857
+ "action",
858
+ "cta"
859
+ ],
860
+ COLOR_KINDS
861
+ ],
862
+ [
863
+ "primary-foreground",
864
+ [
865
+ "primary-foreground",
866
+ "on-primary",
867
+ "on-brand",
868
+ "on-accent"
869
+ ],
870
+ COLOR_KINDS
871
+ ],
872
+ [
873
+ "border",
874
+ [
875
+ "border",
876
+ "stroke",
877
+ "outline",
878
+ "divider"
879
+ ],
880
+ COLOR_KINDS
881
+ ],
882
+ [
883
+ "radius",
884
+ [
885
+ "radius",
886
+ "corner",
887
+ "rounded"
888
+ ],
889
+ SIZE_KINDS
890
+ ],
891
+ [
892
+ "spacing",
893
+ [
894
+ "spacing",
895
+ "space",
896
+ "gap",
897
+ "unit"
898
+ ],
899
+ SIZE_KINDS
900
+ ],
901
+ [
902
+ "font-sans",
903
+ [
904
+ "font-sans",
905
+ "font-family",
906
+ "sans",
907
+ "body-font",
908
+ "font-body"
909
+ ],
910
+ FONT_KINDS
911
+ ],
912
+ [
913
+ "font-heading",
914
+ [
915
+ "font-heading",
916
+ "heading-font",
917
+ "display"
918
+ ],
919
+ FONT_KINDS
920
+ ],
921
+ [
922
+ "heading-weight",
923
+ ["heading-weight", "weight-bold"],
924
+ ["number", "keyword"]
925
+ ],
926
+ [
927
+ "heading-tracking",
928
+ [
929
+ "heading-tracking",
930
+ "tracking",
931
+ "letter-spacing"
932
+ ],
933
+ SIZE_KINDS
934
+ ]
935
+ ];
936
+ new Map(ROLES.map(([role, , kinds]) => [role, kinds]));
937
+ var aliasTarget = (token) => /^var\((--[\w-]+)\)$/.exec(token.value)?.[1];
938
+ function withoutDanglingAliases(tokens) {
939
+ const byName = new Map(tokens.map((t) => [t.name, t]));
940
+ const reachesValue = (token) => {
941
+ const seen = new Set();
942
+ for (let at = token; at !== void 0; at = byName.get(aliasTarget(at) ?? "")) {
943
+ if (aliasTarget(at) === void 0) return true;
944
+ if (seen.has(at.name)) return false;
945
+ seen.add(at.name);
946
+ }
947
+ return false;
948
+ };
949
+ return tokens.filter(reachesValue);
950
+ }
951
+ new Set(ROLES.map(([role]) => `--${role}`));
952
+ function isFigmaExtract(answer) {
953
+ if (typeof answer !== "object" || answer === null) return false;
954
+ const given = answer;
955
+ return Array.isArray(given["tokens"]) && given["tokens"].every((t) => typeof t === "object" && t !== null && typeof t.name === "string" && t.name.startsWith("--") && typeof t.value === "string") && typeof given["origin"] === "string" && typeof given["omitted"] === "number";
956
+ }
957
+ var samples = [
958
+ {
959
+ name: "Apple",
960
+ reference: "https://www.apple.com/",
961
+ colors: [
962
+ "#f5f5f7",
963
+ "#1d1d1f",
964
+ "#ffffff",
965
+ "#e8e8ed",
966
+ "#65656b",
967
+ "#0071e3",
968
+ "#ffffff",
969
+ "#d2d2d7"
970
+ ],
971
+ radius: "20px",
972
+ spacing: "20px",
973
+ font: "system-ui, -apple-system, sans-serif",
974
+ heading: "system-ui, -apple-system, sans-serif",
975
+ weight: "600",
976
+ tracking: "-.035em"
977
+ },
978
+ {
979
+ name: "BMW",
980
+ reference: "https://www.bmw.com/",
981
+ colors: [
982
+ "#f2f4f7",
983
+ "#17191c",
984
+ "#ffffff",
985
+ "#e4e9f0",
986
+ "#5c6470",
987
+ "#1c69d4",
988
+ "#ffffff",
989
+ "#cbd2dc"
990
+ ],
991
+ radius: "2px",
992
+ spacing: "20px",
993
+ font: "system-ui, -apple-system, sans-serif",
994
+ heading: "Arial, Helvetica, sans-serif",
995
+ weight: "600",
996
+ tracking: "-.025em"
997
+ },
998
+ {
999
+ name: "Spotify",
1000
+ reference: "https://open.spotify.com/",
1001
+ colors: [
1002
+ "#121212",
1003
+ "#ffffff",
1004
+ "#181818",
1005
+ "#282828",
1006
+ "#b3b3b3",
1007
+ "#1ed760",
1008
+ "#07150c",
1009
+ "#3e3e3e"
1010
+ ],
1011
+ radius: "24px",
1012
+ spacing: "16px",
1013
+ font: "system-ui, -apple-system, sans-serif",
1014
+ heading: "system-ui, sans-serif",
1015
+ weight: "800",
1016
+ tracking: "-.04em"
1017
+ },
1018
+ {
1019
+ name: "Airbnb",
1020
+ reference: "https://www.airbnb.com/",
1021
+ colors: [
1022
+ "#fff9f6",
1023
+ "#222222",
1024
+ "#ffffff",
1025
+ "#f9e6e8",
1026
+ "#71615f",
1027
+ "#d90b46",
1028
+ "#ffffff",
1029
+ "#e8d5d4"
1030
+ ],
1031
+ radius: "16px",
1032
+ spacing: "20px",
1033
+ font: "system-ui, -apple-system, sans-serif",
1034
+ heading: "system-ui, sans-serif",
1035
+ weight: "600",
1036
+ tracking: "-.025em"
1037
+ },
1038
+ {
1039
+ name: "Nike",
1040
+ reference: "https://www.nike.com/",
1041
+ colors: [
1042
+ "#ffffff",
1043
+ "#111111",
1044
+ "#f5f5f5",
1045
+ "#e8e8e8",
1046
+ "#666666",
1047
+ "#111111",
1048
+ "#ffffff",
1049
+ "#d1d1d1"
1050
+ ],
1051
+ radius: "2px",
1052
+ spacing: "20px",
1053
+ font: "system-ui, -apple-system, sans-serif",
1054
+ heading: "\"Arial Black\", Arial, sans-serif",
1055
+ weight: "900",
1056
+ tracking: "-.055em"
1057
+ },
1058
+ {
1059
+ name: "Stripe",
1060
+ reference: "https://stripe.com/",
1061
+ colors: [
1062
+ "#f6f9fc",
1063
+ "#0a2540",
1064
+ "#ffffff",
1065
+ "#e6edff",
1066
+ "#425466",
1067
+ "#635bff",
1068
+ "#ffffff",
1069
+ "#d5deea"
1070
+ ],
1071
+ radius: "8px",
1072
+ spacing: "20px",
1073
+ font: "system-ui, -apple-system, sans-serif",
1074
+ heading: "system-ui, sans-serif",
1075
+ weight: "650",
1076
+ tracking: "-.04em"
1077
+ },
1078
+ {
1079
+ name: "Linear",
1080
+ reference: "https://linear.app/",
1081
+ colors: [
1082
+ "#101114",
1083
+ "#f0f1f5",
1084
+ "#191a20",
1085
+ "#262831",
1086
+ "#a4a7b5",
1087
+ "#b5b7ff",
1088
+ "#171731",
1089
+ "#353743"
1090
+ ],
1091
+ radius: "8px",
1092
+ spacing: "12px",
1093
+ font: "system-ui, -apple-system, sans-serif",
1094
+ heading: "system-ui, sans-serif",
1095
+ weight: "500",
1096
+ tracking: "-.035em"
1097
+ },
1098
+ {
1099
+ name: "Notion",
1100
+ reference: "https://www.notion.com/",
1101
+ colors: [
1102
+ "#fffefb",
1103
+ "#2f2d29",
1104
+ "#ffffff",
1105
+ "#f0eee9",
1106
+ "#706c65",
1107
+ "#37352f",
1108
+ "#ffffff",
1109
+ "#dfddd7"
1110
+ ],
1111
+ radius: "4px",
1112
+ spacing: "16px",
1113
+ font: "system-ui, -apple-system, sans-serif",
1114
+ heading: "Georgia, \"Times New Roman\", serif",
1115
+ weight: "500",
1116
+ tracking: "-.025em"
1117
+ },
1118
+ {
1119
+ name: "IKEA",
1120
+ reference: "https://www.ikea.com/",
1121
+ colors: [
1122
+ "#ffffff",
1123
+ "#111111",
1124
+ "#f5f5f5",
1125
+ "#fff3b0",
1126
+ "#626262",
1127
+ "#ffda1a",
1128
+ "#003e7d",
1129
+ "#d6d6d6"
1130
+ ],
1131
+ radius: "24px",
1132
+ spacing: "20px",
1133
+ font: "system-ui, -apple-system, sans-serif",
1134
+ heading: "Arial, Helvetica, sans-serif",
1135
+ weight: "700",
1136
+ tracking: "-.025em"
1137
+ },
1138
+ {
1139
+ name: "Ferrari",
1140
+ reference: "https://www.ferrari.com/",
1141
+ colors: [
1142
+ "#181818",
1143
+ "#faf7f2",
1144
+ "#242424",
1145
+ "#35312e",
1146
+ "#b9b2aa",
1147
+ "#e32119",
1148
+ "#ffffff",
1149
+ "#49433e"
1150
+ ],
1151
+ radius: "0px",
1152
+ spacing: "24px",
1153
+ font: "system-ui, -apple-system, sans-serif",
1154
+ heading: "\"Arial Narrow\", Arial, sans-serif",
1155
+ weight: "700",
1156
+ tracking: "-.03em"
1157
+ }
1158
+ ];
1159
+ function sampleTokens(index) {
1160
+ const sample = samples[index];
1161
+ return [
1162
+ ...[
1163
+ "background",
1164
+ "foreground",
1165
+ "card",
1166
+ "muted",
1167
+ "muted-foreground",
1168
+ "primary",
1169
+ "primary-foreground",
1170
+ "border"
1171
+ ].map((name, i) => ({
1172
+ name: `--${name}`,
1173
+ value: sample.colors[i]
1174
+ })),
1175
+ {
1176
+ name: "--radius",
1177
+ value: sample.radius
1178
+ },
1179
+ {
1180
+ name: "--font-sans",
1181
+ value: sample.font
1182
+ },
1183
+ {
1184
+ name: "--spacing",
1185
+ value: sample.spacing
1186
+ },
1187
+ {
1188
+ name: "--font-heading",
1189
+ value: sample.heading
1190
+ },
1191
+ {
1192
+ name: "--heading-weight",
1193
+ value: sample.weight
1194
+ },
1195
+ {
1196
+ name: "--heading-tracking",
1197
+ value: sample.tracking
1198
+ }
1199
+ ];
1200
+ }
1201
+ var _tmpl$$1 = template(`<button type=button>From Figma`);
1202
+ var _tmpl$2$1 = template(`<section aria-label="Theme starting point"><div><span>Presets</span><div><button type=button aria-label="Previous presets">‹</button><button type=button aria-label="Next presets">›</button></div></div><div aria-label="Brand-inspired presets"role=group></div><div><button type=button>Extract from selection`);
1203
+ var _tmpl$3$1 = template(`<button type=button><span aria-hidden=true>`);
1204
+ var _tmpl$4 = template(`<i>`);
1205
+ function ThemeSources(props) {
1206
+ let track;
1207
+ const [atStart, setAtStart] = createSignal(true);
1208
+ const [atEnd, setAtEnd] = createSignal(false);
1209
+ const updateBounds = () => {
1210
+ setAtStart(track.scrollLeft <= 1);
1211
+ setAtEnd(track.scrollLeft + track.clientWidth >= track.scrollWidth - 1);
1212
+ };
1213
+ const move = (direction) => {
1214
+ track.scrollBy({ left: direction * (track.clientWidth + 6) });
1215
+ updateBounds();
1216
+ };
1217
+ onSettled(() => {
1218
+ const observer = new ResizeObserver(updateBounds);
1219
+ observer.observe(track);
1220
+ updateBounds();
1221
+ return () => observer.disconnect();
1222
+ });
1223
+ var _el$ = _tmpl$2$1();
1224
+ var _el$2 = _el$.firstChild;
1225
+ var _el$5 = _el$2.firstChild.nextSibling.firstChild;
1226
+ var _el$6 = _el$5.nextSibling;
1227
+ var _el$7 = _el$2.nextSibling;
1228
+ var _el$8 = _el$7.nextSibling;
1229
+ var _el$9 = _el$8.firstChild;
1230
+ _el$5._$$click = () => move(-1);
1231
+ _el$6._$$click = () => move(1);
1232
+ _el$7.addEventListener("scroll", updateBounds);
1233
+ var _ref$ = track;
1234
+ typeof _ref$ === "function" || Array.isArray(_ref$) ? ref(() => {
1235
+ return _ref$;
1236
+ }, _el$7) : track = _el$7;
1237
+ insert(_el$7, createComponent(For, {
1238
+ each: samples,
1239
+ children: (sample, index) => (() => {
1240
+ var _el$11 = _tmpl$3$1();
1241
+ var _el$12 = _el$11.firstChild;
1242
+ _el$11._$$click = () => {
1243
+ props.draft.load(sampleTokens(index()), `${sample.name} inspired`, sample.name);
1244
+ props.onPreset();
1245
+ };
1246
+ insert(_el$12, createComponent(For, {
1247
+ get each() {
1248
+ return [
1249
+ sample.colors[0],
1250
+ sample.colors[1],
1251
+ sample.colors[5]
1252
+ ];
1253
+ },
1254
+ children: (color) => (() => {
1255
+ var _el$13 = _tmpl$4();
1256
+ setStyleProperty(_el$13, "background-color", color);
1257
+ return _el$13;
1258
+ })()
1259
+ }));
1260
+ insert(_el$11, () => {
1261
+ return sample.name;
1262
+ }, null);
1263
+ effect(() => {
1264
+ return {
1265
+ e: sample.name,
1266
+ t: `${sample.name} inspired · unofficial interpretation`,
1267
+ a: props.draft.preset() === sample.name ? "true" : "false",
1268
+ o: `${props.prefix}-preset-colors`
1269
+ };
1270
+ }, ({ e, t, a, o }, _p$) => {
1271
+ e !== _p$?.e && setAttribute(_el$11, "aria-label", e);
1272
+ t !== _p$?.t && setAttribute(_el$11, "title", t);
1273
+ a !== _p$?.a && setAttribute(_el$11, "aria-pressed", a);
1274
+ className(_el$12, o, _p$?.o);
1275
+ });
1276
+ return _el$11;
1277
+ })()
1278
+ }));
1279
+ _el$9._$$click = () => props.onExtract();
1280
+ insert(_el$8, createComponent(Show, {
1281
+ get when() {
1282
+ return props.figma;
1283
+ },
1284
+ get children() {
1285
+ var _el$10 = _tmpl$$1();
1286
+ _el$10._$$click = () => props.onFigma();
1287
+ effect(() => props.busy, (_v$) => {
1288
+ setAttribute(_el$10, "disabled", _v$);
1289
+ });
1290
+ return _el$10;
1291
+ }
1292
+ }), null);
1293
+ effect(() => {
1294
+ return {
1295
+ e: `${props.prefix}-sources`,
1296
+ t: `${props.prefix}-preset-heading`,
1297
+ a: atStart(),
1298
+ o: atEnd(),
1299
+ i: `${props.prefix}-presets`,
1300
+ n: `${props.prefix}-extractors`,
1301
+ s: `${props.prefix}-extract`,
1302
+ h: !props.extractable || props.busy
1303
+ };
1304
+ }, ({ e, t, a, o, i, n, s, h }, _p$) => {
1305
+ className(_el$, e, _p$?.e);
1306
+ className(_el$2, t, _p$?.t);
1307
+ a !== _p$?.a && setAttribute(_el$5, "disabled", a);
1308
+ o !== _p$?.o && setAttribute(_el$6, "disabled", o);
1309
+ className(_el$7, i, _p$?.i);
1310
+ className(_el$8, n, _p$?.n);
1311
+ className(_el$9, s, _p$?.s);
1312
+ h !== _p$?.h && setAttribute(_el$9, "disabled", h);
1313
+ });
1314
+ return _el$;
1315
+ }
1316
+ delegateEvents(["click"]);
1317
+ var _tmpl$ = template(`<div><div><div><strong></strong><span></span></div><button type=button>Reset edits</button></div><div aria-label="Theme preview"><h3>Make it feel like you.</h3><p>A few small decisions.<br>A different point of view.</p><span>Start a collection</span><div aria-hidden=true></div></div><details><summary>Refine tokens · </summary><fieldset><legend>Edit a value, then press Enter</legend></fieldset></details><div><button type=button>Apply to selected viewports</button><button type=button></button></div><p role=status></p><p role=alert>`);
1318
+ var _tmpl$2 = template(`<span>`);
1319
+ var _tmpl$3 = template(`<label><span></span><input>`);
1320
+ function figmaSentence(cause) {
1321
+ const said = (cause instanceof Error ? cause.message : String(cause)).trim();
1322
+ if (!said) return "Figma could not answer.";
1323
+ return /[.!?]$/.test(said) ? said : `${said}.`;
1324
+ }
1325
+ function ThemePanel(props) {
1326
+ const p = untrack(() => props.prefix);
1327
+ const [status, setStatus] = createSignal("");
1328
+ const [error, setError] = createSignal("");
1329
+ const [busy, setBusy] = createSignal(false);
1330
+ const [extractable, setExtractable] = createSignal(0);
1331
+ const [canApply, setCanApply] = createSignal(false);
1332
+ const [figma, setFigma] = createSignal(false);
1333
+ let alive = true;
1334
+ onCleanup(() => {
1335
+ alive = false;
1336
+ });
1337
+ const probe = createProbe({
1338
+ ask: async () => await props.dd.hostPart.call("figma-reachable") === true,
1339
+ answer: (reachable) => setFigma(reachable),
1340
+ target: window
1341
+ });
1342
+ const projectHook = untrack(() => {
1343
+ probe.probe();
1344
+ return props.dd.on("project", () => probe.probe());
1345
+ });
1346
+ onCleanup(() => {
1347
+ probe.dispose();
1348
+ projectHook.dispose();
1349
+ });
1350
+ createEffect(() => [
1351
+ props.context.selection(),
1352
+ [...props.context.itemSelection()],
1353
+ props.dd.documentVersion(),
1354
+ props.dd.interactionVersion()
1355
+ ], () => untrack(() => {
1356
+ setExtractable(extractionViewports(props.dd, props.context).length);
1357
+ setCanApply(selectedPages(props.dd, props.context).length > 0);
1358
+ }));
1359
+ const report = (action) => {
1360
+ setError("");
1361
+ try {
1362
+ setStatus(action());
1363
+ } catch (cause) {
1364
+ setError(cause instanceof Error ? cause.message : String(cause));
1365
+ }
1366
+ };
1367
+ const edit = (token, input) => report(() => untrack(() => {
1368
+ try {
1369
+ const next = changedToken(token, input.value, props.dd.core);
1370
+ props.draft.edit(props.draft.tokens().map((t) => t.name === token.name ? next : t));
1371
+ return "Preview updated. Apply to write these tokens to the selected viewports.";
1372
+ } catch (cause) {
1373
+ input.value = token.value;
1374
+ throw cause;
1375
+ }
1376
+ }));
1377
+ const create = async () => {
1378
+ if (busy()) return;
1379
+ const version = props.dd.loadVersion();
1380
+ const html = specimen(props.draft.tokens());
1381
+ setBusy(true);
1382
+ setError("");
1383
+ try {
1384
+ const made = await props.dd.createPage(html, { frame: { width: 960 } });
1385
+ if (!alive || props.dd.loadVersion() !== version) return;
1386
+ props.dd.select(made.viewportId);
1387
+ props.dd.canvas.reveal([made.viewportId]);
1388
+ setStatus(`Created ${made.path}. Edit the preview, then Apply to update the canvas.`);
1389
+ } catch (cause) {
1390
+ if (alive && props.dd.loadVersion() === version) setError(cause instanceof Error ? cause.message : String(cause));
1391
+ } finally {
1392
+ if (alive) setBusy(false);
1393
+ }
1394
+ };
1395
+ const fromFigma = async () => {
1396
+ if (busy()) return;
1397
+ const version = props.dd.loadVersion();
1398
+ setBusy(true);
1399
+ setError("");
1400
+ try {
1401
+ const answer = await props.dd.hostPart.call("figma-extract");
1402
+ if (!alive || props.dd.loadVersion() !== version) return;
1403
+ if (!isFigmaExtract(answer)) throw new Error("The host part answered no tokens.");
1404
+ const tokens = [];
1405
+ for (const token of answer.tokens) try {
1406
+ tokens.push(...readTokens(tokenCss([token]), props.dd.core));
1407
+ } catch {}
1408
+ const left = answer.omitted + answer.tokens.length - tokens.length;
1409
+ const resolved = withoutDanglingAliases(tokens);
1410
+ if (!resolved.length) throw new Error("No usable variables in the Figma selection.");
1411
+ props.draft.load(resolved, answer.origin);
1412
+ setStatus(`Extracted ${resolved.length} tokens from Figma${left > 0 ? `, ${left} left out` : ""}. Ready to refine.`);
1413
+ } catch (cause) {
1414
+ if (alive && props.dd.loadVersion() === version) setError(figmaSentence(cause));
1415
+ } finally {
1416
+ if (alive) setBusy(false);
1417
+ }
1418
+ };
1419
+ const extract = () => report(() => untrack(() => {
1420
+ const result = extractTheme(props.dd, props.context);
1421
+ props.draft.load(result.tokens, `Extracted from ${result.pages} page${result.pages === 1 ? "" : "s"}`);
1422
+ return `Extracted ${result.tokens.length} tokens. Ready to refine.`;
1423
+ }));
1424
+ var _el$ = _tmpl$();
1425
+ var _el$2 = _el$.firstChild;
1426
+ var _el$3 = _el$2.firstChild;
1427
+ var _el$4 = _el$3.firstChild;
1428
+ var _el$5 = _el$4.nextSibling;
1429
+ var _el$6 = _el$3.nextSibling;
1430
+ var _el$7 = _el$2.nextSibling;
1431
+ var _el$10 = _el$7.firstChild.nextSibling.nextSibling;
1432
+ var _el$11 = _el$10.nextSibling;
1433
+ var _el$12 = _el$7.nextSibling;
1434
+ var _el$13 = _el$12.firstChild;
1435
+ _el$13.firstChild;
1436
+ var _el$15 = _el$13.nextSibling;
1437
+ var _el$16 = _el$15.firstChild;
1438
+ var _el$17 = _el$12.nextSibling;
1439
+ var _el$18 = _el$17.firstChild;
1440
+ var _el$19 = _el$18.nextSibling;
1441
+ var _el$20 = _el$17.nextSibling;
1442
+ var _el$21 = _el$20.nextSibling;
1443
+ insert(_el$, createComponent(ThemeSources, {
1444
+ prefix: p,
1445
+ get draft() {
1446
+ return props.draft;
1447
+ },
1448
+ get extractable() {
1449
+ return extractable();
1450
+ },
1451
+ get figma() {
1452
+ return figma();
1453
+ },
1454
+ get busy() {
1455
+ return busy();
1456
+ },
1457
+ onExtract: extract,
1458
+ onFigma: () => void fromFigma(),
1459
+ onPreset: () => {
1460
+ setError("");
1461
+ setStatus("Preset loaded into the preview. Your pages are unchanged.");
1462
+ }
1463
+ }), _el$2);
1464
+ insert(_el$4, () => {
1465
+ return props.draft.origin();
1466
+ });
1467
+ insert(_el$5, () => {
1468
+ return props.draft.modified() ? "Edited preview" : "Preview only";
1469
+ });
1470
+ _el$6._$$click = () => {
1471
+ props.draft.reset();
1472
+ setError("");
1473
+ setStatus("Preview edits reset. Your pages are unchanged.");
1474
+ };
1475
+ insert(_el$11, createComponent(For, {
1476
+ each: [
1477
+ "primary",
1478
+ "foreground",
1479
+ "muted",
1480
+ "card"
1481
+ ],
1482
+ children: (name) => (() => {
1483
+ var _el$22 = _tmpl$2();
1484
+ setStyleProperty(_el$22, "background-color", `var(--${name})`);
1485
+ effect(() => `${p}-swatch`, (_v$, _$p) => {
1486
+ className(_el$22, _v$, _$p);
1487
+ });
1488
+ return _el$22;
1489
+ })()
1490
+ }));
1491
+ insert(_el$13, () => {
1492
+ return props.draft.tokens().length;
1493
+ }, null);
1494
+ insert(_el$15, createComponent(For, {
1495
+ get each() {
1496
+ return props.draft.tokens();
1497
+ },
1498
+ children: (token) => (() => {
1499
+ var _el$23 = _tmpl$3();
1500
+ var _el$24 = _el$23.firstChild;
1501
+ var _el$25 = _el$24.nextSibling;
1502
+ insert(_el$24, () => {
1503
+ return token.name;
1504
+ });
1505
+ _el$25._$$keydown = (event) => {
1506
+ if (event.key === "Enter") event.currentTarget.blur();
1507
+ };
1508
+ _el$25.addEventListener("change", (event) => edit(token, event.currentTarget));
1509
+ effect(() => {
1510
+ return {
1511
+ e: `${p}-token`,
1512
+ t: token.value
1513
+ };
1514
+ }, ({ e, t }, _p$) => {
1515
+ className(_el$23, e, _p$?.e);
1516
+ _el$25.value = t ?? "";
1517
+ });
1518
+ return _el$23;
1519
+ })()
1520
+ }), null);
1521
+ _el$18._$$click = () => report(() => untrack(() => `Applied to ${applyTokens(props.dd, props.context, props.draft.tokens())}. Undo is available in Daydream.`));
1522
+ _el$19._$$click = () => void create();
1523
+ insert(_el$19, () => {
1524
+ return busy() ? "Creating specimen…" : "Create specimen page";
1525
+ });
1526
+ insert(_el$20, status);
1527
+ insert(_el$21, error);
1528
+ effect(() => {
1529
+ return {
1530
+ e: readShallow(p),
1531
+ t: `${p}-preview-heading`,
1532
+ a: `${p}-note`,
1533
+ o: !props.draft.modified(),
1534
+ i: `${p}-preview`,
1535
+ n: readShallow(Object.fromEntries(props.draft.tokens().map((t) => [t.name, t.value]))),
1536
+ s: `${p}-sample`,
1537
+ h: `${p}-swatches`,
1538
+ r: `${p}-note`,
1539
+ d: `${p}-actions`,
1540
+ l: `${p}-primary`,
1541
+ u: !canApply() || busy(),
1542
+ c: busy(),
1543
+ w: `${p}-status`,
1544
+ m: `${p}-status ${p}-error`
1545
+ };
1546
+ }, ({ e, t, a, o, i, n, s, h, r, d, l, u, c, w, m }, _p$) => {
1547
+ className(_el$, e, _p$?.e);
1548
+ className(_el$2, t, _p$?.t);
1549
+ className(_el$5, a, _p$?.a);
1550
+ o !== _p$?.o && setAttribute(_el$6, "disabled", o);
1551
+ className(_el$7, i, _p$?.i);
1552
+ style(_el$7, n, _p$?.n);
1553
+ className(_el$10, s, _p$?.s);
1554
+ className(_el$11, h, _p$?.h);
1555
+ className(_el$16, r, _p$?.r);
1556
+ className(_el$17, d, _p$?.d);
1557
+ className(_el$18, l, _p$?.l);
1558
+ u !== _p$?.u && setAttribute(_el$18, "disabled", u);
1559
+ c !== _p$?.c && setAttribute(_el$19, "disabled", c);
1560
+ className(_el$20, w, _p$?.w);
1561
+ className(_el$21, m, _p$?.m);
1562
+ });
1563
+ return _el$;
1564
+ }
1565
+ delegateEvents(["click", "keydown"]);
1566
+ function createThemeDraft() {
1567
+ const initial = () => ({
1568
+ tokens: sampleTokens(0),
1569
+ origin: `${samples[0].name} inspired`,
1570
+ preset: samples[0].name
1571
+ });
1572
+ const [base, setBase] = createSignal(initial());
1573
+ const [edits, setEdits] = createSignal(null);
1574
+ return {
1575
+ tokens: () => edits() ?? base().tokens,
1576
+ origin: () => base().origin,
1577
+ preset: () => edits() ? "" : base().preset,
1578
+ modified: () => edits() !== null,
1579
+ edit: (tokens) => setEdits(tokens),
1580
+ reset: () => setEdits(null),
1581
+ load: (tokens, origin, preset = "") => {
1582
+ setBase({
1583
+ tokens,
1584
+ origin,
1585
+ preset
1586
+ });
1587
+ setEdits(null);
1588
+ },
1589
+ clear: () => {
1590
+ setBase(initial());
1591
+ setEdits(null);
1592
+ }
1593
+ };
1594
+ }
1595
+ var css = (p) => `
1596
+ .${p} { padding: 6px 14px 14px; color: var(--dream-app-bright, #c8c8ce); font-size: 12px; line-height: 1.5; overflow: auto; min-height: 0; }
1597
+ .${p} * { box-sizing: border-box; }
1598
+ .${p} p { margin: 0 0 12px; }
1599
+ .${p} .${p}-note { color: var(--dream-app-dim, #9a9aa0); font-size: 11px; overflow-wrap: anywhere; }
1600
+ .${p} button, .${p} input { font: inherit; color: inherit; background: var(--dream-app-field, #26262b); border: 1px solid var(--dream-app-line-strong, #4a4a52); border-radius: 4px; }
1601
+ .${p} button { padding: 5px 9px; cursor: pointer; }
1602
+ .${p} button:hover:not(:disabled) { background: var(--dream-app-hover, #36363d); }
1603
+ .${p} button:disabled { opacity: .5; cursor: default; }
1604
+ .${p} :focus-visible { outline: 2px solid var(--dream-app-accent, #8ab9ff); outline-offset: 2px; }
1605
+ .${p}-sources { border-bottom: 1px solid var(--dream-app-line, #3a3a41); margin-bottom: 14px; }
1606
+ .${p}-extractors { display: flex; gap: 6px; margin-bottom: 14px; }
1607
+ .${p}-extractors button { flex: 1 1 0; min-width: 0; }
1608
+ .${p}-preset-heading { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
1609
+ .${p}-preset-heading > div { display: flex; gap: 4px; }
1610
+ .${p}-preset-heading button { width: 26px; padding: 0; font-size: 20px; line-height: 24px; }
1611
+ .${p}-presets { display: flex; gap: 6px; margin-bottom: 8px; padding: 3px 0 6px; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: thin; scrollbar-color: var(--dream-app-muted, #55555f) transparent; }
1612
+ .${p}-presets button { flex: 0 0 calc((100% - 12px) / 3); min-width: 0; scroll-snap-align: start; }
1613
+ .${p}-presets button:focus-visible { outline-offset: -3px; }
1614
+ .${p}-presets button[aria-pressed=true] { border-color: var(--dream-app-text, #b8b8c0); background: var(--dream-app-hover, #37373e); }
1615
+ .${p}-preset-colors { display: flex; justify-content: center; margin: 3px 0 6px; }
1616
+ .${p}-preset-colors i { display: block; width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--dream-app-line, #ffffff30); }
1617
+ .${p}-preset-colors i + i { margin-left: -4px; }
1618
+ .${p}-preview-heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
1619
+ .${p}-preview-heading strong { display: block; font-weight: 600; }
1620
+ .${p}-preview-heading button { flex-shrink: 0; font-size: 11px; }
1621
+ .${p}-preview { padding: 20px; margin-bottom: 16px; border-radius: 6px; background-color: var(--background, #f6f3ec); color: var(--foreground, #242a25); font-family: var(--font-sans, system-ui); }
1622
+ .${p}-preview h3 { font-size: 24px; font-family: var(--font-heading, var(--font-sans, system-ui)); font-weight: var(--heading-weight, 500); letter-spacing: var(--heading-tracking, normal); line-height: 1.2; margin: 0 0 10px; }
1623
+ .${p}-preview p { color: var(--muted-foreground, #62685f); }
1624
+ .${p}-preview .${p}-sample { display: inline-block; padding: 8px 12px; background-color: var(--primary, #355c48); color: var(--primary-foreground, white); border-radius: var(--radius, 8px); }
1625
+ .${p}-swatches { display: flex; gap: 5px; margin-top: 16px; }
1626
+ .${p}-swatch { width: 22px; height: 22px; border-radius: 50%; border: 1px solid var(--border, #d5d7cc); }
1627
+ .${p} fieldset { border: 0; margin: 0; padding: 0; }
1628
+ .${p} legend { font-weight: 600; margin-bottom: 8px; }
1629
+ .${p}-token { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; gap: 8px; margin-bottom: 7px; }
1630
+ .${p}-token span { overflow-wrap: anywhere; font-size: 11px; font-family: var(--dream-app-mono, monospace); }
1631
+ .${p}-token input { width: 100%; min-width: 0; padding: 5px 6px; font-size: 11px; font-family: var(--dream-app-mono, monospace); }
1632
+ .${p} details { margin-top: 14px; }
1633
+ .${p} summary { cursor: pointer; margin-bottom: 8px; }
1634
+ .${p}-actions { display: grid; gap: 8px; margin-top: 14px; border-top: 1px solid var(--dream-app-line, #3a3a41); padding-top: 14px; }
1635
+ .${p}-actions .${p}-primary { background: var(--dream-app-accent-fill, #d8dbe0); color: var(--dream-app-on-accent, #202126); border-color: var(--dream-app-accent-fill, #d8dbe0); }
1636
+ .${p}-actions .${p}-primary:hover:not(:disabled) { background: var(--dream-app-accent-fill, #eef0f3); filter: brightness(1.08); }
1637
+ .${p} .${p}-status { margin: 10px 0 0; font-size: 11px; overflow-wrap: anywhere; }
1638
+ .${p} .${p}-error { color: var(--dream-app-danger, #ffb7a9); }
1639
+ `;
1640
+ function activate(dd) {
1641
+ const draft = createThemeDraft();
1642
+ dd.on("project", draft.clear);
1643
+ const prefix = dd.plugin.id.replace(/[^A-Za-z0-9_-]/g, "-");
1644
+ dd.registerPanel({
1645
+ id: "tokens",
1646
+ title: "Tokens",
1647
+ ariaLabel: "Tokens",
1648
+ styles: css(prefix),
1649
+ grow: 1,
1650
+ render: (context) => createComponent(ThemePanel, {
1651
+ dd,
1652
+ context,
1653
+ prefix,
1654
+ draft
1655
+ })
1656
+ });
1657
+ }
1658
+ export { activate as default };