@zkmake/three-meter 0.2.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.
- package/README.md +33 -21
- package/dist/{mount-perf-hud-w4VnSoWa.d.ts → mount-perf-hud-B3wBLrcu.d.ts} +64 -39
- package/dist/{mount-perf-hud-DvDMg6uj.js → mount-perf-hud-CZNSZVYn.js} +208 -81
- package/dist/mount-perf-hud-CZNSZVYn.js.map +1 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/styles.css +48 -0
- package/dist/ui.d.ts +2 -2
- package/dist/ui.js +1 -1
- package/package.json +1 -1
- package/dist/mount-perf-hud-DvDMg6uj.js.map +0 -1
package/README.md
CHANGED
|
@@ -8,8 +8,9 @@ geometries, textures and programs, with a small dockable HUD. Zero dependencies.
|
|
|
8
8
|
bun add -d @zkmake/three-meter # or npm i -D / pnpm add -D
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Live demo: [three-meter.pages.dev](https://three-meter.pages.dev/)
|
|
12
|
-
renderer and `?count=5000` to
|
|
11
|
+
Live demo: [three-meter.pages.dev](https://three-meter.pages.dev/), with a vanilla three and a
|
|
12
|
+
React Three Fiber take on the same scene. Add `?webgpu` for the WebGPU renderer and `?count=5000` to
|
|
13
|
+
load the scene up.
|
|
13
14
|
|
|
14
15
|
The sampler and the card are separate components, so toggling the HUD never remounts your canvas.
|
|
15
16
|
GPU time is measured on WebGPU as well as WebGL. The counters are draw calls, render passes and
|
|
@@ -71,29 +72,38 @@ Canvas is its own React root. To run two canvases on one page, pass the same `st
|
|
|
71
72
|
|
|
72
73
|
`mountPerfHud` and `PerfHud` take:
|
|
73
74
|
|
|
74
|
-
| Option | Default | Meaning
|
|
75
|
-
| ------------------ | ------------------ |
|
|
76
|
-
| `mode` | `"compact"` | `compact` is the card. `full` is the checkbox list that configures it.
|
|
77
|
-
| `theme` | `"system"` | `dark`, `light`, or `system` to follow `prefers-color-scheme` live.
|
|
78
|
-
| `defaultPlacement` | `{ edge: "left" }` | First-visit dock, as `edge` plus `align` of `start`, `center` or `end`. A drag overrides it.
|
|
79
|
-
| `storageKey` | `three-meter` | localStorage key for the selection and the dock. `null` disables persistence.
|
|
80
|
-
| `parent` | `document.body` | Where the host element is appended.
|
|
81
|
-
| `injectStyles` | `true` | Append the stylesheet once per document.
|
|
82
|
-
| `refreshHz` | `10` | Repaint rate.
|
|
75
|
+
| Option | Default | Meaning |
|
|
76
|
+
| ------------------ | ------------------ | --------------------------------------------------------------------------------------------- |
|
|
77
|
+
| `mode` | `"compact"` | `compact` is the card. `full` is the checkbox list that configures it. |
|
|
78
|
+
| `theme` | `"system"` | `dark`, `light`, or `system` to follow `prefers-color-scheme` live. A pick in the panel wins. |
|
|
79
|
+
| `defaultPlacement` | `{ edge: "left" }` | First-visit dock, as `edge` plus `align` of `start`, `center` or `end`. A drag overrides it. |
|
|
80
|
+
| `storageKey` | `three-meter` | localStorage key for the selection and the dock. `null` disables persistence. |
|
|
81
|
+
| `parent` | `document.body` | Where the host element is appended. |
|
|
82
|
+
| `injectStyles` | `true` | Append the stylesheet once per document. |
|
|
83
|
+
| `refreshHz` | `10` | Repaint rate. |
|
|
83
84
|
|
|
84
85
|
`PerformanceMonitor` and `PerfSampler` take `trackGPU` (default true), `gpuQueryPoolSize` (default 5)
|
|
85
86
|
and `historySize` (default 120).
|
|
86
87
|
|
|
87
88
|
## Theme
|
|
88
89
|
|
|
89
|
-
The HUD ships a dark and a light palette.
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
The HUD ships a dark and a light palette. Two layers decide which shows:
|
|
91
|
+
|
|
92
|
+
1. **The panel's own pick.** The full view has a light / system / dark row. A pick there is kept
|
|
93
|
+
with the other settings under `storageKey` and wins while set.
|
|
94
|
+
2. **Your `theme` option.** `dark`, `light`, or `system` (the default), which follows the OS and
|
|
95
|
+
switches when it does. Applies whenever the person using the HUD hasn't picked anything.
|
|
96
|
+
|
|
97
|
+
Change your layer later from the handle, or from the `PerfHud` prop in React, which applies without
|
|
98
|
+
remounting:
|
|
92
99
|
|
|
93
100
|
```ts
|
|
94
101
|
const hud = mountPerfHud(monitor, { theme: "system" });
|
|
95
|
-
hud.setTheme("light"); // "dark" | "light" | "system"
|
|
96
|
-
hud.getTheme(); //
|
|
102
|
+
hud.setTheme("light"); // your layer: "dark" | "light" | "system"
|
|
103
|
+
hud.getTheme(); // your layer, as asked for
|
|
104
|
+
hud.settings.theme; // the panel's pick, or null
|
|
105
|
+
hud.settings.setTheme(null); // clear the pick so your layer applies again
|
|
106
|
+
hud.theme.effective; // whichever layer is in force
|
|
97
107
|
hud.theme.resolved; // "dark" | "light", what is on screen right now
|
|
98
108
|
hud.theme.subscribe(() => syncMyPageWith(hud.theme.resolved));
|
|
99
109
|
```
|
|
@@ -111,11 +121,13 @@ On WebGPU, construct the renderer with `trackTimestamp: true`. Timings resolve a
|
|
|
111
121
|
|
|
112
122
|
## Examples
|
|
113
123
|
|
|
114
|
-
[`examples/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
[`examples/site`](examples/site) is what runs at [three-meter.pages.dev](https://three-meter.pages.dev/):
|
|
125
|
+
one Vite app with both integrations of the same scene, swapped from the header.
|
|
126
|
+
[`src/demos/vanilla.ts`](examples/site/src/demos/vanilla.ts) is plain three with `mountPerfHud`;
|
|
127
|
+
[`src/demos/r3f.tsx`](examples/site/src/demos/r3f.tsx) is React Three Fiber with `PerfSampler` and
|
|
128
|
+
`PerfHud`. `?r3f` opens on Fiber, `?webgpu` uses `WebGPURenderer` in either, `?count=` scales the
|
|
129
|
+
scene. The header's theme toggle sets the page theme and the HUD's `theme` layer together; the row
|
|
130
|
+
inside the panel overrides the HUD alone. Run `bun run dev` inside it.
|
|
119
131
|
|
|
120
132
|
## Shipping it
|
|
121
133
|
|
|
@@ -32,11 +32,58 @@ type DockHandle = {
|
|
|
32
32
|
*/
|
|
33
33
|
declare const dockPanel: (element: HTMLElement, options?: DockPanelOptions) => DockHandle;
|
|
34
34
|
//#endregion
|
|
35
|
+
//#region src/ui/theme.d.ts
|
|
36
|
+
/**
|
|
37
|
+
* Dark, light, or follow the OS. One instance per HUD: the floating host and
|
|
38
|
+
* the card inside it both paint `resolved` onto `data-theme`, and the
|
|
39
|
+
* stylesheet picks the palette from that attribute.
|
|
40
|
+
*
|
|
41
|
+
* Two layers. `mode` is what the consumer asked for (the `theme` option,
|
|
42
|
+
* `setTheme`). `override` is what the person using the HUD picked in the
|
|
43
|
+
* panel, kept in `HudSettings`; it wins while set. `effective` is whichever
|
|
44
|
+
* applies, and `system` there tracks `prefers-color-scheme` for as long as
|
|
45
|
+
* the instance lives.
|
|
46
|
+
*/
|
|
47
|
+
type ThemeMode = "dark" | "light" | "system";
|
|
48
|
+
type ResolvedTheme = Exclude<ThemeMode, "system">;
|
|
49
|
+
declare const THEME_MODES: readonly ThemeMode[];
|
|
50
|
+
declare const isThemeMode: (value: unknown) => value is ThemeMode;
|
|
51
|
+
declare class HudTheme {
|
|
52
|
+
private current;
|
|
53
|
+
private userPick;
|
|
54
|
+
private readonly listeners;
|
|
55
|
+
private media;
|
|
56
|
+
private readonly onSchemeChange;
|
|
57
|
+
constructor(mode?: ThemeMode);
|
|
58
|
+
/** What the consumer asked for. */
|
|
59
|
+
get mode(): ThemeMode;
|
|
60
|
+
/** What the person using the HUD picked in the panel, or `null` for no pick. */
|
|
61
|
+
get override(): ThemeMode | null;
|
|
62
|
+
/** The layer that applies: the user's pick if there is one, else the consumer's mode. */
|
|
63
|
+
get effective(): ThemeMode;
|
|
64
|
+
/**
|
|
65
|
+
* `dark` or `light`, with `system` resolved against the OS preference.
|
|
66
|
+
* Where `matchMedia` is missing, `system` falls back to `dark`, the HUD's
|
|
67
|
+
* original palette.
|
|
68
|
+
*/
|
|
69
|
+
get resolved(): ResolvedTheme;
|
|
70
|
+
setMode(mode: ThemeMode): void;
|
|
71
|
+
setOverride(mode: ThemeMode | null): void;
|
|
72
|
+
/** Fires on `setMode`, `setOverride` and, in `system`, whenever the OS preference flips. */
|
|
73
|
+
subscribe(listener: () => void): () => void;
|
|
74
|
+
/** Stop tracking the OS preference and drop every subscriber. */
|
|
75
|
+
dispose(): void;
|
|
76
|
+
private watch;
|
|
77
|
+
private unwatch;
|
|
78
|
+
private notify;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
35
81
|
//#region src/ui/hud-settings.d.ts
|
|
36
82
|
type HudSelection = {
|
|
37
83
|
dim: boolean;
|
|
38
84
|
graphs: ReadonlySet<TimingMetric>;
|
|
39
85
|
numbers: ReadonlySet<string>;
|
|
86
|
+
theme: ThemeMode | null;
|
|
40
87
|
};
|
|
41
88
|
type HudDefaults = {
|
|
42
89
|
dim?: boolean;
|
|
@@ -59,6 +106,9 @@ declare class HudSettings {
|
|
|
59
106
|
get selection(): HudSelection;
|
|
60
107
|
get dim(): boolean;
|
|
61
108
|
setDim(enabled: boolean): void;
|
|
109
|
+
/** The user's pick in the panel; `null` means none, so the consumer's option applies. */
|
|
110
|
+
get theme(): ThemeMode | null;
|
|
111
|
+
setTheme(mode: ThemeMode | null): void;
|
|
62
112
|
isNumberEnabled(key: string): boolean;
|
|
63
113
|
isGraphEnabled(metric: TimingMetric): boolean;
|
|
64
114
|
setNumberEnabled(key: string, enabled: boolean): void;
|
|
@@ -68,41 +118,6 @@ declare class HudSettings {
|
|
|
68
118
|
private persistAndNotify;
|
|
69
119
|
}
|
|
70
120
|
//#endregion
|
|
71
|
-
//#region src/ui/theme.d.ts
|
|
72
|
-
/**
|
|
73
|
-
* Dark, light, or follow the OS. One instance per HUD: the floating host and
|
|
74
|
-
* the card inside it both paint `resolved` onto `data-theme`, and the
|
|
75
|
-
* stylesheet picks the palette from that attribute. `system` tracks
|
|
76
|
-
* `prefers-color-scheme` for as long as the instance lives.
|
|
77
|
-
*/
|
|
78
|
-
type ThemeMode = "dark" | "light" | "system";
|
|
79
|
-
type ResolvedTheme = Exclude<ThemeMode, "system">;
|
|
80
|
-
declare const THEME_MODES: readonly ThemeMode[];
|
|
81
|
-
declare const isThemeMode: (value: unknown) => value is ThemeMode;
|
|
82
|
-
declare class HudTheme {
|
|
83
|
-
private current;
|
|
84
|
-
private readonly listeners;
|
|
85
|
-
private media;
|
|
86
|
-
private readonly onSchemeChange;
|
|
87
|
-
constructor(mode?: ThemeMode);
|
|
88
|
-
/** What the host asked for. */
|
|
89
|
-
get mode(): ThemeMode;
|
|
90
|
-
/**
|
|
91
|
-
* `dark` or `light`, with `system` resolved against the OS preference.
|
|
92
|
-
* Where `matchMedia` is missing, `system` falls back to `dark`, the HUD's
|
|
93
|
-
* original palette.
|
|
94
|
-
*/
|
|
95
|
-
get resolved(): ResolvedTheme;
|
|
96
|
-
setMode(mode: ThemeMode): void;
|
|
97
|
-
/** Fires on `setMode` and, in `system`, whenever the OS preference flips. */
|
|
98
|
-
subscribe(listener: () => void): () => void;
|
|
99
|
-
/** Stop tracking the OS preference and drop every subscriber. */
|
|
100
|
-
dispose(): void;
|
|
101
|
-
private watch;
|
|
102
|
-
private unwatch;
|
|
103
|
-
private notify;
|
|
104
|
-
}
|
|
105
|
-
//#endregion
|
|
106
121
|
//#region src/ui/performance-view.d.ts
|
|
107
122
|
type PerformanceViewMode = "full" | "compact";
|
|
108
123
|
type PerformanceViewOptions = {
|
|
@@ -140,6 +155,7 @@ declare class PerformanceView {
|
|
|
140
155
|
private readonly statValueEls;
|
|
141
156
|
private readonly statCheckboxes;
|
|
142
157
|
private dimCheckbox;
|
|
158
|
+
private readonly themeRadios;
|
|
143
159
|
private hudGraphsEl;
|
|
144
160
|
private hudGridEl;
|
|
145
161
|
private readonly hudGraphs;
|
|
@@ -161,6 +177,8 @@ declare class PerformanceView {
|
|
|
161
177
|
private build;
|
|
162
178
|
private rebuildHud;
|
|
163
179
|
private onSelectionChanged;
|
|
180
|
+
/** Repaint the palette and mark the radio matching what applies (pick, else consumer mode). */
|
|
181
|
+
private onThemeChanged;
|
|
164
182
|
private buildCheckbox;
|
|
165
183
|
private applyModeClass;
|
|
166
184
|
private resizeCanvases;
|
|
@@ -172,7 +190,8 @@ type MountPerfHudOptions = {
|
|
|
172
190
|
mode?: PerformanceViewMode;
|
|
173
191
|
/**
|
|
174
192
|
* `dark`, `light`, or `system` to follow the OS preference. Default
|
|
175
|
-
* `system`. Change it later with the handle's `setTheme`.
|
|
193
|
+
* `system`. Change it later with the handle's `setTheme`. A pick made in
|
|
194
|
+
* the panel's own theme row (kept in `settings`) sits on top of this.
|
|
176
195
|
*/
|
|
177
196
|
theme?: ThemeMode;
|
|
178
197
|
/** localStorage key for the selection and the dock. `null` disables persistence. */
|
|
@@ -194,15 +213,21 @@ type PerfHudHandle = {
|
|
|
194
213
|
element: HTMLDivElement;
|
|
195
214
|
view: PerformanceView;
|
|
196
215
|
settings: HudSettings;
|
|
197
|
-
/**
|
|
216
|
+
/**
|
|
217
|
+
* `mode` is the consumer layer, `override` the panel's pick, `effective`
|
|
218
|
+
* whichever applies, `resolved` the `dark` / `light` on screen. Subscribe
|
|
219
|
+
* for changes, including the OS preference moving under `system`.
|
|
220
|
+
*/
|
|
198
221
|
theme: HudTheme;
|
|
199
222
|
getMode: () => PerformanceViewMode;
|
|
200
223
|
setMode: (mode: PerformanceViewMode) => void;
|
|
224
|
+
/** The consumer layer. The panel's pick, if any, is `settings.theme`. */
|
|
201
225
|
getTheme: () => ThemeMode;
|
|
226
|
+
/** Sets the consumer layer; a pick made in the panel still wins until `settings.setTheme(null)`. */
|
|
202
227
|
setTheme: (mode: ThemeMode) => void;
|
|
203
228
|
dispose: () => void;
|
|
204
229
|
};
|
|
205
230
|
declare const mountPerfHud: (monitor: PerformanceMonitor, options?: MountPerfHudOptions) => PerfHudHandle;
|
|
206
231
|
//#endregion
|
|
207
|
-
export { dockPanel as C, ScreenEdge as S, DefaultPlacement as _, PerformanceViewMode as a, DockPanelOptions as b,
|
|
208
|
-
//# sourceMappingURL=mount-perf-hud-
|
|
232
|
+
export { dockPanel as C, ScreenEdge as S, DefaultPlacement as _, PerformanceViewMode as a, DockPanelOptions as b, HudDefaults as c, HudSettingsOptions as d, HudTheme as f, isThemeMode as g, ThemeMode as h, PerformanceView as i, HudSelection as l, THEME_MODES as m, PerfHudHandle as n, PerformanceViewOptions as o, ResolvedTheme as p, mountPerfHud as r, DEFAULT_STORAGE_KEY as s, MountPerfHudOptions as t, HudSettings as u, DockAlign as v, Placement as x, DockHandle as y };
|
|
233
|
+
//# sourceMappingURL=mount-perf-hud-B3wBLrcu.d.ts.map
|
|
@@ -1,3 +1,90 @@
|
|
|
1
|
+
//#region src/ui/theme.ts
|
|
2
|
+
const THEME_MODES = [
|
|
3
|
+
"dark",
|
|
4
|
+
"light",
|
|
5
|
+
"system"
|
|
6
|
+
];
|
|
7
|
+
const DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
|
|
8
|
+
const isThemeMode = (value) => typeof value === "string" && THEME_MODES.includes(value);
|
|
9
|
+
var HudTheme = class {
|
|
10
|
+
current;
|
|
11
|
+
userPick = null;
|
|
12
|
+
listeners = /* @__PURE__ */ new Set();
|
|
13
|
+
media = null;
|
|
14
|
+
onSchemeChange = () => this.notify();
|
|
15
|
+
constructor(mode = "system") {
|
|
16
|
+
this.current = mode;
|
|
17
|
+
this.watch();
|
|
18
|
+
}
|
|
19
|
+
/** What the consumer asked for. */
|
|
20
|
+
get mode() {
|
|
21
|
+
return this.current;
|
|
22
|
+
}
|
|
23
|
+
/** What the person using the HUD picked in the panel, or `null` for no pick. */
|
|
24
|
+
get override() {
|
|
25
|
+
return this.userPick;
|
|
26
|
+
}
|
|
27
|
+
/** The layer that applies: the user's pick if there is one, else the consumer's mode. */
|
|
28
|
+
get effective() {
|
|
29
|
+
return this.userPick ?? this.current;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `dark` or `light`, with `system` resolved against the OS preference.
|
|
33
|
+
* Where `matchMedia` is missing, `system` falls back to `dark`, the HUD's
|
|
34
|
+
* original palette.
|
|
35
|
+
*/
|
|
36
|
+
get resolved() {
|
|
37
|
+
const effective = this.effective;
|
|
38
|
+
if (effective !== "system") return effective;
|
|
39
|
+
if (!this.media) return "dark";
|
|
40
|
+
return this.media.matches ? "dark" : "light";
|
|
41
|
+
}
|
|
42
|
+
setMode(mode) {
|
|
43
|
+
if (mode === this.current) return;
|
|
44
|
+
this.current = mode;
|
|
45
|
+
this.watch();
|
|
46
|
+
this.notify();
|
|
47
|
+
}
|
|
48
|
+
setOverride(mode) {
|
|
49
|
+
if (mode === this.userPick) return;
|
|
50
|
+
this.userPick = mode;
|
|
51
|
+
this.watch();
|
|
52
|
+
this.notify();
|
|
53
|
+
}
|
|
54
|
+
/** Fires on `setMode`, `setOverride` and, in `system`, whenever the OS preference flips. */
|
|
55
|
+
subscribe(listener) {
|
|
56
|
+
this.listeners.add(listener);
|
|
57
|
+
return () => {
|
|
58
|
+
this.listeners.delete(listener);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Stop tracking the OS preference and drop every subscriber. */
|
|
62
|
+
dispose() {
|
|
63
|
+
this.unwatch();
|
|
64
|
+
this.listeners.clear();
|
|
65
|
+
}
|
|
66
|
+
watch() {
|
|
67
|
+
if (this.effective !== "system") {
|
|
68
|
+
this.unwatch();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (this.media || typeof matchMedia !== "function") return;
|
|
72
|
+
this.media = matchMedia(DARK_SCHEME_QUERY);
|
|
73
|
+
this.media.addEventListener("change", this.onSchemeChange);
|
|
74
|
+
}
|
|
75
|
+
unwatch() {
|
|
76
|
+
this.media?.removeEventListener("change", this.onSchemeChange);
|
|
77
|
+
this.media = null;
|
|
78
|
+
}
|
|
79
|
+
notify() {
|
|
80
|
+
for (const listener of this.listeners) listener();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
/** Paint the resolved theme onto an element for the stylesheet to key off. */
|
|
84
|
+
const applyTheme = (element, theme) => {
|
|
85
|
+
element.dataset.theme = theme.resolved;
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
1
88
|
//#region src/ui/hud-settings.ts
|
|
2
89
|
const DEFAULT_STORAGE_KEY = "three-meter";
|
|
3
90
|
const DEFAULT_DIM = false;
|
|
@@ -38,6 +125,14 @@ var HudSettings = class {
|
|
|
38
125
|
this.state.dim = enabled;
|
|
39
126
|
this.persistAndNotify();
|
|
40
127
|
}
|
|
128
|
+
/** The user's pick in the panel; `null` means none, so the consumer's option applies. */
|
|
129
|
+
get theme() {
|
|
130
|
+
return this.state.theme;
|
|
131
|
+
}
|
|
132
|
+
setTheme(mode) {
|
|
133
|
+
this.state.theme = mode;
|
|
134
|
+
this.persistAndNotify();
|
|
135
|
+
}
|
|
41
136
|
isNumberEnabled(key) {
|
|
42
137
|
return this.state.numbers.has(key);
|
|
43
138
|
}
|
|
@@ -64,7 +159,8 @@ var HudSettings = class {
|
|
|
64
159
|
const fallback = () => ({
|
|
65
160
|
dim: this.defaults.dim,
|
|
66
161
|
graphs: new Set(this.defaults.graphs),
|
|
67
|
-
numbers: new Set(this.defaults.numbers)
|
|
162
|
+
numbers: new Set(this.defaults.numbers),
|
|
163
|
+
theme: null
|
|
68
164
|
});
|
|
69
165
|
if (this.storageKey === null) return fallback();
|
|
70
166
|
try {
|
|
@@ -74,7 +170,8 @@ var HudSettings = class {
|
|
|
74
170
|
return {
|
|
75
171
|
dim: parsed.dim ?? this.defaults.dim,
|
|
76
172
|
graphs: new Set((parsed.graphs ?? this.defaults.graphs).filter(isTimingMetric)),
|
|
77
|
-
numbers: new Set(parsed.numbers ?? this.defaults.numbers)
|
|
173
|
+
numbers: new Set(parsed.numbers ?? this.defaults.numbers),
|
|
174
|
+
theme: isThemeMode(parsed.theme) ? parsed.theme : null
|
|
78
175
|
};
|
|
79
176
|
} catch {
|
|
80
177
|
return fallback();
|
|
@@ -85,7 +182,8 @@ var HudSettings = class {
|
|
|
85
182
|
localStorage.setItem(this.storageKey, JSON.stringify({
|
|
86
183
|
dim: this.state.dim,
|
|
87
184
|
graphs: [...this.state.graphs],
|
|
88
|
-
numbers: [...this.state.numbers]
|
|
185
|
+
numbers: [...this.state.numbers],
|
|
186
|
+
theme: this.state.theme ?? void 0
|
|
89
187
|
}));
|
|
90
188
|
} catch {}
|
|
91
189
|
for (const listener of this.listeners) listener();
|
|
@@ -344,7 +442,11 @@ const dockPanel = (element, options = {}) => {
|
|
|
344
442
|
//#region src/ui/icons.ts
|
|
345
443
|
const ICON_MARKUP = {
|
|
346
444
|
blend: "<circle cx=\"9\" cy=\"9\" r=\"7\"/><circle cx=\"15\" cy=\"15\" r=\"7\"/>",
|
|
445
|
+
contrast: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 18a6 6 0 0 0 0-12v12z\"/>",
|
|
347
446
|
grip: "<circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"5\" r=\"1\"/><circle cx=\"9\" cy=\"19\" r=\"1\"/><circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"5\" r=\"1\"/><circle cx=\"15\" cy=\"19\" r=\"1\"/>",
|
|
447
|
+
monitor: "<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/><line x1=\"8\" x2=\"16\" y1=\"21\" y2=\"21\"/><line x1=\"12\" x2=\"12\" y1=\"17\" y2=\"21\"/>",
|
|
448
|
+
moon: "<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/>",
|
|
449
|
+
sun: "<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/><path d=\"m4.93 4.93 1.41 1.41\"/><path d=\"m17.66 17.66 1.41 1.41\"/><path d=\"M2 12h2\"/><path d=\"M20 12h2\"/><path d=\"m6.34 17.66-1.41 1.41\"/><path d=\"m19.07 4.93-1.41 1.41\"/>",
|
|
348
450
|
sliders: "<line x1=\"21\" x2=\"14\" y1=\"4\" y2=\"4\"/><line x1=\"10\" x2=\"3\" y1=\"4\" y2=\"4\"/><line x1=\"21\" x2=\"12\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"3\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"16\" y1=\"20\" y2=\"20\"/><line x1=\"12\" x2=\"3\" y1=\"20\" y2=\"20\"/><line x1=\"14\" x2=\"14\" y1=\"2\" y2=\"6\"/><line x1=\"8\" x2=\"8\" y1=\"10\" y2=\"14\"/><line x1=\"16\" x2=\"16\" y1=\"18\" y2=\"22\"/>"
|
|
349
451
|
};
|
|
350
452
|
const createIcon = (name, className) => {
|
|
@@ -383,77 +485,6 @@ function drawSparkline(ctx, width, height, values, style) {
|
|
|
383
485
|
ctx.fill();
|
|
384
486
|
}
|
|
385
487
|
//#endregion
|
|
386
|
-
//#region src/ui/theme.ts
|
|
387
|
-
const THEME_MODES = [
|
|
388
|
-
"dark",
|
|
389
|
-
"light",
|
|
390
|
-
"system"
|
|
391
|
-
];
|
|
392
|
-
const DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
|
|
393
|
-
const isThemeMode = (value) => typeof value === "string" && THEME_MODES.includes(value);
|
|
394
|
-
var HudTheme = class {
|
|
395
|
-
current;
|
|
396
|
-
listeners = /* @__PURE__ */ new Set();
|
|
397
|
-
media = null;
|
|
398
|
-
onSchemeChange = () => this.notify();
|
|
399
|
-
constructor(mode = "system") {
|
|
400
|
-
this.current = mode;
|
|
401
|
-
this.watch();
|
|
402
|
-
}
|
|
403
|
-
/** What the host asked for. */
|
|
404
|
-
get mode() {
|
|
405
|
-
return this.current;
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* `dark` or `light`, with `system` resolved against the OS preference.
|
|
409
|
-
* Where `matchMedia` is missing, `system` falls back to `dark`, the HUD's
|
|
410
|
-
* original palette.
|
|
411
|
-
*/
|
|
412
|
-
get resolved() {
|
|
413
|
-
if (this.current !== "system") return this.current;
|
|
414
|
-
if (!this.media) return "dark";
|
|
415
|
-
return this.media.matches ? "dark" : "light";
|
|
416
|
-
}
|
|
417
|
-
setMode(mode) {
|
|
418
|
-
if (mode === this.current) return;
|
|
419
|
-
this.current = mode;
|
|
420
|
-
this.watch();
|
|
421
|
-
this.notify();
|
|
422
|
-
}
|
|
423
|
-
/** Fires on `setMode` and, in `system`, whenever the OS preference flips. */
|
|
424
|
-
subscribe(listener) {
|
|
425
|
-
this.listeners.add(listener);
|
|
426
|
-
return () => {
|
|
427
|
-
this.listeners.delete(listener);
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
/** Stop tracking the OS preference and drop every subscriber. */
|
|
431
|
-
dispose() {
|
|
432
|
-
this.unwatch();
|
|
433
|
-
this.listeners.clear();
|
|
434
|
-
}
|
|
435
|
-
watch() {
|
|
436
|
-
if (this.current !== "system") {
|
|
437
|
-
this.unwatch();
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
if (this.media || typeof matchMedia !== "function") return;
|
|
441
|
-
this.media = matchMedia(DARK_SCHEME_QUERY);
|
|
442
|
-
this.media.addEventListener("change", this.onSchemeChange);
|
|
443
|
-
}
|
|
444
|
-
unwatch() {
|
|
445
|
-
this.media?.removeEventListener("change", this.onSchemeChange);
|
|
446
|
-
this.media = null;
|
|
447
|
-
}
|
|
448
|
-
notify() {
|
|
449
|
-
for (const listener of this.listeners) listener();
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
/** Paint the resolved theme onto an element for the stylesheet to key off. */
|
|
453
|
-
const applyTheme = (element, theme) => {
|
|
454
|
-
element.dataset.theme = theme.resolved;
|
|
455
|
-
};
|
|
456
|
-
//#endregion
|
|
457
488
|
//#region src/ui/performance-view.ts
|
|
458
489
|
const TIMINGS = [
|
|
459
490
|
{
|
|
@@ -505,6 +536,23 @@ const TIMINGS = [
|
|
|
505
536
|
unit: "ms"
|
|
506
537
|
}
|
|
507
538
|
];
|
|
539
|
+
const THEME_OPTIONS = [
|
|
540
|
+
{
|
|
541
|
+
icon: "sun",
|
|
542
|
+
label: "Light theme",
|
|
543
|
+
mode: "light"
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
icon: "monitor",
|
|
547
|
+
label: "Match the system theme",
|
|
548
|
+
mode: "system"
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
icon: "moon",
|
|
552
|
+
label: "Dark theme",
|
|
553
|
+
mode: "dark"
|
|
554
|
+
}
|
|
555
|
+
];
|
|
508
556
|
const formatCount = (value) => value.toLocaleString("en-US");
|
|
509
557
|
const readTiming = (sample, metric) => {
|
|
510
558
|
switch (metric) {
|
|
@@ -592,6 +640,7 @@ var PerformanceView = class {
|
|
|
592
640
|
statValueEls = /* @__PURE__ */ new Map();
|
|
593
641
|
statCheckboxes = /* @__PURE__ */ new Map();
|
|
594
642
|
dimCheckbox;
|
|
643
|
+
themeRadios = /* @__PURE__ */ new Map();
|
|
595
644
|
hudGraphsEl;
|
|
596
645
|
hudGridEl;
|
|
597
646
|
hudGraphs = /* @__PURE__ */ new Map();
|
|
@@ -605,17 +654,18 @@ var PerformanceView = class {
|
|
|
605
654
|
this.settings = options.settings ?? new HudSettings();
|
|
606
655
|
this.ownsTheme = !options.theme;
|
|
607
656
|
this.theme = options.theme ?? new HudTheme();
|
|
657
|
+
this.theme.setOverride(this.settings.theme);
|
|
608
658
|
this.mode = options.mode ?? "full";
|
|
609
659
|
this.minIntervalMs = 1e3 / (options.refreshHz ?? 10);
|
|
610
660
|
this.element = document.createElement("div");
|
|
611
661
|
this.element.className = "perf-monitor";
|
|
612
662
|
this.applyModeClass();
|
|
613
|
-
applyTheme(this.element, this.theme);
|
|
614
663
|
this.resizeObserver = new ResizeObserver(() => this.resizeCanvases());
|
|
615
664
|
this.build();
|
|
616
665
|
this.rebuildHud();
|
|
666
|
+
this.onThemeChanged();
|
|
617
667
|
this.unsubscribe = this.settings.subscribe(() => this.onSelectionChanged());
|
|
618
|
-
this.unsubscribeTheme = this.theme.subscribe(() =>
|
|
668
|
+
this.unsubscribeTheme = this.theme.subscribe(() => this.onThemeChanged());
|
|
619
669
|
}
|
|
620
670
|
start() {
|
|
621
671
|
if (this.rafId === null) this.rafId = requestAnimationFrame(this.frame);
|
|
@@ -689,14 +739,37 @@ var PerformanceView = class {
|
|
|
689
739
|
hud.append(this.hudGraphsEl, this.hudGridEl);
|
|
690
740
|
const options = document.createElement("div");
|
|
691
741
|
options.className = "perf-monitor__section perf-monitor__options";
|
|
742
|
+
const themeRow = document.createElement("div");
|
|
743
|
+
themeRow.className = "perf-monitor__row perf-monitor__row--static";
|
|
744
|
+
const themeLabel = document.createElement("span");
|
|
745
|
+
themeLabel.className = "perf-monitor__label";
|
|
746
|
+
themeLabel.textContent = "theme";
|
|
747
|
+
const segment = document.createElement("div");
|
|
748
|
+
segment.className = "perf-monitor__segment";
|
|
749
|
+
segment.setAttribute("role", "radiogroup");
|
|
750
|
+
segment.setAttribute("aria-label", "Panel theme");
|
|
751
|
+
for (const option of THEME_OPTIONS) {
|
|
752
|
+
const radio = document.createElement("button");
|
|
753
|
+
radio.type = "button";
|
|
754
|
+
radio.className = "perf-monitor__segment-option";
|
|
755
|
+
radio.setAttribute("role", "radio");
|
|
756
|
+
radio.setAttribute("aria-checked", "false");
|
|
757
|
+
radio.setAttribute("aria-label", option.label);
|
|
758
|
+
radio.title = option.label;
|
|
759
|
+
radio.append(createIcon(option.icon, "perf-monitor__icon"));
|
|
760
|
+
radio.addEventListener("click", () => this.settings.setTheme(option.mode));
|
|
761
|
+
this.themeRadios.set(option.mode, radio);
|
|
762
|
+
segment.append(radio);
|
|
763
|
+
}
|
|
764
|
+
themeRow.append(createIcon("contrast", "perf-monitor__icon"), themeLabel, segment);
|
|
692
765
|
const dimRow = document.createElement("label");
|
|
693
766
|
dimRow.className = "perf-monitor__row";
|
|
694
767
|
this.dimCheckbox = this.buildCheckbox(this.settings.dim, "Dim the performance panel when the pointer leaves", (on) => this.settings.setDim(on));
|
|
695
768
|
const dimLabel = document.createElement("span");
|
|
696
769
|
dimLabel.className = "perf-monitor__label";
|
|
697
770
|
dimLabel.textContent = "dim on leave";
|
|
698
|
-
dimRow.append(
|
|
699
|
-
options.append(dimRow);
|
|
771
|
+
dimRow.append(createIcon("blend", "perf-monitor__icon"), dimLabel, this.dimCheckbox);
|
|
772
|
+
options.append(themeRow, dimRow);
|
|
700
773
|
const graphs = document.createElement("div");
|
|
701
774
|
graphs.className = "perf-monitor__graphs";
|
|
702
775
|
for (const config of TIMINGS) {
|
|
@@ -807,8 +880,14 @@ var PerformanceView = class {
|
|
|
807
880
|
for (const [metric, checkbox] of this.graphCheckboxes) checkbox.checked = this.settings.isGraphEnabled(metric);
|
|
808
881
|
for (const [key, checkbox] of this.statCheckboxes) checkbox.checked = this.settings.isNumberEnabled(key);
|
|
809
882
|
this.dimCheckbox.checked = this.settings.dim;
|
|
883
|
+
this.theme.setOverride(this.settings.theme);
|
|
810
884
|
this.rebuildHud();
|
|
811
885
|
}
|
|
886
|
+
/** Repaint the palette and mark the radio matching what applies (pick, else consumer mode). */
|
|
887
|
+
onThemeChanged() {
|
|
888
|
+
applyTheme(this.element, this.theme);
|
|
889
|
+
for (const [mode, radio] of this.themeRadios) radio.setAttribute("aria-checked", String(mode === this.theme.effective));
|
|
890
|
+
}
|
|
812
891
|
buildCheckbox(checked, title, onChange) {
|
|
813
892
|
const checkbox = document.createElement("input");
|
|
814
893
|
checkbox.type = "checkbox";
|
|
@@ -986,6 +1065,54 @@ const PERF_HUD_STYLES = `
|
|
|
986
1065
|
border-bottom: 1px solid var(--perf-border);
|
|
987
1066
|
}
|
|
988
1067
|
|
|
1068
|
+
.perf-monitor__row--static {
|
|
1069
|
+
cursor: default;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/* Light / system / dark, as a small segmented radio group. */
|
|
1073
|
+
.perf-monitor__segment {
|
|
1074
|
+
display: inline-flex;
|
|
1075
|
+
flex: none;
|
|
1076
|
+
gap: 1px;
|
|
1077
|
+
padding: 1px;
|
|
1078
|
+
border-radius: 4px;
|
|
1079
|
+
background: var(--perf-row);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.perf-monitor__segment-option {
|
|
1083
|
+
display: flex;
|
|
1084
|
+
align-items: center;
|
|
1085
|
+
justify-content: center;
|
|
1086
|
+
width: 20px;
|
|
1087
|
+
height: 16px;
|
|
1088
|
+
padding: 0;
|
|
1089
|
+
border: 0;
|
|
1090
|
+
border-radius: 3px;
|
|
1091
|
+
background: transparent;
|
|
1092
|
+
color: var(--perf-muted);
|
|
1093
|
+
cursor: pointer;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
.perf-monitor__segment-option:hover {
|
|
1097
|
+
color: var(--perf-fg);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
.perf-monitor__segment-option[aria-checked="true"] {
|
|
1101
|
+
color: var(--perf-fg);
|
|
1102
|
+
background: var(--perf-border);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.perf-monitor__segment-option:focus-visible {
|
|
1106
|
+
outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
|
|
1107
|
+
outline-offset: 1px;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
.perf-monitor__segment-option .perf-monitor__icon {
|
|
1111
|
+
width: 11px;
|
|
1112
|
+
height: 11px;
|
|
1113
|
+
color: inherit;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
989
1116
|
.perf-monitor__checkbox {
|
|
990
1117
|
flex: none;
|
|
991
1118
|
margin: 0;
|
|
@@ -1212,7 +1339,6 @@ const mountPerfHud = (monitor, options = {}) => {
|
|
|
1212
1339
|
host.className = "perf-hud";
|
|
1213
1340
|
host.dataset.edge = "left";
|
|
1214
1341
|
host.setAttribute("aria-label", options.label ?? "Performance");
|
|
1215
|
-
applyTheme(host, theme);
|
|
1216
1342
|
const hotspot = document.createElement("div");
|
|
1217
1343
|
hotspot.className = "perf-hud__hotspot";
|
|
1218
1344
|
hotspot.setAttribute("aria-hidden", "true");
|
|
@@ -1238,6 +1364,7 @@ const mountPerfHud = (monitor, options = {}) => {
|
|
|
1238
1364
|
};
|
|
1239
1365
|
applyMode();
|
|
1240
1366
|
applyDim();
|
|
1367
|
+
applyTheme(host, theme);
|
|
1241
1368
|
const unsubscribe = settings.subscribe(applyDim);
|
|
1242
1369
|
const unsubscribeTheme = theme.subscribe(() => applyTheme(host, theme));
|
|
1243
1370
|
const dock = dockPanel(host, {
|
|
@@ -1305,6 +1432,6 @@ const mountPerfHud = (monitor, options = {}) => {
|
|
|
1305
1432
|
};
|
|
1306
1433
|
};
|
|
1307
1434
|
//#endregion
|
|
1308
|
-
export {
|
|
1435
|
+
export { dockPanel as a, HudTheme as c, PerformanceView as i, THEME_MODES as l, PERF_HUD_STYLES as n, DEFAULT_STORAGE_KEY as o, injectStyles as r, HudSettings as s, mountPerfHud as t, isThemeMode as u };
|
|
1309
1436
|
|
|
1310
|
-
//# sourceMappingURL=mount-perf-hud-
|
|
1437
|
+
//# sourceMappingURL=mount-perf-hud-CZNSZVYn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount-perf-hud-CZNSZVYn.js","names":[],"sources":["../src/ui/theme.ts","../src/ui/hud-settings.ts","../src/ui/dock-panel.ts","../src/ui/icons.ts","../src/ui/sparkline.ts","../src/ui/performance-view.ts","../src/ui/styles.ts","../src/ui/mount-perf-hud.ts"],"sourcesContent":["/**\n * Dark, light, or follow the OS. One instance per HUD: the floating host and\n * the card inside it both paint `resolved` onto `data-theme`, and the\n * stylesheet picks the palette from that attribute.\n *\n * Two layers. `mode` is what the consumer asked for (the `theme` option,\n * `setTheme`). `override` is what the person using the HUD picked in the\n * panel, kept in `HudSettings`; it wins while set. `effective` is whichever\n * applies, and `system` there tracks `prefers-color-scheme` for as long as\n * the instance lives.\n */\ntype ThemeMode = \"dark\" | \"light\" | \"system\";\ntype ResolvedTheme = Exclude<ThemeMode, \"system\">;\n\nconst THEME_MODES: readonly ThemeMode[] = [\"dark\", \"light\", \"system\"];\nconst DARK_SCHEME_QUERY = \"(prefers-color-scheme: dark)\";\n\nconst isThemeMode = (value: unknown): value is ThemeMode =>\n typeof value === \"string\" && (THEME_MODES as readonly string[]).includes(value);\n\nclass HudTheme {\n private current: ThemeMode;\n private userPick: ThemeMode | null = null;\n private readonly listeners = new Set<() => void>();\n private media: MediaQueryList | null = null;\n private readonly onSchemeChange = () => this.notify();\n\n constructor(mode: ThemeMode = \"system\") {\n this.current = mode;\n this.watch();\n }\n\n /** What the consumer asked for. */\n get mode(): ThemeMode {\n return this.current;\n }\n\n /** What the person using the HUD picked in the panel, or `null` for no pick. */\n get override(): ThemeMode | null {\n return this.userPick;\n }\n\n /** The layer that applies: the user's pick if there is one, else the consumer's mode. */\n get effective(): ThemeMode {\n return this.userPick ?? this.current;\n }\n\n /**\n * `dark` or `light`, with `system` resolved against the OS preference.\n * Where `matchMedia` is missing, `system` falls back to `dark`, the HUD's\n * original palette.\n */\n get resolved(): ResolvedTheme {\n const effective = this.effective;\n\n if (effective !== \"system\") {\n return effective;\n }\n\n if (!this.media) {\n return \"dark\";\n }\n\n return this.media.matches ? \"dark\" : \"light\";\n }\n\n setMode(mode: ThemeMode) {\n if (mode === this.current) {\n return;\n }\n\n this.current = mode;\n this.watch();\n this.notify();\n }\n\n setOverride(mode: ThemeMode | null) {\n if (mode === this.userPick) {\n return;\n }\n\n this.userPick = mode;\n this.watch();\n this.notify();\n }\n\n /** Fires on `setMode`, `setOverride` and, in `system`, whenever the OS preference flips. */\n subscribe(listener: () => void) {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** Stop tracking the OS preference and drop every subscriber. */\n dispose() {\n this.unwatch();\n this.listeners.clear();\n }\n\n private watch() {\n if (this.effective !== \"system\") {\n this.unwatch();\n\n return;\n }\n\n if (this.media || typeof matchMedia !== \"function\") {\n return;\n }\n\n this.media = matchMedia(DARK_SCHEME_QUERY);\n this.media.addEventListener(\"change\", this.onSchemeChange);\n }\n\n private unwatch() {\n this.media?.removeEventListener(\"change\", this.onSchemeChange);\n this.media = null;\n }\n\n private notify() {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n\n/** Paint the resolved theme onto an element for the stylesheet to key off. */\nconst applyTheme = (element: HTMLElement, theme: HudTheme) => {\n element.dataset.theme = theme.resolved;\n};\n\nexport { applyTheme, HudTheme, isThemeMode, THEME_MODES };\nexport type { ResolvedTheme, ThemeMode };\n","/**\n * Which metrics the compact HUD shows, the dim-on-leave toggle, and the theme\n * the person using the HUD picked in the panel (`null` until they pick one,\n * so the consumer's `theme` option applies). One instance per HUD; the\n * full-mode controls and the compact card stay in sync through it. Persists\n * to `localStorage` under `storageKey` (pass `null` to keep it in memory only).\n */\nimport type { TimingMetric } from \"../core/types.ts\";\nimport { isThemeMode, type ThemeMode } from \"./theme.ts\";\n\ntype HudSelection = {\n dim: boolean;\n graphs: ReadonlySet<TimingMetric>;\n numbers: ReadonlySet<string>;\n theme: ThemeMode | null;\n};\n\ntype HudDefaults = {\n dim?: boolean;\n graphs?: readonly TimingMetric[];\n numbers?: readonly string[];\n};\n\ntype HudSettingsOptions = {\n /** localStorage key. Default `three-meter`; `null` disables persistence. */\n storageKey?: string | null;\n /** What a first visit shows. */\n defaults?: HudDefaults;\n};\n\ntype MutableSelection = {\n dim: boolean;\n graphs: Set<TimingMetric>;\n numbers: Set<string>;\n theme: ThemeMode | null;\n};\n\nconst DEFAULT_STORAGE_KEY = \"three-meter\";\nconst DEFAULT_DIM = false;\nconst DEFAULT_NUMBERS: readonly string[] = [\"fps\", \"calls\", \"cpu\", \"gpu\"];\nconst DEFAULT_GRAPHS: readonly TimingMetric[] = [];\nconst TIMING_METRICS = new Set<TimingMetric>([\"fps\", \"cpu\", \"gpu\"]);\n\nconst isTimingMetric = (value: string): value is TimingMetric =>\n TIMING_METRICS.has(value as TimingMetric);\n\nclass HudSettings {\n private readonly storageKey: string | null;\n private readonly defaults: Required<HudDefaults>;\n private readonly listeners = new Set<() => void>();\n private readonly state: MutableSelection;\n\n constructor(options: HudSettingsOptions = {}) {\n this.storageKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n this.defaults = {\n dim: options.defaults?.dim ?? DEFAULT_DIM,\n graphs: options.defaults?.graphs ?? DEFAULT_GRAPHS,\n numbers: options.defaults?.numbers ?? DEFAULT_NUMBERS,\n };\n this.state = this.load();\n }\n\n get selection(): HudSelection {\n return this.state;\n }\n\n get dim() {\n return this.state.dim;\n }\n\n setDim(enabled: boolean) {\n this.state.dim = enabled;\n this.persistAndNotify();\n }\n\n /** The user's pick in the panel; `null` means none, so the consumer's option applies. */\n get theme() {\n return this.state.theme;\n }\n\n setTheme(mode: ThemeMode | null) {\n this.state.theme = mode;\n this.persistAndNotify();\n }\n\n isNumberEnabled(key: string) {\n return this.state.numbers.has(key);\n }\n\n isGraphEnabled(metric: TimingMetric) {\n return this.state.graphs.has(metric);\n }\n\n setNumberEnabled(key: string, enabled: boolean) {\n if (enabled) {\n this.state.numbers.add(key);\n } else {\n this.state.numbers.delete(key);\n }\n\n this.persistAndNotify();\n }\n\n setGraphEnabled(metric: TimingMetric, enabled: boolean) {\n if (enabled) {\n this.state.graphs.add(metric);\n } else {\n this.state.graphs.delete(metric);\n }\n\n this.persistAndNotify();\n }\n\n subscribe(listener: () => void) {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private load(): MutableSelection {\n const fallback = (): MutableSelection => ({\n dim: this.defaults.dim,\n graphs: new Set(this.defaults.graphs),\n numbers: new Set(this.defaults.numbers),\n theme: null,\n });\n\n if (this.storageKey === null) {\n return fallback();\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n\n if (!raw) {\n return fallback();\n }\n\n const parsed = JSON.parse(raw) as {\n dim?: boolean;\n graphs?: string[];\n numbers?: string[];\n theme?: unknown;\n };\n\n return {\n dim: parsed.dim ?? this.defaults.dim,\n graphs: new Set((parsed.graphs ?? this.defaults.graphs).filter(isTimingMetric)),\n numbers: new Set(parsed.numbers ?? this.defaults.numbers),\n theme: isThemeMode(parsed.theme) ? parsed.theme : null,\n };\n } catch {\n // Malformed or unavailable storage → defaults.\n return fallback();\n }\n }\n\n private persistAndNotify() {\n if (this.storageKey !== null) {\n try {\n localStorage.setItem(\n this.storageKey,\n JSON.stringify({\n dim: this.state.dim,\n graphs: [...this.state.graphs],\n numbers: [...this.state.numbers],\n // Omitted while null: JSON.stringify drops undefined.\n theme: this.state.theme ?? undefined,\n }),\n );\n } catch {\n // In-memory selection still drives the live views.\n }\n }\n\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n\nexport { DEFAULT_STORAGE_KEY, HudSettings };\nexport type { HudDefaults, HudSelection, HudSettingsOptions };\n","/**\n * Dock a fixed-position panel to a screen edge. Dragging the handle snaps it\n * to the nearest edge on drop; the edge and the offset along it persist under\n * `<storageKey>:placement`. A stored box whose centre sits outside the current\n * viewport is re-docked to whichever edge that box is closest to.\n */\nimport { DEFAULT_STORAGE_KEY } from \"./hud-settings.ts\";\n\nconst INSET = 10;\n/** Room for the stacked control discs on the inward side of the panel. */\nconst SIDE_CLEARANCE = 26;\nconst DRAG_THRESHOLD = 4;\nconst EDGES = [\"left\", \"right\", \"top\", \"bottom\"] as const;\n\ntype ScreenEdge = (typeof EDGES)[number];\n\ntype Placement = {\n /** Top (left/right) or left (top/bottom) of the host, in CSS pixels. */\n along: number;\n edge: ScreenEdge;\n};\n\ntype Box = {\n height: number;\n left: number;\n top: number;\n width: number;\n};\n\ntype DockAlign = \"start\" | \"center\" | \"end\";\n\ntype DefaultPlacement = {\n edge: ScreenEdge;\n /** Where along that edge on a first visit. Default `center`. */\n align?: DockAlign;\n};\n\ntype DockPanelOptions = {\n /** The drag grip. Without one the panel still docks and re-docks on resize. */\n handle?: HTMLElement | null;\n /** First-visit position, before anything is stored. Default left edge, centred. */\n defaultPlacement?: DefaultPlacement;\n /** Shares the HUD's key; `null` disables persistence. Default `three-meter`. */\n storageKey?: string | null;\n};\n\ntype DockHandle = {\n /** Re-resolve against the current viewport and panel size (after a mode change, say). */\n refresh: () => Placement;\n dispose: () => void;\n};\n\nconst viewportBox = (): Box => ({\n height: window.innerHeight,\n left: 0,\n top: 0,\n width: window.innerWidth,\n});\n\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst panelBox = (element: HTMLElement): Box => {\n const rect = element.getBoundingClientRect();\n\n return {\n height: rect.height || element.offsetHeight,\n left: rect.left,\n top: rect.top,\n width: rect.width || element.offsetWidth,\n };\n};\n\nconst clampAlong = (edge: ScreenEdge, along: number, view: Box, panel: Box) => {\n if (edge === \"left\" || edge === \"right\") {\n return clamp(along, INSET, Math.max(INSET, view.height - panel.height - INSET));\n }\n\n // Tools sit on the right (inward) unless the panel is on the right edge.\n return clamp(along, INSET, Math.max(INSET, view.width - panel.width - INSET - SIDE_CLEARANCE));\n};\n\nconst ghostBox = (placement: Placement, panel: Box, view: Box): Box => {\n switch (placement.edge) {\n case \"left\":\n return { height: panel.height, left: INSET, top: placement.along, width: panel.width };\n case \"right\":\n return {\n height: panel.height,\n left: view.width - INSET - panel.width,\n top: placement.along,\n width: panel.width,\n };\n case \"top\":\n return { height: panel.height, left: placement.along, top: INSET, width: panel.width };\n case \"bottom\":\n return {\n height: panel.height,\n left: placement.along,\n top: view.height - INSET - panel.height,\n width: panel.width,\n };\n }\n};\n\nconst centerOutside = (box: Box, view: Box) => {\n const x = box.left + box.width / 2;\n const y = box.top + box.height / 2;\n\n return x < 0 || y < 0 || x > view.width || y > view.height;\n};\n\nconst snapBoxToEdge = (box: Box, view: Box): Placement => {\n const distances: Record<ScreenEdge, number> = {\n bottom: view.height - (box.top + box.height),\n left: box.left,\n right: view.width - (box.left + box.width),\n top: box.top,\n };\n\n let edge: ScreenEdge = \"left\";\n let nearest = Number.POSITIVE_INFINITY;\n\n for (const next of EDGES) {\n if (distances[next] < nearest) {\n nearest = distances[next];\n edge = next;\n }\n }\n\n const along = edge === \"left\" || edge === \"right\" ? box.top : box.left;\n\n return { along: Math.round(clampAlong(edge, along, view, box)), edge };\n};\n\nconst initialPlacement = (preset: DefaultPlacement, view: Box, panel: Box): Placement => {\n const { edge } = preset;\n const vertical = edge === \"left\" || edge === \"right\";\n const span = vertical ? view.height : view.width;\n const size = vertical ? panel.height : panel.width;\n const along =\n preset.align === \"start\" ? 0 : preset.align === \"end\" ? span - size : (span - size) / 2;\n\n return { along: Math.round(clampAlong(edge, along, view, panel)), edge };\n};\n\nconst isEdge = (value: unknown): value is ScreenEdge =>\n typeof value === \"string\" && (EDGES as readonly string[]).includes(value);\n\nconst readPlacement = (key: string | null): Placement | null => {\n if (key === null) {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(key);\n\n if (!raw) {\n return null;\n }\n\n const parsed = JSON.parse(raw) as { along?: unknown; edge?: unknown };\n\n if (\n typeof parsed.along !== \"number\" ||\n !Number.isFinite(parsed.along) ||\n !isEdge(parsed.edge)\n ) {\n return null;\n }\n\n return { along: parsed.along, edge: parsed.edge };\n } catch {\n return null;\n }\n};\n\nconst writePlacement = (key: string | null, placement: Placement) => {\n if (key === null) {\n return;\n }\n\n try {\n localStorage.setItem(key, JSON.stringify(placement));\n } catch {\n // Best-effort; the live dock still applies.\n }\n};\n\nconst resolvePlacement = (\n stored: Placement | null,\n preset: DefaultPlacement,\n panel: Box,\n view: Box,\n): Placement => {\n if (!stored) {\n return initialPlacement(preset, view, panel);\n }\n\n const ghost = ghostBox(stored, panel, view);\n\n if (centerOutside(ghost, view)) {\n return snapBoxToEdge(ghost, view);\n }\n\n return {\n along: Math.round(clampAlong(stored.edge, stored.along, view, panel)),\n edge: stored.edge,\n };\n};\n\nconst paintPlacement = (element: HTMLElement, placement: Placement) => {\n element.dataset.edge = placement.edge;\n element.style.left = \"\";\n element.style.right = \"\";\n element.style.top = \"\";\n element.style.bottom = \"\";\n element.style.transform = \"\";\n\n switch (placement.edge) {\n case \"left\":\n element.style.left = `${INSET}px`;\n element.style.top = `${placement.along}px`;\n break;\n case \"right\":\n element.style.right = `${INSET}px`;\n element.style.top = `${placement.along}px`;\n break;\n case \"top\":\n element.style.top = `${INSET}px`;\n element.style.left = `${placement.along}px`;\n break;\n case \"bottom\":\n element.style.bottom = `${INSET}px`;\n element.style.left = `${placement.along}px`;\n break;\n }\n};\n\n/**\n * Dock `element` from storage (or the left centre), re-dock on resize, and\n * snap to the nearest edge when the handle drops.\n */\nconst dockPanel = (element: HTMLElement, options: DockPanelOptions = {}): DockHandle => {\n const baseKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n const key = baseKey === null ? null : `${baseKey}:placement`;\n const handle = options.handle ?? null;\n const preset = options.defaultPlacement ?? { edge: \"left\" };\n\n const apply = (): Placement => {\n const stored = readPlacement(key);\n const view = viewportBox();\n\n if (element.classList.contains(\"is-dragging\")) {\n return stored ?? initialPlacement(preset, view, panelBox(element));\n }\n\n const panel = panelBox(element);\n\n if (panel.width === 0 || panel.height === 0) {\n return stored ?? initialPlacement(preset, view, panel);\n }\n\n const next = resolvePlacement(stored, preset, panel, view);\n paintPlacement(element, next);\n\n if (!stored || stored.edge !== next.edge || stored.along !== next.along) {\n writePlacement(key, next);\n }\n\n return next;\n };\n\n apply();\n requestAnimationFrame(apply);\n\n const onResize = () => apply();\n window.addEventListener(\"resize\", onResize);\n\n const resizeObserver = new ResizeObserver(() => apply());\n resizeObserver.observe(element);\n\n const disposeBase = () => {\n window.removeEventListener(\"resize\", onResize);\n resizeObserver.disconnect();\n };\n\n if (!handle) {\n return { dispose: disposeBase, refresh: apply };\n }\n\n let pointerId: number | null = null;\n let originX = 0;\n let originY = 0;\n let grabX = 0;\n let grabY = 0;\n let dragging = false;\n\n const finish = (event: PointerEvent) => {\n if (pointerId !== event.pointerId) {\n return;\n }\n\n if (dragging) {\n const next = snapBoxToEdge(panelBox(element), viewportBox());\n paintPlacement(element, next);\n writePlacement(key, next);\n }\n\n dragging = false;\n pointerId = null;\n element.classList.remove(\"is-dragging\");\n\n try {\n handle.releasePointerCapture(event.pointerId);\n } catch {\n // Capture was never set.\n }\n };\n\n const onPointerDown = (event: PointerEvent) => {\n if (event.button !== 0) {\n return;\n }\n\n const box = panelBox(element);\n pointerId = event.pointerId;\n originX = event.clientX;\n originY = event.clientY;\n grabX = event.clientX - box.left;\n grabY = event.clientY - box.top;\n dragging = false;\n handle.setPointerCapture(event.pointerId);\n };\n\n const onPointerMove = (event: PointerEvent) => {\n if (pointerId !== event.pointerId) {\n return;\n }\n\n const dx = event.clientX - originX;\n const dy = event.clientY - originY;\n\n if (!dragging && dx * dx + dy * dy < DRAG_THRESHOLD * DRAG_THRESHOLD) {\n return;\n }\n\n dragging = true;\n element.classList.add(\"is-dragging\");\n element.style.right = \"\";\n element.style.bottom = \"\";\n element.style.transform = \"\";\n element.style.left = `${event.clientX - grabX}px`;\n element.style.top = `${event.clientY - grabY}px`;\n };\n\n handle.addEventListener(\"pointerdown\", onPointerDown);\n handle.addEventListener(\"pointermove\", onPointerMove);\n handle.addEventListener(\"pointerup\", finish);\n handle.addEventListener(\"pointercancel\", finish);\n\n return {\n dispose: () => {\n disposeBase();\n handle.removeEventListener(\"pointerdown\", onPointerDown);\n handle.removeEventListener(\"pointermove\", onPointerMove);\n handle.removeEventListener(\"pointerup\", finish);\n handle.removeEventListener(\"pointercancel\", finish);\n },\n refresh: apply,\n };\n};\n\nexport { dockPanel };\nexport type { DefaultPlacement, DockAlign, DockHandle, DockPanelOptions, Placement, ScreenEdge };\n","/**\n * The Lucide marks the HUD chrome uses, inlined so the package carries no\n * icon dependency. The markup is static, so nothing user-supplied reaches it.\n */\ntype IconName = \"blend\" | \"contrast\" | \"grip\" | \"monitor\" | \"moon\" | \"sliders\" | \"sun\";\n\nconst ICON_MARKUP: Record<IconName, string> = {\n blend: '<circle cx=\"9\" cy=\"9\" r=\"7\"/><circle cx=\"15\" cy=\"15\" r=\"7\"/>',\n contrast: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 18a6 6 0 0 0 0-12v12z\"/>',\n grip:\n '<circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"5\" r=\"1\"/><circle cx=\"9\" cy=\"19\" r=\"1\"/>' +\n '<circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"5\" r=\"1\"/><circle cx=\"15\" cy=\"19\" r=\"1\"/>',\n monitor:\n '<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/>' +\n '<line x1=\"8\" x2=\"16\" y1=\"21\" y2=\"21\"/><line x1=\"12\" x2=\"12\" y1=\"17\" y2=\"21\"/>',\n moon: '<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/>',\n sun:\n '<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/>' +\n '<path d=\"m4.93 4.93 1.41 1.41\"/><path d=\"m17.66 17.66 1.41 1.41\"/>' +\n '<path d=\"M2 12h2\"/><path d=\"M20 12h2\"/>' +\n '<path d=\"m6.34 17.66-1.41 1.41\"/><path d=\"m19.07 4.93-1.41 1.41\"/>',\n sliders:\n '<line x1=\"21\" x2=\"14\" y1=\"4\" y2=\"4\"/><line x1=\"10\" x2=\"3\" y1=\"4\" y2=\"4\"/>' +\n '<line x1=\"21\" x2=\"12\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"3\" y1=\"12\" y2=\"12\"/>' +\n '<line x1=\"21\" x2=\"16\" y1=\"20\" y2=\"20\"/><line x1=\"12\" x2=\"3\" y1=\"20\" y2=\"20\"/>' +\n '<line x1=\"14\" x2=\"14\" y1=\"2\" y2=\"6\"/><line x1=\"8\" x2=\"8\" y1=\"10\" y2=\"14\"/>' +\n '<line x1=\"16\" x2=\"16\" y1=\"18\" y2=\"22\"/>',\n};\n\nconst createIcon = (name: IconName, className: string) => {\n const svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n svg.setAttribute(\"viewBox\", \"0 0 24 24\");\n svg.setAttribute(\"fill\", \"none\");\n svg.setAttribute(\"stroke\", \"currentColor\");\n svg.setAttribute(\"stroke-width\", \"2\");\n svg.setAttribute(\"stroke-linecap\", \"round\");\n svg.setAttribute(\"stroke-linejoin\", \"round\");\n svg.setAttribute(\"aria-hidden\", \"true\");\n svg.setAttribute(\"class\", className);\n svg.innerHTML = ICON_MARKUP[name];\n\n return svg;\n};\n\nexport { createIcon };\nexport type { IconName };\n","/**\n * Minimal canvas sparkline. The caller owns the canvas, its size, and any\n * device-pixel-ratio transform; this just paints a filled line series in\n * logical (CSS) pixel coordinates.\n */\ntype SparklineStyle = {\n fill: string;\n stroke: string;\n};\n\nfunction drawSparkline(\n ctx: CanvasRenderingContext2D,\n width: number,\n height: number,\n values: readonly number[],\n style: SparklineStyle,\n) {\n ctx.clearRect(0, 0, width, height);\n\n if (values.length < 2) {\n return;\n }\n\n let max = 0;\n\n for (const value of values) {\n if (value > max) {\n max = value;\n }\n }\n\n const range = max * 1.1 || 1;\n const stepX = width / (values.length - 1);\n const toY = (value: number) => height - (value / range) * height;\n\n ctx.beginPath();\n ctx.moveTo(0, toY(values[0]!));\n\n for (let index = 1; index < values.length; index += 1) {\n ctx.lineTo(index * stepX, toY(values[index]!));\n }\n\n ctx.lineWidth = 1;\n ctx.strokeStyle = style.stroke;\n ctx.stroke();\n\n ctx.lineTo(width, height);\n ctx.lineTo(0, height);\n ctx.closePath();\n ctx.fillStyle = style.fill;\n ctx.fill();\n}\n\nexport { drawSparkline };\nexport type { SparklineStyle };\n","import type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { Sample, TimingMetric } from \"../core/types.ts\";\nimport { HudSettings } from \"./hud-settings.ts\";\nimport { createIcon } from \"./icons.ts\";\nimport { drawSparkline, type SparklineStyle } from \"./sparkline.ts\";\nimport { applyTheme, HudTheme, type ResolvedTheme, type ThemeMode } from \"./theme.ts\";\n\ntype PerformanceViewMode = \"full\" | \"compact\";\n\ntype PerformanceViewOptions = {\n monitor: PerformanceMonitor;\n /** `full` is the checkbox list; `compact` the card it configures. Default `full`. */\n mode?: PerformanceViewMode;\n /** Repaint rate. Default 10. */\n refreshHz?: number;\n /** Share one between views to keep them in sync; a fresh instance otherwise. */\n settings?: HudSettings;\n /**\n * Share the host's so the card and its chrome switch together. Otherwise a\n * fresh `system` instance is created and disposed with the view.\n */\n theme?: HudTheme;\n};\n\ntype TimingConfig = {\n format: (value: number) => string;\n label: string;\n metric: TimingMetric;\n /** Canvas can't read CSS custom properties, so the sparkline palette lives here per theme. */\n style: Record<ResolvedTheme, SparklineStyle>;\n unit: string;\n};\n\ntype NumberConfig = {\n key: string;\n label: string;\n read: (sample: Sample) => string;\n};\n\nconst TIMINGS: TimingConfig[] = [\n {\n format: (value) => Math.round(value).toString(),\n label: \"FPS\",\n metric: \"fps\",\n style: {\n dark: { fill: \"rgba(74, 222, 128, 0.15)\", stroke: \"#4ade80\" },\n light: { fill: \"rgba(22, 163, 74, 0.12)\", stroke: \"#16a34a\" },\n },\n unit: \"\",\n },\n {\n format: (value) => value.toFixed(1),\n label: \"CPU\",\n metric: \"cpu\",\n style: {\n dark: { fill: \"rgba(96, 165, 250, 0.15)\", stroke: \"#60a5fa\" },\n light: { fill: \"rgba(37, 99, 235, 0.12)\", stroke: \"#2563eb\" },\n },\n unit: \"ms\",\n },\n {\n format: (value) => value.toFixed(1),\n label: \"GPU\",\n metric: \"gpu\",\n style: {\n dark: { fill: \"rgba(244, 114, 182, 0.15)\", stroke: \"#f472b6\" },\n light: { fill: \"rgba(219, 39, 119, 0.12)\", stroke: \"#db2777\" },\n },\n unit: \"ms\",\n },\n];\n\nconst THEME_OPTIONS: { icon: \"sun\" | \"monitor\" | \"moon\"; label: string; mode: ThemeMode }[] = [\n { icon: \"sun\", label: \"Light theme\", mode: \"light\" },\n { icon: \"monitor\", label: \"Match the system theme\", mode: \"system\" },\n { icon: \"moon\", label: \"Dark theme\", mode: \"dark\" },\n];\n\nconst formatCount = (value: number) => value.toLocaleString(\"en-US\");\n\nconst readTiming = (sample: Sample, metric: TimingMetric) => {\n switch (metric) {\n case \"fps\":\n return sample.fps;\n case \"cpu\":\n return sample.cpu;\n case \"gpu\":\n return sample.gpu.ms;\n }\n};\n\nconst isTimingAvailable = (sample: Sample, metric: TimingMetric) =>\n metric !== \"gpu\" || sample.gpu.available;\n\nconst NUMBERS: NumberConfig[] = [\n { key: \"fps\", label: \"FPS\", read: (sample) => Math.round(sample.fps).toString() },\n { key: \"calls\", label: \"Calls\", read: (sample) => formatCount(sample.render.calls) },\n { key: \"cpu\", label: \"CPU\", read: (sample) => sample.cpu.toFixed(1) },\n {\n key: \"gpu\",\n label: \"GPU\",\n read: (sample) => (sample.gpu.available ? sample.gpu.ms.toFixed(1) : \"—\"),\n },\n {\n key: \"triangles\",\n label: \"Triangles\",\n read: (sample) => formatCount(sample.render.triangles),\n },\n { key: \"lines\", label: \"Lines\", read: (sample) => formatCount(sample.render.lines) },\n { key: \"points\", label: \"Points\", read: (sample) => formatCount(sample.render.points) },\n {\n key: \"passes\",\n label: \"Render passes\",\n read: (sample) => formatCount(sample.render.passes),\n },\n {\n key: \"geometries\",\n label: \"Geometries\",\n read: (sample) => formatCount(sample.resources.geometries),\n },\n {\n key: \"textures\",\n label: \"Textures\",\n read: (sample) => formatCount(sample.resources.textures),\n },\n {\n key: \"shaders\",\n label: \"Shaders\",\n read: (sample) => formatCount(sample.resources.programs),\n },\n];\n\ntype GraphCanvas = {\n canvas: HTMLCanvasElement;\n ctx: CanvasRenderingContext2D;\n height: number;\n width: number;\n};\n\ntype HudGraph = GraphCanvas & {\n valueEl: HTMLElement;\n};\n\n/**\n * Host-agnostic widget that paints {@link Sample}s. Mount {@link element}\n * anywhere; the view drives its own throttled repaint. Compact is the floating\n * HUD; full is the checkbox list that chooses what compact shows. Styles are\n * not injected here. Call `injectStyles()` or use `mountPerfHud`.\n */\nclass PerformanceView {\n readonly element: HTMLDivElement;\n readonly settings: HudSettings;\n readonly theme: HudTheme;\n\n private readonly ownsTheme: boolean;\n private readonly unsubscribeTheme: () => void;\n private readonly monitor: PerformanceMonitor;\n private readonly minIntervalMs: number;\n private mode: PerformanceViewMode;\n private readonly graphValueEls = new Map<TimingMetric, HTMLElement>();\n private readonly graphCanvases = new Map<TimingMetric, GraphCanvas>();\n private readonly graphCheckboxes = new Map<TimingMetric, HTMLInputElement>();\n private readonly statValueEls = new Map<string, HTMLElement>();\n private readonly statCheckboxes = new Map<string, HTMLInputElement>();\n private dimCheckbox!: HTMLInputElement;\n private readonly themeRadios = new Map<ThemeMode, HTMLButtonElement>();\n private hudGraphsEl!: HTMLElement;\n private hudGridEl!: HTMLElement;\n private readonly hudGraphs = new Map<TimingMetric, HudGraph>();\n private readonly hudNumberEls = new Map<string, HTMLElement>();\n private rafId: number | null = null;\n private lastPaintAt = 0;\n private readonly resizeObserver: ResizeObserver;\n private readonly unsubscribe: () => void;\n\n constructor(options: PerformanceViewOptions) {\n this.monitor = options.monitor;\n this.settings = options.settings ?? new HudSettings();\n this.ownsTheme = !options.theme;\n this.theme = options.theme ?? new HudTheme();\n // The panel's pick, restored from storage, sits on top of the consumer's mode.\n this.theme.setOverride(this.settings.theme);\n this.mode = options.mode ?? \"full\";\n this.minIntervalMs = 1000 / (options.refreshHz ?? 10);\n this.element = document.createElement(\"div\");\n this.element.className = \"perf-monitor\";\n this.applyModeClass();\n this.resizeObserver = new ResizeObserver(() => this.resizeCanvases());\n this.build();\n this.rebuildHud();\n this.onThemeChanged();\n this.unsubscribe = this.settings.subscribe(() => this.onSelectionChanged());\n this.unsubscribeTheme = this.theme.subscribe(() => this.onThemeChanged());\n }\n\n start() {\n if (this.rafId === null) {\n this.rafId = requestAnimationFrame(this.frame);\n }\n }\n\n stop() {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n setMode(mode: PerformanceViewMode) {\n if (mode === this.mode) {\n return;\n }\n\n this.mode = mode;\n this.applyModeClass();\n this.resizeCanvases();\n }\n\n getMode() {\n return this.mode;\n }\n\n dispose() {\n this.stop();\n this.unsubscribe();\n this.unsubscribeTheme();\n\n if (this.ownsTheme) {\n this.theme.dispose();\n }\n\n this.resizeObserver.disconnect();\n this.element.remove();\n }\n\n private frame = (now: number) => {\n this.rafId = requestAnimationFrame(this.frame);\n\n if (now - this.lastPaintAt < this.minIntervalMs) {\n return;\n }\n\n this.lastPaintAt = now;\n this.render();\n };\n\n private render() {\n const sample = this.monitor.getSample();\n\n if (this.mode === \"compact\") {\n this.renderHud(sample);\n\n return;\n }\n\n for (const config of TIMINGS) {\n const available = isTimingAvailable(sample, config.metric);\n const valueEl = this.graphValueEls.get(config.metric)!;\n valueEl.textContent = available\n ? this.formatTiming(config, readTiming(sample, config.metric))\n : \"unavailable\";\n\n const graph = this.graphCanvases.get(config.metric)!;\n drawSparkline(\n graph.ctx,\n graph.width,\n graph.height,\n available ? this.monitor.getHistory(config.metric) : [],\n config.style[this.theme.resolved],\n );\n }\n\n for (const config of NUMBERS) {\n this.statValueEls.get(config.key)!.textContent = config.read(sample);\n }\n }\n\n private renderHud(sample: Sample) {\n for (const [key, element] of this.hudNumberEls) {\n const config = NUMBERS.find((number) => number.key === key);\n\n if (config) {\n element.textContent = config.read(sample);\n }\n }\n\n for (const [metric, graph] of this.hudGraphs) {\n const config = TIMINGS.find((timing) => timing.metric === metric)!;\n const available = isTimingAvailable(sample, metric);\n\n graph.valueEl.textContent = available\n ? this.formatTiming(config, readTiming(sample, metric))\n : \"—\";\n\n drawSparkline(\n graph.ctx,\n graph.width,\n graph.height,\n available ? this.monitor.getHistory(metric) : [],\n config.style[this.theme.resolved],\n );\n }\n }\n\n private formatTiming(config: TimingConfig, value: number) {\n return `${config.format(value)}${config.unit ? ` ${config.unit}` : \"\"}`;\n }\n\n private build() {\n const hud = document.createElement(\"div\");\n hud.className = \"perf-monitor__hud\";\n this.hudGraphsEl = document.createElement(\"div\");\n this.hudGraphsEl.className = \"perf-monitor__hud-graphs\";\n this.hudGridEl = document.createElement(\"div\");\n this.hudGridEl.className = \"perf-monitor__hud-grid\";\n hud.append(this.hudGraphsEl, this.hudGridEl);\n\n // Option rows read icon, label, control; the stat rows below keep the\n // checkbox first, so the two groups scan differently on purpose.\n const options = document.createElement(\"div\");\n options.className = \"perf-monitor__section perf-monitor__options\";\n\n const themeRow = document.createElement(\"div\");\n themeRow.className = \"perf-monitor__row perf-monitor__row--static\";\n\n const themeLabel = document.createElement(\"span\");\n themeLabel.className = \"perf-monitor__label\";\n themeLabel.textContent = \"theme\";\n\n const segment = document.createElement(\"div\");\n segment.className = \"perf-monitor__segment\";\n segment.setAttribute(\"role\", \"radiogroup\");\n segment.setAttribute(\"aria-label\", \"Panel theme\");\n\n for (const option of THEME_OPTIONS) {\n const radio = document.createElement(\"button\");\n radio.type = \"button\";\n radio.className = \"perf-monitor__segment-option\";\n radio.setAttribute(\"role\", \"radio\");\n radio.setAttribute(\"aria-checked\", \"false\");\n radio.setAttribute(\"aria-label\", option.label);\n radio.title = option.label;\n radio.append(createIcon(option.icon, \"perf-monitor__icon\"));\n radio.addEventListener(\"click\", () => this.settings.setTheme(option.mode));\n this.themeRadios.set(option.mode, radio);\n segment.append(radio);\n }\n\n themeRow.append(createIcon(\"contrast\", \"perf-monitor__icon\"), themeLabel, segment);\n\n const dimRow = document.createElement(\"label\");\n dimRow.className = \"perf-monitor__row\";\n\n this.dimCheckbox = this.buildCheckbox(\n this.settings.dim,\n \"Dim the performance panel when the pointer leaves\",\n (on) => this.settings.setDim(on),\n );\n\n const dimLabel = document.createElement(\"span\");\n dimLabel.className = \"perf-monitor__label\";\n dimLabel.textContent = \"dim on leave\";\n\n dimRow.append(createIcon(\"blend\", \"perf-monitor__icon\"), dimLabel, this.dimCheckbox);\n options.append(themeRow, dimRow);\n\n const graphs = document.createElement(\"div\");\n graphs.className = \"perf-monitor__graphs\";\n\n for (const config of TIMINGS) {\n const graph = document.createElement(\"div\");\n graph.className = \"perf-monitor__graph\";\n\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"perf-monitor__canvas\";\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"perf-monitor__graph-overlay\";\n\n const head = document.createElement(\"label\");\n head.className = \"perf-monitor__graph-head\";\n\n const checkbox = this.buildCheckbox(\n this.settings.isGraphEnabled(config.metric),\n `Show ${config.label} graph in HUD`,\n (on) => this.settings.setGraphEnabled(config.metric, on),\n );\n this.graphCheckboxes.set(config.metric, checkbox);\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__graph-label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__graph-value\";\n value.textContent = \"—\";\n this.graphValueEls.set(config.metric, value);\n\n head.append(checkbox, label);\n overlay.append(head, value);\n graph.append(canvas, overlay);\n graphs.append(graph);\n\n const ctx = canvas.getContext(\"2d\");\n\n if (ctx) {\n this.graphCanvases.set(config.metric, { canvas, ctx, height: 0, width: 0 });\n this.resizeObserver.observe(graph);\n }\n }\n\n const stats = document.createElement(\"div\");\n stats.className = \"perf-monitor__section\";\n\n for (const config of NUMBERS) {\n const rowEl = document.createElement(\"label\");\n rowEl.className = \"perf-monitor__row\";\n\n const checkbox = this.buildCheckbox(\n this.settings.isNumberEnabled(config.key),\n `Show ${config.label} in HUD`,\n (on) => this.settings.setNumberEnabled(config.key, on),\n );\n this.statCheckboxes.set(config.key, checkbox);\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__value\";\n value.textContent = \"—\";\n this.statValueEls.set(config.key, value);\n\n rowEl.append(checkbox, label, value);\n stats.append(rowEl);\n }\n\n this.element.append(hud, options, graphs, stats);\n }\n\n private rebuildHud() {\n const { selection } = this.settings;\n\n for (const graph of this.hudGraphs.values()) {\n this.resizeObserver.unobserve(graph.canvas.parentElement ?? graph.canvas);\n }\n\n this.hudGraphs.clear();\n this.hudNumberEls.clear();\n this.hudGraphsEl.replaceChildren();\n this.hudGridEl.replaceChildren();\n\n for (const config of TIMINGS) {\n if (!selection.graphs.has(config.metric)) {\n continue;\n }\n\n const row = document.createElement(\"div\");\n row.className = \"perf-monitor__hud-graph\";\n\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"perf-monitor__canvas\";\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"perf-monitor__graph-overlay\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__graph-label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__graph-value\";\n value.textContent = \"—\";\n\n overlay.append(label, value);\n row.append(canvas, overlay);\n this.hudGraphsEl.append(row);\n\n const ctx = canvas.getContext(\"2d\");\n\n if (ctx) {\n this.hudGraphs.set(config.metric, { canvas, ctx, height: 0, valueEl: value, width: 0 });\n this.resizeObserver.observe(row);\n }\n }\n\n for (const config of NUMBERS) {\n if (!selection.numbers.has(config.key)) {\n continue;\n }\n\n const item = document.createElement(\"div\");\n item.className = \"perf-monitor__hud-item\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__hud-label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__hud-value\";\n value.textContent = \"—\";\n\n item.append(label, value);\n this.hudGridEl.append(item);\n this.hudNumberEls.set(config.key, value);\n }\n\n this.resizeCanvases();\n }\n\n private onSelectionChanged() {\n for (const [metric, checkbox] of this.graphCheckboxes) {\n checkbox.checked = this.settings.isGraphEnabled(metric);\n }\n\n for (const [key, checkbox] of this.statCheckboxes) {\n checkbox.checked = this.settings.isNumberEnabled(key);\n }\n\n this.dimCheckbox.checked = this.settings.dim;\n this.theme.setOverride(this.settings.theme);\n this.rebuildHud();\n }\n\n /** Repaint the palette and mark the radio matching what applies (pick, else consumer mode). */\n private onThemeChanged() {\n applyTheme(this.element, this.theme);\n\n for (const [mode, radio] of this.themeRadios) {\n radio.setAttribute(\"aria-checked\", String(mode === this.theme.effective));\n }\n }\n\n private buildCheckbox(checked: boolean, title: string, onChange: (enabled: boolean) => void) {\n const checkbox = document.createElement(\"input\");\n checkbox.type = \"checkbox\";\n checkbox.className = \"perf-monitor__checkbox\";\n checkbox.checked = checked;\n checkbox.title = title;\n checkbox.setAttribute(\"aria-label\", title);\n checkbox.addEventListener(\"change\", () => onChange(checkbox.checked));\n\n return checkbox;\n }\n\n private applyModeClass() {\n this.element.classList.toggle(\"perf-monitor--compact\", this.mode === \"compact\");\n this.element.classList.toggle(\"perf-monitor--full\", this.mode === \"full\");\n }\n\n private resizeCanvases() {\n const dpr = window.devicePixelRatio || 1;\n\n const sizeAll = (graphs: Iterable<GraphCanvas>) => {\n for (const graph of graphs) {\n const { canvas, ctx } = graph;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n\n if (width === 0 || height === 0) {\n continue;\n }\n\n canvas.width = Math.round(width * dpr);\n canvas.height = Math.round(height * dpr);\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n graph.width = width;\n graph.height = height;\n }\n };\n\n sizeAll(this.graphCanvases.values());\n sizeAll(this.hudGraphs.values());\n }\n}\n\nexport { PerformanceView };\nexport type { PerformanceViewMode, PerformanceViewOptions };\n","/**\n * The HUD stylesheet as a string, so the package works without a bundler and\n * injects itself once per document. `.perf-monitor` is the metrics card,\n * `.perf-hud` the floating host around it. Both namespaced so a host page can\n * override safely; the `--perf-*` custom properties are the theming surface.\n *\n * Both elements carry `data-theme=\"dark\" | \"light\"` (see `HudTheme`) and each\n * resolves its own tokens from it, so the card also themes when mounted on\n * its own, outside a `.perf-hud`.\n */\nconst STYLE_ATTRIBUTE = \"data-three-meter\";\n\nconst PERF_HUD_STYLES = `\n.perf-hud,\n.perf-monitor {\n --perf-bg: rgba(22, 24, 29, 0.85);\n --perf-fg: #e6e8eb;\n --perf-fg-dim: #c5c8ce;\n --perf-muted: #8b909a;\n --perf-row: rgba(255, 255, 255, 0.04);\n --perf-border: rgba(255, 255, 255, 0.12);\n --perf-accent: #60a5fa;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);\n\n color-scheme: dark;\n}\n\n.perf-hud[data-theme=\"light\"],\n.perf-monitor[data-theme=\"light\"] {\n --perf-bg: rgba(250, 250, 252, 0.88);\n --perf-fg: #1a1c21;\n --perf-fg-dim: #4b5058;\n --perf-muted: #6b7079;\n --perf-row: rgba(0, 0, 0, 0.05);\n --perf-border: rgba(0, 0, 0, 0.12);\n --perf-accent: #2563eb;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);\n\n color-scheme: light;\n}\n\n.perf-monitor {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 8px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.4;\n color: var(--perf-fg);\n}\n\n.perf-monitor__graphs {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.perf-monitor__graph {\n position: relative;\n height: 40px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__canvas {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.perf-monitor__graph-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 8px;\n pointer-events: none;\n}\n\n.perf-monitor__graph-head {\n display: flex;\n align-items: center;\n gap: 6px;\n cursor: pointer;\n}\n\n.perf-monitor__graph-overlay .perf-monitor__graph-head {\n pointer-events: auto;\n}\n\n.perf-monitor__graph-label {\n color: var(--perf-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n font-size: 10px;\n}\n\n.perf-monitor__graph-value {\n min-width: 6ch;\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__section {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.perf-monitor__row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 2px 6px;\n border-radius: 3px;\n cursor: pointer;\n}\n\n.perf-monitor__row:nth-child(even) {\n background: var(--perf-row);\n}\n\n.perf-monitor__label {\n flex: 1;\n color: var(--perf-muted);\n}\n\n.perf-monitor__icon {\n flex: none;\n width: 12px;\n height: 12px;\n color: var(--perf-muted);\n}\n\n.perf-monitor__options {\n padding-bottom: 8px;\n border-bottom: 1px solid var(--perf-border);\n}\n\n.perf-monitor__row--static {\n cursor: default;\n}\n\n/* Light / system / dark, as a small segmented radio group. */\n.perf-monitor__segment {\n display: inline-flex;\n flex: none;\n gap: 1px;\n padding: 1px;\n border-radius: 4px;\n background: var(--perf-row);\n}\n\n.perf-monitor__segment-option {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 16px;\n padding: 0;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: var(--perf-muted);\n cursor: pointer;\n}\n\n.perf-monitor__segment-option:hover {\n color: var(--perf-fg);\n}\n\n.perf-monitor__segment-option[aria-checked=\"true\"] {\n color: var(--perf-fg);\n background: var(--perf-border);\n}\n\n.perf-monitor__segment-option:focus-visible {\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\n}\n\n.perf-monitor__segment-option .perf-monitor__icon {\n width: 11px;\n height: 11px;\n color: inherit;\n}\n\n.perf-monitor__checkbox {\n flex: none;\n margin: 0;\n width: 12px;\n height: 12px;\n accent-color: var(--perf-accent);\n cursor: pointer;\n}\n\n.perf-monitor__value {\n min-width: 7ch;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__hud {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 2px;\n}\n\n.perf-monitor__hud-graphs {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.perf-monitor__hud-graphs:empty {\n display: none;\n}\n\n.perf-monitor__hud-graph {\n position: relative;\n height: 28px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__hud-grid {\n display: grid;\n grid-template-columns: auto 6ch auto 6ch;\n align-items: baseline;\n gap: 2px 8px;\n}\n\n.perf-monitor__hud-grid:empty {\n display: none;\n}\n\n.perf-monitor__hud-item {\n display: contents;\n}\n\n.perf-monitor__hud-label {\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__hud-value {\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor--full .perf-monitor__hud {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__graphs,\n.perf-monitor--compact .perf-monitor__section {\n display: none;\n}\n\n.perf-monitor--compact {\n padding: 4px 8px;\n}\n\n.perf-hud {\n position: fixed;\n z-index: 9999;\n /* Safari resolves width:100% canvases against the viewport on a fixed\n host with auto width. Shrink-wrap so expand stays a card, not the window. */\n width: max-content;\n max-width: min(20rem, calc(100vw - 48px));\n overflow: visible;\n background: transparent;\n pointer-events: none;\n user-select: none;\n touch-action: none;\n}\n\n.perf-hud--full > .perf-monitor {\n width: 16rem;\n max-width: 100%;\n}\n\n.perf-hud > .perf-monitor {\n position: relative;\n z-index: 1;\n max-height: 70vh;\n overflow: auto;\n background: var(--perf-bg);\n border-radius: 6px;\n box-shadow: var(--perf-shadow);\n backdrop-filter: blur(2px);\n pointer-events: auto;\n transition: opacity 0.25s ease;\n}\n\n.perf-hud--dim:not(.is-awake):not(.is-dragging) > .perf-monitor {\n opacity: 0.32;\n}\n\n.perf-hud__hotspot {\n position: absolute;\n z-index: 0;\n inset: -20px;\n pointer-events: auto;\n}\n\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__hotspot {\n right: -46px;\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__hotspot {\n left: -46px;\n}\n\n.perf-hud__tools {\n position: absolute;\n top: 0;\n z-index: 2;\n display: flex;\n flex-direction: column;\n gap: 4px;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s ease;\n}\n\n.perf-hud.is-awake .perf-hud__tools,\n.perf-hud.is-dragging .perf-hud__tools {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* Inward side: right by default, left when docked to the right edge. */\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__tools {\n left: calc(100% + 6px);\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__tools {\n right: calc(100% + 6px);\n}\n\n.perf-hud__disc {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border: 0;\n border-radius: 999px;\n background: var(--perf-bg);\n box-shadow: var(--perf-shadow);\n color: var(--perf-fg-dim);\n cursor: pointer;\n backdrop-filter: blur(2px);\n}\n\n.perf-hud__icon {\n width: 12px;\n height: 12px;\n}\n\n.perf-hud__drag {\n cursor: grab;\n}\n\n.perf-hud.is-dragging .perf-hud__drag {\n cursor: grabbing;\n}\n\n.perf-hud__disc:hover,\n.perf-hud__disc:focus-visible,\n.perf-hud__disc[aria-pressed=\"true\"] {\n color: var(--perf-fg);\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\n}\n`;\n\n/**\n * Append the stylesheet to `target`'s head, once. Call before mounting a view\n * by hand; {@link mountPerfHud} does it for you unless told not to.\n */\nconst injectStyles = (target: Document = document) => {\n if (target.head.querySelector(`style[${STYLE_ATTRIBUTE}]`)) {\n return;\n }\n\n const style = target.createElement(\"style\");\n style.setAttribute(STYLE_ATTRIBUTE, \"\");\n style.textContent = PERF_HUD_STYLES;\n target.head.append(style);\n};\n\nexport { injectStyles, PERF_HUD_STYLES };\n","/**\n * The floating HUD: a fixed host docked to a screen edge, the metrics card\n * inside it, and two discs on the inward side (drag grip, compact/full toggle)\n * that stay hidden until the pointer is near or someone taps. Dim-on-leave\n * comes from the settings; the palette from `theme`. This is what the React\n * `PerfHud` wraps; a vanilla three app calls it directly.\n */\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport { type DefaultPlacement, dockPanel } from \"./dock-panel.ts\";\nimport { DEFAULT_STORAGE_KEY, HudSettings } from \"./hud-settings.ts\";\nimport { createIcon } from \"./icons.ts\";\nimport { PerformanceView, type PerformanceViewMode } from \"./performance-view.ts\";\nimport { injectStyles } from \"./styles.ts\";\nimport { applyTheme, HudTheme, type ThemeMode } from \"./theme.ts\";\n\nconst TOUCH_LINGER_MS = 2500;\n\ntype MountPerfHudOptions = {\n /** Start compact (the card) or full (the checkbox list). Default `compact`. */\n mode?: PerformanceViewMode;\n /**\n * `dark`, `light`, or `system` to follow the OS preference. Default\n * `system`. Change it later with the handle's `setTheme`. A pick made in\n * the panel's own theme row (kept in `settings`) sits on top of this.\n */\n theme?: ThemeMode;\n /** localStorage key for the selection and the dock. `null` disables persistence. */\n storageKey?: string | null;\n /** Bring your own; otherwise one is created from `storageKey`. */\n settings?: HudSettings;\n /** Where the card sits on a first visit. Default left edge, centred. */\n defaultPlacement?: DefaultPlacement;\n /** Where the host element goes. Default `document.body`. */\n parent?: HTMLElement;\n /** Append the stylesheet to the document once. Default true. */\n injectStyles?: boolean;\n /** Repaint rate. Default 10. */\n refreshHz?: number;\n /** Accessible name of the panel. Default `Performance`. */\n label?: string;\n};\n\ntype PerfHudHandle = {\n element: HTMLDivElement;\n view: PerformanceView;\n settings: HudSettings;\n /**\n * `mode` is the consumer layer, `override` the panel's pick, `effective`\n * whichever applies, `resolved` the `dark` / `light` on screen. Subscribe\n * for changes, including the OS preference moving under `system`.\n */\n theme: HudTheme;\n getMode: () => PerformanceViewMode;\n setMode: (mode: PerformanceViewMode) => void;\n /** The consumer layer. The panel's pick, if any, is `settings.theme`. */\n getTheme: () => ThemeMode;\n /** Sets the consumer layer; a pick made in the panel still wins until `settings.setTheme(null)`. */\n setTheme: (mode: ThemeMode) => void;\n dispose: () => void;\n};\n\nconst disc = (label: string, className: string, icon: \"grip\" | \"sliders\") => {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = `perf-hud__disc ${className}`.trim();\n button.setAttribute(\"aria-label\", label);\n button.append(createIcon(icon, \"perf-hud__icon\"));\n\n return button;\n};\n\nconst mountPerfHud = (\n monitor: PerformanceMonitor,\n options: MountPerfHudOptions = {},\n): PerfHudHandle => {\n const storageKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n const settings = options.settings ?? new HudSettings({ storageKey });\n const theme = new HudTheme(options.theme ?? \"system\");\n const parent = options.parent ?? document.body;\n\n if (options.injectStyles ?? true) {\n injectStyles(parent.ownerDocument);\n }\n\n const host = document.createElement(\"div\");\n host.className = \"perf-hud\";\n host.dataset.edge = \"left\";\n host.setAttribute(\"aria-label\", options.label ?? \"Performance\");\n\n const hotspot = document.createElement(\"div\");\n hotspot.className = \"perf-hud__hotspot\";\n hotspot.setAttribute(\"aria-hidden\", \"true\");\n\n const tools = document.createElement(\"div\");\n tools.className = \"perf-hud__tools\";\n const grip = disc(\"Drag performance panel\", \"perf-hud__drag\", \"grip\");\n const toggle = disc(\"Toggle full performance metrics\", \"\", \"sliders\");\n tools.append(grip, toggle);\n\n const view = new PerformanceView({\n mode: options.mode ?? \"compact\",\n monitor,\n refreshHz: options.refreshHz,\n settings,\n theme,\n });\n\n host.append(hotspot, tools, view.element);\n parent.append(host);\n\n const applyMode = () => {\n host.classList.toggle(\"perf-hud--full\", view.getMode() === \"full\");\n };\n\n const applyDim = () => {\n host.classList.toggle(\"perf-hud--dim\", settings.dim);\n };\n\n applyMode();\n applyDim();\n // After the view: constructing it restores the panel's stored theme pick.\n applyTheme(host, theme);\n const unsubscribe = settings.subscribe(applyDim);\n const unsubscribeTheme = theme.subscribe(() => applyTheme(host, theme));\n const dock = dockPanel(host, {\n defaultPlacement: options.defaultPlacement,\n handle: grip,\n storageKey,\n });\n\n const setMode = (mode: PerformanceViewMode) => {\n view.setMode(mode);\n applyMode();\n requestAnimationFrame(() => dock.refresh());\n };\n\n toggle.addEventListener(\"click\", () => {\n setMode(view.getMode() === \"compact\" ? \"full\" : \"compact\");\n });\n\n // Wake on approach, sleep on leave; a touch lingers so the discs can be tapped.\n let hideTimer = 0;\n\n const sleep = () => {\n if (host.classList.contains(\"is-dragging\")) {\n return;\n }\n\n host.classList.remove(\"is-awake\");\n };\n\n const wake = (linger: boolean) => {\n host.classList.add(\"is-awake\");\n window.clearTimeout(hideTimer);\n\n if (linger) {\n hideTimer = window.setTimeout(sleep, TOUCH_LINGER_MS);\n }\n };\n\n const onEnter = () => {\n wake(false);\n };\n\n const onLeave = (event: PointerEvent) => {\n if (host.contains(event.relatedTarget as Node | null)) {\n return;\n }\n\n if (event.pointerType === \"touch\" || event.pointerType === \"pen\") {\n wake(true);\n\n return;\n }\n\n sleep();\n };\n\n const onDown = (event: PointerEvent) => {\n wake(event.pointerType !== \"mouse\");\n };\n\n host.addEventListener(\"pointerenter\", onEnter);\n host.addEventListener(\"pointerleave\", onLeave);\n host.addEventListener(\"pointerdown\", onDown);\n\n view.start();\n\n return {\n dispose: () => {\n window.clearTimeout(hideTimer);\n host.removeEventListener(\"pointerenter\", onEnter);\n host.removeEventListener(\"pointerleave\", onLeave);\n host.removeEventListener(\"pointerdown\", onDown);\n unsubscribe();\n unsubscribeTheme();\n dock.dispose();\n view.dispose();\n theme.dispose();\n host.remove();\n },\n element: host,\n getMode: () => view.getMode(),\n setMode,\n getTheme: () => theme.mode,\n setTheme: (mode: ThemeMode) => theme.setMode(mode),\n settings,\n theme,\n view,\n };\n};\n\nexport { mountPerfHud };\nexport type { MountPerfHudOptions, PerfHudHandle };\n"],"mappings":";AAcA,MAAM,cAAoC;CAAC;CAAQ;CAAS;AAAQ;AACpE,MAAM,oBAAoB;AAE1B,MAAM,eAAe,UACnB,OAAO,UAAU,YAAa,YAAkC,SAAS,KAAK;AAEhF,IAAM,WAAN,MAAe;CACb;CACA,WAAqC;CACrC,4BAA6B,IAAI,IAAgB;CACjD,QAAuC;CACvC,uBAAwC,KAAK,OAAO;CAEpD,YAAY,OAAkB,UAAU;EACtC,KAAK,UAAU;EACf,KAAK,MAAM;CACb;;CAGA,IAAI,OAAkB;EACpB,OAAO,KAAK;CACd;;CAGA,IAAI,WAA6B;EAC/B,OAAO,KAAK;CACd;;CAGA,IAAI,YAAuB;EACzB,OAAO,KAAK,YAAY,KAAK;CAC/B;;;;;;CAOA,IAAI,WAA0B;EAC5B,MAAM,YAAY,KAAK;EAEvB,IAAI,cAAc,UAChB,OAAO;EAGT,IAAI,CAAC,KAAK,OACR,OAAO;EAGT,OAAO,KAAK,MAAM,UAAU,SAAS;CACvC;CAEA,QAAQ,MAAiB;EACvB,IAAI,SAAS,KAAK,SAChB;EAGF,KAAK,UAAU;EACf,KAAK,MAAM;EACX,KAAK,OAAO;CACd;CAEA,YAAY,MAAwB;EAClC,IAAI,SAAS,KAAK,UAChB;EAGF,KAAK,WAAW;EAChB,KAAK,MAAM;EACX,KAAK,OAAO;CACd;;CAGA,UAAU,UAAsB;EAC9B,KAAK,UAAU,IAAI,QAAQ;EAE3B,aAAa;GACX,KAAK,UAAU,OAAO,QAAQ;EAChC;CACF;;CAGA,UAAU;EACR,KAAK,QAAQ;EACb,KAAK,UAAU,MAAM;CACvB;CAEA,QAAgB;EACd,IAAI,KAAK,cAAc,UAAU;GAC/B,KAAK,QAAQ;GAEb;EACF;EAEA,IAAI,KAAK,SAAS,OAAO,eAAe,YACtC;EAGF,KAAK,QAAQ,WAAW,iBAAiB;EACzC,KAAK,MAAM,iBAAiB,UAAU,KAAK,cAAc;CAC3D;CAEA,UAAkB;EAChB,KAAK,OAAO,oBAAoB,UAAU,KAAK,cAAc;EAC7D,KAAK,QAAQ;CACf;CAEA,SAAiB;EACf,KAAK,MAAM,YAAY,KAAK,WAC1B,SAAS;CAEb;AACF;;AAGA,MAAM,cAAc,SAAsB,UAAoB;CAC5D,QAAQ,QAAQ,QAAQ,MAAM;AAChC;;;AC9FA,MAAM,sBAAsB;AAC5B,MAAM,cAAc;AACpB,MAAM,kBAAqC;CAAC;CAAO;CAAS;CAAO;AAAK;AACxE,MAAM,iBAA0C,CAAC;AACjD,MAAM,iCAAiB,IAAI,IAAkB;CAAC;CAAO;CAAO;AAAK,CAAC;AAElE,MAAM,kBAAkB,UACtB,eAAe,IAAI,KAAqB;AAE1C,IAAM,cAAN,MAAkB;CAChB;CACA;CACA,4BAA6B,IAAI,IAAgB;CACjD;CAEA,YAAY,UAA8B,CAAC,GAAG;EAC5C,KAAK,aAAa,QAAQ,eAAe,KAAA,IAAY,sBAAsB,QAAQ;EACnF,KAAK,WAAW;GACd,KAAK,QAAQ,UAAU,OAAO;GAC9B,QAAQ,QAAQ,UAAU,UAAU;GACpC,SAAS,QAAQ,UAAU,WAAW;EACxC;EACA,KAAK,QAAQ,KAAK,KAAK;CACzB;CAEA,IAAI,YAA0B;EAC5B,OAAO,KAAK;CACd;CAEA,IAAI,MAAM;EACR,OAAO,KAAK,MAAM;CACpB;CAEA,OAAO,SAAkB;EACvB,KAAK,MAAM,MAAM;EACjB,KAAK,iBAAiB;CACxB;;CAGA,IAAI,QAAQ;EACV,OAAO,KAAK,MAAM;CACpB;CAEA,SAAS,MAAwB;EAC/B,KAAK,MAAM,QAAQ;EACnB,KAAK,iBAAiB;CACxB;CAEA,gBAAgB,KAAa;EAC3B,OAAO,KAAK,MAAM,QAAQ,IAAI,GAAG;CACnC;CAEA,eAAe,QAAsB;EACnC,OAAO,KAAK,MAAM,OAAO,IAAI,MAAM;CACrC;CAEA,iBAAiB,KAAa,SAAkB;EAC9C,IAAI,SACF,KAAK,MAAM,QAAQ,IAAI,GAAG;OAE1B,KAAK,MAAM,QAAQ,OAAO,GAAG;EAG/B,KAAK,iBAAiB;CACxB;CAEA,gBAAgB,QAAsB,SAAkB;EACtD,IAAI,SACF,KAAK,MAAM,OAAO,IAAI,MAAM;OAE5B,KAAK,MAAM,OAAO,OAAO,MAAM;EAGjC,KAAK,iBAAiB;CACxB;CAEA,UAAU,UAAsB;EAC9B,KAAK,UAAU,IAAI,QAAQ;EAE3B,aAAa;GACX,KAAK,UAAU,OAAO,QAAQ;EAChC;CACF;CAEA,OAAiC;EAC/B,MAAM,kBAAoC;GACxC,KAAK,KAAK,SAAS;GACnB,QAAQ,IAAI,IAAI,KAAK,SAAS,MAAM;GACpC,SAAS,IAAI,IAAI,KAAK,SAAS,OAAO;GACtC,OAAO;EACT;EAEA,IAAI,KAAK,eAAe,MACtB,OAAO,SAAS;EAGlB,IAAI;GACF,MAAM,MAAM,aAAa,QAAQ,KAAK,UAAU;GAEhD,IAAI,CAAC,KACH,OAAO,SAAS;GAGlB,MAAM,SAAS,KAAK,MAAM,GAAG;GAO7B,OAAO;IACL,KAAK,OAAO,OAAO,KAAK,SAAS;IACjC,QAAQ,IAAI,KAAK,OAAO,UAAU,KAAK,SAAS,OAAA,CAAQ,OAAO,cAAc,CAAC;IAC9E,SAAS,IAAI,IAAI,OAAO,WAAW,KAAK,SAAS,OAAO;IACxD,OAAO,YAAY,OAAO,KAAK,IAAI,OAAO,QAAQ;GACpD;EACF,QAAQ;GAEN,OAAO,SAAS;EAClB;CACF;CAEA,mBAA2B;EACzB,IAAI,KAAK,eAAe,MACtB,IAAI;GACF,aAAa,QACX,KAAK,YACL,KAAK,UAAU;IACb,KAAK,KAAK,MAAM;IAChB,QAAQ,CAAC,GAAG,KAAK,MAAM,MAAM;IAC7B,SAAS,CAAC,GAAG,KAAK,MAAM,OAAO;IAE/B,OAAO,KAAK,MAAM,SAAS,KAAA;GAC7B,CAAC,CACH;EACF,QAAQ,CAER;EAGF,KAAK,MAAM,YAAY,KAAK,WAC1B,SAAS;CAEb;AACF;;;;;;;;;AC7KA,MAAM,QAAQ;;AAEd,MAAM,iBAAiB;AAEvB,MAAM,QAAQ;CAAC;CAAQ;CAAS;CAAO;AAAQ;AAwC/C,MAAM,qBAA0B;CAC9B,QAAQ,OAAO;CACf,MAAM;CACN,KAAK;CACL,OAAO,OAAO;AAChB;AAEA,MAAM,SAAS,OAAe,KAAa,QAAgB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAE7F,MAAM,YAAY,YAA8B;CAC9C,MAAM,OAAO,QAAQ,sBAAsB;CAE3C,OAAO;EACL,QAAQ,KAAK,UAAU,QAAQ;EAC/B,MAAM,KAAK;EACX,KAAK,KAAK;EACV,OAAO,KAAK,SAAS,QAAQ;CAC/B;AACF;AAEA,MAAM,cAAc,MAAkB,OAAe,MAAW,UAAe;CAC7E,IAAI,SAAS,UAAU,SAAS,SAC9B,OAAO,MAAM,OAAO,OAAO,KAAK,IAAI,OAAO,KAAK,SAAS,MAAM,SAAS,KAAK,CAAC;CAIhF,OAAO,MAAM,OAAO,OAAO,KAAK,IAAI,OAAO,KAAK,QAAQ,MAAM,QAAQ,QAAQ,cAAc,CAAC;AAC/F;AAEA,MAAM,YAAY,WAAsB,OAAY,SAAmB;CACrE,QAAQ,UAAU,MAAlB;EACE,KAAK,QACH,OAAO;GAAE,QAAQ,MAAM;GAAQ,MAAM;GAAO,KAAK,UAAU;GAAO,OAAO,MAAM;EAAM;EACvF,KAAK,SACH,OAAO;GACL,QAAQ,MAAM;GACd,MAAM,KAAK,QAAQ,QAAQ,MAAM;GACjC,KAAK,UAAU;GACf,OAAO,MAAM;EACf;EACF,KAAK,OACH,OAAO;GAAE,QAAQ,MAAM;GAAQ,MAAM,UAAU;GAAO,KAAK;GAAO,OAAO,MAAM;EAAM;EACvF,KAAK,UACH,OAAO;GACL,QAAQ,MAAM;GACd,MAAM,UAAU;GAChB,KAAK,KAAK,SAAS,QAAQ,MAAM;GACjC,OAAO,MAAM;EACf;CACJ;AACF;AAEA,MAAM,iBAAiB,KAAU,SAAc;CAC7C,MAAM,IAAI,IAAI,OAAO,IAAI,QAAQ;CACjC,MAAM,IAAI,IAAI,MAAM,IAAI,SAAS;CAEjC,OAAO,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,SAAS,IAAI,KAAK;AACtD;AAEA,MAAM,iBAAiB,KAAU,SAAyB;CACxD,MAAM,YAAwC;EAC5C,QAAQ,KAAK,UAAU,IAAI,MAAM,IAAI;EACrC,MAAM,IAAI;EACV,OAAO,KAAK,SAAS,IAAI,OAAO,IAAI;EACpC,KAAK,IAAI;CACX;CAEA,IAAI,OAAmB;CACvB,IAAI,UAAU,OAAO;CAErB,KAAK,MAAM,QAAQ,OACjB,IAAI,UAAU,QAAQ,SAAS;EAC7B,UAAU,UAAU;EACpB,OAAO;CACT;CAGF,MAAM,QAAQ,SAAS,UAAU,SAAS,UAAU,IAAI,MAAM,IAAI;CAElE,OAAO;EAAE,OAAO,KAAK,MAAM,WAAW,MAAM,OAAO,MAAM,GAAG,CAAC;EAAG;CAAK;AACvE;AAEA,MAAM,oBAAoB,QAA0B,MAAW,UAA0B;CACvF,MAAM,EAAE,SAAS;CACjB,MAAM,WAAW,SAAS,UAAU,SAAS;CAC7C,MAAM,OAAO,WAAW,KAAK,SAAS,KAAK;CAC3C,MAAM,OAAO,WAAW,MAAM,SAAS,MAAM;CAC7C,MAAM,QACJ,OAAO,UAAU,UAAU,IAAI,OAAO,UAAU,QAAQ,OAAO,QAAQ,OAAO,QAAQ;CAExF,OAAO;EAAE,OAAO,KAAK,MAAM,WAAW,MAAM,OAAO,MAAM,KAAK,CAAC;EAAG;CAAK;AACzE;AAEA,MAAM,UAAU,UACd,OAAO,UAAU,YAAa,MAA4B,SAAS,KAAK;AAE1E,MAAM,iBAAiB,QAAyC;CAC9D,IAAI,QAAQ,MACV,OAAO;CAGT,IAAI;EACF,MAAM,MAAM,aAAa,QAAQ,GAAG;EAEpC,IAAI,CAAC,KACH,OAAO;EAGT,MAAM,SAAS,KAAK,MAAM,GAAG;EAE7B,IACE,OAAO,OAAO,UAAU,YACxB,CAAC,OAAO,SAAS,OAAO,KAAK,KAC7B,CAAC,OAAO,OAAO,IAAI,GAEnB,OAAO;EAGT,OAAO;GAAE,OAAO,OAAO;GAAO,MAAM,OAAO;EAAK;CAClD,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAM,kBAAkB,KAAoB,cAAyB;CACnE,IAAI,QAAQ,MACV;CAGF,IAAI;EACF,aAAa,QAAQ,KAAK,KAAK,UAAU,SAAS,CAAC;CACrD,QAAQ,CAER;AACF;AAEA,MAAM,oBACJ,QACA,QACA,OACA,SACc;CACd,IAAI,CAAC,QACH,OAAO,iBAAiB,QAAQ,MAAM,KAAK;CAG7C,MAAM,QAAQ,SAAS,QAAQ,OAAO,IAAI;CAE1C,IAAI,cAAc,OAAO,IAAI,GAC3B,OAAO,cAAc,OAAO,IAAI;CAGlC,OAAO;EACL,OAAO,KAAK,MAAM,WAAW,OAAO,MAAM,OAAO,OAAO,MAAM,KAAK,CAAC;EACpE,MAAM,OAAO;CACf;AACF;AAEA,MAAM,kBAAkB,SAAsB,cAAyB;CACrE,QAAQ,QAAQ,OAAO,UAAU;CACjC,QAAQ,MAAM,OAAO;CACrB,QAAQ,MAAM,QAAQ;CACtB,QAAQ,MAAM,MAAM;CACpB,QAAQ,MAAM,SAAS;CACvB,QAAQ,MAAM,YAAY;CAE1B,QAAQ,UAAU,MAAlB;EACE,KAAK;GACH,QAAQ,MAAM,OAAO,GAAG,MAAM;GAC9B,QAAQ,MAAM,MAAM,GAAG,UAAU,MAAM;GACvC;EACF,KAAK;GACH,QAAQ,MAAM,QAAQ,GAAG,MAAM;GAC/B,QAAQ,MAAM,MAAM,GAAG,UAAU,MAAM;GACvC;EACF,KAAK;GACH,QAAQ,MAAM,MAAM,GAAG,MAAM;GAC7B,QAAQ,MAAM,OAAO,GAAG,UAAU,MAAM;GACxC;EACF,KAAK;GACH,QAAQ,MAAM,SAAS,GAAG,MAAM;GAChC,QAAQ,MAAM,OAAO,GAAG,UAAU,MAAM;CAE5C;AACF;;;;;AAMA,MAAM,aAAa,SAAsB,UAA4B,CAAC,MAAkB;CACtF,MAAM,UAAU,QAAQ,eAAe,KAAA,IAAY,sBAAsB,QAAQ;CACjF,MAAM,MAAM,YAAY,OAAO,OAAO,GAAG,QAAQ;CACjD,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,SAAS,QAAQ,oBAAoB,EAAE,MAAM,OAAO;CAE1D,MAAM,cAAyB;EAC7B,MAAM,SAAS,cAAc,GAAG;EAChC,MAAM,OAAO,YAAY;EAEzB,IAAI,QAAQ,UAAU,SAAS,aAAa,GAC1C,OAAO,UAAU,iBAAiB,QAAQ,MAAM,SAAS,OAAO,CAAC;EAGnE,MAAM,QAAQ,SAAS,OAAO;EAE9B,IAAI,MAAM,UAAU,KAAK,MAAM,WAAW,GACxC,OAAO,UAAU,iBAAiB,QAAQ,MAAM,KAAK;EAGvD,MAAM,OAAO,iBAAiB,QAAQ,QAAQ,OAAO,IAAI;EACzD,eAAe,SAAS,IAAI;EAE5B,IAAI,CAAC,UAAU,OAAO,SAAS,KAAK,QAAQ,OAAO,UAAU,KAAK,OAChE,eAAe,KAAK,IAAI;EAG1B,OAAO;CACT;CAEA,MAAM;CACN,sBAAsB,KAAK;CAE3B,MAAM,iBAAiB,MAAM;CAC7B,OAAO,iBAAiB,UAAU,QAAQ;CAE1C,MAAM,iBAAiB,IAAI,qBAAqB,MAAM,CAAC;CACvD,eAAe,QAAQ,OAAO;CAE9B,MAAM,oBAAoB;EACxB,OAAO,oBAAoB,UAAU,QAAQ;EAC7C,eAAe,WAAW;CAC5B;CAEA,IAAI,CAAC,QACH,OAAO;EAAE,SAAS;EAAa,SAAS;CAAM;CAGhD,IAAI,YAA2B;CAC/B,IAAI,UAAU;CACd,IAAI,UAAU;CACd,IAAI,QAAQ;CACZ,IAAI,QAAQ;CACZ,IAAI,WAAW;CAEf,MAAM,UAAU,UAAwB;EACtC,IAAI,cAAc,MAAM,WACtB;EAGF,IAAI,UAAU;GACZ,MAAM,OAAO,cAAc,SAAS,OAAO,GAAG,YAAY,CAAC;GAC3D,eAAe,SAAS,IAAI;GAC5B,eAAe,KAAK,IAAI;EAC1B;EAEA,WAAW;EACX,YAAY;EACZ,QAAQ,UAAU,OAAO,aAAa;EAEtC,IAAI;GACF,OAAO,sBAAsB,MAAM,SAAS;EAC9C,QAAQ,CAER;CACF;CAEA,MAAM,iBAAiB,UAAwB;EAC7C,IAAI,MAAM,WAAW,GACnB;EAGF,MAAM,MAAM,SAAS,OAAO;EAC5B,YAAY,MAAM;EAClB,UAAU,MAAM;EAChB,UAAU,MAAM;EAChB,QAAQ,MAAM,UAAU,IAAI;EAC5B,QAAQ,MAAM,UAAU,IAAI;EAC5B,WAAW;EACX,OAAO,kBAAkB,MAAM,SAAS;CAC1C;CAEA,MAAM,iBAAiB,UAAwB;EAC7C,IAAI,cAAc,MAAM,WACtB;EAGF,MAAM,KAAK,MAAM,UAAU;EAC3B,MAAM,KAAK,MAAM,UAAU;EAE3B,IAAI,CAAC,YAAY,KAAK,KAAK,KAAK,KAAK,IACnC;EAGF,WAAW;EACX,QAAQ,UAAU,IAAI,aAAa;EACnC,QAAQ,MAAM,QAAQ;EACtB,QAAQ,MAAM,SAAS;EACvB,QAAQ,MAAM,YAAY;EAC1B,QAAQ,MAAM,OAAO,GAAG,MAAM,UAAU,MAAM;EAC9C,QAAQ,MAAM,MAAM,GAAG,MAAM,UAAU,MAAM;CAC/C;CAEA,OAAO,iBAAiB,eAAe,aAAa;CACpD,OAAO,iBAAiB,eAAe,aAAa;CACpD,OAAO,iBAAiB,aAAa,MAAM;CAC3C,OAAO,iBAAiB,iBAAiB,MAAM;CAE/C,OAAO;EACL,eAAe;GACb,YAAY;GACZ,OAAO,oBAAoB,eAAe,aAAa;GACvD,OAAO,oBAAoB,eAAe,aAAa;GACvD,OAAO,oBAAoB,aAAa,MAAM;GAC9C,OAAO,oBAAoB,iBAAiB,MAAM;EACpD;EACA,SAAS;CACX;AACF;;;AC5WA,MAAM,cAAwC;CAC5C,OAAO;CACP,UAAU;CACV,MACE;CAEF,SACE;CAEF,MAAM;CACN,KACE;CAIF,SACE;AAKJ;AAEA,MAAM,cAAc,MAAgB,cAAsB;CACxD,MAAM,MAAM,SAAS,gBAAgB,8BAA8B,KAAK;CACxE,IAAI,aAAa,WAAW,WAAW;CACvC,IAAI,aAAa,QAAQ,MAAM;CAC/B,IAAI,aAAa,UAAU,cAAc;CACzC,IAAI,aAAa,gBAAgB,GAAG;CACpC,IAAI,aAAa,kBAAkB,OAAO;CAC1C,IAAI,aAAa,mBAAmB,OAAO;CAC3C,IAAI,aAAa,eAAe,MAAM;CACtC,IAAI,aAAa,SAAS,SAAS;CACnC,IAAI,YAAY,YAAY;CAE5B,OAAO;AACT;;;AChCA,SAAS,cACP,KACA,OACA,QACA,QACA,OACA;CACA,IAAI,UAAU,GAAG,GAAG,OAAO,MAAM;CAEjC,IAAI,OAAO,SAAS,GAClB;CAGF,IAAI,MAAM;CAEV,KAAK,MAAM,SAAS,QAClB,IAAI,QAAQ,KACV,MAAM;CAIV,MAAM,QAAQ,MAAM,OAAO;CAC3B,MAAM,QAAQ,SAAS,OAAO,SAAS;CACvC,MAAM,OAAO,UAAkB,SAAU,QAAQ,QAAS;CAE1D,IAAI,UAAU;CACd,IAAI,OAAO,GAAG,IAAI,OAAO,EAAG,CAAC;CAE7B,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,GAClD,IAAI,OAAO,QAAQ,OAAO,IAAI,OAAO,MAAO,CAAC;CAG/C,IAAI,YAAY;CAChB,IAAI,cAAc,MAAM;CACxB,IAAI,OAAO;CAEX,IAAI,OAAO,OAAO,MAAM;CACxB,IAAI,OAAO,GAAG,MAAM;CACpB,IAAI,UAAU;CACd,IAAI,YAAY,MAAM;CACtB,IAAI,KAAK;AACX;;;ACZA,MAAM,UAA0B;CAC9B;EACE,SAAS,UAAU,KAAK,MAAM,KAAK,CAAC,CAAC,SAAS;EAC9C,OAAO;EACP,QAAQ;EACR,OAAO;GACL,MAAM;IAAE,MAAM;IAA4B,QAAQ;GAAU;GAC5D,OAAO;IAAE,MAAM;IAA2B,QAAQ;GAAU;EAC9D;EACA,MAAM;CACR;CACA;EACE,SAAS,UAAU,MAAM,QAAQ,CAAC;EAClC,OAAO;EACP,QAAQ;EACR,OAAO;GACL,MAAM;IAAE,MAAM;IAA4B,QAAQ;GAAU;GAC5D,OAAO;IAAE,MAAM;IAA2B,QAAQ;GAAU;EAC9D;EACA,MAAM;CACR;CACA;EACE,SAAS,UAAU,MAAM,QAAQ,CAAC;EAClC,OAAO;EACP,QAAQ;EACR,OAAO;GACL,MAAM;IAAE,MAAM;IAA6B,QAAQ;GAAU;GAC7D,OAAO;IAAE,MAAM;IAA4B,QAAQ;GAAU;EAC/D;EACA,MAAM;CACR;AACF;AAEA,MAAM,gBAAwF;CAC5F;EAAE,MAAM;EAAO,OAAO;EAAe,MAAM;CAAQ;CACnD;EAAE,MAAM;EAAW,OAAO;EAA0B,MAAM;CAAS;CACnE;EAAE,MAAM;EAAQ,OAAO;EAAc,MAAM;CAAO;AACpD;AAEA,MAAM,eAAe,UAAkB,MAAM,eAAe,OAAO;AAEnE,MAAM,cAAc,QAAgB,WAAyB;CAC3D,QAAQ,QAAR;EACE,KAAK,OACH,OAAO,OAAO;EAChB,KAAK,OACH,OAAO,OAAO;EAChB,KAAK,OACH,OAAO,OAAO,IAAI;CACtB;AACF;AAEA,MAAM,qBAAqB,QAAgB,WACzC,WAAW,SAAS,OAAO,IAAI;AAEjC,MAAM,UAA0B;CAC9B;EAAE,KAAK;EAAO,OAAO;EAAO,OAAO,WAAW,KAAK,MAAM,OAAO,GAAG,CAAC,CAAC,SAAS;CAAE;CAChF;EAAE,KAAK;EAAS,OAAO;EAAS,OAAO,WAAW,YAAY,OAAO,OAAO,KAAK;CAAE;CACnF;EAAE,KAAK;EAAO,OAAO;EAAO,OAAO,WAAW,OAAO,IAAI,QAAQ,CAAC;CAAE;CACpE;EACE,KAAK;EACL,OAAO;EACP,OAAO,WAAY,OAAO,IAAI,YAAY,OAAO,IAAI,GAAG,QAAQ,CAAC,IAAI;CACvE;CACA;EACE,KAAK;EACL,OAAO;EACP,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS;CACvD;CACA;EAAE,KAAK;EAAS,OAAO;EAAS,OAAO,WAAW,YAAY,OAAO,OAAO,KAAK;CAAE;CACnF;EAAE,KAAK;EAAU,OAAO;EAAU,OAAO,WAAW,YAAY,OAAO,OAAO,MAAM;CAAE;CACtF;EACE,KAAK;EACL,OAAO;EACP,OAAO,WAAW,YAAY,OAAO,OAAO,MAAM;CACpD;CACA;EACE,KAAK;EACL,OAAO;EACP,OAAO,WAAW,YAAY,OAAO,UAAU,UAAU;CAC3D;CACA;EACE,KAAK;EACL,OAAO;EACP,OAAO,WAAW,YAAY,OAAO,UAAU,QAAQ;CACzD;CACA;EACE,KAAK;EACL,OAAO;EACP,OAAO,WAAW,YAAY,OAAO,UAAU,QAAQ;CACzD;AACF;;;;;;;AAmBA,IAAM,kBAAN,MAAsB;CACpB;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA,gCAAiC,IAAI,IAA+B;CACpE,gCAAiC,IAAI,IAA+B;CACpE,kCAAmC,IAAI,IAAoC;CAC3E,+BAAgC,IAAI,IAAyB;CAC7D,iCAAkC,IAAI,IAA8B;CACpE;CACA,8BAA+B,IAAI,IAAkC;CACrE;CACA;CACA,4BAA6B,IAAI,IAA4B;CAC7D,+BAAgC,IAAI,IAAyB;CAC7D,QAA+B;CAC/B,cAAsB;CACtB;CACA;CAEA,YAAY,SAAiC;EAC3C,KAAK,UAAU,QAAQ;EACvB,KAAK,WAAW,QAAQ,YAAY,IAAI,YAAY;EACpD,KAAK,YAAY,CAAC,QAAQ;EAC1B,KAAK,QAAQ,QAAQ,SAAS,IAAI,SAAS;EAE3C,KAAK,MAAM,YAAY,KAAK,SAAS,KAAK;EAC1C,KAAK,OAAO,QAAQ,QAAQ;EAC5B,KAAK,gBAAgB,OAAQ,QAAQ,aAAa;EAClD,KAAK,UAAU,SAAS,cAAc,KAAK;EAC3C,KAAK,QAAQ,YAAY;EACzB,KAAK,eAAe;EACpB,KAAK,iBAAiB,IAAI,qBAAqB,KAAK,eAAe,CAAC;EACpE,KAAK,MAAM;EACX,KAAK,WAAW;EAChB,KAAK,eAAe;EACpB,KAAK,cAAc,KAAK,SAAS,gBAAgB,KAAK,mBAAmB,CAAC;EAC1E,KAAK,mBAAmB,KAAK,MAAM,gBAAgB,KAAK,eAAe,CAAC;CAC1E;CAEA,QAAQ;EACN,IAAI,KAAK,UAAU,MACjB,KAAK,QAAQ,sBAAsB,KAAK,KAAK;CAEjD;CAEA,OAAO;EACL,IAAI,KAAK,UAAU,MAAM;GACvB,qBAAqB,KAAK,KAAK;GAC/B,KAAK,QAAQ;EACf;CACF;CAEA,QAAQ,MAA2B;EACjC,IAAI,SAAS,KAAK,MAChB;EAGF,KAAK,OAAO;EACZ,KAAK,eAAe;EACpB,KAAK,eAAe;CACtB;CAEA,UAAU;EACR,OAAO,KAAK;CACd;CAEA,UAAU;EACR,KAAK,KAAK;EACV,KAAK,YAAY;EACjB,KAAK,iBAAiB;EAEtB,IAAI,KAAK,WACP,KAAK,MAAM,QAAQ;EAGrB,KAAK,eAAe,WAAW;EAC/B,KAAK,QAAQ,OAAO;CACtB;CAEA,SAAiB,QAAgB;EAC/B,KAAK,QAAQ,sBAAsB,KAAK,KAAK;EAE7C,IAAI,MAAM,KAAK,cAAc,KAAK,eAChC;EAGF,KAAK,cAAc;EACnB,KAAK,OAAO;CACd;CAEA,SAAiB;EACf,MAAM,SAAS,KAAK,QAAQ,UAAU;EAEtC,IAAI,KAAK,SAAS,WAAW;GAC3B,KAAK,UAAU,MAAM;GAErB;EACF;EAEA,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,YAAY,kBAAkB,QAAQ,OAAO,MAAM;GACzD,MAAM,UAAU,KAAK,cAAc,IAAI,OAAO,MAAM;GACpD,QAAQ,cAAc,YAClB,KAAK,aAAa,QAAQ,WAAW,QAAQ,OAAO,MAAM,CAAC,IAC3D;GAEJ,MAAM,QAAQ,KAAK,cAAc,IAAI,OAAO,MAAM;GAClD,cACE,MAAM,KACN,MAAM,OACN,MAAM,QACN,YAAY,KAAK,QAAQ,WAAW,OAAO,MAAM,IAAI,CAAC,GACtD,OAAO,MAAM,KAAK,MAAM,SAC1B;EACF;EAEA,KAAK,MAAM,UAAU,SACnB,KAAK,aAAa,IAAI,OAAO,GAAG,CAAC,CAAE,cAAc,OAAO,KAAK,MAAM;CAEvE;CAEA,UAAkB,QAAgB;EAChC,KAAK,MAAM,CAAC,KAAK,YAAY,KAAK,cAAc;GAC9C,MAAM,SAAS,QAAQ,MAAM,WAAW,OAAO,QAAQ,GAAG;GAE1D,IAAI,QACF,QAAQ,cAAc,OAAO,KAAK,MAAM;EAE5C;EAEA,KAAK,MAAM,CAAC,QAAQ,UAAU,KAAK,WAAW;GAC5C,MAAM,SAAS,QAAQ,MAAM,WAAW,OAAO,WAAW,MAAM;GAChE,MAAM,YAAY,kBAAkB,QAAQ,MAAM;GAElD,MAAM,QAAQ,cAAc,YACxB,KAAK,aAAa,QAAQ,WAAW,QAAQ,MAAM,CAAC,IACpD;GAEJ,cACE,MAAM,KACN,MAAM,OACN,MAAM,QACN,YAAY,KAAK,QAAQ,WAAW,MAAM,IAAI,CAAC,GAC/C,OAAO,MAAM,KAAK,MAAM,SAC1B;EACF;CACF;CAEA,aAAqB,QAAsB,OAAe;EACxD,OAAO,GAAG,OAAO,OAAO,KAAK,IAAI,OAAO,OAAO,IAAI,OAAO,SAAS;CACrE;CAEA,QAAgB;EACd,MAAM,MAAM,SAAS,cAAc,KAAK;EACxC,IAAI,YAAY;EAChB,KAAK,cAAc,SAAS,cAAc,KAAK;EAC/C,KAAK,YAAY,YAAY;EAC7B,KAAK,YAAY,SAAS,cAAc,KAAK;EAC7C,KAAK,UAAU,YAAY;EAC3B,IAAI,OAAO,KAAK,aAAa,KAAK,SAAS;EAI3C,MAAM,UAAU,SAAS,cAAc,KAAK;EAC5C,QAAQ,YAAY;EAEpB,MAAM,WAAW,SAAS,cAAc,KAAK;EAC7C,SAAS,YAAY;EAErB,MAAM,aAAa,SAAS,cAAc,MAAM;EAChD,WAAW,YAAY;EACvB,WAAW,cAAc;EAEzB,MAAM,UAAU,SAAS,cAAc,KAAK;EAC5C,QAAQ,YAAY;EACpB,QAAQ,aAAa,QAAQ,YAAY;EACzC,QAAQ,aAAa,cAAc,aAAa;EAEhD,KAAK,MAAM,UAAU,eAAe;GAClC,MAAM,QAAQ,SAAS,cAAc,QAAQ;GAC7C,MAAM,OAAO;GACb,MAAM,YAAY;GAClB,MAAM,aAAa,QAAQ,OAAO;GAClC,MAAM,aAAa,gBAAgB,OAAO;GAC1C,MAAM,aAAa,cAAc,OAAO,KAAK;GAC7C,MAAM,QAAQ,OAAO;GACrB,MAAM,OAAO,WAAW,OAAO,MAAM,oBAAoB,CAAC;GAC1D,MAAM,iBAAiB,eAAe,KAAK,SAAS,SAAS,OAAO,IAAI,CAAC;GACzE,KAAK,YAAY,IAAI,OAAO,MAAM,KAAK;GACvC,QAAQ,OAAO,KAAK;EACtB;EAEA,SAAS,OAAO,WAAW,YAAY,oBAAoB,GAAG,YAAY,OAAO;EAEjF,MAAM,SAAS,SAAS,cAAc,OAAO;EAC7C,OAAO,YAAY;EAEnB,KAAK,cAAc,KAAK,cACtB,KAAK,SAAS,KACd,sDACC,OAAO,KAAK,SAAS,OAAO,EAAE,CACjC;EAEA,MAAM,WAAW,SAAS,cAAc,MAAM;EAC9C,SAAS,YAAY;EACrB,SAAS,cAAc;EAEvB,OAAO,OAAO,WAAW,SAAS,oBAAoB,GAAG,UAAU,KAAK,WAAW;EACnF,QAAQ,OAAO,UAAU,MAAM;EAE/B,MAAM,SAAS,SAAS,cAAc,KAAK;EAC3C,OAAO,YAAY;EAEnB,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,QAAQ,SAAS,cAAc,KAAK;GAC1C,MAAM,YAAY;GAElB,MAAM,SAAS,SAAS,cAAc,QAAQ;GAC9C,OAAO,YAAY;GAEnB,MAAM,UAAU,SAAS,cAAc,KAAK;GAC5C,QAAQ,YAAY;GAEpB,MAAM,OAAO,SAAS,cAAc,OAAO;GAC3C,KAAK,YAAY;GAEjB,MAAM,WAAW,KAAK,cACpB,KAAK,SAAS,eAAe,OAAO,MAAM,GAC1C,QAAQ,OAAO,MAAM,iBACpB,OAAO,KAAK,SAAS,gBAAgB,OAAO,QAAQ,EAAE,CACzD;GACA,KAAK,gBAAgB,IAAI,OAAO,QAAQ,QAAQ;GAEhD,MAAM,QAAQ,SAAS,cAAc,MAAM;GAC3C,MAAM,YAAY;GAClB,MAAM,cAAc,OAAO;GAE3B,MAAM,QAAQ,SAAS,cAAc,MAAM;GAC3C,MAAM,YAAY;GAClB,MAAM,cAAc;GACpB,KAAK,cAAc,IAAI,OAAO,QAAQ,KAAK;GAE3C,KAAK,OAAO,UAAU,KAAK;GAC3B,QAAQ,OAAO,MAAM,KAAK;GAC1B,MAAM,OAAO,QAAQ,OAAO;GAC5B,OAAO,OAAO,KAAK;GAEnB,MAAM,MAAM,OAAO,WAAW,IAAI;GAElC,IAAI,KAAK;IACP,KAAK,cAAc,IAAI,OAAO,QAAQ;KAAE;KAAQ;KAAK,QAAQ;KAAG,OAAO;IAAE,CAAC;IAC1E,KAAK,eAAe,QAAQ,KAAK;GACnC;EACF;EAEA,MAAM,QAAQ,SAAS,cAAc,KAAK;EAC1C,MAAM,YAAY;EAElB,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,QAAQ,SAAS,cAAc,OAAO;GAC5C,MAAM,YAAY;GAElB,MAAM,WAAW,KAAK,cACpB,KAAK,SAAS,gBAAgB,OAAO,GAAG,GACxC,QAAQ,OAAO,MAAM,WACpB,OAAO,KAAK,SAAS,iBAAiB,OAAO,KAAK,EAAE,CACvD;GACA,KAAK,eAAe,IAAI,OAAO,KAAK,QAAQ;GAE5C,MAAM,QAAQ,SAAS,cAAc,MAAM;GAC3C,MAAM,YAAY;GAClB,MAAM,cAAc,OAAO;GAE3B,MAAM,QAAQ,SAAS,cAAc,MAAM;GAC3C,MAAM,YAAY;GAClB,MAAM,cAAc;GACpB,KAAK,aAAa,IAAI,OAAO,KAAK,KAAK;GAEvC,MAAM,OAAO,UAAU,OAAO,KAAK;GACnC,MAAM,OAAO,KAAK;EACpB;EAEA,KAAK,QAAQ,OAAO,KAAK,SAAS,QAAQ,KAAK;CACjD;CAEA,aAAqB;EACnB,MAAM,EAAE,cAAc,KAAK;EAE3B,KAAK,MAAM,SAAS,KAAK,UAAU,OAAO,GACxC,KAAK,eAAe,UAAU,MAAM,OAAO,iBAAiB,MAAM,MAAM;EAG1E,KAAK,UAAU,MAAM;EACrB,KAAK,aAAa,MAAM;EACxB,KAAK,YAAY,gBAAgB;EACjC,KAAK,UAAU,gBAAgB;EAE/B,KAAK,MAAM,UAAU,SAAS;GAC5B,IAAI,CAAC,UAAU,OAAO,IAAI,OAAO,MAAM,GACrC;GAGF,MAAM,MAAM,SAAS,cAAc,KAAK;GACxC,IAAI,YAAY;GAEhB,MAAM,SAAS,SAAS,cAAc,QAAQ;GAC9C,OAAO,YAAY;GAEnB,MAAM,UAAU,SAAS,cAAc,KAAK;GAC5C,QAAQ,YAAY;GAEpB,MAAM,QAAQ,SAAS,cAAc,MAAM;GAC3C,MAAM,YAAY;GAClB,MAAM,cAAc,OAAO;GAE3B,MAAM,QAAQ,SAAS,cAAc,MAAM;GAC3C,MAAM,YAAY;GAClB,MAAM,cAAc;GAEpB,QAAQ,OAAO,OAAO,KAAK;GAC3B,IAAI,OAAO,QAAQ,OAAO;GAC1B,KAAK,YAAY,OAAO,GAAG;GAE3B,MAAM,MAAM,OAAO,WAAW,IAAI;GAElC,IAAI,KAAK;IACP,KAAK,UAAU,IAAI,OAAO,QAAQ;KAAE;KAAQ;KAAK,QAAQ;KAAG,SAAS;KAAO,OAAO;IAAE,CAAC;IACtF,KAAK,eAAe,QAAQ,GAAG;GACjC;EACF;EAEA,KAAK,MAAM,UAAU,SAAS;GAC5B,IAAI,CAAC,UAAU,QAAQ,IAAI,OAAO,GAAG,GACnC;GAGF,MAAM,OAAO,SAAS,cAAc,KAAK;GACzC,KAAK,YAAY;GAEjB,MAAM,QAAQ,SAAS,cAAc,MAAM;GAC3C,MAAM,YAAY;GAClB,MAAM,cAAc,OAAO;GAE3B,MAAM,QAAQ,SAAS,cAAc,MAAM;GAC3C,MAAM,YAAY;GAClB,MAAM,cAAc;GAEpB,KAAK,OAAO,OAAO,KAAK;GACxB,KAAK,UAAU,OAAO,IAAI;GAC1B,KAAK,aAAa,IAAI,OAAO,KAAK,KAAK;EACzC;EAEA,KAAK,eAAe;CACtB;CAEA,qBAA6B;EAC3B,KAAK,MAAM,CAAC,QAAQ,aAAa,KAAK,iBACpC,SAAS,UAAU,KAAK,SAAS,eAAe,MAAM;EAGxD,KAAK,MAAM,CAAC,KAAK,aAAa,KAAK,gBACjC,SAAS,UAAU,KAAK,SAAS,gBAAgB,GAAG;EAGtD,KAAK,YAAY,UAAU,KAAK,SAAS;EACzC,KAAK,MAAM,YAAY,KAAK,SAAS,KAAK;EAC1C,KAAK,WAAW;CAClB;;CAGA,iBAAyB;EACvB,WAAW,KAAK,SAAS,KAAK,KAAK;EAEnC,KAAK,MAAM,CAAC,MAAM,UAAU,KAAK,aAC/B,MAAM,aAAa,gBAAgB,OAAO,SAAS,KAAK,MAAM,SAAS,CAAC;CAE5E;CAEA,cAAsB,SAAkB,OAAe,UAAsC;EAC3F,MAAM,WAAW,SAAS,cAAc,OAAO;EAC/C,SAAS,OAAO;EAChB,SAAS,YAAY;EACrB,SAAS,UAAU;EACnB,SAAS,QAAQ;EACjB,SAAS,aAAa,cAAc,KAAK;EACzC,SAAS,iBAAiB,gBAAgB,SAAS,SAAS,OAAO,CAAC;EAEpE,OAAO;CACT;CAEA,iBAAyB;EACvB,KAAK,QAAQ,UAAU,OAAO,yBAAyB,KAAK,SAAS,SAAS;EAC9E,KAAK,QAAQ,UAAU,OAAO,sBAAsB,KAAK,SAAS,MAAM;CAC1E;CAEA,iBAAyB;EACvB,MAAM,MAAM,OAAO,oBAAoB;EAEvC,MAAM,WAAW,WAAkC;GACjD,KAAK,MAAM,SAAS,QAAQ;IAC1B,MAAM,EAAE,QAAQ,QAAQ;IACxB,MAAM,QAAQ,OAAO;IACrB,MAAM,SAAS,OAAO;IAEtB,IAAI,UAAU,KAAK,WAAW,GAC5B;IAGF,OAAO,QAAQ,KAAK,MAAM,QAAQ,GAAG;IACrC,OAAO,SAAS,KAAK,MAAM,SAAS,GAAG;IACvC,IAAI,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;IACrC,MAAM,QAAQ;IACd,MAAM,SAAS;GACjB;EACF;EAEA,QAAQ,KAAK,cAAc,OAAO,CAAC;EACnC,QAAQ,KAAK,UAAU,OAAO,CAAC;CACjC;AACF;;;;;;;;;;;;;ACrjBA,MAAM,kBAAkB;AAExB,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0XxB,MAAM,gBAAgB,SAAmB,aAAa;CACpD,IAAI,OAAO,KAAK,cAAc,SAAS,gBAAgB,EAAE,GACvD;CAGF,MAAM,QAAQ,OAAO,cAAc,OAAO;CAC1C,MAAM,aAAa,iBAAiB,EAAE;CACtC,MAAM,cAAc;CACpB,OAAO,KAAK,OAAO,KAAK;AAC1B;;;AChYA,MAAM,kBAAkB;AA8CxB,MAAM,QAAQ,OAAe,WAAmB,SAA6B;CAC3E,MAAM,SAAS,SAAS,cAAc,QAAQ;CAC9C,OAAO,OAAO;CACd,OAAO,YAAY,kBAAkB,YAAY,KAAK;CACtD,OAAO,aAAa,cAAc,KAAK;CACvC,OAAO,OAAO,WAAW,MAAM,gBAAgB,CAAC;CAEhD,OAAO;AACT;AAEA,MAAM,gBACJ,SACA,UAA+B,CAAC,MACd;CAClB,MAAM,aAAa,QAAQ,eAAe,KAAA,IAAY,sBAAsB,QAAQ;CACpF,MAAM,WAAW,QAAQ,YAAY,IAAI,YAAY,EAAE,WAAW,CAAC;CACnE,MAAM,QAAQ,IAAI,SAAS,QAAQ,SAAS,QAAQ;CACpD,MAAM,SAAS,QAAQ,UAAU,SAAS;CAE1C,IAAI,QAAQ,gBAAgB,MAC1B,aAAa,OAAO,aAAa;CAGnC,MAAM,OAAO,SAAS,cAAc,KAAK;CACzC,KAAK,YAAY;CACjB,KAAK,QAAQ,OAAO;CACpB,KAAK,aAAa,cAAc,QAAQ,SAAS,aAAa;CAE9D,MAAM,UAAU,SAAS,cAAc,KAAK;CAC5C,QAAQ,YAAY;CACpB,QAAQ,aAAa,eAAe,MAAM;CAE1C,MAAM,QAAQ,SAAS,cAAc,KAAK;CAC1C,MAAM,YAAY;CAClB,MAAM,OAAO,KAAK,0BAA0B,kBAAkB,MAAM;CACpE,MAAM,SAAS,KAAK,mCAAmC,IAAI,SAAS;CACpE,MAAM,OAAO,MAAM,MAAM;CAEzB,MAAM,OAAO,IAAI,gBAAgB;EAC/B,MAAM,QAAQ,QAAQ;EACtB;EACA,WAAW,QAAQ;EACnB;EACA;CACF,CAAC;CAED,KAAK,OAAO,SAAS,OAAO,KAAK,OAAO;CACxC,OAAO,OAAO,IAAI;CAElB,MAAM,kBAAkB;EACtB,KAAK,UAAU,OAAO,kBAAkB,KAAK,QAAQ,MAAM,MAAM;CACnE;CAEA,MAAM,iBAAiB;EACrB,KAAK,UAAU,OAAO,iBAAiB,SAAS,GAAG;CACrD;CAEA,UAAU;CACV,SAAS;CAET,WAAW,MAAM,KAAK;CACtB,MAAM,cAAc,SAAS,UAAU,QAAQ;CAC/C,MAAM,mBAAmB,MAAM,gBAAgB,WAAW,MAAM,KAAK,CAAC;CACtE,MAAM,OAAO,UAAU,MAAM;EAC3B,kBAAkB,QAAQ;EAC1B,QAAQ;EACR;CACF,CAAC;CAED,MAAM,WAAW,SAA8B;EAC7C,KAAK,QAAQ,IAAI;EACjB,UAAU;EACV,4BAA4B,KAAK,QAAQ,CAAC;CAC5C;CAEA,OAAO,iBAAiB,eAAe;EACrC,QAAQ,KAAK,QAAQ,MAAM,YAAY,SAAS,SAAS;CAC3D,CAAC;CAGD,IAAI,YAAY;CAEhB,MAAM,cAAc;EAClB,IAAI,KAAK,UAAU,SAAS,aAAa,GACvC;EAGF,KAAK,UAAU,OAAO,UAAU;CAClC;CAEA,MAAM,QAAQ,WAAoB;EAChC,KAAK,UAAU,IAAI,UAAU;EAC7B,OAAO,aAAa,SAAS;EAE7B,IAAI,QACF,YAAY,OAAO,WAAW,OAAO,eAAe;CAExD;CAEA,MAAM,gBAAgB;EACpB,KAAK,KAAK;CACZ;CAEA,MAAM,WAAW,UAAwB;EACvC,IAAI,KAAK,SAAS,MAAM,aAA4B,GAClD;EAGF,IAAI,MAAM,gBAAgB,WAAW,MAAM,gBAAgB,OAAO;GAChE,KAAK,IAAI;GAET;EACF;EAEA,MAAM;CACR;CAEA,MAAM,UAAU,UAAwB;EACtC,KAAK,MAAM,gBAAgB,OAAO;CACpC;CAEA,KAAK,iBAAiB,gBAAgB,OAAO;CAC7C,KAAK,iBAAiB,gBAAgB,OAAO;CAC7C,KAAK,iBAAiB,eAAe,MAAM;CAE3C,KAAK,MAAM;CAEX,OAAO;EACL,eAAe;GACb,OAAO,aAAa,SAAS;GAC7B,KAAK,oBAAoB,gBAAgB,OAAO;GAChD,KAAK,oBAAoB,gBAAgB,OAAO;GAChD,KAAK,oBAAoB,eAAe,MAAM;GAC9C,YAAY;GACZ,iBAAiB;GACjB,KAAK,QAAQ;GACb,KAAK,QAAQ;GACb,MAAM,QAAQ;GACd,KAAK,OAAO;EACd;EACA,SAAS;EACT,eAAe,KAAK,QAAQ;EAC5B;EACA,gBAAgB,MAAM;EACtB,WAAW,SAAoB,MAAM,QAAQ,IAAI;EACjD;EACA;EACA;CACF;AACF"}
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as PerformanceMonitorOptions, t as PerformanceMonitor } from "./performance-monitor-WGLWbxOZ.js";
|
|
2
|
-
import { t as MountPerfHudOptions } from "./mount-perf-hud-
|
|
2
|
+
import { t as MountPerfHudOptions } from "./mount-perf-hud-B3wBLrcu.js";
|
|
3
3
|
//#region src/react/monitor-store.d.ts
|
|
4
4
|
type MonitorListener = (monitor: PerformanceMonitor | null) => void;
|
|
5
5
|
type PerfMonitorStore = {
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as PerformanceMonitor } from "./performance-monitor-qswCj9Nl.js";
|
|
2
|
-
import { t as mountPerfHud } from "./mount-perf-hud-
|
|
2
|
+
import { t as mountPerfHud } from "./mount-perf-hud-CZNSZVYn.js";
|
|
3
3
|
import { useEffect, useRef } from "react";
|
|
4
4
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
5
5
|
//#region src/react/monitor-store.ts
|
package/dist/styles.css
CHANGED
|
@@ -128,6 +128,54 @@
|
|
|
128
128
|
border-bottom: 1px solid var(--perf-border);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
.perf-monitor__row--static {
|
|
132
|
+
cursor: default;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Light / system / dark, as a small segmented radio group. */
|
|
136
|
+
.perf-monitor__segment {
|
|
137
|
+
display: inline-flex;
|
|
138
|
+
flex: none;
|
|
139
|
+
gap: 1px;
|
|
140
|
+
padding: 1px;
|
|
141
|
+
border-radius: 4px;
|
|
142
|
+
background: var(--perf-row);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.perf-monitor__segment-option {
|
|
146
|
+
display: flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
width: 20px;
|
|
150
|
+
height: 16px;
|
|
151
|
+
padding: 0;
|
|
152
|
+
border: 0;
|
|
153
|
+
border-radius: 3px;
|
|
154
|
+
background: transparent;
|
|
155
|
+
color: var(--perf-muted);
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.perf-monitor__segment-option:hover {
|
|
160
|
+
color: var(--perf-fg);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.perf-monitor__segment-option[aria-checked="true"] {
|
|
164
|
+
color: var(--perf-fg);
|
|
165
|
+
background: var(--perf-border);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.perf-monitor__segment-option:focus-visible {
|
|
169
|
+
outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
|
|
170
|
+
outline-offset: 1px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.perf-monitor__segment-option .perf-monitor__icon {
|
|
174
|
+
width: 11px;
|
|
175
|
+
height: 11px;
|
|
176
|
+
color: inherit;
|
|
177
|
+
}
|
|
178
|
+
|
|
131
179
|
.perf-monitor__checkbox {
|
|
132
180
|
flex: none;
|
|
133
181
|
margin: 0;
|
package/dist/ui.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as dockPanel, S as ScreenEdge, _ as DefaultPlacement, a as PerformanceViewMode, b as DockPanelOptions, c as
|
|
1
|
+
import { C as dockPanel, S as ScreenEdge, _ as DefaultPlacement, a as PerformanceViewMode, b as DockPanelOptions, c as HudDefaults, d as HudSettingsOptions, f as HudTheme, g as isThemeMode, h as ThemeMode, i as PerformanceView, l as HudSelection, m as THEME_MODES, n as PerfHudHandle, o as PerformanceViewOptions, p as ResolvedTheme, r as mountPerfHud, s as DEFAULT_STORAGE_KEY, t as MountPerfHudOptions, u as HudSettings, v as DockAlign, x as Placement, y as DockHandle } from "./mount-perf-hud-B3wBLrcu.js";
|
|
2
2
|
//#region src/ui/styles.d.ts
|
|
3
|
-
declare const PERF_HUD_STYLES = "\n.perf-hud,\n.perf-monitor {\n --perf-bg: rgba(22, 24, 29, 0.85);\n --perf-fg: #e6e8eb;\n --perf-fg-dim: #c5c8ce;\n --perf-muted: #8b909a;\n --perf-row: rgba(255, 255, 255, 0.04);\n --perf-border: rgba(255, 255, 255, 0.12);\n --perf-accent: #60a5fa;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);\n\n color-scheme: dark;\n}\n\n.perf-hud[data-theme=\"light\"],\n.perf-monitor[data-theme=\"light\"] {\n --perf-bg: rgba(250, 250, 252, 0.88);\n --perf-fg: #1a1c21;\n --perf-fg-dim: #4b5058;\n --perf-muted: #6b7079;\n --perf-row: rgba(0, 0, 0, 0.05);\n --perf-border: rgba(0, 0, 0, 0.12);\n --perf-accent: #2563eb;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);\n\n color-scheme: light;\n}\n\n.perf-monitor {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 8px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.4;\n color: var(--perf-fg);\n}\n\n.perf-monitor__graphs {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.perf-monitor__graph {\n position: relative;\n height: 40px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__canvas {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.perf-monitor__graph-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 8px;\n pointer-events: none;\n}\n\n.perf-monitor__graph-head {\n display: flex;\n align-items: center;\n gap: 6px;\n cursor: pointer;\n}\n\n.perf-monitor__graph-overlay .perf-monitor__graph-head {\n pointer-events: auto;\n}\n\n.perf-monitor__graph-label {\n color: var(--perf-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n font-size: 10px;\n}\n\n.perf-monitor__graph-value {\n min-width: 6ch;\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__section {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.perf-monitor__row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 2px 6px;\n border-radius: 3px;\n cursor: pointer;\n}\n\n.perf-monitor__row:nth-child(even) {\n background: var(--perf-row);\n}\n\n.perf-monitor__label {\n flex: 1;\n color: var(--perf-muted);\n}\n\n.perf-monitor__icon {\n flex: none;\n width: 12px;\n height: 12px;\n color: var(--perf-muted);\n}\n\n.perf-monitor__options {\n padding-bottom: 8px;\n border-bottom: 1px solid var(--perf-border);\n}\n\n.perf-monitor__checkbox {\n flex: none;\n margin: 0;\n width: 12px;\n height: 12px;\n accent-color: var(--perf-accent);\n cursor: pointer;\n}\n\n.perf-monitor__value {\n min-width: 7ch;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__hud {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 2px;\n}\n\n.perf-monitor__hud-graphs {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.perf-monitor__hud-graphs:empty {\n display: none;\n}\n\n.perf-monitor__hud-graph {\n position: relative;\n height: 28px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__hud-grid {\n display: grid;\n grid-template-columns: auto 6ch auto 6ch;\n align-items: baseline;\n gap: 2px 8px;\n}\n\n.perf-monitor__hud-grid:empty {\n display: none;\n}\n\n.perf-monitor__hud-item {\n display: contents;\n}\n\n.perf-monitor__hud-label {\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__hud-value {\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor--full .perf-monitor__hud {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__graphs,\n.perf-monitor--compact .perf-monitor__section {\n display: none;\n}\n\n.perf-monitor--compact {\n padding: 4px 8px;\n}\n\n.perf-hud {\n position: fixed;\n z-index: 9999;\n /* Safari resolves width:100% canvases against the viewport on a fixed\n host with auto width. Shrink-wrap so expand stays a card, not the window. */\n width: max-content;\n max-width: min(20rem, calc(100vw - 48px));\n overflow: visible;\n background: transparent;\n pointer-events: none;\n user-select: none;\n touch-action: none;\n}\n\n.perf-hud--full > .perf-monitor {\n width: 16rem;\n max-width: 100%;\n}\n\n.perf-hud > .perf-monitor {\n position: relative;\n z-index: 1;\n max-height: 70vh;\n overflow: auto;\n background: var(--perf-bg);\n border-radius: 6px;\n box-shadow: var(--perf-shadow);\n backdrop-filter: blur(2px);\n pointer-events: auto;\n transition: opacity 0.25s ease;\n}\n\n.perf-hud--dim:not(.is-awake):not(.is-dragging) > .perf-monitor {\n opacity: 0.32;\n}\n\n.perf-hud__hotspot {\n position: absolute;\n z-index: 0;\n inset: -20px;\n pointer-events: auto;\n}\n\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__hotspot {\n right: -46px;\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__hotspot {\n left: -46px;\n}\n\n.perf-hud__tools {\n position: absolute;\n top: 0;\n z-index: 2;\n display: flex;\n flex-direction: column;\n gap: 4px;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s ease;\n}\n\n.perf-hud.is-awake .perf-hud__tools,\n.perf-hud.is-dragging .perf-hud__tools {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* Inward side: right by default, left when docked to the right edge. */\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__tools {\n left: calc(100% + 6px);\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__tools {\n right: calc(100% + 6px);\n}\n\n.perf-hud__disc {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border: 0;\n border-radius: 999px;\n background: var(--perf-bg);\n box-shadow: var(--perf-shadow);\n color: var(--perf-fg-dim);\n cursor: pointer;\n backdrop-filter: blur(2px);\n}\n\n.perf-hud__icon {\n width: 12px;\n height: 12px;\n}\n\n.perf-hud__drag {\n cursor: grab;\n}\n\n.perf-hud.is-dragging .perf-hud__drag {\n cursor: grabbing;\n}\n\n.perf-hud__disc:hover,\n.perf-hud__disc:focus-visible,\n.perf-hud__disc[aria-pressed=\"true\"] {\n color: var(--perf-fg);\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\n}\n";
|
|
3
|
+
declare const PERF_HUD_STYLES = "\n.perf-hud,\n.perf-monitor {\n --perf-bg: rgba(22, 24, 29, 0.85);\n --perf-fg: #e6e8eb;\n --perf-fg-dim: #c5c8ce;\n --perf-muted: #8b909a;\n --perf-row: rgba(255, 255, 255, 0.04);\n --perf-border: rgba(255, 255, 255, 0.12);\n --perf-accent: #60a5fa;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);\n\n color-scheme: dark;\n}\n\n.perf-hud[data-theme=\"light\"],\n.perf-monitor[data-theme=\"light\"] {\n --perf-bg: rgba(250, 250, 252, 0.88);\n --perf-fg: #1a1c21;\n --perf-fg-dim: #4b5058;\n --perf-muted: #6b7079;\n --perf-row: rgba(0, 0, 0, 0.05);\n --perf-border: rgba(0, 0, 0, 0.12);\n --perf-accent: #2563eb;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);\n\n color-scheme: light;\n}\n\n.perf-monitor {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 8px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.4;\n color: var(--perf-fg);\n}\n\n.perf-monitor__graphs {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.perf-monitor__graph {\n position: relative;\n height: 40px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__canvas {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.perf-monitor__graph-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 8px;\n pointer-events: none;\n}\n\n.perf-monitor__graph-head {\n display: flex;\n align-items: center;\n gap: 6px;\n cursor: pointer;\n}\n\n.perf-monitor__graph-overlay .perf-monitor__graph-head {\n pointer-events: auto;\n}\n\n.perf-monitor__graph-label {\n color: var(--perf-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n font-size: 10px;\n}\n\n.perf-monitor__graph-value {\n min-width: 6ch;\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__section {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.perf-monitor__row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 2px 6px;\n border-radius: 3px;\n cursor: pointer;\n}\n\n.perf-monitor__row:nth-child(even) {\n background: var(--perf-row);\n}\n\n.perf-monitor__label {\n flex: 1;\n color: var(--perf-muted);\n}\n\n.perf-monitor__icon {\n flex: none;\n width: 12px;\n height: 12px;\n color: var(--perf-muted);\n}\n\n.perf-monitor__options {\n padding-bottom: 8px;\n border-bottom: 1px solid var(--perf-border);\n}\n\n.perf-monitor__row--static {\n cursor: default;\n}\n\n/* Light / system / dark, as a small segmented radio group. */\n.perf-monitor__segment {\n display: inline-flex;\n flex: none;\n gap: 1px;\n padding: 1px;\n border-radius: 4px;\n background: var(--perf-row);\n}\n\n.perf-monitor__segment-option {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 16px;\n padding: 0;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: var(--perf-muted);\n cursor: pointer;\n}\n\n.perf-monitor__segment-option:hover {\n color: var(--perf-fg);\n}\n\n.perf-monitor__segment-option[aria-checked=\"true\"] {\n color: var(--perf-fg);\n background: var(--perf-border);\n}\n\n.perf-monitor__segment-option:focus-visible {\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\n}\n\n.perf-monitor__segment-option .perf-monitor__icon {\n width: 11px;\n height: 11px;\n color: inherit;\n}\n\n.perf-monitor__checkbox {\n flex: none;\n margin: 0;\n width: 12px;\n height: 12px;\n accent-color: var(--perf-accent);\n cursor: pointer;\n}\n\n.perf-monitor__value {\n min-width: 7ch;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__hud {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 2px;\n}\n\n.perf-monitor__hud-graphs {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.perf-monitor__hud-graphs:empty {\n display: none;\n}\n\n.perf-monitor__hud-graph {\n position: relative;\n height: 28px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__hud-grid {\n display: grid;\n grid-template-columns: auto 6ch auto 6ch;\n align-items: baseline;\n gap: 2px 8px;\n}\n\n.perf-monitor__hud-grid:empty {\n display: none;\n}\n\n.perf-monitor__hud-item {\n display: contents;\n}\n\n.perf-monitor__hud-label {\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__hud-value {\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor--full .perf-monitor__hud {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__graphs,\n.perf-monitor--compact .perf-monitor__section {\n display: none;\n}\n\n.perf-monitor--compact {\n padding: 4px 8px;\n}\n\n.perf-hud {\n position: fixed;\n z-index: 9999;\n /* Safari resolves width:100% canvases against the viewport on a fixed\n host with auto width. Shrink-wrap so expand stays a card, not the window. */\n width: max-content;\n max-width: min(20rem, calc(100vw - 48px));\n overflow: visible;\n background: transparent;\n pointer-events: none;\n user-select: none;\n touch-action: none;\n}\n\n.perf-hud--full > .perf-monitor {\n width: 16rem;\n max-width: 100%;\n}\n\n.perf-hud > .perf-monitor {\n position: relative;\n z-index: 1;\n max-height: 70vh;\n overflow: auto;\n background: var(--perf-bg);\n border-radius: 6px;\n box-shadow: var(--perf-shadow);\n backdrop-filter: blur(2px);\n pointer-events: auto;\n transition: opacity 0.25s ease;\n}\n\n.perf-hud--dim:not(.is-awake):not(.is-dragging) > .perf-monitor {\n opacity: 0.32;\n}\n\n.perf-hud__hotspot {\n position: absolute;\n z-index: 0;\n inset: -20px;\n pointer-events: auto;\n}\n\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__hotspot {\n right: -46px;\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__hotspot {\n left: -46px;\n}\n\n.perf-hud__tools {\n position: absolute;\n top: 0;\n z-index: 2;\n display: flex;\n flex-direction: column;\n gap: 4px;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s ease;\n}\n\n.perf-hud.is-awake .perf-hud__tools,\n.perf-hud.is-dragging .perf-hud__tools {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* Inward side: right by default, left when docked to the right edge. */\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__tools {\n left: calc(100% + 6px);\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__tools {\n right: calc(100% + 6px);\n}\n\n.perf-hud__disc {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border: 0;\n border-radius: 999px;\n background: var(--perf-bg);\n box-shadow: var(--perf-shadow);\n color: var(--perf-fg-dim);\n cursor: pointer;\n backdrop-filter: blur(2px);\n}\n\n.perf-hud__icon {\n width: 12px;\n height: 12px;\n}\n\n.perf-hud__drag {\n cursor: grab;\n}\n\n.perf-hud.is-dragging .perf-hud__drag {\n cursor: grabbing;\n}\n\n.perf-hud__disc:hover,\n.perf-hud__disc:focus-visible,\n.perf-hud__disc[aria-pressed=\"true\"] {\n color: var(--perf-fg);\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\n}\n";
|
|
4
4
|
/**
|
|
5
5
|
* Append the stylesheet to `target`'s head, once. Call before mounting a view
|
|
6
6
|
* by hand; {@link mountPerfHud} does it for you unless told not to.
|
package/dist/ui.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as dockPanel, c as HudTheme, i as PerformanceView, l as THEME_MODES, n as PERF_HUD_STYLES, o as DEFAULT_STORAGE_KEY, r as injectStyles, s as HudSettings, t as mountPerfHud, u as isThemeMode } from "./mount-perf-hud-CZNSZVYn.js";
|
|
2
2
|
export { DEFAULT_STORAGE_KEY, HudSettings, HudTheme, PERF_HUD_STYLES, PerformanceView, THEME_MODES, dockPanel, injectStyles, isThemeMode, mountPerfHud };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mount-perf-hud-DvDMg6uj.js","names":[],"sources":["../src/ui/hud-settings.ts","../src/ui/dock-panel.ts","../src/ui/icons.ts","../src/ui/sparkline.ts","../src/ui/theme.ts","../src/ui/performance-view.ts","../src/ui/styles.ts","../src/ui/mount-perf-hud.ts"],"sourcesContent":["/**\n * Which metrics the compact HUD shows, plus the dim-on-leave toggle. One instance per\n * HUD; the full-mode checkboxes and the compact card stay in sync through it.\n * Persists to `localStorage` under `storageKey` (pass `null` to keep it in\n * memory only).\n */\nimport type { TimingMetric } from \"../core/types.ts\";\n\ntype HudSelection = {\n dim: boolean;\n graphs: ReadonlySet<TimingMetric>;\n numbers: ReadonlySet<string>;\n};\n\ntype HudDefaults = {\n dim?: boolean;\n graphs?: readonly TimingMetric[];\n numbers?: readonly string[];\n};\n\ntype HudSettingsOptions = {\n /** localStorage key. Default `three-meter`; `null` disables persistence. */\n storageKey?: string | null;\n /** What a first visit shows. */\n defaults?: HudDefaults;\n};\n\ntype MutableSelection = {\n dim: boolean;\n graphs: Set<TimingMetric>;\n numbers: Set<string>;\n};\n\nconst DEFAULT_STORAGE_KEY = \"three-meter\";\nconst DEFAULT_DIM = false;\nconst DEFAULT_NUMBERS: readonly string[] = [\"fps\", \"calls\", \"cpu\", \"gpu\"];\nconst DEFAULT_GRAPHS: readonly TimingMetric[] = [];\nconst TIMING_METRICS = new Set<TimingMetric>([\"fps\", \"cpu\", \"gpu\"]);\n\nconst isTimingMetric = (value: string): value is TimingMetric =>\n TIMING_METRICS.has(value as TimingMetric);\n\nclass HudSettings {\n private readonly storageKey: string | null;\n private readonly defaults: Required<HudDefaults>;\n private readonly listeners = new Set<() => void>();\n private readonly state: MutableSelection;\n\n constructor(options: HudSettingsOptions = {}) {\n this.storageKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n this.defaults = {\n dim: options.defaults?.dim ?? DEFAULT_DIM,\n graphs: options.defaults?.graphs ?? DEFAULT_GRAPHS,\n numbers: options.defaults?.numbers ?? DEFAULT_NUMBERS,\n };\n this.state = this.load();\n }\n\n get selection(): HudSelection {\n return this.state;\n }\n\n get dim() {\n return this.state.dim;\n }\n\n setDim(enabled: boolean) {\n this.state.dim = enabled;\n this.persistAndNotify();\n }\n\n isNumberEnabled(key: string) {\n return this.state.numbers.has(key);\n }\n\n isGraphEnabled(metric: TimingMetric) {\n return this.state.graphs.has(metric);\n }\n\n setNumberEnabled(key: string, enabled: boolean) {\n if (enabled) {\n this.state.numbers.add(key);\n } else {\n this.state.numbers.delete(key);\n }\n\n this.persistAndNotify();\n }\n\n setGraphEnabled(metric: TimingMetric, enabled: boolean) {\n if (enabled) {\n this.state.graphs.add(metric);\n } else {\n this.state.graphs.delete(metric);\n }\n\n this.persistAndNotify();\n }\n\n subscribe(listener: () => void) {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private load(): MutableSelection {\n const fallback = (): MutableSelection => ({\n dim: this.defaults.dim,\n graphs: new Set(this.defaults.graphs),\n numbers: new Set(this.defaults.numbers),\n });\n\n if (this.storageKey === null) {\n return fallback();\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n\n if (!raw) {\n return fallback();\n }\n\n const parsed = JSON.parse(raw) as { dim?: boolean; graphs?: string[]; numbers?: string[] };\n\n return {\n dim: parsed.dim ?? this.defaults.dim,\n graphs: new Set((parsed.graphs ?? this.defaults.graphs).filter(isTimingMetric)),\n numbers: new Set(parsed.numbers ?? this.defaults.numbers),\n };\n } catch {\n // Malformed or unavailable storage → defaults.\n return fallback();\n }\n }\n\n private persistAndNotify() {\n if (this.storageKey !== null) {\n try {\n localStorage.setItem(\n this.storageKey,\n JSON.stringify({\n dim: this.state.dim,\n graphs: [...this.state.graphs],\n numbers: [...this.state.numbers],\n }),\n );\n } catch {\n // In-memory selection still drives the live views.\n }\n }\n\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n\nexport { DEFAULT_STORAGE_KEY, HudSettings };\nexport type { HudDefaults, HudSelection, HudSettingsOptions };\n","/**\n * Dock a fixed-position panel to a screen edge. Dragging the handle snaps it\n * to the nearest edge on drop; the edge and the offset along it persist under\n * `<storageKey>:placement`. A stored box whose centre sits outside the current\n * viewport is re-docked to whichever edge that box is closest to.\n */\nimport { DEFAULT_STORAGE_KEY } from \"./hud-settings.ts\";\n\nconst INSET = 10;\n/** Room for the stacked control discs on the inward side of the panel. */\nconst SIDE_CLEARANCE = 26;\nconst DRAG_THRESHOLD = 4;\nconst EDGES = [\"left\", \"right\", \"top\", \"bottom\"] as const;\n\ntype ScreenEdge = (typeof EDGES)[number];\n\ntype Placement = {\n /** Top (left/right) or left (top/bottom) of the host, in CSS pixels. */\n along: number;\n edge: ScreenEdge;\n};\n\ntype Box = {\n height: number;\n left: number;\n top: number;\n width: number;\n};\n\ntype DockAlign = \"start\" | \"center\" | \"end\";\n\ntype DefaultPlacement = {\n edge: ScreenEdge;\n /** Where along that edge on a first visit. Default `center`. */\n align?: DockAlign;\n};\n\ntype DockPanelOptions = {\n /** The drag grip. Without one the panel still docks and re-docks on resize. */\n handle?: HTMLElement | null;\n /** First-visit position, before anything is stored. Default left edge, centred. */\n defaultPlacement?: DefaultPlacement;\n /** Shares the HUD's key; `null` disables persistence. Default `three-meter`. */\n storageKey?: string | null;\n};\n\ntype DockHandle = {\n /** Re-resolve against the current viewport and panel size (after a mode change, say). */\n refresh: () => Placement;\n dispose: () => void;\n};\n\nconst viewportBox = (): Box => ({\n height: window.innerHeight,\n left: 0,\n top: 0,\n width: window.innerWidth,\n});\n\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst panelBox = (element: HTMLElement): Box => {\n const rect = element.getBoundingClientRect();\n\n return {\n height: rect.height || element.offsetHeight,\n left: rect.left,\n top: rect.top,\n width: rect.width || element.offsetWidth,\n };\n};\n\nconst clampAlong = (edge: ScreenEdge, along: number, view: Box, panel: Box) => {\n if (edge === \"left\" || edge === \"right\") {\n return clamp(along, INSET, Math.max(INSET, view.height - panel.height - INSET));\n }\n\n // Tools sit on the right (inward) unless the panel is on the right edge.\n return clamp(along, INSET, Math.max(INSET, view.width - panel.width - INSET - SIDE_CLEARANCE));\n};\n\nconst ghostBox = (placement: Placement, panel: Box, view: Box): Box => {\n switch (placement.edge) {\n case \"left\":\n return { height: panel.height, left: INSET, top: placement.along, width: panel.width };\n case \"right\":\n return {\n height: panel.height,\n left: view.width - INSET - panel.width,\n top: placement.along,\n width: panel.width,\n };\n case \"top\":\n return { height: panel.height, left: placement.along, top: INSET, width: panel.width };\n case \"bottom\":\n return {\n height: panel.height,\n left: placement.along,\n top: view.height - INSET - panel.height,\n width: panel.width,\n };\n }\n};\n\nconst centerOutside = (box: Box, view: Box) => {\n const x = box.left + box.width / 2;\n const y = box.top + box.height / 2;\n\n return x < 0 || y < 0 || x > view.width || y > view.height;\n};\n\nconst snapBoxToEdge = (box: Box, view: Box): Placement => {\n const distances: Record<ScreenEdge, number> = {\n bottom: view.height - (box.top + box.height),\n left: box.left,\n right: view.width - (box.left + box.width),\n top: box.top,\n };\n\n let edge: ScreenEdge = \"left\";\n let nearest = Number.POSITIVE_INFINITY;\n\n for (const next of EDGES) {\n if (distances[next] < nearest) {\n nearest = distances[next];\n edge = next;\n }\n }\n\n const along = edge === \"left\" || edge === \"right\" ? box.top : box.left;\n\n return { along: Math.round(clampAlong(edge, along, view, box)), edge };\n};\n\nconst initialPlacement = (preset: DefaultPlacement, view: Box, panel: Box): Placement => {\n const { edge } = preset;\n const vertical = edge === \"left\" || edge === \"right\";\n const span = vertical ? view.height : view.width;\n const size = vertical ? panel.height : panel.width;\n const along =\n preset.align === \"start\" ? 0 : preset.align === \"end\" ? span - size : (span - size) / 2;\n\n return { along: Math.round(clampAlong(edge, along, view, panel)), edge };\n};\n\nconst isEdge = (value: unknown): value is ScreenEdge =>\n typeof value === \"string\" && (EDGES as readonly string[]).includes(value);\n\nconst readPlacement = (key: string | null): Placement | null => {\n if (key === null) {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(key);\n\n if (!raw) {\n return null;\n }\n\n const parsed = JSON.parse(raw) as { along?: unknown; edge?: unknown };\n\n if (\n typeof parsed.along !== \"number\" ||\n !Number.isFinite(parsed.along) ||\n !isEdge(parsed.edge)\n ) {\n return null;\n }\n\n return { along: parsed.along, edge: parsed.edge };\n } catch {\n return null;\n }\n};\n\nconst writePlacement = (key: string | null, placement: Placement) => {\n if (key === null) {\n return;\n }\n\n try {\n localStorage.setItem(key, JSON.stringify(placement));\n } catch {\n // Best-effort; the live dock still applies.\n }\n};\n\nconst resolvePlacement = (\n stored: Placement | null,\n preset: DefaultPlacement,\n panel: Box,\n view: Box,\n): Placement => {\n if (!stored) {\n return initialPlacement(preset, view, panel);\n }\n\n const ghost = ghostBox(stored, panel, view);\n\n if (centerOutside(ghost, view)) {\n return snapBoxToEdge(ghost, view);\n }\n\n return {\n along: Math.round(clampAlong(stored.edge, stored.along, view, panel)),\n edge: stored.edge,\n };\n};\n\nconst paintPlacement = (element: HTMLElement, placement: Placement) => {\n element.dataset.edge = placement.edge;\n element.style.left = \"\";\n element.style.right = \"\";\n element.style.top = \"\";\n element.style.bottom = \"\";\n element.style.transform = \"\";\n\n switch (placement.edge) {\n case \"left\":\n element.style.left = `${INSET}px`;\n element.style.top = `${placement.along}px`;\n break;\n case \"right\":\n element.style.right = `${INSET}px`;\n element.style.top = `${placement.along}px`;\n break;\n case \"top\":\n element.style.top = `${INSET}px`;\n element.style.left = `${placement.along}px`;\n break;\n case \"bottom\":\n element.style.bottom = `${INSET}px`;\n element.style.left = `${placement.along}px`;\n break;\n }\n};\n\n/**\n * Dock `element` from storage (or the left centre), re-dock on resize, and\n * snap to the nearest edge when the handle drops.\n */\nconst dockPanel = (element: HTMLElement, options: DockPanelOptions = {}): DockHandle => {\n const baseKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n const key = baseKey === null ? null : `${baseKey}:placement`;\n const handle = options.handle ?? null;\n const preset = options.defaultPlacement ?? { edge: \"left\" };\n\n const apply = (): Placement => {\n const stored = readPlacement(key);\n const view = viewportBox();\n\n if (element.classList.contains(\"is-dragging\")) {\n return stored ?? initialPlacement(preset, view, panelBox(element));\n }\n\n const panel = panelBox(element);\n\n if (panel.width === 0 || panel.height === 0) {\n return stored ?? initialPlacement(preset, view, panel);\n }\n\n const next = resolvePlacement(stored, preset, panel, view);\n paintPlacement(element, next);\n\n if (!stored || stored.edge !== next.edge || stored.along !== next.along) {\n writePlacement(key, next);\n }\n\n return next;\n };\n\n apply();\n requestAnimationFrame(apply);\n\n const onResize = () => apply();\n window.addEventListener(\"resize\", onResize);\n\n const resizeObserver = new ResizeObserver(() => apply());\n resizeObserver.observe(element);\n\n const disposeBase = () => {\n window.removeEventListener(\"resize\", onResize);\n resizeObserver.disconnect();\n };\n\n if (!handle) {\n return { dispose: disposeBase, refresh: apply };\n }\n\n let pointerId: number | null = null;\n let originX = 0;\n let originY = 0;\n let grabX = 0;\n let grabY = 0;\n let dragging = false;\n\n const finish = (event: PointerEvent) => {\n if (pointerId !== event.pointerId) {\n return;\n }\n\n if (dragging) {\n const next = snapBoxToEdge(panelBox(element), viewportBox());\n paintPlacement(element, next);\n writePlacement(key, next);\n }\n\n dragging = false;\n pointerId = null;\n element.classList.remove(\"is-dragging\");\n\n try {\n handle.releasePointerCapture(event.pointerId);\n } catch {\n // Capture was never set.\n }\n };\n\n const onPointerDown = (event: PointerEvent) => {\n if (event.button !== 0) {\n return;\n }\n\n const box = panelBox(element);\n pointerId = event.pointerId;\n originX = event.clientX;\n originY = event.clientY;\n grabX = event.clientX - box.left;\n grabY = event.clientY - box.top;\n dragging = false;\n handle.setPointerCapture(event.pointerId);\n };\n\n const onPointerMove = (event: PointerEvent) => {\n if (pointerId !== event.pointerId) {\n return;\n }\n\n const dx = event.clientX - originX;\n const dy = event.clientY - originY;\n\n if (!dragging && dx * dx + dy * dy < DRAG_THRESHOLD * DRAG_THRESHOLD) {\n return;\n }\n\n dragging = true;\n element.classList.add(\"is-dragging\");\n element.style.right = \"\";\n element.style.bottom = \"\";\n element.style.transform = \"\";\n element.style.left = `${event.clientX - grabX}px`;\n element.style.top = `${event.clientY - grabY}px`;\n };\n\n handle.addEventListener(\"pointerdown\", onPointerDown);\n handle.addEventListener(\"pointermove\", onPointerMove);\n handle.addEventListener(\"pointerup\", finish);\n handle.addEventListener(\"pointercancel\", finish);\n\n return {\n dispose: () => {\n disposeBase();\n handle.removeEventListener(\"pointerdown\", onPointerDown);\n handle.removeEventListener(\"pointermove\", onPointerMove);\n handle.removeEventListener(\"pointerup\", finish);\n handle.removeEventListener(\"pointercancel\", finish);\n },\n refresh: apply,\n };\n};\n\nexport { dockPanel };\nexport type { DefaultPlacement, DockAlign, DockHandle, DockPanelOptions, Placement, ScreenEdge };\n","/**\n * The three Lucide marks the HUD chrome uses, inlined so the package carries\n * no icon dependency. The markup is static, so nothing user-supplied reaches it.\n */\ntype IconName = \"blend\" | \"grip\" | \"sliders\";\n\nconst ICON_MARKUP: Record<IconName, string> = {\n blend: '<circle cx=\"9\" cy=\"9\" r=\"7\"/><circle cx=\"15\" cy=\"15\" r=\"7\"/>',\n grip:\n '<circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"5\" r=\"1\"/><circle cx=\"9\" cy=\"19\" r=\"1\"/>' +\n '<circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"5\" r=\"1\"/><circle cx=\"15\" cy=\"19\" r=\"1\"/>',\n sliders:\n '<line x1=\"21\" x2=\"14\" y1=\"4\" y2=\"4\"/><line x1=\"10\" x2=\"3\" y1=\"4\" y2=\"4\"/>' +\n '<line x1=\"21\" x2=\"12\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"3\" y1=\"12\" y2=\"12\"/>' +\n '<line x1=\"21\" x2=\"16\" y1=\"20\" y2=\"20\"/><line x1=\"12\" x2=\"3\" y1=\"20\" y2=\"20\"/>' +\n '<line x1=\"14\" x2=\"14\" y1=\"2\" y2=\"6\"/><line x1=\"8\" x2=\"8\" y1=\"10\" y2=\"14\"/>' +\n '<line x1=\"16\" x2=\"16\" y1=\"18\" y2=\"22\"/>',\n};\n\nconst createIcon = (name: IconName, className: string) => {\n const svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n svg.setAttribute(\"viewBox\", \"0 0 24 24\");\n svg.setAttribute(\"fill\", \"none\");\n svg.setAttribute(\"stroke\", \"currentColor\");\n svg.setAttribute(\"stroke-width\", \"2\");\n svg.setAttribute(\"stroke-linecap\", \"round\");\n svg.setAttribute(\"stroke-linejoin\", \"round\");\n svg.setAttribute(\"aria-hidden\", \"true\");\n svg.setAttribute(\"class\", className);\n svg.innerHTML = ICON_MARKUP[name];\n\n return svg;\n};\n\nexport { createIcon };\nexport type { IconName };\n","/**\n * Minimal canvas sparkline. The caller owns the canvas, its size, and any\n * device-pixel-ratio transform; this just paints a filled line series in\n * logical (CSS) pixel coordinates.\n */\ntype SparklineStyle = {\n fill: string;\n stroke: string;\n};\n\nfunction drawSparkline(\n ctx: CanvasRenderingContext2D,\n width: number,\n height: number,\n values: readonly number[],\n style: SparklineStyle,\n) {\n ctx.clearRect(0, 0, width, height);\n\n if (values.length < 2) {\n return;\n }\n\n let max = 0;\n\n for (const value of values) {\n if (value > max) {\n max = value;\n }\n }\n\n const range = max * 1.1 || 1;\n const stepX = width / (values.length - 1);\n const toY = (value: number) => height - (value / range) * height;\n\n ctx.beginPath();\n ctx.moveTo(0, toY(values[0]!));\n\n for (let index = 1; index < values.length; index += 1) {\n ctx.lineTo(index * stepX, toY(values[index]!));\n }\n\n ctx.lineWidth = 1;\n ctx.strokeStyle = style.stroke;\n ctx.stroke();\n\n ctx.lineTo(width, height);\n ctx.lineTo(0, height);\n ctx.closePath();\n ctx.fillStyle = style.fill;\n ctx.fill();\n}\n\nexport { drawSparkline };\nexport type { SparklineStyle };\n","/**\n * Dark, light, or follow the OS. One instance per HUD: the floating host and\n * the card inside it both paint `resolved` onto `data-theme`, and the\n * stylesheet picks the palette from that attribute. `system` tracks\n * `prefers-color-scheme` for as long as the instance lives.\n */\ntype ThemeMode = \"dark\" | \"light\" | \"system\";\ntype ResolvedTheme = Exclude<ThemeMode, \"system\">;\n\nconst THEME_MODES: readonly ThemeMode[] = [\"dark\", \"light\", \"system\"];\nconst DARK_SCHEME_QUERY = \"(prefers-color-scheme: dark)\";\n\nconst isThemeMode = (value: unknown): value is ThemeMode =>\n typeof value === \"string\" && (THEME_MODES as readonly string[]).includes(value);\n\nclass HudTheme {\n private current: ThemeMode;\n private readonly listeners = new Set<() => void>();\n private media: MediaQueryList | null = null;\n private readonly onSchemeChange = () => this.notify();\n\n constructor(mode: ThemeMode = \"system\") {\n this.current = mode;\n this.watch();\n }\n\n /** What the host asked for. */\n get mode(): ThemeMode {\n return this.current;\n }\n\n /**\n * `dark` or `light`, with `system` resolved against the OS preference.\n * Where `matchMedia` is missing, `system` falls back to `dark`, the HUD's\n * original palette.\n */\n get resolved(): ResolvedTheme {\n if (this.current !== \"system\") {\n return this.current;\n }\n\n if (!this.media) {\n return \"dark\";\n }\n\n return this.media.matches ? \"dark\" : \"light\";\n }\n\n setMode(mode: ThemeMode) {\n if (mode === this.current) {\n return;\n }\n\n this.current = mode;\n this.watch();\n this.notify();\n }\n\n /** Fires on `setMode` and, in `system`, whenever the OS preference flips. */\n subscribe(listener: () => void) {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** Stop tracking the OS preference and drop every subscriber. */\n dispose() {\n this.unwatch();\n this.listeners.clear();\n }\n\n private watch() {\n if (this.current !== \"system\") {\n this.unwatch();\n\n return;\n }\n\n if (this.media || typeof matchMedia !== \"function\") {\n return;\n }\n\n this.media = matchMedia(DARK_SCHEME_QUERY);\n this.media.addEventListener(\"change\", this.onSchemeChange);\n }\n\n private unwatch() {\n this.media?.removeEventListener(\"change\", this.onSchemeChange);\n this.media = null;\n }\n\n private notify() {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n\n/** Paint the resolved theme onto an element for the stylesheet to key off. */\nconst applyTheme = (element: HTMLElement, theme: HudTheme) => {\n element.dataset.theme = theme.resolved;\n};\n\nexport { applyTheme, HudTheme, isThemeMode, THEME_MODES };\nexport type { ResolvedTheme, ThemeMode };\n","import type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { Sample, TimingMetric } from \"../core/types.ts\";\nimport { HudSettings } from \"./hud-settings.ts\";\nimport { createIcon } from \"./icons.ts\";\nimport { drawSparkline, type SparklineStyle } from \"./sparkline.ts\";\nimport { applyTheme, HudTheme, type ResolvedTheme } from \"./theme.ts\";\n\ntype PerformanceViewMode = \"full\" | \"compact\";\n\ntype PerformanceViewOptions = {\n monitor: PerformanceMonitor;\n /** `full` is the checkbox list; `compact` the card it configures. Default `full`. */\n mode?: PerformanceViewMode;\n /** Repaint rate. Default 10. */\n refreshHz?: number;\n /** Share one between views to keep them in sync; a fresh instance otherwise. */\n settings?: HudSettings;\n /**\n * Share the host's so the card and its chrome switch together. Otherwise a\n * fresh `system` instance is created and disposed with the view.\n */\n theme?: HudTheme;\n};\n\ntype TimingConfig = {\n format: (value: number) => string;\n label: string;\n metric: TimingMetric;\n /** Canvas can't read CSS custom properties, so the sparkline palette lives here per theme. */\n style: Record<ResolvedTheme, SparklineStyle>;\n unit: string;\n};\n\ntype NumberConfig = {\n key: string;\n label: string;\n read: (sample: Sample) => string;\n};\n\nconst TIMINGS: TimingConfig[] = [\n {\n format: (value) => Math.round(value).toString(),\n label: \"FPS\",\n metric: \"fps\",\n style: {\n dark: { fill: \"rgba(74, 222, 128, 0.15)\", stroke: \"#4ade80\" },\n light: { fill: \"rgba(22, 163, 74, 0.12)\", stroke: \"#16a34a\" },\n },\n unit: \"\",\n },\n {\n format: (value) => value.toFixed(1),\n label: \"CPU\",\n metric: \"cpu\",\n style: {\n dark: { fill: \"rgba(96, 165, 250, 0.15)\", stroke: \"#60a5fa\" },\n light: { fill: \"rgba(37, 99, 235, 0.12)\", stroke: \"#2563eb\" },\n },\n unit: \"ms\",\n },\n {\n format: (value) => value.toFixed(1),\n label: \"GPU\",\n metric: \"gpu\",\n style: {\n dark: { fill: \"rgba(244, 114, 182, 0.15)\", stroke: \"#f472b6\" },\n light: { fill: \"rgba(219, 39, 119, 0.12)\", stroke: \"#db2777\" },\n },\n unit: \"ms\",\n },\n];\n\nconst formatCount = (value: number) => value.toLocaleString(\"en-US\");\n\nconst readTiming = (sample: Sample, metric: TimingMetric) => {\n switch (metric) {\n case \"fps\":\n return sample.fps;\n case \"cpu\":\n return sample.cpu;\n case \"gpu\":\n return sample.gpu.ms;\n }\n};\n\nconst isTimingAvailable = (sample: Sample, metric: TimingMetric) =>\n metric !== \"gpu\" || sample.gpu.available;\n\nconst NUMBERS: NumberConfig[] = [\n { key: \"fps\", label: \"FPS\", read: (sample) => Math.round(sample.fps).toString() },\n { key: \"calls\", label: \"Calls\", read: (sample) => formatCount(sample.render.calls) },\n { key: \"cpu\", label: \"CPU\", read: (sample) => sample.cpu.toFixed(1) },\n {\n key: \"gpu\",\n label: \"GPU\",\n read: (sample) => (sample.gpu.available ? sample.gpu.ms.toFixed(1) : \"—\"),\n },\n {\n key: \"triangles\",\n label: \"Triangles\",\n read: (sample) => formatCount(sample.render.triangles),\n },\n { key: \"lines\", label: \"Lines\", read: (sample) => formatCount(sample.render.lines) },\n { key: \"points\", label: \"Points\", read: (sample) => formatCount(sample.render.points) },\n {\n key: \"passes\",\n label: \"Render passes\",\n read: (sample) => formatCount(sample.render.passes),\n },\n {\n key: \"geometries\",\n label: \"Geometries\",\n read: (sample) => formatCount(sample.resources.geometries),\n },\n {\n key: \"textures\",\n label: \"Textures\",\n read: (sample) => formatCount(sample.resources.textures),\n },\n {\n key: \"shaders\",\n label: \"Shaders\",\n read: (sample) => formatCount(sample.resources.programs),\n },\n];\n\ntype GraphCanvas = {\n canvas: HTMLCanvasElement;\n ctx: CanvasRenderingContext2D;\n height: number;\n width: number;\n};\n\ntype HudGraph = GraphCanvas & {\n valueEl: HTMLElement;\n};\n\n/**\n * Host-agnostic widget that paints {@link Sample}s. Mount {@link element}\n * anywhere; the view drives its own throttled repaint. Compact is the floating\n * HUD; full is the checkbox list that chooses what compact shows. Styles are\n * not injected here. Call `injectStyles()` or use `mountPerfHud`.\n */\nclass PerformanceView {\n readonly element: HTMLDivElement;\n readonly settings: HudSettings;\n readonly theme: HudTheme;\n\n private readonly ownsTheme: boolean;\n private readonly unsubscribeTheme: () => void;\n private readonly monitor: PerformanceMonitor;\n private readonly minIntervalMs: number;\n private mode: PerformanceViewMode;\n private readonly graphValueEls = new Map<TimingMetric, HTMLElement>();\n private readonly graphCanvases = new Map<TimingMetric, GraphCanvas>();\n private readonly graphCheckboxes = new Map<TimingMetric, HTMLInputElement>();\n private readonly statValueEls = new Map<string, HTMLElement>();\n private readonly statCheckboxes = new Map<string, HTMLInputElement>();\n private dimCheckbox!: HTMLInputElement;\n private hudGraphsEl!: HTMLElement;\n private hudGridEl!: HTMLElement;\n private readonly hudGraphs = new Map<TimingMetric, HudGraph>();\n private readonly hudNumberEls = new Map<string, HTMLElement>();\n private rafId: number | null = null;\n private lastPaintAt = 0;\n private readonly resizeObserver: ResizeObserver;\n private readonly unsubscribe: () => void;\n\n constructor(options: PerformanceViewOptions) {\n this.monitor = options.monitor;\n this.settings = options.settings ?? new HudSettings();\n this.ownsTheme = !options.theme;\n this.theme = options.theme ?? new HudTheme();\n this.mode = options.mode ?? \"full\";\n this.minIntervalMs = 1000 / (options.refreshHz ?? 10);\n this.element = document.createElement(\"div\");\n this.element.className = \"perf-monitor\";\n this.applyModeClass();\n applyTheme(this.element, this.theme);\n this.resizeObserver = new ResizeObserver(() => this.resizeCanvases());\n this.build();\n this.rebuildHud();\n this.unsubscribe = this.settings.subscribe(() => this.onSelectionChanged());\n this.unsubscribeTheme = this.theme.subscribe(() => applyTheme(this.element, this.theme));\n }\n\n start() {\n if (this.rafId === null) {\n this.rafId = requestAnimationFrame(this.frame);\n }\n }\n\n stop() {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n setMode(mode: PerformanceViewMode) {\n if (mode === this.mode) {\n return;\n }\n\n this.mode = mode;\n this.applyModeClass();\n this.resizeCanvases();\n }\n\n getMode() {\n return this.mode;\n }\n\n dispose() {\n this.stop();\n this.unsubscribe();\n this.unsubscribeTheme();\n\n if (this.ownsTheme) {\n this.theme.dispose();\n }\n\n this.resizeObserver.disconnect();\n this.element.remove();\n }\n\n private frame = (now: number) => {\n this.rafId = requestAnimationFrame(this.frame);\n\n if (now - this.lastPaintAt < this.minIntervalMs) {\n return;\n }\n\n this.lastPaintAt = now;\n this.render();\n };\n\n private render() {\n const sample = this.monitor.getSample();\n\n if (this.mode === \"compact\") {\n this.renderHud(sample);\n\n return;\n }\n\n for (const config of TIMINGS) {\n const available = isTimingAvailable(sample, config.metric);\n const valueEl = this.graphValueEls.get(config.metric)!;\n valueEl.textContent = available\n ? this.formatTiming(config, readTiming(sample, config.metric))\n : \"unavailable\";\n\n const graph = this.graphCanvases.get(config.metric)!;\n drawSparkline(\n graph.ctx,\n graph.width,\n graph.height,\n available ? this.monitor.getHistory(config.metric) : [],\n config.style[this.theme.resolved],\n );\n }\n\n for (const config of NUMBERS) {\n this.statValueEls.get(config.key)!.textContent = config.read(sample);\n }\n }\n\n private renderHud(sample: Sample) {\n for (const [key, element] of this.hudNumberEls) {\n const config = NUMBERS.find((number) => number.key === key);\n\n if (config) {\n element.textContent = config.read(sample);\n }\n }\n\n for (const [metric, graph] of this.hudGraphs) {\n const config = TIMINGS.find((timing) => timing.metric === metric)!;\n const available = isTimingAvailable(sample, metric);\n\n graph.valueEl.textContent = available\n ? this.formatTiming(config, readTiming(sample, metric))\n : \"—\";\n\n drawSparkline(\n graph.ctx,\n graph.width,\n graph.height,\n available ? this.monitor.getHistory(metric) : [],\n config.style[this.theme.resolved],\n );\n }\n }\n\n private formatTiming(config: TimingConfig, value: number) {\n return `${config.format(value)}${config.unit ? ` ${config.unit}` : \"\"}`;\n }\n\n private build() {\n const hud = document.createElement(\"div\");\n hud.className = \"perf-monitor__hud\";\n this.hudGraphsEl = document.createElement(\"div\");\n this.hudGraphsEl.className = \"perf-monitor__hud-graphs\";\n this.hudGridEl = document.createElement(\"div\");\n this.hudGridEl.className = \"perf-monitor__hud-grid\";\n hud.append(this.hudGraphsEl, this.hudGridEl);\n\n const options = document.createElement(\"div\");\n options.className = \"perf-monitor__section perf-monitor__options\";\n\n const dimRow = document.createElement(\"label\");\n dimRow.className = \"perf-monitor__row\";\n\n this.dimCheckbox = this.buildCheckbox(\n this.settings.dim,\n \"Dim the performance panel when the pointer leaves\",\n (on) => this.settings.setDim(on),\n );\n\n const dimLabel = document.createElement(\"span\");\n dimLabel.className = \"perf-monitor__label\";\n dimLabel.textContent = \"dim on leave\";\n\n dimRow.append(this.dimCheckbox, createIcon(\"blend\", \"perf-monitor__icon\"), dimLabel);\n options.append(dimRow);\n\n const graphs = document.createElement(\"div\");\n graphs.className = \"perf-monitor__graphs\";\n\n for (const config of TIMINGS) {\n const graph = document.createElement(\"div\");\n graph.className = \"perf-monitor__graph\";\n\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"perf-monitor__canvas\";\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"perf-monitor__graph-overlay\";\n\n const head = document.createElement(\"label\");\n head.className = \"perf-monitor__graph-head\";\n\n const checkbox = this.buildCheckbox(\n this.settings.isGraphEnabled(config.metric),\n `Show ${config.label} graph in HUD`,\n (on) => this.settings.setGraphEnabled(config.metric, on),\n );\n this.graphCheckboxes.set(config.metric, checkbox);\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__graph-label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__graph-value\";\n value.textContent = \"—\";\n this.graphValueEls.set(config.metric, value);\n\n head.append(checkbox, label);\n overlay.append(head, value);\n graph.append(canvas, overlay);\n graphs.append(graph);\n\n const ctx = canvas.getContext(\"2d\");\n\n if (ctx) {\n this.graphCanvases.set(config.metric, { canvas, ctx, height: 0, width: 0 });\n this.resizeObserver.observe(graph);\n }\n }\n\n const stats = document.createElement(\"div\");\n stats.className = \"perf-monitor__section\";\n\n for (const config of NUMBERS) {\n const rowEl = document.createElement(\"label\");\n rowEl.className = \"perf-monitor__row\";\n\n const checkbox = this.buildCheckbox(\n this.settings.isNumberEnabled(config.key),\n `Show ${config.label} in HUD`,\n (on) => this.settings.setNumberEnabled(config.key, on),\n );\n this.statCheckboxes.set(config.key, checkbox);\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__value\";\n value.textContent = \"—\";\n this.statValueEls.set(config.key, value);\n\n rowEl.append(checkbox, label, value);\n stats.append(rowEl);\n }\n\n this.element.append(hud, options, graphs, stats);\n }\n\n private rebuildHud() {\n const { selection } = this.settings;\n\n for (const graph of this.hudGraphs.values()) {\n this.resizeObserver.unobserve(graph.canvas.parentElement ?? graph.canvas);\n }\n\n this.hudGraphs.clear();\n this.hudNumberEls.clear();\n this.hudGraphsEl.replaceChildren();\n this.hudGridEl.replaceChildren();\n\n for (const config of TIMINGS) {\n if (!selection.graphs.has(config.metric)) {\n continue;\n }\n\n const row = document.createElement(\"div\");\n row.className = \"perf-monitor__hud-graph\";\n\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"perf-monitor__canvas\";\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"perf-monitor__graph-overlay\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__graph-label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__graph-value\";\n value.textContent = \"—\";\n\n overlay.append(label, value);\n row.append(canvas, overlay);\n this.hudGraphsEl.append(row);\n\n const ctx = canvas.getContext(\"2d\");\n\n if (ctx) {\n this.hudGraphs.set(config.metric, { canvas, ctx, height: 0, valueEl: value, width: 0 });\n this.resizeObserver.observe(row);\n }\n }\n\n for (const config of NUMBERS) {\n if (!selection.numbers.has(config.key)) {\n continue;\n }\n\n const item = document.createElement(\"div\");\n item.className = \"perf-monitor__hud-item\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__hud-label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__hud-value\";\n value.textContent = \"—\";\n\n item.append(label, value);\n this.hudGridEl.append(item);\n this.hudNumberEls.set(config.key, value);\n }\n\n this.resizeCanvases();\n }\n\n private onSelectionChanged() {\n for (const [metric, checkbox] of this.graphCheckboxes) {\n checkbox.checked = this.settings.isGraphEnabled(metric);\n }\n\n for (const [key, checkbox] of this.statCheckboxes) {\n checkbox.checked = this.settings.isNumberEnabled(key);\n }\n\n this.dimCheckbox.checked = this.settings.dim;\n this.rebuildHud();\n }\n\n private buildCheckbox(checked: boolean, title: string, onChange: (enabled: boolean) => void) {\n const checkbox = document.createElement(\"input\");\n checkbox.type = \"checkbox\";\n checkbox.className = \"perf-monitor__checkbox\";\n checkbox.checked = checked;\n checkbox.title = title;\n checkbox.setAttribute(\"aria-label\", title);\n checkbox.addEventListener(\"change\", () => onChange(checkbox.checked));\n\n return checkbox;\n }\n\n private applyModeClass() {\n this.element.classList.toggle(\"perf-monitor--compact\", this.mode === \"compact\");\n this.element.classList.toggle(\"perf-monitor--full\", this.mode === \"full\");\n }\n\n private resizeCanvases() {\n const dpr = window.devicePixelRatio || 1;\n\n const sizeAll = (graphs: Iterable<GraphCanvas>) => {\n for (const graph of graphs) {\n const { canvas, ctx } = graph;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n\n if (width === 0 || height === 0) {\n continue;\n }\n\n canvas.width = Math.round(width * dpr);\n canvas.height = Math.round(height * dpr);\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n graph.width = width;\n graph.height = height;\n }\n };\n\n sizeAll(this.graphCanvases.values());\n sizeAll(this.hudGraphs.values());\n }\n}\n\nexport { PerformanceView };\nexport type { PerformanceViewMode, PerformanceViewOptions };\n","/**\n * The HUD stylesheet as a string, so the package works without a bundler and\n * injects itself once per document. `.perf-monitor` is the metrics card,\n * `.perf-hud` the floating host around it. Both namespaced so a host page can\n * override safely; the `--perf-*` custom properties are the theming surface.\n *\n * Both elements carry `data-theme=\"dark\" | \"light\"` (see `HudTheme`) and each\n * resolves its own tokens from it, so the card also themes when mounted on\n * its own, outside a `.perf-hud`.\n */\nconst STYLE_ATTRIBUTE = \"data-three-meter\";\n\nconst PERF_HUD_STYLES = `\n.perf-hud,\n.perf-monitor {\n --perf-bg: rgba(22, 24, 29, 0.85);\n --perf-fg: #e6e8eb;\n --perf-fg-dim: #c5c8ce;\n --perf-muted: #8b909a;\n --perf-row: rgba(255, 255, 255, 0.04);\n --perf-border: rgba(255, 255, 255, 0.12);\n --perf-accent: #60a5fa;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);\n\n color-scheme: dark;\n}\n\n.perf-hud[data-theme=\"light\"],\n.perf-monitor[data-theme=\"light\"] {\n --perf-bg: rgba(250, 250, 252, 0.88);\n --perf-fg: #1a1c21;\n --perf-fg-dim: #4b5058;\n --perf-muted: #6b7079;\n --perf-row: rgba(0, 0, 0, 0.05);\n --perf-border: rgba(0, 0, 0, 0.12);\n --perf-accent: #2563eb;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);\n\n color-scheme: light;\n}\n\n.perf-monitor {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 8px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.4;\n color: var(--perf-fg);\n}\n\n.perf-monitor__graphs {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.perf-monitor__graph {\n position: relative;\n height: 40px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__canvas {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.perf-monitor__graph-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 8px;\n pointer-events: none;\n}\n\n.perf-monitor__graph-head {\n display: flex;\n align-items: center;\n gap: 6px;\n cursor: pointer;\n}\n\n.perf-monitor__graph-overlay .perf-monitor__graph-head {\n pointer-events: auto;\n}\n\n.perf-monitor__graph-label {\n color: var(--perf-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n font-size: 10px;\n}\n\n.perf-monitor__graph-value {\n min-width: 6ch;\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__section {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.perf-monitor__row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 2px 6px;\n border-radius: 3px;\n cursor: pointer;\n}\n\n.perf-monitor__row:nth-child(even) {\n background: var(--perf-row);\n}\n\n.perf-monitor__label {\n flex: 1;\n color: var(--perf-muted);\n}\n\n.perf-monitor__icon {\n flex: none;\n width: 12px;\n height: 12px;\n color: var(--perf-muted);\n}\n\n.perf-monitor__options {\n padding-bottom: 8px;\n border-bottom: 1px solid var(--perf-border);\n}\n\n.perf-monitor__checkbox {\n flex: none;\n margin: 0;\n width: 12px;\n height: 12px;\n accent-color: var(--perf-accent);\n cursor: pointer;\n}\n\n.perf-monitor__value {\n min-width: 7ch;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__hud {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 2px;\n}\n\n.perf-monitor__hud-graphs {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.perf-monitor__hud-graphs:empty {\n display: none;\n}\n\n.perf-monitor__hud-graph {\n position: relative;\n height: 28px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__hud-grid {\n display: grid;\n grid-template-columns: auto 6ch auto 6ch;\n align-items: baseline;\n gap: 2px 8px;\n}\n\n.perf-monitor__hud-grid:empty {\n display: none;\n}\n\n.perf-monitor__hud-item {\n display: contents;\n}\n\n.perf-monitor__hud-label {\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__hud-value {\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor--full .perf-monitor__hud {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__graphs,\n.perf-monitor--compact .perf-monitor__section {\n display: none;\n}\n\n.perf-monitor--compact {\n padding: 4px 8px;\n}\n\n.perf-hud {\n position: fixed;\n z-index: 9999;\n /* Safari resolves width:100% canvases against the viewport on a fixed\n host with auto width. Shrink-wrap so expand stays a card, not the window. */\n width: max-content;\n max-width: min(20rem, calc(100vw - 48px));\n overflow: visible;\n background: transparent;\n pointer-events: none;\n user-select: none;\n touch-action: none;\n}\n\n.perf-hud--full > .perf-monitor {\n width: 16rem;\n max-width: 100%;\n}\n\n.perf-hud > .perf-monitor {\n position: relative;\n z-index: 1;\n max-height: 70vh;\n overflow: auto;\n background: var(--perf-bg);\n border-radius: 6px;\n box-shadow: var(--perf-shadow);\n backdrop-filter: blur(2px);\n pointer-events: auto;\n transition: opacity 0.25s ease;\n}\n\n.perf-hud--dim:not(.is-awake):not(.is-dragging) > .perf-monitor {\n opacity: 0.32;\n}\n\n.perf-hud__hotspot {\n position: absolute;\n z-index: 0;\n inset: -20px;\n pointer-events: auto;\n}\n\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__hotspot {\n right: -46px;\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__hotspot {\n left: -46px;\n}\n\n.perf-hud__tools {\n position: absolute;\n top: 0;\n z-index: 2;\n display: flex;\n flex-direction: column;\n gap: 4px;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s ease;\n}\n\n.perf-hud.is-awake .perf-hud__tools,\n.perf-hud.is-dragging .perf-hud__tools {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* Inward side: right by default, left when docked to the right edge. */\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__tools {\n left: calc(100% + 6px);\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__tools {\n right: calc(100% + 6px);\n}\n\n.perf-hud__disc {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border: 0;\n border-radius: 999px;\n background: var(--perf-bg);\n box-shadow: var(--perf-shadow);\n color: var(--perf-fg-dim);\n cursor: pointer;\n backdrop-filter: blur(2px);\n}\n\n.perf-hud__icon {\n width: 12px;\n height: 12px;\n}\n\n.perf-hud__drag {\n cursor: grab;\n}\n\n.perf-hud.is-dragging .perf-hud__drag {\n cursor: grabbing;\n}\n\n.perf-hud__disc:hover,\n.perf-hud__disc:focus-visible,\n.perf-hud__disc[aria-pressed=\"true\"] {\n color: var(--perf-fg);\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\n}\n`;\n\n/**\n * Append the stylesheet to `target`'s head, once. Call before mounting a view\n * by hand; {@link mountPerfHud} does it for you unless told not to.\n */\nconst injectStyles = (target: Document = document) => {\n if (target.head.querySelector(`style[${STYLE_ATTRIBUTE}]`)) {\n return;\n }\n\n const style = target.createElement(\"style\");\n style.setAttribute(STYLE_ATTRIBUTE, \"\");\n style.textContent = PERF_HUD_STYLES;\n target.head.append(style);\n};\n\nexport { injectStyles, PERF_HUD_STYLES };\n","/**\n * The floating HUD: a fixed host docked to a screen edge, the metrics card\n * inside it, and two discs on the inward side (drag grip, compact/full toggle)\n * that stay hidden until the pointer is near or someone taps. Dim-on-leave\n * comes from the settings; the palette from `theme`. This is what the React\n * `PerfHud` wraps; a vanilla three app calls it directly.\n */\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport { type DefaultPlacement, dockPanel } from \"./dock-panel.ts\";\nimport { DEFAULT_STORAGE_KEY, HudSettings } from \"./hud-settings.ts\";\nimport { createIcon } from \"./icons.ts\";\nimport { PerformanceView, type PerformanceViewMode } from \"./performance-view.ts\";\nimport { injectStyles } from \"./styles.ts\";\nimport { applyTheme, HudTheme, type ThemeMode } from \"./theme.ts\";\n\nconst TOUCH_LINGER_MS = 2500;\n\ntype MountPerfHudOptions = {\n /** Start compact (the card) or full (the checkbox list). Default `compact`. */\n mode?: PerformanceViewMode;\n /**\n * `dark`, `light`, or `system` to follow the OS preference. Default\n * `system`. Change it later with the handle's `setTheme`.\n */\n theme?: ThemeMode;\n /** localStorage key for the selection and the dock. `null` disables persistence. */\n storageKey?: string | null;\n /** Bring your own; otherwise one is created from `storageKey`. */\n settings?: HudSettings;\n /** Where the card sits on a first visit. Default left edge, centred. */\n defaultPlacement?: DefaultPlacement;\n /** Where the host element goes. Default `document.body`. */\n parent?: HTMLElement;\n /** Append the stylesheet to the document once. Default true. */\n injectStyles?: boolean;\n /** Repaint rate. Default 10. */\n refreshHz?: number;\n /** Accessible name of the panel. Default `Performance`. */\n label?: string;\n};\n\ntype PerfHudHandle = {\n element: HTMLDivElement;\n view: PerformanceView;\n settings: HudSettings;\n /** Subscribe to it for the resolved `dark` / `light` as the OS preference moves. */\n theme: HudTheme;\n getMode: () => PerformanceViewMode;\n setMode: (mode: PerformanceViewMode) => void;\n getTheme: () => ThemeMode;\n setTheme: (mode: ThemeMode) => void;\n dispose: () => void;\n};\n\nconst disc = (label: string, className: string, icon: \"grip\" | \"sliders\") => {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = `perf-hud__disc ${className}`.trim();\n button.setAttribute(\"aria-label\", label);\n button.append(createIcon(icon, \"perf-hud__icon\"));\n\n return button;\n};\n\nconst mountPerfHud = (\n monitor: PerformanceMonitor,\n options: MountPerfHudOptions = {},\n): PerfHudHandle => {\n const storageKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n const settings = options.settings ?? new HudSettings({ storageKey });\n const theme = new HudTheme(options.theme ?? \"system\");\n const parent = options.parent ?? document.body;\n\n if (options.injectStyles ?? true) {\n injectStyles(parent.ownerDocument);\n }\n\n const host = document.createElement(\"div\");\n host.className = \"perf-hud\";\n host.dataset.edge = \"left\";\n host.setAttribute(\"aria-label\", options.label ?? \"Performance\");\n applyTheme(host, theme);\n\n const hotspot = document.createElement(\"div\");\n hotspot.className = \"perf-hud__hotspot\";\n hotspot.setAttribute(\"aria-hidden\", \"true\");\n\n const tools = document.createElement(\"div\");\n tools.className = \"perf-hud__tools\";\n const grip = disc(\"Drag performance panel\", \"perf-hud__drag\", \"grip\");\n const toggle = disc(\"Toggle full performance metrics\", \"\", \"sliders\");\n tools.append(grip, toggle);\n\n const view = new PerformanceView({\n mode: options.mode ?? \"compact\",\n monitor,\n refreshHz: options.refreshHz,\n settings,\n theme,\n });\n\n host.append(hotspot, tools, view.element);\n parent.append(host);\n\n const applyMode = () => {\n host.classList.toggle(\"perf-hud--full\", view.getMode() === \"full\");\n };\n\n const applyDim = () => {\n host.classList.toggle(\"perf-hud--dim\", settings.dim);\n };\n\n applyMode();\n applyDim();\n const unsubscribe = settings.subscribe(applyDim);\n const unsubscribeTheme = theme.subscribe(() => applyTheme(host, theme));\n const dock = dockPanel(host, {\n defaultPlacement: options.defaultPlacement,\n handle: grip,\n storageKey,\n });\n\n const setMode = (mode: PerformanceViewMode) => {\n view.setMode(mode);\n applyMode();\n requestAnimationFrame(() => dock.refresh());\n };\n\n toggle.addEventListener(\"click\", () => {\n setMode(view.getMode() === \"compact\" ? \"full\" : \"compact\");\n });\n\n // Wake on approach, sleep on leave; a touch lingers so the discs can be tapped.\n let hideTimer = 0;\n\n const sleep = () => {\n if (host.classList.contains(\"is-dragging\")) {\n return;\n }\n\n host.classList.remove(\"is-awake\");\n };\n\n const wake = (linger: boolean) => {\n host.classList.add(\"is-awake\");\n window.clearTimeout(hideTimer);\n\n if (linger) {\n hideTimer = window.setTimeout(sleep, TOUCH_LINGER_MS);\n }\n };\n\n const onEnter = () => {\n wake(false);\n };\n\n const onLeave = (event: PointerEvent) => {\n if (host.contains(event.relatedTarget as Node | null)) {\n return;\n }\n\n if (event.pointerType === \"touch\" || event.pointerType === \"pen\") {\n wake(true);\n\n return;\n }\n\n sleep();\n };\n\n const onDown = (event: PointerEvent) => {\n wake(event.pointerType !== \"mouse\");\n };\n\n host.addEventListener(\"pointerenter\", onEnter);\n host.addEventListener(\"pointerleave\", onLeave);\n host.addEventListener(\"pointerdown\", onDown);\n\n view.start();\n\n return {\n dispose: () => {\n window.clearTimeout(hideTimer);\n host.removeEventListener(\"pointerenter\", onEnter);\n host.removeEventListener(\"pointerleave\", onLeave);\n host.removeEventListener(\"pointerdown\", onDown);\n unsubscribe();\n unsubscribeTheme();\n dock.dispose();\n view.dispose();\n theme.dispose();\n host.remove();\n },\n element: host,\n getMode: () => view.getMode(),\n setMode,\n getTheme: () => theme.mode,\n setTheme: (mode: ThemeMode) => theme.setMode(mode),\n settings,\n theme,\n view,\n };\n};\n\nexport { mountPerfHud };\nexport type { MountPerfHudOptions, PerfHudHandle };\n"],"mappings":"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"}
|