@zkmake/three-meter 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ load the scene up.
16
16
  <img
17
17
  src="https://raw.githubusercontent.com/zkmake/three-meter/main/docs/hud-full.png"
18
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, then a footer with the three-meter version, the three revision, a WebGL2 backend badge and the GPU name. Two discs beside it: a drag grip and the compact/full toggle."
19
+ alt="The HUD in full mode, dark theme. Option rows for theme (light, system, dark), dim on leave, explain metrics and a copy-report button; FPS, CPU and GPU sparklines with live values; then one row per metric, each with an icon, its value and a checkbox choosing whether the compact card shows it (FPS, calls, CPU, GPU, 1% low, frame p99, hitches, triangles, lines, points, render passes, geometries, textures, shaders); and a footer with the three-meter version, the three revision, a WebGL2 backend badge and the GPU name. Two discs beside it: a drag grip and the compact/full toggle."
20
20
  />
21
21
  </p>
22
22
 
@@ -143,6 +143,12 @@ monitor.getEnvironment();
143
143
  WebGL's unmasked renderer string, and is `null` where the browser hides it. Chrome's WebGPU gives
144
144
  vendor and architecture, not the model.
145
145
 
146
+ ## Reading the panel
147
+
148
+ Every row has an icon, its value, and a checkbox on the right that puts it in the compact HUD. Hover
149
+ a label for a one-line explanation of the metric and what a bad reading means, or tick **explain
150
+ metrics** to show them all under the rows (also works on touch).
151
+
146
152
  ## Stutter
147
153
 
148
154
  Average FPS hides a hitch every few seconds. Three rows in the full view catch it, and any of them
