@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.
- package/dist/index.d.mts +38 -4
- package/dist/index.d.ts +38 -4
- package/dist/index.js +661 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +656 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/Snowfall.d.mts +0 -5
- package/dist/Snowfall.d.ts +0 -5
- package/dist/Snowfall.js +0 -162
- package/dist/Snowfall.js.map +0 -1
- package/dist/Snowfall.mjs +0 -142
- package/dist/Snowfall.mjs.map +0 -1
- package/dist/SnowfallProvider.d.mts +0 -36
- package/dist/SnowfallProvider.d.ts +0 -36
- package/dist/SnowfallProvider.js +0 -97
- package/dist/SnowfallProvider.js.map +0 -1
- package/dist/SnowfallProvider.mjs +0 -71
- package/dist/SnowfallProvider.mjs.map +0 -1
- package/dist/utils/snowfall/constants.d.mts +0 -10
- package/dist/utils/snowfall/constants.d.ts +0 -10
- package/dist/utils/snowfall/constants.js +0 -50
- package/dist/utils/snowfall/constants.js.map +0 -1
- package/dist/utils/snowfall/constants.mjs +0 -19
- package/dist/utils/snowfall/constants.mjs.map +0 -1
- package/dist/utils/snowfall/dom.d.mts +0 -11
- package/dist/utils/snowfall/dom.d.ts +0 -11
- package/dist/utils/snowfall/dom.js +0 -139
- package/dist/utils/snowfall/dom.js.map +0 -1
- package/dist/utils/snowfall/dom.mjs +0 -122
- package/dist/utils/snowfall/dom.mjs.map +0 -1
- package/dist/utils/snowfall/draw.d.mts +0 -7
- package/dist/utils/snowfall/draw.d.ts +0 -7
- package/dist/utils/snowfall/draw.js +0 -160
- package/dist/utils/snowfall/draw.js.map +0 -1
- package/dist/utils/snowfall/draw.mjs +0 -134
- package/dist/utils/snowfall/draw.mjs.map +0 -1
- package/dist/utils/snowfall/physics.d.mts +0 -11
- package/dist/utils/snowfall/physics.d.ts +0 -11
- package/dist/utils/snowfall/physics.js +0 -239
- package/dist/utils/snowfall/physics.js.map +0 -1
- package/dist/utils/snowfall/physics.mjs +0 -212
- package/dist/utils/snowfall/physics.mjs.map +0 -1
- package/dist/utils/snowfall/types.d.mts +0 -30
- package/dist/utils/snowfall/types.d.ts +0 -30
- package/dist/utils/snowfall/types.js +0 -17
- package/dist/utils/snowfall/types.js.map +0 -1
- package/dist/utils/snowfall/types.mjs +0 -1
- 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: () =>
|
|
33
|
-
Snowfall: () =>
|
|
34
|
-
SnowfallProvider: () =>
|
|
35
|
-
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
|
-
|
|
39
|
-
|
|
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,
|