@hdcodedev/snowfall 1.0.5 → 1.0.7
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 +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +471 -394
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +471 -394
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -2
package/dist/index.js
CHANGED
|
@@ -52,13 +52,14 @@ var DEFAULT_PHYSICS = {
|
|
|
52
52
|
FLAKE_SIZE: {
|
|
53
53
|
MIN: 0.5,
|
|
54
54
|
MAX: 1.6
|
|
55
|
-
}
|
|
55
|
+
},
|
|
56
|
+
MAX_SURFACES: 15
|
|
56
57
|
};
|
|
57
58
|
var SnowfallContext = (0, import_react.createContext)(void 0);
|
|
58
|
-
function SnowfallProvider({ children }) {
|
|
59
|
+
function SnowfallProvider({ children, initialDebug = false }) {
|
|
59
60
|
const [isEnabled, setIsEnabled] = (0, import_react.useState)(true);
|
|
60
61
|
const [physicsConfig, setPhysicsConfig] = (0, import_react.useState)(DEFAULT_PHYSICS);
|
|
61
|
-
const [debugMode, setDebugMode] = (0, import_react.useState)(
|
|
62
|
+
const [debugMode, setDebugMode] = (0, import_react.useState)(initialDebug);
|
|
62
63
|
const [metrics, setMetrics] = (0, import_react.useState)(null);
|
|
63
64
|
const toggleSnow = () => {
|
|
64
65
|
setIsEnabled((prev) => !prev);
|
|
@@ -113,13 +114,16 @@ var VAL_IGNORE = "ignore";
|
|
|
113
114
|
var VAL_TOP = "top";
|
|
114
115
|
var VAL_BOTTOM = "bottom";
|
|
115
116
|
var TAG_HEADER = "header";
|
|
117
|
+
var TAG_FOOTER = "footer";
|
|
116
118
|
var ROLE_BANNER = "banner";
|
|
119
|
+
var ROLE_CONTENTINFO = "contentinfo";
|
|
120
|
+
var TAU = Math.PI * 2;
|
|
117
121
|
|
|
118
122
|
// src/utils/snowfall/dom.ts
|
|
119
123
|
var BOTTOM_TAGS = [TAG_HEADER];
|
|
120
124
|
var BOTTOM_ROLES = [ROLE_BANNER];
|
|
121
|
-
var AUTO_DETECT_TAGS = [
|
|
122
|
-
var AUTO_DETECT_ROLES = [
|
|
125
|
+
var AUTO_DETECT_TAGS = [TAG_HEADER, TAG_FOOTER, "article", "section", "aside", "nav"];
|
|
126
|
+
var AUTO_DETECT_ROLES = [`[role="${ROLE_BANNER}"]`, `[role="${ROLE_CONTENTINFO}"]`, '[role="main"]'];
|
|
123
127
|
var AUTO_DETECT_CLASSES = [
|
|
124
128
|
".card",
|
|
125
129
|
'[class*="card"]',
|
|
@@ -128,18 +132,6 @@ var AUTO_DETECT_CLASSES = [
|
|
|
128
132
|
'[class*="shadow-"]',
|
|
129
133
|
'[class*="rounded-"]'
|
|
130
134
|
];
|
|
131
|
-
var styleCache = /* @__PURE__ */ new Map();
|
|
132
|
-
var getCachedStyle = (el) => {
|
|
133
|
-
let cached = styleCache.get(el);
|
|
134
|
-
if (!cached) {
|
|
135
|
-
cached = window.getComputedStyle(el);
|
|
136
|
-
styleCache.set(el, cached);
|
|
137
|
-
}
|
|
138
|
-
return cached;
|
|
139
|
-
};
|
|
140
|
-
var clearStyleCache = () => {
|
|
141
|
-
styleCache.clear();
|
|
142
|
-
};
|
|
143
135
|
var getElementType = (el) => {
|
|
144
136
|
const tagName = el.tagName.toLowerCase();
|
|
145
137
|
if (BOTTOM_TAGS.includes(tagName)) return VAL_BOTTOM;
|
|
@@ -147,22 +139,21 @@ var getElementType = (el) => {
|
|
|
147
139
|
if (role && BOTTOM_ROLES.includes(role)) return VAL_BOTTOM;
|
|
148
140
|
return VAL_TOP;
|
|
149
141
|
};
|
|
150
|
-
var shouldAccumulate = (el) => {
|
|
142
|
+
var shouldAccumulate = (el, precomputedStyle) => {
|
|
151
143
|
if (el.getAttribute(ATTR_SNOWFALL) === VAL_IGNORE) return false;
|
|
152
144
|
if (el.hasAttribute(ATTR_SNOWFALL)) return true;
|
|
153
|
-
const styles = window.getComputedStyle(el);
|
|
154
|
-
const rect = el.getBoundingClientRect();
|
|
145
|
+
const styles = precomputedStyle || window.getComputedStyle(el);
|
|
155
146
|
const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
|
|
156
147
|
if (!isVisible) return false;
|
|
157
148
|
const bgColor = styles.backgroundColor;
|
|
158
149
|
const hasBackground = bgColor !== "rgba(0, 0, 0, 0)" && bgColor !== "transparent";
|
|
159
|
-
const hasBorder = parseFloat(styles.borderWidth) > 0
|
|
150
|
+
const hasBorder = parseFloat(styles.borderWidth) > 0 && styles.borderColor !== "transparent" && styles.borderColor !== "rgba(0, 0, 0, 0)" && styles.borderStyle !== "none";
|
|
160
151
|
const hasBoxShadow = styles.boxShadow !== "none";
|
|
161
|
-
const
|
|
162
|
-
|
|
152
|
+
const hasFilter = styles.filter !== "none" && styles.filter.includes("drop-shadow");
|
|
153
|
+
const hasBackdropFilter = styles.backdropFilter !== "none";
|
|
154
|
+
return hasBackground || hasBorder || hasBoxShadow || hasFilter || hasBackdropFilter;
|
|
163
155
|
};
|
|
164
|
-
var getAccumulationSurfaces = () => {
|
|
165
|
-
clearStyleCache();
|
|
156
|
+
var getAccumulationSurfaces = (maxSurfaces = 5) => {
|
|
166
157
|
const surfaces = [];
|
|
167
158
|
const seen = /* @__PURE__ */ new Set();
|
|
168
159
|
const candidates = document.querySelectorAll(
|
|
@@ -173,45 +164,31 @@ var getAccumulationSurfaces = () => {
|
|
|
173
164
|
...AUTO_DETECT_CLASSES
|
|
174
165
|
].join(", ")
|
|
175
166
|
);
|
|
176
|
-
|
|
177
|
-
if (
|
|
167
|
+
for (const el of candidates) {
|
|
168
|
+
if (surfaces.length >= maxSurfaces) break;
|
|
169
|
+
if (seen.has(el)) continue;
|
|
178
170
|
const manualOverride = el.getAttribute(ATTR_SNOWFALL);
|
|
179
|
-
if (manualOverride === VAL_IGNORE)
|
|
171
|
+
if (manualOverride === VAL_IGNORE) continue;
|
|
180
172
|
const isManuallyIncluded = manualOverride !== null;
|
|
181
|
-
const styles =
|
|
173
|
+
const styles = window.getComputedStyle(el);
|
|
182
174
|
const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
|
|
183
|
-
if (!isVisible && !isManuallyIncluded)
|
|
175
|
+
if (!isVisible && !isManuallyIncluded) continue;
|
|
184
176
|
const rect = el.getBoundingClientRect();
|
|
185
177
|
const hasSize = rect.width >= 100 && rect.height >= 50;
|
|
186
|
-
if (!hasSize && !isManuallyIncluded)
|
|
178
|
+
if (!hasSize && !isManuallyIncluded) continue;
|
|
187
179
|
const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;
|
|
188
180
|
const isBottomTag = BOTTOM_TAGS.includes(el.tagName.toLowerCase());
|
|
189
181
|
const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute("role") || "");
|
|
190
182
|
const isBottomSurface = isBottomTag || isBottomRole || manualOverride === VAL_BOTTOM;
|
|
191
|
-
if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded)
|
|
192
|
-
|
|
193
|
-
let currentEl = el;
|
|
194
|
-
while (currentEl && currentEl !== document.body) {
|
|
195
|
-
const style = getCachedStyle(currentEl);
|
|
196
|
-
if (style.position === "fixed" || style.position === "sticky") {
|
|
197
|
-
isFixed = true;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
currentEl = currentEl.parentElement;
|
|
201
|
-
}
|
|
202
|
-
if (shouldAccumulate(el)) {
|
|
183
|
+
if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) continue;
|
|
184
|
+
if (shouldAccumulate(el, styles)) {
|
|
203
185
|
let type = getElementType(el);
|
|
204
|
-
if (manualOverride === VAL_BOTTOM)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
type = VAL_TOP;
|
|
208
|
-
}
|
|
209
|
-
surfaces.push({ el, type, isFixed });
|
|
186
|
+
if (manualOverride === VAL_BOTTOM) type = VAL_BOTTOM;
|
|
187
|
+
else if (manualOverride === VAL_TOP) type = VAL_TOP;
|
|
188
|
+
surfaces.push({ el, type });
|
|
210
189
|
seen.add(el);
|
|
211
190
|
}
|
|
212
|
-
}
|
|
213
|
-
console.log(`[Snowfall] Auto-detection found ${surfaces.length} surfaces`);
|
|
214
|
-
console.log("[Snowfall] \u2705 Using OPTIMIZED version with Map-based caching & 5s intervals");
|
|
191
|
+
}
|
|
215
192
|
return surfaces;
|
|
216
193
|
};
|
|
217
194
|
var getElementRects = (accumulationMap) => {
|
|
@@ -219,76 +196,128 @@ var getElementRects = (accumulationMap) => {
|
|
|
219
196
|
for (const [el, acc] of accumulationMap.entries()) {
|
|
220
197
|
if (!el.isConnected) continue;
|
|
221
198
|
const rect = el.getBoundingClientRect();
|
|
222
|
-
|
|
223
|
-
left: rect.left + window.scrollX,
|
|
224
|
-
right: rect.right + window.scrollX,
|
|
225
|
-
top: rect.top + window.scrollY,
|
|
226
|
-
bottom: rect.bottom + window.scrollY,
|
|
227
|
-
width: rect.width,
|
|
228
|
-
height: rect.height,
|
|
229
|
-
x: rect.x,
|
|
230
|
-
// Note: these are strictly viewport relative in DOMRect usually,
|
|
231
|
-
// but we just need consistent absolute coords for physics
|
|
232
|
-
y: rect.y,
|
|
233
|
-
toJSON: rect.toJSON
|
|
234
|
-
};
|
|
235
|
-
elementRects.push({ el, rect: absoluteRect, acc });
|
|
199
|
+
elementRects.push({ el, rect, acc });
|
|
236
200
|
}
|
|
237
201
|
return elementRects;
|
|
238
202
|
};
|
|
239
203
|
|
|
240
204
|
// src/utils/snowfall/physics.ts
|
|
241
|
-
var createSnowflake = (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
205
|
+
var createSnowflake = (worldWidth, config, isBackground = false) => {
|
|
206
|
+
const x = Math.random() * worldWidth;
|
|
207
|
+
const dna = Math.random();
|
|
208
|
+
const noise = {
|
|
209
|
+
speed: dna * 13 % 1,
|
|
210
|
+
wind: dna * 7 % 1,
|
|
211
|
+
wobblePhase: dna * 23 % 1,
|
|
212
|
+
wobbleSpeed: dna * 5 % 1
|
|
213
|
+
};
|
|
214
|
+
const { MIN, MAX } = config.FLAKE_SIZE;
|
|
215
|
+
const sizeRatio = dna;
|
|
216
|
+
const profile = isBackground ? {
|
|
217
|
+
sizeMin: MIN * 0.6,
|
|
218
|
+
sizeRange: (MAX - MIN) * 0.4,
|
|
219
|
+
speedBase: 0.2,
|
|
220
|
+
speedScale: 0.3,
|
|
221
|
+
noiseSpeedScale: 0.2,
|
|
222
|
+
windScale: config.WIND_STRENGTH * 0.625,
|
|
223
|
+
opacityBase: 0.2,
|
|
224
|
+
opacityScale: 0.2,
|
|
225
|
+
wobbleBase: 5e-3,
|
|
226
|
+
wobbleScale: 0.015
|
|
227
|
+
} : {
|
|
228
|
+
sizeMin: MIN,
|
|
229
|
+
sizeRange: MAX - MIN,
|
|
230
|
+
speedBase: 0.5,
|
|
231
|
+
speedScale: 0.5,
|
|
232
|
+
noiseSpeedScale: 0.3,
|
|
233
|
+
windScale: config.WIND_STRENGTH,
|
|
234
|
+
opacityBase: 0.5,
|
|
235
|
+
opacityScale: 0.3,
|
|
236
|
+
wobbleBase: 0.01,
|
|
237
|
+
wobbleScale: 0.02
|
|
238
|
+
};
|
|
239
|
+
const radius = profile.sizeMin + sizeRatio * profile.sizeRange;
|
|
240
|
+
return {
|
|
241
|
+
x,
|
|
242
|
+
y: window.scrollY - 5,
|
|
243
|
+
radius,
|
|
244
|
+
speed: radius * profile.speedScale + noise.speed * profile.noiseSpeedScale + profile.speedBase,
|
|
245
|
+
wind: (noise.wind - 0.5) * profile.windScale,
|
|
246
|
+
opacity: profile.opacityBase + sizeRatio * profile.opacityScale,
|
|
247
|
+
wobble: noise.wobblePhase * TAU,
|
|
248
|
+
wobbleSpeed: noise.wobbleSpeed * profile.wobbleScale + profile.wobbleBase,
|
|
249
|
+
sizeRatio,
|
|
250
|
+
isBackground
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
var initializeMaxHeights = (width, baseMax, borderRadius, isBottom = false) => {
|
|
254
|
+
let maxHeights = new Array(width);
|
|
255
|
+
for (let i = 0; i < width; i++) {
|
|
256
|
+
let edgeFactor = 1;
|
|
257
|
+
if (!isBottom && borderRadius > 0) {
|
|
258
|
+
if (i < borderRadius) {
|
|
259
|
+
edgeFactor = Math.pow(i / borderRadius, 1.2);
|
|
260
|
+
} else if (i > width - borderRadius) {
|
|
261
|
+
edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
|
|
265
|
+
}
|
|
266
|
+
const smoothPasses = 4;
|
|
267
|
+
for (let p = 0; p < smoothPasses; p++) {
|
|
268
|
+
const smoothed = [...maxHeights];
|
|
269
|
+
for (let i = 1; i < width - 1; i++) {
|
|
270
|
+
smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
|
|
271
|
+
}
|
|
272
|
+
maxHeights = smoothed;
|
|
273
|
+
}
|
|
274
|
+
return maxHeights;
|
|
275
|
+
};
|
|
276
|
+
var calculateCurveOffsets = (width, borderRadius, isBottom) => {
|
|
277
|
+
const offsets = new Array(width).fill(0);
|
|
278
|
+
if (borderRadius <= 0 || isBottom) return offsets;
|
|
279
|
+
for (let x = 0; x < width; x++) {
|
|
280
|
+
let offset = 0;
|
|
281
|
+
if (x < borderRadius) {
|
|
282
|
+
const dist = borderRadius - x;
|
|
283
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
284
|
+
} else if (x > width - borderRadius) {
|
|
285
|
+
const dist = x - (width - borderRadius);
|
|
286
|
+
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
287
|
+
}
|
|
288
|
+
offsets[x] = offset;
|
|
289
|
+
}
|
|
290
|
+
return offsets;
|
|
291
|
+
};
|
|
292
|
+
var calculateGravityMultipliers = (height) => {
|
|
293
|
+
const multipliers = new Array(height);
|
|
294
|
+
for (let i = 0; i < height; i++) {
|
|
295
|
+
const ratio = i / height;
|
|
296
|
+
multipliers[i] = Math.sqrt(ratio);
|
|
272
297
|
}
|
|
298
|
+
return multipliers;
|
|
273
299
|
};
|
|
274
300
|
var initializeAccumulation = (accumulationMap, config) => {
|
|
275
|
-
const elements = getAccumulationSurfaces();
|
|
301
|
+
const elements = getAccumulationSurfaces(config.MAX_SURFACES);
|
|
276
302
|
for (const [el] of accumulationMap.entries()) {
|
|
277
303
|
if (!el.isConnected) {
|
|
278
304
|
accumulationMap.delete(el);
|
|
279
305
|
}
|
|
280
306
|
}
|
|
281
|
-
elements.forEach(({ el, type
|
|
307
|
+
elements.forEach(({ el, type }) => {
|
|
282
308
|
const existing = accumulationMap.get(el);
|
|
283
309
|
const rect = el.getBoundingClientRect();
|
|
284
310
|
const width = Math.ceil(rect.width);
|
|
285
311
|
const isBottom = type === VAL_BOTTOM;
|
|
286
312
|
if (existing && existing.heights.length === width) {
|
|
287
313
|
existing.type = type;
|
|
288
|
-
existing.isFixed = isFixed;
|
|
289
314
|
if (existing.borderRadius !== void 0) {
|
|
290
315
|
const styleBuffer = window.getComputedStyle(el);
|
|
291
316
|
existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;
|
|
317
|
+
existing.curveOffsets = calculateCurveOffsets(width, existing.borderRadius, isBottom);
|
|
318
|
+
if (existing.leftSide.length === Math.ceil(rect.height) && !existing.sideGravityMultipliers) {
|
|
319
|
+
existing.sideGravityMultipliers = calculateGravityMultipliers(existing.leftSide.length);
|
|
320
|
+
}
|
|
292
321
|
}
|
|
293
322
|
return;
|
|
294
323
|
}
|
|
@@ -296,26 +325,7 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
296
325
|
const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;
|
|
297
326
|
const styles = window.getComputedStyle(el);
|
|
298
327
|
const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;
|
|
299
|
-
|
|
300
|
-
for (let i = 0; i < width; i++) {
|
|
301
|
-
let edgeFactor = 1;
|
|
302
|
-
if (!isBottom && borderRadius > 0) {
|
|
303
|
-
if (i < borderRadius) {
|
|
304
|
-
edgeFactor = Math.pow(i / borderRadius, 1.2);
|
|
305
|
-
} else if (i > width - borderRadius) {
|
|
306
|
-
edgeFactor = Math.pow((width - i) / borderRadius, 1.2);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);
|
|
310
|
-
}
|
|
311
|
-
const smoothPasses = 4;
|
|
312
|
-
for (let p = 0; p < smoothPasses; p++) {
|
|
313
|
-
const smoothed = [...maxHeights];
|
|
314
|
-
for (let i = 1; i < width - 1; i++) {
|
|
315
|
-
smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;
|
|
316
|
-
}
|
|
317
|
-
maxHeights = smoothed;
|
|
318
|
-
}
|
|
328
|
+
const maxHeights = initializeMaxHeights(width, baseMax, borderRadius, isBottom);
|
|
319
329
|
accumulationMap.set(el, {
|
|
320
330
|
heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),
|
|
321
331
|
maxHeights,
|
|
@@ -323,8 +333,9 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
323
333
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
324
334
|
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
325
335
|
borderRadius,
|
|
326
|
-
|
|
327
|
-
|
|
336
|
+
curveOffsets: calculateCurveOffsets(width, borderRadius, isBottom),
|
|
337
|
+
sideGravityMultipliers: calculateGravityMultipliers(height),
|
|
338
|
+
type
|
|
328
339
|
});
|
|
329
340
|
});
|
|
330
341
|
};
|
|
@@ -343,30 +354,35 @@ var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius
|
|
|
343
354
|
}
|
|
344
355
|
}
|
|
345
356
|
};
|
|
346
|
-
var updateSnowflakes = (snowflakes, elementRects, config, dt,
|
|
357
|
+
var updateSnowflakes = (snowflakes, elementRects, config, dt, worldWidth, worldHeight) => {
|
|
358
|
+
const scrollX = window.scrollX;
|
|
359
|
+
const scrollY = window.scrollY;
|
|
347
360
|
for (let i = snowflakes.length - 1; i >= 0; i--) {
|
|
348
361
|
const flake = snowflakes[i];
|
|
349
362
|
flake.wobble += flake.wobbleSpeed * dt;
|
|
350
363
|
flake.x += (flake.wind + Math.sin(flake.wobble) * 0.5) * dt;
|
|
351
364
|
flake.y += (flake.speed + Math.cos(flake.wobble * 0.5) * 0.1) * dt;
|
|
352
365
|
let landed = false;
|
|
353
|
-
for (const
|
|
366
|
+
for (const item of elementRects) {
|
|
367
|
+
const { rect, acc } = item;
|
|
354
368
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
369
|
+
const flakeViewportX = flake.x - scrollX;
|
|
370
|
+
const flakeViewportY = flake.y - scrollY;
|
|
371
|
+
const isInVerticalBounds = flakeViewportY >= rect.top && flakeViewportY <= rect.bottom;
|
|
355
372
|
if (!landed && acc.maxSideHeight > 0 && !isBottom) {
|
|
356
|
-
const isInVerticalBounds = flake.y >= rect.top && flake.y <= rect.bottom;
|
|
357
373
|
if (isInVerticalBounds) {
|
|
358
|
-
const localY = Math.floor(
|
|
374
|
+
const localY = Math.floor(flakeViewportY - rect.top);
|
|
359
375
|
const borderRadius = acc.borderRadius;
|
|
360
376
|
const isInTopCorner = localY < borderRadius;
|
|
361
377
|
const isInBottomCorner = localY > rect.height - borderRadius;
|
|
362
378
|
const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);
|
|
363
|
-
if (
|
|
379
|
+
if (flakeViewportX >= rect.left - 5 && flakeViewportX < rect.left + 3) {
|
|
364
380
|
if (!isCorner) {
|
|
365
381
|
accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
|
|
366
382
|
landed = true;
|
|
367
383
|
}
|
|
368
384
|
}
|
|
369
|
-
if (!landed &&
|
|
385
|
+
if (!landed && flakeViewportX > rect.right - 3 && flakeViewportX <= rect.right + 5) {
|
|
370
386
|
if (!isCorner) {
|
|
371
387
|
accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
|
|
372
388
|
landed = true;
|
|
@@ -375,12 +391,12 @@ var updateSnowflakes = (snowflakes, elementRects, config, dt, canvasWidth, canva
|
|
|
375
391
|
if (landed) break;
|
|
376
392
|
}
|
|
377
393
|
}
|
|
378
|
-
if (
|
|
379
|
-
const localX = Math.floor(
|
|
394
|
+
if (flakeViewportX >= rect.left && flakeViewportX <= rect.right) {
|
|
395
|
+
const localX = Math.floor(flakeViewportX - rect.left);
|
|
380
396
|
const currentHeight = acc.heights[localX] || 0;
|
|
381
397
|
const maxHeight = acc.maxHeights[localX] || 5;
|
|
382
398
|
const surfaceY = isBottom ? rect.bottom - currentHeight : rect.top - currentHeight;
|
|
383
|
-
if (
|
|
399
|
+
if (flakeViewportY >= surfaceY && flakeViewportY < surfaceY + 10 && currentHeight < maxHeight) {
|
|
384
400
|
const shouldAccumulate2 = isBottom ? Math.random() < 0.15 : true;
|
|
385
401
|
if (shouldAccumulate2) {
|
|
386
402
|
const baseSpread = Math.ceil(flake.radius);
|
|
@@ -415,7 +431,7 @@ var updateSnowflakes = (snowflakes, elementRects, config, dt, canvasWidth, canva
|
|
|
415
431
|
}
|
|
416
432
|
}
|
|
417
433
|
}
|
|
418
|
-
if (landed || flake.y >
|
|
434
|
+
if (landed || flake.y > worldHeight + 10 || flake.x < -20 || flake.x > worldWidth + 20) {
|
|
419
435
|
snowflakes.splice(i, 1);
|
|
420
436
|
}
|
|
421
437
|
}
|
|
@@ -443,136 +459,116 @@ var meltAndSmoothAccumulation = (elementRects, config, dt) => {
|
|
|
443
459
|
};
|
|
444
460
|
|
|
445
461
|
// src/utils/snowfall/draw.ts
|
|
446
|
-
var
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
ctx.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
462
|
+
var ACC_FILL_STYLE = "rgba(255, 255, 255, 0.95)";
|
|
463
|
+
var ACC_SHADOW_COLOR = "rgba(200, 230, 255, 0.6)";
|
|
464
|
+
var drawSnowflakes = (ctx, flakes) => {
|
|
465
|
+
if (flakes.length === 0) return;
|
|
466
|
+
ctx.fillStyle = "#FFFFFF";
|
|
467
|
+
for (const flake of flakes) {
|
|
468
|
+
ctx.globalAlpha = flake.opacity;
|
|
469
|
+
ctx.beginPath();
|
|
470
|
+
ctx.arc(flake.x, flake.y, flake.radius, 0, TAU);
|
|
471
|
+
ctx.fill();
|
|
472
|
+
}
|
|
473
|
+
for (const flake of flakes) {
|
|
474
|
+
if (flake.isBackground) continue;
|
|
475
|
+
ctx.globalAlpha = flake.opacity * 0.2;
|
|
476
|
+
ctx.beginPath();
|
|
477
|
+
ctx.arc(flake.x, flake.y, flake.radius * 1.5, 0, TAU);
|
|
478
|
+
ctx.fill();
|
|
479
|
+
}
|
|
480
|
+
ctx.globalAlpha = 1;
|
|
455
481
|
};
|
|
456
|
-
var drawAccumulations = (ctx,
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
for (const { rect, acc } of elementRects) {
|
|
482
|
+
var drawAccumulations = (ctx, elementRects) => {
|
|
483
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
484
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
485
|
+
ctx.shadowBlur = 4;
|
|
486
|
+
ctx.shadowOffsetY = -1;
|
|
487
|
+
ctx.globalAlpha = 1;
|
|
488
|
+
const currentScrollX = window.scrollX;
|
|
489
|
+
const currentScrollY = window.scrollY;
|
|
490
|
+
ctx.beginPath();
|
|
491
|
+
for (const item of elementRects) {
|
|
492
|
+
const { rect, acc } = item;
|
|
468
493
|
if (!acc.heights.some((h) => h > 0.1)) continue;
|
|
469
|
-
const
|
|
470
|
-
const
|
|
471
|
-
const dx = useFixed ? -scrollX : 0;
|
|
472
|
-
const dy = useFixed ? -scrollY : 0;
|
|
494
|
+
const dx = currentScrollX;
|
|
495
|
+
const dy = currentScrollY;
|
|
473
496
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
474
497
|
const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
|
|
475
|
-
const borderRadius = acc.borderRadius;
|
|
476
|
-
const getCurveOffset = (xPos) => {
|
|
477
|
-
if (borderRadius <= 0 || isBottom) return 0;
|
|
478
|
-
let offset = 0;
|
|
479
|
-
if (xPos < borderRadius) {
|
|
480
|
-
const dist = borderRadius - xPos;
|
|
481
|
-
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
482
|
-
} else if (xPos > rect.width - borderRadius) {
|
|
483
|
-
const dist = xPos - (rect.width - borderRadius);
|
|
484
|
-
offset = borderRadius - Math.sqrt(Math.max(0, borderRadius * borderRadius - dist * dist));
|
|
485
|
-
}
|
|
486
|
-
return offset;
|
|
487
|
-
};
|
|
488
|
-
targetCtx.beginPath();
|
|
489
498
|
let first = true;
|
|
490
499
|
const step = 2;
|
|
491
500
|
const len = acc.heights.length;
|
|
492
501
|
for (let x = 0; x < len; x += step) {
|
|
493
502
|
const height = acc.heights[x] || 0;
|
|
494
503
|
const px = rect.left + x + dx;
|
|
495
|
-
const py = baseY - height +
|
|
504
|
+
const py = baseY - height + (acc.curveOffsets[x] || 0) + dy;
|
|
496
505
|
if (first) {
|
|
497
|
-
|
|
506
|
+
ctx.moveTo(px, py);
|
|
498
507
|
first = false;
|
|
499
508
|
} else {
|
|
500
|
-
|
|
509
|
+
ctx.lineTo(px, py);
|
|
501
510
|
}
|
|
502
511
|
}
|
|
503
512
|
if ((len - 1) % step !== 0) {
|
|
504
513
|
const x = len - 1;
|
|
505
514
|
const height = acc.heights[x] || 0;
|
|
506
515
|
const px = rect.left + x + dx;
|
|
507
|
-
const py = baseY - height +
|
|
508
|
-
|
|
516
|
+
const py = baseY - height + (acc.curveOffsets[x] || 0) + dy;
|
|
517
|
+
ctx.lineTo(px, py);
|
|
509
518
|
}
|
|
510
519
|
for (let x = len - 1; x >= 0; x -= step) {
|
|
511
520
|
const px = rect.left + x + dx;
|
|
512
|
-
const py = baseY +
|
|
513
|
-
|
|
521
|
+
const py = baseY + (acc.curveOffsets[x] || 0) + dy;
|
|
522
|
+
ctx.lineTo(px, py);
|
|
514
523
|
}
|
|
515
524
|
const startX = 0;
|
|
516
525
|
const startPx = rect.left + startX + dx;
|
|
517
|
-
const startPy = baseY +
|
|
518
|
-
|
|
519
|
-
targetCtx.closePath();
|
|
520
|
-
targetCtx.fill();
|
|
526
|
+
const startPy = baseY + (acc.curveOffsets[startX] || 0) + dy;
|
|
527
|
+
ctx.lineTo(startPx, startPy);
|
|
521
528
|
}
|
|
529
|
+
ctx.fill();
|
|
522
530
|
ctx.shadowBlur = 0;
|
|
523
531
|
ctx.shadowOffsetY = 0;
|
|
524
|
-
if (fixedCtx) {
|
|
525
|
-
fixedCtx.shadowBlur = 0;
|
|
526
|
-
fixedCtx.shadowOffsetY = 0;
|
|
527
|
-
}
|
|
528
532
|
};
|
|
529
|
-
var drawSideAccumulations = (ctx,
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
533
|
+
var drawSideAccumulations = (ctx, elementRects) => {
|
|
534
|
+
ctx.fillStyle = ACC_FILL_STYLE;
|
|
535
|
+
ctx.shadowColor = ACC_SHADOW_COLOR;
|
|
536
|
+
ctx.shadowBlur = 3;
|
|
537
|
+
ctx.globalAlpha = 1;
|
|
538
|
+
const currentScrollX = window.scrollX;
|
|
539
|
+
const currentScrollY = window.scrollY;
|
|
540
|
+
ctx.beginPath();
|
|
541
|
+
const drawSide = (sideArray, isLeft, multipliers, rect, dx, dy) => {
|
|
542
|
+
const baseX = isLeft ? rect.left : rect.right;
|
|
543
|
+
ctx.moveTo(baseX + dx, rect.top + dy);
|
|
544
|
+
for (let y = 0; y < sideArray.length; y += 2) {
|
|
545
|
+
const width = sideArray[y] || 0;
|
|
546
|
+
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
547
|
+
const nextWidth = sideArray[nextY] || 0;
|
|
548
|
+
const gravityMultiplier = multipliers[y] || 0;
|
|
549
|
+
const py = rect.top + y + dy;
|
|
550
|
+
const px = (isLeft ? baseX - width * gravityMultiplier : baseX + width * gravityMultiplier) + dx;
|
|
551
|
+
const ny = rect.top + nextY + dy;
|
|
552
|
+
const nGravityMultiplier = multipliers[nextY] || 0;
|
|
553
|
+
const nx = (isLeft ? baseX - nextWidth * nGravityMultiplier : baseX + nextWidth * nGravityMultiplier) + dx;
|
|
554
|
+
ctx.lineTo(px, py);
|
|
555
|
+
ctx.lineTo(nx, ny);
|
|
556
|
+
}
|
|
557
|
+
ctx.lineTo(baseX + dx, rect.bottom + dy);
|
|
534
558
|
};
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const scrollX = window.scrollX;
|
|
538
|
-
const scrollY = window.scrollY;
|
|
539
|
-
for (const { rect, acc } of elementRects) {
|
|
559
|
+
for (const item of elementRects) {
|
|
560
|
+
const { rect, acc } = item;
|
|
540
561
|
if (acc.maxSideHeight === 0) continue;
|
|
541
562
|
const hasLeftSnow = acc.leftSide.some((h) => h > 0.3);
|
|
542
563
|
const hasRightSnow = acc.rightSide.some((h) => h > 0.3);
|
|
543
564
|
if (!hasLeftSnow && !hasRightSnow) continue;
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const drawSide = (sideArray, isLeft) => {
|
|
549
|
-
targetCtx.beginPath();
|
|
550
|
-
const baseX = isLeft ? rect.left : rect.right;
|
|
551
|
-
targetCtx.moveTo(baseX + dx, rect.top + dy);
|
|
552
|
-
for (let y = 0; y < sideArray.length; y += 2) {
|
|
553
|
-
const width = sideArray[y] || 0;
|
|
554
|
-
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
555
|
-
const nextWidth = sideArray[nextY] || 0;
|
|
556
|
-
const heightRatio = y / sideArray.length;
|
|
557
|
-
const gravityMultiplier = Math.pow(heightRatio, 1.5);
|
|
558
|
-
const py = rect.top + y + dy;
|
|
559
|
-
const px = (isLeft ? baseX - width * gravityMultiplier : baseX + width * gravityMultiplier) + dx;
|
|
560
|
-
const ny = rect.top + nextY + dy;
|
|
561
|
-
const nRatio = nextY / sideArray.length;
|
|
562
|
-
const nGravityMultiplier = Math.pow(nRatio, 1.5);
|
|
563
|
-
const nx = (isLeft ? baseX - nextWidth * nGravityMultiplier : baseX + nextWidth * nGravityMultiplier) + dx;
|
|
564
|
-
targetCtx.lineTo(px, py);
|
|
565
|
-
targetCtx.lineTo(nx, ny);
|
|
566
|
-
}
|
|
567
|
-
targetCtx.lineTo(baseX + dx, rect.bottom + dy);
|
|
568
|
-
targetCtx.closePath();
|
|
569
|
-
targetCtx.fill();
|
|
570
|
-
};
|
|
571
|
-
if (hasLeftSnow) drawSide(acc.leftSide, true);
|
|
572
|
-
if (hasRightSnow) drawSide(acc.rightSide, false);
|
|
565
|
+
const dx = currentScrollX;
|
|
566
|
+
const dy = currentScrollY;
|
|
567
|
+
if (hasLeftSnow) drawSide(acc.leftSide, true, acc.sideGravityMultipliers, rect, dx, dy);
|
|
568
|
+
if (hasRightSnow) drawSide(acc.rightSide, false, acc.sideGravityMultipliers, rect, dx, dy);
|
|
573
569
|
}
|
|
570
|
+
ctx.fill();
|
|
574
571
|
ctx.shadowBlur = 0;
|
|
575
|
-
if (fixedCtx) fixedCtx.shadowBlur = 0;
|
|
576
572
|
};
|
|
577
573
|
|
|
578
574
|
// src/Snowfall.tsx
|
|
@@ -599,7 +595,7 @@ function Snowfall() {
|
|
|
599
595
|
drawTime: 0
|
|
600
596
|
});
|
|
601
597
|
(0, import_react2.useEffect)(() => {
|
|
602
|
-
setIsMounted(true);
|
|
598
|
+
requestAnimationFrame(() => setIsMounted(true));
|
|
603
599
|
}, []);
|
|
604
600
|
(0, import_react2.useEffect)(() => {
|
|
605
601
|
isEnabledRef.current = isEnabled;
|
|
@@ -618,31 +614,49 @@ function Snowfall() {
|
|
|
618
614
|
if (!ctx) return;
|
|
619
615
|
const resizeCanvas = () => {
|
|
620
616
|
if (canvasRef.current) {
|
|
621
|
-
const
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
617
|
+
const newWidth = window.innerWidth;
|
|
618
|
+
const newHeight = window.innerHeight;
|
|
619
|
+
const dpr = window.devicePixelRatio || 1;
|
|
620
|
+
canvasRef.current.width = newWidth * dpr;
|
|
621
|
+
canvasRef.current.height = newHeight * dpr;
|
|
622
|
+
canvasRef.current.style.width = `${newWidth}px`;
|
|
623
|
+
canvasRef.current.style.height = `${newHeight}px`;
|
|
627
624
|
}
|
|
628
625
|
};
|
|
629
626
|
resizeCanvas();
|
|
630
|
-
const
|
|
627
|
+
const windowResizeObserver = new ResizeObserver(() => {
|
|
631
628
|
resizeCanvas();
|
|
632
629
|
});
|
|
633
|
-
|
|
630
|
+
windowResizeObserver.observe(document.body);
|
|
631
|
+
const surfaceObserver = new ResizeObserver((entries) => {
|
|
632
|
+
let needsUpdate = false;
|
|
633
|
+
for (const entry of entries) {
|
|
634
|
+
if (entry.target.isConnected) {
|
|
635
|
+
needsUpdate = true;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (needsUpdate) {
|
|
640
|
+
initAccumulationWrapper();
|
|
641
|
+
}
|
|
642
|
+
});
|
|
634
643
|
snowflakesRef.current = [];
|
|
635
644
|
const initAccumulationWrapper = () => {
|
|
636
645
|
const scanStart = performance.now();
|
|
637
646
|
initializeAccumulation(accumulationRef.current, physicsConfigRef.current);
|
|
647
|
+
surfaceObserver.disconnect();
|
|
648
|
+
for (const [el] of accumulationRef.current) {
|
|
649
|
+
surfaceObserver.observe(el);
|
|
650
|
+
}
|
|
638
651
|
metricsRef.current.scanTime = performance.now() - scanStart;
|
|
639
652
|
};
|
|
640
653
|
initAccumulationWrapper();
|
|
641
|
-
|
|
654
|
+
requestAnimationFrame(() => {
|
|
655
|
+
if (isMounted) setIsVisible(true);
|
|
656
|
+
});
|
|
642
657
|
let lastTime = 0;
|
|
643
|
-
let lastRectUpdate = 0;
|
|
644
658
|
let lastMetricsUpdate = 0;
|
|
645
|
-
let
|
|
659
|
+
let elementRects = [];
|
|
646
660
|
const animate = (currentTime) => {
|
|
647
661
|
if (lastTime === 0) {
|
|
648
662
|
lastTime = currentTime;
|
|
@@ -658,29 +672,36 @@ function Snowfall() {
|
|
|
658
672
|
const dt = deltaTime / 16.67;
|
|
659
673
|
const frameStartTime = performance.now();
|
|
660
674
|
const clearStart = performance.now();
|
|
661
|
-
|
|
675
|
+
const dpr = window.devicePixelRatio || 1;
|
|
676
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
677
|
+
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
678
|
+
const scrollX = window.scrollX;
|
|
679
|
+
const scrollY = window.scrollY;
|
|
680
|
+
ctx.translate(-scrollX, -scrollY);
|
|
662
681
|
metricsRef.current.clearTime = performance.now() - clearStart;
|
|
663
682
|
const snowflakes = snowflakesRef.current;
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
metricsRef.current.rectUpdateTime = performance.now() - rectStart;
|
|
668
|
-
lastRectUpdate = currentTime;
|
|
669
|
-
}
|
|
683
|
+
const rectStart = performance.now();
|
|
684
|
+
elementRects = getElementRects(accumulationRef.current);
|
|
685
|
+
metricsRef.current.rectUpdateTime = performance.now() - rectStart;
|
|
670
686
|
const physicsStart = performance.now();
|
|
671
|
-
meltAndSmoothAccumulation(
|
|
672
|
-
updateSnowflakes(
|
|
687
|
+
meltAndSmoothAccumulation(elementRects, physicsConfigRef.current, dt);
|
|
688
|
+
updateSnowflakes(
|
|
689
|
+
snowflakes,
|
|
690
|
+
elementRects,
|
|
691
|
+
physicsConfigRef.current,
|
|
692
|
+
dt,
|
|
693
|
+
document.documentElement.scrollWidth,
|
|
694
|
+
document.documentElement.scrollHeight
|
|
695
|
+
);
|
|
673
696
|
metricsRef.current.physicsTime = performance.now() - physicsStart;
|
|
674
697
|
const drawStart = performance.now();
|
|
675
|
-
|
|
676
|
-
drawSnowflake(ctx, flake);
|
|
677
|
-
}
|
|
698
|
+
drawSnowflakes(ctx, snowflakes);
|
|
678
699
|
if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
|
|
679
700
|
const isBackground = Math.random() < 0.4;
|
|
680
|
-
snowflakes.push(createSnowflake(
|
|
701
|
+
snowflakes.push(createSnowflake(document.documentElement.scrollWidth, physicsConfigRef.current, isBackground));
|
|
681
702
|
}
|
|
682
|
-
drawAccumulations(ctx,
|
|
683
|
-
drawSideAccumulations(ctx,
|
|
703
|
+
drawAccumulations(ctx, elementRects);
|
|
704
|
+
drawSideAccumulations(ctx, elementRects);
|
|
684
705
|
metricsRef.current.drawTime = performance.now() - drawStart;
|
|
685
706
|
metricsRef.current.frameTime = performance.now() - frameStartTime;
|
|
686
707
|
if (currentTime - lastMetricsUpdate > 500) {
|
|
@@ -713,7 +734,8 @@ function Snowfall() {
|
|
|
713
734
|
cancelAnimationFrame(animationIdRef.current);
|
|
714
735
|
window.removeEventListener("resize", handleResize);
|
|
715
736
|
clearInterval(checkInterval);
|
|
716
|
-
|
|
737
|
+
windowResizeObserver.disconnect();
|
|
738
|
+
surfaceObserver.disconnect();
|
|
717
739
|
};
|
|
718
740
|
}, [isMounted]);
|
|
719
741
|
if (!isMounted) return null;
|
|
@@ -722,7 +744,8 @@ function Snowfall() {
|
|
|
722
744
|
{
|
|
723
745
|
ref: canvasRef,
|
|
724
746
|
style: {
|
|
725
|
-
position: "
|
|
747
|
+
position: "fixed",
|
|
748
|
+
// FIXED position to eliminate scroll jitter
|
|
726
749
|
top: 0,
|
|
727
750
|
left: 0,
|
|
728
751
|
pointerEvents: "none",
|
|
@@ -739,9 +762,9 @@ function Snowfall() {
|
|
|
739
762
|
// src/DebugPanel.tsx
|
|
740
763
|
var import_react3 = require("react");
|
|
741
764
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
742
|
-
function DebugPanel() {
|
|
765
|
+
function DebugPanel({ defaultOpen = true }) {
|
|
743
766
|
const { debugMode, toggleDebug, metrics } = useSnowfall();
|
|
744
|
-
const [isMinimized, setIsMinimized] = (0, import_react3.useState)(
|
|
767
|
+
const [isMinimized, setIsMinimized] = (0, import_react3.useState)(!defaultOpen);
|
|
745
768
|
const [copied, setCopied] = (0, import_react3.useState)(false);
|
|
746
769
|
(0, import_react3.useEffect)(() => {
|
|
747
770
|
const handleKeyDown = (e) => {
|
|
@@ -769,145 +792,199 @@ function DebugPanel() {
|
|
|
769
792
|
}
|
|
770
793
|
};
|
|
771
794
|
if (!debugMode) return null;
|
|
772
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
797
|
-
"
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
"ms"
|
|
855
|
-
] }),
|
|
856
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
857
|
-
"Physics: ",
|
|
858
|
-
metrics.physicsTime?.toFixed(2) || 0,
|
|
859
|
-
"ms"
|
|
860
|
-
] }),
|
|
861
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
862
|
-
"Draw: ",
|
|
863
|
-
metrics.drawTime?.toFixed(2) || 0,
|
|
864
|
-
"ms"
|
|
865
|
-
] }),
|
|
866
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
867
|
-
"Scan: ",
|
|
868
|
-
metrics.scanTime.toFixed(2),
|
|
869
|
-
"ms"
|
|
870
|
-
] }),
|
|
871
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
872
|
-
"Rect Update: ",
|
|
873
|
-
metrics.rectUpdateTime.toFixed(2),
|
|
874
|
-
"ms"
|
|
875
|
-
] })
|
|
876
|
-
] }),
|
|
877
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "10px", paddingBottom: "10px", borderBottom: "1px solid #333" }, children: [
|
|
878
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#ff0", marginBottom: "5px", fontWeight: "bold" }, children: "\u{1F4CA} COUNTS" }),
|
|
879
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
880
|
-
"Snowflakes: ",
|
|
881
|
-
metrics.flakeCount,
|
|
882
|
-
" / ",
|
|
883
|
-
metrics.maxFlakes
|
|
795
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
796
|
+
"div",
|
|
797
|
+
{
|
|
798
|
+
"data-snowfall": "top",
|
|
799
|
+
style: {
|
|
800
|
+
position: "fixed",
|
|
801
|
+
bottom: "80px",
|
|
802
|
+
left: "24px",
|
|
803
|
+
backgroundColor: "rgba(15, 23, 42, 0.75)",
|
|
804
|
+
backdropFilter: "blur(16px)",
|
|
805
|
+
WebkitBackdropFilter: "blur(16px)",
|
|
806
|
+
color: "#e2e8f0",
|
|
807
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
808
|
+
fontSize: "12px",
|
|
809
|
+
padding: isMinimized ? "12px" : "20px",
|
|
810
|
+
borderRadius: "16px",
|
|
811
|
+
zIndex: 1e4,
|
|
812
|
+
minWidth: isMinimized ? "auto" : "300px",
|
|
813
|
+
maxWidth: "100%",
|
|
814
|
+
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
815
|
+
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 8px 10px -6px rgba(0, 0, 0, 0.2)",
|
|
816
|
+
transition: "all 0.2s ease"
|
|
817
|
+
},
|
|
818
|
+
children: [
|
|
819
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
|
|
820
|
+
display: "flex",
|
|
821
|
+
justifyContent: "space-between",
|
|
822
|
+
alignItems: "center",
|
|
823
|
+
marginBottom: isMinimized ? 0 : "16px",
|
|
824
|
+
gap: "16px"
|
|
825
|
+
}, children: [
|
|
826
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontWeight: "600", color: "#fff", display: "flex", alignItems: "center", gap: "8px" }, children: [
|
|
827
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: "14px" }, children: "\u2744\uFE0F" }),
|
|
828
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: {
|
|
829
|
+
background: "linear-gradient(to right, #60a5fa, #22d3ee)",
|
|
830
|
+
WebkitBackgroundClip: "text",
|
|
831
|
+
WebkitTextFillColor: "transparent",
|
|
832
|
+
fontWeight: "700"
|
|
833
|
+
}, children: "DEBUG" })
|
|
834
|
+
] }),
|
|
835
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
836
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
837
|
+
"button",
|
|
838
|
+
{
|
|
839
|
+
onClick: () => setIsMinimized(!isMinimized),
|
|
840
|
+
style: {
|
|
841
|
+
background: "rgba(255, 255, 255, 0.1)",
|
|
842
|
+
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
843
|
+
color: "#fff",
|
|
844
|
+
cursor: "pointer",
|
|
845
|
+
width: "24px",
|
|
846
|
+
height: "24px",
|
|
847
|
+
display: "flex",
|
|
848
|
+
alignItems: "center",
|
|
849
|
+
justifyContent: "center",
|
|
850
|
+
borderRadius: "6px",
|
|
851
|
+
fontSize: "10px"
|
|
852
|
+
},
|
|
853
|
+
children: isMinimized ? "\u25B2" : "\u25BC"
|
|
854
|
+
}
|
|
855
|
+
),
|
|
856
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
857
|
+
"button",
|
|
858
|
+
{
|
|
859
|
+
onClick: toggleDebug,
|
|
860
|
+
style: {
|
|
861
|
+
background: "rgba(239, 68, 68, 0.15)",
|
|
862
|
+
border: "1px solid rgba(239, 68, 68, 0.2)",
|
|
863
|
+
color: "#f87171",
|
|
864
|
+
cursor: "pointer",
|
|
865
|
+
width: "24px",
|
|
866
|
+
height: "24px",
|
|
867
|
+
display: "flex",
|
|
868
|
+
alignItems: "center",
|
|
869
|
+
justifyContent: "center",
|
|
870
|
+
borderRadius: "6px",
|
|
871
|
+
fontSize: "12px"
|
|
872
|
+
},
|
|
873
|
+
children: "\u2715"
|
|
874
|
+
}
|
|
875
|
+
)
|
|
876
|
+
] })
|
|
884
877
|
] }),
|
|
885
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
886
|
-
"
|
|
887
|
-
|
|
878
|
+
!isMinimized && metrics && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
879
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "12px", paddingBottom: "12px", borderBottom: "1px solid rgba(255,255,255,0.08)" }, children: [
|
|
880
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#94a3b8", marginBottom: "8px", fontSize: "11px", fontWeight: "600", letterSpacing: "0.5px", textTransform: "uppercase" }, children: "Performance" }),
|
|
881
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px" }, children: [
|
|
882
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "FPS" }),
|
|
883
|
+
/* @__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) })
|
|
884
|
+
] }),
|
|
885
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px" }, children: [
|
|
886
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Frame Time" }),
|
|
887
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { fontFamily: "monospace" }, children: [
|
|
888
|
+
metrics.frameTime.toFixed(2),
|
|
889
|
+
"ms"
|
|
890
|
+
] })
|
|
891
|
+
] }),
|
|
892
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
893
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: metrics.rafGap && metrics.rafGap > 20 ? "#fbbf24" : "inherit" }, children: "rAF Gap" }),
|
|
894
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { fontFamily: "monospace" }, children: [
|
|
895
|
+
metrics.rafGap?.toFixed(1) || 0,
|
|
896
|
+
"ms"
|
|
897
|
+
] })
|
|
898
|
+
] })
|
|
899
|
+
] }),
|
|
900
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "12px", paddingBottom: "12px", borderBottom: "1px solid rgba(255,255,255,0.08)" }, children: [
|
|
901
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#94a3b8", marginBottom: "8px", fontSize: "11px", fontWeight: "600", letterSpacing: "0.5px", textTransform: "uppercase" }, children: "Detailed Timings" }),
|
|
902
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "4px 12px" }, children: [
|
|
903
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
904
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Clear" }),
|
|
905
|
+
" ",
|
|
906
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { fontFamily: "monospace" }, children: [
|
|
907
|
+
metrics.clearTime?.toFixed(2) || 0,
|
|
908
|
+
"ms"
|
|
909
|
+
] })
|
|
910
|
+
] }),
|
|
911
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Physics" }),
|
|
913
|
+
" ",
|
|
914
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { fontFamily: "monospace" }, children: [
|
|
915
|
+
metrics.physicsTime?.toFixed(2) || 0,
|
|
916
|
+
"ms"
|
|
917
|
+
] })
|
|
918
|
+
] }),
|
|
919
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
920
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Draw" }),
|
|
921
|
+
" ",
|
|
922
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { fontFamily: "monospace" }, children: [
|
|
923
|
+
metrics.drawTime?.toFixed(2) || 0,
|
|
924
|
+
"ms"
|
|
925
|
+
] })
|
|
926
|
+
] }),
|
|
927
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
928
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Scan" }),
|
|
929
|
+
" ",
|
|
930
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { fontFamily: "monospace" }, children: [
|
|
931
|
+
metrics.scanTime.toFixed(2),
|
|
932
|
+
"ms"
|
|
933
|
+
] })
|
|
934
|
+
] }),
|
|
935
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", gridColumn: "span 2" }, children: [
|
|
936
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Rect Update" }),
|
|
937
|
+
" ",
|
|
938
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { fontFamily: "monospace" }, children: [
|
|
939
|
+
metrics.rectUpdateTime.toFixed(2),
|
|
940
|
+
"ms"
|
|
941
|
+
] })
|
|
942
|
+
] })
|
|
943
|
+
] })
|
|
944
|
+
] }),
|
|
945
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "16px" }, children: [
|
|
946
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#94a3b8", marginBottom: "8px", fontSize: "11px", fontWeight: "600", letterSpacing: "0.5px", textTransform: "uppercase" }, children: "Counts" }),
|
|
947
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px" }, children: [
|
|
948
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Snowflakes" }),
|
|
949
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { fontFamily: "monospace" }, children: [
|
|
950
|
+
metrics.flakeCount,
|
|
951
|
+
" / ",
|
|
952
|
+
metrics.maxFlakes
|
|
953
|
+
] })
|
|
954
|
+
] }),
|
|
955
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
956
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Surfaces" }),
|
|
957
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontFamily: "monospace" }, children: metrics.surfaceCount })
|
|
958
|
+
] })
|
|
959
|
+
] }),
|
|
960
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
961
|
+
"button",
|
|
962
|
+
{
|
|
963
|
+
onClick: copyToClipboard,
|
|
964
|
+
style: {
|
|
965
|
+
width: "100%",
|
|
966
|
+
padding: "10px",
|
|
967
|
+
background: copied ? "rgba(34, 197, 94, 0.2)" : "rgba(255, 255, 255, 0.05)",
|
|
968
|
+
border: copied ? "1px solid rgba(34, 197, 94, 0.5)" : "1px solid rgba(255, 255, 255, 0.1)",
|
|
969
|
+
color: copied ? "#4ade80" : "#fff",
|
|
970
|
+
cursor: "pointer",
|
|
971
|
+
borderRadius: "8px",
|
|
972
|
+
fontSize: "11px",
|
|
973
|
+
fontWeight: "600",
|
|
974
|
+
transition: "all 0.2s",
|
|
975
|
+
display: "flex",
|
|
976
|
+
alignItems: "center",
|
|
977
|
+
justifyContent: "center",
|
|
978
|
+
gap: "6px"
|
|
979
|
+
},
|
|
980
|
+
children: copied ? "\u2713 COPIED" : "\u{1F4CB} COPY METRICS"
|
|
981
|
+
}
|
|
982
|
+
),
|
|
983
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginTop: "12px", fontSize: "10px", color: "#64748b", textAlign: "center" }, children: "Shift+D to toggle" })
|
|
888
984
|
] })
|
|
889
|
-
]
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
{
|
|
893
|
-
onClick: copyToClipboard,
|
|
894
|
-
style: {
|
|
895
|
-
width: "100%",
|
|
896
|
-
padding: "8px",
|
|
897
|
-
background: copied ? "#0a0" : "#000",
|
|
898
|
-
border: copied ? "1px solid #0f0" : "1px solid #555",
|
|
899
|
-
color: "#0f0",
|
|
900
|
-
cursor: "pointer",
|
|
901
|
-
borderRadius: "4px",
|
|
902
|
-
fontSize: "11px",
|
|
903
|
-
fontFamily: "monospace"
|
|
904
|
-
},
|
|
905
|
-
children: copied ? "\u2713 COPIED!" : "\u{1F4CB} COPY METRICS"
|
|
906
|
-
}
|
|
907
|
-
),
|
|
908
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginTop: "8px", fontSize: "10px", color: "#666", textAlign: "center" }, children: "Shift+D to toggle" })
|
|
909
|
-
] })
|
|
910
|
-
] });
|
|
985
|
+
]
|
|
986
|
+
}
|
|
987
|
+
);
|
|
911
988
|
}
|
|
912
989
|
// Annotate the CommonJS export names for ESM import in node:
|
|
913
990
|
0 && (module.exports = {
|