@zkmake/three-meter 0.2.0 → 0.3.1

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 CHANGED
@@ -8,8 +8,17 @@ 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/). Add `?webgpu` for the WebGPU
12
- renderer and `?count=5000` to load the scene up.
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.
14
+
15
+ <p align="center">
16
+ <img
17
+ src="https://raw.githubusercontent.com/zkmake/three-meter/main/docs/hud-full.png"
18
+ width="320"
19
+ alt="The HUD in full mode, dark theme: a theme row (light, system, dark), a dim-on-leave toggle, FPS, CPU and GPU sparklines with live values, and a checkbox list of counters (FPS, calls, CPU, GPU, triangles, lines, points, render passes, geometries, textures, shaders) choosing what the compact card shows. Two discs beside it: a drag grip and the compact/full toggle."
20
+ />
21
+ </p>
13
22
 
14
23
  The sampler and the card are separate components, so toggling the HUD never remounts your canvas.
15
24
  GPU time is measured on WebGPU as well as WebGL. The counters are draw calls, render passes and
@@ -71,29 +80,38 @@ Canvas is its own React root. To run two canvases on one page, pass the same `st
71
80
 
72
81
  `mountPerfHud` and `PerfHud` take:
73
82
 
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. |
83
+ | Option | Default | Meaning |
84
+ | ------------------ | ------------------ | --------------------------------------------------------------------------------------------- |
85
+ | `mode` | `"compact"` | `compact` is the card. `full` is the checkbox list that configures it. |
86
+ | `theme` | `"system"` | `dark`, `light`, or `system` to follow `prefers-color-scheme` live. A pick in the panel wins. |
87
+ | `defaultPlacement` | `{ edge: "left" }` | First-visit dock, as `edge` plus `align` of `start`, `center` or `end`. A drag overrides it. |
88
+ | `storageKey` | `three-meter` | localStorage key for the selection and the dock. `null` disables persistence. |
89
+ | `parent` | `document.body` | Where the host element is appended. |
90
+ | `injectStyles` | `true` | Append the stylesheet once per document. |
91
+ | `refreshHz` | `10` | Repaint rate. |
83
92
 
84
93
  `PerformanceMonitor` and `PerfSampler` take `trackGPU` (default true), `gpuQueryPoolSize` (default 5)
85
94
  and `historySize` (default 120).
86
95
 
87
96
  ## Theme
88
97
 
89
- The HUD ships a dark and a light palette. `theme` picks one, or `system` (the default) follows the
90
- OS and switches when it does. Change it later from the handle, or from the `PerfHud` prop in React,
91
- which applies without remounting:
98
+ The HUD ships a dark and a light palette. Two layers decide which shows:
99
+
100
+ 1. **The panel's own pick.** The full view has a light / system / dark row. A pick there is kept
101
+ with the other settings under `storageKey` and wins while set.
102
+ 2. **Your `theme` option.** `dark`, `light`, or `system` (the default), which follows the OS and
103
+ switches when it does. Applies whenever the person using the HUD hasn't picked anything.
104
+
105
+ Change your layer later from the handle, or from the `PerfHud` prop in React, which applies without
106
+ remounting:
92
107
 
93
108
  ```ts
94
109
  const hud = mountPerfHud(monitor, { theme: "system" });
95
- hud.setTheme("light"); // "dark" | "light" | "system"
96
- hud.getTheme(); // the mode you asked for
110
+ hud.setTheme("light"); // your layer: "dark" | "light" | "system"
111
+ hud.getTheme(); // your layer, as asked for
112
+ hud.settings.theme; // the panel's pick, or null
113
+ hud.settings.setTheme(null); // clear the pick so your layer applies again
114
+ hud.theme.effective; // whichever layer is in force
97
115
  hud.theme.resolved; // "dark" | "light", what is on screen right now
98
116
  hud.theme.subscribe(() => syncMyPageWith(hud.theme.resolved));
