twiddle-panel 0.6.1 → 0.7.0

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