@hdcodedev/snowfall 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/index.d.mts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +433 -169
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +433 -170
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -23,15 +23,21 @@ var DEFAULT_PHYSICS = {
|
|
|
23
23
|
FLAKE_SIZE: {
|
|
24
24
|
MIN: 0.5,
|
|
25
25
|
MAX: 1.6
|
|
26
|
-
}
|
|
26
|
+
},
|
|
27
|
+
MAX_SURFACES: 15
|
|
27
28
|
};
|
|
28
29
|
var SnowfallContext = createContext(void 0);
|
|
29
|
-
function SnowfallProvider({ children }) {
|
|
30
|
+
function SnowfallProvider({ children, initialDebug = false }) {
|
|
30
31
|
const [isEnabled, setIsEnabled] = useState(true);
|
|
31
32
|
const [physicsConfig, setPhysicsConfig] = useState(DEFAULT_PHYSICS);
|
|
33
|
+
const [debugMode, setDebugMode] = useState(initialDebug);
|
|
34
|
+
const [metrics, setMetrics] = useState(null);
|
|
32
35
|
const toggleSnow = () => {
|
|
33
36
|
setIsEnabled((prev) => !prev);
|
|
34
37
|
};
|
|
38
|
+
const toggleDebug = () => {
|
|
39
|
+
setDebugMode((prev) => !prev);
|
|
40
|
+
};
|
|
35
41
|
const updatePhysicsConfig = (config) => {
|
|
36
42
|
setPhysicsConfig((prev) => ({
|
|
37
43
|
...prev,
|
|
@@ -58,7 +64,11 @@ function SnowfallProvider({ children }) {
|
|
|
58
64
|
toggleSnow,
|
|
59
65
|
physicsConfig,
|
|
60
66
|
updatePhysicsConfig,
|
|
61
|
-
resetPhysics
|
|
67
|
+
resetPhysics,
|
|
68
|
+
debugMode,
|
|
69
|
+
toggleDebug,
|
|
70
|
+
metrics,
|
|
71
|
+
setMetrics
|
|
62
72
|
}, children });
|
|
63
73
|
}
|
|
64
74
|
function useSnowfall() {
|
|
@@ -75,13 +85,15 @@ var VAL_IGNORE = "ignore";
|
|
|
75
85
|
var VAL_TOP = "top";
|
|
76
86
|
var VAL_BOTTOM = "bottom";
|
|
77
87
|
var TAG_HEADER = "header";
|
|
88
|
+
var TAG_FOOTER = "footer";
|
|
78
89
|
var ROLE_BANNER = "banner";
|
|
90
|
+
var ROLE_CONTENTINFO = "contentinfo";
|
|
79
91
|
|
|
80
92
|
// src/utils/snowfall/dom.ts
|
|
81
93
|
var BOTTOM_TAGS = [TAG_HEADER];
|
|
82
94
|
var BOTTOM_ROLES = [ROLE_BANNER];
|
|
83
|
-
var AUTO_DETECT_TAGS = [
|
|
84
|
-
var AUTO_DETECT_ROLES = [
|
|
95
|
+
var AUTO_DETECT_TAGS = [TAG_HEADER, TAG_FOOTER, "article", "section", "aside", "nav"];
|
|
96
|
+
var AUTO_DETECT_ROLES = [`[role="${ROLE_BANNER}"]`, `[role="${ROLE_CONTENTINFO}"]`, '[role="main"]'];
|
|
85
97
|
var AUTO_DETECT_CLASSES = [
|
|
86
98
|
".card",
|
|
87
99
|
'[class*="card"]',
|
|
@@ -97,21 +109,21 @@ var getElementType = (el) => {
|
|
|
97
109
|
if (role && BOTTOM_ROLES.includes(role)) return VAL_BOTTOM;
|
|
98
110
|
return VAL_TOP;
|
|
99
111
|
};
|
|
100
|
-
var shouldAccumulate = (el) => {
|
|
112
|
+
var shouldAccumulate = (el, precomputedStyle) => {
|
|
101
113
|
if (el.getAttribute(ATTR_SNOWFALL) === VAL_IGNORE) return false;
|
|
102
114
|
if (el.hasAttribute(ATTR_SNOWFALL)) return true;
|
|
103
|
-
const styles = window.getComputedStyle(el);
|
|
104
|
-
const rect = el.getBoundingClientRect();
|
|
115
|
+
const styles = precomputedStyle || window.getComputedStyle(el);
|
|
105
116
|
const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
|
|
106
117
|
if (!isVisible) return false;
|
|
107
118
|
const bgColor = styles.backgroundColor;
|
|
108
119
|
const hasBackground = bgColor !== "rgba(0, 0, 0, 0)" && bgColor !== "transparent";
|
|
109
|
-
const hasBorder = parseFloat(styles.borderWidth) > 0
|
|
120
|
+
const hasBorder = parseFloat(styles.borderWidth) > 0 && styles.borderColor !== "transparent" && styles.borderColor !== "rgba(0, 0, 0, 0)" && styles.borderStyle !== "none";
|
|
110
121
|
const hasBoxShadow = styles.boxShadow !== "none";
|
|
111
|
-
const
|
|
112
|
-
|
|
122
|
+
const hasFilter = styles.filter !== "none" && styles.filter.includes("drop-shadow");
|
|
123
|
+
const hasBackdropFilter = styles.backdropFilter !== "none";
|
|
124
|
+
return hasBackground || hasBorder || hasBoxShadow || hasFilter || hasBackdropFilter;
|
|
113
125
|
};
|
|
114
|
-
var getAccumulationSurfaces = () => {
|
|
126
|
+
var getAccumulationSurfaces = (maxSurfaces = 5) => {
|
|
115
127
|
const surfaces = [];
|
|
116
128
|
const seen = /* @__PURE__ */ new Set();
|
|
117
129
|
const candidates = document.querySelectorAll(
|
|
@@ -122,44 +134,31 @@ var getAccumulationSurfaces = () => {
|
|
|
122
134
|
...AUTO_DETECT_CLASSES
|
|
123
135
|
].join(", ")
|
|
124
136
|
);
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
137
|
+
for (const el of candidates) {
|
|
138
|
+
if (surfaces.length >= maxSurfaces) break;
|
|
139
|
+
if (seen.has(el)) continue;
|
|
128
140
|
const manualOverride = el.getAttribute(ATTR_SNOWFALL);
|
|
129
|
-
if (manualOverride === VAL_IGNORE)
|
|
141
|
+
if (manualOverride === VAL_IGNORE) continue;
|
|
130
142
|
const isManuallyIncluded = manualOverride !== null;
|
|
131
143
|
const styles = window.getComputedStyle(el);
|
|
132
144
|
const isVisible = styles.display !== "none" && styles.visibility !== "hidden" && parseFloat(styles.opacity) > 0.1;
|
|
133
|
-
if (!isVisible && !isManuallyIncluded)
|
|
145
|
+
if (!isVisible && !isManuallyIncluded) continue;
|
|
146
|
+
const rect = el.getBoundingClientRect();
|
|
134
147
|
const hasSize = rect.width >= 100 && rect.height >= 50;
|
|
135
|
-
if (!hasSize && !isManuallyIncluded)
|
|
148
|
+
if (!hasSize && !isManuallyIncluded) continue;
|
|
136
149
|
const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;
|
|
137
150
|
const isBottomTag = BOTTOM_TAGS.includes(el.tagName.toLowerCase());
|
|
138
151
|
const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute("role") || "");
|
|
139
152
|
const isBottomSurface = isBottomTag || isBottomRole || manualOverride === VAL_BOTTOM;
|
|
140
|
-
if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded)
|
|
141
|
-
|
|
142
|
-
let currentEl = el;
|
|
143
|
-
while (currentEl && currentEl !== document.body) {
|
|
144
|
-
const style = window.getComputedStyle(currentEl);
|
|
145
|
-
if (style.position === "fixed" || style.position === "sticky") {
|
|
146
|
-
isFixed = true;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
currentEl = currentEl.parentElement;
|
|
150
|
-
}
|
|
151
|
-
if (shouldAccumulate(el)) {
|
|
153
|
+
if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) continue;
|
|
154
|
+
if (shouldAccumulate(el, styles)) {
|
|
152
155
|
let type = getElementType(el);
|
|
153
|
-
if (manualOverride === VAL_BOTTOM)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
type = VAL_TOP;
|
|
157
|
-
}
|
|
158
|
-
surfaces.push({ el, type, isFixed });
|
|
156
|
+
if (manualOverride === VAL_BOTTOM) type = VAL_BOTTOM;
|
|
157
|
+
else if (manualOverride === VAL_TOP) type = VAL_TOP;
|
|
158
|
+
surfaces.push({ el, type });
|
|
159
159
|
seen.add(el);
|
|
160
160
|
}
|
|
161
|
-
}
|
|
162
|
-
console.log(`[Snowfall] Auto-detection found ${surfaces.length} surfaces`);
|
|
161
|
+
}
|
|
163
162
|
return surfaces;
|
|
164
163
|
};
|
|
165
164
|
var getElementRects = (accumulationMap) => {
|
|
@@ -167,31 +166,18 @@ var getElementRects = (accumulationMap) => {
|
|
|
167
166
|
for (const [el, acc] of accumulationMap.entries()) {
|
|
168
167
|
if (!el.isConnected) continue;
|
|
169
168
|
const rect = el.getBoundingClientRect();
|
|
170
|
-
|
|
171
|
-
left: rect.left + window.scrollX,
|
|
172
|
-
right: rect.right + window.scrollX,
|
|
173
|
-
top: rect.top + window.scrollY,
|
|
174
|
-
bottom: rect.bottom + window.scrollY,
|
|
175
|
-
width: rect.width,
|
|
176
|
-
height: rect.height,
|
|
177
|
-
x: rect.x,
|
|
178
|
-
// Note: these are strictly viewport relative in DOMRect usually,
|
|
179
|
-
// but we just need consistent absolute coords for physics
|
|
180
|
-
y: rect.y,
|
|
181
|
-
toJSON: rect.toJSON
|
|
182
|
-
};
|
|
183
|
-
elementRects.push({ el, rect: absoluteRect, acc });
|
|
169
|
+
elementRects.push({ el, rect, acc });
|
|
184
170
|
}
|
|
185
171
|
return elementRects;
|
|
186
172
|
};
|
|
187
173
|
|
|
188
174
|
// src/utils/snowfall/physics.ts
|
|
189
|
-
var createSnowflake = (
|
|
175
|
+
var createSnowflake = (worldWidth, config, isBackground = false) => {
|
|
190
176
|
if (isBackground) {
|
|
191
177
|
const sizeRatio = Math.random();
|
|
192
178
|
const radius = config.FLAKE_SIZE.MIN * 0.6 + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN) * 0.4;
|
|
193
179
|
return {
|
|
194
|
-
x: Math.random() *
|
|
180
|
+
x: Math.random() * worldWidth,
|
|
195
181
|
y: window.scrollY - 5,
|
|
196
182
|
radius,
|
|
197
183
|
speed: radius * 0.3 + Math.random() * 0.2 + 0.2,
|
|
@@ -206,7 +192,7 @@ var createSnowflake = (canvasWidth, config, isBackground = false) => {
|
|
|
206
192
|
const sizeRatio = Math.random();
|
|
207
193
|
const radius = config.FLAKE_SIZE.MIN + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN);
|
|
208
194
|
return {
|
|
209
|
-
x: Math.random() *
|
|
195
|
+
x: Math.random() * worldWidth,
|
|
210
196
|
y: window.scrollY - 5,
|
|
211
197
|
radius,
|
|
212
198
|
speed: radius * 0.5 + Math.random() * 0.3 + 0.5,
|
|
@@ -220,20 +206,19 @@ var createSnowflake = (canvasWidth, config, isBackground = false) => {
|
|
|
220
206
|
}
|
|
221
207
|
};
|
|
222
208
|
var initializeAccumulation = (accumulationMap, config) => {
|
|
223
|
-
const elements = getAccumulationSurfaces();
|
|
209
|
+
const elements = getAccumulationSurfaces(config.MAX_SURFACES);
|
|
224
210
|
for (const [el] of accumulationMap.entries()) {
|
|
225
211
|
if (!el.isConnected) {
|
|
226
212
|
accumulationMap.delete(el);
|
|
227
213
|
}
|
|
228
214
|
}
|
|
229
|
-
elements.forEach(({ el, type
|
|
215
|
+
elements.forEach(({ el, type }) => {
|
|
230
216
|
const existing = accumulationMap.get(el);
|
|
231
217
|
const rect = el.getBoundingClientRect();
|
|
232
218
|
const width = Math.ceil(rect.width);
|
|
233
219
|
const isBottom = type === VAL_BOTTOM;
|
|
234
220
|
if (existing && existing.heights.length === width) {
|
|
235
221
|
existing.type = type;
|
|
236
|
-
existing.isFixed = isFixed;
|
|
237
222
|
if (existing.borderRadius !== void 0) {
|
|
238
223
|
const styleBuffer = window.getComputedStyle(el);
|
|
239
224
|
existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;
|
|
@@ -271,8 +256,7 @@ var initializeAccumulation = (accumulationMap, config) => {
|
|
|
271
256
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
272
257
|
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
273
258
|
borderRadius,
|
|
274
|
-
type
|
|
275
|
-
isFixed
|
|
259
|
+
type
|
|
276
260
|
});
|
|
277
261
|
});
|
|
278
262
|
};
|
|
@@ -291,30 +275,35 @@ var accumulateSide = (sideArray, rectHeight, localY, maxSideHeight, borderRadius
|
|
|
291
275
|
}
|
|
292
276
|
}
|
|
293
277
|
};
|
|
294
|
-
var updateSnowflakes = (snowflakes, elementRects, config, dt,
|
|
278
|
+
var updateSnowflakes = (snowflakes, elementRects, config, dt, worldWidth, worldHeight) => {
|
|
279
|
+
const scrollX = window.scrollX;
|
|
280
|
+
const scrollY = window.scrollY;
|
|
295
281
|
for (let i = snowflakes.length - 1; i >= 0; i--) {
|
|
296
282
|
const flake = snowflakes[i];
|
|
297
283
|
flake.wobble += flake.wobbleSpeed * dt;
|
|
298
284
|
flake.x += (flake.wind + Math.sin(flake.wobble) * 0.5) * dt;
|
|
299
285
|
flake.y += (flake.speed + Math.cos(flake.wobble * 0.5) * 0.1) * dt;
|
|
300
286
|
let landed = false;
|
|
301
|
-
for (const
|
|
287
|
+
for (const item of elementRects) {
|
|
288
|
+
const { rect, acc } = item;
|
|
302
289
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
290
|
+
const flakeViewportX = flake.x - scrollX;
|
|
291
|
+
const flakeViewportY = flake.y - scrollY;
|
|
292
|
+
const isInVerticalBounds = flakeViewportY >= rect.top && flakeViewportY <= rect.bottom;
|
|
303
293
|
if (!landed && acc.maxSideHeight > 0 && !isBottom) {
|
|
304
|
-
const isInVerticalBounds = flake.y >= rect.top && flake.y <= rect.bottom;
|
|
305
294
|
if (isInVerticalBounds) {
|
|
306
|
-
const localY = Math.floor(
|
|
295
|
+
const localY = Math.floor(flakeViewportY - rect.top);
|
|
307
296
|
const borderRadius = acc.borderRadius;
|
|
308
297
|
const isInTopCorner = localY < borderRadius;
|
|
309
298
|
const isInBottomCorner = localY > rect.height - borderRadius;
|
|
310
299
|
const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);
|
|
311
|
-
if (
|
|
300
|
+
if (flakeViewportX >= rect.left - 5 && flakeViewportX < rect.left + 3) {
|
|
312
301
|
if (!isCorner) {
|
|
313
302
|
accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
|
|
314
303
|
landed = true;
|
|
315
304
|
}
|
|
316
305
|
}
|
|
317
|
-
if (!landed &&
|
|
306
|
+
if (!landed && flakeViewportX > rect.right - 3 && flakeViewportX <= rect.right + 5) {
|
|
318
307
|
if (!isCorner) {
|
|
319
308
|
accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);
|
|
320
309
|
landed = true;
|
|
@@ -323,12 +312,12 @@ var updateSnowflakes = (snowflakes, elementRects, config, dt, canvasWidth, canva
|
|
|
323
312
|
if (landed) break;
|
|
324
313
|
}
|
|
325
314
|
}
|
|
326
|
-
if (
|
|
327
|
-
const localX = Math.floor(
|
|
315
|
+
if (flakeViewportX >= rect.left && flakeViewportX <= rect.right) {
|
|
316
|
+
const localX = Math.floor(flakeViewportX - rect.left);
|
|
328
317
|
const currentHeight = acc.heights[localX] || 0;
|
|
329
318
|
const maxHeight = acc.maxHeights[localX] || 5;
|
|
330
319
|
const surfaceY = isBottom ? rect.bottom - currentHeight : rect.top - currentHeight;
|
|
331
|
-
if (
|
|
320
|
+
if (flakeViewportY >= surfaceY && flakeViewportY < surfaceY + 10 && currentHeight < maxHeight) {
|
|
332
321
|
const shouldAccumulate2 = isBottom ? Math.random() < 0.15 : true;
|
|
333
322
|
if (shouldAccumulate2) {
|
|
334
323
|
const baseSpread = Math.ceil(flake.radius);
|
|
@@ -363,7 +352,7 @@ var updateSnowflakes = (snowflakes, elementRects, config, dt, canvasWidth, canva
|
|
|
363
352
|
}
|
|
364
353
|
}
|
|
365
354
|
}
|
|
366
|
-
if (landed || flake.y >
|
|
355
|
+
if (landed || flake.y > worldHeight + 10 || flake.x < -20 || flake.x > worldWidth + 20) {
|
|
367
356
|
snowflakes.splice(i, 1);
|
|
368
357
|
}
|
|
369
358
|
}
|
|
@@ -401,7 +390,7 @@ var drawSnowflake = (ctx, flake) => {
|
|
|
401
390
|
ctx.fillStyle = `rgba(255, 255, 255, ${flake.opacity * 0.2})`;
|
|
402
391
|
ctx.fill();
|
|
403
392
|
};
|
|
404
|
-
var drawAccumulations = (ctx,
|
|
393
|
+
var drawAccumulations = (ctx, elementRects) => {
|
|
405
394
|
const setupCtx = (c) => {
|
|
406
395
|
c.fillStyle = "rgba(255, 255, 255, 0.95)";
|
|
407
396
|
c.shadowColor = "rgba(200, 230, 255, 0.6)";
|
|
@@ -409,15 +398,13 @@ var drawAccumulations = (ctx, fixedCtx, elementRects) => {
|
|
|
409
398
|
c.shadowOffsetY = -1;
|
|
410
399
|
};
|
|
411
400
|
setupCtx(ctx);
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
|
|
401
|
+
const currentScrollX = window.scrollX;
|
|
402
|
+
const currentScrollY = window.scrollY;
|
|
403
|
+
for (const item of elementRects) {
|
|
404
|
+
const { rect, acc } = item;
|
|
416
405
|
if (!acc.heights.some((h) => h > 0.1)) continue;
|
|
417
|
-
const
|
|
418
|
-
const
|
|
419
|
-
const dx = useFixed ? -scrollX : 0;
|
|
420
|
-
const dy = useFixed ? -scrollY : 0;
|
|
406
|
+
const dx = currentScrollX;
|
|
407
|
+
const dy = currentScrollY;
|
|
421
408
|
const isBottom = acc.type === VAL_BOTTOM;
|
|
422
409
|
const baseY = isBottom ? rect.bottom - 1 : rect.top + 1;
|
|
423
410
|
const borderRadius = acc.borderRadius;
|
|
@@ -433,7 +420,7 @@ var drawAccumulations = (ctx, fixedCtx, elementRects) => {
|
|
|
433
420
|
}
|
|
434
421
|
return offset;
|
|
435
422
|
};
|
|
436
|
-
|
|
423
|
+
ctx.beginPath();
|
|
437
424
|
let first = true;
|
|
438
425
|
const step = 2;
|
|
439
426
|
const len = acc.heights.length;
|
|
@@ -442,10 +429,10 @@ var drawAccumulations = (ctx, fixedCtx, elementRects) => {
|
|
|
442
429
|
const px = rect.left + x + dx;
|
|
443
430
|
const py = baseY - height + getCurveOffset(x) + dy;
|
|
444
431
|
if (first) {
|
|
445
|
-
|
|
432
|
+
ctx.moveTo(px, py);
|
|
446
433
|
first = false;
|
|
447
434
|
} else {
|
|
448
|
-
|
|
435
|
+
ctx.lineTo(px, py);
|
|
449
436
|
}
|
|
450
437
|
}
|
|
451
438
|
if ((len - 1) % step !== 0) {
|
|
@@ -453,50 +440,44 @@ var drawAccumulations = (ctx, fixedCtx, elementRects) => {
|
|
|
453
440
|
const height = acc.heights[x] || 0;
|
|
454
441
|
const px = rect.left + x + dx;
|
|
455
442
|
const py = baseY - height + getCurveOffset(x) + dy;
|
|
456
|
-
|
|
443
|
+
ctx.lineTo(px, py);
|
|
457
444
|
}
|
|
458
445
|
for (let x = len - 1; x >= 0; x -= step) {
|
|
459
446
|
const px = rect.left + x + dx;
|
|
460
447
|
const py = baseY + getCurveOffset(x) + dy;
|
|
461
|
-
|
|
448
|
+
ctx.lineTo(px, py);
|
|
462
449
|
}
|
|
463
450
|
const startX = 0;
|
|
464
451
|
const startPx = rect.left + startX + dx;
|
|
465
452
|
const startPy = baseY + getCurveOffset(startX) + dy;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
453
|
+
ctx.lineTo(startPx, startPy);
|
|
454
|
+
ctx.closePath();
|
|
455
|
+
ctx.fill();
|
|
469
456
|
}
|
|
470
457
|
ctx.shadowBlur = 0;
|
|
471
458
|
ctx.shadowOffsetY = 0;
|
|
472
|
-
if (fixedCtx) {
|
|
473
|
-
fixedCtx.shadowBlur = 0;
|
|
474
|
-
fixedCtx.shadowOffsetY = 0;
|
|
475
|
-
}
|
|
476
459
|
};
|
|
477
|
-
var drawSideAccumulations = (ctx,
|
|
460
|
+
var drawSideAccumulations = (ctx, elementRects) => {
|
|
478
461
|
const setupCtx = (c) => {
|
|
479
462
|
c.fillStyle = "rgba(255, 255, 255, 0.95)";
|
|
480
463
|
c.shadowColor = "rgba(200, 230, 255, 0.6)";
|
|
481
464
|
c.shadowBlur = 3;
|
|
482
465
|
};
|
|
483
466
|
setupCtx(ctx);
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
const
|
|
487
|
-
|
|
467
|
+
const currentScrollX = window.scrollX;
|
|
468
|
+
const currentScrollY = window.scrollY;
|
|
469
|
+
for (const item of elementRects) {
|
|
470
|
+
const { rect, acc } = item;
|
|
488
471
|
if (acc.maxSideHeight === 0) continue;
|
|
489
472
|
const hasLeftSnow = acc.leftSide.some((h) => h > 0.3);
|
|
490
473
|
const hasRightSnow = acc.rightSide.some((h) => h > 0.3);
|
|
491
474
|
if (!hasLeftSnow && !hasRightSnow) continue;
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
const dx = useFixed ? -scrollX : 0;
|
|
495
|
-
const dy = useFixed ? -scrollY : 0;
|
|
475
|
+
const dx = currentScrollX;
|
|
476
|
+
const dy = currentScrollY;
|
|
496
477
|
const drawSide = (sideArray, isLeft) => {
|
|
497
|
-
|
|
478
|
+
ctx.beginPath();
|
|
498
479
|
const baseX = isLeft ? rect.left : rect.right;
|
|
499
|
-
|
|
480
|
+
ctx.moveTo(baseX + dx, rect.top + dy);
|
|
500
481
|
for (let y = 0; y < sideArray.length; y += 2) {
|
|
501
482
|
const width = sideArray[y] || 0;
|
|
502
483
|
const nextY = Math.min(y + 2, sideArray.length - 1);
|
|
@@ -509,33 +490,42 @@ var drawSideAccumulations = (ctx, fixedCtx, elementRects) => {
|
|
|
509
490
|
const nRatio = nextY / sideArray.length;
|
|
510
491
|
const nGravityMultiplier = Math.pow(nRatio, 1.5);
|
|
511
492
|
const nx = (isLeft ? baseX - nextWidth * nGravityMultiplier : baseX + nextWidth * nGravityMultiplier) + dx;
|
|
512
|
-
|
|
513
|
-
|
|
493
|
+
ctx.lineTo(px, py);
|
|
494
|
+
ctx.lineTo(nx, ny);
|
|
514
495
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
496
|
+
ctx.lineTo(baseX + dx, rect.bottom + dy);
|
|
497
|
+
ctx.closePath();
|
|
498
|
+
ctx.fill();
|
|
518
499
|
};
|
|
519
500
|
if (hasLeftSnow) drawSide(acc.leftSide, true);
|
|
520
501
|
if (hasRightSnow) drawSide(acc.rightSide, false);
|
|
521
502
|
}
|
|
522
503
|
ctx.shadowBlur = 0;
|
|
523
|
-
if (fixedCtx) fixedCtx.shadowBlur = 0;
|
|
524
504
|
};
|
|
525
505
|
|
|
526
506
|
// src/Snowfall.tsx
|
|
527
|
-
import { Fragment, jsx as jsx2
|
|
507
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
528
508
|
function Snowfall() {
|
|
529
|
-
const { isEnabled, physicsConfig } = useSnowfall();
|
|
509
|
+
const { isEnabled, physicsConfig, setMetrics } = useSnowfall();
|
|
530
510
|
const isEnabledRef = useRef(isEnabled);
|
|
531
511
|
const physicsConfigRef = useRef(physicsConfig);
|
|
512
|
+
const setMetricsRef = useRef(setMetrics);
|
|
532
513
|
const [isMounted, setIsMounted] = useState2(false);
|
|
533
514
|
const [isVisible, setIsVisible] = useState2(false);
|
|
534
515
|
const canvasRef = useRef(null);
|
|
535
|
-
const fixedCanvasRef = useRef(null);
|
|
536
516
|
const snowflakesRef = useRef([]);
|
|
537
517
|
const accumulationRef = useRef(/* @__PURE__ */ new Map());
|
|
538
518
|
const animationIdRef = useRef(0);
|
|
519
|
+
const fpsFrames = useRef([]);
|
|
520
|
+
const metricsRef = useRef({
|
|
521
|
+
scanTime: 0,
|
|
522
|
+
rectUpdateTime: 0,
|
|
523
|
+
frameTime: 0,
|
|
524
|
+
rafGap: 0,
|
|
525
|
+
clearTime: 0,
|
|
526
|
+
physicsTime: 0,
|
|
527
|
+
drawTime: 0
|
|
528
|
+
});
|
|
539
529
|
useEffect(() => {
|
|
540
530
|
setIsMounted(true);
|
|
541
531
|
}, []);
|
|
@@ -545,40 +535,58 @@ function Snowfall() {
|
|
|
545
535
|
useEffect(() => {
|
|
546
536
|
physicsConfigRef.current = physicsConfig;
|
|
547
537
|
}, [physicsConfig]);
|
|
538
|
+
useEffect(() => {
|
|
539
|
+
setMetricsRef.current = setMetrics;
|
|
540
|
+
}, [setMetrics]);
|
|
548
541
|
useEffect(() => {
|
|
549
542
|
if (!isMounted) return;
|
|
550
543
|
const canvas = canvasRef.current;
|
|
551
|
-
|
|
552
|
-
if (!canvas || !fixedCanvas) return;
|
|
544
|
+
if (!canvas) return;
|
|
553
545
|
const ctx = canvas.getContext("2d");
|
|
554
|
-
|
|
555
|
-
if (!ctx || !fixedCtx) return;
|
|
546
|
+
if (!ctx) return;
|
|
556
547
|
const resizeCanvas = () => {
|
|
557
|
-
if (canvasRef.current
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
fixedCanvasRef.current.width = window.innerWidth;
|
|
566
|
-
fixedCanvasRef.current.height = window.innerHeight;
|
|
567
|
-
}
|
|
548
|
+
if (canvasRef.current) {
|
|
549
|
+
const newWidth = window.innerWidth;
|
|
550
|
+
const newHeight = window.innerHeight;
|
|
551
|
+
const dpr = window.devicePixelRatio || 1;
|
|
552
|
+
canvasRef.current.width = newWidth * dpr;
|
|
553
|
+
canvasRef.current.height = newHeight * dpr;
|
|
554
|
+
canvasRef.current.style.width = `${newWidth}px`;
|
|
555
|
+
canvasRef.current.style.height = `${newHeight}px`;
|
|
568
556
|
}
|
|
569
557
|
};
|
|
570
558
|
resizeCanvas();
|
|
571
|
-
const
|
|
559
|
+
const windowResizeObserver = new ResizeObserver(() => {
|
|
572
560
|
resizeCanvas();
|
|
573
561
|
});
|
|
574
|
-
|
|
562
|
+
windowResizeObserver.observe(document.body);
|
|
563
|
+
const surfaceObserver = new ResizeObserver((entries) => {
|
|
564
|
+
let needsUpdate = false;
|
|
565
|
+
for (const entry of entries) {
|
|
566
|
+
if (entry.target.isConnected) {
|
|
567
|
+
needsUpdate = true;
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (needsUpdate) {
|
|
572
|
+
initAccumulationWrapper();
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
575
|
snowflakesRef.current = [];
|
|
576
576
|
const initAccumulationWrapper = () => {
|
|
577
|
+
const scanStart = performance.now();
|
|
577
578
|
initializeAccumulation(accumulationRef.current, physicsConfigRef.current);
|
|
579
|
+
surfaceObserver.disconnect();
|
|
580
|
+
for (const [el] of accumulationRef.current) {
|
|
581
|
+
surfaceObserver.observe(el);
|
|
582
|
+
}
|
|
583
|
+
metricsRef.current.scanTime = performance.now() - scanStart;
|
|
578
584
|
};
|
|
579
585
|
initAccumulationWrapper();
|
|
580
586
|
setIsVisible(true);
|
|
581
587
|
let lastTime = 0;
|
|
588
|
+
let lastMetricsUpdate = 0;
|
|
589
|
+
let elementRects = [];
|
|
582
590
|
const animate = (currentTime) => {
|
|
583
591
|
if (lastTime === 0) {
|
|
584
592
|
lastTime = currentTime;
|
|
@@ -586,23 +594,64 @@ function Snowfall() {
|
|
|
586
594
|
return;
|
|
587
595
|
}
|
|
588
596
|
const deltaTime = Math.min(currentTime - lastTime, 50);
|
|
597
|
+
const now = performance.now();
|
|
598
|
+
fpsFrames.current.push(now);
|
|
599
|
+
fpsFrames.current = fpsFrames.current.filter((t) => now - t < 1e3);
|
|
600
|
+
metricsRef.current.rafGap = currentTime - lastTime;
|
|
589
601
|
lastTime = currentTime;
|
|
590
602
|
const dt = deltaTime / 16.67;
|
|
591
|
-
|
|
592
|
-
|
|
603
|
+
const frameStartTime = performance.now();
|
|
604
|
+
const clearStart = performance.now();
|
|
605
|
+
const dpr = window.devicePixelRatio || 1;
|
|
606
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
607
|
+
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
608
|
+
const scrollX = window.scrollX;
|
|
609
|
+
const scrollY = window.scrollY;
|
|
610
|
+
ctx.translate(-scrollX, -scrollY);
|
|
611
|
+
metricsRef.current.clearTime = performance.now() - clearStart;
|
|
593
612
|
const snowflakes = snowflakesRef.current;
|
|
594
|
-
const
|
|
613
|
+
const rectStart = performance.now();
|
|
614
|
+
elementRects = getElementRects(accumulationRef.current);
|
|
615
|
+
metricsRef.current.rectUpdateTime = performance.now() - rectStart;
|
|
616
|
+
const physicsStart = performance.now();
|
|
595
617
|
meltAndSmoothAccumulation(elementRects, physicsConfigRef.current, dt);
|
|
596
|
-
updateSnowflakes(
|
|
618
|
+
updateSnowflakes(
|
|
619
|
+
snowflakes,
|
|
620
|
+
elementRects,
|
|
621
|
+
physicsConfigRef.current,
|
|
622
|
+
dt,
|
|
623
|
+
document.documentElement.scrollWidth,
|
|
624
|
+
document.documentElement.scrollHeight
|
|
625
|
+
);
|
|
626
|
+
metricsRef.current.physicsTime = performance.now() - physicsStart;
|
|
627
|
+
const drawStart = performance.now();
|
|
597
628
|
for (const flake of snowflakes) {
|
|
598
629
|
drawSnowflake(ctx, flake);
|
|
599
630
|
}
|
|
600
631
|
if (isEnabledRef.current && snowflakes.length < physicsConfigRef.current.MAX_FLAKES) {
|
|
601
632
|
const isBackground = Math.random() < 0.4;
|
|
602
|
-
snowflakes.push(createSnowflake(
|
|
633
|
+
snowflakes.push(createSnowflake(document.documentElement.scrollWidth, physicsConfigRef.current, isBackground));
|
|
634
|
+
}
|
|
635
|
+
drawAccumulations(ctx, elementRects);
|
|
636
|
+
drawSideAccumulations(ctx, elementRects);
|
|
637
|
+
metricsRef.current.drawTime = performance.now() - drawStart;
|
|
638
|
+
metricsRef.current.frameTime = performance.now() - frameStartTime;
|
|
639
|
+
if (currentTime - lastMetricsUpdate > 500) {
|
|
640
|
+
setMetricsRef.current({
|
|
641
|
+
fps: fpsFrames.current.length,
|
|
642
|
+
frameTime: metricsRef.current.frameTime,
|
|
643
|
+
scanTime: metricsRef.current.scanTime,
|
|
644
|
+
rectUpdateTime: metricsRef.current.rectUpdateTime,
|
|
645
|
+
surfaceCount: accumulationRef.current.size,
|
|
646
|
+
flakeCount: snowflakes.length,
|
|
647
|
+
maxFlakes: physicsConfigRef.current.MAX_FLAKES,
|
|
648
|
+
rafGap: metricsRef.current.rafGap,
|
|
649
|
+
clearTime: metricsRef.current.clearTime,
|
|
650
|
+
physicsTime: metricsRef.current.physicsTime,
|
|
651
|
+
drawTime: metricsRef.current.drawTime
|
|
652
|
+
});
|
|
653
|
+
lastMetricsUpdate = currentTime;
|
|
603
654
|
}
|
|
604
|
-
drawAccumulations(ctx, fixedCtx, elementRects);
|
|
605
|
-
drawSideAccumulations(ctx, fixedCtx, elementRects);
|
|
606
655
|
animationIdRef.current = requestAnimationFrame(animate);
|
|
607
656
|
};
|
|
608
657
|
animationIdRef.current = requestAnimationFrame(animate);
|
|
@@ -612,52 +661,266 @@ function Snowfall() {
|
|
|
612
661
|
initAccumulationWrapper();
|
|
613
662
|
};
|
|
614
663
|
window.addEventListener("resize", handleResize);
|
|
615
|
-
const checkInterval = setInterval(initAccumulationWrapper,
|
|
664
|
+
const checkInterval = setInterval(initAccumulationWrapper, 5e3);
|
|
616
665
|
return () => {
|
|
617
666
|
cancelAnimationFrame(animationIdRef.current);
|
|
618
667
|
window.removeEventListener("resize", handleResize);
|
|
619
668
|
clearInterval(checkInterval);
|
|
620
|
-
|
|
669
|
+
windowResizeObserver.disconnect();
|
|
670
|
+
surfaceObserver.disconnect();
|
|
621
671
|
};
|
|
622
672
|
}, [isMounted]);
|
|
623
673
|
if (!isMounted) return null;
|
|
624
|
-
return /* @__PURE__ */
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
674
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: /* @__PURE__ */ jsx2(
|
|
675
|
+
"canvas",
|
|
676
|
+
{
|
|
677
|
+
ref: canvasRef,
|
|
678
|
+
style: {
|
|
679
|
+
position: "fixed",
|
|
680
|
+
// FIXED position to eliminate scroll jitter
|
|
681
|
+
top: 0,
|
|
682
|
+
left: 0,
|
|
683
|
+
pointerEvents: "none",
|
|
684
|
+
zIndex: 9999,
|
|
685
|
+
opacity: isVisible ? 1 : 0,
|
|
686
|
+
transition: "opacity 0.3s ease-in",
|
|
687
|
+
willChange: "transform"
|
|
688
|
+
},
|
|
689
|
+
"aria-hidden": "true"
|
|
690
|
+
}
|
|
691
|
+
) });
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/DebugPanel.tsx
|
|
695
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
696
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
697
|
+
function DebugPanel({ defaultOpen = true }) {
|
|
698
|
+
const { debugMode, toggleDebug, metrics } = useSnowfall();
|
|
699
|
+
const [isMinimized, setIsMinimized] = useState3(!defaultOpen);
|
|
700
|
+
const [copied, setCopied] = useState3(false);
|
|
701
|
+
useEffect2(() => {
|
|
702
|
+
const handleKeyDown = (e) => {
|
|
703
|
+
if (e.shiftKey && e.key === "D") {
|
|
704
|
+
toggleDebug();
|
|
655
705
|
}
|
|
656
|
-
|
|
657
|
-
|
|
706
|
+
};
|
|
707
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
708
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
709
|
+
}, [toggleDebug]);
|
|
710
|
+
const copyToClipboard = () => {
|
|
711
|
+
if (metrics) {
|
|
712
|
+
const data = {
|
|
713
|
+
...metrics,
|
|
714
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
715
|
+
userAgent: navigator.userAgent,
|
|
716
|
+
canvasSize: {
|
|
717
|
+
width: window.innerWidth,
|
|
718
|
+
height: window.innerHeight
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
navigator.clipboard.writeText(JSON.stringify(data, null, 2));
|
|
722
|
+
setCopied(true);
|
|
723
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
if (!debugMode) return null;
|
|
727
|
+
return /* @__PURE__ */ jsxs(
|
|
728
|
+
"div",
|
|
729
|
+
{
|
|
730
|
+
"data-snowfall": "top",
|
|
731
|
+
style: {
|
|
732
|
+
position: "fixed",
|
|
733
|
+
bottom: "80px",
|
|
734
|
+
left: "24px",
|
|
735
|
+
backgroundColor: "rgba(15, 23, 42, 0.75)",
|
|
736
|
+
backdropFilter: "blur(16px)",
|
|
737
|
+
WebkitBackdropFilter: "blur(16px)",
|
|
738
|
+
color: "#e2e8f0",
|
|
739
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
740
|
+
fontSize: "12px",
|
|
741
|
+
padding: isMinimized ? "12px" : "20px",
|
|
742
|
+
borderRadius: "16px",
|
|
743
|
+
zIndex: 1e4,
|
|
744
|
+
minWidth: isMinimized ? "auto" : "300px",
|
|
745
|
+
maxWidth: "100%",
|
|
746
|
+
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
747
|
+
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 8px 10px -6px rgba(0, 0, 0, 0.2)",
|
|
748
|
+
transition: "all 0.2s ease"
|
|
749
|
+
},
|
|
750
|
+
children: [
|
|
751
|
+
/* @__PURE__ */ jsxs("div", { style: {
|
|
752
|
+
display: "flex",
|
|
753
|
+
justifyContent: "space-between",
|
|
754
|
+
alignItems: "center",
|
|
755
|
+
marginBottom: isMinimized ? 0 : "16px",
|
|
756
|
+
gap: "16px"
|
|
757
|
+
}, children: [
|
|
758
|
+
/* @__PURE__ */ jsxs("div", { style: { fontWeight: "600", color: "#fff", display: "flex", alignItems: "center", gap: "8px" }, children: [
|
|
759
|
+
/* @__PURE__ */ jsx3("span", { style: { fontSize: "14px" }, children: "\u2744\uFE0F" }),
|
|
760
|
+
/* @__PURE__ */ jsx3("span", { style: {
|
|
761
|
+
background: "linear-gradient(to right, #60a5fa, #22d3ee)",
|
|
762
|
+
WebkitBackgroundClip: "text",
|
|
763
|
+
WebkitTextFillColor: "transparent",
|
|
764
|
+
fontWeight: "700"
|
|
765
|
+
}, children: "DEBUG" })
|
|
766
|
+
] }),
|
|
767
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
768
|
+
/* @__PURE__ */ jsx3(
|
|
769
|
+
"button",
|
|
770
|
+
{
|
|
771
|
+
onClick: () => setIsMinimized(!isMinimized),
|
|
772
|
+
style: {
|
|
773
|
+
background: "rgba(255, 255, 255, 0.1)",
|
|
774
|
+
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
775
|
+
color: "#fff",
|
|
776
|
+
cursor: "pointer",
|
|
777
|
+
width: "24px",
|
|
778
|
+
height: "24px",
|
|
779
|
+
display: "flex",
|
|
780
|
+
alignItems: "center",
|
|
781
|
+
justifyContent: "center",
|
|
782
|
+
borderRadius: "6px",
|
|
783
|
+
fontSize: "10px"
|
|
784
|
+
},
|
|
785
|
+
children: isMinimized ? "\u25B2" : "\u25BC"
|
|
786
|
+
}
|
|
787
|
+
),
|
|
788
|
+
/* @__PURE__ */ jsx3(
|
|
789
|
+
"button",
|
|
790
|
+
{
|
|
791
|
+
onClick: toggleDebug,
|
|
792
|
+
style: {
|
|
793
|
+
background: "rgba(239, 68, 68, 0.15)",
|
|
794
|
+
border: "1px solid rgba(239, 68, 68, 0.2)",
|
|
795
|
+
color: "#f87171",
|
|
796
|
+
cursor: "pointer",
|
|
797
|
+
width: "24px",
|
|
798
|
+
height: "24px",
|
|
799
|
+
display: "flex",
|
|
800
|
+
alignItems: "center",
|
|
801
|
+
justifyContent: "center",
|
|
802
|
+
borderRadius: "6px",
|
|
803
|
+
fontSize: "12px"
|
|
804
|
+
},
|
|
805
|
+
children: "\u2715"
|
|
806
|
+
}
|
|
807
|
+
)
|
|
808
|
+
] })
|
|
809
|
+
] }),
|
|
810
|
+
!isMinimized && metrics && /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
811
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "12px", paddingBottom: "12px", borderBottom: "1px solid rgba(255,255,255,0.08)" }, children: [
|
|
812
|
+
/* @__PURE__ */ jsx3("div", { style: { color: "#94a3b8", marginBottom: "8px", fontSize: "11px", fontWeight: "600", letterSpacing: "0.5px", textTransform: "uppercase" }, children: "Performance" }),
|
|
813
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px" }, children: [
|
|
814
|
+
/* @__PURE__ */ jsx3("span", { children: "FPS" }),
|
|
815
|
+
/* @__PURE__ */ jsx3("span", { style: { fontWeight: "bold", color: metrics.fps < 30 ? "#f87171" : metrics.fps < 50 ? "#facc15" : "#4ade80" }, children: metrics.fps.toFixed(1) })
|
|
816
|
+
] }),
|
|
817
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px" }, children: [
|
|
818
|
+
/* @__PURE__ */ jsx3("span", { children: "Frame Time" }),
|
|
819
|
+
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
820
|
+
metrics.frameTime.toFixed(2),
|
|
821
|
+
"ms"
|
|
822
|
+
] })
|
|
823
|
+
] }),
|
|
824
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
825
|
+
/* @__PURE__ */ jsx3("span", { style: { color: metrics.rafGap && metrics.rafGap > 20 ? "#fbbf24" : "inherit" }, children: "rAF Gap" }),
|
|
826
|
+
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
827
|
+
metrics.rafGap?.toFixed(1) || 0,
|
|
828
|
+
"ms"
|
|
829
|
+
] })
|
|
830
|
+
] })
|
|
831
|
+
] }),
|
|
832
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "12px", paddingBottom: "12px", borderBottom: "1px solid rgba(255,255,255,0.08)" }, children: [
|
|
833
|
+
/* @__PURE__ */ jsx3("div", { style: { color: "#94a3b8", marginBottom: "8px", fontSize: "11px", fontWeight: "600", letterSpacing: "0.5px", textTransform: "uppercase" }, children: "Detailed Timings" }),
|
|
834
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "4px 12px" }, children: [
|
|
835
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
836
|
+
/* @__PURE__ */ jsx3("span", { children: "Clear" }),
|
|
837
|
+
" ",
|
|
838
|
+
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
839
|
+
metrics.clearTime?.toFixed(2) || 0,
|
|
840
|
+
"ms"
|
|
841
|
+
] })
|
|
842
|
+
] }),
|
|
843
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
844
|
+
/* @__PURE__ */ jsx3("span", { children: "Physics" }),
|
|
845
|
+
" ",
|
|
846
|
+
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
847
|
+
metrics.physicsTime?.toFixed(2) || 0,
|
|
848
|
+
"ms"
|
|
849
|
+
] })
|
|
850
|
+
] }),
|
|
851
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
852
|
+
/* @__PURE__ */ jsx3("span", { children: "Draw" }),
|
|
853
|
+
" ",
|
|
854
|
+
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
855
|
+
metrics.drawTime?.toFixed(2) || 0,
|
|
856
|
+
"ms"
|
|
857
|
+
] })
|
|
858
|
+
] }),
|
|
859
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
860
|
+
/* @__PURE__ */ jsx3("span", { children: "Scan" }),
|
|
861
|
+
" ",
|
|
862
|
+
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
863
|
+
metrics.scanTime.toFixed(2),
|
|
864
|
+
"ms"
|
|
865
|
+
] })
|
|
866
|
+
] }),
|
|
867
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", gridColumn: "span 2" }, children: [
|
|
868
|
+
/* @__PURE__ */ jsx3("span", { children: "Rect Update" }),
|
|
869
|
+
" ",
|
|
870
|
+
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
871
|
+
metrics.rectUpdateTime.toFixed(2),
|
|
872
|
+
"ms"
|
|
873
|
+
] })
|
|
874
|
+
] })
|
|
875
|
+
] })
|
|
876
|
+
] }),
|
|
877
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
|
|
878
|
+
/* @__PURE__ */ jsx3("div", { style: { color: "#94a3b8", marginBottom: "8px", fontSize: "11px", fontWeight: "600", letterSpacing: "0.5px", textTransform: "uppercase" }, children: "Counts" }),
|
|
879
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: "4px" }, children: [
|
|
880
|
+
/* @__PURE__ */ jsx3("span", { children: "Snowflakes" }),
|
|
881
|
+
/* @__PURE__ */ jsxs("span", { style: { fontFamily: "monospace" }, children: [
|
|
882
|
+
metrics.flakeCount,
|
|
883
|
+
" / ",
|
|
884
|
+
metrics.maxFlakes
|
|
885
|
+
] })
|
|
886
|
+
] }),
|
|
887
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
888
|
+
/* @__PURE__ */ jsx3("span", { children: "Surfaces" }),
|
|
889
|
+
/* @__PURE__ */ jsx3("span", { style: { fontFamily: "monospace" }, children: metrics.surfaceCount })
|
|
890
|
+
] })
|
|
891
|
+
] }),
|
|
892
|
+
/* @__PURE__ */ jsx3(
|
|
893
|
+
"button",
|
|
894
|
+
{
|
|
895
|
+
onClick: copyToClipboard,
|
|
896
|
+
style: {
|
|
897
|
+
width: "100%",
|
|
898
|
+
padding: "10px",
|
|
899
|
+
background: copied ? "rgba(34, 197, 94, 0.2)" : "rgba(255, 255, 255, 0.05)",
|
|
900
|
+
border: copied ? "1px solid rgba(34, 197, 94, 0.5)" : "1px solid rgba(255, 255, 255, 0.1)",
|
|
901
|
+
color: copied ? "#4ade80" : "#fff",
|
|
902
|
+
cursor: "pointer",
|
|
903
|
+
borderRadius: "8px",
|
|
904
|
+
fontSize: "11px",
|
|
905
|
+
fontWeight: "600",
|
|
906
|
+
transition: "all 0.2s",
|
|
907
|
+
display: "flex",
|
|
908
|
+
alignItems: "center",
|
|
909
|
+
justifyContent: "center",
|
|
910
|
+
gap: "6px"
|
|
911
|
+
},
|
|
912
|
+
children: copied ? "\u2713 COPIED" : "\u{1F4CB} COPY METRICS"
|
|
913
|
+
}
|
|
914
|
+
),
|
|
915
|
+
/* @__PURE__ */ jsx3("div", { style: { marginTop: "12px", fontSize: "10px", color: "#64748b", textAlign: "center" }, children: "Shift+D to toggle" })
|
|
916
|
+
] })
|
|
917
|
+
]
|
|
918
|
+
}
|
|
919
|
+
);
|
|
658
920
|
}
|
|
659
921
|
export {
|
|
660
922
|
DEFAULT_PHYSICS,
|
|
923
|
+
DebugPanel,
|
|
661
924
|
Snowfall,
|
|
662
925
|
SnowfallProvider,
|
|
663
926
|
useSnowfall
|