@kitlangton/knobs 0.0.0-stage → 0.1.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1270 -2
  3. package/dist/dev/chunk-e13znr7c.js +155 -0
  4. package/dist/dev/chunk-kdewv0mt.js +1334 -0
  5. package/dist/dev/chunk-ktsn42p7.js +15668 -0
  6. package/dist/dev/chunk-qtt698hp.js +1118 -0
  7. package/dist/dev/index.js +1389 -0
  8. package/dist/index.js +2367 -0
  9. package/dist/panel.js +23635 -0
  10. package/dist/react.js +326 -0
  11. package/dist/solid.js +336 -0
  12. package/dist/themes.js +235 -0
  13. package/dist/types/core/adapter.d.ts +21 -0
  14. package/dist/types/core/code.d.ts +3 -0
  15. package/dist/types/core/color.d.ts +27 -0
  16. package/dist/types/core/easing.d.ts +21 -0
  17. package/dist/types/core/internal.d.ts +18 -0
  18. package/dist/types/core/keys.d.ts +21 -0
  19. package/dist/types/core/mutate.d.ts +21 -0
  20. package/dist/types/core/ramp.d.ts +150 -0
  21. package/dist/types/core/random.d.ts +2 -0
  22. package/dist/types/core/readout.d.ts +73 -0
  23. package/dist/types/core/remote.d.ts +258 -0
  24. package/dist/types/core/schema.d.ts +526 -0
  25. package/dist/types/core/spring.d.ts +38 -0
  26. package/dist/types/core/storage.d.ts +8 -0
  27. package/dist/types/core/store.d.ts +474 -0
  28. package/dist/types/core/theme.d.ts +27 -0
  29. package/dist/types/core/variants.d.ts +17 -0
  30. package/dist/types/index.d.ts +13 -0
  31. package/dist/types/panel/attach.d.ts +5 -0
  32. package/dist/types/panel/choice.d.ts +56 -0
  33. package/dist/types/panel/die.d.ts +121 -0
  34. package/dist/types/panel/editors.d.ts +159 -0
  35. package/dist/types/panel/folds.d.ts +14 -0
  36. package/dist/types/panel/give.d.ts +73 -0
  37. package/dist/types/panel/gpu/atlas.d.ts +40 -0
  38. package/dist/types/panel/gpu/color.d.ts +128 -0
  39. package/dist/types/panel/gpu/die.wgsl.d.ts +1 -0
  40. package/dist/types/panel/gpu/renderer.d.ts +712 -0
  41. package/dist/types/panel/gpu/shader.wgsl.d.ts +2 -0
  42. package/dist/types/panel/haptics.d.ts +8 -0
  43. package/dist/types/panel/has.d.ts +14 -0
  44. package/dist/types/panel/hints.d.ts +45 -0
  45. package/dist/types/panel/icons.d.ts +30 -0
  46. package/dist/types/panel/index.d.ts +10 -0
  47. package/dist/types/panel/inspector.d.ts +20 -0
  48. package/dist/types/panel/measure.d.ts +6 -0
  49. package/dist/types/panel/menu.d.ts +38 -0
  50. package/dist/types/panel/morph.d.ts +38 -0
  51. package/dist/types/panel/motion.d.ts +77 -0
  52. package/dist/types/panel/page-hints.d.ts +82 -0
  53. package/dist/types/panel/page.d.ts +423 -0
  54. package/dist/types/panel/panel.d.ts +43 -0
  55. package/dist/types/panel/position.d.ts +5 -0
  56. package/dist/types/panel/prefs.d.ts +28 -0
  57. package/dist/types/panel/presets.d.ts +48 -0
  58. package/dist/types/panel/ramps.d.ts +14 -0
  59. package/dist/types/panel/readout-row.d.ts +21 -0
  60. package/dist/types/panel/reveal.d.ts +15 -0
  61. package/dist/types/panel/rolling-css.d.ts +2 -0
  62. package/dist/types/panel/search.d.ts +36 -0
  63. package/dist/types/panel/seg.d.ts +15 -0
  64. package/dist/types/panel/session.d.ts +21 -0
  65. package/dist/types/panel/slider.d.ts +92 -0
  66. package/dist/types/panel/smear.d.ts +7 -0
  67. package/dist/types/panel/spin.d.ts +13 -0
  68. package/dist/types/panel/styles.d.ts +11 -0
  69. package/dist/types/panel/swipe.d.ts +114 -0
  70. package/dist/types/panel/switcher.d.ts +20 -0
  71. package/dist/types/panel/tabs.d.ts +31 -0
  72. package/dist/types/panel/thumb.d.ts +36 -0
  73. package/dist/types/react/Knobs.d.ts +15 -0
  74. package/dist/types/react/VariantSwitcher.d.ts +10 -0
  75. package/dist/types/react/index.d.ts +3 -0
  76. package/dist/types/react/useControls.d.ts +11 -0
  77. package/dist/types/solid/index.d.ts +50 -0
  78. package/dist/types/themes/index.d.ts +1 -0
  79. package/dist/types/ui/bento-css.d.ts +1 -0
  80. package/dist/types/ui/bento-model.d.ts +96 -0
  81. package/dist/types/ui/bento.d.ts +77 -0
  82. package/dist/types/ui/commands.d.ts +11 -0
  83. package/dist/types/ui/confirm.d.ts +13 -0
  84. package/dist/types/ui/index.d.ts +20 -0
  85. package/dist/types/ui/layer.d.ts +70 -0
  86. package/dist/types/ui/look.d.ts +53 -0
  87. package/dist/types/ui/menu.d.ts +63 -0
  88. package/dist/types/ui/overlay-css.d.ts +1 -0
  89. package/dist/types/ui/overlay-model.d.ts +87 -0
  90. package/dist/types/ui/palette.d.ts +38 -0
  91. package/dist/types/ui/popover.d.ts +35 -0
  92. package/dist/types/ui/react.d.ts +45 -0
  93. package/dist/types/ui/readout-css.d.ts +1 -0
  94. package/dist/types/ui/readout-model.d.ts +137 -0
  95. package/dist/types/ui/readouts.d.ts +85 -0
  96. package/dist/types/ui/rgba.d.ts +6 -0
  97. package/dist/types/ui/solid.d.ts +40 -0
  98. package/dist/types/ui/split-model.d.ts +81 -0
  99. package/dist/types/ui/split.d.ts +75 -0
  100. package/dist/types/ui/timeline-model.d.ts +83 -0
  101. package/dist/types/ui/timeline.d.ts +76 -0
  102. package/dist/types/ui/toast.d.ts +19 -0
  103. package/dist/types/ui/tooltip.d.ts +19 -0
  104. package/dist/types/ui/tree-model.d.ts +125 -0
  105. package/dist/types/ui/tree.d.ts +95 -0
  106. package/dist/ui-react.js +6558 -0
  107. package/dist/ui-solid.js +6511 -0
  108. package/dist/ui.js +9238 -0
  109. package/package.json +99 -4
