twiddle-panel 0.6.0 → 0.7.0

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