99
117
  ```
@@ -111,11 +129,13 @@ On WebGPU, construct the renderer with `trackTimestamp: true`. Timings resolve a
111
129
 
112
130
  ## Examples
113
131
 
114
- [`examples/vanilla`](examples/vanilla) is plain three and is what runs at
115
- [three-meter.pages.dev](https://three-meter.pages.dev/). Add `?webgpu` to use `WebGPURenderer` and
116
- `?count=` to scale the scene. Its theme toggle drives the HUD through `setTheme` and the page follows
117
- `hud.theme.resolved`. [`examples/r3f`](examples/r3f) is the React Three Fiber version. Each is a Vite
118
- app. Run `bun run dev` inside it.
132
+ [`examples/site`](examples/site) is what runs at [three-meter.pages.dev](https://three-meter.pages.dev/):
133
+ one Vite app with both integrations of the same scene, swapped from the header.
134
+ [`src/demos/vanilla.ts`](examples/site/src/demos/vanilla.ts) is plain three with `mountPerfHud`;
135
+ [`src/demos/r3f.tsx`](examples/site/src/demos/r3f.tsx) is React Three Fiber with `PerfSampler` and
136
+ `PerfHud`. `?r3f` opens on Fiber, `?webgpu` uses `WebGPURenderer` in either, `?count=` scales the
137
+ scene. The header's theme toggle sets the page theme and the HUD's `theme` layer together; the row
138
+ inside the panel overrides the HUD alone. Run `bun run dev` inside it.
119
139
 
120
140
  ## Shipping it
121
141
 
@@ -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
- /** Subscribe to it for the resolved `dark` / `light` as the OS preference moves. */
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, ResolvedTheme as c, isThemeMode as d, DEFAULT_STORAGE_KEY as f, HudSettingsOptions as g, HudSettings as h, PerformanceView as i, THEME_MODES as l, HudSelection as m, PerfHudHandle as n, PerformanceViewOptions as o, HudDefaults as p, mountPerfHud as r, HudTheme as s, MountPerfHudOptions as t, ThemeMode as u, DockAlign as v, Placement as x, DockHandle as y };
208
- //# sourceMappingURL=mount-perf-hud-w4VnSoWa.d.ts.map
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) => {
@@ -361,6 +463,21 @@ const createIcon = (name, className) => {
361
463
  return svg;
362
464
  };
363
465
  //#endregion
466
+ //#region src/ui/format.ts
467
+ /**
468
+ * Number formatting for the card. Counts stay exact with thousands separators
469
+ * while they fit the compact grid's 6ch value column (`99,999`), then switch
470
+ * to compact notation (`250K`, `1.4M`, `12.3M`, `1.2B`) so a triangle count in
471
+ * the millions can't run into the neighbouring label.
472
+ */
473
+ const COMPACT_FROM = 1e5;
474
+ const exact = new Intl.NumberFormat("en-US");
475
+ const compact = new Intl.NumberFormat("en-US", {
476
+ maximumFractionDigits: 1,
477
+ notation: "compact"
478
+ });
479
+ const formatCount = (value) => Math.abs(value) < COMPACT_FROM ? exact.format(value) : compact.format(value);
480
+ //#endregion
364
481
  //#region src/ui/sparkline.ts
365
482
  function drawSparkline(ctx, width, height, values, style) {
366
483
  ctx.clearRect(0, 0, width, height);
@@ -383,77 +500,6 @@ function drawSparkline(ctx, width, height, values, style) {
383
500
  ctx.fill();
384
501
  }
385
502
  //#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
503
  //#region src/ui/performance-view.ts
458
504
  const TIMINGS = [
459
505
  {
@@ -505,7 +551,23 @@ const TIMINGS = [
505
551
  unit: "ms"
506
552
  }
507
553
  ];
508
- const formatCount = (value) => value.toLocaleString("en-US");
554
+ const THEME_OPTIONS = [
555
+ {
556
+ icon: "sun",
557
+ label: "Light theme",
558
+ mode: "light"
559
+ },
560
+ {
561
+ icon: "monitor",
562
+ label: "Match the system theme",
563
+ mode: "system"
564
+ },
565
+ {
566
+ icon: "moon",
567
+ label: "Dark theme",
568
+ mode: "dark"
569
+ }
570
+ ];
509
571
  const readTiming = (sample, metric) => {
510
572
  switch (metric) {
511
573
  case "fps": return sample.fps;
@@ -592,6 +654,7 @@ var PerformanceView = class {
592
654
  statValueEls = /* @__PURE__ */ new Map();
593
655
  statCheckboxes = /* @__PURE__ */ new Map();
594
656
  dimCheckbox;
657
+ themeRadios = /* @__PURE__ */ new Map();
595
658
  hudGraphsEl;
596
659
  hudGridEl;
597
660
  hudGraphs = /* @__PURE__ */ new Map();
@@ -605,17 +668,18 @@ var PerformanceView = class {
605
668
  this.settings = options.settings ?? new HudSettings();
606
669
  this.ownsTheme = !options.theme;
607
670
  this.theme = options.theme ?? new HudTheme();
671
+ this.theme.setOverride(this.settings.theme);
608
672
  this.mode = options.mode ?? "full";
609
673
  this.minIntervalMs = 1e3 / (options.refreshHz ?? 10);
610
674
  this.element = document.createElement("div");
611
675
  this.element.className = "perf-monitor";
612
676
  this.applyModeClass();
613
- applyTheme(this.element, this.theme);
614
677
  this.resizeObserver = new ResizeObserver(() => this.resizeCanvases());
615
678
  this.build();
616
679
  this.rebuildHud();
680
+ this.onThemeChanged();
617
681
  this.unsubscribe = this.settings.subscribe(() => this.onSelectionChanged());
618
- this.unsubscribeTheme = this.theme.subscribe(() => applyTheme(this.element, this.theme));
682
+ this.unsubscribeTheme = this.theme.subscribe(() => this.onThemeChanged());
619
683
  }
620
684
  start() {
621
685
  if (this.rafId === null) this.rafId = requestAnimationFrame(this.frame);
@@ -689,14 +753,37 @@ var PerformanceView = class {
689
753
  hud.append(this.hudGraphsEl, this.hudGridEl);
690
754
  const options = document.createElement("div");
691
755
  options.className = "perf-monitor__section perf-monitor__options";
756
+ const themeRow = document.createElement("div");
757
+ themeRow.className = "perf-monitor__row perf-monitor__row--static";
758
+ const themeLabel = document.createElement("span");
759
+ themeLabel.className = "perf-monitor__label";
760
+ themeLabel.textContent = "theme";
761
+ const segment = document.createElement("div");
762
+ segment.className = "perf-monitor__segment";
763
+ segment.setAttribute("role", "radiogroup");
764
+ segment.setAttribute("aria-label", "Panel theme");
765
+ for (const option of THEME_OPTIONS) {
766
+ const radio = document.createElement("button");
767
+ radio.type = "button";
768
+ radio.className = "perf-monitor__segment-option";
769
+ radio.setAttribute("role", "radio");
770
+ radio.setAttribute("aria-checked", "false");
771
+ radio.setAttribute("aria-label", option.label);
772
+ radio.title = option.label;
773
+ radio.append(createIcon(option.icon, "perf-monitor__icon"));
774
+ radio.addEventListener("click", () => this.settings.setTheme(option.mode));
775
+ this.themeRadios.set(option.mode, radio);
776
+ segment.append(radio);
777
+ }
778
+ themeRow.append(createIcon("contrast", "perf-monitor__icon"), themeLabel, segment);
692
779
  const dimRow = document.createElement("label");
693
780
  dimRow.className = "perf-monitor__row";
694
781
  this.dimCheckbox = this.buildCheckbox(this.settings.dim, "Dim the performance panel when the pointer leaves", (on) => this.settings.setDim(on));
695
782
  const dimLabel = document.createElement("span");
696
783
  dimLabel.className = "perf-monitor__label";
697
784
  dimLabel.textContent = "dim on leave";
698
- dimRow.append(this.dimCheckbox, createIcon("blend", "perf-monitor__icon"), dimLabel);
699
- options.append(dimRow);
785
+ dimRow.append(createIcon("blend", "perf-monitor__icon"), dimLabel, this.dimCheckbox);
786
+ options.append(themeRow, dimRow);
700
787
  const graphs = document.createElement("div");
701
788
  graphs.className = "perf-monitor__graphs";
702
789
  for (const config of TIMINGS) {
@@ -807,8 +894,14 @@ var PerformanceView = class {
807
894
  for (const [metric, checkbox] of this.graphCheckboxes) checkbox.checked = this.settings.isGraphEnabled(metric);
808
895
  for (const [key, checkbox] of this.statCheckboxes) checkbox.checked = this.settings.isNumberEnabled(key);
809
896
  this.dimCheckbox.checked = this.settings.dim;
897
+ this.theme.setOverride(this.settings.theme);
810
898
  this.rebuildHud();
811
899
  }
900
+ /** Repaint the palette and mark the radio matching what applies (pick, else consumer mode). */
901
+ onThemeChanged() {
902
+ applyTheme(this.element, this.theme);
903
+ for (const [mode, radio] of this.themeRadios) radio.setAttribute("aria-checked", String(mode === this.theme.effective));
904
+ }
812
905
  buildCheckbox(checked, title, onChange) {
813
906
  const checkbox = document.createElement("input");
814
907
  checkbox.type = "checkbox";
@@ -986,6 +1079,54 @@ const PERF_HUD_STYLES = `
986
1079
  border-bottom: 1px solid var(--perf-border);