@@ -453,10 +453,26 @@ const dockPanel = (element, options = {}) => {
453
453
  //#endregion
454
454
  //#region src/ui/icons.ts
455
455
  const ICON_MARKUP = {
456
+ activity: "<path d=\"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2\"/>",
457
+ box: "<path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"/><path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/>",
458
+ circleDot: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"1\"/>",
459
+ cpu: "<rect width=\"16\" height=\"16\" x=\"4\" y=\"4\" rx=\"2\"/><rect width=\"6\" height=\"6\" x=\"9\" y=\"9\" rx=\"1\"/><path d=\"M15 2v2\"/><path d=\"M15 20v2\"/><path d=\"M2 15h2\"/><path d=\"M2 9h2\"/><path d=\"M20 15h2\"/><path d=\"M20 9h2\"/><path d=\"M9 2v2\"/><path d=\"M9 20v2\"/>",
460
+ gauge: "<path d=\"m12 14 4-4\"/><path d=\"M3.34 19a10 10 0 1 1 17.32 0\"/>",
461
+ image: "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/>",
462
+ info: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/>",
463
+ layers: "<path d=\"m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z\"/><path d=\"m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65\"/><path d=\"m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65\"/>",
464
+ palette: "<circle cx=\"13.5\" cy=\"6.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"17.5\" cy=\"10.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"8.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"6.5\" cy=\"12.5\" r=\".5\" fill=\"currentColor\"/><path d=\"M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z\"/>",
465
+ repeat: "<path d=\"m17 2 4 4-4 4\"/><path d=\"M3 11v-1a4 4 0 0 1 4-4h14\"/><path d=\"m7 22-4-4 4-4\"/><path d=\"M21 13v1a4 4 0 0 1-4 4H3\"/>",
466
+ spline: "<circle cx=\"19\" cy=\"5\" r=\"2\"/><circle cx=\"5\" cy=\"19\" r=\"2\"/><path d=\"M5 17A12 12 0 0 1 17 5\"/>",
467
+ timer: "<line x1=\"10\" x2=\"14\" y1=\"2\" y2=\"2\"/><line x1=\"12\" x2=\"15\" y1=\"14\" y2=\"11\"/><circle cx=\"12\" cy=\"14\" r=\"8\"/>",
468
+ trendingDown: "<polyline points=\"22 17 13.5 8.5 8.5 13.5 2 7\"/><polyline points=\"16 17 22 17 22 11\"/>",
469
+ triangle: "<path d=\"M13.73 4a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\"/>",
470
+ zap: "<path d=\"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z\"/>",
456
471
  blend: "<circle cx=\"9\" cy=\"9\" r=\"7\"/><circle cx=\"15\" cy=\"15\" r=\"7\"/>",
457
472
  clipboard: "<rect width=\"8\" height=\"4\" x=\"8\" y=\"2\" rx=\"1\" ry=\"1\"/><path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\"/>",
458
473
  contrast: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 18a6 6 0 0 0 0-12v12z\"/>",
459
474
  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\"/>",
475
+ help: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/><path d=\"M12 17h.01\"/>",
460
476
  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\"/>",
461
477
  moon: "<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/>",
462
478
  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\"/>",
@@ -477,7 +493,7 @@ const createIcon = (name, className) => {
477
493
  };
478
494
  //#endregion
479
495
  //#region package.json
480
- var version = "0.5.0";
496
+ var version = "0.6.0";
481
497
  //#endregion
482
498
  //#region src/ui/clipboard.ts
483
499
  /**
@@ -529,6 +545,29 @@ const compact = new Intl.NumberFormat("en-US", {
529
545
  });
530
546
  const formatCount = (value) => Math.abs(value) < COMPACT_FROM ? exact.format(value) : compact.format(value);
531
547
  //#endregion
548
+ //#region src/ui/metric-help.ts
549
+ /**
550
+ * One plain-language line per metric: what it is, and what a bad reading
551
+ * means. Shown as the label's tooltip and, with "explain metrics" on, under
552
+ * each row. Keyed like the stat rows; the graphs reuse `fps`, `cpu`, `gpu`.
553
+ */
554
+ const METRIC_HELP = {
555
+ calls: "Separate draw commands sent to the GPU this frame. Fewer is better: merge or instance meshes that share a material.",
556
+ cpu: "JavaScript time per frame: your update code plus three.js preparing the render. It has to fit the frame budget (16.7 ms at 60 FPS).",
557
+ fps: "Frames drawn per second. Higher is smoother; most displays top out at 60 or 120.",
558
+ geometries: "Geometries held in GPU memory. Should level off; a steady climb is a leak, so dispose() what you remove.",
559
+ gpu: "Time the graphics card spent drawing the frame. High means shaders, overdraw or resolution are the bottleneck.",
560
+ hitches: "Frames that took over twice as long as usual, in the last 1,000 frames. Each one is a visible stutter.",
561
+ lines: "Line segments drawn this frame.",
562
+ low: "Frame rate during the slowest 1% of frames. Far below FPS means stutter the average hides.",
563
+ p99: "99% of frames finished within this many ms. Above the frame budget (16.7 ms at 60 FPS) means dropped frames.",
564
+ passes: "Times renderer.render() ran this frame. Shadows, post-processing and extra cameras each add passes.",
565
+ points: "Points drawn this frame.",
566
+ shaders: "Compiled shader programs. A jump mid-session usually means a stutter while a new material compiled.",
567
+ textures: "Textures held in GPU memory. Should level off; a steady climb is a leak, so dispose() what you remove.",
568
+ triangles: "Triangles drawn this frame. Mostly GPU cost: use lower-poly models or level of detail to cut it."
569
+ };
570
+ //#endregion
532
571
  //#region src/ui/report.ts
533
572
  /**
534
573
  * A plain-text snapshot of the monitor for bug reports: versions, backend and
@@ -625,6 +664,7 @@ function drawSparkline(ctx, width, height, values, style) {
625
664
  const TIMINGS = [
626
665
  {
627
666
  format: (value) => Math.round(value).toString(),
667
+ icon: "gauge",
628
668
  label: "FPS",
629
669
  metric: "fps",
630
670
  style: {
@@ -641,6 +681,7 @@ const TIMINGS = [
641
681
  },
642
682
  {
643
683
  format: (value) => value.toFixed(1),
684
+ icon: "cpu",
644
685
  label: "CPU",
645
686
  metric: "cpu",
646
687
  style: {
@@ -657,6 +698,7 @@ const TIMINGS = [
657
698
  },
658
699
  {
659
700
  format: (value) => value.toFixed(1),
701
+ icon: "zap",
660
702
  label: "GPU",
661
703
  metric: "gpu",
662
704
  style: {
@@ -702,71 +744,85 @@ const readTiming = (sample, metric) => {
702
744
  const isTimingAvailable = (sample, metric) => metric !== "gpu" || sample.gpu.available;
703
745
  const NUMBERS = [
704
746
  {
747
+ icon: "gauge",
705
748
  key: "fps",
706
749
  label: "FPS",
707
750
  read: (sample) => Math.round(sample.fps).toString()
708
751
  },
709
752
  {
753
+ icon: "layers",
710
754
  key: "calls",
711
755
  label: "Calls",
712
756
  read: (sample) => formatCount(sample.render.calls)
713
757
  },
714
758
  {
759
+ icon: "cpu",
715
760
  key: "cpu",
716
761
  label: "CPU",
717
762
  read: (sample) => sample.cpu.toFixed(1)
718
763
  },
719
764
  {
765
+ icon: "zap",
720
766
  key: "gpu",
721
767
  label: "GPU",
722
768
  read: (sample) => sample.gpu.available ? sample.gpu.ms.toFixed(1) : "—"
723
769
  },
724
770
  {
771
+ icon: "trendingDown",
725
772
  key: "low",
726
773
  label: "1% low",
727
774
  read: (_sample, stats) => stats.frames ? Math.round(stats.lowFps).toString() : "—"
728
775
  },
729
776
  {
777
+ icon: "timer",
730
778
  key: "p99",
731
779
  label: "Frame p99",
732
780
  read: (_sample, stats) => stats.frames ? stats.p99Ms.toFixed(1) : "—"
733
781
  },
734
782
  {
783
+ icon: "activity",
735
784
  key: "hitches",
736
785
  label: "Hitches",
737
786
  read: (_sample, stats) => stats.frames ? formatCount(stats.hitches) : "—"
738
787
  },
739
788
  {
789
+ icon: "triangle",
740
790
  key: "triangles",
741
791
  label: "Triangles",
742
792
  read: (sample) => formatCount(sample.render.triangles)
743
793
  },
744
794
  {
795
+ icon: "spline",
745
796
  key: "lines",
746
797
  label: "Lines",
747
798
  read: (sample) => formatCount(sample.render.lines)
748
799
  },
749
800
  {
801
+ icon: "circleDot",
750
802
  key: "points",
751
803
  label: "Points",
752
804
  read: (sample) => formatCount(sample.render.points)
753
805
  },
754
806
  {
807
+ icon: "repeat",
755
808
  key: "passes",
756
809
  label: "Render passes",
757
810
  read: (sample) => formatCount(sample.render.passes)
758
811
  },
759
812
  {
813
+ icon: "box",
760
814
  key: "geometries",
761
815
  label: "Geometries",
762
816
  read: (sample) => formatCount(sample.resources.geometries)
763
817
  },
764
818
  {
819
+ icon: "image",
765
820
  key: "textures",
766
821
  label: "Textures",
767
822
  read: (sample) => formatCount(sample.resources.textures)
768
823
  },
769
824
  {
825
+ icon: "palette",
770
826
  key: "shaders",
771
827
  label: "Shaders",
772
828
  read: (sample) => formatCount(sample.resources.programs)
@@ -951,7 +1007,14 @@ var PerformanceView = class {
951
1007
  dimLabel.className = "perf-monitor__label";
952
1008
  dimLabel.textContent = "dim on leave";
953
1009
  dimRow.append(createIcon("blend", "perf-monitor__icon"), dimLabel, this.dimCheckbox);
954
- options.append(themeRow, dimRow, this.buildReportRow());
1010
+ const explainRow = document.createElement("label");
1011
+ explainRow.className = "perf-monitor__row";
1012
+ const explainCheckbox = this.buildCheckbox(false, "Explain each metric", (on) => this.element.classList.toggle("perf-monitor--explain", on));
1013
+ const explainLabel = document.createElement("span");
1014
+ explainLabel.className = "perf-monitor__label";
1015
+ explainLabel.textContent = "explain metrics";
1016
+ explainRow.append(createIcon("help", "perf-monitor__icon"), explainLabel, explainCheckbox);
1017
+ options.append(themeRow, dimRow, explainRow, this.buildReportRow());
955
1018
  const graphs = document.createElement("div");
956
1019
  graphs.className = "perf-monitor__graphs";
957
1020
  for (const config of TIMINGS) {
@@ -959,21 +1022,25 @@ var PerformanceView = class {
959
1022
  graph.className = "perf-monitor__graph";
960
1023
  const canvas = document.createElement("canvas");
961
1024
  canvas.className = "perf-monitor__canvas";
962
- const overlay = document.createElement("div");
963
- overlay.className = "perf-monitor__graph-overlay";
964
- const head = document.createElement("label");
1025
+ const overlay = document.createElement("label");
1026
+ overlay.className = "perf-monitor__graph-overlay perf-monitor__graph-overlay--toggle";
1027
+ const head = document.createElement("span");
965
1028
  head.className = "perf-monitor__graph-head";
966
1029
  const checkbox = this.buildCheckbox(this.settings.isGraphEnabled(config.metric), `Show ${config.label} graph in HUD`, (on) => this.settings.setGraphEnabled(config.metric, on));
967
1030
  this.graphCheckboxes.set(config.metric, checkbox);
968
1031
  const label = document.createElement("span");
969
1032
  label.className = "perf-monitor__graph-label";
970
1033
  label.textContent = config.label;
1034
+ label.title = METRIC_HELP[config.metric] ?? "";
971
1035
  const value = document.createElement("span");
972
1036
  value.className = "perf-monitor__graph-value";
973
1037
  value.textContent = "—";
974
1038
  this.graphValueEls.set(config.metric, value);
975
- head.append(checkbox, label);
976
- overlay.append(head, value);
1039
+ const tail = document.createElement("span");
1040
+ tail.className = "perf-monitor__graph-head";
1041
+ tail.append(value, checkbox);
1042
+ head.append(createIcon(config.icon, "perf-monitor__icon"), label);
1043
+ overlay.append(head, tail);
977
1044
  graph.append(canvas, overlay);
978
1045
  graphs.append(graph);
979
1046
  const ctx = canvas.getContext("2d");
@@ -994,14 +1061,19 @@ var PerformanceView = class {
994
1061
  rowEl.className = "perf-monitor__row";
995
1062
  const checkbox = this.buildCheckbox(this.settings.isNumberEnabled(config.key), `Show ${config.label} in HUD`, (on) => this.settings.setNumberEnabled(config.key, on));
996
1063
  this.statCheckboxes.set(config.key, checkbox);
1064
+ const help = METRIC_HELP[config.key] ?? "";
997
1065
  const label = document.createElement("span");
998
1066
  label.className = "perf-monitor__label";
999
1067
  label.textContent = config.label;
1068
+ label.title = help;
1000
1069
  const value = document.createElement("span");
1001
1070
  value.className = "perf-monitor__value";
1002
1071
  value.textContent = "—";
1003
1072
  this.statValueEls.set(config.key, value);
1004
- rowEl.append(checkbox, label, value);
1073
+ const hint = document.createElement("span");
1074
+ hint.className = "perf-monitor__hint";
1075
+ hint.textContent = help;
1076
+ rowEl.append(createIcon(config.icon, "perf-monitor__icon"), label, value, checkbox, hint);
1005
1077
  stats.append(rowEl);
1006
1078
  }
1007
1079
  const footer = document.createElement("div");
@@ -1029,7 +1101,7 @@ var PerformanceView = class {
1029
1101
  this.footerGpuEl.className = "perf-monitor__footer-gpu";
1030
1102
  this.footerHardwareEl.append(this.footerBackendEl, this.footerGpuEl);
1031
1103
  footerBody.append(softwareRow, this.footerHardwareEl);
1032
- footer.append(this.infoCheckbox, footerBody);
1104
+ footer.append(createIcon("info", "perf-monitor__icon"), footerBody, this.infoCheckbox);
1033
1105
  this.element.append(hud, options, graphs, stats, footer);
1034
1106
  }
1035
1107
  rebuildHud() {
@@ -1050,6 +1122,7 @@ var PerformanceView = class {
1050
1122
  const label = document.createElement("span");
1051
1123
  label.className = "perf-monitor__graph-label";
1052
1124
  label.textContent = config.label;
1125
+ label.title = METRIC_HELP[config.metric] ?? "";
1053
1126
  const value = document.createElement("span");
1054
1127
  value.className = "perf-monitor__graph-value";
1055
1128
  value.textContent = "—";
@@ -1075,6 +1148,7 @@ var PerformanceView = class {
1075
1148
  const label = document.createElement("span");
1076
1149
  label.className = "perf-monitor__hud-label";
1077
1150
  label.textContent = config.label;
1151
+ label.title = METRIC_HELP[config.key] ?? "";
1078
1152
  const value = document.createElement("span");
1079
1153
  value.className = "perf-monitor__hud-value";
1080
1154
  value.textContent = "—";
@@ -1249,8 +1323,9 @@ const PERF_HUD_STYLES = `
1249
1323
  cursor: pointer;
1250
1324
  }
1251
1325
 
1252
- .perf-monitor__graph-overlay .perf-monitor__graph-head {
1326
+ .perf-monitor__graph-overlay--toggle {
1253
1327
  pointer-events: auto;
1328
+ cursor: pointer;
1254
1329
  }
1255
1330
 
1256
1331
  .perf-monitor__graph-label {
@@ -1282,6 +1357,25 @@ const PERF_HUD_STYLES = `
1282
1357
  cursor: pointer;
1283
1358
  }
1284
1359
 
1360
+ /* Hidden until "explain metrics" is on; then the row wraps it under the label. */
1361
+ .perf-monitor__hint {
1362
+ display: none;
1363
+ flex-basis: 100%;
1364
+ padding: 0 0 2px 18px;
1365
+ color: var(--perf-muted);
1366
+ font-size: 10px;
1367
+ line-height: 1.35;
1368
+ opacity: 0.85;
1369
+ }
1370
+
1371
+ .perf-monitor--explain .perf-monitor__hint {
1372
+ display: block;
1373
+ }
1374
+
1375
+ .perf-monitor--explain .perf-monitor__section .perf-monitor__row {
1376
+ flex-wrap: wrap;
1377
+ }
1378
+
1285
1379
  .perf-monitor__row:nth-child(even) {
1286
1380
  background: var(--perf-row);
1287
1381
  }
@@ -1514,7 +1608,8 @@ const PERF_HUD_STYLES = `
1514
1608
  .perf-monitor--compact .perf-monitor__graphs,
1515
1609
  .perf-monitor--compact .perf-monitor__section,
1516
1610
  .perf-monitor--compact:not(.perf-monitor--info) .perf-monitor__footer,
1517
- .perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox {
1611
+ .perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox,
1612
+ .perf-monitor--compact .perf-monitor__footer > .perf-monitor__icon {
1518
1613
  display: none;
1519
1614
  }
1520
1615
 
@@ -1545,6 +1640,11 @@ const PERF_HUD_STYLES = `
1545
1640
  max-width: 100%;
1546
1641
  }
1547
1642
 
1643
+ /* Room for the hints, so most fit on two or three lines. Plus padding, this fills the host's 20rem cap. */
1644
+ .perf-hud--full > .perf-monitor--explain {
1645
+ width: 19rem;
1646
+ }
1647
+
1548
1648
  .perf-hud > .perf-monitor {
1549
1649
  position: relative;
1550
1650
  z-index: 1;
@@ -1768,4 +1868,4 @@ const mountPerfHud = (monitor, options = {}) => {
1768
1868
  //#endregion
1769
1869
  export { formatReport as a, HudSettings as c, isThemeMode as d, PerformanceView as i, HudTheme as l, PERF_HUD_STYLES as n, dockPanel as o, injectStyles as r, DEFAULT_STORAGE_KEY as s, mountPerfHud as t, THEME_MODES as u };
1770
1870
 
1771
- //# sourceMappingURL=mount-perf-hud-BLs1jkwM.js.map
1871
+ //# sourceMappingURL=mount-perf-hud-C5wdjgmM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-perf-hud-C5wdjgmM.js","names":[],"sources":["../src/ui/theme.ts","../src/ui/hud-settings.ts","../src/ui/dock-panel.ts","../src/ui/icons.ts","../package.json","../src/ui/clipboard.ts","../src/ui/format.ts","../src/ui/metric-help.ts","../src/ui/report.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, whether it shows the environment\n * footer, 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 /** Version, three revision, backend and GPU footer in the compact HUD. */\n info: boolean;\n numbers: ReadonlySet<string>;\n theme: ThemeMode | null;\n};\n\ntype HudDefaults = {\n dim?: boolean;\n graphs?: readonly TimingMetric[];\n info?: boolean;\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 info: boolean;\n numbers: Set<string>;\n theme: ThemeMode | null;\n};\n\nconst DEFAULT_STORAGE_KEY = \"three-meter\";\nconst DEFAULT_DIM = false;\nconst DEFAULT_INFO = 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 info: options.defaults?.info ?? DEFAULT_INFO,\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 get info() {\n return this.state.info;\n }\n\n setInfo(enabled: boolean) {\n this.state.info = 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 info: this.defaults.info,\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 info?: boolean;\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 info: parsed.info ?? this.defaults.info,\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 info: this.state.info,\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 =\n | \"activity\"\n | \"blend\"\n | \"box\"\n | \"circleDot\"\n | \"clipboard\"\n | \"contrast\"\n | \"cpu\"\n | \"gauge\"\n | \"grip\"\n | \"help\"\n | \"image\"\n | \"info\"\n | \"layers\"\n | \"monitor\"\n | \"moon\"\n | \"palette\"\n | \"repeat\"\n | \"sliders\"\n | \"spline\"\n | \"sun\"\n | \"timer\"\n | \"trendingDown\"\n | \"triangle\"\n | \"zap\";\n\nconst ICON_MARKUP: Record<IconName, string> = {\n activity:\n '<path d=\"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2\"/>',\n box:\n '<path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"/>' +\n '<path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/>',\n circleDot: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"1\"/>',\n cpu:\n '<rect width=\"16\" height=\"16\" x=\"4\" y=\"4\" rx=\"2\"/><rect width=\"6\" height=\"6\" x=\"9\" y=\"9\" rx=\"1\"/>' +\n '<path d=\"M15 2v2\"/><path d=\"M15 20v2\"/><path d=\"M2 15h2\"/><path d=\"M2 9h2\"/>' +\n '<path d=\"M20 15h2\"/><path d=\"M20 9h2\"/><path d=\"M9 2v2\"/><path d=\"M9 20v2\"/>',\n gauge: '<path d=\"m12 14 4-4\"/><path d=\"M3.34 19a10 10 0 1 1 17.32 0\"/>',\n image:\n '<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/>' +\n '<path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/>',\n info: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/>',\n layers:\n '<path d=\"m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z\"/>' +\n '<path d=\"m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65\"/>' +\n '<path d=\"m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65\"/>',\n palette:\n '<circle cx=\"13.5\" cy=\"6.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"17.5\" cy=\"10.5\" r=\".5\" fill=\"currentColor\"/>' +\n '<circle cx=\"8.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"6.5\" cy=\"12.5\" r=\".5\" fill=\"currentColor\"/>' +\n '<path d=\"M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z\"/>',\n repeat:\n '<path d=\"m17 2 4 4-4 4\"/><path d=\"M3 11v-1a4 4 0 0 1 4-4h14\"/>' +\n '<path d=\"m7 22-4-4 4-4\"/><path d=\"M21 13v1a4 4 0 0 1-4 4H3\"/>',\n spline:\n '<circle cx=\"19\" cy=\"5\" r=\"2\"/><circle cx=\"5\" cy=\"19\" r=\"2\"/><path d=\"M5 17A12 12 0 0 1 17 5\"/>',\n timer:\n '<line x1=\"10\" x2=\"14\" y1=\"2\" y2=\"2\"/><line x1=\"12\" x2=\"15\" y1=\"14\" y2=\"11\"/><circle cx=\"12\" cy=\"14\" r=\"8\"/>',\n trendingDown:\n '<polyline points=\"22 17 13.5 8.5 8.5 13.5 2 7\"/><polyline points=\"16 17 22 17 22 11\"/>',\n triangle: '<path d=\"M13.73 4a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\"/>',\n zap: '<path d=\"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z\"/>',\n blend: '<circle cx=\"9\" cy=\"9\" r=\"7\"/><circle cx=\"15\" cy=\"15\" r=\"7\"/>',\n clipboard:\n '<rect width=\"8\" height=\"4\" x=\"8\" y=\"2\" rx=\"1\" ry=\"1\"/>' +\n '<path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\"/>',\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 help:\n '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/>' +\n '<path d=\"M12 17h.01\"/>',\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 * Copy text from a click handler. The async Clipboard API needs a secure\n * context and the `clipboard-write` permission; a dev server opened over a LAN\n * IP has neither, and some embeds deny the permission. The fallback is the old\n * select-a-textarea `execCommand(\"copy\")`, still honoured inside a user gesture.\n */\nconst copyWithSelection = (text: string) => {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.setAttribute(\"readonly\", \"\");\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n textarea.style.pointerEvents = \"none\";\n\n const previous = document.activeElement as HTMLElement | null;\n document.body.append(textarea);\n textarea.select();\n\n try {\n return document.execCommand(\"copy\");\n } catch {\n return false;\n } finally {\n textarea.remove();\n previous?.focus?.();\n }\n};\n\nconst copyText = async (text: string): Promise<boolean> => {\n try {\n await navigator.clipboard.writeText(text);\n\n return true;\n } catch {\n return copyWithSelection(text);\n }\n};\n\nexport { copyText };\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 * One plain-language line per metric: what it is, and what a bad reading\n * means. Shown as the label's tooltip and, with \"explain metrics\" on, under\n * each row. Keyed like the stat rows; the graphs reuse `fps`, `cpu`, `gpu`.\n */\nconst METRIC_HELP: Record<string, string> = {\n calls:\n \"Separate draw commands sent to the GPU this frame. Fewer is better: merge or instance meshes that share a material.\",\n cpu: \"JavaScript time per frame: your update code plus three.js preparing the render. It has to fit the frame budget (16.7 ms at 60 FPS).\",\n fps: \"Frames drawn per second. Higher is smoother; most displays top out at 60 or 120.\",\n geometries:\n \"Geometries held in GPU memory. Should level off; a steady climb is a leak, so dispose() what you remove.\",\n gpu: \"Time the graphics card spent drawing the frame. High means shaders, overdraw or resolution are the bottleneck.\",\n hitches:\n \"Frames that took over twice as long as usual, in the last 1,000 frames. Each one is a visible stutter.\",\n lines: \"Line segments drawn this frame.\",\n low: \"Frame rate during the slowest 1% of frames. Far below FPS means stutter the average hides.\",\n p99: \"99% of frames finished within this many ms. Above the frame budget (16.7 ms at 60 FPS) means dropped frames.\",\n passes:\n \"Times renderer.render() ran this frame. Shadows, post-processing and extra cameras each add passes.\",\n points: \"Points drawn this frame.\",\n shaders:\n \"Compiled shader programs. A jump mid-session usually means a stutter while a new material compiled.\",\n textures:\n \"Textures held in GPU memory. Should level off; a steady climb is a leak, so dispose() what you remove.\",\n triangles:\n \"Triangles drawn this frame. Mostly GPU cost: use lower-poly models or level of detail to cut it.\",\n};\n\nexport { METRIC_HELP };\n","/**\n * A plain-text snapshot of the monitor for bug reports: versions, backend and\n * GPU, the current sample, stutter stats and the page. Fenced as a `text`\n * code block so GitHub and Slack keep the line breaks.\n */\nimport { version } from \"../../package.json\";\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { Environment, FrameStats, Sample } from \"../core/types.ts\";\nimport { formatCount } from \"./format.ts\";\n\ntype ReportPage = {\n dpr: number;\n height: number;\n userAgent: string;\n width: number;\n};\n\ntype ReportData = {\n environment: Environment;\n page: ReportPage | null;\n sample: Sample;\n stats: FrameStats;\n};\n\nconst BACKEND_LABELS = { webgl: \"WebGL\", webgl2: \"WebGL2\", webgpu: \"WebGPU\" } as const;\n\n/** `WebGPU`, `WebGL2`, `WebGL2 fallback`, or `\"\"` before the backend settles. */\nconst backendLabel = (environment: Environment) => {\n if (!environment.backend) {\n return \"\";\n }\n\n const label = BACKEND_LABELS[environment.backend];\n\n return environment.fallback ? `${label} fallback` : label;\n};\n\nconst ms = (value: number) => `${value.toFixed(1)} ms`;\n\nconst renderReport = ({ environment, page, sample, stats }: ReportData): string => {\n const { render, resources } = sample;\n const heading = [\n `three-meter v${version}`,\n environment.three ? `three r${environment.three}` : \"\",\n backendLabel(environment),\n environment.gpu ?? \"\",\n ].filter(Boolean);\n\n const frames =\n stats.frames === 0\n ? `FPS ${Math.round(sample.fps)}`\n : [\n `FPS ${Math.round(sample.fps)}`,\n `1% low ${Math.round(stats.lowFps)}`,\n `p99 ${ms(stats.p99Ms)}`,\n `hitches ${formatCount(stats.hitches)} / ${formatCount(stats.frames)} frames`,\n ].join(\" · \");\n\n const lines = [\n heading.join(\" · \"),\n frames,\n `CPU ${ms(sample.cpu)} · GPU ${sample.gpu.available ? ms(sample.gpu.ms) : \"unavailable\"}`,\n [\n `Calls ${formatCount(render.calls)}`,\n `passes ${formatCount(render.passes)}`,\n `triangles ${formatCount(render.triangles)}`,\n `lines ${formatCount(render.lines)}`,\n `points ${formatCount(render.points)}`,\n ].join(\" · \"),\n [\n `Geometries ${formatCount(resources.geometries)}`,\n `textures ${formatCount(resources.textures)}`,\n `shaders ${formatCount(resources.programs)}`,\n ].join(\" · \"),\n ];\n\n if (page) {\n lines.push(`Viewport ${page.width}×${page.height} @${page.dpr}x`, page.userAgent);\n }\n\n return [\"```text\", ...lines, \"```\"].join(\"\\n\");\n};\n\nconst readPage = (): ReportPage | null =>\n typeof window === \"undefined\"\n ? null\n : {\n dpr: window.devicePixelRatio || 1,\n height: window.innerHeight,\n userAgent: navigator.userAgent,\n width: window.innerWidth,\n };\n\n/** The report for `monitor` right now, as Markdown ready to paste into an issue. */\nconst formatReport = (monitor: PerformanceMonitor): string =>\n renderReport({\n environment: monitor.getEnvironment(),\n page: readPage(),\n sample: monitor.getSample(),\n stats: monitor.getFrameStats(),\n });\n\nexport { backendLabel, formatReport, renderReport };\nexport type { ReportData, ReportPage };\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 { version } from \"../../package.json\";\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { FrameStats, Sample, TimingMetric } from \"../core/types.ts\";\nimport { copyText } from \"./clipboard.ts\";\nimport { formatCount } from \"./format.ts\";\nimport { HudSettings } from \"./hud-settings.ts\";\nimport { createIcon, type IconName } from \"./icons.ts\";\nimport { METRIC_HELP } from \"./metric-help.ts\";\nimport { backendLabel, formatReport } from \"./report.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 icon: IconName;\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 icon: IconName;\n key: string;\n label: string;\n read: (sample: Sample, stats: FrameStats) => string;\n};\n\nconst TIMINGS: TimingConfig[] = [\n {\n format: (value) => Math.round(value).toString(),\n icon: \"gauge\",\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 icon: \"cpu\",\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 icon: \"zap\",\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 RELEASE_URL = `https://github.com/zkmake/three-meter/releases/tag/v${version}`;\n\n/** How long the report button says `copied` / `failed` before reading `copy` again. */\nconst COPY_FEEDBACK_MS = 1500;\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 { icon: \"gauge\", key: \"fps\", label: \"FPS\", read: (sample) => Math.round(sample.fps).toString() },\n {\n icon: \"layers\",\n key: \"calls\",\n label: \"Calls\",\n read: (sample) => formatCount(sample.render.calls),\n },\n { icon: \"cpu\", key: \"cpu\", label: \"CPU\", read: (sample) => sample.cpu.toFixed(1) },\n {\n icon: \"zap\",\n key: \"gpu\",\n label: \"GPU\",\n read: (sample) => (sample.gpu.available ? sample.gpu.ms.toFixed(1) : \"—\"),\n },\n {\n icon: \"trendingDown\",\n key: \"low\",\n label: \"1% low\",\n read: (_sample, stats) => (stats.frames ? Math.round(stats.lowFps).toString() : \"—\"),\n },\n {\n icon: \"timer\",\n key: \"p99\",\n label: \"Frame p99\",\n read: (_sample, stats) => (stats.frames ? stats.p99Ms.toFixed(1) : \"—\"),\n },\n {\n icon: \"activity\",\n key: \"hitches\",\n label: \"Hitches\",\n read: (_sample, stats) => (stats.frames ? formatCount(stats.hitches) : \"—\"),\n },\n {\n icon: \"triangle\",\n key: \"triangles\",\n label: \"Triangles\",\n read: (sample) => formatCount(sample.render.triangles),\n },\n {\n icon: \"spline\",\n key: \"lines\",\n label: \"Lines\",\n read: (sample) => formatCount(sample.render.lines),\n },\n {\n icon: \"circleDot\",\n key: \"points\",\n label: \"Points\",\n read: (sample) => formatCount(sample.render.points),\n },\n {\n icon: \"repeat\",\n key: \"passes\",\n label: \"Render passes\",\n read: (sample) => formatCount(sample.render.passes),\n },\n {\n icon: \"box\",\n key: \"geometries\",\n label: \"Geometries\",\n read: (sample) => formatCount(sample.resources.geometries),\n },\n {\n icon: \"image\",\n key: \"textures\",\n label: \"Textures\",\n read: (sample) => formatCount(sample.resources.textures),\n },\n {\n icon: \"palette\",\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 infoCheckbox!: HTMLInputElement;\n private readonly themeRadios = new Map<ThemeMode, HTMLButtonElement>();\n private footerThreeEl!: HTMLElement;\n private footerHardwareEl!: HTMLElement;\n private footerBackendEl!: HTMLElement;\n private footerGpuEl!: HTMLElement;\n private lastEnvironmentKey = \"\";\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.applyInfoClass();\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 if (this.settings.info) {\n this.renderFooter();\n }\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 const stats = this.monitor.getFrameStats();\n\n for (const config of NUMBERS) {\n this.statValueEls.get(config.key)!.textContent = config.read(sample, stats);\n }\n\n this.renderFooter();\n }\n\n /** The monitor caches the environment once its backend settles; until then this re-reads. */\n private renderFooter() {\n const environment = this.monitor.getEnvironment();\n const three = environment.three ? `three r${environment.three}` : \"\";\n const backend = backendLabel(environment);\n const gpu = environment.gpu ?? \"\";\n const key = `${three}\\n${backend}\\n${gpu}`;\n\n if (key === this.lastEnvironmentKey) {\n return;\n }\n\n this.lastEnvironmentKey = key;\n this.footerThreeEl.textContent = three;\n this.footerBackendEl.textContent = backend;\n this.footerBackendEl.hidden = backend === \"\";\n this.footerBackendEl.title = environment.fallback\n ? \"WebGPURenderer couldn't get WebGPU and fell back to WebGL2\"\n : \"\";\n this.footerBackendEl.classList.toggle(\"is-fallback\", environment.fallback);\n this.footerGpuEl.textContent = gpu;\n this.footerGpuEl.title = gpu;\n this.footerHardwareEl.hidden = backend === \"\" && gpu === \"\";\n }\n\n private renderHud(sample: Sample) {\n const stats = this.hudNumberEls.size > 0 ? this.monitor.getFrameStats() : null;\n\n for (const [key, element] of this.hudNumberEls) {\n const config = NUMBERS.find((number) => number.key === key);\n\n if (config && stats) {\n element.textContent = config.read(sample, stats);\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\n // View-local, not a setting: it's a way to learn the panel, not a preference.\n const explainRow = document.createElement(\"label\");\n explainRow.className = \"perf-monitor__row\";\n\n const explainCheckbox = this.buildCheckbox(false, \"Explain each metric\", (on) =>\n this.element.classList.toggle(\"perf-monitor--explain\", on),\n );\n\n const explainLabel = document.createElement(\"span\");\n explainLabel.className = \"perf-monitor__label\";\n explainLabel.textContent = \"explain metrics\";\n\n explainRow.append(createIcon(\"help\", \"perf-monitor__icon\"), explainLabel, explainCheckbox);\n options.append(themeRow, dimRow, explainRow, this.buildReportRow());\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 // The whole overlay is the label, so a click anywhere on the graph toggles it.\n const overlay = document.createElement(\"label\");\n overlay.className = \"perf-monitor__graph-overlay perf-monitor__graph-overlay--toggle\";\n\n const head = document.createElement(\"span\");\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 label.title = METRIC_HELP[config.metric] ?? \"\";\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 const tail = document.createElement(\"span\");\n tail.className = \"perf-monitor__graph-head\";\n tail.append(value, checkbox);\n\n head.append(createIcon(config.icon, \"perf-monitor__icon\"), label);\n overlay.append(head, tail);\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 help = METRIC_HELP[config.key] ?? \"\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__label\";\n label.textContent = config.label;\n label.title = help;\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 const hint = document.createElement(\"span\");\n hint.className = \"perf-monitor__hint\";\n hint.textContent = help;\n\n rowEl.append(createIcon(config.icon, \"perf-monitor__icon\"), label, value, checkbox, hint);\n stats.append(rowEl);\n }\n\n // Icon, rows, checkbox like the stat rows; icon and checkbox hide in compact, the rows don't.\n const footer = document.createElement(\"div\");\n footer.className = \"perf-monitor__footer\";\n\n this.infoCheckbox = this.buildCheckbox(\n this.settings.info,\n \"Show version and environment in HUD\",\n (on) => this.settings.setInfo(on),\n );\n\n const footerBody = document.createElement(\"div\");\n footerBody.className = \"perf-monitor__footer-body\";\n\n // Software on the first row, hardware on the second.\n const softwareRow = document.createElement(\"div\");\n softwareRow.className = \"perf-monitor__footer-row\";\n\n const release = document.createElement(\"a\");\n release.className = \"perf-monitor__link\";\n release.href = RELEASE_URL;\n release.target = \"_blank\";\n release.rel = \"noopener noreferrer\";\n release.title = `Release notes for v${version}`;\n release.textContent = `three-meter v${version}`;\n\n this.footerThreeEl = document.createElement(\"span\");\n softwareRow.append(release, this.footerThreeEl);\n\n this.footerHardwareEl = document.createElement(\"div\");\n this.footerHardwareEl.className = \"perf-monitor__footer-row\";\n this.footerHardwareEl.hidden = true;\n\n this.footerBackendEl = document.createElement(\"span\");\n this.footerBackendEl.className = \"perf-monitor__badge\";\n\n this.footerGpuEl = document.createElement(\"span\");\n this.footerGpuEl.className = \"perf-monitor__footer-gpu\";\n\n this.footerHardwareEl.append(this.footerBackendEl, this.footerGpuEl);\n footerBody.append(softwareRow, this.footerHardwareEl);\n footer.append(createIcon(\"info\", \"perf-monitor__icon\"), footerBody, this.infoCheckbox);\n\n this.element.append(hud, options, graphs, stats, footer);\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 label.title = METRIC_HELP[config.metric] ?? \"\";\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 label.title = METRIC_HELP[config.key] ?? \"\";\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.infoCheckbox.checked = this.settings.info;\n this.applyInfoClass();\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 buildReportRow() {\n const row = document.createElement(\"div\");\n row.className = \"perf-monitor__row perf-monitor__row--static\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__label\";\n label.textContent = \"report\";\n\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"perf-monitor__button\";\n button.title = \"Copy versions, GPU and current metrics as Markdown for a bug report\";\n button.textContent = \"copy\";\n\n let resetTimer = 0;\n\n const flash = (text: string) => {\n button.textContent = text;\n window.clearTimeout(resetTimer);\n resetTimer = window.setTimeout(() => {\n button.textContent = \"copy\";\n }, COPY_FEEDBACK_MS);\n };\n\n button.addEventListener(\"click\", () => {\n void copyText(formatReport(this.monitor)).then((copied) =>\n flash(copied ? \"copied\" : \"failed\"),\n );\n });\n\n row.append(createIcon(\"clipboard\", \"perf-monitor__icon\"), label, button);\n\n return row;\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 applyInfoClass() {\n this.element.classList.toggle(\"perf-monitor--info\", this.settings.info);\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--toggle {\n pointer-events: auto;\n cursor: pointer;\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/* Hidden until \"explain metrics\" is on; then the row wraps it under the label. */\n.perf-monitor__hint {\n display: none;\n flex-basis: 100%;\n padding: 0 0 2px 18px;\n color: var(--perf-muted);\n font-size: 10px;\n line-height: 1.35;\n opacity: 0.85;\n}\n\n.perf-monitor--explain .perf-monitor__hint {\n display: block;\n}\n\n.perf-monitor--explain .perf-monitor__section .perf-monitor__row {\n flex-wrap: wrap;\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__button {\n flex: none;\n min-width: 7ch;\n padding: 0 6px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n background: var(--perf-row);\n color: var(--perf-fg-dim);\n font: inherit;\n cursor: pointer;\n}\n\n.perf-monitor__button:hover {\n color: var(--perf-fg);\n}\n\n.perf-monitor__button:focus-visible {\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\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__footer {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n padding: 8px 6px 0;\n border-top: 1px solid var(--perf-border);\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__footer-body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row[hidden],\n.perf-monitor__badge[hidden] {\n display: none;\n}\n\n.perf-monitor__footer-gpu {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--perf-fg-dim);\n}\n\n.perf-monitor__badge {\n flex: none;\n padding: 0 5px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n color: var(--perf-fg-dim);\n letter-spacing: 0.04em;\n}\n\n.perf-monitor__badge.is-fallback {\n border-color: #f59e0b;\n color: #f59e0b;\n}\n\n.perf-monitor[data-theme=\"light\"] .perf-monitor__badge.is-fallback {\n border-color: #d97706;\n color: #b45309;\n}\n\n.perf-monitor__link {\n color: inherit;\n text-decoration: none;\n}\n\n.perf-monitor__link:hover,\n.perf-monitor__link:focus-visible {\n color: var(--perf-accent);\n text-decoration: underline;\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.perf-monitor--compact:not(.perf-monitor--info) .perf-monitor__footer,\n.perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox,\n.perf-monitor--compact .perf-monitor__footer > .perf-monitor__icon {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__footer {\n padding: 6px 2px 2px;\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/* Room for the hints, so most fit on two or three lines. Plus padding, this fills the host's 20rem cap. */\n.perf-hud--full > .perf-monitor--explain {\n width: 19rem;\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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as PerformanceMonitor } from "./performance-monitor-yxsxs57v.js";
2
- import { t as mountPerfHud } from "./mount-perf-hud-BLs1jkwM.js";
2
+ import { t as mountPerfHud } from "./mount-perf-hud-C5wdjgmM.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
@@ -74,8 +74,9 @@
74
74
  cursor: pointer;
75
75
  }
76
76
 
77
- .perf-monitor__graph-overlay .perf-monitor__graph-head {
77
+ .perf-monitor__graph-overlay--toggle {
78
78
  pointer-events: auto;
79
+ cursor: pointer;
79
80
  }
80
81
 
81
82
  .perf-monitor__graph-label {
@@ -107,6 +108,25 @@
107
108
  cursor: pointer;
108
109
  }
109
110
 
111
+ /* Hidden until "explain metrics" is on; then the row wraps it under the label. */
112
+ .perf-monitor__hint {
113
+ display: none;
114
+ flex-basis: 100%;
115
+ padding: 0 0 2px 18px;
116
+ color: var(--perf-muted);
117
+ font-size: 10px;
118
+ line-height: 1.35;
119
+ opacity: 0.85;
120
+ }
121
+
122
+ .perf-monitor--explain .perf-monitor__hint {
123
+ display: block;
124
+ }
125
+
126
+ .perf-monitor--explain .perf-monitor__section .perf-monitor__row {
127
+ flex-wrap: wrap;
128
+ }
129
+
110
130
  .perf-monitor__row:nth-child(even) {
111
131
  background: var(--perf-row);
112
132
  }
@@ -339,7 +359,8 @@
339
359
  .perf-monitor--compact .perf-monitor__graphs,
340
360
  .perf-monitor--compact .perf-monitor__section,
341
361
  .perf-monitor--compact:not(.perf-monitor--info) .perf-monitor__footer,
342
- .perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox {
362
+ .perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox,
363
+ .perf-monitor--compact .perf-monitor__footer > .perf-monitor__icon {
343
364
  display: none;
344
365
  }
345
366
 
@@ -370,6 +391,11 @@
370
391
  max-width: 100%;
371
392
  }
372
393
 
394
+ /* Room for the hints, so most fit on two or three lines. Plus padding, this fills the host's 20rem cap. */
395
+ .perf-hud--full > .perf-monitor--explain {
396
+ width: 19rem;
397
+ }
398
+
373
399
  .perf-hud > .perf-monitor {
374
400
  position: relative;
375
401
  z-index: 1;
package/dist/ui.d.ts CHANGED
@@ -5,7 +5,7 @@ import { C as dockPanel, S as ScreenEdge, _ as DefaultPlacement, a as Performanc
5
5
  declare const formatReport: (monitor: PerformanceMonitor) => string;
6
6
  //#endregion
7
7
  //#region src/ui/styles.d.ts
8
- 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__button {\n flex: none;\n min-width: 7ch;\n padding: 0 6px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n background: var(--perf-row);\n color: var(--perf-fg-dim);\n font: inherit;\n cursor: pointer;\n}\n\n.perf-monitor__button:hover {\n color: var(--perf-fg);\n}\n\n.perf-monitor__button:focus-visible {\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\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__footer {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n padding: 8px 6px 0;\n border-top: 1px solid var(--perf-border);\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__footer-body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row[hidden],\n.perf-monitor__badge[hidden] {\n display: none;\n}\n\n.perf-monitor__footer-gpu {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--perf-fg-dim);\n}\n\n.perf-monitor__badge {\n flex: none;\n padding: 0 5px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n color: var(--perf-fg-dim);\n letter-spacing: 0.04em;\n}\n\n.perf-monitor__badge.is-fallback {\n border-color: #f59e0b;\n color: #f59e0b;\n}\n\n.perf-monitor[data-theme=\"light\"] .perf-monitor__badge.is-fallback {\n border-color: #d97706;\n color: #b45309;\n}\n\n.perf-monitor__link {\n color: inherit;\n text-decoration: none;\n}\n\n.perf-monitor__link:hover,\n.perf-monitor__link:focus-visible {\n color: var(--perf-accent);\n text-decoration: underline;\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.perf-monitor--compact:not(.perf-monitor--info) .perf-monitor__footer,\n.perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__footer {\n padding: 6px 2px 2px;\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";
8
+ 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--toggle {\n pointer-events: auto;\n cursor: pointer;\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/* Hidden until \"explain metrics\" is on; then the row wraps it under the label. */\n.perf-monitor__hint {\n display: none;\n flex-basis: 100%;\n padding: 0 0 2px 18px;\n color: var(--perf-muted);\n font-size: 10px;\n line-height: 1.35;\n opacity: 0.85;\n}\n\n.perf-monitor--explain .perf-monitor__hint {\n display: block;\n}\n\n.perf-monitor--explain .perf-monitor__section .perf-monitor__row {\n flex-wrap: wrap;\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__button {\n flex: none;\n min-width: 7ch;\n padding: 0 6px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n background: var(--perf-row);\n color: var(--perf-fg-dim);\n font: inherit;\n cursor: pointer;\n}\n\n.perf-monitor__button:hover {\n color: var(--perf-fg);\n}\n\n.perf-monitor__button:focus-visible {\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\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__footer {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n padding: 8px 6px 0;\n border-top: 1px solid var(--perf-border);\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__footer-body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row[hidden],\n.perf-monitor__badge[hidden] {\n display: none;\n}\n\n.perf-monitor__footer-gpu {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--perf-fg-dim);\n}\n\n.perf-monitor__badge {\n flex: none;\n padding: 0 5px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n color: var(--perf-fg-dim);\n letter-spacing: 0.04em;\n}\n\n.perf-monitor__badge.is-fallback {\n border-color: #f59e0b;\n color: #f59e0b;\n}\n\n.perf-monitor[data-theme=\"light\"] .perf-monitor__badge.is-fallback {\n border-color: #d97706;\n color: #b45309;\n}\n\n.perf-monitor__link {\n color: inherit;\n text-decoration: none;\n}\n\n.perf-monitor__link:hover,\n.perf-monitor__link:focus-visible {\n color: var(--perf-accent);\n text-decoration: underline;\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.perf-monitor--compact:not(.perf-monitor--info) .perf-monitor__footer,\n.perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox,\n.perf-monitor--compact .perf-monitor__footer > .perf-monitor__icon {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__footer {\n padding: 6px 2px 2px;\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/* Room for the hints, so most fit on two or three lines. Plus padding, this fills the host's 20rem cap. */\n.perf-hud--full > .perf-monitor--explain {\n width: 19rem;\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";
9
9
  /**
10
10
  * Append the stylesheet to `target`'s head, once. Call before mounting a view
11
11
  * 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 formatReport, c as HudSettings, d as isThemeMode, i as PerformanceView, l as HudTheme, n as PERF_HUD_STYLES, o as dockPanel, r as injectStyles, s as DEFAULT_STORAGE_KEY, t as mountPerfHud, u as THEME_MODES } from "./mount-perf-hud-BLs1jkwM.js";
1
+ import { a as formatReport, c as HudSettings, d as isThemeMode, i as PerformanceView, l as HudTheme, n as PERF_HUD_STYLES, o as dockPanel, r as injectStyles, s as DEFAULT_STORAGE_KEY, t as mountPerfHud, u as THEME_MODES } from "./mount-perf-hud-C5wdjgmM.js";
2
2
  export { DEFAULT_STORAGE_KEY, HudSettings, HudTheme, PERF_HUD_STYLES, PerformanceView, THEME_MODES, dockPanel, formatReport, injectStyles, isThemeMode, mountPerfHud };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkmake/three-meter",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
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-BLs1jkwM.js","names":[],"sources":["../src/ui/theme.ts","../src/ui/hud-settings.ts","../src/ui/dock-panel.ts","../src/ui/icons.ts","../package.json","../src/ui/clipboard.ts","../src/ui/format.ts","../src/ui/report.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, whether it shows the environment\n * footer, 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 /** Version, three revision, backend and GPU footer in the compact HUD. */\n info: boolean;\n numbers: ReadonlySet<string>;\n theme: ThemeMode | null;\n};\n\ntype HudDefaults = {\n dim?: boolean;\n graphs?: readonly TimingMetric[];\n info?: boolean;\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 info: boolean;\n numbers: Set<string>;\n theme: ThemeMode | null;\n};\n\nconst DEFAULT_STORAGE_KEY = \"three-meter\";\nconst DEFAULT_DIM = false;\nconst DEFAULT_INFO = 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 info: options.defaults?.info ?? DEFAULT_INFO,\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 get info() {\n return this.state.info;\n }\n\n setInfo(enabled: boolean) {\n this.state.info = 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 info: this.defaults.info,\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 info?: boolean;\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 info: parsed.info ?? this.defaults.info,\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 info: this.state.info,\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 =\n | \"blend\"\n | \"clipboard\"\n | \"contrast\"\n | \"grip\"\n | \"monitor\"\n | \"moon\"\n | \"sliders\"\n | \"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 clipboard:\n '<rect width=\"8\" height=\"4\" x=\"8\" y=\"2\" rx=\"1\" ry=\"1\"/>' +\n '<path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\"/>',\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 * Copy text from a click handler. The async Clipboard API needs a secure\n * context and the `clipboard-write` permission; a dev server opened over a LAN\n * IP has neither, and some embeds deny the permission. The fallback is the old\n * select-a-textarea `execCommand(\"copy\")`, still honoured inside a user gesture.\n */\nconst copyWithSelection = (text: string) => {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.setAttribute(\"readonly\", \"\");\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n textarea.style.pointerEvents = \"none\";\n\n const previous = document.activeElement as HTMLElement | null;\n document.body.append(textarea);\n textarea.select();\n\n try {\n return document.execCommand(\"copy\");\n } catch {\n return false;\n } finally {\n textarea.remove();\n previous?.focus?.();\n }\n};\n\nconst copyText = async (text: string): Promise<boolean> => {\n try {\n await navigator.clipboard.writeText(text);\n\n return true;\n } catch {\n return copyWithSelection(text);\n }\n};\n\nexport { copyText };\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 * A plain-text snapshot of the monitor for bug reports: versions, backend and\n * GPU, the current sample, stutter stats and the page. Fenced as a `text`\n * code block so GitHub and Slack keep the line breaks.\n */\nimport { version } from \"../../package.json\";\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { Environment, FrameStats, Sample } from \"../core/types.ts\";\nimport { formatCount } from \"./format.ts\";\n\ntype ReportPage = {\n dpr: number;\n height: number;\n userAgent: string;\n width: number;\n};\n\ntype ReportData = {\n environment: Environment;\n page: ReportPage | null;\n sample: Sample;\n stats: FrameStats;\n};\n\nconst BACKEND_LABELS = { webgl: \"WebGL\", webgl2: \"WebGL2\", webgpu: \"WebGPU\" } as const;\n\n/** `WebGPU`, `WebGL2`, `WebGL2 fallback`, or `\"\"` before the backend settles. */\nconst backendLabel = (environment: Environment) => {\n if (!environment.backend) {\n return \"\";\n }\n\n const label = BACKEND_LABELS[environment.backend];\n\n return environment.fallback ? `${label} fallback` : label;\n};\n\nconst ms = (value: number) => `${value.toFixed(1)} ms`;\n\nconst renderReport = ({ environment, page, sample, stats }: ReportData): string => {\n const { render, resources } = sample;\n const heading = [\n `three-meter v${version}`,\n environment.three ? `three r${environment.three}` : \"\",\n backendLabel(environment),\n environment.gpu ?? \"\",\n ].filter(Boolean);\n\n const frames =\n stats.frames === 0\n ? `FPS ${Math.round(sample.fps)}`\n : [\n `FPS ${Math.round(sample.fps)}`,\n `1% low ${Math.round(stats.lowFps)}`,\n `p99 ${ms(stats.p99Ms)}`,\n `hitches ${formatCount(stats.hitches)} / ${formatCount(stats.frames)} frames`,\n ].join(\" · \");\n\n const lines = [\n heading.join(\" · \"),\n frames,\n `CPU ${ms(sample.cpu)} · GPU ${sample.gpu.available ? ms(sample.gpu.ms) : \"unavailable\"}`,\n [\n `Calls ${formatCount(render.calls)}`,\n `passes ${formatCount(render.passes)}`,\n `triangles ${formatCount(render.triangles)}`,\n `lines ${formatCount(render.lines)}`,\n `points ${formatCount(render.points)}`,\n ].join(\" · \"),\n [\n `Geometries ${formatCount(resources.geometries)}`,\n `textures ${formatCount(resources.textures)}`,\n `shaders ${formatCount(resources.programs)}`,\n ].join(\" · \"),\n ];\n\n if (page) {\n lines.push(`Viewport ${page.width}×${page.height} @${page.dpr}x`, page.userAgent);\n }\n\n return [\"```text\", ...lines, \"```\"].join(\"\\n\");\n};\n\nconst readPage = (): ReportPage | null =>\n typeof window === \"undefined\"\n ? null\n : {\n dpr: window.devicePixelRatio || 1,\n height: window.innerHeight,\n userAgent: navigator.userAgent,\n width: window.innerWidth,\n };\n\n/** The report for `monitor` right now, as Markdown ready to paste into an issue. */\nconst formatReport = (monitor: PerformanceMonitor): string =>\n renderReport({\n environment: monitor.getEnvironment(),\n page: readPage(),\n sample: monitor.getSample(),\n stats: monitor.getFrameStats(),\n });\n\nexport { backendLabel, formatReport, renderReport };\nexport type { ReportData, ReportPage };\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 { version } from \"../../package.json\";\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { FrameStats, Sample, TimingMetric } from \"../core/types.ts\";\nimport { copyText } from \"./clipboard.ts\";\nimport { formatCount } from \"./format.ts\";\nimport { HudSettings } from \"./hud-settings.ts\";\nimport { createIcon } from \"./icons.ts\";\nimport { backendLabel, formatReport } from \"./report.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, stats: FrameStats) => 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 RELEASE_URL = `https://github.com/zkmake/three-meter/releases/tag/v${version}`;\n\n/** How long the report button says `copied` / `failed` before reading `copy` again. */\nconst COPY_FEEDBACK_MS = 1500;\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: \"low\",\n label: \"1% low\",\n read: (_sample, stats) => (stats.frames ? Math.round(stats.lowFps).toString() : \"—\"),\n },\n {\n key: \"p99\",\n label: \"Frame p99\",\n read: (_sample, stats) => (stats.frames ? stats.p99Ms.toFixed(1) : \"—\"),\n },\n {\n key: \"hitches\",\n label: \"Hitches\",\n read: (_sample, stats) => (stats.frames ? formatCount(stats.hitches) : \"—\"),\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 infoCheckbox!: HTMLInputElement;\n private readonly themeRadios = new Map<ThemeMode, HTMLButtonElement>();\n private footerThreeEl!: HTMLElement;\n private footerHardwareEl!: HTMLElement;\n private footerBackendEl!: HTMLElement;\n private footerGpuEl!: HTMLElement;\n private lastEnvironmentKey = \"\";\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.applyInfoClass();\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 if (this.settings.info) {\n this.renderFooter();\n }\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 const stats = this.monitor.getFrameStats();\n\n for (const config of NUMBERS) {\n this.statValueEls.get(config.key)!.textContent = config.read(sample, stats);\n }\n\n this.renderFooter();\n }\n\n /** The monitor caches the environment once its backend settles; until then this re-reads. */\n private renderFooter() {\n const environment = this.monitor.getEnvironment();\n const three = environment.three ? `three r${environment.three}` : \"\";\n const backend = backendLabel(environment);\n const gpu = environment.gpu ?? \"\";\n const key = `${three}\\n${backend}\\n${gpu}`;\n\n if (key === this.lastEnvironmentKey) {\n return;\n }\n\n this.lastEnvironmentKey = key;\n this.footerThreeEl.textContent = three;\n this.footerBackendEl.textContent = backend;\n this.footerBackendEl.hidden = backend === \"\";\n this.footerBackendEl.title = environment.fallback\n ? \"WebGPURenderer couldn't get WebGPU and fell back to WebGL2\"\n : \"\";\n this.footerBackendEl.classList.toggle(\"is-fallback\", environment.fallback);\n this.footerGpuEl.textContent = gpu;\n this.footerGpuEl.title = gpu;\n this.footerHardwareEl.hidden = backend === \"\" && gpu === \"\";\n }\n\n private renderHud(sample: Sample) {\n const stats = this.hudNumberEls.size > 0 ? this.monitor.getFrameStats() : null;\n\n for (const [key, element] of this.hudNumberEls) {\n const config = NUMBERS.find((number) => number.key === key);\n\n if (config && stats) {\n element.textContent = config.read(sample, stats);\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, this.buildReportRow());\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 // Checkbox first like the stat rows; the checkbox hides in compact, the rows don't.\n const footer = document.createElement(\"div\");\n footer.className = \"perf-monitor__footer\";\n\n this.infoCheckbox = this.buildCheckbox(\n this.settings.info,\n \"Show version and environment in HUD\",\n (on) => this.settings.setInfo(on),\n );\n\n const footerBody = document.createElement(\"div\");\n footerBody.className = \"perf-monitor__footer-body\";\n\n // Software on the first row, hardware on the second.\n const softwareRow = document.createElement(\"div\");\n softwareRow.className = \"perf-monitor__footer-row\";\n\n const release = document.createElement(\"a\");\n release.className = \"perf-monitor__link\";\n release.href = RELEASE_URL;\n release.target = \"_blank\";\n release.rel = \"noopener noreferrer\";\n release.title = `Release notes for v${version}`;\n release.textContent = `three-meter v${version}`;\n\n this.footerThreeEl = document.createElement(\"span\");\n softwareRow.append(release, this.footerThreeEl);\n\n this.footerHardwareEl = document.createElement(\"div\");\n this.footerHardwareEl.className = \"perf-monitor__footer-row\";\n this.footerHardwareEl.hidden = true;\n\n this.footerBackendEl = document.createElement(\"span\");\n this.footerBackendEl.className = \"perf-monitor__badge\";\n\n this.footerGpuEl = document.createElement(\"span\");\n this.footerGpuEl.className = \"perf-monitor__footer-gpu\";\n\n this.footerHardwareEl.append(this.footerBackendEl, this.footerGpuEl);\n footerBody.append(softwareRow, this.footerHardwareEl);\n footer.append(this.infoCheckbox, footerBody);\n\n this.element.append(hud, options, graphs, stats, footer);\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.infoCheckbox.checked = this.settings.info;\n this.applyInfoClass();\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 buildReportRow() {\n const row = document.createElement(\"div\");\n row.className = \"perf-monitor__row perf-monitor__row--static\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__label\";\n label.textContent = \"report\";\n\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"perf-monitor__button\";\n button.title = \"Copy versions, GPU and current metrics as Markdown for a bug report\";\n button.textContent = \"copy\";\n\n let resetTimer = 0;\n\n const flash = (text: string) => {\n button.textContent = text;\n window.clearTimeout(resetTimer);\n resetTimer = window.setTimeout(() => {\n button.textContent = \"copy\";\n }, COPY_FEEDBACK_MS);\n };\n\n button.addEventListener(\"click\", () => {\n void copyText(formatReport(this.monitor)).then((copied) =>\n flash(copied ? \"copied\" : \"failed\"),\n );\n });\n\n row.append(createIcon(\"clipboard\", \"perf-monitor__icon\"), label, button);\n\n return row;\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 applyInfoClass() {\n this.element.classList.toggle(\"perf-monitor--info\", this.settings.info);\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__button {\n flex: none;\n min-width: 7ch;\n padding: 0 6px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n background: var(--perf-row);\n color: var(--perf-fg-dim);\n font: inherit;\n cursor: pointer;\n}\n\n.perf-monitor__button:hover {\n color: var(--perf-fg);\n}\n\n.perf-monitor__button:focus-visible {\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\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__footer {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n padding: 8px 6px 0;\n border-top: 1px solid var(--perf-border);\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__footer-body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row[hidden],\n.perf-monitor__badge[hidden] {\n display: none;\n}\n\n.perf-monitor__footer-gpu {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--perf-fg-dim);\n}\n\n.perf-monitor__badge {\n flex: none;\n padding: 0 5px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n color: var(--perf-fg-dim);\n letter-spacing: 0.04em;\n}\n\n.perf-monitor__badge.is-fallback {\n border-color: #f59e0b;\n color: #f59e0b;\n}\n\n.perf-monitor[data-theme=\"light\"] .perf-monitor__badge.is-fallback {\n border-color: #d97706;\n color: #b45309;\n}\n\n.perf-monitor__link {\n color: inherit;\n text-decoration: none;\n}\n\n.perf-monitor__link:hover,\n.perf-monitor__link:focus-visible {\n color: var(--perf-accent);\n text-decoration: underline;\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.perf-monitor--compact:not(.perf-monitor--info) .perf-monitor__footer,\n.perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__footer {\n padding: 6px 2px 2px;\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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"}