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