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