twiddle-panel 0.6.0 → 0.7.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/README.md +6 -2
- package/dist/twiddle-tokens.css +18 -1
- package/dist/twiddle.js +245 -135
- package/dist/twiddle.mjs +1770 -1279
- package/package.json +1 -1
package/dist/twiddle.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const
|
|
2
|
-
let
|
|
3
|
-
function
|
|
1
|
+
const pt = /* @__PURE__ */ new Set();
|
|
2
|
+
let T = Xn(), X = [], ve = [], Ie = null, Xt = 0;
|
|
3
|
+
function Xn() {
|
|
4
4
|
const t = new URLSearchParams(location.search).get("twd");
|
|
5
5
|
if (!t) return {};
|
|
6
6
|
try {
|
|
@@ -9,140 +9,140 @@ function En() {
|
|
|
9
9
|
return {};
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
function
|
|
12
|
+
function Jn() {
|
|
13
13
|
const t = new URL(location.href);
|
|
14
|
-
Object.keys(
|
|
14
|
+
Object.keys(T).length ? t.searchParams.set("twd", JSON.stringify(T)) : t.searchParams.delete("twd"), history.replaceState(history.state, "", t);
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
16
|
+
function yt() {
|
|
17
|
+
Jn(), pt.forEach((t) => t(T));
|
|
18
18
|
}
|
|
19
|
-
function
|
|
20
|
-
if (JSON.stringify(t) === JSON.stringify(
|
|
19
|
+
function se(t, e) {
|
|
20
|
+
if (JSON.stringify(t) === JSON.stringify(T)) return;
|
|
21
21
|
const n = Date.now();
|
|
22
|
-
e && e ===
|
|
22
|
+
e && e === Ie && n - Xt < 400 || (X.push(T), X.length > 100 && X.shift()), ve = [], Ie = e, Xt = n, T = t, yt();
|
|
23
23
|
}
|
|
24
|
-
function
|
|
24
|
+
function Be(t, e) {
|
|
25
25
|
const n = { ...t, ...e };
|
|
26
|
-
return Object.keys(n).forEach((
|
|
27
|
-
n[
|
|
26
|
+
return Object.keys(n).forEach((a) => {
|
|
27
|
+
n[a] == null && delete n[a];
|
|
28
28
|
}), Object.keys(n).length ? n : null;
|
|
29
29
|
}
|
|
30
|
-
function
|
|
31
|
-
const
|
|
32
|
-
return n == null ? delete
|
|
30
|
+
function U(t, e, n) {
|
|
31
|
+
const a = { ...t };
|
|
32
|
+
return n == null ? delete a[e] : a[e] = n, a;
|
|
33
33
|
}
|
|
34
|
-
function
|
|
35
|
-
return JSON.parse(JSON.stringify(
|
|
34
|
+
function ut() {
|
|
35
|
+
return JSON.parse(JSON.stringify(T));
|
|
36
36
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
37
|
+
function Qn(t, e) {
|
|
38
|
+
se(U(T, t, Be(T[t], e)), `${t}:${Object.keys(e)}`);
|
|
39
39
|
}
|
|
40
|
-
function
|
|
41
|
-
const n = Object.entries(t).reduce((
|
|
42
|
-
|
|
40
|
+
function ea(t, e = `multi:${Object.keys(t)}`) {
|
|
41
|
+
const n = Object.entries(t).reduce((a, [o, r]) => U(a, o, Be(a[o], r)), T);
|
|
42
|
+
se(n, e);
|
|
43
43
|
}
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
|
|
44
|
+
function vn(t, e, n) {
|
|
45
|
+
const a = U(T.scopes?.[t], e, Be(T.scopes?.[t]?.[e], n)), o = U(T.scopes, t, Object.keys(a).length ? a : null);
|
|
46
|
+
se(U(T, "scopes", Object.keys(o).length ? o : null), `${t}|${e}:${Object.keys(n)}`);
|
|
47
47
|
}
|
|
48
|
-
function
|
|
49
|
-
if (e) return
|
|
50
|
-
Object.keys(
|
|
48
|
+
function ta(t, e) {
|
|
49
|
+
if (e) return vn(t, e, Object.fromEntries(
|
|
50
|
+
Object.keys(T.scopes?.[t]?.[e] || {}).map((a) => [a, null])
|
|
51
51
|
));
|
|
52
|
-
const n =
|
|
53
|
-
|
|
52
|
+
const n = U(T.scopes, t, null);
|
|
53
|
+
se(U(T, "scopes", Object.keys(n).length ? n : null));
|
|
54
54
|
}
|
|
55
|
-
function
|
|
56
|
-
const n =
|
|
55
|
+
function na(t, e) {
|
|
56
|
+
const n = T.scopes?.[t];
|
|
57
57
|
if (!n || t === e) return;
|
|
58
|
-
let
|
|
59
|
-
Object.entries(n).forEach(([
|
|
60
|
-
|
|
58
|
+
let a = { ...T.scopes[e] };
|
|
59
|
+
Object.entries(n).forEach(([r, s]) => {
|
|
60
|
+
a = U(a, r, Be(a[r], s));
|
|
61
61
|
});
|
|
62
|
-
const
|
|
63
|
-
|
|
62
|
+
const o = U(U(T.scopes, t, null), e, a);
|
|
63
|
+
se(U(T, "scopes", o));
|
|
64
64
|
}
|
|
65
|
-
function
|
|
66
|
-
|
|
65
|
+
function yn() {
|
|
66
|
+
se({});
|
|
67
67
|
}
|
|
68
|
-
function
|
|
69
|
-
|
|
68
|
+
function kn() {
|
|
69
|
+
X.length && (ve.push(T), T = X.pop(), Ie = null, yt());
|
|
70
70
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
71
|
+
function Sn() {
|
|
72
|
+
ve.length && (X.push(T), T = ve.pop(), Ie = null, yt());
|
|
73
73
|
}
|
|
74
|
-
const
|
|
75
|
-
function
|
|
76
|
-
return
|
|
74
|
+
const aa = () => X.length > 0, oa = () => ve.length > 0;
|
|
75
|
+
function $n(t) {
|
|
76
|
+
return pt.add(t), () => pt.delete(t);
|
|
77
77
|
}
|
|
78
|
-
const
|
|
78
|
+
const ra = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
79
79
|
__proto__: null,
|
|
80
|
-
canRedo:
|
|
81
|
-
canUndo:
|
|
82
|
-
getState:
|
|
83
|
-
moveScope:
|
|
84
|
-
onChange:
|
|
85
|
-
redo:
|
|
86
|
-
removeScope:
|
|
87
|
-
resetState:
|
|
88
|
-
setScope:
|
|
89
|
-
setSection:
|
|
90
|
-
setSections:
|
|
91
|
-
undo:
|
|
92
|
-
}, Symbol.toStringTag, { value: "Module" })),
|
|
93
|
-
let
|
|
94
|
-
function
|
|
95
|
-
typeof t == "function" &&
|
|
96
|
-
}
|
|
97
|
-
const
|
|
80
|
+
canRedo: oa,
|
|
81
|
+
canUndo: aa,
|
|
82
|
+
getState: ut,
|
|
83
|
+
moveScope: na,
|
|
84
|
+
onChange: $n,
|
|
85
|
+
redo: Sn,
|
|
86
|
+
removeScope: ta,
|
|
87
|
+
resetState: yn,
|
|
88
|
+
setScope: vn,
|
|
89
|
+
setSection: Qn,
|
|
90
|
+
setSections: ea,
|
|
91
|
+
undo: kn
|
|
92
|
+
}, Symbol.toStringTag, { value: "Module" })), En = [];
|
|
93
|
+
let ht = [], kt = {}, R = null, oe = !1, Cn = {};
|
|
94
|
+
function sa(t) {
|
|
95
|
+
typeof t == "function" && En.push(t);
|
|
96
|
+
}
|
|
97
|
+
const Jt = (t) => Object.assign({}, ...En.map((e) => e(t) || {})), ft = (t) => /^(\.[\w-]+|\[data-twd="[^"]+"\])$/.test(t), ia = (t) => {
|
|
98
98
|
try {
|
|
99
99
|
return document.querySelector(t), !0;
|
|
100
100
|
} catch {
|
|
101
101
|
return !1;
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
|
-
function
|
|
104
|
+
function la() {
|
|
105
105
|
return (!R || !R.isConnected) && (R = document.createElement("style"), R.id = "twiddle-scoped", document.head.appendChild(R)), R;
|
|
106
106
|
}
|
|
107
|
-
function
|
|
107
|
+
function Tn() {
|
|
108
108
|
const t = document.documentElement;
|
|
109
|
-
|
|
109
|
+
ht.forEach((e) => t.style.removeProperty(e)), ht = [], kt = {};
|
|
110
110
|
}
|
|
111
|
-
function
|
|
112
|
-
if (
|
|
111
|
+
function gt(t) {
|
|
112
|
+
if (Cn = t, oe) return;
|
|
113
113
|
const e = document.documentElement;
|
|
114
|
-
|
|
115
|
-
const n =
|
|
116
|
-
Object.entries(n).forEach(([
|
|
117
|
-
const
|
|
118
|
-
${Object.entries(
|
|
114
|
+
Tn();
|
|
115
|
+
const n = Jt(t), a = Object.entries(t.scopes || {}).filter(([r]) => ia(r)).sort(([r], [s]) => ft(s) - ft(r)).map(([r, s]) => [r, Jt(s)]);
|
|
116
|
+
Object.entries(n).forEach(([r, s]) => e.style.setProperty(r, s)), ht = Object.keys(n), kt = n;
|
|
117
|
+
const o = a.filter(([, r]) => Object.keys(r).length).map(([r, s]) => `${r} {
|
|
118
|
+
${Object.entries(s).map(([i, l]) => ` ${i}: ${l} !important;`).join(`
|
|
119
119
|
`)}
|
|
120
120
|
}`).join(`
|
|
121
121
|
`);
|
|
122
|
-
(
|
|
122
|
+
(o || R) && (la().textContent = o);
|
|
123
123
|
}
|
|
124
|
-
function
|
|
125
|
-
const e = document.documentElement, n =
|
|
126
|
-
Object.keys(
|
|
124
|
+
function Mn(t) {
|
|
125
|
+
const e = document.documentElement, n = oe, a = { ...kt };
|
|
126
|
+
Object.keys(a).forEach((o) => e.style.removeProperty(o)), R && (R.disabled = !0);
|
|
127
127
|
try {
|
|
128
128
|
return t();
|
|
129
129
|
} finally {
|
|
130
|
-
n || (Object.entries(
|
|
130
|
+
n || (Object.entries(a).forEach(([o, r]) => e.style.setProperty(o, r)), R && (R.disabled = !1));
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
function
|
|
134
|
-
return
|
|
133
|
+
function da(t, e = document.documentElement) {
|
|
134
|
+
return Mn(() => {
|
|
135
135
|
const n = getComputedStyle(e);
|
|
136
|
-
return Object.fromEntries(t.map((
|
|
136
|
+
return Object.fromEntries(t.map((a) => [a, n.getPropertyValue(a).trim()]));
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
139
|
+
function ca() {
|
|
140
|
+
oe || (Tn(), R && (R.disabled = !0), oe = !0);
|
|
141
141
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
142
|
+
function pa() {
|
|
143
|
+
oe && (oe = !1, R && (R.disabled = !1), gt(Cn));
|
|
144
144
|
}
|
|
145
|
-
const
|
|
145
|
+
const ua = `
|
|
146
146
|
:host { all: initial; } /* block fonts/colours inherited from the page */
|
|
147
147
|
* { box-sizing: border-box; }
|
|
148
148
|
|
|
@@ -168,44 +168,55 @@ const Dn = `
|
|
|
168
168
|
align-items: flex-end;
|
|
169
169
|
gap: 10px;
|
|
170
170
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
171
|
+
/* Dark is the panel's default look (Ravi, 2026-10-15): these override the
|
|
172
|
+
light values above, whatever the page or system setting. */
|
|
173
|
+
.wrap {
|
|
174
|
+
--p-bg: #1c1c1f;
|
|
175
|
+
--p-fg: #f4f4f5;
|
|
176
|
+
--p-muted: #a1a1aa;
|
|
177
|
+
--p-line: #2e2e33;
|
|
178
|
+
--p-hover: #27272a;
|
|
179
|
+
--p-track: #27272a;
|
|
180
|
+
--p-selected: #3f3f46;
|
|
181
|
+
--p-accent: #f4f4f5;
|
|
182
|
+
--p-on-accent: #18181b;
|
|
183
|
+
--p-shadow: 0 12px 40px rgb(0 0 0 / 0.5), 0 2px 6px rgb(0 0 0 / 0.3);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/* Launcher */
|
|
187
|
+
/* Launcher = the Panel product mark (Twiddle brand): ink disc, indigo knob,
|
|
188
|
+
solid-tint trail. Closed = knob at "off" (bottom), no trail; open = "on".
|
|
189
|
+
The Toggle animation (panel.js) moves between them; these are the rest states. */
|
|
187
190
|
.launcher {
|
|
188
191
|
width: 44px; height: 44px;
|
|
189
|
-
border-radius: 50%;
|
|
190
|
-
|
|
191
|
-
background: var(--p-bg);
|
|
192
|
-
color: var(--p-fg);
|
|
192
|
+
border-radius: 50%; border: 0;
|
|
193
|
+
background: transparent;
|
|
193
194
|
box-shadow: var(--p-shadow);
|
|
194
195
|
display: grid; place-items: center;
|
|
195
196
|
cursor: pointer;
|
|
196
197
|
padding: 0;
|
|
197
198
|
}
|
|
198
|
-
.
|
|
199
|
-
.
|
|
200
|
-
.
|
|
199
|
+
.mark { display: block; overflow: visible; }
|
|
200
|
+
.mark-disc { fill: #16161a; }
|
|
201
|
+
.mark-knob { fill: #4f46e5; }
|
|
202
|
+
.mark-t1 { fill: #443cbc; }
|
|
203
|
+
.mark-t2 { fill: #3b359e; }
|
|
204
|
+
.mark circle { transform-box: fill-box; transform-origin: center; }
|
|
205
|
+
.launcher[aria-expanded="false"] .mark-knob { transform: translateY(32px); }
|
|
206
|
+
.launcher[aria-expanded="false"] .mark-t1, .launcher[aria-expanded="false"] .mark-t2 { opacity: 0; }
|
|
207
|
+
.launcher:hover .mark-knob { fill: #6366f1; }
|
|
201
208
|
.launcher:focus-visible, button:focus-visible, summary:focus-visible {
|
|
202
209
|
outline: 2px solid var(--p-accent); outline-offset: 2px;
|
|
203
210
|
}
|
|
204
211
|
|
|
205
212
|
/* Panel */
|
|
206
213
|
.panel {
|
|
207
|
-
width:
|
|
208
|
-
max-
|
|
214
|
+
width: 360px; /* tabbed layout (Figma 254:323); was 300 */
|
|
215
|
+
max-width: calc(100vw - 32px);
|
|
216
|
+
/* As tall as its content; scrolls only when the window is shorter.
|
|
217
|
+
90px = 16 margin + 44 launcher + 10 gap + 20 room at the top. */
|
|
218
|
+
max-height: calc(100vh - 90px);
|
|
219
|
+
max-height: calc(100dvh - 90px);
|
|
209
220
|
background: var(--p-bg);
|
|
210
221
|
border: 1px solid var(--p-line);
|
|
211
222
|
border-radius: 14px;
|
|
@@ -217,8 +228,7 @@ const Dn = `
|
|
|
217
228
|
|
|
218
229
|
.head {
|
|
219
230
|
display: flex; align-items: center; gap: 4px;
|
|
220
|
-
padding: 10px 10px
|
|
221
|
-
border-bottom: 1px solid var(--p-line);
|
|
231
|
+
padding: 10px 10px 6px 14px; /* the actions row below carries the divider */
|
|
222
232
|
}
|
|
223
233
|
.brand { font-weight: 600; font-size: 14px; letter-spacing: -0.01em; flex: 1; }
|
|
224
234
|
.icon-btn {
|
|
@@ -415,14 +425,17 @@ const Dn = `
|
|
|
415
425
|
.swatch-label { font-size: 12px; color: var(--p-muted); }
|
|
416
426
|
.swatch[aria-checked="true"] .swatch-label { color: var(--p-fg); }
|
|
417
427
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
428
|
+
/* Second header row: the global actions that used to be the footer */
|
|
429
|
+
.actions {
|
|
430
|
+
border-bottom: 1px solid var(--p-line);
|
|
431
|
+
padding: 2px 10px 10px;
|
|
421
432
|
display: flex; align-items: center; gap: 2px;
|
|
422
433
|
color: var(--p-muted); font-size: 12px;
|
|
423
434
|
}
|
|
424
435
|
.spacer { flex: 1; }
|
|
425
436
|
.icon-btn:disabled { opacity: .35; cursor: default; background: transparent; }
|
|
437
|
+
.actions .icon-btn, .actions .apply { flex: none; } /* Undo / Redo / Copy prompt keep their size; Changes gives way if space runs out */
|
|
438
|
+
.actions .text-btn { min-width: 0; overflow: hidden; }
|
|
426
439
|
.icon-btn[aria-pressed="true"] { background: var(--p-accent); color: var(--p-on-accent); }
|
|
427
440
|
kbd {
|
|
428
441
|
font: 11px ui-monospace, "SF Mono", Menlo, monospace;
|
|
@@ -430,8 +443,10 @@ const Dn = `
|
|
|
430
443
|
padding: 1px 4px;
|
|
431
444
|
}
|
|
432
445
|
|
|
446
|
+
/* Beside the launcher (left of it, centred on it). The panel always sits
|
|
447
|
+
above the launcher, so a toast here never covers the footer's Copy prompt. */
|
|
433
448
|
.toast {
|
|
434
|
-
position: absolute; bottom:
|
|
449
|
+
position: absolute; bottom: 22px; right: 54px; transform: translateY(50%);
|
|
435
450
|
background: var(--p-accent); color: var(--p-on-accent);
|
|
436
451
|
padding: 6px 10px; border-radius: 8px; font-size: 12px;
|
|
437
452
|
white-space: nowrap;
|
|
@@ -476,7 +491,7 @@ const Dn = `
|
|
|
476
491
|
background: var(--p-accent); color: var(--p-on-accent); cursor: pointer;
|
|
477
492
|
}
|
|
478
493
|
.menu {
|
|
479
|
-
position: absolute; right: 0;
|
|
494
|
+
position: absolute; right: 0; top: calc(100% + 6px); z-index: 2; /* opens downwards from the header */
|
|
480
495
|
min-width: 180px; padding: 4px; display: grid; gap: 2px;
|
|
481
496
|
background: var(--p-bg); border: 1px solid var(--p-line); border-radius: 8px;
|
|
482
497
|
box-shadow: var(--p-shadow);
|
|
@@ -546,7 +561,7 @@ const Dn = `
|
|
|
546
561
|
font-size: 12px; line-height: 1.4;
|
|
547
562
|
}
|
|
548
563
|
.warning svg { flex: none; width: 16px; height: 16px; margin-top: 0.5px; }
|
|
549
|
-
|
|
564
|
+
.warning { color: #fbbf24; } /* amber text that reads on the dark panel */
|
|
550
565
|
|
|
551
566
|
/* Changes list */
|
|
552
567
|
.change-group { padding: 10px 14px; border-bottom: 1px solid var(--p-line); display: grid; gap: 6px; }
|
|
@@ -574,11 +589,9 @@ const Dn = `
|
|
|
574
589
|
--p-accent: #18181b; --p-on-accent: #ffffff;
|
|
575
590
|
--p-shadow: 0 12px 40px rgb(0 0 0 / 0.14), 0 2px 6px rgb(0 0 0 / 0.06);
|
|
576
591
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
--p-accent: #f4f4f5; --p-on-accent: #18181b;
|
|
581
|
-
}
|
|
592
|
+
.dialog-backdrop { /* dark by default, like the panel */
|
|
593
|
+
--p-bg: #1c1c1f; --p-fg: #f4f4f5; --p-muted: #a1a1aa; --p-line: #2e2e33; --p-hover: #27272a;
|
|
594
|
+
--p-accent: #f4f4f5; --p-on-accent: #18181b;
|
|
582
595
|
}
|
|
583
596
|
.dialog-backdrop[hidden] { display: none; }
|
|
584
597
|
.dialog {
|
|
@@ -640,35 +653,13 @@ const Dn = `
|
|
|
640
653
|
width: 10px; height: 10px; border-radius: 50%;
|
|
641
654
|
background: #d97706; box-shadow: 0 0 0 2px var(--p-bg);
|
|
642
655
|
}
|
|
643
|
-
|
|
656
|
+
.setup-head svg { color: #fbbf24; }
|
|
644
657
|
|
|
645
658
|
/* ---------- Layout direction A (Figma 80:245 › A1b, A2) ---------- */
|
|
646
|
-
.
|
|
647
|
-
.subhead { padding-left: 8px; }
|
|
659
|
+
.page[hidden] { display: none; }
|
|
648
660
|
.page-fields { padding: 14px 14px 16px; display: grid; gap: 14px; color: var(--p-muted); }
|
|
649
661
|
.page-warnings:empty { display: none; }
|
|
650
662
|
|
|
651
|
-
/* Edit list rows look like the Figma Section row (6:134) */
|
|
652
|
-
.nav-row {
|
|
653
|
-
width: 100%; display: flex; align-items: center; gap: 8px;
|
|
654
|
-
padding: 11px 14px; border: 0; border-bottom: 1px solid var(--p-line);
|
|
655
|
-
background: transparent; color: var(--p-fg);
|
|
656
|
-
font: inherit; font-weight: 500; text-align: left; cursor: pointer;
|
|
657
|
-
}
|
|
658
|
-
.nav-row:last-child { border-bottom: 0; }
|
|
659
|
-
.nav-row:hover { background: var(--p-hover); }
|
|
660
|
-
.nav-row .chev { margin-left: auto; color: var(--p-muted); display: grid; }
|
|
661
|
-
|
|
662
|
-
/* "Edit every token" row above the footer (Figma: Edit row (proposal)) */
|
|
663
|
-
.edit-row {
|
|
664
|
-
display: flex; align-items: center; gap: 8px;
|
|
665
|
-
padding: 11px 14px; border: 0; border-top: 1px solid var(--p-line);
|
|
666
|
-
background: transparent; color: var(--p-fg);
|
|
667
|
-
font: inherit; font-weight: 500; text-align: left; cursor: pointer;
|
|
668
|
-
}
|
|
669
|
-
.edit-row:hover { background: var(--p-hover); }
|
|
670
|
-
.edit-row .chev { margin-left: auto; color: var(--p-muted); display: grid; }
|
|
671
|
-
|
|
672
663
|
/* Character pad (Figma: Character pad (proposal)): 170px tall, 9 × 5 dots */
|
|
673
664
|
.pad {
|
|
674
665
|
position: relative; height: 170px; border-radius: 10px;
|
|
@@ -756,74 +747,208 @@ const Dn = `
|
|
|
756
747
|
.role-row.edited .step-select select, .step-select select:focus-visible { border-color: var(--p-accent); outline: none; }
|
|
757
748
|
.step-chevron { position: absolute; right: 7px; top: 50%; transform: translateY(-50%); display: grid; color: var(--p-muted); pointer-events: none; }
|
|
758
749
|
.role-note { margin: 0; font-size: 11px; line-height: 1.45; color: var(--p-muted); }
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
750
|
+
|
|
751
|
+
/* ---------- Tabbed layout (Figma: Panel — tabbed layout (proposal), 254:323) ---------- */
|
|
752
|
+
.tabbar {
|
|
753
|
+
display: flex; gap: 2px; padding: 6px;
|
|
754
|
+
border-top: 1px solid var(--p-line);
|
|
755
|
+
}
|
|
756
|
+
.tab {
|
|
757
|
+
flex: 1 1 0; min-width: 0;
|
|
758
|
+
display: grid; justify-items: center; gap: 3px;
|
|
759
|
+
padding: 7px 2px 6px; border: 0; border-radius: 8px;
|
|
760
|
+
background: transparent; color: var(--p-muted);
|
|
761
|
+
font: inherit; font-size: 11px; cursor: pointer;
|
|
762
|
+
}
|
|
763
|
+
.tab:hover { background: var(--p-hover); color: var(--p-fg); }
|
|
764
|
+
.tab[aria-pressed="true"] { background: var(--p-hover); color: var(--p-fg); font-weight: 600; }
|
|
765
|
+
.tab span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
766
|
+
.page-fields { gap: 16px; }
|
|
767
|
+
.page-heading { display: flex; align-items: baseline; justify-content: space-between; font-size: 13px; font-weight: 500; color: var(--p-fg); margin-bottom: -6px; }
|
|
768
|
+
.page-heading .muted { font-size: 12px; font-weight: 400; }
|
|
769
|
+
|
|
770
|
+
/* Tiles (Figma: Tile (proposal)) */
|
|
771
|
+
.tiles { display: grid; grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr)); gap: 8px; }
|
|
772
|
+
.tile {
|
|
773
|
+
display: grid; justify-items: center; align-content: start; gap: 6px; min-width: 0;
|
|
774
|
+
padding: 8px; border: 1px solid var(--p-line); border-radius: 10px;
|
|
775
|
+
background: var(--p-bg); color: var(--p-muted);
|
|
776
|
+
font: inherit; font-size: 11px; text-align: center; cursor: pointer;
|
|
777
|
+
}
|
|
778
|
+
.tile:hover { border-color: var(--p-muted); }
|
|
779
|
+
.tile[aria-checked="true"] { border-color: var(--p-accent); box-shadow: inset 0 0 0 1px var(--p-accent); color: var(--p-fg); font-weight: 600; }
|
|
780
|
+
.tile:focus-visible { outline: 2px solid var(--p-accent); outline-offset: 2px; }
|
|
781
|
+
.tile.wide { grid-column: 1 / -1; }
|
|
782
|
+
.tile-text { display: grid; gap: 1px; min-width: 0; max-width: 100%; }
|
|
783
|
+
.tile-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
784
|
+
.tile-sub { font-size: 11px; font-weight: 400; color: var(--p-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
785
|
+
.tile-preview { display: grid; place-items: center; }
|
|
786
|
+
|
|
787
|
+
/* Font tiles: left-aligned, big "Aa" in the pairing's heading font */
|
|
788
|
+
.tiles-fonts .tile { justify-items: start; text-align: left; padding: 10px 12px; gap: 2px; }
|
|
789
|
+
.tiles-fonts .tile-label { font-size: 13px; font-weight: 500; color: var(--p-fg); }
|
|
790
|
+
.tiles-fonts .tile[aria-checked="true"] .tile-label { font-weight: 600; }
|
|
791
|
+
.tile-aa { font-size: 26px; line-height: 32px; font-weight: 600; color: var(--p-fg); }
|
|
792
|
+
.tiles-fonts .tile.wide { grid-template-columns: auto 1fr; align-items: center; column-gap: 10px; }
|
|
793
|
+
.tiles-fonts .tile.wide .tile-aa { font-size: 18px; line-height: 24px; }
|
|
794
|
+
.tiles-fonts .tile.wide .tile-text { display: flex; justify-content: space-between; align-items: center; gap: 8px; width: 100%; }
|
|
795
|
+
.tiles-fonts .tile.wide { justify-items: stretch; }
|
|
796
|
+
|
|
797
|
+
/* Previews drawn in CSS */
|
|
798
|
+
.ladder { display: flex; align-items: baseline; gap: 3px; height: 32px; color: var(--p-fg); }
|
|
799
|
+
.ladder b { font-weight: 600; line-height: 1; }
|
|
800
|
+
.sketch { display: grid; align-content: start; width: 84px; height: 56px; border: 1px solid var(--p-line); border-radius: 6px; background: var(--p-hover); }
|
|
801
|
+
.sketch i { height: 9px; border-radius: 2px; background: var(--p-line); }
|
|
802
|
+
.sketch i:first-child { background: var(--p-muted); }
|
|
803
|
+
.shape { display: block; }
|
|
804
|
+
.shape-button { width: 52px; height: 24px; background: #4f46e5; }
|
|
805
|
+
.shape-button.size-small { width: 38px; height: 16px; border-radius: 4px; }
|
|
806
|
+
.shape-button.size-medium { width: 52px; height: 22px; border-radius: 5px; }
|
|
807
|
+
.shape-button.size-large { width: 66px; height: 28px; border-radius: 6px; }
|
|
808
|
+
.shape-card { width: 52px; height: 38px; background: var(--p-bg); border: 1px solid var(--p-muted); }
|
|
809
|
+
.shape-card.border-none { border-color: transparent; background: var(--p-hover); border-radius: 6px; }
|
|
810
|
+
.shape-card.border-thin { border-radius: 6px; }
|
|
811
|
+
.shape-card.border-thick { border-width: 2px; border-radius: 6px; }
|
|
812
|
+
/* Style tiles (v0.4): Figma "Shape — with styles" 257:711 */
|
|
813
|
+
.shape-button[class*="style-"] { width: 52px; height: 22px; border-radius: 5px; display: grid; place-items: center; border: 1.5px solid transparent; }
|
|
814
|
+
.shape-button[class*="style-"]::after { content: ""; width: 22px; height: 3px; border-radius: 2px; background: #a5b4fc; }
|
|
815
|
+
.shape-button.style-solid::after { background: #fff; }
|
|
816
|
+
.shape-button.style-soft { background: rgb(79 70 229 / 0.32); }
|
|
817
|
+
.shape-button.style-outline { background: transparent; border-color: #818cf8; }
|
|
818
|
+
.shape-button.style-ghost { background: transparent; }
|
|
819
|
+
.shape-card[class*="style-"] { border-radius: 6px; }
|
|
820
|
+
.shape-card.style-filled { background: var(--p-hover); border-color: var(--p-line); }
|
|
821
|
+
.shape-card.style-outlined { background: transparent; border-color: var(--p-muted); }
|
|
822
|
+
.shape-card.style-elevated { background: color-mix(in oklch, var(--p-hover) 88%, white); border-color: transparent; box-shadow: 0 5px 12px rgb(0 0 0 / 0.55); }
|
|
823
|
+
.shape-card.style-tinted { background: rgb(79 70 229 / 0.2); border-color: rgb(129 140 248 / 0.5); }
|
|
824
|
+
.shape-card[class*="shadow-"] { width: 44px; height: 32px; border-radius: 6px; border-color: var(--p-line); margin: 4px 0 6px; }
|
|
825
|
+
.shape-card.shadow-bold { border-color: var(--p-fg); }
|
|
826
|
+
|
|
827
|
+
/* Colour tab */
|
|
828
|
+
.tiles-themes .tile { justify-items: stretch; }
|
|
829
|
+
.tiles-themes .tile-preview { display: block; width: 100%; place-items: normal; } /* stretch the palette (block centring is supported now) */
|
|
830
|
+
.theme { width: 100%; }
|
|
831
|
+
.theme { display: block; height: 58px; border-radius: 6px; background: var(--bg); border: 1px solid var(--line); position: relative; }
|
|
832
|
+
.theme i { position: absolute; left: 10px; right: 30px; top: 9px; bottom: 9px; background: #fff; border: 1px solid var(--line); border-radius: 5px; }
|
|
833
|
+
.theme b, .theme s, .theme u { position: absolute; left: 7px; height: 4px; border-radius: 2px; text-decoration: none; }
|
|
834
|
+
.theme b { top: 7px; width: 40%; background: var(--ink); }
|
|
835
|
+
.theme s { top: 15px; width: 60%; height: 3px; background: var(--soft); }
|
|
836
|
+
.theme u { top: 24px; width: 26px; height: 9px; border-radius: 3px; background: var(--pri); }
|
|
837
|
+
.palette { display: flex; gap: 2px; margin-top: 6px; }
|
|
838
|
+
.palette i { flex: 1; height: 10px; border-radius: 2px; }
|
|
839
|
+
.tiles-swatches { gap: 6px; }
|
|
840
|
+
.tiles-swatches .tile { padding: 0; width: 30px; height: 30px; border-radius: 50%; border: 0; display: grid; place-items: center; align-content: center; gap: 0; justify-self: center; } /* dot centred in its ring */
|
|
841
|
+
.tiles-swatches .tile .tile-text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
842
|
+
.tiles-swatches .tile[aria-checked="true"] { box-shadow: inset 0 0 0 2px var(--p-accent); }
|
|
843
|
+
.tiles-swatches .dot { width: 22px; height: 22px; border-radius: 50%; display: block; }
|
|
844
|
+
.tiles-greys .tile { padding: 6px 4px; font-size: 10px; }
|
|
845
|
+
.greys { display: flex; border-radius: 4px; overflow: hidden; }
|
|
846
|
+
.greys i { width: 10px; height: 18px; }
|
|
847
|
+
.rainbow { width: 16px; height: 16px; border-radius: 50%; background: conic-gradient(#ef4444, #eab308, #22c55e, #06b6d4, #6366f1, #d946ef, #ef4444); flex: none; }
|
|
848
|
+
|
|
849
|
+
/* Fold-out rows ("Colour tokens"…, "Custom colour…") */
|
|
850
|
+
.more { border: 1px solid var(--p-line); border-radius: 8px; }
|
|
851
|
+
.more summary { display: flex; align-items: center; gap: 6px; padding: 9px 10px; font-weight: 500; font-size: 12px; color: var(--p-fg); cursor: pointer; border-radius: 8px; }
|
|
852
|
+
.more summary::-webkit-details-marker { display: none; }
|
|
853
|
+
.more summary:hover { background: var(--p-hover); }
|
|
854
|
+
.more summary .chev { display: grid; color: var(--p-muted); transition: transform .15s ease; }
|
|
855
|
+
.more[open] summary .chev { transform: rotate(90deg); }
|
|
856
|
+
.more summary .muted { margin-left: auto; font-weight: 400; }
|
|
857
|
+
.more summary > span:nth-child(2) { flex: 1; }
|
|
858
|
+
.more-body { display: grid; gap: 14px; padding: 4px 10px 12px; }
|
|
859
|
+
|
|
860
|
+
/* Overview: "Current look" rows */
|
|
861
|
+
.summary { border: 1px solid var(--p-line); border-radius: 10px; overflow: hidden; }
|
|
862
|
+
.summary-row {
|
|
863
|
+
width: 100%; display: flex; align-items: center; gap: 8px;
|
|
864
|
+
padding: 9px 10px 9px 12px; border: 0; border-bottom: 1px solid var(--p-line);
|
|
865
|
+
background: transparent; color: var(--p-fg); font: inherit; text-align: left; cursor: pointer;
|
|
866
|
+
}
|
|
867
|
+
.summary-row:last-child { border-bottom: 0; }
|
|
868
|
+
.summary-row:hover { background: var(--p-hover); }
|
|
869
|
+
.summary-name { flex: 1; font-weight: 500; }
|
|
870
|
+
.summary-value { font-size: 12px; color: var(--p-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 180px; }
|
|
871
|
+
.summary-swatch { width: 14px; height: 14px; border-radius: 4px; flex: none; }
|
|
872
|
+
.summary-swatch[hidden] { display: none; }
|
|
873
|
+
.summary-row .chev { display: grid; color: var(--p-muted); }
|
|
874
|
+
`, P = (t, e, n) => `<svg width="${t}" height="${t}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${e}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${n}</svg>`, A = {
|
|
875
|
+
dial: P(20, 1.8, '<circle cx="12" cy="12" r="8"/><path d="M12 12l4-4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>'),
|
|
876
|
+
link: P(15, 2, '<path d="M10 13a5 5 0 0 0 7.07 0l3-3a5 5 0 0 0-7.07-7.07l-1 1"/><path d="M14 11a5 5 0 0 0-7.07 0l-3 3a5 5 0 0 0 7.07 7.07l1-1"/>'),
|
|
877
|
+
reset: P(15, 2, '<path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 3v6h6"/>'),
|
|
878
|
+
close: P(15, 2, '<path d="M18 6L6 18M6 6l12 12"/>'),
|
|
879
|
+
chevron: P(12, 2.4, '<path d="M9 6l6 6-6 6"/>'),
|
|
765
880
|
// Proposals: not yet in the Figma Icon/* set.
|
|
766
|
-
pick:
|
|
767
|
-
compare:
|
|
768
|
-
undo:
|
|
769
|
-
redo:
|
|
770
|
-
back:
|
|
771
|
-
down:
|
|
772
|
-
copy:
|
|
773
|
-
warn:
|
|
774
|
-
|
|
881
|
+
pick: P(15, 2, '<circle cx="12" cy="12" r="7"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/>'),
|
|
882
|
+
compare: P(15, 2, '<circle cx="12" cy="12" r="9"/><path d="M12 3v18" /><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/>'),
|
|
883
|
+
undo: P(15, 2, '<path d="M9 14L4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-3"/>'),
|
|
884
|
+
redo: P(15, 2, '<path d="M15 14l5-5-5-5"/><path d="M20 9H9a5 5 0 0 0 0 10h3"/>'),
|
|
885
|
+
back: P(15, 2, '<path d="M15 18l-6-6 6-6"/>'),
|
|
886
|
+
down: P(12, 2.4, '<path d="M6 9l6 6 6-6"/>'),
|
|
887
|
+
copy: P(13, 2, '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/>'),
|
|
888
|
+
warn: P(13, 2, '<path d="M12 3l9.5 17h-19z"/><path d="M12 10v4M12 17.5v.01"/>'),
|
|
889
|
+
// Launcher: the Panel product mark (Twiddle brand; Figma Logo page › Panel mark; also site/favicon.svg).
|
|
890
|
+
// A switch knocked out of a disc; knob + two-dot trail. Classes let the Toggle animation move them.
|
|
891
|
+
panelMark: '<svg class="mark" width="44" height="44" viewBox="0 0 120 120" aria-hidden="true"><path class="mark-disc" fill-rule="evenodd" clip-rule="evenodd" d="M60 120C93.1371 120 120 93.1371 120 60C120 26.8629 93.1371 0 60 0C26.8629 0 0 26.8629 0 60C0 93.1371 26.8629 120 60 120ZM86 76C86 90.3594 74.3594 102 60 102C45.6406 102 34 90.3594 34 76V44C34 29.6406 45.6406 18 60 18C74.3594 18 86 29.6406 86 44V76ZM60 22C72.1503 22 82 31.8497 82 44V76C82 88.1503 72.1503 98 60 98C47.8497 98 38 88.1503 38 76V44C38 31.8497 47.8497 22 60 22Z"/><circle class="mark-t2" cx="60" cy="80.46" r="4.28"/><circle class="mark-t1" cx="60" cy="66.04" r="7.14"/><circle class="mark-knob" cx="60" cy="44" r="11.9"/></svg>',
|
|
892
|
+
// Tab bar (Figma: Panel — tabbed layout (proposal), Icon/Tab * components)
|
|
893
|
+
tabOverview: P(18, 1.8, '<circle cx="12" cy="12" r="8"/><path d="M12 12l4-4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>'),
|
|
894
|
+
tabColour: P(18, 2, '<path d="M12 3c-3.5 4.5-6 7.8-6 11a6 6 0 0 0 12 0c0-3.2-2.5-6.5-6-11z"/>'),
|
|
895
|
+
tabType: P(18, 2, '<path d="M5 7V5h14v2"/><path d="M12 5v14"/><path d="M9 19h6"/>'),
|
|
896
|
+
tabSpacing: P(18, 2, '<path d="M4 5v14M20 5v14"/><path d="M8 12h8"/><path d="M10 9l-3 3 3 3M14 9l3 3-3 3"/>'),
|
|
897
|
+
tabShape: P(18, 2, '<path d="M20 4h-9a7 7 0 0 0-7 7v9"/><path d="M20 10h-5a5 5 0 0 0-5 5v5"/>'),
|
|
898
|
+
tabShadows: P(18, 2, '<rect x="4" y="4" width="12" height="12" rx="2"/><path d="M20 8v10a2 2 0 0 1-2 2H8"/>'),
|
|
899
|
+
sliders: P(15, 2, '<path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/>')
|
|
775
900
|
// Figma: Icon/Sliders (proposal)
|
|
776
|
-
},
|
|
777
|
-
function
|
|
901
|
+
}, An = "twiddle:";
|
|
902
|
+
function fe(t) {
|
|
778
903
|
try {
|
|
779
|
-
return localStorage.getItem(
|
|
904
|
+
return localStorage.getItem(An + t);
|
|
780
905
|
} catch {
|
|
781
906
|
return null;
|
|
782
907
|
}
|
|
783
908
|
}
|
|
784
|
-
function
|
|
909
|
+
function ge(t, e) {
|
|
785
910
|
try {
|
|
786
|
-
localStorage.setItem(
|
|
911
|
+
localStorage.setItem(An + t, e);
|
|
787
912
|
} catch {
|
|
788
913
|
}
|
|
789
914
|
}
|
|
790
|
-
function
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
const
|
|
794
|
-
const
|
|
795
|
-
return
|
|
915
|
+
function re({ label: t, options: e, onSelect: n }) {
|
|
916
|
+
const a = document.createElement("div");
|
|
917
|
+
a.className = "seg", a.setAttribute("role", "radiogroup"), a.setAttribute("aria-label", t);
|
|
918
|
+
const o = e.map((l) => {
|
|
919
|
+
const p = document.createElement("button");
|
|
920
|
+
return p.type = "button", p.setAttribute("role", "radio"), p.dataset.value = l.value, p.textContent = l.label, l.title && (p.title = l.title), p.addEventListener("click", () => i(l.value)), p;
|
|
796
921
|
});
|
|
797
|
-
|
|
798
|
-
let
|
|
799
|
-
function
|
|
800
|
-
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
922
|
+
a.append(...o);
|
|
923
|
+
let r = null;
|
|
924
|
+
function s(l) {
|
|
925
|
+
r = l, o.forEach((c) => c.setAttribute("aria-checked", String(c.dataset.value === l)));
|
|
926
|
+
const p = Math.max(0, o.findIndex((c) => c.dataset.value === l));
|
|
927
|
+
o.forEach((c, h) => {
|
|
928
|
+
c.tabIndex = h === p ? 0 : -1;
|
|
804
929
|
});
|
|
805
930
|
}
|
|
806
|
-
function
|
|
807
|
-
l !==
|
|
808
|
-
}
|
|
809
|
-
return
|
|
810
|
-
const
|
|
811
|
-
if (
|
|
812
|
-
const
|
|
813
|
-
let
|
|
814
|
-
if (
|
|
815
|
-
else if (l.key === "Home")
|
|
816
|
-
else if (l.key === "End")
|
|
931
|
+
function i(l, p) {
|
|
932
|
+
l !== r && (s(l), n(l)), p && o.find((c) => c.dataset.value === l).focus();
|
|
933
|
+
}
|
|
934
|
+
return a.addEventListener("keydown", (l) => {
|
|
935
|
+
const p = o.indexOf(l.target);
|
|
936
|
+
if (p < 0) return;
|
|
937
|
+
const c = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[l.key];
|
|
938
|
+
let h;
|
|
939
|
+
if (c) h = (p + c + o.length) % o.length;
|
|
940
|
+
else if (l.key === "Home") h = 0;
|
|
941
|
+
else if (l.key === "End") h = o.length - 1;
|
|
817
942
|
else return;
|
|
818
|
-
l.preventDefault(),
|
|
819
|
-
}),
|
|
943
|
+
l.preventDefault(), i(o[h].dataset.value, !0);
|
|
944
|
+
}), s(null), { el: a, setValue: s };
|
|
820
945
|
}
|
|
821
|
-
const
|
|
822
|
-
function*
|
|
946
|
+
const Ln = /var\(\s*(--twd-[\w-]+)/g, ha = /::?[\w-]+(\([^)]*\))?/g;
|
|
947
|
+
function* Ue(t) {
|
|
823
948
|
for (const e of t)
|
|
824
|
-
e.selectorText != null && (yield e), e.cssRules && (yield*
|
|
949
|
+
e.selectorText != null && (yield e), e.cssRules && (yield* Ue(e.cssRules));
|
|
825
950
|
}
|
|
826
|
-
function
|
|
951
|
+
function fa() {
|
|
827
952
|
const t = [];
|
|
828
953
|
for (const e of document.styleSheets) {
|
|
829
954
|
if (e.ownerNode?.id === "twiddle-scoped") continue;
|
|
@@ -833,92 +958,92 @@ function Vn() {
|
|
|
833
958
|
} catch {
|
|
834
959
|
continue;
|
|
835
960
|
}
|
|
836
|
-
for (const
|
|
837
|
-
const
|
|
838
|
-
|
|
961
|
+
for (const a of Ue(n)) {
|
|
962
|
+
const o = [...a.style.cssText.matchAll(Ln)].map((r) => r[1]);
|
|
963
|
+
o.length && t.push({ selectors: a.selectorText.split(","), vars: o });
|
|
839
964
|
}
|
|
840
965
|
}
|
|
841
966
|
return t;
|
|
842
967
|
}
|
|
843
|
-
function
|
|
844
|
-
const n = e.replace(
|
|
968
|
+
function ga(t, e) {
|
|
969
|
+
const n = e.replace(ha, "").trim() || "*";
|
|
845
970
|
try {
|
|
846
971
|
return t.matches(n);
|
|
847
972
|
} catch {
|
|
848
973
|
return !1;
|
|
849
974
|
}
|
|
850
975
|
}
|
|
851
|
-
function
|
|
976
|
+
function Pn(t) {
|
|
852
977
|
const e = /* @__PURE__ */ new Set(), n = [];
|
|
853
|
-
return
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
}), [...(t.getAttribute("style") || "").matchAll(
|
|
978
|
+
return fa().forEach(({ selectors: a, vars: o }) => {
|
|
979
|
+
const r = a.find((s) => ga(t, s));
|
|
980
|
+
r && (o.forEach((s) => e.add(s)), n.push(r.trim()));
|
|
981
|
+
}), [...(t.getAttribute("style") || "").matchAll(Ln)].forEach((a) => e.add(a[1])), { names: e, selectors: n };
|
|
857
982
|
}
|
|
858
|
-
const
|
|
859
|
-
function
|
|
983
|
+
const mt = (t) => window.CSS && CSS.escape ? CSS.escape(t) : t;
|
|
984
|
+
function ma(t) {
|
|
860
985
|
const e = [];
|
|
861
986
|
let n = t;
|
|
862
987
|
for (; n && n.nodeType === 1 && n !== document.body && n !== document.documentElement; ) {
|
|
863
|
-
if (n.id && document.querySelectorAll(`#${
|
|
864
|
-
return e.unshift(`#${
|
|
865
|
-
const
|
|
866
|
-
e.unshift(
|
|
988
|
+
if (n.id && document.querySelectorAll(`#${mt(n.id)}`).length === 1)
|
|
989
|
+
return e.unshift(`#${mt(n.id)}`), e.join(" > ");
|
|
990
|
+
const a = n.localName, o = [...n.parentElement.children].filter((r) => r.localName === a);
|
|
991
|
+
e.unshift(o.length > 1 ? `${a}:nth-of-type(${o.indexOf(n) + 1})` : a), n = n.parentElement;
|
|
867
992
|
}
|
|
868
993
|
return ["body", ...e].join(" > ");
|
|
869
994
|
}
|
|
870
|
-
function
|
|
995
|
+
function ba(t, e = Pn(t).selectors) {
|
|
871
996
|
const n = t.getAttribute("data-twd");
|
|
872
997
|
if (n) return `[data-twd="${n.replace(/"/g, "")}"]`;
|
|
873
|
-
const
|
|
874
|
-
return
|
|
998
|
+
const a = [...t.classList], r = a.find((s) => e.some((i) => new RegExp(`\\.${s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(i))) || a[0];
|
|
999
|
+
return r ? `.${mt(r)}` : null;
|
|
875
1000
|
}
|
|
876
|
-
function
|
|
877
|
-
const e =
|
|
1001
|
+
function xa(t) {
|
|
1002
|
+
const e = Pn(t), n = ba(t, e.selectors), a = (t.getAttribute("aria-label") || t.placeholder || t.textContent || "").trim().replace(/\s+/g, " ");
|
|
878
1003
|
return {
|
|
879
1004
|
el: t,
|
|
880
1005
|
tag: t.localName,
|
|
881
1006
|
classes: [...t.classList],
|
|
882
|
-
text:
|
|
1007
|
+
text: a.length > 40 ? `${a.slice(0, 40)}…` : a,
|
|
883
1008
|
tokens: e.names,
|
|
884
|
-
elementSelector:
|
|
1009
|
+
elementSelector: ma(t),
|
|
885
1010
|
classSelector: n,
|
|
886
1011
|
classCount: n ? document.querySelectorAll(n).length : 0
|
|
887
1012
|
};
|
|
888
1013
|
}
|
|
889
|
-
const
|
|
890
|
-
function
|
|
891
|
-
const
|
|
892
|
-
let
|
|
893
|
-
if (
|
|
894
|
-
const
|
|
895
|
-
|
|
1014
|
+
const bt = (t) => t.localName + [...t.classList].map((e) => `.${e}`).join(""), wa = { radius: "px", intensity: "%", base: "px" }, On = (t) => t.charAt(0).toUpperCase() + t.slice(1), va = (t, e) => typeof e == "number" || /^\d+$/.test(e) ? `${e}${wa[t] || ""}` : On(String(e));
|
|
1015
|
+
function Qt(t, e, n) {
|
|
1016
|
+
const a = Mn(() => t.tokens({ [t.id]: e }) || {}), o = Object.keys(a);
|
|
1017
|
+
let r = {};
|
|
1018
|
+
if (o.length) {
|
|
1019
|
+
const s = n.selector ? document.querySelector(n.selector) : void 0;
|
|
1020
|
+
r = da(o, s || void 0);
|
|
896
1021
|
}
|
|
897
1022
|
return {
|
|
898
1023
|
...n,
|
|
899
1024
|
sectionId: t.id,
|
|
900
1025
|
title: t.title,
|
|
901
|
-
settings: Object.entries(e).map(([
|
|
902
|
-
tokens:
|
|
1026
|
+
settings: Object.entries(e).map(([s, i]) => ({ key: s, label: On(s), value: va(s, i), ...t.describe?.(s, i) || {} })),
|
|
1027
|
+
tokens: o.map((s) => ({ name: s, from: r[s] || "", to: a[s] }))
|
|
903
1028
|
};
|
|
904
1029
|
}
|
|
905
|
-
function
|
|
1030
|
+
function en(t, e) {
|
|
906
1031
|
const n = [];
|
|
907
|
-
return e.forEach((
|
|
908
|
-
t[
|
|
909
|
-
}), Object.entries(t.scopes || {}).forEach(([
|
|
910
|
-
const
|
|
911
|
-
e.forEach((
|
|
912
|
-
|
|
1032
|
+
return e.forEach((a) => {
|
|
1033
|
+
t[a.id] && n.push(Qt(a, t[a.id], { kind: "global", selector: null }));
|
|
1034
|
+
}), Object.entries(t.scopes || {}).forEach(([a, o]) => {
|
|
1035
|
+
const r = ft(a) ? "class" : "element";
|
|
1036
|
+
e.forEach((s) => {
|
|
1037
|
+
o[s.id] && n.push(Qt(s, o[s.id], { kind: r, selector: a }));
|
|
913
1038
|
});
|
|
914
1039
|
}), n;
|
|
915
1040
|
}
|
|
916
|
-
function
|
|
917
|
-
const e = (
|
|
918
|
-
return e(
|
|
1041
|
+
function ya(t) {
|
|
1042
|
+
const e = (o) => Object.values(o || {}).reduce((r, s) => r + Object.keys(s || {}).length, 0), { scopes: n, ...a } = t;
|
|
1043
|
+
return e(a) + Object.values(n || {}).reduce((o, r) => o + e(r), 0);
|
|
919
1044
|
}
|
|
920
|
-
const
|
|
921
|
-
function
|
|
1045
|
+
const ae = (t) => `\`${t}\``, Ze = (t) => t.map((e) => ` - ${ae(e.name)}: ${e.from ? `${ae(e.from)} → ` : ""}${ae(e.to)}`), Xe = (t) => `${t.title} › ${t.settings.map((e) => `${e.label}: ${e.value}`).join(", ")}`;
|
|
1046
|
+
function ka(t) {
|
|
922
1047
|
let e = null;
|
|
923
1048
|
try {
|
|
924
1049
|
e = document.querySelector(t);
|
|
@@ -926,58 +1051,58 @@ function Yn(t) {
|
|
|
926
1051
|
}
|
|
927
1052
|
if (!e) return "not found on the current page";
|
|
928
1053
|
const n = (e.getAttribute("aria-label") || e.placeholder || e.textContent || "").trim().replace(/\s+/g, " ").slice(0, 60);
|
|
929
|
-
return `${
|
|
1054
|
+
return `${ae(bt(e))}${n ? ` with text “${n}”` : ""}`;
|
|
930
1055
|
}
|
|
931
|
-
function
|
|
1056
|
+
function tn(t, e) {
|
|
932
1057
|
const n = /* @__PURE__ */ new Map();
|
|
933
|
-
return t.forEach((
|
|
934
|
-
const
|
|
935
|
-
n.has(
|
|
1058
|
+
return t.forEach((a) => {
|
|
1059
|
+
const o = a[e];
|
|
1060
|
+
n.has(o) || n.set(o, []), n.get(o).push(a);
|
|
936
1061
|
}), [...n];
|
|
937
1062
|
}
|
|
938
|
-
function
|
|
1063
|
+
function Sa(t, { link: e = location.href, warnings: n = [] } = {}) {
|
|
939
1064
|
if (!t.length) return "";
|
|
940
|
-
const
|
|
941
|
-
if (
|
|
1065
|
+
const a = t.filter((i) => i.kind === "global"), o = t.filter((i) => i.kind === "class"), r = t.filter((i) => i.kind === "element"), s = [];
|
|
1066
|
+
if (s.push(
|
|
942
1067
|
"# Apply design tweaks from Panel",
|
|
943
1068
|
"",
|
|
944
1069
|
"I explored design changes to this prototype with Panel (by Twiddle), a tweak panel that overrides the prototype's `--twd-*` CSS custom properties (design tokens) at runtime. Make these changes permanent in the source code.",
|
|
945
1070
|
"",
|
|
946
1071
|
`Preview of the tweaked version: ${e}`
|
|
947
|
-
),
|
|
1072
|
+
), a.length && (s.push(
|
|
948
1073
|
"",
|
|
949
1074
|
"## 1. Global token changes",
|
|
950
1075
|
"",
|
|
951
1076
|
"Update the default value of each token where it is defined (usually a `:root` rule or a `tokens.css` / `twiddle-tokens.css` file). Change the definition, not the places that use it.",
|
|
952
1077
|
""
|
|
953
|
-
),
|
|
1078
|
+
), a.forEach((i) => s.push(`- **${Xe(i)}**`, ...Ze(i.tokens)))), o.length && (s.push(
|
|
954
1079
|
"",
|
|
955
|
-
`## ${
|
|
1080
|
+
`## ${a.length ? 2 : 1}. Component-wide changes`,
|
|
956
1081
|
"",
|
|
957
1082
|
"These apply to every element matching the selector. In the CSS rule for that component, redeclare the token, e.g. `.input { --twd-radius-control: 999px; }`. Don't replace the `var(--twd-…)` usages with fixed values.",
|
|
958
1083
|
""
|
|
959
|
-
),
|
|
960
|
-
|
|
961
|
-
})),
|
|
962
|
-
const
|
|
963
|
-
|
|
1084
|
+
), tn(o, "selector").forEach(([i, l]) => {
|
|
1085
|
+
s.push(`- ${ae(i)} (${document.querySelectorAll(i).length} on this page)`), l.forEach((p) => s.push(` - **${Xe(p)}**`, ...Ze(p.tokens).map((c) => ` ${c}`)));
|
|
1086
|
+
})), r.length) {
|
|
1087
|
+
const i = 1 + (a.length ? 1 : 0) + (o.length ? 1 : 0);
|
|
1088
|
+
s.push(
|
|
964
1089
|
"",
|
|
965
|
-
`## ${
|
|
1090
|
+
`## ${i}. Single-element changes`,
|
|
966
1091
|
"",
|
|
967
1092
|
"These apply to one element only. Add a modifier class to that element (e.g. `input--pill`) and redeclare the token in a rule for that class. The selectors below are runtime paths to help you find the element; don't copy them into the stylesheet.",
|
|
968
1093
|
""
|
|
969
|
-
),
|
|
970
|
-
|
|
1094
|
+
), tn(r, "selector").forEach(([l, p]) => {
|
|
1095
|
+
s.push(`- ${ka(l)}, found at ${ae(l)}`), p.forEach((c) => s.push(` - **${Xe(c)}**`, ...Ze(c.tokens).map((h) => ` ${h}`)));
|
|
971
1096
|
});
|
|
972
1097
|
}
|
|
973
|
-
return n.length &&
|
|
1098
|
+
return n.length && s.push(
|
|
974
1099
|
"",
|
|
975
1100
|
"## Contrast to check",
|
|
976
1101
|
"",
|
|
977
1102
|
"Panel flagged these colour pairs as below WCAG AA with the new values:",
|
|
978
1103
|
"",
|
|
979
|
-
...n.map((
|
|
980
|
-
),
|
|
1104
|
+
...n.map((i) => `- ${i.text}`)
|
|
1105
|
+
), s.push(
|
|
981
1106
|
"",
|
|
982
1107
|
"## Constraints",
|
|
983
1108
|
"",
|
|
@@ -985,71 +1110,71 @@ function Xn(t, { link: e = location.href, warnings: n = [] } = {}) {
|
|
|
985
1110
|
"- Change only what is listed above.",
|
|
986
1111
|
"- If a value uses `color-mix()` and the project targets older browsers, ask before converting it.",
|
|
987
1112
|
"- When done, list the files you changed."
|
|
988
|
-
),
|
|
1113
|
+
), s.join(`
|
|
989
1114
|
`);
|
|
990
1115
|
}
|
|
991
|
-
const
|
|
1116
|
+
const Re = 4.5, $a = 3, Ea = [
|
|
992
1117
|
["--twd-text", "--twd-bg", "Text on background"],
|
|
993
1118
|
["--twd-text", "--twd-surface", "Text on surface"],
|
|
994
1119
|
["--twd-text-muted", "--twd-surface", "Muted text on surface"],
|
|
995
1120
|
["--twd-on-primary", "--twd-primary", "Text on brand"]
|
|
996
1121
|
];
|
|
997
|
-
let
|
|
998
|
-
function
|
|
999
|
-
if (!t || (
|
|
1000
|
-
|
|
1001
|
-
const [e, n,
|
|
1002
|
-
return [e, n,
|
|
1003
|
-
}
|
|
1004
|
-
function
|
|
1122
|
+
let W;
|
|
1123
|
+
function St(t) {
|
|
1124
|
+
if (!t || (W ||= Object.assign(document.createElement("canvas"), { width: 1, height: 1 }).getContext("2d", { willReadFrequently: !0 }), !W)) return null;
|
|
1125
|
+
W.clearRect(0, 0, 1, 1), W.fillStyle = "#000", W.fillStyle = t, W.fillRect(0, 0, 1, 1);
|
|
1126
|
+
const [e, n, a, o] = W.getImageData(0, 0, 1, 1).data;
|
|
1127
|
+
return [e, n, a, o / 255];
|
|
1128
|
+
}
|
|
1129
|
+
function nn(t) {
|
|
1005
1130
|
const e = document.createElement("i");
|
|
1006
1131
|
e.style.cssText = `position:absolute;visibility:hidden;color:var(${t})`, document.documentElement.appendChild(e);
|
|
1007
1132
|
const n = getComputedStyle(e).color;
|
|
1008
|
-
return e.remove(),
|
|
1009
|
-
}
|
|
1010
|
-
const
|
|
1011
|
-
const
|
|
1012
|
-
return 0.2126 *
|
|
1013
|
-
},
|
|
1014
|
-
function
|
|
1015
|
-
const n =
|
|
1016
|
-
return (Math.max(n,
|
|
1017
|
-
}
|
|
1018
|
-
const
|
|
1019
|
-
function
|
|
1133
|
+
return e.remove(), St(n);
|
|
1134
|
+
}
|
|
1135
|
+
const an = ([t, e, n]) => {
|
|
1136
|
+
const a = (o) => (o /= 255, o <= 0.03928 ? o / 12.92 : ((o + 0.055) / 1.055) ** 2.4);
|
|
1137
|
+
return 0.2126 * a(t) + 0.7152 * a(e) + 0.0722 * a(n);
|
|
1138
|
+
}, Fn = (t, e) => t.slice(0, 3).map((n, a) => n * t[3] + e[a] * (1 - t[3])).concat(1);
|
|
1139
|
+
function Nn(t, e) {
|
|
1140
|
+
const n = an(t[3] < 1 ? Fn(t, e) : t), a = an(e);
|
|
1141
|
+
return (Math.max(n, a) + 0.05) / (Math.min(n, a) + 0.05);
|
|
1142
|
+
}
|
|
1143
|
+
const jn = (t) => `${t.toFixed(1)}:1`;
|
|
1144
|
+
function Je() {
|
|
1020
1145
|
const t = [];
|
|
1021
|
-
return
|
|
1022
|
-
const
|
|
1023
|
-
if (!
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1146
|
+
return Ea.forEach(([e, n, a]) => {
|
|
1147
|
+
const o = nn(e), r = nn(n);
|
|
1148
|
+
if (!o || !r || r[3] === 0) return;
|
|
1149
|
+
const s = Nn(o, r);
|
|
1150
|
+
s < Re && t.push({ label: a, ratio: s, needed: Re, text: `${a}: ${jn(s)} · AA needs ${Re}` });
|
|
1026
1151
|
}), t;
|
|
1027
1152
|
}
|
|
1028
|
-
function
|
|
1153
|
+
function Ca(t) {
|
|
1029
1154
|
const e = [];
|
|
1030
1155
|
for (let n = t; n && n.nodeType === 1; n = n.parentElement) {
|
|
1031
|
-
const
|
|
1032
|
-
if (
|
|
1156
|
+
const a = St(getComputedStyle(n).backgroundColor);
|
|
1157
|
+
if (a && a[3] > 0 && (e.push(a), a[3] >= 1))
|
|
1033
1158
|
break;
|
|
1034
1159
|
}
|
|
1035
|
-
return e.reduceRight((n,
|
|
1160
|
+
return e.reduceRight((n, a) => Fn(a, n), [255, 255, 255, 1]);
|
|
1036
1161
|
}
|
|
1037
|
-
function
|
|
1162
|
+
function Ta(t) {
|
|
1038
1163
|
if (!t.textContent.trim() && !t.placeholder) return null;
|
|
1039
|
-
const e = getComputedStyle(t), n =
|
|
1164
|
+
const e = getComputedStyle(t), n = St(e.color);
|
|
1040
1165
|
if (!n) return null;
|
|
1041
|
-
const
|
|
1042
|
-
return
|
|
1166
|
+
const a = parseFloat(e.fontSize), r = a >= 24 || a >= 18.66 && parseInt(e.fontWeight, 10) >= 700 ? $a : Re, s = Nn(n, Ca(t));
|
|
1167
|
+
return s < r ? { label: "This element", ratio: s, needed: r, text: `This element’s text: ${jn(s)} · AA needs ${r}` } : null;
|
|
1043
1168
|
}
|
|
1044
|
-
const
|
|
1045
|
-
function
|
|
1169
|
+
const on = "twiddle-panel", Ma = "0.7.0", rn = "https://panel.twiddle.in", Aa = "--twd-radius-control", La = /var\(\s*--twd-/;
|
|
1170
|
+
function Pa(t) {
|
|
1046
1171
|
return t.style.cssText.split(";").some((e) => {
|
|
1047
1172
|
const n = e.slice(0, e.indexOf(":")).trim();
|
|
1048
|
-
return n && !n.startsWith("--") &&
|
|
1173
|
+
return n && !n.startsWith("--") && La.test(e);
|
|
1049
1174
|
});
|
|
1050
1175
|
}
|
|
1051
|
-
function
|
|
1052
|
-
if (!getComputedStyle(document.documentElement).getPropertyValue(
|
|
1176
|
+
function Oa() {
|
|
1177
|
+
if (!getComputedStyle(document.documentElement).getPropertyValue(Aa).trim()) return "no-tokens";
|
|
1053
1178
|
let t = !1;
|
|
1054
1179
|
for (const e of document.styleSheets) {
|
|
1055
1180
|
if (e.ownerNode?.id === "twiddle-scoped") continue;
|
|
@@ -1060,43 +1185,45 @@ function ro() {
|
|
|
1060
1185
|
t = !0;
|
|
1061
1186
|
continue;
|
|
1062
1187
|
}
|
|
1063
|
-
for (const
|
|
1188
|
+
for (const a of Ue(n)) if (Pa(a)) return "ok";
|
|
1064
1189
|
}
|
|
1065
1190
|
return document.querySelector('[style*="var(--twd-"]') ? "ok" : t ? "unknown" : "unused";
|
|
1066
1191
|
}
|
|
1067
|
-
function
|
|
1192
|
+
function Fa() {
|
|
1068
1193
|
const t = /* @__PURE__ */ new Set(), e = (n) => {
|
|
1069
|
-
for (const
|
|
1194
|
+
for (const a of n.matchAll(/var\(\s*(--twd-[\w-]+)/g)) t.add(a[1]);
|
|
1070
1195
|
};
|
|
1071
1196
|
for (const n of document.styleSheets) {
|
|
1072
1197
|
if (n.ownerNode?.id === "twiddle-scoped") continue;
|
|
1073
|
-
let
|
|
1198
|
+
let a;
|
|
1074
1199
|
try {
|
|
1075
|
-
|
|
1200
|
+
a = n.cssRules;
|
|
1076
1201
|
} catch {
|
|
1077
1202
|
continue;
|
|
1078
1203
|
}
|
|
1079
|
-
for (const
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1204
|
+
for (const o of Ue(a))
|
|
1205
|
+
o.style.cssText.split(";").forEach((r) => {
|
|
1206
|
+
const s = r.slice(0, r.indexOf(":")).trim();
|
|
1207
|
+
s && !s.startsWith("--") && e(r);
|
|
1083
1208
|
});
|
|
1084
1209
|
}
|
|
1085
1210
|
return document.querySelectorAll('[style*="var(--twd-"]').forEach((n) => e(n.getAttribute("style"))), t;
|
|
1086
1211
|
}
|
|
1087
|
-
const
|
|
1088
|
-
{ label: "With a build tool, in your entry file", code: `import '${
|
|
1212
|
+
const sn = [
|
|
1213
|
+
{ label: "With a build tool, in your entry file", code: `import '${on}/tokens.css';` },
|
|
1089
1214
|
// Pinned to this build's version: unversioned jsDelivr links can lag a release by days, and unpkg once failed for a fresh one.
|
|
1090
|
-
{ label: "Plain HTML, in <head>", code: `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/${
|
|
1091
|
-
],
|
|
1215
|
+
{ label: "Plain HTML, in <head>", code: `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/${on}@${Ma}/dist/twiddle-tokens.css">` }
|
|
1216
|
+
], Na = `Convert this prototype's styles to use the Panel design tokens (the --twd-* CSS custom properties from twiddle-panel), so the panel can tweak them live.
|
|
1092
1217
|
|
|
1093
1218
|
1. Keep the current look. In a :root rule loaded after twiddle-tokens.css, set each token below to the value this project already uses (for example, if buttons use #0f766e, set --twd-primary: #0f766e). Only set the tokens the project actually needs.
|
|
1094
1219
|
2. Replace fixed values in component styles with var(--twd-…). Map each value to the closest token by role:
|
|
1095
1220
|
- Fonts: --twd-font-heading, --twd-font-body
|
|
1096
1221
|
- Text sizes: --twd-text-xs, -sm, -base, -lg, -xl, -2xl, -3xl; line height: --twd-leading-tight, --twd-leading-normal
|
|
1222
|
+
- Font weights: --twd-weight-heading (headings, big numbers), --twd-weight-strong (buttons, labels, emphasis)
|
|
1097
1223
|
- Colours: --twd-bg (page), --twd-surface (cards, inputs), --twd-surface-raised (menus, popovers), --twd-text, --twd-text-muted, --twd-border, --twd-primary, --twd-primary-hover, --twd-on-primary (text on primary), --twd-focus, --twd-success, --twd-warning, --twd-danger
|
|
1098
1224
|
- Buttons, inputs, chips: --twd-radius-control, --twd-control-height, --twd-control-pad-x
|
|
1099
1225
|
- Cards, menus, modals: --twd-radius-container, --twd-border-width-container
|
|
1226
|
+
- Main button and default card, preferred where they fit: --twd-button-bg, --twd-button-fg, --twd-button-border, --twd-button-hover (hover background); --twd-card-bg, --twd-card-border, --twd-card-shadow
|
|
1100
1227
|
- Borders: --twd-border-width
|
|
1101
1228
|
- Spacing (padding, margin, gap): --twd-space-1 to --twd-space-8 (4, 8, 12, 16, 24, 32, 48, 64px)
|
|
1102
1229
|
- Layout roles, preferred where they fit: --twd-page-pad (side padding of the page), --twd-section-gap (between page sections), --twd-card-pad (inside cards, menus, modals), --twd-card-gap (between items in a card), --twd-control-gap (between buttons, chips, inputs in a row)
|
|
@@ -1109,309 +1236,783 @@ function B(t) {
|
|
|
1109
1236
|
e.className = "field";
|
|
1110
1237
|
const n = document.createElement("div");
|
|
1111
1238
|
n.className = "field-head";
|
|
1112
|
-
const o = document.createElement("span");
|
|
1113
|
-
o.className = "field-label", o.textContent = t;
|
|
1114
1239
|
const a = document.createElement("span");
|
|
1115
|
-
|
|
1240
|
+
a.className = "field-label", a.textContent = t;
|
|
1241
|
+
const o = document.createElement("span");
|
|
1242
|
+
return o.className = "field-value", o.hidden = !0, n.append(a, o), e.append(n), {
|
|
1116
1243
|
el: e,
|
|
1117
|
-
setValue(
|
|
1118
|
-
|
|
1244
|
+
setValue(r) {
|
|
1245
|
+
o.textContent = r || "", o.hidden = !r;
|
|
1119
1246
|
},
|
|
1120
|
-
append(...
|
|
1121
|
-
e.append(...
|
|
1247
|
+
append(...r) {
|
|
1248
|
+
e.append(...r);
|
|
1122
1249
|
}
|
|
1123
1250
|
};
|
|
1124
1251
|
}
|
|
1125
|
-
function
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
const
|
|
1129
|
-
const
|
|
1130
|
-
return
|
|
1252
|
+
function ja({ label: t, ends: e, cols: n = 9, rows: a = 5, onInput: o }) {
|
|
1253
|
+
const r = document.createElement("div");
|
|
1254
|
+
r.className = "pad", r.tabIndex = 0, r.setAttribute("role", "group"), r.setAttribute("aria-roledescription", "2D slider");
|
|
1255
|
+
const s = (b, u) => {
|
|
1256
|
+
const v = document.createElement("span");
|
|
1257
|
+
return v.className = `pad-edge ${b}`, v.textContent = u, v.setAttribute("aria-hidden", "true"), v;
|
|
1131
1258
|
};
|
|
1132
|
-
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
for (let
|
|
1259
|
+
r.append(s("top", e.top), s("bottom", e.bottom), s("left", e.left), s("right", e.right));
|
|
1260
|
+
const i = document.createElement("div");
|
|
1261
|
+
i.className = "pad-area";
|
|
1262
|
+
for (let b = 0; b < a; b++)
|
|
1136
1263
|
for (let u = 0; u < n; u++) {
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1264
|
+
const v = document.createElement("i");
|
|
1265
|
+
v.className = "pad-dot", v.style.left = `${u / (n - 1) * 100}%`, v.style.top = `${b / (a - 1) * 100}%`, i.append(v);
|
|
1139
1266
|
}
|
|
1140
1267
|
const l = document.createElement("span");
|
|
1141
|
-
l.className = "pad-knob",
|
|
1142
|
-
let
|
|
1143
|
-
const
|
|
1144
|
-
function
|
|
1145
|
-
l.style.left = `${
|
|
1146
|
-
}
|
|
1147
|
-
function f(
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
const
|
|
1151
|
-
const u =
|
|
1152
|
-
f((
|
|
1268
|
+
l.className = "pad-knob", i.append(l), r.append(i);
|
|
1269
|
+
let p = 0.5, c = 0.5;
|
|
1270
|
+
const h = (b, u) => Math.round(Math.min(1, Math.max(0, b)) * (u - 1)) / (u - 1);
|
|
1271
|
+
function y() {
|
|
1272
|
+
l.style.left = `${p * 100}%`, l.style.top = `${c * 100}%`;
|
|
1273
|
+
}
|
|
1274
|
+
function f(b, u, v) {
|
|
1275
|
+
b = v === "x" ? p : h(b, n), u = v === "y" ? c : h(u, a), !(b === p && u === c) && (p = b, c = u, y(), o(p, c));
|
|
1276
|
+
}
|
|
1277
|
+
const m = (b) => {
|
|
1278
|
+
const u = i.getBoundingClientRect();
|
|
1279
|
+
f((b.clientX - u.left) / u.width, (b.clientY - u.top) / u.height);
|
|
1153
1280
|
};
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
}),
|
|
1157
|
-
|
|
1281
|
+
r.addEventListener("pointerdown", (b) => {
|
|
1282
|
+
r.setPointerCapture(b.pointerId), r.classList.add("dragging"), m(b);
|
|
1283
|
+
}), r.addEventListener("pointermove", (b) => {
|
|
1284
|
+
r.hasPointerCapture(b.pointerId) && m(b);
|
|
1158
1285
|
});
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1286
|
+
const x = (b) => {
|
|
1287
|
+
r.hasPointerCapture(b.pointerId) && r.releasePointerCapture(b.pointerId), r.classList.remove("dragging");
|
|
1161
1288
|
};
|
|
1162
|
-
return
|
|
1163
|
-
const u = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] }[
|
|
1164
|
-
u && (
|
|
1289
|
+
return r.addEventListener("pointerup", x), r.addEventListener("pointercancel", x), r.addEventListener("keydown", (b) => {
|
|
1290
|
+
const u = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] }[b.key];
|
|
1291
|
+
u && (b.preventDefault(), u[0] ? f(h(p, n) + u[0] / (n - 1), c, "y") : f(p, h(c, a) + u[1] / (a - 1), "x"));
|
|
1165
1292
|
}), {
|
|
1166
|
-
el:
|
|
1293
|
+
el: r,
|
|
1167
1294
|
/** Show a position without telling anyone (used to sync). Off-grid values are shown as they are. */
|
|
1168
|
-
setValue(
|
|
1169
|
-
|
|
1295
|
+
setValue(b, u, v) {
|
|
1296
|
+
p = Math.min(1, Math.max(0, b)), c = Math.min(1, Math.max(0, u)), y(), r.setAttribute("aria-label", v ? `${t}: ${v}` : t);
|
|
1170
1297
|
}
|
|
1171
1298
|
};
|
|
1172
1299
|
}
|
|
1173
|
-
function
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1300
|
+
function Rn({ label: t, min: e, max: n, step: a = 1, format: o = String, onInput: r }) {
|
|
1301
|
+
const s = document.createElement("div");
|
|
1302
|
+
s.className = "slider";
|
|
1303
|
+
const i = document.createElement("input");
|
|
1304
|
+
i.type = "range", i.min = String(e), i.max = String(n), i.step = String(a), i.setAttribute("aria-label", t);
|
|
1178
1305
|
const l = document.createElement("span");
|
|
1179
|
-
l.className = "slider-value", l.setAttribute("aria-hidden", "true"),
|
|
1180
|
-
const
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1306
|
+
l.className = "slider-value", l.setAttribute("aria-hidden", "true"), s.append(i, l);
|
|
1307
|
+
const p = (c) => {
|
|
1308
|
+
const h = (Number(i.value) - e) / (n - e) * 100;
|
|
1309
|
+
i.style.setProperty("--fill", `${h}%`), l.textContent = c ?? o(Number(i.value)), i.setAttribute("aria-valuetext", l.textContent);
|
|
1183
1310
|
};
|
|
1184
|
-
return
|
|
1185
|
-
|
|
1311
|
+
return i.addEventListener("input", () => {
|
|
1312
|
+
p(), r(Number(i.value));
|
|
1186
1313
|
}), {
|
|
1187
|
-
el:
|
|
1314
|
+
el: s,
|
|
1188
1315
|
/** Show a value without reporting it (sync after reset / URL load). */
|
|
1189
|
-
setValue(
|
|
1190
|
-
|
|
1316
|
+
setValue(c, h) {
|
|
1317
|
+
i.value = String(c), p(h);
|
|
1191
1318
|
}
|
|
1192
1319
|
};
|
|
1193
1320
|
}
|
|
1321
|
+
function Ra({ label: t, options: e, columns: n = 3, className: a = "", onSelect: o }) {
|
|
1322
|
+
const r = document.createElement("div");
|
|
1323
|
+
r.className = `tiles ${a}`.trim(), r.style.setProperty("--cols", n), r.setAttribute("role", "radiogroup"), r.setAttribute("aria-label", t);
|
|
1324
|
+
const s = e.map((c) => {
|
|
1325
|
+
const h = document.createElement("button");
|
|
1326
|
+
return h.type = "button", h.className = "tile", c.wide && h.classList.add("wide"), h.setAttribute("role", "radio"), h.dataset.value = c.value, c.title && (h.title = c.title), h.innerHTML = `${c.preview ? `<span class="tile-preview" aria-hidden="true">${c.preview}</span>` : ""}<span class="tile-text"><span class="tile-label"></span>${c.sub ? '<span class="tile-sub"></span>' : ""}</span>`, h.querySelector(".tile-label").textContent = c.label, c.sub && (h.querySelector(".tile-sub").textContent = c.sub), h.addEventListener("click", () => p(c.value)), h;
|
|
1327
|
+
});
|
|
1328
|
+
r.append(...s);
|
|
1329
|
+
let i = null;
|
|
1330
|
+
function l(c) {
|
|
1331
|
+
i = c, s.forEach((y) => y.setAttribute("aria-checked", String(y.dataset.value === c)));
|
|
1332
|
+
const h = Math.max(0, s.findIndex((y) => y.dataset.value === c));
|
|
1333
|
+
s.forEach((y, f) => {
|
|
1334
|
+
y.tabIndex = f === h ? 0 : -1;
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
function p(c, h) {
|
|
1338
|
+
c !== i && (l(c), o(c)), h && s.find((y) => y.dataset.value === c).focus();
|
|
1339
|
+
}
|
|
1340
|
+
return r.addEventListener("keydown", (c) => {
|
|
1341
|
+
const h = s.indexOf(c.target.closest(".tile"));
|
|
1342
|
+
if (h < 0) return;
|
|
1343
|
+
const y = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[c.key];
|
|
1344
|
+
let f;
|
|
1345
|
+
if (y) f = (h + y + s.length) % s.length;
|
|
1346
|
+
else if (c.key === "Home") f = 0;
|
|
1347
|
+
else if (c.key === "End") f = s.length - 1;
|
|
1348
|
+
else return;
|
|
1349
|
+
c.preventDefault(), p(s[f].dataset.value, !0);
|
|
1350
|
+
}), l(null), { el: r, setValue: l };
|
|
1351
|
+
}
|
|
1194
1352
|
const F = (t) => getComputedStyle(document.documentElement).getPropertyValue(t).trim();
|
|
1195
|
-
function
|
|
1353
|
+
function ye(t, e) {
|
|
1196
1354
|
const n = /^(-?\d*\.?\d+)([a-z%]*)$/i.exec(String(t).trim());
|
|
1197
1355
|
if (!n) return null;
|
|
1198
|
-
const
|
|
1199
|
-
return
|
|
1200
|
-
}
|
|
1201
|
-
const
|
|
1202
|
-
function
|
|
1203
|
-
const c = (
|
|
1204
|
-
if (typeof
|
|
1205
|
-
return { [
|
|
1206
|
-
const
|
|
1207
|
-
return
|
|
1356
|
+
const a = parseFloat(n[1]) * e, o = n[2] || "px";
|
|
1357
|
+
return o === "px" ? `${Math.round(a)}px` : `${+a.toFixed(3)}${o}`;
|
|
1358
|
+
}
|
|
1359
|
+
const za = 999;
|
|
1360
|
+
function zn({ id: t, title: e, hint: n, token: a, uses: o = [], presets: r, max: s, extra: i = [], extraTokens: l = () => ({}), shape: p = "card" }) {
|
|
1361
|
+
const c = (f) => r.find((m) => m.value === f), h = (f) => f >= za ? "full" : `${f}px`, y = (f) => {
|
|
1362
|
+
if (typeof f.radius == "number" && isFinite(f.radius))
|
|
1363
|
+
return { [a]: `${Math.min(Math.max(f.radius, 0), s)}px` };
|
|
1364
|
+
const m = c(f.corners);
|
|
1365
|
+
return m ? { [a]: `${m.radius}px` } : {};
|
|
1208
1366
|
};
|
|
1209
1367
|
return {
|
|
1210
1368
|
id: t,
|
|
1211
1369
|
title: e,
|
|
1212
1370
|
hint: n,
|
|
1213
|
-
presets:
|
|
1214
|
-
uses: [
|
|
1215
|
-
tokens(
|
|
1216
|
-
const
|
|
1217
|
-
return { ...m
|
|
1371
|
+
presets: r,
|
|
1372
|
+
uses: [a, ...o],
|
|
1373
|
+
tokens(f) {
|
|
1374
|
+
const m = f[t] || {};
|
|
1375
|
+
return { ...y(m), ...l(m) };
|
|
1218
1376
|
},
|
|
1219
1377
|
// One render function per knob, so a layout can place them separately.
|
|
1220
1378
|
// Each takes an optional label (e.g. "Buttons" on the Corners page).
|
|
1221
1379
|
parts: {
|
|
1222
|
-
corners(
|
|
1223
|
-
const b = B(x),
|
|
1380
|
+
corners(f, m, { label: x = "Corners" } = {}) {
|
|
1381
|
+
const b = B(x), u = re({
|
|
1224
1382
|
label: `${e} corners`,
|
|
1225
|
-
options:
|
|
1226
|
-
onSelect: (
|
|
1227
|
-
}),
|
|
1383
|
+
options: r.map((E) => ({ value: E.value, label: E.label, title: h(E.radius) })),
|
|
1384
|
+
onSelect: (E) => m.setSection(t, { corners: E, radius: null })
|
|
1385
|
+
}), v = Rn({
|
|
1228
1386
|
label: `${e} corner radius`,
|
|
1229
1387
|
min: 0,
|
|
1230
|
-
max:
|
|
1231
|
-
format:
|
|
1232
|
-
onInput: (
|
|
1388
|
+
max: s,
|
|
1389
|
+
format: h,
|
|
1390
|
+
onInput: (E) => m.setSection(t, { radius: E, corners: null })
|
|
1233
1391
|
});
|
|
1234
|
-
b.append(
|
|
1235
|
-
const
|
|
1236
|
-
const
|
|
1237
|
-
if (
|
|
1238
|
-
|
|
1392
|
+
b.append(u.el, v.el), f.append(b.el);
|
|
1393
|
+
const L = () => {
|
|
1394
|
+
const E = parseFloat((m.readToken || F)(a)), J = m.getState()[t] || {}, K = c(J.corners) || r.find((G) => G.radius === E);
|
|
1395
|
+
if (u.setValue(K ? K.value : null), isNaN(E)) {
|
|
1396
|
+
v.setValue(0, "—"), b.setValue("");
|
|
1239
1397
|
return;
|
|
1240
1398
|
}
|
|
1241
|
-
|
|
1399
|
+
v.setValue(Math.min(E, s), h(E)), b.setValue(h(E));
|
|
1242
1400
|
};
|
|
1243
|
-
|
|
1401
|
+
L(), m.onChange(L);
|
|
1402
|
+
},
|
|
1403
|
+
...Object.fromEntries(i.map((f) => [f.key, (m, x, { label: b } = {}) => m.append(ke(x, { section: t, ...f, label: b || f.label }))])),
|
|
1404
|
+
// Tabbed layout: presets as tiles showing the real rounding.
|
|
1405
|
+
cornerTiles(f, m, { label: x = "Corners" } = {}) {
|
|
1406
|
+
const b = (u) => `<span class="shape shape-${p}" style="border-radius:${Math.min(u, p === "button" ? 12 : 16)}px"></span>`;
|
|
1407
|
+
f.append(V(m, {
|
|
1408
|
+
section: t,
|
|
1409
|
+
key: "corners",
|
|
1410
|
+
label: x,
|
|
1411
|
+
columns: r.length,
|
|
1412
|
+
options: r.map((u) => ({ value: u.value, label: u.label, title: h(u.radius), preview: b(u.radius) })),
|
|
1413
|
+
current: (u) => {
|
|
1414
|
+
const v = u[t] || {};
|
|
1415
|
+
if (v.radius != null) return null;
|
|
1416
|
+
const L = parseFloat((m.readToken || F)(a));
|
|
1417
|
+
return (c(v.corners) || r.find((E) => E.radius === L))?.value || null;
|
|
1418
|
+
},
|
|
1419
|
+
select: (u) => m.setSection(t, { corners: u, radius: null }),
|
|
1420
|
+
valueText: () => {
|
|
1421
|
+
const u = parseFloat((m.readToken || F)(a));
|
|
1422
|
+
return isNaN(u) ? "" : h(u);
|
|
1423
|
+
}
|
|
1424
|
+
}));
|
|
1244
1425
|
},
|
|
1245
|
-
...Object.fromEntries(
|
|
1426
|
+
...Object.fromEntries(i.filter((f) => f.preview).map((f) => [`${f.key}Tiles`, (m, x, { label: b } = {}) => m.append(V(x, {
|
|
1427
|
+
section: t,
|
|
1428
|
+
key: f.key,
|
|
1429
|
+
label: b || f.label,
|
|
1430
|
+
fallback: f.fallback,
|
|
1431
|
+
columns: f.columns || f.options.length,
|
|
1432
|
+
options: f.options.map((u) => ({ ...u, preview: f.preview(u.value) }))
|
|
1433
|
+
}))]))
|
|
1246
1434
|
},
|
|
1247
|
-
render(
|
|
1248
|
-
Object.
|
|
1435
|
+
render(f, m) {
|
|
1436
|
+
Object.entries(this.parts).filter(([x]) => !x.endsWith("Tiles")).forEach(([, x]) => x(f, m));
|
|
1249
1437
|
}
|
|
1250
1438
|
};
|
|
1251
1439
|
}
|
|
1252
|
-
function
|
|
1253
|
-
const
|
|
1254
|
-
label:
|
|
1255
|
-
options:
|
|
1256
|
-
onSelect: (
|
|
1440
|
+
function ke(t, { section: e, key: n, label: a, options: o, fallback: r, valueText: s }) {
|
|
1441
|
+
const i = B(a), l = re({
|
|
1442
|
+
label: a,
|
|
1443
|
+
options: o,
|
|
1444
|
+
onSelect: (c) => t.setSection(e, { [n]: c })
|
|
1257
1445
|
});
|
|
1258
|
-
|
|
1259
|
-
const
|
|
1260
|
-
const
|
|
1261
|
-
l.setValue(
|
|
1446
|
+
i.append(l.el);
|
|
1447
|
+
const p = () => {
|
|
1448
|
+
const c = (t.getState()[e] || {})[n] || r;
|
|
1449
|
+
l.setValue(c), i.setValue(s ? s(c) : "");
|
|
1262
1450
|
};
|
|
1263
|
-
return
|
|
1451
|
+
return p(), t.onChange(p), i.el;
|
|
1264
1452
|
}
|
|
1265
|
-
function
|
|
1266
|
-
const l = B(
|
|
1267
|
-
l.append(
|
|
1268
|
-
const
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1453
|
+
function Ia(t, { section: e, key: n, label: a, min: o, max: r, fallback: s, format: i }) {
|
|
1454
|
+
const l = B(a), p = Rn({ label: a, min: o, max: r, format: i, onInput: (h) => t.setSection(e, { [n]: h }) });
|
|
1455
|
+
l.append(p.el);
|
|
1456
|
+
const c = () => {
|
|
1457
|
+
const h = (t.getState()[e] || {})[n];
|
|
1458
|
+
p.setValue(typeof h == "number" ? h : s);
|
|
1459
|
+
};
|
|
1460
|
+
return c(), t.onChange(c), l.el;
|
|
1461
|
+
}
|
|
1462
|
+
function V(t, { section: e, key: n, label: a, options: o, columns: r = 3, className: s = "", fallback: i, valueText: l, current: p, select: c }) {
|
|
1463
|
+
const h = B(a), y = Ra({
|
|
1464
|
+
label: a,
|
|
1465
|
+
options: o,
|
|
1466
|
+
columns: r,
|
|
1467
|
+
className: s,
|
|
1468
|
+
onSelect: (m) => c ? c(m) : t.setSection(e, { [n]: m })
|
|
1469
|
+
});
|
|
1470
|
+
h.append(y.el);
|
|
1471
|
+
const f = () => {
|
|
1472
|
+
const m = t.getState(), x = p ? p(m) : (m[e] || {})[n] || i || null;
|
|
1473
|
+
y.setValue(x), h.setValue(l ? l(x, m) : "");
|
|
1271
1474
|
};
|
|
1272
|
-
return
|
|
1475
|
+
return f(), t.onChange(f), h.el;
|
|
1273
1476
|
}
|
|
1274
|
-
const
|
|
1477
|
+
const ln = (t, e) => e.map((n) => `${t}${n}`), In = {
|
|
1275
1478
|
fonts: { label: "Tokens", tokens: ["font-heading", "font-body"], kind: "text" },
|
|
1276
|
-
type: { label: "Tokens", tokens:
|
|
1479
|
+
type: { label: "Tokens", tokens: ln("text-", ["xs", "sm", "base", "lg", "xl", "2xl", "3xl"]), kind: "length" },
|
|
1277
1480
|
leading: { label: "Line height", tokens: ["leading-tight", "leading-normal"], kind: "number" },
|
|
1481
|
+
weights: { label: "Weights", tokens: ["weight-heading", "weight-strong"], kind: "number" },
|
|
1278
1482
|
colours: { label: "Tokens", tokens: ["bg", "surface", "surface-raised", "text", "text-muted", "border", "primary", "primary-hover", "on-primary", "focus", "success", "warning", "danger"], kind: "colour" },
|
|
1279
|
-
spacing: { label: "Steps", tokens:
|
|
1483
|
+
spacing: { label: "Steps", tokens: ln("space-", [1, 2, 3, 4, 5, 6, 7, 8]), kind: "length", bar: 64 },
|
|
1280
1484
|
controls: { label: "Controls", tokens: ["control-height", "control-pad-x"], kind: "length" },
|
|
1281
1485
|
corners: { label: "Tokens", tokens: ["radius-control", "radius-container"], kind: "length" },
|
|
1282
1486
|
borders: { label: "Tokens", tokens: ["border-width", "border-width-container"], kind: "length" },
|
|
1283
|
-
shadows: { label: "Tokens", tokens: ["shadow-sm", "shadow-md", "shadow-lg"], kind: "text" }
|
|
1284
|
-
|
|
1285
|
-
|
|
1487
|
+
shadows: { label: "Tokens", tokens: ["shadow-sm", "shadow-md", "shadow-lg"], kind: "text" },
|
|
1488
|
+
buttonStyle: { label: "Button", tokens: ["button-bg", "button-fg", "button-border", "button-hover"], kind: "colour" },
|
|
1489
|
+
cardStyle: { label: "Card", tokens: ["card-bg", "card-border"], kind: "colour" },
|
|
1490
|
+
cardShadow: { label: "Card shadow", tokens: ["card-shadow"], kind: "text" }
|
|
1491
|
+
}, _ = (t) => `--twd-${t}`, Qe = (t, e) => Object.prototype.hasOwnProperty.call(t.getState().custom || {}, e), dn = (t, e) => In[e].tokens.some((n) => (t.custom || {})[n] != null);
|
|
1492
|
+
function Da(t, e) {
|
|
1286
1493
|
const n = /^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());
|
|
1287
1494
|
return !n || e === "text" || e === "colour" ? null : n[2] === "rem" ? { n: +(parseFloat(n[1]) * 16).toFixed(1), unit: "px" } : { n: +parseFloat(n[1]).toFixed(3), unit: n[2] || (e === "length" ? "px" : "") };
|
|
1288
1495
|
}
|
|
1289
|
-
function
|
|
1496
|
+
function Ba(t, e) {
|
|
1290
1497
|
const n = t.trim();
|
|
1291
1498
|
return n ? e === "length" && /^-?\d*\.?\d+$/.test(n) ? `${n}px` : n : null;
|
|
1292
1499
|
}
|
|
1293
|
-
function
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1296
|
-
<code class="token-name" title="${
|
|
1297
|
-
${
|
|
1500
|
+
function Ua(t, e, n, a) {
|
|
1501
|
+
const o = document.createElement("div");
|
|
1502
|
+
o.className = `token-row kind-${n}`, o.innerHTML = `
|
|
1503
|
+
<code class="token-name" title="${_(e)}">${e}</code>
|
|
1504
|
+
${a ? '<span class="token-bar"><span></span></span>' : '<span class="token-gap"></span>'}
|
|
1298
1505
|
<span class="custom-tag" hidden>Custom</span>
|
|
1299
1506
|
<label class="token-value">
|
|
1300
1507
|
${n === "colour" ? '<span class="token-swatch"></span>' : ""}
|
|
1301
|
-
<input type="text" spellcheck="false" aria-label="${
|
|
1508
|
+
<input type="text" spellcheck="false" aria-label="${_(e)}" ${n === "length" || n === "number" ? 'inputmode="decimal"' : ""}>
|
|
1302
1509
|
<span class="token-unit"></span>
|
|
1303
1510
|
</label>
|
|
1304
|
-
<button class="icon-btn token-reset" title="Back to the page’s value" aria-label="Reset ${
|
|
1511
|
+
<button class="icon-btn token-reset" title="Back to the page’s value" aria-label="Reset ${_(e)}" hidden>${A.reset}</button>
|
|
1305
1512
|
`;
|
|
1306
|
-
const
|
|
1307
|
-
let
|
|
1308
|
-
const
|
|
1309
|
-
let
|
|
1310
|
-
|
|
1311
|
-
const
|
|
1312
|
-
if (
|
|
1313
|
-
|
|
1513
|
+
const r = o.querySelector("input"), s = o.querySelector(".token-unit"), i = o.querySelector(".token-bar span"), l = o.querySelector(".token-swatch"), p = o.querySelector(".custom-tag"), c = o.querySelector(".token-reset");
|
|
1514
|
+
let h = "";
|
|
1515
|
+
const y = () => {
|
|
1516
|
+
let m = r.value;
|
|
1517
|
+
h && /^-?\d*\.?\d+$/.test(m.trim()) && (m = `${m.trim()}${h}`);
|
|
1518
|
+
const x = Ba(m, n), b = F(_(e));
|
|
1519
|
+
if (x === null) {
|
|
1520
|
+
Qe(t, e) ? t.setSection("custom", { [e]: null }) : f();
|
|
1314
1521
|
return;
|
|
1315
1522
|
}
|
|
1316
|
-
if (
|
|
1317
|
-
t.setSection("custom", { [e]:
|
|
1523
|
+
if (x === b && !Qe(t, e)) return f();
|
|
1524
|
+
t.setSection("custom", { [e]: x });
|
|
1318
1525
|
};
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
}),
|
|
1526
|
+
r.addEventListener("change", y), r.addEventListener("blur", () => f()), r.addEventListener("keydown", (m) => {
|
|
1527
|
+
m.key === "Enter" && (m.preventDefault(), r.blur()), m.key === "Escape" && (m.stopPropagation(), f(), r.blur());
|
|
1528
|
+
}), c.addEventListener("click", () => t.setSection("custom", { [e]: null }));
|
|
1322
1529
|
function f() {
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1530
|
+
const m = F(_(e)), x = Qe(t, e), b = Da(m, n);
|
|
1531
|
+
h = b ? b.unit : "", document.activeElement !== r && o.getRootNode().activeElement !== r && (r.value = b ? String(b.n) : m), r.title = m, s.textContent = h, s.hidden = !h, i && (i.style.width = `${Math.max(2, Math.min(100, (b ? b.n : 0) / a * 100))}%`), l && (l.style.background = m), o.classList.toggle("edited", x), p.hidden = !x, c.hidden = !x;
|
|
1325
1532
|
}
|
|
1326
|
-
return f(), t.onChange(f),
|
|
1533
|
+
return f(), t.onChange(f), o;
|
|
1327
1534
|
}
|
|
1328
|
-
const
|
|
1535
|
+
const qa = {
|
|
1329
1536
|
id: "custom",
|
|
1330
1537
|
title: "Custom tokens",
|
|
1331
1538
|
hint: "Any token, set to a fixed value",
|
|
1332
1539
|
uses: [],
|
|
1333
1540
|
// not offered for a single picked element (yet)
|
|
1334
1541
|
tokens(t) {
|
|
1335
|
-
return Object.fromEntries(Object.entries(t.custom || {}).map(([e, n]) => [
|
|
1542
|
+
return Object.fromEntries(Object.entries(t.custom || {}).map(([e, n]) => [_(e), n]));
|
|
1336
1543
|
},
|
|
1337
|
-
describe: (t, e) => ({ label:
|
|
1544
|
+
describe: (t, e) => ({ label: _(t), value: String(e) }),
|
|
1338
1545
|
// One part per Edit page group, e.g. parts.spacing → rows for --twd-space-1 … 8.
|
|
1339
|
-
parts: Object.fromEntries(Object.entries(
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1546
|
+
parts: Object.fromEntries(Object.entries(In).map(([t, e]) => [t, (n, a, { label: o = e.label } = {}) => {
|
|
1547
|
+
const r = B(o);
|
|
1548
|
+
r.el.classList.add("token-field");
|
|
1549
|
+
const s = e.tokens[0], i = e.tokens[e.tokens.length - 1];
|
|
1550
|
+
r.setValue(e.tokens.length > 2 && /-\w+$/.test(s) ? `${_(s)} … ${i.split("-").pop()}` : "");
|
|
1344
1551
|
const l = document.createElement("div");
|
|
1345
|
-
l.className = "token-rows", e.tokens.forEach((
|
|
1552
|
+
l.className = "token-rows", e.tokens.forEach((p) => l.append(Ua(a, p, e.kind, e.bar))), r.append(l), n.append(r.el);
|
|
1346
1553
|
}]))
|
|
1347
|
-
},
|
|
1348
|
-
function
|
|
1349
|
-
return typeof t.factor == "number" && isFinite(t.factor) ? Math.min(
|
|
1554
|
+
}, we = { compact: 0.75, regular: 1, comfy: 1.25 }, xt = [0.75, 1.25], cn = [1, 2, 3, 4, 5, 6, 7, 8].map((t) => `--twd-space-${t}`);
|
|
1555
|
+
function te(t = {}) {
|
|
1556
|
+
return typeof t.factor == "number" && isFinite(t.factor) ? Math.min(xt[1], Math.max(xt[0], t.factor)) : we[t.spacing] || 1;
|
|
1350
1557
|
}
|
|
1351
|
-
const
|
|
1558
|
+
const et = (t) => `${Math.round(t * 100)}%`, Va = {
|
|
1352
1559
|
id: "density",
|
|
1353
1560
|
title: "Density",
|
|
1354
1561
|
hint: "Compact · regular · comfortable",
|
|
1355
|
-
uses:
|
|
1562
|
+
uses: cn,
|
|
1356
1563
|
tokens(t) {
|
|
1357
|
-
const e =
|
|
1564
|
+
const e = te(t.density);
|
|
1358
1565
|
if (e === 1) return {};
|
|
1359
1566
|
const n = {};
|
|
1360
|
-
return
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1567
|
+
return cn.forEach((a) => {
|
|
1568
|
+
const o = ye(F(a), e);
|
|
1569
|
+
o && (n[a] = o);
|
|
1363
1570
|
}), n;
|
|
1364
1571
|
},
|
|
1365
1572
|
/** How the Changes list and prompt describe a setting. */
|
|
1366
|
-
describe: (t, e) => t === "factor" ? { label: "Spacing", value:
|
|
1573
|
+
describe: (t, e) => t === "factor" ? { label: "Spacing", value: et(e) } : null,
|
|
1367
1574
|
parts: {
|
|
1575
|
+
// Tabbed layout: presets as tiles with a tiny layout sketch.
|
|
1576
|
+
spacingTiles(t, e, { label: n = "Density" } = {}) {
|
|
1577
|
+
const a = (o, r) => `<span class="sketch" style="padding:${r}px;gap:${o}px"><i></i><i></i><i></i></span>`;
|
|
1578
|
+
t.append(V(e, {
|
|
1579
|
+
section: "density",
|
|
1580
|
+
key: "spacing",
|
|
1581
|
+
label: n,
|
|
1582
|
+
options: [{ value: "compact", label: "Compact", preview: a(2, 5) }, { value: "regular", label: "Regular", preview: a(4, 8) }, { value: "comfy", label: "Comfy", preview: a(6, 11) }],
|
|
1583
|
+
current: (o) => {
|
|
1584
|
+
const r = te(o.density);
|
|
1585
|
+
return Object.keys(we).find((s) => we[s] === r) || null;
|
|
1586
|
+
},
|
|
1587
|
+
select: (o) => e.setSection("density", { spacing: o, factor: null }),
|
|
1588
|
+
valueText: (o, r) => dn(r, "spacing") ? "Custom" : o ? "" : et(te(r.density))
|
|
1589
|
+
}));
|
|
1590
|
+
},
|
|
1368
1591
|
spacing(t, e, { label: n = "Spacing" } = {}) {
|
|
1369
|
-
const
|
|
1592
|
+
const a = B(n), o = re({
|
|
1370
1593
|
label: n,
|
|
1371
1594
|
options: [{ value: "compact", label: "Compact" }, { value: "regular", label: "Regular" }, { value: "comfy", label: "Comfy" }],
|
|
1372
|
-
onSelect: (
|
|
1595
|
+
onSelect: (s) => e.setSection("density", { spacing: s, factor: null })
|
|
1373
1596
|
});
|
|
1374
|
-
|
|
1375
|
-
const
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1597
|
+
a.append(o.el);
|
|
1598
|
+
const r = () => {
|
|
1599
|
+
const s = e.getState().density || {}, i = te(s), l = Object.keys(we).find((p) => we[p] === i);
|
|
1600
|
+
o.setValue(l || null), a.setValue(dn(e.getState(), "spacing") ? "Custom" : l ? "" : et(i));
|
|
1378
1601
|
};
|
|
1379
|
-
|
|
1602
|
+
r(), e.onChange(r), t.append(a.el);
|
|
1380
1603
|
}
|
|
1381
1604
|
},
|
|
1382
1605
|
render(t, e) {
|
|
1383
|
-
Object.
|
|
1606
|
+
Object.entries(this.parts).filter(([n]) => !n.endsWith("Tiles")).forEach(([, n]) => n(t, e));
|
|
1607
|
+
}
|
|
1608
|
+
}, Ha = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 6l4 4M4 20l1-4 9.5-9.5a2.1 2.1 0 013 3L8 19l-4 1z"/></svg>', _a = [
|
|
1609
|
+
[50, "white", 10],
|
|
1610
|
+
[100, "white", 20],
|
|
1611
|
+
[200, "white", 36],
|
|
1612
|
+
[300, "white", 56],
|
|
1613
|
+
[400, "white", 78],
|
|
1614
|
+
[500, null, 100],
|
|
1615
|
+
[600, "black", 86],
|
|
1616
|
+
[700, "black", 72],
|
|
1617
|
+
[800, "black", 58],
|
|
1618
|
+
[900, "black", 45],
|
|
1619
|
+
[950, "black", 30]
|
|
1620
|
+
], tt = (t) => {
|
|
1621
|
+
let e = String(t).trim().replace(/^#/, "");
|
|
1622
|
+
return /^[0-9a-f]{3}$/i.test(e) && (e = e.split("").map((n) => n + n).join("")), /^[0-9a-f]{6}$/i.test(e) ? `#${e.toUpperCase()}` : null;
|
|
1623
|
+
};
|
|
1624
|
+
function Ka({ label: t, onInput: e }) {
|
|
1625
|
+
const n = document.createElement("div");
|
|
1626
|
+
n.className = "colour";
|
|
1627
|
+
const a = document.createElement("div");
|
|
1628
|
+
a.className = "colour-field";
|
|
1629
|
+
const o = document.createElement("input");
|
|
1630
|
+
o.type = "color", o.className = "colour-picker", o.tabIndex = -1, o.setAttribute("aria-hidden", "true");
|
|
1631
|
+
const r = document.createElement("button");
|
|
1632
|
+
r.type = "button", r.className = "colour-swatch", r.setAttribute("aria-label", `${t}: open colour picker`);
|
|
1633
|
+
const s = document.createElement("input");
|
|
1634
|
+
s.type = "text", s.className = "colour-hex", s.spellcheck = !1, s.maxLength = 7, s.setAttribute("aria-label", `${t} hex value`);
|
|
1635
|
+
const i = document.createElement("button");
|
|
1636
|
+
i.type = "button", i.className = "colour-pipette", i.innerHTML = Ha, i.title = "Pick a colour from the screen", i.setAttribute("aria-label", "Pick a colour from the screen"), a.append(o, r, s, i);
|
|
1637
|
+
const l = document.createElement("div");
|
|
1638
|
+
l.className = "ramp", l.setAttribute("aria-hidden", "true"), l.innerHTML = _a.map(([f, m, x]) => {
|
|
1639
|
+
const b = m ? `color-mix(in oklch, var(--brand) ${x}%, ${m})` : "var(--brand)";
|
|
1640
|
+
return `<span title="${f}" style="background:${b}"></span>`;
|
|
1641
|
+
}).join(""), n.append(a, l);
|
|
1642
|
+
let p = "#000000";
|
|
1643
|
+
const c = (f) => {
|
|
1644
|
+
p = f, o.value = f.toLowerCase(), r.style.background = f, n.getRootNode().activeElement !== s && (s.value = f), n.style.setProperty("--brand", f);
|
|
1645
|
+
}, h = (f) => {
|
|
1646
|
+
f !== p && (c(f), e(f));
|
|
1647
|
+
};
|
|
1648
|
+
r.addEventListener("click", () => {
|
|
1649
|
+
if (o.showPicker)
|
|
1650
|
+
try {
|
|
1651
|
+
o.showPicker();
|
|
1652
|
+
return;
|
|
1653
|
+
} catch {
|
|
1654
|
+
}
|
|
1655
|
+
o.click();
|
|
1656
|
+
}), o.addEventListener("input", () => h(o.value.toUpperCase())), s.addEventListener("input", () => {
|
|
1657
|
+
const f = tt(s.value);
|
|
1658
|
+
f && h(f);
|
|
1659
|
+
});
|
|
1660
|
+
const y = () => {
|
|
1661
|
+
s.value = p;
|
|
1662
|
+
};
|
|
1663
|
+
return s.addEventListener("blur", y), s.addEventListener("keydown", (f) => {
|
|
1664
|
+
f.key === "Enter" && (y(), s.select());
|
|
1665
|
+
}), i.addEventListener("click", async () => {
|
|
1666
|
+
if ("EyeDropper" in window)
|
|
1667
|
+
try {
|
|
1668
|
+
const { sRGBHex: f } = await new window.EyeDropper().open(), m = tt(f);
|
|
1669
|
+
m && h(m);
|
|
1670
|
+
} catch {
|
|
1671
|
+
}
|
|
1672
|
+
else
|
|
1673
|
+
r.click();
|
|
1674
|
+
}), { el: n, setValue: (f) => c(tt(f) || p) };
|
|
1675
|
+
}
|
|
1676
|
+
function Ga({ label: t, options: e, onSelect: n }) {
|
|
1677
|
+
const a = document.createElement("div");
|
|
1678
|
+
a.className = "swatches", a.setAttribute("role", "radiogroup"), a.setAttribute("aria-label", t);
|
|
1679
|
+
const o = e.map((l) => {
|
|
1680
|
+
const p = document.createElement("button");
|
|
1681
|
+
return p.type = "button", p.className = "swatch", p.setAttribute("role", "radio"), p.dataset.value = l.value, p.innerHTML = `<span class="swatch-ring"><span class="swatch-dot" style="background:${l.colour}"></span></span><span class="swatch-label">${l.label}</span>`, p.addEventListener("click", () => i(l.value)), p;
|
|
1682
|
+
});
|
|
1683
|
+
a.append(...o);
|
|
1684
|
+
let r = null;
|
|
1685
|
+
function s(l) {
|
|
1686
|
+
r = l, o.forEach((c) => c.setAttribute("aria-checked", String(c.dataset.value === l)));
|
|
1687
|
+
const p = Math.max(0, o.findIndex((c) => c.dataset.value === l));
|
|
1688
|
+
o.forEach((c, h) => {
|
|
1689
|
+
c.tabIndex = h === p ? 0 : -1;
|
|
1690
|
+
});
|
|
1384
1691
|
}
|
|
1385
|
-
|
|
1692
|
+
function i(l, p) {
|
|
1693
|
+
l !== r && (s(l), n(l)), p && o.find((c) => c.dataset.value === l).focus();
|
|
1694
|
+
}
|
|
1695
|
+
return a.addEventListener("keydown", (l) => {
|
|
1696
|
+
const p = o.indexOf(l.target.closest(".swatch"));
|
|
1697
|
+
if (p < 0) return;
|
|
1698
|
+
const c = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[l.key];
|
|
1699
|
+
let h;
|
|
1700
|
+
if (c) h = (p + c + o.length) % o.length;
|
|
1701
|
+
else if (l.key === "Home") h = 0;
|
|
1702
|
+
else if (l.key === "End") h = o.length - 1;
|
|
1703
|
+
else return;
|
|
1704
|
+
l.preventDefault(), i(o[h].dataset.value, !0);
|
|
1705
|
+
}), s(null), { el: a, setValue: s };
|
|
1706
|
+
}
|
|
1707
|
+
const Wa = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950], me = (t) => Object.fromEntries(t.split(" ").map((e, n) => [Wa[n], `#${e}`])), nt = {
|
|
1708
|
+
slate: me("f8fafc f1f5f9 e2e8f0 cbd5e1 94a3b8 64748b 475569 334155 1e293b 0f172a 020617"),
|
|
1709
|
+
gray: me("f9fafb f3f4f6 e5e7eb d1d5db 9ca3af 6b7280 4b5563 374151 1f2937 111827 030712"),
|
|
1710
|
+
zinc: me("fafafa f4f4f5 e4e4e7 d4d4d8 a1a1aa 71717a 52525b 3f3f46 27272a 18181b 09090b"),
|
|
1711
|
+
neutral: me("fafafa f5f5f5 e5e5e5 d4d4d4 a3a3a3 737373 525252 404040 262626 171717 0a0a0a"),
|
|
1712
|
+
stone: me("fafaf9 f5f5f4 e7e5e4 d6d3d1 a8a29e 78716c 57534e 44403c 292524 1c1917 0c0a09")
|
|
1713
|
+
}, N = (t) => Object.fromEntries(t.split(" ").map((e, n) => [400 + 100 * n, `#${e}`])), at = {
|
|
1714
|
+
red: N("f87171 ef4444 dc2626 b91c1c 991b1b"),
|
|
1715
|
+
orange: N("fb923c f97316 ea580c c2410c 9a3412"),
|
|
1716
|
+
amber: N("fbbf24 f59e0b d97706 b45309 92400e"),
|
|
1717
|
+
yellow: N("facc15 eab308 ca8a04 a16207 854d0e"),
|
|
1718
|
+
lime: N("a3e635 84cc16 65a30d 4d7c0f 3f6212"),
|
|
1719
|
+
green: N("4ade80 22c55e 16a34a 15803d 166534"),
|
|
1720
|
+
emerald: N("34d399 10b981 059669 047857 065f46"),
|
|
1721
|
+
teal: N("2dd4bf 14b8a6 0d9488 0f766e 115e59"),
|
|
1722
|
+
cyan: N("22d3ee 06b6d4 0891b2 0e7490 155e75"),
|
|
1723
|
+
sky: N("38bdf8 0ea5e9 0284c7 0369a1 075985"),
|
|
1724
|
+
blue: N("60a5fa 3b82f6 2563eb 1d4ed8 1e40af"),
|
|
1725
|
+
indigo: N("818cf8 6366f1 4f46e5 4338ca 3730a3"),
|
|
1726
|
+
violet: N("a78bfa 8b5cf6 7c3aed 6d28d9 5b21b6"),
|
|
1727
|
+
purple: N("c084fc a855f7 9333ea 7e22ce 6b21a8"),
|
|
1728
|
+
fuchsia: N("e879f9 d946ef c026d3 a21caf 86198f"),
|
|
1729
|
+
pink: N("f472b6 ec4899 db2777 be185d 9d174d"),
|
|
1730
|
+
rose: N("fb7185 f43f5e e11d48 be123c 9f1239")
|
|
1731
|
+
}, ze = [
|
|
1732
|
+
{ value: "indigo-zinc", label: "Indigo · Zinc", primary: "indigo", neutral: "zinc" },
|
|
1733
|
+
{ value: "emerald-stone", label: "Emerald · Stone", primary: "emerald", neutral: "stone" },
|
|
1734
|
+
{ value: "rose-slate", label: "Rose · Slate", primary: "rose", neutral: "slate" },
|
|
1735
|
+
{ value: "amber-neutral", label: "Amber · Neutral", primary: "amber", neutral: "neutral" }
|
|
1736
|
+
], pn = "#4F46E5", Ya = "#A0743C", Za = [
|
|
1737
|
+
// Figma Swatch row sample colours
|
|
1738
|
+
{ value: "pure", label: "Pure", colour: "#8E8E93" },
|
|
1739
|
+
{ value: "subtle", label: "Subtle", colour: "#8A89A6" },
|
|
1740
|
+
{ value: "warm", label: "Warm", colour: "#9C9389" }
|
|
1741
|
+
], un = ["bg", "surface", "surface-raised", "text", "text-muted", "border"], Xa = {
|
|
1742
|
+
// proposal
|
|
1743
|
+
bg: "#0F0F11",
|
|
1744
|
+
surface: "#18181B",
|
|
1745
|
+
"surface-raised": "#1F1F23",
|
|
1746
|
+
text: "#F4F4F5",
|
|
1747
|
+
"text-muted": "#A1A1AA",
|
|
1748
|
+
border: "#2E2E33"
|
|
1749
|
+
}, Ja = { bg: 4, surface: 2, "surface-raised": 2, text: 5, "text-muted": 12, border: 10 }, Y = (t) => /^#[0-9a-f]{6}$/i.test(t || ""), ot = (t) => [500, 600, 700].includes(Number(t.shade)) ? Number(t.shade) : 600, rt = (t) => t[0].toUpperCase() + t.slice(1);
|
|
1750
|
+
function hn(t) {
|
|
1751
|
+
const e = (l) => (l /= 255, l <= 0.03928 ? l / 12.92 : ((l + 0.055) / 1.055) ** 2.4), [n, a, o] = [1, 3, 5].map((l) => e(parseInt(t.slice(l, l + 2), 16))), r = 0.2126 * n + 0.7152 * a + 0.0722 * o, s = 1.05 / (r + 0.05), i = (r + 0.05) / (98e-4 + 0.05);
|
|
1752
|
+
return s >= i ? "#FFFFFF" : "#18181B";
|
|
1753
|
+
}
|
|
1754
|
+
const De = {
|
|
1755
|
+
id: "colour",
|
|
1756
|
+
title: "Colour",
|
|
1757
|
+
hint: "Brand colour · neutral tint · mode",
|
|
1758
|
+
uses: ["--twd-primary", "--twd-primary-hover", "--twd-on-primary", "--twd-focus", ...un.map((t) => `--twd-${t}`)],
|
|
1759
|
+
tokens(t) {
|
|
1760
|
+
const e = t.colour || {}, n = {}, a = e.mode === "dark", o = at[e.primary];
|
|
1761
|
+
if (!Y(e.brand) && o) {
|
|
1762
|
+
const i = ot(e);
|
|
1763
|
+
n["--twd-primary"] = o[i], n["--twd-primary-hover"] = o[a ? i - 100 : i + 100], n["--twd-on-primary"] = hn(o[i]), n["--twd-focus"] = o[500];
|
|
1764
|
+
}
|
|
1765
|
+
Y(e.brand) && (n["--twd-primary"] = e.brand, n["--twd-primary-hover"] = a ? `color-mix(in oklch, ${e.brand} 85%, white)` : `color-mix(in oklch, ${e.brand} 86%, black)`, n["--twd-on-primary"] = hn(e.brand), n["--twd-focus"] = e.brand);
|
|
1766
|
+
const r = e.tint === "subtle" ? Y(e.brand) ? e.brand : F("--twd-primary") || pn : e.tint === "warm" ? Ya : null, s = nt[e.neutral];
|
|
1767
|
+
if (s) {
|
|
1768
|
+
const i = a ? { bg: s[950], surface: s[900], "surface-raised": s[800], text: s[100], "text-muted": s[400], border: s[800] } : { bg: s[50], surface: "#ffffff", "surface-raised": "#ffffff", text: s[900], "text-muted": s[500], border: s[200] };
|
|
1769
|
+
Object.entries(i).forEach(([l, p]) => {
|
|
1770
|
+
n[`--twd-${l}`] = p;
|
|
1771
|
+
});
|
|
1772
|
+
} else (a || r) && un.forEach((i) => {
|
|
1773
|
+
const l = a ? Xa[i] : F(`--twd-${i}`);
|
|
1774
|
+
l && (n[`--twd-${i}`] = r ? `color-mix(in oklch, ${l} ${100 - Ja[i]}%, ${r})` : l);
|
|
1775
|
+
});
|
|
1776
|
+
return n;
|
|
1777
|
+
},
|
|
1778
|
+
parts: {
|
|
1779
|
+
// ---- Tabbed layout (Colour tab): themes, then Tailwind fine-tuning ----
|
|
1780
|
+
themeTiles(t, e, { label: n = "Theme" } = {}) {
|
|
1781
|
+
const a = (o) => {
|
|
1782
|
+
const r = nt[o.neutral], s = at[o.primary];
|
|
1783
|
+
return `<span class="theme" style="--bg:${r[50]};--line:${r[200]};--ink:${r[900]};--soft:${r[500]};--pri:${s[600]}"><i><b></b><s></s><u></u></i></span><span class="palette">${[s[400], s[500], s[600], s[700], s[800], r[200], r[500], r[900]].map((i) => `<i style="background:${i}"></i>`).join("")}</span>`;
|
|
1784
|
+
};
|
|
1785
|
+
t.append(V(e, {
|
|
1786
|
+
section: "colour",
|
|
1787
|
+
key: "theme",
|
|
1788
|
+
label: n,
|
|
1789
|
+
columns: 2,
|
|
1790
|
+
className: "tiles-themes",
|
|
1791
|
+
options: ze.map((o) => ({ value: o.value, label: o.label, preview: a(o) })),
|
|
1792
|
+
current: (o) => {
|
|
1793
|
+
const r = o.colour || {};
|
|
1794
|
+
return Y(r.brand) ? null : ze.find((s) => s.primary === r.primary && s.neutral === r.neutral)?.value || null;
|
|
1795
|
+
},
|
|
1796
|
+
select: (o) => {
|
|
1797
|
+
const r = ze.find((s) => s.value === o);
|
|
1798
|
+
e.setSection("colour", { primary: r.primary, neutral: r.neutral, shade: null, brand: null, tint: null });
|
|
1799
|
+
},
|
|
1800
|
+
valueText: (o, r) => {
|
|
1801
|
+
const s = r.colour || {};
|
|
1802
|
+
return o ? "" : s.primary || s.neutral || s.brand ? "Custom" : "Prototype colours";
|
|
1803
|
+
}
|
|
1804
|
+
}));
|
|
1805
|
+
},
|
|
1806
|
+
primary(t, e, { label: n = "Primary" } = {}) {
|
|
1807
|
+
const a = B(n), o = V(e, {
|
|
1808
|
+
section: "colour",
|
|
1809
|
+
key: "primary",
|
|
1810
|
+
label: `${n} colour family`,
|
|
1811
|
+
columns: 9,
|
|
1812
|
+
className: "tiles-swatches",
|
|
1813
|
+
options: Object.entries(at).map(([i, l]) => ({ value: i, label: rt(i), title: rt(i), preview: `<span class="dot" style="background:${l[500]}"></span>` })),
|
|
1814
|
+
current: (i) => {
|
|
1815
|
+
const l = i.colour || {};
|
|
1816
|
+
return Y(l.brand) ? null : l.primary || null;
|
|
1817
|
+
},
|
|
1818
|
+
select: (i) => e.setSection("colour", { primary: i, brand: null })
|
|
1819
|
+
});
|
|
1820
|
+
o.querySelector(".field-head").remove();
|
|
1821
|
+
const r = re({ label: "Shade", options: [500, 600, 700].map((i) => ({ value: String(i), label: String(i) })), onSelect: (i) => e.setSection("colour", { shade: Number(i) }) });
|
|
1822
|
+
a.append(o, r.el);
|
|
1823
|
+
const s = () => {
|
|
1824
|
+
const i = e.getState().colour || {};
|
|
1825
|
+
r.setValue(String(ot(i))), a.setValue(Y(i.brand) ? i.brand.toLowerCase() : i.primary ? `${i.primary}-${ot(i)}` : "");
|
|
1826
|
+
};
|
|
1827
|
+
s(), e.onChange(s), t.append(a.el);
|
|
1828
|
+
},
|
|
1829
|
+
neutralTiles(t, e, { label: n = "Neutral" } = {}) {
|
|
1830
|
+
t.append(V(e, {
|
|
1831
|
+
section: "colour",
|
|
1832
|
+
key: "neutral",
|
|
1833
|
+
label: n,
|
|
1834
|
+
columns: 5,
|
|
1835
|
+
className: "tiles-greys",
|
|
1836
|
+
options: Object.entries(nt).map(([a, o]) => ({ value: a, label: rt(a), preview: `<span class="greys">${[o[50], o[200], o[500], o[900]].map((r) => `<i style="background:${r}"></i>`).join("")}</span>` })),
|
|
1837
|
+
select: (a) => e.setSection("colour", { neutral: a, tint: null }),
|
|
1838
|
+
valueText: (a) => a || ""
|
|
1839
|
+
}));
|
|
1840
|
+
},
|
|
1841
|
+
custom(t, e) {
|
|
1842
|
+
const n = document.createElement("details");
|
|
1843
|
+
n.className = "more more-custom", n.innerHTML = '<summary><span class="rainbow" aria-hidden="true"></span><span>Custom colour…</span><span class="muted">any hex</span></summary>';
|
|
1844
|
+
const a = document.createElement("div");
|
|
1845
|
+
a.className = "more-body", n.append(a), De.parts.brand(a, e, { label: "Brand" }), Y((e.getState().colour || {}).brand) && (n.open = !0), t.append(n);
|
|
1846
|
+
},
|
|
1847
|
+
brand(t, e, { label: n = "Brand" } = {}) {
|
|
1848
|
+
const a = B(n), o = Ka({ label: "Brand colour", onInput: (s) => e.setSection("colour", { brand: s }) });
|
|
1849
|
+
a.append(o.el);
|
|
1850
|
+
const r = () => o.setValue((e.getState().colour || {}).brand || (e.readToken || F)("--twd-primary") || pn);
|
|
1851
|
+
r(), e.onChange(r), t.append(a.el);
|
|
1852
|
+
},
|
|
1853
|
+
neutral(t, e, { label: n = "Neutral" } = {}) {
|
|
1854
|
+
const a = B(n), o = Ga({ label: "Neutral tint", options: Za, onSelect: (s) => e.setSection("colour", { tint: s }) });
|
|
1855
|
+
a.append(o.el);
|
|
1856
|
+
const r = () => o.setValue((e.getState().colour || {}).tint || "pure");
|
|
1857
|
+
r(), e.onChange(r), t.append(a.el);
|
|
1858
|
+
},
|
|
1859
|
+
mode(t, e, { label: n = "Mode" } = {}) {
|
|
1860
|
+
t.append(ke(e, {
|
|
1861
|
+
section: "colour",
|
|
1862
|
+
key: "mode",
|
|
1863
|
+
label: n,
|
|
1864
|
+
fallback: "light",
|
|
1865
|
+
options: [{ value: "light", label: "Light" }, { value: "dark", label: "Dark" }]
|
|
1866
|
+
}));
|
|
1867
|
+
}
|
|
1868
|
+
},
|
|
1869
|
+
render(t, e) {
|
|
1870
|
+
["brand", "neutral", "mode"].forEach((n) => this.parts[n](t, e));
|
|
1871
|
+
}
|
|
1872
|
+
}, Le = (t, e = "var(--twd-surface)") => `color-mix(in oklch, var(--twd-primary) ${t}%, ${e})`, Qa = {
|
|
1873
|
+
// proposal recipes; Solid leaves tokens.css defaults
|
|
1874
|
+
soft: { bg: Le(14), fg: "var(--twd-primary)", border: "transparent", hover: Le(22) },
|
|
1875
|
+
outline: { bg: "transparent", fg: "var(--twd-primary)", border: "var(--twd-primary)", hover: Le(8, "transparent") },
|
|
1876
|
+
ghost: { bg: "transparent", fg: "var(--twd-primary)", border: "transparent", hover: Le(10, "transparent") }
|
|
1877
|
+
}, eo = { small: { height: 0.8, pad: 0.75 }, large: { height: 1.2, pad: 1.25 } }, Dn = zn({
|
|
1878
|
+
id: "buttons",
|
|
1879
|
+
title: "Buttons",
|
|
1880
|
+
hint: "Style · corners · size",
|
|
1881
|
+
token: "--twd-radius-control",
|
|
1882
|
+
shape: "button",
|
|
1883
|
+
// tile previews: a filled button
|
|
1884
|
+
uses: ["--twd-control-height", "--twd-control-pad-x", "--twd-button-bg", "--twd-button-fg", "--twd-button-border", "--twd-button-hover"],
|
|
1885
|
+
max: 24,
|
|
1886
|
+
// slider range in px; "Pill" sits at the end and reads "full"
|
|
1887
|
+
presets: [
|
|
1888
|
+
{ value: "square", label: "Square", radius: 0 },
|
|
1889
|
+
// proposal
|
|
1890
|
+
{ value: "soft", label: "Soft", radius: 4 },
|
|
1891
|
+
// proposal
|
|
1892
|
+
{ value: "rounded", label: "Rounded", radius: 8 },
|
|
1893
|
+
// Figma: "8px"
|
|
1894
|
+
{ value: "pill", label: "Pill", radius: 999 }
|
|
1895
|
+
],
|
|
1896
|
+
extraTokens(t) {
|
|
1897
|
+
const e = {}, n = Qa[t.style];
|
|
1898
|
+
n && Object.entries(n).forEach(([s, i]) => {
|
|
1899
|
+
e[`--twd-button-${s}`] = i;
|
|
1900
|
+
});
|
|
1901
|
+
const a = eo[t.size];
|
|
1902
|
+
if (!a) return e;
|
|
1903
|
+
const o = ye(F("--twd-control-height"), a.height), r = ye(F("--twd-control-pad-x"), a.pad);
|
|
1904
|
+
return o && (e["--twd-control-height"] = o), r && (e["--twd-control-pad-x"] = r), e;
|
|
1905
|
+
},
|
|
1906
|
+
extra: [
|
|
1907
|
+
{
|
|
1908
|
+
key: "style",
|
|
1909
|
+
label: "Style",
|
|
1910
|
+
fallback: "solid",
|
|
1911
|
+
columns: 4,
|
|
1912
|
+
preview: (t) => `<span class="shape shape-button style-${t}"></span>`,
|
|
1913
|
+
options: [{ value: "solid", label: "Solid" }, { value: "soft", label: "Soft" }, { value: "outline", label: "Outline" }, { value: "ghost", label: "Ghost" }]
|
|
1914
|
+
},
|
|
1915
|
+
{
|
|
1916
|
+
key: "size",
|
|
1917
|
+
label: "Size",
|
|
1918
|
+
fallback: "medium",
|
|
1919
|
+
preview: (t) => `<span class="shape shape-button size-${t}"></span>`,
|
|
1920
|
+
options: [{ value: "small", label: "Small" }, { value: "medium", label: "Medium" }, { value: "large", label: "Large" }]
|
|
1921
|
+
}
|
|
1922
|
+
]
|
|
1923
|
+
}), to = {
|
|
1924
|
+
// proposal recipes; Filled leaves tokens.css defaults
|
|
1925
|
+
outlined: { bg: "transparent", border: "color-mix(in oklch, var(--twd-border) 60%, var(--twd-text))", shadow: "none" },
|
|
1926
|
+
elevated: { bg: "var(--twd-surface-raised)", border: "transparent", shadow: "var(--twd-shadow-md)" },
|
|
1927
|
+
tinted: { bg: "color-mix(in oklch, var(--twd-primary) 8%, var(--twd-surface))", border: "color-mix(in oklch, var(--twd-primary) 25%, var(--twd-surface))", shadow: "none" }
|
|
1928
|
+
}, wt = zn({
|
|
1929
|
+
id: "cards",
|
|
1930
|
+
title: "Cards",
|
|
1931
|
+
hint: "Style · corners · border",
|
|
1932
|
+
token: "--twd-radius-container",
|
|
1933
|
+
shape: "card",
|
|
1934
|
+
// tile previews: an outlined card
|
|
1935
|
+
uses: ["--twd-border-width-container", "--twd-card-bg", "--twd-card-border", "--twd-card-shadow"],
|
|
1936
|
+
max: 32,
|
|
1937
|
+
presets: [
|
|
1938
|
+
{ value: "square", label: "Square", radius: 0 },
|
|
1939
|
+
// proposal
|
|
1940
|
+
{ value: "soft", label: "Soft", radius: 6 },
|
|
1941
|
+
// proposal
|
|
1942
|
+
{ value: "rounded", label: "Rounded", radius: 12 },
|
|
1943
|
+
// = tokens.css default
|
|
1944
|
+
{ value: "large", label: "Large", radius: 20 }
|
|
1945
|
+
// proposal (cards don't go fully pill)
|
|
1946
|
+
],
|
|
1947
|
+
extraTokens(t) {
|
|
1948
|
+
const e = {}, n = to[t.style];
|
|
1949
|
+
if (n && Object.entries(n).forEach(([a, o]) => {
|
|
1950
|
+
e[`--twd-card-${a}`] = o;
|
|
1951
|
+
}), t.border === "none" && (e["--twd-border-width-container"] = "0px"), t.border === "thick") {
|
|
1952
|
+
const a = ye(F("--twd-border-width-container"), 2);
|
|
1953
|
+
a && (e["--twd-border-width-container"] = a);
|
|
1954
|
+
}
|
|
1955
|
+
return e;
|
|
1956
|
+
},
|
|
1957
|
+
extra: [
|
|
1958
|
+
{
|
|
1959
|
+
key: "style",
|
|
1960
|
+
label: "Style",
|
|
1961
|
+
fallback: "filled",
|
|
1962
|
+
columns: 4,
|
|
1963
|
+
preview: (t) => `<span class="shape shape-card style-${t}"></span>`,
|
|
1964
|
+
options: [{ value: "filled", label: "Filled" }, { value: "outlined", label: "Outlined" }, { value: "elevated", label: "Elevated" }, { value: "tinted", label: "Tinted" }]
|
|
1965
|
+
},
|
|
1966
|
+
{
|
|
1967
|
+
key: "border",
|
|
1968
|
+
label: "Border",
|
|
1969
|
+
fallback: "thin",
|
|
1970
|
+
preview: (t) => `<span class="shape shape-card border-${t}"></span>`,
|
|
1971
|
+
options: [{ value: "none", label: "None" }, { value: "thin", label: "Thin" }, { value: "thick", label: "Thick" }]
|
|
1972
|
+
}
|
|
1973
|
+
]
|
|
1974
|
+
}), ne = 60, st = {
|
|
1386
1975
|
soft: { sm: [0, 1, 2, 0, 0.06], md: [0, 4, 12, 0, 0.08], lg: [0, 12, 32, 0, 0.12] },
|
|
1387
1976
|
// = tokens.css
|
|
1388
1977
|
crisp: { sm: [0, 1, 1, 0, 0.12], md: [0, 2, 4, 0, 0.14], lg: [0, 4, 8, 0, 0.16] },
|
|
1389
1978
|
bold: { sm: [2, 2, 0, 0, 0.85], md: [3, 3, 0, 0, 0.85], lg: [5, 5, 0, 0, 0.85] }
|
|
1390
|
-
},
|
|
1391
|
-
const
|
|
1392
|
-
return
|
|
1393
|
-
},
|
|
1979
|
+
}, Pe = ([t, e, n, a, o], r) => {
|
|
1980
|
+
const s = Math.min(1, +(o * r).toFixed(3));
|
|
1981
|
+
return s <= 0 ? "none" : `${t}px ${e}px ${n}px ${a}px rgb(0 0 0 / ${s})`;
|
|
1982
|
+
}, vt = {
|
|
1394
1983
|
id: "depth",
|
|
1395
1984
|
title: "Depth",
|
|
1396
1985
|
hint: "Flat · soft · crisp · bold",
|
|
1397
1986
|
uses: ["--twd-shadow-sm", "--twd-shadow-md", "--twd-shadow-lg"],
|
|
1398
1987
|
tokens(t) {
|
|
1399
|
-
const e = t.depth || {}, n = e.style || "soft",
|
|
1988
|
+
const e = t.depth || {}, n = e.style || "soft", a = typeof e.intensity == "number" ? Math.min(100, Math.max(0, e.intensity)) : ne;
|
|
1400
1989
|
if (n === "flat")
|
|
1401
1990
|
return { "--twd-shadow-sm": "none", "--twd-shadow-md": "none", "--twd-shadow-lg": "none" };
|
|
1402
|
-
const
|
|
1403
|
-
if (!
|
|
1404
|
-
if (n === "soft" &&
|
|
1405
|
-
const
|
|
1991
|
+
const o = st[n];
|
|
1992
|
+
if (!o) return {};
|
|
1993
|
+
if (n === "soft" && a === ne) return {};
|
|
1994
|
+
const r = a / ne;
|
|
1406
1995
|
return {
|
|
1407
|
-
"--twd-shadow-sm":
|
|
1408
|
-
"--twd-shadow-md":
|
|
1409
|
-
"--twd-shadow-lg":
|
|
1996
|
+
"--twd-shadow-sm": Pe(o.sm, r),
|
|
1997
|
+
"--twd-shadow-md": Pe(o.md, r),
|
|
1998
|
+
"--twd-shadow-lg": Pe(o.lg, r)
|
|
1410
1999
|
};
|
|
1411
2000
|
},
|
|
1412
2001
|
parts: {
|
|
2002
|
+
// Tabbed layout: each style as a small card wearing that shadow.
|
|
2003
|
+
styleTiles(t, e, { label: n = "Style" } = {}) {
|
|
2004
|
+
const a = (o) => `<span class="shape shape-card shadow-${o}" style="box-shadow:${st[o] ? Pe(st[o].md, 1) : "none"}"></span>`;
|
|
2005
|
+
t.append(V(e, {
|
|
2006
|
+
section: "depth",
|
|
2007
|
+
key: "style",
|
|
2008
|
+
label: n,
|
|
2009
|
+
fallback: "soft",
|
|
2010
|
+
columns: 4,
|
|
2011
|
+
options: ["flat", "soft", "crisp", "bold"].map((o) => ({ value: o, label: o[0].toUpperCase() + o.slice(1), preview: a(o) }))
|
|
2012
|
+
}));
|
|
2013
|
+
},
|
|
1413
2014
|
style(t, e, { label: n = "Style" } = {}) {
|
|
1414
|
-
t.append(
|
|
2015
|
+
t.append(ke(e, {
|
|
1415
2016
|
section: "depth",
|
|
1416
2017
|
key: "style",
|
|
1417
2018
|
label: n,
|
|
@@ -1420,208 +2021,431 @@ const Dt = (t) => `${Math.round(t * 100)}%`, bo = {
|
|
|
1420
2021
|
}));
|
|
1421
2022
|
},
|
|
1422
2023
|
intensity(t, e, { label: n = "Intensity" } = {}) {
|
|
1423
|
-
t.append(
|
|
2024
|
+
t.append(Ia(e, { section: "depth", key: "intensity", label: n, min: 0, max: 100, fallback: ne, format: (a) => `${a}%` }));
|
|
1424
2025
|
}
|
|
1425
2026
|
},
|
|
1426
2027
|
render(t, e) {
|
|
1427
|
-
Object.
|
|
2028
|
+
Object.entries(this.parts).filter(([n]) => !n.endsWith("Tiles")).forEach(([, n]) => n(t, e));
|
|
1428
2029
|
}
|
|
1429
|
-
},
|
|
1430
|
-
function
|
|
1431
|
-
return t
|
|
2030
|
+
}, no = (t) => Math.min(1, Math.max(0, t)), fn = (t) => Math.round(t / 50) * 50, it = (t) => `${+t.toFixed(1)}%`;
|
|
2031
|
+
function $t(t = {}) {
|
|
2032
|
+
return typeof t.bold == "number" && isFinite(t.bold) && t.bold !== 0.5 ? no(t.bold) : null;
|
|
1432
2033
|
}
|
|
1433
|
-
const
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
});
|
|
1443
|
-
|
|
1444
|
-
const s = (
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
buttons: { corners: c, radius: null },
|
|
1456
|
-
cards: { corners: Vt[c], radius: null }
|
|
1457
|
-
})
|
|
1458
|
-
});
|
|
1459
|
-
s.append(r.el);
|
|
1460
|
-
const d = (c, p, m) => {
|
|
1461
|
-
if (p.radius != null) return null;
|
|
1462
|
-
if (p.corners) return p.corners;
|
|
1463
|
-
const v = parseFloat(F(m));
|
|
1464
|
-
return c.presets.find((f) => f.radius === v)?.value || null;
|
|
1465
|
-
}, l = () => {
|
|
1466
|
-
const c = e.getState(), p = d(o, c.buttons || {}, "--twd-radius-control"), m = d(a, c.cards || {}, "--twd-radius-container");
|
|
1467
|
-
r.setValue(p && Vt[p] === m ? p : null);
|
|
1468
|
-
const v = (b) => {
|
|
1469
|
-
const g = parseFloat(F(b));
|
|
1470
|
-
return isNaN(g) ? "" : g >= 999 ? "full" : `${g}px`;
|
|
1471
|
-
}, f = v("--twd-radius-control"), x = v("--twd-radius-container");
|
|
1472
|
-
s.setValue(f && x ? `${f} · ${x}` : "");
|
|
1473
|
-
};
|
|
1474
|
-
l(), e.onChange(l), t.append(s.el);
|
|
1475
|
-
}
|
|
1476
|
-
}, Ie = [
|
|
1477
|
-
{ key: "page-pad", label: "Page padding", step: 5 },
|
|
1478
|
-
{ key: "section-gap", label: "Section gap", step: 6 },
|
|
1479
|
-
{ key: "card-pad", label: "Card padding", step: 5 },
|
|
1480
|
-
{ key: "card-gap", label: "Card gap", step: 2 },
|
|
1481
|
-
{ key: "control-gap", label: "Control gap", step: 3 },
|
|
1482
|
-
{ key: "control-pad-x", label: "Control padding", step: null }
|
|
1483
|
-
], ko = [1, 2, 3, 4, 5, 6, 7, 8], So = /^var\(\s*--twd-space-(\d)\s*\)$/, De = (t) => {
|
|
1484
|
-
const e = /^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());
|
|
1485
|
-
return e ? +(parseFloat(e[1]) * (e[2] === "rem" ? 16 : 1)).toFixed(1) : null;
|
|
1486
|
-
};
|
|
1487
|
-
function Eo(t, e, n) {
|
|
1488
|
-
const o = `--twd-${e.key}`, a = document.createElement("div");
|
|
1489
|
-
a.className = "role-row", n || a.classList.add("unused"), a.innerHTML = `
|
|
1490
|
-
<span class="role-label" title="${o}${n ? "" : " (not used on this page)"}">${e.label}</span>
|
|
1491
|
-
<label class="token-value role-fixed" hidden><input type="text" inputmode="decimal" spellcheck="false" aria-label="${e.label}, fixed value"><span class="token-unit">px</span></label>
|
|
1492
|
-
<span class="step-select">
|
|
1493
|
-
<select aria-label="${e.label}"></select>
|
|
1494
|
-
<span class="step-chevron" aria-hidden="true">${$.down}</span>
|
|
1495
|
-
</span>
|
|
1496
|
-
<button class="icon-btn token-reset" title="Back to the default" aria-label="Reset ${e.label}" hidden>${$.reset}</button>
|
|
1497
|
-
`;
|
|
1498
|
-
const s = a.querySelector("select"), r = a.querySelector(".role-fixed"), d = r.querySelector("input"), l = a.querySelector(".token-reset"), c = () => (t.getState().custom || {})[e.key], p = () => {
|
|
1499
|
-
const b = c();
|
|
1500
|
-
if (b == null) return e.step ? { step: e.step } : { fixed: F(o) };
|
|
1501
|
-
const g = So.exec(b);
|
|
1502
|
-
return g ? { step: +g[1] } : { fixed: b };
|
|
1503
|
-
}, m = (b) => t.setSection("custom", { [e.key]: b }), v = (b) => e.step ? b === `var(--twd-space-${e.step})` : !1;
|
|
1504
|
-
s.addEventListener("change", () => {
|
|
1505
|
-
if (s.value === "fixed") {
|
|
1506
|
-
const g = De(F(o));
|
|
1507
|
-
m(g != null ? `${g}px` : F(o)), d.focus(), d.select();
|
|
1508
|
-
return;
|
|
2034
|
+
const Oe = (t, e, n) => (t.tokens(e) || {})[n] || F(n), ao = {
|
|
2035
|
+
id: "character",
|
|
2036
|
+
title: "Character",
|
|
2037
|
+
hint: "Subtle ↔ Bold",
|
|
2038
|
+
uses: [],
|
|
2039
|
+
// set from the Simple page only
|
|
2040
|
+
tokens(t) {
|
|
2041
|
+
const e = $t(t.character);
|
|
2042
|
+
if (e === null) return {};
|
|
2043
|
+
const n = {}, a = 2 * e - 1, o = Math.max(0, a), r = Math.max(0, -a);
|
|
2044
|
+
n["--twd-weight-heading"] = String(fn(600 + 200 * a)), n["--twd-weight-strong"] = String(fn(600 + 100 * a));
|
|
2045
|
+
const s = Oe(De, t, "--twd-border");
|
|
2046
|
+
if (r && (n["--twd-border"] = `color-mix(in oklch, ${s} ${it(100 - 50 * r)}, var(--twd-bg))`), o) {
|
|
2047
|
+
n["--twd-border"] = `color-mix(in oklch, ${s} ${it(100 - 90 * o)}, var(--twd-text))`;
|
|
2048
|
+
const l = Oe(De, t, "--twd-text-muted");
|
|
2049
|
+
n["--twd-text-muted"] = `color-mix(in oklch, ${l} ${it(100 - 35 * o)}, var(--twd-text))`, ["--twd-border-width", "--twd-border-width-container"].forEach((p) => {
|
|
2050
|
+
const c = p === "--twd-border-width-container" ? Oe(wt, t, p) : F(p), h = parseFloat(c);
|
|
2051
|
+
isNaN(h) || (n[p] = `${Math.round(h * (1 + o))}px`);
|
|
2052
|
+
}), [[Dn, "--twd-radius-control"], [wt, "--twd-radius-container"]].forEach(([p, c]) => {
|
|
2053
|
+
const h = parseFloat(Oe(p, t, c));
|
|
2054
|
+
!isNaN(h) && h < 999 && (n[c] = ye(`${h}px`, 1 - 0.5 * o));
|
|
2055
|
+
});
|
|
1509
2056
|
}
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
const b = d.value.trim();
|
|
1515
|
-
if (!b) return m(null);
|
|
1516
|
-
m(/^-?\d*\.?\d+$/.test(b) ? `${b}px` : b);
|
|
1517
|
-
};
|
|
1518
|
-
d.addEventListener("change", f), d.addEventListener("blur", () => x()), d.addEventListener("keydown", (b) => {
|
|
1519
|
-
b.key === "Enter" && (b.preventDefault(), d.blur()), b.key === "Escape" && (b.stopPropagation(), x(), d.blur());
|
|
1520
|
-
}), l.addEventListener("click", () => m(null));
|
|
1521
|
-
function x() {
|
|
1522
|
-
const b = p();
|
|
1523
|
-
if (s.innerHTML = ko.map((u) => `<option value="${u}">space-${u} · ${De(F(`--twd-space-${u}`)) ?? "?"}</option>`).join("") + '<option value="fixed">Fixed</option>', s.value = b.step ? String(b.step) : "fixed", r.hidden = !b.fixed, a.classList.toggle("is-fixed", !!b.fixed), b.fixed && a.getRootNode().activeElement !== d) {
|
|
1524
|
-
const u = De(b.fixed);
|
|
1525
|
-
d.value = u != null ? String(u) : b.fixed;
|
|
2057
|
+
const i = t.depth || {};
|
|
2058
|
+
if (!i.style) {
|
|
2059
|
+
const l = typeof i.intensity == "number" ? i.intensity : ne, p = r ? vt.tokens({ depth: { style: "soft", intensity: l * (1 - r) } }) : vt.tokens({ depth: { style: "bold", intensity: ne * o } });
|
|
2060
|
+
Object.assign(n, p);
|
|
1526
2061
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
2062
|
+
return n;
|
|
2063
|
+
},
|
|
2064
|
+
describe: (t, e) => t === "bold" ? { label: "Subtle ↔ Bold", value: `${Math.round(e * 100)}%` } : null
|
|
2065
|
+
}, oo = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>', ro = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
|
|
2066
|
+
let so = 0;
|
|
2067
|
+
function io({ label: t, options: e, onSelect: n, placeholder: a, onOpen: o }) {
|
|
2068
|
+
const r = `twd-fonts-${++so}`, s = document.createElement("div");
|
|
2069
|
+
s.className = "font-select";
|
|
2070
|
+
const i = document.createElement("button");
|
|
2071
|
+
i.type = "button", i.className = "font-trigger", i.setAttribute("aria-haspopup", "listbox"), i.setAttribute("aria-expanded", "false"), i.setAttribute("aria-controls", r), i.innerHTML = `
|
|
2072
|
+
<span class="font-aa" aria-hidden="true">Aa</span>
|
|
2073
|
+
<span class="font-name"></span>
|
|
2074
|
+
<span class="font-fonts"></span>
|
|
2075
|
+
<span class="font-chevron">${oo}</span>`;
|
|
2076
|
+
const l = document.createElement("div");
|
|
2077
|
+
l.className = "font-menu", l.id = r, l.setAttribute("role", "listbox"), l.setAttribute("aria-label", t), l.hidden = !0;
|
|
2078
|
+
const p = e.map((u) => {
|
|
2079
|
+
const v = document.createElement("div");
|
|
2080
|
+
return v.className = "font-option", v.setAttribute("role", "option"), v.tabIndex = -1, v.dataset.value = u.value, v.innerHTML = `
|
|
2081
|
+
<span class="font-aa" style="font-family:${u.preview}" aria-hidden="true">Aa</span>
|
|
2082
|
+
<span class="font-text"><span class="font-name">${u.name}</span><span class="font-fonts">${u.fonts}</span></span>
|
|
2083
|
+
<span class="font-check">${ro}</span>`, v.addEventListener("click", () => b(u.value)), v;
|
|
2084
|
+
});
|
|
2085
|
+
l.append(...p), s.append(i, l);
|
|
2086
|
+
let c = null;
|
|
2087
|
+
function h(u) {
|
|
2088
|
+
const v = e.find((E) => E.value === u), L = v || a || e[0];
|
|
2089
|
+
c = v ? v.value : a ? null : e[0].value, i.querySelector(".font-name").textContent = L.name, i.querySelector(".font-fonts").textContent = L.fonts, i.querySelector(".font-aa").style.fontFamily = L.preview, i.setAttribute("aria-label", `${t}: ${L.name}, ${L.fonts}`), p.forEach((E) => E.setAttribute("aria-selected", String(E.dataset.value === c)));
|
|
1529
2090
|
}
|
|
1530
|
-
|
|
2091
|
+
function y() {
|
|
2092
|
+
const u = i.getBoundingClientRect(), v = l.offsetHeight, E = window.innerHeight - u.bottom - 8 >= v ? u.bottom + 4 : Math.max(8, u.top - 4 - v);
|
|
2093
|
+
l.style.top = `${E}px`, l.style.left = `${u.left}px`, l.style.width = `${u.width}px`;
|
|
2094
|
+
}
|
|
2095
|
+
function f() {
|
|
2096
|
+
l.hidden && (l.hidden = !1, i.setAttribute("aria-expanded", "true"), o && o(), y(), (p.find((u) => u.dataset.value === c) || p[0]).focus(), setTimeout(() => document.addEventListener("pointerdown", x, !0)), window.addEventListener("resize", m), s.closest(".body")?.addEventListener("scroll", m, { once: !0 }));
|
|
2097
|
+
}
|
|
2098
|
+
function m(u) {
|
|
2099
|
+
l.hidden || (l.hidden = !0, i.setAttribute("aria-expanded", "false"), document.removeEventListener("pointerdown", x, !0), window.removeEventListener("resize", m), u === !0 && i.focus());
|
|
2100
|
+
}
|
|
2101
|
+
function x(u) {
|
|
2102
|
+
u.composedPath().includes(s) || m();
|
|
2103
|
+
}
|
|
2104
|
+
function b(u) {
|
|
2105
|
+
u !== c && (h(u), n(u)), m(!0);
|
|
2106
|
+
}
|
|
2107
|
+
return i.addEventListener("click", () => l.hidden ? f() : m(!0)), i.addEventListener("keydown", (u) => {
|
|
2108
|
+
(u.key === "ArrowDown" || u.key === "ArrowUp") && (u.preventDefault(), f());
|
|
2109
|
+
}), l.addEventListener("keydown", (u) => {
|
|
2110
|
+
const v = p.indexOf(u.target);
|
|
2111
|
+
let L = null;
|
|
2112
|
+
if (u.key === "ArrowDown") L = Math.min(p.length - 1, v + 1);
|
|
2113
|
+
else if (u.key === "ArrowUp") L = Math.max(0, v - 1);
|
|
2114
|
+
else if (u.key === "Home") L = 0;
|
|
2115
|
+
else if (u.key === "End") L = p.length - 1;
|
|
2116
|
+
else if (u.key === "Enter" || u.key === " ") {
|
|
2117
|
+
u.preventDefault(), b(u.target.dataset.value);
|
|
2118
|
+
return;
|
|
2119
|
+
} else if (u.key === "Escape") {
|
|
2120
|
+
u.preventDefault(), u.stopPropagation(), m(!0);
|
|
2121
|
+
return;
|
|
2122
|
+
} else if (u.key === "Tab") {
|
|
2123
|
+
m();
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
L !== null && (u.preventDefault(), p[L].focus());
|
|
2127
|
+
}), h(a ? null : e[0].value), { el: s, setValue: h };
|
|
2128
|
+
}
|
|
2129
|
+
const I = 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif', Fe = 'Georgia, "Times New Roman", serif', Z = [
|
|
2130
|
+
{ value: "neutral", name: "Neutral", fonts: "Inter · Inter", heading: `"Inter", ${I}`, body: `"Inter", ${I}`, load: ["Inter:wght@400;500;600;700"] },
|
|
2131
|
+
{ value: "modern", name: "Modern", fonts: "Geist · Geist", heading: `"Geist", ${I}`, body: `"Geist", ${I}`, load: ["Geist:wght@400;500;600;700"] },
|
|
2132
|
+
{ value: "editorial", name: "Editorial", fonts: "Fraunces · Inter", heading: `"Fraunces", ${Fe}`, body: `"Inter", ${I}`, load: ["Fraunces:wght@400;600;700", "Inter:wght@400;500;600;700"] },
|
|
2133
|
+
{ value: "technical", name: "Technical", fonts: "Space Grotesk · DM Sans", heading: `"Space Grotesk", ${I}`, body: `"DM Sans", ${I}`, load: ["Space+Grotesk:wght@500;600;700", "DM+Sans:wght@400;500;600;700"] },
|
|
2134
|
+
{ value: "classic", name: "Classic", fonts: "Playfair · DM Sans", heading: `"Playfair Display", ${Fe}`, body: `"DM Sans", ${I}`, load: ["Playfair+Display:wght@600;700", "DM+Sans:wght@400;500;600;700"] },
|
|
2135
|
+
{ value: "plex", name: "Plex", fonts: "Plex Serif · Plex Sans", heading: `"IBM Plex Serif", ${Fe}`, body: `"IBM Plex Sans", ${I}`, load: ["IBM+Plex+Serif:wght@500;600;700", "IBM+Plex+Sans:wght@400;500;600"] },
|
|
2136
|
+
{ value: "elegant", name: "Elegant", fonts: "Instrument Serif · Inter", heading: `"Instrument Serif", ${Fe}`, body: `"Inter", ${I}`, load: ["Instrument+Serif", "Inter:wght@400;500;600;700"] },
|
|
2137
|
+
{ value: "friendly", name: "Friendly", fonts: "Manrope · Manrope", heading: `"Manrope", ${I}`, body: `"Manrope", ${I}`, load: ["Manrope:wght@400;500;600;700"] }
|
|
2138
|
+
], be = { compact: 1.125, balanced: 1.2, expressive: 1.333 }, gn = { xs: -2, sm: -1, base: 0, lg: 1, xl: 2, "2xl": 3, "3xl": 4 }, mn = /* @__PURE__ */ new Set();
|
|
2139
|
+
function lt(t) {
|
|
2140
|
+
const e = t.filter((a) => !mn.has(a));
|
|
2141
|
+
if (!e.length) return;
|
|
2142
|
+
e.forEach((a) => mn.add(a));
|
|
2143
|
+
const n = document.createElement("link");
|
|
2144
|
+
n.rel = "stylesheet", n.href = `https://fonts.googleapis.com/css2?${e.map((a) => `family=${a}`).join("&")}&display=swap`, n.dataset.twiddle = "fonts", document.head.appendChild(n);
|
|
2145
|
+
}
|
|
2146
|
+
const lo = {
|
|
2147
|
+
id: "typography",
|
|
2148
|
+
title: "Typography",
|
|
2149
|
+
hint: "Font pairing · base size · scale",
|
|
2150
|
+
uses: ["--twd-font-heading", "--twd-font-body", ...Object.keys(gn).map((t) => `--twd-text-${t}`)],
|
|
2151
|
+
tokens(t) {
|
|
2152
|
+
const e = t.typography || {}, n = {}, a = Z.find((o) => o.value === e.font);
|
|
2153
|
+
if (a && (lt(a.load), n["--twd-font-heading"] = a.heading, n["--twd-font-body"] = a.body), e.base || e.scale) {
|
|
2154
|
+
const o = Number(e.base) || 16, r = be[e.scale] || be.balanced;
|
|
2155
|
+
Object.entries(gn).forEach(([s, i]) => {
|
|
2156
|
+
n[`--twd-text-${s}`] = `${+(o * r ** i / 16).toFixed(3)}rem`;
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
return n;
|
|
2160
|
+
},
|
|
2161
|
+
parts: {
|
|
2162
|
+
fonts(t, e, { label: n = "Fonts" } = {}) {
|
|
2163
|
+
const a = B(n), o = io({
|
|
2164
|
+
label: "Font pairing",
|
|
2165
|
+
options: Z.map((s) => ({ value: s.value, name: s.name, fonts: s.fonts, preview: s.heading })),
|
|
2166
|
+
placeholder: { name: "Default", fonts: "Prototype fonts", preview: I },
|
|
2167
|
+
// Load every pairing's fonts when the list opens, so the "Aa" previews are real.
|
|
2168
|
+
onOpen: () => lt([...new Set(Z.flatMap((s) => s.load))]),
|
|
2169
|
+
onSelect: (s) => e.setSection("typography", { font: s })
|
|
2170
|
+
});
|
|
2171
|
+
a.append(o.el);
|
|
2172
|
+
const r = () => o.setValue((e.getState().typography || {}).font || null);
|
|
2173
|
+
r(), e.onChange(r), t.append(a.el);
|
|
2174
|
+
},
|
|
2175
|
+
base(t, e, { label: n = "Base size" } = {}) {
|
|
2176
|
+
t.append(ke(e, {
|
|
2177
|
+
section: "typography",
|
|
2178
|
+
key: "base",
|
|
2179
|
+
label: n,
|
|
2180
|
+
fallback: "16",
|
|
2181
|
+
options: ["14", "15", "16"].map((a) => ({ value: a, label: a })),
|
|
2182
|
+
valueText: (a) => `${a}px`
|
|
2183
|
+
}));
|
|
2184
|
+
},
|
|
2185
|
+
// Tabbed layout: pairings as tiles, each "Aa" in its own heading font.
|
|
2186
|
+
// The fonts load (from Google Fonts) only once the tiles are on screen.
|
|
2187
|
+
fontTiles(t, e, { label: n = "Fonts" } = {}) {
|
|
2188
|
+
const a = (i) => i.replace(/"/g, """), o = (i) => `<span class="tile-aa" style="font-family:${a(i)}">Aa</span>`, r = V(e, {
|
|
2189
|
+
section: "typography",
|
|
2190
|
+
key: "font",
|
|
2191
|
+
label: n,
|
|
2192
|
+
columns: 2,
|
|
2193
|
+
className: "tiles-fonts",
|
|
2194
|
+
options: [
|
|
2195
|
+
{ value: "default", label: "Prototype fonts", sub: "Default", preview: o(I), wide: !0 },
|
|
2196
|
+
...Z.map((i) => ({ value: i.value, label: i.name, sub: i.fonts, preview: o(i.heading) }))
|
|
2197
|
+
],
|
|
2198
|
+
current: (i) => (i.typography || {}).font || "default",
|
|
2199
|
+
select: (i) => e.setSection("typography", { font: i === "default" ? null : i }),
|
|
2200
|
+
valueText: (i) => i === "default" ? "Default" : Z.find((l) => l.value === i)?.name || ""
|
|
2201
|
+
}), s = new IntersectionObserver((i) => {
|
|
2202
|
+
i.some((l) => l.isIntersecting) && (lt([...new Set(Z.flatMap((l) => l.load))]), s.disconnect());
|
|
2203
|
+
});
|
|
2204
|
+
s.observe(r), t.append(r);
|
|
2205
|
+
},
|
|
2206
|
+
scaleTiles(t, e, { label: n = "Scale" } = {}) {
|
|
2207
|
+
const a = (o) => `<span class="ladder">${[-1, 0, 1, 2].map((r) => `<b style="font-size:${Math.round(13 * o ** r)}px">A</b>`).join("")}</span>`;
|
|
2208
|
+
t.append(V(e, {
|
|
2209
|
+
section: "typography",
|
|
2210
|
+
key: "scale",
|
|
2211
|
+
label: n,
|
|
2212
|
+
fallback: "balanced",
|
|
2213
|
+
options: Object.entries(be).map(([o, r]) => ({ value: o, label: o[0].toUpperCase() + o.slice(1), preview: a(r) })),
|
|
2214
|
+
valueText: (o) => String(be[o])
|
|
2215
|
+
}));
|
|
2216
|
+
},
|
|
2217
|
+
scale(t, e, { label: n = "Scale" } = {}) {
|
|
2218
|
+
t.append(ke(e, {
|
|
2219
|
+
section: "typography",
|
|
2220
|
+
key: "scale",
|
|
2221
|
+
label: n,
|
|
2222
|
+
fallback: "balanced",
|
|
2223
|
+
options: [{ value: "compact", label: "Compact" }, { value: "balanced", label: "Balanced" }, { value: "expressive", label: "Expressive" }],
|
|
2224
|
+
valueText: (a) => String(be[a])
|
|
2225
|
+
}));
|
|
2226
|
+
}
|
|
2227
|
+
},
|
|
2228
|
+
render(t, e) {
|
|
2229
|
+
Object.entries(this.parts).filter(([n]) => !n.endsWith("Tiles")).forEach(([, n]) => n(t, e));
|
|
2230
|
+
}
|
|
2231
|
+
}, [bn, Ne] = xt;
|
|
2232
|
+
function co(t) {
|
|
2233
|
+
return t > 0.56 ? "Bold" : t < 0.44 ? "Subtle" : "Balanced";
|
|
2234
|
+
}
|
|
2235
|
+
function Bn(t) {
|
|
2236
|
+
return t > 1.06 ? "Comfy" : t < 0.94 ? "Compact" : "Regular";
|
|
1531
2237
|
}
|
|
1532
|
-
const
|
|
2238
|
+
const je = (t) => t && t[0].toUpperCase() + t.slice(1), xn = [
|
|
2239
|
+
["colour", "Colour", (t) => {
|
|
2240
|
+
const e = t.colour || {}, n = ze.find((o) => o.primary === e.primary && o.neutral === e.neutral);
|
|
2241
|
+
return { text: `${e.brand ? e.brand.toUpperCase() : n && !e.shade ? n.label : e.primary ? `${je(e.primary)}${e.neutral ? ` · ${je(e.neutral)}` : ""}` : "Prototype colours"}${e.mode === "dark" ? " · Dark" : ""}`, swatch: F("--twd-primary") };
|
|
2242
|
+
}],
|
|
2243
|
+
["type", "Type", (t) => {
|
|
2244
|
+
const e = t.typography || {};
|
|
2245
|
+
return { text: `${Z.find((n) => n.value === e.font)?.name || "Prototype fonts"} · ${e.base || 16}px` };
|
|
2246
|
+
}],
|
|
2247
|
+
["spacing", "Spacing", (t) => ({ text: Bn(te(t.density)) })],
|
|
2248
|
+
["shape", "Shape", (t) => ({ text: `${je((t.buttons || {}).corners) || "Default"} corners` })],
|
|
2249
|
+
["shadows", "Shadows", (t) => ({ text: je((t.depth || {}).style) || ($t(t.character) !== null ? "From the pad" : "Default") })]
|
|
2250
|
+
], po = {
|
|
2251
|
+
summary(t, e, { label: n = "Current look", goTab: a } = {}) {
|
|
2252
|
+
const o = B(n), r = document.createElement("div");
|
|
2253
|
+
r.className = "summary", xn.forEach(([i, l]) => {
|
|
2254
|
+
const p = document.createElement("button");
|
|
2255
|
+
p.type = "button", p.className = "summary-row", p.dataset.summary = i, p.innerHTML = `<span class="summary-name"></span><span class="summary-swatch" hidden></span><span class="summary-value"></span><span class="chev" aria-hidden="true">${A.chevron}</span>`, p.querySelector(".summary-name").textContent = l, p.addEventListener("click", () => a && a(i)), r.append(p);
|
|
2256
|
+
}), o.append(r);
|
|
2257
|
+
const s = () => {
|
|
2258
|
+
const i = e.getState();
|
|
2259
|
+
xn.forEach(([l, , p]) => {
|
|
2260
|
+
const c = r.querySelector(`[data-summary="${l}"]`), h = p(i);
|
|
2261
|
+
c.querySelector(".summary-value").textContent = h.text;
|
|
2262
|
+
const y = c.querySelector(".summary-swatch");
|
|
2263
|
+
y.hidden = !h.swatch, h.swatch && (y.style.background = h.swatch);
|
|
2264
|
+
});
|
|
2265
|
+
};
|
|
2266
|
+
s(), e.onChange(s), t.append(o.el);
|
|
2267
|
+
},
|
|
2268
|
+
character(t, e, { label: n = "Character" } = {}) {
|
|
2269
|
+
const a = B(n), o = ja({
|
|
2270
|
+
label: n,
|
|
2271
|
+
ends: { top: "Spacious", bottom: "Compact", left: "Subtle", right: "Bold" },
|
|
2272
|
+
onInput: (s, i) => {
|
|
2273
|
+
const l = +(Ne - i * (Ne - bn)).toFixed(3), p = +s.toFixed(3);
|
|
2274
|
+
e.setSections({
|
|
2275
|
+
density: { factor: l === 1 ? null : l, spacing: null },
|
|
2276
|
+
character: { bold: p === 0.5 ? null : p }
|
|
2277
|
+
}, "character");
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
a.append(o.el);
|
|
2281
|
+
const r = () => {
|
|
2282
|
+
const s = e.getState(), i = te(s.density), l = $t(s.character), p = l === null ? 0.5 : l, c = `${Bn(i)} · ${co(p)}`;
|
|
2283
|
+
o.setValue(p, (Ne - i) / (Ne - bn), c), a.setValue(c);
|
|
2284
|
+
};
|
|
2285
|
+
r(), e.onChange(r), t.append(a.el);
|
|
2286
|
+
}
|
|
2287
|
+
}, dt = [
|
|
2288
|
+
{ key: "page-pad", label: "Page padding", step: 5 },
|
|
2289
|
+
{ key: "section-gap", label: "Section gap", step: 6 },
|
|
2290
|
+
{ key: "card-pad", label: "Card padding", step: 5 },
|
|
2291
|
+
{ key: "card-gap", label: "Card gap", step: 2 },
|
|
2292
|
+
{ key: "control-gap", label: "Control gap", step: 3 },
|
|
2293
|
+
{ key: "control-pad-x", label: "Control padding", step: null }
|
|
2294
|
+
], uo = [1, 2, 3, 4, 5, 6, 7, 8], ho = /^var\(\s*--twd-space-(\d)\s*\)$/, ct = (t) => {
|
|
2295
|
+
const e = /^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());
|
|
2296
|
+
return e ? +(parseFloat(e[1]) * (e[2] === "rem" ? 16 : 1)).toFixed(1) : null;
|
|
2297
|
+
};
|
|
2298
|
+
function fo(t, e, n) {
|
|
2299
|
+
const a = `--twd-${e.key}`, o = document.createElement("div");
|
|
2300
|
+
o.className = "role-row", n || o.classList.add("unused"), o.innerHTML = `
|
|
2301
|
+
<span class="role-label" title="${a}${n ? "" : " (not used on this page)"}">${e.label}</span>
|
|
2302
|
+
<label class="token-value role-fixed" hidden><input type="text" inputmode="decimal" spellcheck="false" aria-label="${e.label}, fixed value"><span class="token-unit">px</span></label>
|
|
2303
|
+
<span class="step-select">
|
|
2304
|
+
<select aria-label="${e.label}"></select>
|
|
2305
|
+
<span class="step-chevron" aria-hidden="true">${A.down}</span>
|
|
2306
|
+
</span>
|
|
2307
|
+
<button class="icon-btn token-reset" title="Back to the default" aria-label="Reset ${e.label}" hidden>${A.reset}</button>
|
|
2308
|
+
`;
|
|
2309
|
+
const r = o.querySelector("select"), s = o.querySelector(".role-fixed"), i = s.querySelector("input"), l = o.querySelector(".token-reset"), p = () => (t.getState().custom || {})[e.key], c = () => {
|
|
2310
|
+
const x = p();
|
|
2311
|
+
if (x == null) return e.step ? { step: e.step } : { fixed: F(a) };
|
|
2312
|
+
const b = ho.exec(x);
|
|
2313
|
+
return b ? { step: +b[1] } : { fixed: x };
|
|
2314
|
+
}, h = (x) => t.setSection("custom", { [e.key]: x }), y = (x) => e.step ? x === `var(--twd-space-${e.step})` : !1;
|
|
2315
|
+
r.addEventListener("change", () => {
|
|
2316
|
+
if (r.value === "fixed") {
|
|
2317
|
+
const b = ct(F(a));
|
|
2318
|
+
h(b != null ? `${b}px` : F(a)), i.focus(), i.select();
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
const x = `var(--twd-space-${r.value})`;
|
|
2322
|
+
h(y(x) ? null : x);
|
|
2323
|
+
});
|
|
2324
|
+
const f = () => {
|
|
2325
|
+
const x = i.value.trim();
|
|
2326
|
+
if (!x) return h(null);
|
|
2327
|
+
h(/^-?\d*\.?\d+$/.test(x) ? `${x}px` : x);
|
|
2328
|
+
};
|
|
2329
|
+
i.addEventListener("change", f), i.addEventListener("blur", () => m()), i.addEventListener("keydown", (x) => {
|
|
2330
|
+
x.key === "Enter" && (x.preventDefault(), i.blur()), x.key === "Escape" && (x.stopPropagation(), m(), i.blur());
|
|
2331
|
+
}), l.addEventListener("click", () => h(null));
|
|
2332
|
+
function m() {
|
|
2333
|
+
const x = c();
|
|
2334
|
+
if (r.innerHTML = uo.map((u) => `<option value="${u}">space-${u} · ${ct(F(`--twd-space-${u}`)) ?? "?"}</option>`).join("") + '<option value="fixed">Fixed</option>', r.value = x.step ? String(x.step) : "fixed", s.hidden = !x.fixed, o.classList.toggle("is-fixed", !!x.fixed), x.fixed && o.getRootNode().activeElement !== i) {
|
|
2335
|
+
const u = ct(x.fixed);
|
|
2336
|
+
i.value = u != null ? String(u) : x.fixed;
|
|
2337
|
+
}
|
|
2338
|
+
const b = p() != null;
|
|
2339
|
+
o.classList.toggle("edited", b), l.hidden = !b;
|
|
2340
|
+
}
|
|
2341
|
+
return m(), t.onChange(m), o;
|
|
2342
|
+
}
|
|
2343
|
+
const go = {
|
|
1533
2344
|
spacing(t, e, { label: n = "Roles" } = {}) {
|
|
1534
|
-
const
|
|
1535
|
-
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
}
|
|
1543
|
-
},
|
|
1544
|
-
["simple", "character"],
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
{ id: "
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
{ id: "
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
2345
|
+
const a = Fa(), o = B(n);
|
|
2346
|
+
o.el.classList.add("token-field");
|
|
2347
|
+
const r = dt.filter((l) => !a.has(`--twd-${l.key}`));
|
|
2348
|
+
o.setValue(r.length === dt.length ? "not used on this page" : "");
|
|
2349
|
+
const s = document.createElement("div");
|
|
2350
|
+
s.className = "token-rows", dt.forEach((l) => s.append(fo(e, l, a.has(`--twd-${l.key}`)))), o.append(s), t.append(o.el);
|
|
2351
|
+
const i = document.createElement("p");
|
|
2352
|
+
i.className = "role-note", i.textContent = r.length ? `Only elements styled with these tokens change. Not used on this page: ${r.map((l) => l.label).join(", ")}.` : "Each role points at a spacing step, so Density still scales it. Pick another step, or a fixed value.", t.append(i);
|
|
2353
|
+
}
|
|
2354
|
+
}, xe = [
|
|
2355
|
+
{ id: "overview", title: "Overview", icon: "tabOverview", parts: [["simple", "character"], ["simple", "summary"]] },
|
|
2356
|
+
{ id: "colour", title: "Colour", icon: "tabColour", parts: [
|
|
2357
|
+
["colour", "themeTiles"],
|
|
2358
|
+
{ heading: "Fine-tune", note: "Tailwind palette" },
|
|
2359
|
+
["colour", "primary"],
|
|
2360
|
+
["colour", "neutralTiles"],
|
|
2361
|
+
["colour", "custom"],
|
|
2362
|
+
["colour", "mode"],
|
|
2363
|
+
"warnings",
|
|
2364
|
+
{ more: "Colour tokens", parts: [["custom", "colours"]] }
|
|
2365
|
+
] },
|
|
2366
|
+
{ id: "type", title: "Type", icon: "tabType", parts: [
|
|
2367
|
+
["typography", "fontTiles"],
|
|
2368
|
+
["typography", "base"],
|
|
2369
|
+
["typography", "scaleTiles"],
|
|
2370
|
+
{ more: "Type tokens", parts: [["custom", "fonts"], ["custom", "type"], ["custom", "leading"], ["custom", "weights"]] }
|
|
2371
|
+
] },
|
|
2372
|
+
{ id: "spacing", title: "Spacing", icon: "tabSpacing", parts: [
|
|
2373
|
+
["density", "spacingTiles"],
|
|
2374
|
+
{ more: "Layout roles", parts: [["roles", "spacing"]] },
|
|
2375
|
+
{ more: "Spacing tokens", parts: [["custom", "spacing"]] }
|
|
2376
|
+
] },
|
|
2377
|
+
{ id: "shape", title: "Shape", icon: "tabShape", parts: [
|
|
2378
|
+
// Figma: "Shape — with styles" (257:711), agreed 2026-10-15
|
|
2379
|
+
{ heading: "Buttons" },
|
|
2380
|
+
["buttons", "styleTiles", { label: "Style" }],
|
|
2381
|
+
["buttons", "cornerTiles", { label: "Corners" }],
|
|
2382
|
+
["buttons", "sizeTiles", { label: "Size" }],
|
|
2383
|
+
{ heading: "Cards" },
|
|
2384
|
+
["cards", "styleTiles", { label: "Style" }],
|
|
2385
|
+
["cards", "cornerTiles", { label: "Corners" }],
|
|
2386
|
+
["cards", "borderTiles", { label: "Borders" }],
|
|
2387
|
+
{ more: "Shape tokens", parts: [["custom", "buttonStyle"], ["custom", "cardStyle"], ["custom", "cardShadow"], ["custom", "corners"], ["custom", "controls"], ["custom", "borders"]] }
|
|
2388
|
+
] },
|
|
2389
|
+
{ id: "shadows", title: "Shadows", icon: "tabShadows", parts: [
|
|
2390
|
+
["depth", "styleTiles"],
|
|
2391
|
+
["depth", "intensity"],
|
|
2392
|
+
{ more: "Shadow tokens", parts: [["custom", "shadows"]] }
|
|
2393
|
+
] }
|
|
2394
|
+
], O = (t) => String(t).replace(/[&<>"]/g, (e) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[e]);
|
|
2395
|
+
function mo({ sections: t, store: e, view: n }) {
|
|
2396
|
+
const a = document.createElement("div");
|
|
2397
|
+
a.id = "twiddle-root", document.body.appendChild(a);
|
|
2398
|
+
const o = a.attachShadow({ mode: "open" }), r = (d) => d.map((g) => `
|
|
2399
|
+
<details data-section="${g.id}">
|
|
2400
|
+
<summary>${g.title}<span class="chev">${A.chevron}</span></summary>
|
|
2401
|
+
<div class="section-body"><div class="placeholder">${g.hint}</div></div>
|
|
1571
2402
|
</details>
|
|
1572
2403
|
`).join("");
|
|
1573
|
-
|
|
1574
|
-
<style>${
|
|
2404
|
+
o.innerHTML = `
|
|
2405
|
+
<style>${ua}</style>
|
|
1575
2406
|
<div class="pick-box" hidden><span class="pick-tag"></span></div>
|
|
1576
2407
|
<div class="wrap">
|
|
1577
2408
|
<div class="toast" role="status"></div>
|
|
1578
2409
|
<div class="panel" role="dialog" aria-label="Panel" hidden>
|
|
1579
2410
|
<div class="head">
|
|
1580
2411
|
<span class="brand">Panel</span>
|
|
1581
|
-
<button class="icon-btn" data-action="pick" title="Pick an element to tweak on its own" aria-label="Pick an element" aria-pressed="false">${
|
|
1582
|
-
<button class="icon-btn" data-action="compare" title="Hold to see the original (Alt+C toggles)" aria-label="Compare with original" aria-pressed="false">${
|
|
1583
|
-
<button class="icon-btn" data-action="copy" title="Copy link to this version" aria-label="Copy link">${
|
|
1584
|
-
<button class="icon-btn" data-action="reset" title="Reset to defaults" aria-label="Reset">${
|
|
1585
|
-
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${
|
|
1586
|
-
</div>
|
|
1587
|
-
<div class="head subhead" hidden>
|
|
1588
|
-
<button class="icon-btn" data-action="page-back" title="Back" aria-label="Back">${$.back}</button>
|
|
1589
|
-
<span class="brand page-title"></span>
|
|
1590
|
-
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${$.close}</button>
|
|
2412
|
+
<button class="icon-btn" data-action="pick" title="Pick an element to tweak on its own" aria-label="Pick an element" aria-pressed="false">${A.pick}</button>
|
|
2413
|
+
<button class="icon-btn" data-action="compare" title="Hold to see the original (Alt+C toggles)" aria-label="Compare with original" aria-pressed="false">${A.compare}</button>
|
|
2414
|
+
<button class="icon-btn" data-action="copy" title="Copy link to this version" aria-label="Copy link">${A.link}</button>
|
|
2415
|
+
<button class="icon-btn" data-action="reset" title="Reset to defaults" aria-label="Reset">${A.reset}</button>
|
|
2416
|
+
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${A.close}</button>
|
|
1591
2417
|
</div>
|
|
1592
|
-
<div class="
|
|
1593
|
-
|
|
1594
|
-
<
|
|
1595
|
-
<div class="page" data-page="edit" hidden>
|
|
1596
|
-
${we.map((i) => `<button class="nav-row" data-go="${i.id}">${i.title}<span class="chev">${$.chevron}</span></button>`).join("")}
|
|
1597
|
-
</div>
|
|
1598
|
-
${we.map((i) => `<div class="page page-fields" data-page="${i.id}" hidden></div>`).join("")}
|
|
1599
|
-
</div>
|
|
1600
|
-
<div class="body" data-view="local" hidden></div>
|
|
1601
|
-
<div class="body" data-view="changes" hidden></div>
|
|
1602
|
-
<button class="edit-row" data-action="edit">${$.sliders}<span>Edit every token</span><span class="chev">${$.chevron}</span></button>
|
|
1603
|
-
<div class="foot">
|
|
1604
|
-
<button class="icon-btn" data-action="undo" title="Undo (⌘Z)" aria-label="Undo">${$.undo}</button>
|
|
1605
|
-
<button class="icon-btn" data-action="redo" title="Redo (⇧⌘Z)" aria-label="Redo">${$.redo}</button>
|
|
2418
|
+
<div class="actions">
|
|
2419
|
+
<button class="icon-btn" data-action="undo" title="Undo (⌘Z)" aria-label="Undo">${A.undo}</button>
|
|
2420
|
+
<button class="icon-btn" data-action="redo" title="Redo (⇧⌘Z)" aria-label="Redo">${A.redo}</button>
|
|
1606
2421
|
<button class="text-btn" data-action="changes" aria-pressed="false">Changes <span class="badge" hidden></span></button>
|
|
1607
2422
|
<span class="spacer"></span>
|
|
1608
2423
|
<div class="apply">
|
|
1609
|
-
<button class="apply-btn" data-action="apply" title="Copy these tweaks as a prompt for your coding agent"
|
|
1610
|
-
<button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${
|
|
2424
|
+
<button class="apply-btn" data-action="apply" title="Copy these tweaks as a prompt for your coding agent">Copy prompt</button>
|
|
2425
|
+
<button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${A.down}</button>
|
|
1611
2426
|
<div class="menu apply-menu" role="menu" hidden>
|
|
1612
2427
|
<button role="menuitemradio" data-apply-mode="preview">Preview before copying</button>
|
|
1613
2428
|
<button role="menuitemradio" data-apply-mode="copy">Copy without preview</button>
|
|
1614
2429
|
</div>
|
|
1615
2430
|
</div>
|
|
1616
2431
|
</div>
|
|
2432
|
+
<div class="original-pill" hidden>Showing original</div>
|
|
2433
|
+
<div class="body" data-view="global"><div class="setup-hint" role="status" hidden></div>
|
|
2434
|
+
${xe.map((d) => `<div class="page page-fields" data-page="${d.id}" hidden></div>`).join("")}
|
|
2435
|
+
</div>
|
|
2436
|
+
<div class="body" data-view="local" hidden></div>
|
|
2437
|
+
<div class="body" data-view="changes" hidden></div>
|
|
2438
|
+
<nav class="tabbar" aria-label="Areas">
|
|
2439
|
+
${xe.map((d) => `<button class="tab" data-tab="${d.id}" aria-pressed="false">${A[d.icon]}<span>${d.title}</span></button>`).join("")}
|
|
2440
|
+
</nav>
|
|
1617
2441
|
</div>
|
|
1618
|
-
<button class="launcher" aria-label="Open Panel" aria-expanded="false">${
|
|
2442
|
+
<button class="launcher" aria-label="Open Panel" aria-expanded="false">${A.panelMark}</button>
|
|
1619
2443
|
</div>
|
|
1620
2444
|
<div class="dialog-backdrop" hidden>
|
|
1621
2445
|
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="twd-dialog-title">
|
|
1622
2446
|
<div class="dialog-head">
|
|
1623
2447
|
<span id="twd-dialog-title">Prompt for your agent</span>
|
|
1624
|
-
<button class="icon-btn" data-action="dialog-close" aria-label="Close">${
|
|
2448
|
+
<button class="icon-btn" data-action="dialog-close" aria-label="Close">${A.close}</button>
|
|
1625
2449
|
</div>
|
|
1626
2450
|
<textarea class="prompt-text" readonly spellcheck="false" aria-label="Generated prompt"></textarea>
|
|
1627
2451
|
<div class="dialog-foot">
|
|
@@ -1631,754 +2455,421 @@ function Mo({ sections: t, store: e, view: n }) {
|
|
|
1631
2455
|
</div>
|
|
1632
2456
|
</div>
|
|
1633
2457
|
`;
|
|
1634
|
-
const
|
|
1635
|
-
function
|
|
1636
|
-
|
|
1637
|
-
if (typeof
|
|
1638
|
-
const
|
|
1639
|
-
|
|
2458
|
+
const s = (d) => o.querySelector(d), i = s(".panel"), l = s(".launcher"), p = s(".toast"), c = { global: s('[data-view="global"]'), local: s('[data-view="local"]'), changes: s('[data-view="changes"]') }, h = s(".pick-box"), y = s(".pick-tag"), f = { getState: e.getState, setSection: e.setSection, setSections: e.setSections, onChange: e.onChange };
|
|
2459
|
+
function m(d, g, w) {
|
|
2460
|
+
g.forEach((k) => {
|
|
2461
|
+
if (typeof k.render != "function") return;
|
|
2462
|
+
const C = d.querySelector(`[data-section="${k.id}"] .section-body`);
|
|
2463
|
+
C.innerHTML = "", k.render(C, w);
|
|
1640
2464
|
});
|
|
1641
|
-
const
|
|
1642
|
-
return
|
|
1643
|
-
|
|
1644
|
-
|
|
2465
|
+
const $ = [...d.querySelectorAll("details")];
|
|
2466
|
+
return $.forEach((k) => k.addEventListener("toggle", () => {
|
|
2467
|
+
k.open && $.forEach((C) => {
|
|
2468
|
+
C !== k && (C.open = !1);
|
|
1645
2469
|
});
|
|
1646
|
-
})),
|
|
2470
|
+
})), $;
|
|
1647
2471
|
}
|
|
1648
|
-
const
|
|
1649
|
-
function
|
|
1650
|
-
|
|
2472
|
+
const x = Object.fromEntries(t.map((d) => [d.id, d]));
|
|
2473
|
+
function b(d, g) {
|
|
2474
|
+
g.forEach((w) => {
|
|
1651
2475
|
if (w === "warnings") {
|
|
1652
|
-
const
|
|
1653
|
-
|
|
2476
|
+
const S = document.createElement("div");
|
|
2477
|
+
S.className = "warnings page-warnings", d.append(S);
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
if (w.heading) {
|
|
2481
|
+
const S = document.createElement("div");
|
|
2482
|
+
S.className = "page-heading", S.innerHTML = `<span></span>${w.note ? '<span class="muted"></span>' : ""}`, S.firstChild.textContent = w.heading, w.note && (S.lastChild.textContent = w.note), d.append(S);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
if (w.more) {
|
|
2486
|
+
const S = document.createElement("details");
|
|
2487
|
+
S.className = "more", S.innerHTML = `<summary><span class="chev" aria-hidden="true">${A.chevron}</span><span></span></summary><div class="more-body"></div>`, S.querySelector("summary span:last-child").textContent = w.more, b(S.querySelector(".more-body"), w.parts), d.append(S);
|
|
1654
2488
|
return;
|
|
1655
2489
|
}
|
|
1656
2490
|
if (w.tabs) {
|
|
1657
|
-
const
|
|
1658
|
-
const
|
|
1659
|
-
return
|
|
1660
|
-
}),
|
|
2491
|
+
const S = w.tabs.map((j) => {
|
|
2492
|
+
const z = document.createElement("div");
|
|
2493
|
+
return z.className = "tab-pane", b(z, j.parts), z;
|
|
2494
|
+
}), he = re({
|
|
1661
2495
|
label: "View",
|
|
1662
|
-
options: w.tabs.map((
|
|
1663
|
-
onSelect: (
|
|
1664
|
-
|
|
1665
|
-
|
|
2496
|
+
options: w.tabs.map((j, z) => ({ value: String(z), label: j.label })),
|
|
2497
|
+
onSelect: (j) => {
|
|
2498
|
+
he.setValue(j), S.forEach((z, Zn) => {
|
|
2499
|
+
z.hidden = String(Zn) !== j;
|
|
1666
2500
|
});
|
|
1667
2501
|
}
|
|
1668
2502
|
});
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
}),
|
|
2503
|
+
he.setValue("0"), S.forEach((j, z) => {
|
|
2504
|
+
j.hidden = z !== 0;
|
|
2505
|
+
}), d.append(he.el, ...S);
|
|
1672
2506
|
return;
|
|
1673
2507
|
}
|
|
1674
|
-
const [
|
|
1675
|
-
|
|
2508
|
+
const [$, k, C = {}] = w, q = $ === "simple" ? po[k] : $ === "roles" ? go[k] : x[$]?.parts?.[k];
|
|
2509
|
+
q && q(d, f, { ...C, buttons: x.buttons, cards: x.cards, goTab: (S) => {
|
|
2510
|
+
G("global"), E(S);
|
|
2511
|
+
} });
|
|
1676
2512
|
});
|
|
1677
2513
|
}
|
|
1678
|
-
|
|
1679
|
-
const u =
|
|
1680
|
-
let
|
|
1681
|
-
function
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
2514
|
+
xe.forEach((d) => b(c.global.querySelector(`[data-page="${d.id}"]`), d.parts));
|
|
2515
|
+
const u = [...o.querySelectorAll(".tab")];
|
|
2516
|
+
let v = xe.some((d) => d.id === fe("tab")) ? fe("tab") : xe[0].id;
|
|
2517
|
+
function L() {
|
|
2518
|
+
u.forEach((d) => d.setAttribute("aria-pressed", String(K === "global" && d.dataset.tab === v)));
|
|
2519
|
+
}
|
|
2520
|
+
function E(d) {
|
|
2521
|
+
v = d, ge("tab", d), c.global.querySelectorAll(".page").forEach((g) => {
|
|
2522
|
+
g.hidden = g.dataset.page !== d;
|
|
2523
|
+
}), c.global.scrollTop = 0, L();
|
|
2524
|
+
}
|
|
2525
|
+
const J = (d) => d.map((g) => `<div class="warning">${A.warn}<span>${O(g.text)}</span></div>`).join("");
|
|
2526
|
+
let K = "global";
|
|
2527
|
+
function G(d) {
|
|
2528
|
+
K = d, Object.entries(c).forEach(([g, w]) => {
|
|
2529
|
+
w.hidden = g !== d;
|
|
2530
|
+
}), s('[data-action="changes"]').setAttribute("aria-pressed", String(d === "changes")), d !== "local" && Hn(), d === "changes" && Ut(), L();
|
|
2531
|
+
}
|
|
2532
|
+
E(v);
|
|
2533
|
+
const qe = "cubic-bezier(0.3, 0.7, 0.2, 1)", Ve = "cubic-bezier(0.2, 0.8, 0.2, 1)", Se = 640, ie = 100, He = 50, Un = () => l.querySelector(".mark-knob"), qn = () => [l.querySelector(".mark-t1"), l.querySelector(".mark-t2")];
|
|
2534
|
+
function Et(d) {
|
|
2535
|
+
if (matchMedia("(prefers-reduced-motion: reduce)").matches || !l.animate) return;
|
|
2536
|
+
const g = Un(), [w, $] = qn(), k = (C) => `translateY(${C}px)`;
|
|
2537
|
+
if (d) {
|
|
2538
|
+
g.animate([{ transform: k(32) }, { transform: k(-6), offset: 0.78 }, { transform: k(0) }], { duration: Se, easing: qe });
|
|
2539
|
+
const C = [{ transform: "scale(0.4)", opacity: 0 }, { transform: "scale(1)", opacity: 1 }];
|
|
2540
|
+
$.animate(C, { duration: ie * 2, delay: Se * 0.12, easing: qe, fill: "backwards" }), w.animate(C, { duration: ie * 2, delay: Se * 0.32 + He, easing: qe, fill: "backwards" });
|
|
2541
|
+
} else {
|
|
2542
|
+
const C = [{ opacity: 1 }, { opacity: 0 }];
|
|
2543
|
+
w.animate(C, { duration: ie, easing: Ve }), $.animate(C, { duration: ie, delay: He / 2, easing: Ve, fill: "backwards" }), g.animate([{ transform: k(0) }, { transform: k(34), offset: 0.8 }, { transform: k(32) }], { duration: Se * 0.7, delay: ie + He / 2, easing: Ve, fill: "backwards" });
|
|
2544
|
+
}
|
|
1689
2545
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
function Ce(i) {
|
|
1693
|
-
ce = i, Object.entries(p).forEach(([h, w]) => {
|
|
1694
|
-
w.hidden = h !== i;
|
|
1695
|
-
}), r('[data-action="changes"]').setAttribute("aria-pressed", String(i === "changes")), i !== "local" && bn(), i === "changes" && bt(), Z();
|
|
2546
|
+
function le({ animate: d = !0 } = {}) {
|
|
2547
|
+
i.hidden && d && Et(!0), i.hidden = !1, l.setAttribute("aria-expanded", "true"), l.setAttribute("aria-label", "Close Panel"), ge("panel", "open");
|
|
1696
2548
|
}
|
|
1697
|
-
function
|
|
1698
|
-
|
|
2549
|
+
function _e() {
|
|
2550
|
+
i.hidden || Et(!1), i.hidden = !0, l.setAttribute("aria-expanded", "false"), l.setAttribute("aria-label", "Open Panel"), ge("panel", "closed"), $e();
|
|
1699
2551
|
}
|
|
1700
|
-
function
|
|
1701
|
-
|
|
2552
|
+
function Ke() {
|
|
2553
|
+
i.hidden ? le() : _e();
|
|
1702
2554
|
}
|
|
1703
|
-
|
|
1704
|
-
|
|
2555
|
+
let Ct;
|
|
2556
|
+
function H(d) {
|
|
2557
|
+
p.textContent = d, p.classList.add("show"), clearTimeout(Ct), Ct = setTimeout(() => p.classList.remove("show"), 1600);
|
|
1705
2558
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
c.textContent = i, c.classList.add("show"), clearTimeout(nt), nt = setTimeout(() => c.classList.remove("show"), 1600);
|
|
2559
|
+
function de(d, g) {
|
|
2560
|
+
navigator.clipboard.writeText(d).then(() => H(g)).catch(() => H("Couldn’t copy — select the text and copy it instead"));
|
|
1709
2561
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
function G(i, h) {
|
|
1715
|
-
if (Te = i, !i) {
|
|
1716
|
-
m.hidden = !0;
|
|
2562
|
+
let Ge = null;
|
|
2563
|
+
function Q(d, g) {
|
|
2564
|
+
if (Ge = d, !d) {
|
|
2565
|
+
h.hidden = !0;
|
|
1717
2566
|
return;
|
|
1718
2567
|
}
|
|
1719
|
-
const w =
|
|
1720
|
-
Object.assign(
|
|
2568
|
+
const w = d.getBoundingClientRect();
|
|
2569
|
+
Object.assign(h.style, { top: `${w.top}px`, left: `${w.left}px`, width: `${w.width}px`, height: `${w.height}px` }), y.textContent = g || bt(d), h.hidden = !1;
|
|
1721
2570
|
}
|
|
1722
|
-
const
|
|
1723
|
-
|
|
2571
|
+
const Tt = () => {
|
|
2572
|
+
Ge && !h.hidden && Q(Ge, y.textContent);
|
|
1724
2573
|
};
|
|
1725
|
-
window.addEventListener("scroll",
|
|
1726
|
-
let
|
|
1727
|
-
const
|
|
1728
|
-
function
|
|
1729
|
-
if (
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1732
|
-
}
|
|
1733
|
-
function
|
|
1734
|
-
if (
|
|
1735
|
-
const
|
|
1736
|
-
!
|
|
1737
|
-
}
|
|
1738
|
-
function
|
|
1739
|
-
|
|
1740
|
-
}
|
|
1741
|
-
const
|
|
1742
|
-
function
|
|
1743
|
-
if (!
|
|
1744
|
-
if (
|
|
1745
|
-
const
|
|
1746
|
-
|
|
2574
|
+
window.addEventListener("scroll", Tt, !0), window.addEventListener("resize", Tt);
|
|
2575
|
+
let ce = !1;
|
|
2576
|
+
const Mt = s('[data-action="pick"]'), At = (d) => d.composedPath().includes(a);
|
|
2577
|
+
function Lt(d) {
|
|
2578
|
+
if (At(d)) return;
|
|
2579
|
+
const g = d.target;
|
|
2580
|
+
g && g.nodeType === 1 && g !== document.documentElement && g !== document.body && Q(g);
|
|
2581
|
+
}
|
|
2582
|
+
function Pt(d) {
|
|
2583
|
+
if (At(d) || (d.preventDefault(), d.stopPropagation(), d.type !== "click")) return;
|
|
2584
|
+
const g = d.target;
|
|
2585
|
+
!g || g === document.documentElement || g === document.body || ($e(), jt(g));
|
|
2586
|
+
}
|
|
2587
|
+
function Ot(d) {
|
|
2588
|
+
d.key === "Escape" && (d.preventDefault(), $e(), M && Q(M.el));
|
|
2589
|
+
}
|
|
2590
|
+
const Ft = ["pointerdown", "mousedown", "mouseup", "click"];
|
|
2591
|
+
function Nt() {
|
|
2592
|
+
if (!ce) {
|
|
2593
|
+
if (ce = !0, !document.getElementById("twiddle-picking")) {
|
|
2594
|
+
const d = document.createElement("style");
|
|
2595
|
+
d.id = "twiddle-picking", d.textContent = "html.twd-picking, html.twd-picking * { cursor: crosshair !important; }", document.head.appendChild(d);
|
|
1747
2596
|
}
|
|
1748
|
-
|
|
2597
|
+
le(), Mt.setAttribute("aria-pressed", "true"), document.documentElement.classList.add("twd-picking"), document.addEventListener("pointermove", Lt, !0), Ft.forEach((d) => document.addEventListener(d, Pt, !0)), document.addEventListener("keydown", Ot, !0), H("Click an element · Esc to cancel");
|
|
1749
2598
|
}
|
|
1750
2599
|
}
|
|
1751
|
-
function
|
|
1752
|
-
|
|
2600
|
+
function $e() {
|
|
2601
|
+
ce && (ce = !1, Mt.setAttribute("aria-pressed", "false"), document.documentElement.classList.remove("twd-picking"), document.removeEventListener("pointermove", Lt, !0), Ft.forEach((d) => document.removeEventListener(d, Pt, !0)), document.removeEventListener("keydown", Ot, !0), Q(M ? M.el : null));
|
|
1753
2602
|
}
|
|
1754
2603
|
let M = null;
|
|
1755
|
-
function
|
|
2604
|
+
function Vn(d, g) {
|
|
1756
2605
|
const w = [];
|
|
1757
2606
|
return {
|
|
1758
2607
|
ctx: {
|
|
1759
|
-
getState: () => (e.getState().scopes || {})[
|
|
1760
|
-
setSection: (
|
|
1761
|
-
onChange: (
|
|
1762
|
-
const
|
|
1763
|
-
return w.push(
|
|
2608
|
+
getState: () => (e.getState().scopes || {})[d] || {},
|
|
2609
|
+
setSection: ($, k) => e.setScope(d, $, k),
|
|
2610
|
+
onChange: ($) => {
|
|
2611
|
+
const k = e.onChange($);
|
|
2612
|
+
return w.push(k), k;
|
|
1764
2613
|
},
|
|
1765
|
-
readToken: (
|
|
2614
|
+
readToken: ($) => getComputedStyle(g).getPropertyValue($).trim()
|
|
1766
2615
|
},
|
|
1767
|
-
dispose: () => w.forEach((
|
|
2616
|
+
dispose: () => w.forEach(($) => $())
|
|
1768
2617
|
};
|
|
1769
2618
|
}
|
|
1770
|
-
function
|
|
1771
|
-
const w =
|
|
1772
|
-
|
|
1773
|
-
} },
|
|
2619
|
+
function jt(d, g) {
|
|
2620
|
+
const w = xa(d), $ = e.getState().scopes || {};
|
|
2621
|
+
g || (g = !$[w.elementSelector] && w.classSelector && $[w.classSelector] ? "class" : "element"), M && M.dispose(), M = { el: d, info: w, target: g, dispose: () => {
|
|
2622
|
+
} }, We(), G("local"), le(), Q(d);
|
|
1774
2623
|
}
|
|
1775
|
-
const
|
|
1776
|
-
function
|
|
2624
|
+
const Rt = () => M.target === "class" ? M.info.classSelector : M.info.elementSelector;
|
|
2625
|
+
function We() {
|
|
1777
2626
|
M.dispose();
|
|
1778
|
-
const { info:
|
|
2627
|
+
const { info: d } = M, g = t.filter((j) => (j.uses || []).some((z) => d.tokens.has(z))), w = c.local;
|
|
1779
2628
|
if (w.innerHTML = `
|
|
1780
2629
|
<div class="scope-bar">
|
|
1781
2630
|
<div class="scope-head">
|
|
1782
|
-
<button class="icon-btn" data-action="back" title="Back to the whole page" aria-label="Back to the whole page">${
|
|
2631
|
+
<button class="icon-btn" data-action="back" title="Back to the whole page" aria-label="Back to the whole page">${A.back}</button>
|
|
1783
2632
|
<div class="scope-name">
|
|
1784
|
-
<code>${
|
|
1785
|
-
${
|
|
2633
|
+
<code>${O(bt(d.el))}</code>
|
|
2634
|
+
${d.text ? `<span class="muted">“${O(d.text)}”</span>` : ""}
|
|
1786
2635
|
</div>
|
|
1787
2636
|
</div>
|
|
1788
2637
|
<div class="scope-target"></div>
|
|
1789
2638
|
<div class="warnings"></div>
|
|
1790
2639
|
</div>
|
|
1791
|
-
${
|
|
1792
|
-
`,
|
|
1793
|
-
const
|
|
2640
|
+
${g.length ? r(g) : '<div class="empty">This element doesn’t use Panel tokens yet, so there’s nothing to tweak here. Style it with <code>var(--twd-…)</code> to make it tweakable.</div>'}
|
|
2641
|
+
`, d.classSelector) {
|
|
2642
|
+
const j = re({
|
|
1794
2643
|
label: "Apply to",
|
|
1795
2644
|
options: [
|
|
1796
2645
|
{ value: "element", label: "This element" },
|
|
1797
|
-
{ value: "class", label: `All ${
|
|
2646
|
+
{ value: "class", label: `All ${d.classSelector} (${d.classCount})`, title: `Every element matching ${d.classSelector}` }
|
|
1798
2647
|
],
|
|
1799
|
-
onSelect: (
|
|
1800
|
-
|
|
2648
|
+
onSelect: (z) => {
|
|
2649
|
+
z === "class" && e.moveScope(d.elementSelector, d.classSelector), M.target = z, We();
|
|
1801
2650
|
}
|
|
1802
2651
|
});
|
|
1803
|
-
|
|
2652
|
+
j.setValue(M.target), w.querySelector(".scope-target").append(j.el);
|
|
1804
2653
|
}
|
|
1805
|
-
const { ctx:
|
|
1806
|
-
|
|
1807
|
-
const
|
|
1808
|
-
const
|
|
1809
|
-
|
|
2654
|
+
const { ctx: $, dispose: k } = Vn(Rt(), d.el), C = m(w, g, $);
|
|
2655
|
+
C[0] && (C[0].open = !0);
|
|
2656
|
+
const q = w.querySelector(".warnings"), S = () => {
|
|
2657
|
+
const j = d.tokens.size ? Ta(d.el) : null;
|
|
2658
|
+
q.innerHTML = j ? J([j]) : "";
|
|
1810
2659
|
};
|
|
1811
|
-
|
|
1812
|
-
const
|
|
2660
|
+
S();
|
|
2661
|
+
const he = e.onChange(S);
|
|
1813
2662
|
M.dispose = () => {
|
|
1814
|
-
|
|
2663
|
+
k(), he();
|
|
1815
2664
|
};
|
|
1816
2665
|
}
|
|
1817
|
-
function
|
|
2666
|
+
function zt() {
|
|
1818
2667
|
if (!M || !M.info.classSelector) return;
|
|
1819
|
-
const
|
|
1820
|
-
!
|
|
2668
|
+
const d = e.getState().scopes || {}, g = M.target === "class" ? "element" : "class", w = g === "class" ? M.info.classSelector : M.info.elementSelector;
|
|
2669
|
+
!d[Rt()] && d[w] && (M.target = g, We());
|
|
1821
2670
|
}
|
|
1822
|
-
const
|
|
1823
|
-
e.undo(),
|
|
1824
|
-
},
|
|
1825
|
-
e.redo(),
|
|
2671
|
+
const It = () => {
|
|
2672
|
+
e.undo(), zt();
|
|
2673
|
+
}, Dt = () => {
|
|
2674
|
+
e.redo(), zt();
|
|
1826
2675
|
};
|
|
1827
|
-
function
|
|
1828
|
-
M && (M.dispose(), M = null,
|
|
1829
|
-
}
|
|
1830
|
-
const
|
|
1831
|
-
function
|
|
1832
|
-
const
|
|
1833
|
-
if (!
|
|
1834
|
-
w.innerHTML = `<div class="empty">No changes yet. Tweak a section, or pick an element to change it on its own.</div>${
|
|
2676
|
+
function Hn() {
|
|
2677
|
+
M && (M.dispose(), M = null, c.local.innerHTML = "", Q(null));
|
|
2678
|
+
}
|
|
2679
|
+
const Bt = (d) => d.kind === "global" ? "Whole page" : d.kind === "class" ? `All ${d.selector}` : "One element";
|
|
2680
|
+
function Ut() {
|
|
2681
|
+
const d = en(e.getState(), t), g = Je(), w = c.changes;
|
|
2682
|
+
if (!d.length) {
|
|
2683
|
+
w.innerHTML = `<div class="empty">No changes yet. Tweak a section, or pick an element to change it on its own.</div>${J(g)}`;
|
|
1835
2684
|
return;
|
|
1836
2685
|
}
|
|
1837
|
-
const
|
|
1838
|
-
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
}), w.innerHTML = (
|
|
2686
|
+
const $ = /* @__PURE__ */ new Map();
|
|
2687
|
+
d.forEach((k) => {
|
|
2688
|
+
const C = k.selector || "";
|
|
2689
|
+
$.has(C) || $.set(C, []), $.get(C).push(k);
|
|
2690
|
+
}), w.innerHTML = (g.length ? `<div class="warnings">${J(g)}</div>` : "") + [...$].map(([k, C]) => `
|
|
1842
2691
|
<div class="change-group">
|
|
1843
2692
|
<div class="change-scope">
|
|
1844
|
-
${
|
|
2693
|
+
${k ? `<button class="link-btn" data-select="${O(k)}" title="Select it on the page">${O(Bt(C[0]))}</button><code>${O(k)}</code>` : `<span>${Bt(C[0])}</span>`}
|
|
1845
2694
|
</div>
|
|
1846
|
-
${
|
|
2695
|
+
${C.map((q) => `
|
|
1847
2696
|
<div class="change-row">
|
|
1848
2697
|
<div class="change-main">
|
|
1849
|
-
<div class="change-title">${
|
|
1850
|
-
${
|
|
2698
|
+
<div class="change-title">${O(q.title)} <span class="muted">›</span> ${q.settings.map((S) => `${O(S.label)}: <b>${O(S.value)}</b>`).join(", ")}</div>
|
|
2699
|
+
${q.tokens.map((S) => `<div class="change-token"><code>${O(S.name)}</code> ${S.from ? `${O(S.from)} → ` : ""}${O(S.to)}</div>`).join("")}
|
|
1851
2700
|
</div>
|
|
1852
|
-
<button class="icon-btn" data-remove="${
|
|
2701
|
+
<button class="icon-btn" data-remove="${O(q.sectionId)}" data-remove-scope="${O(k)}" aria-label="Remove ${O(q.title)} change">${A.close}</button>
|
|
1853
2702
|
</div>`).join("")}
|
|
1854
2703
|
</div>`).join("");
|
|
1855
2704
|
}
|
|
1856
|
-
function
|
|
1857
|
-
if (
|
|
1858
|
-
const w = e.getState()[
|
|
1859
|
-
e.setSection(
|
|
2705
|
+
function _n(d, g) {
|
|
2706
|
+
if (g) return e.removeScope(g, d);
|
|
2707
|
+
const w = e.getState()[d] || {};
|
|
2708
|
+
e.setSection(d, Object.fromEntries(Object.keys(w).map(($) => [$, null])));
|
|
1860
2709
|
}
|
|
1861
|
-
const
|
|
1862
|
-
function
|
|
1863
|
-
|
|
2710
|
+
const pe = s(".apply-menu"), qt = s('[data-action="apply-menu"]'), Vt = () => fe("apply-mode") === "copy" ? "copy" : "preview";
|
|
2711
|
+
function Kn() {
|
|
2712
|
+
pe.querySelectorAll("[data-apply-mode]").forEach((d) => d.setAttribute("aria-checked", String(d.dataset.applyMode === Vt())));
|
|
1864
2713
|
}
|
|
1865
|
-
function
|
|
1866
|
-
|
|
2714
|
+
function Ee(d) {
|
|
2715
|
+
pe.hidden = !d, qt.setAttribute("aria-expanded", String(d)), d && Kn();
|
|
1867
2716
|
}
|
|
1868
|
-
function
|
|
1869
|
-
return
|
|
2717
|
+
function Ht() {
|
|
2718
|
+
return Sa(en(e.getState(), t), { warnings: Je() });
|
|
1870
2719
|
}
|
|
1871
|
-
const
|
|
1872
|
-
function
|
|
1873
|
-
const
|
|
1874
|
-
if (!
|
|
1875
|
-
if (
|
|
1876
|
-
|
|
2720
|
+
const Ce = s(".dialog-backdrop"), _t = s(".prompt-text");
|
|
2721
|
+
function Gn() {
|
|
2722
|
+
const d = Ht();
|
|
2723
|
+
if (!d) return H("No changes to copy yet");
|
|
2724
|
+
if (Vt() === "copy") return de(d, "Prompt copied · paste it into your agent");
|
|
2725
|
+
_t.value = d, Ce.hidden = !1, s('[data-action="dialog-copy"]').focus();
|
|
1877
2726
|
}
|
|
1878
|
-
function
|
|
1879
|
-
|
|
2727
|
+
function Ye() {
|
|
2728
|
+
Ce.hidden = !0, s('[data-action="apply"]').focus();
|
|
1880
2729
|
}
|
|
1881
|
-
const
|
|
1882
|
-
function
|
|
1883
|
-
|
|
2730
|
+
const ee = s('[data-action="compare"]'), Wn = s(".original-pill");
|
|
2731
|
+
function Te(d) {
|
|
2732
|
+
d ? n.suspend() : n.resume(), ee.setAttribute("aria-pressed", String(d)), Wn.hidden = !d;
|
|
1884
2733
|
}
|
|
1885
|
-
let
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2734
|
+
let Kt = null, ue = !1;
|
|
2735
|
+
ee.addEventListener("pointerdown", () => {
|
|
2736
|
+
ue = !1, Kt = setTimeout(() => {
|
|
2737
|
+
ue = !0, Te(!0);
|
|
1889
2738
|
}, 180);
|
|
1890
2739
|
});
|
|
1891
|
-
const
|
|
1892
|
-
clearTimeout(
|
|
2740
|
+
const Gt = () => {
|
|
2741
|
+
clearTimeout(Kt), ue && (Te(!1), ue = !1);
|
|
1893
2742
|
};
|
|
1894
|
-
|
|
1895
|
-
const
|
|
2743
|
+
ee.addEventListener("pointerup", Gt), ee.addEventListener("pointerleave", Gt);
|
|
2744
|
+
const Me = s(".setup-hint"), Wt = `setup-hint-dismissed:${location.pathname}`, Yn = {
|
|
1896
2745
|
"no-tokens": {
|
|
1897
2746
|
title: "The Panel tokens aren’t loaded",
|
|
1898
2747
|
text: "The knobs change the --twd-* variables, but this page doesn’t load them. Add the token stylesheet before your own CSS:",
|
|
1899
|
-
snippets:
|
|
1900
|
-
link: `${
|
|
2748
|
+
snippets: sn,
|
|
2749
|
+
link: `${rn}/#install`
|
|
1901
2750
|
},
|
|
1902
2751
|
unused: {
|
|
1903
2752
|
title: "Nothing here uses the Panel tokens yet",
|
|
1904
2753
|
text: "The tokens are loaded, but no style on this page uses var(--twd-…), so the knobs won’t change anything. Ask your coding agent to convert the styles:",
|
|
1905
2754
|
copy: "Copy prompt for your agent",
|
|
1906
2755
|
copied: "Prompt copied",
|
|
1907
|
-
text2copy:
|
|
1908
|
-
link: `${
|
|
2756
|
+
text2copy: Na,
|
|
2757
|
+
link: `${rn}/#tokens`
|
|
1909
2758
|
}
|
|
1910
2759
|
};
|
|
1911
|
-
let
|
|
1912
|
-
function
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1915
|
-
<div class="setup-head">${
|
|
1916
|
-
<button class="icon-btn" data-action="setup-dismiss" title="Hide this message" aria-label="Hide this message">${
|
|
2760
|
+
let D = null;
|
|
2761
|
+
function Ae() {
|
|
2762
|
+
const d = Oa();
|
|
2763
|
+
D = fe(Wt) === d ? null : Yn[d] || null, l.toggleAttribute("data-hint", !!D), Me.hidden = !D, D && (Me.innerHTML = `
|
|
2764
|
+
<div class="setup-head">${A.warn}<b>${O(D.title)}</b>
|
|
2765
|
+
<button class="icon-btn" data-action="setup-dismiss" title="Hide this message" aria-label="Hide this message">${A.close}</button>
|
|
1917
2766
|
</div>
|
|
1918
|
-
<p>${
|
|
1919
|
-
${(
|
|
2767
|
+
<p>${O(D.text)}</p>
|
|
2768
|
+
${(D.snippets || []).map((g, w) => `
|
|
1920
2769
|
<div class="setup-snippet">
|
|
1921
|
-
<span>${
|
|
1922
|
-
<div class="setup-code"><code>${
|
|
2770
|
+
<span>${O(g.label)}</span>
|
|
2771
|
+
<div class="setup-code"><code>${O(g.code)}</code><button class="text-btn" data-setup-snippet="${w}">Copy</button></div>
|
|
1923
2772
|
</div>`).join("")}
|
|
1924
2773
|
<div class="setup-actions">
|
|
1925
|
-
${
|
|
1926
|
-
<a href="${
|
|
1927
|
-
</div>`,
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
const
|
|
1931
|
-
function
|
|
1932
|
-
|
|
1933
|
-
const
|
|
1934
|
-
|
|
1935
|
-
const
|
|
1936
|
-
|
|
1937
|
-
w.innerHTML =
|
|
1938
|
-
}),
|
|
1939
|
-
}
|
|
1940
|
-
return e.onChange(
|
|
1941
|
-
if (
|
|
1942
|
-
const
|
|
1943
|
-
if (
|
|
1944
|
-
if (
|
|
1945
|
-
if (
|
|
1946
|
-
const w = document.querySelector(
|
|
1947
|
-
return w ?
|
|
2774
|
+
${D.copy ? `<button class="apply-btn" data-action="setup-copy">${O(D.copy)}</button>` : ""}
|
|
2775
|
+
<a href="${D.link}" target="_blank" rel="noopener">Setup guide</a>
|
|
2776
|
+
</div>`, Me.dataset.status = d);
|
|
2777
|
+
}
|
|
2778
|
+
Ae(), document.readyState !== "complete" && window.addEventListener("load", Ae, { once: !0 }), setTimeout(Ae, 1500);
|
|
2779
|
+
const Yt = s(".badge");
|
|
2780
|
+
function Zt() {
|
|
2781
|
+
s('[data-action="undo"]').disabled = !e.canUndo(), s('[data-action="redo"]').disabled = !e.canRedo();
|
|
2782
|
+
const d = ya(e.getState());
|
|
2783
|
+
Yt.textContent = d, Yt.hidden = !d;
|
|
2784
|
+
const g = J(Je());
|
|
2785
|
+
c.global.querySelectorAll(".page-warnings").forEach((w) => {
|
|
2786
|
+
w.innerHTML = g;
|
|
2787
|
+
}), K === "changes" && Ut();
|
|
2788
|
+
}
|
|
2789
|
+
return e.onChange(Zt), Zt(), o.addEventListener("click", (d) => {
|
|
2790
|
+
if (d.target === Ce) return Ye();
|
|
2791
|
+
const g = d.target.closest("button");
|
|
2792
|
+
if (g) {
|
|
2793
|
+
if (g !== qt && !pe.contains(g) && Ee(!1), g === l) return Ke();
|
|
2794
|
+
if (g.dataset.select) {
|
|
2795
|
+
const w = document.querySelector(g.dataset.select);
|
|
2796
|
+
return w ? jt(w) : H("That element isn’t on this page");
|
|
1948
2797
|
}
|
|
1949
|
-
if (
|
|
1950
|
-
if (
|
|
1951
|
-
|
|
1952
|
-
if (
|
|
1953
|
-
|
|
1954
|
-
|
|
2798
|
+
if (g.dataset.setupSnippet) return de(sn[g.dataset.setupSnippet].code, "Copied");
|
|
2799
|
+
if (g.dataset.tab)
|
|
2800
|
+
return G("global"), E(g.dataset.tab);
|
|
2801
|
+
if (g.dataset.remove) return _n(g.dataset.remove, g.dataset.removeScope);
|
|
2802
|
+
if (g.dataset.applyMode)
|
|
2803
|
+
return ge("apply-mode", g.dataset.applyMode), Ee(!1), H(g.dataset.applyMode === "copy" ? "Copy prompt copies straight away" : "Copy prompt shows a preview first");
|
|
2804
|
+
switch (g.dataset.action) {
|
|
1955
2805
|
case "close":
|
|
1956
|
-
return
|
|
2806
|
+
return _e();
|
|
1957
2807
|
case "reset":
|
|
1958
|
-
return e.resetState(),
|
|
2808
|
+
return e.resetState(), H("Reset to defaults · ⌘Z to undo");
|
|
1959
2809
|
case "copy":
|
|
1960
|
-
return
|
|
2810
|
+
return de(location.href, "Link copied");
|
|
1961
2811
|
case "pick":
|
|
1962
|
-
return
|
|
2812
|
+
return ce ? $e() : Nt();
|
|
1963
2813
|
case "compare":
|
|
1964
|
-
|
|
2814
|
+
ue || Te(ee.getAttribute("aria-pressed") !== "true");
|
|
1965
2815
|
return;
|
|
1966
2816
|
case "undo":
|
|
1967
|
-
return
|
|
2817
|
+
return It();
|
|
1968
2818
|
case "redo":
|
|
1969
|
-
return
|
|
2819
|
+
return Dt();
|
|
1970
2820
|
case "back":
|
|
1971
|
-
return
|
|
1972
|
-
case "edit":
|
|
1973
|
-
return Q("edit");
|
|
1974
|
-
case "page-back":
|
|
1975
|
-
return gn();
|
|
2821
|
+
return G("global");
|
|
1976
2822
|
case "changes":
|
|
1977
|
-
return
|
|
2823
|
+
return G(K === "changes" ? "global" : "changes");
|
|
1978
2824
|
case "apply":
|
|
1979
|
-
return
|
|
2825
|
+
return Gn();
|
|
1980
2826
|
case "apply-menu":
|
|
1981
|
-
return
|
|
2827
|
+
return Ee(pe.hidden);
|
|
1982
2828
|
case "dialog-close":
|
|
1983
|
-
return
|
|
2829
|
+
return Ye();
|
|
1984
2830
|
case "dialog-copy":
|
|
1985
|
-
return
|
|
2831
|
+
return de(_t.value, "Prompt copied · paste it into your agent");
|
|
1986
2832
|
case "setup-copy":
|
|
1987
|
-
return
|
|
2833
|
+
return D && de(D.text2copy, D.copied);
|
|
1988
2834
|
case "setup-dismiss":
|
|
1989
|
-
return
|
|
2835
|
+
return ge(Wt, Me.dataset.status), Ae();
|
|
1990
2836
|
}
|
|
1991
2837
|
}
|
|
1992
|
-
}),
|
|
1993
|
-
if (
|
|
1994
|
-
if (!
|
|
1995
|
-
if (!
|
|
2838
|
+
}), o.addEventListener("keydown", (d) => {
|
|
2839
|
+
if (d.key === "Escape") {
|
|
2840
|
+
if (!Ce.hidden) return Ye();
|
|
2841
|
+
if (!pe.hidden) return Ee(!1);
|
|
1996
2842
|
}
|
|
1997
|
-
!(
|
|
1998
|
-
}), document.addEventListener("keydown", (
|
|
1999
|
-
|
|
2000
|
-
}),
|
|
2001
|
-
}
|
|
2002
|
-
const Ao = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>', To = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
|
|
2003
|
-
let Lo = 0;
|
|
2004
|
-
function Po({ label: t, options: e, onSelect: n, placeholder: o, onOpen: a }) {
|
|
2005
|
-
const s = `twd-fonts-${++Lo}`, r = document.createElement("div");
|
|
2006
|
-
r.className = "font-select";
|
|
2007
|
-
const d = document.createElement("button");
|
|
2008
|
-
d.type = "button", d.className = "font-trigger", d.setAttribute("aria-haspopup", "listbox"), d.setAttribute("aria-expanded", "false"), d.setAttribute("aria-controls", s), d.innerHTML = `
|
|
2009
|
-
<span class="font-aa" aria-hidden="true">Aa</span>
|
|
2010
|
-
<span class="font-name"></span>
|
|
2011
|
-
<span class="font-fonts"></span>
|
|
2012
|
-
<span class="font-chevron">${Ao}</span>`;
|
|
2013
|
-
const l = document.createElement("div");
|
|
2014
|
-
l.className = "font-menu", l.id = s, l.setAttribute("role", "listbox"), l.setAttribute("aria-label", t), l.hidden = !0;
|
|
2015
|
-
const c = e.map((u) => {
|
|
2016
|
-
const y = document.createElement("div");
|
|
2017
|
-
return y.className = "font-option", y.setAttribute("role", "option"), y.tabIndex = -1, y.dataset.value = u.value, y.innerHTML = `
|
|
2018
|
-
<span class="font-aa" style="font-family:${u.preview}" aria-hidden="true">Aa</span>
|
|
2019
|
-
<span class="font-text"><span class="font-name">${u.name}</span><span class="font-fonts">${u.fonts}</span></span>
|
|
2020
|
-
<span class="font-check">${To}</span>`, y.addEventListener("click", () => g(u.value)), y;
|
|
2021
|
-
});
|
|
2022
|
-
l.append(...c), r.append(d, l);
|
|
2023
|
-
let p = null;
|
|
2024
|
-
function m(u) {
|
|
2025
|
-
const y = e.find((O) => O.value === u), k = y || o || e[0];
|
|
2026
|
-
p = y ? y.value : o ? null : e[0].value, d.querySelector(".font-name").textContent = k.name, d.querySelector(".font-fonts").textContent = k.fonts, d.querySelector(".font-aa").style.fontFamily = k.preview, d.setAttribute("aria-label", `${t}: ${k.name}, ${k.fonts}`), c.forEach((O) => O.setAttribute("aria-selected", String(O.dataset.value === p)));
|
|
2027
|
-
}
|
|
2028
|
-
function v() {
|
|
2029
|
-
const u = d.getBoundingClientRect(), y = l.offsetHeight, O = window.innerHeight - u.bottom - 8 >= y ? u.bottom + 4 : Math.max(8, u.top - 4 - y);
|
|
2030
|
-
l.style.top = `${O}px`, l.style.left = `${u.left}px`, l.style.width = `${u.width}px`;
|
|
2031
|
-
}
|
|
2032
|
-
function f() {
|
|
2033
|
-
l.hidden && (l.hidden = !1, d.setAttribute("aria-expanded", "true"), a && a(), v(), (c.find((u) => u.dataset.value === p) || c[0]).focus(), setTimeout(() => document.addEventListener("pointerdown", b, !0)), window.addEventListener("resize", x), r.closest(".body")?.addEventListener("scroll", x, { once: !0 }));
|
|
2034
|
-
}
|
|
2035
|
-
function x(u) {
|
|
2036
|
-
l.hidden || (l.hidden = !0, d.setAttribute("aria-expanded", "false"), document.removeEventListener("pointerdown", b, !0), window.removeEventListener("resize", x), u === !0 && d.focus());
|
|
2037
|
-
}
|
|
2038
|
-
function b(u) {
|
|
2039
|
-
u.composedPath().includes(r) || x();
|
|
2040
|
-
}
|
|
2041
|
-
function g(u) {
|
|
2042
|
-
u !== p && (m(u), n(u)), x(!0);
|
|
2043
|
-
}
|
|
2044
|
-
return d.addEventListener("click", () => l.hidden ? f() : x(!0)), d.addEventListener("keydown", (u) => {
|
|
2045
|
-
(u.key === "ArrowDown" || u.key === "ArrowUp") && (u.preventDefault(), f());
|
|
2046
|
-
}), l.addEventListener("keydown", (u) => {
|
|
2047
|
-
const y = c.indexOf(u.target);
|
|
2048
|
-
let k = null;
|
|
2049
|
-
if (u.key === "ArrowDown") k = Math.min(c.length - 1, y + 1);
|
|
2050
|
-
else if (u.key === "ArrowUp") k = Math.max(0, y - 1);
|
|
2051
|
-
else if (u.key === "Home") k = 0;
|
|
2052
|
-
else if (u.key === "End") k = c.length - 1;
|
|
2053
|
-
else if (u.key === "Enter" || u.key === " ") {
|
|
2054
|
-
u.preventDefault(), g(u.target.dataset.value);
|
|
2055
|
-
return;
|
|
2056
|
-
} else if (u.key === "Escape") {
|
|
2057
|
-
u.preventDefault(), u.stopPropagation(), x(!0);
|
|
2058
|
-
return;
|
|
2059
|
-
} else if (u.key === "Tab") {
|
|
2060
|
-
x();
|
|
2061
|
-
return;
|
|
2062
|
-
}
|
|
2063
|
-
k !== null && (u.preventDefault(), c[k].focus());
|
|
2064
|
-
}), m(o ? null : e[0].value), { el: r, setValue: m };
|
|
2843
|
+
!(d.metaKey || d.ctrlKey) || d.key.toLowerCase() !== "z" || d.target.matches?.('input[type="text"], textarea') || (d.preventDefault(), d.shiftKey ? Dt() : It());
|
|
2844
|
+
}), document.addEventListener("keydown", (d) => {
|
|
2845
|
+
d.altKey && (d.code === "KeyT" && (d.preventDefault(), Ke()), d.code === "KeyC" && (d.preventDefault(), Te(ee.getAttribute("aria-pressed") !== "true")));
|
|
2846
|
+
}), fe("panel") === "open" && le({ animate: !1 }), { open: le, close: _e, toggle: Ke, toast: H, pick: Nt, prompt: Ht };
|
|
2065
2847
|
}
|
|
2066
|
-
const
|
|
2067
|
-
|
|
2068
|
-
{ value: "modern", name: "Modern", fonts: "Geist · Geist", heading: `"Geist", ${I}`, body: `"Geist", ${I}`, load: ["Geist:wght@400;500;600;700"] },
|
|
2069
|
-
{ value: "editorial", name: "Editorial", fonts: "Fraunces · Inter", heading: `"Fraunces", ${ve}`, body: `"Inter", ${I}`, load: ["Fraunces:wght@400;600;700", "Inter:wght@400;500;600;700"] },
|
|
2070
|
-
{ value: "technical", name: "Technical", fonts: "Space Grotesk · DM Sans", heading: `"Space Grotesk", ${I}`, body: `"DM Sans", ${I}`, load: ["Space+Grotesk:wght@500;600;700", "DM+Sans:wght@400;500;600;700"] },
|
|
2071
|
-
{ value: "classic", name: "Classic", fonts: "Playfair · DM Sans", heading: `"Playfair Display", ${ve}`, body: `"DM Sans", ${I}`, load: ["Playfair+Display:wght@600;700", "DM+Sans:wght@400;500;600;700"] },
|
|
2072
|
-
{ value: "plex", name: "Plex", fonts: "Plex Serif · Plex Sans", heading: `"IBM Plex Serif", ${ve}`, body: `"IBM Plex Sans", ${I}`, load: ["IBM+Plex+Serif:wght@500;600;700", "IBM+Plex+Sans:wght@400;500;600"] },
|
|
2073
|
-
{ value: "elegant", name: "Elegant", fonts: "Instrument Serif · Inter", heading: `"Instrument Serif", ${ve}`, body: `"Inter", ${I}`, load: ["Instrument+Serif", "Inter:wght@400;500;600;700"] },
|
|
2074
|
-
{ value: "friendly", name: "Friendly", fonts: "Manrope · Manrope", heading: `"Manrope", ${I}`, body: `"Manrope", ${I}`, load: ["Manrope:wght@400;500;600;700"] }
|
|
2075
|
-
], Ve = { compact: 1.125, balanced: 1.2, expressive: 1.333 }, Ut = { xs: -2, sm: -1, base: 0, lg: 1, xl: 2, "2xl": 3, "3xl": 4 }, qt = /* @__PURE__ */ new Set();
|
|
2076
|
-
function Ht(t) {
|
|
2077
|
-
const e = t.filter((o) => !qt.has(o));
|
|
2078
|
-
if (!e.length) return;
|
|
2079
|
-
e.forEach((o) => qt.add(o));
|
|
2080
|
-
const n = document.createElement("link");
|
|
2081
|
-
n.rel = "stylesheet", n.href = `https://fonts.googleapis.com/css2?${e.map((o) => `family=${o}`).join("&")}&display=swap`, n.dataset.twiddle = "fonts", document.head.appendChild(n);
|
|
2082
|
-
}
|
|
2083
|
-
const Fo = {
|
|
2084
|
-
id: "typography",
|
|
2085
|
-
title: "Typography",
|
|
2086
|
-
hint: "Font pairing · base size · scale",
|
|
2087
|
-
uses: ["--twd-font-heading", "--twd-font-body", ...Object.keys(Ut).map((t) => `--twd-text-${t}`)],
|
|
2088
|
-
tokens(t) {
|
|
2089
|
-
const e = t.typography || {}, n = {}, o = Be.find((a) => a.value === e.font);
|
|
2090
|
-
if (o && (Ht(o.load), n["--twd-font-heading"] = o.heading, n["--twd-font-body"] = o.body), e.base || e.scale) {
|
|
2091
|
-
const a = Number(e.base) || 16, s = Ve[e.scale] || Ve.balanced;
|
|
2092
|
-
Object.entries(Ut).forEach(([r, d]) => {
|
|
2093
|
-
n[`--twd-text-${r}`] = `${+(a * s ** d / 16).toFixed(3)}rem`;
|
|
2094
|
-
});
|
|
2095
|
-
}
|
|
2096
|
-
return n;
|
|
2097
|
-
},
|
|
2098
|
-
parts: {
|
|
2099
|
-
fonts(t, e, { label: n = "Fonts" } = {}) {
|
|
2100
|
-
const o = B(n), a = Po({
|
|
2101
|
-
label: "Font pairing",
|
|
2102
|
-
options: Be.map((r) => ({ value: r.value, name: r.name, fonts: r.fonts, preview: r.heading })),
|
|
2103
|
-
placeholder: { name: "Default", fonts: "Prototype fonts", preview: I },
|
|
2104
|
-
// Load every pairing's fonts when the list opens, so the "Aa" previews are real.
|
|
2105
|
-
onOpen: () => Ht([...new Set(Be.flatMap((r) => r.load))]),
|
|
2106
|
-
onSelect: (r) => e.setSection("typography", { font: r })
|
|
2107
|
-
});
|
|
2108
|
-
o.append(a.el);
|
|
2109
|
-
const s = () => a.setValue((e.getState().typography || {}).font || null);
|
|
2110
|
-
s(), e.onChange(s), t.append(o.el);
|
|
2111
|
-
},
|
|
2112
|
-
base(t, e, { label: n = "Base size" } = {}) {
|
|
2113
|
-
t.append(le(e, {
|
|
2114
|
-
section: "typography",
|
|
2115
|
-
key: "base",
|
|
2116
|
-
label: n,
|
|
2117
|
-
fallback: "16",
|
|
2118
|
-
options: ["14", "15", "16"].map((o) => ({ value: o, label: o })),
|
|
2119
|
-
valueText: (o) => `${o}px`
|
|
2120
|
-
}));
|
|
2121
|
-
},
|
|
2122
|
-
scale(t, e, { label: n = "Scale" } = {}) {
|
|
2123
|
-
t.append(le(e, {
|
|
2124
|
-
section: "typography",
|
|
2125
|
-
key: "scale",
|
|
2126
|
-
label: n,
|
|
2127
|
-
fallback: "balanced",
|
|
2128
|
-
options: [{ value: "compact", label: "Compact" }, { value: "balanced", label: "Balanced" }, { value: "expressive", label: "Expressive" }],
|
|
2129
|
-
valueText: (o) => String(Ve[o])
|
|
2130
|
-
}));
|
|
2131
|
-
}
|
|
2132
|
-
},
|
|
2133
|
-
render(t, e) {
|
|
2134
|
-
Object.values(this.parts).forEach((n) => n(t, e));
|
|
2135
|
-
}
|
|
2136
|
-
}, Oo = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 6l4 4M4 20l1-4 9.5-9.5a2.1 2.1 0 013 3L8 19l-4 1z"/></svg>', Ro = [
|
|
2137
|
-
[50, "white", 10],
|
|
2138
|
-
[100, "white", 20],
|
|
2139
|
-
[200, "white", 36],
|
|
2140
|
-
[300, "white", 56],
|
|
2141
|
-
[400, "white", 78],
|
|
2142
|
-
[500, null, 100],
|
|
2143
|
-
[600, "black", 86],
|
|
2144
|
-
[700, "black", 72],
|
|
2145
|
-
[800, "black", 58],
|
|
2146
|
-
[900, "black", 45],
|
|
2147
|
-
[950, "black", 30]
|
|
2148
|
-
], Ue = (t) => {
|
|
2149
|
-
let e = String(t).trim().replace(/^#/, "");
|
|
2150
|
-
return /^[0-9a-f]{3}$/i.test(e) && (e = e.split("").map((n) => n + n).join("")), /^[0-9a-f]{6}$/i.test(e) ? `#${e.toUpperCase()}` : null;
|
|
2151
|
-
};
|
|
2152
|
-
function No({ label: t, onInput: e }) {
|
|
2153
|
-
const n = document.createElement("div");
|
|
2154
|
-
n.className = "colour";
|
|
2155
|
-
const o = document.createElement("div");
|
|
2156
|
-
o.className = "colour-field";
|
|
2157
|
-
const a = document.createElement("input");
|
|
2158
|
-
a.type = "color", a.className = "colour-picker", a.tabIndex = -1, a.setAttribute("aria-hidden", "true");
|
|
2159
|
-
const s = document.createElement("button");
|
|
2160
|
-
s.type = "button", s.className = "colour-swatch", s.setAttribute("aria-label", `${t}: open colour picker`);
|
|
2161
|
-
const r = document.createElement("input");
|
|
2162
|
-
r.type = "text", r.className = "colour-hex", r.spellcheck = !1, r.maxLength = 7, r.setAttribute("aria-label", `${t} hex value`);
|
|
2163
|
-
const d = document.createElement("button");
|
|
2164
|
-
d.type = "button", d.className = "colour-pipette", d.innerHTML = Oo, d.title = "Pick a colour from the screen", d.setAttribute("aria-label", "Pick a colour from the screen"), o.append(a, s, r, d);
|
|
2165
|
-
const l = document.createElement("div");
|
|
2166
|
-
l.className = "ramp", l.setAttribute("aria-hidden", "true"), l.innerHTML = Ro.map(([f, x, b]) => {
|
|
2167
|
-
const g = x ? `color-mix(in oklch, var(--brand) ${b}%, ${x})` : "var(--brand)";
|
|
2168
|
-
return `<span title="${f}" style="background:${g}"></span>`;
|
|
2169
|
-
}).join(""), n.append(o, l);
|
|
2170
|
-
let c = "#000000";
|
|
2171
|
-
const p = (f) => {
|
|
2172
|
-
c = f, a.value = f.toLowerCase(), s.style.background = f, n.getRootNode().activeElement !== r && (r.value = f), n.style.setProperty("--brand", f);
|
|
2173
|
-
}, m = (f) => {
|
|
2174
|
-
f !== c && (p(f), e(f));
|
|
2175
|
-
};
|
|
2176
|
-
s.addEventListener("click", () => {
|
|
2177
|
-
if (a.showPicker)
|
|
2178
|
-
try {
|
|
2179
|
-
a.showPicker();
|
|
2180
|
-
return;
|
|
2181
|
-
} catch {
|
|
2182
|
-
}
|
|
2183
|
-
a.click();
|
|
2184
|
-
}), a.addEventListener("input", () => m(a.value.toUpperCase())), r.addEventListener("input", () => {
|
|
2185
|
-
const f = Ue(r.value);
|
|
2186
|
-
f && m(f);
|
|
2187
|
-
});
|
|
2188
|
-
const v = () => {
|
|
2189
|
-
r.value = c;
|
|
2190
|
-
};
|
|
2191
|
-
return r.addEventListener("blur", v), r.addEventListener("keydown", (f) => {
|
|
2192
|
-
f.key === "Enter" && (v(), r.select());
|
|
2193
|
-
}), d.addEventListener("click", async () => {
|
|
2194
|
-
if ("EyeDropper" in window)
|
|
2195
|
-
try {
|
|
2196
|
-
const { sRGBHex: f } = await new window.EyeDropper().open(), x = Ue(f);
|
|
2197
|
-
x && m(x);
|
|
2198
|
-
} catch {
|
|
2199
|
-
}
|
|
2200
|
-
else
|
|
2201
|
-
s.click();
|
|
2202
|
-
}), { el: n, setValue: (f) => p(Ue(f) || c) };
|
|
2203
|
-
}
|
|
2204
|
-
function jo({ label: t, options: e, onSelect: n }) {
|
|
2205
|
-
const o = document.createElement("div");
|
|
2206
|
-
o.className = "swatches", o.setAttribute("role", "radiogroup"), o.setAttribute("aria-label", t);
|
|
2207
|
-
const a = e.map((l) => {
|
|
2208
|
-
const c = document.createElement("button");
|
|
2209
|
-
return c.type = "button", c.className = "swatch", c.setAttribute("role", "radio"), c.dataset.value = l.value, c.innerHTML = `<span class="swatch-ring"><span class="swatch-dot" style="background:${l.colour}"></span></span><span class="swatch-label">${l.label}</span>`, c.addEventListener("click", () => d(l.value)), c;
|
|
2210
|
-
});
|
|
2211
|
-
o.append(...a);
|
|
2212
|
-
let s = null;
|
|
2213
|
-
function r(l) {
|
|
2214
|
-
s = l, a.forEach((p) => p.setAttribute("aria-checked", String(p.dataset.value === l)));
|
|
2215
|
-
const c = Math.max(0, a.findIndex((p) => p.dataset.value === l));
|
|
2216
|
-
a.forEach((p, m) => {
|
|
2217
|
-
p.tabIndex = m === c ? 0 : -1;
|
|
2218
|
-
});
|
|
2219
|
-
}
|
|
2220
|
-
function d(l, c) {
|
|
2221
|
-
l !== s && (r(l), n(l)), c && a.find((p) => p.dataset.value === l).focus();
|
|
2222
|
-
}
|
|
2223
|
-
return o.addEventListener("keydown", (l) => {
|
|
2224
|
-
const c = a.indexOf(l.target.closest(".swatch"));
|
|
2225
|
-
if (c < 0) return;
|
|
2226
|
-
const p = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[l.key];
|
|
2227
|
-
let m;
|
|
2228
|
-
if (p) m = (c + p + a.length) % a.length;
|
|
2229
|
-
else if (l.key === "Home") m = 0;
|
|
2230
|
-
else if (l.key === "End") m = a.length - 1;
|
|
2231
|
-
else return;
|
|
2232
|
-
l.preventDefault(), d(a[m].dataset.value, !0);
|
|
2233
|
-
}), r(null), { el: o, setValue: r };
|
|
2234
|
-
}
|
|
2235
|
-
const _t = "#4F46E5", zo = "#A0743C", Io = [
|
|
2236
|
-
// Figma Swatch row sample colours
|
|
2237
|
-
{ value: "pure", label: "Pure", colour: "#8E8E93" },
|
|
2238
|
-
{ value: "subtle", label: "Subtle", colour: "#8A89A6" },
|
|
2239
|
-
{ value: "warm", label: "Warm", colour: "#9C9389" }
|
|
2240
|
-
], Gt = ["bg", "surface", "surface-raised", "text", "text-muted", "border"], Do = {
|
|
2241
|
-
// proposal
|
|
2242
|
-
bg: "#0F0F11",
|
|
2243
|
-
surface: "#18181B",
|
|
2244
|
-
"surface-raised": "#1F1F23",
|
|
2245
|
-
text: "#F4F4F5",
|
|
2246
|
-
"text-muted": "#A1A1AA",
|
|
2247
|
-
border: "#2E2E33"
|
|
2248
|
-
}, Bo = { bg: 4, surface: 2, "surface-raised": 2, text: 5, "text-muted": 12, border: 10 }, Kt = (t) => /^#[0-9a-f]{6}$/i.test(t || "");
|
|
2249
|
-
function Vo(t) {
|
|
2250
|
-
const e = (l) => (l /= 255, l <= 0.03928 ? l / 12.92 : ((l + 0.055) / 1.055) ** 2.4), [n, o, a] = [1, 3, 5].map((l) => e(parseInt(t.slice(l, l + 2), 16))), s = 0.2126 * n + 0.7152 * o + 0.0722 * a, r = 1.05 / (s + 0.05), d = (s + 0.05) / (98e-4 + 0.05);
|
|
2251
|
-
return r >= d ? "#FFFFFF" : "#18181B";
|
|
2252
|
-
}
|
|
2253
|
-
const Uo = {
|
|
2254
|
-
id: "colour",
|
|
2255
|
-
title: "Colour",
|
|
2256
|
-
hint: "Brand colour · neutral tint · mode",
|
|
2257
|
-
uses: ["--twd-primary", "--twd-primary-hover", "--twd-on-primary", "--twd-focus", ...Gt.map((t) => `--twd-${t}`)],
|
|
2258
|
-
tokens(t) {
|
|
2259
|
-
const e = t.colour || {}, n = {}, o = e.mode === "dark";
|
|
2260
|
-
Kt(e.brand) && (n["--twd-primary"] = e.brand, n["--twd-primary-hover"] = o ? `color-mix(in oklch, ${e.brand} 85%, white)` : `color-mix(in oklch, ${e.brand} 86%, black)`, n["--twd-on-primary"] = Vo(e.brand), n["--twd-focus"] = e.brand);
|
|
2261
|
-
const a = e.tint === "subtle" ? Kt(e.brand) ? e.brand : F("--twd-primary") || _t : e.tint === "warm" ? zo : null;
|
|
2262
|
-
return (o || a) && Gt.forEach((s) => {
|
|
2263
|
-
const r = o ? Do[s] : F(`--twd-${s}`);
|
|
2264
|
-
r && (n[`--twd-${s}`] = a ? `color-mix(in oklch, ${r} ${100 - Bo[s]}%, ${a})` : r);
|
|
2265
|
-
}), n;
|
|
2266
|
-
},
|
|
2267
|
-
parts: {
|
|
2268
|
-
brand(t, e, { label: n = "Brand" } = {}) {
|
|
2269
|
-
const o = B(n), a = No({ label: "Brand colour", onInput: (r) => e.setSection("colour", { brand: r }) });
|
|
2270
|
-
o.append(a.el);
|
|
2271
|
-
const s = () => a.setValue((e.getState().colour || {}).brand || (e.readToken || F)("--twd-primary") || _t);
|
|
2272
|
-
s(), e.onChange(s), t.append(o.el);
|
|
2273
|
-
},
|
|
2274
|
-
neutral(t, e, { label: n = "Neutral" } = {}) {
|
|
2275
|
-
const o = B(n), a = jo({ label: "Neutral tint", options: Io, onSelect: (r) => e.setSection("colour", { tint: r }) });
|
|
2276
|
-
o.append(a.el);
|
|
2277
|
-
const s = () => a.setValue((e.getState().colour || {}).tint || "pure");
|
|
2278
|
-
s(), e.onChange(s), t.append(o.el);
|
|
2279
|
-
},
|
|
2280
|
-
mode(t, e, { label: n = "Mode" } = {}) {
|
|
2281
|
-
t.append(le(e, {
|
|
2282
|
-
section: "colour",
|
|
2283
|
-
key: "mode",
|
|
2284
|
-
label: n,
|
|
2285
|
-
fallback: "light",
|
|
2286
|
-
options: [{ value: "light", label: "Light" }, { value: "dark", label: "Dark" }]
|
|
2287
|
-
}));
|
|
2288
|
-
}
|
|
2289
|
-
},
|
|
2290
|
-
render(t, e) {
|
|
2291
|
-
Object.values(this.parts).forEach((n) => n(t, e));
|
|
2292
|
-
}
|
|
2293
|
-
}, qo = { small: { height: 0.8, pad: 0.75 }, large: { height: 1.2, pad: 1.25 } }, Ho = hn({
|
|
2294
|
-
id: "buttons",
|
|
2295
|
-
title: "Buttons",
|
|
2296
|
-
hint: "Corners · size",
|
|
2297
|
-
token: "--twd-radius-control",
|
|
2298
|
-
uses: ["--twd-control-height", "--twd-control-pad-x"],
|
|
2299
|
-
max: 24,
|
|
2300
|
-
// slider range in px; "Pill" sits at the end and reads "full"
|
|
2301
|
-
presets: [
|
|
2302
|
-
{ value: "square", label: "Square", radius: 0 },
|
|
2303
|
-
// proposal
|
|
2304
|
-
{ value: "soft", label: "Soft", radius: 4 },
|
|
2305
|
-
// proposal
|
|
2306
|
-
{ value: "rounded", label: "Rounded", radius: 8 },
|
|
2307
|
-
// Figma: "8px"
|
|
2308
|
-
{ value: "pill", label: "Pill", radius: 999 }
|
|
2309
|
-
],
|
|
2310
|
-
extraTokens(t) {
|
|
2311
|
-
const e = qo[t.size];
|
|
2312
|
-
if (!e) return {};
|
|
2313
|
-
const n = {}, o = Se(F("--twd-control-height"), e.height), a = Se(F("--twd-control-pad-x"), e.pad);
|
|
2314
|
-
return o && (n["--twd-control-height"] = o), a && (n["--twd-control-pad-x"] = a), n;
|
|
2315
|
-
},
|
|
2316
|
-
extra: [
|
|
2317
|
-
{
|
|
2318
|
-
key: "size",
|
|
2319
|
-
label: "Size",
|
|
2320
|
-
fallback: "medium",
|
|
2321
|
-
options: [{ value: "small", label: "Small" }, { value: "medium", label: "Medium" }, { value: "large", label: "Large" }]
|
|
2322
|
-
}
|
|
2323
|
-
]
|
|
2324
|
-
}), _o = hn({
|
|
2325
|
-
id: "cards",
|
|
2326
|
-
title: "Cards",
|
|
2327
|
-
hint: "Corners · border",
|
|
2328
|
-
token: "--twd-radius-container",
|
|
2329
|
-
uses: ["--twd-border-width-container"],
|
|
2330
|
-
max: 32,
|
|
2331
|
-
presets: [
|
|
2332
|
-
{ value: "square", label: "Square", radius: 0 },
|
|
2333
|
-
// proposal
|
|
2334
|
-
{ value: "soft", label: "Soft", radius: 6 },
|
|
2335
|
-
// proposal
|
|
2336
|
-
{ value: "rounded", label: "Rounded", radius: 12 },
|
|
2337
|
-
// = tokens.css default
|
|
2338
|
-
{ value: "large", label: "Large", radius: 20 }
|
|
2339
|
-
// proposal (cards don't go fully pill)
|
|
2340
|
-
],
|
|
2341
|
-
extraTokens(t) {
|
|
2342
|
-
if (t.border === "none") return { "--twd-border-width-container": "0px" };
|
|
2343
|
-
if (t.border === "thick") {
|
|
2344
|
-
const e = Se(F("--twd-border-width-container"), 2);
|
|
2345
|
-
return e ? { "--twd-border-width-container": e } : {};
|
|
2346
|
-
}
|
|
2347
|
-
return {};
|
|
2348
|
-
},
|
|
2349
|
-
extra: [
|
|
2350
|
-
{
|
|
2351
|
-
key: "border",
|
|
2352
|
-
label: "Border",
|
|
2353
|
-
fallback: "thin",
|
|
2354
|
-
options: [{ value: "none", label: "None" }, { value: "thin", label: "Thin" }, { value: "thick", label: "Thick" }]
|
|
2355
|
-
}
|
|
2356
|
-
]
|
|
2357
|
-
}), Wt = [Fo, Uo, Ho, _o, bo, wo, mo], Go = ':root{--twd-font-heading: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-text-xs: .694rem;--twd-text-sm: .833rem;--twd-text-base: 1rem;--twd-text-lg: 1.2rem;--twd-text-xl: 1.44rem;--twd-text-2xl: 1.728rem;--twd-text-3xl: 2.074rem;--twd-leading-tight: 1.2;--twd-leading-normal: 1.5;--twd-bg: #fafafa;--twd-surface: #ffffff;--twd-surface-raised: #ffffff;--twd-text: #18181b;--twd-text-muted: #71717a;--twd-border: #e4e4e7;--twd-primary: #4f46e5;--twd-primary-hover: #4338ca;--twd-on-primary: #ffffff;--twd-focus: #6366f1;--twd-success: #16a34a;--twd-warning: #d97706;--twd-danger: #dc2626;--twd-radius-control: 8px;--twd-radius-container: 12px;--twd-space-1: 4px;--twd-space-2: 8px;--twd-space-3: 12px;--twd-space-4: 16px;--twd-space-5: 24px;--twd-space-6: 32px;--twd-space-7: 48px;--twd-space-8: 64px;--twd-control-height: 40px;--twd-control-pad-x: 16px;--twd-page-pad: var(--twd-space-5);--twd-section-gap: var(--twd-space-6);--twd-card-pad: var(--twd-space-5);--twd-card-gap: var(--twd-space-2);--twd-control-gap: var(--twd-space-3);--twd-border-width: 1px;--twd-border-width-container: var(--twd-border-width);--twd-shadow-sm: 0 1px 2px rgb(0 0 0 / .06);--twd-shadow-md: 0 4px 12px rgb(0 0 0 / .08);--twd-shadow-lg: 0 12px 32px rgb(0 0 0 / .12)}';
|
|
2358
|
-
function Ko() {
|
|
2848
|
+
const wn = [lo, De, Dn, wt, Va, vt, ao, qa], bo = ':root{--twd-font-heading: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-text-xs: .694rem;--twd-text-sm: .833rem;--twd-text-base: 1rem;--twd-text-lg: 1.2rem;--twd-text-xl: 1.44rem;--twd-text-2xl: 1.728rem;--twd-text-3xl: 2.074rem;--twd-leading-tight: 1.2;--twd-leading-normal: 1.5;--twd-weight-heading: 600;--twd-weight-strong: 600;--twd-bg: #fafafa;--twd-surface: #ffffff;--twd-surface-raised: #ffffff;--twd-text: #18181b;--twd-text-muted: #71717a;--twd-border: #e4e4e7;--twd-primary: #4f46e5;--twd-primary-hover: #4338ca;--twd-on-primary: #ffffff;--twd-focus: #6366f1;--twd-success: #16a34a;--twd-warning: #d97706;--twd-danger: #dc2626;--twd-radius-control: 8px;--twd-radius-container: 12px;--twd-space-1: 4px;--twd-space-2: 8px;--twd-space-3: 12px;--twd-space-4: 16px;--twd-space-5: 24px;--twd-space-6: 32px;--twd-space-7: 48px;--twd-space-8: 64px;--twd-control-height: 40px;--twd-control-pad-x: 16px;--twd-page-pad: var(--twd-space-5);--twd-section-gap: var(--twd-space-6);--twd-card-pad: var(--twd-space-5);--twd-card-gap: var(--twd-space-2);--twd-control-gap: var(--twd-space-3);--twd-border-width: 1px;--twd-border-width-container: var(--twd-border-width);--twd-shadow-sm: 0 1px 2px rgb(0 0 0 / .06);--twd-shadow-md: 0 4px 12px rgb(0 0 0 / .08);--twd-shadow-lg: 0 12px 32px rgb(0 0 0 / .12);--twd-button-bg: var(--twd-primary);--twd-button-fg: var(--twd-on-primary);--twd-button-border: transparent;--twd-button-hover: var(--twd-primary-hover);--twd-card-bg: var(--twd-surface);--twd-card-border: var(--twd-border);--twd-card-shadow: var(--twd-shadow-sm)}';
|
|
2849
|
+
function xo() {
|
|
2359
2850
|
if (getComputedStyle(document.documentElement).getPropertyValue("--twd-radius-control").trim() || document.getElementById("twiddle-tokens")) return;
|
|
2360
2851
|
const e = document.createElement("style");
|
|
2361
|
-
e.id = "twiddle-tokens", e.textContent =
|
|
2852
|
+
e.id = "twiddle-tokens", e.textContent = bo, document.head.prepend(e);
|
|
2362
2853
|
}
|
|
2363
2854
|
if (!window.Twiddle) {
|
|
2364
|
-
|
|
2855
|
+
xo(), wn.forEach((n) => sa(n.tokens)), $n(gt);
|
|
2365
2856
|
let t;
|
|
2366
2857
|
const e = () => {
|
|
2367
|
-
|
|
2858
|
+
gt(ut()), t = mo({ sections: wn, store: ra, view: { suspend: ca, resume: pa } });
|
|
2368
2859
|
};
|
|
2369
2860
|
document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", e) : e(), window.Twiddle = {
|
|
2370
2861
|
open: () => t && t.open(),
|
|
2371
2862
|
close: () => t && t.close(),
|
|
2372
2863
|
toggle: () => t && t.toggle(),
|
|
2373
|
-
reset: () =>
|
|
2374
|
-
undo: () =>
|
|
2375
|
-
redo: () =>
|
|
2864
|
+
reset: () => yn(),
|
|
2865
|
+
undo: () => kn(),
|
|
2866
|
+
redo: () => Sn(),
|
|
2376
2867
|
pick: () => t && t.pick(),
|
|
2377
2868
|
prompt: () => t ? t.prompt() : "",
|
|
2378
|
-
getState:
|
|
2869
|
+
getState: ut
|
|
2379
2870
|
};
|
|
2380
2871
|
}
|
|
2381
|
-
const
|
|
2872
|
+
const wo = window.Twiddle;
|
|
2382
2873
|
export {
|
|
2383
|
-
|
|
2874
|
+
wo as Twiddle
|
|
2384
2875
|
};
|