@hdcodedev/snowfall 1.0.14 → 1.0.15
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 +24 -27
- package/dist/index.d.mts +1 -5
- package/dist/index.d.ts +1 -5
- package/dist/index.js +1 -1230
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1200
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,1201 +1,2 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
// src/components/Snowfall.tsx
|
|
4
|
-
import { useEffect as useEffect2, useRef as useRef4, useState as useState2 } from "react";
|
|
5
|
-
|
|
6
|
-
// src/components/SnowfallProvider.tsx
|
|
7
|
-
import { createContext, useContext, useState } from "react";
|
|
8
|
-
import { jsx } from "react/jsx-runtime";
|
|
9
|
-
var DEFAULT_PHYSICS = {
|
|
10
|
-
MAX_FLAKES: 1e3,
|
|
11
|
-
MELT_SPEED: 1e-5,
|
|
12
|
-
WIND_STRENGTH: 1.5,
|
|
13
|
-
ACCUMULATION: {
|
|
14
|
-
SIDE_RATE: 1,
|
|
15
|
-
TOP_RATE: 5,
|
|
16
|
-
BOTTOM_RATE: 5
|
|
17
|
-
},
|
|
18
|
-
MAX_DEPTH: {
|
|
19
|
-
TOP: 100,
|
|
20
|
-
BOTTOM: 50,
|
|
21
|
-
SIDE: 20
|
|
22
|
-
},
|
|
23
|
-
FLAKE_SIZE: {
|
|
24
|
-
MIN: 0.5,
|
|
25
|
-
MAX: 1.6
|
|
26
|
-
},
|
|
27
|
-
MAX_SURFACES: 15,
|
|
28
|
-
COLLISION_CHECK_RATE: 0.3,
|
|
29
|
-
// 30% of snowflakes check collisions per frame
|
|
30
|
-
MAX_RENDER_DPR: 1.25
|
|
31
|
-
};
|
|
32
|
-
var SnowfallContext = createContext(void 0);
|
|
33
|
-
function SnowfallProvider({ children, initialDebug = false, initialEnabled = true }) {
|
|
34
|
-
const [isEnabled, setIsEnabled] = useState(initialEnabled);
|
|
35
|
-
const [physicsConfig, setPhysicsConfig] = useState(DEFAULT_PHYSICS);
|
|
36
|
-
const [debugMode, setDebugMode] = useState(initialDebug);
|
|
37
|
-
const [metrics, setMetrics] = useState(null);
|
|
38
|
-
const toggleSnow = () => {
|
|
39
|
-
setIsEnabled((prev) => !prev);
|
|
40
|
-
};
|
|
41
|
-
const toggleDebug = () => {
|
|
42
|
-
setDebugMode((prev) => !prev);
|
|
43
|
-
};
|
|
44
|
-
const updatePhysicsConfig = (config) => {
|
|
45
|
-
setPhysicsConfig((prev) => ({
|
|
46
|
-
...prev,
|
|
47
|
-
...config,
|
|
48
|
-
ACCUMULATION: {
|
|
49
|
-
...prev.ACCUMULATION,
|
|
50
|
-
...config.ACCUMULATION || {}
|
|
51
|
-
},
|
|
52
|
-
MAX_DEPTH: {
|
|
53
|
-
...prev.MAX_DEPTH,
|
|
54
|
-
...config.MAX_DEPTH || {}
|
|
55
|
-
},
|
|
56
|
-
FLAKE_SIZE: {
|
|
57
|
-
...prev.FLAKE_SIZE,
|
|
58
|
-
...config.FLAKE_SIZE || {}
|
|
59
|
-
}
|
|
60
|
-
}));
|
|
61
|
-
};
|
|
62
|
-
const resetPhysics = () => {
|
|
63
|
-
setPhysicsConfig(DEFAULT_PHYSICS);
|
|
64
|
-
};
|
|
65
|
-
return /* @__PURE__ */ jsx(SnowfallContext.Provider, { value: {
|
|
66
|
-
isEnabled,
|
|
67
|
-
toggleSnow,
|
|
68
|
-
physicsConfig,
|
|
69
|
-
updatePhysicsConfig,
|
|
70
|
-
resetPhysics,
|
|
71
|
-
debugMode,
|
|
72
|
-
toggleDebug,
|
|
73
|
-
metrics,
|
|
74
|
-
setMetrics
|
|
75
|
-
}, children });
|
|
76
|
-
}
|
|
77
|
-
function useSnowfall() {
|
|
78
|
-
const context = useContext(SnowfallContext);
|
|
79
|
-
if (context === void 0) {
|
|
80
|
-
throw new Error("useSnowfall must be used within a SnowfallProvider");
|
|
81
|
-
}
|
|
82
|
-
return context;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// src/core/constants.ts
|
|
86
|
-
var ATTR_SNOWFALL = "data-snowfall";
|
|
87
|
-
var VAL_IGNORE = "ignore";
|
|
88
|
-
var VAL_TOP = "top";
|
|
89
|
-
var VAL_BOTTOM = "bottom";
|
|
90
|
-
var TAG_HEADER = "header";
|
|
91
|
-
var TAG_FOOTER = "footer";
|
|
92
|
-
var ROLE_BANNER = "banner";
|
|
93
|
-
var ROLE_CONTENTINFO = "contentinfo";
|
|
94
|
-
var TAU = Math.PI * 2;
|
|
95
|
-
|
|
96
|
-
// src/core/dom.ts
|
|
97
|
-
var BOTTOM_TAGS = [TAG_HEADER];
|
|
98
|
-
var BOTTOM_ROLES = [ROLE_BANNER];
|
|
99
|
-
var AUTO_DETECT_TAGS = [TAG_HEADER, TAG_FOOTER, "article", "section", "aside", "nav"];
|
|
100
|
-
var AUTO_DETECT_ROLES = [`[role="${ROLE_BANNER}"]`, `[role="${ROLE_CONTENTINFO}"]`, '[role="main"]'];
|
|
101
|
-
var AUTO_DETECT_CLASSES = [
|
|
102
|
-
".card",
|
|
103
|
-
'[class*="card"]',
|
|
104
|
-
'[class*="Card"]',
|
|
105
|
-
'[class*="bg-"]',
|
|
106
|
-
'[class*="shadow-"]',
|
|
107
|
-
'[class*="rounded-"]'
|
|
108
|
-
];
|
|
109
|
-
var getElementType = (el) => {
|
|
110
|
-
const tagName = el.tagName.toLowerCase();
|
|
111
|
-
if (BOTTOM_TAGS.includes(tagName)) return VAL_BOTTOM;
|
|
112
|
-
const role = el.getAttribute("role");
|
|
113
|
-
if (role && BOTTOM_ROLES.includes(role)) return VAL_BOTTOM;
|
|
114
|
-
return VAL_TOP;
|
|
115
|
-
};
|
|
116
|
-
var shouldAccumulate = (el, precomputedStyle) => {
|
|
117
|
-
if (el.getAttribute(ATTR_SNOWFALL) === VAL_IGNORE) return false;
|
|
118
|
-
if (el.hasAttribute(ATTR_SNOWFALL)) return true;
|
|
119
|
-
const styles = precomputedStyle || window.getComputedStyle(el);
|
|
120
|
-
const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
|
|
121
|
-
if (!isVisible) return false;
|
|
122
|
-
const bgColor = styles.backgroundColor;
|
|
123
|
-
const hasBackground = bgColor !== "rgba(0, 0, 0, 0)" && bgColor !== "transparent";
|
|
124
|
-
const hasBorder = parseFloat(styles.borderWidth) > 0 && styles.borderColor !== "transparent" && styles.borderColor !== "rgba(0, 0, 0, 0)" && styles.borderStyle !== "none";
|
|
125
|
-
const hasBoxShadow = styles.boxShadow !== "none";
|
|
126
|
-
const hasFilter = styles.filter !== "none" && styles.filter.includes("drop-shadow");
|
|
127
|
-
const hasBackdropFilter = styles.backdropFilter !== "none";
|
|
128
|
-
return hasBackground || hasBorder || hasBoxShadow || hasFilter || hasBackdropFilter;
|
|
129
|
-
};
|
|
130
|
-
var getAccumulationSurfaces = (maxSurfaces = 5) => {
|
|
131
|
-
const surfaces = [];
|
|
132
|
-
const seen = /* @__PURE__ */ new Set();
|
|
133
|
-
const candidates = document.querySelectorAll(
|
|
134
|
-
[
|
|
135
|
-
`[${ATTR_SNOWFALL}]`,
|
|
136
|
-
...AUTO_DETECT_TAGS,
|
|
137
|
-
...AUTO_DETECT_ROLES,
|
|
138
|
-
...AUTO_DETECT_CLASSES
|
|
139
|
-
].join(", ")
|
|
140
|
-
);
|
|
141
|
-
for (const el of candidates) {
|
|
142
|
-
if (surfaces.length >= maxSurfaces) break;
|
|
143
|
-
if (seen.has(el)) continue;
|
|
144
|
-
const manualOverride = el.getAttribute(ATTR_SNOWFALL);
|
|
145
|
-
if (manualOverride === VAL_IGNORE) continue;
|
|
146
|
-
const isManuallyIncluded = manualOverride !== null;
|
|
147
|
-
const styles = window.getComputedStyle(el);
|
|
148
|
-
const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
|
|
149
|
-
if (!isVisible && !isManuallyIncluded) continue;
|
|
150
|
-
const rect = el.getBoundingClientRect();
|
|
151
|
-
const hasSize = rect.width >= 100 && rect.height >= 50;
|
|
152
|
-
if (!hasSize && !isManuallyIncluded) continue;
|
|
153
|
-
const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;
|
|
154
|
-
const isBottomTag = BOTTOM_TAGS.includes(el.tagName.toLowerCase());
|
|
155
|
-
const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute("role") || "");
|
|
156
|
-
const isBottomSurface = isBottomTag || isBottomRole || manualOverride === VAL_BOTTOM;
|
|
157
|
-
if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) continue;
|
|
158
|
-
if (shouldAccumulate(el, styles)) {
|
|
159
|
-
let type = getElementType(el);
|
|
160
|
-
if (manualOverride === VAL_BOTTOM) type = VAL_BOTTOM;
|
|
161
|
-
else if (manualOverride === VAL_TOP) type = VAL_TOP;
|
|
162
|
-
surfaces.push({ el, type });
|
|
163
|
-
seen.add(el);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return surfaces;
|
|
167
|
-
};
|
|
168
|
-
var getElementRects = (accumulationMap) => {
|
|
169
|
-
const elementRects = [];
|
|
170
|
-
for (const [el, acc] of accumulationMap.entries()) {
|
|
171
|
-
if (!el.isConnected) continue;
|
|
172
|
-
const rect = el.getBoundingClientRect();
|
|
173
|
-
elementRects.push({ el, rect, acc });
|
|
174
|
-
}
|
|
175
|
-
return elementRects;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// src/core/physics.ts
|
|
179
|
-
var OPACITY_BUCKETS = [0.3, 0.5, 0.7, 0.9];
|
|
180
|
-
var quantizeOpacity = (opacity) => {
|
|
181
|
-
return OPACITY_BUCKETS.reduce(
|
|
182
|
-
(prev, curr) => Math.abs(curr - opacity) < Math.abs(prev - opacity) ? curr : prev
|
|
183
|
-
);
|
|
184
|
-
};
|
|
185
|
-
var createSnowflake = (worldWidth, config, isBackground = false) => {
|
|
186
|
-
const x = Math.random() * worldWidth;
|
|
187
|
-
const dna = Math.random();
|
|
188
|
-
const noise = {
|
|
189
|
-
speed: dna * 13 % 1,
|
|
190
|
-
wind: dna * 7 % 1,
|
|
191
|
-
wobblePhase: dna * 23 % 1,
|
|
192
|
-
wobbleSpeed: dna * 5 % 1
|
|
193
|
-
};
|
|
194
|
-
const { MIN, MAX } = config.FLAKE_SIZE;
|
|
195
|
-
const sizeRatio = dna;
|
|
196
|
-
const profile = isBackground ? {
|
|
197
|
-
sizeMin: MIN * 0.6,
|
|
198
|
-
sizeRange: (MAX - MIN) * 0.4,
|
|
199
|
-
speedBase: 0.2,
|
|
200
|
-
speedScale: 0.3,
|
|
201
|
-
noiseSpeedScale: 0.2,
|
|
202
|
-
windScale: config.WIND_STRENGTH * 0.625,
|
|
203
|
-
opacityBase: 0.2,
|
|
204
|
-
opacityScale: 0.2,
|
|
205
|
-
wobbleBase: 5e-3,
|
|
206
|
-
wobbleScale: 0.015
|
|
207
|
-
} : {
|
|
208
|
-
sizeMin: MIN,
|
|
209
|
-
sizeRange: MAX - MIN,
|
|
210
|
-
speedBase: 0.5,
|
|
211
|
-
speedScale: 0.5,
|
|
212
|
-
noiseSpeedScale: 0.3,
|
|
213
|
-
windScale: config.WIND_STRENGTH,
|
|
214
|
-
opacityBase: 0.5,
|
|
215
|
-
opacityScale: 0.3,
|
|
216
|
-
wobbleBase: 0.01,
|
|
217
|
-
wobbleScale: 0.02
|
|
218
|
-
};
|
|
219
|
-
const radius = profile.sizeMin + sizeRatio * profile.sizeRange;
|
|
220
|
-
const glowRadius = radius * 1.5;
|
|
221
|
-
const rawOpacity = profile.opacityBase + sizeRatio * profile.opacityScale;
|
|
222
|
-
const opacity = quantizeOpacity(rawOpacity);
|
|
223
|
-
const rawGlowOpacity = opacity * 0.2;
|
|
224
|
-
const glowOpacity = quantizeOpacity(rawGlowOpacity);
|
|
225
|
-
const initialWobble = noise.wobblePhase * TAU;
|
|
226
|
-
return {
|
|
227
|
-
x,
|
|
228
|
-
y: window.scrollY - 5,
|
|
229
|
-
radius,
|
|
230
|
-
glowRadius,
|
|
231
|
-
speed: radius * profile.speedScale + noise.speed * profile.noiseSpeedScale + profile.speedBase,
|
|
232
|
-
wind: (noise.wind - 0.5) * profile.windScale,
|
|
233
|
-
opacity,
|
|
234
|
-
glowOpacity,
|
|
235
|
-
wobble: initialWobble,
|
|
236
|
-
wobbleSpeed: noise.wobbleSpeed * profile.wobbleScale + profile.wobbleBase,
|
|
237
|
-
sizeRatio,
|
|
238
|
-
isBackground
|
|
239
|
-
};
|
|
240
|
-
};
|
|
241
|
-
var initializeMaxHeights = (width, baseMax, borderRadius, isBottom = false) => {
|
|
242
|
-
let maxHeights = new Array(width);
|
|
243
|
-
for (let i = 0; i < width; i++) {
|
|
244
|
-
let edgeFactor = 1;
|
|
245
|
-
if (!isBottom && borderRadius > 0) {
|
|
246
|
-
if (i < borderRadius) {
|
|
247
|
-
edgeFactor = Math.pow(i / borderRadius, 1.2);
|
|
248
|
-
} else if (i > width - borderRadius) {
|
|
249
|
-
edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
|
|
253
|
-
}
|
|
254
|
-
const smoothPasses = 4;
|
|
255
|
-
for (let p = 0; p < smoothPasses; p++) {
|
|
256
|
-
const smoothed = [...maxHeights];
|
|
257
|
-
for (let i = 1; i < width - 1; i++) {
|
|
258
|
-
smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
|
|
259
|
-
}
|
|
260
|
-
maxHeights = smoothed;
|
|
261
|
-
}
|
|
262
|
-
return maxHeights;
|
|
263
|
-
};
|
|
264
|
-
var calculateCurveOffsets = (width, borderRadius, isBottom) => {
|
|
265
|
-
const offsets = new Array(width).fill(0);
|
|
266
|
-
if (borderRadius <= 0 || isBottom) return offsets;
|
|
267
|
-
for (let x = 0; x < width; x++) {
|
|
268
|
-
let offset = 0;
|
|
269
|
-
if (x < borderRadius) {
|
|
270
|
-
const dist = borderRadius - x;
|
|
271
|
-
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
272
|
-
} else if (x > width - borderRadius) {
|
|
273
|
-
const dist = x - (width - borderRadius);
|
|
274
|
-
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
275
|
-
}
|
|
276
|
-
offsets[x] = offset;
|
|
277
|
-
}
|
|
278
|
-
return offsets;
|
|
279
|
-
};
|
|
280
|
-
var calculateGravityMultipliers = (height) => {
|
|
281
|
-
const multipliers = new Array(height);
|
|
282
|
-
for (let i = 0; i < height; i++) {
|
|
283
|
-
const ratio = i / height;
|
|
284
|
-
multipliers[i] = Math.sqrt(ratio);
|
|
285
|
-
}
|
|
286
|
-
return multipliers;
|
|
287
|
-
};
|
|
288
|
-
var initializeAccumulation = (accumulationMap, config) => {
|
|
289
|
-
const elements = getAccumulationSurfaces(config.MAX_SURFACES);
|
|
290
|
-
for (const [el] of accumulationMap.entries()) {
|
|
291
|
-
if (!el.isConnected) {
|
|
292
|
-
accumulationMap.delete(el);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
elements.forEach(({ el, type }) => {
|
|
296
|
-
const existing = accumulationMap.get(el);
|
|
297
|
-
const rect = el.getBoundingClientRect();
|
|
298
|
-
const width = Math.ceil(rect.width);
|
|
299
|
-
const isBottom = type === VAL_BOTTOM;
|
|
300
|
-
if (existing && existing.heights.length === width) {
|
|
301
|
-
existing.type = type;
|
|
302
|
-
if (existing.borderRadius !== void 0) {
|
|
303
|
-
const styleBuffer = window.getComputedStyle(el);
|
|
304
|
-
existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;
|
|
305
|
-
existing.curveOffsets = calculateCurveOffsets(width, existing.borderRadius, isBottom);
|
|
306
|
-
if (existing.leftSide.length === Math.ceil(rect.height) && !existing.sideGravityMultipliers) {
|
|
307
|
-
existing.sideGravityMultipliers = calculateGravityMultipliers(existing.leftSide.length);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
const height = Math.ceil(rect.height);
|
|
313
|
-
const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;
|
|
314
|
-
const styles = window.getComputedStyle(el);
|
|
315
|
-
const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;
|
|
316
|
-
const maxHeights = initializeMaxHeights(width, baseMax, borderRadius, isBottom);
|
|
317
|
-
accumulationMap.set(el, {
|
|
318
|
-
heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),
|
|
319
|
-
maxHeights,
|
|
320
|
-
leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),
|
|
321
|
-
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
322
|
-
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
323
|
-
leftMax: existing?.leftSide.length === height ? existing.leftMax : 0,
|
|
324
|
-
rightMax: existing?.rightSide.length === height ? existing.rightMax : 0,
|
|
325
|
-
borderRadius,
|
|
326
|
-
curveOffsets: calculateCurveOffsets(width, borderRadius, isBottom),
|
|
327
|
-
sideGravityMultipliers: calculateGravityMultipliers(height),
|
|
328
|
-
type
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
};
|
|
332
|
-
var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius, config, currentMax) => {
|
|
333
|
-
const spread = 4;
|
|
334
|
-
const addHeight = config.ACCUMULATION.SIDE_RATE * (0.8 + Math.random() * 0.4);
|
|
335
|
-
let newMax = currentMax;
|
|
336
|
-
for (let dy = -spread; dy <= spread; dy++) {
|
|
337
|
-
const y = localY + dy;
|
|
338
|
-
if (y >= 0 && y < sideArray.length) {
|
|
339
|
-
const inTop = y < borderRadius;
|
|
340
|
-
const inBottom = y > rectHeight - borderRadius;
|
|
341
|
-
if (borderRadius > 0 && (inTop || inBottom)) continue;
|
|
342
|
-
const normalizedDist = Math.abs(dy) / spread;
|
|
343
|
-
const falloff = (Math.cos(normalizedDist * Math.PI) + 1) / 2;
|
|
344
|
-
const newHeight = Math.min(maxSideHeight, sideArray[y] + addHeight * falloff);
|
|
345
|
-
sideArray[y] = newHeight;
|
|
346
|
-
if (newHeight > newMax) newMax = newHeight;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return newMax;
|
|
350
|
-
};
|
|
351
|
-
var updateSnowflakePosition = (flake, dt) => {
|
|
352
|
-
flake.wobble += flake.wobbleSpeed * dt;
|
|
353
|
-
flake.x += (flake.wind + Math.sin(flake.wobble) * 0.5) * dt;
|
|
354
|
-
flake.y += (flake.speed + Math.cos(flake.wobble * 0.5) * 0.1) * dt;
|
|
355
|
-
};
|
|
356
|
-
var checkSideCollision = (flakeViewportX, flakeViewportY, rect, acc, config) => {
|
|
357
|
-
const isInVerticalBounds = flakeViewportY >= rect.top && flakeViewportY <= rect.bottom;
|
|
358
|
-
if (!isInVerticalBounds || acc.maxSideHeight <= 0) {
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
const localY = Math.floor(flakeViewportY - rect.top);
|
|
362
|
-
const borderRadius = acc.borderRadius;
|
|
363
|
-
const isInTopCorner = localY < borderRadius;
|
|
364
|
-
const isInBottomCorner = localY > rect.height - borderRadius;
|
|
365
|
-
const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);
|
|
366
|
-
if (isCorner) {
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
if (flakeViewportX >= rect.left - 5 && flakeViewportX < rect.left + 3) {
|
|
370
|
-
acc.leftMax = accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config, acc.leftMax);
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
373
|
-
if (flakeViewportX > rect.right - 3 && flakeViewportX <= rect.right + 5) {
|
|
374
|
-
acc.rightMax = accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config, acc.rightMax);
|
|
375
|
-
return true;
|
|
376
|
-
}
|
|
377
|
-
return false;
|
|
378
|
-
};
|
|
379
|
-
var checkSurfaceCollision = (flake, flakeViewportX, flakeViewportY, rect, acc, isBottom, config) => {
|
|
380
|
-
if (flakeViewportX < rect.left || flakeViewportX > rect.right) {
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
const localX = Math.floor(flakeViewportX - rect.left);
|
|
384
|
-
const currentHeight = acc.heights[localX] || 0;
|
|
385
|
-
const maxHeight = acc.maxHeights[localX] || 5;
|
|
386
|
-
const surfaceY = isBottom ? rect.bottom - currentHeight : rect.top - currentHeight;
|
|
387
|
-
if (flakeViewportY < surfaceY || flakeViewportY >= surfaceY + 10 || currentHeight >= maxHeight) {
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
|
-
const shouldAccumulate2 = isBottom ? Math.random() < 0.15 : true;
|
|
391
|
-
if (shouldAccumulate2) {
|
|
392
|
-
const baseSpread = Math.ceil(flake.radius);
|
|
393
|
-
const spread = baseSpread + Math.floor(Math.random() * 2);
|
|
394
|
-
const accumRate = isBottom ? config.ACCUMULATION.BOTTOM_RATE : config.ACCUMULATION.TOP_RATE;
|
|
395
|
-
const centerOffset = Math.floor(Math.random() * 3) - 1;
|
|
396
|
-
for (let dx = -spread; dx <= spread; dx++) {
|
|
397
|
-
if (Math.random() < 0.15) continue;
|
|
398
|
-
const idx = localX + dx + centerOffset;
|
|
399
|
-
if (idx >= 0 && idx < acc.heights.length) {
|
|
400
|
-
const dist = Math.abs(dx);
|
|
401
|
-
const pixelMax = acc.maxHeights[idx] || 5;
|
|
402
|
-
const normDist = dist / spread;
|
|
403
|
-
const falloff = (Math.cos(normDist * Math.PI) + 1) / 2;
|
|
404
|
-
const baseAdd = 0.3 * falloff;
|
|
405
|
-
const randomFactor = 0.8 + Math.random() * 0.4;
|
|
406
|
-
const addHeight = baseAdd * randomFactor * accumRate;
|
|
407
|
-
if (acc.heights[idx] < pixelMax && addHeight > 0) {
|
|
408
|
-
acc.heights[idx] = Math.min(pixelMax, acc.heights[idx] + addHeight);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
if (isBottom) {
|
|
413
|
-
return true;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
return !isBottom;
|
|
417
|
-
};
|
|
418
|
-
var shouldRemoveSnowflake = (flake, landed, worldWidth, worldHeight) => {
|
|
419
|
-
return landed || flake.y > worldHeight + 10 || flake.x < -20 || flake.x > worldWidth + 20;
|
|
420
|
-
};
|
|
421
|
-
var updateSnowflakes = (snowflakes, elementRects, config, dt, worldWidth, worldHeight) => {
|
|
422
|
-
const scrollX = window.scrollX;
|
|
423
|
-
const scrollY = window.scrollY;
|
|
424
|
-
for (let i = snowflakes.length - 1; i >= 0; i--) {
|
|
425
|
-
const flake = snowflakes[i];
|
|
426
|
-
updateSnowflakePosition(flake, dt);
|
|
427
|
-
let landed = false;
|
|
428
|
-
const shouldCheckCollision = Math.random() < config.COLLISION_CHECK_RATE;
|
|
429
|
-
if (shouldCheckCollision) {
|
|
430
|
-
const flakeViewportX = flake.x - scrollX;
|
|
431
|
-
const flakeViewportY = flake.y - scrollY;
|
|
432
|
-
for (const item of elementRects) {
|
|
433
|
-
const { rect, acc } = item;
|
|
434
|
-
const isBottom = acc.type === VAL_BOTTOM;
|
|
435
|
-
if (!landed && !isBottom) {
|
|
436
|
-
landed = checkSideCollision(flakeViewportX, flakeViewportY, rect, acc, config);
|
|
437
|
-
if (landed) break;
|
|
438
|
-
}
|
|
439
|
-
if (!landed) {
|
|
440
|
-
landed = checkSurfaceCollision(flake, flakeViewportX, flakeViewportY, rect, acc, isBottom, config);
|
|
441
|
-
if (landed) break;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
if (shouldRemoveSnowflake(flake, landed, worldWidth, worldHeight)) {
|
|
446
|
-
snowflakes.splice(i, 1);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
var meltAndSmoothAccumulation = (elementRects, config, dt) => {
|
|
451
|
-
for (const { acc } of elementRects) {
|
|
452
|
-
const meltRate = config.MELT_SPEED * dt;
|
|
453
|
-
const len = acc.heights.length;
|
|
454
|
-
if (len > 2) {
|
|
455
|
-
for (let i = 1; i < len - 1; i++) {
|
|
456
|
-
if (acc.heights[i] > 0.05) {
|
|
457
|
-
const avg = (acc.heights[i - 1] + acc.heights[i + 1]) / 2;
|
|
458
|
-
acc.heights[i] = acc.heights[i] * 0.99 + avg * 0.01;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
for (let i = 0; i < acc.heights.length; i++) {
|
|
463
|
-
if (acc.heights[i] > 0) acc.heights[i] = Math.max(0, acc.heights[i] - meltRate);
|
|
464
|
-
}
|
|
465
|
-
let leftMax = 0;
|
|
466
|
-
let rightMax = 0;
|
|
467
|
-
for (let i = 0; i < acc.leftSide.length; i++) {
|
|
468
|
-
if (acc.leftSide[i] > 0) {
|
|
469
|
-
acc.leftSide[i] = Math.max(0, acc.leftSide[i] - meltRate);
|
|
470
|
-
if (acc.leftSide[i] > leftMax) leftMax = acc.leftSide[i];
|
|
471
|
-
}
|
|
472
|
-
if (acc.rightSide[i] > 0) {
|
|
473
|
-
acc.rightSide[i] = Math.max(0, acc.rightSide[i] - meltRate);
|
|
474
|
-
if (acc.rightSide[i] > rightMax) rightMax = acc.rightSide[i];
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
acc.leftMax = leftMax;
|
|
478
|
-
acc.rightMax = rightMax;
|
|
479
|
-
}
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
// src/hooks/usePerformanceMetrics.ts
|
|
483
|
-
import { useRef, useCallback } from "react";
|
|
484
|
-
function usePerformanceMetrics() {
|
|
485
|
-
const lastFpsSecondRef = useRef(0);
|
|
486
|
-
const framesInSecondRef = useRef(0);
|
|
487
|
-
const currentFpsRef = useRef(0);
|
|
488
|
-
const metricsRef = useRef({
|
|
489
|
-
scanTime: 0,
|
|
490
|
-
rectUpdateTime: 0,
|
|
491
|
-
frameTime: 0,
|
|
492
|
-
rafGap: 0,
|
|
493
|
-
clearTime: 0,
|
|
494
|
-
physicsTime: 0,
|
|
495
|
-
drawTime: 0
|
|
496
|
-
});
|
|
497
|
-
const updateFps = useCallback((now) => {
|
|
498
|
-
const currentSecond = Math.floor(now / 1e3);
|
|
499
|
-
if (currentSecond !== lastFpsSecondRef.current) {
|
|
500
|
-
currentFpsRef.current = framesInSecondRef.current;
|
|
501
|
-
framesInSecondRef.current = 1;
|
|
502
|
-
lastFpsSecondRef.current = currentSecond;
|
|
503
|
-
} else {
|
|
504
|
-
framesInSecondRef.current++;
|
|
505
|
-
}
|
|
506
|
-
}, []);
|
|
507
|
-
const getCurrentFps = useCallback(() => {
|
|
508
|
-
return currentFpsRef.current || framesInSecondRef.current;
|
|
509
|
-
}, []);
|
|
510
|
-
const buildMetrics = useCallback((surfaceCount, flakeCount, maxFlakes) => {
|
|
511
|
-
return {
|
|
512
|
-
fps: currentFpsRef.current || framesInSecondRef.current,
|
|
513
|
-
frameTime: metricsRef.current.frameTime,
|
|
514
|
-
scanTime: metricsRef.current.scanTime,
|
|
515
|
-
rectUpdateTime: metricsRef.current.rectUpdateTime,
|
|
516
|
-
surfaceCount,
|
|
517
|
-
flakeCount,
|
|
518
|
-
maxFlakes,
|
|
519
|
-
rafGap: metricsRef.current.rafGap,
|
|
520
|
-
clearTime: metricsRef.current.clearTime,
|
|
521
|
-
physicsTime: metricsRef.current.physicsTime,
|
|
522
|
-
drawTime: metricsRef.current.drawTime
|
|
523
|
-
};
|
|
524
|
-
}, []);
|
|
525
|
-
return {
|
|
526
|
-
metricsRef,
|
|
527
|
-
updateFps,
|
|
528
|
-
getCurrentFps,
|
|
529
|
-
buildMetrics
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// src/hooks/useSnowfallCanvas.ts
|
|
534
|
-
import { useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
535
|
-
function useSnowfallCanvas() {
|
|
536
|
-
const canvasRef = useRef2(null);
|
|
537
|
-
const dprRef = useRef2(1);
|
|
538
|
-
const resizeCanvas = useCallback2((maxRenderDpr = Number.POSITIVE_INFINITY) => {
|
|
539
|
-
if (canvasRef.current) {
|
|
540
|
-
const newWidth = window.innerWidth;
|
|
541
|
-
const newHeight = window.innerHeight;
|
|
542
|
-
const dpr = Math.min(window.devicePixelRatio || 1, Math.max(1, maxRenderDpr));
|
|
543
|
-
dprRef.current = dpr;
|
|
544
|
-
canvasRef.current.width = newWidth * dpr;
|
|
545
|
-
canvasRef.current.height = newHeight * dpr;
|
|
546
|
-
canvasRef.current.style.width = `${newWidth}px`;
|
|
547
|
-
canvasRef.current.style.height = `${newHeight}px`;
|
|
548
|
-
}
|
|
549
|
-
}, []);
|
|
550
|
-
return {
|
|
551
|
-
canvasRef,
|
|
552
|
-
dprRef,
|
|
553
|
-
resizeCanvas
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// src/hooks/useAnimationLoop.ts
|
|
558
|
-
import { useEffect, useRef as useRef3, useCallback as useCallback3 } from "react";
|
|
559
|
-
|
|
560
|
-
// src/core/draw.ts
|
|
561
|
-
var ACC_FILL_STYLE = "rgba(255, 255, 255, 0.95)";
|
|
562
|
-
var ACC_SHADOW_COLOR = "rgba(200, 230, 255, 0.6)";
|
|
563
|
-
var OPACITY_BUCKETS2 = [0.3, 0.5, 0.7, 0.9];
|
|
564
|
-
var drawSnowflakes = (ctx, flakes) => {
|
|
565
|
-
if (flakes.length === 0) return;
|
|
566
|
-
ctx.fillStyle = "#FFFFFF";
|
|
567
|
-
for (const alpha of OPACITY_BUCKETS2) {
|
|
568
|
-
let hasPath = false;
|
|
569
|
-
for (const flake of flakes) {
|
|
570
|
-
if (flake.isBackground || flake.glowOpacity !== alpha) continue;
|
|
571
|
-
if (!hasPath) {
|
|
572
|
-
ctx.globalAlpha = alpha;
|
|
573
|
-
ctx.beginPath();
|
|
574
|
-
hasPath = true;
|
|
575
|
-
}
|
|
576
|
-
ctx.moveTo(flake.x + flake.glowRadius, flake.y);
|
|
577
|
-
ctx.arc(flake.x, flake.y, flake.glowRadius, 0, TAU);
|
|
578
|
-
}
|
|
579
|
-
if (hasPath) {
|
|
580
|
-
ctx.fill();
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
for (const alpha of OPACITY_BUCKETS2) {
|
|
584
|
-
let hasPath = false;
|
|
585
|
-
for (const flake of flakes) {
|
|
586
|
-
if (flake.opacity !== alpha) continue;
|
|
587
|
-
if (!hasPath) {
|
|
588
|
-
ctx.globalAlpha = alpha;
|
|
589
|
-
ctx.beginPath();
|
|
590
|
-
hasPath = true;
|
|
591
|
-
}
|
|
592
|
-
ctx.moveTo(flake.x + flake.radius, flake.y);
|
|
593
|
-
ctx.arc(flake.x, flake.y, flake.radius, 0, TAU);
|
|
594
|
-
}
|
|
595
|
-
if (hasPath) {
|
|
596
|
-
ctx.fill();
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
ctx.globalAlpha = 1;
|
|
600
|
-
};
|
|
601
|
-
var drawAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
602
|
-
ctx.fillStyle = ACC_FILL_STYLE;
|
|
603
|
-
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
604
|
-
ctx.shadowBlur = 4;
|
|
605
|
-
ctx.shadowOffsetY = -1;
|
|
606
|
-
ctx.globalAlpha = 1;
|
|
607
|
-
ctx.beginPath();
|
|
608
|
-
for (const item of elementRects) {
|
|
609
|
-
const { rect, acc } = item;
|
|
610
|
-
if (!acc.heights.some((h) => h > 0.1)) continue;
|
|
611
|
-
const isBottom = acc.type === VAL_BOTTOM;
|
|
612
|
-
const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
|
|
613
|
-
const worldLeft = rect.left + scrollX;
|
|
614
|
-
const worldBaseY = baseY + scrollY;
|
|
615
|
-
let first = true;
|
|
616
|
-
const step = 2;
|
|
617
|
-
const len = acc.heights.length;
|
|
618
|
-
for (let x = 0; x < len; x += step) {
|
|
619
|
-
const height = acc.heights[x] || 0;
|
|
620
|
-
const px = worldLeft + x;
|
|
621
|
-
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
622
|
-
if (first) {
|
|
623
|
-
ctx.moveTo(px, py);
|
|
624
|
-
first = false;
|
|
625
|
-
} else {
|
|
626
|
-
ctx.lineTo(px, py);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
if ((len - 1) % step !== 0) {
|
|
630
|
-
const x = len - 1;
|
|
631
|
-
const height = acc.heights[x] || 0;
|
|
632
|
-
const px = worldLeft + x;
|
|
633
|
-
const py = worldBaseY - height + (acc.curveOffsets[x] || 0);
|
|
634
|
-
ctx.lineTo(px, py);
|
|
635
|
-
}
|
|
636
|
-
for (let x = len - 1; x >= 0; x -= step) {
|
|
637
|
-
const px = worldLeft + x;
|
|
638
|
-
const py = worldBaseY + (acc.curveOffsets[x] || 0);
|
|
639
|
-
ctx.lineTo(px, py);
|
|
640
|
-
}
|
|
641
|
-
const startX = 0;
|
|
642
|
-
const startPx = worldLeft + startX;
|
|
643
|
-
const startPy = worldBaseY + (acc.curveOffsets[startX] || 0);
|
|
644
|
-
ctx.lineTo(startPx, startPy);
|
|
645
|
-
}
|
|
646
|
-
ctx.fill();
|
|
647
|
-
ctx.shadowBlur = 0;
|
|
648
|
-
ctx.shadowOffsetY = 0;
|
|
649
|
-
};
|
|
650
|
-
var drawSideAccumulations = (ctx, elementRects, scrollX, scrollY) => {
|
|
651
|
-
ctx.fillStyle = ACC_FILL_STYLE;
|
|
652
|
-
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
653
|
-
ctx.shadowBlur = 3;
|
|
654
|
-
ctx.globalAlpha = 1;
|
|
655
|
-
ctx.beginPath();
|
|
656
|
-
const drawSide = (sideArray, isLeft, multipliers, rect, dx, dy) => {
|
|
657
|
-
const baseX = isLeft ? rect.left : rect.right;
|
|
658
|
-
const worldBaseX = baseX + dx;
|
|
659
|
-
const worldTop = rect.top + dy;
|
|
660
|
-
const worldBottom = rect.bottom + dy;
|
|
661
|
-
ctx.moveTo(worldBaseX, worldTop);
|
|
662
|
-
for (let y = 0; y < sideArray.length; y += 2) {
|
|
663
|
-
const width = sideArray[y] || 0;
|
|
664
|
-
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
665
|
-
const nextWidth = sideArray[nextY] || 0;
|
|
666
|
-
const gravityMultiplier = multipliers[y] || 0;
|
|
667
|
-
const py = worldTop + y;
|
|
668
|
-
const px = isLeft ? worldBaseX - width * gravityMultiplier : worldBaseX + width * gravityMultiplier;
|
|
669
|
-
const ny = worldTop + nextY;
|
|
670
|
-
const nGravityMultiplier = multipliers[nextY] || 0;
|
|
671
|
-
const nx = isLeft ? worldBaseX - nextWidth * nGravityMultiplier : worldBaseX + nextWidth * nGravityMultiplier;
|
|
672
|
-
ctx.lineTo(px, py);
|
|
673
|
-
ctx.lineTo(nx, ny);
|
|
674
|
-
}
|
|
675
|
-
ctx.lineTo(worldBaseX, worldBottom);
|
|
676
|
-
};
|
|
677
|
-
for (const item of elementRects) {
|
|
678
|
-
const { rect, acc } = item;
|
|
679
|
-
if (acc.maxSideHeight === 0) continue;
|
|
680
|
-
const hasLeftSnow = acc.leftMax > 0.3;
|
|
681
|
-
const hasRightSnow = acc.rightMax > 0.3;
|
|
682
|
-
if (!hasLeftSnow && !hasRightSnow) continue;
|
|
683
|
-
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
684
|
-
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, scrollX, scrollY);
|
|
685
|
-
}
|
|
686
|
-
ctx.fill();
|
|
687
|
-
ctx.shadowBlur = 0;
|
|
688
|
-
};
|
|
689
|
-
|
|
690
|
-
// src/hooks/useAnimationLoop.ts
|
|
691
|
-
function useAnimationLoop(params) {
|
|
692
|
-
const animationIdRef = useRef3(0);
|
|
693
|
-
const animateRef = useRef3(() => {
|
|
694
|
-
});
|
|
695
|
-
const lastTimeRef = useRef3(0);
|
|
696
|
-
const lastMetricsUpdateRef = useRef3(0);
|
|
697
|
-
const elementRectsRef = useRef3([]);
|
|
698
|
-
const visibleRectsRef = useRef3([]);
|
|
699
|
-
const dirtyRectsRef = useRef3(true);
|
|
700
|
-
const animate = useCallback3((currentTime) => {
|
|
701
|
-
const {
|
|
702
|
-
canvasRef,
|
|
703
|
-
dprRef,
|
|
704
|
-
snowflakesRef,
|
|
705
|
-
accumulationRef,
|
|
706
|
-
isEnabledRef,
|
|
707
|
-
physicsConfigRef,
|
|
708
|
-
metricsRef,
|
|
709
|
-
updateFps,
|
|
710
|
-
getCurrentFps,
|
|
711
|
-
buildMetrics,
|
|
712
|
-
setMetricsRef
|
|
713
|
-
} = params;
|
|
714
|
-
const canvas = canvasRef.current;
|
|
715
|
-
if (!canvas) {
|
|
716
|
-
animationIdRef.current = requestAnimationFrame(animateRef.current);
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
const ctx = canvas.getContext("2d");
|
|
720
|
-
if (!ctx) {
|
|
721
|
-
animationIdRef.current = requestAnimationFrame(animateRef.current);
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
if (lastTimeRef.current === 0) {
|
|
725
|
-
lastTimeRef.current = currentTime;
|
|
726
|
-
animationIdRef.current = requestAnimationFrame(animateRef.current);
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
const deltaTime = Math.min(currentTime - lastTimeRef.current, 50);
|
|
730
|
-
const now = performance.now();
|
|
731
|
-
updateFps(now);
|
|
732
|
-
metricsRef.current.rafGap = currentTime - lastTimeRef.current;
|
|
733
|
-
lastTimeRef.current = currentTime;
|
|
734
|
-
const dt = deltaTime / 16.67;
|
|
735
|
-
const frameStartTime = now;
|
|
736
|
-
const clearStart = now;
|
|
737
|
-
const dpr = dprRef.current;
|
|
738
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
739
|
-
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
740
|
-
const scrollX = window.scrollX;
|
|
741
|
-
const scrollY = window.scrollY;
|
|
742
|
-
ctx.translate(-scrollX, -scrollY);
|
|
743
|
-
metricsRef.current.clearTime = performance.now() - clearStart;
|
|
744
|
-
const snowflakes = snowflakesRef.current;
|
|
745
|
-
if (dirtyRectsRef.current) {
|
|
746
|
-
const rectStart = performance.now();
|
|
747
|
-
elementRectsRef.current = getElementRects(accumulationRef.current);
|
|
748
|
-
metricsRef.current.rectUpdateTime = performance.now() - rectStart;
|
|
749
|
-
dirtyRectsRef.current = false;
|
|
750
|
-
}
|
|
751
|
-
const physicsStart = performance.now();
|
|
752
|
-
meltAndSmoothAccumulation(elementRectsRef.current, physicsConfigRef.current, dt);
|
|
753
|
-
const docEl = document.documentElement;
|
|
754
|
-
const worldWidth = docEl.scrollWidth;
|
|
755
|
-
const worldHeight = docEl.scrollHeight;
|
|
756
|
-
updateSnowflakes(
|
|
757
|
-
snowflakes,
|
|
758
|
-
elementRectsRef.current,
|
|
759
|
-
physicsConfigRef.current,
|
|
760
|
-
dt,
|
|
761
|
-
worldWidth,
|
|
762
|
-
worldHeight
|
|
763
|
-
);
|
|
764
|
-
metricsRef.current.physicsTime = performance.now() - physicsStart;
|
|
765
|
-
const drawStart = performance.now();
|
|
766
|
-
drawSnowflakes(ctx, snowflakes);
|
|
767
|
-
if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
|
|
768
|
-
const minFlakeFloor = Math.min(80, physicsConfigRef.current.MAX_FLAKES);
|
|
769
|
-
const shouldForceSpawn = snowflakes.length < minFlakeFloor;
|
|
770
|
-
const currentFps = getCurrentFps();
|
|
771
|
-
const isVisible = document.visibilityState === "visible";
|
|
772
|
-
const isUnderFpsThreshold = isVisible && currentFps > 0 && currentFps < 40;
|
|
773
|
-
const shouldSpawn = shouldForceSpawn || !isUnderFpsThreshold || Math.random() < 0.2;
|
|
774
|
-
if (shouldSpawn) {
|
|
775
|
-
const isBackground = Math.random() < 0.4;
|
|
776
|
-
snowflakes.push(createSnowflake(worldWidth, physicsConfigRef.current, isBackground));
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
const viewportWidth = window.innerWidth;
|
|
780
|
-
const viewportHeight = window.innerHeight;
|
|
781
|
-
const visibleRects = visibleRectsRef.current;
|
|
782
|
-
visibleRects.length = 0;
|
|
783
|
-
for (const item of elementRectsRef.current) {
|
|
784
|
-
const { rect } = item;
|
|
785
|
-
if (rect.right >= 0 && rect.left <= viewportWidth && rect.bottom >= 0 && rect.top <= viewportHeight) {
|
|
786
|
-
visibleRects.push(item);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
if (visibleRects.length > 0) {
|
|
790
|
-
drawAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
791
|
-
drawSideAccumulations(ctx, visibleRects, scrollX, scrollY);
|
|
792
|
-
}
|
|
793
|
-
metricsRef.current.drawTime = performance.now() - drawStart;
|
|
794
|
-
metricsRef.current.frameTime = performance.now() - frameStartTime;
|
|
795
|
-
if (currentTime - lastMetricsUpdateRef.current > 500) {
|
|
796
|
-
setMetricsRef.current(buildMetrics(
|
|
797
|
-
accumulationRef.current.size,
|
|
798
|
-
snowflakes.length,
|
|
799
|
-
physicsConfigRef.current.MAX_FLAKES
|
|
800
|
-
));
|
|
801
|
-
lastMetricsUpdateRef.current = currentTime;
|
|
802
|
-
}
|
|
803
|
-
animationIdRef.current = requestAnimationFrame(animateRef.current);
|
|
804
|
-
}, [params]);
|
|
805
|
-
useEffect(() => {
|
|
806
|
-
animateRef.current = animate;
|
|
807
|
-
}, [animate]);
|
|
808
|
-
const start = useCallback3(() => {
|
|
809
|
-
lastTimeRef.current = 0;
|
|
810
|
-
lastMetricsUpdateRef.current = 0;
|
|
811
|
-
animationIdRef.current = requestAnimationFrame(animateRef.current);
|
|
812
|
-
}, []);
|
|
813
|
-
const stop = useCallback3(() => {
|
|
814
|
-
cancelAnimationFrame(animationIdRef.current);
|
|
815
|
-
}, []);
|
|
816
|
-
const markRectsDirty = useCallback3(() => {
|
|
817
|
-
dirtyRectsRef.current = true;
|
|
818
|
-
}, []);
|
|
819
|
-
return {
|
|
820
|
-
start,
|
|
821
|
-
stop,
|
|
822
|
-
markRectsDirty
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// src/components/Snowfall.tsx
|
|
827
|
-
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
828
|
-
function Snowfall() {
|
|
829
|
-
const { isEnabled, physicsConfig, setMetrics } = useSnowfall();
|
|
830
|
-
const isEnabledRef = useRef4(isEnabled);
|
|
831
|
-
const physicsConfigRef = useRef4(physicsConfig);
|
|
832
|
-
const setMetricsRef = useRef4(setMetrics);
|
|
833
|
-
const [isMounted, setIsMounted] = useState2(false);
|
|
834
|
-
const [isVisible, setIsVisible] = useState2(false);
|
|
835
|
-
const snowflakesRef = useRef4([]);
|
|
836
|
-
const accumulationRef = useRef4(/* @__PURE__ */ new Map());
|
|
837
|
-
const { canvasRef, dprRef, resizeCanvas } = useSnowfallCanvas();
|
|
838
|
-
const { metricsRef, updateFps, getCurrentFps, buildMetrics } = usePerformanceMetrics();
|
|
839
|
-
const { start: startAnimation, stop: stopAnimation, markRectsDirty } = useAnimationLoop({
|
|
840
|
-
canvasRef,
|
|
841
|
-
dprRef,
|
|
842
|
-
snowflakesRef,
|
|
843
|
-
accumulationRef,
|
|
844
|
-
isEnabledRef,
|
|
845
|
-
physicsConfigRef,
|
|
846
|
-
metricsRef,
|
|
847
|
-
updateFps,
|
|
848
|
-
getCurrentFps,
|
|
849
|
-
buildMetrics,
|
|
850
|
-
setMetricsRef
|
|
851
|
-
});
|
|
852
|
-
useEffect2(() => {
|
|
853
|
-
requestAnimationFrame(() => setIsMounted(true));
|
|
854
|
-
}, []);
|
|
855
|
-
useEffect2(() => {
|
|
856
|
-
isEnabledRef.current = isEnabled;
|
|
857
|
-
}, [isEnabled]);
|
|
858
|
-
useEffect2(() => {
|
|
859
|
-
physicsConfigRef.current = physicsConfig;
|
|
860
|
-
if (isMounted) {
|
|
861
|
-
resizeCanvas(physicsConfig.MAX_RENDER_DPR);
|
|
862
|
-
}
|
|
863
|
-
}, [isMounted, physicsConfig, resizeCanvas]);
|
|
864
|
-
useEffect2(() => {
|
|
865
|
-
setMetricsRef.current = setMetrics;
|
|
866
|
-
}, [setMetrics]);
|
|
867
|
-
useEffect2(() => {
|
|
868
|
-
if (!isMounted) return;
|
|
869
|
-
const canvas = canvasRef.current;
|
|
870
|
-
if (!canvas) return;
|
|
871
|
-
const ctx = canvas.getContext("2d");
|
|
872
|
-
if (!ctx) return;
|
|
873
|
-
resizeCanvas(physicsConfigRef.current.MAX_RENDER_DPR);
|
|
874
|
-
snowflakesRef.current = [];
|
|
875
|
-
let isUnmounted = false;
|
|
876
|
-
let scheduledInitFrame = 0;
|
|
877
|
-
const surfaceObserver = new ResizeObserver((entries) => {
|
|
878
|
-
let needsUpdate = false;
|
|
879
|
-
for (const entry of entries) {
|
|
880
|
-
if (entry.target.isConnected) {
|
|
881
|
-
needsUpdate = true;
|
|
882
|
-
break;
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
if (needsUpdate) {
|
|
886
|
-
scheduleAccumulationInit();
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
const initAccumulationWrapper = () => {
|
|
890
|
-
if (isUnmounted) return;
|
|
891
|
-
const scanStart = performance.now();
|
|
892
|
-
initializeAccumulation(accumulationRef.current, physicsConfigRef.current);
|
|
893
|
-
surfaceObserver.disconnect();
|
|
894
|
-
for (const [el] of accumulationRef.current) {
|
|
895
|
-
surfaceObserver.observe(el);
|
|
896
|
-
}
|
|
897
|
-
metricsRef.current.scanTime = performance.now() - scanStart;
|
|
898
|
-
markRectsDirty();
|
|
899
|
-
};
|
|
900
|
-
const scheduleAccumulationInit = () => {
|
|
901
|
-
if (scheduledInitFrame !== 0 || isUnmounted) return;
|
|
902
|
-
scheduledInitFrame = requestAnimationFrame(() => {
|
|
903
|
-
scheduledInitFrame = 0;
|
|
904
|
-
initAccumulationWrapper();
|
|
905
|
-
});
|
|
906
|
-
};
|
|
907
|
-
initAccumulationWrapper();
|
|
908
|
-
requestAnimationFrame(() => {
|
|
909
|
-
if (!isUnmounted && isMounted) setIsVisible(true);
|
|
910
|
-
});
|
|
911
|
-
startAnimation();
|
|
912
|
-
const handleResize = () => {
|
|
913
|
-
resizeCanvas(physicsConfigRef.current.MAX_RENDER_DPR);
|
|
914
|
-
accumulationRef.current.clear();
|
|
915
|
-
initAccumulationWrapper();
|
|
916
|
-
markRectsDirty();
|
|
917
|
-
};
|
|
918
|
-
window.addEventListener("resize", handleResize);
|
|
919
|
-
const mutationObserver = new MutationObserver((mutations) => {
|
|
920
|
-
let hasStructuralChange = false;
|
|
921
|
-
for (const mutation of mutations) {
|
|
922
|
-
if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0) {
|
|
923
|
-
hasStructuralChange = true;
|
|
924
|
-
break;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
if (hasStructuralChange) {
|
|
928
|
-
scheduleAccumulationInit();
|
|
929
|
-
}
|
|
930
|
-
});
|
|
931
|
-
mutationObserver.observe(document.body, {
|
|
932
|
-
childList: true,
|
|
933
|
-
subtree: true
|
|
934
|
-
});
|
|
935
|
-
return () => {
|
|
936
|
-
isUnmounted = true;
|
|
937
|
-
if (scheduledInitFrame !== 0) {
|
|
938
|
-
cancelAnimationFrame(scheduledInitFrame);
|
|
939
|
-
}
|
|
940
|
-
stopAnimation();
|
|
941
|
-
window.removeEventListener("resize", handleResize);
|
|
942
|
-
mutationObserver.disconnect();
|
|
943
|
-
surfaceObserver.disconnect();
|
|
944
|
-
};
|
|
945
|
-
}, [isMounted]);
|
|
946
|
-
if (!isMounted) return null;
|
|
947
|
-
return /* @__PURE__ */ jsx2(Fragment, { children: /* @__PURE__ */ jsx2(
|
|
948
|
-
"canvas",
|
|
949
|
-
{
|
|
950
|
-
ref: canvasRef,
|
|
951
|
-
style: {
|
|
952
|
-
position: "fixed",
|
|
953
|
-
// FIXED position to eliminate scroll jitter
|
|
954
|
-
top: 0,
|
|
955
|
-
left: 0,
|
|
956
|
-
pointerEvents: "none",
|
|
957
|
-
zIndex: 9999,
|
|
958
|
-
opacity: isVisible ? 1 : 0,
|
|
959
|
-
transition: "opacity 0.3s ease-in",
|
|
960
|
-
willChange: "transform"
|
|
961
|
-
},
|
|
962
|
-
"aria-hidden": "true"
|
|
963
|
-
}
|
|
964
|
-
) });
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// src/components/DebugPanel.tsx
|
|
968
|
-
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
969
|
-
import { Fragment as Fragment2, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
970
|
-
function DebugPanel({ defaultOpen = true }) {
|
|
971
|
-
const { debugMode, toggleDebug, metrics } = useSnowfall();
|
|
972
|
-
const [isMinimized, setIsMinimized] = useState3(!defaultOpen);
|
|
973
|
-
const [copied, setCopied] = useState3(false);
|
|
974
|
-
useEffect3(() => {
|
|
975
|
-
const handleKeyDown = (e) => {
|
|
976
|
-
if (e.shiftKey && e.key === "D") {
|
|
977
|
-
toggleDebug();
|
|
978
|
-
}
|
|
979
|
-
};
|
|
980
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
981
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
982
|
-
}, [toggleDebug]);
|
|
983
|
-
const copyToClipboard = () => {
|
|
984
|
-
if (metrics) {
|
|
985
|
-
const data = {
|
|
986
|
-
...metrics,
|
|
987
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
988
|
-
userAgent: navigator.userAgent,
|
|
989
|
-
canvasSize: {
|
|
990
|
-
width: window.innerWidth,
|
|
991
|
-
height: window.innerHeight
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
navigator.clipboard.writeText(JSON.stringify(data, null, 2));
|
|
995
|
-
setCopied(true);
|
|
996
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
997
|
-
}
|
|
998
|
-
};
|
|
999
|
-
if (!debugMode) return null;
|
|
1000
|
-
return /* @__PURE__ */ jsxs(
|
|
1001
|
-
"div",
|
|
1002
|
-
{
|
|
1003
|
-
"data-snowfall": "top",
|
|
1004
|
-
style: {
|
|
1005
|
-
position: "fixed",
|
|
1006
|
-
bottom: "80px",
|
|
1007
|
-
left: "24px",
|
|
1008
|
-
backgroundColor: "rgba(15, 23, 42, 0.75)",
|
|
1009
|
-
backdropFilter: "blur(16px)",
|
|
1010
|
-
WebkitBackdropFilter: "blur(16px)",
|
|
1011
|
-
color: "#e2e8f0",
|
|
1012
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
1013
|
-
fontSize: "12px",
|
|
1014
|
-
padding: isMinimized ? "12px" : "20px",
|
|
1015
|
-
borderRadius: "16px",
|
|
1016
|
-
zIndex: 1e4,
|
|
1017
|
-
minWidth: isMinimized ? "auto" : "300px",
|
|
1018
|
-
maxWidth: "100%",
|
|
1019
|
-
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
1020
|
-
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 8px 10px -6px rgba(0, 0, 0, 0.2)",
|
|
1021
|
-
transition: "all 0.2s ease"
|
|
1022
|
-
},
|
|
1023
|
-
children: [
|
|
1024
|
-
/* @__PURE__ */ jsxs("div", { style: {
|
|
1025
|
-
display: "flex",
|
|
1026
|
-
justifyContent: "space-between",
|
|
1027
|
-
alignItems: "center",
|
|
1028
|
-
marginBottom: isMinimized ? 0 : "16px",
|
|
1029
|
-
gap: "16px"
|
|
1030
|
-
}, children: [
|
|
1031
|
-
/* @__PURE__ */ jsxs("div", { style: { fontWeight: "600", color: "#fff", display: "flex", alignItems: "center", gap: "8px" }, children: [
|
|
1032
|
-
/* @__PURE__ */ jsx3("span", { style: { fontSize: "14px" }, children: "\u2744\uFE0F" }),
|
|
1033
|
-
/* @__PURE__ */ jsx3("span", { style: {
|
|
1034
|
-
background: "linear-gradient(to right, #60a5fa, #22d3ee)",
|
|
1035
|
-
WebkitBackgroundClip: "text",
|
|
1036
|
-
WebkitTextFillColor: "transparent",
|
|
1037
|
-
fontWeight: "700"
|
|
1038
|
-
}, children: "DEBUG" })
|
|
1039
|
-
] }),
|
|
1040
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
1041
|
-
/* @__PURE__ */ jsx3(
|
|
1042
|
-
"button",
|
|
1043
|
-
{
|
|
1044
|
-
onClick: () => setIsMinimized(!isMinimized),
|
|
1045
|
-
style: {
|
|
1046
|
-
background: "rgba(255, 255, 255, 0.1)",
|
|
1047
|
-
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
1048
|
-
color: "#fff",
|
|
1049
|
-
cursor: "pointer",
|
|
1050
|
-
width: "24px",
|
|
1051
|
-
height: "24px",
|
|
1052
|
-
display: "flex",
|
|
1053
|
-
alignItems: "center",
|
|
1054
|
-
justifyContent: "center",
|
|
1055
|
-
borderRadius: "6px",
|
|
1056
|
-
fontSize: "10px"
|
|
1057
|
-
},
|
|
1058
|
-
children: isMinimized ? "\u25B2" : "\u25BC"
|
|
1059
|
-
}
|
|
1060
|
-
),
|
|
1061
|
-
/* @__PURE__ */ jsx3(
|
|
1062
|
-
"button",
|
|
1063
|
-
{
|
|
1064
|
-
onClick: toggleDebug,
|
|
1065
|
-
style: {
|
|
1066
|
-
background: "rgba(239, 68, 68, 0.15)",
|
|
1067
|
-
border: "1px solid rgba(239, 68, 68, 0.2)",
|
|
1068
|
-
color: "#f87171",
|
|
1069
|
-
cursor: "pointer",
|
|
1070
|
-
width: "24px",
|
|
1071
|
-
height: "24px",
|
|
1072
|
-
display: "flex",
|
|
1073
|
-
alignItems: "center",
|
|
1074
|
-
justifyContent: "center",
|
|
1075
|
-
borderRadius: "6px",
|
|
1076
|
-
fontSize: "12px"
|
|
1077
|
-
},
|
|
1078
|
-
children: "\u2715"
|
|
1079
|
-
}
|
|
1080
|
-
)
|
|
1081
|
-
] })
|
|
1082
|
-
] }),
|
|
1083
|
-
!isMinimized && metrics && /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
1084
|
-
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "12px", paddingBottom: "12px", borderBottom: "1px solid rgba(255,255,255,0.08)" }, children: [
|
|
1085
|
-
/* @__PURE__ */ jsx3("div", { style: { color: "#94a3b8", marginBottom: "8px", fontSize: "11px", fontWeight: "600", letterSpacing: "0.5px", textTransform: "uppercase" }, children: "Performance" }),
|
|
1086
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px" }, children: [
|
|
1087
|
-
/* @__PURE__ */ jsx3("span", { children: "FPS" }),
|
|
1088
|
-
/* @__PURE__ */ jsx3("span", { style: { fontWeight: "bold", color: metrics.fps < 30 ? "#f87171" : metrics.fps < 50 ? "#facc15" : "#4ade80" }, children: metrics.fps.toFixed(1) })
|
|
1089
|
-
] }),
|
|
1090
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px" }, children: [
|
|
1091
|
-
/* @__PURE__ */ jsx3("span", { children: "Frame Time" }),
|
|
1092
|
-
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
1093
|
-
metrics.frameTime.toFixed(2),
|
|
1094
|
-
"ms"
|
|
1095
|
-
] })
|
|
1096
|
-
] }),
|
|
1097
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
1098
|
-
/* @__PURE__ */ jsx3("span", { style: { color: metrics.rafGap && metrics.rafGap > 20 ? "#fbbf24" : "inherit" }, children: "rAF Gap" }),
|
|
1099
|
-
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
1100
|
-
metrics.rafGap?.toFixed(1) || 0,
|
|
1101
|
-
"ms"
|
|
1102
|
-
] })
|
|
1103
|
-
] })
|
|
1104
|
-
] }),
|
|
1105
|
-
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "12px", paddingBottom: "12px", borderBottom: "1px solid rgba(255,255,255,0.08)" }, children: [
|
|
1106
|
-
/* @__PURE__ */ jsx3("div", { style: { color: "#94a3b8", marginBottom: "8px", fontSize: "11px", fontWeight: "600", letterSpacing: "0.5px", textTransform: "uppercase" }, children: "Detailed Timings" }),
|
|
1107
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "4px 12px" }, children: [
|
|
1108
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
1109
|
-
/* @__PURE__ */ jsx3("span", { children: "Clear" }),
|
|
1110
|
-
" ",
|
|
1111
|
-
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
1112
|
-
metrics.clearTime?.toFixed(2) || 0,
|
|
1113
|
-
"ms"
|
|
1114
|
-
] })
|
|
1115
|
-
] }),
|
|
1116
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
1117
|
-
/* @__PURE__ */ jsx3("span", { children: "Physics" }),
|
|
1118
|
-
" ",
|
|
1119
|
-
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
1120
|
-
metrics.physicsTime?.toFixed(2) || 0,
|
|
1121
|
-
"ms"
|
|
1122
|
-
] })
|
|
1123
|
-
] }),
|
|
1124
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
1125
|
-
/* @__PURE__ */ jsx3("span", { children: "Draw" }),
|
|
1126
|
-
" ",
|
|
1127
|
-
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
1128
|
-
metrics.drawTime?.toFixed(2) || 0,
|
|
1129
|
-
"ms"
|
|
1130
|
-
] })
|
|
1131
|
-
] }),
|
|
1132
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
1133
|
-
/* @__PURE__ */ jsx3("span", { children: "Scan" }),
|
|
1134
|
-
" ",
|
|
1135
|
-
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
1136
|
-
metrics.scanTime.toFixed(2),
|
|
1137
|
-
"ms"
|
|
1138
|
-
] })
|
|
1139
|
-
] }),
|
|
1140
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", gridColumn: "span 2" }, children: [
|
|
1141
|
-
/* @__PURE__ */ jsx3("span", { children: "Rect Update" }),
|
|
1142
|
-
" ",
|
|
1143
|
-
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
1144
|
-
metrics.rectUpdateTime.toFixed(2),
|
|
1145
|
-
"ms"
|
|
1146
|
-
] })
|
|
1147
|
-
] })
|
|
1148
|
-
] })
|
|
1149
|
-
] }),
|
|
1150
|
-
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
|
|
1151
|
-
/* @__PURE__ */ jsx3("div", { style: { color: "#94a3b8", marginBottom: "8px", fontSize: "11px", fontWeight: "600", letterSpacing: "0.5px", textTransform: "uppercase" }, children: "Counts" }),
|
|
1152
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px" }, children: [
|
|
1153
|
-
/* @__PURE__ */ jsx3("span", { children: "Snowflakes" }),
|
|
1154
|
-
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
1155
|
-
metrics.flakeCount,
|
|
1156
|
-
" / ",
|
|
1157
|
-
metrics.maxFlakes
|
|
1158
|
-
] })
|
|
1159
|
-
] }),
|
|
1160
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
1161
|
-
/* @__PURE__ */ jsx3("span", { children: "Surfaces" }),
|
|
1162
|
-
/* @__PURE__ */ jsx3("span", { style: { fontFamily: "monospace" }, children: metrics.surfaceCount })
|
|
1163
|
-
] })
|
|
1164
|
-
] }),
|
|
1165
|
-
/* @__PURE__ */ jsx3(
|
|
1166
|
-
"button",
|
|
1167
|
-
{
|
|
1168
|
-
onClick: copyToClipboard,
|
|
1169
|
-
style: {
|
|
1170
|
-
width: "100%",
|
|
1171
|
-
padding: "10px",
|
|
1172
|
-
background: copied ? "rgba(34, 197, 94, 0.2)" : "rgba(255, 255, 255, 0.05)",
|
|
1173
|
-
border: copied ? "1px solid rgba(34, 197, 94, 0.5)" : "1px solid rgba(255, 255, 255, 0.1)",
|
|
1174
|
-
color: copied ? "#4ade80" : "#fff",
|
|
1175
|
-
cursor: "pointer",
|
|
1176
|
-
borderRadius: "8px",
|
|
1177
|
-
fontSize: "11px",
|
|
1178
|
-
fontWeight: "600",
|
|
1179
|
-
transition: "all 0.2s",
|
|
1180
|
-
display: "flex",
|
|
1181
|
-
alignItems: "center",
|
|
1182
|
-
justifyContent: "center",
|
|
1183
|
-
gap: "6px"
|
|
1184
|
-
},
|
|
1185
|
-
children: copied ? "\u2713 COPIED" : "\u{1F4CB} COPY METRICS"
|
|
1186
|
-
}
|
|
1187
|
-
),
|
|
1188
|
-
/* @__PURE__ */ jsx3("div", { style: { marginTop: "12px", fontSize: "10px", color: "#64748b", textAlign: "center" }, children: "Shift+D to toggle" })
|
|
1189
|
-
] })
|
|
1190
|
-
]
|
|
1191
|
-
}
|
|
1192
|
-
);
|
|
1193
|
-
}
|
|
1194
|
-
export {
|
|
1195
|
-
DEFAULT_PHYSICS,
|
|
1196
|
-
DebugPanel,
|
|
1197
|
-
Snowfall,
|
|
1198
|
-
SnowfallProvider,
|
|
1199
|
-
useSnowfall
|
|
1200
|
-
};
|
|
1
|
+
"use client";import{useEffect as K,useRef as W,useState as $e}from"react";import{createContext as ot,useContext as rt,useState as J,useMemo as st,useCallback as Q}from"react";import{jsx as ct}from"react/jsx-runtime";var ne={MAX_FLAKES:1e3,MELT_SPEED:1e-5,WIND_STRENGTH:1.5,ACCUMULATION:{SIDE_RATE:1,TOP_RATE:5,BOTTOM_RATE:5},MAX_DEPTH:{TOP:100,BOTTOM:50,SIDE:20},FLAKE_SIZE:{MIN:.5,MAX:1.6},MAX_SURFACES:15,COLLISION_CHECK_RATE:.3,MAX_RENDER_DPR:1.25},be=ot(void 0);function it({children:e,initialDebug:o=!1,initialEnabled:r=!0}){let[s,n]=J(r),[i,c]=J(ne),[t,u]=J(o),[l,m]=J(null),b=Q(()=>{n(p=>!p)},[]),h=Q(()=>{u(p=>!p)},[]),d=Q(p=>{c(g=>({...g,...p,ACCUMULATION:{...g.ACCUMULATION,...p.ACCUMULATION||{}},MAX_DEPTH:{...g.MAX_DEPTH,...p.MAX_DEPTH||{}},FLAKE_SIZE:{...g.FLAKE_SIZE,...p.FLAKE_SIZE||{}}}))},[]),a=Q(()=>{c(ne)},[]),f=st(()=>({isEnabled:s,toggleSnow:b,physicsConfig:i,updatePhysicsConfig:d,resetPhysics:a,debugMode:t,toggleDebug:h,metrics:l,setMetrics:m}),[s,b,i,d,a,t,h,l,m]);return ct(be.Provider,{value:f,children:e})}function oe(){let e=rt(be);if(e===void 0)throw new Error("useSnowfall must be used within a SnowfallProvider");return e}var z="data-snowfall",re="ignore";var R="bottom",se="header",ge="footer",ie="banner",pe="contentinfo",_=Math.PI*2,w=4;var Se=[se],Me=[ie],at=[se,ge,"article","section","aside","nav"],lt=[`[role="${ie}"]`,`[role="${pe}"]`,'[role="main"]'],ut=[".card",'[class*="card"]','[class*="Card"]','[class*="bg-"]','[class*="shadow-"]','[class*="rounded-"]'],ft=e=>{let o=e.tagName.toLowerCase();if(Se.includes(o))return R;let r=e.getAttribute("role");return r&&Me.includes(r)?R:"top"},mt=(e,o)=>{if(e.getAttribute(z)===re)return!1;if(e.hasAttribute(z))return!0;let r=o||window.getComputedStyle(e);if(!(r.display!=="none"&&r.visibility!=="hidden"&&parseFloat(r.opacity)>.1))return!1;let n=r.backgroundColor,i=n!=="rgba(0, 0, 0, 0)"&&n!=="transparent",c=parseFloat(r.borderWidth)>0&&r.borderColor!=="transparent"&&r.borderColor!=="rgba(0, 0, 0, 0)"&&r.borderStyle!=="none",t=r.boxShadow!=="none",u=r.filter!=="none"&&r.filter.includes("drop-shadow"),l=r.backdropFilter!=="none";return i||c||t||u||l},we=(e=5)=>{let o=[],r=new Set,s=document.querySelectorAll([`[${z}]`,...at,...lt,...ut].join(", "));for(let n of s){if(o.length>=e)break;if(r.has(n))continue;let i=n.getAttribute(z);if(i===re)continue;let c=i!==null,t=window.getComputedStyle(n);if(!(t.display!=="none"&&t.visibility!=="hidden"&&parseFloat(t.opacity)>.1)&&!c)continue;let l=n.getBoundingClientRect();if(!(l.width>=100&&l.height>=50)&&!c)continue;let b=l.top<=10&&l.height>=window.innerHeight*.9,h=Se.includes(n.tagName.toLowerCase()),d=Me.includes(n.getAttribute("role")||"");if(!(b&&!(h||d||i===R)&&!c)&&mt(n,t)){let f=ft(n);i===R?f=R:i==="top"&&(f="top"),o.push({el:n,type:f}),r.add(n)}}return o},Te=e=>{let o=[];for(let[r,s]of e.entries()){if(!r.isConnected)continue;let n=r.getBoundingClientRect();o.push({el:r,rect:n,acc:s,hasChanged:!1})}return o};var U=512,ye=U-1,ve=U/_,Oe=new Float64Array(U),Fe=new Float64Array(U);for(let e=0;e<U;e++){let o=e/U*_;Oe[e]=Math.sin(o),Fe[e]=Math.cos(o)}var ht=e=>{let o=e<0?e%_+_:e%_;return Oe[(o*ve|0)&ye]},dt=e=>{let o=e<0?e%_+_:e%_;return Fe[(o*ve|0)&ye]},k=64,Le=new Float64Array(k);for(let e=0;e<k;e++){let o=e/(k-1);Le[e]=(Math.cos(o*Math.PI)+1)/2}var Pe=e=>{let o=e*(k-1)+.5|0;return Le[o<0?0:o>=k?k-1:o]},le=e=>e/w|0,Ie=1024,ae=new Map,He=()=>{ae.clear()},bt=e=>e*Ie|0,gt=(e,o,r)=>{let n=((o?Ie:0)+bt(e)).toString(36),i=ae.get(n);if(i)return i;let{MIN:c,MAX:t}=r.FLAKE_SIZE,u=o?{sizeMin:c*.6,sizeRange:(t-c)*.4,speedBase:.2,speedScale:.3,noiseSpeedScale:.2,windScale:r.WIND_STRENGTH*.625,wobbleBase:.005,wobbleScale:.015}:{sizeMin:c,sizeRange:t-c,speedBase:.5,speedScale:.5,noiseSpeedScale:.3,windScale:r.WIND_STRENGTH,wobbleBase:.01,wobbleScale:.02},l=u.sizeMin+e*u.sizeRange,m=e*13%1,b=e*7%1,h=e*5%1;return i={radius:l,speed:l*u.speedScale+m*u.noiseSpeedScale+u.speedBase,wind:(b-.5)*u.windScale,wobbleSpeed:h*u.wobbleScale+u.wobbleBase},ae.set(n,i),i},De=(e,o,r=!1,s=0)=>{let n=Math.random()*e,i=Math.random(),c=gt(i,r,o),t=i*23%1;return{x:n,y:s-5,wobble:t*_,visual:c}},pt=(e,o,r,s=!1)=>{let n=Math.ceil(e/w),i=new Float32Array(n);for(let u=0;u<n;u++){let l=u*w+w/2,m=1;!s&&r>0&&(l<r?m=Math.pow(l/r,1.2):l>e-r&&(m=Math.pow((e-l)/r,1.2))),i[u]=o*m*(.85+Math.random()*.15)}let c=new Float32Array(n),t=4;for(let u=0;u<t;u++){c[0]=i[0],c[n-1]=i[n-1];for(let l=1;l<n-1;l++)c[l]=(i[l-1]+i[l]+i[l+1])/3;i.set(c)}return i},Ae=(e,o,r)=>{let s=Math.ceil(e/w),n=new Float32Array(s);if(o<=0||r)return n;for(let i=0;i<s;i++){let c=i*w+w/2,t=0;if(c<o){let u=o-c;t=o-Math.sqrt(Math.max(0,o*o-u*u))}else if(c>e-o){let u=c-(e-o);t=o-Math.sqrt(Math.max(0,o*o-u*u))}n[i]=t}return n},Ee=e=>{let o=Math.ceil(e/w),r=new Float32Array(o);for(let s=0;s<o;s++){let i=(s*w+w/2)/e;r[s]=Math.sqrt(i)}return r},Ne=(e,o)=>{let r=we(o.MAX_SURFACES);for(let[s]of e.entries())s.isConnected||e.delete(s);for(let{el:s,type:n}of r){let i=s.getBoundingClientRect(),c=window.getComputedStyle(s),t=e.get(s),u=Math.ceil(i.width),l=n===R,m=Math.ceil(u/w);if(t&&t.heights.length===m){if(t.type=n,t.borderRadius!==void 0){t.borderRadius=parseFloat(c.borderTopLeftRadius)||0,t.curveOffsets=Ae(u,t.borderRadius,l);let p=Math.ceil(Math.ceil(i.height)/w);t.leftSide.length===p&&!t.sideGravityMultipliers.length&&(t.sideGravityMultipliers=Ee(Math.ceil(i.height)))}continue}let b=Math.ceil(i.height),h=l?o.MAX_DEPTH.BOTTOM:o.MAX_DEPTH.TOP,d=parseFloat(c.borderTopLeftRadius)||0,a=Math.ceil(b/w),f=pt(u,h,d,l);e.set(s,{heights:t?.heights.length===m?t.heights:new Float32Array(m),maxHeights:f,leftSide:t?.leftSide.length===a?t.leftSide:new Float32Array(a),rightSide:t?.rightSide.length===a?t.rightSide:new Float32Array(a),maxSideHeight:l?0:o.MAX_DEPTH.SIDE,leftMax:t?.leftSide.length===a?t.leftMax:0,rightMax:t?.rightSide.length===a?t.rightMax:0,maxHeight:t?.maxHeight||0,borderRadius:d,curveOffsets:Ae(u,d,l),sideGravityMultipliers:Ee(b),type:n,_smoothTemp:t?._smoothTemp??new Float32Array(0),dirtyMin:t?.dirtyMin??m,dirtyMax:t?.dirtyMax??0,bucketSize:w,_cacheCanvas:t?._cacheCanvas??null,_cacheCtx:t?._cacheCtx??null,_cacheMaxHeight:t?._cacheMaxHeight??-1})}},St=(e,o)=>{o<e.dirtyMin&&(e.dirtyMin=o),o>e.dirtyMax&&(e.dirtyMax=o)},Ce=(e,o,r,s,n,i,c)=>{let t=Math.max(1,Math.ceil(4/w)),u=le(r),l=i*(.8+Math.random()*.4),m=c;for(let b=-t;b<=t;b++){let h=u+b;if(h>=0&&h<e.length){let d=h*w+w/2,a=d<n,f=d>o-n;if(n>0&&(a||f))continue;let p=Math.abs(b)/t,g=Pe(p),S=Math.min(s,e[h]+l*g);e[h]=S,S>m&&(m=S)}}return m},Mt=(e,o)=>{let{visual:r}=e;e.wobble+=r.wobbleSpeed*o,e.x+=(r.wind+ht(e.wobble)*.5)*o,e.y+=(r.speed+dt(e.wobble*.5)*.1)*o},wt=(e,o,r,s,n)=>{if(!(o>=r.top&&o<=r.bottom)||s.maxSideHeight<=0)return!1;let c=Math.floor(o-r.top),t=s.borderRadius,l=le(c)*w+w/2,m=l<t,b=l>r.height-t;return t>0&&(m||b)?!1:e>=r.left-5&&e<r.left+3?(s.leftMax=Ce(s.leftSide,r.height,c,s.maxSideHeight,t,n,s.leftMax),!0):e>r.right-3&&e<=r.right+5?(s.rightMax=Ce(s.rightSide,r.height,c,s.maxSideHeight,t,n,s.rightMax),!0):!1},Tt=(e,o,r,s,n,i,c)=>{if(o<s.left||o>s.right)return!1;let t=Math.floor(o-s.left),u=le(t);if(u<0||u>=n.heights.length)return!1;let l=n.heights[u],m=n.maxHeights[u],b=i?s.bottom-l:s.top-l;if(r<b||r>=b+10||l>=m||i&&Math.random()>=.15)return!1;let h=Math.max(1,Math.ceil(e.visual.radius/w)),d=Math.random(),a=Math.random(),f=h+(d<.5?0:1),p=i?c.ACCUMULATION.BOTTOM_RATE:c.ACCUMULATION.TOP_RATE,g=(a*3|0)-1;for(let S=-f;S<=f;S++){if(Math.random()<.15)continue;let M=u+S+g;if(M>=0&&M<n.heights.length){let A=Math.abs(S),L=n.maxHeights[M],C=A/f,E=.3*Pe(C),v=.8+(d+S*.1)%1*.4,D=E*v*p;if(n.heights[M]<L&&D>0){let P=Math.min(L,n.heights[M]+D);n.heights[M]=P,P>n.maxHeight&&(n.maxHeight=P),St(n,M)}}}return!0},At=(e,o,r,s)=>o||e.y>s+10||e.x<-20||e.x>r+20,Et=e=>{let o=1/0,r=-1/0,s=1/0,n=-1/0;for(let i=0;i<e.length;i++){let c=e[i].rect;c.top<o&&(o=c.top),c.bottom>r&&(r=c.bottom),c.left<s&&(s=c.left),c.right>n&&(n=c.right)}return{top:o,bottom:r,left:s,right:n}},xe=60,_e=10,Be=(e,o,r,s,n,i,c,t,u=0)=>{let l=Math.max(1,Math.round(1/r.COLLISION_CHECK_RATE)),m=r.ACCUMULATION.SIDE_RATE,b=Et(o),h=b.top-xe,d=b.bottom+xe,a=b.left-_e,f=b.right+_e,p=e.length;for(;p-- >0;){let g=e[p];Mt(g,s);let S=!1;if(p%l===u%l){let M=g.x-c,A=g.y-t;if(A>=h&&A<=d&&M>=a&&M<=f)for(let C of o){let{rect:y,acc:E}=C,v=E.type===R;if(!S&&!v&&(S=wt(M,A,y,E,m),S)){C.hasChanged=!0;break}if(!S&&(S=Tt(g,M,A,y,E,v,r),S)){C.hasChanged=!0;break}}}if(At(g,S,n,i)){let M=e.length-1;p<M&&(e[p]=e[M]),e.length=M}}},Ct=4,Re=3,xt=6,ke=(e,o,r,s=0)=>{let n=s%Re===0,i=s%Ct===0,c=n?o.MELT_SPEED*r*Re:0;for(let{acc:t}of e){let u=t.heights.length;if(t.dirtyMin>t.dirtyMax)continue;let l=Math.max(1,t.dirtyMin),m=Math.min(u-2,t.dirtyMax),b=m-l+1,h=i&&b>=xt,d=0;if(h&&c>0)for(let a=l;a<=m;a++){let f=t.heights[a];if(f>.05){let p=(t.heights[a-1]+t.heights[a+1])/2,g=f*.99+p*.01;g-=c,g<0&&(g=0),t.heights[a]=g,g>d&&(d=g)}else f>0&&(t.heights[a]=0)}else if(c>0)for(let a=l;a<=m;a++){let f=t.heights[a];if(f>0){let p=f-c,g=p>0?p:0;t.heights[a]=g,g>d&&(d=g)}}else if(h)for(let a=l;a<=m;a++){let f=t.heights[a];if(f>.05){let p=(t.heights[a-1]+t.heights[a+1])/2,g=f*.99+p*.01;t.heights[a]=g,g>d&&(d=g)}}else for(let a=l;a<=m;a++)t.heights[a]>d&&(d=t.heights[a]);for(let a=0;a<Math.min(l,u);a++)t.heights[a]>d&&(d=t.heights[a]);for(let a=Math.max(m+1,0);a<u;a++)t.heights[a]>d&&(d=t.heights[a]);if(t.maxHeight=d,n&&(t.leftMax>0||t.rightMax>0)){let a=0,f=0,p=t.leftSide.length;for(let g=0;g<p;g++){let S=t.leftSide[g];if(S>0){let A=S>c?S-c:0;t.leftSide[g]=A,A>a&&(a=A)}let M=t.rightSide[g];if(M>0){let A=M>c?M-c:0;t.rightSide[g]=A,A>f&&(f=A)}}t.leftMax=a,t.rightMax=f}if(n){let a=t.dirtyMin,f=t.dirtyMax;for(;a<=f&&t.heights[a]<=.05;)a++;for(;f>=a&&t.heights[f]<=.05;)f--;t.dirtyMin=a,t.dirtyMax=f}}};import{useRef as j,useCallback as ue}from"react";function Ue(){let e=j(0),o=j(0),r=j(0),s=j({scanTime:0,rectUpdateTime:0,frameTime:0,rafGap:0,clearTime:0,physicsTime:0,drawTime:0}),n=ue(t=>{let u=Math.floor(t/1e3);u!==e.current?(r.current=o.current,o.current=1,e.current=u):o.current++},[]),i=ue(()=>r.current||o.current,[]),c=ue((t,u,l)=>({fps:r.current||o.current,frameTime:s.current.frameTime,scanTime:s.current.scanTime,rectUpdateTime:s.current.rectUpdateTime,surfaceCount:t,flakeCount:u,maxFlakes:l,rafGap:s.current.rafGap,clearTime:s.current.clearTime,physicsTime:s.current.physicsTime,drawTime:s.current.drawTime}),[]);return{metricsRef:s,updateFps:n,getCurrentFps:i,buildMetrics:c}}import{useRef as Xe,useCallback as _t}from"react";function Ge(){let e=Xe(null),o=Xe(1),r=_t((s=Number.POSITIVE_INFINITY)=>{if(e.current){let n=window.innerWidth,i=window.innerHeight,c=Math.min(window.devicePixelRatio||1,Math.max(1,s));o.current=c,e.current.width=n*c,e.current.height=i*c,e.current.style.width=`${n}px`,e.current.style.height=`${i}px`}},[]);return{canvasRef:e,dprRef:o,resizeCanvas:r}}import{useEffect as Ye,useRef as x,useCallback as ee}from"react";var Ve="rgba(255, 255, 255, 0.95)",Rt=.5,V=3,ze=(e,o,r,s,n,i,c,t)=>{let l=(r?n.left:n.right)+i,m=n.top+c,b=n.bottom+c,h=r?-1:1,d=o.length*t;e.moveTo(l,m);for(let f=0;f<d;f+=V){let p=F(o,f,t),g=F(s,f,t);e.lineTo(l+p*g*h,m+f)}let a=d-1;if(a%V!==0){let f=F(o,a,t),p=F(s,a,t);e.lineTo(l+f*p*h,m+a)}e.lineTo(l,b)},Ke=(e,o)=>{if(o.length!==0){e.globalAlpha=1,e.fillStyle="#FFFFFF",e.beginPath();for(let r=0,s=o.length;r<s;r++){let n=o[r],i=n.visual.radius;i<2?e.rect(n.x-i,n.y-i,i*2,i*2):(e.moveTo(n.x+i,n.y),e.arc(n.x,n.y,i,0,_))}e.fill()}},F=(e,o,r)=>{let s=o/r,n=s|0;if(n>=e.length-1)return e[e.length-1]||0;let i=s-n;return e[n]*(1-i)+e[n+1]*i},yt=(e,o,r)=>{let s=Math.ceil(r)+10;if(e._cacheCanvas&&e._cacheCtx)(e._cacheCanvas.width<o||e._cacheCanvas.height<s)&&(e._cacheCanvas.width=o,e._cacheCanvas.height=s);else{let n=document.createElement("canvas");n.width=o,n.height=s;let i=n.getContext("2d");if(!i)throw new Error("Failed to create cache canvas context");e._cacheCanvas=n,e._cacheCtx=i}return e._cacheCtx},vt=(e,o,r)=>{let s=e.maxHeight,n=yt(e,o,s),i=e._cacheCanvas,c=i.width,t=i.height;n.clearRect(0,0,c,t);let u=e.bucketSize,l=Math.ceil(s)+5;n.beginPath();let m=!0;for(let h=0;h<o;h+=V){let d=F(e.heights,h,u),a=F(e.curveOffsets,h,u),f=l-d+a;m?(n.moveTo(h,f),m=!1):n.lineTo(h,f)}let b=o-1;if(b%V!==0){let h=F(e.heights,b,u),d=F(e.curveOffsets,b,u);n.lineTo(b,l-h+d)}for(let h=o-1;h>=0;h-=V){let d=F(e.curveOffsets,h,u);n.lineTo(h,l+d)}n.closePath(),n.fillStyle=Ve,n.shadowColor="rgba(200, 230, 255, 0.6)",n.shadowBlur=4,n.shadowOffsetY=r?1:-1,n.fill(),n.shadowColor="transparent",n.shadowBlur=0,n.shadowOffsetY=0,e._cacheMaxHeight=s},We=(e,o,r,s)=>{e.globalAlpha=1;for(let n of o){let{rect:i,acc:c}=n;if(c.maxHeight<=.1)continue;let t=c.type===R,u=c.heights.length*c.bucketSize;(!c._cacheCanvas||Math.abs(c.maxHeight-c._cacheMaxHeight)>Rt)&&vt(c,u,t);let l=c._cacheCanvas;if(!l)continue;let m=Math.ceil(c._cacheMaxHeight)+5,b=i.left+r;if(t){let h=i.bottom+s-m;e.drawImage(l,b,h)}else{let h=i.top+s-m;e.drawImage(l,b,h)}}},qe=(e,o,r,s)=>{e.globalAlpha=1,e.beginPath();let n=!1;for(let i of o){let{rect:c,acc:t}=i;if(t.maxSideHeight===0)continue;let u=t.leftMax>.3,l=t.rightMax>.3;!u&&!l||(u&&(ze(e,t.leftSide,!0,t.sideGravityMultipliers,c,r,s,t.bucketSize),n=!0),l&&(ze(e,t.rightSide,!1,t.sideGravityMultipliers,c,r,s,t.bucketSize),n=!0))}n&&(e.fillStyle=Ve,e.fill())};function Ze(e){let o=x(0),r=x(()=>{}),s=x(0),n=x(0),i=x([]),c=x([]),t=x(!0),u=x(0),l=x({width:0,height:0}),m=x({width:0,height:0}),b=x(null),h=x(1),d=x(e);Ye(()=>{d.current=e});let a=ee(S=>{let{canvasRef:M,dprRef:A,snowflakesRef:L,accumulationRef:C,isEnabledRef:y,physicsConfigRef:E,metricsRef:v,updateFps:D,getCurrentFps:P,buildMetrics:q,setMetricsRef:Y}=d.current,O=M.current;if(!O){o.current=requestAnimationFrame(r.current);return}let T=b.current||O.getContext("2d");if(!T){o.current=requestAnimationFrame(r.current);return}if(b.current||(b.current=T),s.current===0){s.current=S,o.current=requestAnimationFrame(r.current);return}let I=Math.min(S-s.current,50);D(S),v.current.rafGap=S-s.current,s.current=S;let te=I/16.67,H=u.current++,fe=A.current,me=h.current;T.setTransform(fe,0,0,fe,0,0),T.clearRect(0,0,O.width*me,O.height*me);let Z=window.scrollX,X=window.scrollY;T.translate(-Z,-X);let he=l.current.width,Qe=l.current.height,je=m.current.width,et=m.current.height,N=L.current;if(t.current&&(i.current=Te(C.current),t.current=!1),ke(i.current,E.current,te,H),Be(N,i.current,E.current,te,he,Qe,Z,X,H),Ke(T,N),y.current&&N.length<E.current.MAX_FLAKES){let $=Math.min(80,E.current.MAX_FLAKES),B=N.length<$,de=P(),tt=document.visibilityState==="visible"&&de>0&&de<40;if(B||!tt||Math.random()<.2){let nt=Math.random()<.4;N.push(De(he,E.current,nt,X))}}let G=c.current;G.length=0;for(let $ of i.current){let{rect:B}=$;B.right>=0&&B.left<=je&&B.bottom>=0&&B.top<=et&&G.push($)}G.length>0&&(We(T,G,Z,X),qe(T,G,Z,X)),S-n.current>500&&(Y.current(q(C.current.size,N.length,E.current.MAX_FLAKES)),n.current=S),o.current=requestAnimationFrame(r.current)},[]);Ye(()=>{r.current=a},[a]);let f=ee(()=>{s.current=0,n.current=0,o.current=requestAnimationFrame(r.current)},[]),p=ee(()=>{cancelAnimationFrame(o.current)},[]),g=ee(()=>{t.current=!0,l.current.width=document.documentElement.scrollWidth,l.current.height=document.documentElement.scrollHeight,m.current.width=window.innerWidth,m.current.height=window.innerHeight,h.current=1/(d.current.dprRef.current||1)},[]);return{start:f,stop:p,markRectsDirty:g}}import{jsx as Ot}from"react/jsx-runtime";function Je(){let{isEnabled:e,physicsConfig:o,setMetrics:r}=oe(),s=W(e),n=W(o),i=W(r),[c,t]=$e(!1),[u,l]=$e(!1),m=W([]),b=W(new Map),{canvasRef:h,dprRef:d,resizeCanvas:a}=Ge(),{metricsRef:f,updateFps:p,getCurrentFps:g,buildMetrics:S}=Ue(),{start:M,stop:A,markRectsDirty:L}=Ze({canvasRef:h,dprRef:d,snowflakesRef:m,accumulationRef:b,isEnabledRef:s,physicsConfigRef:n,metricsRef:f,updateFps:p,getCurrentFps:g,buildMetrics:S,setMetricsRef:i});return K(()=>{requestAnimationFrame(()=>t(!0))},[]),K(()=>{s.current=e},[e]),K(()=>{n.current=o,He(),m.current=[],c&&a(o.MAX_RENDER_DPR)},[c,o,a]),K(()=>{i.current=r},[r]),K(()=>{if(!c)return;let C=h.current;if(!C)return;a(n.current.MAX_RENDER_DPR),m.current=[];let y=!1,E=0,v=new ResizeObserver(O=>{let T=!1;for(let I of O)if(I.target.isConnected){T=!0;break}T&&P()}),D=()=>{if(y)return;let O=performance.now();Ne(b.current,n.current),v.disconnect();for(let[T]of b.current)v.observe(T);f.current.scanTime=performance.now()-O,L()},P=()=>{E!==0||y||(E=requestAnimationFrame(()=>{E=0,D()}))};D(),requestAnimationFrame(()=>{!y&&c&&l(!0)}),M();let q=()=>{a(n.current.MAX_RENDER_DPR),L()};window.addEventListener("resize",q);let Y=new MutationObserver(O=>{let T=!1;for(let I of O)if(!(I.type!=="childList"||I.target===C)){for(let H of I.addedNodes)if(H.nodeType===Node.ELEMENT_NODE&&H!==C){T=!0;break}if(T)break;for(let H of I.removedNodes)if(H.nodeType===Node.ELEMENT_NODE&&H!==C){T=!0;break}if(T)break}T&&P()});return Y.observe(document.body,{childList:!0,subtree:!0}),()=>{y=!0,E!==0&&cancelAnimationFrame(E),A(),window.removeEventListener("resize",q),Y.disconnect(),v.disconnect()}},[c]),c?Ot("canvas",{ref:h,style:{position:"fixed",top:0,left:0,pointerEvents:"none",zIndex:9999,opacity:u?1:0,transition:u?void 0:"opacity 0.3s ease-in",willChange:u?void 0:"opacity"},"aria-hidden":"true"}):null}export{ne as DEFAULT_PHYSICS,Je as Snowfall,it as SnowfallProvider,oe as useSnowfall};
|
|
1201
2
|
//# sourceMappingURL=index.mjs.map
|