@kitlangton/knobs 0.2.0 → 0.4.0-next.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.
Files changed (62) hide show
  1. package/README.md +197 -19
  2. package/dist/dev/{chunk-gezhc24g.js → chunk-7xgkerd2.js} +14 -5
  3. package/dist/dev/{chunk-693jd1gj.js → chunk-pmkvb9ep.js} +1544 -160
  4. package/dist/dev/{chunk-5cr85vv6.js → chunk-stbcgz1y.js} +7924 -7931
  5. package/dist/dev/{chunk-4km87zfx.js → chunk-t68smqkc.js} +18 -7
  6. package/dist/dev/index.js +187 -31
  7. package/dist/index.js +197 -33
  8. package/dist/panel.js +20342 -18461
  9. package/dist/types/core/keys.d.ts +2 -0
  10. package/dist/types/core/remote.d.ts +2 -0
  11. package/dist/types/core/schema.d.ts +5 -0
  12. package/dist/types/core/store.d.ts +29 -2
  13. package/dist/types/core/unlock.d.ts +77 -0
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/panel/attach.d.ts +2 -0
  16. package/dist/types/panel/crumbs.d.ts +19 -0
  17. package/dist/types/panel/editors.d.ts +9 -0
  18. package/dist/types/panel/feel.d.ts +103 -0
  19. package/dist/types/panel/give.d.ts +34 -1
  20. package/dist/types/panel/gpu/color.d.ts +4 -4
  21. package/dist/types/panel/gpu/renderer.d.ts +32 -0
  22. package/dist/types/panel/gpu/shader.wgsl.d.ts +1 -1
  23. package/dist/types/panel/index.d.ts +2 -1
  24. package/dist/types/panel/inspector.d.ts +1 -1
  25. package/dist/types/panel/lens.d.ts +113 -0
  26. package/dist/types/panel/motion.d.ts +2 -0
  27. package/dist/types/panel/page-hints.d.ts +2 -1
  28. package/dist/types/panel/prefs.d.ts +2 -2
  29. package/dist/types/panel/presence.d.ts +20 -0
  30. package/dist/types/panel/scope.d.ts +29 -0
  31. package/dist/types/panel/slider.d.ts +21 -4
  32. package/dist/types/panel/tab-transition.d.ts +15 -0
  33. package/dist/types/panel/tabs.d.ts +1 -1
  34. package/dist/types/panel/toggle.d.ts +27 -0
  35. package/dist/types/ui/compare-css.d.ts +1 -0
  36. package/dist/types/ui/compare-frames.d.ts +41 -0
  37. package/dist/types/ui/compare-gpu.d.ts +58 -0
  38. package/dist/types/ui/compare-model.d.ts +169 -0
  39. package/dist/types/ui/compare-mp4.d.ts +29 -0
  40. package/dist/types/ui/compare-strip.d.ts +28 -0
  41. package/dist/types/ui/compare.d.ts +103 -0
  42. package/dist/types/ui/diff-css.d.ts +1 -0
  43. package/dist/types/ui/diff-model.d.ts +128 -0
  44. package/dist/types/ui/diff-scroll.d.ts +29 -0
  45. package/dist/types/ui/diff.d.ts +158 -0
  46. package/dist/types/ui/index.d.ts +9 -1
  47. package/dist/types/ui/look.d.ts +1 -1
  48. package/dist/types/ui/react.d.ts +66 -0
  49. package/dist/types/ui/scrollbar.d.ts +12 -0
  50. package/dist/types/ui/solid.d.ts +52 -0
  51. package/dist/types/ui/split-model.d.ts +4 -3
  52. package/dist/types/ui/split.d.ts +8 -6
  53. package/dist/types/ui/timeline.d.ts +35 -2
  54. package/dist/types/ui/tree.d.ts +7 -2
  55. package/dist/types/ui/walkthrough-css.d.ts +1 -0
  56. package/dist/types/ui/walkthrough-media.d.ts +29 -0
  57. package/dist/types/ui/walkthrough-model.d.ts +447 -0
  58. package/dist/types/ui/walkthrough.d.ts +63 -0
  59. package/dist/ui-react.js +16486 -8140
  60. package/dist/ui-solid.js +16030 -7775
  61. package/dist/ui.js +17510 -9506
  62. package/package.json +6 -1
@@ -1,10 +1,11 @@
1
1
  import {
2
+ isTypingTarget,
2
3
  GRADIENT_SAMPLES,
3
4
  Spring,
4
5
  springEasing,
5
6
  springCss2,
6
7
  storageKey
7
- } from "./chunk-gezhc24g.js";
8
+ } from "./chunk-7xgkerd2.js";
8
9
 
9
10
  // src/panel/icons.ts
10
11
  var ICONS = {
@@ -62,147 +63,6 @@ function writeSession(id, patch) {
62
63
  } catch {}
63
64
  }
64
65
 
65
- // src/panel/motion.ts
66
- var FILL = { omega: 30, zeta: 0.88 };
67
- var LEAD = { omega: 34, zeta: 0.86 }, TRAIL = { omega: 25, zeta: 1 }, trailScale = (blur) => 0.6 + 0.4 * clamp(blur, 0, 2);
68
-
69
- class Edges {
70
- start;
71
- end;
72
- constructor(start = 0, end = 0) {
73
- this.start = new Spring(start, LEAD.omega, LEAD.zeta, 0.05), this.end = new Spring(end, LEAD.omega, LEAD.zeta, 0.05);
74
- }
75
- to(start, end, delay = 0, trail = 1) {
76
- if ((start !== this.start.target || end !== this.end.target) && delay <= 0) {
77
- let forward = start + end > this.start.target + this.end.target, [lead, tail] = forward ? [this.end, this.start] : [this.start, this.end];
78
- lead.omega = LEAD.omega, lead.zeta = LEAD.zeta, tail.omega = TRAIL.omega / trail, tail.zeta = TRAIL.zeta;
79
- }
80
- return this.start.set(start, delay), this.end.set(end, delay), this;
81
- }
82
- shift(ds, de) {
83
- return this.start.x += ds, this.start.target += ds, this.end.x += de, this.end.target += de, this;
84
- }
85
- snap(start, end) {
86
- return this.start.snap(start), this.end.snap(end), this;
87
- }
88
- step(dt) {
89
- return this.start.step(dt), this.end.step(dt), this;
90
- }
91
- get resting() {
92
- return this.start.resting && this.end.resting;
93
- }
94
- get velocity() {
95
- return (this.start.v + this.end.v) / 2;
96
- }
97
- }
98
- var POP_MS = 360;
99
- function popScale(t, peak = 0.04) {
100
- if (t <= 0 || t >= POP_MS)
101
- return 1;
102
- let u = t / 55;
103
- return 1 + peak * u * Math.exp(1 - u);
104
- }
105
-
106
- class VelocityTracker {
107
- samples = [];
108
- add(x, y, t = performance.now()) {
109
- this.samples.push({ t, x, y });
110
- while (this.samples.length > 2 && t - this.samples[0].t > 100)
111
- this.samples.shift();
112
- }
113
- reset() {
114
- this.samples = [];
115
- }
116
- get velocity() {
117
- let s = this.samples;
118
- if (s.length < 2)
119
- return [0, 0];
120
- let a = s[0], b = s[s.length - 1], dt = (b.t - a.t) / 1000;
121
- if (dt <= 0 || performance.now() - b.t > 60)
122
- return [0, 0];
123
- return [(b.x - a.x) / dt, (b.y - a.y) / dt];
124
- }
125
- }
126
- var clamp = (x, a, b) => Math.min(b, Math.max(a, x));
127
-
128
- class Roll {
129
- from;
130
- to = 0;
131
- inherited = 0;
132
- start = 0;
133
- duration = 0;
134
- constructor(position) {
135
- this.from = this.to = position;
136
- }
137
- get target() {
138
- return this.to;
139
- }
140
- sample(now) {
141
- let t = now - this.start;
142
- if (this.duration <= 0 || t >= this.duration)
143
- return { position: this.to, velocity: 0 };
144
- if (t <= 0)
145
- return { position: this.from, velocity: 0 };
146
- let seconds = this.duration / 1000, tau = t / this.duration, d = this.from - this.to, e = Math.exp(-10 * tau);
147
- return {
148
- position: this.to + (d + (this.inherited + 10 * d) * tau) * e,
149
- velocity: e * (this.inherited - 10 * (this.inherited + 10 * d) * tau) / seconds
150
- };
151
- }
152
- set(target, duration, now, delay = 0) {
153
- let { position, velocity } = this.sample(now);
154
- if (duration <= 0)
155
- return this.snap(target);
156
- let seconds = duration / 1000, distance = position - target, limit = Math.max(Math.abs(distance), 1) * 12 / seconds;
157
- return this.inherited = clamp(delay > 0 ? 0 : velocity, -limit, limit) * seconds, this.from = position, this.to = target, this.start = now + delay, this.duration = duration, this;
158
- }
159
- snap(position) {
160
- return this.from = this.to = position, this.duration = 0, this.inherited = 0, this;
161
- }
162
- resting(now) {
163
- return this.duration <= 0 || now - this.start >= this.duration;
164
- }
165
- span(now) {
166
- if (this.resting(now))
167
- return [this.to, this.to];
168
- return [Math.min(this.from, this.to), Math.max(this.from, this.to)];
169
- }
170
- }
171
- function rollTarget(position, digit, trend) {
172
- let target = Math.floor(position / 10) * 10 + digit;
173
- if (trend > 0 && target < position - 0.001)
174
- target += 10;
175
- else if (trend < 0 && target > position + 0.001)
176
- target -= 10;
177
- else if (trend === 0)
178
- target += Math.round((position - target) / 10) * 10;
179
- return target;
180
- }
181
- var numeral = (position) => String((position % 10 + 10) % 10);
182
- function entranceOffset(elapsed, duration, hold = 0.14) {
183
- if (duration <= 0 || elapsed >= duration)
184
- return 0;
185
- let p = clamp((elapsed / duration - hold) / (1 - hold), 0, 1);
186
- return (1 + 10 * p) * Math.exp(-10 * p);
187
- }
188
-
189
- // src/panel/flash.ts
190
- var FLASH_MS = 320, flashes = /* @__PURE__ */ new WeakMap;
191
- function flash(button, wake) {
192
- if (!button || button.hidden)
193
- return;
194
- flashes.set(button, button.ownerDocument.defaultView?.performance.now() ?? performance.now()), button.dataset.kFlash = button.dataset.kFlash === "a" ? "b" : "a", wake?.();
195
- }
196
- function flashOf(button, now) {
197
- let at = flashes.get(button);
198
- if (at === void 0)
199
- return 0;
200
- let p = (now - at) / 320;
201
- if (p >= 1 || p < 0)
202
- return 0;
203
- return (1 - p) ** 3;
204
- }
205
-
206
66
  // src/panel/rolling-css.ts
