@kitlangton/knobs 0.1.0 → 0.3.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 (65) hide show
  1. package/README.md +303 -41
  2. package/dist/dev/{chunk-qtt698hp.js → chunk-7xgkerd2.js} +34 -11
  3. package/dist/dev/{chunk-ktsn42p7.js → chunk-jx1ktap3.js} +13762 -11794
  4. package/dist/dev/{chunk-kdewv0mt.js → chunk-pmkvb9ep.js} +1692 -207
  5. package/dist/dev/{chunk-e13znr7c.js → chunk-v8ssbcbk.js} +18 -7
  6. package/dist/dev/index.js +557 -130
  7. package/dist/index.js +589 -141
  8. package/dist/panel.js +21615 -17048
  9. package/dist/types/core/keys.d.ts +2 -0
  10. package/dist/types/core/random.d.ts +61 -1
  11. package/dist/types/core/remote.d.ts +5 -1
  12. package/dist/types/core/schema.d.ts +35 -6
  13. package/dist/types/core/store.d.ts +97 -8
  14. package/dist/types/core/unlock.d.ts +77 -0
  15. package/dist/types/core/variants.d.ts +10 -2
  16. package/dist/types/index.d.ts +4 -2
  17. package/dist/types/panel/attach.d.ts +2 -0
  18. package/dist/types/panel/crumbs.d.ts +19 -0
  19. package/dist/types/panel/editors.d.ts +20 -0
  20. package/dist/types/panel/feel.d.ts +103 -0
  21. package/dist/types/panel/flash.d.ts +13 -0
  22. package/dist/types/panel/give.d.ts +34 -1
  23. package/dist/types/panel/gpu/color.d.ts +4 -4
  24. package/dist/types/panel/gpu/renderer.d.ts +110 -6
  25. package/dist/types/panel/gpu/shader.wgsl.d.ts +1 -1
  26. package/dist/types/panel/haptics.d.ts +6 -2
  27. package/dist/types/panel/has.d.ts +0 -1
  28. package/dist/types/panel/index.d.ts +3 -1
  29. package/dist/types/panel/inspector.d.ts +33 -3
  30. package/dist/types/panel/lens.d.ts +113 -0
  31. package/dist/types/panel/motion.d.ts +2 -0
  32. package/dist/types/panel/optkeys.d.ts +7 -0
  33. package/dist/types/panel/page-hints.d.ts +2 -1
  34. package/dist/types/panel/prefs.d.ts +2 -2
  35. package/dist/types/panel/presence.d.ts +20 -0
  36. package/dist/types/panel/presets.d.ts +17 -5
  37. package/dist/types/panel/ramp-morph.d.ts +117 -0
  38. package/dist/types/panel/ramps.d.ts +1 -1
  39. package/dist/types/panel/reel.d.ts +43 -0
  40. package/dist/types/panel/scope.d.ts +48 -0
  41. package/dist/types/panel/slider.d.ts +22 -5
  42. package/dist/types/panel/sound.d.ts +137 -0
  43. package/dist/types/panel/styles.d.ts +1 -1
  44. package/dist/types/panel/tab-transition.d.ts +15 -0
  45. package/dist/types/panel/tabs.d.ts +1 -1
  46. package/dist/types/panel/toggle.d.ts +27 -0
  47. package/dist/types/panel/toolbar-grid.d.ts +6 -0
  48. package/dist/types/ui/activity-css.d.ts +1 -0
  49. package/dist/types/ui/activity-model.d.ts +122 -0
  50. package/dist/types/ui/activity.d.ts +67 -0
  51. package/dist/types/ui/follow.d.ts +17 -0
  52. package/dist/types/ui/index.d.ts +5 -1
  53. package/dist/types/ui/look.d.ts +9 -0
  54. package/dist/types/ui/picker-css.d.ts +1 -0
  55. package/dist/types/ui/picker-model.d.ts +53 -0
  56. package/dist/types/ui/picker.d.ts +67 -0
  57. package/dist/types/ui/react.d.ts +27 -0
  58. package/dist/types/ui/scrollbar.d.ts +12 -0
  59. package/dist/types/ui/solid.d.ts +23 -0
  60. package/dist/types/ui/split-model.d.ts +55 -0
  61. package/dist/types/ui/split.d.ts +22 -3
  62. package/dist/ui-react.js +3462 -690
  63. package/dist/ui-solid.js +3440 -689
  64. package/dist/ui.js +2963 -264
  65. package/package.json +1 -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-qtt698hp.js";
8
+ } from "./chunk-7xgkerd2.js";
8
9
 
9
10
  // src/panel/icons.ts
