@hdcodedev/snowfall 1.0.3 → 1.0.5

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/dist/index.js CHANGED
@@ -22,6 +22,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
24
  DEFAULT_PHYSICS: () => DEFAULT_PHYSICS,
25
+ DebugPanel: () => DebugPanel,
25
26
  Snowfall: () => Snowfall,
26
27
  SnowfallProvider: () => SnowfallProvider,
27
28
  useSnowfall: () => useSnowfall
@@ -57,9 +58,14 @@ var SnowfallContext = (0, import_react.createContext)(void 0);
57
58
  function SnowfallProvider({ children }) {
58
59
  const [isEnabled, setIsEnabled] = (0, import_react.useState)(true);
59
60
  const [physicsConfig, setPhysicsConfig] = (0, import_react.useState)(DEFAULT_PHYSICS);
61
+ const [debugMode, setDebugMode] = (0, import_react.useState)(false);
62
+ const [metrics, setMetrics] = (0, import_react.useState)(null);
60
63
  const toggleSnow = () => {
61
64
  setIsEnabled((prev) => !prev);
62
65
  };
66
+ const toggleDebug = () => {
67
+ setDebugMode((prev) => !prev);
68
+ };
63
69
  const updatePhysicsConfig = (config) => {
64
70
  setPhysicsConfig((prev) => ({
65
71
  ...prev,
@@ -86,7 +92,11 @@ function SnowfallProvider({ children }) {
86
92
  toggleSnow,
87
93
  physicsConfig,
88
94
  updatePhysicsConfig,
89
- resetPhysics
95
+ resetPhysics,
96
+ debugMode,
97
+ toggleDebug,
98
+ metrics,
99
+ setMetrics
90
100
  }, children });
91
101
  }
92
102
  function useSnowfall() {
@@ -103,13 +113,11 @@ var VAL_IGNORE = "ignore";
103
113
  var VAL_TOP = "top";
104
114
  var VAL_BOTTOM = "bottom";
105
115
  var TAG_HEADER = "header";
106
- var TAG_FOOTER = "footer";
107
116
  var ROLE_BANNER = "banner";
108
- var ROLE_CONTENTINFO = "contentinfo";
109
117
 
110
118
  // src/utils/snowfall/dom.ts
111
- var BOTTOM_TAGS = [TAG_HEADER, TAG_FOOTER];
112
- var BOTTOM_ROLES = [ROLE_BANNER, ROLE_CONTENTINFO];
119
+ var BOTTOM_TAGS = [TAG_HEADER];
120
+ var BOTTOM_ROLES = [ROLE_BANNER];
113
121
  var AUTO_DETECT_TAGS = ["header", "footer", "article", "section", "aside", "nav"];
114
122
  var AUTO_DETECT_ROLES = ['[role="banner"]', '[role="contentinfo"]', '[role="main"]'];
115
123
  var AUTO_DETECT_CLASSES = [
@@ -120,6 +128,18 @@ var AUTO_DETECT_CLASSES = [
120
128
  '[class*="shadow-"]',
121
129
  '[class*="rounded-"]'
122
130
  ];
131
+ var styleCache = /* @__PURE__ */ new Map();
132
+ var getCachedStyle = (el) => {
133
+ let cached = styleCache.get(el);
134
+ if (!cached) {
135
+ cached = window.getComputedStyle(el);
136
+ styleCache.set(el, cached);
137
+ }
138
+ return cached;
139
+ };
140
+ var clearStyleCache = () => {
141
+ styleCache.clear();
142
+ };
123
143
  var getElementType = (el) => {
124
144
  const tagName = el.tagName.toLowerCase();
125
145
  if (BOTTOM_TAGS.includes(tagName)) return VAL_BOTTOM;
@@ -142,6 +162,7 @@ var shouldAccumulate = (el) => {
142
162
  return hasBackground || hasBorder || hasBoxShadow || hasBorderRadius;
143
163
  };
144
164
  var getAccumulationSurfaces = () => {
165
+ clearStyleCache();
145
166
  const surfaces = [];
146
167
  const seen = /* @__PURE__ */ new Set();
147
168
  const candidates = document.querySelectorAll(
@@ -154,13 +175,13 @@ var getAccumulationSurfaces = () => {
154
175
  );
155
176
  candidates.forEach((el) => {
156
177
  if (seen.has(el)) return;
157
- const rect = el.getBoundingClientRect();
158
178
  const manualOverride = el.getAttribute(ATTR_SNOWFALL);
159
179
  if (manualOverride === VAL_IGNORE) return;
160
180
  const isManuallyIncluded = manualOverride !== null;
161
- const styles = window.getComputedStyle(el);
181
+ const styles = getCachedStyle(el);
162
182
  const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
163
183
  if (!isVisible && !isManuallyIncluded) return;
184
+ const rect = el.getBoundingClientRect();
164
185
  const hasSize = rect.width >= 100 && rect.height >= 50;
165
186
  if (!hasSize && !isManuallyIncluded) return;
166
187
  const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;
@@ -171,7 +192,7 @@ var getAccumulationSurfaces = () => {
171
192
  let isFixed = false;
172
193
  let currentEl = el;
173
194
  while (currentEl && currentEl !== document.body) {
174
- const style = window.getComputedStyle(currentEl);
195
+ const style = getCachedStyle(currentEl);
175
196
  if (style.position === "fixed" || style.position === "sticky") {
176
197
  isFixed = true;
177
198
  break;
@@ -190,6 +211,7 @@ var getAccumulationSurfaces = () => {
190
211
  }
191
212
  });
192
213
  console.log(`[Snowfall] Auto-detection found ${surfaces.length} surfaces`);
214
+ console.log("[Snowfall] \u2705 Using OPTIMIZED version with Map-based caching & 5s intervals");
193
215
  return surfaces;
194
216
  };
195
217
  var getElementRects = (accumulationMap) => {
@@ -531,10 +553,14 @@ var drawSideAccumulations = (ctx, fixedCtx, elementRects) => {
531
553
  const width = sideArray[y] || 0;
532
554
  const nextY = Math.min(y + 2, sideArray.length - 1);
533
555
  const nextWidth = sideArray[nextY] || 0;
556
+ const heightRatio = y / sideArray.length;
557
+ const gravityMultiplier = Math.pow(heightRatio, 1.5);
534
558
  const py = rect.top + y + dy;
535
- const px = (isLeft ? baseX - width : baseX + width) + dx;
559
+ const px = (isLeft ? baseX - width * gravityMultiplier : baseX + width * gravityMultiplier) + dx;
536
560
  const ny = rect.top + nextY + dy;
537
- const nx = (isLeft ? baseX - nextWidth : baseX + nextWidth) + dx;
561
+ const nRatio = nextY / sideArray.length;
562
+ const nGravityMultiplier = Math.pow(nRatio, 1.5);
563
+ const nx = (isLeft ? baseX - nextWidth * nGravityMultiplier : baseX + nextWidth * nGravityMultiplier) + dx;
538
564
  targetCtx.lineTo(px, py);
539
565
  targetCtx.lineTo(nx, ny);
540
566
  }
@@ -552,16 +578,26 @@ var drawSideAccumulations = (ctx, fixedCtx, elementRects) => {
552
578
  // src/Snowfall.tsx
553
579
  var import_jsx_runtime2 = require("react/jsx-runtime");
554
580
  function Snowfall() {
555
- const { isEnabled, physicsConfig } = useSnowfall();
581
+ const { isEnabled, physicsConfig, setMetrics } = useSnowfall();
556
582
  const isEnabledRef = (0, import_react2.useRef)(isEnabled);
557
583
  const physicsConfigRef = (0, import_react2.useRef)(physicsConfig);
584
+ const setMetricsRef = (0, import_react2.useRef)(setMetrics);
558
585
  const [isMounted, setIsMounted] = (0, import_react2.useState)(false);
559
586
  const [isVisible, setIsVisible] = (0, import_react2.useState)(false);
560
587
  const canvasRef = (0, import_react2.useRef)(null);
561
- const fixedCanvasRef = (0, import_react2.useRef)(null);
562
588
  const snowflakesRef = (0, import_react2.useRef)([]);
563
589
  const accumulationRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
564
590
  const animationIdRef = (0, import_react2.useRef)(0);
591
+ const fpsFrames = (0, import_react2.useRef)([]);
592
+ const metricsRef = (0, import_react2.useRef)({
593
+ scanTime: 0,
594
+ rectUpdateTime: 0,
595
+ frameTime: 0,
596
+ rafGap: 0,
597
+ clearTime: 0,
598
+ physicsTime: 0,
599
+ drawTime: 0
600
+ });
565
601
  (0, import_react2.useEffect)(() => {
566
602
  setIsMounted(true);
567
603
  }, []);
@@ -571,26 +607,23 @@ function Snowfall() {
571
607
  (0, import_react2.useEffect)(() => {
572
608
  physicsConfigRef.current = physicsConfig;
573
609
  }, [physicsConfig]);
610
+ (0, import_react2.useEffect)(() => {
611
+ setMetricsRef.current = setMetrics;
612
+ }, [setMetrics]);
574
613
  (0, import_react2.useEffect)(() => {
575
614
  if (!isMounted) return;
576
615
  const canvas = canvasRef.current;
577
- const fixedCanvas = fixedCanvasRef.current;
578
- if (!canvas || !fixedCanvas) return;
616
+ if (!canvas) return;
579
617
  const ctx = canvas.getContext("2d");
580
- const fixedCtx = fixedCanvas.getContext("2d");
581
- if (!ctx || !fixedCtx) return;
618
+ if (!ctx) return;
582
619
  const resizeCanvas = () => {
583
- if (canvasRef.current && fixedCanvasRef.current) {
620
+ if (canvasRef.current) {
584
621
  const newHeight = Math.max(document.documentElement.scrollHeight, window.innerHeight);
585
622
  const newWidth = Math.max(document.documentElement.scrollWidth, window.innerWidth);
586
623
  if (canvasRef.current.height !== newHeight || canvasRef.current.width !== newWidth) {
587
624
  canvasRef.current.width = newWidth;
588
625
  canvasRef.current.height = newHeight;
589
626
  }
590
- if (fixedCanvasRef.current.width !== window.innerWidth || fixedCanvasRef.current.height !== window.innerHeight) {
591
- fixedCanvasRef.current.width = window.innerWidth;
592
- fixedCanvasRef.current.height = window.innerHeight;
593
- }
594
627
  }
595
628
  };
596
629
  resizeCanvas();
@@ -600,11 +633,16 @@ function Snowfall() {
600
633
  resizeObserver.observe(document.body);
601
634
  snowflakesRef.current = [];
602
635
  const initAccumulationWrapper = () => {
636
+ const scanStart = performance.now();
603
637
  initializeAccumulation(accumulationRef.current, physicsConfigRef.current);
638
+ metricsRef.current.scanTime = performance.now() - scanStart;
604
639
  };
605
640
  initAccumulationWrapper();
606
641
  setIsVisible(true);
607
642
  let lastTime = 0;
643
+ let lastRectUpdate = 0;
644
+ let lastMetricsUpdate = 0;
645
+ let cachedElementRects = [];
608
646
  const animate = (currentTime) => {
609
647
  if (lastTime === 0) {
610
648
  lastTime = currentTime;
@@ -612,14 +650,28 @@ function Snowfall() {
612
650
  return;
613
651
  }
614
652
  const deltaTime = Math.min(currentTime - lastTime, 50);
653
+ const now = performance.now();
654
+ fpsFrames.current.push(now);
655
+ fpsFrames.current = fpsFrames.current.filter((t) => now - t < 1e3);
656
+ metricsRef.current.rafGap = currentTime - lastTime;
615
657
  lastTime = currentTime;
616
658
  const dt = deltaTime / 16.67;
659
+ const frameStartTime = performance.now();
660
+ const clearStart = performance.now();
617
661
  ctx.clearRect(0, 0, canvas.width, canvas.height);
618
- fixedCtx.clearRect(0, 0, fixedCanvas.width, fixedCanvas.height);
662
+ metricsRef.current.clearTime = performance.now() - clearStart;
619
663
  const snowflakes = snowflakesRef.current;
620
- const elementRects = getElementRects(accumulationRef.current);
621
- meltAndSmoothAccumulation(elementRects, physicsConfigRef.current, dt);
622
- updateSnowflakes(snowflakes, elementRects, physicsConfigRef.current, dt, canvas.width, canvas.height);
664
+ if (currentTime - lastRectUpdate > 100) {
665
+ const rectStart = performance.now();
666
+ cachedElementRects = getElementRects(accumulationRef.current);
667
+ metricsRef.current.rectUpdateTime = performance.now() - rectStart;
668
+ lastRectUpdate = currentTime;
669
+ }
670
+ const physicsStart = performance.now();
671
+ meltAndSmoothAccumulation(cachedElementRects, physicsConfigRef.current, dt);
672
+ updateSnowflakes(snowflakes, cachedElementRects, physicsConfigRef.current, dt, canvas.width, canvas.height);
673
+ metricsRef.current.physicsTime = performance.now() - physicsStart;
674
+ const drawStart = performance.now();
623
675
  for (const flake of snowflakes) {
624
676
  drawSnowflake(ctx, flake);
625
677
  }
@@ -627,8 +679,26 @@ function Snowfall() {
627
679
  const isBackground = Math.random() < 0.4;
628
680
  snowflakes.push(createSnowflake(canvas.width, physicsConfigRef.current, isBackground));
629
681
  }
630
- drawAccumulations(ctx, fixedCtx, elementRects);
631
- drawSideAccumulations(ctx, fixedCtx, elementRects);
682
+ drawAccumulations(ctx, ctx, cachedElementRects);
683
+ drawSideAccumulations(ctx, ctx, cachedElementRects);
684
+ metricsRef.current.drawTime = performance.now() - drawStart;
685
+ metricsRef.current.frameTime = performance.now() - frameStartTime;
686
+ if (currentTime - lastMetricsUpdate > 500) {
687
+ setMetricsRef.current({
688
+ fps: fpsFrames.current.length,
689
+ frameTime: metricsRef.current.frameTime,
690
+ scanTime: metricsRef.current.scanTime,
691
+ rectUpdateTime: metricsRef.current.rectUpdateTime,
692
+ surfaceCount: accumulationRef.current.size,
693
+ flakeCount: snowflakes.length,
694
+ maxFlakes: physicsConfigRef.current.MAX_FLAKES,
695
+ rafGap: metricsRef.current.rafGap,
696
+ clearTime: metricsRef.current.clearTime,
697
+ physicsTime: metricsRef.current.physicsTime,
698
+ drawTime: metricsRef.current.drawTime
699
+ });
700
+ lastMetricsUpdate = currentTime;
701
+ }
632
702
  animationIdRef.current = requestAnimationFrame(animate);
633
703
  };
634
704
  animationIdRef.current = requestAnimationFrame(animate);
@@ -638,7 +708,7 @@ function Snowfall() {
638
708
  initAccumulationWrapper();
639
709
  };
640
710
  window.addEventListener("resize", handleResize);
641
- const checkInterval = setInterval(initAccumulationWrapper, 3e3);
711
+ const checkInterval = setInterval(initAccumulationWrapper, 5e3);
642
712
  return () => {
643
713
  cancelAnimationFrame(animationIdRef.current);
644
714
  window.removeEventListener("resize", handleResize);
@@ -647,44 +717,202 @@ function Snowfall() {
647
717
  };
648
718
  }, [isMounted]);
649
719
  if (!isMounted) return null;
650
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
651
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
652
- "canvas",
653
- {
654
- ref: canvasRef,
655
- style: {
656
- position: "absolute",
657
- top: 0,
658
- left: 0,
659
- pointerEvents: "none",
660
- zIndex: 9999,
661
- opacity: isVisible ? 1 : 0,
662
- transition: "opacity 0.3s ease-in"
663
- },
664
- "aria-hidden": "true"
665
- }
666
- ),
667
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
668
- "canvas",
669
- {
670
- ref: fixedCanvasRef,
671
- style: {
672
- position: "fixed",
673
- top: 0,
674
- left: 0,
675
- pointerEvents: "none",
676
- zIndex: 9999,
677
- opacity: isVisible ? 1 : 0,
678
- transition: "opacity 0.3s ease-in"
679
- },
680
- "aria-hidden": "true"
720
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
721
+ "canvas",
722
+ {
723
+ ref: canvasRef,
724
+ style: {
725
+ position: "absolute",
726
+ top: 0,
727
+ left: 0,
728
+ pointerEvents: "none",
729
+ zIndex: 9999,
730
+ opacity: isVisible ? 1 : 0,
731
+ transition: "opacity 0.3s ease-in",
732
+ willChange: "transform"
733
+ },
734
+ "aria-hidden": "true"
735
+ }
736
+ ) });
737
+ }
738
+
739
+ // src/DebugPanel.tsx
740
+ var import_react3 = require("react");
741
+ var import_jsx_runtime3 = require("react/jsx-runtime");
742
+ function DebugPanel() {
743
+ const { debugMode, toggleDebug, metrics } = useSnowfall();
744
+ const [isMinimized, setIsMinimized] = (0, import_react3.useState)(false);
745
+ const [copied, setCopied] = (0, import_react3.useState)(false);
746
+ (0, import_react3.useEffect)(() => {
747
+ const handleKeyDown = (e) => {
748
+ if (e.shiftKey && e.key === "D") {
749
+ toggleDebug();
681
750
  }
682
- )
751
+ };
752
+ window.addEventListener("keydown", handleKeyDown);
753
+ return () => window.removeEventListener("keydown", handleKeyDown);
754
+ }, [toggleDebug]);
755
+ const copyToClipboard = () => {
756
+ if (metrics) {
757
+ const data = {
758
+ ...metrics,
759
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
760
+ userAgent: navigator.userAgent,
761
+ canvasSize: {
762
+ width: window.innerWidth,
763
+ height: window.innerHeight
764
+ }
765
+ };
766
+ navigator.clipboard.writeText(JSON.stringify(data, null, 2));
767
+ setCopied(true);
768
+ setTimeout(() => setCopied(false), 2e3);
769
+ }
770
+ };
771
+ if (!debugMode) return null;
772
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
773
+ position: "fixed",
774
+ bottom: "20px",
775
+ left: "20px",
776
+ backgroundColor: "rgba(0, 0, 0, 0.9)",
777
+ color: "#0f0",
778
+ fontFamily: "monospace",
779
+ fontSize: "12px",
780
+ padding: isMinimized ? "10px" : "15px",
781
+ borderRadius: "8px",
782
+ zIndex: 1e4,
783
+ minWidth: isMinimized ? "auto" : "320px",
784
+ maxWidth: "400px",
785
+ border: "1px solid #0f0",
786
+ boxShadow: "0 4px 20px rgba(0, 255, 0, 0.3)"
787
+ }, children: [
788
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
789
+ display: "flex",
790
+ justifyContent: "space-between",
791
+ alignItems: "center",
792
+ marginBottom: isMinimized ? 0 : "10px"
793
+ }, children: [
794
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontWeight: "bold", color: "#0ff" }, children: "\u2744\uFE0F SNOWFALL DEBUG" }),
795
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
796
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
797
+ "button",
798
+ {
799
+ onClick: () => setIsMinimized(!isMinimized),
800
+ style: {
801
+ background: "none",
802
+ border: "1px solid #0f0",
803
+ color: "#0f0",
804
+ cursor: "pointer",
805
+ padding: "2px 8px",
806
+ borderRadius: "4px",
807
+ fontSize: "10px"
808
+ },
809
+ children: isMinimized ? "\u25B2" : "\u25BC"
810
+ }
811
+ ),
812
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
813
+ "button",
814
+ {
815
+ onClick: toggleDebug,
816
+ style: {
817
+ background: "none",
818
+ border: "1px solid #f00",
819
+ color: "#f00",
820
+ cursor: "pointer",
821
+ padding: "2px 8px",
822
+ borderRadius: "4px",
823
+ fontSize: "10px"
824
+ },
825
+ children: "\u2715"
826
+ }
827
+ )
828
+ ] })
829
+ ] }),
830
+ !isMinimized && metrics && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
831
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "10px", paddingBottom: "10px", borderBottom: "1px solid #333" }, children: [
832
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#ff0", marginBottom: "5px", fontWeight: "bold" }, children: "\u26A1 PERFORMANCE" }),
833
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
834
+ "FPS: ",
835
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: metrics.fps < 30 ? "#f00" : metrics.fps < 50 ? "#ff0" : "#0f0" }, children: metrics.fps.toFixed(1) })
836
+ ] }),
837
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
838
+ "Frame Time: ",
839
+ metrics.frameTime.toFixed(2),
840
+ "ms"
841
+ ] }),
842
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { color: "#f80" }, children: [
843
+ "rAF Gap: ",
844
+ metrics.rafGap?.toFixed(1) || 0,
845
+ "ms ",
846
+ metrics.rafGap && metrics.rafGap > 20 ? "\u26A0\uFE0F THROTTLED!" : ""
847
+ ] })
848
+ ] }),
849
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "10px", paddingBottom: "10px", borderBottom: "1px solid #333" }, children: [
850
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#ff0", marginBottom: "5px", fontWeight: "bold" }, children: "\u{1F52C} DETAILED TIMINGS" }),
851
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
852
+ "Clear: ",
853
+ metrics.clearTime?.toFixed(2) || 0,
854
+ "ms"
855
+ ] }),
856
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
857
+ "Physics: ",
858
+ metrics.physicsTime?.toFixed(2) || 0,
859
+ "ms"
860
+ ] }),
861
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
862
+ "Draw: ",
863
+ metrics.drawTime?.toFixed(2) || 0,
864
+ "ms"
865
+ ] }),
866
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
867
+ "Scan: ",
868
+ metrics.scanTime.toFixed(2),
869
+ "ms"
870
+ ] }),
871
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
872
+ "Rect Update: ",
873
+ metrics.rectUpdateTime.toFixed(2),
874
+ "ms"
875
+ ] })
876
+ ] }),
877
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "10px", paddingBottom: "10px", borderBottom: "1px solid #333" }, children: [
878
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#ff0", marginBottom: "5px", fontWeight: "bold" }, children: "\u{1F4CA} COUNTS" }),
879
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
880
+ "Snowflakes: ",
881
+ metrics.flakeCount,
882
+ " / ",
883
+ metrics.maxFlakes
884
+ ] }),
885
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
886
+ "Surfaces: ",
887
+ metrics.surfaceCount
888
+ ] })
889
+ ] }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
891
+ "button",
892
+ {
893
+ onClick: copyToClipboard,
894
+ style: {
895
+ width: "100%",
896
+ padding: "8px",
897
+ background: copied ? "#0a0" : "#000",
898
+ border: copied ? "1px solid #0f0" : "1px solid #555",
899
+ color: "#0f0",
900
+ cursor: "pointer",
901
+ borderRadius: "4px",
902
+ fontSize: "11px",
903
+ fontFamily: "monospace"
904
+ },
905
+ children: copied ? "\u2713 COPIED!" : "\u{1F4CB} COPY METRICS"
906
+ }
907
+ ),
908
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginTop: "8px", fontSize: "10px", color: "#666", textAlign: "center" }, children: "Shift+D to toggle" })
909
+ ] })
683
910
  ] });
684
911
  }
685
912
  // Annotate the CommonJS export names for ESM import in node:
686
913
  0 && (module.exports = {
687
914
  DEFAULT_PHYSICS,
915
+ DebugPanel,
688
916
  Snowfall,
689
917
  SnowfallProvider,
690
918
  useSnowfall