@hdcodedev/snowfall 1.0.4 → 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/README.md +7 -6
- package/dist/index.d.mts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +280 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +280 -55
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# @hdcodedev/snowfall
|
|
1
|
+
# [@hdcodedev/snowfall](https://next-snowfall.vercel.app)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@hdcodedev/snowfall)
|
|
4
|
+
|
|
2
5
|
|
|
3
6
|
A realistic snowfall effect for React with physics-based accumulation on surfaces. Features melting, wind, and smart surface detection.
|
|
4
7
|
|
|
5
|
-

|
|
9
|
+
|
|
10
|
+
|
|
6
11
|
|
|
7
12
|
## Features
|
|
8
13
|
|
|
@@ -135,10 +140,6 @@ Hook to access snowfall controls. Must be used within `SnowfallProvider`.
|
|
|
135
140
|
}
|
|
136
141
|
```
|
|
137
142
|
|
|
138
|
-
## Demo
|
|
139
|
-
|
|
140
|
-
Check out the [live demo](https://snowfall-2026.vercel.app)
|
|
141
|
-
|
|
142
143
|
## Tips
|
|
143
144
|
|
|
144
145
|
- The snowfall canvas has `pointer-events: none`, so it won't interfere with user interactions
|
package/dist/index.d.mts
CHANGED
|
@@ -23,16 +23,35 @@ interface PhysicsConfig {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
declare const DEFAULT_PHYSICS: PhysicsConfig;
|
|
26
|
+
interface PerformanceMetrics {
|
|
27
|
+
fps: number;
|
|
28
|
+
frameTime: number;
|
|
29
|
+
scanTime: number;
|
|
30
|
+
rectUpdateTime: number;
|
|
31
|
+
surfaceCount: number;
|
|
32
|
+
flakeCount: number;
|
|
33
|
+
maxFlakes: number;
|
|
34
|
+
rafGap: number;
|
|
35
|
+
clearTime: number;
|
|
36
|
+
physicsTime: number;
|
|
37
|
+
drawTime: number;
|
|
38
|
+
}
|
|
26
39
|
interface SnowfallContextType {
|
|
27
40
|
isEnabled: boolean;
|
|
28
41
|
toggleSnow: () => void;
|
|
29
42
|
physicsConfig: PhysicsConfig;
|
|
30
43
|
updatePhysicsConfig: (config: Partial<PhysicsConfig>) => void;
|
|
31
44
|
resetPhysics: () => void;
|
|
45
|
+
debugMode: boolean;
|
|
46
|
+
toggleDebug: () => void;
|
|
47
|
+
metrics: PerformanceMetrics | null;
|
|
48
|
+
setMetrics: (metrics: PerformanceMetrics) => void;
|
|
32
49
|
}
|
|
33
50
|
declare function SnowfallProvider({ children }: {
|
|
34
51
|
children: ReactNode;
|
|
35
52
|
}): react_jsx_runtime.JSX.Element;
|
|
36
53
|
declare function useSnowfall(): SnowfallContextType;
|
|
37
54
|
|
|
38
|
-
|
|
55
|
+
declare function DebugPanel(): react_jsx_runtime.JSX.Element | null;
|
|
56
|
+
|
|
57
|
+
export { DEFAULT_PHYSICS, DebugPanel, type PerformanceMetrics, type PhysicsConfig, Snowfall, SnowfallProvider, useSnowfall };
|
package/dist/index.d.ts
CHANGED
|
@@ -23,16 +23,35 @@ interface PhysicsConfig {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
declare const DEFAULT_PHYSICS: PhysicsConfig;
|
|
26
|
+
interface PerformanceMetrics {
|
|
27
|
+
fps: number;
|
|
28
|
+
frameTime: number;
|
|
29
|
+
scanTime: number;
|
|
30
|
+
rectUpdateTime: number;
|
|
31
|
+
surfaceCount: number;
|
|
32
|
+
flakeCount: number;
|
|
33
|
+
maxFlakes: number;
|
|
34
|
+
rafGap: number;
|
|
35
|
+
clearTime: number;
|
|
36
|
+
physicsTime: number;
|
|
37
|
+
drawTime: number;
|
|
38
|
+
}
|
|
26
39
|
interface SnowfallContextType {
|
|
27
40
|
isEnabled: boolean;
|
|
28
41
|
toggleSnow: () => void;
|
|
29
42
|
physicsConfig: PhysicsConfig;
|
|
30
43
|
updatePhysicsConfig: (config: Partial<PhysicsConfig>) => void;
|
|
31
44
|
resetPhysics: () => void;
|
|
45
|
+
debugMode: boolean;
|
|
46
|
+
toggleDebug: () => void;
|
|
47
|
+
metrics: PerformanceMetrics | null;
|
|
48
|
+
setMetrics: (metrics: PerformanceMetrics) => void;
|
|
32
49
|
}
|
|
33
50
|
declare function SnowfallProvider({ children }: {
|
|
34
51
|
children: ReactNode;
|
|
35
52
|
}): react_jsx_runtime.JSX.Element;
|
|
36
53
|
declare function useSnowfall(): SnowfallContextType;
|
|
37
54
|
|
|
38
|
-
|
|
55
|
+
declare function DebugPanel(): react_jsx_runtime.JSX.Element | null;
|
|
56
|
+
|
|
57
|
+
export { DEFAULT_PHYSICS, DebugPanel, type PerformanceMetrics, type PhysicsConfig, Snowfall, SnowfallProvider, useSnowfall };
|
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() {
|
|
@@ -118,6 +128,18 @@ var AUTO_DETECT_CLASSES = [
|
|
|
118
128
|
'[class*="shadow-"]',
|
|
119
129
|
'[class*="rounded-"]'
|
|
120
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
|
+
};
|
|
121
143
|
var getElementType = (el) => {
|
|
122
144
|
const tagName = el.tagName.toLowerCase();
|
|
123
145
|
if (BOTTOM_TAGS.includes(tagName)) return VAL_BOTTOM;
|
|
@@ -140,6 +162,7 @@ var shouldAccumulate = (el) => {
|
|
|
140
162
|
return hasBackground || hasBorder || hasBoxShadow || hasBorderRadius;
|
|
141
163
|
};
|
|
142
164
|
var getAccumulationSurfaces = () => {
|
|
165
|
+
clearStyleCache();
|
|
143
166
|
const surfaces = [];
|
|
144
167
|
const seen = /* @__PURE__ */ new Set();
|
|
145
168
|
const candidates = document.querySelectorAll(
|
|
@@ -152,13 +175,13 @@ var getAccumulationSurfaces = () => {
|
|
|
152
175
|
);
|
|
153
176
|
candidates.forEach((el) => {
|
|
154
177
|
if (seen.has(el)) return;
|
|
155
|
-
const rect = el.getBoundingClientRect();
|
|
156
178
|
const manualOverride = el.getAttribute(ATTR_SNOWFALL);
|
|
157
179
|
if (manualOverride === VAL_IGNORE) return;
|
|
158
180
|
const isManuallyIncluded = manualOverride !== null;
|
|
159
|
-
const styles =
|
|
181
|
+
const styles = getCachedStyle(el);
|
|
160
182
|
const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
|
|
161
183
|
if (!isVisible && !isManuallyIncluded) return;
|
|
184
|
+
const rect = el.getBoundingClientRect();
|
|
162
185
|
const hasSize = rect.width >= 100 && rect.height >= 50;
|
|
163
186
|
if (!hasSize && !isManuallyIncluded) return;
|
|
164
187
|
const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;
|
|
@@ -169,7 +192,7 @@ var getAccumulationSurfaces = () => {
|
|
|
169
192
|
let isFixed = false;
|
|
170
193
|
let currentEl = el;
|
|
171
194
|
while (currentEl && currentEl !== document.body) {
|
|
172
|
-
const style =
|
|
195
|
+
const style = getCachedStyle(currentEl);
|
|
173
196
|
if (style.position === "fixed" || style.position === "sticky") {
|
|
174
197
|
isFixed = true;
|
|
175
198
|
break;
|
|
@@ -188,6 +211,7 @@ var getAccumulationSurfaces = () => {
|
|
|
188
211
|
}
|
|
189
212
|
});
|
|
190
213
|
console.log(`[Snowfall] Auto-detection found ${surfaces.length} surfaces`);
|
|
214
|
+
console.log("[Snowfall] \u2705 Using OPTIMIZED version with Map-based caching & 5s intervals");
|
|
191
215
|
return surfaces;
|
|
192
216
|
};
|
|
193
217
|
var getElementRects = (accumulationMap) => {
|
|
@@ -554,16 +578,26 @@ var drawSideAccumulations = (ctx, fixedCtx, elementRects) => {
|
|
|
554
578
|
// src/Snowfall.tsx
|
|
555
579
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
556
580
|
function Snowfall() {
|
|
557
|
-
const { isEnabled, physicsConfig } = useSnowfall();
|
|
581
|
+
const { isEnabled, physicsConfig, setMetrics } = useSnowfall();
|
|
558
582
|
const isEnabledRef = (0, import_react2.useRef)(isEnabled);
|
|
559
583
|
const physicsConfigRef = (0, import_react2.useRef)(physicsConfig);
|
|
584
|
+
const setMetricsRef = (0, import_react2.useRef)(setMetrics);
|
|
560
585
|
const [isMounted, setIsMounted] = (0, import_react2.useState)(false);
|
|
561
586
|
const [isVisible, setIsVisible] = (0, import_react2.useState)(false);
|
|
562
587
|
const canvasRef = (0, import_react2.useRef)(null);
|
|
563
|
-
const fixedCanvasRef = (0, import_react2.useRef)(null);
|
|
564
588
|
const snowflakesRef = (0, import_react2.useRef)([]);
|
|
565
589
|
const accumulationRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
566
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
|
+
});
|
|
567
601
|
(0, import_react2.useEffect)(() => {
|
|
568
602
|
setIsMounted(true);
|
|
569
603
|
}, []);
|
|
@@ -573,26 +607,23 @@ function Snowfall() {
|
|
|
573
607
|
(0, import_react2.useEffect)(() => {
|
|
574
608
|
physicsConfigRef.current = physicsConfig;
|
|
575
609
|
}, [physicsConfig]);
|
|
610
|
+
(0, import_react2.useEffect)(() => {
|
|
611
|
+
setMetricsRef.current = setMetrics;
|
|
612
|
+
}, [setMetrics]);
|
|
576
613
|
(0, import_react2.useEffect)(() => {
|
|
577
614
|
if (!isMounted) return;
|
|
578
615
|
const canvas = canvasRef.current;
|
|
579
|
-
|
|
580
|
-
if (!canvas || !fixedCanvas) return;
|
|
616
|
+
if (!canvas) return;
|
|
581
617
|
const ctx = canvas.getContext("2d");
|
|
582
|
-
|
|
583
|
-
if (!ctx || !fixedCtx) return;
|
|
618
|
+
if (!ctx) return;
|
|
584
619
|
const resizeCanvas = () => {
|
|
585
|
-
if (canvasRef.current
|
|
620
|
+
if (canvasRef.current) {
|
|
586
621
|
const newHeight = Math.max(document.documentElement.scrollHeight, window.innerHeight);
|
|
587
622
|
const newWidth = Math.max(document.documentElement.scrollWidth, window.innerWidth);
|
|
588
623
|
if (canvasRef.current.height !== newHeight || canvasRef.current.width !== newWidth) {
|
|
589
624
|
canvasRef.current.width = newWidth;
|
|
590
625
|
canvasRef.current.height = newHeight;
|
|
591
626
|
}
|
|
592
|
-
if (fixedCanvasRef.current.width !== window.innerWidth || fixedCanvasRef.current.height !== window.innerHeight) {
|
|
593
|
-
fixedCanvasRef.current.width = window.innerWidth;
|
|
594
|
-
fixedCanvasRef.current.height = window.innerHeight;
|
|
595
|
-
}
|
|
596
627
|
}
|
|
597
628
|
};
|
|
598
629
|
resizeCanvas();
|
|
@@ -602,11 +633,16 @@ function Snowfall() {
|
|
|
602
633
|
resizeObserver.observe(document.body);
|
|
603
634
|
snowflakesRef.current = [];
|
|
604
635
|
const initAccumulationWrapper = () => {
|
|
636
|
+
const scanStart = performance.now();
|
|
605
637
|
initializeAccumulation(accumulationRef.current, physicsConfigRef.current);
|
|
638
|
+
metricsRef.current.scanTime = performance.now() - scanStart;
|
|
606
639
|
};
|
|
607
640
|
initAccumulationWrapper();
|
|
608
641
|
setIsVisible(true);
|
|
609
642
|
let lastTime = 0;
|
|
643
|
+
let lastRectUpdate = 0;
|
|
644
|
+
let lastMetricsUpdate = 0;
|
|
645
|
+
let cachedElementRects = [];
|
|
610
646
|
const animate = (currentTime) => {
|
|
611
647
|
if (lastTime === 0) {
|
|
612
648
|
lastTime = currentTime;
|
|
@@ -614,14 +650,28 @@ function Snowfall() {
|
|
|
614
650
|
return;
|
|
615
651
|
}
|
|
616
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;
|
|
617
657
|
lastTime = currentTime;
|
|
618
658
|
const dt = deltaTime / 16.67;
|
|
659
|
+
const frameStartTime = performance.now();
|
|
660
|
+
const clearStart = performance.now();
|
|
619
661
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
620
|
-
|
|
662
|
+
metricsRef.current.clearTime = performance.now() - clearStart;
|
|
621
663
|
const snowflakes = snowflakesRef.current;
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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();
|
|
625
675
|
for (const flake of snowflakes) {
|
|
626
676
|
drawSnowflake(ctx, flake);
|
|
627
677
|
}
|
|
@@ -629,8 +679,26 @@ function Snowfall() {
|
|
|
629
679
|
const isBackground = Math.random() < 0.4;
|
|
630
680
|
snowflakes.push(createSnowflake(canvas.width, physicsConfigRef.current, isBackground));
|
|
631
681
|
}
|
|
632
|
-
drawAccumulations(ctx,
|
|
633
|
-
drawSideAccumulations(ctx,
|
|
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
|
+
}
|
|
634
702
|
animationIdRef.current = requestAnimationFrame(animate);
|
|
635
703
|
};
|
|
636
704
|
animationIdRef.current = requestAnimationFrame(animate);
|
|
@@ -640,7 +708,7 @@ function Snowfall() {
|
|
|
640
708
|
initAccumulationWrapper();
|
|
641
709
|
};
|
|
642
710
|
window.addEventListener("resize", handleResize);
|
|
643
|
-
const checkInterval = setInterval(initAccumulationWrapper,
|
|
711
|
+
const checkInterval = setInterval(initAccumulationWrapper, 5e3);
|
|
644
712
|
return () => {
|
|
645
713
|
cancelAnimationFrame(animationIdRef.current);
|
|
646
714
|
window.removeEventListener("resize", handleResize);
|
|
@@ -649,44 +717,202 @@ function Snowfall() {
|
|
|
649
717
|
};
|
|
650
718
|
}, [isMounted]);
|
|
651
719
|
if (!isMounted) return null;
|
|
652
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
"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();
|
|
683
750
|
}
|
|
684
|
-
|
|
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
|
+
] })
|
|
685
910
|
] });
|
|
686
911
|
}
|
|
687
912
|
// Annotate the CommonJS export names for ESM import in node:
|
|
688
913
|
0 && (module.exports = {
|
|
689
914
|
DEFAULT_PHYSICS,
|
|
915
|
+
DebugPanel,
|
|
690
916
|
Snowfall,
|
|
691
917
|
SnowfallProvider,
|
|
692
918
|
useSnowfall
|