10
11
  var ICONS = {
@@ -62,130 +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
66
  // src/panel/rolling-css.ts
190
67
  var rolling_css_default = `.rn-root {
191
68
  display: inline-block;
@@ -376,8 +253,428 @@ var problemsPreset = {
376
253
  }
377
254
  };
378
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
+
379
676
  // src/panel/styles.ts
380
- 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)", 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-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_T = Array.from({ length: GRAD_N }, (_, i) => `--kgrad-${i} ${FILL_T} ${DELAY}`).join(","), 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(`
381
678
  `), COLOR_TOKENS = [
382
679
  "bg",
383
680
  "surface",
@@ -413,8 +710,13 @@ ${register(["width"], "<length-percentage>", "288px")}
413
710
  @property --k-copy{syntax:"<number>";inherits:true;initial-value:1}
414
711
  @property --k-check{syntax:"<number>";inherits:true;initial-value:0}
415
712
  @property --k-check-a{syntax:"<number>";inherits:true;initial-value:0}
713
+ @property --k-head-s{syntax:"<number>";inherits:true;initial-value:0}
714
+ @property --k-head-v{syntax:"<number>";inherits:true;initial-value:0}
416
715
  @property --kc-value-size{syntax:"<length>";inherits:true;initial-value:11.5px}
716
+ @property --k-armed{syntax:"<number>";inherits:false;initial-value:0}
417
717
  @property --kr-lo{syntax:"<number>";inherits:true;initial-value:0}
718
+ @property --kpt-sel{syntax:"<number>";inherits:false;initial-value:1}
719
+ @property --kgs-sel{syntax:"<number>";inherits:false;initial-value:1}
418
720
  @property --kr-hi{syntax:"<number>";inherits:true;initial-value:0}
419
721
  @property --kr-show-lo{syntax:"<number>";inherits:true;initial-value:0}
420
722
  @property --kr-show-hi{syntax:"<number>";inherits:true;initial-value:0}
@@ -431,8 +733,10 @@ ${GRAD_PROPS}
431
733
  --k-mono:"SF Mono",SFMono-Regular,ui-monospace,Menlo,monospace;
432
734
  --k-spring:0.24 0.16;--k-ease:cubic-bezier(.2,.8,.2,1);--k-reveal-ease:var(--k-ease);
433
735
  --k-font-weight:400;--k-title-weight:600;--k-thumb:#fff;--k-lift:1;--k-frame:card;
434
- --k-row-gap:0px;--k-hover-in:150;--k-hover-out:150;--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-group-transform:uppercase;--k-value-font:var(--k-font);
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);
435
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;
436
740
  }
437
741
  /* Glass edges are hairlines: --k-rim-width is one device pixel at common pixel ratios. */
438
742
  @media(min-resolution:1.5dppx){:where(:root,:host){--k-rim-width:.67px}}
@@ -498,7 +802,7 @@ ${GRAD_PROPS}
498
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}
499
803
  :where(.knobs-panel,.knobs-chip) :where(*,*::before,*::after){box-sizing:border-box}
500
804
  .knobs-panel .rn-slot{box-sizing:content-box}
501
- .knobs-panel [hidden],.knobs-chip[hidden]{display:none!important}
805
+ .knobs-panel [hidden],.knobs-panel[hidden],.knobs-chip[hidden]{display:none!important}
502
806
  .knobs-mono{font-family:var(--k-mono);font-variant-numeric:tabular-nums}
503
807
 
504
808
  /* The chip: a round cog button, or a capsule with a label beside the cog. */
@@ -525,13 +829,17 @@ ${GRAD_PROPS}
525
829
  .knobs-panel,.knobs-chip{--k-token-t:calc(var(--k-fill-ms) * 1ms) var(--k-fill-ease)}
526
830
  :root[data-knobs-scrub] .knobs-chip{--k-token-t:0s}
527
831
  :root[data-knobs-scrub] .knobs-panel{transition:none}
528
- @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}
529
834
  /* Panel material: --k-glass (0 opaque … 1 clearest) thins the surface over a blurred, saturated
530
835
  backdrop, toned toward the theme (--k-glass-tone dims it under dark panels) so text keeps its
531
836
  contrast. Its edge is a hairline (--k-edge, --k-rim-width wide) and a hairline of light (--k-rim)
532
837
  catches its top. */
533
- .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);
534
- 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);
535
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)));
536
844
  -webkit-backdrop-filter:var(--k-backdrop);backdrop-filter:var(--k-backdrop);
537
845
  animation:knobs-in .16s var(--k-ease);}
@@ -549,14 +857,14 @@ ${GRAD_PROPS}
549
857
  .knobs-morph::after{box-shadow:var(--k-shadow);opacity:var(--p)}
550
858
  /* Meanwhile the chip is just its label, fading on the shape. */
551
859
  .knobs-chip.knobs-chip[data-morph]{visibility:visible;pointer-events:auto;transform:none;transition:none;background:transparent;border-color:transparent;box-shadow:none}
552
- .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;
553
861
  /* --k-chrome 0: an embedded panel sheds its card (background, border, rim) for the host's column,
554
862
  border width too, so its rows line up with the column's edges. */
555
863
  border-width:calc(var(--k-chrome) * 1px);
556
864
  background:color-mix(in srgb,color-mix(in srgb,var(--k-bg) calc(100% - var(--k-glass) * 20%),transparent) calc(var(--k-chrome) * 100%),transparent);border-color:transparent;
557
865
  --k-backdrop:blur(calc(var(--k-glass) * var(--k-chrome) * 24px)) saturate(calc(1 + var(--k-glass) * var(--k-chrome) * .8)) brightness(calc(1 + (var(--k-glass-tone) - 1) * var(--k-glass) * var(--k-chrome)))}
558
866
  @keyframes knobs-in{from{opacity:0;transform:translateY(4px) scale(.985)}to{opacity:1;transform:none}}
559
- @media(prefers-reduced-motion:reduce){.knobs-panel{animation:none}.knobs-panel *{transition:none!important}}
867
+ @media(prefers-reduced-motion:reduce){.knobs-panel{animation:none}.knobs-panel *{transition:none!important}.knobs-keys .knobs-keys-text,.knobs-number,.knobs-cell-edit{animation:none}}
560
868
 
561
869
  /* A scoped reset, (0,1,1): above a host's element rules (button{...}, button:hover{...},
562
870
  input[type=text]{...}) and below every knobs rule that styles these elements, which are all
@@ -568,10 +876,48 @@ ${GRAD_PROPS}
568
876
  :is(.knobs-panel,.knobs-menu) :is(:focus,:focus-visible):where(:not(.knobs-accessory *)){outline:none}
569
877
 
570
878
  /* Header */
571
- .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)}
572
880
  .knobs-panel.knobs-inline header,.knobs-panel.knobs-inline header:active{cursor:default}
881
+ /* The title row stays pinned while the rows scroll under it, in the panel (it scrolls once it's
882
+ bounded) or, with .knobs-pin-ancestor (which also makes the panel overflow:visible), in the
883
+ nearest scroller around it. Once rows are under it, and only then, it frosts (backdrop blur over
884
+ the panel's colour) and softens its foot with a short fade. That's scroll-driven, with no
885
+ script: --k-head-s follows the panel's own scroll, --k-head-v (pinned to an ancestor) the
886
+ panel's top passing its scroller's, over their first 24px. Until then the header is as it
887
+ always was: no background, no filter, no change in spacing. The GPU reads them too, to clip the
888
+ rows at the header's foot and fade them in below it. Without scroll-driven animations nothing
889
+ pins. */
890
+ @keyframes knobs-head-s{from{-webkit-backdrop-filter:none;backdrop-filter:none}to{--k-head-s:1;-webkit-backdrop-filter:blur(16px) saturate(1.4);backdrop-filter:blur(16px) saturate(1.4)}}
891
+ @keyframes knobs-head-v{from{-webkit-backdrop-filter:none;backdrop-filter:none}to{--k-head-v:1;-webkit-backdrop-filter:blur(16px) saturate(1.4);backdrop-filter:blur(16px) saturate(1.4)}}
892
+ @supports (animation-timeline:scroll()){
893
+ .knobs-panel:not(.knobs-page){scroll-timeline:--knobs-self block}
894
+ .knobs-panel.knobs-pin-ancestor:not(.knobs-page){overflow:visible;view-timeline:--knobs-view block}
895
+ .knobs-panel:not(.knobs-page)>header{position:sticky;top:0;z-index:4;border-radius:max(0px,calc(var(--k-panel-radius) - 1px)) max(0px,calc(var(--k-panel-radius) - 1px)) 0 0;
896
+ --k-head-on:max(var(--k-head-s),var(--k-head-v));--k-head-bg:color-mix(in srgb,var(--k-bg) 88%,transparent);
897
+ background:color-mix(in srgb,var(--k-head-bg) calc(var(--k-head-on) * 100%),transparent);
898
+ animation:knobs-head-s linear both;animation-timeline:--knobs-self;animation-range:0px 24px}
899
+ .knobs-panel.knobs-pin-ancestor:not(.knobs-page)>header{animation:knobs-head-v linear both;animation-timeline:--knobs-view;animation-range:exit-crossing 0px exit-crossing 24px}
900
+ .knobs-panel:not(.knobs-page)>header::after{content:"";position:absolute;left:0;right:0;top:100%;height:10px;pointer-events:none;
901
+ background:linear-gradient(var(--k-head-bg),transparent);opacity:var(--k-head-on)}
902
+ .knobs-panel[data-gpu-active="true"]:not(.knobs-page)>header{background:transparent!important;-webkit-backdrop-filter:none!important;backdrop-filter:none!important}
903
+ .knobs-panel[data-gpu-active="true"]:not(.knobs-page)>header::after{display:none}
904
+ }
573
905
  .knobs-panel header:active{cursor:grabbing}
574
- .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}
575
921
  /* Holding \\ shows the values from before the last change; the title dims meanwhile. */
576
922
  .knobs-panel[data-comparing] header strong{opacity:.4}
577
923
  .knobs-head-actions{display:flex;align-items:center;margin-right:calc((var(--k-icon-size) - var(--k-icon-btn)) / 2)}
@@ -582,26 +928,36 @@ ${GRAD_PROPS}
582
928
  .knobs-panel[data-gpu-active="true"] .knobs-die-icon>canvas{visibility:hidden}
583
929
  /* The tools sit quiet (--k-dim) until the panel has the pointer or keyboard focus, then read as
584
930
  labels, and as text under the pointer. */
585
- .knobs-panel header button{position:relative;width:var(--k-icon-btn);height:var(--k-icon-btn);padding:0;display:inline-flex;align-items:center;justify-content:center;color:var(--k-dim);background:transparent;border-radius:var(--k-round);cursor:pointer;font-size:12px;line-height:1;transition:background .15s,color .15s,opacity .15s}
931
+ .knobs-panel header button{position:relative;width:var(--k-icon-btn);height:var(--k-icon-btn);padding:0;display:inline-flex;align-items:center;justify-content:center;color:var(--k-dim);background:transparent;border-radius:var(--k-round);cursor:pointer;font-size:12px;line-height:1;transition:background var(--k-hover-t),color var(--k-hover-t),opacity .15s}
586
932
  .knobs-panel:is(:hover,:has(:focus-visible)) header button{color:var(--k-label)}
587
933
  .knobs-panel header button:is(:hover:not([disabled]),:focus-visible),.knobs-panel header button[aria-expanded="true"]{background:var(--k-surface-hi);color:var(--k-text)}
588
934
  .knobs-panel header button[disabled]{opacity:var(--k-disabled-opacity);cursor:default}
589
935
  /* The close X's ink sits a fifth of its box in from each side, a digit's barely: nudged right, its
590
936
  ink ends where the values' digits do. */
591
937
  .knobs-panel header .knobs-close>.knobs-icon{translate:calc(var(--k-icon-size) * .1) 0}
592
- /* Undo and redo fold in (room first, then the icons) once there's history, and away without it. */
593
- .knobs-head-history{display:flex;flex:none;overflow:hidden;width:calc(var(--k-icon-btn) * 2);margin-left:calc(var(--k-unit) * 1.5);transition:width .26s var(--k-ease),margin .26s var(--k-ease)}
594
- .knobs-head-history[data-single]{width:var(--k-icon-btn)}
595
- .knobs-head-history[data-shown="false"]{width:0;margin-left:0;transition:width .24s var(--k-ease) .06s,margin .24s var(--k-ease) .06s}
596
- .knobs-panel .knobs-head-history>button{transition:background .15s,color .15s,opacity .18s var(--k-ease) .08s,scale .3s cubic-bezier(.34,1.45,.64,1) .08s}
597
- .knobs-panel .knobs-head-history[data-shown="false"]>button{opacity:0;scale:.6;transition:background .15s,color .15s,opacity .12s ease-in,scale .14s ease-in}
938
+ /* Undo · Redo sit apart from Reset · Copy · Random. Undo, Redo and Reset (and a group's reset)
939
+ keep their slots while they're away and bloom in and out of them: nothing beside them moves. */
940
+ .knobs-head-history{display:flex;flex:none;margin-right:calc(var(--k-unit) * 1.5)}
941
+ .knobs-panel :is(.knobs-head-actions,.knobs-group-head) button[data-shown]{transition:background var(--k-hover-t),color var(--k-hover-t),opacity ${BLOOM_IN},scale ${BLOOM_IN},filter 0s,visibility 0s}
942
+ .knobs-panel :is(.knobs-head-actions,.knobs-group-head) button[data-shown="false"]{opacity:0;scale:.9;filter:blur(2.5px);visibility:hidden;
943
+ transition:background var(--k-hover-t),color var(--k-hover-t),opacity ${BLOOM_OUT},scale ${BLOOM_OUT},filter ${BLOOM_OUT},visibility 0s ${snappyOut.ms}ms}
944
+ @media(prefers-reduced-motion:reduce){
945
+ .knobs-panel :is(.knobs-head-actions,.knobs-group-head) button[data-shown]{transition:opacity .15s,visibility 0s!important}
946
+ .knobs-panel :is(.knobs-head-actions,.knobs-group-head) button[data-shown="false"]{scale:1;filter:none;transition:opacity .15s,visibility 0s .15s!important}}
947
+ /* A press flash (flash.ts): from a key or a click, the button's wash brightens at once and eases
948
+ back to its own, its ink with it. Two names, so a second press restarts it. */
949
+ @keyframes knobs-flash-a{from{background-color:var(--k-flash-bg);color:var(--k-text)}}
950
+ @keyframes knobs-flash-b{from{background-color:var(--k-flash-bg);color:var(--k-text)}}
951
+ .knobs-panel :is(header,.knobs-group-head) button{--k-flash-bg:color-mix(in srgb,var(--k-surface-hi),var(--k-text) 10%)}
952
+ .knobs-panel :is(header,.knobs-group-head) button[data-k-flash="a"]{animation:knobs-flash-a ${FLASH_MS}ms cubic-bezier(.33,1,.68,1)}
953
+ .knobs-panel :is(header,.knobs-group-head) button[data-k-flash="b"]{animation:knobs-flash-b ${FLASH_MS}ms cubic-bezier(.33,1,.68,1)}
598
954
  /* Copied: the glyph shrinks away and a check draws itself in its place, held, then they trade
599
955
  back (the check fading). Driven by --k-copy (the glyph), --k-check (how much of the check is
600
956
  drawn) and --k-check-a (its opacity), which the GPU renderer reads too. */
601
957
  .knobs-panel header .knobs-copy{--k-copy:1;--k-check:0;--k-check-a:0;
602
- transition:background .15s,color .15s,opacity .15s,--k-copy .32s cubic-bezier(.34,1.45,.64,1) .08s,--k-check 0s .18s,--k-check-a .16s ease-in}
958
+ transition:background var(--k-hover-t),color var(--k-hover-t),opacity .15s,--k-copy ${BLOOM_IN} .04s,--k-check 0s .1s,--k-check-a .1s cubic-bezier(.4,0,1,1)}
603
959
  .knobs-panel header .knobs-copy[data-copied]{--k-copy:0;--k-check:1;--k-check-a:1;color:var(--k-text);
604
- transition:background .15s,color .15s,opacity .15s,--k-copy .14s ease-in,--k-check .28s cubic-bezier(.3,.7,.2,1) .06s,--k-check-a .05s linear .06s}
960
+ transition:background var(--k-hover-t),color var(--k-hover-t),opacity .15s,--k-copy .1s cubic-bezier(.4,0,1,1),--k-check .18s cubic-bezier(.2,.8,.2,1) .03s,--k-check-a .04s linear .03s}
605
961
  .knobs-copy>.knobs-icon:not(.knobs-check){opacity:var(--k-copy);scale:calc(.5 + .5 * var(--k-copy))}
606
962
  .knobs-copy>.knobs-check{position:absolute;inset:0;margin:auto;opacity:var(--k-check-a)}
607
963
  /* (Over .knobs-icon>path's fill: the check is a stroke, 16 of 256 wide like Phosphor's regular icons.) */
@@ -629,6 +985,18 @@ ${GRAD_PROPS}
629
985
  .knobs-panel .knobs-preset-chip[aria-checked="true"]{color:var(--k-text);font-weight:var(--k-title-weight)}
630
986
  .knobs-panel .knobs-preset-save{flex:none;width:calc((var(--k-inset) - var(--k-tray-pad)) * 2 + 8px);padding:0!important;color:var(--k-dim)!important;font-weight:400!important;font-size:var(--k-text-title)!important}
631
987
  .knobs-panel .knobs-preset-save:hover{color:var(--k-text)!important}
988
+ /* The active preset's modified mark: the values have drifted off it (a row's dot, in its corner). */
989
+ .knobs-panel .knobs-preset-chip::after{content:"";position:absolute;top:4px;right:4px;width:4px;height:4px;border-radius:50%;background:var(--k-modified);opacity:0;transform:scale(.4);pointer-events:none;transition:opacity .16s ease-out,transform .28s cubic-bezier(.34,1.5,.64,1)}
990
+ .knobs-panel .knobs-preset-chip[data-drifted]::after{opacity:.95;transform:none}
991
+ /* Naming a saved preset: a field over its chip that opens out from the middle to the chip's width;
992
+ the chip's own name hides beneath it. Leaving, it fades. */
993
+ .knobs-panel .knobs-preset-chip[data-naming]{color:transparent!important}
994
+ .knobs-panel .knobs-preset-name{position:absolute;z-index:2;box-sizing:border-box;min-width:0;margin:0;padding:0 6px;border:1px solid color-mix(in srgb,var(--k-accent) 55%,transparent);border-radius:var(--k-button-radius,var(--k-radius-md));outline:none;background:color-mix(in srgb,var(--k-accent) 10%,transparent);color:var(--k-text);caret-color:var(--k-accent);
995
+ font-family:inherit;font-size:var(--k-button-font-size,var(--k-text-control));font-weight:var(--k-title-weight);letter-spacing:inherit;text-align:center;animation:knobs-name-in .25s cubic-bezier(.2,.8,.2,1);transition:opacity .11s cubic-bezier(.4,0,1,1)}
996
+ .knobs-preset-name::selection{background:color-mix(in srgb,var(--k-accent) 24%,transparent);color:var(--k-text)}
997
+ .knobs-panel .knobs-preset-name[data-leaving]{opacity:0;pointer-events:none}
998
+ @keyframes knobs-name-in{from{clip-path:inset(0 50% round 6px);opacity:.4}to{clip-path:inset(0 round 6px);opacity:1}}
999
+ @media(prefers-reduced-motion:reduce){.knobs-panel .knobs-preset-name{animation:knobs-fade .15s ease-out}}
632
1000
 
633
1001
  /* Group headers */
634
1002
  .knobs-group-head{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:20px;margin:var(--k-group-gap) 0 var(--k-gap);padding:0 var(--k-group-inset);color:var(--k-group-color);font-size:var(--k-group-size);font-weight:var(--k-title-weight);letter-spacing:var(--k-group-tracking);text-transform:var(--k-group-transform)}
@@ -637,27 +1005,52 @@ ${GRAD_PROPS}
637
1005
  .knobs-group-head>span:last-child{display:flex;align-items:center;gap:var(--k-tray-pad);margin-right:calc((var(--k-icon-sm) - 20px) / 2)}
638
1006
  .knobs-group-head button{height:20px;padding:0 6px;font-size:var(--k-text-meta);font-weight:500;letter-spacing:0;text-transform:none;color:var(--k-label);background:transparent;border-radius:var(--k-radius-sm);cursor:pointer}
639
1007
  .knobs-group-head button:is(:hover:not([disabled]),:focus-visible){background:var(--k-surface-hi);color:var(--k-text)}
640
- .knobs-group-head .knobs-group-reset{width:20px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--k-round);
641
- transition:opacity .18s var(--k-ease),transform .3s cubic-bezier(.34,1.45,.64,1),background .15s,color .15s,visibility 0s}
1008
+ .knobs-group-head .knobs-group-reset{width:20px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--k-round)}
642
1009
  .knobs-group-head :is(.knobs-icon,.knobs-die-icon){width:var(--k-icon-sm);height:var(--k-icon-sm)}
643
- .knobs-group-reset[data-shown="false"]{opacity:0;visibility:hidden;transform:scale(.55);transition:opacity .14s ease-in,transform .14s ease-in,visibility 0s .14s}
644
1010
  .knobs-group-head .knobs-group-die{width:20px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--k-round);color:var(--k-dim)}
645
1011
  .knobs-group-head .knobs-group-die[disabled]{opacity:var(--k-disabled-opacity);cursor:default}
646
- .knobs-panel .knobs-group-head button:where(:not(.knobs-group-reset)){transition:background .15s,color .15s,opacity .2s,transform .12s}
1012
+ .knobs-panel .knobs-group-head button:where(:not(.knobs-group-reset)){transition:background var(--k-hover-t),color var(--k-hover-t),opacity .2s,transform .12s}
647
1013
 
648
- /* Grouped cards: rows share one surface, split by hairlines */
1014
+ /* Grouped cards: rows share one surface, split by hairlines that stay put whatever a row is doing */
649
1015
  .knobs-card{position:relative;margin-bottom:var(--k-section-gap);border-radius:var(--k-radius);background:var(--k-surface);overflow:hidden}
650
1016
  .knobs-card:last-child{margin-bottom:0}
651
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)))}
652
1035
  .knobs-card>:hover,.knobs-card>.knobs-active{background:var(--k-row-hover)}
653
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}
654
- .knobs-card>:is(:hover:not(.knobs-x),.knobs-x:has(>.knobs-x-head:is(:hover,:focus-visible)),:has(:focus-visible):not(.knobs-x),.knobs-active)::before,.knobs-card>:is(:hover:not(.knobs-x),.knobs-x:has(>.knobs-x-head:is(:hover,:focus-visible)),:has(:focus-visible):not(.knobs-x),.knobs-active)+*::before{opacity:0}
655
- .knobs-card>::after{content:"";position:absolute;z-index:2;inset:0;background:var(--k-surface);opacity:0;pointer-events:none;transition:opacity .15s,box-shadow .15s}
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}
1038
+ /* The card focus is on (a click on its empty area or heading, Esc from a row): a soft wash over all
1039
+ of it, for R and ⇧R acting on its group. */
1040
+ :is(.knobs-card,.knobs-tile)::after{content:"";position:absolute;inset:0;border-radius:inherit;background:var(--k-surface);opacity:0;pointer-events:none;transition:opacity .2s var(--k-ease)}
1041
+ :is(.knobs-card,.knobs-tile)[data-k-scope]::after{opacity:1}
1042
+ .knobs-panel:focus{outline:none}
656
1043
  .knobs-card>:has(:focus-visible)::after{opacity:1}
657
1044
  /* One wash at a time: while the pointer has a row (and keys haven't taken over since), a focus
658
- wash elsewhere steps aside, and the hairlines beside that row come back. */
1045
+ wash elsewhere steps aside. */
659
1046
  ${POINTER_ROW} :is(.knobs-card>*,.knobs-sub,.knobs-x-head):not(:hover)::after{opacity:0}
660
- ${POINTER_ROW} .knobs-card>:not(:hover,.knobs-active,:hover+*,.knobs-active+*)::before{opacity:1}
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}
661
1054
  .knobs-panel .knobs-slider{height:var(--k-row);contain:layout style}
662
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}
663
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}
@@ -694,12 +1087,19 @@ ${POINTER_ROW} .knobs-card>:not(:hover,.knobs-active,:hover+*,.knobs-active+*)::
694
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))}
695
1088
  .knobs-smear .knobs-handle::before{scale:calc(1 + var(--k-smear) / 3) 1;opacity:calc(3 / (3 + var(--k-smear)))}
696
1089
  .knobs-animate .knobs-fill,.knobs-animate .knobs-handle{transition:--k-fill ${SLIDE_T} ${DELAY},--k-fill-mix .15s,opacity .15s}
697
- .knobs-settle .knobs-fill,.knobs-settle .knobs-handle{transition:--k-fill .1s cubic-bezier(.25,1,.5,1),--k-fill-mix .12s,opacity .15s}
698
- /* 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. */
699
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}
700
1094
  .knobs-slider:is(:hover,.knobs-active,:has(:focus-visible)) .knobs-notch{opacity:.38}
701
- .knobs-slider.knobs-modified:is(:hover,.knobs-active,:has(:focus-visible)) .knobs-notch{opacity:.55}
702
- .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
+ `)}
703
1103
  /* The handle is a full-width layer moved by the fill (translate's % is its own width, the track's). */
