twiddle-panel 0.0.0-stage → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +61 -2
- package/dist/twiddle-tokens.css +71 -0
- package/dist/twiddle.d.ts +34 -0
- package/dist/twiddle.js +764 -0
- package/dist/twiddle.mjs +2384 -0
- package/package.json +50 -4
package/dist/twiddle.mjs
ADDED
|
@@ -0,0 +1,2384 @@
|
|
|
1
|
+
const qe = /* @__PURE__ */ new Set();
|
|
2
|
+
let C = En(), _ = [], ie = [], ke = null, Mt = 0;
|
|
3
|
+
function En() {
|
|
4
|
+
const t = new URLSearchParams(location.search).get("twd");
|
|
5
|
+
if (!t) return {};
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(t);
|
|
8
|
+
} catch {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function $n() {
|
|
13
|
+
const t = new URL(location.href);
|
|
14
|
+
Object.keys(C).length ? t.searchParams.set("twd", JSON.stringify(C)) : t.searchParams.delete("twd"), history.replaceState(history.state, "", t);
|
|
15
|
+
}
|
|
16
|
+
function Qe() {
|
|
17
|
+
$n(), qe.forEach((t) => t(C));
|
|
18
|
+
}
|
|
19
|
+
function J(t, e) {
|
|
20
|
+
if (JSON.stringify(t) === JSON.stringify(C)) return;
|
|
21
|
+
const n = Date.now();
|
|
22
|
+
e && e === ke && n - Mt < 400 || (_.push(C), _.length > 100 && _.shift()), ie = [], ke = e, Mt = n, C = t, Qe();
|
|
23
|
+
}
|
|
24
|
+
function Ee(t, e) {
|
|
25
|
+
const n = { ...t, ...e };
|
|
26
|
+
return Object.keys(n).forEach((o) => {
|
|
27
|
+
n[o] == null && delete n[o];
|
|
28
|
+
}), Object.keys(n).length ? n : null;
|
|
29
|
+
}
|
|
30
|
+
function D(t, e, n) {
|
|
31
|
+
const o = { ...t };
|
|
32
|
+
return n == null ? delete o[e] : o[e] = n, o;
|
|
33
|
+
}
|
|
34
|
+
function He() {
|
|
35
|
+
return JSON.parse(JSON.stringify(C));
|
|
36
|
+
}
|
|
37
|
+
function Cn(t, e) {
|
|
38
|
+
J(D(C, t, Ee(C[t], e)), `${t}:${Object.keys(e)}`);
|
|
39
|
+
}
|
|
40
|
+
function Mn(t, e = `multi:${Object.keys(t)}`) {
|
|
41
|
+
const n = Object.entries(t).reduce((o, [a, s]) => D(o, a, Ee(o[a], s)), C);
|
|
42
|
+
J(n, e);
|
|
43
|
+
}
|
|
44
|
+
function Yt(t, e, n) {
|
|
45
|
+
const o = D(C.scopes?.[t], e, Ee(C.scopes?.[t]?.[e], n)), a = D(C.scopes, t, Object.keys(o).length ? o : null);
|
|
46
|
+
J(D(C, "scopes", Object.keys(a).length ? a : null), `${t}|${e}:${Object.keys(n)}`);
|
|
47
|
+
}
|
|
48
|
+
function An(t, e) {
|
|
49
|
+
if (e) return Yt(t, e, Object.fromEntries(
|
|
50
|
+
Object.keys(C.scopes?.[t]?.[e] || {}).map((o) => [o, null])
|
|
51
|
+
));
|
|
52
|
+
const n = D(C.scopes, t, null);
|
|
53
|
+
J(D(C, "scopes", Object.keys(n).length ? n : null));
|
|
54
|
+
}
|
|
55
|
+
function Tn(t, e) {
|
|
56
|
+
const n = C.scopes?.[t];
|
|
57
|
+
if (!n || t === e) return;
|
|
58
|
+
let o = { ...C.scopes[e] };
|
|
59
|
+
Object.entries(n).forEach(([s, r]) => {
|
|
60
|
+
o = D(o, s, Ee(o[s], r));
|
|
61
|
+
});
|
|
62
|
+
const a = D(D(C.scopes, t, null), e, o);
|
|
63
|
+
J(D(C, "scopes", a));
|
|
64
|
+
}
|
|
65
|
+
function Xt() {
|
|
66
|
+
J({});
|
|
67
|
+
}
|
|
68
|
+
function Jt() {
|
|
69
|
+
_.length && (ie.push(C), C = _.pop(), ke = null, Qe());
|
|
70
|
+
}
|
|
71
|
+
function Zt() {
|
|
72
|
+
ie.length && (_.push(C), C = ie.pop(), ke = null, Qe());
|
|
73
|
+
}
|
|
74
|
+
const Ln = () => _.length > 0, Pn = () => ie.length > 0;
|
|
75
|
+
function Qt(t) {
|
|
76
|
+
return qe.add(t), () => qe.delete(t);
|
|
77
|
+
}
|
|
78
|
+
const Fn = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
79
|
+
__proto__: null,
|
|
80
|
+
canRedo: Pn,
|
|
81
|
+
canUndo: Ln,
|
|
82
|
+
getState: He,
|
|
83
|
+
moveScope: Tn,
|
|
84
|
+
onChange: Qt,
|
|
85
|
+
redo: Zt,
|
|
86
|
+
removeScope: An,
|
|
87
|
+
resetState: Xt,
|
|
88
|
+
setScope: Yt,
|
|
89
|
+
setSection: Cn,
|
|
90
|
+
setSections: Mn,
|
|
91
|
+
undo: Jt
|
|
92
|
+
}, Symbol.toStringTag, { value: "Module" })), en = [];
|
|
93
|
+
let _e = [], et = {}, R = null, Y = !1, tn = {};
|
|
94
|
+
function On(t) {
|
|
95
|
+
typeof t == "function" && en.push(t);
|
|
96
|
+
}
|
|
97
|
+
const At = (t) => Object.assign({}, ...en.map((e) => e(t) || {})), Ge = (t) => /^(\.[\w-]+|\[data-twd="[^"]+"\])$/.test(t), Rn = (t) => {
|
|
98
|
+
try {
|
|
99
|
+
return document.querySelector(t), !0;
|
|
100
|
+
} catch {
|
|
101
|
+
return !1;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
function Nn() {
|
|
105
|
+
return (!R || !R.isConnected) && (R = document.createElement("style"), R.id = "twiddle-scoped", document.head.appendChild(R)), R;
|
|
106
|
+
}
|
|
107
|
+
function nn() {
|
|
108
|
+
const t = document.documentElement;
|
|
109
|
+
_e.forEach((e) => t.style.removeProperty(e)), _e = [], et = {};
|
|
110
|
+
}
|
|
111
|
+
function Ke(t) {
|
|
112
|
+
if (tn = t, Y) return;
|
|
113
|
+
const e = document.documentElement;
|
|
114
|
+
nn();
|
|
115
|
+
const n = At(t), o = Object.entries(t.scopes || {}).filter(([s]) => Rn(s)).sort(([s], [r]) => Ge(r) - Ge(s)).map(([s, r]) => [s, At(r)]);
|
|
116
|
+
Object.entries(n).forEach(([s, r]) => e.style.setProperty(s, r)), _e = Object.keys(n), et = n;
|
|
117
|
+
const a = o.filter(([, s]) => Object.keys(s).length).map(([s, r]) => `${s} {
|
|
118
|
+
${Object.entries(r).map(([d, l]) => ` ${d}: ${l} !important;`).join(`
|
|
119
|
+
`)}
|
|
120
|
+
}`).join(`
|
|
121
|
+
`);
|
|
122
|
+
(a || R) && (Nn().textContent = a);
|
|
123
|
+
}
|
|
124
|
+
function on(t) {
|
|
125
|
+
const e = document.documentElement, n = Y, o = { ...et };
|
|
126
|
+
Object.keys(o).forEach((a) => e.style.removeProperty(a)), R && (R.disabled = !0);
|
|
127
|
+
try {
|
|
128
|
+
return t();
|
|
129
|
+
} finally {
|
|
130
|
+
n || (Object.entries(o).forEach(([a, s]) => e.style.setProperty(a, s)), R && (R.disabled = !1));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function jn(t, e = document.documentElement) {
|
|
134
|
+
return on(() => {
|
|
135
|
+
const n = getComputedStyle(e);
|
|
136
|
+
return Object.fromEntries(t.map((o) => [o, n.getPropertyValue(o).trim()]));
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function zn() {
|
|
140
|
+
Y || (nn(), R && (R.disabled = !0), Y = !0);
|
|
141
|
+
}
|
|
142
|
+
function In() {
|
|
143
|
+
Y && (Y = !1, R && (R.disabled = !1), Ke(tn));
|
|
144
|
+
}
|
|
145
|
+
const Dn = `
|
|
146
|
+
:host { all: initial; } /* block fonts/colours inherited from the page */
|
|
147
|
+
* { box-sizing: border-box; }
|
|
148
|
+
|
|
149
|
+
.wrap {
|
|
150
|
+
--p-bg: #ffffff;
|
|
151
|
+
--p-fg: #18181b;
|
|
152
|
+
--p-muted: #71717a;
|
|
153
|
+
--p-line: #e4e4e7;
|
|
154
|
+
--p-hover: #f4f4f5;
|
|
155
|
+
--p-track: #f4f4f5;
|
|
156
|
+
--p-selected: #ffffff;
|
|
157
|
+
--p-accent: #18181b;
|
|
158
|
+
--p-on-accent: #ffffff;
|
|
159
|
+
--p-shadow: 0 12px 40px rgb(0 0 0 / 0.14), 0 2px 6px rgb(0 0 0 / 0.06);
|
|
160
|
+
position: fixed;
|
|
161
|
+
right: 16px;
|
|
162
|
+
bottom: 16px;
|
|
163
|
+
z-index: 2147483647;
|
|
164
|
+
font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
165
|
+
color: var(--p-fg);
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
align-items: flex-end;
|
|
169
|
+
gap: 10px;
|
|
170
|
+
}
|
|
171
|
+
@media (prefers-color-scheme: dark) {
|
|
172
|
+
.wrap {
|
|
173
|
+
--p-bg: #1c1c1f;
|
|
174
|
+
--p-fg: #f4f4f5;
|
|
175
|
+
--p-muted: #a1a1aa;
|
|
176
|
+
--p-line: #2e2e33;
|
|
177
|
+
--p-hover: #27272a;
|
|
178
|
+
--p-track: #27272a;
|
|
179
|
+
--p-selected: #3f3f46;
|
|
180
|
+
--p-accent: #f4f4f5;
|
|
181
|
+
--p-on-accent: #18181b;
|
|
182
|
+
--p-shadow: 0 12px 40px rgb(0 0 0 / 0.5), 0 2px 6px rgb(0 0 0 / 0.3);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Launcher */
|
|
187
|
+
.launcher {
|
|
188
|
+
width: 44px; height: 44px;
|
|
189
|
+
border-radius: 50%;
|
|
190
|
+
border: 1px solid var(--p-line);
|
|
191
|
+
background: var(--p-bg);
|
|
192
|
+
color: var(--p-fg);
|
|
193
|
+
box-shadow: var(--p-shadow);
|
|
194
|
+
display: grid; place-items: center;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
padding: 0;
|
|
197
|
+
}
|
|
198
|
+
.launcher svg { transition: transform .15s ease; }
|
|
199
|
+
.launcher:hover svg { transform: rotate(-20deg); }
|
|
200
|
+
.launcher[aria-expanded="true"] { background: var(--p-accent); color: var(--p-on-accent); border-color: var(--p-accent); }
|
|
201
|
+
.launcher:focus-visible, button:focus-visible, summary:focus-visible {
|
|
202
|
+
outline: 2px solid var(--p-accent); outline-offset: 2px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Panel */
|
|
206
|
+
.panel {
|
|
207
|
+
width: 300px;
|
|
208
|
+
max-height: min(640px, calc(100vh - 90px));
|
|
209
|
+
background: var(--p-bg);
|
|
210
|
+
border: 1px solid var(--p-line);
|
|
211
|
+
border-radius: 14px;
|
|
212
|
+
box-shadow: var(--p-shadow);
|
|
213
|
+
display: flex; flex-direction: column;
|
|
214
|
+
overflow: hidden;
|
|
215
|
+
}
|
|
216
|
+
.panel[hidden] { display: none; }
|
|
217
|
+
|
|
218
|
+
.head {
|
|
219
|
+
display: flex; align-items: center; gap: 4px;
|
|
220
|
+
padding: 10px 10px 10px 14px;
|
|
221
|
+
border-bottom: 1px solid var(--p-line);
|
|
222
|
+
}
|
|
223
|
+
.brand { font-weight: 600; font-size: 14px; letter-spacing: -0.01em; flex: 1; }
|
|
224
|
+
.icon-btn {
|
|
225
|
+
width: 28px; height: 28px; padding: 0;
|
|
226
|
+
border: 0; border-radius: 6px;
|
|
227
|
+
background: transparent; color: var(--p-muted);
|
|
228
|
+
display: grid; place-items: center;
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
}
|
|
231
|
+
.icon-btn:hover { background: var(--p-hover); color: var(--p-fg); }
|
|
232
|
+
|
|
233
|
+
.body { overflow-y: auto; padding: 4px 0; }
|
|
234
|
+
|
|
235
|
+
details { border-bottom: 1px solid var(--p-line); }
|
|
236
|
+
details:last-child { border-bottom: 0; }
|
|
237
|
+
summary {
|
|
238
|
+
list-style: none;
|
|
239
|
+
display: flex; align-items: center; gap: 8px;
|
|
240
|
+
padding: 11px 14px;
|
|
241
|
+
cursor: pointer;
|
|
242
|
+
font-weight: 500;
|
|
243
|
+
user-select: none;
|
|
244
|
+
}
|
|
245
|
+
summary::-webkit-details-marker { display: none; }
|
|
246
|
+
summary:hover { background: var(--p-hover); }
|
|
247
|
+
summary .chev { margin-left: auto; color: var(--p-muted); display: grid; transition: transform .15s ease; }
|
|
248
|
+
details[open] summary .chev { transform: rotate(90deg); }
|
|
249
|
+
.section-body { padding: 2px 14px 16px; color: var(--p-muted); display: grid; gap: 14px; }
|
|
250
|
+
.placeholder {
|
|
251
|
+
border: 1px dashed var(--p-line);
|
|
252
|
+
border-radius: 8px;
|
|
253
|
+
padding: 10px;
|
|
254
|
+
font-size: 12px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Field = Figma "Field header" + control (gap 4) */
|
|
258
|
+
.field { display: grid; gap: 4px; }
|
|
259
|
+
.field-head {
|
|
260
|
+
display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
|
|
261
|
+
min-height: 18px;
|
|
262
|
+
}
|
|
263
|
+
.field-label { font-size: 12px; font-weight: 500; color: var(--p-muted); }
|
|
264
|
+
.field-value { font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-muted); }
|
|
265
|
+
|
|
266
|
+
/* Segmented control (Figma 2:36) built from Segment (Figma 2:14) */
|
|
267
|
+
.seg {
|
|
268
|
+
display: flex; gap: 2px; padding: 2px;
|
|
269
|
+
border-radius: 6px;
|
|
270
|
+
background: var(--p-track);
|
|
271
|
+
}
|
|
272
|
+
.seg [role="radio"] {
|
|
273
|
+
flex: 1 1 0; min-width: 0;
|
|
274
|
+
height: 26px; padding: 0 10px;
|
|
275
|
+
border: 0; border-radius: 6px;
|
|
276
|
+
background: transparent; color: var(--p-muted);
|
|
277
|
+
font: inherit; font-size: 12px; font-weight: 500; line-height: 1;
|
|
278
|
+
white-space: nowrap;
|
|
279
|
+
/* Like Figma: a long label (e.g. "Rounded" in a 4-option control) may
|
|
280
|
+
spill into the 10px padding, staying centred, rather than truncating. */
|
|
281
|
+
display: flex; align-items: center; justify-content: center;
|
|
282
|
+
cursor: pointer;
|
|
283
|
+
}
|
|
284
|
+
.seg [role="radio"]:hover { background: var(--p-hover); color: var(--p-fg); }
|
|
285
|
+
.seg [aria-checked="true"] {
|
|
286
|
+
background: var(--p-bg); color: var(--p-fg);
|
|
287
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / 0.08);
|
|
288
|
+
}
|
|
289
|
+
.seg [aria-checked="true"]:hover { background: var(--p-hover); }
|
|
290
|
+
.seg [role="radio"]:focus-visible { outline: 2px solid var(--p-accent); outline-offset: 0; position: relative; }
|
|
291
|
+
.seg [aria-checked="true"]:focus-visible { background: var(--p-selected); } /* as in Figma Selected=True, State=Focus */
|
|
292
|
+
|
|
293
|
+
/* Slider (Figma 4:135): 4px rail, 14px thumb, value label 36px */
|
|
294
|
+
.slider { display: flex; align-items: center; gap: 8px; height: 20px; }
|
|
295
|
+
.slider input {
|
|
296
|
+
-webkit-appearance: none; appearance: none;
|
|
297
|
+
flex: 1; min-width: 0; height: 20px; margin: 0; background: transparent; cursor: pointer;
|
|
298
|
+
--fill: 50%;
|
|
299
|
+
}
|
|
300
|
+
.slider input::-webkit-slider-runnable-track {
|
|
301
|
+
height: 4px; border-radius: 2px;
|
|
302
|
+
background: linear-gradient(to right, var(--p-accent) var(--fill), var(--p-line) var(--fill));
|
|
303
|
+
}
|
|
304
|
+
.slider input::-moz-range-track { height: 4px; border-radius: 2px; background: var(--p-line); }
|
|
305
|
+
.slider input::-moz-range-progress { height: 4px; border-radius: 2px; background: var(--p-accent); }
|
|
306
|
+
.slider input:hover::-webkit-slider-runnable-track {
|
|
307
|
+
background: linear-gradient(to right, var(--p-accent) var(--fill), var(--p-muted) var(--fill));
|
|
308
|
+
}
|
|
309
|
+
.slider input:hover::-moz-range-track { background: var(--p-muted); }
|
|
310
|
+
.slider input::-webkit-slider-thumb {
|
|
311
|
+
-webkit-appearance: none; appearance: none;
|
|
312
|
+
width: 14px; height: 14px; margin-top: -5px; border-radius: 50%;
|
|
313
|
+
background: var(--p-bg); border: 1.5px solid var(--p-line);
|
|
314
|
+
box-shadow: 0 1px 3px rgb(0 0 0 / 0.15);
|
|
315
|
+
transition: width .1s ease, height .1s ease, margin .1s ease;
|
|
316
|
+
}
|
|
317
|
+
.slider input::-moz-range-thumb {
|
|
318
|
+
width: 14px; height: 14px; border-radius: 50%; box-sizing: border-box;
|
|
319
|
+
background: var(--p-bg); border: 1.5px solid var(--p-line); box-shadow: 0 1px 3px rgb(0 0 0 / 0.15);
|
|
320
|
+
}
|
|
321
|
+
.slider input:active::-webkit-slider-thumb { width: 18px; height: 18px; margin-top: -7px; border-color: var(--p-accent); }
|
|
322
|
+
.slider input:active::-moz-range-thumb { width: 18px; height: 18px; border-color: var(--p-accent); }
|
|
323
|
+
.slider input:focus { outline: none; }
|
|
324
|
+
.slider input:focus-visible::-webkit-slider-thumb { border: 2px solid var(--p-accent); }
|
|
325
|
+
.slider input:focus-visible::-moz-range-thumb { border: 2px solid var(--p-accent); }
|
|
326
|
+
.slider-value {
|
|
327
|
+
width: 36px; flex: none; text-align: right;
|
|
328
|
+
font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-muted);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* Font select (Figma 3:78) + menu (3:79) + option (3:53) */
|
|
332
|
+
.font-trigger {
|
|
333
|
+
width: 100%; height: 36px; padding: 0 10px;
|
|
334
|
+
display: flex; align-items: center; gap: 8px;
|
|
335
|
+
border: 1px solid var(--p-line); border-radius: 6px;
|
|
336
|
+
background: var(--p-bg); color: var(--p-fg);
|
|
337
|
+
font: inherit; text-align: left; cursor: pointer;
|
|
338
|
+
}
|
|
339
|
+
.font-trigger:hover { background: var(--p-hover); }
|
|
340
|
+
.font-trigger[aria-expanded="true"] { border-color: var(--p-accent); }
|
|
341
|
+
.font-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--p-accent); }
|
|
342
|
+
.font-trigger .font-aa { font-size: 15px; font-weight: 600; }
|
|
343
|
+
.font-trigger .font-name { flex: 1; min-width: 0; font-size: 13px; font-weight: 500; }
|
|
344
|
+
.font-trigger .font-fonts { font-size: 12px; color: var(--p-muted); white-space: nowrap; }
|
|
345
|
+
.font-chevron { display: grid; color: var(--p-muted); transition: transform .15s ease; }
|
|
346
|
+
.font-trigger[aria-expanded="true"] .font-chevron { transform: rotate(180deg); }
|
|
347
|
+
|
|
348
|
+
.font-menu {
|
|
349
|
+
position: fixed; z-index: 1; max-height: calc(100vh - 16px); overflow-y: auto;
|
|
350
|
+
display: grid; gap: 2px; padding: 4px;
|
|
351
|
+
background: var(--p-bg); border: 1px solid var(--p-line); border-radius: 6px;
|
|
352
|
+
box-shadow: var(--p-shadow);
|
|
353
|
+
}
|
|
354
|
+
.font-menu[hidden] { display: none; }
|
|
355
|
+
.font-option {
|
|
356
|
+
display: flex; align-items: center; gap: 10px;
|
|
357
|
+
min-height: 40px; padding: 4px 8px; border-radius: 6px; cursor: pointer; color: var(--p-fg);
|
|
358
|
+
}
|
|
359
|
+
.font-option:hover, .font-option[aria-selected="true"] { background: var(--p-hover); }
|
|
360
|
+
.font-option:focus { outline: none; }
|
|
361
|
+
.font-option:focus-visible { box-shadow: inset 0 0 0 2px var(--p-accent); }
|
|
362
|
+
.font-option .font-aa { width: 34px; flex: none; text-align: center; font-size: 22px; font-weight: 600; line-height: 30px; }
|
|
363
|
+
.font-text { flex: 1; min-width: 0; display: grid; line-height: 1.25; } /* keeps each option at Figma's 40px */
|
|
364
|
+
.font-option .font-name { font-size: 13px; font-weight: 500; }
|
|
365
|
+
.font-option .font-fonts { font-size: 12px; color: var(--p-muted); }
|
|
366
|
+
.font-check { display: grid; visibility: hidden; }
|
|
367
|
+
.font-option[aria-selected="true"] .font-check { visibility: visible; }
|
|
368
|
+
|
|
369
|
+
/* Colour field (Figma 4:72) + ramp (4:73) */
|
|
370
|
+
.colour { display: grid; gap: 6px; }
|
|
371
|
+
.colour-field {
|
|
372
|
+
position: relative;
|
|
373
|
+
height: 36px; padding: 0 10px 0 6px;
|
|
374
|
+
display: flex; align-items: center; gap: 8px;
|
|
375
|
+
border: 1px solid var(--p-line); border-radius: 6px; background: var(--p-bg);
|
|
376
|
+
}
|
|
377
|
+
.colour-field:hover { background: var(--p-hover); }
|
|
378
|
+
.colour-field:focus-within { border-color: var(--p-accent); box-shadow: inset 0 0 0 1px var(--p-accent); }
|
|
379
|
+
.colour-picker { position: absolute; left: 6px; bottom: 0; width: 24px; height: 1px; opacity: 0; pointer-events: none; border: 0; padding: 0; }
|
|
380
|
+
.colour-swatch {
|
|
381
|
+
width: 24px; height: 24px; flex: none; padding: 0; cursor: pointer;
|
|
382
|
+
border: 0; border-radius: 6px; box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.08);
|
|
383
|
+
}
|
|
384
|
+
.colour-hex {
|
|
385
|
+
flex: 1; min-width: 0; height: 100%; padding: 0;
|
|
386
|
+
border: 0; background: transparent; color: var(--p-fg);
|
|
387
|
+
font: inherit; font-size: 13px; font-weight: 500;
|
|
388
|
+
}
|
|
389
|
+
.colour-hex:focus { outline: none; }
|
|
390
|
+
.colour-pipette {
|
|
391
|
+
width: 24px; height: 24px; margin-right: -4px; padding: 0; flex: none;
|
|
392
|
+
display: grid; place-items: center; cursor: pointer;
|
|
393
|
+
border: 0; border-radius: 6px; background: transparent; color: var(--p-muted);
|
|
394
|
+
}
|
|
395
|
+
.colour-pipette:hover { color: var(--p-fg); }
|
|
396
|
+
.ramp { display: flex; gap: 2px; height: 14px; border-radius: 6px; overflow: hidden; }
|
|
397
|
+
.ramp span { flex: 1; }
|
|
398
|
+
|
|
399
|
+
/* Swatch row (Figma 4:98) + swatch (4:97) */
|
|
400
|
+
.swatches { display: flex; gap: 14px; }
|
|
401
|
+
.swatch {
|
|
402
|
+
display: grid; justify-items: center; gap: 4px;
|
|
403
|
+
padding: 0; border: 0; background: none; cursor: pointer; font: inherit;
|
|
404
|
+
}
|
|
405
|
+
.swatch:focus { outline: none; }
|
|
406
|
+
.swatch-ring {
|
|
407
|
+
width: 32px; height: 32px; border-radius: 50%;
|
|
408
|
+
display: grid; place-items: center;
|
|
409
|
+
box-shadow: inset 0 0 0 2px transparent;
|
|
410
|
+
}
|
|
411
|
+
.swatch:hover .swatch-ring { box-shadow: inset 0 0 0 2px var(--p-line); }
|
|
412
|
+
.swatch[aria-checked="true"] .swatch-ring { box-shadow: inset 0 0 0 2px var(--p-accent); }
|
|
413
|
+
.swatch:focus-visible .swatch-ring { box-shadow: none; outline: 2px dashed var(--p-accent); outline-offset: -2px; }
|
|
414
|
+
.swatch-dot { width: 22px; height: 22px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.08); }
|
|
415
|
+
.swatch-label { font-size: 12px; color: var(--p-muted); }
|
|
416
|
+
.swatch[aria-checked="true"] .swatch-label { color: var(--p-fg); }
|
|
417
|
+
|
|
418
|
+
.foot {
|
|
419
|
+
border-top: 1px solid var(--p-line);
|
|
420
|
+
padding: 8px 10px;
|
|
421
|
+
display: flex; align-items: center; gap: 2px;
|
|
422
|
+
color: var(--p-muted); font-size: 12px;
|
|
423
|
+
}
|
|
424
|
+
.spacer { flex: 1; }
|
|
425
|
+
.icon-btn:disabled { opacity: .35; cursor: default; background: transparent; }
|
|
426
|
+
.icon-btn[aria-pressed="true"] { background: var(--p-accent); color: var(--p-on-accent); }
|
|
427
|
+
kbd {
|
|
428
|
+
font: 11px ui-monospace, "SF Mono", Menlo, monospace;
|
|
429
|
+
border: 1px solid var(--p-line); border-radius: 4px;
|
|
430
|
+
padding: 1px 4px;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.toast {
|
|
434
|
+
position: absolute; bottom: 64px; right: 0;
|
|
435
|
+
background: var(--p-accent); color: var(--p-on-accent);
|
|
436
|
+
padding: 6px 10px; border-radius: 8px; font-size: 12px;
|
|
437
|
+
white-space: nowrap;
|
|
438
|
+
opacity: 0; transition: opacity .15s ease; pointer-events: none;
|
|
439
|
+
}
|
|
440
|
+
.toast.show { opacity: 1; }
|
|
441
|
+
|
|
442
|
+
/* ---------- Proposals below: not yet designed in Figma ---------- */
|
|
443
|
+
|
|
444
|
+
/* Text button + count badge (footer "Changes") */
|
|
445
|
+
.text-btn {
|
|
446
|
+
height: 28px; padding: 0 8px; margin-left: 4px;
|
|
447
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
448
|
+
border: 0; border-radius: 6px; background: transparent;
|
|
449
|
+
color: var(--p-muted); font: inherit; font-size: 12px; font-weight: 500; cursor: pointer;
|
|
450
|
+
}
|
|
451
|
+
.text-btn:hover { background: var(--p-hover); color: var(--p-fg); }
|
|
452
|
+
.text-btn[aria-pressed="true"] { background: var(--p-hover); color: var(--p-fg); }
|
|
453
|
+
.badge {
|
|
454
|
+
min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
|
|
455
|
+
display: inline-grid; place-items: center;
|
|
456
|
+
background: var(--p-accent); color: var(--p-on-accent);
|
|
457
|
+
font-size: 11px; font-weight: 600;
|
|
458
|
+
}
|
|
459
|
+
.badge[hidden] { display: none; }
|
|
460
|
+
|
|
461
|
+
/* Copy prompt split button + menu */
|
|
462
|
+
.apply { position: relative; display: flex; }
|
|
463
|
+
.apply-btn {
|
|
464
|
+
height: 28px; padding: 0 12px;
|
|
465
|
+
border: 0; border-radius: 6px;
|
|
466
|
+
background: var(--p-accent); color: var(--p-on-accent);
|
|
467
|
+
font: inherit; font-size: 12px; font-weight: 400; cursor: pointer;
|
|
468
|
+
}
|
|
469
|
+
.apply { flex: none; }
|
|
470
|
+
.apply .apply-btn { border-radius: 6px 0 0 6px; display: inline-flex; align-items: center; gap: 6px; padding: 0 10px; white-space: nowrap; }
|
|
471
|
+
.apply-menu-btn {
|
|
472
|
+
width: 22px; height: 28px; padding: 0;
|
|
473
|
+
display: grid; place-items: center;
|
|
474
|
+
border: 0; border-left: 1px solid color-mix(in srgb, var(--p-on-accent) 25%, transparent);
|
|
475
|
+
border-radius: 0 6px 6px 0;
|
|
476
|
+
background: var(--p-accent); color: var(--p-on-accent); cursor: pointer;
|
|
477
|
+
}
|
|
478
|
+
.menu {
|
|
479
|
+
position: absolute; right: 0; bottom: calc(100% + 6px); z-index: 2;
|
|
480
|
+
min-width: 180px; padding: 4px; display: grid; gap: 2px;
|
|
481
|
+
background: var(--p-bg); border: 1px solid var(--p-line); border-radius: 8px;
|
|
482
|
+
box-shadow: var(--p-shadow);
|
|
483
|
+
}
|
|
484
|
+
.menu[hidden] { display: none; }
|
|
485
|
+
.menu button {
|
|
486
|
+
display: flex; align-items: center; gap: 8px;
|
|
487
|
+
height: 30px; padding: 0 8px 0 24px; position: relative;
|
|
488
|
+
border: 0; border-radius: 6px; background: transparent;
|
|
489
|
+
color: var(--p-fg); font: inherit; font-size: 12px; text-align: left; cursor: pointer;
|
|
490
|
+
}
|
|
491
|
+
.menu button:hover { background: var(--p-hover); }
|
|
492
|
+
.menu button[aria-checked="true"]::before {
|
|
493
|
+
content: ""; position: absolute; left: 10px; top: 50%;
|
|
494
|
+
width: 6px; height: 6px; margin-top: -3px; border-radius: 50%; background: var(--p-fg);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* Compare: "showing original" strip */
|
|
498
|
+
.original-pill {
|
|
499
|
+
padding: 6px 14px;
|
|
500
|
+
background: var(--p-accent); color: var(--p-on-accent);
|
|
501
|
+
font-size: 12px; font-weight: 500;
|
|
502
|
+
}
|
|
503
|
+
.original-pill[hidden] { display: none; }
|
|
504
|
+
|
|
505
|
+
/* Picker outline over the page */
|
|
506
|
+
.pick-box {
|
|
507
|
+
position: fixed; z-index: 2147483646; pointer-events: none;
|
|
508
|
+
outline: 2px solid #2563eb; outline-offset: 1px;
|
|
509
|
+
background: rgb(37 99 235 / 0.08);
|
|
510
|
+
border-radius: 2px;
|
|
511
|
+
}
|
|
512
|
+
.pick-box[hidden] { display: none; }
|
|
513
|
+
.pick-tag {
|
|
514
|
+
position: absolute; left: -2px; bottom: calc(100% + 4px);
|
|
515
|
+
padding: 2px 6px; border-radius: 4px;
|
|
516
|
+
background: #2563eb; color: #fff;
|
|
517
|
+
font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; white-space: nowrap;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/* Local view: what's selected and where tweaks go */
|
|
521
|
+
.scope-bar {
|
|
522
|
+
display: grid; gap: 10px;
|
|
523
|
+
padding: 6px 14px 12px;
|
|
524
|
+
border-bottom: 1px solid var(--p-line);
|
|
525
|
+
}
|
|
526
|
+
.scope-head { display: flex; align-items: center; gap: 4px; margin-left: -6px; }
|
|
527
|
+
.scope-name { min-width: 0; display: grid; line-height: 1.3; }
|
|
528
|
+
.scope-name code, .change-scope code, .change-token code, .empty code {
|
|
529
|
+
font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-fg);
|
|
530
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
531
|
+
}
|
|
532
|
+
.scope-name .muted { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
533
|
+
.scope-target:empty, .warnings:empty { display: none; }
|
|
534
|
+
.muted { color: var(--p-muted); }
|
|
535
|
+
.empty { padding: 14px; color: var(--p-muted); font-size: 12px; line-height: 1.5; }
|
|
536
|
+
|
|
537
|
+
/* Contrast warnings */
|
|
538
|
+
.warnings { display: grid; gap: 6px; }
|
|
539
|
+
.body > .warnings { padding: 10px 14px 0; }
|
|
540
|
+
.warning {
|
|
541
|
+
display: flex; align-items: flex-start; gap: 6px;
|
|
542
|
+
padding: 6px 8px; border-radius: 6px; /* Figma Warning (83:337), 31px tall with its 1px border */
|
|
543
|
+
border: 1px solid #d97706;
|
|
544
|
+
background: linear-gradient(rgb(217 119 6 / 0.04), rgb(217 119 6 / 0.04)), var(--p-bg);
|
|
545
|
+
color: #b45309;
|
|
546
|
+
font-size: 12px; line-height: 1.4;
|
|
547
|
+
}
|
|
548
|
+
.warning svg { flex: none; width: 16px; height: 16px; margin-top: 0.5px; }
|
|
549
|
+
@media (prefers-color-scheme: dark) { .warning { color: #fbbf24; } }
|
|
550
|
+
|
|
551
|
+
/* Changes list */
|
|
552
|
+
.change-group { padding: 10px 14px; border-bottom: 1px solid var(--p-line); display: grid; gap: 6px; }
|
|
553
|
+
.change-group:last-child { border-bottom: 0; }
|
|
554
|
+
.change-scope { display: flex; align-items: baseline; gap: 8px; min-width: 0; font-size: 12px; font-weight: 600; }
|
|
555
|
+
.change-scope code { color: var(--p-muted); font-weight: 400; }
|
|
556
|
+
.link-btn {
|
|
557
|
+
padding: 0; border: 0; background: none; font: inherit; color: var(--p-fg);
|
|
558
|
+
text-decoration: underline; text-underline-offset: 2px; cursor: pointer; white-space: nowrap;
|
|
559
|
+
}
|
|
560
|
+
.change-row { display: flex; align-items: flex-start; gap: 6px; }
|
|
561
|
+
.change-main { flex: 1; min-width: 0; display: grid; gap: 2px; }
|
|
562
|
+
.change-title { font-size: 12px; }
|
|
563
|
+
.change-title b { font-weight: 600; }
|
|
564
|
+
.change-token { font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-muted); overflow-wrap: anywhere; }
|
|
565
|
+
.change-row .icon-btn { width: 22px; height: 22px; flex: none; }
|
|
566
|
+
|
|
567
|
+
/* Apply preview dialog */
|
|
568
|
+
.dialog-backdrop {
|
|
569
|
+
position: fixed; inset: 0; z-index: 2147483647;
|
|
570
|
+
display: grid; place-items: center; padding: 16px;
|
|
571
|
+
background: rgb(0 0 0 / 0.35);
|
|
572
|
+
font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
573
|
+
--p-bg: #ffffff; --p-fg: #18181b; --p-muted: #71717a; --p-line: #e4e4e7; --p-hover: #f4f4f5;
|
|
574
|
+
--p-accent: #18181b; --p-on-accent: #ffffff;
|
|
575
|
+
--p-shadow: 0 12px 40px rgb(0 0 0 / 0.14), 0 2px 6px rgb(0 0 0 / 0.06);
|
|
576
|
+
}
|
|
577
|
+
@media (prefers-color-scheme: dark) {
|
|
578
|
+
.dialog-backdrop {
|
|
579
|
+
--p-bg: #1c1c1f; --p-fg: #f4f4f5; --p-muted: #a1a1aa; --p-line: #2e2e33; --p-hover: #27272a;
|
|
580
|
+
--p-accent: #f4f4f5; --p-on-accent: #18181b;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
.dialog-backdrop[hidden] { display: none; }
|
|
584
|
+
.dialog {
|
|
585
|
+
width: min(640px, 100%); max-height: calc(100vh - 32px);
|
|
586
|
+
display: flex; flex-direction: column;
|
|
587
|
+
background: var(--p-bg); color: var(--p-fg);
|
|
588
|
+
border: 1px solid var(--p-line); border-radius: 14px; box-shadow: var(--p-shadow);
|
|
589
|
+
overflow: hidden;
|
|
590
|
+
}
|
|
591
|
+
.dialog-head {
|
|
592
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
593
|
+
padding: 10px 10px 10px 16px; border-bottom: 1px solid var(--p-line);
|
|
594
|
+
font-weight: 600; font-size: 14px;
|
|
595
|
+
}
|
|
596
|
+
.prompt-text {
|
|
597
|
+
flex: 1; min-height: 320px; margin: 0; padding: 14px 16px; resize: none;
|
|
598
|
+
border: 0; background: var(--p-bg); color: var(--p-fg);
|
|
599
|
+
font: 12px/1.55 ui-monospace, "SF Mono", Menlo, monospace;
|
|
600
|
+
}
|
|
601
|
+
.prompt-text:focus { outline: none; }
|
|
602
|
+
.dialog-foot {
|
|
603
|
+
display: flex; align-items: center; gap: 12px; justify-content: space-between;
|
|
604
|
+
padding: 10px 16px; border-top: 1px solid var(--p-line); font-size: 12px;
|
|
605
|
+
}
|
|
606
|
+
.dialog-foot .apply-btn { flex: none; }
|
|
607
|
+
|
|
608
|
+
/* Setup hint (proposal): shown when the page isn't ready for Twiddle */
|
|
609
|
+
.setup-hint {
|
|
610
|
+
margin: 8px 10px 4px; padding: 10px 12px;
|
|
611
|
+
display: grid; gap: 8px;
|
|
612
|
+
border: 1px solid var(--p-line); border-radius: 10px;
|
|
613
|
+
background: var(--p-hover); color: var(--p-fg);
|
|
614
|
+
font-size: 12px; line-height: 1.45;
|
|
615
|
+
}
|
|
616
|
+
.setup-hint[hidden] { display: none; }
|
|
617
|
+
.setup-head { display: flex; align-items: center; gap: 6px; }
|
|
618
|
+
.setup-head svg { flex: none; color: #b45309; }
|
|
619
|
+
.setup-head b { flex: 1; font-size: 12.5px; font-weight: 600; }
|
|
620
|
+
.setup-head .icon-btn { width: 22px; height: 22px; margin: -4px -6px -4px 0; }
|
|
621
|
+
.setup-hint p { margin: 0; color: var(--p-muted); }
|
|
622
|
+
.setup-snippet { display: grid; gap: 3px; min-width: 0; }
|
|
623
|
+
.setup-snippet > span { font-size: 11px; color: var(--p-muted); }
|
|
624
|
+
.setup-code {
|
|
625
|
+
display: flex; align-items: center; gap: 4px; min-width: 0;
|
|
626
|
+
padding: 2px 2px 2px 8px; border-radius: 6px;
|
|
627
|
+
background: var(--p-bg); border: 1px solid var(--p-line);
|
|
628
|
+
}
|
|
629
|
+
.setup-code code {
|
|
630
|
+
flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; padding: 4px 0;
|
|
631
|
+
font: 11px/1.5 ui-monospace, "SF Mono", Menlo, monospace;
|
|
632
|
+
scrollbar-width: none;
|
|
633
|
+
}
|
|
634
|
+
.setup-code .text-btn { height: 24px; margin: 0; padding: 0 8px; flex: none; }
|
|
635
|
+
.setup-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
636
|
+
.setup-actions a { color: var(--p-fg); font-weight: 500; text-underline-offset: 2px; }
|
|
637
|
+
.launcher { position: relative; }
|
|
638
|
+
.launcher[data-hint]::after {
|
|
639
|
+
content: ""; position: absolute; top: 2px; right: 2px;
|
|
640
|
+
width: 10px; height: 10px; border-radius: 50%;
|
|
641
|
+
background: #d97706; box-shadow: 0 0 0 2px var(--p-bg);
|
|
642
|
+
}
|
|
643
|
+
@media (prefers-color-scheme: dark) { .setup-head svg { color: #fbbf24; } }
|
|
644
|
+
|
|
645
|
+
/* ---------- Layout direction A (Figma 80:245 › A1b, A2) ---------- */
|
|
646
|
+
.head[hidden], .page[hidden], .edit-row[hidden] { display: none; }
|
|
647
|
+
.subhead { padding-left: 8px; }
|
|
648
|
+
.page-fields { padding: 14px 14px 16px; display: grid; gap: 14px; color: var(--p-muted); }
|
|
649
|
+
.page-warnings:empty { display: none; }
|
|
650
|
+
|
|
651
|
+
/* Edit list rows look like the Figma Section row (6:134) */
|
|
652
|
+
.nav-row {
|
|
653
|
+
width: 100%; display: flex; align-items: center; gap: 8px;
|
|
654
|
+
padding: 11px 14px; border: 0; border-bottom: 1px solid var(--p-line);
|
|
655
|
+
background: transparent; color: var(--p-fg);
|
|
656
|
+
font: inherit; font-weight: 500; text-align: left; cursor: pointer;
|
|
657
|
+
}
|
|
658
|
+
.nav-row:last-child { border-bottom: 0; }
|
|
659
|
+
.nav-row:hover { background: var(--p-hover); }
|
|
660
|
+
.nav-row .chev { margin-left: auto; color: var(--p-muted); display: grid; }
|
|
661
|
+
|
|
662
|
+
/* "Edit every token" row above the footer (Figma: Edit row (proposal)) */
|
|
663
|
+
.edit-row {
|
|
664
|
+
display: flex; align-items: center; gap: 8px;
|
|
665
|
+
padding: 11px 14px; border: 0; border-top: 1px solid var(--p-line);
|
|
666
|
+
background: transparent; color: var(--p-fg);
|
|
667
|
+
font: inherit; font-weight: 500; text-align: left; cursor: pointer;
|
|
668
|
+
}
|
|
669
|
+
.edit-row:hover { background: var(--p-hover); }
|
|
670
|
+
.edit-row .chev { margin-left: auto; color: var(--p-muted); display: grid; }
|
|
671
|
+
|
|
672
|
+
/* Character pad (Figma: Character pad (proposal)): 170px tall, 9 × 5 dots */
|
|
673
|
+
.pad {
|
|
674
|
+
position: relative; height: 170px; border-radius: 10px;
|
|
675
|
+
background: var(--p-track); touch-action: none; cursor: pointer;
|
|
676
|
+
user-select: none;
|
|
677
|
+
}
|
|
678
|
+
.pad:focus-visible { outline: 2px solid var(--p-accent); outline-offset: 2px; }
|
|
679
|
+
.pad-area { position: absolute; inset: 33px 31px; }
|
|
680
|
+
.pad-dot {
|
|
681
|
+
position: absolute; width: 2px; height: 2px; margin: -1px 0 0 -1px; border-radius: 50%;
|
|
682
|
+
background: var(--p-muted); opacity: .35;
|
|
683
|
+
}
|
|
684
|
+
.pad-knob {
|
|
685
|
+
position: absolute; width: 20px; height: 20px; margin: -10px 0 0 -10px; border-radius: 50%;
|
|
686
|
+
background: var(--p-bg); border: 2px solid var(--p-accent);
|
|
687
|
+
box-shadow: 0 1px 3px rgb(0 0 0 / 0.15);
|
|
688
|
+
transition: left .08s ease, top .08s ease;
|
|
689
|
+
}
|
|
690
|
+
.pad.dragging .pad-knob { transition: none; }
|
|
691
|
+
.pad-edge { position: absolute; font-size: 11px; color: var(--p-muted); line-height: 1; pointer-events: none; }
|
|
692
|
+
.pad-edge.top { top: 9px; left: 50%; transform: translateX(-50%); }
|
|
693
|
+
.pad-edge.bottom { bottom: 9px; left: 50%; transform: translateX(-50%); }
|
|
694
|
+
.pad-edge.left { left: 9px; top: 50%; transform: translate(-50%, -50%) rotate(-90deg); transform-origin: center; margin-left: 5px; }
|
|
695
|
+
.pad-edge.right { right: 9px; top: 50%; transform: translate(50%, -50%) rotate(90deg); transform-origin: center; margin-right: 5px; }
|
|
696
|
+
|
|
697
|
+
/* Token rows (Figma: Token row 86:337, Value field 83:348, Custom tag 83:358) */
|
|
698
|
+
.token-rows { display: grid; gap: 2px; }
|
|
699
|
+
.token-row { display: flex; align-items: center; gap: 8px; min-height: 30px; }
|
|
700
|
+
.token-name {
|
|
701
|
+
min-width: 56px; flex: none;
|
|
702
|
+
font: 11px/1.4 ui-monospace, "SF Mono", Menlo, monospace; color: var(--p-muted);
|
|
703
|
+
white-space: nowrap;
|
|
704
|
+
}
|
|
705
|
+
.token-row.edited .token-name { color: var(--p-fg); }
|
|
706
|
+
.token-bar { flex: 1; min-width: 0; height: 6px; display: flex; }
|
|
707
|
+
.token-bar span { height: 6px; border-radius: 2px; background: var(--p-line); }
|
|
708
|
+
.token-row.edited .token-bar span { background: var(--p-accent); }
|
|
709
|
+
.token-gap { flex: 1; }
|
|
710
|
+
.custom-tag {
|
|
711
|
+
flex: none; height: 18px; padding: 0 6px; border-radius: 9px;
|
|
712
|
+
display: inline-flex; align-items: center;
|
|
713
|
+
background: var(--p-hover); color: var(--p-fg); font-size: 11px; font-weight: 500;
|
|
714
|
+
}
|
|
715
|
+
.custom-tag[hidden], .token-reset[hidden] { display: none; }
|
|
716
|
+
.token-value {
|
|
717
|
+
flex: none; width: 64px; height: 28px; padding: 0 8px;
|
|
718
|
+
display: flex; align-items: center; gap: 2px;
|
|
719
|
+
border: 1px solid var(--p-line); border-radius: 6px; background: var(--p-bg); cursor: text;
|
|
720
|
+
}
|
|
721
|
+
.token-row.edited .token-value, .token-value:focus-within { border-color: var(--p-accent); }
|
|
722
|
+
.token-value input {
|
|
723
|
+
flex: 1; min-width: 0; width: 100%; padding: 0; border: 0; background: transparent;
|
|
724
|
+
color: var(--p-fg); font: inherit; font-size: 13px; font-weight: 500;
|
|
725
|
+
}
|
|
726
|
+
.token-value input:focus { outline: none; }
|
|
727
|
+
.token-unit { font-size: 12px; color: var(--p-muted); }
|
|
728
|
+
.token-unit[hidden] { display: none; }
|
|
729
|
+
.kind-colour .token-value { width: 108px; padding-left: 4px; gap: 6px; }
|
|
730
|
+
.kind-text .token-gap { display: none; }
|
|
731
|
+
.kind-text .token-value { flex: 1; width: auto; min-width: 0; } /* fills what's left, so an edited row still fits */
|
|
732
|
+
.kind-text .token-value input, .kind-colour .token-value input { font: 11px ui-monospace, "SF Mono", Menlo, monospace; }
|
|
733
|
+
.token-swatch { width: 18px; height: 18px; flex: none; border-radius: 4px; box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.1); }
|
|
734
|
+
.token-reset { width: 22px; height: 22px; flex: none; }
|
|
735
|
+
.token-row:not(.edited) .token-value { margin-right: 30px; } /* keeps values aligned where the reset sits */
|
|
736
|
+
|
|
737
|
+
/* Spacing › Roles (Figma: Role row 86:349, Step select 83:357) */
|
|
738
|
+
.tab-pane { display: grid; gap: 14px; }
|
|
739
|
+
.page-fields > *, .tab-pane > *, .token-field > *, .token-rows > * { min-width: 0; } /* let long rows shrink (labels ellipsis) instead of widening the panel */
|
|
740
|
+
.tab-pane[hidden] { display: none; }
|
|
741
|
+
.role-row { display: flex; align-items: center; gap: 8px; min-height: 32px; }
|
|
742
|
+
.role-label { flex: 1; min-width: 0; font-size: 12px; color: var(--p-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
743
|
+
.role-row.unused .role-label { color: var(--p-muted); }
|
|
744
|
+
.role-row.edited .role-label { font-weight: 500; }
|
|
745
|
+
.role-fixed { width: 56px; }
|
|
746
|
+
.role-row.is-fixed .step-select select { width: 74px; } /* "Fixed" needs less room, so the label fits */
|
|
747
|
+
.role-fixed[hidden] { display: none; }
|
|
748
|
+
.role-row:not(.edited) .step-select { margin-right: 30px; } /* aligned with rows that show a reset */
|
|
749
|
+
.step-select { position: relative; flex: none; display: flex; }
|
|
750
|
+
.step-select select {
|
|
751
|
+
appearance: none; -webkit-appearance: none;
|
|
752
|
+
height: 28px; padding: 0 24px 0 8px; max-width: 118px;
|
|
753
|
+
border: 1px solid var(--p-line); border-radius: 6px; background: var(--p-bg);
|
|
754
|
+
color: var(--p-fg); font: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
|
|
755
|
+
}
|
|
756
|
+
.role-row.edited .step-select select, .step-select select:focus-visible { border-color: var(--p-accent); outline: none; }
|
|
757
|
+
.step-chevron { position: absolute; right: 7px; top: 50%; transform: translateY(-50%); display: grid; color: var(--p-muted); pointer-events: none; }
|
|
758
|
+
.role-note { margin: 0; font-size: 11px; line-height: 1.45; color: var(--p-muted); }
|
|
759
|
+
`, j = (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>`, $ = {
|
|
760
|
+
dial: j(20, 1.8, '<circle cx="12" cy="12" r="8"/><path d="M12 12l4-4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>'),
|
|
761
|
+
link: j(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"/>'),
|
|
762
|
+
reset: j(15, 2, '<path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 3v6h6"/>'),
|
|
763
|
+
close: j(15, 2, '<path d="M18 6L6 18M6 6l12 12"/>'),
|
|
764
|
+
chevron: j(12, 2.4, '<path d="M9 6l6 6-6 6"/>'),
|
|
765
|
+
// Proposals: not yet in the Figma Icon/* set.
|
|
766
|
+
pick: j(15, 2, '<circle cx="12" cy="12" r="7"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/>'),
|
|
767
|
+
compare: j(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"/>'),
|
|
768
|
+
undo: j(15, 2, '<path d="M9 14L4 9l5-5"/><path d="M4 9h11a5 5 0 0 1 0 10h-3"/>'),
|
|
769
|
+
redo: j(15, 2, '<path d="M15 14l5-5-5-5"/><path d="M20 9H9a5 5 0 0 0 0 10h3"/>'),
|
|
770
|
+
back: j(15, 2, '<path d="M15 18l-6-6 6-6"/>'),
|
|
771
|
+
down: j(12, 2.4, '<path d="M6 9l6 6 6-6"/>'),
|
|
772
|
+
copy: j(13, 2, '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/>'),
|
|
773
|
+
warn: j(13, 2, '<path d="M12 3l9.5 17h-19z"/><path d="M12 10v4M12 17.5v.01"/>'),
|
|
774
|
+
sliders: j(15, 2, '<path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/>')
|
|
775
|
+
// Figma: Icon/Sliders (proposal)
|
|
776
|
+
}, an = "twiddle:";
|
|
777
|
+
function Fe(t) {
|
|
778
|
+
try {
|
|
779
|
+
return localStorage.getItem(an + t);
|
|
780
|
+
} catch {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function be(t, e) {
|
|
785
|
+
try {
|
|
786
|
+
localStorage.setItem(an + t, e);
|
|
787
|
+
} catch {
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function X({ label: t, options: e, onSelect: n }) {
|
|
791
|
+
const o = document.createElement("div");
|
|
792
|
+
o.className = "seg", o.setAttribute("role", "radiogroup"), o.setAttribute("aria-label", t);
|
|
793
|
+
const a = e.map((l) => {
|
|
794
|
+
const c = document.createElement("button");
|
|
795
|
+
return c.type = "button", c.setAttribute("role", "radio"), c.dataset.value = l.value, c.textContent = l.label, l.title && (c.title = l.title), c.addEventListener("click", () => d(l.value)), c;
|
|
796
|
+
});
|
|
797
|
+
o.append(...a);
|
|
798
|
+
let s = null;
|
|
799
|
+
function r(l) {
|
|
800
|
+
s = l, a.forEach((p) => p.setAttribute("aria-checked", String(p.dataset.value === l)));
|
|
801
|
+
const c = Math.max(0, a.findIndex((p) => p.dataset.value === l));
|
|
802
|
+
a.forEach((p, m) => {
|
|
803
|
+
p.tabIndex = m === c ? 0 : -1;
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
function d(l, c) {
|
|
807
|
+
l !== s && (r(l), n(l)), c && a.find((p) => p.dataset.value === l).focus();
|
|
808
|
+
}
|
|
809
|
+
return o.addEventListener("keydown", (l) => {
|
|
810
|
+
const c = a.indexOf(l.target);
|
|
811
|
+
if (c < 0) return;
|
|
812
|
+
const p = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[l.key];
|
|
813
|
+
let m;
|
|
814
|
+
if (p) m = (c + p + a.length) % a.length;
|
|
815
|
+
else if (l.key === "Home") m = 0;
|
|
816
|
+
else if (l.key === "End") m = a.length - 1;
|
|
817
|
+
else return;
|
|
818
|
+
l.preventDefault(), d(a[m].dataset.value, !0);
|
|
819
|
+
}), r(null), { el: o, setValue: r };
|
|
820
|
+
}
|
|
821
|
+
const rn = /var\(\s*(--twd-[\w-]+)/g, Bn = /::?[\w-]+(\([^)]*\))?/g;
|
|
822
|
+
function* $e(t) {
|
|
823
|
+
for (const e of t)
|
|
824
|
+
e.selectorText != null && (yield e), e.cssRules && (yield* $e(e.cssRules));
|
|
825
|
+
}
|
|
826
|
+
function Vn() {
|
|
827
|
+
const t = [];
|
|
828
|
+
for (const e of document.styleSheets) {
|
|
829
|
+
if (e.ownerNode?.id === "twiddle-scoped") continue;
|
|
830
|
+
let n;
|
|
831
|
+
try {
|
|
832
|
+
n = e.cssRules;
|
|
833
|
+
} catch {
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
for (const o of $e(n)) {
|
|
837
|
+
const a = [...o.style.cssText.matchAll(rn)].map((s) => s[1]);
|
|
838
|
+
a.length && t.push({ selectors: o.selectorText.split(","), vars: a });
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return t;
|
|
842
|
+
}
|
|
843
|
+
function Un(t, e) {
|
|
844
|
+
const n = e.replace(Bn, "").trim() || "*";
|
|
845
|
+
try {
|
|
846
|
+
return t.matches(n);
|
|
847
|
+
} catch {
|
|
848
|
+
return !1;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function sn(t) {
|
|
852
|
+
const e = /* @__PURE__ */ new Set(), n = [];
|
|
853
|
+
return Vn().forEach(({ selectors: o, vars: a }) => {
|
|
854
|
+
const s = o.find((r) => Un(t, r));
|
|
855
|
+
s && (a.forEach((r) => e.add(r)), n.push(s.trim()));
|
|
856
|
+
}), [...(t.getAttribute("style") || "").matchAll(rn)].forEach((o) => e.add(o[1])), { names: e, selectors: n };
|
|
857
|
+
}
|
|
858
|
+
const We = (t) => window.CSS && CSS.escape ? CSS.escape(t) : t;
|
|
859
|
+
function qn(t) {
|
|
860
|
+
const e = [];
|
|
861
|
+
let n = t;
|
|
862
|
+
for (; n && n.nodeType === 1 && n !== document.body && n !== document.documentElement; ) {
|
|
863
|
+
if (n.id && document.querySelectorAll(`#${We(n.id)}`).length === 1)
|
|
864
|
+
return e.unshift(`#${We(n.id)}`), e.join(" > ");
|
|
865
|
+
const o = n.localName, a = [...n.parentElement.children].filter((s) => s.localName === o);
|
|
866
|
+
e.unshift(a.length > 1 ? `${o}:nth-of-type(${a.indexOf(n) + 1})` : o), n = n.parentElement;
|
|
867
|
+
}
|
|
868
|
+
return ["body", ...e].join(" > ");
|
|
869
|
+
}
|
|
870
|
+
function Hn(t, e = sn(t).selectors) {
|
|
871
|
+
const n = t.getAttribute("data-twd");
|
|
872
|
+
if (n) return `[data-twd="${n.replace(/"/g, "")}"]`;
|
|
873
|
+
const o = [...t.classList], s = o.find((r) => e.some((d) => new RegExp(`\\.${r.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(d))) || o[0];
|
|
874
|
+
return s ? `.${We(s)}` : null;
|
|
875
|
+
}
|
|
876
|
+
function _n(t) {
|
|
877
|
+
const e = sn(t), n = Hn(t, e.selectors), o = (t.getAttribute("aria-label") || t.placeholder || t.textContent || "").trim().replace(/\s+/g, " ");
|
|
878
|
+
return {
|
|
879
|
+
el: t,
|
|
880
|
+
tag: t.localName,
|
|
881
|
+
classes: [...t.classList],
|
|
882
|
+
text: o.length > 40 ? `${o.slice(0, 40)}…` : o,
|
|
883
|
+
tokens: e.names,
|
|
884
|
+
elementSelector: qn(t),
|
|
885
|
+
classSelector: n,
|
|
886
|
+
classCount: n ? document.querySelectorAll(n).length : 0
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
const Ye = (t) => t.localName + [...t.classList].map((e) => `.${e}`).join(""), Gn = { radius: "px", intensity: "%", base: "px" }, ln = (t) => t.charAt(0).toUpperCase() + t.slice(1), Kn = (t, e) => typeof e == "number" || /^\d+$/.test(e) ? `${e}${Gn[t] || ""}` : ln(String(e));
|
|
890
|
+
function Tt(t, e, n) {
|
|
891
|
+
const o = on(() => t.tokens({ [t.id]: e }) || {}), a = Object.keys(o);
|
|
892
|
+
let s = {};
|
|
893
|
+
if (a.length) {
|
|
894
|
+
const r = n.selector ? document.querySelector(n.selector) : void 0;
|
|
895
|
+
s = jn(a, r || void 0);
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
...n,
|
|
899
|
+
sectionId: t.id,
|
|
900
|
+
title: t.title,
|
|
901
|
+
settings: Object.entries(e).map(([r, d]) => ({ key: r, label: ln(r), value: Kn(r, d), ...t.describe?.(r, d) || {} })),
|
|
902
|
+
tokens: a.map((r) => ({ name: r, from: s[r] || "", to: o[r] }))
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function Lt(t, e) {
|
|
906
|
+
const n = [];
|
|
907
|
+
return e.forEach((o) => {
|
|
908
|
+
t[o.id] && n.push(Tt(o, t[o.id], { kind: "global", selector: null }));
|
|
909
|
+
}), Object.entries(t.scopes || {}).forEach(([o, a]) => {
|
|
910
|
+
const s = Ge(o) ? "class" : "element";
|
|
911
|
+
e.forEach((r) => {
|
|
912
|
+
a[r.id] && n.push(Tt(r, a[r.id], { kind: s, selector: o }));
|
|
913
|
+
});
|
|
914
|
+
}), n;
|
|
915
|
+
}
|
|
916
|
+
function Wn(t) {
|
|
917
|
+
const e = (a) => Object.values(a || {}).reduce((s, r) => s + Object.keys(r || {}).length, 0), { scopes: n, ...o } = t;
|
|
918
|
+
return e(o) + Object.values(n || {}).reduce((a, s) => a + e(s), 0);
|
|
919
|
+
}
|
|
920
|
+
const W = (t) => `\`${t}\``, Oe = (t) => t.map((e) => ` - ${W(e.name)}: ${e.from ? `${W(e.from)} → ` : ""}${W(e.to)}`), Re = (t) => `${t.title} › ${t.settings.map((e) => `${e.label}: ${e.value}`).join(", ")}`;
|
|
921
|
+
function Yn(t) {
|
|
922
|
+
let e = null;
|
|
923
|
+
try {
|
|
924
|
+
e = document.querySelector(t);
|
|
925
|
+
} catch {
|
|
926
|
+
}
|
|
927
|
+
if (!e) return "not found on the current page";
|
|
928
|
+
const n = (e.getAttribute("aria-label") || e.placeholder || e.textContent || "").trim().replace(/\s+/g, " ").slice(0, 60);
|
|
929
|
+
return `${W(Ye(e))}${n ? ` with text “${n}”` : ""}`;
|
|
930
|
+
}
|
|
931
|
+
function Pt(t, e) {
|
|
932
|
+
const n = /* @__PURE__ */ new Map();
|
|
933
|
+
return t.forEach((o) => {
|
|
934
|
+
const a = o[e];
|
|
935
|
+
n.has(a) || n.set(a, []), n.get(a).push(o);
|
|
936
|
+
}), [...n];
|
|
937
|
+
}
|
|
938
|
+
function Xn(t, { link: e = location.href, warnings: n = [] } = {}) {
|
|
939
|
+
if (!t.length) return "";
|
|
940
|
+
const o = t.filter((d) => d.kind === "global"), a = t.filter((d) => d.kind === "class"), s = t.filter((d) => d.kind === "element"), r = [];
|
|
941
|
+
if (r.push(
|
|
942
|
+
"# Apply design tweaks from Panel",
|
|
943
|
+
"",
|
|
944
|
+
"I explored design changes to this prototype with Panel (by Twiddle), a tweak panel that overrides the prototype's `--twd-*` CSS custom properties (design tokens) at runtime. Make these changes permanent in the source code.",
|
|
945
|
+
"",
|
|
946
|
+
`Preview of the tweaked version: ${e}`
|
|
947
|
+
), o.length && (r.push(
|
|
948
|
+
"",
|
|
949
|
+
"## 1. Global token changes",
|
|
950
|
+
"",
|
|
951
|
+
"Update the default value of each token where it is defined (usually a `:root` rule or a `tokens.css` / `twiddle-tokens.css` file). Change the definition, not the places that use it.",
|
|
952
|
+
""
|
|
953
|
+
), o.forEach((d) => r.push(`- **${Re(d)}**`, ...Oe(d.tokens)))), a.length && (r.push(
|
|
954
|
+
"",
|
|
955
|
+
`## ${o.length ? 2 : 1}. Component-wide changes`,
|
|
956
|
+
"",
|
|
957
|
+
"These apply to every element matching the selector. In the CSS rule for that component, redeclare the token, e.g. `.input { --twd-radius-control: 999px; }`. Don't replace the `var(--twd-…)` usages with fixed values.",
|
|
958
|
+
""
|
|
959
|
+
), Pt(a, "selector").forEach(([d, l]) => {
|
|
960
|
+
r.push(`- ${W(d)} (${document.querySelectorAll(d).length} on this page)`), l.forEach((c) => r.push(` - **${Re(c)}**`, ...Oe(c.tokens).map((p) => ` ${p}`)));
|
|
961
|
+
})), s.length) {
|
|
962
|
+
const d = 1 + (o.length ? 1 : 0) + (a.length ? 1 : 0);
|
|
963
|
+
r.push(
|
|
964
|
+
"",
|
|
965
|
+
`## ${d}. Single-element changes`,
|
|
966
|
+
"",
|
|
967
|
+
"These apply to one element only. Add a modifier class to that element (e.g. `input--pill`) and redeclare the token in a rule for that class. The selectors below are runtime paths to help you find the element; don't copy them into the stylesheet.",
|
|
968
|
+
""
|
|
969
|
+
), Pt(s, "selector").forEach(([l, c]) => {
|
|
970
|
+
r.push(`- ${Yn(l)}, found at ${W(l)}`), c.forEach((p) => r.push(` - **${Re(p)}**`, ...Oe(p.tokens).map((m) => ` ${m}`)));
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
return n.length && r.push(
|
|
974
|
+
"",
|
|
975
|
+
"## Contrast to check",
|
|
976
|
+
"",
|
|
977
|
+
"Panel flagged these colour pairs as below WCAG AA with the new values:",
|
|
978
|
+
"",
|
|
979
|
+
...n.map((d) => `- ${d.text}`)
|
|
980
|
+
), r.push(
|
|
981
|
+
"",
|
|
982
|
+
"## Constraints",
|
|
983
|
+
"",
|
|
984
|
+
"- Keep styling through the `--twd-*` tokens; don't hard-code the new values in component rules.",
|
|
985
|
+
"- Change only what is listed above.",
|
|
986
|
+
"- If a value uses `color-mix()` and the project targets older browsers, ask before converting it.",
|
|
987
|
+
"- When done, list the files you changed."
|
|
988
|
+
), r.join(`
|
|
989
|
+
`);
|
|
990
|
+
}
|
|
991
|
+
const ye = 4.5, Jn = 3, Zn = [
|
|
992
|
+
["--twd-text", "--twd-bg", "Text on background"],
|
|
993
|
+
["--twd-text", "--twd-surface", "Text on surface"],
|
|
994
|
+
["--twd-text-muted", "--twd-surface", "Muted text on surface"],
|
|
995
|
+
["--twd-on-primary", "--twd-primary", "Text on brand"]
|
|
996
|
+
];
|
|
997
|
+
let H;
|
|
998
|
+
function tt(t) {
|
|
999
|
+
if (!t || (H ||= Object.assign(document.createElement("canvas"), { width: 1, height: 1 }).getContext("2d", { willReadFrequently: !0 }), !H)) return null;
|
|
1000
|
+
H.clearRect(0, 0, 1, 1), H.fillStyle = "#000", H.fillStyle = t, H.fillRect(0, 0, 1, 1);
|
|
1001
|
+
const [e, n, o, a] = H.getImageData(0, 0, 1, 1).data;
|
|
1002
|
+
return [e, n, o, a / 255];
|
|
1003
|
+
}
|
|
1004
|
+
function Ft(t) {
|
|
1005
|
+
const e = document.createElement("i");
|
|
1006
|
+
e.style.cssText = `position:absolute;visibility:hidden;color:var(${t})`, document.documentElement.appendChild(e);
|
|
1007
|
+
const n = getComputedStyle(e).color;
|
|
1008
|
+
return e.remove(), tt(n);
|
|
1009
|
+
}
|
|
1010
|
+
const Ot = ([t, e, n]) => {
|
|
1011
|
+
const o = (a) => (a /= 255, a <= 0.03928 ? a / 12.92 : ((a + 0.055) / 1.055) ** 2.4);
|
|
1012
|
+
return 0.2126 * o(t) + 0.7152 * o(e) + 0.0722 * o(n);
|
|
1013
|
+
}, dn = (t, e) => t.slice(0, 3).map((n, o) => n * t[3] + e[o] * (1 - t[3])).concat(1);
|
|
1014
|
+
function cn(t, e) {
|
|
1015
|
+
const n = Ot(t[3] < 1 ? dn(t, e) : t), o = Ot(e);
|
|
1016
|
+
return (Math.max(n, o) + 0.05) / (Math.min(n, o) + 0.05);
|
|
1017
|
+
}
|
|
1018
|
+
const pn = (t) => `${t.toFixed(1)}:1`;
|
|
1019
|
+
function Ne() {
|
|
1020
|
+
const t = [];
|
|
1021
|
+
return Zn.forEach(([e, n, o]) => {
|
|
1022
|
+
const a = Ft(e), s = Ft(n);
|
|
1023
|
+
if (!a || !s || s[3] === 0) return;
|
|
1024
|
+
const r = cn(a, s);
|
|
1025
|
+
r < ye && t.push({ label: o, ratio: r, needed: ye, text: `${o}: ${pn(r)} · AA needs ${ye}` });
|
|
1026
|
+
}), t;
|
|
1027
|
+
}
|
|
1028
|
+
function Qn(t) {
|
|
1029
|
+
const e = [];
|
|
1030
|
+
for (let n = t; n && n.nodeType === 1; n = n.parentElement) {
|
|
1031
|
+
const o = tt(getComputedStyle(n).backgroundColor);
|
|
1032
|
+
if (o && o[3] > 0 && (e.push(o), o[3] >= 1))
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
return e.reduceRight((n, o) => dn(o, n), [255, 255, 255, 1]);
|
|
1036
|
+
}
|
|
1037
|
+
function eo(t) {
|
|
1038
|
+
if (!t.textContent.trim() && !t.placeholder) return null;
|
|
1039
|
+
const e = getComputedStyle(t), n = tt(e.color);
|
|
1040
|
+
if (!n) return null;
|
|
1041
|
+
const o = parseFloat(e.fontSize), s = o >= 24 || o >= 18.66 && parseInt(e.fontWeight, 10) >= 700 ? Jn : ye, r = cn(n, Qn(t));
|
|
1042
|
+
return r < s ? { label: "This element", ratio: r, needed: s, text: `This element’s text: ${pn(r)} · AA needs ${s}` } : null;
|
|
1043
|
+
}
|
|
1044
|
+
const Rt = "twiddle-panel", to = "0.6.0", Nt = "https://panel.twiddle.in", no = "--twd-radius-control", oo = /var\(\s*--twd-/;
|
|
1045
|
+
function ao(t) {
|
|
1046
|
+
return t.style.cssText.split(";").some((e) => {
|
|
1047
|
+
const n = e.slice(0, e.indexOf(":")).trim();
|
|
1048
|
+
return n && !n.startsWith("--") && oo.test(e);
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
function ro() {
|
|
1052
|
+
if (!getComputedStyle(document.documentElement).getPropertyValue(no).trim()) return "no-tokens";
|
|
1053
|
+
let t = !1;
|
|
1054
|
+
for (const e of document.styleSheets) {
|
|
1055
|
+
if (e.ownerNode?.id === "twiddle-scoped") continue;
|
|
1056
|
+
let n;
|
|
1057
|
+
try {
|
|
1058
|
+
n = e.cssRules;
|
|
1059
|
+
} catch {
|
|
1060
|
+
t = !0;
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
for (const o of $e(n)) if (ao(o)) return "ok";
|
|
1064
|
+
}
|
|
1065
|
+
return document.querySelector('[style*="var(--twd-"]') ? "ok" : t ? "unknown" : "unused";
|
|
1066
|
+
}
|
|
1067
|
+
function so() {
|
|
1068
|
+
const t = /* @__PURE__ */ new Set(), e = (n) => {
|
|
1069
|
+
for (const o of n.matchAll(/var\(\s*(--twd-[\w-]+)/g)) t.add(o[1]);
|
|
1070
|
+
};
|
|
1071
|
+
for (const n of document.styleSheets) {
|
|
1072
|
+
if (n.ownerNode?.id === "twiddle-scoped") continue;
|
|
1073
|
+
let o;
|
|
1074
|
+
try {
|
|
1075
|
+
o = n.cssRules;
|
|
1076
|
+
} catch {
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
for (const a of $e(o))
|
|
1080
|
+
a.style.cssText.split(";").forEach((s) => {
|
|
1081
|
+
const r = s.slice(0, s.indexOf(":")).trim();
|
|
1082
|
+
r && !r.startsWith("--") && e(s);
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
return document.querySelectorAll('[style*="var(--twd-"]').forEach((n) => e(n.getAttribute("style"))), t;
|
|
1086
|
+
}
|
|
1087
|
+
const jt = [
|
|
1088
|
+
{ label: "With a build tool, in your entry file", code: `import '${Rt}/tokens.css';` },
|
|
1089
|
+
// Pinned to this build's version: unversioned jsDelivr links can lag a release by days, and unpkg once failed for a fresh one.
|
|
1090
|
+
{ label: "Plain HTML, in <head>", code: `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/${Rt}@${to}/dist/twiddle-tokens.css">` }
|
|
1091
|
+
], io = `Convert this prototype's styles to use the Panel design tokens (the --twd-* CSS custom properties from twiddle-panel), so the panel can tweak them live.
|
|
1092
|
+
|
|
1093
|
+
1. Keep the current look. In a :root rule loaded after twiddle-tokens.css, set each token below to the value this project already uses (for example, if buttons use #0f766e, set --twd-primary: #0f766e). Only set the tokens the project actually needs.
|
|
1094
|
+
2. Replace fixed values in component styles with var(--twd-…). Map each value to the closest token by role:
|
|
1095
|
+
- Fonts: --twd-font-heading, --twd-font-body
|
|
1096
|
+
- Text sizes: --twd-text-xs, -sm, -base, -lg, -xl, -2xl, -3xl; line height: --twd-leading-tight, --twd-leading-normal
|
|
1097
|
+
- Colours: --twd-bg (page), --twd-surface (cards, inputs), --twd-surface-raised (menus, popovers), --twd-text, --twd-text-muted, --twd-border, --twd-primary, --twd-primary-hover, --twd-on-primary (text on primary), --twd-focus, --twd-success, --twd-warning, --twd-danger
|
|
1098
|
+
- Buttons, inputs, chips: --twd-radius-control, --twd-control-height, --twd-control-pad-x
|
|
1099
|
+
- Cards, menus, modals: --twd-radius-container, --twd-border-width-container
|
|
1100
|
+
- Borders: --twd-border-width
|
|
1101
|
+
- Spacing (padding, margin, gap): --twd-space-1 to --twd-space-8 (4, 8, 12, 16, 24, 32, 48, 64px)
|
|
1102
|
+
- 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)
|
|
1103
|
+
- Shadows: --twd-shadow-sm, --twd-shadow-md, --twd-shadow-lg
|
|
1104
|
+
3. Leave values that don't match a role as they are (layout widths, one-off illustrations, icon sizes). Don't invent new --twd-* tokens.
|
|
1105
|
+
4. Don't change the layout or how the page looks; after the change it should look the same until someone uses the panel.
|
|
1106
|
+
5. When done, list the files you changed and any values you left fixed, with the reason.`;
|
|
1107
|
+
function B(t) {
|
|
1108
|
+
const e = document.createElement("div");
|
|
1109
|
+
e.className = "field";
|
|
1110
|
+
const n = document.createElement("div");
|
|
1111
|
+
n.className = "field-head";
|
|
1112
|
+
const o = document.createElement("span");
|
|
1113
|
+
o.className = "field-label", o.textContent = t;
|
|
1114
|
+
const a = document.createElement("span");
|
|
1115
|
+
return a.className = "field-value", a.hidden = !0, n.append(o, a), e.append(n), {
|
|
1116
|
+
el: e,
|
|
1117
|
+
setValue(s) {
|
|
1118
|
+
a.textContent = s || "", a.hidden = !s;
|
|
1119
|
+
},
|
|
1120
|
+
append(...s) {
|
|
1121
|
+
e.append(...s);
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
function lo({ label: t, ends: e, cols: n = 9, rows: o = 5, onInput: a }) {
|
|
1126
|
+
const s = document.createElement("div");
|
|
1127
|
+
s.className = "pad", s.tabIndex = 0, s.setAttribute("role", "group"), s.setAttribute("aria-roledescription", "2D slider");
|
|
1128
|
+
const r = (g, u) => {
|
|
1129
|
+
const y = document.createElement("span");
|
|
1130
|
+
return y.className = `pad-edge ${g}`, y.textContent = u, y.setAttribute("aria-hidden", "true"), y;
|
|
1131
|
+
};
|
|
1132
|
+
s.append(r("top", e.top), r("bottom", e.bottom), r("left", e.left), r("right", e.right));
|
|
1133
|
+
const d = document.createElement("div");
|
|
1134
|
+
d.className = "pad-area";
|
|
1135
|
+
for (let g = 0; g < o; g++)
|
|
1136
|
+
for (let u = 0; u < n; u++) {
|
|
1137
|
+
const y = document.createElement("i");
|
|
1138
|
+
y.className = "pad-dot", y.style.left = `${u / (n - 1) * 100}%`, y.style.top = `${g / (o - 1) * 100}%`, d.append(y);
|
|
1139
|
+
}
|
|
1140
|
+
const l = document.createElement("span");
|
|
1141
|
+
l.className = "pad-knob", d.append(l), s.append(d);
|
|
1142
|
+
let c = 0.5, p = 0.5;
|
|
1143
|
+
const m = (g, u) => Math.round(Math.min(1, Math.max(0, g)) * (u - 1)) / (u - 1);
|
|
1144
|
+
function v() {
|
|
1145
|
+
l.style.left = `${c * 100}%`, l.style.top = `${p * 100}%`;
|
|
1146
|
+
}
|
|
1147
|
+
function f(g, u, y) {
|
|
1148
|
+
g = y === "x" ? c : m(g, n), u = y === "y" ? p : m(u, o), !(g === c && u === p) && (c = g, p = u, v(), a(c, p));
|
|
1149
|
+
}
|
|
1150
|
+
const x = (g) => {
|
|
1151
|
+
const u = d.getBoundingClientRect();
|
|
1152
|
+
f((g.clientX - u.left) / u.width, (g.clientY - u.top) / u.height);
|
|
1153
|
+
};
|
|
1154
|
+
s.addEventListener("pointerdown", (g) => {
|
|
1155
|
+
s.setPointerCapture(g.pointerId), s.classList.add("dragging"), x(g);
|
|
1156
|
+
}), s.addEventListener("pointermove", (g) => {
|
|
1157
|
+
s.hasPointerCapture(g.pointerId) && x(g);
|
|
1158
|
+
});
|
|
1159
|
+
const b = (g) => {
|
|
1160
|
+
s.hasPointerCapture(g.pointerId) && s.releasePointerCapture(g.pointerId), s.classList.remove("dragging");
|
|
1161
|
+
};
|
|
1162
|
+
return s.addEventListener("pointerup", b), s.addEventListener("pointercancel", b), s.addEventListener("keydown", (g) => {
|
|
1163
|
+
const u = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] }[g.key];
|
|
1164
|
+
u && (g.preventDefault(), u[0] ? f(m(c, n) + u[0] / (n - 1), p, "y") : f(c, m(p, o) + u[1] / (o - 1), "x"));
|
|
1165
|
+
}), {
|
|
1166
|
+
el: s,
|
|
1167
|
+
/** Show a position without telling anyone (used to sync). Off-grid values are shown as they are. */
|
|
1168
|
+
setValue(g, u, y) {
|
|
1169
|
+
c = Math.min(1, Math.max(0, g)), p = Math.min(1, Math.max(0, u)), v(), s.setAttribute("aria-label", y ? `${t}: ${y}` : t);
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
function un({ label: t, min: e, max: n, step: o = 1, format: a = String, onInput: s }) {
|
|
1174
|
+
const r = document.createElement("div");
|
|
1175
|
+
r.className = "slider";
|
|
1176
|
+
const d = document.createElement("input");
|
|
1177
|
+
d.type = "range", d.min = String(e), d.max = String(n), d.step = String(o), d.setAttribute("aria-label", t);
|
|
1178
|
+
const l = document.createElement("span");
|
|
1179
|
+
l.className = "slider-value", l.setAttribute("aria-hidden", "true"), r.append(d, l);
|
|
1180
|
+
const c = (p) => {
|
|
1181
|
+
const m = (Number(d.value) - e) / (n - e) * 100;
|
|
1182
|
+
d.style.setProperty("--fill", `${m}%`), l.textContent = p ?? a(Number(d.value)), d.setAttribute("aria-valuetext", l.textContent);
|
|
1183
|
+
};
|
|
1184
|
+
return d.addEventListener("input", () => {
|
|
1185
|
+
c(), s(Number(d.value));
|
|
1186
|
+
}), {
|
|
1187
|
+
el: r,
|
|
1188
|
+
/** Show a value without reporting it (sync after reset / URL load). */
|
|
1189
|
+
setValue(p, m) {
|
|
1190
|
+
d.value = String(p), c(m);
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
const F = (t) => getComputedStyle(document.documentElement).getPropertyValue(t).trim();
|
|
1195
|
+
function Se(t, e) {
|
|
1196
|
+
const n = /^(-?\d*\.?\d+)([a-z%]*)$/i.exec(String(t).trim());
|
|
1197
|
+
if (!n) return null;
|
|
1198
|
+
const o = parseFloat(n[1]) * e, a = n[2] || "px";
|
|
1199
|
+
return a === "px" ? `${Math.round(o)}px` : `${+o.toFixed(3)}${a}`;
|
|
1200
|
+
}
|
|
1201
|
+
const co = 999;
|
|
1202
|
+
function hn({ id: t, title: e, hint: n, token: o, uses: a = [], presets: s, max: r, extra: d = [], extraTokens: l = () => ({}) }) {
|
|
1203
|
+
const c = (v) => s.find((f) => f.value === v), p = (v) => v >= co ? "full" : `${v}px`, m = (v) => {
|
|
1204
|
+
if (typeof v.radius == "number" && isFinite(v.radius))
|
|
1205
|
+
return { [o]: `${Math.min(Math.max(v.radius, 0), r)}px` };
|
|
1206
|
+
const f = c(v.corners);
|
|
1207
|
+
return f ? { [o]: `${f.radius}px` } : {};
|
|
1208
|
+
};
|
|
1209
|
+
return {
|
|
1210
|
+
id: t,
|
|
1211
|
+
title: e,
|
|
1212
|
+
hint: n,
|
|
1213
|
+
presets: s,
|
|
1214
|
+
uses: [o, ...a],
|
|
1215
|
+
tokens(v) {
|
|
1216
|
+
const f = v[t] || {};
|
|
1217
|
+
return { ...m(f), ...l(f) };
|
|
1218
|
+
},
|
|
1219
|
+
// One render function per knob, so a layout can place them separately.
|
|
1220
|
+
// Each takes an optional label (e.g. "Buttons" on the Corners page).
|
|
1221
|
+
parts: {
|
|
1222
|
+
corners(v, f, { label: x = "Corners" } = {}) {
|
|
1223
|
+
const b = B(x), g = X({
|
|
1224
|
+
label: `${e} corners`,
|
|
1225
|
+
options: s.map((k) => ({ value: k.value, label: k.label, title: p(k.radius) })),
|
|
1226
|
+
onSelect: (k) => f.setSection(t, { corners: k, radius: null })
|
|
1227
|
+
}), u = un({
|
|
1228
|
+
label: `${e} corner radius`,
|
|
1229
|
+
min: 0,
|
|
1230
|
+
max: r,
|
|
1231
|
+
format: p,
|
|
1232
|
+
onInput: (k) => f.setSection(t, { radius: k, corners: null })
|
|
1233
|
+
});
|
|
1234
|
+
b.append(g.el, u.el), v.append(b.el);
|
|
1235
|
+
const y = () => {
|
|
1236
|
+
const k = parseFloat((f.readToken || F)(o)), O = f.getState()[t] || {}, Z = c(O.corners) || s.find((Q) => Q.radius === k);
|
|
1237
|
+
if (g.setValue(Z ? Z.value : null), isNaN(k)) {
|
|
1238
|
+
u.setValue(0, "—"), b.setValue("");
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
u.setValue(Math.min(k, r), p(k)), b.setValue(p(k));
|
|
1242
|
+
};
|
|
1243
|
+
y(), f.onChange(y);
|
|
1244
|
+
},
|
|
1245
|
+
...Object.fromEntries(d.map((v) => [v.key, (f, x, { label: b } = {}) => f.append(le(x, { section: t, ...v, label: b || v.label }))]))
|
|
1246
|
+
},
|
|
1247
|
+
render(v, f) {
|
|
1248
|
+
Object.values(this.parts).forEach((x) => x(v, f));
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
function le(t, { section: e, key: n, label: o, options: a, fallback: s, valueText: r }) {
|
|
1253
|
+
const d = B(o), l = X({
|
|
1254
|
+
label: o,
|
|
1255
|
+
options: a,
|
|
1256
|
+
onSelect: (p) => t.setSection(e, { [n]: p })
|
|
1257
|
+
});
|
|
1258
|
+
d.append(l.el);
|
|
1259
|
+
const c = () => {
|
|
1260
|
+
const p = (t.getState()[e] || {})[n] || s;
|
|
1261
|
+
l.setValue(p), d.setValue(r ? r(p) : "");
|
|
1262
|
+
};
|
|
1263
|
+
return c(), t.onChange(c), d.el;
|
|
1264
|
+
}
|
|
1265
|
+
function po(t, { section: e, key: n, label: o, min: a, max: s, fallback: r, format: d }) {
|
|
1266
|
+
const l = B(o), c = un({ label: o, min: a, max: s, format: d, onInput: (m) => t.setSection(e, { [n]: m }) });
|
|
1267
|
+
l.append(c.el);
|
|
1268
|
+
const p = () => {
|
|
1269
|
+
const m = (t.getState()[e] || {})[n];
|
|
1270
|
+
c.setValue(typeof m == "number" ? m : r);
|
|
1271
|
+
};
|
|
1272
|
+
return p(), t.onChange(p), l.el;
|
|
1273
|
+
}
|
|
1274
|
+
const zt = (t, e) => e.map((n) => `${t}${n}`), fn = {
|
|
1275
|
+
fonts: { label: "Tokens", tokens: ["font-heading", "font-body"], kind: "text" },
|
|
1276
|
+
type: { label: "Tokens", tokens: zt("text-", ["xs", "sm", "base", "lg", "xl", "2xl", "3xl"]), kind: "length" },
|
|
1277
|
+
leading: { label: "Line height", tokens: ["leading-tight", "leading-normal"], kind: "number" },
|
|
1278
|
+
colours: { label: "Tokens", tokens: ["bg", "surface", "surface-raised", "text", "text-muted", "border", "primary", "primary-hover", "on-primary", "focus", "success", "warning", "danger"], kind: "colour" },
|
|
1279
|
+
spacing: { label: "Steps", tokens: zt("space-", [1, 2, 3, 4, 5, 6, 7, 8]), kind: "length", bar: 64 },
|
|
1280
|
+
controls: { label: "Controls", tokens: ["control-height", "control-pad-x"], kind: "length" },
|
|
1281
|
+
corners: { label: "Tokens", tokens: ["radius-control", "radius-container"], kind: "length" },
|
|
1282
|
+
borders: { label: "Tokens", tokens: ["border-width", "border-width-container"], kind: "length" },
|
|
1283
|
+
shadows: { label: "Tokens", tokens: ["shadow-sm", "shadow-md", "shadow-lg"], kind: "text" }
|
|
1284
|
+
}, q = (t) => `--twd-${t}`, je = (t, e) => Object.prototype.hasOwnProperty.call(t.getState().custom || {}, e), uo = (t, e) => fn[e].tokens.some((n) => (t.custom || {})[n] != null);
|
|
1285
|
+
function ho(t, e) {
|
|
1286
|
+
const n = /^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());
|
|
1287
|
+
return !n || e === "text" || e === "colour" ? null : n[2] === "rem" ? { n: +(parseFloat(n[1]) * 16).toFixed(1), unit: "px" } : { n: +parseFloat(n[1]).toFixed(3), unit: n[2] || (e === "length" ? "px" : "") };
|
|
1288
|
+
}
|
|
1289
|
+
function fo(t, e) {
|
|
1290
|
+
const n = t.trim();
|
|
1291
|
+
return n ? e === "length" && /^-?\d*\.?\d+$/.test(n) ? `${n}px` : n : null;
|
|
1292
|
+
}
|
|
1293
|
+
function go(t, e, n, o) {
|
|
1294
|
+
const a = document.createElement("div");
|
|
1295
|
+
a.className = `token-row kind-${n}`, a.innerHTML = `
|
|
1296
|
+
<code class="token-name" title="${q(e)}">${e}</code>
|
|
1297
|
+
${o ? '<span class="token-bar"><span></span></span>' : '<span class="token-gap"></span>'}
|
|
1298
|
+
<span class="custom-tag" hidden>Custom</span>
|
|
1299
|
+
<label class="token-value">
|
|
1300
|
+
${n === "colour" ? '<span class="token-swatch"></span>' : ""}
|
|
1301
|
+
<input type="text" spellcheck="false" aria-label="${q(e)}" ${n === "length" || n === "number" ? 'inputmode="decimal"' : ""}>
|
|
1302
|
+
<span class="token-unit"></span>
|
|
1303
|
+
</label>
|
|
1304
|
+
<button class="icon-btn token-reset" title="Back to the page’s value" aria-label="Reset ${q(e)}" hidden>${$.reset}</button>
|
|
1305
|
+
`;
|
|
1306
|
+
const s = a.querySelector("input"), r = a.querySelector(".token-unit"), d = a.querySelector(".token-bar span"), l = a.querySelector(".token-swatch"), c = a.querySelector(".custom-tag"), p = a.querySelector(".token-reset");
|
|
1307
|
+
let m = "";
|
|
1308
|
+
const v = () => {
|
|
1309
|
+
let x = s.value;
|
|
1310
|
+
m && /^-?\d*\.?\d+$/.test(x.trim()) && (x = `${x.trim()}${m}`);
|
|
1311
|
+
const b = fo(x, n), g = F(q(e));
|
|
1312
|
+
if (b === null) {
|
|
1313
|
+
je(t, e) ? t.setSection("custom", { [e]: null }) : f();
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
if (b === g && !je(t, e)) return f();
|
|
1317
|
+
t.setSection("custom", { [e]: b });
|
|
1318
|
+
};
|
|
1319
|
+
s.addEventListener("change", v), s.addEventListener("blur", () => f()), s.addEventListener("keydown", (x) => {
|
|
1320
|
+
x.key === "Enter" && (x.preventDefault(), s.blur()), x.key === "Escape" && (x.stopPropagation(), f(), s.blur());
|
|
1321
|
+
}), p.addEventListener("click", () => t.setSection("custom", { [e]: null }));
|
|
1322
|
+
function f() {
|
|
1323
|
+
const x = F(q(e)), b = je(t, e), g = ho(x, n);
|
|
1324
|
+
m = g ? g.unit : "", document.activeElement !== s && a.getRootNode().activeElement !== s && (s.value = g ? String(g.n) : x), s.title = x, r.textContent = m, r.hidden = !m, d && (d.style.width = `${Math.max(2, Math.min(100, (g ? g.n : 0) / o * 100))}%`), l && (l.style.background = x), a.classList.toggle("edited", b), c.hidden = !b, p.hidden = !b;
|
|
1325
|
+
}
|
|
1326
|
+
return f(), t.onChange(f), a;
|
|
1327
|
+
}
|
|
1328
|
+
const mo = {
|
|
1329
|
+
id: "custom",
|
|
1330
|
+
title: "Custom tokens",
|
|
1331
|
+
hint: "Any token, set to a fixed value",
|
|
1332
|
+
uses: [],
|
|
1333
|
+
// not offered for a single picked element (yet)
|
|
1334
|
+
tokens(t) {
|
|
1335
|
+
return Object.fromEntries(Object.entries(t.custom || {}).map(([e, n]) => [q(e), n]));
|
|
1336
|
+
},
|
|
1337
|
+
describe: (t, e) => ({ label: q(t), value: String(e) }),
|
|
1338
|
+
// One part per Edit page group, e.g. parts.spacing → rows for --twd-space-1 … 8.
|
|
1339
|
+
parts: Object.fromEntries(Object.entries(fn).map(([t, e]) => [t, (n, o, { label: a = e.label } = {}) => {
|
|
1340
|
+
const s = B(a);
|
|
1341
|
+
s.el.classList.add("token-field");
|
|
1342
|
+
const r = e.tokens[0], d = e.tokens[e.tokens.length - 1];
|
|
1343
|
+
s.setValue(e.tokens.length > 2 && /-\w+$/.test(r) ? `${q(r)} … ${d.split("-").pop()}` : "");
|
|
1344
|
+
const l = document.createElement("div");
|
|
1345
|
+
l.className = "token-rows", e.tokens.forEach((c) => l.append(go(o, c, e.kind, e.bar))), s.append(l), n.append(s.el);
|
|
1346
|
+
}]))
|
|
1347
|
+
}, Xe = { compact: 0.75, regular: 1, comfy: 1.25 }, Je = [0.75, 1.25], It = [1, 2, 3, 4, 5, 6, 7, 8].map((t) => `--twd-space-${t}`);
|
|
1348
|
+
function Ze(t = {}) {
|
|
1349
|
+
return typeof t.factor == "number" && isFinite(t.factor) ? Math.min(Je[1], Math.max(Je[0], t.factor)) : Xe[t.spacing] || 1;
|
|
1350
|
+
}
|
|
1351
|
+
const Dt = (t) => `${Math.round(t * 100)}%`, bo = {
|
|
1352
|
+
id: "density",
|
|
1353
|
+
title: "Density",
|
|
1354
|
+
hint: "Compact · regular · comfortable",
|
|
1355
|
+
uses: It,
|
|
1356
|
+
tokens(t) {
|
|
1357
|
+
const e = Ze(t.density);
|
|
1358
|
+
if (e === 1) return {};
|
|
1359
|
+
const n = {};
|
|
1360
|
+
return It.forEach((o) => {
|
|
1361
|
+
const a = Se(F(o), e);
|
|
1362
|
+
a && (n[o] = a);
|
|
1363
|
+
}), n;
|
|
1364
|
+
},
|
|
1365
|
+
/** How the Changes list and prompt describe a setting. */
|
|
1366
|
+
describe: (t, e) => t === "factor" ? { label: "Spacing", value: Dt(e) } : null,
|
|
1367
|
+
parts: {
|
|
1368
|
+
spacing(t, e, { label: n = "Spacing" } = {}) {
|
|
1369
|
+
const o = B(n), a = X({
|
|
1370
|
+
label: n,
|
|
1371
|
+
options: [{ value: "compact", label: "Compact" }, { value: "regular", label: "Regular" }, { value: "comfy", label: "Comfy" }],
|
|
1372
|
+
onSelect: (r) => e.setSection("density", { spacing: r, factor: null })
|
|
1373
|
+
});
|
|
1374
|
+
o.append(a.el);
|
|
1375
|
+
const s = () => {
|
|
1376
|
+
const r = e.getState().density || {}, d = Ze(r), l = Object.keys(Xe).find((c) => Xe[c] === d);
|
|
1377
|
+
a.setValue(l || null), o.setValue(uo(e.getState(), "spacing") ? "Custom" : l ? "" : Dt(d));
|
|
1378
|
+
};
|
|
1379
|
+
s(), e.onChange(s), t.append(o.el);
|
|
1380
|
+
}
|
|
1381
|
+
},
|
|
1382
|
+
render(t, e) {
|
|
1383
|
+
Object.values(this.parts).forEach((n) => n(t, e));
|
|
1384
|
+
}
|
|
1385
|
+
}, se = 60, xo = {
|
|
1386
|
+
soft: { sm: [0, 1, 2, 0, 0.06], md: [0, 4, 12, 0, 0.08], lg: [0, 12, 32, 0, 0.12] },
|
|
1387
|
+
// = tokens.css
|
|
1388
|
+
crisp: { sm: [0, 1, 1, 0, 0.12], md: [0, 2, 4, 0, 0.14], lg: [0, 4, 8, 0, 0.16] },
|
|
1389
|
+
bold: { sm: [2, 2, 0, 0, 0.85], md: [3, 3, 0, 0, 0.85], lg: [5, 5, 0, 0, 0.85] }
|
|
1390
|
+
}, ze = ([t, e, n, o, a], s) => {
|
|
1391
|
+
const r = Math.min(1, +(a * s).toFixed(3));
|
|
1392
|
+
return r <= 0 ? "none" : `${t}px ${e}px ${n}px ${o}px rgb(0 0 0 / ${r})`;
|
|
1393
|
+
}, wo = {
|
|
1394
|
+
id: "depth",
|
|
1395
|
+
title: "Depth",
|
|
1396
|
+
hint: "Flat · soft · crisp · bold",
|
|
1397
|
+
uses: ["--twd-shadow-sm", "--twd-shadow-md", "--twd-shadow-lg"],
|
|
1398
|
+
tokens(t) {
|
|
1399
|
+
const e = t.depth || {}, n = e.style || "soft", o = typeof e.intensity == "number" ? Math.min(100, Math.max(0, e.intensity)) : se;
|
|
1400
|
+
if (n === "flat")
|
|
1401
|
+
return { "--twd-shadow-sm": "none", "--twd-shadow-md": "none", "--twd-shadow-lg": "none" };
|
|
1402
|
+
const a = xo[n];
|
|
1403
|
+
if (!a) return {};
|
|
1404
|
+
if (n === "soft" && o === se) return {};
|
|
1405
|
+
const s = o / se;
|
|
1406
|
+
return {
|
|
1407
|
+
"--twd-shadow-sm": ze(a.sm, s),
|
|
1408
|
+
"--twd-shadow-md": ze(a.md, s),
|
|
1409
|
+
"--twd-shadow-lg": ze(a.lg, s)
|
|
1410
|
+
};
|
|
1411
|
+
},
|
|
1412
|
+
parts: {
|
|
1413
|
+
style(t, e, { label: n = "Style" } = {}) {
|
|
1414
|
+
t.append(le(e, {
|
|
1415
|
+
section: "depth",
|
|
1416
|
+
key: "style",
|
|
1417
|
+
label: n,
|
|
1418
|
+
fallback: "soft",
|
|
1419
|
+
options: [{ value: "flat", label: "Flat" }, { value: "soft", label: "Soft" }, { value: "crisp", label: "Crisp" }, { value: "bold", label: "Bold" }]
|
|
1420
|
+
}));
|
|
1421
|
+
},
|
|
1422
|
+
intensity(t, e, { label: n = "Intensity" } = {}) {
|
|
1423
|
+
t.append(po(e, { section: "depth", key: "intensity", label: n, min: 0, max: 100, fallback: se, format: (o) => `${o}%` }));
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
render(t, e) {
|
|
1427
|
+
Object.values(this.parts).forEach((n) => n(t, e));
|
|
1428
|
+
}
|
|
1429
|
+
}, [Bt, xe] = Je, Vt = { square: "square", soft: "soft", rounded: "rounded", pill: "large" };
|
|
1430
|
+
function vo(t) {
|
|
1431
|
+
return t > 1.06 ? "Comfy" : t < 0.94 ? "Compact" : "Regular";
|
|
1432
|
+
}
|
|
1433
|
+
const yo = {
|
|
1434
|
+
character(t, e, { label: n = "Character" } = {}) {
|
|
1435
|
+
const o = B(n), a = lo({
|
|
1436
|
+
label: n,
|
|
1437
|
+
ends: { top: "Spacious", bottom: "Compact", left: "Subtle", right: "Bold" },
|
|
1438
|
+
onInput: (r, d) => {
|
|
1439
|
+
const l = +(xe - d * (xe - Bt)).toFixed(3), c = { intensity: Math.round(r * 100) };
|
|
1440
|
+
(e.getState().depth || {}).style === "flat" && (c.style = null), e.setSections({ density: { factor: l === 1 ? null : l, spacing: null }, depth: c }, "character");
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
o.append(a.el);
|
|
1444
|
+
const s = () => {
|
|
1445
|
+
const r = e.getState(), d = Ze(r.density), l = r.depth || {}, c = l.style === "flat" ? 0 : typeof l.intensity == "number" ? l.intensity : se, p = `${vo(d)} · ${c}%`;
|
|
1446
|
+
a.setValue(c / 100, (xe - d) / (xe - Bt), p), o.setValue(p);
|
|
1447
|
+
};
|
|
1448
|
+
s(), e.onChange(s), t.append(o.el);
|
|
1449
|
+
},
|
|
1450
|
+
corners(t, e, { label: n = "Corners", buttons: o, cards: a }) {
|
|
1451
|
+
const s = B(n), r = X({
|
|
1452
|
+
label: n,
|
|
1453
|
+
options: o.presets.map((c) => ({ value: c.value, label: c.label })),
|
|
1454
|
+
onSelect: (c) => e.setSections({
|
|
1455
|
+
buttons: { corners: c, radius: null },
|
|
1456
|
+
cards: { corners: Vt[c], radius: null }
|
|
1457
|
+
})
|
|
1458
|
+
});
|
|
1459
|
+
s.append(r.el);
|
|
1460
|
+
const d = (c, p, m) => {
|
|
1461
|
+
if (p.radius != null) return null;
|
|
1462
|
+
if (p.corners) return p.corners;
|
|
1463
|
+
const v = parseFloat(F(m));
|
|
1464
|
+
return c.presets.find((f) => f.radius === v)?.value || null;
|
|
1465
|
+
}, l = () => {
|
|
1466
|
+
const c = e.getState(), p = d(o, c.buttons || {}, "--twd-radius-control"), m = d(a, c.cards || {}, "--twd-radius-container");
|
|
1467
|
+
r.setValue(p && Vt[p] === m ? p : null);
|
|
1468
|
+
const v = (b) => {
|
|
1469
|
+
const g = parseFloat(F(b));
|
|
1470
|
+
return isNaN(g) ? "" : g >= 999 ? "full" : `${g}px`;
|
|
1471
|
+
}, f = v("--twd-radius-control"), x = v("--twd-radius-container");
|
|
1472
|
+
s.setValue(f && x ? `${f} · ${x}` : "");
|
|
1473
|
+
};
|
|
1474
|
+
l(), e.onChange(l), t.append(s.el);
|
|
1475
|
+
}
|
|
1476
|
+
}, Ie = [
|
|
1477
|
+
{ key: "page-pad", label: "Page padding", step: 5 },
|
|
1478
|
+
{ key: "section-gap", label: "Section gap", step: 6 },
|
|
1479
|
+
{ key: "card-pad", label: "Card padding", step: 5 },
|
|
1480
|
+
{ key: "card-gap", label: "Card gap", step: 2 },
|
|
1481
|
+
{ key: "control-gap", label: "Control gap", step: 3 },
|
|
1482
|
+
{ key: "control-pad-x", label: "Control padding", step: null }
|
|
1483
|
+
], ko = [1, 2, 3, 4, 5, 6, 7, 8], So = /^var\(\s*--twd-space-(\d)\s*\)$/, De = (t) => {
|
|
1484
|
+
const e = /^(-?\d*\.?\d+)(px|rem)?$/.exec(String(t).trim());
|
|
1485
|
+
return e ? +(parseFloat(e[1]) * (e[2] === "rem" ? 16 : 1)).toFixed(1) : null;
|
|
1486
|
+
};
|
|
1487
|
+
function Eo(t, e, n) {
|
|
1488
|
+
const o = `--twd-${e.key}`, a = document.createElement("div");
|
|
1489
|
+
a.className = "role-row", n || a.classList.add("unused"), a.innerHTML = `
|
|
1490
|
+
<span class="role-label" title="${o}${n ? "" : " (not used on this page)"}">${e.label}</span>
|
|
1491
|
+
<label class="token-value role-fixed" hidden><input type="text" inputmode="decimal" spellcheck="false" aria-label="${e.label}, fixed value"><span class="token-unit">px</span></label>
|
|
1492
|
+
<span class="step-select">
|
|
1493
|
+
<select aria-label="${e.label}"></select>
|
|
1494
|
+
<span class="step-chevron" aria-hidden="true">${$.down}</span>
|
|
1495
|
+
</span>
|
|
1496
|
+
<button class="icon-btn token-reset" title="Back to the default" aria-label="Reset ${e.label}" hidden>${$.reset}</button>
|
|
1497
|
+
`;
|
|
1498
|
+
const s = a.querySelector("select"), r = a.querySelector(".role-fixed"), d = r.querySelector("input"), l = a.querySelector(".token-reset"), c = () => (t.getState().custom || {})[e.key], p = () => {
|
|
1499
|
+
const b = c();
|
|
1500
|
+
if (b == null) return e.step ? { step: e.step } : { fixed: F(o) };
|
|
1501
|
+
const g = So.exec(b);
|
|
1502
|
+
return g ? { step: +g[1] } : { fixed: b };
|
|
1503
|
+
}, m = (b) => t.setSection("custom", { [e.key]: b }), v = (b) => e.step ? b === `var(--twd-space-${e.step})` : !1;
|
|
1504
|
+
s.addEventListener("change", () => {
|
|
1505
|
+
if (s.value === "fixed") {
|
|
1506
|
+
const g = De(F(o));
|
|
1507
|
+
m(g != null ? `${g}px` : F(o)), d.focus(), d.select();
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
const b = `var(--twd-space-${s.value})`;
|
|
1511
|
+
m(v(b) ? null : b);
|
|
1512
|
+
});
|
|
1513
|
+
const f = () => {
|
|
1514
|
+
const b = d.value.trim();
|
|
1515
|
+
if (!b) return m(null);
|
|
1516
|
+
m(/^-?\d*\.?\d+$/.test(b) ? `${b}px` : b);
|
|
1517
|
+
};
|
|
1518
|
+
d.addEventListener("change", f), d.addEventListener("blur", () => x()), d.addEventListener("keydown", (b) => {
|
|
1519
|
+
b.key === "Enter" && (b.preventDefault(), d.blur()), b.key === "Escape" && (b.stopPropagation(), x(), d.blur());
|
|
1520
|
+
}), l.addEventListener("click", () => m(null));
|
|
1521
|
+
function x() {
|
|
1522
|
+
const b = p();
|
|
1523
|
+
if (s.innerHTML = ko.map((u) => `<option value="${u}">space-${u} · ${De(F(`--twd-space-${u}`)) ?? "?"}</option>`).join("") + '<option value="fixed">Fixed</option>', s.value = b.step ? String(b.step) : "fixed", r.hidden = !b.fixed, a.classList.toggle("is-fixed", !!b.fixed), b.fixed && a.getRootNode().activeElement !== d) {
|
|
1524
|
+
const u = De(b.fixed);
|
|
1525
|
+
d.value = u != null ? String(u) : b.fixed;
|
|
1526
|
+
}
|
|
1527
|
+
const g = c() != null;
|
|
1528
|
+
a.classList.toggle("edited", g), l.hidden = !g;
|
|
1529
|
+
}
|
|
1530
|
+
return x(), t.onChange(x), a;
|
|
1531
|
+
}
|
|
1532
|
+
const $o = {
|
|
1533
|
+
spacing(t, e, { label: n = "Roles" } = {}) {
|
|
1534
|
+
const o = so(), a = B(n);
|
|
1535
|
+
a.el.classList.add("token-field");
|
|
1536
|
+
const s = Ie.filter((l) => !o.has(`--twd-${l.key}`));
|
|
1537
|
+
a.setValue(s.length === Ie.length ? "not used on this page" : "");
|
|
1538
|
+
const r = document.createElement("div");
|
|
1539
|
+
r.className = "token-rows", Ie.forEach((l) => r.append(Eo(e, l, o.has(`--twd-${l.key}`)))), a.append(r), t.append(a.el);
|
|
1540
|
+
const d = document.createElement("p");
|
|
1541
|
+
d.className = "role-note", d.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(d);
|
|
1542
|
+
}
|
|
1543
|
+
}, Co = [
|
|
1544
|
+
["simple", "character"],
|
|
1545
|
+
// first, by Ravi's call (2026-10-13); the A1b mock has it last
|
|
1546
|
+
["typography", "fonts"],
|
|
1547
|
+
["colour", "brand"],
|
|
1548
|
+
["colour", "neutral"],
|
|
1549
|
+
["colour", "mode"],
|
|
1550
|
+
"warnings",
|
|
1551
|
+
["simple", "corners"]
|
|
1552
|
+
], we = [
|
|
1553
|
+
{ id: "fonts", title: "Fonts", parts: [["typography", "fonts"], ["custom", "fonts"]] },
|
|
1554
|
+
{ id: "type", title: "Type scale", parts: [["typography", "base"], ["typography", "scale"], ["custom", "type"], ["custom", "leading"]] },
|
|
1555
|
+
{ id: "colours", title: "Colours", parts: [["colour", "brand"], ["colour", "neutral"], ["colour", "mode"], "warnings", ["custom", "colours"]] },
|
|
1556
|
+
{ id: "spacing", title: "Spacing", parts: [{ tabs: [
|
|
1557
|
+
{ label: "Scale", parts: [["density", "spacing"], ["custom", "spacing"], ["buttons", "size", { label: "Control size" }], ["custom", "controls"]] },
|
|
1558
|
+
{ label: "Roles", parts: [["roles", "spacing"]] }
|
|
1559
|
+
] }] },
|
|
1560
|
+
{ id: "corners", title: "Corners", parts: [["buttons", "corners", { label: "Buttons" }], ["cards", "corners", { label: "Cards" }], ["custom", "corners"]] },
|
|
1561
|
+
{ id: "borders", title: "Borders", parts: [["cards", "border", { label: "Cards" }], ["custom", "borders"]] },
|
|
1562
|
+
{ id: "shadows", title: "Shadows", parts: [["depth", "style"], ["depth", "intensity"], ["custom", "shadows"]] }
|
|
1563
|
+
], A = (t) => String(t).replace(/[&<>"]/g, (e) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[e]);
|
|
1564
|
+
function Mo({ sections: t, store: e, view: n }) {
|
|
1565
|
+
const o = document.createElement("div");
|
|
1566
|
+
o.id = "twiddle-root", document.body.appendChild(o);
|
|
1567
|
+
const a = o.attachShadow({ mode: "open" }), s = (i) => i.map((h) => `
|
|
1568
|
+
<details data-section="${h.id}">
|
|
1569
|
+
<summary>${h.title}<span class="chev">${$.chevron}</span></summary>
|
|
1570
|
+
<div class="section-body"><div class="placeholder">${h.hint}</div></div>
|
|
1571
|
+
</details>
|
|
1572
|
+
`).join("");
|
|
1573
|
+
a.innerHTML = `
|
|
1574
|
+
<style>${Dn}</style>
|
|
1575
|
+
<div class="pick-box" hidden><span class="pick-tag"></span></div>
|
|
1576
|
+
<div class="wrap">
|
|
1577
|
+
<div class="toast" role="status"></div>
|
|
1578
|
+
<div class="panel" role="dialog" aria-label="Panel" hidden>
|
|
1579
|
+
<div class="head">
|
|
1580
|
+
<span class="brand">Panel</span>
|
|
1581
|
+
<button class="icon-btn" data-action="pick" title="Pick an element to tweak on its own" aria-label="Pick an element" aria-pressed="false">${$.pick}</button>
|
|
1582
|
+
<button class="icon-btn" data-action="compare" title="Hold to see the original (Alt+C toggles)" aria-label="Compare with original" aria-pressed="false">${$.compare}</button>
|
|
1583
|
+
<button class="icon-btn" data-action="copy" title="Copy link to this version" aria-label="Copy link">${$.link}</button>
|
|
1584
|
+
<button class="icon-btn" data-action="reset" title="Reset to defaults" aria-label="Reset">${$.reset}</button>
|
|
1585
|
+
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${$.close}</button>
|
|
1586
|
+
</div>
|
|
1587
|
+
<div class="head subhead" hidden>
|
|
1588
|
+
<button class="icon-btn" data-action="page-back" title="Back" aria-label="Back">${$.back}</button>
|
|
1589
|
+
<span class="brand page-title"></span>
|
|
1590
|
+
<button class="icon-btn" data-action="close" title="Close (Alt+T)" aria-label="Close">${$.close}</button>
|
|
1591
|
+
</div>
|
|
1592
|
+
<div class="original-pill" hidden>Showing original</div>
|
|
1593
|
+
<div class="body" data-view="global"><div class="setup-hint" role="status" hidden></div>
|
|
1594
|
+
<div class="page page-fields" data-page="simple"></div>
|
|
1595
|
+
<div class="page" data-page="edit" hidden>
|
|
1596
|
+
${we.map((i) => `<button class="nav-row" data-go="${i.id}">${i.title}<span class="chev">${$.chevron}</span></button>`).join("")}
|
|
1597
|
+
</div>
|
|
1598
|
+
${we.map((i) => `<div class="page page-fields" data-page="${i.id}" hidden></div>`).join("")}
|
|
1599
|
+
</div>
|
|
1600
|
+
<div class="body" data-view="local" hidden></div>
|
|
1601
|
+
<div class="body" data-view="changes" hidden></div>
|
|
1602
|
+
<button class="edit-row" data-action="edit">${$.sliders}<span>Edit every token</span><span class="chev">${$.chevron}</span></button>
|
|
1603
|
+
<div class="foot">
|
|
1604
|
+
<button class="icon-btn" data-action="undo" title="Undo (⌘Z)" aria-label="Undo">${$.undo}</button>
|
|
1605
|
+
<button class="icon-btn" data-action="redo" title="Redo (⇧⌘Z)" aria-label="Redo">${$.redo}</button>
|
|
1606
|
+
<button class="text-btn" data-action="changes" aria-pressed="false">Changes <span class="badge" hidden></span></button>
|
|
1607
|
+
<span class="spacer"></span>
|
|
1608
|
+
<div class="apply">
|
|
1609
|
+
<button class="apply-btn" data-action="apply" title="Copy these tweaks as a prompt for your coding agent">${$.copy}Copy prompt</button>
|
|
1610
|
+
<button class="apply-menu-btn" data-action="apply-menu" aria-label="Copy prompt options" aria-haspopup="true" aria-expanded="false">${$.down}</button>
|
|
1611
|
+
<div class="menu apply-menu" role="menu" hidden>
|
|
1612
|
+
<button role="menuitemradio" data-apply-mode="preview">Preview before copying</button>
|
|
1613
|
+
<button role="menuitemradio" data-apply-mode="copy">Copy without preview</button>
|
|
1614
|
+
</div>
|
|
1615
|
+
</div>
|
|
1616
|
+
</div>
|
|
1617
|
+
</div>
|
|
1618
|
+
<button class="launcher" aria-label="Open Panel" aria-expanded="false">${$.dial}</button>
|
|
1619
|
+
</div>
|
|
1620
|
+
<div class="dialog-backdrop" hidden>
|
|
1621
|
+
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="twd-dialog-title">
|
|
1622
|
+
<div class="dialog-head">
|
|
1623
|
+
<span id="twd-dialog-title">Prompt for your agent</span>
|
|
1624
|
+
<button class="icon-btn" data-action="dialog-close" aria-label="Close">${$.close}</button>
|
|
1625
|
+
</div>
|
|
1626
|
+
<textarea class="prompt-text" readonly spellcheck="false" aria-label="Generated prompt"></textarea>
|
|
1627
|
+
<div class="dialog-foot">
|
|
1628
|
+
<span class="muted">Paste it into Claude Code, Cursor or any agent working on this prototype.</span>
|
|
1629
|
+
<button class="apply-btn" data-action="dialog-copy">Copy prompt</button>
|
|
1630
|
+
</div>
|
|
1631
|
+
</div>
|
|
1632
|
+
</div>
|
|
1633
|
+
`;
|
|
1634
|
+
const r = (i) => a.querySelector(i), d = r(".panel"), l = r(".launcher"), c = r(".toast"), p = { global: r('[data-view="global"]'), local: r('[data-view="local"]'), changes: r('[data-view="changes"]') }, m = r(".pick-box"), v = r(".pick-tag"), f = { getState: e.getState, setSection: e.setSection, setSections: e.setSections, onChange: e.onChange };
|
|
1635
|
+
function x(i, h, w) {
|
|
1636
|
+
h.forEach((E) => {
|
|
1637
|
+
if (typeof E.render != "function") return;
|
|
1638
|
+
const T = i.querySelector(`[data-section="${E.id}"] .section-body`);
|
|
1639
|
+
T.innerHTML = "", E.render(T, w);
|
|
1640
|
+
});
|
|
1641
|
+
const S = [...i.querySelectorAll("details")];
|
|
1642
|
+
return S.forEach((E) => E.addEventListener("toggle", () => {
|
|
1643
|
+
E.open && S.forEach((T) => {
|
|
1644
|
+
T !== E && (T.open = !1);
|
|
1645
|
+
});
|
|
1646
|
+
})), S;
|
|
1647
|
+
}
|
|
1648
|
+
const b = Object.fromEntries(t.map((i) => [i.id, i]));
|
|
1649
|
+
function g(i, h) {
|
|
1650
|
+
h.forEach((w) => {
|
|
1651
|
+
if (w === "warnings") {
|
|
1652
|
+
const L = document.createElement("div");
|
|
1653
|
+
L.className = "warnings page-warnings", i.append(L);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
if (w.tabs) {
|
|
1657
|
+
const L = w.tabs.map((P) => {
|
|
1658
|
+
const N = document.createElement("div");
|
|
1659
|
+
return N.className = "tab-pane", g(N, P.parts), N;
|
|
1660
|
+
}), re = X({
|
|
1661
|
+
label: "View",
|
|
1662
|
+
options: w.tabs.map((P, N) => ({ value: String(N), label: P.label })),
|
|
1663
|
+
onSelect: (P) => {
|
|
1664
|
+
re.setValue(P), L.forEach((N, Sn) => {
|
|
1665
|
+
N.hidden = String(Sn) !== P;
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
re.setValue("0"), L.forEach((P, N) => {
|
|
1670
|
+
P.hidden = N !== 0;
|
|
1671
|
+
}), i.append(re.el, ...L);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
const [S, E, T = {}] = w, V = S === "simple" ? yo[E] : S === "roles" ? $o[E] : b[S]?.parts?.[E];
|
|
1675
|
+
V && V(i, f, { ...T, buttons: b.buttons, cards: b.cards });
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
g(p.global.querySelector('[data-page="simple"]'), Co), we.forEach((i) => g(p.global.querySelector(`[data-page="${i.id}"]`), i.parts));
|
|
1679
|
+
const u = r(".head:not(.subhead)"), y = r(".subhead"), k = r(".edit-row");
|
|
1680
|
+
let O = "simple";
|
|
1681
|
+
function Z() {
|
|
1682
|
+
const i = ce === "global", h = i && O !== "simple";
|
|
1683
|
+
u.hidden = h, y.hidden = !h, k.hidden = !(i && O === "simple"), h && (r(".page-title").textContent = O === "edit" ? "Edit every token" : we.find((w) => w.id === O).title);
|
|
1684
|
+
}
|
|
1685
|
+
function Q(i) {
|
|
1686
|
+
O = i, p.global.querySelectorAll(".page").forEach((h) => {
|
|
1687
|
+
h.hidden = h.dataset.page !== i;
|
|
1688
|
+
}), p.global.scrollTop = 0, Z();
|
|
1689
|
+
}
|
|
1690
|
+
const gn = () => Q(O === "edit" ? "simple" : "edit"), de = (i) => i.map((h) => `<div class="warning">${$.warn}<span>${A(h.text)}</span></div>`).join("");
|
|
1691
|
+
let ce = "global";
|
|
1692
|
+
function Ce(i) {
|
|
1693
|
+
ce = i, Object.entries(p).forEach(([h, w]) => {
|
|
1694
|
+
w.hidden = h !== i;
|
|
1695
|
+
}), r('[data-action="changes"]').setAttribute("aria-pressed", String(i === "changes")), i !== "local" && bn(), i === "changes" && bt(), Z();
|
|
1696
|
+
}
|
|
1697
|
+
function ee() {
|
|
1698
|
+
d.hidden = !1, l.setAttribute("aria-expanded", "true"), l.setAttribute("aria-label", "Close Panel"), be("panel", "open");
|
|
1699
|
+
}
|
|
1700
|
+
function Me() {
|
|
1701
|
+
d.hidden = !0, l.setAttribute("aria-expanded", "false"), l.setAttribute("aria-label", "Open Panel"), be("panel", "closed"), pe();
|
|
1702
|
+
}
|
|
1703
|
+
function Ae() {
|
|
1704
|
+
d.hidden ? ee() : Me();
|
|
1705
|
+
}
|
|
1706
|
+
let nt;
|
|
1707
|
+
function U(i) {
|
|
1708
|
+
c.textContent = i, c.classList.add("show"), clearTimeout(nt), nt = setTimeout(() => c.classList.remove("show"), 1600);
|
|
1709
|
+
}
|
|
1710
|
+
function te(i, h) {
|
|
1711
|
+
navigator.clipboard.writeText(i).then(() => U(h)).catch(() => U("Couldn’t copy — select the text and copy it instead"));
|
|
1712
|
+
}
|
|
1713
|
+
let Te = null;
|
|
1714
|
+
function G(i, h) {
|
|
1715
|
+
if (Te = i, !i) {
|
|
1716
|
+
m.hidden = !0;
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
const w = i.getBoundingClientRect();
|
|
1720
|
+
Object.assign(m.style, { top: `${w.top}px`, left: `${w.left}px`, width: `${w.width}px`, height: `${w.height}px` }), v.textContent = h || Ye(i), m.hidden = !1;
|
|
1721
|
+
}
|
|
1722
|
+
const ot = () => {
|
|
1723
|
+
Te && !m.hidden && G(Te, v.textContent);
|
|
1724
|
+
};
|
|
1725
|
+
window.addEventListener("scroll", ot, !0), window.addEventListener("resize", ot);
|
|
1726
|
+
let ne = !1;
|
|
1727
|
+
const at = r('[data-action="pick"]'), rt = (i) => i.composedPath().includes(o);
|
|
1728
|
+
function st(i) {
|
|
1729
|
+
if (rt(i)) return;
|
|
1730
|
+
const h = i.target;
|
|
1731
|
+
h && h.nodeType === 1 && h !== document.documentElement && h !== document.body && G(h);
|
|
1732
|
+
}
|
|
1733
|
+
function it(i) {
|
|
1734
|
+
if (rt(i) || (i.preventDefault(), i.stopPropagation(), i.type !== "click")) return;
|
|
1735
|
+
const h = i.target;
|
|
1736
|
+
!h || h === document.documentElement || h === document.body || (pe(), pt(h));
|
|
1737
|
+
}
|
|
1738
|
+
function lt(i) {
|
|
1739
|
+
i.key === "Escape" && (i.preventDefault(), pe(), M && G(M.el));
|
|
1740
|
+
}
|
|
1741
|
+
const dt = ["pointerdown", "mousedown", "mouseup", "click"];
|
|
1742
|
+
function ct() {
|
|
1743
|
+
if (!ne) {
|
|
1744
|
+
if (ne = !0, !document.getElementById("twiddle-picking")) {
|
|
1745
|
+
const i = document.createElement("style");
|
|
1746
|
+
i.id = "twiddle-picking", i.textContent = "html.twd-picking, html.twd-picking * { cursor: crosshair !important; }", document.head.appendChild(i);
|
|
1747
|
+
}
|
|
1748
|
+
ee(), at.setAttribute("aria-pressed", "true"), document.documentElement.classList.add("twd-picking"), document.addEventListener("pointermove", st, !0), dt.forEach((i) => document.addEventListener(i, it, !0)), document.addEventListener("keydown", lt, !0), U("Click an element · Esc to cancel");
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
function pe() {
|
|
1752
|
+
ne && (ne = !1, at.setAttribute("aria-pressed", "false"), document.documentElement.classList.remove("twd-picking"), document.removeEventListener("pointermove", st, !0), dt.forEach((i) => document.removeEventListener(i, it, !0)), document.removeEventListener("keydown", lt, !0), G(M ? M.el : null));
|
|
1753
|
+
}
|
|
1754
|
+
let M = null;
|
|
1755
|
+
function mn(i, h) {
|
|
1756
|
+
const w = [];
|
|
1757
|
+
return {
|
|
1758
|
+
ctx: {
|
|
1759
|
+
getState: () => (e.getState().scopes || {})[i] || {},
|
|
1760
|
+
setSection: (S, E) => e.setScope(i, S, E),
|
|
1761
|
+
onChange: (S) => {
|
|
1762
|
+
const E = e.onChange(S);
|
|
1763
|
+
return w.push(E), E;
|
|
1764
|
+
},
|
|
1765
|
+
readToken: (S) => getComputedStyle(h).getPropertyValue(S).trim()
|
|
1766
|
+
},
|
|
1767
|
+
dispose: () => w.forEach((S) => S())
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
function pt(i, h) {
|
|
1771
|
+
const w = _n(i), S = e.getState().scopes || {};
|
|
1772
|
+
h || (h = !S[w.elementSelector] && w.classSelector && S[w.classSelector] ? "class" : "element"), M && M.dispose(), M = { el: i, info: w, target: h, dispose: () => {
|
|
1773
|
+
} }, Le(), Ce("local"), ee(), G(i);
|
|
1774
|
+
}
|
|
1775
|
+
const ut = () => M.target === "class" ? M.info.classSelector : M.info.elementSelector;
|
|
1776
|
+
function Le() {
|
|
1777
|
+
M.dispose();
|
|
1778
|
+
const { info: i } = M, h = t.filter((P) => (P.uses || []).some((N) => i.tokens.has(N))), w = p.local;
|
|
1779
|
+
if (w.innerHTML = `
|
|
1780
|
+
<div class="scope-bar">
|
|
1781
|
+
<div class="scope-head">
|
|
1782
|
+
<button class="icon-btn" data-action="back" title="Back to the whole page" aria-label="Back to the whole page">${$.back}</button>
|
|
1783
|
+
<div class="scope-name">
|
|
1784
|
+
<code>${A(Ye(i.el))}</code>
|
|
1785
|
+
${i.text ? `<span class="muted">“${A(i.text)}”</span>` : ""}
|
|
1786
|
+
</div>
|
|
1787
|
+
</div>
|
|
1788
|
+
<div class="scope-target"></div>
|
|
1789
|
+
<div class="warnings"></div>
|
|
1790
|
+
</div>
|
|
1791
|
+
${h.length ? s(h) : '<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>'}
|
|
1792
|
+
`, i.classSelector) {
|
|
1793
|
+
const P = X({
|
|
1794
|
+
label: "Apply to",
|
|
1795
|
+
options: [
|
|
1796
|
+
{ value: "element", label: "This element" },
|
|
1797
|
+
{ value: "class", label: `All ${i.classSelector} (${i.classCount})`, title: `Every element matching ${i.classSelector}` }
|
|
1798
|
+
],
|
|
1799
|
+
onSelect: (N) => {
|
|
1800
|
+
N === "class" && e.moveScope(i.elementSelector, i.classSelector), M.target = N, Le();
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
P.setValue(M.target), w.querySelector(".scope-target").append(P.el);
|
|
1804
|
+
}
|
|
1805
|
+
const { ctx: S, dispose: E } = mn(ut(), i.el), T = x(w, h, S);
|
|
1806
|
+
T[0] && (T[0].open = !0);
|
|
1807
|
+
const V = w.querySelector(".warnings"), L = () => {
|
|
1808
|
+
const P = i.tokens.size ? eo(i.el) : null;
|
|
1809
|
+
V.innerHTML = P ? de([P]) : "";
|
|
1810
|
+
};
|
|
1811
|
+
L();
|
|
1812
|
+
const re = e.onChange(L);
|
|
1813
|
+
M.dispose = () => {
|
|
1814
|
+
E(), re();
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
function ht() {
|
|
1818
|
+
if (!M || !M.info.classSelector) return;
|
|
1819
|
+
const i = e.getState().scopes || {}, h = M.target === "class" ? "element" : "class", w = h === "class" ? M.info.classSelector : M.info.elementSelector;
|
|
1820
|
+
!i[ut()] && i[w] && (M.target = h, Le());
|
|
1821
|
+
}
|
|
1822
|
+
const ft = () => {
|
|
1823
|
+
e.undo(), ht();
|
|
1824
|
+
}, gt = () => {
|
|
1825
|
+
e.redo(), ht();
|
|
1826
|
+
};
|
|
1827
|
+
function bn() {
|
|
1828
|
+
M && (M.dispose(), M = null, p.local.innerHTML = "", G(null));
|
|
1829
|
+
}
|
|
1830
|
+
const mt = (i) => i.kind === "global" ? "Whole page" : i.kind === "class" ? `All ${i.selector}` : "One element";
|
|
1831
|
+
function bt() {
|
|
1832
|
+
const i = Lt(e.getState(), t), h = Ne(), w = p.changes;
|
|
1833
|
+
if (!i.length) {
|
|
1834
|
+
w.innerHTML = `<div class="empty">No changes yet. Tweak a section, or pick an element to change it on its own.</div>${de(h)}`;
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
const S = /* @__PURE__ */ new Map();
|
|
1838
|
+
i.forEach((E) => {
|
|
1839
|
+
const T = E.selector || "";
|
|
1840
|
+
S.has(T) || S.set(T, []), S.get(T).push(E);
|
|
1841
|
+
}), w.innerHTML = (h.length ? `<div class="warnings">${de(h)}</div>` : "") + [...S].map(([E, T]) => `
|
|
1842
|
+
<div class="change-group">
|
|
1843
|
+
<div class="change-scope">
|
|
1844
|
+
${E ? `<button class="link-btn" data-select="${A(E)}" title="Select it on the page">${A(mt(T[0]))}</button><code>${A(E)}</code>` : `<span>${mt(T[0])}</span>`}
|
|
1845
|
+
</div>
|
|
1846
|
+
${T.map((V) => `
|
|
1847
|
+
<div class="change-row">
|
|
1848
|
+
<div class="change-main">
|
|
1849
|
+
<div class="change-title">${A(V.title)} <span class="muted">›</span> ${V.settings.map((L) => `${A(L.label)}: <b>${A(L.value)}</b>`).join(", ")}</div>
|
|
1850
|
+
${V.tokens.map((L) => `<div class="change-token"><code>${A(L.name)}</code> ${L.from ? `${A(L.from)} → ` : ""}${A(L.to)}</div>`).join("")}
|
|
1851
|
+
</div>
|
|
1852
|
+
<button class="icon-btn" data-remove="${A(V.sectionId)}" data-remove-scope="${A(E)}" aria-label="Remove ${A(V.title)} change">${$.close}</button>
|
|
1853
|
+
</div>`).join("")}
|
|
1854
|
+
</div>`).join("");
|
|
1855
|
+
}
|
|
1856
|
+
function xn(i, h) {
|
|
1857
|
+
if (h) return e.removeScope(h, i);
|
|
1858
|
+
const w = e.getState()[i] || {};
|
|
1859
|
+
e.setSection(i, Object.fromEntries(Object.keys(w).map((S) => [S, null])));
|
|
1860
|
+
}
|
|
1861
|
+
const oe = r(".apply-menu"), xt = r('[data-action="apply-menu"]'), wt = () => Fe("apply-mode") === "copy" ? "copy" : "preview";
|
|
1862
|
+
function wn() {
|
|
1863
|
+
oe.querySelectorAll("[data-apply-mode]").forEach((i) => i.setAttribute("aria-checked", String(i.dataset.applyMode === wt())));
|
|
1864
|
+
}
|
|
1865
|
+
function ue(i) {
|
|
1866
|
+
oe.hidden = !i, xt.setAttribute("aria-expanded", String(i)), i && wn();
|
|
1867
|
+
}
|
|
1868
|
+
function vt() {
|
|
1869
|
+
return Xn(Lt(e.getState(), t), { warnings: Ne() });
|
|
1870
|
+
}
|
|
1871
|
+
const he = r(".dialog-backdrop"), yt = r(".prompt-text");
|
|
1872
|
+
function vn() {
|
|
1873
|
+
const i = vt();
|
|
1874
|
+
if (!i) return U("No changes to copy yet");
|
|
1875
|
+
if (wt() === "copy") return te(i, "Prompt copied · paste it into your agent");
|
|
1876
|
+
yt.value = i, he.hidden = !1, r('[data-action="dialog-copy"]').focus();
|
|
1877
|
+
}
|
|
1878
|
+
function Pe() {
|
|
1879
|
+
he.hidden = !0, r('[data-action="apply"]').focus();
|
|
1880
|
+
}
|
|
1881
|
+
const K = r('[data-action="compare"]'), yn = r(".original-pill");
|
|
1882
|
+
function fe(i) {
|
|
1883
|
+
i ? n.suspend() : n.resume(), K.setAttribute("aria-pressed", String(i)), yn.hidden = !i;
|
|
1884
|
+
}
|
|
1885
|
+
let kt = null, ae = !1;
|
|
1886
|
+
K.addEventListener("pointerdown", () => {
|
|
1887
|
+
ae = !1, kt = setTimeout(() => {
|
|
1888
|
+
ae = !0, fe(!0);
|
|
1889
|
+
}, 180);
|
|
1890
|
+
});
|
|
1891
|
+
const St = () => {
|
|
1892
|
+
clearTimeout(kt), ae && (fe(!1), ae = !1);
|
|
1893
|
+
};
|
|
1894
|
+
K.addEventListener("pointerup", St), K.addEventListener("pointerleave", St);
|
|
1895
|
+
const ge = r(".setup-hint"), Et = `setup-hint-dismissed:${location.pathname}`, kn = {
|
|
1896
|
+
"no-tokens": {
|
|
1897
|
+
title: "The Panel tokens aren’t loaded",
|
|
1898
|
+
text: "The knobs change the --twd-* variables, but this page doesn’t load them. Add the token stylesheet before your own CSS:",
|
|
1899
|
+
snippets: jt,
|
|
1900
|
+
link: `${Nt}/#install`
|
|
1901
|
+
},
|
|
1902
|
+
unused: {
|
|
1903
|
+
title: "Nothing here uses the Panel tokens yet",
|
|
1904
|
+
text: "The tokens are loaded, but no style on this page uses var(--twd-…), so the knobs won’t change anything. Ask your coding agent to convert the styles:",
|
|
1905
|
+
copy: "Copy prompt for your agent",
|
|
1906
|
+
copied: "Prompt copied",
|
|
1907
|
+
text2copy: io,
|
|
1908
|
+
link: `${Nt}/#tokens`
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
let z = null;
|
|
1912
|
+
function me() {
|
|
1913
|
+
const i = ro();
|
|
1914
|
+
z = Fe(Et) === i ? null : kn[i] || null, l.toggleAttribute("data-hint", !!z), ge.hidden = !z, z && (ge.innerHTML = `
|
|
1915
|
+
<div class="setup-head">${$.warn}<b>${A(z.title)}</b>
|
|
1916
|
+
<button class="icon-btn" data-action="setup-dismiss" title="Hide this message" aria-label="Hide this message">${$.close}</button>
|
|
1917
|
+
</div>
|
|
1918
|
+
<p>${A(z.text)}</p>
|
|
1919
|
+
${(z.snippets || []).map((h, w) => `
|
|
1920
|
+
<div class="setup-snippet">
|
|
1921
|
+
<span>${A(h.label)}</span>
|
|
1922
|
+
<div class="setup-code"><code>${A(h.code)}</code><button class="text-btn" data-setup-snippet="${w}">Copy</button></div>
|
|
1923
|
+
</div>`).join("")}
|
|
1924
|
+
<div class="setup-actions">
|
|
1925
|
+
${z.copy ? `<button class="apply-btn" data-action="setup-copy">${A(z.copy)}</button>` : ""}
|
|
1926
|
+
<a href="${z.link}" target="_blank" rel="noopener">Setup guide</a>
|
|
1927
|
+
</div>`, ge.dataset.status = i);
|
|
1928
|
+
}
|
|
1929
|
+
me(), document.readyState !== "complete" && window.addEventListener("load", me, { once: !0 }), setTimeout(me, 1500);
|
|
1930
|
+
const $t = r(".badge");
|
|
1931
|
+
function Ct() {
|
|
1932
|
+
r('[data-action="undo"]').disabled = !e.canUndo(), r('[data-action="redo"]').disabled = !e.canRedo();
|
|
1933
|
+
const i = Wn(e.getState());
|
|
1934
|
+
$t.textContent = i, $t.hidden = !i;
|
|
1935
|
+
const h = de(Ne());
|
|
1936
|
+
p.global.querySelectorAll(".page-warnings").forEach((w) => {
|
|
1937
|
+
w.innerHTML = h;
|
|
1938
|
+
}), ce === "changes" && bt();
|
|
1939
|
+
}
|
|
1940
|
+
return e.onChange(Ct), Ct(), a.addEventListener("click", (i) => {
|
|
1941
|
+
if (i.target === he) return Pe();
|
|
1942
|
+
const h = i.target.closest("button");
|
|
1943
|
+
if (h) {
|
|
1944
|
+
if (h !== xt && !oe.contains(h) && ue(!1), h === l) return Ae();
|
|
1945
|
+
if (h.dataset.select) {
|
|
1946
|
+
const w = document.querySelector(h.dataset.select);
|
|
1947
|
+
return w ? pt(w) : U("That element isn’t on this page");
|
|
1948
|
+
}
|
|
1949
|
+
if (h.dataset.setupSnippet) return te(jt[h.dataset.setupSnippet].code, "Copied");
|
|
1950
|
+
if (h.dataset.go) return Q(h.dataset.go);
|
|
1951
|
+
if (h.dataset.remove) return xn(h.dataset.remove, h.dataset.removeScope);
|
|
1952
|
+
if (h.dataset.applyMode)
|
|
1953
|
+
return be("apply-mode", h.dataset.applyMode), ue(!1), U(h.dataset.applyMode === "copy" ? "Copy prompt copies straight away" : "Copy prompt shows a preview first");
|
|
1954
|
+
switch (h.dataset.action) {
|
|
1955
|
+
case "close":
|
|
1956
|
+
return Me();
|
|
1957
|
+
case "reset":
|
|
1958
|
+
return e.resetState(), U("Reset to defaults · ⌘Z to undo");
|
|
1959
|
+
case "copy":
|
|
1960
|
+
return te(location.href, "Link copied");
|
|
1961
|
+
case "pick":
|
|
1962
|
+
return ne ? pe() : ct();
|
|
1963
|
+
case "compare":
|
|
1964
|
+
ae || fe(K.getAttribute("aria-pressed") !== "true");
|
|
1965
|
+
return;
|
|
1966
|
+
case "undo":
|
|
1967
|
+
return ft();
|
|
1968
|
+
case "redo":
|
|
1969
|
+
return gt();
|
|
1970
|
+
case "back":
|
|
1971
|
+
return Ce("global");
|
|
1972
|
+
case "edit":
|
|
1973
|
+
return Q("edit");
|
|
1974
|
+
case "page-back":
|
|
1975
|
+
return gn();
|
|
1976
|
+
case "changes":
|
|
1977
|
+
return Ce(ce === "changes" ? "global" : "changes");
|
|
1978
|
+
case "apply":
|
|
1979
|
+
return vn();
|
|
1980
|
+
case "apply-menu":
|
|
1981
|
+
return ue(oe.hidden);
|
|
1982
|
+
case "dialog-close":
|
|
1983
|
+
return Pe();
|
|
1984
|
+
case "dialog-copy":
|
|
1985
|
+
return te(yt.value, "Prompt copied · paste it into your agent");
|
|
1986
|
+
case "setup-copy":
|
|
1987
|
+
return z && te(z.text2copy, z.copied);
|
|
1988
|
+
case "setup-dismiss":
|
|
1989
|
+
return be(Et, ge.dataset.status), me();
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}), a.addEventListener("keydown", (i) => {
|
|
1993
|
+
if (i.key === "Escape") {
|
|
1994
|
+
if (!he.hidden) return Pe();
|
|
1995
|
+
if (!oe.hidden) return ue(!1);
|
|
1996
|
+
}
|
|
1997
|
+
!(i.metaKey || i.ctrlKey) || i.key.toLowerCase() !== "z" || i.target.matches?.('input[type="text"], textarea') || (i.preventDefault(), i.shiftKey ? gt() : ft());
|
|
1998
|
+
}), document.addEventListener("keydown", (i) => {
|
|
1999
|
+
i.altKey && (i.code === "KeyT" && (i.preventDefault(), Ae()), i.code === "KeyC" && (i.preventDefault(), fe(K.getAttribute("aria-pressed") !== "true")));
|
|
2000
|
+
}), Fe("panel") === "open" && ee(), { open: ee, close: Me, toggle: Ae, toast: U, pick: ct, prompt: vt };
|
|
2001
|
+
}
|
|
2002
|
+
const Ao = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>', To = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
|
|
2003
|
+
let Lo = 0;
|
|
2004
|
+
function Po({ label: t, options: e, onSelect: n, placeholder: o, onOpen: a }) {
|
|
2005
|
+
const s = `twd-fonts-${++Lo}`, r = document.createElement("div");
|
|
2006
|
+
r.className = "font-select";
|
|
2007
|
+
const d = document.createElement("button");
|
|
2008
|
+
d.type = "button", d.className = "font-trigger", d.setAttribute("aria-haspopup", "listbox"), d.setAttribute("aria-expanded", "false"), d.setAttribute("aria-controls", s), d.innerHTML = `
|
|
2009
|
+
<span class="font-aa" aria-hidden="true">Aa</span>
|
|
2010
|
+
<span class="font-name"></span>
|
|
2011
|
+
<span class="font-fonts"></span>
|
|
2012
|
+
<span class="font-chevron">${Ao}</span>`;
|
|
2013
|
+
const l = document.createElement("div");
|
|
2014
|
+
l.className = "font-menu", l.id = s, l.setAttribute("role", "listbox"), l.setAttribute("aria-label", t), l.hidden = !0;
|
|
2015
|
+
const c = e.map((u) => {
|
|
2016
|
+
const y = document.createElement("div");
|
|
2017
|
+
return y.className = "font-option", y.setAttribute("role", "option"), y.tabIndex = -1, y.dataset.value = u.value, y.innerHTML = `
|
|
2018
|
+
<span class="font-aa" style="font-family:${u.preview}" aria-hidden="true">Aa</span>
|
|
2019
|
+
<span class="font-text"><span class="font-name">${u.name}</span><span class="font-fonts">${u.fonts}</span></span>
|
|
2020
|
+
<span class="font-check">${To}</span>`, y.addEventListener("click", () => g(u.value)), y;
|
|
2021
|
+
});
|
|
2022
|
+
l.append(...c), r.append(d, l);
|
|
2023
|
+
let p = null;
|
|
2024
|
+
function m(u) {
|
|
2025
|
+
const y = e.find((O) => O.value === u), k = y || o || e[0];
|
|
2026
|
+
p = y ? y.value : o ? null : e[0].value, d.querySelector(".font-name").textContent = k.name, d.querySelector(".font-fonts").textContent = k.fonts, d.querySelector(".font-aa").style.fontFamily = k.preview, d.setAttribute("aria-label", `${t}: ${k.name}, ${k.fonts}`), c.forEach((O) => O.setAttribute("aria-selected", String(O.dataset.value === p)));
|
|
2027
|
+
}
|
|
2028
|
+
function v() {
|
|
2029
|
+
const u = d.getBoundingClientRect(), y = l.offsetHeight, O = window.innerHeight - u.bottom - 8 >= y ? u.bottom + 4 : Math.max(8, u.top - 4 - y);
|
|
2030
|
+
l.style.top = `${O}px`, l.style.left = `${u.left}px`, l.style.width = `${u.width}px`;
|
|
2031
|
+
}
|
|
2032
|
+
function f() {
|
|
2033
|
+
l.hidden && (l.hidden = !1, d.setAttribute("aria-expanded", "true"), a && a(), v(), (c.find((u) => u.dataset.value === p) || c[0]).focus(), setTimeout(() => document.addEventListener("pointerdown", b, !0)), window.addEventListener("resize", x), r.closest(".body")?.addEventListener("scroll", x, { once: !0 }));
|
|
2034
|
+
}
|
|
2035
|
+
function x(u) {
|
|
2036
|
+
l.hidden || (l.hidden = !0, d.setAttribute("aria-expanded", "false"), document.removeEventListener("pointerdown", b, !0), window.removeEventListener("resize", x), u === !0 && d.focus());
|
|
2037
|
+
}
|
|
2038
|
+
function b(u) {
|
|
2039
|
+
u.composedPath().includes(r) || x();
|
|
2040
|
+
}
|
|
2041
|
+
function g(u) {
|
|
2042
|
+
u !== p && (m(u), n(u)), x(!0);
|
|
2043
|
+
}
|
|
2044
|
+
return d.addEventListener("click", () => l.hidden ? f() : x(!0)), d.addEventListener("keydown", (u) => {
|
|
2045
|
+
(u.key === "ArrowDown" || u.key === "ArrowUp") && (u.preventDefault(), f());
|
|
2046
|
+
}), l.addEventListener("keydown", (u) => {
|
|
2047
|
+
const y = c.indexOf(u.target);
|
|
2048
|
+
let k = null;
|
|
2049
|
+
if (u.key === "ArrowDown") k = Math.min(c.length - 1, y + 1);
|
|
2050
|
+
else if (u.key === "ArrowUp") k = Math.max(0, y - 1);
|
|
2051
|
+
else if (u.key === "Home") k = 0;
|
|
2052
|
+
else if (u.key === "End") k = c.length - 1;
|
|
2053
|
+
else if (u.key === "Enter" || u.key === " ") {
|
|
2054
|
+
u.preventDefault(), g(u.target.dataset.value);
|
|
2055
|
+
return;
|
|
2056
|
+
} else if (u.key === "Escape") {
|
|
2057
|
+
u.preventDefault(), u.stopPropagation(), x(!0);
|
|
2058
|
+
return;
|
|
2059
|
+
} else if (u.key === "Tab") {
|
|
2060
|
+
x();
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
k !== null && (u.preventDefault(), c[k].focus());
|
|
2064
|
+
}), m(o ? null : e[0].value), { el: r, setValue: m };
|
|
2065
|
+
}
|
|
2066
|
+
const I = 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif', ve = 'Georgia, "Times New Roman", serif', Be = [
|
|
2067
|
+
{ value: "neutral", name: "Neutral", fonts: "Inter · Inter", heading: `"Inter", ${I}`, body: `"Inter", ${I}`, load: ["Inter:wght@400;500;600;700"] },
|
|
2068
|
+
{ value: "modern", name: "Modern", fonts: "Geist · Geist", heading: `"Geist", ${I}`, body: `"Geist", ${I}`, load: ["Geist:wght@400;500;600;700"] },
|
|
2069
|
+
{ value: "editorial", name: "Editorial", fonts: "Fraunces · Inter", heading: `"Fraunces", ${ve}`, body: `"Inter", ${I}`, load: ["Fraunces:wght@400;600;700", "Inter:wght@400;500;600;700"] },
|
|
2070
|
+
{ value: "technical", name: "Technical", fonts: "Space Grotesk · DM Sans", heading: `"Space Grotesk", ${I}`, body: `"DM Sans", ${I}`, load: ["Space+Grotesk:wght@500;600;700", "DM+Sans:wght@400;500;600;700"] },
|
|
2071
|
+
{ value: "classic", name: "Classic", fonts: "Playfair · DM Sans", heading: `"Playfair Display", ${ve}`, body: `"DM Sans", ${I}`, load: ["Playfair+Display:wght@600;700", "DM+Sans:wght@400;500;600;700"] },
|
|
2072
|
+
{ value: "plex", name: "Plex", fonts: "Plex Serif · Plex Sans", heading: `"IBM Plex Serif", ${ve}`, body: `"IBM Plex Sans", ${I}`, load: ["IBM+Plex+Serif:wght@500;600;700", "IBM+Plex+Sans:wght@400;500;600"] },
|
|
2073
|
+
{ value: "elegant", name: "Elegant", fonts: "Instrument Serif · Inter", heading: `"Instrument Serif", ${ve}`, body: `"Inter", ${I}`, load: ["Instrument+Serif", "Inter:wght@400;500;600;700"] },
|
|
2074
|
+
{ value: "friendly", name: "Friendly", fonts: "Manrope · Manrope", heading: `"Manrope", ${I}`, body: `"Manrope", ${I}`, load: ["Manrope:wght@400;500;600;700"] }
|
|
2075
|
+
], Ve = { compact: 1.125, balanced: 1.2, expressive: 1.333 }, Ut = { xs: -2, sm: -1, base: 0, lg: 1, xl: 2, "2xl": 3, "3xl": 4 }, qt = /* @__PURE__ */ new Set();
|
|
2076
|
+
function Ht(t) {
|
|
2077
|
+
const e = t.filter((o) => !qt.has(o));
|
|
2078
|
+
if (!e.length) return;
|
|
2079
|
+
e.forEach((o) => qt.add(o));
|
|
2080
|
+
const n = document.createElement("link");
|
|
2081
|
+
n.rel = "stylesheet", n.href = `https://fonts.googleapis.com/css2?${e.map((o) => `family=${o}`).join("&")}&display=swap`, n.dataset.twiddle = "fonts", document.head.appendChild(n);
|
|
2082
|
+
}
|
|
2083
|
+
const Fo = {
|
|
2084
|
+
id: "typography",
|
|
2085
|
+
title: "Typography",
|
|
2086
|
+
hint: "Font pairing · base size · scale",
|
|
2087
|
+
uses: ["--twd-font-heading", "--twd-font-body", ...Object.keys(Ut).map((t) => `--twd-text-${t}`)],
|
|
2088
|
+
tokens(t) {
|
|
2089
|
+
const e = t.typography || {}, n = {}, o = Be.find((a) => a.value === e.font);
|
|
2090
|
+
if (o && (Ht(o.load), n["--twd-font-heading"] = o.heading, n["--twd-font-body"] = o.body), e.base || e.scale) {
|
|
2091
|
+
const a = Number(e.base) || 16, s = Ve[e.scale] || Ve.balanced;
|
|
2092
|
+
Object.entries(Ut).forEach(([r, d]) => {
|
|
2093
|
+
n[`--twd-text-${r}`] = `${+(a * s ** d / 16).toFixed(3)}rem`;
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
return n;
|
|
2097
|
+
},
|
|
2098
|
+
parts: {
|
|
2099
|
+
fonts(t, e, { label: n = "Fonts" } = {}) {
|
|
2100
|
+
const o = B(n), a = Po({
|
|
2101
|
+
label: "Font pairing",
|
|
2102
|
+
options: Be.map((r) => ({ value: r.value, name: r.name, fonts: r.fonts, preview: r.heading })),
|
|
2103
|
+
placeholder: { name: "Default", fonts: "Prototype fonts", preview: I },
|
|
2104
|
+
// Load every pairing's fonts when the list opens, so the "Aa" previews are real.
|
|
2105
|
+
onOpen: () => Ht([...new Set(Be.flatMap((r) => r.load))]),
|
|
2106
|
+
onSelect: (r) => e.setSection("typography", { font: r })
|
|
2107
|
+
});
|
|
2108
|
+
o.append(a.el);
|
|
2109
|
+
const s = () => a.setValue((e.getState().typography || {}).font || null);
|
|
2110
|
+
s(), e.onChange(s), t.append(o.el);
|
|
2111
|
+
},
|
|
2112
|
+
base(t, e, { label: n = "Base size" } = {}) {
|
|
2113
|
+
t.append(le(e, {
|
|
2114
|
+
section: "typography",
|
|
2115
|
+
key: "base",
|
|
2116
|
+
label: n,
|
|
2117
|
+
fallback: "16",
|
|
2118
|
+
options: ["14", "15", "16"].map((o) => ({ value: o, label: o })),
|
|
2119
|
+
valueText: (o) => `${o}px`
|
|
2120
|
+
}));
|
|
2121
|
+
},
|
|
2122
|
+
scale(t, e, { label: n = "Scale" } = {}) {
|
|
2123
|
+
t.append(le(e, {
|
|
2124
|
+
section: "typography",
|
|
2125
|
+
key: "scale",
|
|
2126
|
+
label: n,
|
|
2127
|
+
fallback: "balanced",
|
|
2128
|
+
options: [{ value: "compact", label: "Compact" }, { value: "balanced", label: "Balanced" }, { value: "expressive", label: "Expressive" }],
|
|
2129
|
+
valueText: (o) => String(Ve[o])
|
|
2130
|
+
}));
|
|
2131
|
+
}
|
|
2132
|
+
},
|
|
2133
|
+
render(t, e) {
|
|
2134
|
+
Object.values(this.parts).forEach((n) => n(t, e));
|
|
2135
|
+
}
|
|
2136
|
+
}, Oo = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 6l4 4M4 20l1-4 9.5-9.5a2.1 2.1 0 013 3L8 19l-4 1z"/></svg>', Ro = [
|
|
2137
|
+
[50, "white", 10],
|
|
2138
|
+
[100, "white", 20],
|
|
2139
|
+
[200, "white", 36],
|
|
2140
|
+
[300, "white", 56],
|
|
2141
|
+
[400, "white", 78],
|
|
2142
|
+
[500, null, 100],
|
|
2143
|
+
[600, "black", 86],
|
|
2144
|
+
[700, "black", 72],
|
|
2145
|
+
[800, "black", 58],
|
|
2146
|
+
[900, "black", 45],
|
|
2147
|
+
[950, "black", 30]
|
|
2148
|
+
], Ue = (t) => {
|
|
2149
|
+
let e = String(t).trim().replace(/^#/, "");
|
|
2150
|
+
return /^[0-9a-f]{3}$/i.test(e) && (e = e.split("").map((n) => n + n).join("")), /^[0-9a-f]{6}$/i.test(e) ? `#${e.toUpperCase()}` : null;
|
|
2151
|
+
};
|
|
2152
|
+
function No({ label: t, onInput: e }) {
|
|
2153
|
+
const n = document.createElement("div");
|
|
2154
|
+
n.className = "colour";
|
|
2155
|
+
const o = document.createElement("div");
|
|
2156
|
+
o.className = "colour-field";
|
|
2157
|
+
const a = document.createElement("input");
|
|
2158
|
+
a.type = "color", a.className = "colour-picker", a.tabIndex = -1, a.setAttribute("aria-hidden", "true");
|
|
2159
|
+
const s = document.createElement("button");
|
|
2160
|
+
s.type = "button", s.className = "colour-swatch", s.setAttribute("aria-label", `${t}: open colour picker`);
|
|
2161
|
+
const r = document.createElement("input");
|
|
2162
|
+
r.type = "text", r.className = "colour-hex", r.spellcheck = !1, r.maxLength = 7, r.setAttribute("aria-label", `${t} hex value`);
|
|
2163
|
+
const d = document.createElement("button");
|
|
2164
|
+
d.type = "button", d.className = "colour-pipette", d.innerHTML = Oo, d.title = "Pick a colour from the screen", d.setAttribute("aria-label", "Pick a colour from the screen"), o.append(a, s, r, d);
|
|
2165
|
+
const l = document.createElement("div");
|
|
2166
|
+
l.className = "ramp", l.setAttribute("aria-hidden", "true"), l.innerHTML = Ro.map(([f, x, b]) => {
|
|
2167
|
+
const g = x ? `color-mix(in oklch, var(--brand) ${b}%, ${x})` : "var(--brand)";
|
|
2168
|
+
return `<span title="${f}" style="background:${g}"></span>`;
|
|
2169
|
+
}).join(""), n.append(o, l);
|
|
2170
|
+
let c = "#000000";
|
|
2171
|
+
const p = (f) => {
|
|
2172
|
+
c = f, a.value = f.toLowerCase(), s.style.background = f, n.getRootNode().activeElement !== r && (r.value = f), n.style.setProperty("--brand", f);
|
|
2173
|
+
}, m = (f) => {
|
|
2174
|
+
f !== c && (p(f), e(f));
|
|
2175
|
+
};
|
|
2176
|
+
s.addEventListener("click", () => {
|
|
2177
|
+
if (a.showPicker)
|
|
2178
|
+
try {
|
|
2179
|
+
a.showPicker();
|
|
2180
|
+
return;
|
|
2181
|
+
} catch {
|
|
2182
|
+
}
|
|
2183
|
+
a.click();
|
|
2184
|
+
}), a.addEventListener("input", () => m(a.value.toUpperCase())), r.addEventListener("input", () => {
|
|
2185
|
+
const f = Ue(r.value);
|
|
2186
|
+
f && m(f);
|
|
2187
|
+
});
|
|
2188
|
+
const v = () => {
|
|
2189
|
+
r.value = c;
|
|
2190
|
+
};
|
|
2191
|
+
return r.addEventListener("blur", v), r.addEventListener("keydown", (f) => {
|
|
2192
|
+
f.key === "Enter" && (v(), r.select());
|
|
2193
|
+
}), d.addEventListener("click", async () => {
|
|
2194
|
+
if ("EyeDropper" in window)
|
|
2195
|
+
try {
|
|
2196
|
+
const { sRGBHex: f } = await new window.EyeDropper().open(), x = Ue(f);
|
|
2197
|
+
x && m(x);
|
|
2198
|
+
} catch {
|
|
2199
|
+
}
|
|
2200
|
+
else
|
|
2201
|
+
s.click();
|
|
2202
|
+
}), { el: n, setValue: (f) => p(Ue(f) || c) };
|
|
2203
|
+
}
|
|
2204
|
+
function jo({ label: t, options: e, onSelect: n }) {
|
|
2205
|
+
const o = document.createElement("div");
|
|
2206
|
+
o.className = "swatches", o.setAttribute("role", "radiogroup"), o.setAttribute("aria-label", t);
|
|
2207
|
+
const a = e.map((l) => {
|
|
2208
|
+
const c = document.createElement("button");
|
|
2209
|
+
return c.type = "button", c.className = "swatch", c.setAttribute("role", "radio"), c.dataset.value = l.value, c.innerHTML = `<span class="swatch-ring"><span class="swatch-dot" style="background:${l.colour}"></span></span><span class="swatch-label">${l.label}</span>`, c.addEventListener("click", () => d(l.value)), c;
|
|
2210
|
+
});
|
|
2211
|
+
o.append(...a);
|
|
2212
|
+
let s = null;
|
|
2213
|
+
function r(l) {
|
|
2214
|
+
s = l, a.forEach((p) => p.setAttribute("aria-checked", String(p.dataset.value === l)));
|
|
2215
|
+
const c = Math.max(0, a.findIndex((p) => p.dataset.value === l));
|
|
2216
|
+
a.forEach((p, m) => {
|
|
2217
|
+
p.tabIndex = m === c ? 0 : -1;
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
function d(l, c) {
|
|
2221
|
+
l !== s && (r(l), n(l)), c && a.find((p) => p.dataset.value === l).focus();
|
|
2222
|
+
}
|
|
2223
|
+
return o.addEventListener("keydown", (l) => {
|
|
2224
|
+
const c = a.indexOf(l.target.closest(".swatch"));
|
|
2225
|
+
if (c < 0) return;
|
|
2226
|
+
const p = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[l.key];
|
|
2227
|
+
let m;
|
|
2228
|
+
if (p) m = (c + p + a.length) % a.length;
|
|
2229
|
+
else if (l.key === "Home") m = 0;
|
|
2230
|
+
else if (l.key === "End") m = a.length - 1;
|
|
2231
|
+
else return;
|
|
2232
|
+
l.preventDefault(), d(a[m].dataset.value, !0);
|
|
2233
|
+
}), r(null), { el: o, setValue: r };
|
|
2234
|
+
}
|
|
2235
|
+
const _t = "#4F46E5", zo = "#A0743C", Io = [
|
|
2236
|
+
// Figma Swatch row sample colours
|
|
2237
|
+
{ value: "pure", label: "Pure", colour: "#8E8E93" },
|
|
2238
|
+
{ value: "subtle", label: "Subtle", colour: "#8A89A6" },
|
|
2239
|
+
{ value: "warm", label: "Warm", colour: "#9C9389" }
|
|
2240
|
+
], Gt = ["bg", "surface", "surface-raised", "text", "text-muted", "border"], Do = {
|
|
2241
|
+
// proposal
|
|
2242
|
+
bg: "#0F0F11",
|
|
2243
|
+
surface: "#18181B",
|
|
2244
|
+
"surface-raised": "#1F1F23",
|
|
2245
|
+
text: "#F4F4F5",
|
|
2246
|
+
"text-muted": "#A1A1AA",
|
|
2247
|
+
border: "#2E2E33"
|
|
2248
|
+
}, Bo = { bg: 4, surface: 2, "surface-raised": 2, text: 5, "text-muted": 12, border: 10 }, Kt = (t) => /^#[0-9a-f]{6}$/i.test(t || "");
|
|
2249
|
+
function Vo(t) {
|
|
2250
|
+
const e = (l) => (l /= 255, l <= 0.03928 ? l / 12.92 : ((l + 0.055) / 1.055) ** 2.4), [n, o, a] = [1, 3, 5].map((l) => e(parseInt(t.slice(l, l + 2), 16))), s = 0.2126 * n + 0.7152 * o + 0.0722 * a, r = 1.05 / (s + 0.05), d = (s + 0.05) / (98e-4 + 0.05);
|
|
2251
|
+
return r >= d ? "#FFFFFF" : "#18181B";
|
|
2252
|
+
}
|
|
2253
|
+
const Uo = {
|
|
2254
|
+
id: "colour",
|
|
2255
|
+
title: "Colour",
|
|
2256
|
+
hint: "Brand colour · neutral tint · mode",
|
|
2257
|
+
uses: ["--twd-primary", "--twd-primary-hover", "--twd-on-primary", "--twd-focus", ...Gt.map((t) => `--twd-${t}`)],
|
|
2258
|
+
tokens(t) {
|
|
2259
|
+
const e = t.colour || {}, n = {}, o = e.mode === "dark";
|
|
2260
|
+
Kt(e.brand) && (n["--twd-primary"] = e.brand, n["--twd-primary-hover"] = o ? `color-mix(in oklch, ${e.brand} 85%, white)` : `color-mix(in oklch, ${e.brand} 86%, black)`, n["--twd-on-primary"] = Vo(e.brand), n["--twd-focus"] = e.brand);
|
|
2261
|
+
const a = e.tint === "subtle" ? Kt(e.brand) ? e.brand : F("--twd-primary") || _t : e.tint === "warm" ? zo : null;
|
|
2262
|
+
return (o || a) && Gt.forEach((s) => {
|
|
2263
|
+
const r = o ? Do[s] : F(`--twd-${s}`);
|
|
2264
|
+
r && (n[`--twd-${s}`] = a ? `color-mix(in oklch, ${r} ${100 - Bo[s]}%, ${a})` : r);
|
|
2265
|
+
}), n;
|
|
2266
|
+
},
|
|
2267
|
+
parts: {
|
|
2268
|
+
brand(t, e, { label: n = "Brand" } = {}) {
|
|
2269
|
+
const o = B(n), a = No({ label: "Brand colour", onInput: (r) => e.setSection("colour", { brand: r }) });
|
|
2270
|
+
o.append(a.el);
|
|
2271
|
+
const s = () => a.setValue((e.getState().colour || {}).brand || (e.readToken || F)("--twd-primary") || _t);
|
|
2272
|
+
s(), e.onChange(s), t.append(o.el);
|
|
2273
|
+
},
|
|
2274
|
+
neutral(t, e, { label: n = "Neutral" } = {}) {
|
|
2275
|
+
const o = B(n), a = jo({ label: "Neutral tint", options: Io, onSelect: (r) => e.setSection("colour", { tint: r }) });
|
|
2276
|
+
o.append(a.el);
|
|
2277
|
+
const s = () => a.setValue((e.getState().colour || {}).tint || "pure");
|
|
2278
|
+
s(), e.onChange(s), t.append(o.el);
|
|
2279
|
+
},
|
|
2280
|
+
mode(t, e, { label: n = "Mode" } = {}) {
|
|
2281
|
+
t.append(le(e, {
|
|
2282
|
+
section: "colour",
|
|
2283
|
+
key: "mode",
|
|
2284
|
+
label: n,
|
|
2285
|
+
fallback: "light",
|
|
2286
|
+
options: [{ value: "light", label: "Light" }, { value: "dark", label: "Dark" }]
|
|
2287
|
+
}));
|
|
2288
|
+
}
|
|
2289
|
+
},
|
|
2290
|
+
render(t, e) {
|
|
2291
|
+
Object.values(this.parts).forEach((n) => n(t, e));
|
|
2292
|
+
}
|
|
2293
|
+
}, qo = { small: { height: 0.8, pad: 0.75 }, large: { height: 1.2, pad: 1.25 } }, Ho = hn({
|
|
2294
|
+
id: "buttons",
|
|
2295
|
+
title: "Buttons",
|
|
2296
|
+
hint: "Corners · size",
|
|
2297
|
+
token: "--twd-radius-control",
|
|
2298
|
+
uses: ["--twd-control-height", "--twd-control-pad-x"],
|
|
2299
|
+
max: 24,
|
|
2300
|
+
// slider range in px; "Pill" sits at the end and reads "full"
|
|
2301
|
+
presets: [
|
|
2302
|
+
{ value: "square", label: "Square", radius: 0 },
|
|
2303
|
+
// proposal
|
|
2304
|
+
{ value: "soft", label: "Soft", radius: 4 },
|
|
2305
|
+
// proposal
|
|
2306
|
+
{ value: "rounded", label: "Rounded", radius: 8 },
|
|
2307
|
+
// Figma: "8px"
|
|
2308
|
+
{ value: "pill", label: "Pill", radius: 999 }
|
|
2309
|
+
],
|
|
2310
|
+
extraTokens(t) {
|
|
2311
|
+
const e = qo[t.size];
|
|
2312
|
+
if (!e) return {};
|
|
2313
|
+
const n = {}, o = Se(F("--twd-control-height"), e.height), a = Se(F("--twd-control-pad-x"), e.pad);
|
|
2314
|
+
return o && (n["--twd-control-height"] = o), a && (n["--twd-control-pad-x"] = a), n;
|
|
2315
|
+
},
|
|
2316
|
+
extra: [
|
|
2317
|
+
{
|
|
2318
|
+
key: "size",
|
|
2319
|
+
label: "Size",
|
|
2320
|
+
fallback: "medium",
|
|
2321
|
+
options: [{ value: "small", label: "Small" }, { value: "medium", label: "Medium" }, { value: "large", label: "Large" }]
|
|
2322
|
+
}
|
|
2323
|
+
]
|
|
2324
|
+
}), _o = hn({
|
|
2325
|
+
id: "cards",
|
|
2326
|
+
title: "Cards",
|
|
2327
|
+
hint: "Corners · border",
|
|
2328
|
+
token: "--twd-radius-container",
|
|
2329
|
+
uses: ["--twd-border-width-container"],
|
|
2330
|
+
max: 32,
|
|
2331
|
+
presets: [
|
|
2332
|
+
{ value: "square", label: "Square", radius: 0 },
|
|
2333
|
+
// proposal
|
|
2334
|
+
{ value: "soft", label: "Soft", radius: 6 },
|
|
2335
|
+
// proposal
|
|
2336
|
+
{ value: "rounded", label: "Rounded", radius: 12 },
|
|
2337
|
+
// = tokens.css default
|
|
2338
|
+
{ value: "large", label: "Large", radius: 20 }
|
|
2339
|
+
// proposal (cards don't go fully pill)
|
|
2340
|
+
],
|
|
2341
|
+
extraTokens(t) {
|
|
2342
|
+
if (t.border === "none") return { "--twd-border-width-container": "0px" };
|
|
2343
|
+
if (t.border === "thick") {
|
|
2344
|
+
const e = Se(F("--twd-border-width-container"), 2);
|
|
2345
|
+
return e ? { "--twd-border-width-container": e } : {};
|
|
2346
|
+
}
|
|
2347
|
+
return {};
|
|
2348
|
+
},
|
|
2349
|
+
extra: [
|
|
2350
|
+
{
|
|
2351
|
+
key: "border",
|
|
2352
|
+
label: "Border",
|
|
2353
|
+
fallback: "thin",
|
|
2354
|
+
options: [{ value: "none", label: "None" }, { value: "thin", label: "Thin" }, { value: "thick", label: "Thick" }]
|
|
2355
|
+
}
|
|
2356
|
+
]
|
|
2357
|
+
}), Wt = [Fo, Uo, Ho, _o, bo, wo, mo], Go = ':root{--twd-font-heading: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--twd-text-xs: .694rem;--twd-text-sm: .833rem;--twd-text-base: 1rem;--twd-text-lg: 1.2rem;--twd-text-xl: 1.44rem;--twd-text-2xl: 1.728rem;--twd-text-3xl: 2.074rem;--twd-leading-tight: 1.2;--twd-leading-normal: 1.5;--twd-bg: #fafafa;--twd-surface: #ffffff;--twd-surface-raised: #ffffff;--twd-text: #18181b;--twd-text-muted: #71717a;--twd-border: #e4e4e7;--twd-primary: #4f46e5;--twd-primary-hover: #4338ca;--twd-on-primary: #ffffff;--twd-focus: #6366f1;--twd-success: #16a34a;--twd-warning: #d97706;--twd-danger: #dc2626;--twd-radius-control: 8px;--twd-radius-container: 12px;--twd-space-1: 4px;--twd-space-2: 8px;--twd-space-3: 12px;--twd-space-4: 16px;--twd-space-5: 24px;--twd-space-6: 32px;--twd-space-7: 48px;--twd-space-8: 64px;--twd-control-height: 40px;--twd-control-pad-x: 16px;--twd-page-pad: var(--twd-space-5);--twd-section-gap: var(--twd-space-6);--twd-card-pad: var(--twd-space-5);--twd-card-gap: var(--twd-space-2);--twd-control-gap: var(--twd-space-3);--twd-border-width: 1px;--twd-border-width-container: var(--twd-border-width);--twd-shadow-sm: 0 1px 2px rgb(0 0 0 / .06);--twd-shadow-md: 0 4px 12px rgb(0 0 0 / .08);--twd-shadow-lg: 0 12px 32px rgb(0 0 0 / .12)}';
|
|
2358
|
+
function Ko() {
|
|
2359
|
+
if (getComputedStyle(document.documentElement).getPropertyValue("--twd-radius-control").trim() || document.getElementById("twiddle-tokens")) return;
|
|
2360
|
+
const e = document.createElement("style");
|
|
2361
|
+
e.id = "twiddle-tokens", e.textContent = Go, document.head.prepend(e);
|
|
2362
|
+
}
|
|
2363
|
+
if (!window.Twiddle) {
|
|
2364
|
+
Ko(), Wt.forEach((n) => On(n.tokens)), Qt(Ke);
|
|
2365
|
+
let t;
|
|
2366
|
+
const e = () => {
|
|
2367
|
+
Ke(He()), t = Mo({ sections: Wt, store: Fn, view: { suspend: zn, resume: In } });
|
|
2368
|
+
};
|
|
2369
|
+
document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", e) : e(), window.Twiddle = {
|
|
2370
|
+
open: () => t && t.open(),
|
|
2371
|
+
close: () => t && t.close(),
|
|
2372
|
+
toggle: () => t && t.toggle(),
|
|
2373
|
+
reset: () => Xt(),
|
|
2374
|
+
undo: () => Jt(),
|
|
2375
|
+
redo: () => Zt(),
|
|
2376
|
+
pick: () => t && t.pick(),
|
|
2377
|
+
prompt: () => t ? t.prompt() : "",
|
|
2378
|
+
getState: He
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
const Wo = window.Twiddle;
|
|
2382
|
+
export {
|
|
2383
|
+
Wo as Twiddle
|
|
2384
|
+
};
|