@hdcodedev/snowfall 1.0.1 → 1.0.2

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.
Files changed (49) hide show
  1. package/dist/index.d.mts +38 -4
  2. package/dist/index.d.ts +38 -4
  3. package/dist/index.js +661 -16
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +656 -3
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +1 -1
  8. package/dist/Snowfall.d.mts +0 -5
  9. package/dist/Snowfall.d.ts +0 -5
  10. package/dist/Snowfall.js +0 -162
  11. package/dist/Snowfall.js.map +0 -1
  12. package/dist/Snowfall.mjs +0 -142
  13. package/dist/Snowfall.mjs.map +0 -1
  14. package/dist/SnowfallProvider.d.mts +0 -36
  15. package/dist/SnowfallProvider.d.ts +0 -36
  16. package/dist/SnowfallProvider.js +0 -97
  17. package/dist/SnowfallProvider.js.map +0 -1
  18. package/dist/SnowfallProvider.mjs +0 -71
  19. package/dist/SnowfallProvider.mjs.map +0 -1
  20. package/dist/utils/snowfall/constants.d.mts +0 -10
  21. package/dist/utils/snowfall/constants.d.ts +0 -10
  22. package/dist/utils/snowfall/constants.js +0 -50
  23. package/dist/utils/snowfall/constants.js.map +0 -1
  24. package/dist/utils/snowfall/constants.mjs +0 -19
  25. package/dist/utils/snowfall/constants.mjs.map +0 -1
  26. package/dist/utils/snowfall/dom.d.mts +0 -11
  27. package/dist/utils/snowfall/dom.d.ts +0 -11
  28. package/dist/utils/snowfall/dom.js +0 -139
  29. package/dist/utils/snowfall/dom.js.map +0 -1
  30. package/dist/utils/snowfall/dom.mjs +0 -122
  31. package/dist/utils/snowfall/dom.mjs.map +0 -1
  32. package/dist/utils/snowfall/draw.d.mts +0 -7
  33. package/dist/utils/snowfall/draw.d.ts +0 -7
  34. package/dist/utils/snowfall/draw.js +0 -160
  35. package/dist/utils/snowfall/draw.js.map +0 -1
  36. package/dist/utils/snowfall/draw.mjs +0 -134
  37. package/dist/utils/snowfall/draw.mjs.map +0 -1
  38. package/dist/utils/snowfall/physics.d.mts +0 -11
  39. package/dist/utils/snowfall/physics.d.ts +0 -11
  40. package/dist/utils/snowfall/physics.js +0 -239
  41. package/dist/utils/snowfall/physics.js.map +0 -1
  42. package/dist/utils/snowfall/physics.mjs +0 -212
  43. package/dist/utils/snowfall/physics.mjs.map +0 -1
  44. package/dist/utils/snowfall/types.d.mts +0 -30
  45. package/dist/utils/snowfall/types.d.ts +0 -30
  46. package/dist/utils/snowfall/types.js +0 -17
  47. package/dist/utils/snowfall/types.js.map +0 -1
  48. package/dist/utils/snowfall/types.mjs +0 -1
  49. package/dist/utils/snowfall/types.mjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,10 +1,8 @@
1
1
  "use strict";
2
2
  "use client";
3
- var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
5
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
7
  var __export = (target, all) => {
10
8
  for (var name in all)
@@ -18,25 +16,672 @@ var __copyProps = (to, from, except, desc) => {
18
16
  }
19
17
  return to;
20
18
  };
21
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
- // If the importer is in node compatibility mode or this is not an ESM
23
- // file that has been converted to a CommonJS file using a Babel-
24
- // compatible transform (i.e. "__esModule" has not been set), then set
25
- // "default" to the CommonJS "module.exports" for node compatibility.
26
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
- mod
28
- ));
29
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
30
22
  var index_exports = {};
31
23
  __export(index_exports, {
32
- DEFAULT_PHYSICS: () => import_SnowfallProvider.DEFAULT_PHYSICS,
33
- Snowfall: () => import_Snowfall.default,
34
- SnowfallProvider: () => import_SnowfallProvider.SnowfallProvider,
35
- useSnowfall: () => import_SnowfallProvider.useSnowfall
24
+ DEFAULT_PHYSICS: () => DEFAULT_PHYSICS,
25
+ Snowfall: () => Snowfall,
26
+ SnowfallProvider: () => SnowfallProvider,
27
+ useSnowfall: () => useSnowfall
36
28
  });