704
1104
  .knobs-handle{position:absolute;inset:0;pointer-events:none;transform:translateX(var(--k-fill));opacity:var(--k-handle-rest);transition:opacity .15s}
705
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);
@@ -748,7 +1148,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
748
1148
  .knobs-smear-pill{margin-inline:calc(var(--k-smear) * -.5px);
749
1149
  -webkit-mask:linear-gradient(to right,transparent,#000 calc(var(--k-smear) * 1px),#000 calc(100% - var(--k-smear) * 1px),transparent);
750
1150
  mask:linear-gradient(to right,transparent,#000 calc(var(--k-smear) * 1px),#000 calc(100% - var(--k-smear) * 1px),transparent)}
751
- .knobs-seg button{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;height:100%;padding:0 7px;font-size:var(--k-text-control);font-weight:var(--k-label-weight);white-space:nowrap;color:var(--k-label);background:transparent;cursor:pointer;border-radius:var(--k-radius-sm);transition:background .15s,color .15s}
1151
+ .knobs-seg button{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;height:100%;padding:0 7px;font-size:var(--k-text-control);font-weight:var(--k-label-weight);white-space:nowrap;color:var(--k-label);background:transparent;cursor:pointer;border-radius:var(--k-radius-sm);transition:background var(--k-hover-t),color var(--k-hover-t)}
752
1152
  .knobs-seg button:is(:hover,:focus-visible){color:var(--k-text)}
753
1153
  .knobs-seg button[aria-checked=true]{color:var(--k-text)}
754
1154
 
@@ -765,10 +1165,17 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
765
1165
  .knobs-panel .knobs-action-row{padding:0}
766
1166
  .knobs-panel .knobs-action{width:100%;height:100%;padding:0 var(--k-inset);text-align:left;background:transparent;border-radius:0;color:var(--k-button-color,var(--k-text));font-size:var(--k-button-font-size,calc(var(--k-text-control) + .5px));font-weight:var(--k-label-weight);cursor:pointer;transition:color var(--k-hover-t),background var(--k-hover-t),box-shadow var(--k-hover-t)}
767
1167
  .knobs-panel .knobs-action:is(:hover,:focus-visible){color:var(--k-accent)}
768
- .knobs-panel .knobs-action:active{opacity:.7}
1168
+ .knobs-panel .knobs-action:is(:active,.knobs-pressed){opacity:.7}
1169
+ .knobs-panel .knobs-action.knobs-pressed{color:var(--k-accent)}
1170
+ /* A shortcut: its key cap at the row's end, the button taking the rest. */
1171
+ .knobs-panel .knobs-action-row[data-keyed]{gap:6px}
1172
+ .knobs-panel .knobs-action-row[data-keyed]>.knobs-action{flex:1 1 0;width:auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1173
+ .knobs-panel .knobs-action-row>.knobs-keys{order:1;margin-right:var(--k-inset)}
769
1174
  /* With a button fill or outline (--k-button-bg, --k-button-line), an action is a sized button in
770
1175
  its row, and the presets strip a row of them, as toolbar buttons are. */
771
1176
  .knobs-panel[data-buttons] .knobs-action-row{padding:0 var(--k-inset)}
1177
+ .knobs-panel[data-buttons] .knobs-action-row[data-keyed]>.knobs-action{flex:0 1 auto;margin-right:auto}
1178
+ .knobs-panel[data-buttons] .knobs-action-row>.knobs-keys{margin-right:0}
772
1179
  .knobs-panel[data-buttons] .knobs-action{width:auto;max-width:100%;height:var(--k-button-h,var(--kc-control-h));padding:0 10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
773
1180
  background:var(--k-button-bg,transparent);box-shadow:inset 0 0 0 1px var(--k-button-line,transparent);border-radius:var(--k-button-radius,var(--k-radius-md))}
774
1181
  .knobs-panel[data-buttons] .knobs-action:is(:hover,:focus-visible){color:var(--k-text);background:var(--k-button-hover-bg,var(--k-surface-hi));box-shadow:inset 0 0 0 1px var(--k-button-hover-line,var(--k-button-line,transparent))}
@@ -794,13 +1201,12 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
794
1201
  .knobs-sub{position:relative;transition:background var(--k-hover-t)}
795
1202
  .knobs-sub:hover,.knobs-sub.knobs-active,.knobs-x-head:hover,.knobs-x-head[data-panned]{background:var(--k-row-hover)}
796
1203
  .knobs-sub+.knobs-sub::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}
797
- .knobs-sub:is(:hover,:has(:focus-visible),.knobs-active)::before,.knobs-sub:is(:hover,:has(:focus-visible),.knobs-active)+.knobs-sub::before{opacity:0}
798
- :is(.knobs-sub,.knobs-x-head)::after{content:"";position:absolute;z-index:2;inset:0;background:var(--k-surface);opacity:0;pointer-events:none;transition:opacity .15s}
1204
+ :is(.knobs-sub,.knobs-x-head)::after{content:"";position:absolute;z-index:2;inset:0;background:var(--k-surface-hi);opacity:0;pointer-events:none;transition:opacity .15s}
799
1205
  :is(.knobs-sub:has(:focus-visible),.knobs-x-head:focus-visible)::after{opacity:1}
800
1206
  .knobs-spring-curve{position:relative;flex:none;width:calc(var(--k-row) * 2.5);margin:var(--k-gap) var(--k-inset) var(--k-gap) 0;border-radius:var(--k-radius-sm);background:var(--k-seg-bg);pointer-events:none}
801
1207
  .knobs-panel :is(.knobs-spring-curve,.knobs-ease)>svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
802
1208
  .knobs-guide{fill:none;stroke:var(--k-line);stroke-width:1}
803
- .knobs-arm{fill:none;stroke:var(--k-dim);stroke-width:1;transition:d ${FILL_T} ${DELAY}}
1209
+ .knobs-arm{fill:none;stroke:var(--k-dim);stroke-width:1}
804
1210
  .knobs-curve{fill:none;stroke:var(--k-accent);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;transition:d ${FILL_T} ${DELAY}}
805
1211
  .knobs-spring-dot{position:absolute;left:0;top:0;width:5px;height:5px;margin:-2.5px;border-radius:50%;background:var(--k-accent);box-shadow:0 0 calc(var(--k-glow) * 8px) var(--k-accent);opacity:0;scale:var(--k-curve-dot)}
806
1212
  .knobs-guide-dashed{stroke:var(--k-line-hi)}
@@ -808,12 +1214,16 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
808
1214
 
809
1215
  .knobs-panel :is(input,textarea,[contenteditable]){-webkit-user-select:text;user-select:text}
810
1216
  .knobs-x-head{position:relative;display:flex;align-items:center;justify-content:space-between;gap:8px;height:var(--k-row);padding:0 var(--k-inset);cursor:pointer;user-select:none;transition:background var(--k-hover-t)}
1217
+ .knobs-x-head[data-panned]{cursor:ew-resize}
811
1218
  .knobs-panel .knobs-x-head:focus-visible{outline:none;box-shadow:none}
812
1219
  .knobs-x-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--k-label);font-weight:var(--k-label-weight)}
813
1220
  .knobs-x-label::after{content:"";display:inline-block;width:4px;height:4px;margin-left:5px;border-radius:50%;background:var(--k-modified);vertical-align:middle;opacity:0;transform:scale(.4);transition:opacity .16s ease-out,transform .28s cubic-bezier(.34,1.5,.64,1)}
814
1221
  .knobs-modified .knobs-x-label::after{opacity:.95;transform:none}
815
1222
  .knobs-x-sum{display:flex;align-items:center;gap:8px;min-width:0}
816
1223
  .knobs-x-read,.knobs-strip-read{color:var(--k-label);font-size:var(--kc-value-size);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";white-space:nowrap}
1224
+ /* A name crossfading (reel.ts fadeText): the old one fades out over the new, where it was. */
1225
+ .knobs-x-read{position:relative}
1226
+ .knobs-ghost{position:absolute;right:0;top:0;white-space:nowrap;pointer-events:none}
817
1227
  .knobs-caret{width:10px;height:10px;flex:none;margin-right:-2.5px;color:var(--k-dim);transition:transform ${LEAD_T},color .15s}
818
1228
  .knobs-caret path{fill:none;stroke:currentColor;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round}
819
1229
  .knobs-x-head:hover .knobs-caret{color:var(--k-label)}
@@ -838,33 +1248,33 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
838
1248
  .knobs-pad.knobs-active>i{transition:scale .2s var(--k-ease)}
839
1249
  .knobs-pad:is(.knobs-active,:focus-visible) .knobs-pad-puck{scale:1.2}
840
1250
  .knobs-ease{height:calc(var(--k-row) * 4.5)}
841
- .knobs-panel .knobs-ease-handle{position:absolute;left:0;top:0;width:12px;height:12px;margin:-6px;padding:0;border-radius:var(--k-round);background:var(--k-bg);box-shadow:inset 0 0 0 2px var(--k-accent),0 1px 3px rgba(0,0,0,calc(.3 * var(--k-lift)));cursor:grab;touch-action:none;transition:translate ${FILL_T} ${DELAY},scale .2s var(--k-ease)}
1251
+ .knobs-panel .knobs-ease-handle{position:absolute;left:0;top:0;width:12px;height:12px;margin:-6px;padding:0;border-radius:var(--k-round);background:var(--k-bg);box-shadow:inset 0 0 0 2px var(--k-accent),0 1px 3px rgba(0,0,0,calc(.3 * var(--k-lift)));cursor:grab;touch-action:none;transition:scale .2s var(--k-ease)}
842
1252
  .knobs-ease-handle::before{content:"";position:absolute;inset:-8px;border-radius:50%}
843
- .knobs-ease.knobs-active :is(.knobs-curve,.knobs-arm){transition:none}
844
- .knobs-ease.knobs-active .knobs-ease-handle{transition:scale .2s var(--k-ease)}
1253
+ /* The row's morph moves the curve, arms and handles each frame (ramp-morph.ts), so they don't
1254
+ transition. */
1255
+ :is(.knobs-ease,.knobs-ease-mini) .knobs-curve{transition:none}
845
1256
  .knobs-ease-handle:active{scale:1.2;cursor:grabbing}
846
1257
  .knobs-ease-handle:focus-visible{scale:1.2}
847
1258
  .knobs-ease-mini{flex:none;width:30px;height:var(--k-track-h);overflow:visible}
848
1259
  .knobs-ease-chips{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--k-tray-pad);margin-top:6px;padding:var(--k-tray-pad);border-radius:var(--k-radius-md);background:var(--k-seg-bg)}
849
- .knobs-panel .knobs-ease-chip{min-width:0;height:calc(var(--k-row) - 10px);padding:0 6px;font-size:var(--k-text-control);font-weight:var(--k-label-weight);color:var(--k-label);background:transparent;border-radius:var(--k-radius-sm);cursor:pointer;white-space:nowrap;transition:background .15s,color .15s,box-shadow .15s}
1260
+ .knobs-panel .knobs-ease-chip{min-width:0;height:calc(var(--k-row) - 10px);padding:0 6px;font-size:var(--k-text-control);font-weight:var(--k-label-weight);color:var(--k-label);background:transparent;border-radius:var(--k-radius-sm);cursor:pointer;white-space:nowrap;transition:background var(--k-hover-t),color var(--k-hover-t),box-shadow .15s}
850
1261
  .knobs-panel .knobs-ease-chip:is(:hover,:focus-visible){color:var(--k-text)}
851
1262
  .knobs-panel .knobs-ease-chip[aria-checked="true"]{color:var(--k-text);background:var(--k-pill-bg);box-shadow:var(--k-lift-shadow)}
852
1263
 
853
1264
  /* Gradients: a swatch strip with stop ticks in the head; a wide strip with a handle per stop (the
854
1265
  selected one full size), then that stop's colour strips. The ramp runs a handle's radius in from
855
1266
  each end, as the colour strips' do. */
856
- .knobs-grad-mini{position:relative;flex:none;width:calc(var(--k-row) * 2.5);height:var(--k-track-h);border-radius:var(--k-radius-sm);box-shadow:inset 0 0 0 1px var(--k-line);background:${GRAD_MINI};transition:${GRAD_T}}
857
- .knobs-grad-mini>i{position:absolute;top:calc(100% + 2px);left:0;width:1px;height:3px;margin-left:-.5px;background:var(--k-dim);transition:left ${FILL_T} ${DELAY},opacity .15s}
858
- .knobs-grad{--kg-h:calc(var(--k-row) - 6px);--kg-r:calc(var(--kg-h) / 2);--kg-pin:round(calc(var(--kg-h) * .42),1px);position:relative;height:var(--kg-h);margin:2px 0 var(--k-gap);border-radius:var(--k-round);box-shadow:inset 0 0 0 1px var(--k-line);background:${GRAD_STRIP};cursor:pointer;touch-action:none;user-select:none;outline:none;transition:${GRAD_T}}
859
- .knobs-grad-stop{position:absolute;top:50%;left:0;width:var(--kg-pin);height:calc(var(--kg-h) - 4px);margin:calc(2px - var(--kg-h) / 2) 0 0 calc(var(--kg-pin) / -2);border-radius:var(--k-round);box-shadow:0 0 0 2px var(--k-thumb),0 1px 4px rgba(0,0,0,calc(.4 * var(--k-lift)));pointer-events:none;scale:.78;
860
- transition:left ${FILL_T} ${DELAY},background-color .2s ${DELAY},scale .2s var(--k-ease),opacity .15s}
861
- .knobs-grad-stop[data-selected]{scale:1}
862
- .knobs-grad:is(:focus-visible,.knobs-active) .knobs-grad-stop[data-selected]{scale:1.12}
863
- .knobs-grad-stop[data-leaving]{opacity:.35;scale:.6}
864
- .knobs-x[data-k-active] :is(.knobs-grad,.knobs-grad-mini,.knobs-grad-mini>i){transition:none}
865
- .knobs-x[data-k-active] .knobs-grad-stop{transition:scale .2s var(--k-ease),opacity .15s}
866
- :is(.knobs-grad-stop,.knobs-graph-pt)[data-born]{animation:knobs-born .24s var(--k-ease)}
867
- @keyframes knobs-born{from{opacity:0;scale:.4}}
1267
+ .knobs-grad-mini{position:relative;flex:none;width:calc(var(--k-row) * 2.5);height:var(--k-track-h);border-radius:var(--k-radius-sm);box-shadow:inset 0 0 0 1px var(--k-line);background:${GRAD_MINI}}
1268
+ .knobs-grad-mini>i{position:absolute;top:calc(100% + 2px);left:0;width:1px;height:3px;margin-left:-.5px;background:var(--k-dim);scale:1 var(--kgs-grow,1);transform-origin:top;transition:opacity .15s}
1269
+ .knobs-grad{--kg-h:calc(var(--k-row) - 6px);--kg-r:calc(var(--kg-h) / 2);--kg-pin:round(calc(var(--kg-h) * .42),1px);position:relative;height:var(--kg-h);margin:2px 0 var(--k-gap);border-radius:var(--k-round);box-shadow:inset 0 0 0 1px var(--k-line);background:${GRAD_STRIP};cursor:pointer;touch-action:none;user-select:none;outline:none}
1270
+ .knobs-grad-stop{position:absolute;top:50%;left:0;width:var(--kg-pin);height:calc(var(--kg-h) - 4px);margin:calc(2px - var(--kg-h) / 2) 0 0 calc(var(--kg-pin) / -2);border-radius:var(--k-round);box-shadow:0 0 0 2px var(--k-thumb),0 1px 4px rgba(0,0,0,calc(.4 * var(--k-lift)));pointer-events:none;
1271
+ --kgs-sel:.78;scale:calc(var(--kgs-sel) * var(--kgs-grow,1));transition:--kgs-sel .2s var(--k-ease),opacity .15s}
1272
+ /* The row's morph moves the strip, stops and ticks each frame (ramp-morph.ts), so none transition
1273
+ but a stop's focus scale (--kgs-sel); its growth (--kgs-grow) buds it from the strip or merges it
1274
+ into a neighbour. */
1275
+ .knobs-grad-stop[data-selected]{--kgs-sel:1}
1276
+ .knobs-grad:is(:focus-visible,.knobs-active) .knobs-grad-stop[data-selected]{--kgs-sel:1.12}
1277
+ .knobs-grad-stop[data-leaving]{opacity:.35;--kgs-sel:.6}
868
1278
  .knobs-mixed :is(.knobs-grad,.knobs-grad-mini){background:transparent}
869
1279
  /* A ramp's shapes and presets share one row: each chip gives up padding before its words, and only
870
1280
  a very narrow column ellipsizes them (the GPU overlay does the same). */
@@ -879,13 +1289,15 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
879
1289
  .knobs-graph{position:relative;height:calc(var(--k-row) * 4.5);border-radius:var(--k-radius-md);background:var(--k-seg-bg);cursor:crosshair;touch-action:none;user-select:none;outline:none;overflow:hidden}
880
1290
  .knobs-panel .knobs-graph>svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
881
1291
  .knobs-graph>i{position:absolute;left:0;top:0;pointer-events:none}
1292
+ /* The row's morph places the line and points each frame (ramp-morph.ts), so neither transitions:
1293
+ a point's size is its focus scale (--kpt-sel, which does) times its growth (--kpt-grow, budding
1294
+ from the line or merging into a neighbour). */
882
1295
  .knobs-graph-pt{width:10px;height:10px;margin:-5px;border-radius:var(--k-round);background:var(--k-bg);box-shadow:inset 0 0 0 2px var(--k-accent),0 1px 3px rgba(0,0,0,calc(.3 * var(--k-lift)));
883
- transition:translate ${FILL_T} ${DELAY},background-color .15s,scale .2s var(--k-ease),opacity .15s}
1296
+ scale:calc(var(--kpt-sel) * var(--kpt-grow,1));transition:background-color .15s,--kpt-sel .2s var(--k-ease),opacity .15s}
884
1297
  .knobs-graph-pt[data-selected]{background:var(--k-accent)}
