twiddle-panel 0.8.2 → 0.9.0

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