207
67
  var rolling_css_default = `.rn-root {
208
68
  display: inline-block;
@@ -393,8 +253,428 @@ var problemsPreset = {
393
253
  }
394
254
  };
395
255
 
256
+ // src/panel/motion.ts
257
+ var FILL = { omega: 30, zeta: 0.88 };
258
+ var LEAD = { omega: 34, zeta: 0.86 }, TRAIL = { omega: 25, zeta: 1 }, trailScale = (blur) => 0.6 + 0.4 * clamp(blur, 0, 2);
259
+
260
+ class Edges {
261
+ start;
262
+ end;
263
+ constructor(start = 0, end = 0) {
264
+ this.start = new Spring(start, LEAD.omega, LEAD.zeta, 0.05), this.end = new Spring(end, LEAD.omega, LEAD.zeta, 0.05);
265
+ }
266
+ to(start, end, delay = 0, trail = 1) {
267
+ if ((start !== this.start.target || end !== this.end.target) && delay <= 0) {
268
+ let forward = start + end > this.start.target + this.end.target, [lead, tail] = forward ? [this.end, this.start] : [this.start, this.end];
269
+ lead.omega = LEAD.omega, lead.zeta = LEAD.zeta, tail.omega = TRAIL.omega / trail, tail.zeta = TRAIL.zeta;
270
+ }
271
+ return this.start.set(start, delay), this.end.set(end, delay), this;
272
+ }
273
+ shift(ds, de) {
274
+ return this.start.x += ds, this.start.target += ds, this.end.x += de, this.end.target += de, this;
275
+ }
276
+ snap(start, end) {
277
+ return this.start.snap(start), this.end.snap(end), this;
278
+ }
279
+ step(dt) {
280
+ return this.start.step(dt), this.end.step(dt), this;
281
+ }
282
+ get resting() {
283
+ return this.start.resting && this.end.resting;
284
+ }
285
+ get velocity() {
286
+ return (this.start.v + this.end.v) / 2;
287
+ }
288
+ }
289
+ var POP_MS = 360;
290
+ function popScale(t, peak = 0.04) {
291
+ if (t <= 0 || t >= POP_MS)
292
+ return 1;
293
+ let u = t / 55;
294
+ return 1 + peak * u * Math.exp(1 - u);
295
+ }
296
+ var settleScale = (t) => popScale(t, 0.14);
297
+
298
+ class VelocityTracker {
299
+ samples = [];
300
+ add(x, y, t = performance.now()) {
301
+ this.samples.push({ t, x, y });
302
+ while (this.samples.length > 2 && t - this.samples[0].t > 100)
303
+ this.samples.shift();
304
+ }
305
+ reset() {
306
+ this.samples = [];
307
+ }
308
+ get velocity() {
309
+ let s = this.samples;
310
+ if (s.length < 2)
311
+ return [0, 0];
312
+ let a = s[0], b = s[s.length - 1], dt = (b.t - a.t) / 1000;
313
+ if (dt <= 0 || performance.now() - b.t > 60)
314
+ return [0, 0];
315
+ return [(b.x - a.x) / dt, (b.y - a.y) / dt];
316
+ }
317
+ }
318
+ var clamp = (x, a, b) => Math.min(b, Math.max(a, x));
319
+
320
+ class Roll {
321
+ from;
322
+ to = 0;
323
+ inherited = 0;
324
+ start = 0;
325
+ duration = 0;
326
+ constructor(position) {
327
+ this.from = this.to = position;
328
+ }
329
+ get target() {
330
+ return this.to;
331
+ }
332
+ sample(now) {
333
+ let t = now - this.start;
334
+ if (this.duration <= 0 || t >= this.duration)
335
+ return { position: this.to, velocity: 0 };
336
+ if (t <= 0)
337
+ return { position: this.from, velocity: 0 };
338
+ let seconds = this.duration / 1000, tau = t / this.duration, d = this.from - this.to, e = Math.exp(-10 * tau);
339
+ return {
340
+ position: this.to + (d + (this.inherited + 10 * d) * tau) * e,
341
+ velocity: e * (this.inherited - 10 * (this.inherited + 10 * d) * tau) / seconds
342
+ };
343
+ }
344
+ set(target, duration, now, delay = 0) {
345
+ let { position, velocity } = this.sample(now);
346
+ if (duration <= 0)
347
+ return this.snap(target);
348
+ let seconds = duration / 1000, distance = position - target, limit = Math.max(Math.abs(distance), 1) * 12 / seconds;
349
+ return this.inherited = clamp(delay > 0 ? 0 : velocity, -limit, limit) * seconds, this.from = position, this.to = target, this.start = now + delay, this.duration = duration, this;
350
+ }
351
+ snap(position) {
352
+ return this.from = this.to = position, this.duration = 0, this.inherited = 0, this;
353
+ }
354
+ resting(now) {
355
+ return this.duration <= 0 || now - this.start >= this.duration;
356
+ }
357
+ span(now) {
358
+ if (this.resting(now))
359
+ return [this.to, this.to];
360
+ return [Math.min(this.from, this.to), Math.max(this.from, this.to)];
361
+ }
362
+ }
363
+ function rollTarget(position, digit, trend) {
364
+ let target = Math.floor(position / 10) * 10 + digit;
365
+ if (trend > 0 && target < position - 0.001)
366
+ target += 10;
367
+ else if (trend < 0 && target > position + 0.001)
368
+ target -= 10;
369
+ else if (trend === 0)
370
+ target += Math.round((position - target) / 10) * 10;
371
+ return target;
372
+ }
373
+ var numeral = (position) => String((position % 10 + 10) % 10);
374
+ function entranceOffset(elapsed, duration, hold = 0.14) {
375
+ if (duration <= 0 || elapsed >= duration)
376
+ return 0;
377
+ let p = clamp((elapsed / duration - hold) / (1 - hold), 0, 1);
378
+ return (1 + 10 * p) * Math.exp(-10 * p);
379
+ }
380
+
381
+ // src/panel/flash.ts
382
+ var FLASH_MS = 320, flashes = /* @__PURE__ */ new WeakMap;
383
+ function flash(button, wake) {
384
+ if (!button || button.hidden)
385
+ return;
386
+ flashes.set(button, button.ownerDocument.defaultView?.performance.now() ?? performance.now()), button.dataset.kFlash = button.dataset.kFlash === "a" ? "b" : "a", wake?.();
387
+ }
388
+ function flashOf(button, now) {
389
+ let at = flashes.get(button);
390
+ if (at === void 0)
391
+ return 0;
392
+ let p = (now - at) / 320;
393
+ if (p >= 1 || p < 0)
394
+ return 0;
395
+ return (1 - p) ** 3;
396
+ }
397
+
398
+ // src/panel/feel.ts
399
+ var clamp2 = (x, a, b) => Math.min(b, Math.max(a, x)), smooth = (a, b, x) => {
400
+ let t = clamp2((x - a) / (b - a), 0, 1);
401
+ return t * t * (3 - 2 * t);
402
+ }, PRECISION = [
403
+ [24, 44],
404
+ [76, 96],
405
+ [126, 146]
406
+ ], offRow = (y, top, bottom) => Math.max(0, top - y, y - bottom), precisionDepth = (off) => PRECISION.reduce((n, [a, b]) => n + smooth(a, b, Math.abs(off)), 0), precisionRate = (off) => 2 ** -precisionDepth(off), RULER = [
407
+ [20, 5, 0.2],
408
+ [40, 4, 0.17],
409
+ [80, 3, 0.14]
410
+ ], RULER_NEAR = 36, RULER_FADE = 120, rulerLevels = (steps) => {
411
+ let n = 0;
412
+ while (n < 3 && 10 * 2 ** (n + 1) <= steps)
413
+ n++;
414
+ return n;
415
+ }, rulerAlpha = (depth, level) => clamp2(depth - (level - 1), 0, 1), rulerDepth = (rate, levels) => rate >= 1 ? 0 : clamp2(-Math.log2(rate), 0, levels), THIN = 1.7, THINNEST = 0.85, thinOf = (scale) => Math.max(0.85, 1 - 1.7 * Math.max(0, scale - 1));
416
+ var roundOf = (give) => clamp2(Math.abs(give) / 4, 0, 1), stretchOf = (give, length) => ({
417
+ scale: length > 0 ? 1 + Math.abs(give) / length : 1,
418
+ anchor: give < 0 ? 1 : 0
419
+ }), stretchAt = (at, give, length) => {
420
+ let { scale, anchor } = stretchOf(give, length), o = anchor * length;
421
+ return o + (at - o) * scale;
422
+ }, giveRoom = (room, most) => Math.max(0.5, Math.min(most, room)), DETENTS = {
423
+ default: { hold: 4, ramp: 6 },
424
+ base: { hold: 4, ramp: 6 },
425
+ round: { hold: 2.5, ramp: 4 }
426
+ }, detentScale = (rate) => clamp2(rate, 0, 1) ** 1.5, reach = (d, k = 1) => {
427
+ let { hold, ramp } = DETENTS[d.kind];
428
+ return { h: hold * k, r: ramp * k };
429
+ };
430
+ function detentsOf(width, min, max, o) {
431
+ let span = max - min;
432
+ if (!(span > 0) || !(width > 0))
433
+ return [];
434
+ let list = [], add = (value, kind) => {
435
+ if (value === void 0 || !Number.isFinite(value) || value < min || value > max)
436
+ return;
437
+ let at = (value - min) / span * width, room = (d) => {
438
+ let a = reach(d), b = DETENTS[kind];
439
+ return a.h + a.r + b.hold + b.ramp;
440
+ };
441
+ if (list.some((d) => Math.abs(d.at - at) < room(d)))
442
+ return;
443
+ list.push({ at, value, kind });
444
+ };
445
+ add(o.value, "default"), add(o.base, "base");
446
+ for (let v of o.round ?? [])
447
+ add(v, "round");
448
+ return list;
449
+ }
450
+ var roundValues = (min, max) => {
451
+ let mid = Number(((min + max) / 2).toPrecision(12));
452
+ return min < 0 && max > 0 && mid !== 0 ? [0, mid] : [mid];
453
+ };
454
+ function warp(x, list, k = 1) {
455
+ for (let d of list) {
456
+ let { h, r } = reach(d, k), u = x - d.at, a = Math.abs(u);
457
+ if (a >= h + r)
458
+ continue;
459
+ if (a <= h)
460
+ return d.at;
461
+ return d.at + Math.sign(u) * (a - h) * (h + r) / r;
462
+ }
463
+ return x;
464
+ }
465
+ function unwarp(shown, x, list, k = 1) {
466
+ for (let d of list) {
467
+ let { h, r } = reach(d, k), v = shown - d.at, a = Math.abs(v);
468
+ if (a >= h + r)
469
+ continue;
470
+ if (a === 0)
471
+ return d.at + clamp2(x - d.at, -h, h);
472
+ return d.at + Math.sign(v) * (h + a * r / (h + r));
473
+ }
474
+ return shown;
475
+ }
476
+ var REARM = 2;
477
+
478
+ class Notches {
479
+ list;
480
+ held = -1;
481
+ armed;
482
+ constructor(list, x) {
483
+ this.list = list;
484
+ this.armed = list.map((d) => Math.abs(x - d.at) > REARM), this.held = list.findIndex((d) => warp(x, [d]) === d.at);
485
+ }
486
+ move(x, k = 1) {
487
+ let shown = warp(x, this.list, k), held = this.list.findIndex((d) => d.at === shown), caught;
488
+ if (this.list.forEach((d, i) => {
489
+ let { h, r } = reach(d, k);
490
+ if (Math.abs(x - d.at) > Math.max(h + r, REARM))
491
+ this.armed[i] = !0;
492
+ }), held >= 0 && held !== this.held && this.armed[held])
493
+ caught = this.list[held], this.armed[held] = !1;
494
+ return this.held = held, caught ? { shown, caught } : { shown };
495
+ }
496
+ }
497
+
498
+ // src/panel/scope.ts
499
+ function escapeTo(level, inCard = !0) {
500
+ if (level === "row")
501
+ return inCard ? "card" : "panel";
502
+ if (level === "card")
503
+ return "panel";
504
+ return "none";
505
+ }
506
+ function commandScope(level, selection = !1) {
507
+ if (selection)
508
+ return "selection";
509
+ if (level === "row")
510
+ return "row";
511
+ if (level === "card")
512
+ return "group";
513
+ return "all";
514
+ }
515
+ function scopeLabel(verb, scope) {
516
+ return `${verb} ${scope === "selection" ? "selected" : scope === "row" ? "this" : scope === "group" ? "group" : "all"}`;
517
+ }
518
+ function crumbTrail(path) {
519
+ let out = [];
520
+ for (let level of ["card", "row", "editor"]) {
521
+ let label = path[level]?.trim();
522
+ if (label)
523
+ out.push({ level, label });
524
+ }
525
+ return out;
526
+ }
527
+ function escTarget(trail, title) {
528
+ if (!trail.length)
529
+ return;
530
+ return trail.at(-2)?.label ?? (title.trim() || "panel");
531
+ }
532
+ function fitCrumbs(widths, room, sep, more, hint = 0) {
533
+ let n = widths.length;
534
+ if (!n)
535
+ return { from: 0, hint: !1 };
536
+ let span = (from) => {
537
+ let w = 0;
538
+ for (let i = from;i < n; i++)
539
+ w += widths[i] + (i > from ? sep : 0);
540
+ return from > 0 ? w + more + sep : w;
541
+ }, from = 0;
542
+ while (from < n - 1 && span(from) > room)
543
+ from++;
544
+ let used = span(from);
545
+ return { from, hint: hint > 0 && used + sep + hint <= room };
546
+ }
547
+ function digitOption(key, count) {
548
+ if (!/^[1-9]$/.test(key))
549
+ return;
550
+ let i = Number(key) - 1;
551
+ return i < count ? i : void 0;
552
+ }
553
+ function digitHints(count, showing) {
554
+ let n = Math.min(9, count);
555
+ if (n <= 0)
556
+ return [];
557
+ if (showing)
558
+ return Array.from({ length: n }, (_, i) => String(i + 1));
559
+ return [n === 1 ? "1" : `1–${n}`];
560
+ }
561
+
562
+ // src/panel/crumbs.ts
563
+ var glide2 = springEasing(2 * Math.PI / 0.3, 1), CRUMB_GLIDE = `${glide2.ms}ms ${glide2.easing}`, SLIDE = 5, GAP = 6, HINT_GAP = 10, SEP = "›";
564
+ function createCrumbs(o) {
565
+ let doc = o.header.ownerDocument, element = doc.createElement("div");
566
+ element.className = "knobs-crumbs", element.setAttribute("aria-hidden", "true"), o.header.append(element);
567
+ let live = /* @__PURE__ */ new Map, trail = [], esc, hinting = !1, ctx, width = (text) => {
568
+ if (ctx === void 0)
569
+ ctx = doc.createElement("canvas").getContext("2d");
570
+ if (!ctx)
571
+ return text.length * 6;
572
+ let cs = getComputedStyle(element);
573
+ return ctx.font = `${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`, ctx.measureText(text).width;
574
+ }, titleEnd = (head) => {
575
+ let t = o.title.getBoundingClientRect();
576
+ if (!o.title.textContent)
577
+ return { x: t.left - head.left, right: t.right - head.left, t };
578
+ let range = doc.createRange();
579
+ range.selectNodeContents(o.title);
580
+ let r = range.getBoundingClientRect();
581
+ return { x: Math.min(r.right, t.right) - head.left + GAP, right: t.right - head.left, t };
582
+ }, build = () => {
583
+ if (!trail.length)
584
+ return { pieces: [], xs: [] };
585
+ let head = o.header.getBoundingClientRect(), { x, right } = titleEnd(head), sepW = width(SEP) + GAP * 2, escText = esc ? `esc ↑ ${esc}` : "", fit = fitCrumbs(trail.map((c) => width(c.label)), Math.max(0, right - x - sepW), sepW, width("…"), escText ? width(escText) + HINT_GAP - sepW : 0), pieces = [{ key: "sep:lead", text: SEP, kind: "sep" }];
586
+ if (fit.from > 0)
587
+ pieces.push({ key: "more", text: "…", kind: "more" }, { key: "sep:more", text: SEP, kind: "sep" });
588
+ if (trail.forEach((c, i) => {
589
+ if (i < fit.from)
590
+ return;
591
+ if (i > fit.from)
592
+ pieces.push({ key: `sep:${c.level}`, text: SEP, kind: "sep" });
593
+ pieces.push({
594
+ key: `${c.level}:${c.label}`,
595
+ text: c.label,
596
+ kind: "crumb",
597
+ level: c.level,
598
+ deep: i === trail.length - 1
599
+ });
600
+ }), escText && fit.hint)
601
+ pieces.push({ key: `esc:${esc}`, text: escText, kind: "esc" });
602
+ let xs = [], at = x;
603
+ for (let p of pieces) {
604
+ if (p.kind === "esc")
605
+ at += HINT_GAP - GAP;
606
+ xs.push(at), at += width(p.text) + (p.kind === "sep" ? GAP : p.kind === "esc" ? 0 : GAP);
607
+ }
608
+ return { pieces, xs };
609
+ }, place = () => {
610
+ let head = o.header.getBoundingClientRect(), t = o.title.getBoundingClientRect();
611
+ element.style.top = `${(t.top - head.top + t.height / 2).toFixed(2)}px`;
612
+ }, leave = (key, node, x) => {
613
+ if (live.delete(key), node.dataset.leaving = "", node.style.opacity = "0", !o.reduced())
614
+ node.style.translate = `${(x - SLIDE).toFixed(2)}px -50%`;
615
+ setTimeout(() => node.remove(), 320);
616
+ }, render = () => {
617
+ place();
618
+ let { pieces, xs } = build(), keep = new Set(pieces.map((p) => p.key));
619
+ for (let [key, node] of live)
620
+ if (!keep.has(key))
621
+ leave(key, node, parseFloat(node.dataset.x ?? "0"));
622
+ let reduced = o.reduced();
623
+ pieces.forEach((p, i) => {
624
+ let x = xs[i], node = live.get(p.key);
625
+ if (!node) {
626
+ if (node = doc.createElement("span"), node.className = `knobs-crumb-${p.kind}`, node.textContent = p.text, p.level)
627
+ node.dataset.level = p.level;
628
+ node.style.opacity = "0", node.style.translate = `${(x + (reduced ? 0 : SLIDE)).toFixed(2)}px -50%`, element.append(node), live.set(p.key, node), node.getBoundingClientRect();
629
+ }
630
+ node.dataset.x = String(x), node.toggleAttribute("data-deep", Boolean(p.deep)), node.style.translate = `${x.toFixed(2)}px -50%`, node.style.opacity = p.kind === "esc" && !hinting ? "0" : "1";
631
+ }), o.wake();
632
+ }, onDown = (event) => {
633
+ if (!event.target.closest?.("[data-level]"))
634
+ return;
635
+ event.preventDefault(), event.stopPropagation();
636
+ }, onClick = (event) => {
637
+ let node = event.target.closest?.("[data-level]");
638
+ if (!node || "leaving" in node.dataset)
639
+ return;
640
+ event.stopPropagation(), o.jump(node.dataset.level);
641
+ };
642
+ element.addEventListener("pointerdown", onDown), element.addEventListener("mousedown", onDown), element.addEventListener("click", onClick);
643
+ let size = "", sizes = typeof ResizeObserver > "u" ? void 0 : new ResizeObserver(() => {
644
+ let r = o.title.getBoundingClientRect(), next = `${r.width}|${r.height}|${o.title.textContent}`;
645
+ if (next === size)
646
+ return;
647
+ if (size = next, trail.length)
648
+ render();
649
+ });
650
+ return sizes?.observe(o.header), sizes?.observe(o.title), {
651
+ element,
652
+ set(next, nextEsc) {
653
+ if (next.length === trail.length && next.every((c, i) => c.level === trail[i].level && c.label === trail[i].label) && nextEsc === esc)
654
+ return;
655
+ trail = next, esc = nextEsc, render();
656
+ },
657
+ hint(on) {
658
+ if (on === hinting)
659
+ return;
660
+ hinting = on;
661
+ for (let [key, node] of live)
662
+ if (key.startsWith("esc:"))
663
+ node.style.opacity = on ? "1" : "0";
664
+ o.wake();
665
+ },
666
+ layout() {
667
+ if (trail.length)
668
+ render();
669
+ },
670
+ dispose() {
671
+ sizes?.disconnect(), element.remove();
672
+ }
673
+ };
674
+ }
675
+
396
676
  // src/panel/styles.ts
397
- var lead = springEasing(LEAD.omega, LEAD.zeta), trail = springEasing(TRAIL.omega, TRAIL.zeta), fill = springEasing(FILL.omega, FILL.zeta), LEAD_T = `${lead.ms}ms var(--k-lead)`, TRAIL_T = `calc(${trail.ms}ms * (.6 + .4 * clamp(0, var(--k-blur), 2))) var(--k-trail)`, FILL_T = "calc(var(--k-fill-ms) * 1ms * clamp(var(--k-blur),.1,2)) var(--k-fill-ease)", SLIDE_T = "calc(var(--k-fill-ms) * 1ms) var(--k-fill-ease)", snappy = springEasing(2 * Math.PI / 0.25, 1), snappyOut = springEasing(2 * Math.PI / 0.2, 1), BLOOM_IN = `${snappy.ms}ms ${snappy.easing}`, BLOOM_OUT = `${snappyOut.ms}ms ${snappyOut.easing}`, DELAY = "var(--k-delay,0ms)", POINTER_ROW = ".knobs-panel[data-k-row-hover]:not([data-keyboard])", DELAYED = ".knobs-fill,.knobs-handle,.knobs-seg-pill,.knobs-toggle,.knobs-toggle i,.knobs-arm,.knobs-curve,.knobs-swatch,.knobs-strip-thumb,.knobs-pad>i,.knobs-ease-handle,.knobs-range-track,.knobs-range-fill,.knobs-range-thumb,.knobs-dial-hand,.knobs-grad,.knobs-grad-mini,.knobs-grad-mini>i,.knobs-grad-stop,.knobs-graph-pt", GRAD_N = GRADIENT_SAMPLES, gradStops = (at) => Array.from({ length: GRAD_N }, (_, i) => `var(--kgrad-${i}) ${at(i)}`).join(","), GRAD_STRIP = `linear-gradient(to right,${gradStops((i) => `calc(var(--kg-r) + (100% - var(--kg-r) * 2) * ${Number((i / (GRAD_N - 1)).toFixed(5))})`)})`, GRAD_MINI = `linear-gradient(to right,${gradStops((i) => `${Number((i / (GRAD_N - 1) * 100).toFixed(3))}%`)})`, GRAD_PROPS = Array.from({ length: GRAD_N }, (_, i) => `@property --kgrad-${i}{syntax:"<color>";inherits:false;initial-value:transparent}`).join(`
677
+ var lead = springEasing(LEAD.omega, LEAD.zeta), trail = springEasing(TRAIL.omega, TRAIL.zeta), fill = springEasing(FILL.omega, FILL.zeta), LEAD_T = `${lead.ms}ms var(--k-lead)`, TRAIL_T = `calc(${trail.ms}ms * (.6 + .4 * clamp(0, var(--k-blur), 2))) var(--k-trail)`, FILL_T = "calc(var(--k-fill-ms) * 1ms * clamp(var(--k-blur),.1,2)) var(--k-fill-ease)", SLIDE_T = "calc(var(--k-fill-ms) * 1ms) var(--k-fill-ease)", snappy = springEasing(2 * Math.PI / 0.25, 1), snappyOut = springEasing(2 * Math.PI / 0.2, 1), BLOOM_IN = `${snappy.ms}ms ${snappy.easing}`, BLOOM_OUT = `${snappyOut.ms}ms ${snappyOut.easing}`, DELAY = "var(--k-delay,0ms)", armed = springEasing(2 * Math.PI / 0.3, 1), ARMED_T = `${armed.ms}ms ${armed.easing}`, POINTER_ROW = ".knobs-panel[data-k-row-hover]:not([data-keyboard])", DELAYED = ".knobs-fill,.knobs-handle,.knobs-seg-pill,.knobs-toggle,.knobs-toggle i,.knobs-arm,.knobs-curve,.knobs-swatch,.knobs-strip-thumb,.knobs-pad>i,.knobs-ease-handle,.knobs-range-track,.knobs-range-fill,.knobs-range-thumb,.knobs-dial-hand,.knobs-grad,.knobs-grad-mini,.knobs-grad-mini>i,.knobs-grad-stop,.knobs-graph-pt", GRAD_N = GRADIENT_SAMPLES, gradStops = (at) => Array.from({ length: GRAD_N }, (_, i) => `var(--kgrad-${i}) ${at(i)}`).join(","), GRAD_STRIP = `linear-gradient(to right,${gradStops((i) => `calc(var(--kg-r) + (100% - var(--kg-r) * 2) * ${Number((i / (GRAD_N - 1)).toFixed(5))})`)})`, GRAD_MINI = `linear-gradient(to right,${gradStops((i) => `${Number((i / (GRAD_N - 1) * 100).toFixed(3))}%`)})`, GRAD_PROPS = Array.from({ length: GRAD_N }, (_, i) => `@property --kgrad-${i}{syntax:"<color>";inherits:false;initial-value:transparent}`).join(`
398
678
  `), COLOR_TOKENS = [
399
679
  "bg",
400
680
  "surface",
@@ -433,6 +713,7 @@ ${register(["width"], "<length-percentage>", "288px")}
433
713
  @property --k-head-s{syntax:"<number>";inherits:true;initial-value:0}
434
714
  @property --k-head-v{syntax:"<number>";inherits:true;initial-value:0}
435
715
  @property --kc-value-size{syntax:"<length>";inherits:true;initial-value:11.5px}
716
+ @property --k-armed{syntax:"<number>";inherits:false;initial-value:0}
436
717
  @property --kr-lo{syntax:"<number>";inherits:true;initial-value:0}
437
718
  @property --kpt-sel{syntax:"<number>";inherits:false;initial-value:1}
438
719
  @property --kgs-sel{syntax:"<number>";inherits:false;initial-value:1}
@@ -454,6 +735,8 @@ ${GRAD_PROPS}
454
735
  --k-font-weight:400;--k-title-weight:600;--k-thumb:#fff;--k-lift:1;--k-frame:card;
455
736
  --k-row-gap:0px;--k-hover-in:0;--k-hover-out:120;--k-fill-base:var(--k-surface-hi);--k-fill-rest-mix:20%;--k-fill-hover-mix:var(--k-fill-rest-mix);--k-fill-active-mix:30%;--k-chrome:1;--k-handle:var(--k-accent);--k-handle-inset:0px;--k-handle-rest:0;--k-step-resist:0.25;--k-motion-blur:0;--k-range-blur:.5;--k-curve-blur:.5;--k-group-transform:uppercase;--k-value-font:var(--k-font);
456
737
  --k-lead:${lead.easing};--k-trail:${trail.easing};--k-fill-ease:${fill.easing};--k-fill-ms:${fill.ms};
738
+ --k-lens-ms:180;--k-lens-lead:1;--k-lens-wash:.9;--k-lens-line:.65;--k-lens-panel-wash:.35;--k-lens-inset:0px;--k-lens-panel-inset:0px;
739
+ --k-armed-edge:.1;--k-armed-glow:0;--k-armed-glow-a:0;--k-title-rest:.6;--k-esc-hint-ms:1800;
457
740
  }
458
741
  /* Glass edges are hairlines: --k-rim-width is one device pixel at common pixel ratios. */
459
742
  @media(min-resolution:1.5dppx){:where(:root,:host){--k-rim-width:.67px}}
@@ -519,7 +802,7 @@ ${GRAD_PROPS}
519
802
  .knobs-panel,.knobs-chip,.knobs-menu{letter-spacing:normal;word-spacing:normal;text-transform:none;text-indent:0;text-shadow:none;text-decoration:none;font-style:normal;font-variant:normal;font-feature-settings:normal;font-kerning:auto;white-space:normal;direction:ltr;margin:0;float:none}
520
803
  :where(.knobs-panel,.knobs-chip) :where(*,*::before,*::after){box-sizing:border-box}
521
804
  .knobs-panel .rn-slot{box-sizing:content-box}
522
- .knobs-panel [hidden],.knobs-chip[hidden]{display:none!important}
805
+ .knobs-panel [hidden],.knobs-panel[hidden],.knobs-chip[hidden]{display:none!important}
523
806
  .knobs-mono{font-family:var(--k-mono);font-variant-numeric:tabular-nums}
524
807
 
525
808
  /* The chip: a round cog button, or a capsule with a label beside the cog. */
@@ -546,13 +829,17 @@ ${GRAD_PROPS}
546
829
  .knobs-panel,.knobs-chip{--k-token-t:calc(var(--k-fill-ms) * 1ms) var(--k-fill-ease)}
547
830
  :root[data-knobs-scrub] .knobs-chip{--k-token-t:0s}
548
831
  :root[data-knobs-scrub] .knobs-panel{transition:none}
549
- @media(prefers-reduced-motion:reduce){.knobs-chip{--k-token-t:0s}.knobs-panel.knobs-panel{transition:none}}
832
+ @media(prefers-reduced-motion:reduce){.knobs-chip{--k-token-t:0s}.knobs-panel.knobs-panel{transition:--k-armed .16s linear}}
833
+ .knobs-panel[data-k-armed]{--k-armed:1}
550
834
  /* Panel material: --k-glass (0 opaque … 1 clearest) thins the surface over a blurred, saturated
551
835
  backdrop, toned toward the theme (--k-glass-tone dims it under dark panels) so text keeps its
552
836
  contrast. Its edge is a hairline (--k-edge, --k-rim-width wide) and a hairline of light (--k-rim)
553
837
  catches its top. */
554
- .knobs-panel{-webkit-user-select:none;user-select:none;transition:${TOKEN_TRANSITIONS};position:fixed;z-index:2147480001;width:var(--k-width);max-width:calc(100vw - 16px);max-height:calc(100dvh - 16px);overflow:auto;padding:var(--k-pad);
555
- background:color-mix(in srgb,var(--k-bg) calc(100% - var(--k-glass) * 20%),transparent);border:1px solid transparent;border-radius:var(--k-panel-radius);box-shadow:var(--k-shadow),0 0 0 var(--k-rim-width) var(--k-edge),inset 0 var(--k-rim-width) 0 var(--k-rim);scrollbar-width:thin;
838
+ .knobs-panel{-webkit-user-select:none;user-select:none;transition:${TOKEN_TRANSITIONS},--k-armed ${ARMED_T};position:fixed;z-index:2147480001;width:var(--k-width);max-width:calc(100vw - 16px);max-height:calc(100dvh - 16px);overflow:auto;padding:var(--k-pad);
839
+ background:color-mix(in srgb,var(--k-bg) calc(100% - var(--k-glass) * 20%),transparent);border:1px solid transparent;border-radius:var(--k-panel-radius);box-shadow:var(--k-shadow),0 0 0 var(--k-rim-width) var(--ka-edge),inset 0 var(--k-rim-width) 0 var(--k-rim),var(--ka-glow);scrollbar-width:thin;
840
+ /* Armed (focus inside: keys come here), the edge takes the accent and a faint glow lifts it. */
841
+ --ka-edge:color-mix(in srgb,var(--k-accent) calc(var(--k-armed) * var(--k-armed-edge) * 100%),var(--k-edge));
842
+ --ka-glow:0 0 calc(var(--k-armed-glow) * 1px) color-mix(in srgb,var(--k-accent) calc(var(--k-armed) * var(--k-armed-glow-a) * var(--k-chrome,1) * 100%),transparent);
556
843
  --k-backdrop:blur(calc(var(--k-glass) * 24px)) saturate(calc(1 + var(--k-glass) * .8)) brightness(calc(1 + (var(--k-glass-tone) - 1) * var(--k-glass)));
557
844
  -webkit-backdrop-filter:var(--k-backdrop);backdrop-filter:var(--k-backdrop);
558
845
  animation:knobs-in .16s var(--k-ease);}
@@ -570,7 +857,7 @@ ${GRAD_PROPS}
570
857
  .knobs-morph::after{box-shadow:var(--k-shadow);opacity:var(--p)}
571
858
  /* Meanwhile the chip is just its label, fading on the shape. */
572
859
  .knobs-chip.knobs-chip[data-morph]{visibility:visible;pointer-events:auto;transform:none;transition:none;background:transparent;border-color:transparent;box-shadow:none}
573
- .knobs-panel.knobs-inline{position:relative;z-index:auto;left:auto!important;top:auto!important;max-width:100%;max-height:none;box-shadow:0 0 0 var(--k-rim-width) color-mix(in srgb,var(--k-edge) calc(var(--k-chrome) * 100%),transparent),inset 0 var(--k-rim-width) 0 color-mix(in srgb,var(--k-rim) calc(var(--k-chrome) * 100%),transparent);animation:none;
860
+ .knobs-panel.knobs-inline{position:relative;z-index:auto;left:auto!important;top:auto!important;max-width:100%;max-height:none;box-shadow:0 0 0 var(--k-rim-width) color-mix(in srgb,var(--ka-edge) calc(var(--k-chrome) * 100%),transparent),inset 0 var(--k-rim-width) 0 color-mix(in srgb,var(--k-rim) calc(var(--k-chrome) * 100%),transparent),var(--ka-glow);animation:none;
574
861
  /* --k-chrome 0: an embedded panel sheds its card (background, border, rim) for the host's column,
575
862
  border width too, so its rows line up with the column's edges. */
576
863
  border-width:calc(var(--k-chrome) * 1px);
@@ -589,7 +876,7 @@ ${GRAD_PROPS}
589
876
  :is(.knobs-panel,.knobs-menu) :is(:focus,:focus-visible):where(:not(.knobs-accessory *)){outline:none}
590
877
 
591
878
  /* Header */
592
- .knobs-panel header{display:flex;align-items:center;justify-content:space-between;gap:6px;cursor:grab;touch-action:none;padding:0 var(--k-inset) var(--k-section-gap);min-height:var(--k-icon-btn)}
879
+ .knobs-panel header{position:relative;display:flex;align-items:center;justify-content:space-between;gap:6px;cursor:grab;touch-action:none;padding:0 var(--k-inset) var(--k-section-gap);min-height:var(--k-icon-btn)}
593
880
  .knobs-panel.knobs-inline header,.knobs-panel.knobs-inline header:active{cursor:default}
594
881
  /* The title row stays pinned while the rows scroll under it, in the panel (it scrolls once it's
595
882
  bounded) or, with .knobs-pin-ancestor (which also makes the panel overflow:visible), in the
@@ -616,7 +903,21 @@ ${GRAD_PROPS}
616
903
  .knobs-panel[data-gpu-active="true"]:not(.knobs-page)>header::after{display:none}
617
904
  }
618
905
  .knobs-panel header:active{cursor:grabbing}
619
- .knobs-panel header strong{flex:1;min-width:0;font-weight:var(--k-title-weight);font-size:var(--k-text-title);letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s var(--k-ease)}
906
+ .knobs-panel header strong{flex:1;min-width:0;font-weight:var(--k-title-weight);font-size:var(--k-text-title);letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:opacity .2s var(--k-ease),color ${ARMED_T};
907
+ color:color-mix(in srgb,var(--k-text) calc(var(--k-title-rest) * 100%),var(--k-label))}
908
+ /* At rest a panel's title is a little quiet; armed (keys come here) it's at full strength. */
909
+ .knobs-panel[data-k-armed] header strong{color:var(--k-text)}
910
+ /* The scope line beside the title (crumbs.ts): where keys go, and for a moment where Esc goes. */
911
+ .knobs-crumbs{position:absolute;left:0;top:50%;height:0;pointer-events:none;white-space:nowrap;font-size:var(--k-text-meta);font-weight:var(--k-label-weight);line-height:1.2;letter-spacing:0;text-transform:none}
912
+ .knobs-crumbs>*{position:absolute;left:0;top:0;translate:0 -50%;color:var(--k-dim);transition:opacity .2s var(--k-ease),translate ${CRUMB_GLIDE},color .2s var(--k-ease)}
913
+ .knobs-crumbs>[data-leaving]{transition:opacity .14s cubic-bezier(.4,0,1,1),translate .14s cubic-bezier(.4,0,1,1)}
914
+ .knobs-crumbs>.knobs-crumb-crumb{pointer-events:auto;cursor:pointer}
915
+ .knobs-crumbs>.knobs-crumb-crumb[data-deep]{color:var(--k-text)}
916
+ .knobs-crumbs>.knobs-crumb-crumb:not([data-deep]):hover{color:var(--k-label)}
917
+ .knobs-crumbs>.knobs-crumb-sep{color:color-mix(in srgb,var(--k-dim) 70%,transparent)}
918
+ .knobs-crumbs>.knobs-crumb-esc{font-family:var(--k-mono);font-size:var(--k-text-cap);transition:opacity .35s var(--k-ease),translate ${CRUMB_GLIDE}}
919
+ @media(prefers-reduced-motion:reduce){.knobs-panel .knobs-crumbs>*{transition:opacity .15s linear!important}}
920
+ .knobs-panel[data-gpu-active="true"] .knobs-crumbs>*{color:transparent!important}
620
921
  /* Holding \\ shows the values from before the last change; the title dims meanwhile. */
621
922
  .knobs-panel[data-comparing] header strong{opacity:.4}
622
923
  .knobs-head-actions{display:flex;align-items:center;margin-right:calc((var(--k-icon-size) - var(--k-icon-btn)) / 2)}
@@ -714,6 +1015,23 @@ ${GRAD_PROPS}
714
1015
  .knobs-card{position:relative;margin-bottom:var(--k-section-gap);border-radius:var(--k-radius);background:var(--k-surface);overflow:hidden}
715
1016
  .knobs-card:last-child{margin-bottom:0}
716
1017
  .knobs-card>*{position:relative;min-height:var(--k-row);transition:background var(--k-hover-t),opacity .2s}
1018
+ .knobs-panel [data-tab-opacity]{transition:none!important}
1019
+ /* Rubber-band ends (drawStretch in give.ts): past a limit a control stretches about its far end
1020
+ and thins a little, its stretching corners rounding off; a row draws past its card's edge
1021
+ meanwhile (keeping the card's corners where it has them), and its text stays put. */
1022
+ .knobs-stretching{scale:var(--k-sx) var(--k-sy);transform-origin:var(--k-sox) var(--k-soy)}
1023
+ .knobs-card[data-k-stretch]{overflow:visible}
1024
+ .knobs-card>:first-child{--k-rt:1}
1025
+ .knobs-card>:last-child{--k-rb:1}
1026
+ .knobs-card>.knobs-stretching{--k-r:var(--k-radius);border-radius:calc(var(--k-r) * var(--k-rt,0)) calc(var(--k-r) * var(--k-rt,0)) calc(var(--k-r) * var(--k-rb,0)) calc(var(--k-r) * var(--k-rb,0))}
1027
+ .knobs-card>.knobs-stretching[data-stretch="end"]{border-top-right-radius:calc(var(--k-r) * max(var(--k-rt,0),var(--k-sf)));border-bottom-right-radius:calc(var(--k-r) * max(var(--k-rb,0),var(--k-sf)))}
1028
+ .knobs-card>.knobs-stretching[data-stretch="start"]{border-top-left-radius:calc(var(--k-r) * max(var(--k-rt,0),var(--k-sf)));border-bottom-left-radius:calc(var(--k-r) * max(var(--k-rb,0),var(--k-sf)))}
1029
+ .knobs-stretching>:is(.knobs-track,.knobs-range-track){border-radius:inherit}
1030
+ .knobs-slider.knobs-stretching :is(.knobs-label,.knobs-value),.knobs-range.knobs-stretching .knobs-range-text{scale:calc(1 / var(--k-sx)) calc(1 / var(--k-sy))}
1031
+ .knobs-slider.knobs-stretching .knobs-label{transform-origin:calc(var(--k-ox) - var(--k-inset)) 0}
1032
+ .knobs-panel .knobs-slider.knobs-stretching .knobs-value{transform-origin:calc(var(--k-ox) - var(--k-w) + var(--k-inset) + 100%) 0}
1033
+ .knobs-range.knobs-stretching .knobs-range-text{transform-origin:var(--k-sox) 50%}
1034
+ .knobs-stretching>:is(.knobs-strip-thumb,.knobs-pad-puck,.knobs-pad-home){transform:scale(calc(1 / var(--k-sx)),calc(1 / var(--k-sy)))}
717
1035
  .knobs-card>:hover,.knobs-card>.knobs-active{background:var(--k-row-hover)}
718
1036
  .knobs-card>*+*::before{content:"";position:absolute;z-index:1;left:0;right:0;top:0;height:1px;transform:scaleY(.5);transform-origin:top;background:var(--k-line);pointer-events:none;transition:opacity .15s}
719
1037
  .knobs-card>::after{content:"";position:absolute;z-index:2;inset:0;background:var(--k-surface-hi);opacity:0;pointer-events:none;transition:opacity .15s,box-shadow .15s}
@@ -726,6 +1044,13 @@ ${GRAD_PROPS}
726
1044
  /* One wash at a time: while the pointer has a row (and keys haven't taken over since), a focus
727
1045
  wash elsewhere steps aside. */
728
1046
  ${POINTER_ROW} :is(.knobs-card>*,.knobs-sub,.knobs-x-head):not(:hover)::after{opacity:0}
1047
+ /* The focus lens (lens.ts) marks the level keys act on, and replaces the washes above: one shape,
1048
+ a soft wash and a hairline edge, placed from script (--kl-w, --kl-l). */
1049
+ .knobs-panel[data-k-lens] :is(.knobs-card>*,.knobs-sub,.knobs-x-head,.knobs-card,.knobs-tile)::after{opacity:0!important}
1050
+ .knobs-lens{position:absolute;left:0;top:0;z-index:2;width:0;height:0;opacity:0;pointer-events:none;box-sizing:border-box;contain:strict;will-change:transform,opacity;
1051
+ background:color-mix(in srgb,var(--k-surface-hi) calc(var(--kl-w,0) * 100%),transparent);
1052
+ box-shadow:inset 0 0 0 var(--k-rim-width) color-mix(in srgb,var(--k-line-hi) calc(var(--kl-l,0) * 100%),transparent)}
1053
+ .knobs-panel[data-gpu-active="true"] .knobs-lens{visibility:hidden}
729
1054
  .knobs-panel .knobs-slider{height:var(--k-row);contain:layout style}
730
1055
  .knobs-panel .knobs-row:where(:not(.knobs-slider,.knobs-x,.knobs-spring)){display:flex;align-items:center;justify-content:space-between;gap:8px;height:var(--k-row);padding:0 var(--k-inset);contain:layout style}
731
1056
  .knobs-panel .knobs-row>span:first-child{color:var(--k-label);font-weight:var(--k-label-weight);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -762,12 +1087,19 @@ ${POINTER_ROW} :is(.knobs-card>*,.knobs-sub,.knobs-x-head):not(:hover)::after{op
762
1087
  .knobs-smear .knobs-fill{background:linear-gradient(to left,transparent,var(--k-fill-tint) calc(var(--k-smear) * 1px));transform:translateX(calc(var(--k-fill) - 100% + var(--k-smear) * .5px))}
763
1088
  .knobs-smear .knobs-handle::before{scale:calc(1 + var(--k-smear) / 3) 1;opacity:calc(3 / (3 + var(--k-smear)))}
764
1089
  .knobs-animate .knobs-fill,.knobs-animate .knobs-handle{transition:--k-fill ${SLIDE_T} ${DELAY},--k-fill-mix .15s,opacity .15s}
765
- .knobs-settle .knobs-fill,.knobs-settle .knobs-handle{transition:--k-fill .1s cubic-bezier(.25,1,.5,1),--k-fill-mix .12s,opacity .15s}
766
- /* The default: a short notch rising from the bottom edge, only while the row is in hand. */
1090
+ /* The default: a short notch rising from the bottom edge, only while the row is in hand. The
1091
+ active preset's value hangs one from the top edge. */
767
1092
  .knobs-marks>.knobs-notch{top:auto;bottom:0;width:1.5px;height:4px;border-radius:999px 999px 0 0;transform:translateX(-50%);background:var(--k-accent);opacity:0;transition:opacity .2s,height .15s var(--k-ease)}
1093
+ .knobs-marks>.knobs-base{top:0;bottom:auto;border-radius:0 0 999px 999px}
768
1094
  .knobs-slider:is(:hover,.knobs-active,:has(:focus-visible)) .knobs-notch{opacity:.38}
769
- .knobs-slider.knobs-modified:is(:hover,.knobs-active,:has(:focus-visible)) .knobs-notch{opacity:.55}
770
- .knobs-magnet .knobs-notch{opacity:.95;height:6px}
1095
+ .knobs-slider.knobs-modified:is(:hover,.knobs-active,:has(:focus-visible)) .knobs-notch:not(.knobs-base){opacity:.55}
1096
+ .knobs-magnet .knobs-notch:not(.knobs-base),.knobs-held-base .knobs-base{opacity:.95;height:6px}
1097
+ /* Precision by distance: a fine ruler around the handle, a level for each halving of the rate,
1098
+ each finer, shorter and fainter than the last. */
1099
+ .knobs-ruler{position:absolute;inset:0;pointer-events:none;--k-ruler-w:linear-gradient(to right,transparent calc(var(--k-fill) - ${RULER_FADE}px),#000 calc(var(--k-fill) - ${RULER_NEAR}px),#000 calc(var(--k-fill) + ${RULER_NEAR}px),transparent calc(var(--k-fill) + ${RULER_FADE}px));-webkit-mask:var(--k-ruler-w);mask:var(--k-ruler-w)}
1100
+ .knobs-ruler>i{position:absolute;left:0;right:0;top:50%;opacity:0;translate:-.5px 0;transition:opacity .18s var(--k-ease)}
1101
+ ${RULER.map(([n, h, a], i) => `.knobs-ruler>i:nth-child(${i + 1}){height:${h}px;margin-top:${-h / 2}px;background:repeating-linear-gradient(to right,color-mix(in srgb,var(--k-accent) ${a * 100}%,transparent) 0 1px,transparent 1px calc(100% / ${n}))}`).join(`
1102
+ `)}
771
1103
  /* The handle is a full-width layer moved by the fill (translate's % is its own width, the track's). */
772
1104
  .knobs-handle{position:absolute;inset:0;pointer-events:none;transform:translateX(var(--k-fill));opacity:var(--k-handle-rest);transition:opacity .15s}
773
1105
  .knobs-handle::before{content:"";position:absolute;left:0;top:50%;width:3px;height:calc(var(--k-row) - 12px);margin-top:calc(6px - var(--k-row) / 2);translate:calc(-50% - var(--k-handle-inset)) 0;border-radius:var(--k-round);background:var(--k-handle);
@@ -977,7 +1309,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
977
1309
  /* Rows as separate bars (--k-row-gap above 0): each row carries its own surface, no hairlines.
978
1310
  A spring stacks its curve over its two sliders. */
979
1311
  .knobs-panel[data-rows] .knobs-card{display:flex;flex-direction:column;gap:var(--k-row-gap);background:transparent;overflow:visible}
980
- .knobs-panel[data-rows] .knobs-card>*{background:var(--k-surface);border-radius:var(--k-radius);overflow:hidden}
1312
+ .knobs-panel[data-rows] .knobs-card>*{background:var(--k-surface);border-radius:var(--k-radius);overflow:hidden;--k-rt:1;--k-rb:1}
981
1313
  .knobs-panel[data-rows] .knobs-card>:is(:hover,.knobs-active){background:color-mix(in srgb,var(--k-surface-hi) 50%,var(--k-surface))}
982
1314
  .knobs-panel[data-rows] :is(.knobs-card>*,.knobs-sub)::before{display:none}
983
1315
  .knobs-panel[data-rows] .knobs-card>.knobs-subhead{background:transparent}
@@ -1180,17 +1512,19 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
1180
1512
  /* Hint mode: stable short codes, zero layout shift */
1181
1513
  .knobs-hinting .knobs-hint-miss{opacity:var(--k-disabled-opacity);transition:opacity .1s}
1182
1514
  .knobs-hint-layer{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;z-index:5;animation:knobs-in .1s var(--k-ease)}
1183
- .knobs-hint{position:absolute;transform:translateY(-50%);display:inline-flex;align-items:center;height:15px;padding:0 4px;border-radius:var(--k-radius-sm);background:var(--k-accent);color:var(--k-bg);box-shadow:0 1px 3px rgba(0,0,0,calc(.35 * var(--k-lift)));
1515
+ /* The badges are <kbd>s, so they outrank the scoped reset's (0,1,1). */
1516
+ .knobs-panel .knobs-hint{position:absolute;transform:translateY(-50%);display:inline-flex;align-items:center;height:15px;padding:0 4px;border-radius:var(--k-radius-sm);background:var(--k-accent);color:var(--k-bg);box-shadow:0 1px 3px rgba(0,0,0,calc(.35 * var(--k-lift)));
1184
1517
  font:700 var(--k-text-cap)/1 var(--k-mono);letter-spacing:.04em;text-transform:uppercase;pointer-events:none}
1518
+ .knobs-panel .knobs-hint[hidden]{display:none}
1185
1519
  .knobs-hint-typed{opacity:var(--k-hint-typed-opacity)}
1186
1520
  .knobs-hint-rest{opacity:1}
1187
1521
 
1188
1522
  /* A focused row's options: tiny digits in hint mode's look, once focus settles (optkeys.ts). */
1189
1523
  .knobs-optkeys{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;z-index:5}
1190
- .knobs-optkey{position:absolute;transform:translateY(-50%);display:inline-flex;align-items:center;height:12px;padding:0 3px;border-radius:var(--k-radius-sm);background:var(--k-accent);color:var(--k-bg);box-shadow:0 1px 3px rgba(0,0,0,calc(.35 * var(--k-lift)));
1524
+ .knobs-panel .knobs-optkey{position:absolute;transform:translateY(-50%);display:inline-flex;align-items:center;height:12px;padding:0 3px;border-radius:var(--k-radius-sm);background:var(--k-accent);color:var(--k-bg);box-shadow:0 1px 3px rgba(0,0,0,calc(.35 * var(--k-lift)));
1191
1525
  font:700 calc(var(--k-text-cap) * .85)/1 var(--k-mono);letter-spacing:.04em;pointer-events:none;opacity:0;transition:opacity .12s var(--k-ease)}
1192
- .knobs-optkey-range{transform:translate(-100%,-50%)}
1193
- .knobs-optkey[data-shown]{opacity:.85;transition-duration:.15s}
1526
+ .knobs-panel .knobs-optkey-range{transform:translate(-100%,-50%)}
1527
+ .knobs-panel .knobs-optkey[data-shown]{opacity:.85;transition-duration:.15s}
1194
1528
 
1195
1529
  /* Custom actions */
1196
1530
  .knobs-panel .knobs-custom-actions{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:var(--k-gap);margin-top:var(--k-section-gap)}
@@ -1432,4 +1766,1054 @@ function injectStyles(doc) {
1432
1766
  style.dataset.knobsStyle = "", style.textContent = content, doc.head.append(style);
1433
1767
  }
1434
1768
 
1435
- export { CHECK_POINTS, CHECK_PATH, iconPath, iconSvg, readSession, writeSession, rolling_css_default, presetCss, FILL, LEAD, trailScale, Edges, POP_MS, popScale, VelocityTracker, clamp, Roll, rollTarget, numeral, entranceOffset, FLASH_MS, flash, flashOf, DELAYED, injectPreset, injectStyles };
1769
+ // src/panel/sound.ts
1770
+ var click = (f, p, d = 0.005, q = 4, at = 0) => ({
1771
+ f,
1772
+ q,
1773
+ a: 0.0008,
1774
+ d,
1775
+ p,
1776
+ at
1777
+ }), thud = (f, p, d, at = 0) => ({
1778
+ filter: "lowpass",
1779
+ f,
1780
+ q: 0.7,
1781
+ a: 0.0015,
1782
+ d,
1783
+ p,
1784
+ at
1785
+ }), air = (f, to, d, p, a = 0.012, at = 0) => ({
1786
+ f,
1787
+ to,
1788
+ glide: a + d * 0.6,
1789
+ q: 1.4,
1790
+ a,
1791
+ d,
1792
+ p,
1793
+ at
1794
+ }), knock = (f, d, p, at = 0) => ({
1795
+ wave: "sine",
1796
+ f: f * 1.6,
1797
+ to: f,
1798
+ glide: 0.018,
1799
+ a: 0.001,
1800
+ d,
1801
+ p,
1802
+ at
1803
+ }), glass = (f, d, p, at = 0) => [
1804
+ [1, 1],
1805
+ [2.76, 0.28],
1806
+ [5.4, 0.08]
1807
+ ].filter(([ratio]) => f * ratio <= 5000).map(([ratio, level]) => ({
1808
+ wave: "sine",
1809
+ f: f * ratio,
1810
+ a: 0.002,
1811
+ d: Math.max(0.028, d / ratio ** 0.75),
1812
+ p: p * level,
1813
+ at
1814
+ })), clamp3 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
1815
+ var cadence = (sinceMs) => clamp3((240 - sinceMs) / 170, 0, 1);
1816
+ function recipe(cue, detail = {}, speed = 0, random = Math.random) {
1817
+ let dir = Math.sign(detail.direction ?? 0), light = 1 - 0.45 * speed, vary = (amount) => 1 + (random() * 2 - 1) * amount;
1818
+ switch (cue) {
1819
+ case "step": {
1820
+ let k = 2 ** (clamp3(detail.position ?? 0.5, 0, 1) * 0.9 - 0.35), even = light / k ** 0.75, layers = [
1821
+ click(3000 * k, 0.5 * even, 0.004, 5),
1822
+ click(1300 * k, 0.22 * even, 0.01, 2.5)
1823
+ ];
1824
+ if (speed < 0.6)
1825
+ layers.push({ wave: "sine", f: 1480 * k, a: 0.001, d: 0.05, p: 0.008 * light });
1826
+ if (detail.strong)
1827
+ layers.push(knock(240, 0.03, 0.05), click(2100, 0.3, 0.006, 3));
1828
+ return { layers, gain: 1.8 };
1829
+ }
1830
+ case "limit":
1831
+ return { layers: [thud(380, 0.9, 0.05), knock(115, 0.06, 0.07)], gain: 0.9 };
1832
+ case "toggle":
1833
+ return dir < 0 ? {
1834
+ layers: [
1835
+ click(2800, 0.14, 0.003, 3),
1836
+ click(1450, 0.5, 0.013, 2, 0.012),
1837
+ knock(250, 0.034, 0.06, 0.012)
1838
+ ],
1839
+ gain: 1.2
1840
+ } : {
1841
+ layers: [
1842
+ click(3300, 0.14, 0.003, 3),
1843
+ click(1850, 0.5, 0.012, 2, 0.013),
1844
+ knock(330, 0.03, 0.06, 0.013)
1845
+ ],
1846
+ gain: 1.2
1847
+ };
1848
+ case "select": {
1849
+ let k = 1 + 0.06 * dir;
1850
+ return {
1851
+ layers: [click(2700 * k, 0.4 * light, 0.006, 3), knock(430 * k, 0.02, 0.045 * light)],
1852
+ gain: 1.5
1853
+ };
1854
+ }
1855
+ case "preset":
1856
+ return {
1857
+ layers: [
1858
+ click(2400, 0.3, 0.005, 3),
1859
+ thud(1800, 0.08, 0.004),
1860
+ ...glass(1046.5, 0.24, 0.016, 0.012),
1861
+ ...glass(1568, 0.2, 0.01, 0.012)
1862
+ ],
1863
+ gain: 2.5,
1864
+ room: 0.6
1865
+ };
1866
+ case "random":
1867
+ case "mutate": {
1868
+ let n = cue === "random" ? 5 : 2, layers = [], at = 0, gap = 0.044;
1869
+ for (let i = 0;i < n; i++) {
1870
+ let level = 0.8 ** i;
1871
+ layers.push(click(1500 + 1900 * random(), 0.36 * level, 0.006, 3, at), knock(380 + 320 * random(), 0.018, 0.04 * level, at)), at += gap * vary(0.15), gap *= 0.78;
1872
+ }
1873
+ return { layers, gain: 1.6 };
1874
+ }
1875
+ case "reset":
1876
+ return {
1877
+ layers: [
1878
+ { f: 2600, to: 750, glide: 0.09, q: 1.3, a: 0.07, d: 0.03, p: 0.18 },
1879
+ click(2200, 0.3, 0.004, 3, 0.082),
1880
+ knock(300, 0.02, 0.04, 0.082)
1881
+ ],
1882
+ gain: 1.1
1883
+ };
1884
+ case "copy":
1885
+ return {
1886
+ layers: [
1887
+ thud(2200, 0.06, 0.003),
1888
+ ...glass(1174.66, 0.2, 0.014),
1889
+ { wave: "sine", f: 1178.5, a: 0.002, d: 0.18, p: 0.006 },
1890
+ ...glass(1567.98, 0.24, 0.014, 0.06)
1891
+ ],
1892
+ gain: 2.6,
1893
+ room: 1
1894
+ };
1895
+ case "undo":
1896
+ case "redo": {
1897
+ let back = cue === "undo";
1898
+ return {
1899
+ layers: [
1900
+ air(back ? 1700 : 850, back ? 750 : 1800, 0.04, 0.14),
1901
+ knock(back ? 330 : 420, 0.02, 0.04, 0.028)
1902
+ ],
1903
+ gain: 1.4
1904
+ };
1905
+ }
1906
+ case "open":
1907
+ return { layers: [click(2600, 0.16, 0.004, 3), air(700, 1600, 0.045, 0.14, 0.02)], gain: 1.7 };
1908
+ case "close":
1909
+ return {
1910
+ layers: [
1911
+ air(1500, 650, 0.04, 0.13, 0.015),
1912
+ knock(240, 0.028, 0.06, 0.03),
1913
+ click(1700, 0.2, 0.006, 3, 0.03)
1914
+ ],
1915
+ gain: 1.1
1916
+ };
1917
+ case "hint":
1918
+ return {
1919
+ layers: [air(1200, 2400, 0.05, 0.06, 0.02), ...glass(1568, 0.14, 0.01, 0.012)],
1920
+ gain: 2.3,
1921
+ room: 0.8
1922
+ };
1923
+ case "type": {
1924
+ let k = vary(0.06);
1925
+ return {
1926
+ layers: [click(3600 * k, 0.16 * light, 0.003, 1), click(1600 * k, 0.3 * light, 0.008, 3)],
1927
+ gain: 1.4
1928
+ };
1929
+ }
1930
+ case "press":
1931
+ return {
1932
+ layers: [click(2650, 0.4, 0.004, 1.5), knock(380, 0.02, 0.05), thud(220, 0.25, 0.022)],
1933
+ gain: 2
1934
+ };
1935
+ case "capture":
1936
+ return {
1937
+ layers: [
1938
+ click(3000, 0.3, 0.003, 3),
1939
+ click(2400, 0.22, 0.004, 3, 0.035),
1940
+ ...glass(1318.5, 0.12, 0.012, 0.04)
1941
+ ],
1942
+ gain: 2.5,
1943
+ room: 0.5
1944
+ };
1945
+ }
1946
+ }
1947
+ var themed = (layers, theme) => theme === "mech" ? layers.map((l) => l.wave ? { ...l, p: l.p * 0.5, d: l.d * 0.5 } : { ...l, d: l.d * 0.75, q: (l.q ?? 1) * 1.3, p: l.p * 1.1 }) : [...layers], GAPS = {
1948
+ step: 24,
1949
+ limit: 140,
1950
+ toggle: 40,
1951
+ select: 28,
1952
+ preset: 80,
1953
+ random: 120,
1954
+ mutate: 120,
1955
+ reset: 120,
1956
+ copy: 200,
1957
+ undo: 45,
1958
+ redo: 45,
1959
+ open: 60,
1960
+ close: 60,
1961
+ hint: 120,
1962
+ type: 18,
1963
+ press: 50,
1964
+ capture: 120
1965
+ };
1966
+ function createLimiter(gaps = GAPS) {
1967
+ let last = /* @__PURE__ */ new Map;
1968
+ return (cue, now) => {
1969
+ let since = now - (last.get(cue) ?? Number.NEGATIVE_INFINITY);
1970
+ if (since < (gaps[cue] ?? 0))
1971
+ return null;
1972
+ return last.set(cue, now), cadence(since);
1973
+ };
1974
+ }
1975
+ var FRESH_MS = 150, DETENTS2 = 24;
1976
+ function createDetents() {
1977
+ let last = /* @__PURE__ */ new Map;
1978
+ return (key, before, after, detents, now) => {
1979
+ let since = now - (last.get(key) ?? Number.NEGATIVE_INFINITY);
1980
+ if (last.set(key, now), since > FRESH_MS)
1981
+ return !0;
1982
+ let n = Math.max(1, Math.round(detents));
1983
+ return Math.round(clamp3(before, 0, 1) * n) !== Math.round(clamp3(after, 0, 1) * n);
1984
+ };
1985
+ }
1986
+ var numbers = (v) => typeof v === "number" ? [v] : v && typeof v === "object" ? Object.values(v).filter((n) => typeof n === "number") : [];
1987
+ function changeCue(field, before, after) {
1988
+ if (field.kind === "toggle")
1989
+ return { cue: "toggle", detail: { direction: after ? 1 : -1 }, detents: 1, at: 1, was: 0 };
1990
+ if (field.kind === "choice") {
1991
+ let options = field.options ?? [];
1992
+ return { cue: "select", detail: { direction: options.indexOf(after) - options.indexOf(before) }, detents: 1, at: 1, was: 0 };
1993
+ }
1994
+ if (field.kind === "keybind")
1995
+ return after ? { cue: "capture", detail: {}, detents: 1, at: 1, was: 0 } : { cue: "select", detail: { direction: -1 }, detents: 1, at: 1, was: 0 };
1996
+ let { min, max, step } = field, a = numbers(before), b = numbers(after);
1997
+ if (typeof min !== "number" || typeof max !== "number" || !(max > min) || !b.length)
1998
+ return { cue: "step", detail: {}, detents: 1, at: 0.5, was: 0.5 };
1999
+ let i = b.findIndex((n, j) => n !== a[j]);
2000
+ if (i < 0)
2001
+ i = 0;
2002
+ let pos = (n) => clamp3(((n ?? min) - min) / (max - min), 0, 1), steps = typeof step === "number" && step > 0 ? (max - min) / step : Number.POSITIVE_INFINITY;
2003
+ return {
2004
+ cue: "step",
2005
+ detail: { position: pos(b[i]), strong: same(after, field.value) && !same(before, field.value) },
2006
+ detents: steps <= 10 ? steps : DETENTS2,
2007
+ at: pos(b[i]),
2008
+ was: pos(a[i])
2009
+ };
2010
+ }
2011
+ var same = (x, y) => {
2012
+ let a = numbers(x), b = numbers(y);
2013
+ return a.length > 0 && a.length === b.length && a.every((n, i) => n === b[i]);
2014
+ };
2015
+ function resolveSound(option) {
2016
+ if (!option)
2017
+ return null;
2018
+ if (option === !0)
2019
+ return {};
2020
+ return {
2021
+ ...option.volume !== void 0 ? { volume: clamp3(Number(option.volume) || 0, 0, 1) } : {},
2022
+ ...option.theme ? { theme: option.theme } : {}
2023
+ };
2024
+ }
2025
+ var MAX_VOICES = 10, PAD = 0.04, FADE = 0.015, LEVEL = 0.5, browserEnv = () => {
2026
+ let win = typeof window > "u" ? void 0 : window;
2027
+ return {
2028
+ AudioContext: win?.AudioContext ?? win?.webkitAudioContext,
2029
+ activated: () => win?.navigator?.userActivation?.hasBeenActive !== !1,
2030
+ now: () => performance.now()
2031
+ };
2032
+ };
2033
+ function createSound(env = browserEnv()) {
2034
+ let enabled = !0, volume = 1, theme = "glass", context = null, bus = null, voices = [], listeners = /* @__PURE__ */ new Set, limit = createLimiter(), now = env.now ?? (() => Date.now()), random = env.random ?? Math.random, notify = () => {
2035
+ for (let f of listeners)
2036
+ f();
2037
+ }, ensure = () => {
2038
+ if (context)
2039
+ return context;
2040
+ if (!env.AudioContext || env.activated?.() === !1)
2041
+ return null;
2042
+ try {
2043
+ context = new env.AudioContext;
2044
+ } catch {
2045
+ return null;
2046
+ }
2047
+ return context;
2048
+ }, wake = (ctx) => {
2049
+ if (ctx.state === "suspended" && enabled)
2050
+ try {
2051
+ ctx.resume().catch(() => {});
2052
+ } catch {}
2053
+ }, build = (ctx) => {
2054
+ if (bus)
2055
+ return bus;
2056
+ let input = ctx.createGain();
2057
+ input.gain.value = enabled ? volume : 0;
2058
+ let subsonic = ctx.createBiquadFilter();
2059
+ subsonic.type = "highpass", subsonic.frequency.value = 45;
2060
+ let squeeze = ctx.createDynamicsCompressor();
2061
+ squeeze.threshold.value = -22, squeeze.knee.value = 12, squeeze.ratio.value = 3, squeeze.attack.value = 0.002, squeeze.release.value = 0.12, input.connect(subsonic).connect(squeeze).connect(ctx.destination);
2062
+ let rate = ctx.sampleRate, noise = ctx.createBuffer(1, rate, rate), data = noise.getChannelData(0), s = 7;
2063
+ for (let i = 0;i < data.length; i++)
2064
+ s = s * 1664525 + 1013904223 >>> 0, data[i] = s / 2147483648 - 1;
2065
+ let len = Math.floor(rate * 0.2), impulse = ctx.createBuffer(2, len, rate);
2066
+ for (let c = 0;c < 2; c++) {
2067
+ let ch = impulse.getChannelData(c);
2068
+ for (let i = Math.floor(rate * 0.006);i < len; i++)
2069
+ ch[i] = data[i * (c + 3) % data.length] * Math.exp(-6.9 * i / len) / 9;
2070
+ }
2071
+ let verb = ctx.createConvolver();
2072
+ verb.normalize = !1, verb.buffer = impulse;
2073
+ let walls = ctx.createBiquadFilter();
2074
+ return walls.type = "lowpass", walls.frequency.value = 3800, walls.connect(verb).connect(input), bus = { input, room: walls, noise }, bus;
2075
+ }, render = (ctx, layers, level, room) => {
2076
+ let b = build(ctx), t0 = ctx.currentTime + 0.002;
2077
+ for (let i = voices.length - 1;i >= 0; i--)
2078
+ if (voices[i].end < t0)
2079
+ voices.splice(i, 1);
2080
+ while (voices.length >= MAX_VOICES) {
2081
+ let old = voices.shift();
2082
+ old.gain.gain.cancelScheduledValues(t0), old.gain.gain.setValueAtTime(old.gain.gain.value, t0), old.gain.gain.linearRampToValueAtTime(0, t0 + FADE);
2083
+ }
2084
+ let out = ctx.createGain();
2085
+ out.gain.value = level, out.connect(b.input);
2086
+ let send;
2087
+ if (room > 0)
2088
+ send = ctx.createGain(), send.gain.value = 0.1 * room, out.connect(send).connect(b.room);
2089
+ let end = t0;
2090
+ for (let l of layers) {
2091
+ let t = t0 + (l.at ?? 0), stop = t + l.a + l.d + PAD;
2092
+ end = Math.max(end, stop);
2093
+ let g = ctx.createGain();
2094
+ g.gain.setValueAtTime(0, t), g.gain.linearRampToValueAtTime(l.p, t + l.a), g.gain.setTargetAtTime(0, t + l.a, l.d / 6.9);
2095
+ let source, param;
2096
+ if (l.wave) {
2097
+ let osc = ctx.createOscillator();
2098
+ osc.type = l.wave, param = osc.frequency, osc.connect(g), source = osc;
2099
+ } else {
2100
+ let src = ctx.createBufferSource();
2101
+ src.buffer = b.noise, src.loop = !0;
2102
+ let filter = ctx.createBiquadFilter();
2103
+ if (filter.type = l.filter ?? "bandpass", l.q !== void 0)
2104
+ filter.Q.value = l.q;
2105
+ param = filter.frequency, src.connect(filter).connect(g), source = src;
2106
+ }
2107
+ if (param.setValueAtTime(l.f, t), l.to !== void 0)
2108
+ param.exponentialRampToValueAtTime(l.to, t + (l.glide ?? l.a + l.d));
2109
+ if (g.connect(out), l.wave)
2110
+ source.start(t);
2111
+ else
2112
+ source.start(t, random() * 0.9);
2113
+ source.stop(stop);
2114
+ }
2115
+ voices.push({ gain: out, end }), setTimeout(() => {
2116
+ out.disconnect(), send?.disconnect();
2117
+ }, (end - ctx.currentTime + 0.1) * 1000);
2118
+ };
2119
+ return {
2120
+ get enabled() {
2121
+ return enabled;
2122
+ },
2123
+ enable(on = !0) {
2124
+ if (on === enabled)
2125
+ return;
2126
+ if (enabled = on, context && bus) {
2127
+ let t = context.currentTime, g = bus.input.gain;
2128
+ g.cancelScheduledValues(t), g.setValueAtTime(g.value, t), g.linearRampToValueAtTime(on ? volume : 0, t + FADE * 2);
2129
+ }
2130
+ if (on && context)
2131
+ wake(context);
2132
+ notify();
2133
+ },
2134
+ get volume() {
2135
+ return volume;
2136
+ },
2137
+ setVolume(next) {
2138
+ let v = clamp3(Number(next) || 0, 0, 1);
2139
+ if (v === volume)
2140
+ return;
2141
+ if (volume = v, context && bus && enabled)
2142
+ bus.input.gain.setTargetAtTime(v, context.currentTime, 0.02);
2143
+ notify();
2144
+ },
2145
+ get theme() {
2146
+ return theme;
2147
+ },
2148
+ setTheme(next) {
2149
+ if (next !== "glass" && next !== "mech")
2150
+ return;
2151
+ if (next === theme)
2152
+ return;
2153
+ theme = next, notify();
2154
+ },
2155
+ play(cue, detail = {}) {
2156
+ if (!enabled || volume === 0)
2157
+ return;
2158
+ let level = LEVEL * clamp3(detail.volume ?? 1, 0, 1);
2159
+ if (level === 0)
2160
+ return;
2161
+ let made;
2162
+ if (typeof cue === "string") {
2163
+ let speed = limit(cue, now());
2164
+ if (speed === null)
2165
+ return;
2166
+ made = recipe(cue, detail, speed, random);
2167
+ } else
2168
+ made = { layers: cue };
2169
+ let ctx = ensure();
2170
+ if (!ctx)
2171
+ return;
2172
+ let gain = level * (made.gain ?? 1), layers = themed(made.layers, detail.theme ?? theme), room = (detail.theme ?? theme) === "mech" ? 0 : made.room ?? 0;
2173
+ if (ctx.state === "running")
2174
+ render(ctx, layers, gain, room);
2175
+ else
2176
+ try {
2177
+ ctx.resume().then(() => {
2178
+ if (enabled && ctx.state === "running")
2179
+ render(ctx, layers, gain, room);
2180
+ }).catch(() => {});
2181
+ } catch {}
2182
+ },
2183
+ output() {
2184
+ let ctx = ensure();
2185
+ if (!ctx)
2186
+ return null;
2187
+ return wake(ctx), { context: ctx, destination: build(ctx).input };
2188
+ },
2189
+ prime(doc = typeof document > "u" ? void 0 : document) {
2190
+ let win = doc?.defaultView;
2191
+ if (!win || context)
2192
+ return;
2193
+ let kinds = ["pointerdown", "keydown", "touchend"], unlock = () => {
2194
+ if (!enabled)
2195
+ return;
2196
+ let ctx = ensure();
2197
+ if (!ctx)
2198
+ return;
2199
+ build(ctx), wake(ctx);
2200
+ for (let k of kinds)
2201
+ win.removeEventListener(k, unlock, !0);
2202
+ };
2203
+ for (let k of kinds)
2204
+ win.addEventListener(k, unlock, !0);
2205
+ },
2206
+ subscribe(listener) {
2207
+ return listeners.add(listener), () => {
2208
+ listeners.delete(listener);
2209
+ };
2210
+ }
2211
+ };
2212
+ }
2213
+ var sound = /* @__PURE__ */ createSound(), panels = /* @__PURE__ */ new WeakMap, SOUND_PREF = "knobs:sound:v1";
2214
+ function soundPanel(root, options) {
2215
+ panels.set(root, options), sound.prime(root.ownerDocument);
2216
+ }
2217
+ var sounds = (from) => {
2218
+ let root = from?.closest?.(".knobs-panel");
2219
+ return Boolean(root && panels.has(root));
2220
+ }, detents = createDetents();
2221
+ function changed(from, key, field, before, after) {
2222
+ if (!sounds(from))
2223
+ return;
2224
+ let c = changeCue(field, before, after);
2225
+ if (!c)
2226
+ return;
2227
+ if (c.cue === "step" && !detents(key, c.was, c.at, c.detents, performance.now()))
2228
+ return;
2229
+ cue(from, c.cue, c.detail);
2230
+ }
2231
+ function cue(from, name, detail = {}) {
2232
+ let root = from?.closest?.(".knobs-panel"), options = root && panels.get(root);
2233
+ if (!options)
2234
+ return;
2235
+ sound.play(name, {
2236
+ ...detail,
2237
+ volume: (options.volume ?? 1) * (detail.volume ?? 1),
2238
+ ...options.theme && !detail.theme ? { theme: options.theme } : {}
2239
+ });
2240
+ }
2241
+
2242
+ // src/panel/hints.ts
2243
+ function extractWords(text) {
2244
+ return text.toLowerCase().match(/[a-z]+/g) ?? [];
2245
+ }
2246
+ function candidatePool(words) {
2247
+ let flat = words.join(""), pool = [], push = (s) => {
2248
+ if (s && /^[a-z]{1,3}$/.test(s) && !pool.includes(s))
2249
+ pool.push(s);
2250
+ };
2251
+ if (words.length >= 2) {
2252
+ let w0 = words[0], w1 = words[1], base2 = w0[0] + w1[0];
2253
+ push(base2);
2254
+ for (let i = 1;i < w1.length; i++)
2255
+ push(w0[0] + w1[i]);
2256
+ for (let i = 1;i < w0.length; i++)
2257
+ push(w0[i] + w1[0]);
2258
+ if (words.length >= 3)
2259
+ push(base2 + words[2][0]);
2260
+ if (w1.length >= 2)
2261
+ push(base2 + w1[1]);
2262
+ if (w0.length >= 2)
2263
+ push(base2 + w0[1]);
2264
+ } else if (flat.length > 0) {
2265
+ for (let i = 1;i < flat.length; i++)
2266
+ push(flat[0] + flat[i]);
2267
+ if (flat.length >= 3)
2268
+ push(flat.slice(0, 3));
2269
+ }
2270
+ let f0 = flat[0] || "x";
2271
+ for (let c = 97;c <= 122; c++)
2272
+ push(f0 + String.fromCharCode(c));
2273
+ return pool;
2274
+ }
2275
+ function assignHintCodes(items) {
2276
+ let n = items.length;
2277
+ if (n === 0)
2278
+ return [];
2279
+ let displayCounts = /* @__PURE__ */ new Map;
2280
+ for (let item of items) {
2281
+ let d = item.display.toLowerCase();
2282
+ displayCounts.set(d, (displayCounts.get(d) ?? 0) + 1);
2283
+ }
2284
+ let itemWords = items.map((item) => {
2285
+ let d = item.display.toLowerCase(), useKey = (displayCounts.get(d) ?? 0) > 1 && d !== item.key.toLowerCase(), w = extractWords(useKey ? item.key : item.display);
2286
+ return w.length > 0 ? w : extractWords(item.key);
2287
+ }), pools = itemWords.map((w) => candidatePool(w)), base2Of = pools.map((p) => p[0] ?? "x"), firstLetterCounts = /* @__PURE__ */ new Map, base2Counts = /* @__PURE__ */ new Map;
2288
+ for (let i = 0;i < n; i++) {
2289
+ let b2 = base2Of[i], f1 = b2[0];
2290
+ firstLetterCounts.set(f1, (firstLetterCounts.get(f1) ?? 0) + 1), base2Counts.set(b2, (base2Counts.get(b2) ?? 0) + 1);
2291
+ }
2292
+ let codes = Array(n).fill(""), used = /* @__PURE__ */ new Set;
2293
+ for (let i = 0;i < n; i++) {
2294
+ let words = itemWords[i], b2 = base2Of[i], f1 = b2[0];
2295
+ if (words.length === 1 && firstLetterCounts.get(f1) === 1 && !used.has(f1))
2296
+ codes[i] = f1, used.add(f1);
2297
+ else if (base2Counts.get(b2) === 1 && !used.has(b2))
2298
+ codes[i] = b2, used.add(b2);
2299
+ }
2300
+ for (let i = 0;i < n; i++) {
2301
+ if (codes[i])
2302
+ continue;
2303
+ let pool = pools[i], chosen = pool.find((cand) => cand.length === 2 && !used.has(cand) && !used.has(cand[0])) ?? pool.find((cand) => !used.has(cand) && !used.has(cand[0])) ?? `x${i}`;
2304
+ codes[i] = chosen, used.add(chosen);
2305
+ }
2306
+ return codes;
2307
+ }
2308
+ function resolveHintCodes(codes, rawQuery = "") {
2309
+ let q = rawQuery.toLowerCase().replace(/[^a-z]/g, ""), matchIndices = [], badges = codes.map((code, i) => {
2310
+ let match = q.length === 0 || code.startsWith(q);
2311
+ if (match)
2312
+ matchIndices.push(i);
2313
+ let typedLen = match ? Math.min(code.length, q.length) : 0;
2314
+ return {
2315
+ match,
2316
+ prefix: code,
2317
+ typed: code.slice(0, typedLen),
2318
+ rest: code.slice(typedLen)
2319
+ };
2320
+ }), uniqueMatchIndex = matchIndices.length === 1 && q.length > 0 ? matchIndices[0] : -1;
2321
+ return {
2322
+ badges,
2323
+ matchIndices,
2324
+ uniqueMatchIndex
2325
+ };
2326
+ }
2327
+ function createHintController(doc, root, wakeGpu) {
2328
+ let layer = doc.createElement("div");
2329
+ layer.className = "knobs-hint-layer", layer.setAttribute("aria-hidden", "true");
2330
+ let active = !1, local = !1, query = "", spots = [], targets = [], codes = [], badgeNodes = [];
2331
+ function stop() {
2332
+ if (!active)
2333
+ return;
2334
+ active = !1, local = !1, query = "", root.classList.remove("knobs-hinting");
2335
+ for (let t of spots)
2336
+ t.el.classList.remove("knobs-hint-match", "knobs-hint-miss");
2337
+ spots = [], targets = [], codes = [], badgeNodes = [], layer.replaceChildren(), layer.remove(), wakeGpu();
2338
+ }
2339
+ function paint() {
2340
+ if (!active)
2341
+ return;
2342
+ let resolution = resolveHintCodes(codes, query);
2343
+ if (local && resolution.uniqueMatchIndex >= 0) {
2344
+ let hit = targets[resolution.uniqueMatchIndex];
2345
+ stop(), hit.activate();
2346
+ return;
2347
+ }
2348
+ for (let i = 0;i < spots.length; i++) {
2349
+ let t = spots[i], b = resolution.badges[i], node = badgeNodes[i];
2350
+ if (t.el.classList.toggle("knobs-hint-match", b.match), t.el.classList.toggle("knobs-hint-miss", !b.match), !node)
2351
+ continue;
2352
+ if (node.tag.hidden = !b.match, b.match)
2353
+ node.typedSpan.textContent = b.typed, node.restSpan.textContent = b.rest;
2354
+ }
2355
+ wakeGpu();
2356
+ }
2357
+ function show(nextSpots, nextCodes) {
2358
+ if (stop(), nextSpots.length === 0)
2359
+ return;
2360
+ if (active = !0, query = "", spots = nextSpots, codes = nextCodes, root.classList.add("knobs-hinting"), !layer.isConnected)
2361
+ root.append(layer);
2362
+ cue(root, "hint");
2363
+ let rootRect = root.getBoundingClientRect();
2364
+ badgeNodes = spots.map((t) => {
2365
+ let tag = doc.createElement("kbd");
2366
+ tag.className = "knobs-hint";
2367
+ let typedSpan = doc.createElement("span");
2368
+ typedSpan.className = "knobs-hint-typed";
2369
+ let restSpan = doc.createElement("span");
2370
+ restSpan.className = "knobs-hint-rest", tag.append(typedSpan, restSpan);
2371
+ let rect = t.el.getBoundingClientRect();
2372
+ if (t.labelEl) {
2373
+ let lRect = t.labelEl.getBoundingClientRect(), left = lRect.right - rootRect.left - root.clientLeft + root.scrollLeft + 6, top = lRect.top - rootRect.top - root.clientTop + root.scrollTop + lRect.height * 0.5;
2374
+ tag.style.left = `${Math.round(left)}px`, tag.style.top = `${Math.round(top)}px`;
2375
+ } else {
2376
+ let left = rect.left - rootRect.left - root.clientLeft + root.scrollLeft + 1, top = rect.top - rootRect.top - root.clientTop + root.scrollTop + 1;
2377
+ tag.style.left = `${Math.round(left)}px`, tag.style.top = `${Math.round(top)}px`;
2378
+ }
2379
+ return layer.append(tag), { tag, typedSpan, restSpan };
2380
+ }), paint();
2381
+ }
2382
+ function start(nextTargets) {
2383
+ if (show(nextTargets, assignHintCodes(nextTargets)), !active)
2384
+ return;
2385
+ local = !0, targets = nextTargets;
2386
+ }
2387
+ function narrow(next) {
2388
+ if (next.length > query.length)
2389
+ cue(root, "type");
2390
+ query = next, paint();
2391
+ }
2392
+ function handleKey(event) {
2393
+ if (!active || !local)
2394
+ return !1;
2395
+ if (event.preventDefault(), event.stopPropagation(), event.key === "Escape")
2396
+ stop();
2397
+ else if (event.key === "Backspace")
2398
+ query = query.slice(0, -1), paint();
2399
+ else if (event.key === "Enter") {
2400
+ let resolution = resolveHintCodes(codes, query), first = targets[resolution.matchIndices[0] ?? -1];
2401
+ stop(), first?.activate();
2402
+ } else if (/^[a-zA-Z]$/.test(event.key))
2403
+ cue(root, "type"), query += event.key.toLowerCase(), paint();
2404
+ return !0;
2405
+ }
2406
+ return {
2407
+ get isActive() {
2408
+ return active;
2409
+ },
2410
+ start,
2411
+ show,
2412
+ narrow,
2413
+ stop,
2414
+ handleKey
2415
+ };
2416
+ }
2417
+
2418
+ // src/panel/page-hints.ts
2419
+ var HINT_LETTERS = "asdfghjkleruiowtyqpcvnmxzb", HOME_ROW = 9;
2420
+ function assignPageCodes(counts) {
2421
+ let n = HINT_LETTERS.length, result = counts.map((c) => Array(c).fill("")), need = counts.map((c) => c > 0 ? Math.ceil(c / n) : 0), used = need.reduce((a, b) => a + b, 0);
2422
+ if (used <= n) {
2423
+ while (used < n) {
2424
+ let best = -1, most = HOME_ROW;
2425
+ if (counts.forEach((c, i) => {
2426
+ let per = need[i] ? Math.ceil(c / need[i]) : 0;
2427
+ if (per > most)
2428
+ best = i, most = per;
2429
+ }), best < 0)
2430
+ break;
2431
+ need[best]++, used++;
2432
+ }
2433
+ let letter = 0;
2434
+ return counts.forEach((c, i) => {
2435
+ let k = need[i], at = 0;
2436
+ for (let chunk = 0;chunk < k; chunk++) {
2437
+ let size = Math.floor(c / k) + (chunk < c % k ? 1 : 0), first = HINT_LETTERS[letter++];
2438
+ for (let j = 0;j < size; j++)
2439
+ result[i][at + j] = size === 1 ? first : first + HINT_LETTERS[j];
2440
+ at += size;
2441
+ }
2442
+ }), result;
2443
+ }
2444
+ let slot = 0;
2445
+ return counts.forEach((c, i) => {
2446
+ for (let j = 0;j < c; j++, slot++) {
2447
+ let first = Math.floor(slot / n);
2448
+ if (first < n)
2449
+ result[i][j] = HINT_LETTERS[first] + HINT_LETTERS[slot % n];
2450
+ }
2451
+ }), result;
2452
+ }
2453
+ var COLUMN = 200;
2454
+ function orderHintGroups(groups) {
2455
+ return groups.map((g, i) => ({ g, i })).sort((a, b) => a.g.rank - b.g.rank || Math.floor(a.g.left / COLUMN) - Math.floor(b.g.left / COLUMN) || a.g.top - b.g.top || a.g.left - b.g.left || a.i - b.i).map((x) => x.i);
2456
+ }
2457
+ var PAGE_HINT_KEY = "s", KEY = Symbol.for("knobs.pageHints.v1");
2458
+ function pageHints(doc = document) {
2459
+ let holder = doc, registry = holder[KEY] ?? createRegistry(doc);
2460
+ return holder[KEY] = registry, registry;
2461
+ }
2462
+ var isTyping = isTypingTarget, PRESSABLE = "button,a[href],summary,[role=button],[role=tab],[role=radio],[role=switch],[role=checkbox],[role=menuitem],[role=option]";
2463
+ function activateElement(el) {
2464
+ let pressable = el.matches(PRESSABLE) || el.tabIndex < 0 && !el.matches("input,select,textarea");
2465
+ if (el.tabIndex >= 0 || el.matches("input,select,textarea"))
2466
+ el.focus({ preventScroll: !0, focusVisible: !0 });
2467
+ if (pressable)
2468
+ el.click();
2469
+ }
2470
+ var labelOf = (el) => (el.dataset.hint || el.getAttribute("aria-label") || el.textContent || "").trim();
2471
+ function createRegistry(doc) {
2472
+ let win = doc.defaultView, sources = [], session = null, listening = !1, onDocKey = (e) => {
2473
+ if (session || e.defaultPrevented || e.repeat || e.isComposing)
2474
+ return;
2475
+ if (e.metaKey || e.ctrlKey || e.altKey || e.key !== PAGE_HINT_KEY)
2476
+ return;
2477
+ if (isTyping(e.target) || !wide())
2478
+ return;
2479
+ if (registry.start())
2480
+ e.preventDefault();
2481
+ }, wide = () => listening && (sources.length > 1 || [...doc.querySelectorAll("[data-hint]")].some((el) => !el.closest(".knobs-panel"))), relisten = () => {
2482
+ let next = sources.some((s) => s.listen);
2483
+ if (next === listening)
2484
+ return;
2485
+ if (listening = next, next)
2486
+ doc.addEventListener("keydown", onDocKey);
2487
+ else
2488
+ doc.removeEventListener("keydown", onDocKey);
2489
+ }, shown = (el) => {
2490
+ if (!el.isConnected || el.disabled)
2491
+ return !1;
2492
+ if (el.closest("[hidden],[inert],[data-leaving]"))
2493
+ return !1;
2494
+ if (el.getClientRects().length === 0)
2495
+ return !1;
2496
+ let check = el.checkVisibility;
2497
+ return check ? check.call(el, { visibilityProperty: !0 }) : !0;
2498
+ }, collect = () => {
2499
+ let seen = /* @__PURE__ */ new Set, take = (item) => {
2500
+ let t = typeof item.nodeType === "number" ? { el: item } : item, el = t.el;
2501
+ if (seen.has(el) || !shown(el))
2502
+ return null;
2503
+ let label = (t.label ?? labelOf(el)).trim();
2504
+ return seen.add(el), {
2505
+ el,
2506
+ label: label || "x",
2507
+ ...t.labelEl ? { labelEl: t.labelEl } : {},
2508
+ activate: t.activate ?? (() => activateElement(el))
2509
+ };
2510
+ }, groups = [];
2511
+ for (let source of [...sources].sort((a, b) => a.rank - b.rank)) {
2512
+ let targets = [];
2513
+ for (let item of source.targets()) {
2514
+ let t = take(item);
2515
+ if (t)
2516
+ targets.push(t);
2517
+ }
2518
+ if (targets.length)
2519
+ groups.push({ source, targets });
2520
+ }
2521
+ let byGroup = /* @__PURE__ */ new Map;
2522
+ for (let el of doc.querySelectorAll("[data-hint]")) {
2523
+ if (el.closest(".knobs-panel"))
2524
+ continue;
2525
+ let t = take(el);
2526
+ if (!t)
2527
+ continue;
2528
+ let key = el.closest("[data-hint-group]"), list = byGroup.get(key);
2529
+ if (list)
2530
+ list.push(t);
2531
+ else
2532
+ byGroup.set(key, [t]);
2533
+ }
2534
+ for (let targets of byGroup.values())
2535
+ groups.push({ targets });
2536
+ return groups;
2537
+ }, inView = (el, clips) => {
2538
+ if (!win)
2539
+ return !0;
2540
+ let r = el.getBoundingClientRect(), y = r.top + Math.min(r.height / 2, 10), top = 0, left = 0, { innerWidth: right, innerHeight: bottom } = win;
2541
+ for (let p = el.parentElement;p && p !== doc.body; p = p.parentElement) {
2542
+ let clip = clips.get(p);
2543
+ if (clip === void 0) {
2544
+ let cs = win.getComputedStyle(p);
2545
+ clip = cs.overflowX !== "visible" || cs.overflowY !== "visible" ? p.getBoundingClientRect() : null, clips.set(p, clip);
2546
+ }
2547
+ if (!clip)
2548
+ continue;
2549
+ top = Math.max(top, clip.top), left = Math.max(left, clip.left), right = Math.min(right, clip.right), bottom = Math.min(bottom, clip.bottom);
2550
+ }
2551
+ return y >= top && y <= bottom && r.right > left + 2 && r.left < right - 2;
2552
+ }, registry = {
2553
+ register(root, targets, options = {}) {
2554
+ let source = {
2555
+ root,
2556
+ targets,
2557
+ listen: options.listen !== !1,
2558
+ rank: options.rank ?? 2,
2559
+ ...options.paint ? { paint: options.paint } : {}
2560
+ };
2561
+ return sources.push(source), relisten(), () => {
2562
+ let i = sources.indexOf(source);
2563
+ if (i < 0)
2564
+ return;
2565
+ if (sources.splice(i, 1), session?.has(source))
2566
+ registry.stop();
2567
+ relisten();
2568
+ };
2569
+ },
2570
+ start() {
2571
+ registry.stop();
2572
+ let groups = collect();
2573
+ if (groups.length === 0)
2574
+ return !1;
2575
+ let codes;
2576
+ if (groups.length === 1) {
2577
+ let only = groups[0].targets;
2578
+ codes = [assignHintCodes(only.map((t) => ({ key: t.label, display: t.label })))];
2579
+ } else {
2580
+ let clips = /* @__PURE__ */ new Map;
2581
+ if (groups = groups.map((g) => ({ ...g, targets: g.targets.filter((t) => inView(t.el, clips)) })).filter((g) => g.targets.length > 0), groups.length === 0)
2582
+ return !1;
2583
+ groups = orderHintGroups(groups.map((g) => {
2584
+ let left = 1 / 0, top = 1 / 0;
2585
+ for (let t of g.targets) {
2586
+ let r = t.el.getBoundingClientRect();
2587
+ left = Math.min(left, r.left), top = Math.min(top, r.top);
2588
+ }
2589
+ return { rank: g.source?.rank ?? 2, left, top };
2590
+ })).map((i) => groups[i]), codes = groups.length === 1 ? [assignHintCodes(groups[0].targets.map((t) => ({ key: t.label, display: t.label })))] : assignPageCodes(groups.map((g) => g.targets.length));
2591
+ }
2592
+ return session = createSession(doc, groups, codes, () => {
2593
+ session = null;
2594
+ }), !0;
2595
+ },
2596
+ stop() {
2597
+ session?.stop(), session = null;
2598
+ },
2599
+ get active() {
2600
+ return session !== null;
2601
+ },
2602
+ get listening() {
2603
+ return wide();
2604
+ }
2605
+ };
2606
+ return registry;
2607
+ }
2608
+ function createSession(doc, groups, codes, ended) {
2609
+ let win = doc.defaultView, entries = [], painters = [], shared = { spots: [], codes: [] };
2610
+ if (groups.forEach((g, i) => {
2611
+ let spots = [], mine = [];
2612
+ if (g.targets.forEach((t, j) => {
2613
+ let code = codes[i][j];
2614
+ if (!code)
2615
+ return;
2616
+ entries.push({ target: t, code }), spots.push(t), mine.push(code);
2617
+ }), !spots.length)
2618
+ return;
2619
+ if (g.source?.paint)
2620
+ painters.push(g.source.paint), g.source.paint.show(spots, mine);
2621
+ else
2622
+ shared.spots.push(...spots), shared.codes.push(...mine);
2623
+ }), shared.spots.length) {
2624
+ let layer = layerPainter(doc, groups);
2625
+ painters.push(layer), layer.show(shared.spots, shared.codes);
2626
+ }
2627
+ let allCodes = entries.map((e) => e.code), query = "", over = !1, stop = () => {
2628
+ if (over)
2629
+ return;
2630
+ over = !0;
2631
+ for (let p of painters)
2632
+ p.clear();
2633
+ win?.removeEventListener("keydown", onKey, !0), doc.removeEventListener("scroll", onScroll, !0), doc.removeEventListener("pointerdown", onPointer, !0), win?.removeEventListener("resize", stop), win?.removeEventListener("blur", stop), ended();
2634
+ }, finish = (i) => {
2635
+ let hit = i === void 0 ? void 0 : entries[i];
2636
+ stop(), hit?.target.activate();
2637
+ }, narrow = () => {
2638
+ let r = resolveHintCodes(allCodes, query);
2639
+ if (r.uniqueMatchIndex >= 0)
2640
+ return finish(r.uniqueMatchIndex);
2641
+ for (let p of painters)
2642
+ p.narrow(query);
2643
+ }, onKey = (e) => {
2644
+ if (e.isComposing || ["Shift", "Meta", "Control", "Alt", "CapsLock"].includes(e.key))
2645
+ return;
2646
+ if (e.preventDefault(), e.stopImmediatePropagation(), e.metaKey || e.ctrlKey)
2647
+ return stop();
2648
+ if (e.key === "Escape")
2649
+ stop();
2650
+ else if (e.key === "Backspace")
2651
+ query = query.slice(0, -1), narrow();
2652
+ else if (e.key === "Enter")
2653
+ finish(resolveHintCodes(allCodes, query).matchIndices[0]);
2654
+ else if (/^[a-zA-Z]$/.test(e.key))
2655
+ query += e.key.toLowerCase(), narrow();
2656
+ }, scrolled = /* @__PURE__ */ new Map, offset = (t) => t === doc ? (win?.scrollY ?? 0) + (win?.scrollX ?? 0) : t.scrollTop + t.scrollLeft, onScroll = (e) => {
2657
+ let t = e.target;
2658
+ if (!t)
2659
+ return;
2660
+ let at = offset(t), from = scrolled.get(t);
2661
+ if (from === void 0)
2662
+ scrolled.set(t, at);
2663
+ else if (Math.abs(at - from) > 24)
2664
+ stop();
2665
+ }, onPointer = () => stop();
2666
+ return win?.addEventListener("keydown", onKey, !0), doc.addEventListener("scroll", onScroll, !0), doc.addEventListener("pointerdown", onPointer, !0), win?.addEventListener("resize", stop), win?.addEventListener("blur", stop), {
2667
+ has: (source) => groups.some((g) => g.source === source),
2668
+ stop
2669
+ };
2670
+ }
2671
+ var STYLE_ID = "knobs-page-hints-css", CSS = `
2672
+ .knobs-page-hints{position:fixed;left:0;top:0;width:0;height:0;pointer-events:none;z-index:2147483000}
2673
+ .knobs-page-hint{position:fixed;transform:translateY(-50%);display:inline-flex;align-items:center;height:15px;padding:0 4px;border-radius:var(--kh-radius);background:var(--kh-accent);color:var(--kh-bg);
2674
+ box-shadow:0 1px 3px rgba(0,0,0,calc(.35 * var(--kh-lift)));font:700 var(--kh-size)/1 var(--kh-mono);letter-spacing:.04em;text-transform:uppercase;white-space:nowrap;pointer-events:none;
2675
+ animation:knobs-page-hint-in .1s var(--kh-ease)}
2676
+ .knobs-page-hint[hidden]{display:none}
2677
+ .knobs-page-hint>.knobs-hint-typed{opacity:var(--kh-typed)}
2678
+ [data-knobs-hint-miss]{opacity:.32;transition:opacity .1s}
2679
+ @keyframes knobs-page-hint-in{from{opacity:0;transform:translateY(calc(4px - 50%)) scale(.985)}to{opacity:1;transform:translateY(-50%)}}
2680
+ @media(prefers-reduced-motion:reduce){@keyframes knobs-page-hint-in{from{opacity:0}to{opacity:1}}}
2681
+ `;
2682
+ function hintTokens(doc, groups) {
2683
+ let win = doc.defaultView, read = (el, names) => {
2684
+ if (!el || !win)
2685
+ return "";
2686
+ let cs = win.getComputedStyle(el);
2687
+ for (let name of names) {
2688
+ let v = cs.getPropertyValue(name).trim();
2689
+ if (v)
2690
+ return v;
2691
+ }
2692
+ return "";
2693
+ }, panel = doc.querySelector(".knobs-panel"), piece = groups.find((g) => g.source && !g.source.paint)?.source?.root, pick = (k, fallback) => read(panel, [`--k-${k}`]) || read(piece, [`--ku-${k}`, `--k-${k}`]) || fallback;
2694
+ return {
2695
+ "--kh-accent": pick("accent", "#fff"),
2696
+ "--kh-bg": pick("bg", "#1c1c1e"),
2697
+ "--kh-mono": pick("mono", "ui-monospace,Menlo,monospace"),
2698
+ "--kh-radius": read(panel, ["--k-radius-sm"]) || "4px",
2699
+ "--kh-size": read(panel, ["--k-text-cap"]) || "10px",
2700
+ "--kh-typed": read(panel, ["--k-hint-typed-opacity"]) || ".42",
2701
+ "--kh-lift": read(panel, ["--k-lift"]) || "1",
2702
+ "--kh-ease": read(panel, ["--k-ease"]) || "cubic-bezier(.2,.8,.2,1)"
2703
+ };
2704
+ }
2705
+ function layerPainter(doc, groups) {
2706
+ let layer = doc.createElement("div");
2707
+ layer.className = "knobs-page-hints", layer.setAttribute("aria-hidden", "true");
2708
+ let spots = [], codes = [], nodes = [];
2709
+ return {
2710
+ show(next, nextCodes) {
2711
+ if (!doc.getElementById(STYLE_ID)) {
2712
+ let style = doc.createElement("style");
2713
+ style.id = STYLE_ID, style.textContent = CSS, doc.head.append(style);
2714
+ }
2715
+ spots = next, codes = nextCodes;
2716
+ for (let [k, v] of Object.entries(hintTokens(doc, groups)))
2717
+ layer.style.setProperty(k, v);
2718
+ nodes = spots.map((t, i) => {
2719
+ let tag = doc.createElement("kbd");
2720
+ tag.className = "knobs-page-hint";
2721
+ let typed = doc.createElement("span");
2722
+ typed.className = "knobs-hint-typed";
2723
+ let rest = doc.createElement("span");
2724
+ rest.textContent = codes[i] ?? "", tag.append(typed, rest);
2725
+ let r = (t.labelEl ?? t.el).getBoundingClientRect(), left = t.labelEl ? r.right + 6 : r.left + 1;
2726
+ return tag.style.left = `${Math.round(left)}px`, tag.style.top = `${Math.round(r.top + r.height / 2)}px`, layer.append(tag), { tag, typed, rest };
2727
+ }), doc.body.append(layer);
2728
+ },
2729
+ narrow(query) {
2730
+ let r = resolveHintCodes(codes, query);
2731
+ nodes.forEach((n, i) => {
2732
+ let b = r.badges[i];
2733
+ n.tag.hidden = !b.match, n.typed.textContent = b.typed, n.rest.textContent = b.rest, spots[i].el.toggleAttribute("data-knobs-hint-miss", !b.match);
2734
+ });
2735
+ },
2736
+ clear() {
2737
+ for (let t of spots)
2738
+ t.el.removeAttribute("data-knobs-hint-miss");
2739
+ layer.remove(), layer.replaceChildren(), spots = [], nodes = [];
2740
+ }
2741
+ };
2742
+ }
2743
+
2744
+ // src/panel/toggle.ts
2745
+ function isToggleKey(event, key) {
2746
+ if (!key || event.defaultPrevented || event.repeat || event.isComposing)
2747
+ return !1;
2748
+ if (event.ctrlKey || event.metaKey || event.altKey)
2749
+ return !1;
2750
+ if (isTypingTarget(event.target))
2751
+ return !1;
2752
+ if (event.key === key)
2753
+ return !0;
2754
+ return key === "`" && event.code === "Backquote" && !event.shiftKey;
2755
+ }
2756
+ var KEY2 = Symbol.for("knobs.toggle.v1"), memoryKey = (id) => storageKey(id, "shown");
2757
+ function toggleLayer(doc) {
2758
+ let holder = doc;
2759
+ return holder[KEY2] ??= createLayer(doc);
2760
+ }
2761
+ function createLayer(doc) {
2762
+ let win = doc.defaultView, surfaces = /* @__PURE__ */ new Set, state = /* @__PURE__ */ new Map, pressed = /* @__PURE__ */ new Map, lastFocus, isShown = (s) => {
2763
+ let on = state.get(s.id) ?? pressed.get(s.key);
2764
+ if (on === void 0)
2765
+ try {
2766
+ on = s.storage()?.getItem(memoryKey(s.id)) !== "0";
2767
+ } catch {
2768
+ on = !0;
2769
+ }
2770
+ return state.set(s.id, on), on;
2771
+ }, toggle = (key) => {
2772
+ let mine = [...surfaces].filter((s) => s.key === key);
2773
+ if (!mine.length)
2774
+ return !1;
2775
+ let next = !mine.some(isShown);
2776
+ if (pressed.set(key, next), !next) {
2777
+ let active = doc.activeElement;
2778
+ if (active && mine.some((s) => s.root.contains(active)))
2779
+ lastFocus = active, active.blur();
2780
+ pageHints(doc).stop();
2781
+ }
2782
+ for (let s of mine) {
2783
+ state.set(s.id, next);
2784
+ try {
2785
+ s.storage()?.setItem(memoryKey(s.id), next ? "1" : "0");
2786
+ } catch {}
2787
+ }
2788
+ for (let s of mine)
2789
+ s.show(next, !0);
2790
+ if (next) {
2791
+ let active = doc.activeElement;
2792
+ if (lastFocus?.isConnected && (!active || active === doc.body))
2793
+ lastFocus.focus({ preventScroll: !0 });
2794
+ lastFocus = void 0;
2795
+ }
2796
+ return !0;
2797
+ }, onKey = (event) => {
2798
+ let keys = new Set([...surfaces].map((s) => s.key));
2799
+ for (let key of keys)
2800
+ if (isToggleKey(event, key))
2801
+ return void toggle(key);
2802
+ };
2803
+ return {
2804
+ register(surface) {
2805
+ if (!surfaces.size)
2806
+ win.addEventListener("keydown", onKey, !0);
2807
+ return surfaces.add(surface), {
2808
+ shown: isShown(surface),
2809
+ dispose() {
2810
+ if (surfaces.delete(surface), !surfaces.size)
2811
+ win.removeEventListener("keydown", onKey, !0);
2812
+ }
2813
+ };
2814
+ },
2815
+ toggle
2816
+ };
2817
+ }
2818
+
2819
+ export { CHECK_POINTS, CHECK_PATH, iconPath, iconSvg, readSession, writeSession, rolling_css_default, presetCss, FILL, LEAD, trailScale, Edges, POP_MS, popScale, settleScale, VelocityTracker, clamp, Roll, rollTarget, numeral, entranceOffset, FLASH_MS, flash, flashOf, offRow, precisionRate, RULER, RULER_NEAR, RULER_FADE, rulerLevels, rulerAlpha, rulerDepth, THIN, THINNEST, thinOf, roundOf, stretchOf, stretchAt, giveRoom, detentScale, detentsOf, roundValues, unwarp, Notches, escapeTo, commandScope, scopeLabel, crumbTrail, escTarget, digitOption, digitHints, createCrumbs, DELAYED, injectPreset, injectStyles, resolveSound, sound, SOUND_PREF, soundPanel, changed, cue, createHintController, pageHints, toggleLayer };