885
- .knobs-graph:is(:focus-visible,.knobs-active) .knobs-graph-pt[data-selected]{scale:1.2}
886
- .knobs-graph-pt[data-leaving]{opacity:.35;scale:.7}
887
- .knobs-graph.knobs-active .knobs-graph-pt{transition:background-color .15s,scale .2s var(--k-ease),opacity .15s}
888
- .knobs-graph.knobs-active .knobs-curve,.knobs-x[data-k-active] .knobs-curve-mini .knobs-curve{transition:none}
1298
+ .knobs-graph:is(:focus-visible,.knobs-active) .knobs-graph-pt[data-selected]{--kpt-sel:1.2}
1299
+ .knobs-graph-pt[data-leaving]{opacity:.35;--kpt-sel:.7}
1300
+ .knobs-graph .knobs-curve,.knobs-curve-mini .knobs-curve{transition:none}
889
1301
  .knobs-graph :is(.knobs-graph-v,.knobs-graph-h,.knobs-graph-dot,.knobs-graph-read){opacity:0;transition:opacity .15s}
890
1302
  .knobs-graph[data-hover] :is(.knobs-graph-v,.knobs-graph-h,.knobs-graph-dot,.knobs-graph-read){opacity:1}
891
1303
  .knobs-graph-v{bottom:0;width:1px;margin-left:-.5px;background:var(--k-line-hi)}
