@toriistudio/shader-ui 0.0.3 → 0.0.4

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 CHANGED
@@ -163,4 +163,29 @@ type EfectoProps = Omit<ComponentProps<"div">, "ref"> & {
163
163
  };
164
164
  declare function Efecto({ postProcessing, src, mouseParallax, parallaxIntensity, mediaAdjustments, cellSize, invert, colorMode, style, asciiStyle, ...wrapperProps }: EfectoProps): react_jsx_runtime.JSX.Element;
165
165
 
166
- export { EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, ShaderArt, type ShaderArtUniforms };
166
+ type SnowUniforms = {
167
+ color: string;
168
+ fallSpeed: number;
169
+ windStrength: number;
170
+ turbulence: number;
171
+ flakeSize: number;
172
+ twinkleStrength: number;
173
+ flakeCount: number;
174
+ };
175
+ type SnowProps = SnowUniforms & Omit<ComponentProps<"div">, keyof SnowUniforms | "ref" | "width" | "height"> & {
176
+ width?: string | number;
177
+ height?: string | number;
178
+ mouseWindInteraction?: boolean;
179
+ };
180
+ declare function Snow({ className, style, width, height, color, fallSpeed, windStrength, turbulence, flakeSize, twinkleStrength, flakeCount, mouseWindInteraction, ...divProps }: SnowProps): react_jsx_runtime.JSX.Element;
181
+
182
+ type AnimatedDrawingSVGProps = {
183
+ svgMarkup: string;
184
+ animated?: boolean;
185
+ size?: number | string;
186
+ onAnimated?: () => void;
187
+ delay?: number;
188
+ } & Omit<ComponentProps<"div">, "children">;
189
+ declare function AnimatedDrawingSVG({ svgMarkup, animated, size, onAnimated, delay, className, style, ...divProps }: AnimatedDrawingSVGProps): react_jsx_runtime.JSX.Element;
190
+
191
+ export { AnimatedDrawingSVG, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, ShaderArt, type ShaderArtUniforms, Snow };
package/dist/index.d.ts CHANGED
@@ -163,4 +163,29 @@ type EfectoProps = Omit<ComponentProps<"div">, "ref"> & {
163
163
  };
164
164
  declare function Efecto({ postProcessing, src, mouseParallax, parallaxIntensity, mediaAdjustments, cellSize, invert, colorMode, style, asciiStyle, ...wrapperProps }: EfectoProps): react_jsx_runtime.JSX.Element;
165
165
 
166
- export { EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, ShaderArt, type ShaderArtUniforms };
166
+ type SnowUniforms = {
167
+ color: string;
168
+ fallSpeed: number;
169
+ windStrength: number;
170
+ turbulence: number;
171
+ flakeSize: number;
172
+ twinkleStrength: number;
173
+ flakeCount: number;
174
+ };
175
+ type SnowProps = SnowUniforms & Omit<ComponentProps<"div">, keyof SnowUniforms | "ref" | "width" | "height"> & {
176
+ width?: string | number;
177
+ height?: string | number;
178
+ mouseWindInteraction?: boolean;
179
+ };
180
+ declare function Snow({ className, style, width, height, color, fallSpeed, windStrength, turbulence, flakeSize, twinkleStrength, flakeCount, mouseWindInteraction, ...divProps }: SnowProps): react_jsx_runtime.JSX.Element;
181
+
182
+ type AnimatedDrawingSVGProps = {
183
+ svgMarkup: string;
184
+ animated?: boolean;
185
+ size?: number | string;
186
+ onAnimated?: () => void;
187
+ delay?: number;
188
+ } & Omit<ComponentProps<"div">, "children">;
189
+ declare function AnimatedDrawingSVG({ svgMarkup, animated, size, onAnimated, delay, className, style, ...divProps }: AnimatedDrawingSVGProps): react_jsx_runtime.JSX.Element;
190
+
191
+ export { AnimatedDrawingSVG, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, ShaderArt, type ShaderArtUniforms, Snow };
package/dist/index.js CHANGED
@@ -30,13 +30,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ AnimatedDrawingSVG: () => AnimatedDrawingSVG,
33
34
  EFECTO_ASCII_COMPONENT_DEFAULTS: () => EFECTO_ASCII_COMPONENT_DEFAULTS,
34
35
  EFECTO_ASCII_POST_PROCESSING_DEFAULTS: () => EFECTO_ASCII_POST_PROCESSING_DEFAULTS,
35
36
  Efecto: () => Efecto,
36
37
  FractalFlower: () => FractalFlower,
37
38
  MenuGlitch: () => MenuGlitch,
38
39
  OranoParticles: () => OranoParticles,
39
- ShaderArt: () => ShaderArt
40
+ ShaderArt: () => ShaderArt,
41
+ Snow: () => Snow
40
42
  });
41
43
  module.exports = __toCommonJS(src_exports);
42
44
 
@@ -2111,13 +2113,561 @@ function Efecto({
2111
2113
  }
2112
2114
  );
2113
2115
  }
