twiddle-panel 0.6.1 → 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 +241 -135
- package/dist/twiddle.mjs +1770 -1282
- 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,16 +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; }
|
|
426
|
-
.
|
|
427
|
-
.
|
|
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; }
|
|
428
439
|
.icon-btn[aria-pressed="true"] { background: var(--p-accent); color: var(--p-on-accent); }
|
|
429
440
|
kbd {
|
|
430
441
|
font: 11px ui-monospace, "SF Mono", Menlo, monospace;
|
|
@@ -480,7 +491,7 @@ const Dn = `
|
|
|
480
491
|
background: var(--p-accent); color: var(--p-on-accent); cursor: pointer;
|
|
481
492
|
}
|
|
482
493
|
.menu {
|
|
483
|
-
position: absolute; right: 0;
|
|
494
|
+
position: absolute; right: 0; top: calc(100% + 6px); z-index: 2; /* opens downwards from the header */
|
|
484
495
|
min-width: 180px; padding: 4px; display: grid; gap: 2px;
|
|
485
496
|
background: var(--p-bg); border: 1px solid var(--p-line); border-radius: 8px;
|
|
486
497
|
box-shadow: var(--p-shadow);
|
|
@@ -550,7 +561,7 @@ const Dn = `
|
|
|
550
561
|
font-size: 12px; line-height: 1.4;
|
|
551
562
|
}
|
|
552
563
|
.warning svg { flex: none; width: 16px; height: 16px; margin-top: 0.5px; }
|
|
553
|
-
|
|
564
|
+
.warning { color: #fbbf24; } /* amber text that reads on the dark panel */
|
|
554
565
|
|
|
555
566
|
/* Changes list */
|
|
556
567
|
.change-group { padding: 10px 14px; border-bottom: 1px solid var(--p-line); display: grid; gap: 6px; }
|
|
@@ -578,11 +589,9 @@ const Dn = `
|
|
|
578
589
|
--p-accent: #18181b; --p-on-accent: #ffffff;
|
|
579
590
|
--p-shadow: 0 12px 40px rgb(0 0 0 / 0.14), 0 2px 6px rgb(0 0 0 / 0.06);
|
|
580
591
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
--p-accent: #f4f4f5; --p-on-accent: #18181b;
|
|
585
|
-
}
|
|
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;
|
|
586
595
|
}
|
|
587
596
|
.dialog-backdrop[hidden] { display: none; }
|
|
588
597
|
.dialog {
|
|
@@ -644,35 +653,13 @@ const Dn = `
|
|
|
644
653
|
width: 10px; height: 10px; border-radius: 50%;
|
|
645
654
|
background: #d97706; box-shadow: 0 0 0 2px var(--p-bg);
|
|
646
655
|
}
|
|
647
|
-
|
|
656
|
+
.setup-head svg { color: #fbbf24; }
|
|
648
657
|
|
|
649
658
|
/* ---------- Layout direction A (Figma 80:245 › A1b, A2) ---------- */
|
|
650
|
-
.
|
|
651
|
-
.subhead { padding-left: 8px; }
|
|
659
|
+
.page[hidden] { display: none; }
|
|
652
660
|
.page-fields { padding: 14px 14px 16px; display: grid; gap: 14px; color: var(--p-muted); }
|
|
653
661
|
.page-warnings:empty { display: none; }
|
|
654
662
|
|
|
655
|
-
/* Edit list rows look like the Figma Section row (6:134) */
|
|
656
|
-
.nav-row {
|
|
657
|
-
width: 100%; display: flex; align-items: center; gap: 8px;
|
|
658
|
-
padding: 11px 14px; border: 0; border-bottom: 1px solid var(--p-line);
|
|
659
|
-
background: transparent; color: var(--p-fg);
|
|
660
|
-
font: inherit; font-weight: 500; text-align: left; cursor: pointer;
|
|
661
|
-
}
|
|
662
|
-
.nav-row:last-child { border-bottom: 0; }
|
|
663
|
-
.nav-row:hover { background: var(--p-hover); }
|
|
664
|
-
.nav-row .chev { margin-left: auto; color: var(--p-muted); display: grid; }
|
|
665
|
-
|
|
666
|
-
/* "Edit every token" row above the footer (Figma: Edit row (proposal)) */
|
|
667
|
-
.edit-row {
|
|
668
|
-
display: flex; align-items: center; gap: 8px;
|
|
669
|
-
padding: 11px 14px; border: 0; border-top: 1px solid var(--p-line);
|
|
670
|
-
background: transparent; color: var(--p-fg);
|
|
671
|
-
font: inherit; font-weight: 500; text-align: left; cursor: pointer;
|
|
672
|
-
}
|
|
673
|
-
.edit-row:hover { background: var(--p-hover); }
|
|
674
|
-
.edit-row .chev { margin-left: auto; color: var(--p-muted); display: grid; }
|
|
675
|
-
|
|
676
663
|
/* Character pad (Figma: Character pad (proposal)): 170px tall, 9 × 5 dots */
|
|
677
664
|
.pad {
|
|
678
665
|
position: relative; height: 170px; border-radius: 10px;
|
|
@@ -760,73 +747,208 @@ const Dn = `
|
|
|
760
747
|
.role-row.edited .step-select select, .step-select select:focus-visible { border-color: var(--p-accent); outline: none; }
|
|
761
748
|
.step-chevron { position: absolute; right: 7px; top: 50%; transform: translateY(-50%); display: grid; color: var(--p-muted); pointer-events: none; }
|
|
762
749
|
.role-note { margin: 0; font-size: 11px; line-height: 1.45; color: var(--p-muted); }
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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"/>'),
|
|
769
880
|
// Proposals: not yet in the Figma Icon/* set.
|
|
770
|
-
pick:
|
|
771
|
-
compare:
|
|
772
|
-
undo:
|
|
773
|
-
redo:
|
|
774
|
-
back:
|
|
775
|
-
down:
|
|
776
|
-
|
|
777
|
-
|
|
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"/>')
|
|
778
900
|
// Figma: Icon/Sliders (proposal)
|
|
779
|
-
},
|
|
780
|
-
function
|
|
901
|
+
}, An = "twiddle:";
|
|
902
|
+
function fe(t) {
|
|
781
903
|
try {
|
|
782
|
-
return localStorage.getItem(
|
|
904
|
+
return localStorage.getItem(An + t);
|
|
783
905
|
} catch {
|
|
784
906
|
return null;
|
|
785
907
|
}
|
|
786
908
|
}
|
|
787
|
-
function
|
|
909
|
+
function ge(t, e) {
|
|
788
910
|
try {
|
|
789
|
-
localStorage.setItem(
|
|
911
|
+
localStorage.setItem(An + t, e);
|
|
790
912
|
} catch {
|
|
791
913
|
}
|
|
792
914
|
}
|
|
793
|
-
function
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
const
|
|
798
|
-
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;
|
|
799
921
|
});
|
|
800
|
-
|
|
801
|
-
let
|
|
802
|
-
function
|
|
803
|
-
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
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;
|
|
807
929
|
});
|
|
808
930
|
}
|
|
809
|
-
function
|
|
810
|
-
l !==
|
|
811
|
-
}
|
|
812
|
-
return
|
|
813
|
-
const
|
|
814
|
-
if (
|
|
815
|
-
const
|
|
816
|
-
let
|
|
817
|
-
if (
|
|
818
|
-
else if (l.key === "Home")
|
|
819
|
-
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;
|
|
820
942
|
else return;
|
|
821
|
-
l.preventDefault(),
|
|
822
|
-
}),
|
|
943
|
+
l.preventDefault(), i(o[h].dataset.value, !0);
|
|
944
|
+
}), s(null), { el: a, setValue: s };
|
|
823
945
|
}
|
|
824
|
-
const
|
|
825
|
-
function*
|
|
946
|
+
const Ln = /var\(\s*(--twd-[\w-]+)/g, ha = /::?[\w-]+(\([^)]*\))?/g;
|
|
947
|
+
function* Ue(t) {
|
|
826
948
|
for (const e of t)
|
|
827
|
-
e.selectorText != null && (yield e), e.cssRules && (yield*
|
|
949
|
+
e.selectorText != null && (yield e), e.cssRules && (yield* Ue(e.cssRules));
|
|
828
950
|
}
|
|
829
|
-
function
|
|
951
|
+
function fa() {
|
|
830
952
|
const t = [];
|
|
831
953
|
for (const e of document.styleSheets) {
|
|
832
954
|
if (e.ownerNode?.id === "twiddle-scoped") continue;
|
|
@@ -836,92 +958,92 @@ function Un() {
|
|
|
836
958
|
} catch {
|
|
837
959
|
continue;
|
|
838
960
|
}
|
|
839
|
-
for (const
|
|
840
|
-
const
|
|
841
|
-
|
|
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 });
|
|
842
964
|
}
|
|
843
965
|
}
|
|
844
966
|
return t;
|
|
845
967
|
}
|
|
846
|
-
function
|
|
847
|
-
const n = e.replace(
|
|
968
|
+
function ga(t, e) {
|
|
969
|
+
const n = e.replace(ha, "").trim() || "*";
|
|
848
970
|
try {
|
|
849
971
|
return t.matches(n);
|
|
850
972
|
} catch {
|
|
851
973
|
return !1;
|
|
852
974
|
}
|
|
853
975
|
}
|
|
854
|
-
function
|
|
976
|
+
function Pn(t) {
|
|
855
977
|
const e = /* @__PURE__ */ new Set(), n = [];
|
|
856
|
-
return
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
}), [...(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 };
|
|
860
982
|
}
|
|
861
|
-
const
|
|
862
|
-
function
|
|
983
|
+
const mt = (t) => window.CSS && CSS.escape ? CSS.escape(t) : t;
|
|
984
|
+
function ma(t) {
|
|
863
985
|
const e = [];
|
|
864
986
|
let n = t;
|
|
865
987
|
for (; n && n.nodeType === 1 && n !== document.body && n !== document.documentElement; ) {
|
|
866
|
-
if (n.id && document.querySelectorAll(`#${
|
|
867
|
-
return e.unshift(`#${
|
|
868
|
-
const
|
|
869
|
-
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;
|
|
870
992
|
}
|
|
871
993
|
return ["body", ...e].join(" > ");
|
|
872
994
|
}
|
|
873
|
-
function
|
|
995
|
+
function ba(t, e = Pn(t).selectors) {
|
|
874
996
|
const n = t.getAttribute("data-twd");
|
|
875
997
|
if (n) return `[data-twd="${n.replace(/"/g, "")}"]`;
|
|
876
|
-
const
|
|
877
|
-
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;
|
|
878
1000
|
}
|
|
879
|
-
function
|
|
880
|
-
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, " ");
|
|
881
1003
|
return {
|
|
882
1004
|
el: t,
|
|
883
1005
|
tag: t.localName,
|
|
884
1006
|
classes: [...t.classList],
|
|
885
|
-
text:
|
|
1007
|
+
text: a.length > 40 ? `${a.slice(0, 40)}…` : a,
|
|
886
1008
|
tokens: e.names,
|
|
887
|
-
elementSelector:
|
|
1009
|
+
elementSelector: ma(t),
|
|
888
1010
|
classSelector: n,
|
|
889
1011
|
classCount: n ? document.querySelectorAll(n).length : 0
|
|
890
1012
|
};
|
|
891
1013
|
}
|
|
892
|
-
const
|
|
893
|
-
function
|
|
894
|
-
const
|
|
895
|
-
let
|
|
896
|
-
if (
|
|
897
|
-
const
|
|
898
|
-
|
|
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);
|
|
899
1021
|
}
|
|
900
1022
|
return {
|
|
901
1023
|
...n,
|
|
902
1024
|
sectionId: t.id,
|
|
903
1025
|
title: t.title,
|
|
904
|
-
settings: Object.entries(e).map(([
|
|
905
|
-
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] }))
|
|
906
1028
|
};
|
|
907
1029
|
}
|
|
908
|
-
function
|
|
1030
|
+
function en(t, e) {
|
|
909
1031
|
const n = [];
|
|
910
|
-
return e.forEach((
|
|
911
|
-
t[
|
|
912
|
-
}), Object.entries(t.scopes || {}).forEach(([
|
|
913
|
-
const
|
|
914
|
-
e.forEach((
|
|
915
|
-
|
|
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 }));
|
|
916
1038
|
});
|
|
917
1039
|
}), n;
|
|
918
1040
|
}
|
|
919
|
-
function
|
|
920
|
-
const e = (
|
|
921
|
-
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);
|
|
922
1044
|
}
|
|
923
|
-
const
|
|
924
|
-
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) {
|
|
925
1047
|
let e = null;
|
|
926
1048
|
try {
|
|
927
1049
|
e = document.querySelector(t);
|
|
@@ -929,58 +1051,58 @@ function Yn(t) {
|
|
|
929
1051
|
}
|
|
930
1052
|
if (!e) return "not found on the current page";
|
|
931
1053
|
const n = (e.getAttribute("aria-label") || e.placeholder || e.textContent || "").trim().replace(/\s+/g, " ").slice(0, 60);
|
|
932
|
-
return `${
|
|
1054
|
+
return `${ae(bt(e))}${n ? ` with text “${n}”` : ""}`;
|
|
933
1055
|
}
|
|
934
|
-
function
|
|
1056
|
+
function tn(t, e) {
|
|
935
1057
|
const n = /* @__PURE__ */ new Map();
|
|
936
|
-
return t.forEach((
|
|
937
|
-
const
|
|
938
|
-
n.has(
|
|
1058
|
+
return t.forEach((a) => {
|
|
1059
|
+
const o = a[e];
|
|
1060
|
+
n.has(o) || n.set(o, []), n.get(o).push(a);
|
|
939
1061
|
}), [...n];
|
|
940
1062
|
}
|
|
941
|
-
function
|
|
1063
|
+
function Sa(t, { link: e = location.href, warnings: n = [] } = {}) {
|
|
942
1064
|
if (!t.length) return "";
|
|
943
|
-
const
|
|
944
|
-
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(
|
|
945
1067
|
"# Apply design tweaks from Panel",
|
|
946
1068
|
"",
|
|
947
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.",
|
|
948
1070
|
"",
|
|
949
1071
|
`Preview of the tweaked version: ${e}`
|
|
950
|
-
),
|
|
1072
|
+
), a.length && (s.push(
|
|
951
1073
|
"",
|
|
952
1074
|
"## 1. Global token changes",
|
|
953
1075
|
"",
|
|
954
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.",
|
|
955
1077
|
""
|
|
956
|
-
),
|
|
1078
|
+
), a.forEach((i) => s.push(`- **${Xe(i)}**`, ...Ze(i.tokens)))), o.length && (s.push(
|
|
957
1079
|
"",
|
|
958
|
-
`## ${
|
|
1080
|
+
`## ${a.length ? 2 : 1}. Component-wide changes`,
|
|
959
1081
|
"",
|
|
960
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.",
|
|
961
1083
|
""
|
|
962
|
-
),
|
|
963
|
-
|
|
964
|
-
})),
|
|
965
|
-
const
|
|
966
|
-
|
|
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(
|
|
967
1089
|
"",
|
|
968
|
-
`## ${
|
|
1090
|
+
`## ${i}. Single-element changes`,
|
|
969
1091
|
"",
|
|
970
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.",
|
|
971
1093
|
""
|
|
972
|
-
),
|
|
973
|
-
|
|
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}`)));
|
|
974
1096
|
});
|
|
975
1097
|
}
|
|
976
|
-
return n.length &&
|
|
1098
|
+
return n.length && s.push(
|
|
977
1099
|
"",
|
|
978
1100
|
"## Contrast to check",
|
|
979
1101
|
"",
|
|
980
1102
|
"Panel flagged these colour pairs as below WCAG AA with the new values:",
|
|
981
1103
|
"",
|
|
982
|
-
...n.map((
|
|
983
|
-
),
|
|
1104
|
+
...n.map((i) => `- ${i.text}`)
|
|
1105
|
+
), s.push(
|
|
984
1106
|
"",
|
|
985
1107
|
"## Constraints",
|
|
986
1108
|
"",
|
|
@@ -988,71 +1110,71 @@ function Xn(t, { link: e = location.href, warnings: n = [] } = {}) {
|
|
|
988
1110
|
"- Change only what is listed above.",
|
|
989
1111
|
"- If a value uses `color-mix()` and the project targets older browsers, ask before converting it.",
|
|
990
1112
|
"- When done, list the files you changed."
|
|
991
|
-
),
|
|
1113
|
+
), s.join(`
|
|
992
1114
|
`);
|
|
993
1115
|
}
|
|
994
|
-
const
|
|
1116
|
+
const Re = 4.5, $a = 3, Ea = [
|
|
995
1117
|
["--twd-text", "--twd-bg", "Text on background"],
|
|
996
1118
|
["--twd-text", "--twd-surface", "Text on surface"],
|
|
997
1119
|
["--twd-text-muted", "--twd-surface", "Muted text on surface"],
|
|
998
1120
|
["--twd-on-primary", "--twd-primary", "Text on brand"]
|
|
999
1121
|
];
|
|
1000
|
-
let
|
|
1001
|
-
function
|
|
1002
|
-
if (!t || (
|
|
1003
|
-
|
|
1004
|
-
const [e, n,
|
|
1005
|
-
return [e, n,
|
|
1006
|
-
}
|
|
1007
|
-
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) {
|
|
1008
1130
|
const e = document.createElement("i");
|
|
1009
1131
|
e.style.cssText = `position:absolute;visibility:hidden;color:var(${t})`, document.documentElement.appendChild(e);
|
|
1010
1132
|
const n = getComputedStyle(e).color;
|
|
1011
|
-
return e.remove(),
|
|
1012
|
-
}
|
|
1013
|
-
const
|
|
1014
|
-
const
|
|
1015
|
-
return 0.2126 *
|
|
1016
|
-
},
|
|
1017
|
-
function
|
|
1018
|
-
const n =
|
|
1019
|
-
return (Math.max(n,
|
|
1020
|
-
}
|
|
1021
|
-
const
|
|
1022
|
-
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() {
|
|
1023
1145
|
const t = [];
|
|
1024
|
-
return
|
|
1025
|
-
const
|
|
1026
|
-
if (!
|
|
1027
|
-
const
|
|
1028
|
-
|
|
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}` });
|
|
1029
1151
|
}), t;
|
|
1030
1152
|
}
|
|
1031
|
-
function
|
|
1153
|
+
function Ca(t) {
|
|
1032
1154
|
const e = [];
|
|
1033
1155
|
for (let n = t; n && n.nodeType === 1; n = n.parentElement) {
|
|
1034
|
-
const
|
|
1035
|
-
if (
|
|
1156
|
+
const a = St(getComputedStyle(n).backgroundColor);
|
|
1157
|
+
if (a && a[3] > 0 && (e.push(a), a[3] >= 1))
|
|
1036
1158
|
break;
|
|
1037
1159
|
}
|
|
1038
|
-
return e.reduceRight((n,
|
|
1160
|
+
return e.reduceRight((n, a) => Fn(a, n), [255, 255, 255, 1]);
|
|
1039
1161
|
}
|
|
1040
|
-
function
|
|
1162
|
+
function Ta(t) {
|
|
1041
1163
|
if (!t.textContent.trim() && !t.placeholder) return null;
|
|
1042
|
-
const e = getComputedStyle(t), n =
|
|
1164
|
+
const e = getComputedStyle(t), n = St(e.color);
|
|
1043
1165
|
if (!n) return null;
|
|
1044
|
-
const
|
|
1045
|
-
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;
|
|
1046
1168
|
}
|
|
1047
|
-
const
|
|
1048
|
-
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) {
|
|
1049
1171
|
return t.style.cssText.split(";").some((e) => {
|
|
1050
1172
|
const n = e.slice(0, e.indexOf(":")).trim();
|
|
1051
|
-
return n && !n.startsWith("--") &&
|
|
1173
|
+
return n && !n.startsWith("--") && La.test(e);
|
|
1052
1174
|
});
|
|
1053
1175
|
}
|
|
1054
|
-
function
|
|
1055
|
-
if (!getComputedStyle(document.documentElement).getPropertyValue(
|
|
1176
|
+
function Oa() {
|
|
1177
|
+
if (!getComputedStyle(document.documentElement).getPropertyValue(Aa).trim()) return "no-tokens";
|
|
1056
1178
|
let t = !1;
|
|
1057
1179
|
for (const e of document.styleSheets) {
|
|
1058
1180
|
if (e.ownerNode?.id === "twiddle-scoped") continue;
|
|
@@ -1063,43 +1185,45 @@ function ro() {
|
|
|
1063
1185
|
t = !0;
|
|
1064
1186
|
continue;
|
|
1065
1187
|
}
|
|
1066
|
-
for (const
|
|
1188
|
+
for (const a of Ue(n)) if (Pa(a)) return "ok";
|
|
1067
1189
|
}
|
|
1068
1190
|
return document.querySelector('[style*="var(--twd-"]') ? "ok" : t ? "unknown" : "unused";
|
|
1069
1191
|
}
|
|
1070
|
-
function
|
|
1192
|
+
function Fa() {
|
|
1071
1193
|
const t = /* @__PURE__ */ new Set(), e = (n) => {
|
|
1072
|
-
for (const
|
|
1194
|
+
for (const a of n.matchAll(/var\(\s*(--twd-[\w-]+)/g)) t.add(a[1]);
|
|
1073
1195
|
};
|
|
1074
1196
|
for (const n of document.styleSheets) {
|
|
1075
1197
|
if (n.ownerNode?.id === "twiddle-scoped") continue;
|
|
1076
|
-
let
|
|
1198
|
+
let a;
|
|
1077
1199
|
try {
|
|
1078
|
-
|
|
1200
|
+
a = n.cssRules;
|
|
1079
1201
|
} catch {
|
|
1080
1202
|
continue;
|
|
1081
1203
|
}
|
|
1082
|
-
for (const
|
|
1083
|
-
|
|
1084
|
-
const
|
|
1085
|
-
|
|
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);
|
|
1086
1208
|
});
|
|
1087
1209
|
}
|
|
1088
1210
|
return document.querySelectorAll('[style*="var(--twd-"]').forEach((n) => e(n.getAttribute("style"))), t;
|
|
1089
1211
|
}
|
|
1090
|
-
const
|
|
1091
|
-
{ 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';` },
|
|
1092
1214
|
// Pinned to this build's version: unversioned jsDelivr links can lag a release by days, and unpkg once failed for a fresh one.
|
|
1093
|
-
{ label: "Plain HTML, in <head>", code: `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/${
|
|
1094
|
-
],
|
|
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.
|
|
1095
1217
|
|
|
1096
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.
|
|
1097
1219
|
2. Replace fixed values in component styles with var(--twd-…). Map each value to the closest token by role:
|
|
1098
1220
|
- Fonts: --twd-font-heading, --twd-font-body
|
|
1099
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)
|
|
1100
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
|
|
1101
1224
|
- Buttons, inputs, chips: --twd-radius-control, --twd-control-height, --twd-control-pad-x
|
|
1102
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
|
|
1103
1227
|
- Borders: --twd-border-width
|
|
1104
1228
|
- Spacing (padding, margin, gap): --twd-space-1 to --twd-space-8 (4, 8, 12, 16, 24, 32, 48, 64px)
|
|
1105
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)
|
|
@@ -1112,309 +1236,783 @@ function B(t) {
|
|
|
1112
1236
|
e.className = "field";
|
|
1113
1237
|
const n = document.createElement("div");
|
|
1114
1238
|
n.className = "field-head";
|
|
1115
|
-
const o = document.createElement("span");
|
|
1116
|
-
o.className = "field-label", o.textContent = t;
|
|
1117
1239
|
const a = document.createElement("span");
|
|
1118
|
-
|
|
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), {
|
|
1119
1243
|
el: e,
|
|
1120
|
-
setValue(
|
|
1121
|
-
|
|
1244
|
+
setValue(r) {
|
|
1245
|
+
o.textContent = r || "", o.hidden = !r;
|
|
1122
1246
|
},
|
|
1123
|
-
append(...
|
|
1124
|
-
e.append(...
|
|
1247
|
+
append(...r) {
|
|
1248
|
+
e.append(...r);
|
|
1125
1249
|
}
|
|
1126
1250
|
};
|
|
1127
1251
|
}
|
|
1128
|
-
function
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
const
|
|
1132
|
-
const
|
|
1133
|
-
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;
|
|
1134
1258
|
};
|
|
1135
|
-
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
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++)
|
|
1139
1263
|
for (let u = 0; u < n; u++) {
|
|
1140
|
-
const
|
|
1141
|
-
|
|
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);
|
|
1142
1266
|
}
|
|
1143
1267
|
const l = document.createElement("span");
|
|
1144
|
-
l.className = "pad-knob",
|
|
1145
|
-
let
|
|
1146
|
-
const
|
|
1147
|
-
function
|
|
1148
|
-
l.style.left = `${
|
|
1149
|
-
}
|
|
1150
|
-
function f(
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1153
|
-
const
|
|
1154
|
-
const u =
|
|
1155
|
-
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);
|
|
1156
1280
|
};
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
}),
|
|
1160
|
-
|
|
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);
|
|
1161
1285
|
});
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1286
|
+
const x = (b) => {
|
|
1287
|
+
r.hasPointerCapture(b.pointerId) && r.releasePointerCapture(b.pointerId), r.classList.remove("dragging");
|
|
1164
1288
|
};
|
|
1165
|
-
return
|
|
1166
|
-
const u = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] }[
|
|
1167
|
-
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"));
|
|
1168
1292
|
}), {
|
|
1169
|
-
el:
|
|
1293
|
+
el: r,
|
|
1170
1294
|
/** Show a position without telling anyone (used to sync). Off-grid values are shown as they are. */
|
|
1171
|
-
setValue(
|
|
1172
|
-
|
|
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);
|
|
1173
1297
|
}
|
|
1174
1298
|
};
|
|
1175
1299
|
}
|
|
1176
|
-
function
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
const
|
|
1180
|
-
|
|
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);
|
|
1181
1305
|
const l = document.createElement("span");
|
|
1182
|
-
l.className = "slider-value", l.setAttribute("aria-hidden", "true"),
|
|
1183
|
-
const
|
|
1184
|
-
const
|
|
1185
|
-
|
|
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);
|
|
1186
1310
|
};
|
|
1187
|
-
return
|
|
1188
|
-
|
|
1311
|
+
return i.addEventListener("input", () => {
|
|
1312
|
+
p(), r(Number(i.value));
|
|
1189
1313
|
}), {
|
|
1190
|
-
el:
|
|
1314
|
+
el: s,
|
|
1191
1315
|
/** Show a value without reporting it (sync after reset / URL load). */
|
|
1192
|
-
setValue(
|
|
1193
|
-
|
|
1316
|
+
setValue(c, h) {
|
|
1317
|
+
i.value = String(c), p(h);
|
|
1194
1318
|
}
|
|
1195
1319
|
};
|
|
1196
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
|
+
}
|
|
1197
1352
|
const F = (t) => getComputedStyle(document.documentElement).getPropertyValue(t).trim();
|
|
1198
|
-
function
|
|
1353
|
+
function ye(t, e) {
|
|
1199
1354
|
const n = /^(-?\d*\.?\d+)([a-z%]*)$/i.exec(String(t).trim());
|
|
1200
1355
|
if (!n) return null;
|
|
1201
|
-
const
|
|
1202
|
-
return
|
|
1203
|
-
}
|
|
1204
|
-
const
|
|
1205
|
-
function
|
|
1206
|
-
const c = (
|
|
1207
|
-
if (typeof
|
|
1208
|
-
return { [
|
|
1209
|
-
const
|
|
1210
|
-
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` } : {};
|
|
1211
1366
|
};
|
|
1212
1367
|
return {
|
|
1213
1368
|
id: t,
|
|
1214
1369
|
title: e,
|
|
1215
1370
|
hint: n,
|
|
1216
|
-
presets:
|
|
1217
|
-
uses: [
|
|
1218
|
-
tokens(
|
|
1219
|
-
const
|
|
1220
|
-
return { ...m
|
|
1371
|
+
presets: r,
|
|
1372
|
+
uses: [a, ...o],
|
|
1373
|
+
tokens(f) {
|
|
1374
|
+
const m = f[t] || {};
|
|
1375
|
+
return { ...y(m), ...l(m) };
|
|
1221
1376
|
},
|
|
1222
1377
|
// One render function per knob, so a layout can place them separately.
|
|
1223
1378
|
// Each takes an optional label (e.g. "Buttons" on the Corners page).
|
|
1224
1379
|
parts: {
|
|
1225
|
-
corners(
|
|
1226
|
-
const b = B(x),
|
|
1380
|
+
corners(f, m, { label: x = "Corners" } = {}) {
|
|
1381
|
+
const b = B(x), u = re({
|
|
1227
1382
|
label: `${e} corners`,
|
|
1228
|
-
options:
|
|
1229
|
-
onSelect: (
|
|
1230
|
-
}),
|
|
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({
|
|
1231
1386
|
label: `${e} corner radius`,
|
|
1232
1387
|
min: 0,
|
|
1233
|
-
max:
|
|
1234
|
-
format:
|
|
1235
|
-
onInput: (
|
|
1388
|
+
max: s,
|
|
1389
|
+
format: h,
|
|
1390
|
+
onInput: (E) => m.setSection(t, { radius: E, corners: null })
|
|
1236
1391
|
});
|
|
1237
|
-
b.append(
|
|
1238
|
-
const
|
|
1239
|
-
const
|
|
1240
|
-
if (
|
|
1241
|
-
|
|
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("");
|
|
1242
1397
|
return;
|
|
1243
1398
|
}
|
|
1244
|
-
|
|
1399
|
+
v.setValue(Math.min(E, s), h(E)), b.setValue(h(E));
|
|
1245
1400
|
};
|
|
1246
|
-
|
|
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
|
+
}));
|
|
1247
1425
|
},
|
|
1248
|
-
...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
|
+
}))]))
|
|
1249
1434
|
},
|
|
1250
|
-
render(
|
|
1251
|
-
Object.
|
|
1435
|
+
render(f, m) {
|
|
1436
|
+
Object.entries(this.parts).filter(([x]) => !x.endsWith("Tiles")).forEach(([, x]) => x(f, m));
|
|
1252
1437
|
}
|
|
1253
1438
|
};
|
|
1254
1439
|
}
|
|
1255
|
-
function
|
|
1256
|
-
const
|
|
1257
|
-
label:
|
|
1258
|
-
options:
|
|
1259
|
-
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 })
|
|
1260
1445
|
});
|
|
1261
|
-
|
|
1262
|
-
const
|
|
1263
|
-
const
|
|
1264
|
-
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) : "");
|
|
1265
1450
|
};
|
|
1266
|
-
return
|
|
1451
|
+
return p(), t.onChange(p), i.el;
|
|
1267
1452
|
}
|
|
1268
|
-
function
|
|
1269
|
-
const l = B(
|
|
1270
|
-
l.append(
|
|
1271
|
-
const
|
|
1272
|
-
const
|
|
1273
|
-
|
|
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) : "");
|
|
1274
1474
|
};
|
|
1275
|
-
return
|
|
1475
|
+
return f(), t.onChange(f), h.el;
|
|
1276
1476
|
}
|
|
1277
|
-
const
|
|
1477
|
+
const ln = (t, e) => e.map((n) => `${t}${n}`), In = {
|
|
1278
1478
|
fonts: { label: "Tokens", tokens: ["font-heading", "font-body"], kind: "text" },
|
|
1279
|
-
type: { label: "Tokens", tokens:
|
|
1479
|
+
type: { label: "Tokens", tokens: ln("text-", ["xs", "sm", "base", "lg", "xl", "2xl", "3xl"]), kind: "length" },
|
|
1280
1480
|
leading: { label: "Line height", tokens: ["leading-tight", "leading-normal"], kind: "number" },
|
|
1481
|
+
weights: { label: "Weights", tokens: ["weight-heading", "weight-strong"], kind: "number" },
|
|
1281
1482
|
colours: { label: "Tokens", tokens: ["bg", "surface", "surface-raised", "text", "text-muted", "border", "primary", "primary-hover", "on-primary", "focus", "success", "warning", "danger"], kind: "colour" },
|
|
1282
|
-
spacing: { label: "Steps", tokens:
|
|
1483
|
+
spacing: { label: "Steps", tokens: ln("space-", [1, 2, 3, 4, 5, 6, 7, 8]), kind: "length", bar: 64 },
|
|
1283
1484
|
controls: { label: "Controls", tokens: ["control-height", "control-pad-x"], kind: "length" },
|
|
1284
1485
|
corners: { label: "Tokens", tokens: ["radius-control", "radius-container"], kind: "length" },
|
|
1285
1486
|
borders: { label: "Tokens", tokens: ["border-width", "border-width-container"], kind: "length" },
|
|
1286
|
-
shadows: { label: "Tokens", tokens: ["shadow-sm", "shadow-md", "shadow-lg"], kind: "text" }
|
|
1287
|
-
|
|
1288
|
-
|
|
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) {
|
|
1289
1493
|
const n = /^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());
|
|
1290
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" : "") };
|
|
1291
1495
|
}
|
|
1292
|
-
function
|
|
1496
|
+
function Ba(t, e) {
|
|
1293
1497
|
const n = t.trim();
|
|
1294
1498
|
return n ? e === "length" && /^-?\d*\.?\d+$/.test(n) ? `${n}px` : n : null;
|
|
1295
1499
|
}
|
|
1296
|
-
function
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
<code class="token-name" title="${
|
|
1300
|
-
${
|
|
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>'}
|
|
1301
1505
|
<span class="custom-tag" hidden>Custom</span>
|
|
1302
1506
|
<label class="token-value">
|
|
1303
1507
|
${n === "colour" ? '<span class="token-swatch"></span>' : ""}
|
|
1304
|
-
<input type="text" spellcheck="false" aria-label="${
|
|
1508
|
+
<input type="text" spellcheck="false" aria-label="${_(e)}" ${n === "length" || n === "number" ? 'inputmode="decimal"' : ""}>
|
|
1305
1509
|
<span class="token-unit"></span>
|
|
1306
1510
|
</label>
|
|
1307
|
-
<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>
|
|
1308
1512
|
`;
|
|
1309
|
-
const
|
|
1310
|
-
let
|
|
1311
|
-
const
|
|
1312
|
-
let
|
|
1313
|
-
|
|
1314
|
-
const
|
|
1315
|
-
if (
|
|
1316
|
-
|
|
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();
|
|
1317
1521
|
return;
|
|
1318
1522
|
}
|
|
1319
|
-
if (
|
|
1320
|
-
t.setSection("custom", { [e]:
|
|
1523
|
+
if (x === b && !Qe(t, e)) return f();
|
|
1524
|
+
t.setSection("custom", { [e]: x });
|
|
1321
1525
|
};
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
}),
|
|
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 }));
|
|
1325
1529
|
function f() {
|
|
1326
|
-
const
|
|
1327
|
-
|
|
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;
|
|
1328
1532
|
}
|
|
1329
|
-
return f(), t.onChange(f),
|
|
1533
|
+
return f(), t.onChange(f), o;
|
|
1330
1534
|
}
|
|
1331
|
-
const
|
|
1535
|
+
const qa = {
|
|
1332
1536
|
id: "custom",
|
|
1333
1537
|
title: "Custom tokens",
|
|
1334
1538
|
hint: "Any token, set to a fixed value",
|
|
1335
1539
|
uses: [],
|
|
1336
1540
|
// not offered for a single picked element (yet)
|
|
1337
1541
|
tokens(t) {
|
|
1338
|
-
return Object.fromEntries(Object.entries(t.custom || {}).map(([e, n]) => [
|
|
1542
|
+
return Object.fromEntries(Object.entries(t.custom || {}).map(([e, n]) => [_(e), n]));
|
|
1339
1543
|
},
|
|
1340
|
-
describe: (t, e) => ({ label:
|
|
1544
|
+
describe: (t, e) => ({ label: _(t), value: String(e) }),
|
|
1341
1545
|
// One part per Edit page group, e.g. parts.spacing → rows for --twd-space-1 … 8.
|
|
1342
|
-
parts: Object.fromEntries(Object.entries(
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
const
|
|
1346
|
-
|
|
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()}` : "");
|
|
1347
1551
|
const l = document.createElement("div");
|
|
1348
|
-
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);
|
|
1349
1553
|
}]))
|
|
1350
|
-
},
|
|
1351
|
-
function
|
|
1352
|
-
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;
|
|
1353
1557
|
}
|
|
1354
|
-
const
|
|
1558
|
+
const et = (t) => `${Math.round(t * 100)}%`, Va = {
|
|
1355
1559
|
id: "density",
|
|
1356
1560
|
title: "Density",
|
|
1357
1561
|
hint: "Compact · regular · comfortable",
|
|
1358
|
-
uses:
|
|
1562
|
+
uses: cn,
|
|
1359
1563
|
tokens(t) {
|
|
1360
|
-
const e =
|
|
1564
|
+
const e = te(t.density);
|
|
1361
1565
|
if (e === 1) return {};
|
|
1362
1566
|
const n = {};
|
|
1363
|
-
return
|
|
1364
|
-
const
|
|
1365
|
-
|
|
1567
|
+
return cn.forEach((a) => {
|
|
1568
|
+
const o = ye(F(a), e);
|
|
1569
|
+
o && (n[a] = o);
|
|
1366
1570
|
}), n;
|
|
1367
1571
|
},
|
|
1368
1572
|
/** How the Changes list and prompt describe a setting. */
|
|
1369
|
-
describe: (t, e) => t === "factor" ? { label: "Spacing", value:
|
|
1573
|
+
describe: (t, e) => t === "factor" ? { label: "Spacing", value: et(e) } : null,
|
|
1370
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
|
+
},
|
|
1371
1591
|
spacing(t, e, { label: n = "Spacing" } = {}) {
|
|
1372
|
-
const
|
|
1592
|
+
const a = B(n), o = re({
|
|
1373
1593
|
label: n,
|
|
1374
1594
|
options: [{ value: "compact", label: "Compact" }, { value: "regular", label: "Regular" }, { value: "comfy", label: "Comfy" }],
|
|
1375
|
-
onSelect: (
|
|
1595
|
+
onSelect: (s) => e.setSection("density", { spacing: s, factor: null })
|
|
1376
1596
|
});
|
|
1377
|
-
|
|
1378
|
-
const
|
|
1379
|
-
const
|
|
1380
|
-
|
|
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));
|
|
1381
1601
|
};
|
|
1382
|
-
|
|
1602
|
+
r(), e.onChange(r), t.append(a.el);
|
|
1383
1603
|
}
|
|
1384
1604
|
},
|
|
1385
1605
|
render(t, e) {
|
|
1386
|
-
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
|
+
});
|
|
1387
1691
|
}
|
|
1388
|
-
|
|
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 = {
|
|
1389
1975
|
soft: { sm: [0, 1, 2, 0, 0.06], md: [0, 4, 12, 0, 0.08], lg: [0, 12, 32, 0, 0.12] },
|
|
1390
1976
|
// = tokens.css
|
|
1391
1977
|
crisp: { sm: [0, 1, 1, 0, 0.12], md: [0, 2, 4, 0, 0.14], lg: [0, 4, 8, 0, 0.16] },
|
|
1392
1978
|
bold: { sm: [2, 2, 0, 0, 0.85], md: [3, 3, 0, 0, 0.85], lg: [5, 5, 0, 0, 0.85] }
|
|
1393
|
-
},
|
|
1394
|
-
const
|
|
1395
|
-
return
|
|
1396
|
-
},
|
|
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 = {
|
|
1397
1983
|
id: "depth",
|
|
1398
1984
|
title: "Depth",
|
|
1399
1985
|
hint: "Flat · soft · crisp · bold",
|
|
1400
1986
|
uses: ["--twd-shadow-sm", "--twd-shadow-md", "--twd-shadow-lg"],
|
|
1401
1987
|
tokens(t) {
|
|
1402
|
-
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;
|
|
1403
1989
|
if (n === "flat")
|
|
1404
1990
|
return { "--twd-shadow-sm": "none", "--twd-shadow-md": "none", "--twd-shadow-lg": "none" };
|
|
1405
|
-
const
|
|
1406
|
-
if (!
|
|
1407
|
-
if (n === "soft" &&
|
|
1408
|
-
const
|
|
1991
|
+
const o = st[n];
|
|
1992
|
+
if (!o) return {};
|
|
1993
|
+
if (n === "soft" && a === ne) return {};
|
|
1994
|
+
const r = a / ne;
|
|
1409
1995
|
return {
|
|
1410
|
-
"--twd-shadow-sm":
|
|
1411
|
-
"--twd-shadow-md":
|
|
1412
|
-
"--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)
|
|
1413
1999
|
};
|
|
1414
2000
|
},
|
|
1415
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
|
+
},
|
|
1416
2014
|
style(t, e, { label: n = "Style" } = {}) {
|
|
1417
|
-
t.append(
|
|
2015
|
+
t.append(ke(e, {
|
|
1418
2016
|
section: "depth",
|
|
1419
2017
|
key: "style",
|
|
1420
2018
|
label: n,
|
|
@@ -1423,208 +2021,431 @@ const Dt = (t) => `${Math.round(t * 100)}%`, bo = {
|
|
|
1423
2021
|
}));
|
|
1424
2022
|
},
|
|
1425
2023
|
intensity(t, e, { label: n = "Intensity" } = {}) {
|
|
1426
|
-
t.append(
|
|
2024
|
+
t.append(Ia(e, { section: "depth", key: "intensity", label: n, min: 0, max: 100, fallback: ne, format: (a) => `${a}%` }));
|
|
1427
2025
|
}
|
|
1428
2026
|
},
|
|
1429
2027
|
render(t, e) {
|
|
1430
|
-
Object.
|
|
2028
|
+
Object.entries(this.parts).filter(([n]) => !n.endsWith("Tiles")).forEach(([, n]) => n(t, e));
|
|
1431
2029
|
}
|
|
1432
|
-
},
|
|
1433
|
-
function
|
|
1434
|
-
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;
|
|
1435
2033
|
}
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
});
|
|
1446
|
-
|
|
1447
|
-
const s = (
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
buttons: { corners: c, radius: null },
|
|
1459
|
-
cards: { corners: Ut[c], radius: null }
|
|
1460
|
-
})
|
|
1461
|
-
});
|
|
1462
|
-
s.append(r.el);
|
|
1463
|
-
const d = (c, p, m) => {
|
|
1464
|
-
if (p.radius != null) return null;
|
|
1465
|
-
if (p.corners) return p.corners;
|
|
1466
|
-
const v = parseFloat(F(m));
|
|
1467
|
-
return c.presets.find((f) => f.radius === v)?.value || null;
|
|
1468
|
-
}, l = () => {
|
|
1469
|
-
const c = e.getState(), p = d(o, c.buttons || {}, "--twd-radius-control"), m = d(a, c.cards || {}, "--twd-radius-container");
|
|
1470
|
-
r.setValue(p && Ut[p] === m ? p : null);
|
|
1471
|
-
const v = (b) => {
|
|
1472
|
-
const g = parseFloat(F(b));
|
|
1473
|
-
return isNaN(g) ? "" : g >= 999 ? "full" : `${g}px`;
|
|
1474
|
-
}, f = v("--twd-radius-control"), x = v("--twd-radius-container");
|
|
1475
|
-
s.setValue(f && x ? `${f} · ${x}` : "");
|
|
1476
|
-
};
|
|
1477
|
-
l(), e.onChange(l), t.append(s.el);
|
|
1478
|
-
}
|
|
1479
|
-
}, Ie = [
|
|
1480
|
-
{ key: "page-pad", label: "Page padding", step: 5 },
|
|
1481
|
-
{ key: "section-gap", label: "Section gap", step: 6 },
|
|
1482
|
-
{ key: "card-pad", label: "Card padding", step: 5 },
|
|
1483
|
-
{ key: "card-gap", label: "Card gap", step: 2 },
|
|
1484
|
-
{ key: "control-gap", label: "Control gap", step: 3 },
|
|
1485
|
-
{ key: "control-pad-x", label: "Control padding", step: null }
|
|
1486
|
-
], ko = [1, 2, 3, 4, 5, 6, 7, 8], So = /^var\(\s*--twd-space-(\d)\s*\)$/, De = (t) => {
|
|
1487
|
-
const e = /^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());
|
|
1488
|
-
return e ? +(parseFloat(e[1]) * (e[2] === "rem" ? 16 : 1)).toFixed(1) : null;
|
|
1489
|
-
};
|
|
1490
|
-
function Eo(t, e, n) {
|
|
1491
|
-
const o = `--twd-${e.key}`, a = document.createElement("div");
|
|
1492
|
-
a.className = "role-row", n || a.classList.add("unused"), a.innerHTML = `
|
|
1493
|
-
<span class="role-label" title="${o}${n ? "" : " (not used on this page)"}">${e.label}</span>
|
|
1494
|
-
<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>
|
|
1495
|
-
<span class="step-select">
|
|
1496
|
-
<select aria-label="${e.label}"></select>
|
|
1497
|
-
<span class="step-chevron" aria-hidden="true">${M.down}</span>
|
|
1498
|
-
</span>
|
|
1499
|
-
<button class="icon-btn token-reset" title="Back to the default" aria-label="Reset ${e.label}" hidden>${M.reset}</button>
|
|
1500
|
-
`;
|
|
1501
|
-
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 = () => {
|
|
1502
|
-
const b = c();
|
|
1503
|
-
if (b == null) return e.step ? { step: e.step } : { fixed: F(o) };
|
|
1504
|
-
const g = So.exec(b);
|
|
1505
|
-
return g ? { step: +g[1] } : { fixed: b };
|
|
1506
|
-
}, m = (b) => t.setSection("custom", { [e.key]: b }), v = (b) => e.step ? b === `var(--twd-space-${e.step})` : !1;
|
|
1507
|
-
s.addEventListener("change", () => {
|
|
1508
|
-
if (s.value === "fixed") {
|
|
1509
|
-
const g = De(F(o));
|
|
1510
|
-
m(g != null ? `${g}px` : F(o)), d.focus(), d.select();
|
|
1511
|
-
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
|
+
});
|
|
1512
2056
|
}
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
const b = d.value.trim();
|
|
1518
|
-
if (!b) return m(null);
|
|
1519
|
-
m(/^-?\d*\.?\d+$/.test(b) ? `${b}px` : b);
|
|
1520
|
-
};
|
|
1521
|
-
d.addEventListener("change", f), d.addEventListener("blur", () => x()), d.addEventListener("keydown", (b) => {
|
|
1522
|
-
b.key === "Enter" && (b.preventDefault(), d.blur()), b.key === "Escape" && (b.stopPropagation(), x(), d.blur());
|
|
1523
|
-
}), l.addEventListener("click", () => m(null));
|
|
1524
|
-
function x() {
|
|
1525
|
-
const b = p();
|
|
1526
|
-
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) {
|
|
1527
|
-
const u = De(b.fixed);
|
|
1528
|
-
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);
|
|
1529
2061
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
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)));
|
|
1532
2090
|
}
|
|
1533
|
-
|
|
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";
|
|
1534
2237
|
}
|
|
1535
|
-
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 = {
|
|
1536
2344
|
spacing(t, e, { label: n = "Roles" } = {}) {
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
const
|
|
1544
|
-
|
|
1545
|
-
}
|
|
1546
|
-
},
|
|
1547
|
-
["simple", "character"],
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
{ id: "
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
{ id: "
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
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>
|
|
1574
2402
|
</details>
|
|
1575
2403
|
`).join("");
|
|
1576
|
-
|
|
1577
|
-
<style>${
|
|
2404
|
+
o.innerHTML = `
|
|
2405
|
+
<style>${ua}</style>
|
|
1578
2406
|
<div class="pick-box" hidden><span class="pick-tag"></span></div>
|
|
1579
2407
|
<div class="wrap">
|
|
1580
2408
|
<div class="toast" role="status"></div>
|
|
1581
2409
|
<div class="panel" role="dialog" aria-label="Panel" hidden>
|
|
1582
2410
|
<div class="head">
|
|
1583
2411
|
<span class="brand">Panel</span>
|
|
1584
|
-
<button class="icon-btn" data-action="pick" title="Pick an element to tweak on its own" aria-label="Pick an element" aria-pressed="false">${
|
|
1585
|
-
<button class="icon-btn" data-action="compare" title="Hold to see the original (Alt+C toggles)" aria-label="Compare with original" aria-pressed="false">${
|
|
1586
|
-
<button class="icon-btn" data-action="copy" title="Copy link to this version" aria-label="Copy link">${
|
|
1587
|
-
<button class="icon-btn" data-action="reset" title="Reset to defaults" aria-label="Reset">${
|
|
1588
|
-
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${
|
|
1589
|
-
</div>
|
|
1590
|
-
<div class="head subhead" hidden>
|
|
1591
|
-
<button class="icon-btn" data-action="page-back" title="Back" aria-label="Back">${M.back}</button>
|
|
1592
|
-
<span class="brand page-title"></span>
|
|
1593
|
-
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${M.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>
|
|
1594
2417
|
</div>
|
|
1595
|
-
<div class="
|
|
1596
|
-
|
|
1597
|
-
<
|
|
1598
|
-
<div class="page" data-page="edit" hidden>
|
|
1599
|
-
${we.map((i) => `<button class="nav-row" data-go="${i.id}">${i.title}<span class="chev">${M.chevron}</span></button>`).join("")}
|
|
1600
|
-
</div>
|
|
1601
|
-
${we.map((i) => `<div class="page page-fields" data-page="${i.id}" hidden></div>`).join("")}
|
|
1602
|
-
</div>
|
|
1603
|
-
<div class="body" data-view="local" hidden></div>
|
|
1604
|
-
<div class="body" data-view="changes" hidden></div>
|
|
1605
|
-
<button class="edit-row" data-action="edit">${M.sliders}<span>Edit every token</span><span class="chev">${M.chevron}</span></button>
|
|
1606
|
-
<div class="foot">
|
|
1607
|
-
<button class="icon-btn" data-action="undo" title="Undo (⌘Z)" aria-label="Undo">${M.undo}</button>
|
|
1608
|
-
<button class="icon-btn" data-action="redo" title="Redo (⇧⌘Z)" aria-label="Redo">${M.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>
|
|
1609
2421
|
<button class="text-btn" data-action="changes" aria-pressed="false">Changes <span class="badge" hidden></span></button>
|
|
1610
2422
|
<span class="spacer"></span>
|
|
1611
2423
|
<div class="apply">
|
|
1612
2424
|
<button class="apply-btn" data-action="apply" title="Copy these tweaks as a prompt for your coding agent">Copy prompt</button>
|
|
1613
|
-
<button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${
|
|
2425
|
+
<button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${A.down}</button>
|
|
1614
2426
|
<div class="menu apply-menu" role="menu" hidden>
|
|
1615
2427
|
<button role="menuitemradio" data-apply-mode="preview">Preview before copying</button>
|
|
1616
2428
|
<button role="menuitemradio" data-apply-mode="copy">Copy without preview</button>
|
|
1617
2429
|
</div>
|
|
1618
2430
|
</div>
|
|
1619
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>
|
|
1620
2441
|
</div>
|
|
1621
|
-
<button class="launcher" aria-label="Open Panel" aria-expanded="false">${
|
|
2442
|
+
<button class="launcher" aria-label="Open Panel" aria-expanded="false">${A.panelMark}</button>
|
|
1622
2443
|
</div>
|
|
1623
2444
|
<div class="dialog-backdrop" hidden>
|
|
1624
2445
|
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="twd-dialog-title">
|
|
1625
2446
|
<div class="dialog-head">
|
|
1626
2447
|
<span id="twd-dialog-title">Prompt for your agent</span>
|
|
1627
|
-
<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>
|
|
1628
2449
|
</div>
|
|
1629
2450
|
<textarea class="prompt-text" readonly spellcheck="false" aria-label="Generated prompt"></textarea>
|
|
1630
2451
|
<div class="dialog-foot">
|
|
@@ -1634,754 +2455,421 @@ function Mo({ sections: t, store: e, view: n }) {
|
|
|
1634
2455
|
</div>
|
|
1635
2456
|
</div>
|
|
1636
2457
|
`;
|
|
1637
|
-
const
|
|
1638
|
-
function
|
|
1639
|
-
|
|
1640
|
-
if (typeof
|
|
1641
|
-
const
|
|
1642
|
-
|
|
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);
|
|
1643
2464
|
});
|
|
1644
|
-
const
|
|
1645
|
-
return
|
|
1646
|
-
|
|
1647
|
-
|
|
2465
|
+
const $ = [...d.querySelectorAll("details")];
|
|
2466
|
+
return $.forEach((k) => k.addEventListener("toggle", () => {
|
|
2467
|
+
k.open && $.forEach((C) => {
|
|
2468
|
+
C !== k && (C.open = !1);
|
|
1648
2469
|
});
|
|
1649
|
-
})),
|
|
2470
|
+
})), $;
|
|
1650
2471
|
}
|
|
1651
|
-
const
|
|
1652
|
-
function
|
|
1653
|
-
|
|
2472
|
+
const x = Object.fromEntries(t.map((d) => [d.id, d]));
|
|
2473
|
+
function b(d, g) {
|
|
2474
|
+
g.forEach((w) => {
|
|
1654
2475
|
if (w === "warnings") {
|
|
1655
|
-
const
|
|
1656
|
-
|
|
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);
|
|
1657
2488
|
return;
|
|
1658
2489
|
}
|
|
1659
2490
|
if (w.tabs) {
|
|
1660
|
-
const
|
|
1661
|
-
const
|
|
1662
|
-
return
|
|
1663
|
-
}),
|
|
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({
|
|
1664
2495
|
label: "View",
|
|
1665
|
-
options: w.tabs.map((
|
|
1666
|
-
onSelect: (
|
|
1667
|
-
|
|
1668
|
-
|
|
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;
|
|
1669
2500
|
});
|
|
1670
2501
|
}
|
|
1671
2502
|
});
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
}),
|
|
2503
|
+
he.setValue("0"), S.forEach((j, z) => {
|
|
2504
|
+
j.hidden = z !== 0;
|
|
2505
|
+
}), d.append(he.el, ...S);
|
|
1675
2506
|
return;
|
|
1676
2507
|
}
|
|
1677
|
-
const [
|
|
1678
|
-
|
|
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
|
+
} });
|
|
1679
2512
|
});
|
|
1680
2513
|
}
|
|
1681
|
-
|
|
1682
|
-
const u =
|
|
1683
|
-
let
|
|
1684
|
-
function
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
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
|
+
}
|
|
1692
2545
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
function Ce(i) {
|
|
1696
|
-
ce = i, Object.entries(p).forEach(([h, w]) => {
|
|
1697
|
-
w.hidden = h !== i;
|
|
1698
|
-
}), 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");
|
|
1699
2548
|
}
|
|
1700
|
-
function
|
|
1701
|
-
|
|
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();
|
|
1702
2551
|
}
|
|
1703
|
-
function
|
|
1704
|
-
|
|
2552
|
+
function Ke() {
|
|
2553
|
+
i.hidden ? le() : _e();
|
|
1705
2554
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
2555
|
+
let Ct;
|
|
2556
|
+
function H(d) {
|
|
2557
|
+
p.textContent = d, p.classList.add("show"), clearTimeout(Ct), Ct = setTimeout(() => p.classList.remove("show"), 1600);
|
|
1708
2558
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
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"));
|
|
1712
2561
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
function G(i, h) {
|
|
1718
|
-
if (Te = i, !i) {
|
|
1719
|
-
m.hidden = !0;
|
|
2562
|
+
let Ge = null;
|
|
2563
|
+
function Q(d, g) {
|
|
2564
|
+
if (Ge = d, !d) {
|
|
2565
|
+
h.hidden = !0;
|
|
1720
2566
|
return;
|
|
1721
2567
|
}
|
|
1722
|
-
const w =
|
|
1723
|
-
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;
|
|
1724
2570
|
}
|
|
1725
|
-
const
|
|
1726
|
-
|
|
2571
|
+
const Tt = () => {
|
|
2572
|
+
Ge && !h.hidden && Q(Ge, y.textContent);
|
|
1727
2573
|
};
|
|
1728
|
-
window.addEventListener("scroll",
|
|
1729
|
-
let
|
|
1730
|
-
const
|
|
1731
|
-
function
|
|
1732
|
-
if (
|
|
1733
|
-
const
|
|
1734
|
-
|
|
1735
|
-
}
|
|
1736
|
-
function
|
|
1737
|
-
if (
|
|
1738
|
-
const
|
|
1739
|
-
!
|
|
1740
|
-
}
|
|
1741
|
-
function
|
|
1742
|
-
|
|
1743
|
-
}
|
|
1744
|
-
const
|
|
1745
|
-
function
|
|
1746
|
-
if (!
|
|
1747
|
-
if (
|
|
1748
|
-
const
|
|
1749
|
-
|
|
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);
|
|
1750
2596
|
}
|
|
1751
|
-
|
|
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");
|
|
1752
2598
|
}
|
|
1753
2599
|
}
|
|
1754
|
-
function
|
|
1755
|
-
|
|
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));
|
|
1756
2602
|
}
|
|
1757
|
-
let
|
|
1758
|
-
function
|
|
2603
|
+
let M = null;
|
|
2604
|
+
function Vn(d, g) {
|
|
1759
2605
|
const w = [];
|
|
1760
2606
|
return {
|
|
1761
2607
|
ctx: {
|
|
1762
|
-
getState: () => (e.getState().scopes || {})[
|
|
1763
|
-
setSection: (
|
|
1764
|
-
onChange: (
|
|
1765
|
-
const
|
|
1766
|
-
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;
|
|
1767
2613
|
},
|
|
1768
|
-
readToken: (
|
|
2614
|
+
readToken: ($) => getComputedStyle(g).getPropertyValue($).trim()
|
|
1769
2615
|
},
|
|
1770
|
-
dispose: () => w.forEach((
|
|
2616
|
+
dispose: () => w.forEach(($) => $())
|
|
1771
2617
|
};
|
|
1772
2618
|
}
|
|
1773
|
-
function
|
|
1774
|
-
const w =
|
|
1775
|
-
|
|
1776
|
-
} },
|
|
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);
|
|
1777
2623
|
}
|
|
1778
|
-
const
|
|
1779
|
-
function
|
|
1780
|
-
|
|
1781
|
-
const { info:
|
|
2624
|
+
const Rt = () => M.target === "class" ? M.info.classSelector : M.info.elementSelector;
|
|
2625
|
+
function We() {
|
|
2626
|
+
M.dispose();
|
|
2627
|
+
const { info: d } = M, g = t.filter((j) => (j.uses || []).some((z) => d.tokens.has(z))), w = c.local;
|
|
1782
2628
|
if (w.innerHTML = `
|
|
1783
2629
|
<div class="scope-bar">
|
|
1784
2630
|
<div class="scope-head">
|
|
1785
|
-
<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>
|
|
1786
2632
|
<div class="scope-name">
|
|
1787
|
-
<code>${
|
|
1788
|
-
${
|
|
2633
|
+
<code>${O(bt(d.el))}</code>
|
|
2634
|
+
${d.text ? `<span class="muted">“${O(d.text)}”</span>` : ""}
|
|
1789
2635
|
</div>
|
|
1790
2636
|
</div>
|
|
1791
2637
|
<div class="scope-target"></div>
|
|
1792
2638
|
<div class="warnings"></div>
|
|
1793
2639
|
</div>
|
|
1794
|
-
${
|
|
1795
|
-
`,
|
|
1796
|
-
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({
|
|
1797
2643
|
label: "Apply to",
|
|
1798
2644
|
options: [
|
|
1799
2645
|
{ value: "element", label: "This element" },
|
|
1800
|
-
{ value: "class", label: `All ${
|
|
2646
|
+
{ value: "class", label: `All ${d.classSelector} (${d.classCount})`, title: `Every element matching ${d.classSelector}` }
|
|
1801
2647
|
],
|
|
1802
|
-
onSelect: (
|
|
1803
|
-
|
|
2648
|
+
onSelect: (z) => {
|
|
2649
|
+
z === "class" && e.moveScope(d.elementSelector, d.classSelector), M.target = z, We();
|
|
1804
2650
|
}
|
|
1805
2651
|
});
|
|
1806
|
-
|
|
2652
|
+
j.setValue(M.target), w.querySelector(".scope-target").append(j.el);
|
|
1807
2653
|
}
|
|
1808
|
-
const { ctx:
|
|
1809
|
-
|
|
1810
|
-
const
|
|
1811
|
-
const
|
|
1812
|
-
|
|
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]) : "";
|
|
1813
2659
|
};
|
|
1814
|
-
|
|
1815
|
-
const
|
|
1816
|
-
|
|
1817
|
-
|
|
2660
|
+
S();
|
|
2661
|
+
const he = e.onChange(S);
|
|
2662
|
+
M.dispose = () => {
|
|
2663
|
+
k(), he();
|
|
1818
2664
|
};
|
|
1819
2665
|
}
|
|
1820
|
-
function
|
|
1821
|
-
if (!
|
|
1822
|
-
const
|
|
1823
|
-
!
|
|
2666
|
+
function zt() {
|
|
2667
|
+
if (!M || !M.info.classSelector) return;
|
|
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());
|
|
1824
2670
|
}
|
|
1825
|
-
const
|
|
1826
|
-
e.undo(),
|
|
1827
|
-
},
|
|
1828
|
-
e.redo(),
|
|
2671
|
+
const It = () => {
|
|
2672
|
+
e.undo(), zt();
|
|
2673
|
+
}, Dt = () => {
|
|
2674
|
+
e.redo(), zt();
|
|
1829
2675
|
};
|
|
1830
|
-
function
|
|
1831
|
-
|
|
1832
|
-
}
|
|
1833
|
-
const
|
|
1834
|
-
function
|
|
1835
|
-
const
|
|
1836
|
-
if (!
|
|
1837
|
-
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)}`;
|
|
1838
2684
|
return;
|
|
1839
2685
|
}
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
const
|
|
1843
|
-
|
|
1844
|
-
}), 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]) => `
|
|
1845
2691
|
<div class="change-group">
|
|
1846
2692
|
<div class="change-scope">
|
|
1847
|
-
${
|
|
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>`}
|
|
1848
2694
|
</div>
|
|
1849
|
-
${
|
|
2695
|
+
${C.map((q) => `
|
|
1850
2696
|
<div class="change-row">
|
|
1851
2697
|
<div class="change-main">
|
|
1852
|
-
<div class="change-title">${
|
|
1853
|
-
${
|
|
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("")}
|
|
1854
2700
|
</div>
|
|
1855
|
-
<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>
|
|
1856
2702
|
</div>`).join("")}
|
|
1857
2703
|
</div>`).join("");
|
|
1858
2704
|
}
|
|
1859
|
-
function
|
|
1860
|
-
if (
|
|
1861
|
-
const w = e.getState()[
|
|
1862
|
-
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])));
|
|
1863
2709
|
}
|
|
1864
|
-
const
|
|
1865
|
-
function
|
|
1866
|
-
|
|
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())));
|
|
1867
2713
|
}
|
|
1868
|
-
function
|
|
1869
|
-
|
|
2714
|
+
function Ee(d) {
|
|
2715
|
+
pe.hidden = !d, qt.setAttribute("aria-expanded", String(d)), d && Kn();
|
|
1870
2716
|
}
|
|
1871
|
-
function
|
|
1872
|
-
return
|
|
2717
|
+
function Ht() {
|
|
2718
|
+
return Sa(en(e.getState(), t), { warnings: Je() });
|
|
1873
2719
|
}
|
|
1874
|
-
const
|
|
1875
|
-
function
|
|
1876
|
-
const
|
|
1877
|
-
if (!
|
|
1878
|
-
if (
|
|
1879
|
-
|
|
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();
|
|
1880
2726
|
}
|
|
1881
|
-
function
|
|
1882
|
-
|
|
2727
|
+
function Ye() {
|
|
2728
|
+
Ce.hidden = !0, s('[data-action="apply"]').focus();
|
|
1883
2729
|
}
|
|
1884
|
-
const
|
|
1885
|
-
function
|
|
1886
|
-
|
|
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;
|
|
1887
2733
|
}
|
|
1888
|
-
let
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2734
|
+
let Kt = null, ue = !1;
|
|
2735
|
+
ee.addEventListener("pointerdown", () => {
|
|
2736
|
+
ue = !1, Kt = setTimeout(() => {
|
|
2737
|
+
ue = !0, Te(!0);
|
|
1892
2738
|
}, 180);
|
|
1893
2739
|
});
|
|
1894
|
-
const
|
|
1895
|
-
clearTimeout(
|
|
2740
|
+
const Gt = () => {
|
|
2741
|
+
clearTimeout(Kt), ue && (Te(!1), ue = !1);
|
|
1896
2742
|
};
|
|
1897
|
-
|
|
1898
|
-
const
|
|
2743
|
+
ee.addEventListener("pointerup", Gt), ee.addEventListener("pointerleave", Gt);
|
|
2744
|
+
const Me = s(".setup-hint"), Wt = `setup-hint-dismissed:${location.pathname}`, Yn = {
|
|
1899
2745
|
"no-tokens": {
|
|
1900
2746
|
title: "The Panel tokens aren’t loaded",
|
|
1901
2747
|
text: "The knobs change the --twd-* variables, but this page doesn’t load them. Add the token stylesheet before your own CSS:",
|
|
1902
|
-
snippets:
|
|
1903
|
-
link: `${
|
|
2748
|
+
snippets: sn,
|
|
2749
|
+
link: `${rn}/#install`
|
|
1904
2750
|
},
|
|
1905
2751
|
unused: {
|
|
1906
2752
|
title: "Nothing here uses the Panel tokens yet",
|
|
1907
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:",
|
|
1908
2754
|
copy: "Copy prompt for your agent",
|
|
1909
2755
|
copied: "Prompt copied",
|
|
1910
|
-
text2copy:
|
|
1911
|
-
link: `${
|
|
2756
|
+
text2copy: Na,
|
|
2757
|
+
link: `${rn}/#tokens`
|
|
1912
2758
|
}
|
|
1913
2759
|
};
|
|
1914
|
-
let
|
|
1915
|
-
function
|
|
1916
|
-
const
|
|
1917
|
-
|
|
1918
|
-
<div class="setup-head">${
|
|
1919
|
-
<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>
|
|
1920
2766
|
</div>
|
|
1921
|
-
<p>${
|
|
1922
|
-
${(
|
|
2767
|
+
<p>${O(D.text)}</p>
|
|
2768
|
+
${(D.snippets || []).map((g, w) => `
|
|
1923
2769
|
<div class="setup-snippet">
|
|
1924
|
-
<span>${
|
|
1925
|
-
<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>
|
|
1926
2772
|
</div>`).join("")}
|
|
1927
2773
|
<div class="setup-actions">
|
|
1928
|
-
${
|
|
1929
|
-
<a href="${
|
|
1930
|
-
</div>`,
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
const
|
|
1934
|
-
function
|
|
1935
|
-
|
|
1936
|
-
const
|
|
1937
|
-
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1940
|
-
w.innerHTML =
|
|
1941
|
-
}),
|
|
1942
|
-
}
|
|
1943
|
-
return e.onChange(
|
|
1944
|
-
if (
|
|
1945
|
-
const
|
|
1946
|
-
if (
|
|
1947
|
-
if (
|
|
1948
|
-
if (
|
|
1949
|
-
const w = document.querySelector(
|
|
1950
|
-
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");
|
|
1951
2797
|
}
|
|
1952
|
-
if (
|
|
1953
|
-
if (
|
|
1954
|
-
|
|
1955
|
-
if (
|
|
1956
|
-
|
|
1957
|
-
|
|
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) {
|
|
1958
2805
|
case "close":
|
|
1959
|
-
return
|
|
2806
|
+
return _e();
|
|
1960
2807
|
case "reset":
|
|
1961
|
-
return e.resetState(),
|
|
2808
|
+
return e.resetState(), H("Reset to defaults · ⌘Z to undo");
|
|
1962
2809
|
case "copy":
|
|
1963
|
-
return
|
|
2810
|
+
return de(location.href, "Link copied");
|
|
1964
2811
|
case "pick":
|
|
1965
|
-
return
|
|
2812
|
+
return ce ? $e() : Nt();
|
|
1966
2813
|
case "compare":
|
|
1967
|
-
|
|
2814
|
+
ue || Te(ee.getAttribute("aria-pressed") !== "true");
|
|
1968
2815
|
return;
|
|
1969
2816
|
case "undo":
|
|
1970
|
-
return
|
|
2817
|
+
return It();
|
|
1971
2818
|
case "redo":
|
|
1972
|
-
return
|
|
2819
|
+
return Dt();
|
|
1973
2820
|
case "back":
|
|
1974
|
-
return
|
|
1975
|
-
case "edit":
|
|
1976
|
-
return Q("edit");
|
|
1977
|
-
case "page-back":
|
|
1978
|
-
return gn();
|
|
2821
|
+
return G("global");
|
|
1979
2822
|
case "changes":
|
|
1980
|
-
return
|
|
2823
|
+
return G(K === "changes" ? "global" : "changes");
|
|
1981
2824
|
case "apply":
|
|
1982
|
-
return
|
|
2825
|
+
return Gn();
|
|
1983
2826
|
case "apply-menu":
|
|
1984
|
-
return
|
|
2827
|
+
return Ee(pe.hidden);
|
|
1985
2828
|
case "dialog-close":
|
|
1986
|
-
return
|
|
2829
|
+
return Ye();
|
|
1987
2830
|
case "dialog-copy":
|
|
1988
|
-
return
|
|
2831
|
+
return de(_t.value, "Prompt copied · paste it into your agent");
|
|
1989
2832
|
case "setup-copy":
|
|
1990
|
-
return
|
|
2833
|
+
return D && de(D.text2copy, D.copied);
|
|
1991
2834
|
case "setup-dismiss":
|
|
1992
|
-
return
|
|
2835
|
+
return ge(Wt, Me.dataset.status), Ae();
|
|
1993
2836
|
}
|
|
1994
2837
|
}
|
|
1995
|
-
}),
|
|
1996
|
-
if (
|
|
1997
|
-
if (!
|
|
1998
|
-
if (!
|
|
2838
|
+
}), o.addEventListener("keydown", (d) => {
|
|
2839
|
+
if (d.key === "Escape") {
|
|
2840
|
+
if (!Ce.hidden) return Ye();
|
|
2841
|
+
if (!pe.hidden) return Ee(!1);
|
|
1999
2842
|
}
|
|
2000
|
-
!(
|
|
2001
|
-
}), document.addEventListener("keydown", (
|
|
2002
|
-
|
|
2003
|
-
}),
|
|
2004
|
-
}
|
|
2005
|
-
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>';
|
|
2006
|
-
let Lo = 0;
|
|
2007
|
-
function Po({ label: t, options: e, onSelect: n, placeholder: o, onOpen: a }) {
|
|
2008
|
-
const s = `twd-fonts-${++Lo}`, r = document.createElement("div");
|
|
2009
|
-
r.className = "font-select";
|
|
2010
|
-
const d = document.createElement("button");
|
|
2011
|
-
d.type = "button", d.className = "font-trigger", d.setAttribute("aria-haspopup", "listbox"), d.setAttribute("aria-expanded", "false"), d.setAttribute("aria-controls", s), d.innerHTML = `
|
|
2012
|
-
<span class="font-aa" aria-hidden="true">Aa</span>
|
|
2013
|
-
<span class="font-name"></span>
|
|
2014
|
-
<span class="font-fonts"></span>
|
|
2015
|
-
<span class="font-chevron">${Ao}</span>`;
|
|
2016
|
-
const l = document.createElement("div");
|
|
2017
|
-
l.className = "font-menu", l.id = s, l.setAttribute("role", "listbox"), l.setAttribute("aria-label", t), l.hidden = !0;
|
|
2018
|
-
const c = e.map((u) => {
|
|
2019
|
-
const y = document.createElement("div");
|
|
2020
|
-
return y.className = "font-option", y.setAttribute("role", "option"), y.tabIndex = -1, y.dataset.value = u.value, y.innerHTML = `
|
|
2021
|
-
<span class="font-aa" style="font-family:${u.preview}" aria-hidden="true">Aa</span>
|
|
2022
|
-
<span class="font-text"><span class="font-name">${u.name}</span><span class="font-fonts">${u.fonts}</span></span>
|
|
2023
|
-
<span class="font-check">${To}</span>`, y.addEventListener("click", () => g(u.value)), y;
|
|
2024
|
-
});
|
|
2025
|
-
l.append(...c), r.append(d, l);
|
|
2026
|
-
let p = null;
|
|
2027
|
-
function m(u) {
|
|
2028
|
-
const y = e.find((O) => O.value === u), k = y || o || e[0];
|
|
2029
|
-
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)));
|
|
2030
|
-
}
|
|
2031
|
-
function v() {
|
|
2032
|
-
const u = d.getBoundingClientRect(), y = l.offsetHeight, O = window.innerHeight - u.bottom - 8 >= y ? u.bottom + 4 : Math.max(8, u.top - 4 - y);
|
|
2033
|
-
l.style.top = `${O}px`, l.style.left = `${u.left}px`, l.style.width = `${u.width}px`;
|
|
2034
|
-
}
|
|
2035
|
-
function f() {
|
|
2036
|
-
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 }));
|
|
2037
|
-
}
|
|
2038
|
-
function x(u) {
|
|
2039
|
-
l.hidden || (l.hidden = !0, d.setAttribute("aria-expanded", "false"), document.removeEventListener("pointerdown", b, !0), window.removeEventListener("resize", x), u === !0 && d.focus());
|
|
2040
|
-
}
|
|
2041
|
-
function b(u) {
|
|
2042
|
-
u.composedPath().includes(r) || x();
|
|
2043
|
-
}
|
|
2044
|
-
function g(u) {
|
|
2045
|
-
u !== p && (m(u), n(u)), x(!0);
|
|
2046
|
-
}
|
|
2047
|
-
return d.addEventListener("click", () => l.hidden ? f() : x(!0)), d.addEventListener("keydown", (u) => {
|
|
2048
|
-
(u.key === "ArrowDown" || u.key === "ArrowUp") && (u.preventDefault(), f());
|
|
2049
|
-
}), l.addEventListener("keydown", (u) => {
|
|
2050
|
-
const y = c.indexOf(u.target);
|
|
2051
|
-
let k = null;
|
|
2052
|
-
if (u.key === "ArrowDown") k = Math.min(c.length - 1, y + 1);
|
|
2053
|
-
else if (u.key === "ArrowUp") k = Math.max(0, y - 1);
|
|
2054
|
-
else if (u.key === "Home") k = 0;
|
|
2055
|
-
else if (u.key === "End") k = c.length - 1;
|
|
2056
|
-
else if (u.key === "Enter" || u.key === " ") {
|
|
2057
|
-
u.preventDefault(), g(u.target.dataset.value);
|
|
2058
|
-
return;
|
|
2059
|
-
} else if (u.key === "Escape") {
|
|
2060
|
-
u.preventDefault(), u.stopPropagation(), x(!0);
|
|
2061
|
-
return;
|
|
2062
|
-
} else if (u.key === "Tab") {
|
|
2063
|
-
x();
|
|
2064
|
-
return;
|
|
2065
|
-
}
|
|
2066
|
-
k !== null && (u.preventDefault(), c[k].focus());
|
|
2067
|
-
}), 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 };
|
|
2068
2847
|
}
|
|
2069
|
-
const
|
|
2070
|
-
|
|
2071
|
-
{ value: "modern", name: "Modern", fonts: "Geist · Geist", heading: `"Geist", ${I}`, body: `"Geist", ${I}`, load: ["Geist:wght@400;500;600;700"] },
|
|
2072
|
-
{ 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"] },
|
|
2073
|
-
{ 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"] },
|
|
2074
|
-
{ 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"] },
|
|
2075
|
-
{ 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"] },
|
|
2076
|
-
{ value: "elegant", name: "Elegant", fonts: "Instrument Serif · Inter", heading: `"Instrument Serif", ${ve}`, body: `"Inter", ${I}`, load: ["Instrument+Serif", "Inter:wght@400;500;600;700"] },
|
|
2077
|
-
{ value: "friendly", name: "Friendly", fonts: "Manrope · Manrope", heading: `"Manrope", ${I}`, body: `"Manrope", ${I}`, load: ["Manrope:wght@400;500;600;700"] }
|
|
2078
|
-
], Ue = { compact: 1.125, balanced: 1.2, expressive: 1.333 }, Vt = { xs: -2, sm: -1, base: 0, lg: 1, xl: 2, "2xl": 3, "3xl": 4 }, qt = /* @__PURE__ */ new Set();
|
|
2079
|
-
function Ht(t) {
|
|
2080
|
-
const e = t.filter((o) => !qt.has(o));
|
|
2081
|
-
if (!e.length) return;
|
|
2082
|
-
e.forEach((o) => qt.add(o));
|
|
2083
|
-
const n = document.createElement("link");
|
|
2084
|
-
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);
|
|
2085
|
-
}
|
|
2086
|
-
const Fo = {
|
|
2087
|
-
id: "typography",
|
|
2088
|
-
title: "Typography",
|
|
2089
|
-
hint: "Font pairing · base size · scale",
|
|
2090
|
-
uses: ["--twd-font-heading", "--twd-font-body", ...Object.keys(Vt).map((t) => `--twd-text-${t}`)],
|
|
2091
|
-
tokens(t) {
|
|
2092
|
-
const e = t.typography || {}, n = {}, o = Be.find((a) => a.value === e.font);
|
|
2093
|
-
if (o && (Ht(o.load), n["--twd-font-heading"] = o.heading, n["--twd-font-body"] = o.body), e.base || e.scale) {
|
|
2094
|
-
const a = Number(e.base) || 16, s = Ue[e.scale] || Ue.balanced;
|
|
2095
|
-
Object.entries(Vt).forEach(([r, d]) => {
|
|
2096
|
-
n[`--twd-text-${r}`] = `${+(a * s ** d / 16).toFixed(3)}rem`;
|
|
2097
|
-
});
|
|
2098
|
-
}
|
|
2099
|
-
return n;
|
|
2100
|
-
},
|
|
2101
|
-
parts: {
|
|
2102
|
-
fonts(t, e, { label: n = "Fonts" } = {}) {
|
|
2103
|
-
const o = B(n), a = Po({
|
|
2104
|
-
label: "Font pairing",
|
|
2105
|
-
options: Be.map((r) => ({ value: r.value, name: r.name, fonts: r.fonts, preview: r.heading })),
|
|
2106
|
-
placeholder: { name: "Default", fonts: "Prototype fonts", preview: I },
|
|
2107
|
-
// Load every pairing's fonts when the list opens, so the "Aa" previews are real.
|
|
2108
|
-
onOpen: () => Ht([...new Set(Be.flatMap((r) => r.load))]),
|
|
2109
|
-
onSelect: (r) => e.setSection("typography", { font: r })
|
|
2110
|
-
});
|
|
2111
|
-
o.append(a.el);
|
|
2112
|
-
const s = () => a.setValue((e.getState().typography || {}).font || null);
|
|
2113
|
-
s(), e.onChange(s), t.append(o.el);
|
|
2114
|
-
},
|
|
2115
|
-
base(t, e, { label: n = "Base size" } = {}) {
|
|
2116
|
-
t.append(le(e, {
|
|
2117
|
-
section: "typography",
|
|
2118
|
-
key: "base",
|
|
2119
|
-
label: n,
|
|
2120
|
-
fallback: "16",
|
|
2121
|
-
options: ["14", "15", "16"].map((o) => ({ value: o, label: o })),
|
|
2122
|
-
valueText: (o) => `${o}px`
|
|
2123
|
-
}));
|
|
2124
|
-
},
|
|
2125
|
-
scale(t, e, { label: n = "Scale" } = {}) {
|
|
2126
|
-
t.append(le(e, {
|
|
2127
|
-
section: "typography",
|
|
2128
|
-
key: "scale",
|
|
2129
|
-
label: n,
|
|
2130
|
-
fallback: "balanced",
|
|
2131
|
-
options: [{ value: "compact", label: "Compact" }, { value: "balanced", label: "Balanced" }, { value: "expressive", label: "Expressive" }],
|
|
2132
|
-
valueText: (o) => String(Ue[o])
|
|
2133
|
-
}));
|
|
2134
|
-
}
|
|
2135
|
-
},
|
|
2136
|
-
render(t, e) {
|
|
2137
|
-
Object.values(this.parts).forEach((n) => n(t, e));
|
|
2138
|
-
}
|
|
2139
|
-
}, 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 = [
|
|
2140
|
-
[50, "white", 10],
|
|
2141
|
-
[100, "white", 20],
|
|
2142
|
-
[200, "white", 36],
|
|
2143
|
-
[300, "white", 56],
|
|
2144
|
-
[400, "white", 78],
|
|
2145
|
-
[500, null, 100],
|
|
2146
|
-
[600, "black", 86],
|
|
2147
|
-
[700, "black", 72],
|
|
2148
|
-
[800, "black", 58],
|
|
2149
|
-
[900, "black", 45],
|
|
2150
|
-
[950, "black", 30]
|
|
2151
|
-
], Ve = (t) => {
|
|
2152
|
-
let e = String(t).trim().replace(/^#/, "");
|
|
2153
|
-
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;
|
|
2154
|
-
};
|
|
2155
|
-
function No({ label: t, onInput: e }) {
|
|
2156
|
-
const n = document.createElement("div");
|
|
2157
|
-
n.className = "colour";
|
|
2158
|
-
const o = document.createElement("div");
|
|
2159
|
-
o.className = "colour-field";
|
|
2160
|
-
const a = document.createElement("input");
|
|
2161
|
-
a.type = "color", a.className = "colour-picker", a.tabIndex = -1, a.setAttribute("aria-hidden", "true");
|
|
2162
|
-
const s = document.createElement("button");
|
|
2163
|
-
s.type = "button", s.className = "colour-swatch", s.setAttribute("aria-label", `${t}: open colour picker`);
|
|
2164
|
-
const r = document.createElement("input");
|
|
2165
|
-
r.type = "text", r.className = "colour-hex", r.spellcheck = !1, r.maxLength = 7, r.setAttribute("aria-label", `${t} hex value`);
|
|
2166
|
-
const d = document.createElement("button");
|
|
2167
|
-
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);
|
|
2168
|
-
const l = document.createElement("div");
|
|
2169
|
-
l.className = "ramp", l.setAttribute("aria-hidden", "true"), l.innerHTML = Ro.map(([f, x, b]) => {
|
|
2170
|
-
const g = x ? `color-mix(in oklch, var(--brand) ${b}%, ${x})` : "var(--brand)";
|
|
2171
|
-
return `<span title="${f}" style="background:${g}"></span>`;
|
|
2172
|
-
}).join(""), n.append(o, l);
|
|
2173
|
-
let c = "#000000";
|
|
2174
|
-
const p = (f) => {
|
|
2175
|
-
c = f, a.value = f.toLowerCase(), s.style.background = f, n.getRootNode().activeElement !== r && (r.value = f), n.style.setProperty("--brand", f);
|
|
2176
|
-
}, m = (f) => {
|
|
2177
|
-
f !== c && (p(f), e(f));
|
|
2178
|
-
};
|
|
2179
|
-
s.addEventListener("click", () => {
|
|
2180
|
-
if (a.showPicker)
|
|
2181
|
-
try {
|
|
2182
|
-
a.showPicker();
|
|
2183
|
-
return;
|
|
2184
|
-
} catch {
|
|
2185
|
-
}
|
|
2186
|
-
a.click();
|
|
2187
|
-
}), a.addEventListener("input", () => m(a.value.toUpperCase())), r.addEventListener("input", () => {
|
|
2188
|
-
const f = Ve(r.value);
|
|
2189
|
-
f && m(f);
|
|
2190
|
-
});
|
|
2191
|
-
const v = () => {
|
|
2192
|
-
r.value = c;
|
|
2193
|
-
};
|
|
2194
|
-
return r.addEventListener("blur", v), r.addEventListener("keydown", (f) => {
|
|
2195
|
-
f.key === "Enter" && (v(), r.select());
|
|
2196
|
-
}), d.addEventListener("click", async () => {
|
|
2197
|
-
if ("EyeDropper" in window)
|
|
2198
|
-
try {
|
|
2199
|
-
const { sRGBHex: f } = await new window.EyeDropper().open(), x = Ve(f);
|
|
2200
|
-
x && m(x);
|
|
2201
|
-
} catch {
|
|
2202
|
-
}
|
|
2203
|
-
else
|
|
2204
|
-
s.click();
|
|
2205
|
-
}), { el: n, setValue: (f) => p(Ve(f) || c) };
|
|
2206
|
-
}
|
|
2207
|
-
function jo({ label: t, options: e, onSelect: n }) {
|
|
2208
|
-
const o = document.createElement("div");
|
|
2209
|
-
o.className = "swatches", o.setAttribute("role", "radiogroup"), o.setAttribute("aria-label", t);
|
|
2210
|
-
const a = e.map((l) => {
|
|
2211
|
-
const c = document.createElement("button");
|
|
2212
|
-
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;
|
|
2213
|
-
});
|
|
2214
|
-
o.append(...a);
|
|
2215
|
-
let s = null;
|
|
2216
|
-
function r(l) {
|
|
2217
|
-
s = l, a.forEach((p) => p.setAttribute("aria-checked", String(p.dataset.value === l)));
|
|
2218
|
-
const c = Math.max(0, a.findIndex((p) => p.dataset.value === l));
|
|
2219
|
-
a.forEach((p, m) => {
|
|
2220
|
-
p.tabIndex = m === c ? 0 : -1;
|
|
2221
|
-
});
|
|
2222
|
-
}
|
|
2223
|
-
function d(l, c) {
|
|
2224
|
-
l !== s && (r(l), n(l)), c && a.find((p) => p.dataset.value === l).focus();
|
|
2225
|
-
}
|
|
2226
|
-
return o.addEventListener("keydown", (l) => {
|
|
2227
|
-
const c = a.indexOf(l.target.closest(".swatch"));
|
|
2228
|
-
if (c < 0) return;
|
|
2229
|
-
const p = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[l.key];
|
|
2230
|
-
let m;
|
|
2231
|
-
if (p) m = (c + p + a.length) % a.length;
|
|
2232
|
-
else if (l.key === "Home") m = 0;
|
|
2233
|
-
else if (l.key === "End") m = a.length - 1;
|
|
2234
|
-
else return;
|
|
2235
|
-
l.preventDefault(), d(a[m].dataset.value, !0);
|
|
2236
|
-
}), r(null), { el: o, setValue: r };
|
|
2237
|
-
}
|
|
2238
|
-
const _t = "#4F46E5", zo = "#A0743C", Io = [
|
|
2239
|
-
// Figma Swatch row sample colours
|
|
2240
|
-
{ value: "pure", label: "Pure", colour: "#8E8E93" },
|
|
2241
|
-
{ value: "subtle", label: "Subtle", colour: "#8A89A6" },
|
|
2242
|
-
{ value: "warm", label: "Warm", colour: "#9C9389" }
|
|
2243
|
-
], Gt = ["bg", "surface", "surface-raised", "text", "text-muted", "border"], Do = {
|
|
2244
|
-
// proposal
|
|
2245
|
-
bg: "#0F0F11",
|
|
2246
|
-
surface: "#18181B",
|
|
2247
|
-
"surface-raised": "#1F1F23",
|
|
2248
|
-
text: "#F4F4F5",
|
|
2249
|
-
"text-muted": "#A1A1AA",
|
|
2250
|
-
border: "#2E2E33"
|
|
2251
|
-
}, Bo = { bg: 4, surface: 2, "surface-raised": 2, text: 5, "text-muted": 12, border: 10 }, Kt = (t) => /^#[0-9a-f]{6}$/i.test(t || "");
|
|
2252
|
-
function Uo(t) {
|
|
2253
|
-
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);
|
|
2254
|
-
return r >= d ? "#FFFFFF" : "#18181B";
|
|
2255
|
-
}
|
|
2256
|
-
const Vo = {
|
|
2257
|
-
id: "colour",
|
|
2258
|
-
title: "Colour",
|
|
2259
|
-
hint: "Brand colour · neutral tint · mode",
|
|
2260
|
-
uses: ["--twd-primary", "--twd-primary-hover", "--twd-on-primary", "--twd-focus", ...Gt.map((t) => `--twd-${t}`)],
|
|
2261
|
-
tokens(t) {
|
|
2262
|
-
const e = t.colour || {}, n = {}, o = e.mode === "dark";
|
|
2263
|
-
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"] = Uo(e.brand), n["--twd-focus"] = e.brand);
|
|
2264
|
-
const a = e.tint === "subtle" ? Kt(e.brand) ? e.brand : F("--twd-primary") || _t : e.tint === "warm" ? zo : null;
|
|
2265
|
-
return (o || a) && Gt.forEach((s) => {
|
|
2266
|
-
const r = o ? Do[s] : F(`--twd-${s}`);
|
|
2267
|
-
r && (n[`--twd-${s}`] = a ? `color-mix(in oklch, ${r} ${100 - Bo[s]}%, ${a})` : r);
|
|
2268
|
-
}), n;
|
|
2269
|
-
},
|
|
2270
|
-
parts: {
|
|
2271
|
-
brand(t, e, { label: n = "Brand" } = {}) {
|
|
2272
|
-
const o = B(n), a = No({ label: "Brand colour", onInput: (r) => e.setSection("colour", { brand: r }) });
|
|
2273
|
-
o.append(a.el);
|
|
2274
|
-
const s = () => a.setValue((e.getState().colour || {}).brand || (e.readToken || F)("--twd-primary") || _t);
|
|
2275
|
-
s(), e.onChange(s), t.append(o.el);
|
|
2276
|
-
},
|
|
2277
|
-
neutral(t, e, { label: n = "Neutral" } = {}) {
|
|
2278
|
-
const o = B(n), a = jo({ label: "Neutral tint", options: Io, onSelect: (r) => e.setSection("colour", { tint: r }) });
|
|
2279
|
-
o.append(a.el);
|
|
2280
|
-
const s = () => a.setValue((e.getState().colour || {}).tint || "pure");
|
|
2281
|
-
s(), e.onChange(s), t.append(o.el);
|
|
2282
|
-
},
|
|
2283
|
-
mode(t, e, { label: n = "Mode" } = {}) {
|
|
2284
|
-
t.append(le(e, {
|
|
2285
|
-
section: "colour",
|
|
2286
|
-
key: "mode",
|
|
2287
|
-
label: n,
|
|
2288
|
-
fallback: "light",
|
|
2289
|
-
options: [{ value: "light", label: "Light" }, { value: "dark", label: "Dark" }]
|
|
2290
|
-
}));
|
|
2291
|
-
}
|
|
2292
|
-
},
|
|
2293
|
-
render(t, e) {
|
|
2294
|
-
Object.values(this.parts).forEach((n) => n(t, e));
|
|
2295
|
-
}
|
|
2296
|
-
}, qo = { small: { height: 0.8, pad: 0.75 }, large: { height: 1.2, pad: 1.25 } }, Ho = hn({
|
|
2297
|
-
id: "buttons",
|
|
2298
|
-
title: "Buttons",
|
|
2299
|
-
hint: "Corners · size",
|
|
2300
|
-
token: "--twd-radius-control",
|
|
2301
|
-
uses: ["--twd-control-height", "--twd-control-pad-x"],
|
|
2302
|
-
max: 24,
|
|
2303
|
-
// slider range in px; "Pill" sits at the end and reads "full"
|
|
2304
|
-
presets: [
|
|
2305
|
-
{ value: "square", label: "Square", radius: 0 },
|
|
2306
|
-
// proposal
|
|
2307
|
-
{ value: "soft", label: "Soft", radius: 4 },
|
|
2308
|
-
// proposal
|
|
2309
|
-
{ value: "rounded", label: "Rounded", radius: 8 },
|
|
2310
|
-
// Figma: "8px"
|
|
2311
|
-
{ value: "pill", label: "Pill", radius: 999 }
|
|
2312
|
-
],
|
|
2313
|
-
extraTokens(t) {
|
|
2314
|
-
const e = qo[t.size];
|
|
2315
|
-
if (!e) return {};
|
|
2316
|
-
const n = {}, o = Se(F("--twd-control-height"), e.height), a = Se(F("--twd-control-pad-x"), e.pad);
|
|
2317
|
-
return o && (n["--twd-control-height"] = o), a && (n["--twd-control-pad-x"] = a), n;
|
|
2318
|
-
},
|
|
2319
|
-
extra: [
|
|
2320
|
-
{
|
|
2321
|
-
key: "size",
|
|
2322
|
-
label: "Size",
|
|
2323
|
-
fallback: "medium",
|
|
2324
|
-
options: [{ value: "small", label: "Small" }, { value: "medium", label: "Medium" }, { value: "large", label: "Large" }]
|
|
2325
|
-
}
|
|
2326
|
-
]
|
|
2327
|
-
}), _o = hn({
|
|
2328
|
-
id: "cards",
|
|
2329
|
-
title: "Cards",
|
|
2330
|
-
hint: "Corners · border",
|
|
2331
|
-
token: "--twd-radius-container",
|
|
2332
|
-
uses: ["--twd-border-width-container"],
|
|
2333
|
-
max: 32,
|
|
2334
|
-
presets: [
|
|
2335
|
-
{ value: "square", label: "Square", radius: 0 },
|
|
2336
|
-
// proposal
|
|
2337
|
-
{ value: "soft", label: "Soft", radius: 6 },
|
|
2338
|
-
// proposal
|
|
2339
|
-
{ value: "rounded", label: "Rounded", radius: 12 },
|
|
2340
|
-
// = tokens.css default
|
|
2341
|
-
{ value: "large", label: "Large", radius: 20 }
|
|
2342
|
-
// proposal (cards don't go fully pill)
|
|
2343
|
-
],
|
|
2344
|
-
extraTokens(t) {
|
|
2345
|
-
if (t.border === "none") return { "--twd-border-width-container": "0px" };
|
|
2346
|
-
if (t.border === "thick") {
|
|
2347
|
-
const e = Se(F("--twd-border-width-container"), 2);
|
|
2348
|
-
return e ? { "--twd-border-width-container": e } : {};
|
|
2349
|
-
}
|
|
2350
|
-
return {};
|
|
2351
|
-
},
|
|
2352
|
-
extra: [
|
|
2353
|
-
{
|
|
2354
|
-
key: "border",
|
|
2355
|
-
label: "Border",
|
|
2356
|
-
fallback: "thin",
|
|
2357
|
-
options: [{ value: "none", label: "None" }, { value: "thin", label: "Thin" }, { value: "thick", label: "Thick" }]
|
|
2358
|
-
}
|
|
2359
|
-
]
|
|
2360
|
-
}), Wt = [Fo, Vo, 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)}';
|
|
2361
|
-
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() {
|
|
2362
2850
|
if (getComputedStyle(document.documentElement).getPropertyValue("--twd-radius-control").trim() || document.getElementById("twiddle-tokens")) return;
|
|
2363
2851
|
const e = document.createElement("style");
|
|
2364
|
-
e.id = "twiddle-tokens", e.textContent =
|
|
2852
|
+
e.id = "twiddle-tokens", e.textContent = bo, document.head.prepend(e);
|
|
2365
2853
|
}
|
|
2366
2854
|
if (!window.Twiddle) {
|
|
2367
|
-
|
|
2855
|
+
xo(), wn.forEach((n) => sa(n.tokens)), $n(gt);
|
|
2368
2856
|
let t;
|
|
2369
2857
|
const e = () => {
|
|
2370
|
-
|
|
2858
|
+
gt(ut()), t = mo({ sections: wn, store: ra, view: { suspend: ca, resume: pa } });
|
|
2371
2859
|
};
|
|
2372
2860
|
document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", e) : e(), window.Twiddle = {
|
|
2373
2861
|
open: () => t && t.open(),
|
|
2374
2862
|
close: () => t && t.close(),
|
|
2375
2863
|
toggle: () => t && t.toggle(),
|
|
2376
|
-
reset: () =>
|
|
2377
|
-
undo: () =>
|
|
2378
|
-
redo: () =>
|
|
2864
|
+
reset: () => yn(),
|
|
2865
|
+
undo: () => kn(),
|
|
2866
|
+
redo: () => Sn(),
|
|
2379
2867
|
pick: () => t && t.pick(),
|
|
2380
2868
|
prompt: () => t ? t.prompt() : "",
|
|
2381
|
-
getState:
|
|
2869
|
+
getState: ut
|
|
2382
2870
|
};
|
|
2383
2871
|
}
|
|
2384
|
-
const
|
|
2872
|
+
const wo = window.Twiddle;
|
|
2385
2873
|
export {
|
|
2386
|
-
|
|
2874
|
+
wo as Twiddle
|
|
2387
2875
|
};
|