@@ -897,7 +1309,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
897
1309
  /* Rows as separate bars (--k-row-gap above 0): each row carries its own surface, no hairlines.
898
1310
  A spring stacks its curve over its two sliders. */
899
1311
  .knobs-panel[data-rows] .knobs-card{display:flex;flex-direction:column;gap:var(--k-row-gap);background:transparent;overflow:visible}
900
- .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}
901
1313
  .knobs-panel[data-rows] .knobs-card>:is(:hover,.knobs-active){background:color-mix(in srgb,var(--k-surface-hi) 50%,var(--k-surface))}
902
1314
  .knobs-panel[data-rows] :is(.knobs-card>*,.knobs-sub)::before{display:none}
903
1315
  .knobs-panel[data-rows] .knobs-card>.knobs-subhead{background:transparent}
@@ -950,7 +1362,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
950
1362
  .knobs-panel .knobs-search input{flex:1;min-width:0;height:100%;padding:0;background:transparent;border-radius:0;color:var(--k-text);font-size:var(--k-text-control);-webkit-appearance:none;appearance:none}
951
1363
  .knobs-panel .knobs-search input::placeholder{color:var(--k-dim)}
952
1364
  .knobs-panel .knobs-search input::-webkit-search-cancel-button{display:none}
953
- .knobs-panel .knobs-search-chip{flex:none;display:inline-flex;align-items:center;gap:5px;height:var(--kc-control-h);padding:0 8px;background:transparent;border-radius:var(--k-radius-md);box-shadow:inset 0 0 0 1px var(--k-line);color:var(--k-label);font-size:var(--k-text-meta);font-weight:var(--k-label-weight);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s,box-shadow .15s,opacity .15s}
1365
+ .knobs-panel .knobs-search-chip{flex:none;display:inline-flex;align-items:center;gap:5px;height:var(--kc-control-h);padding:0 8px;background:transparent;border-radius:var(--k-radius-md);box-shadow:inset 0 0 0 1px var(--k-line);color:var(--k-label);font-size:var(--k-text-meta);font-weight:var(--k-label-weight);white-space:nowrap;cursor:pointer;transition:background var(--k-hover-t),color var(--k-hover-t),box-shadow .15s,opacity .15s}
954
1366
  .knobs-panel .knobs-search-chip:is(:hover,:focus-visible){color:var(--k-text);box-shadow:inset 0 0 0 1px var(--k-line-hi)}