37
29
  module.exports = __toCommonJS(index_exports);
38
- var import_Snowfall = __toESM(require("./Snowfall"));
39
- var import_SnowfallProvider = require("./SnowfallProvider");
30
+
31
+ // src/Snowfall.tsx
32
+ var import_react2 = require("react");
33
+
34
+ // src/SnowfallProvider.tsx
35
+ var import_react = require("react");
36
+ var import_jsx_runtime = require("react/jsx-runtime");
37
+ var DEFAULT_PHYSICS = {
38
+ MAX_FLAKES: 500,
39
+ MELT_SPEED: 5e-5,
40
+ WIND_STRENGTH: 0.8,
41
+ ACCUMULATION: {
42
+ SIDE_RATE: 1.2,
43
+ TOP_RATE: 1.9,
44
+ BOTTOM_RATE: 1.2
45
+ },
46
+ MAX_DEPTH: {
47
+ TOP: 50,
48
+ BOTTOM: 25,
49
+ SIDE: 8
50
+ },
51
+ FLAKE_SIZE: {
52
+ MIN: 0.5,
53
+ MAX: 2.5
54
+ }
55
+ };
56
+ var SnowfallContext = (0, import_react.createContext)(void 0);
57
+ function SnowfallProvider({ children }) {
58
+ const [isEnabled, setIsEnabled] = (0, import_react.useState)(true);
59
+ const [physicsConfig, setPhysicsConfig] = (0, import_react.useState)(DEFAULT_PHYSICS);
60
+ const toggleSnow = () => {
61
+ setIsEnabled((prev) => !prev);
62
+ };
63
+ const updatePhysicsConfig = (config) => {
64
+ setPhysicsConfig((prev) => ({
65
+ ...prev,
66
+ ...config,
67
+ ACCUMULATION: {
68
+ ...prev.ACCUMULATION,
69
+ ...config.ACCUMULATION || {}
70
+ },
71
+ MAX_DEPTH: {
72
+ ...prev.MAX_DEPTH,
73
+ ...config.MAX_DEPTH || {}
74
+ },
75
+ FLAKE_SIZE: {
76
+ ...prev.FLAKE_SIZE,
77
+ ...config.FLAKE_SIZE || {}
78
+ }
79
+ }));
80
+ };
81
+ const resetPhysics = () => {
82
+ setPhysicsConfig(DEFAULT_PHYSICS);
83
+ };
84
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SnowfallContext.Provider, { value: {
85
+ isEnabled,
86
+ toggleSnow,
87
+ physicsConfig,
88
+ updatePhysicsConfig,
89
+ resetPhysics
90
+ }, children });
91
+ }
92
+ function useSnowfall() {
93
+ const context = (0, import_react.useContext)(SnowfallContext);
94
+ if (context === void 0) {
95
+ throw new Error("useSnowfall must be used within a SnowfallProvider");
96
+ }
97
+ return context;
98
+ }
99
+
100
+ // src/utils/snowfall/constants.ts
101
+ var ATTR_SNOWFALL = "data-snowfall";
102
+ var VAL_IGNORE = "ignore";
103
+ var VAL_TOP = "top";
104
+ var VAL_BOTTOM = "bottom";
105
+ var TAG_HEADER = "header";
106
+ var TAG_FOOTER = "footer";
107
+ var ROLE_BANNER = "banner";
108
+ var ROLE_CONTENTINFO = "contentinfo";
109
+
110
+ // src/utils/snowfall/dom.ts
111
+ var BOTTOM_TAGS = [TAG_HEADER, TAG_FOOTER];
112
+ var BOTTOM_ROLES = [ROLE_BANNER, ROLE_CONTENTINFO];
113
+ var AUTO_DETECT_TAGS = ["header", "footer", "article", "section", "aside", "nav"];
114
+ var AUTO_DETECT_ROLES = ['[role="banner"]', '[role="contentinfo"]', '[role="main"]'];
115
+ var AUTO_DETECT_CLASSES = [
116
+ ".card",
117
+ '[class*="card"]',
118
+ '[class*="Card"]',
119
+ '[class*="bg-"]',
120
+ '[class*="shadow-"]',
121
+ '[class*="rounded-"]'
122
+ ];
123
+ var getElementType = (el) => {
124
+ const tagName = el.tagName.toLowerCase();
125
+ if (BOTTOM_TAGS.includes(tagName)) return VAL_BOTTOM;
126
+ const role = el.getAttribute("role");
127
+ if (role && BOTTOM_ROLES.includes(role)) return VAL_BOTTOM;
128
+ return VAL_TOP;
129
+ };
130
+ var shouldAccumulate = (el) => {
131
+ if (el.getAttribute(ATTR_SNOWFALL) === VAL_IGNORE) return false;
132
+ if (el.hasAttribute(ATTR_SNOWFALL)) return true;
133
+ const styles = window.getComputedStyle(el);
134
+ const rect = el.getBoundingClientRect();
135
+ const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
136
+ if (!isVisible) return false;
137
+ const bgColor = styles.backgroundColor;
138
+ const hasBackground = bgColor !== "rgba(0, 0, 0, 0)" && bgColor !== "transparent";
139
+ const hasBorder = parseFloat(styles.borderWidth) > 0 || styles.borderStyle !== "none";
140
+ const hasBoxShadow = styles.boxShadow !== "none";
141
+ const hasBorderRadius = parseFloat(styles.borderRadius) > 0;
142
+ return hasBackground || hasBorder || hasBoxShadow || hasBorderRadius;
143
+ };
144
+ var getAccumulationSurfaces = () => {
145
+ const surfaces = [];
146
+ const seen = /* @__PURE__ */ new Set();
147
+ const candidates = document.querySelectorAll(
148
+ [
149
+ `[${ATTR_SNOWFALL}]`,
150
+ ...AUTO_DETECT_TAGS,
151
+ ...AUTO_DETECT_ROLES,
152
+ ...AUTO_DETECT_CLASSES
153
+ ].join(", ")
154
+ );
155
+ candidates.forEach((el) => {
156
+ if (seen.has(el)) return;
157
+ const rect = el.getBoundingClientRect();
158
+ const manualOverride = el.getAttribute(ATTR_SNOWFALL);
159
+ if (manualOverride === VAL_IGNORE) return;
160
+ const isManuallyIncluded = manualOverride !== null;
161
+ const styles = window.getComputedStyle(el);
162
+ const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
163
+ if (!isVisible && !isManuallyIncluded) return;
164
+ const hasSize = rect.width >= 100 && rect.height >= 50;
165
+ if (!hasSize && !isManuallyIncluded) return;
166
+ const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;
167
+ const isBottomTag = BOTTOM_TAGS.includes(el.tagName.toLowerCase());
168
+ const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute("role") || "");
169
+ const isBottomSurface = isBottomTag || isBottomRole || manualOverride === VAL_BOTTOM;
170
+ if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) return;
171
+ let isFixed = false;
172
+ let currentEl = el;
173
+ while (currentEl && currentEl !== document.body) {
174
+ const style = window.getComputedStyle(currentEl);
175
+ if (style.position === "fixed" || style.position === "sticky") {
176
+ isFixed = true;
177
+ break;
178
+ }
179
+ currentEl = currentEl.parentElement;
180
+ }
181
+ if (shouldAccumulate(el)) {
182
+ let type = getElementType(el);
183
+ if (manualOverride === VAL_BOTTOM) {
184
+ type = VAL_BOTTOM;
185
+ } else if (manualOverride === VAL_TOP) {
186
+ type = VAL_TOP;
187
+ }
188
+ surfaces.push({ el, type, isFixed });
189
+ seen.add(el);
190
+ }
191
+ });
192
+ console.log(`[Snowfall] Auto-detection found ${surfaces.length} surfaces`);
193
+ return surfaces;
194
+ };
195
+ var getElementRects = (accumulationMap) => {
196
+ const elementRects = [];
197
+ for (const [el, acc] of accumulationMap.entries()) {
198
+ if (!el.isConnected) continue;
199
+ const rect = el.getBoundingClientRect();
200
+ const absoluteRect = {
201
+ left: rect.left + window.scrollX,
202
+ right: rect.right + window.scrollX,
203
+ top: rect.top + window.scrollY,
204
+ bottom: rect.bottom + window.scrollY,
205
+ width: rect.width,
206
+ height: rect.height,
207
+ x: rect.x,
208
+ // Note: these are strictly viewport relative in DOMRect usually,
209
+ // but we just need consistent absolute coords for physics
210
+ y: rect.y,
211
+ toJSON: rect.toJSON
212
+ };
213
+ elementRects.push({ el, rect: absoluteRect, acc });
214
+ }
215
+ return elementRects;
216
+ };
217
+
218
+ // src/utils/snowfall/physics.ts
219
+ var createSnowflake = (canvasWidth, config, isBackground = false) => {
220
+ if (isBackground) {
221
+ const sizeRatio = Math.random();
222
+ const radius = config.FLAKE_SIZE.MIN * 0.6 + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN) * 0.4;
223
+ return {
224
+ x: Math.random() * canvasWidth,
225
+ y: window.scrollY - 5,
226
+ radius,
227
+ speed: radius * 0.3 + Math.random() * 0.2 + 0.2,
228
+ wind: (Math.random() - 0.5) * (config.WIND_STRENGTH * 0.625),
229
+ opacity: Math.random() * 0.2 + 0.2,
230
+ wobble: Math.random() * Math.PI * 2,
231
+ wobbleSpeed: Math.random() * 0.015 + 5e-3,
232
+ sizeRatio,
233
+ isBackground: true
234
+ };
235
+ } else {
236
+ const sizeRatio = Math.random();
237
+ const radius = config.FLAKE_SIZE.MIN + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN);
238
+ return {
239
+ x: Math.random() * canvasWidth,
240
+ y: window.scrollY - 5,
241
+ radius,
242
+ speed: radius * 0.5 + Math.random() * 0.3 + 0.5,
243
+ wind: (Math.random() - 0.5) * config.WIND_STRENGTH,
244
+ opacity: Math.random() * 0.3 + 0.5,
245
+ wobble: Math.random() * Math.PI * 2,
246
+ wobbleSpeed: Math.random() * 0.02 + 0.01,
247
+ sizeRatio,
248
+ isBackground: false
249
+ };
250
+ }
251
+ };
252
+ var initializeAccumulation = (accumulationMap, config) => {
253
+ const elements = getAccumulationSurfaces();
254
+ for (const [el] of accumulationMap.entries()) {
255
+ if (!el.isConnected) {
256
+ accumulationMap.delete(el);
257
+ }
258
+ }
259
+ elements.forEach(({ el, type, isFixed }) => {
260
+ const existing = accumulationMap.get(el);
261
+ const rect = el.getBoundingClientRect();
262
+ const width = Math.ceil(rect.width);
263
+ const isBottom = type === VAL_BOTTOM;
264
+ if (existing && existing.heights.length === width) {
265
+ existing.type = type;
266
+ existing.isFixed = isFixed;
267
+ if (existing.borderRadius !== void 0) {
268
+ const styleBuffer = window.getComputedStyle(el);
269
+ existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;
270
+ }
271
+ return;
272
+ }
273
+ const height = Math.ceil(rect.height);
274
+ const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;
275
+ const styles = window.getComputedStyle(el);
276
+ const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;
277
+ let maxHeights = new Array(width);
278
+ for (let i = 0; i < width; i++) {
279
+ let edgeFactor = 1;
280
+ if (!isBottom && borderRadius > 0) {
281
+ if (i < borderRadius) {
282
+ edgeFactor = Math.pow(i / borderRadius, 1.2);
283
+ } else if (i > width - borderRadius) {
284
+ edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
285
+ }
286
+ }
287
+ maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
288
+ }
289
+ const smoothPasses = 4;
290
+ for (let p = 0; p < smoothPasses; p++) {
291
+ const smoothed = [...maxHeights];
292
+ for (let i = 1; i < width - 1; i++) {
293
+ smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
294
+ }
295
+ maxHeights = smoothed;
296
+ }
297
+ accumulationMap.set(el, {
298
+ heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),
299
+ maxHeights,
300
+ leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),
301
+ rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
302
+ maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
303
+ borderRadius,
304
+ type,
305
+ isFixed
306
+ });
307
+ });
308
+ };
309
+ var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius, config) => {
310
+ const spread = 4;
311
+ const addHeight = config.ACCUMULATION.SIDE_RATE * (0.8 + Math.random() * 0.4);
312
+ for (let dy = -spread; dy <= spread; dy++) {
313
+ const y = localY + dy;
314
+ if (y >= 0 && y < sideArray.length) {
315
+ const inTop = y < borderRadius;
316
+ const inBottom = y > rectHeight - borderRadius;
317
+ if (borderRadius > 0 && (inTop || inBottom)) continue;
318
+ const normalizedDist = Math.abs(dy) / spread;
319
+ const falloff = (Math.cos(normalizedDist * Math.PI) + 1) / 2;
320
+ sideArray[y] = Math.min(maxSideHeight, sideArray[y] + addHeight * falloff);
321
+ }
322
+ }
323
+ };
324
+ var updateSnowflakes = (snowflakes, elementRects, config, dt, canvasWidth, canvasHeight) => {
325
+ for (let i = snowflakes.length - 1; i >= 0; i--) {
326
+ const flake = snowflakes[i];
327
+ flake.wobble += flake.wobbleSpeed * dt;
328
+ flake.x += (flake.wind + Math.sin(flake.wobble) * 0.5) * dt;
329
+ flake.y += (flake.speed + Math.cos(flake.wobble * 0.5) * 0.1) * dt;
330
+ let landed = false;
331
+ for (const { rect, acc } of elementRects) {
332
+ const isBottom = acc.type === VAL_BOTTOM;
333
+ if (!landed && acc.maxSideHeight > 0 && !isBottom) {
334
+ const isInVerticalBounds = flake.y >= rect.top && flake.y <= rect.bottom;
335
+ if (isInVerticalBounds) {
336
+ const localY = Math.floor(flake.y - rect.top);
337
+ const borderRadius = acc.borderRadius;
338
+ const isInTopCorner = localY < borderRadius;
339
+ const isInBottomCorner = localY > rect.height - borderRadius;
340
+ const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);
341
+ if (flake.x >= rect.left - 5 && flake.x < rect.left + 3) {
342
+ if (!isCorner) {
343
+ accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
344
+ landed = true;
345
+ }
346
+ }
347
+ if (!landed && flake.x > rect.right - 3 && flake.x <= rect.right + 5) {
348
+ if (!isCorner) {
349
+ accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
350
+ landed = true;
351
+ }
352
+ }
353
+ if (landed) break;
354
+ }
355
+ }
356
+ if (flake.x >= rect.left && flake.x <= rect.right) {
357
+ const localX = Math.floor(flake.x - rect.left);
358
+ const currentHeight = acc.heights[localX] || 0;
359
+ const maxHeight = acc.maxHeights[localX] || 5;
360
+ const surfaceY = isBottom ? rect.bottom - currentHeight : rect.top - currentHeight;
361
+ if (flake.y >= surfaceY && flake.y < surfaceY + 10 && currentHeight < maxHeight) {
362
+ const shouldAccumulate2 = isBottom ? Math.random() < 0.15 : true;
363
+ if (shouldAccumulate2) {
364
+ const baseSpread = Math.ceil(flake.radius);
365
+ const spread = baseSpread + Math.floor(Math.random() * 2);
366
+ const accumRate = isBottom ? config.ACCUMULATION.BOTTOM_RATE : config.ACCUMULATION.TOP_RATE;
367
+ const centerOffset = Math.floor(Math.random() * 3) - 1;
368
+ for (let dx = -spread; dx <= spread; dx++) {
369
+ if (Math.random() < 0.15) continue;
370
+ const idx = localX + dx + centerOffset;
371
+ if (idx >= 0 && idx < acc.heights.length) {
372
+ const dist = Math.abs(dx);
373
+ const pixelMax = acc.maxHeights[idx] || 5;
374
+ const normDist = dist / spread;
375
+ const falloff = (Math.cos(normDist * Math.PI) + 1) / 2;
376
+ const baseAdd = 0.3 * falloff;
377
+ const randomFactor = 0.8 + Math.random() * 0.4;
378
+ const addHeight = baseAdd * randomFactor * accumRate;
379
+ if (acc.heights[idx] < pixelMax && addHeight > 0) {
380
+ acc.heights[idx] = Math.min(pixelMax, acc.heights[idx] + addHeight);
381
+ }
382
+ }
383
+ }
384
+ if (isBottom) {
385
+ landed = true;
386
+ break;
387
+ }
388
+ }
389
+ if (!isBottom) {
390
+ landed = true;
391
+ break;
392
+ }
393
+ }
394
+ }
395
+ }
396
+ if (landed || flake.y > canvasHeight + 10 || flake.x < -20 || flake.x > canvasWidth + 20) {
397
+ snowflakes.splice(i, 1);
398
+ }
399
+ }
400
+ };
401
+ var meltAndSmoothAccumulation = (elementRects, config, dt) => {
402
+ for (const { acc } of elementRects) {
403
+ const meltRate = config.MELT_SPEED * dt;
404
+ const len = acc.heights.length;
405
+ if (len > 2) {
406
+ for (let i = 1; i < len - 1; i++) {
407
+ if (acc.heights[i] > 0.05) {
408
+ const avg = (acc.heights[i - 1] + acc.heights[i + 1]) / 2;
409
+ acc.heights[i] = acc.heights[i] * 0.99 + avg * 0.01;
410
+ }
411
+ }
412
+ }
413
+ for (let i = 0; i < acc.heights.length; i++) {
414
+ if (acc.heights[i] > 0) acc.heights[i] = Math.max(0, acc.heights[i] - meltRate);
415
+ }
416
+ for (let i = 0; i < acc.leftSide.length; i++) {
417
+ if (acc.leftSide[i] > 0) acc.leftSide[i] = Math.max(0, acc.leftSide[i] - meltRate);
418
+ if (acc.rightSide[i] > 0) acc.rightSide[i] = Math.max(0, acc.rightSide[i] - meltRate);
419
+ }
420
+ }
421
+ };
422
+
423
+ // src/utils/snowfall/draw.ts
424
+ var drawSnowflake = (ctx, flake) => {
425
+ ctx.beginPath();
426
+ ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);
427
+ ctx.fillStyle = `rgba(255, 255, 255, ${flake.opacity})`;
428
+ ctx.fill();
429
+ ctx.beginPath();
430
+ ctx.arc(flake.x, flake.y, flake.radius * 1.5, 0, Math.PI * 2);
431
+ ctx.fillStyle = `rgba(255, 255, 255, ${flake.opacity * 0.2})`;
432
+ ctx.fill();
433
+ };
434
+ var drawAccumulations = (ctx, fixedCtx, elementRects) => {
435
+ const setupCtx = (c) => {
436
+ c.fillStyle = "rgba(255, 255, 255, 0.95)";
437
+ c.shadowColor = "rgba(200, 230, 255, 0.6)";
438
+ c.shadowBlur = 4;
439
+ c.shadowOffsetY = -1;
440
+ };
441
+ setupCtx(ctx);
442
+ if (fixedCtx) setupCtx(fixedCtx);
443
+ const scrollX = window.scrollX;
444
+ const scrollY = window.scrollY;
445
+ for (const { rect, acc } of elementRects) {
446
+ if (!acc.heights.some((h) => h > 0.1)) continue;
447
+ const useFixed = acc.isFixed && fixedCtx;
448
+ const targetCtx = useFixed ? fixedCtx : ctx;
449
+ const dx = useFixed ? -scrollX : 0;
450
+ const dy = useFixed ? -scrollY : 0;
451
+ const isBottom = acc.type === VAL_BOTTOM;
452
+ const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
453
+ const borderRadius = acc.borderRadius;
454
+ const getCurveOffset = (xPos) => {
455
+ if (borderRadius <= 0 || isBottom) return 0;
456
+ let offset = 0;
457
+ if (xPos < borderRadius) {
458
+ const dist = borderRadius - xPos;
459
+ offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
460
+ } else if (xPos > rect.width - borderRadius) {
461
+ const dist = xPos - (rect.width - borderRadius);
462
+ offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
463
+ }
464
+ return offset;
465
+ };
466
+ targetCtx.beginPath();
467
+ let first = true;
468
+ const step = 2;
469
+ const len = acc.heights.length;
470
+ for (let x = 0; x < len; x += step) {
471
+ const height = acc.heights[x] || 0;
472
+ const px = rect.left + x + dx;
473
+ const py = baseY - height + getCurveOffset(x) + dy;
474
+ if (first) {
475
+ targetCtx.moveTo(px, py);
476
+ first = false;
477
+ } else {
478
+ targetCtx.lineTo(px, py);
479
+ }
480
+ }
481
+ if ((len - 1) % step !== 0) {
482
+ const x = len - 1;
483
+ const height = acc.heights[x] || 0;
484
+ const px = rect.left + x + dx;
485
+ const py = baseY - height + getCurveOffset(x) + dy;
486
+ targetCtx.lineTo(px, py);
487
+ }
488
+ for (let x = len - 1; x >= 0; x -= step) {
489
+ const px = rect.left + x + dx;
490
+ const py = baseY + getCurveOffset(x) + dy;
491
+ targetCtx.lineTo(px, py);
492
+ }
493
+ const startX = 0;
494
+ const startPx = rect.left + startX + dx;
495
+ const startPy = baseY + getCurveOffset(startX) + dy;
496
+ targetCtx.lineTo(startPx, startPy);
497
+ targetCtx.closePath();
498
+ targetCtx.fill();
499
+ }
500
+ ctx.shadowBlur = 0;
501
+ ctx.shadowOffsetY = 0;
502
+ if (fixedCtx) {
503
+ fixedCtx.shadowBlur = 0;
504
+ fixedCtx.shadowOffsetY = 0;
505
+ }
506
+ };
507
+ var drawSideAccumulations = (ctx, fixedCtx, elementRects) => {
508
+ const setupCtx = (c) => {
509
+ c.fillStyle = "rgba(255, 255, 255, 0.95)";
510
+ c.shadowColor = "rgba(200, 230, 255, 0.6)";
511
+ c.shadowBlur = 3;
512
+ };
513
+ setupCtx(ctx);
514
+ if (fixedCtx) setupCtx(fixedCtx);
515
+ const scrollX = window.scrollX;
516
+ const scrollY = window.scrollY;
517
+ for (const { rect, acc } of elementRects) {
518
+ if (acc.maxSideHeight === 0) continue;
519
+ const hasLeftSnow = acc.leftSide.some((h) => h > 0.3);
520
+ const hasRightSnow = acc.rightSide.some((h) => h > 0.3);
521
+ if (!hasLeftSnow && !hasRightSnow) continue;
522
+ const useFixed = acc.isFixed && fixedCtx;
523
+ const targetCtx = useFixed ? fixedCtx : ctx;
524
+ const dx = useFixed ? -scrollX : 0;
525
+ const dy = useFixed ? -scrollY : 0;
526
+ const drawSide = (sideArray, isLeft) => {
527
+ targetCtx.beginPath();
528
+ const baseX = isLeft ? rect.left : rect.right;
529
+ targetCtx.moveTo(baseX + dx, rect.top + dy);
530
+ for (let y = 0; y < sideArray.length; y += 2) {
531
+ const width = sideArray[y] || 0;
532
+ const nextY = Math.min(y + 2, sideArray.length - 1);
533
+ const nextWidth = sideArray[nextY] || 0;
534
+ const py = rect.top + y + dy;
535
+ const px = (isLeft ? baseX - width : baseX + width) + dx;
536
+ const ny = rect.top + nextY + dy;
537
+ const nx = (isLeft ? baseX - nextWidth : baseX + nextWidth) + dx;
538
+ targetCtx.lineTo(px, py);
539
+ targetCtx.lineTo(nx, ny);
540
+ }
541
+ targetCtx.lineTo(baseX + dx, rect.bottom + dy);
542
+ targetCtx.closePath();
543
+ targetCtx.fill();
544
+ };
545
+ if (hasLeftSnow) drawSide(acc.leftSide, true);
546
+ if (hasRightSnow) drawSide(acc.rightSide, false);
547
+ }
548
+ ctx.shadowBlur = 0;
549
+ if (fixedCtx) fixedCtx.shadowBlur = 0;
550
+ };
551
+
552
+ // src/Snowfall.tsx
553
+ var import_jsx_runtime2 = require("react/jsx-runtime");
554
+ function Snowfall() {
555
+ const { isEnabled, physicsConfig } = useSnowfall();
556
+ const isEnabledRef = (0, import_react2.useRef)(isEnabled);
557
+ const physicsConfigRef = (0, import_react2.useRef)(physicsConfig);
558
+ const [isMounted, setIsMounted] = (0, import_react2.useState)(false);
559
+ const [isVisible, setIsVisible] = (0, import_react2.useState)(false);
560
+ const canvasRef = (0, import_react2.useRef)(null);
561
+ const fixedCanvasRef = (0, import_react2.useRef)(null);
562
+ const snowflakesRef = (0, import_react2.useRef)([]);
563
+ const accumulationRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
564
+ const animationIdRef = (0, import_react2.useRef)(0);
565
+ (0, import_react2.useEffect)(() => {
566
+ setIsMounted(true);
567
+ }, []);
568
+ (0, import_react2.useEffect)(() => {
569
+ isEnabledRef.current = isEnabled;
570
+ }, [isEnabled]);
571
+ (0, import_react2.useEffect)(() => {
572
+ physicsConfigRef.current = physicsConfig;
573
+ }, [physicsConfig]);
574
+ (0, import_react2.useEffect)(() => {
575
+ if (!isMounted) return;
576
+ const canvas = canvasRef.current;
577
+ const fixedCanvas = fixedCanvasRef.current;
578
+ if (!canvas || !fixedCanvas) return;
579
+ const ctx = canvas.getContext("2d");
580
+ const fixedCtx = fixedCanvas.getContext("2d");
581
+ if (!ctx || !fixedCtx) return;
582
+ const resizeCanvas = () => {
583
+ if (canvasRef.current && fixedCanvasRef.current) {
584
+ const newHeight = Math.max(document.documentElement.scrollHeight, window.innerHeight);
585
+ const newWidth = Math.max(document.documentElement.scrollWidth, window.innerWidth);
586
+ if (canvasRef.current.height !== newHeight || canvasRef.current.width !== newWidth) {
587
+ canvasRef.current.width = newWidth;
588
+ canvasRef.current.height = newHeight;
589
+ }
590
+ if (fixedCanvasRef.current.width !== window.innerWidth || fixedCanvasRef.current.height !== window.innerHeight) {
591
+ fixedCanvasRef.current.width = window.innerWidth;
592
+ fixedCanvasRef.current.height = window.innerHeight;
593
+ }
594
+ }
595
+ };
596
+ resizeCanvas();
597
+ const resizeObserver = new ResizeObserver(() => {
598
+ resizeCanvas();
599
+ });
600
+ resizeObserver.observe(document.body);
601
+ snowflakesRef.current = [];
602
+ const initAccumulationWrapper = () => {
603
+ initializeAccumulation(accumulationRef.current, physicsConfigRef.current);
604
+ };
605
+ initAccumulationWrapper();
606
+ setIsVisible(true);
607
+ let lastTime = 0;
608
+ const animate = (currentTime) => {
609
+ if (lastTime === 0) {
610
+ lastTime = currentTime;
611
+ animationIdRef.current = requestAnimationFrame(animate);
612
+ return;
613
+ }
614
+ const deltaTime = Math.min(currentTime - lastTime, 50);
615
+ lastTime = currentTime;
616
+ const dt = deltaTime / 16.67;
617
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
618
+ fixedCtx.clearRect(0, 0, fixedCanvas.width, fixedCanvas.height);
619
+ const snowflakes = snowflakesRef.current;
620
+ const elementRects = getElementRects(accumulationRef.current);
621
+ meltAndSmoothAccumulation(elementRects, physicsConfigRef.current, dt);
622
+ updateSnowflakes(snowflakes, elementRects, physicsConfigRef.current, dt, canvas.width, canvas.height);
623
+ for (const flake of snowflakes) {
624
+ drawSnowflake(ctx, flake);
625
+ }
626
+ if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
627
+ const isBackground = Math.random() < 0.4;
628
+ snowflakes.push(createSnowflake(canvas.width, physicsConfigRef.current, isBackground));
629
+ }
630
+ drawAccumulations(ctx, fixedCtx, elementRects);
631
+ drawSideAccumulations(ctx, fixedCtx, elementRects);
632
+ animationIdRef.current = requestAnimationFrame(animate);
633
+ };
634
+ animationIdRef.current = requestAnimationFrame(animate);
635
+ const handleResize = () => {
636
+ resizeCanvas();
637
+ accumulationRef.current.clear();
638
+ initAccumulationWrapper();
639
+ };
640
+ window.addEventListener("resize", handleResize);
641
+ const checkInterval = setInterval(initAccumulationWrapper, 3e3);
642
+ return () => {
643
+ cancelAnimationFrame(animationIdRef.current);
644
+ window.removeEventListener("resize", handleResize);
645
+ clearInterval(checkInterval);
646
+ resizeObserver.disconnect();
647
+ };
648
+ }, [isMounted]);
649
+ if (!isMounted) return null;
650
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
651
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
652
+ "canvas",
653
+ {
654
+ ref: canvasRef,
655
+ style: {
656
+ position: "absolute",
657
+ top: 0,
658
+ left: 0,
659
+ pointerEvents: "none",
660
+ zIndex: 9999,
661
+ opacity: isVisible ? 1 : 0,
662
+ transition: "opacity 0.3s ease-in"
663
+ },
664
+ "aria-hidden": "true"
665
+ }
666
+ ),
667
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
668
+ "canvas",
669
+ {
670
+ ref: fixedCanvasRef,
671
+ style: {
672
+ position: "fixed",
673
+ top: 0,
674
+ left: 0,
675
+ pointerEvents: "none",
676
+ zIndex: 9999,
677
+ opacity: isVisible ? 1 : 0,
678
+ transition: "opacity 0.3s ease-in"
679
+ },
680
+ "aria-hidden": "true"
681
+ }
682
+ )
683
+ ] });
684
+ }
40
685
  // Annotate the CommonJS export names for ESM import in node:
41
686
  0 && (module.exports = {
42
687
  DEFAULT_PHYSICS,