twiddle-panel 0.6.1 → 0.7.1

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