955
1367
  .knobs-panel .knobs-search-chip[aria-pressed="true"]{background:var(--k-pill-bg);box-shadow:inset 0 0 0 1px var(--k-pill-border);color:var(--k-text)}
956
1368
  .knobs-panel .knobs-search-chip[data-none]{opacity:.5}
@@ -977,14 +1389,14 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
977
1389
  .knobs-cell-axis{flex:none;font-style:normal;font-size:var(--k-text-cap);font-weight:var(--k-title-weight);color:var(--k-dim)}
978
1390
  .knobs-cell-read,.knobs-range-read,.knobs-angle-read,.knobs-seed-read{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--k-label);font-family:var(--k-value-font);font-size:var(--kc-value-size);font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
979
1391
  .knobs-cell:focus-visible .knobs-cell-read,.knobs-active .knobs-cell:focus .knobs-cell-read,.knobs-cell[data-panned] .knobs-cell-read{color:var(--k-text)}
980
- .knobs-panel .knobs-link{flex:none;width:20px;height:20px;margin:0 -4px 0 auto;padding:0;display:inline-flex;align-items:center;justify-content:center;background:transparent;border-radius:var(--k-round);color:var(--k-dim);cursor:pointer;transition:background .15s,color .15s}
1392
+ .knobs-panel .knobs-link{flex:none;width:20px;height:20px;margin:0 -4px 0 auto;padding:0;display:inline-flex;align-items:center;justify-content:center;background:transparent;border-radius:var(--k-round);color:var(--k-dim);cursor:pointer;transition:background var(--k-hover-t),color var(--k-hover-t)}
981
1393
  .knobs-panel .knobs-link:hover{background:var(--k-surface-hi);color:var(--k-text)}
982
1394
  .knobs-panel .knobs-link[aria-pressed="true"]{color:var(--k-text)}
983
1395
  .knobs-link .knobs-icon{width:var(--k-icon-sm);height:var(--k-icon-sm)}
984
1396
  /* The inline editor over a readout: above the GPU canvas, which leaves that readout to it. */
985
- .knobs-panel .knobs-cell-edit{position:absolute;z-index:3;right:var(--k-inset);top:50%;transform:translateY(-50%);width:7ch;height:auto;padding:1px 4px;text-align:right;background:var(--k-bg);border-radius:var(--k-radius-sm);border-bottom:1px solid var(--k-text);color:var(--k-text);font-family:var(--k-value-font);font-variant-numeric:tabular-nums;font-size:var(--kc-value-size)}
1397
+ .knobs-panel .knobs-cell-edit{position:absolute;z-index:3;right:calc(var(--k-inset) - 6px);top:50%;transform:translateY(-50%);width:calc(7ch + 12px);height:var(--kc-control-h);padding:0 6px;text-align:right;background:linear-gradient(var(--k-surface-hi),var(--k-surface-hi)),var(--k-bg);border:0;border-radius:var(--k-radius-md);outline:none;color:var(--k-text);caret-color:var(--k-accent);font-family:var(--k-value-font);font-variant-numeric:tabular-nums;font-size:var(--kc-value-size);animation:knobs-fade .12s ease-out}
986
1398
  .knobs-cell.knobs-editing>:not(.knobs-cell-edit){visibility:hidden}
987
- .knobs-panel .knobs-cell>.knobs-cell-edit{inset:0;width:auto;transform:none;padding:0 6px;border-radius:var(--k-radius-md)}
1399
+ .knobs-panel .knobs-cell>.knobs-cell-edit{inset:0;width:auto;height:auto;transform:none}
988
1400
  .knobs-panel[data-gpu-active="true"] .knobs-cell-edit{z-index:2147480003}
989
1401
 
990
1402
  .knobs-panel .knobs-row.knobs-range{display:block;height:var(--k-row);padding:0}
@@ -998,19 +1410,18 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
998
1410
  background:linear-gradient(to right,transparent calc(var(--ks-a) - var(--kr-smear-lo,0) * .5px),var(--kr-tint) calc(var(--ks-a) + var(--kr-smear-lo,0) * .5px),var(--kr-tint) calc(var(--ks-b) - var(--kr-smear-hi,0) * .5px),transparent calc(var(--ks-b) + var(--kr-smear-hi,0) * .5px))}
999
1411
  .knobs-range-smearing .knobs-range-smear{display:block}
1000
1412
  .knobs-range-smearing .knobs-range-fill{opacity:0}
1001
- .knobs-range-thumb{position:absolute;top:0;bottom:0;left:calc(var(--kr-lo) * 100%);width:0;outline:none}
1002
- .knobs-range-thumb[data-end="hi"]{left:calc(var(--kr-hi) * 100%)}
1413
+ /* Each thumb sits on its end, centred, held 1.5px in at the track's ends so its hairline isn't cut. */
1414
+ .knobs-range-thumb{position:absolute;top:0;bottom:0;left:clamp(1.5px,calc(var(--kr-lo) * 100%),calc(100% - 1.5px));width:0;outline:none}
1415
+ .knobs-range-thumb[data-end="hi"]{left:clamp(1.5px,calc(var(--kr-hi) * 100%),calc(100% - 1.5px))}
1003
1416
  /* Where a thumb shows, the label and readout fade around it (2.5px either side of its hairline). */
