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