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