1004
1417
  .knobs-range-text{position:absolute;inset:0;pointer-events:none;transition:--kr-show-lo .15s,--kr-show-hi .15s;
1005
- --kg-lo:calc(var(--kr-lo) * 100% + 1.5px);--kg-hi:calc(var(--kr-hi) * 100% - 1.5px);--kg-in-lo:rgb(0 0 0/calc(1 - var(--kr-show-lo)));--kg-in-hi:rgb(0 0 0/calc(1 - var(--kr-show-hi)));
1418
+ --kg-lo:clamp(1.5px,calc(var(--kr-lo) * 100%),calc(100% - 1.5px));--kg-hi:clamp(1.5px,calc(var(--kr-hi) * 100%),calc(100% - 1.5px));--kg-in-lo:rgb(0 0 0/calc(1 - var(--kr-show-lo)));--kg-in-hi:rgb(0 0 0/calc(1 - var(--kr-show-hi)));
1006
1419
  --kg-mask:linear-gradient(to right,#000 calc(var(--kg-lo) - 6.5px),var(--kg-in-lo) calc(var(--kg-lo) - 2.5px),var(--kg-in-lo) calc(var(--kg-lo) + 2.5px),#000 calc(var(--kg-lo) + 6.5px)),linear-gradient(to right,#000 calc(var(--kg-hi) - 6.5px),var(--kg-in-hi) calc(var(--kg-hi) - 2.5px),var(--kg-in-hi) calc(var(--kg-hi) + 2.5px),#000 calc(var(--kg-hi) + 6.5px));
1007
1420
  -webkit-mask:var(--kg-mask);mask:var(--kg-mask);-webkit-mask-composite:source-in;mask-composite:intersect}
1008
1421
  .knobs-range:not(.knobs-mixed):hover .knobs-range-text{--kr-show-lo:1;--kr-show-hi:1}
1009
1422
  .knobs-range:not(.knobs-mixed)[data-end-focus="lo"]:is([data-k-fv],.knobs-active) .knobs-range-text{--kr-show-lo:1}
1010
1423
  .knobs-range:not(.knobs-mixed)[data-end-focus="hi"]:is([data-k-fv],.knobs-active) .knobs-range-text{--kr-show-hi:1}
1011
1424
  .knobs-range-thumb::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:-50% 0;border-radius:var(--k-round);background:var(--k-handle);opacity:0;scale:.25 1;transition:opacity .15s,scale .22s var(--k-ease)}
1012
- .knobs-range-thumb[data-end="lo"]::before{translate:0 0}
1013
- .knobs-range-thumb[data-end="hi"]::before{translate:-100% 0}
1014
1425
  .knobs-range:hover .knobs-range-thumb::before{opacity:.5}
1015
1426
  .knobs-range-thumb:focus-visible::before,.knobs-range.knobs-active .knobs-range-thumb:focus::before{opacity:.95;scale:1}
1016
1427
  .knobs-range .knobs-label{max-width:calc(55% - var(--k-inset))}
@@ -1036,27 +1447,37 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
1036
1447
  .knobs-seed-read{display:inline-flex;align-items:center}
1037
1448
  .knobs-seed-read[data-mixed]::before{content:"—"}
1038
1449
  .knobs-seed-read[data-mixed]>.knobs-seed-digits{display:none}
1039
- .knobs-panel .knobs-seed-roll{flex:none;width:var(--k-icon-btn);height:var(--k-icon-btn);padding:0;display:inline-flex;align-items:center;justify-content:center;background:transparent;border-radius:var(--k-round);color:var(--k-dim);cursor:pointer;transition:background .15s,color .15s}
1450
+ .knobs-panel .knobs-seed-roll{flex:none;width:var(--k-icon-btn);height:var(--k-icon-btn);padding:0;display:inline-flex;align-items:center;justify-content:center;background:transparent;border-radius:var(--k-round);color:var(--k-dim);cursor:pointer;transition:background var(--k-hover-t),color var(--k-hover-t)}
1040
1451
  .knobs-panel .knobs-seed-roll:hover{background:var(--k-surface-hi);color:var(--k-text)}
1041
1452
  .knobs-seed-roll .knobs-die-icon{width:var(--k-icon-sm);height:var(--k-icon-sm)}
1042
1453
 
1043
- .knobs-panel .knobs-keys{flex:none;display:inline-flex;align-items:center;max-width:62%;height:var(--kc-control-h);padding:0 8px;background:var(--k-seg-bg);border-radius:var(--k-radius-md);color:var(--k-text);font-size:var(--kc-value-size);letter-spacing:.08em;white-space:nowrap;cursor:pointer;transition:background .15s,color .15s,box-shadow .15s}
1454
+ .knobs-panel .knobs-keys{flex:none;display:inline-flex;align-items:center;justify-content:center;max-width:62%;height:var(--kc-control-h);padding:0 8px;overflow:hidden;background:var(--k-seg-bg);border-radius:var(--k-radius-md);color:var(--k-text);font-family:inherit;font-weight:inherit;font-size:var(--kc-value-size);letter-spacing:.08em;white-space:nowrap;cursor:pointer;transition:background var(--k-hover-t),color var(--k-hover-t)}
1044
1455
  .knobs-panel .knobs-keys:is(:hover,:focus-visible){background:color-mix(in srgb,var(--k-surface-hi) 70%,var(--k-seg-bg))}
1045
1456
  .knobs-panel .knobs-keys[data-empty]{color:var(--k-dim);letter-spacing:0}
1046
- .knobs-panel .knobs-keys[aria-pressed="true"]{background:var(--k-pill-bg);box-shadow:inset 0 0 0 1px var(--k-pill-border);color:var(--k-label);letter-spacing:.08em}
1457
+ /* Listening: a soft accent wash, no ring, and the text breathing in "Type keys…" (keyCap). */
1458
+ .knobs-panel .knobs-keys[aria-pressed="true"]{background:var(--k-pill-bg);color:var(--k-label)}
1459
+ .knobs-keys[aria-pressed="true"] .knobs-keys-text{animation:knobs-breathe 1.6s ease-in-out infinite}
1460
+ @keyframes knobs-breathe{50%{opacity:.55}}
1047
1461
  .knobs-keys-text{min-width:0;overflow:hidden;text-overflow:ellipsis}
1462
+ .knobs-keys[data-morph] .knobs-keys-text{flex:none;overflow:visible}
1463
+ .knobs-panel .knobs-keys[data-static]{cursor:default}
1464
+ .knobs-panel .knobs-keys[hidden]{display:none}
1048
1465
 
1049
- /* Toolbars: icon, label and shortcut per button; they wrap. Buttons take the button tokens. */
1050
- .knobs-panel .knobs-row.knobs-toolbar{flex-wrap:wrap;justify-content:flex-start;gap:var(--k-tray-pad);height:auto;min-height:var(--k-row);padding:calc((var(--k-row) - var(--k-button-h,var(--kc-control-h))) / 2) var(--k-inset)}
1466
+ /* Toolbars: icon, label and shortcut per button, in equal columns balanced across their rows
1467
+ (inspector.ts sets --k-tool-cols); one gap between them and around them. Buttons take the
1468
+ button tokens. */
1469
+ .knobs-panel .knobs-row.knobs-toolbar{--k-tool-gap:max(4px,calc((var(--k-row) - var(--k-button-h,var(--kc-control-h))) / 2));display:grid;grid-template-columns:repeat(var(--k-tool-cols,1),minmax(0,1fr));gap:var(--k-tool-gap);height:auto;min-height:var(--k-row);padding:var(--k-tool-gap) var(--k-inset)}
1051
1470
  .knobs-card>.knobs-toolbar:hover{background:transparent}
1052
1471
  .knobs-card>.knobs-toolbar:has(:focus-visible)::after{opacity:0}
1053
- .knobs-panel .knobs-tool{display:inline-flex;align-items:center;gap:6px;height:var(--k-button-h,var(--kc-control-h));padding:0 8px;background:var(--k-button-bg,var(--k-seg-bg));border-radius:var(--k-button-radius,var(--k-radius-md));box-shadow:inset 0 0 0 1px var(--k-button-line,transparent);
1472
+ .knobs-panel .knobs-tool{display:flex;align-items:center;gap:6px;min-width:0;height:var(--k-button-h,var(--kc-control-h));padding:0 8px;background:var(--k-button-bg,var(--k-seg-bg));border-radius:var(--k-button-radius,var(--k-radius-md));box-shadow:inset 0 0 0 1px var(--k-button-line,transparent);
1054
1473
  color:var(--k-button-color,var(--k-label));font-size:var(--k-button-font-size,var(--k-text-control));font-weight:var(--k-label-weight);white-space:nowrap;cursor:pointer;transition:background var(--k-hover-t),color .15s,box-shadow .15s,scale .12s var(--k-ease),opacity .15s}
1055
1474
  .knobs-panel .knobs-tool:is(:hover:not([disabled]),:focus-visible){background:var(--k-button-hover-bg,var(--k-surface-hi));box-shadow:inset 0 0 0 1px var(--k-button-hover-line,var(--k-button-line,transparent));color:var(--k-text)}
1056
1475
  .knobs-panel .knobs-tool:is(:active:not([disabled]),.knobs-pressed){scale:.96}
1057
1476
  .knobs-panel .knobs-tool[disabled]{opacity:var(--k-disabled-opacity);cursor:default}
1058
- .knobs-tool .knobs-icon{width:var(--k-icon-sm);height:var(--k-icon-sm)}
1059
- .knobs-panel .knobs-tool-key{font:inherit;font-size:var(--k-text-cap);color:var(--k-dim);letter-spacing:.06em}
1477
+ .knobs-tool .knobs-icon{flex:none;width:var(--k-icon-sm);height:var(--k-icon-sm)}
1478
+ .knobs-tool-label{min-width:0;overflow:hidden;text-overflow:ellipsis}
1479
+ /* The shortcut sits at the button's end, so a column of buttons lines their chips up. */
1480
+ .knobs-panel .knobs-tool-key{flex:none;margin-left:auto;padding-left:4px;font:inherit;font-size:var(--k-text-cap);font-variant-numeric:tabular-nums;color:var(--k-dim);letter-spacing:.06em}
1060
1481
  .knobs-panel :is([data-disabled],[data-readonly]) :is(.knobs-cell,.knobs-range-track,.knobs-angle,.knobs-dial,.knobs-seed,.knobs-keys,.knobs-tool){cursor:default}
1061
1482
 
1062
1483
  /* Row accessories (rowAccessory): the host's node, just after the label. */
@@ -1066,6 +1487,7 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
1066
1487
  .knobs-track>.knobs-accessory{position:absolute;top:0;bottom:0;left:calc(var(--k-label-right,0px) + 4px);z-index:1}
1067
1488
  .knobs-x-head>.knobs-accessory{margin:0 auto 0 -4px}
1068
1489
  .knobs-action-row>.knobs-accessory{position:absolute;top:0;bottom:0;right:var(--k-inset)}
1490
+ .knobs-action-row[data-keyed]>.knobs-accessory{position:static}
1069
1491
 
1070
1492
  /* Foldable groups (folded / groupFolded): the header toggles its card. */
1071
1493
  .knobs-panel .knobs-group-head[data-foldable]{cursor:pointer;user-select:none;transition:background var(--k-hover-t)}
@@ -1090,23 +1512,33 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
1090
1512
  /* Hint mode: stable short codes, zero layout shift */
1091
1513
  .knobs-hinting .knobs-hint-miss{opacity:var(--k-disabled-opacity);transition:opacity .1s}
1092
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)}
1093
- .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)));
1094
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}
1095
1519
  .knobs-hint-typed{opacity:var(--k-hint-typed-opacity)}
