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