twiddle-panel 0.8.2 → 0.9.1

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