1096
1520
  .knobs-hint-rest{opacity:1}
1097
1521
 
1522
+ /* A focused row's options: tiny digits in hint mode's look, once focus settles (optkeys.ts). */
1523
+ .knobs-optkeys{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;z-index:5}
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)));
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)}
1526
+ .knobs-panel .knobs-optkey-range{transform:translate(-100%,-50%)}
1527
+ .knobs-panel .knobs-optkey[data-shown]{opacity:.85;transition-duration:.15s}
1528
+
1098
1529
  /* Custom actions */
1099
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)}
1100
1531
  .knobs-panel .knobs-custom-actions button{height:var(--k-button-h,var(--k-action-h));min-height:24px;padding:0 8px;font-size:var(--k-button-font-size,var(--k-text-control));font-weight:var(--k-label-weight);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--k-button-color,var(--k-label));cursor:pointer;
1101
- background:var(--k-button-bg,var(--k-surface));border-radius:var(--k-button-radius,var(--k-radius));box-shadow:inset 0 0 0 1px var(--k-button-line,transparent);transition:background .15s,color .15s,opacity .15s,transform .12s,box-shadow .15s}
1532
+ background:var(--k-button-bg,var(--k-surface));border-radius:var(--k-button-radius,var(--k-radius));box-shadow:inset 0 0 0 1px var(--k-button-line,transparent);transition:background var(--k-hover-t),color var(--k-hover-t),opacity .15s,transform .12s,box-shadow .15s}
1102
1533
  .knobs-panel .knobs-custom-actions button:is(:hover:not([disabled]),:focus-visible){background:var(--k-button-hover-bg,var(--k-surface-hi));box-shadow:inset 0 0 0 1px var(--k-button-hover-line,var(--k-button-line,transparent));color:var(--k-text)}
1103
1534
  .knobs-panel .knobs-custom-actions button:active:not([disabled]){transform:scale(.97)}
1104
1535
  .knobs-panel .knobs-tile-body>.knobs-custom-actions button[disabled]{opacity:var(--k-disabled-opacity);cursor:default}
1105
1536
 
1106
1537
  /* Variants Disclosure Drawer */
1107
1538
  .knobs-transfer{display:grid;grid-template-columns:1fr 1fr;gap:var(--k-gap);margin-top:8px;padding-top:8px;border-top:1px solid var(--k-line)}
1108
- .knobs-transfer button{height:var(--k-button-h,24px);padding:0 8px;background:var(--k-button-bg,var(--k-surface));border-radius:var(--k-button-radius,var(--k-radius));box-shadow:inset 0 0 0 1px var(--k-button-line,transparent);font-size:var(--k-button-font-size,var(--k-text-meta));color:var(--k-button-color,var(--k-label));cursor:pointer;transition:background .15s,color .15s,box-shadow .15s}
1539
+ .knobs-transfer button{height:var(--k-button-h,24px);padding:0 8px;background:var(--k-button-bg,var(--k-surface));border-radius:var(--k-button-radius,var(--k-radius));box-shadow:inset 0 0 0 1px var(--k-button-line,transparent);font-size:var(--k-button-font-size,var(--k-text-meta));color:var(--k-button-color,var(--k-label));cursor:pointer;transition:background var(--k-hover-t),color var(--k-hover-t),box-shadow .15s}
1109
1540
  .knobs-transfer button:is(:hover,:focus-visible){background:var(--k-button-hover-bg,var(--k-surface-hi));box-shadow:inset 0 0 0 1px var(--k-button-hover-line,var(--k-button-line,transparent));color:var(--k-text)}
1541
+ .knobs-sound-pref{grid-template-columns:1fr}
1110
1542
 
1111
1543
  /* Context menu: borrows its panel's tokens (see menu.ts) and floats above everything. */
1112
1544
  .knobs-menu{position:fixed;z-index:2147480004;min-width:176px;padding:var(--k-unit);box-sizing:border-box;font:var(--k-font-weight) var(--k-text-control)/1.35 var(--k-font);color:var(--k-text);text-align:left;-webkit-font-smoothing:antialiased;
@@ -1157,11 +1589,14 @@ ${POINTER_ROW} .knobs-slider.knobs-modified:not(:hover,.knobs-active) .knobs-val
1157
1589
  .knobs-panel[data-gpu-active="true"] :is(.knobs-sub,.knobs-x-head,.knobs-x-label)::after,
1158
1590
  .knobs-panel[data-gpu-active="true"] .knobs-card:not(.knobs-live *)>::before,
1159
1591
  .knobs-panel[data-gpu-active="true"] .knobs-card:not(.knobs-live *)>::after,
1592
+ .knobs-panel[data-gpu-active="true"] :is(.knobs-card:not(.knobs-live *),.knobs-tile:not(.knobs-live))::after,
1593
+ .knobs-panel[data-gpu-active="true"] .knobs-optkey,
1160
1594
  .knobs-panel[data-gpu-active="true"] .knobs-row:not(.knobs-live *)>span:first-child::after,
1595
+ .knobs-panel[data-gpu-active="true"] .knobs-preset-chip::after,
1161
1596
  .knobs-panel[data-gpu-active="true"] :is(.knobs-tile:not(.knobs-live)>.knobs-veil,.knobs-tile:not(.knobs-live)>.knobs-mark,.knobs-section-head>.knobs-mark){opacity:0!important}
1162
1597
  /* Parts the canvas doesn't draw (shortcuts, status, editors) sit above it. */
1163
1598
  .knobs-panel[data-gpu-active="true"] :where(.knobs-popover,.knobs-status,textarea,.knobs-apply-btn){position:relative;z-index:2147480003}
1164
- .knobs-panel[data-gpu-active="true"] .knobs-number{z-index:2147480003}
1599
+ .knobs-panel[data-gpu-active="true"] :is(.knobs-number,.knobs-preset-name){z-index:2147480003}
1165
1600
  /* A row accessory is the host's own DOM, so it sits above the canvas too (its row then can't
1166
1601
  contain its layout, which would trap it in a stacking context under the canvas). */
1167
1602
  .knobs-panel[data-gpu-active="true"] .knobs-row[data-accessory]{contain:style}
@@ -1319,7 +1754,7 @@ function injectPreset(doc, preset) {
1319
1754
  let style = doc.createElement("style");
1320
1755
  style.dataset.knobsPreset = preset.name, style.textContent = content, doc.head.append(style);
1321
1756
  }
1322
- var keyboardQuietsHover = (css) => css.replaceAll(":hover", ":hover:where(:not([data-k-quiet]))"), withoutHas = (css) => css.replaceAll(".knobs-x:has(>.knobs-x-head:is(:hover,:focus-visible))", '.knobs-x:is([data-k-head-hover]:where(:not([data-k-quiet])),[data-k-fv="head"])').replaceAll(":has(:focus-visible)", "[data-k-fv]").replaceAll(":has(.knobs-track:focus-visible)", '[data-k-fv="track"]').replaceAll(":has(.knobs-active)", "[data-k-active]");
1757
+ var keyboardQuietsHover = (css) => css.replaceAll(":hover", ":hover:where(:not([data-k-quiet]))"), withoutHas = (css) => css.replaceAll(":has(:focus-visible)", "[data-k-fv]").replaceAll(":has(.knobs-track:focus-visible)", '[data-k-fv="track"]').replaceAll(":has(.knobs-active)", "[data-k-active]");
1323
1758
  function injectStyles(doc) {
1324
1759
  let content = rolling_css_default + DRIVERS + defaultsCss + keyboardQuietsHover(withoutHas(aliased(css))), existing = doc.querySelector("style[data-knobs-style]");
1325
1760
  if (existing) {
@@ -1331,4 +1766,1054 @@ function injectStyles(doc) {
1331
1766
  style.dataset.knobsStyle = "", style.textContent = content, doc.head.append(style);
1332
1767
  }
1333
1768
 
1334
- export { CHECK_POINTS, CHECK_PATH, iconPath, iconSvg, readSession, writeSession, presetCss, FILL, LEAD, trailScale, Edges, POP_MS, popScale, VelocityTracker, clamp, Roll, rollTarget, numeral, entranceOffset, 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 };