2116
+
2117
+ // src/components/Snow.tsx
2118
+ var import_react7 = require("react");
2119
+ var THREE7 = __toESM(require("three"));
2120
+
2121
+ // src/shaders/snow/fragment.glsl
2122
+ var fragment_default7 = "precision mediump float;\n\nuniform vec3 uColor;\n\nvarying float vAlpha;\n\nvoid main() {\n vec2 uv = gl_PointCoord - 0.5;\n float dist = length(uv);\n float mask = smoothstep(0.5, 0.0, dist);\n if (mask <= 0.01) {\n discard;\n }\n\n float centerGlow = smoothstep(0.22, 0.0, dist);\n vec3 color = mix(uColor * 1.2, uColor, centerGlow);\n\n gl_FragColor = vec4(color, mask * vAlpha);\n}\n";
2123
+
2124
+ // src/shaders/snow/vertex.glsl
2125
+ var vertex_default6 = "uniform float uTime;\nuniform float uFallSpeed;\nuniform float uWindStrength;\nuniform float uTurbulence;\nuniform float uSize;\nuniform float uTwinkleStrength;\nuniform vec3 uArea;\n\nattribute float aSpeed;\nattribute float aSize;\nattribute float aSeed;\n\nvarying float vAlpha;\n\nfloat wrap(float value, float size) {\n return mod(value + size * 0.5, size) - size * 0.5;\n}\n\nvoid main() {\n float height = uArea.y;\n float width = uArea.x;\n float depth = uArea.z;\n\n float fall = uFallSpeed * (0.3 + aSpeed);\n float droppedY = position.y - uTime * fall;\n float wrappedY = wrap(droppedY, height);\n\n float sway =\n sin((wrappedY + aSeed) * 0.45 + uTime * 0.8) * uTurbulence +\n cos(uTime * 0.35 + aSeed) * 0.15;\n float wind = uWindStrength * (0.4 + aSpeed);\n float displacedX = wrap(position.x + sway + wind, width);\n\n float driftZ =\n sin(uTime * 0.25 + aSeed * 1.7) * 0.5 +\n cos((wrappedY + aSeed) * 0.2) * 0.4;\n float displacedZ = wrap(position.z + driftZ, depth);\n\n vec4 modelPosition =\n modelMatrix * vec4(displacedX, wrappedY, displacedZ, 1.0);\n vec4 viewPosition = viewMatrix * modelPosition;\n\n float baseSize = mix(0.45, 1.0, aSize) * uSize;\n float twinkle =\n 1.0 + sin(uTime * (0.6 + aSpeed) + aSeed) * uTwinkleStrength;\n float perspective = clamp(15.0 / max(1.0, -viewPosition.z), 0.5, 3.0);\n gl_PointSize = baseSize * twinkle * perspective;\n gl_Position = projectionMatrix * viewPosition;\n\n vAlpha = mix(0.35, 1.0, aSize);\n}\n";
2126
+
2127
+ // src/components/Snow.tsx
2128
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2129
+ var AREA_BOUNDS = {
2130
+ width: 36,
2131
+ height: 44,
2132
+ depth: 26
2133
+ };
2134
+ function createSnowGeometry(count) {
2135
+ const geometry = new THREE7.BufferGeometry();
2136
+ const positions = new Float32Array(count * 3);
2137
+ const speeds = new Float32Array(count);
2138
+ const sizes = new Float32Array(count);
2139
+ const seeds = new Float32Array(count);
2140
+ for (let i = 0; i < count; i += 1) {
2141
+ const x = (Math.random() - 0.5) * AREA_BOUNDS.width;
2142
+ const y = (Math.random() - 0.5) * AREA_BOUNDS.height;
2143
+ const z = (Math.random() - 0.5) * AREA_BOUNDS.depth;
2144
+ positions[i * 3] = x;
2145
+ positions[i * 3 + 1] = y;
2146
+ positions[i * 3 + 2] = z;
2147
+ speeds[i] = Math.random();
2148
+ sizes[i] = Math.random();
2149
+ seeds[i] = Math.random() * 100;
2150
+ }
2151
+ geometry.setAttribute("position", new THREE7.BufferAttribute(positions, 3));
2152
+ geometry.setAttribute("aSpeed", new THREE7.BufferAttribute(speeds, 1));
2153
+ geometry.setAttribute("aSize", new THREE7.BufferAttribute(sizes, 1));
2154
+ geometry.setAttribute("aSeed", new THREE7.BufferAttribute(seeds, 1));
2155
+ geometry.computeBoundingSphere();
2156
+ return geometry;
2157
+ }
2158
+ function buildUniforms2({
2159
+ color,
2160
+ fallSpeed,
2161
+ windStrength,
2162
+ turbulence,
2163
+ flakeSize,
2164
+ twinkleStrength
2165
+ }) {
2166
+ return {
2167
+ uTime: { value: 0 },
2168
+ uFallSpeed: { value: fallSpeed },
2169
+ uWindStrength: { value: windStrength },
2170
+ uTurbulence: { value: turbulence },
2171
+ uSize: { value: flakeSize },
2172
+ uTwinkleStrength: { value: twinkleStrength },
2173
+ uColor: { value: new THREE7.Color(color) },
2174
+ uArea: {
2175
+ value: new THREE7.Vector3(
2176
+ AREA_BOUNDS.width,
2177
+ AREA_BOUNDS.height,
2178
+ AREA_BOUNDS.depth
2179
+ )
2180
+ }
2181
+ };
2182
+ }
2183
+ function Snow({
2184
+ className,
2185
+ style,
2186
+ width,
2187
+ height,
2188
+ color,
2189
+ fallSpeed,
2190
+ windStrength,
2191
+ turbulence,
2192
+ flakeSize,
2193
+ twinkleStrength,
2194
+ flakeCount,
2195
+ mouseWindInteraction = false,
2196
+ ...divProps
2197
+ }) {
2198
+ const snowRef = (0, import_react7.useRef)(null);
2199
+ const uniformsRef = (0, import_react7.useRef)({
2200
+ color,
2201
+ fallSpeed,
2202
+ windStrength,
2203
+ turbulence,
2204
+ flakeSize,
2205
+ twinkleStrength,
2206
+ flakeCount
2207
+ });
2208
+ uniformsRef.current = {
2209
+ color,
2210
+ fallSpeed,
2211
+ windStrength,
2212
+ turbulence,
2213
+ flakeSize,
2214
+ twinkleStrength,
2215
+ flakeCount
2216
+ };
2217
+ const pointerWindOffsetRef = (0, import_react7.useRef)(0);
2218
+ const pointerWindTargetRef = (0, import_react7.useRef)(0);
2219
+ const pointerStateRef = (0, import_react7.useRef)({
2220
+ lastX: 0,
2221
+ lastTime: 0,
2222
+ timeoutId: null
2223
+ });
2224
+ const pointerActiveRef = (0, import_react7.useRef)(false);
2225
+ const baseWindRef = (0, import_react7.useRef)(windStrength);
2226
+ (0, import_react7.useEffect)(() => {
2227
+ baseWindRef.current = windStrength;
2228
+ }, [windStrength]);
2229
+ const handleCreate = (0, import_react7.useCallback)(({ scene }) => {
2230
+ const uniforms = buildUniforms2(uniformsRef.current);
2231
+ const geometry = createSnowGeometry(
2232
+ Math.max(1, Math.floor(uniformsRef.current.flakeCount))
2233
+ );
2234
+ const material = new THREE7.ShaderMaterial({
2235
+ fragmentShader: fragment_default7,
2236
+ vertexShader: vertex_default6,
2237
+ uniforms,
2238
+ transparent: true,
2239
+ depthWrite: false,
2240
+ blending: THREE7.AdditiveBlending
2241
+ });
2242
+ const points = new THREE7.Points(geometry, material);
2243
+ points.frustumCulled = false;
2244
+ scene.add(points);
2245
+ snowRef.current = { points, geometry, material, uniforms };
2246
+ return () => {
2247
+ scene.remove(points);
2248
+ geometry.dispose();
2249
+ material.dispose();
2250
+ snowRef.current = null;
2251
+ };
2252
+ }, []);
2253
+ const handleRender = (0, import_react7.useCallback)(
2254
+ (_context, delta, elapsedTime) => {
2255
+ const assets = snowRef.current;
2256
+ if (!assets) return;
2257
+ assets.uniforms.uTime.value = elapsedTime;
2258
+ const currentOffset = pointerWindOffsetRef.current;
2259
+ const targetOffset = pointerWindTargetRef.current;
2260
+ const nextOffset = THREE7.MathUtils.damp(
2261
+ currentOffset,
2262
+ targetOffset,
2263
+ 3.5,
2264
+ delta
2265
+ );
2266
+ if (Math.abs(nextOffset - currentOffset) > 5e-5) {
2267
+ pointerWindOffsetRef.current = nextOffset;
2268
+ assets.uniforms.uWindStrength.value = baseWindRef.current + nextOffset;
2269
+ }
2270
+ },
2271
+ []
2272
+ );
2273
+ const { containerRef } = useScene({
2274
+ camera: {
2275
+ position: [0, 0, 18]
2276
+ },
2277
+ onCreate: handleCreate,
2278
+ onRender: handleRender
2279
+ });
2280
+ (0, import_react7.useEffect)(() => {
2281
+ const assets = snowRef.current;
2282
+ if (!assets) return;
2283
+ assets.uniforms.uColor.value.set(color);
2284
+ }, [color]);
2285
+ (0, import_react7.useEffect)(() => {
2286
+ const assets = snowRef.current;
2287
+ if (!assets) return;
2288
+ assets.uniforms.uFallSpeed.value = fallSpeed;
2289
+ }, [fallSpeed]);
2290
+ (0, import_react7.useEffect)(() => {
2291
+ const assets = snowRef.current;
2292
+ if (!assets) return;
2293
+ assets.uniforms.uWindStrength.value = windStrength + pointerWindOffsetRef.current;
2294
+ }, [windStrength]);
2295
+ (0, import_react7.useEffect)(() => {
2296
+ const assets = snowRef.current;
2297
+ if (!assets) return;
2298
+ assets.uniforms.uTurbulence.value = turbulence;
2299
+ }, [turbulence]);
2300
+ (0, import_react7.useEffect)(() => {
2301
+ const assets = snowRef.current;
2302
+ if (!assets) return;
2303
+ assets.uniforms.uSize.value = flakeSize;
2304
+ }, [flakeSize]);
2305
+ (0, import_react7.useEffect)(() => {
2306
+ const assets = snowRef.current;
2307
+ if (!assets) return;
2308
+ assets.uniforms.uTwinkleStrength.value = twinkleStrength;
2309
+ }, [twinkleStrength]);
2310
+ (0, import_react7.useEffect)(() => {
2311
+ const assets = snowRef.current;
2312
+ if (!assets) return;
2313
+ const geometry = createSnowGeometry(Math.max(1, Math.floor(flakeCount)));
2314
+ assets.points.geometry.dispose();
2315
+ assets.points.geometry = geometry;
2316
+ assets.geometry = geometry;
2317
+ }, [flakeCount]);
2318
+ (0, import_react7.useEffect)(() => {
2319
+ const pointerState = pointerStateRef.current;
2320
+ const clearTimeoutIfNeeded = () => {
2321
+ if (pointerState.timeoutId !== null) {
2322
+ window.clearTimeout(pointerState.timeoutId);
2323
+ pointerState.timeoutId = null;
2324
+ }
2325
+ };
2326
+ if (!mouseWindInteraction) {
2327
+ clearTimeoutIfNeeded();
2328
+ pointerWindOffsetRef.current = 0;
2329
+ pointerWindTargetRef.current = 0;
2330
+ pointerState.lastTime = 0;
2331
+ pointerActiveRef.current = false;
2332
+ const assets = snowRef.current;
2333
+ if (assets) {
2334
+ assets.uniforms.uWindStrength.value = windStrength;
2335
+ }
2336
+ return;
2337
+ }
2338
+ const container = containerRef.current;
2339
+ if (!container) return;
2340
+ const scheduleReset = () => {
2341
+ clearTimeoutIfNeeded();
2342
+ pointerState.timeoutId = window.setTimeout(() => {
2343
+ pointerWindTargetRef.current = 0;
2344
+ pointerState.timeoutId = null;
2345
+ }, 220);
2346
+ };
2347
+ const handlePointerMove = (event) => {
2348
+ const isMouse = event.pointerType === "mouse";
2349
+ if (!isMouse && !pointerActiveRef.current) return;
2350
+ const now = performance.now();
2351
+ if (pointerState.lastTime === 0) {
2352
+ pointerState.lastX = event.clientX;
2353
+ pointerState.lastTime = now;
2354
+ return;
2355
+ }
2356
+ const dx = event.clientX - pointerState.lastX;
2357
+ const dt = Math.max(1, now - pointerState.lastTime);
2358
+ const velocity = dx / dt;
2359
+ const offset = THREE7.MathUtils.clamp(velocity * 0.9, -1.6, 1.6);
2360
+ pointerWindTargetRef.current = offset;
2361
+ pointerState.lastX = event.clientX;
2362
+ pointerState.lastTime = now;
2363
+ scheduleReset();
2364
+ };
2365
+ const handlePointerDown = (event) => {
2366
+ pointerActiveRef.current = true;
2367
+ pointerState.lastX = event.clientX;
2368
+ pointerState.lastTime = performance.now();
2369
+ scheduleReset();
2370
+ };
2371
+ const handlePointerUp = () => {
2372
+ pointerActiveRef.current = false;
2373
+ pointerState.lastTime = 0;
2374
+ pointerWindTargetRef.current = 0;
2375
+ scheduleReset();
2376
+ };
2377
+ const handlePointerLeave = () => {
2378
+ pointerActiveRef.current = false;
2379
+ pointerState.lastTime = 0;
2380
+ pointerWindTargetRef.current = 0;
2381
+ clearTimeoutIfNeeded();
2382
+ };
2383
+ container.addEventListener("pointermove", handlePointerMove);
2384
+ container.addEventListener("pointerdown", handlePointerDown);
2385
+ container.addEventListener("pointerup", handlePointerUp);
2386
+ container.addEventListener("pointercancel", handlePointerUp);
2387
+ container.addEventListener("pointerout", handlePointerLeave);
2388
+ container.addEventListener("pointerleave", handlePointerLeave);
2389
+ return () => {
2390
+ container.removeEventListener("pointermove", handlePointerMove);
2391
+ container.removeEventListener("pointerdown", handlePointerDown);
2392
+ container.removeEventListener("pointerup", handlePointerUp);
2393
+ container.removeEventListener("pointercancel", handlePointerUp);
2394
+ container.removeEventListener("pointerout", handlePointerLeave);
2395
+ container.removeEventListener("pointerleave", handlePointerLeave);
2396
+ pointerState.lastTime = 0;
2397
+ pointerWindOffsetRef.current = 0;
2398
+ pointerWindTargetRef.current = 0;
2399
+ pointerActiveRef.current = false;
2400
+ clearTimeoutIfNeeded();
2401
+ const assets = snowRef.current;
2402
+ if (assets) {
2403
+ assets.uniforms.uWindStrength.value = windStrength;
2404
+ }
2405
+ };
2406
+ }, [containerRef, mouseWindInteraction, windStrength]);
2407
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2408
+ "div",
2409
+ {
2410
+ ref: containerRef,
2411
+ className,
2412
+ style: {
2413
+ width: width ?? "100%",
2414
+ height: height ?? "100%",
2415
+ ...style
2416
+ },
2417
+ ...divProps
2418
+ }
2419
+ );
2420
+ }
2421
+
2422
+ // src/components/AnimatedDrawingSVG.tsx
2423
+ var import_clsx = __toESM(require("clsx"));
2424
+ var import_react8 = require("react");
2425
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2426
+ var PATH_SELECTOR = "path, line, polyline, polygon, circle, ellipse";
2427
+ function AnimatedDrawingSVG({
2428
+ svgMarkup,
2429
+ animated = true,
2430
+ size,
2431
+ onAnimated,
2432
+ delay,
2433
+ className,
2434
+ style,
2435
+ ...divProps
2436
+ }) {
2437
+ const containerRef = (0, import_react8.useRef)(null);
2438
+ const animationsRef = (0, import_react8.useRef)([]);
2439
+ const parserRef = (0, import_react8.useRef)(null);
2440
+ const onAnimatedRef = (0, import_react8.useRef)(onAnimated);
2441
+ const animationRunIdRef = (0, import_react8.useRef)(0);
2442
+ const onAnimationCompleteRef = (0, import_react8.useRef)(false);
2443
+ const timeoutRef = (0, import_react8.useRef)([]);
2444
+ const monitorRafRef = (0, import_react8.useRef)(null);
2445
+ const sanitizedMarkup = (svgMarkup ?? "").toString().trim();
2446
+ const normalizedDelay = typeof delay === "number" && delay > 0 ? delay : 0;
2447
+ (0, import_react8.useEffect)(() => {
2448
+ onAnimatedRef.current = onAnimated;
2449
+ }, [onAnimated]);
2450
+ (0, import_react8.useEffect)(() => {
2451
+ return () => {
2452
+ animationsRef.current.forEach((animation) => animation.cancel());
2453
+ animationsRef.current = [];
2454
+ timeoutRef.current.forEach((id) => window.clearTimeout(id));
2455
+ timeoutRef.current = [];
2456
+ if (monitorRafRef.current !== null) {
2457
+ cancelAnimationFrame(monitorRafRef.current);
2458
+ monitorRafRef.current = null;
2459
+ }
2460
+ };
2461
+ }, []);
2462
+ (0, import_react8.useLayoutEffect)(() => {
2463
+ const container = containerRef.current;
2464
+ if (!container) return;
2465
+ let rafId = null;
2466
+ let delayId = null;
2467
+ let started = false;
2468
+ if (normalizedDelay > 0) {
2469
+ container.style.visibility = "hidden";
2470
+ } else {
2471
+ container.style.removeProperty("visibility");
2472
+ }
2473
+ animationRunIdRef.current += 1;
2474
+ const currentRunId = animationRunIdRef.current;
2475
+ onAnimationCompleteRef.current = false;
2476
+ timeoutRef.current.forEach((id) => window.clearTimeout(id));
2477
+ timeoutRef.current = [];
2478
+ const markComplete = () => {
2479
+ if (animationRunIdRef.current === currentRunId && !onAnimationCompleteRef.current) {
2480
+ onAnimationCompleteRef.current = true;
2481
+ onAnimatedRef.current?.();
2482
+ }
2483
+ };
2484
+ animationsRef.current.forEach((animation) => animation.cancel());
2485
+ animationsRef.current = [];
2486
+ if (monitorRafRef.current !== null) {
2487
+ cancelAnimationFrame(monitorRafRef.current);
2488
+ monitorRafRef.current = null;
2489
+ }
2490
+ if (!sanitizedMarkup) {
2491
+ container.replaceChildren();
2492
+ markComplete();
2493
+ return;
2494
+ }
2495
+ const parser = parserRef.current ?? new DOMParser();
2496
+ parserRef.current = parser;
2497
+ let parsed;
2498
+ try {
2499
+ parsed = parser.parseFromString(sanitizedMarkup, "image/svg+xml");
2500
+ } catch {
2501
+ return;
2502
+ }
2503
+ if (parsed.querySelector("parsererror")) {
2504
+ return;
2505
+ }
2506
+ const parsedSvg = parsed.querySelector("svg");
2507
+ if (!parsedSvg) {
2508
+ container.replaceChildren();
2509
+ onAnimatedRef.current?.();
2510
+ return;
2511
+ }
2512
+ const svgElement = document.importNode(parsedSvg, true);
2513
+ svgElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
2514
+ if (size !== void 0) {
2515
+ svgElement.removeAttribute("width");
2516
+ svgElement.removeAttribute("height");
2517
+ const sizeValue = typeof size === "number" ? `${Math.max(0, size)}px` : `${size}`;
2518
+ svgElement.style.width = sizeValue;
2519
+ svgElement.style.height = "auto";
2520
+ } else {
2521
+ svgElement.style.width = "100%";
2522
+ svgElement.style.height = "100%";
2523
+ svgElement.style.maxWidth = "100%";
2524
+ svgElement.style.maxHeight = "100%";
2525
+ }
2526
+ svgElement.style.display = "block";
2527
+ container.replaceChildren(svgElement);
2528
+ const runAnimations = () => {
2529
+ const drawTargets = Array.from(
2530
+ svgElement.querySelectorAll(PATH_SELECTOR)
2531
+ );
2532
+ const scheduleFallback = (delay2) => {
2533
+ const fallbackId = window.setTimeout(markComplete, delay2);
2534
+ timeoutRef.current.push(fallbackId);
2535
+ };
2536
+ if (!drawTargets.length) {
2537
+ if (!animated) {
2538
+ markComplete();
2539
+ } else {
2540
+ Promise.resolve().then(() => {
2541
+ markComplete();
2542
+ });
2543
+ }
2544
+ return;
2545
+ }
2546
+ let maxDuration = 0;
2547
+ const resolveTimingValue = (value, fallback) => {
2548
+ if (typeof value === "number") {
2549
+ return value;
2550
+ }
2551
+ if (typeof value === "string") {
2552
+ const parsed2 = Number.parseFloat(value);
2553
+ return Number.isFinite(parsed2) ? parsed2 : fallback;
2554
+ }
2555
+ if (typeof value === "object" && value !== null) {
2556
+ const parsed2 = Number.parseFloat(value.toString());
2557
+ return Number.isFinite(parsed2) ? parsed2 : fallback;
2558
+ }
2559
+ return fallback;
2560
+ };
2561
+ drawTargets.forEach((element, index) => {
2562
+ const length = typeof element.getTotalLength === "function" ? element.getTotalLength() : null;
2563
+ if (!length || Number.isNaN(length)) {
2564
+ element.style.removeProperty("stroke-dasharray");
2565
+ element.style.removeProperty("stroke-dashoffset");
2566
+ return;
2567
+ }
2568
+ const dashValue = `${length}`;
2569
+ element.style.strokeDasharray = dashValue;
2570
+ element.style.strokeDashoffset = animated ? dashValue : "0";
2571
+ if (!element.style.strokeLinecap) {
2572
+ element.style.strokeLinecap = "round";
2573
+ }
2574
+ if (!animated) {
2575
+ return;
2576
+ }
2577
+ const animation = element.animate(
2578
+ [{ strokeDashoffset: dashValue }, { strokeDashoffset: "0" }],
2579
+ {
2580
+ duration: Math.min(6500, Math.max(1200, length * 12)),
2581
+ delay: index * 120,
2582
+ easing: "ease-in-out",
2583
+ fill: "forwards"
2584
+ }
2585
+ );
2586
+ const timing = animation.effect?.getTiming();
2587
+ const baseDuration = Math.min(6500, Math.max(1200, length * 12));
2588
+ const total = resolveTimingValue(timing?.delay, index * 120) + resolveTimingValue(timing?.duration, baseDuration);
2589
+ if (total > maxDuration) {
2590
+ maxDuration = total;
2591
+ }
2592
+ animationsRef.current.push(animation);
2593
+ });
2594
+ if (!animated) {
2595
+ markComplete();
2596
+ return;
2597
+ }
2598
+ const startMonitor = () => {
2599
+ const monitor = () => {
2600
+ if (animationRunIdRef.current !== currentRunId) {
2601
+ return;
2602
+ }
2603
+ const allFinished = animationsRef.current.every((animation) => {
2604
+ const state = animation.playState;
2605
+ return state === "finished" || state === "idle";
2606
+ });
2607
+ if (allFinished) {
2608
+ if (monitorRafRef.current !== null) {
2609
+ cancelAnimationFrame(monitorRafRef.current);
2610
+ monitorRafRef.current = null;
2611
+ }
2612
+ markComplete();
2613
+ return;
2614
+ }
2615
+ monitorRafRef.current = requestAnimationFrame(monitor);
2616
+ };
2617
+ if (monitorRafRef.current !== null) {
2618
+ cancelAnimationFrame(monitorRafRef.current);
2619
+ }
2620
+ monitorRafRef.current = requestAnimationFrame(monitor);
2621
+ };
2622
+ startMonitor();
2623
+ if (animated && maxDuration > 0) {
2624
+ scheduleFallback(maxDuration + 50);
2625
+ }
2626
+ };
2627
+ const triggerStart = () => {
2628
+ if (started) return;
2629
+ started = true;
2630
+ container.style.removeProperty("visibility");
2631
+ rafId = requestAnimationFrame(runAnimations);
2632
+ };
2633
+ if (normalizedDelay > 0) {
2634
+ delayId = window.setTimeout(triggerStart, normalizedDelay);
2635
+ } else {
2636
+ triggerStart();
2637
+ }
2638
+ return () => {
2639
+ if (delayId !== null) {
2640
+ window.clearTimeout(delayId);
2641
+ }
2642
+ if (rafId !== null) {
2643
+ cancelAnimationFrame(rafId);
2644
+ }
2645
+ };
2646
+ }, [sanitizedMarkup, animated, size, normalizedDelay]);
2647
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2648
+ "div",
2649
+ {
2650
+ ref: containerRef,
2651
+ className: (0, import_clsx.default)(
2652
+ "flex items-center justify-center [&_svg]:block",
2653
+ className
2654
+ ),
2655
+ style: {
2656
+ ...style
2657
+ },
2658
+ ...divProps
2659
+ }
2660
+ );
2661
+ }
2114
2662
  // Annotate the CommonJS export names for ESM import in node:
2115
2663
  0 && (module.exports = {
2664
+ AnimatedDrawingSVG,
2116
2665
  EFECTO_ASCII_COMPONENT_DEFAULTS,
2117
2666
  EFECTO_ASCII_POST_PROCESSING_DEFAULTS,
2118
2667
  Efecto,
2119
2668
  FractalFlower,
2120
2669
  MenuGlitch,
2121
2670
  OranoParticles,
2122
- ShaderArt
2671
+ ShaderArt,
2672
+ Snow
2123
2673
  });
package/dist/index.mjs CHANGED
@@ -2090,12 +2090,560 @@ function Efecto({
2090
2090
  }
2091
2091
  );
2092
2092
  }
2093
+
2094
+ // src/components/Snow.tsx
2095
+ import { useCallback as useCallback6, useEffect as useEffect7, useRef as useRef7 } from "react";
2096
+ import * as THREE7 from "three";
2097
+
2098
+ // src/shaders/snow/fragment.glsl
2099
+ var fragment_default7 = "precision mediump float;\n\nuniform vec3 uColor;\n\nvarying float vAlpha;\n\nvoid main() {\n vec2 uv = gl_PointCoord - 0.5;\n float dist = length(uv);\n float mask = smoothstep(0.5, 0.0, dist);\n if (mask <= 0.01) {\n discard;\n }\n\n float centerGlow = smoothstep(0.22, 0.0, dist);\n vec3 color = mix(uColor * 1.2, uColor, centerGlow);\n\n gl_FragColor = vec4(color, mask * vAlpha);\n}\n";
2100
+
2101
+ // src/shaders/snow/vertex.glsl
2102
+ var vertex_default6 = "uniform float uTime;\nuniform float uFallSpeed;\nuniform float uWindStrength;\nuniform float uTurbulence;\nuniform float uSize;\nuniform float uTwinkleStrength;\nuniform vec3 uArea;\n\nattribute float aSpeed;\nattribute float aSize;\nattribute float aSeed;\n\nvarying float vAlpha;\n\nfloat wrap(float value, float size) {\n return mod(value + size * 0.5, size) - size * 0.5;\n}\n\nvoid main() {\n float height = uArea.y;\n float width = uArea.x;\n float depth = uArea.z;\n\n float fall = uFallSpeed * (0.3 + aSpeed);\n float droppedY = position.y - uTime * fall;\n float wrappedY = wrap(droppedY, height);\n\n float sway =\n sin((wrappedY + aSeed) * 0.45 + uTime * 0.8) * uTurbulence +\n cos(uTime * 0.35 + aSeed) * 0.15;\n float wind = uWindStrength * (0.4 + aSpeed);\n float displacedX = wrap(position.x + sway + wind, width);\n\n float driftZ =\n sin(uTime * 0.25 + aSeed * 1.7) * 0.5 +\n cos((wrappedY + aSeed) * 0.2) * 0.4;\n float displacedZ = wrap(position.z + driftZ, depth);\n\n vec4 modelPosition =\n modelMatrix * vec4(displacedX, wrappedY, displacedZ, 1.0);\n vec4 viewPosition = viewMatrix * modelPosition;\n\n float baseSize = mix(0.45, 1.0, aSize) * uSize;\n float twinkle =\n 1.0 + sin(uTime * (0.6 + aSpeed) + aSeed) * uTwinkleStrength;\n float perspective = clamp(15.0 / max(1.0, -viewPosition.z), 0.5, 3.0);\n gl_PointSize = baseSize * twinkle * perspective;\n gl_Position = projectionMatrix * viewPosition;\n\n vAlpha = mix(0.35, 1.0, aSize);\n}\n";
2103
+
2104
+ // src/components/Snow.tsx
2105
+ import { jsx as jsx8 } from "react/jsx-runtime";
2106
+ var AREA_BOUNDS = {
2107
+ width: 36,
2108
+ height: 44,
2109
+ depth: 26
2110
+ };
2111
+ function createSnowGeometry(count) {
2112
+ const geometry = new THREE7.BufferGeometry();
2113
+ const positions = new Float32Array(count * 3);
2114
+ const speeds = new Float32Array(count);
2115
+ const sizes = new Float32Array(count);
2116
+ const seeds = new Float32Array(count);
2117
+ for (let i = 0; i < count; i += 1) {
2118
+ const x = (Math.random() - 0.5) * AREA_BOUNDS.width;
2119
+ const y = (Math.random() - 0.5) * AREA_BOUNDS.height;
2120
+ const z = (Math.random() - 0.5) * AREA_BOUNDS.depth;
2121
+ positions[i * 3] = x;
2122
+ positions[i * 3 + 1] = y;
2123
+ positions[i * 3 + 2] = z;
2124
+ speeds[i] = Math.random();
2125
+ sizes[i] = Math.random();
2126
+ seeds[i] = Math.random() * 100;
2127
+ }
2128
+ geometry.setAttribute("position", new THREE7.BufferAttribute(positions, 3));
2129
+ geometry.setAttribute("aSpeed", new THREE7.BufferAttribute(speeds, 1));
2130
+ geometry.setAttribute("aSize", new THREE7.BufferAttribute(sizes, 1));
2131
+ geometry.setAttribute("aSeed", new THREE7.BufferAttribute(seeds, 1));
2132
+ geometry.computeBoundingSphere();
2133
+ return geometry;
2134
+ }
2135
+ function buildUniforms2({
2136
+ color,
2137
+ fallSpeed,
2138
+ windStrength,
2139
+ turbulence,
2140
+ flakeSize,
2141
+ twinkleStrength
2142
+ }) {
2143
+ return {
2144
+ uTime: { value: 0 },
2145
+ uFallSpeed: { value: fallSpeed },
2146
+ uWindStrength: { value: windStrength },
2147
+ uTurbulence: { value: turbulence },
2148
+ uSize: { value: flakeSize },
2149
+ uTwinkleStrength: { value: twinkleStrength },
2150
+ uColor: { value: new THREE7.Color(color) },
2151
+ uArea: {
2152
+ value: new THREE7.Vector3(
2153
+ AREA_BOUNDS.width,
2154
+ AREA_BOUNDS.height,
2155
+ AREA_BOUNDS.depth
2156
+ )
2157
+ }
2158
+ };
2159
+ }
2160
+ function Snow({
2161
+ className,
2162
+ style,
2163
+ width,
2164
+ height,
2165
+ color,
2166
+ fallSpeed,
2167
+ windStrength,
2168
+ turbulence,
2169
+ flakeSize,
2170
+ twinkleStrength,
2171
+ flakeCount,
2172
+ mouseWindInteraction = false,
2173
+ ...divProps
2174
+ }) {
2175
+ const snowRef = useRef7(null);
2176
+ const uniformsRef = useRef7({
2177
+ color,
2178
+ fallSpeed,
2179
+ windStrength,
2180
+ turbulence,
2181
+ flakeSize,
2182
+ twinkleStrength,
2183
+ flakeCount
2184
+ });
2185
+ uniformsRef.current = {
2186
+ color,
2187
+ fallSpeed,
2188
+ windStrength,
2189
+ turbulence,
2190
+ flakeSize,
2191
+ twinkleStrength,
2192
+ flakeCount
2193
+ };
2194
+ const pointerWindOffsetRef = useRef7(0);
2195
+ const pointerWindTargetRef = useRef7(0);
2196
+ const pointerStateRef = useRef7({
2197
+ lastX: 0,
2198
+ lastTime: 0,
2199
+ timeoutId: null
2200
+ });
2201
+ const pointerActiveRef = useRef7(false);
2202
+ const baseWindRef = useRef7(windStrength);
2203
+ useEffect7(() => {
2204
+ baseWindRef.current = windStrength;
2205
+ }, [windStrength]);
2206
+ const handleCreate = useCallback6(({ scene }) => {
2207
+ const uniforms = buildUniforms2(uniformsRef.current);
2208
+ const geometry = createSnowGeometry(
2209
+ Math.max(1, Math.floor(uniformsRef.current.flakeCount))
2210
+ );
2211
+ const material = new THREE7.ShaderMaterial({
2212
+ fragmentShader: fragment_default7,
2213
+ vertexShader: vertex_default6,
2214
+ uniforms,
2215
+ transparent: true,
2216
+ depthWrite: false,
2217
+ blending: THREE7.AdditiveBlending
2218
+ });
2219
+ const points = new THREE7.Points(geometry, material);
2220
+ points.frustumCulled = false;
2221
+ scene.add(points);
2222
+ snowRef.current = { points, geometry, material, uniforms };
2223
+ return () => {
2224
+ scene.remove(points);
2225
+ geometry.dispose();
2226
+ material.dispose();
2227
+ snowRef.current = null;
2228
+ };
2229
+ }, []);
2230
+ const handleRender = useCallback6(
2231
+ (_context, delta, elapsedTime) => {
2232
+ const assets = snowRef.current;
2233
+ if (!assets) return;
2234
+ assets.uniforms.uTime.value = elapsedTime;
2235
+ const currentOffset = pointerWindOffsetRef.current;
2236
+ const targetOffset = pointerWindTargetRef.current;
2237
+ const nextOffset = THREE7.MathUtils.damp(
2238
+ currentOffset,
2239
+ targetOffset,
2240
+ 3.5,
2241
+ delta
2242
+ );
2243
+ if (Math.abs(nextOffset - currentOffset) > 5e-5) {
2244
+ pointerWindOffsetRef.current = nextOffset;
2245
+ assets.uniforms.uWindStrength.value = baseWindRef.current + nextOffset;
2246
+ }
2247
+ },
2248
+ []
2249
+ );
2250
+ const { containerRef } = useScene({
2251
+ camera: {
2252
+ position: [0, 0, 18]
2253
+ },
2254
+ onCreate: handleCreate,
2255
+ onRender: handleRender
2256
+ });
2257
+ useEffect7(() => {
2258
+ const assets = snowRef.current;
2259
+ if (!assets) return;
2260
+ assets.uniforms.uColor.value.set(color);
2261
+ }, [color]);
2262
+ useEffect7(() => {
2263
+ const assets = snowRef.current;
2264
+ if (!assets) return;
2265
+ assets.uniforms.uFallSpeed.value = fallSpeed;
2266
+ }, [fallSpeed]);
2267
+ useEffect7(() => {
2268
+ const assets = snowRef.current;
2269
+ if (!assets) return;
2270
+ assets.uniforms.uWindStrength.value = windStrength + pointerWindOffsetRef.current;
2271
+ }, [windStrength]);
2272
+ useEffect7(() => {
2273
+ const assets = snowRef.current;
2274
+ if (!assets) return;
2275
+ assets.uniforms.uTurbulence.value = turbulence;
2276
+ }, [turbulence]);
2277
+ useEffect7(() => {
2278
+ const assets = snowRef.current;
2279
+ if (!assets) return;
2280
+ assets.uniforms.uSize.value = flakeSize;
2281
+ }, [flakeSize]);
2282
+ useEffect7(() => {
2283
+ const assets = snowRef.current;
2284
+ if (!assets) return;
2285
+ assets.uniforms.uTwinkleStrength.value = twinkleStrength;
2286
+ }, [twinkleStrength]);
2287
+ useEffect7(() => {
2288
+ const assets = snowRef.current;
2289
+ if (!assets) return;
2290
+ const geometry = createSnowGeometry(Math.max(1, Math.floor(flakeCount)));
2291
+ assets.points.geometry.dispose();
2292
+ assets.points.geometry = geometry;
2293
+ assets.geometry = geometry;
2294
+ }, [flakeCount]);
2295
+ useEffect7(() => {
2296
+ const pointerState = pointerStateRef.current;
2297
+ const clearTimeoutIfNeeded = () => {
2298
+ if (pointerState.timeoutId !== null) {
2299
+ window.clearTimeout(pointerState.timeoutId);
2300
+ pointerState.timeoutId = null;
2301
+ }
2302
+ };
2303
+ if (!mouseWindInteraction) {
2304
+ clearTimeoutIfNeeded();
2305
+ pointerWindOffsetRef.current = 0;
2306
+ pointerWindTargetRef.current = 0;
2307
+ pointerState.lastTime = 0;
2308
+ pointerActiveRef.current = false;
2309
+ const assets = snowRef.current;
2310
+ if (assets) {
2311
+ assets.uniforms.uWindStrength.value = windStrength;
2312
+ }
2313
+ return;
2314
+ }
2315
+ const container = containerRef.current;
2316
+ if (!container) return;
2317
+ const scheduleReset = () => {
2318
+ clearTimeoutIfNeeded();
2319
+ pointerState.timeoutId = window.setTimeout(() => {
2320
+ pointerWindTargetRef.current = 0;
2321
+ pointerState.timeoutId = null;
2322
+ }, 220);
2323
+ };
2324
+ const handlePointerMove = (event) => {
2325
+ const isMouse = event.pointerType === "mouse";
2326
+ if (!isMouse && !pointerActiveRef.current) return;
2327
+ const now = performance.now();
2328
+ if (pointerState.lastTime === 0) {
2329
+ pointerState.lastX = event.clientX;
2330
+ pointerState.lastTime = now;
2331
+ return;
2332
+ }
2333
+ const dx = event.clientX - pointerState.lastX;
2334
+ const dt = Math.max(1, now - pointerState.lastTime);
2335
+ const velocity = dx / dt;
2336
+ const offset = THREE7.MathUtils.clamp(velocity * 0.9, -1.6, 1.6);
2337
+ pointerWindTargetRef.current = offset;
2338
+ pointerState.lastX = event.clientX;
2339
+ pointerState.lastTime = now;
2340
+ scheduleReset();
2341
+ };
2342
+ const handlePointerDown = (event) => {
2343
+ pointerActiveRef.current = true;
2344
+ pointerState.lastX = event.clientX;
2345
+ pointerState.lastTime = performance.now();
2346
+ scheduleReset();
2347
+ };
2348
+ const handlePointerUp = () => {
2349
+ pointerActiveRef.current = false;
2350
+ pointerState.lastTime = 0;
2351
+ pointerWindTargetRef.current = 0;
2352
+ scheduleReset();
2353
+ };
2354
+ const handlePointerLeave = () => {
2355
+ pointerActiveRef.current = false;
2356
+ pointerState.lastTime = 0;
2357
+ pointerWindTargetRef.current = 0;
2358
+ clearTimeoutIfNeeded();
2359
+ };
2360
+ container.addEventListener("pointermove", handlePointerMove);
2361
+ container.addEventListener("pointerdown", handlePointerDown);
2362
+ container.addEventListener("pointerup", handlePointerUp);
2363
+ container.addEventListener("pointercancel", handlePointerUp);
2364
+ container.addEventListener("pointerout", handlePointerLeave);
2365
+ container.addEventListener("pointerleave", handlePointerLeave);
2366
+ return () => {
2367
+ container.removeEventListener("pointermove", handlePointerMove);
2368
+ container.removeEventListener("pointerdown", handlePointerDown);
2369
+ container.removeEventListener("pointerup", handlePointerUp);
2370
+ container.removeEventListener("pointercancel", handlePointerUp);
2371
+ container.removeEventListener("pointerout", handlePointerLeave);
2372
+ container.removeEventListener("pointerleave", handlePointerLeave);
2373
+ pointerState.lastTime = 0;
2374
+ pointerWindOffsetRef.current = 0;
2375
+ pointerWindTargetRef.current = 0;
2376
+ pointerActiveRef.current = false;
2377
+ clearTimeoutIfNeeded();
2378
+ const assets = snowRef.current;
2379
+ if (assets) {
2380
+ assets.uniforms.uWindStrength.value = windStrength;
2381
+ }
2382
+ };
2383
+ }, [containerRef, mouseWindInteraction, windStrength]);
2384
+ return /* @__PURE__ */ jsx8(
2385
+ "div",
2386
+ {
2387
+ ref: containerRef,
2388
+ className,
2389
+ style: {
2390
+ width: width ?? "100%",
2391
+ height: height ?? "100%",
2392
+ ...style
2393
+ },
2394
+ ...divProps
2395
+ }
2396
+ );
2397
+ }
2398
+
2399
+ // src/components/AnimatedDrawingSVG.tsx
2400
+ import clsx from "clsx";
2401
+ import { useEffect as useEffect8, useLayoutEffect, useRef as useRef8 } from "react";
2402
+ import { jsx as jsx9 } from "react/jsx-runtime";
2403
+ var PATH_SELECTOR = "path, line, polyline, polygon, circle, ellipse";
2404
+ function AnimatedDrawingSVG({
2405
+ svgMarkup,
2406
+ animated = true,
2407
+ size,
2408
+ onAnimated,
2409
+ delay,
2410
+ className,
2411
+ style,
2412
+ ...divProps
2413
+ }) {
2414
+ const containerRef = useRef8(null);
2415
+ const animationsRef = useRef8([]);
2416
+ const parserRef = useRef8(null);
2417
+ const onAnimatedRef = useRef8(onAnimated);
2418
+ const animationRunIdRef = useRef8(0);
2419
+ const onAnimationCompleteRef = useRef8(false);
2420
+ const timeoutRef = useRef8([]);
2421
+ const monitorRafRef = useRef8(null);
2422
+ const sanitizedMarkup = (svgMarkup ?? "").toString().trim();
2423
+ const normalizedDelay = typeof delay === "number" && delay > 0 ? delay : 0;
2424
+ useEffect8(() => {
2425
+ onAnimatedRef.current = onAnimated;
2426
+ }, [onAnimated]);
2427
+ useEffect8(() => {
2428
+ return () => {
2429
+ animationsRef.current.forEach((animation) => animation.cancel());
2430
+ animationsRef.current = [];
2431
+ timeoutRef.current.forEach((id) => window.clearTimeout(id));
2432
+ timeoutRef.current = [];
2433
+ if (monitorRafRef.current !== null) {
2434
+ cancelAnimationFrame(monitorRafRef.current);
2435
+ monitorRafRef.current = null;
2436
+ }
2437
+ };
2438
+ }, []);
2439
+ useLayoutEffect(() => {
2440
+ const container = containerRef.current;
2441
+ if (!container) return;
2442
+ let rafId = null;
2443
+ let delayId = null;
2444
+ let started = false;
2445
+ if (normalizedDelay > 0) {
2446
+ container.style.visibility = "hidden";
2447
+ } else {
2448
+ container.style.removeProperty("visibility");
2449
+ }
2450
+ animationRunIdRef.current += 1;
2451
+ const currentRunId = animationRunIdRef.current;
2452
+ onAnimationCompleteRef.current = false;
2453
+ timeoutRef.current.forEach((id) => window.clearTimeout(id));
2454
+ timeoutRef.current = [];
2455
+ const markComplete = () => {
2456
+ if (animationRunIdRef.current === currentRunId && !onAnimationCompleteRef.current) {
2457
+ onAnimationCompleteRef.current = true;
2458
+ onAnimatedRef.current?.();
2459
+ }
2460
+ };
2461
+ animationsRef.current.forEach((animation) => animation.cancel());
2462
+ animationsRef.current = [];
2463
+ if (monitorRafRef.current !== null) {
2464
+ cancelAnimationFrame(monitorRafRef.current);
2465
+ monitorRafRef.current = null;
2466
+ }
2467
+ if (!sanitizedMarkup) {
2468
+ container.replaceChildren();
2469
+ markComplete();
2470
+ return;
2471
+ }
2472
+ const parser = parserRef.current ?? new DOMParser();
2473
+ parserRef.current = parser;
2474
+ let parsed;
2475
+ try {
2476
+ parsed = parser.parseFromString(sanitizedMarkup, "image/svg+xml");
2477
+ } catch {
2478
+ return;
2479
+ }
2480
+ if (parsed.querySelector("parsererror")) {
2481
+ return;
2482
+ }
2483
+ const parsedSvg = parsed.querySelector("svg");
2484
+ if (!parsedSvg) {
2485
+ container.replaceChildren();
2486
+ onAnimatedRef.current?.();
2487
+ return;
2488
+ }
2489
+ const svgElement = document.importNode(parsedSvg, true);
2490
+ svgElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
2491
+ if (size !== void 0) {
2492
+ svgElement.removeAttribute("width");
2493
+ svgElement.removeAttribute("height");
2494
+ const sizeValue = typeof size === "number" ? `${Math.max(0, size)}px` : `${size}`;
2495
+ svgElement.style.width = sizeValue;
2496
+ svgElement.style.height = "auto";
2497
+ } else {
2498
+ svgElement.style.width = "100%";
2499
+ svgElement.style.height = "100%";
2500
+ svgElement.style.maxWidth = "100%";
2501
+ svgElement.style.maxHeight = "100%";
2502
+ }
2503
+ svgElement.style.display = "block";
2504
+ container.replaceChildren(svgElement);
2505
+ const runAnimations = () => {
2506
+ const drawTargets = Array.from(
2507
+ svgElement.querySelectorAll(PATH_SELECTOR)
2508
+ );
2509
+ const scheduleFallback = (delay2) => {
2510
+ const fallbackId = window.setTimeout(markComplete, delay2);
2511
+ timeoutRef.current.push(fallbackId);
2512
+ };
2513
+ if (!drawTargets.length) {
2514
+ if (!animated) {
2515
+ markComplete();
2516
+ } else {
2517
+ Promise.resolve().then(() => {
2518
+ markComplete();
2519
+ });
2520
+ }
2521
+ return;
2522
+ }
2523
+ let maxDuration = 0;
2524
+ const resolveTimingValue = (value, fallback) => {
2525
+ if (typeof value === "number") {
2526
+ return value;
2527
+ }
2528
+ if (typeof value === "string") {
2529
+ const parsed2 = Number.parseFloat(value);
2530
+ return Number.isFinite(parsed2) ? parsed2 : fallback;
2531
+ }
2532
+ if (typeof value === "object" && value !== null) {
2533
+ const parsed2 = Number.parseFloat(value.toString());
2534
+ return Number.isFinite(parsed2) ? parsed2 : fallback;
2535
+ }
2536
+ return fallback;
2537
+ };
2538
+ drawTargets.forEach((element, index) => {
2539
+ const length = typeof element.getTotalLength === "function" ? element.getTotalLength() : null;
2540
+ if (!length || Number.isNaN(length)) {
2541
+ element.style.removeProperty("stroke-dasharray");
2542
+ element.style.removeProperty("stroke-dashoffset");
2543
+ return;
2544
+ }
2545
+ const dashValue = `${length}`;
2546
+ element.style.strokeDasharray = dashValue;
2547
+ element.style.strokeDashoffset = animated ? dashValue : "0";
2548
+ if (!element.style.strokeLinecap) {
2549
+ element.style.strokeLinecap = "round";
2550
+ }
2551
+ if (!animated) {
2552
+ return;
2553
+ }
2554
+ const animation = element.animate(
2555
+ [{ strokeDashoffset: dashValue }, { strokeDashoffset: "0" }],
2556
+ {
2557
+ duration: Math.min(6500, Math.max(1200, length * 12)),
2558
+ delay: index * 120,
2559
+ easing: "ease-in-out",
2560
+ fill: "forwards"
2561
+ }
2562
+ );
2563
+ const timing = animation.effect?.getTiming();
2564
+ const baseDuration = Math.min(6500, Math.max(1200, length * 12));
2565
+ const total = resolveTimingValue(timing?.delay, index * 120) + resolveTimingValue(timing?.duration, baseDuration);
2566
+ if (total > maxDuration) {
2567
+ maxDuration = total;
2568
+ }
2569
+ animationsRef.current.push(animation);
2570
+ });
2571
+ if (!animated) {
2572
+ markComplete();
2573
+ return;
2574
+ }
2575
+ const startMonitor = () => {
2576
+ const monitor = () => {
2577
+ if (animationRunIdRef.current !== currentRunId) {
2578
+ return;
2579
+ }
2580
+ const allFinished = animationsRef.current.every((animation) => {
2581
+ const state = animation.playState;
2582
+ return state === "finished" || state === "idle";
2583
+ });
2584
+ if (allFinished) {
2585
+ if (monitorRafRef.current !== null) {
2586
+ cancelAnimationFrame(monitorRafRef.current);
2587
+ monitorRafRef.current = null;
2588
+ }
2589
+ markComplete();
2590
+ return;
2591
+ }
2592
+ monitorRafRef.current = requestAnimationFrame(monitor);
2593
+ };
2594
+ if (monitorRafRef.current !== null) {
2595
+ cancelAnimationFrame(monitorRafRef.current);
2596
+ }
2597
+ monitorRafRef.current = requestAnimationFrame(monitor);
2598
+ };
2599
+ startMonitor();
2600
+ if (animated && maxDuration > 0) {
2601
+ scheduleFallback(maxDuration + 50);
2602
+ }
2603
+ };
2604
+ const triggerStart = () => {
2605
+ if (started) return;
2606
+ started = true;
2607
+ container.style.removeProperty("visibility");
2608
+ rafId = requestAnimationFrame(runAnimations);
2609
+ };
2610
+ if (normalizedDelay > 0) {
2611
+ delayId = window.setTimeout(triggerStart, normalizedDelay);
2612
+ } else {
2613
+ triggerStart();
2614
+ }
2615
+ return () => {
2616
+ if (delayId !== null) {
2617
+ window.clearTimeout(delayId);
2618
+ }
2619
+ if (rafId !== null) {
2620
+ cancelAnimationFrame(rafId);
2621
+ }
2622
+ };
2623
+ }, [sanitizedMarkup, animated, size, normalizedDelay]);
2624
+ return /* @__PURE__ */ jsx9(
2625
+ "div",
2626
+ {
2627
+ ref: containerRef,
2628
+ className: clsx(
2629
+ "flex items-center justify-center [&_svg]:block",
2630
+ className
2631
+ ),
2632
+ style: {
2633
+ ...style
2634
+ },
2635
+ ...divProps
2636
+ }
2637
+ );
2638
+ }
2093
2639
  export {
2640
+ AnimatedDrawingSVG,
2094
2641
  EFECTO_ASCII_COMPONENT_DEFAULTS,
2095
2642
  EFECTO_ASCII_POST_PROCESSING_DEFAULTS,
2096
2643
  Efecto,
2097
2644
  FractalFlower,
2098
2645
  MenuGlitch,
2099
2646
  OranoParticles,
2100
- ShaderArt
2647
+ ShaderArt,
2648
+ Snow
2101
2649
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toriistudio/shader-ui",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Shader components",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -54,6 +54,7 @@
54
54
  },
55
55
  "homepage": "https://github.com/toriistudio/shader-ui#readme",
56
56
  "peerDependencies": {
57
+ "clsx": "^2.1.1",
57
58
  "gsap": ">=3.14.2",
58
59
  "postprocessing": ">=6.38.1",
59
60
  "react": ">=19 <20",
@@ -74,6 +75,7 @@
74
75
  "@types/react": "^19.0.0",
75
76
  "@types/react-dom": "^19.0.0",
76
77
  "@types/three": "^0.177.0",
78
+ "clsx": "^2.1.1",
77
79
  "gsap": "^3.14.2",
78
80
  "postprocessing": "^6.38.1",
79
81
  "three": "^0.177.0",