@@ -0,0 +1,77 @@
1
+ import { type BentoPlan } from "./bento-model.js";
2
+ import { type UiLook } from "./look.js";
3
+ export interface BentoItem {
4
+ readonly id: string;
5
+ /** Width / height. Images without one take their natural size. */
6
+ readonly aspect?: number;
7
+ /** An image. */
8
+ readonly src?: string;
9
+ /** Responsive candidates for the image; the tile's size picks one, and a zoom a larger one. */
10
+ readonly srcset?: string;
11
+ readonly alt?: string;
12
+ /**
13
+ * Your own content (a live canvas, a prototype), drawn into `el` when the tile comes into view;
14
+ * return a cleanup for when it leaves. Zoomed, it takes the pointer.
15
+ */
16
+ readonly render?: (el: HTMLElement) => void | (() => void);
17
+ /** Shown as type on hover. */
18
+ readonly label?: string;
19
+ /** Items in the same group sit together under its name (the set then scrolls). */
20
+ readonly group?: string;
21
+ /** Asks to be the large tile when the set fits one screen. */
22
+ readonly hero?: boolean;
23
+ }
24
+ export interface BentoOptions extends UiLook {
25
+ readonly items: readonly BentoItem[];
26
+ /** Between tiles, px (2). */
27
+ readonly gap?: number;
28
+ /** Around the tiles, px (the gap). */
29
+ readonly padding?: number;
30
+ /** How far a tile may be cropped to fill a row (.08). */
31
+ readonly tolerance?: number;
32
+ /** Scrolling rows' target height, px (about a third of the view). */
33
+ readonly rowHeight?: number;
34
+ /** Whether a few items may lay out on one screen (true). */
35
+ readonly fit?: boolean;
36
+ /** The most a zooming or sliding tile blurs with its speed, px (2.5; 0 for none). */
37
+ readonly blur?: number;
38
+ readonly picks?: Iterable<string>;
39
+ /** Space toggles a pick; the picks in the items' order. */
40
+ readonly onPick?: (ids: readonly string[]) => void;
41
+ readonly onZoom?: (id: string | null) => void;
42
+ readonly onActive?: (id: string | null) => void;
43
+ /** The grid's accessible name. */
44
+ readonly label?: string;
45
+ }
46
+ export interface Bento {
47
+ readonly element: HTMLElement;
48
+ /** New items: tiles that stay glide to their places, new ones come in, gone ones soften out. */
49
+ update(items: readonly BentoItem[]): void;
50
+ /** Zooms an item to fill the view (`null` zooms back out). */
51
+ zoom(id: string | null): void;
52
+ readonly zoomed: string | null;
53
+ readonly active: string | null;
54
+ focus(id?: string): void;
55
+ readonly picks: readonly string[];
56
+ pick(ids: Iterable<string>): void;
57
+ readonly plan: BentoPlan;
58
+ /** Whether anything is moving (the frame loop runs only then). */
59
+ readonly moving: boolean;
60
+ /** Frames stepped, and the script time each took (ms), for perf checks. */
61
+ stats(): {
62
+ frames: number;
63
+ p50: number;
64
+ p95: number;
65
+ max: number;
66
+ };
67
+ dispose(): void;
68
+ }
69
+ /**
70
+ * An ideas grid: a bento of images and live content, laid out to show as much as possible, that
71
+ * zooms an item to fill the view. See `planBento` for the layout. Click or Enter zooms; Esc, a
72
+ * click or a pinch out returns to the exact grid place; ←/→ (or a swipe) step through neighbours
73
+ * while zoomed; ⌘-scroll or a pinch zooms by hand. Arrows move a wash through the grid by
74
+ * geometry, Space picks, 1–9 jump. Offscreen tiles aren't in the page, and the frame loop sleeps
75
+ * when nothing moves.
76
+ */
77
+ export declare function bento(container: HTMLElement, options: BentoOptions): Bento;
@@ -0,0 +1,11 @@
1
+ import type { Controls } from "../core/store.js";
2
+ import type { Command } from "./palette.js";
3
+ export interface CommandsFromOptions {
4
+ /** Prefix for sections ("Station" → "Station · Presets"); the store id is never shown. */
5
+ readonly title?: string;
6
+ /** Leave out fields (only actions and presets). */
7
+ readonly fields?: boolean;
8
+ /** A field picked from the palette: show it (scroll a panel to it, focus it). */
9
+ reveal?(key: string): void;
10
+ }
11
+ export declare function commandsFrom(controls: Controls<any>, options?: CommandsFromOptions): Command[];
@@ -0,0 +1,13 @@
1
+ import type { UiLook } from "./look.js";
2
+ export interface ConfirmOptions {
3
+ readonly title: string;
4
+ readonly body?: string;
5
+ /** The confirming button's label ("OK"). */
6
+ readonly confirm?: string;
7
+ readonly cancel?: string;
8
+ /** It deletes or discards: the button reads in the critical colour, and Cancel takes focus. */
9
+ readonly destructive?: boolean;
10
+ readonly look?: UiLook;
11
+ readonly doc?: Document;
12
+ }
13
+ export declare function confirm(options: ConfirmOptions): Promise<boolean>;
@@ -0,0 +1,20 @@
1
+ export { injectUiStyles, restyle, type RestyleLook, type UiLook, type UiTheme } from "./look.js";
2
+ export { commandsFrom, type CommandsFromOptions } from "./commands.js";
3
+ export { confirm, type ConfirmOptions } from "./confirm.js";
4
+ export { overlayLook, type Anchor } from "./layer.js";
5
+ export { assignPageCodes, hints, pageHints, type HintPainter, type HintSourceOptions, type PageHints, type PageHintTarget, } from "../panel/page-hints.js";
6
+ export { fuzzy, inTriangle, marks, matchItem, place, safeTriangle, stepItem, rank, type Box, type FuzzyMatch, type Placed, type Placement, type Ranked, type Side, } from "./overlay-model.js";
7
+ export { commandPalette, type Command, type CommandPalette, type CommandPaletteOptions, } from "./palette.js";
8
+ export { contextMenu, menu, type ContextMenu, type ContextMenuOptions, type Menu, type MenuCloseReason, type MenuEntry, type MenuItem, type MenuOptions, } from "./menu.js";
9
+ export { popover, type Popover, type PopoverCloseReason, type PopoverOptions } from "./popover.js";
10
+ export { readout, type Readout } from "./readouts.js";
11
+ export { toast, type Toast, type ToastOptions } from "./toast.js";
12
+ export { tooltip, tooltips, type TooltipContent, type TooltipOptions } from "./tooltip.js";
13
+ export { split, type PaneAction, type PaneOptions, type Split, type SplitOptions, type SplitPane, } from "./split.js";
14
+ export { COLLAPSE_PAST, drag as dragDivider, parseLayout, softClamp, solve as solveSplit, type PaneSpec, type SplitDirection, type SplitLayout, } from "./split-model.js";
15
+ export { timeline, type SeekSource, type Timeline, type TimelineMarker, type TimelineOptions, type TimelineRange, } from "./timeline.js";
16
+ export { adjacentMarker, advance, clampView, followView, frameTime, magnet, rulerTicks, scrubTime, timeLabel, toFrame, zoomView, type Tick, type Ticks, type TimeUnits, type TimeView, } from "./timeline-model.js";
17
+ export { tree, type Tree, type TreeOptions, type TreeRowContext, type TreeToggle } from "./tree.js";
18
+ export { applyMove, plainAccess, dropTarget, flatten, indexTree, matchLabel, navigate, select, stepMove, topmost, typeahead, validMove, virtualWindow, type DropTarget, type FlatRow, type Selection, type TreeAccess, type TreeFilter, type TreeMove, } from "./tree-model.js";
19
+ export { bento, type Bento, type BentoItem, type BentoOptions } from "./bento.js";
20
+ export { fitRect, FIT_MAX, motionBlur, neighbour as bentoNeighbour, planBento, rowTarget, windowOf as bentoWindow, type BentoDirection, type BentoLayout, type BentoMode, type BentoPlan, type BentoRect, } from "./bento-model.js";
@@ -0,0 +1,70 @@
1
+ import { type UiLook } from "./look.js";
2
+ /** The look every overlay takes unless given its own (a preset and theme, as panels take). */
3
+ export declare function overlayLook(look: UiLook): void;
4
+ export declare const lookOf: (look?: UiLook) => UiLook;
5
+ /** The document's overlay layer: fixed, above everything, passing the pointer through. */
6
+ export declare function layerRoot(doc: Document): HTMLElement;
7
+ /** A new overlay element in the layer, with the pieces' look. */
8
+ export declare function layerElement(doc: Document, cls: string, look?: UiLook): HTMLDivElement;
9
+ export interface PresenceOptions {
10
+ /** Scale it grows from (0.94). */
11
+ readonly scale?: number;
12
+ /** Blur (px) it sharpens from (6). */
13
+ readonly blur?: number;
14
+ /** Where it slides from, px (toward its anchor). */
15
+ readonly dx?: number;
16
+ readonly dy?: number;
17
+ /** Perceptual duration in, s (0.32). */
18
+ readonly duration?: number;
19
+ /** Bounce in (0.2); exits never bounce. */
20
+ readonly bounce?: number;
21
+ /** Perceptual duration out, s (0.2): exits run shorter than entrances. */
22
+ readonly exit?: number;
23
+ /** Called once it has gone (faded all the way out). */
24
+ onGone?(): void;
25
+ /** Each frame, with progress (0 → 1, may overshoot a little). */
26
+ onFrame?(p: number): void;
27
+ }
28
+ export interface Presence {
29
+ show(): void;
30
+ hide(): Promise<void>;
31
+ /** Jumps to shown (a hand-off between neighbours) or gone, with no motion. */
32
+ snap(to?: 0 | 1): void;
33
+ readonly shown: boolean;
34
+ /** Changes where it slides from (after a flip). */
35
+ from(dx: number, dy: number): void;
36
+ dispose(): void;
37
+ }
38
+ /** Brings `el` in and out on a spring: opacity, scale and blur from its transform-origin. */
39
+ export declare function presence(el: HTMLElement, o?: PresenceOptions): Presence;
40
+ export interface Dismissible {
41
+ readonly el: HTMLElement;
42
+ /** Pointer-downs inside these don't count as outside (the anchor, say). */
43
+ readonly inside?: () => readonly (Element | null | undefined)[];
44
+ /** Esc and outside clicks reach the layers under a modal one only through it. */
45
+ readonly modal?: boolean;
46
+ readonly outside?: boolean;
47
+ dismiss(reason: "escape" | "outside"): void;
48
+ }
49
+ export declare function pushLayer(doc: Document, layer: Dismissible): () => void;
50
+ export declare const focusables: (root: HTMLElement) => HTMLElement[];
51
+ /** Keeps Tab inside `root`; returns the remover. */
52
+ export declare function trapFocus(root: HTMLElement): () => void;
53
+ /** Where focus was, to return it there when the layer closes (if focus is still in the layer). */
54
+ export declare function focusReturn(doc: Document, layer: HTMLElement): () => void;
55
+ export type Anchor = HTMLElement | DOMRect | {
56
+ readonly x: number;
57
+ readonly y: number;
58
+ };
59
+ export declare const anchorBox: (a: Anchor) => {
60
+ x: number;
61
+ y: number;
62
+ w: number;
63
+ h: number;
64
+ };
65
+ export declare const viewBox: (doc: Document) => {
66
+ x: number;
67
+ y: number;
68
+ w: number;
69
+ h: number;
70
+ };
@@ -0,0 +1,53 @@
1
+ import { type TokenPreset } from "../core/theme.js";
2
+ export type UiTheme = "dark" | "light" | "auto";
3
+ /** What every piece takes to match the panels beside it. */
4
+ export interface UiLook {
5
+ /** A token preset (`labPreset`): the same tokens a panel takes. */
6
+ readonly preset?: TokenPreset;
7
+ readonly theme?: UiTheme;
8
+ }
9
+ /** Adds the pieces' stylesheet once per document. */
10
+ export declare function injectUiStyles(doc: Document): void;
11
+ /** Styles, preset and theme for a piece's root. */
12
+ export declare function applyLook(root: HTMLElement, look: UiLook): void;
13
+ /**
14
+ * `--k-frame: flat` (a preset's, or a host's on any ancestor) marks the piece `data-frame`. Read
15
+ * again once the piece is in the page.
16
+ */
17
+ export declare function syncFrame(root: HTMLElement): void;
18
+ /** A look for `restyle`: what the pieces take, and a panel's density. */
19
+ export interface RestyleLook extends UiLook {
20
+ readonly density?: "compact" | "default" | "comfortable";
21
+ }
22
+ /**
23
+ * Changes the look of what's already on screen: every knobs/ui piece and panel inside `target`
24
+ * (the whole document by default) takes `look`'s preset (none for knobs' own), theme and, for
25
+ * panels, density. Panels' tokens glide to the new values; splits and trees measure again. On a
26
+ * document, overlays opened afterwards take it too (`overlayLook`).
27
+ */
28
+ export declare function restyle(target: Document | Element, look: RestyleLook): void;
29
+ /** The look overlays take unless given their own (`overlayLook`, `restyle(document)`). */
30
+ export declare const lookDefaults: {
31
+ current: UiLook;
32
+ };
33
+ export declare const reducedMotion: (win: Window | null) => boolean;
34
+ /**
35
+ * Tells the page's GPU overlay that layout moved (a pane resizing, collapsing or sliding): it
36
+ * reads rects afresh on its next frame, so a panel inside a pane keeps up.
37
+ */
38
+ export declare function notifyLayout(doc: Document): void;
39
+ /** One rAF loop: `step(dt)` runs each frame while it returns true. */
40
+ export declare function frameLoop(win: Window, step: (dt: number) => boolean): {
41
+ wake(): void;
42
+ readonly running: boolean;
43
+ stop(): void;
44
+ };
45
+ export declare const svg: (body: string, box?: number) => string;
46
+ export declare const ICONS: {
47
+ caret: string;
48
+ chevronDown: string;
49
+ eye: string;
50
+ eyeOff: string;
51
+ lock: string;
52
+ unlock: string;
53
+ };
@@ -0,0 +1,63 @@
1
+ import type { UiLook } from "./look.js";
2
+ import { type Anchor } from "./layer.js";
3
+ import { type Placement } from "./overlay-model.js";
4
+ export interface MenuItem {
5
+ readonly id?: string;
6
+ readonly label: string;
7
+ /** A glyph, or an `<svg>` string. */
8
+ readonly icon?: string;
9
+ /** A key combination shown as chips ("Mod+D", "Backspace"); display only, it binds nothing. */
10
+ readonly shortcut?: string;
11
+ /** Dim text at the end, before any shortcut. */
12
+ readonly hint?: string;
13
+ readonly disabled?: boolean;
14
+ /** A check mark (`menuitemcheckbox`), or with `radio` a dot (`menuitemradio`). */
15
+ readonly checked?: boolean;
16
+ readonly radio?: boolean;
17
+ /** Drawn in `--k-critical`, for what can't be taken back. */
18
+ readonly destructive?: boolean;
19
+ /** A submenu (or a function read when it opens). */
20
+ readonly items?: readonly MenuEntry[] | (() => readonly MenuEntry[]);
21
+ run?(): unknown;
22
+ }
23
+ /** An item, `"-"` for a separator, or `null` / `false` for nothing (a conditional item). */
24
+ export type MenuEntry = MenuItem | "-" | null | false | undefined;
25
+ export type MenuCloseReason = "select" | "escape" | "outside" | "close";
26
+ export interface MenuOptions {
27
+ /** Where it opens beside its anchor (`bottom-start`; a point opens at its corner). */
28
+ readonly placement?: Placement;
29
+ /** Px between the anchor and the menu (4; 0 at a point). */
30
+ readonly offset?: number;
31
+ readonly label?: string;
32
+ readonly look?: UiLook;
33
+ /** The first item starts active (opened by a key rather than the pointer). */
34
+ readonly keyboard?: boolean;
35
+ onClose?(reason: MenuCloseReason): void;
36
+ /** Each item chosen, beside its own `run`. */
37
+ onSelect?(item: MenuItem): void;
38
+ }
39
+ export interface Menu {
40
+ readonly element: HTMLElement;
41
+ readonly open: boolean;
42
+ /** Closes it and any submenu (animated); resolves once they've gone. */
43
+ close(): Promise<void>;
44
+ }
45
+ /** A menu beside `anchor` (a button, a rect or a point). */
46
+ export declare function menu(anchor: Anchor, items: readonly MenuEntry[], options?: MenuOptions): Menu;
47
+ export interface ContextMenuOptions extends Omit<MenuOptions, "placement" | "offset" | "keyboard"> {
48
+ }
49
+ export interface ContextMenu {
50
+ /** Opens it at a point (or the focused element), as a right-click or the menu key would. */
51
+ open(at?: {
52
+ readonly x: number;
53
+ readonly y: number;
54
+ }): Menu | null;
55
+ readonly current: Menu | null;
56
+ dispose(): void;
57
+ }
58
+ /**
59
+ * A context menu for `target`: a right-click (or a two-finger click) opens it at the pointer, and
60
+ * the menu key or ⇧F10 at the focused element. `items` may be a function of the event, read each
61
+ * time (return `null` for no menu there, and the browser's own shows).
62
+ */
63
+ export declare function contextMenu(target: HTMLElement, items: readonly MenuEntry[] | ((event: MouseEvent | KeyboardEvent | null) => readonly MenuEntry[] | null | undefined), options?: ContextMenuOptions): ContextMenu;
@@ -0,0 +1 @@
1
+ export declare const overlayCss = "\n.knobs-layers{position:fixed;inset:0;z-index:2147480005;pointer-events:none}\n.knobs-layers>.knobs-layer,.knobs-ui.knobs-toast{position:fixed;left:0;top:0;box-sizing:border-box;pointer-events:auto;outline:none;\n font:var(--ku-font-weight) var(--ku-font-size)/1.4 var(--ku-font);color:var(--ku-text);letter-spacing:normal;text-align:left;text-transform:none;\n -webkit-font-smoothing:antialiased;will-change:transform,opacity,filter}\n/* The overlays' own elements stop a host's element rules; what you put in a popover is yours. */\n:where(.knobs-layers,.knobs-toast) :is(.knobs-kbd,.knobs-mitem,.knobs-menu-sep,.knobs-palette-input,.knobs-palette-label>mark,.knobs-confirm-title,.knobs-confirm-body,.knobs-confirm-btn,.knobs-toast-action){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;\n background:transparent;border:0;margin:0;padding:0;outline:none;box-shadow:none}\n.knobs-layers .knobs-layer *{box-sizing:border-box}\n/* A floating surface: frosted, a hairline rim, a soft shadow. */\n.knobs-layers>:is(.knobs-popover-layer,.knobs-palette,.knobs-confirm,.knobs-menu-layer){background:color-mix(in srgb,var(--ku-bg) 88%,transparent);\n -webkit-backdrop-filter:blur(28px) saturate(1.7);backdrop-filter:blur(28px) saturate(1.7);border-radius:var(--ku-panel-radius);\n box-shadow:0 0 0 .5px var(--ku-line-hi),inset 0 1px 0 var(--ku-rim),0 12px 40px rgba(0,0,0,.36),0 2px 8px rgba(0,0,0,.18)}\n.knobs-layers>.knobs-popover-layer{min-width:120px;max-width:min(420px,calc(100vw - 16px));padding:6px}\n.knobs-popover-layer .knobs-popover-text{padding:4px 6px;color:var(--ku-label)}\n\n/* Menus: rows of the panel's control height, a soft wash on the active one (never an outline),\n concentric corners inside the surface. */\n.knobs-layers>.knobs-menu-layer{display:flex;flex-direction:column;min-width:184px;max-width:min(340px,calc(100vw - 16px));max-height:calc(100vh - 16px);overflow:auto;padding:5px;\n scrollbar-width:thin;overscroll-behavior:contain;user-select:none;-webkit-user-select:none}\n.knobs-menu-layer .knobs-mitem{position:relative;display:flex;align-items:center;gap:8px;flex:none;height:max(22px,calc(var(--ku-row) - 6px));padding:0 8px;\n border-radius:max(0px,calc(var(--ku-panel-radius) - 5px));color:var(--ku-text);cursor:default;white-space:nowrap}\n.knobs-menu-layer .knobs-mitem[data-active],.knobs-menu-layer .knobs-mitem[data-open]{background:var(--ku-wash-hi)}\n.knobs-menu-layer .knobs-mitem[data-destructive]{color:var(--ku-critical)}\n.knobs-menu-layer .knobs-mitem[data-destructive][data-active]{background:color-mix(in srgb,var(--ku-critical) 16%,transparent)}\n.knobs-menu-layer .knobs-mitem[aria-disabled=\"true\"]{opacity:.38}\n.knobs-menu-layer .knobs-menu-lead{flex:none;display:flex;align-items:center;justify-content:center;width:14px;margin-left:-2px;color:var(--ku-label);font-size:calc(var(--ku-font-size) - 1px)}\n.knobs-menu-layer .knobs-mitem[data-destructive] .knobs-menu-lead,.knobs-menu-layer .knobs-mitem[aria-checked=\"true\"] .knobs-menu-lead{color:inherit}\n.knobs-menu-layer .knobs-menu-lead svg{width:13px;height:13px}\n.knobs-menu-layer .knobs-menu-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}\n.knobs-menu-layer .knobs-menu-hint{flex:none;color:var(--ku-dim)}\n.knobs-menu-layer .knobs-menu-keys{display:inline-flex;gap:2px;flex:none;margin-left:12px}\n.knobs-menu-layer .knobs-mitem[data-active] .knobs-kbd{background:color-mix(in srgb,var(--ku-text) 14%,transparent);color:var(--ku-text)}\n.knobs-menu-layer .knobs-menu-caret{flex:none;display:flex;margin-left:8px;margin-right:-3px;color:var(--ku-dim)}\n.knobs-menu-layer .knobs-menu-caret svg{width:10px;height:10px}\n.knobs-menu-layer .knobs-menu-sep{flex:none;height:1px;margin:4px 8px;background:var(--ku-line)}\n\n.knobs-layers>.knobs-tooltip{display:inline-flex;align-items:center;gap:7px;max-width:300px;padding:4px 8px;pointer-events:none;white-space:nowrap;\n font-size:calc(var(--ku-font-size) - .5px);background:var(--ku-bg);color:var(--ku-text);border-radius:min(var(--ku-radius),6px);\n box-shadow:0 0 0 .5px var(--ku-line-hi),0 4px 14px rgba(0,0,0,.3)}\n.knobs-tooltip .knobs-tooltip-text{overflow:hidden;text-overflow:ellipsis}\n.knobs-layer :is(.knobs-tooltip-keys,.knobs-palette-keys){display:inline-flex;gap:2px;flex:none}\n.knobs-layer .knobs-tooltip-keys[hidden]{display:none}\n.knobs-layer .knobs-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:17px;height:17px;padding:0 4px;\n border-radius:min(var(--ku-radius),4px);background:var(--ku-surface-hi);color:var(--ku-label);font:500 calc(var(--ku-font-size) - 1.5px)/1 var(--ku-font)}\n\n.knobs-layers>.knobs-palette{left:50%;top:max(48px,15vh);translate:-50% 0;transform-origin:50% 0;display:flex;flex-direction:column;\n width:min(580px,calc(100vw - 32px));max-height:min(460px,72vh);overflow:hidden}\n.knobs-palette .knobs-palette-field{display:flex;align-items:center;gap:10px;flex:none;height:46px;padding:0 16px;border-bottom:1px solid var(--ku-line);color:var(--ku-dim)}\n.knobs-palette .knobs-palette-field svg{width:15px;height:15px;flex:none}\n.knobs-palette .knobs-palette-input{flex:1;min-width:0;height:100%;font-size:calc(var(--ku-font-size) + 3px);color:var(--ku-text);caret-color:var(--ku-accent)}\n.knobs-palette .knobs-palette-input::placeholder{color:var(--ku-dim)}\n.knobs-palette .knobs-palette-list{position:relative;flex:1;min-height:0;overflow:auto;padding:6px;scrollbar-width:thin;overscroll-behavior:contain}\n.knobs-palette .knobs-palette-hl{position:absolute;left:6px;right:6px;top:0;border-radius:max(0px,calc(var(--ku-panel-radius) - 6px));background:var(--ku-wash-hi);opacity:0;pointer-events:none;\n transition:transform var(--ku-ease-ms) var(--ku-ease),height var(--ku-ease-ms) var(--ku-ease),opacity .12s}\n.knobs-palette .knobs-palette-section{padding:10px 10px 4px;color:var(--ku-dim);font-size:calc(var(--ku-font-size) - 1px);font-weight:var(--ku-label-weight);user-select:none}\n.knobs-palette .knobs-palette-section:first-of-type{padding-top:4px}\n.knobs-palette .knobs-palette-item{position:relative;display:flex;align-items:center;gap:8px;height:max(32px,var(--ku-row));padding:0 10px;\n color:var(--ku-label);cursor:default;user-select:none}\n.knobs-palette .knobs-palette-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ku-text)}\n/* Searching, the label dims and its matched letters light up. */\n.knobs-palette[data-query] .knobs-palette-label{color:var(--ku-label)}\n.knobs-palette .knobs-palette-label mark{color:var(--ku-accent);font-weight:var(--ku-title-weight)}\n.knobs-palette[data-query] .knobs-palette-item[data-active] .knobs-palette-label{color:var(--ku-text)}\n.knobs-palette .knobs-palette-hint{flex:none;color:var(--ku-dim)}\n.knobs-palette .knobs-palette-fill{flex:1}\n.knobs-palette .knobs-palette-tag{flex:none;color:var(--ku-dim);font-size:calc(var(--ku-font-size) - 1px);white-space:nowrap}\n.knobs-palette .knobs-palette-item[aria-disabled=\"true\"]{opacity:.4}\n.knobs-palette .knobs-palette-empty{padding:18px 10px;color:var(--ku-dim);text-align:center}\n\n.knobs-layers>.knobs-scrim{inset:0;width:100%;height:100%;background:rgba(0,0,0,.3)}\n.knobs-layers>.knobs-confirm{left:50%;top:42%;translate:-50% -50%;width:min(340px,calc(100vw - 32px));padding:18px 18px 14px}\n.knobs-confirm .knobs-confirm-title{margin:0 0 6px;font-size:calc(var(--ku-font-size) + 1.5px);font-weight:var(--ku-title-weight);color:var(--ku-text);line-height:1.3}\n.knobs-confirm .knobs-confirm-body{margin:0;color:var(--ku-label);line-height:1.45}\n.knobs-confirm .knobs-confirm-buttons{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}\n.knobs-confirm .knobs-confirm-btn{height:28px;padding:0 14px;border-radius:min(var(--ku-radius),7px);background:var(--ku-surface-hi);color:var(--ku-text);\n font-weight:var(--ku-label-weight);cursor:pointer;transition:background .15s,filter .15s}\n.knobs-confirm .knobs-confirm-cancel:is(:hover,:focus-visible){background:color-mix(in srgb,var(--ku-text) 18%,transparent)}\n.knobs-confirm .knobs-confirm-ok{background:var(--ku-accent);color:var(--ku-bg)}\n.knobs-confirm .knobs-confirm-ok[data-destructive]{background:var(--ku-critical);color:#fff}\n.knobs-confirm .knobs-confirm-ok:is(:hover,:focus-visible){filter:brightness(1.14)}\n\n.knobs-ui.knobs-toast{left:50%;top:auto;bottom:24px;z-index:2147480005;translate:-50% 0;display:inline-flex;align-items:center;gap:10px;\n max-width:min(480px,80vw);padding:8px 16px;background:var(--ku-bg);color:var(--ku-text);border-radius:calc(var(--ku-radius) * 999);\n box-shadow:0 0 0 .5px var(--ku-line-hi),0 8px 28px rgba(0,0,0,.32)}\n.knobs-toast .knobs-toast-text{overflow-wrap:anywhere}\n.knobs-toast[data-tone=\"warn\"]::before,.knobs-toast[data-tone=\"error\"]::before{content:\"\";flex:none;width:7px;height:7px;border-radius:50%;background:var(--ku-warn)}\n.knobs-toast[data-tone=\"error\"]::before{background:var(--ku-critical)}\n.knobs-toast .knobs-toast-action{flex:none;color:var(--ku-accent);font-weight:var(--ku-title-weight);cursor:pointer}\n.knobs-toast .knobs-toast-action[hidden]{display:none}\n";
@@ -0,0 +1,87 @@
1
+ export interface Box {
2
+ readonly x: number;
3
+ readonly y: number;
4
+ readonly w: number;
5
+ readonly h: number;
6
+ }
7
+ export type Side = "top" | "bottom" | "left" | "right";
8
+ export type Placement = Side | `${Side}-start` | `${Side}-end`;
9
+ export interface Placed {
10
+ readonly x: number;
11
+ readonly y: number;
12
+ /** The placement after flipping. */
13
+ readonly placement: Placement;
14
+ /** The anchor's nearest point, in the layer's own px: where it grows from. */
15
+ readonly origin: {
16
+ readonly x: number;
17
+ readonly y: number;
18
+ };
19
+ }
20
+ /**
21
+ * Places a `size` layer on `placement`'s side of `anchor`, `offset` px away: flipped to the
22
+ * other side when it doesn't fit and that side has more room, then shifted along the side to stay
23
+ * `margin` px inside `view`.
24
+ */
25
+ export declare function place(o: {
26
+ anchor: Box;
27
+ size: {
28
+ readonly w: number;
29
+ readonly h: number;
30
+ };
31
+ view: Box;
32
+ placement?: Placement;
33
+ offset?: number;
34
+ margin?: number;
35
+ }): Placed;
36
+ export interface FuzzyMatch {
37
+ readonly score: number;
38
+ /** The matched characters' indices in the text, ascending. */
39
+ readonly indices: readonly number[];
40
+ }
41
+ /**
42
+ * Matches `query`'s characters in order anywhere in `text` (case-insensitive), or null. The best
43
+ * alignment wins: a match at the start, at word starts and in runs scores higher; gaps cost a
44
+ * little. Spaces in the query are ignored.
45
+ */
46
+ export declare function fuzzy(query: string, text: string): FuzzyMatch | null;
47
+ export interface Rankable {
48
+ readonly label: string;
49
+ readonly keywords?: readonly string[];
50
+ readonly section?: string;
51
+ }
52
+ export interface Ranked<T> {
53
+ readonly item: T;
54
+ readonly score: number;
55
+ /** Indices in the label (empty when only a keyword or the section matched). */
56
+ readonly indices: readonly number[];
57
+ }
58
+ /**
59
+ * Items matching `query`, best first; a label match beats a keyword's or the section's. Matches
60
+ * scattered thinly (letters strewn across words) are left out: a keyword or section must match
61
+ * at word starts or in runs, a label at least loosely.
62
+ */
63
+ export declare function rank<T extends Rankable>(items: readonly T[], query: string): Ranked<T>[];
64
+ /** `text` split into runs at `indices`: `[{ text, hit }]`, for highlighting. */
65
+ export declare function marks(text: string, indices: readonly number[]): {
66
+ text: string;
67
+ hit: boolean;
68
+ }[];
69
+ /** The next item from `from` by `by` (±1) that `ok` accepts, wrapping; `from` -1 starts outside. */
70
+ export declare function stepItem(count: number, from: number, by: 1 | -1, ok: (i: number) => boolean): number;
71
+ /**
72
+ * Type-ahead over menu labels: the first item after `from` starting with `typed` (repeating one
73
+ * letter cycles through the items that start with it; more letters refine from where it is).
74
+ */
75
+ export declare function matchItem(labels: readonly (string | null)[], from: number, typed: string): number;
76
+ type Pt = {
77
+ readonly x: number;
78
+ readonly y: number;
79
+ };
80
+ /** Whether `p` is inside the triangle `a b c` (either winding). */
81
+ export declare function inTriangle(p: Pt, a: Pt, b: Pt, c: Pt): boolean;
82
+ /**
83
+ * The safe triangle of a submenu's hover intent: from where the pointer was to the submenu's
84
+ * near edge (a little past its corners). While the pointer travels inside it, the submenu stays.
85
+ */
86
+ export declare function safeTriangle(from: Pt, sub: Box): [Pt, Pt, Pt];
87
+ export {};
@@ -0,0 +1,38 @@
1
+ import type { UiLook } from "./look.js";
2
+ export interface Command {
3
+ /** Stable across sessions (recents remember it); the label by default. */
4
+ readonly id?: string;
5
+ readonly label: string;
6
+ /** The heading it sits under with no query, and its dimmed tag in results. */
7
+ readonly section?: string;
8
+ /** A shortcut shown as key chips (the palette doesn't bind it). */
9
+ readonly shortcut?: string;
10
+ /** Other words it answers to. */
11
+ readonly keywords?: readonly string[];
12
+ /** Text after the label, dimmed (a current value, say). */
13
+ readonly hint?: string;
14
+ readonly disabled?: boolean;
15
+ /** What it does; a string or a promise of one shows as a toast. */
16
+ run?(): unknown;
17
+ }
18
+ export interface CommandPaletteOptions {
19
+ /** The commands, or a function asked each time it opens. */
20
+ readonly commands: readonly Command[] | (() => readonly Command[]);
21
+ /** Called after a command runs (or instead, for commands with no `run`). */
22
+ onRun?(command: Command): void;
23
+ /** The key that toggles it, anywhere on the page (`"Mod+K"`); `false` for none. */
24
+ readonly hotkey?: string | false;
25
+ readonly placeholder?: string;
26
+ /** Recent commands are remembered under this key in localStorage; `false` remembers nothing. */
27
+ readonly recents?: string | false;
28
+ readonly look?: UiLook;
29
+ readonly doc?: Document;
30
+ }
31
+ export interface CommandPalette {
32
+ open(query?: string): void;
33
+ close(): void;
34
+ toggle(): void;
35
+ readonly isOpen: boolean;
36
+ dispose(): void;
37
+ }
38
+ export declare function commandPalette(options: CommandPaletteOptions): CommandPalette;
@@ -0,0 +1,35 @@
1
+ import type { UiLook } from "./look.js";
2
+ import { type Anchor } from "./layer.js";
3
+ import { type Placement } from "./overlay-model.js";
4
+ export interface PopoverOptions {
5
+ /** `bottom` by default; `top`, `left`, `right`, each with `-start` or `-end`. */
6
+ readonly placement?: Placement;
7
+ /** Px between the anchor and the layer (6). */
8
+ readonly offset?: number;
9
+ /** `dialog` by default; `menu`, `listbox`… for what the content is. */
10
+ readonly role?: string;
11
+ readonly label?: string;
12
+ /**
13
+ * Where focus goes when it opens: the first focusable element (`first`, the default), the
14
+ * layer itself (`layer`), or nowhere (`none`, focus stays where it was).
15
+ */
16
+ readonly focus?: "first" | "layer" | "none";
17
+ /** Tab stays inside (a modal popover). */
18
+ readonly trap?: boolean;
19
+ /** A click outside closes it (true). */
20
+ readonly closeOnOutside?: boolean;
21
+ readonly look?: UiLook;
22
+ /** Extra class names for the layer. */
23
+ readonly className?: string;
24
+ onClose?(reason: PopoverCloseReason): void;
25
+ }
26
+ export type PopoverCloseReason = "escape" | "outside" | "close";
27
+ export interface Popover {
28
+ readonly element: HTMLElement;
29
+ /** Closes it (animated); resolves once it's gone. */
30
+ close(): Promise<void>;
31
+ /** Places it again (after its content or anchor moved). */
32
+ reposition(): void;
33
+ readonly open: boolean;
34
+ }
35
+ export declare function popover(anchor: Anchor, content: HTMLElement | string | ((close: () => void) => HTMLElement), options?: PopoverOptions): Popover;
@@ -0,0 +1,45 @@
1
+ import { type CSSProperties, type ReactNode } from "react";
2
+ import { type Bento as BentoHandle, type BentoOptions } from "./bento.js";
3
+ import { type Split as SplitHandle, type SplitOptions } from "./split.js";
4
+ import { type Timeline as TimelineHandle, type TimelineOptions } from "./timeline.js";
5
+ import { type Tree as TreeHandle, type TreeOptions } from "./tree.js";
6
+ export type SplitProps = SplitOptions & {
7
+ /** One child per pane, in the panes' order; each renders into its pane's body. */
8
+ children?: ReactNode;
9
+ className?: string;
10
+ style?: CSSProperties;
11
+ onReady?: (split: SplitHandle) => void;
12
+ };
13
+ /** `split()` as a component: its children portal into the panes. */
14
+ export declare function Split(props: SplitProps): import("react").JSX.Element;
15
+ export type TreeProps<T> = TreeOptions<T> & {
16
+ className?: string;
17
+ style?: CSSProperties;
18
+ onReady?: (tree: TreeHandle<T>) => void;
19
+ };
20
+ /**
21
+ * `tree()` as a component. New `items` update it in place (rows glide); `selection` and
22
+ * `filter` follow their props; callbacks always call the latest props.
23
+ */
24
+ export declare function Tree<T>(props: TreeProps<T>): import("react").JSX.Element;
25
+ export type TimelineProps = TimelineOptions & {
26
+ className?: string;
27
+ style?: CSSProperties;
28
+ onReady?: (timeline: TimelineHandle) => void;
29
+ };
30
+ /**
31
+ * `timeline()` as a component. `time`, `playing`, `markers`, `ranges`, `duration`, `fps`,
32
+ * `units` and `speed` follow their props (a `time` it reported itself is left alone); callbacks
33
+ * always call the latest props.
34
+ */
35
+ export declare function Timeline(props: TimelineProps): import("react").JSX.Element;
36
+ export type BentoProps = BentoOptions & {
37
+ className?: string;
38
+ style?: CSSProperties;
39
+ onReady?: (bento: BentoHandle) => void;
40
+ };
41
+ /**
42
+ * `bento()` as a component. New `items` update it in place (tiles glide); `picks` follows its
43
+ * prop; callbacks always call the latest props.
44
+ */
45
+ export declare function Bento(props: BentoProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare const readoutCss = "\n/* Readouts: live values, sparklines, graphs, meters, tables and logs. */\n:where(.knobs-ui) :is(.knobs-rd-chip,.knobs-rd-copy,.knobs-rd-jump,.knobs-rd-find,.knobs-rd-match){all:revert;box-sizing:border-box;\n font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}\n.knobs-ui.knobs-rd,.knobs-rd *{box-sizing:border-box}\n.knobs-ui.knobs-rd{display:flex;align-items:center;gap:8px;min-width:0;color:var(--ku-text);letter-spacing:normal}\n.knobs-rd .knobs-rd-v{font:var(--ku-font-weight) var(--ku-value-size)/1.25 var(--ku-value-font);font-variant-numeric:tabular-nums;font-feature-settings:\"tnum\";white-space:nowrap}\n.knobs-rd .knobs-rd-mono{font:400 var(--ku-text-meta)/1.25 var(--ku-mono);font-variant-numeric:tabular-nums;white-space:pre}\n.knobs-rd .knobs-rd-meta{font:var(--ku-font-weight) var(--ku-text-meta)/1.25 var(--ku-font);white-space:nowrap}\n.knobs-rd .knobs-rd-value{display:inline-flex;justify-content:flex-end;align-items:baseline;flex:none;margin-left:auto;contain:layout paint style}\n.knobs-rd .knobs-rd-num{color:var(--ku-text);text-align:right;transition:color .15s}\n.knobs-rd .knobs-rd-unit{margin-left:var(--k-unit-gap,.28em);color:var(--ku-dim)}\n.knobs-rd .knobs-rd-unit[hidden]{display:none}\n.knobs-rd.knobs-rd-watch{flex:1 1 auto;justify-content:flex-end}\n.knobs-rd.knobs-rd-spark,.knobs-rd.knobs-rd-meter{flex:0 1 64%;margin-left:auto}\n.knobs-rd .knobs-rd-plot{display:block;flex:1 1 0;min-width:24px;height:max(12px,calc(var(--ku-control-h) - 2px))}\n.knobs-rd.knobs-rd-meter>.knobs-rd-plot{height:8px}\n.knobs-rd.knobs-rd-spark[data-hover] .knobs-rd-num{color:var(--ku-label)}\n.knobs-rd .knobs-rd-cap,.knobs-rd .knobs-rd-head>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ku-label);font-weight:var(--ku-label-weight)}\n\n/* Blocks: a head line (the label and what goes with it) over a body. In a panel the row grows to fit. */\n.knobs-panel .knobs-row.knobs-rd-rowblock{display:block;height:auto}\n.knobs-rd.knobs-rd-block{display:flex;flex-direction:column;align-items:stretch;gap:2px;width:100%}\n.knobs-rd .knobs-rd-head{display:flex;align-items:center;gap:0 10px;min-height:var(--ku-row);min-width:0}\n/* The title keeps its words; the legend or tools beside it give way (see fitHead). */\n.knobs-rd .knobs-rd-head>span:first-child{flex:0 0 auto;max-width:100%}\n.knobs-rd .knobs-rd-legend{display:flex;align-items:center;gap:12px;margin-left:auto;min-width:0;overflow:hidden}\n.knobs-rd .knobs-rd-tools{display:flex;align-items:center;gap:10px;margin-left:auto;min-width:0}\n.knobs-rd .knobs-rd-tools>.knobs-rd-find{flex:1 1 56px;min-width:56px;max-width:84px}\n.knobs-rd .knobs-rd-head[data-short] .knobs-rd-legend .knobs-rd-unit{display:none}\n.knobs-rd .knobs-rd-head[data-wrap]{flex-wrap:wrap;padding-bottom:4px}\n.knobs-rd .knobs-rd-head[data-wrap]>span:first-child{display:flex;align-items:center;min-height:var(--ku-row)}\n.knobs-rd .knobs-rd-head[data-wrap]>:is(.knobs-rd-legend,.knobs-rd-tools){flex:1 0 100%;margin-left:0}\n.knobs-rd .knobs-rd-head[data-wrap]>.knobs-rd-tools{flex-wrap:wrap;row-gap:4px}\n.knobs-rd .knobs-rd-key{display:inline-flex;align-items:center;gap:5px;white-space:nowrap}\n.knobs-rd .knobs-rd-swatch{flex:none;width:8px;height:2px;border-radius:1px}\n.knobs-rd .knobs-rd-name{color:var(--ku-label)}\n.knobs-rd .knobs-rd-key>.knobs-rd-value{margin-left:2px}\n.knobs-rd .knobs-rd-plotbox{position:relative;outline:none;margin-bottom:6px}\n.knobs-rd .knobs-rd-plotbox>.knobs-rd-plot{position:absolute;inset:0;width:100%;height:100%;min-width:0}\n.knobs-rd .knobs-rd-tick,.knobs-rd .knobs-rd-mark{position:absolute;color:var(--ku-dim);pointer-events:none;line-height:14px;height:14px}\n.knobs-rd .knobs-rd-tick{left:0}\n.knobs-rd .knobs-rd-tick[data-at=\"top\"]{top:-2px}\n.knobs-rd .knobs-rd-tick[data-at=\"bottom\"]{bottom:0}\n.knobs-rd .knobs-rd-mark{right:0;top:0}\n.knobs-rd .knobs-rd-mark[hidden]{display:none}\n\n.knobs-rd .knobs-rd-grid{display:grid;column-gap:14px;align-items:center;outline:none;margin-bottom:4px}\n.knobs-rd .knobs-rd-tr{display:contents}\n.knobs-rd .knobs-rd-tr>*{height:calc(var(--ku-row) * .72);line-height:calc(var(--ku-row) * .72);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.knobs-rd .knobs-rd-thead>*{height:var(--ku-row);line-height:var(--ku-row)}\n.knobs-rd .knobs-rd-th{color:var(--ku-dim);text-align:right}\n.knobs-rd .knobs-rd-td{color:var(--ku-text)}\n.knobs-rd .knobs-rd-td:first-child{color:var(--ku-label)}\n.knobs-rd .knobs-rd-td[data-num]{text-align:right}\n\n.knobs-rd.knobs-rd-log{--rd-lh:18px}\n.knobs-rd .knobs-rd-chips{display:inline-flex;align-items:center;gap:2px;margin-right:auto}\n.knobs-ui .knobs-rd-chip,.knobs-ui .knobs-rd-copy{height:calc(var(--ku-control-h) - 2px);padding:0 7px;border-radius:min(var(--ku-radius),5px);color:var(--ku-label);cursor:pointer;transition:background .15s,color .15s}\n.knobs-ui .knobs-rd-chip:hover,.knobs-ui .knobs-rd-copy:hover{background:var(--ku-wash);color:var(--ku-text)}\n.knobs-ui .knobs-rd-chip[aria-checked=\"true\"]{background:var(--ku-wash-hi);color:var(--ku-text)}\n.knobs-ui .knobs-rd-find{width:84px;height:calc(var(--ku-control-h) - 2px);padding:0 7px;border-radius:min(var(--ku-radius),5px);\n background:color-mix(in srgb,var(--ku-surface-hi) 60%,transparent);color:var(--ku-text);caret-color:var(--ku-accent)}\n.knobs-rd .knobs-rd-find::placeholder{color:var(--ku-dim)}\n.knobs-rd .knobs-rd-find::-webkit-search-cancel-button{display:none}\n.knobs-rd .knobs-rd-logbody{position:relative;margin-bottom:6px}\n.knobs-rd .knobs-rd-scroll{position:relative;overflow-x:hidden;overflow-y:auto;height:calc(var(--knobs-rd-lines,8) * var(--rd-lh));outline:none;\n background:color-mix(in srgb,var(--ku-surface-hi) 45%,transparent);border-radius:min(var(--ku-radius),6px);scrollbar-width:thin;overscroll-behavior:contain}\n.knobs-rd .knobs-rd-space{position:relative;width:100%}\n.knobs-rd .knobs-rd-line{position:absolute;left:0;right:0;top:0;display:flex;gap:10px;height:var(--rd-lh);line-height:var(--rd-lh);padding:0 8px;contain:layout style;will-change:transform}\n.knobs-rd .knobs-rd-line[hidden]{display:none}\n.knobs-rd .knobs-rd-time{flex:none;color:var(--ku-dim)}\n.knobs-rd .knobs-rd-text{min-width:0;overflow:hidden;text-overflow:ellipsis;color:var(--ku-text)}\n.knobs-rd .knobs-rd-line[data-level=\"warn\"]>.knobs-rd-text{color:var(--ku-warn)}\n.knobs-rd .knobs-rd-line[data-level=\"error\"]>.knobs-rd-text{color:var(--ku-critical)}\n.knobs-rd .knobs-rd-line[data-level=\"debug\"]>.knobs-rd-text{color:var(--ku-dim)}\n.knobs-ui .knobs-rd-match{color:var(--ku-accent)}\n.knobs-ui .knobs-rd-jump{position:absolute;left:50%;bottom:6px;height:20px;padding:0 10px;border-radius:calc(var(--ku-radius) * 999);\n background:var(--ku-accent);color:var(--ku-bg);opacity:0;transform:translate(-50%,6px);cursor:pointer;white-space:nowrap}\n\n/* Under the GPU overlay the readouts' DOM stays for focus, hit-testing and screen readers; the\n overlay draws them. Their filter field sits above it. */\n.knobs-panel[data-gpu-active=\"true\"] .knobs-rd :where(*:not(.knobs-rd-find)),.knobs-panel[data-gpu-active=\"true\"] .knobs-rd{color:transparent!important;background:transparent!important;border-color:transparent!important;box-shadow:none!important}\n.knobs-panel[data-gpu-active=\"true\"] .knobs-rd-plot{visibility:hidden}\n.knobs-panel[data-gpu-active=\"true\"] .knobs-rd-find{position:relative;z-index:2147480003}\n@media(prefers-reduced-motion:reduce){.knobs-rd :where(*){transition:none!important}}\n";