987
1080
  }
988
1081
 
1082
+ .perf-monitor__row--static {
1083
+ cursor: default;
1084
+ }
1085
+
1086
+ /* Light / system / dark, as a small segmented radio group. */
1087
+ .perf-monitor__segment {
1088
+ display: inline-flex;
1089
+ flex: none;
1090
+ gap: 1px;
1091
+ padding: 1px;
1092
+ border-radius: 4px;
1093
+ background: var(--perf-row);
1094
+ }
1095
+
1096
+ .perf-monitor__segment-option {
1097
+ display: flex;
1098
+ align-items: center;
1099
+ justify-content: center;
1100
+ width: 20px;
1101
+ height: 16px;
1102
+ padding: 0;
1103
+ border: 0;
1104
+ border-radius: 3px;
1105
+ background: transparent;
1106
+ color: var(--perf-muted);
1107
+ cursor: pointer;
1108
+ }
1109
+
1110
+ .perf-monitor__segment-option:hover {
1111
+ color: var(--perf-fg);
1112
+ }
1113
+
1114
+ .perf-monitor__segment-option[aria-checked="true"] {
1115
+ color: var(--perf-fg);
1116
+ background: var(--perf-border);
1117
+ }
1118
+
1119
+ .perf-monitor__segment-option:focus-visible {
1120
+ outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
1121
+ outline-offset: 1px;
1122
+ }
1123
+
1124
+ .perf-monitor__segment-option .perf-monitor__icon {
1125
+ width: 11px;
1126
+ height: 11px;
1127
+ color: inherit;
1128
+ }
1129
+
989
1130
  .perf-monitor__checkbox {
990
1131
  flex: none;
991
1132
  margin: 0;
@@ -1212,7 +1353,6 @@ const mountPerfHud = (monitor, options = {}) => {
1212
1353
  host.className = "perf-hud";
1213
1354
  host.dataset.edge = "left";
1214
1355
  host.setAttribute("aria-label", options.label ?? "Performance");
1215
- applyTheme(host, theme);
1216
1356
  const hotspot = document.createElement("div");
1217
1357
  hotspot.className = "perf-hud__hotspot";
1218
1358
  hotspot.setAttribute("aria-hidden", "true");
@@ -1238,6 +1378,7 @@ const mountPerfHud = (monitor, options = {}) => {
1238
1378
  };
1239
1379
  applyMode();
1240
1380
  applyDim();
1381
+ applyTheme(host, theme);
1241
1382
  const unsubscribe = settings.subscribe(applyDim);
1242
1383
  const unsubscribeTheme = theme.subscribe(() => applyTheme(host, theme));
1243
1384
  const dock = dockPanel(host, {
@@ -1305,6 +1446,6 @@ const mountPerfHud = (monitor, options = {}) => {
1305
1446
  };
1306
1447
  };
1307
1448
  //#endregion
1308
- export { HudTheme as a, dockPanel as c, PerformanceView as i, DEFAULT_STORAGE_KEY as l, PERF_HUD_STYLES as n, THEME_MODES as o, injectStyles as r, isThemeMode as s, mountPerfHud as t, HudSettings as u };
1449
+ 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
1450
 
1310
- //# sourceMappingURL=mount-perf-hud-DvDMg6uj.js.map
1451
+ //# sourceMappingURL=mount-perf-hud-CX9DrxjA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-perf-hud-CX9DrxjA.js","names":[],"sources":["../src/ui/theme.ts","../src/ui/hud-settings.ts","../src/ui/dock-panel.ts","../src/ui/icons.ts","../src/ui/format.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 * Number formatting for the card. Counts stay exact with thousands separators\n * while they fit the compact grid's 6ch value column (`99,999`), then switch\n * to compact notation (`250K`, `1.4M`, `12.3M`, `1.2B`) so a triangle count in\n * the millions can't run into the neighbouring label.\n */\nconst COMPACT_FROM = 100_000;\n\nconst exact = new Intl.NumberFormat(\"en-US\");\nconst compact = new Intl.NumberFormat(\"en-US\", {\n maximumFractionDigits: 1,\n notation: \"compact\",\n});\n\nconst formatCount = (value: number) =>\n Math.abs(value) < COMPACT_FROM ? exact.format(value) : compact.format(value);\n\nexport { formatCount };\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 { formatCount } from \"./format.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 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":"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"}
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-w4VnSoWa.js";
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-DvDMg6uj.js";
2
+ import { t as mountPerfHud } from "./mount-perf-hud-CX9DrxjA.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 ResolvedTheme, d as isThemeMode, f as DEFAULT_STORAGE_KEY, g as HudSettingsOptions, h as HudSettings, i as PerformanceView, l as THEME_MODES, m as HudSelection, n as PerfHudHandle, o as PerformanceViewOptions, p as HudDefaults, r as mountPerfHud, s as HudTheme, t as MountPerfHudOptions, u as ThemeMode, v as DockAlign, x as Placement, y as DockHandle } from "./mount-perf-hud-w4VnSoWa.js";
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 HudTheme, c as dockPanel, i as PerformanceView, l as DEFAULT_STORAGE_KEY, n as PERF_HUD_STYLES, o as THEME_MODES, r as injectStyles, s as isThemeMode, t as mountPerfHud, u as HudSettings } from "./mount-perf-hud-DvDMg6uj.js";
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-CX9DrxjA.js";
2
2
  export { DEFAULT_STORAGE_KEY, HudSettings, HudTheme, PERF_HUD_STYLES, PerformanceView, THEME_MODES, dockPanel, injectStyles, isThemeMode, mountPerfHud };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkmake/three-meter",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Zero-dependency three.js frame metrics (FPS, CPU, GPU, draw calls) with a dockable HUD. Vanilla three and React Three Fiber adapters.",
5
5
  "keywords": [
6
6
  "devtools",
@@ -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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"}