@toriistudio/shader-ui 0.0.1 → 0.0.3

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.mjs CHANGED
@@ -13,13 +13,15 @@ function useScene({
13
13
  onCreate,
14
14
  onRender,
15
15
  onResize,
16
- deps
16
+ deps,
17
+ manualRender = false
17
18
  } = {}) {
18
19
  const containerRef = useRef(null);
19
20
  const contextRef = useRef(null);
20
21
  const onCreateRef = useRef(onCreate);
21
22
  const onRenderRef = useRef(onRender);
22
23
  const onResizeRef = useRef(onResize);
24
+ const sizeRef = useRef({ width: 0, height: 0 });
23
25
  useEffect(() => {
24
26
  onCreateRef.current = onCreate;
25
27
  }, [onCreate]);
@@ -34,43 +36,90 @@ function useScene({
34
36
  const container = containerRef.current;
35
37
  if (!container) return;
36
38
  const resolvedPixelRatio = pixelRatio ?? (typeof window !== "undefined" ? Math.min(window.devicePixelRatio, 2) : 1);
39
+ const initialWidth = container.clientWidth || 1;
40
+ const initialHeight = container.clientHeight || 1;
41
+ sizeRef.current = { width: initialWidth, height: initialHeight };
37
42
  const renderer = new THREE.WebGLRenderer({
38
43
  alpha: true,
39
44
  antialias: true,
40
45
  ...rendererOptions
41
46
  });
47
+ renderer.autoClear = false;
48
+ renderer.autoClearColor = true;
49
+ renderer.autoClearDepth = true;
50
+ renderer.autoClearStencil = true;
42
51
  renderer.setPixelRatio(resolvedPixelRatio);
43
- renderer.setSize(container.clientWidth, container.clientHeight, false);
52
+ renderer.setSize(initialWidth, initialHeight, false);
44
53
  renderer.setClearColor(0, 0);
45
54
  renderer.domElement.style.width = "100%";
46
55
  renderer.domElement.style.height = "100%";
47
56
  container.appendChild(renderer.domElement);
48
57
  const scene = new THREE.Scene();
49
- const camera = new THREE.PerspectiveCamera(
50
- cameraOptions?.fov ?? 55,
51
- container.clientWidth / Math.max(1, container.clientHeight),
52
- cameraOptions?.near ?? 0.1,
53
- cameraOptions?.far ?? 500
54
- );
55
- const [x = 0, y = 0, z = 15] = cameraOptions?.position ?? [];
56
- camera.position.set(x, y, z);
58
+ let camera;
59
+ if (cameraOptions?.type === "orthographic") {
60
+ const halfWidth = initialWidth / 2;
61
+ const halfHeight = initialHeight / 2;
62
+ const orthoCamera = new THREE.OrthographicCamera(
63
+ -halfWidth,
64
+ halfWidth,
65
+ halfHeight,
66
+ -halfHeight,
67
+ cameraOptions?.near ?? 0.1,
68
+ cameraOptions?.far ?? 1e3
69
+ );
70
+ const [, , z = 10] = cameraOptions?.position ?? [];
71
+ orthoCamera.position.set(0, 0, z);
72
+ camera = orthoCamera;
73
+ } else {
74
+ const perspectiveCamera = new THREE.PerspectiveCamera(
75
+ cameraOptions?.fov ?? 55,
76
+ initialWidth / Math.max(1, initialHeight),
77
+ cameraOptions?.near ?? 0.1,
78
+ cameraOptions?.far ?? 500
79
+ );
80
+ const [x = 0, y = 0, z = 15] = cameraOptions?.position ?? [];
81
+ perspectiveCamera.position.set(x, y, z);
82
+ camera = perspectiveCamera;
83
+ }
57
84
  const clock = new THREE.Clock();
58
- const context = { renderer, scene, camera, clock };
85
+ const context = {
86
+ renderer,
87
+ scene,
88
+ camera,
89
+ clock,
90
+ size: { ...sizeRef.current }
91
+ };
59
92
  contextRef.current = context;
60
93
  const teardownCreate = onCreateRef.current?.(context);
94
+ let elapsedTime = 0;
61
95
  let animationFrameId = 0;
62
96
  const renderLoop = () => {
63
- onRenderRef.current?.(context);
64
- renderer.render(scene, camera);
97
+ const delta = clock.getDelta();
98
+ elapsedTime += delta;
99
+ context.size = { ...sizeRef.current };
100
+ onRenderRef.current?.(context, delta, elapsedTime);
101
+ if (!manualRender) {
102
+ renderer.render(scene, camera);
103
+ }
65
104
  animationFrameId = requestAnimationFrame(renderLoop);
66
105
  };
67
106
  animationFrameId = requestAnimationFrame(renderLoop);
68
107
  const resizeObserver = new ResizeObserver(() => {
69
- const width = container.clientWidth;
70
- const height = container.clientHeight;
108
+ const width = container.clientWidth || 1;
109
+ const height = container.clientHeight || 1;
71
110
  renderer.setSize(width, height, false);
72
- camera.aspect = width / Math.max(1, height);
73
- camera.updateProjectionMatrix();
111
+ sizeRef.current = { width, height };
112
+ if (camera instanceof THREE.PerspectiveCamera) {
113
+ camera.aspect = width / Math.max(1, height);
114
+ camera.updateProjectionMatrix();
115
+ } else if (camera instanceof THREE.OrthographicCamera) {
116
+ camera.left = -width / 2;
117
+ camera.right = width / 2;
118
+ camera.top = height / 2;
119
+ camera.bottom = -height / 2;
120
+ camera.updateProjectionMatrix();
121
+ }
122
+ context.size = { ...sizeRef.current };
74
123
  onResizeRef.current?.(context, { width, height });
75
124
  });
76
125
  resizeObserver.observe(container);
@@ -130,15 +179,14 @@ function ShaderArt({
130
179
  assetsRef.current = null;
131
180
  };
132
181
  }, []);
133
- const handleRender = useCallback((context) => {
134
- shaderUniformsRef.current.iTime.value = context.clock.getElapsedTime();
135
- const canvas = context.renderer.domElement;
136
- shaderUniformsRef.current.iResolution.value.set(
137
- canvas.width,
138
- canvas.height,
139
- 1
140
- );
141
- }, []);
182
+ const handleRender = useCallback(
183
+ (context, _delta, elapsedTime) => {
184
+ shaderUniformsRef.current.iTime.value = elapsedTime;
185
+ const { width: width2, height: height2 } = context.size;
186
+ shaderUniformsRef.current.iResolution.value.set(width2, height2, 1);
187
+ },
188
+ []
189
+ );
142
190
  const { containerRef } = useScene({
143
191
  onCreate: handleCreate,
144
192
  onRender: handleRender
@@ -285,7 +333,12 @@ function FractalFlower({
285
333
  const points = new THREE3.Points(geometry, material);
286
334
  points.frustumCulled = false;
287
335
  scene.add(points);
288
- assetsRef.current = { points, geometry, material, uniforms: uniformValues };
336
+ assetsRef.current = {
337
+ points,
338
+ geometry,
339
+ material,
340
+ uniforms: uniformValues
341
+ };
289
342
  return () => {
290
343
  scene.remove(points);
291
344
  geometry.dispose();
@@ -293,25 +346,29 @@ function FractalFlower({
293
346
  assetsRef.current = null;
294
347
  };
295
348
  },
296
- [attributes.eValues, attributes.kValues, attributes.positions, attributes.rotations]
349
+ [
350
+ attributes.eValues,
351
+ attributes.kValues,
352
+ attributes.positions,
353
+ attributes.rotations
354
+ ]
297
355
  );
298
356
  const handleRender = useCallback2(
299
- (context) => {
357
+ (context, delta, elapsedTime) => {
300
358
  const assets = assetsRef.current;
301
359
  if (!assets) return;
302
360
  const uniforms = assets.uniforms;
303
- uniforms.uTime.value = context.clock.getElapsedTime();
304
- const canvas = context.renderer.domElement;
305
- uniforms.uResolution.value.set(canvas.width, canvas.height);
361
+ uniforms.uTime.value = elapsedTime;
362
+ const { width: width2, height: height2 } = context.size;
363
+ uniforms.uResolution.value.set(width2, height2);
306
364
  const basePointSize = Math.max(
307
365
  2,
308
- canvas.height / 400 * (uniforms.uPetalRadius.value * 32)
366
+ height2 / 400 * (uniforms.uPetalRadius.value * 32)
309
367
  );
310
368
  const pointSize = Math.min(basePointSize, 6);
311
369
  uniforms.uPointSize.value = pointSize;
312
370
  const morph = morphRef.current;
313
371
  if (Math.abs(morph.target - morph.progress) > 1e-3) {
314
- const delta = context.clock.getDelta();
315
372
  const direction = Math.sign(morph.target - morph.progress);
316
373
  morph.progress = clamp(
317
374
  morph.progress + direction * delta * MORPH_SPEED,
@@ -462,11 +519,14 @@ function OranoParticles({
462
519
  particlesRef.current = null;
463
520
  };
464
521
  }, []);
465
- const handleRender = useCallback3((context) => {
466
- const assets = particlesRef.current;
467
- if (!assets) return;
468
- assets.uniforms.uTime.value = context.clock.getElapsedTime();
469
- }, []);
522
+ const handleRender = useCallback3(
523
+ (_context, _delta, elapsedTime) => {
524
+ const assets = particlesRef.current;
525
+ if (!assets) return;
526
+ assets.uniforms.uTime.value = elapsedTime;
527
+ },
528
+ []
529
+ );
470
530
  const { containerRef } = useScene({
471
531
  onCreate: handleCreate,
472
532
  onRender: handleRender
@@ -525,8 +585,1517 @@ function OranoParticles({
525
585
  }
526
586
  );
527
587
  }
588
+
589
+ // src/components/MenuGlitch.tsx
590
+ import { gsap } from "gsap";
591
+ import {
592
+ useCallback as useCallback4,
593
+ useEffect as useEffect5,
594
+ useMemo as useMemo2,
595
+ useRef as useRef5
596
+ } from "react";
597
+ import * as THREE5 from "three";
598
+
599
+ // src/shaders/menu-glitch/fragment.glsl
600
+ var fragment_default4 = "#define M_PI 3.1415926535897932384626433832795\n\nuniform sampler2D tDiffuse;\nuniform sampler2D tGradient;\n\nuniform float uTime;\n\nuniform float uTileProgressVertical;\nuniform float uTileProgressHorizontal;\nuniform float uTileAmplitude;\nuniform vec2 uTileFrequency;\nuniform vec2 uTileOffset;\n\nuniform float uWaveProgress;\nuniform float uWaveAmplitude;\nuniform vec2 uWaveStrength;\n\nuniform float uGradientProgress;\nuniform float uGradientOffset;\nuniform float uGradientAmplitude;\n\nuniform float uBlueProgress;\nuniform float uBlueAmplitude;\n\nuniform float uWhiteTileChances;\nuniform float uWhiteTileFrequency;\nuniform float uWhiteTileStrength;\n\nuniform float uSaturation;\n\nvarying vec2 vUv;\n\n\n// --------------------------------------------------\n// Utilities\n// --------------------------------------------------\n\nfloat random(in vec2 st) {\n return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);\n}\n\nvec2 getTileCoord(vec2 pos, vec2 frequency) {\n vec2 coord = vec2(\n floor(pos.x * frequency.x),\n floor(pos.y * frequency.y)\n );\n\n coord /= frequency;\n return coord;\n}\n\nfloat toSin(float value) {\n return (sin((value - 0.5) * M_PI) + 1.0) * 0.5;\n}\n\n\n// --------------------------------------------------\n// Main\n// --------------------------------------------------\n\nvoid main() {\n\n // ------------------------\n // Tiles\n // ------------------------\n vec2 tileCoord1 = getTileCoord(vUv, uTileFrequency * 1.8942);\n vec2 tileCoord2 = getTileCoord(vUv, uTileFrequency * 1.0);\n vec2 tileCoord3 = getTileCoord(vUv, uTileFrequency * 2.1245);\n\n vec2 tileCoord = tileCoord2 +\n step(random(tileCoord1), 0.5) * (tileCoord3 - tileCoord2);\n\n float tileRandom = random(tileCoord);\n float tileAngle = tileRandom * M_PI * 2.0;\n\n vec2 tileOffset = vec2(sin(tileAngle), cos(tileAngle)) * uTileOffset;\n\n float tileProgress = 1.0 -\n (distance(tileCoord.y, uTileProgressVertical) / (uTileAmplitude * 0.5));\n\n tileProgress = clamp(tileProgress, 0.0, 1.0);\n\n float tileProgressHorizontal = 1.0 -\n (distance(tileCoord.x, uTileProgressHorizontal) / (uTileAmplitude * 0.5));\n tileProgressHorizontal = clamp(tileProgressHorizontal, 0.0, 1.0);\n\n // ------------------------\n // Wave\n // ------------------------\n float waveProgress = 1.0 -\n (distance(vUv.x, uWaveProgress) / (uWaveAmplitude * 0.5));\n\n waveProgress = clamp(waveProgress, 0.0, 1.0);\n\n vec2 waveOffset = toSin(waveProgress) * uWaveStrength;\n\n\n // ------------------------\n // Gradient\n // ------------------------\n float gradientProgress = (tileCoord.x - uGradientProgress) / uGradientAmplitude;\n gradientProgress += (tileRandom - 0.5) * uGradientOffset;\n\n vec4 gradientColor = texture2D(\n tGradient,\n vec2(clamp(gradientProgress, 0.0, 1.0), 0.0)\n );\n\n\n // ------------------------\n // Blue tint\n // ------------------------\n float blueProgress = (tileCoord.x - uBlueProgress) / uBlueAmplitude;\n blueProgress += tileOffset.x;\n blueProgress = clamp(blueProgress, 0.0, 1.0);\n\n\n // ------------------------\n // White flickering tiles\n // ------------------------\n float whiteTileProgress =\n sin(uTime * uWhiteTileFrequency + tileRandom * M_PI * 2.0) * 0.5 + 0.5;\n\n whiteTileProgress =\n clamp(whiteTileProgress - (1.0 - uWhiteTileChances), 0.0, 1.0) *\n (1.0 / uWhiteTileChances) *\n uWhiteTileStrength;\n\n\n // ------------------------\n // Final color pipeline\n // ------------------------\n vec2 uv = vUv;\n\n // Apply tile offset\n uv += tileOffset * tileProgress;\n\n // Apply waves (optional)\n // uv += waveOffset;\n\n // Repeat UV\n uv = mod(uv, vec2(1.0));\n\n // Base color removed so overlay only contains glitch elements\n vec4 color = vec4(0.0);\n\n // Add gradient\n color.rgb += gradientColor.rgb * gradientColor.a * tileProgressHorizontal;\n\n // Add white flicker effect\n color.rgb += vec3(whiteTileProgress) * tileProgressHorizontal;\n\n // Blue tone shift\n vec3 blueColor = vec3((color.r + color.g + color.b) / 3.0) *\n vec3(0.3, 0.5, 1.0);\n\n color.rgb = mix(color.rgb, blueColor, vec3(blueProgress));\n\n // Saturation\n color.rgb *= uSaturation;\n\n float effectAlpha =\n clamp(tileProgressHorizontal * (tileProgress * 0.6 + whiteTileProgress + gradientColor.a), 0.0, 1.0);\n\n // Output\n gl_FragColor = vec4(color.rgb, effectAlpha);\n}\n";
601
+
602
+ // src/shaders/menu-glitch/vertex.glsl
603
+ var vertex_default4 = "varying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n";
604
+
605
+ // src/components/MenuGlitch.tsx
606
+ import { jsx as jsx4 } from "react/jsx-runtime";
607
+ var GRADIENT_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAABCAYAAAAxWXB3AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RTVBRkY1OTJDREMyMTFFODhGRjFFRjgxRjM2QjM2MDMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RTVBRkY1OTNDREMyMTFFODhGRjFFRjgxRjM2QjM2MDMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFNUFGRjU5MENEQzIxMUU4OEZGMUVGODFGMzZCMzYwMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFNUFGRjU5MUNEQzIxMUU4OEZGMUVGODFGMzZCMzYwMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pksi3ywAAADMSURBVHjarFDLDoMwDDMtUDjtME3TtMcn7f9/hngBCoTSTjsMydixnVZqxTcAX51Quwsad1M80PonWvdCUA7+rrii82eEulcG+hoTdwsrQtRB0UZu3KxrN8OPqIBKgRHxI8qaUTD1RuY2p90V+hOjmWix2uSSeGLPKrDdKc5GHzqZLPWY7XEG+F1PLBktpiuzf8ilkMvqcZfLLqf1ltnu7rLh4G88ZPxh4uUOmjMYO3aPqz8Yve0tGiaDduNr/xn8cWbBY8HLzQd8BBgAgOx+ERYDbIEAAAAASUVORK5CYII=";
608
+ var BASE_TRANSITION_PROGRESS = 0.5;
609
+ var SHOW_BASE_DURATION = 2.72;
610
+ var HIDE_BASE_DURATION = 1.8;
611
+ var SHOW_PRIMARY_DURATION = 1.1;
612
+ var MIN_GLITCH_DURATION = 0.25;
613
+ function MenuGlitch({
614
+ settings,
615
+ onShowDone,
616
+ onHideDone,
617
+ className,
618
+ style,
619
+ width,
620
+ height,
621
+ ...divProps
622
+ }) {
623
+ const diffuseTexture = useMemo2(() => createDiffuseTexture(), []);
624
+ const gradientTextureRef = useRef5(new THREE5.Texture());
625
+ const sanitizedValues = useMemo2(() => {
626
+ const clamp2 = THREE5.MathUtils.clamp;
627
+ return {
628
+ tileAmplitude: Math.max(0.1, settings.tileAmplitude),
629
+ planeScale: Math.max(0.1, settings.planeScale ?? 1),
630
+ tileOffsetX: settings.tileOffset.x,
631
+ tileOffsetY: settings.tileOffset.y,
632
+ tileFrequencyX: Math.max(0.1, settings.tileFrequency.x),
633
+ tileFrequencyY: Math.max(0.1, settings.tileFrequency.y),
634
+ gradientAmplitude: Math.max(0.05, settings.gradientAmplitude),
635
+ gradientOffset: settings.gradientOffset,
636
+ gradientProgress: settings.gradientProgress ?? 1,
637
+ blueAmplitude: Math.max(0.05, settings.blueAmplitude),
638
+ blueProgress: settings.blueProgress ?? 1,
639
+ waveAmplitude: Math.max(0.05, settings.waveAmplitude),
640
+ waveProgress: settings.waveProgress ?? 0.5,
641
+ waveStrengthX: settings.waveStrength.x,
642
+ waveStrengthY: settings.waveStrength.y,
643
+ whiteTileChances: clamp2(settings.whiteTileChances, 0.05, 1),
644
+ whiteTileFrequency: Math.max(0.01, settings.whiteTileFrequency),
645
+ whiteTileStrength: Math.max(0, settings.whiteTileStrength),
646
+ saturation: Math.max(0, settings.saturation),
647
+ duration: Math.max(
648
+ MIN_GLITCH_DURATION,
649
+ settings.duration ?? SHOW_PRIMARY_DURATION
650
+ ),
651
+ debug: settings.debug
652
+ };
653
+ }, [settings]);
654
+ const sanitizedRef = useRef5(sanitizedValues);
655
+ sanitizedRef.current = sanitizedValues;
656
+ const sizeRef = useRef5({ width: 0, height: 0 });
657
+ const assetsRef = useRef5(null);
658
+ const animationVisibleRef = useRef5(false);
659
+ const shaderUniforms = useRef5({
660
+ tDiffuse: { value: diffuseTexture },
661
+ tGradient: { value: gradientTextureRef.current },
662
+ uTime: { value: 0 },
663
+ uTileProgressVertical: { value: -0.5 },
664
+ uTileProgressHorizontal: { value: -0.5 },
665
+ uTileAmplitude: { value: settings.tileAmplitude },
666
+ uTileOffset: {
667
+ value: new THREE5.Vector2(settings.tileOffset.x, settings.tileOffset.y)
668
+ },
669
+ uTileFrequency: {
670
+ value: new THREE5.Vector2(
671
+ settings.tileFrequency.x,
672
+ settings.tileFrequency.y
673
+ )
674
+ },
675
+ uWaveProgress: { value: settings.waveProgress },
676
+ uWaveAmplitude: { value: settings.waveAmplitude },
677
+ uWaveStrength: {
678
+ value: new THREE5.Vector2(
679
+ settings.waveStrength.x,
680
+ settings.waveStrength.y
681
+ )
682
+ },
683
+ uGradientProgress: { value: settings.gradientProgress },
684
+ uGradientOffset: { value: settings.gradientOffset },
685
+ uGradientAmplitude: { value: settings.gradientAmplitude },
686
+ uBlueProgress: { value: settings.blueProgress },
687
+ uBlueAmplitude: { value: settings.blueAmplitude },
688
+ uWhiteTileChances: { value: settings.whiteTileChances },
689
+ uWhiteTileFrequency: { value: settings.whiteTileFrequency },
690
+ uWhiteTileStrength: { value: settings.whiteTileStrength },
691
+ uSaturation: { value: settings.saturation }
692
+ });
693
+ useEffect5(() => {
694
+ let disposed = false;
695
+ const loader = new THREE5.TextureLoader();
696
+ loader.load(
697
+ GRADIENT_DATA_URL,
698
+ (texture) => {
699
+ if (disposed) return;
700
+ texture.wrapS = THREE5.ClampToEdgeWrapping;
701
+ texture.wrapT = THREE5.ClampToEdgeWrapping;
702
+ texture.magFilter = THREE5.LinearFilter;
703
+ texture.minFilter = THREE5.LinearFilter;
704
+ texture.colorSpace = THREE5.SRGBColorSpace;
705
+ gradientTextureRef.current = texture;
706
+ shaderUniforms.current.tGradient.value = texture;
707
+ },
708
+ void 0,
709
+ () => {
710
+ if (disposed) return;
711
+ gradientTextureRef.current = new THREE5.Texture();
712
+ shaderUniforms.current.tGradient.value = gradientTextureRef.current;
713
+ }
714
+ );
715
+ return () => {
716
+ disposed = true;
717
+ };
718
+ }, []);
719
+ const updateMeshScale = useCallback4(() => {
720
+ const assets = assetsRef.current;
721
+ if (!assets) return;
722
+ const base = Math.max(
723
+ sizeRef.current.width || 1,
724
+ sizeRef.current.height || 1
725
+ );
726
+ const scale = base * sanitizedRef.current.planeScale;
727
+ assets.mesh.scale.set(scale, scale, 1);
728
+ }, []);
729
+ const updateVisibility = useCallback4(() => {
730
+ const mesh = assetsRef.current?.mesh;
731
+ if (!mesh) return;
732
+ mesh.visible = sanitizedRef.current.debug || animationVisibleRef.current;
733
+ }, []);
734
+ const handleCreate = useCallback4(
735
+ ({ scene, size }) => {
736
+ const geometry = new THREE5.PlaneGeometry(1, 1);
737
+ const material = new THREE5.ShaderMaterial({
738
+ vertexShader: vertex_default4,
739
+ fragmentShader: fragment_default4,
740
+ uniforms: shaderUniforms.current,
741
+ depthWrite: false,
742
+ depthTest: false,
743
+ transparent: true,
744
+ blending: THREE5.AdditiveBlending
745
+ });
746
+ const mesh = new THREE5.Mesh(geometry, material);
747
+ scene.add(mesh);
748
+ assetsRef.current = { mesh, geometry, material };
749
+ sizeRef.current = size;
750
+ updateMeshScale();
751
+ updateVisibility();
752
+ return () => {
753
+ scene.remove(mesh);
754
+ geometry.dispose();
755
+ material.dispose();
756
+ assetsRef.current = null;
757
+ };
758
+ },
759
+ [updateMeshScale, updateVisibility]
760
+ );
761
+ const handleRender = useCallback4(
762
+ (_context, _delta, elapsedTime) => {
763
+ shaderUniforms.current.uTime.value = elapsedTime;
764
+ },
765
+ []
766
+ );
767
+ const handleResize = useCallback4(
768
+ (_context, size) => {
769
+ sizeRef.current = size;
770
+ updateMeshScale();
771
+ },
772
+ [updateMeshScale]
773
+ );
774
+ const { containerRef } = useScene({
775
+ camera: { type: "orthographic", position: [0, 0, 10], near: 0.1, far: 100 },
776
+ onCreate: handleCreate,
777
+ onRender: handleRender,
778
+ onResize: handleResize
779
+ });
780
+ const showTimeline = useRef5(null);
781
+ const hideTimeline = useRef5(null);
782
+ const transitionTimeline = useRef5(null);
783
+ const timelineParams = useMemo2(
784
+ () => ({
785
+ tileAmplitude: sanitizedValues.tileAmplitude,
786
+ gradientAmplitude: sanitizedValues.gradientAmplitude,
787
+ gradientOffset: sanitizedValues.gradientOffset,
788
+ tileOffsetX: sanitizedValues.tileOffsetX,
789
+ duration: sanitizedValues.duration
790
+ }),
791
+ [
792
+ sanitizedValues.duration,
793
+ sanitizedValues.gradientAmplitude,
794
+ sanitizedValues.gradientOffset,
795
+ sanitizedValues.tileAmplitude,
796
+ sanitizedValues.tileOffsetX
797
+ ]
798
+ );
799
+ const primeShowAnimation = useCallback4(() => {
800
+ const uniforms = shaderUniforms.current;
801
+ uniforms.uTileProgressHorizontal.value = 1 + timelineParams.tileAmplitude * 0.5;
802
+ uniforms.uGradientProgress.value = 1 + timelineParams.gradientOffset;
803
+ uniforms.uBlueProgress.value = 1 + timelineParams.tileOffsetX;
804
+ uniforms.uSaturation.value = 1;
805
+ uniforms.uWhiteTileStrength.value = 0;
806
+ }, [
807
+ timelineParams.gradientOffset,
808
+ timelineParams.tileAmplitude,
809
+ timelineParams.tileOffsetX
810
+ ]);
811
+ const buildTimelines = useCallback4(() => {
812
+ const uniforms = shaderUniforms.current;
813
+ const glitchDuration = sanitizedRef.current.duration;
814
+ const desiredShowTotal = glitchDuration * (SHOW_BASE_DURATION / SHOW_PRIMARY_DURATION);
815
+ const desiredHideTotal = glitchDuration * (HIDE_BASE_DURATION / SHOW_PRIMARY_DURATION);
816
+ const showScale = desiredShowTotal / SHOW_BASE_DURATION;
817
+ const hideScale = desiredHideTotal / HIDE_BASE_DURATION;
818
+ const showAt = (time) => time / SHOW_BASE_DURATION * desiredShowTotal;
819
+ const hideAt = (time) => time / HIDE_BASE_DURATION * desiredHideTotal;
820
+ showTimeline.current?.kill();
821
+ hideTimeline.current?.kill();
822
+ transitionTimeline.current?.kill();
823
+ const showTl = gsap.timeline({ paused: true });
824
+ showTl.add(() => {
825
+ animationVisibleRef.current = true;
826
+ updateVisibility();
827
+ }, 0);
828
+ showTl.fromTo(
829
+ uniforms.uTileProgressHorizontal,
830
+ { value: 1 + timelineParams.tileAmplitude * 0.5 },
831
+ {
832
+ value: -timelineParams.tileAmplitude * 0.5,
833
+ duration: glitchDuration,
834
+ ease: "sine.inOut"
835
+ },
836
+ 0
837
+ );
838
+ showTl.fromTo(
839
+ uniforms.uGradientProgress,
840
+ { value: 1 + timelineParams.gradientOffset },
841
+ {
842
+ value: -timelineParams.gradientOffset * 0.5 - timelineParams.gradientAmplitude,
843
+ duration: 0.6 * showScale,
844
+ ease: "sine.inOut"
845
+ },
846
+ showAt(0.1)
847
+ );
848
+ showTl.fromTo(
849
+ uniforms.uBlueProgress,
850
+ { value: 1 + timelineParams.tileOffsetX },
851
+ {
852
+ value: -timelineParams.tileOffsetX * 0.5 - timelineParams.gradientAmplitude,
853
+ duration: 0.6 * showScale,
854
+ ease: "sine.inOut"
855
+ },
856
+ showAt(0.125)
857
+ );
858
+ showTl.fromTo(
859
+ uniforms.uSaturation,
860
+ { value: 1 },
861
+ { value: 1.01, duration: 0.52 * showScale, ease: "sine.inOut" },
862
+ 0
863
+ );
864
+ showTl.fromTo(
865
+ uniforms.uSaturation,
866
+ { value: 1 },
867
+ { value: 2.5, duration: 0.1 * showScale, ease: "sine.inOut" },
868
+ showAt(0.32)
869
+ );
870
+ showTl.fromTo(
871
+ uniforms.uSaturation,
872
+ { value: 2.5 },
873
+ { value: 1, duration: 1.8 * showScale, ease: "sine.out" },
874
+ showAt(0.92)
875
+ );
876
+ showTl.fromTo(
877
+ uniforms.uWhiteTileStrength,
878
+ { value: 0 },
879
+ { value: 0.1, duration: 1 * showScale, ease: "sine.inOut" },
880
+ showAt(0.3)
881
+ );
882
+ const hideTl = gsap.timeline({ paused: true });
883
+ hideTl.fromTo(
884
+ uniforms.uWhiteTileStrength,
885
+ { value: 0.1 },
886
+ { value: 0, duration: 1 * hideScale, ease: "sine.inOut" },
887
+ 0
888
+ );
889
+ hideTl.fromTo(
890
+ uniforms.uSaturation,
891
+ { value: 1 },
892
+ { value: 1.5, duration: 0.4 * hideScale, ease: "sine.inOut" },
893
+ hideAt(0.4)
894
+ );
895
+ hideTl.fromTo(
896
+ uniforms.uSaturation,
897
+ { value: 1.5 },
898
+ { value: 1, duration: 1 * hideScale, ease: "sine.inOut" },
899
+ hideAt(0.8)
900
+ );
901
+ hideTl.fromTo(
902
+ uniforms.uBlueProgress,
903
+ {
904
+ value: -timelineParams.tileOffsetX * 0.5 - timelineParams.gradientAmplitude
905
+ },
906
+ {
907
+ value: 1 + timelineParams.tileOffsetX,
908
+ duration: 0.6 * hideScale,
909
+ ease: "sine.inOut"
910
+ },
911
+ hideAt(0.2)
912
+ );
913
+ hideTl.fromTo(
914
+ uniforms.uTileProgressHorizontal,
915
+ { value: -timelineParams.tileAmplitude * 0.5 },
916
+ {
917
+ value: 1 + timelineParams.tileAmplitude * 0.5,
918
+ duration: 0.9 * hideScale,
919
+ ease: "sine.inOut"
920
+ },
921
+ hideAt(0.2)
922
+ );
923
+ hideTl.fromTo(
924
+ uniforms.uGradientProgress,
925
+ {
926
+ value: -timelineParams.gradientOffset * 0.5 - timelineParams.gradientAmplitude
927
+ },
928
+ {
929
+ value: 1 + timelineParams.gradientOffset,
930
+ duration: 0.6 * hideScale,
931
+ ease: "sine.inOut"
932
+ },
933
+ hideAt(0.225)
934
+ );
935
+ hideTl.add(() => {
936
+ animationVisibleRef.current = false;
937
+ updateVisibility();
938
+ onHideDone?.();
939
+ }, hideAt(HIDE_BASE_DURATION));
940
+ showTl.call(
941
+ () => {
942
+ onShowDone?.();
943
+ },
944
+ void 0,
945
+ showAt(0.65)
946
+ );
947
+ showTimeline.current = showTl;
948
+ hideTimeline.current = hideTl;
949
+ }, [onHideDone, onShowDone, timelineParams, updateVisibility]);
950
+ useEffect5(() => {
951
+ const uniforms = shaderUniforms.current;
952
+ uniforms.tDiffuse.value = diffuseTexture;
953
+ if (gradientTextureRef.current) {
954
+ uniforms.tGradient.value = gradientTextureRef.current;
955
+ }
956
+ uniforms.uTileAmplitude.value = sanitizedValues.tileAmplitude;
957
+ uniforms.uTileOffset.value.set(
958
+ sanitizedValues.tileOffsetX,
959
+ sanitizedValues.tileOffsetY
960
+ );
961
+ uniforms.uTileFrequency.value.set(
962
+ sanitizedValues.tileFrequencyX,
963
+ sanitizedValues.tileFrequencyY
964
+ );
965
+ uniforms.uWaveAmplitude.value = sanitizedValues.waveAmplitude;
966
+ uniforms.uWaveProgress.value = sanitizedValues.waveProgress;
967
+ uniforms.uWaveStrength.value.set(
968
+ sanitizedValues.waveStrengthX,
969
+ sanitizedValues.waveStrengthY
970
+ );
971
+ uniforms.uGradientProgress.value = sanitizedValues.gradientProgress;
972
+ uniforms.uGradientOffset.value = sanitizedValues.gradientOffset;
973
+ uniforms.uGradientAmplitude.value = sanitizedValues.gradientAmplitude;
974
+ uniforms.uBlueProgress.value = sanitizedValues.blueProgress;
975
+ uniforms.uBlueAmplitude.value = sanitizedValues.blueAmplitude;
976
+ uniforms.uWhiteTileChances.value = sanitizedValues.whiteTileChances;
977
+ uniforms.uWhiteTileFrequency.value = sanitizedValues.whiteTileFrequency;
978
+ uniforms.uWhiteTileStrength.value = sanitizedValues.whiteTileStrength;
979
+ uniforms.uSaturation.value = sanitizedValues.saturation;
980
+ uniforms.uTileProgressVertical.value = BASE_TRANSITION_PROGRESS;
981
+ updateMeshScale();
982
+ updateVisibility();
983
+ }, [diffuseTexture, sanitizedValues, updateMeshScale, updateVisibility]);
984
+ useEffect5(() => {
985
+ buildTimelines();
986
+ return () => {
987
+ showTimeline.current?.kill();
988
+ hideTimeline.current?.kill();
989
+ transitionTimeline.current?.kill();
990
+ };
991
+ }, [buildTimelines]);
992
+ useEffect5(() => {
993
+ const mesh = assetsRef.current?.mesh;
994
+ if (mesh) {
995
+ mesh.visible = sanitizedValues.debug || animationVisibleRef.current;
996
+ }
997
+ }, [sanitizedValues.debug]);
998
+ const lastShowSignal = useRef5(settings.showSignal);
999
+ useEffect5(() => {
1000
+ if (settings.showSignal === lastShowSignal.current) {
1001
+ return;
1002
+ }
1003
+ lastShowSignal.current = settings.showSignal;
1004
+ hideTimeline.current?.pause(0);
1005
+ primeShowAnimation();
1006
+ showTimeline.current?.seek(0).play();
1007
+ }, [primeShowAnimation, settings.showSignal]);
1008
+ const lastHideSignal = useRef5(settings.hideSignal);
1009
+ useEffect5(() => {
1010
+ if (settings.hideSignal === lastHideSignal.current) {
1011
+ return;
1012
+ }
1013
+ lastHideSignal.current = settings.hideSignal;
1014
+ showTimeline.current?.pause(0);
1015
+ hideTimeline.current?.seek(0).play();
1016
+ }, [settings.hideSignal]);
1017
+ return /* @__PURE__ */ jsx4(
1018
+ "div",
1019
+ {
1020
+ ref: containerRef,
1021
+ className,
1022
+ style: {
1023
+ width: width ?? "100%",
1024
+ height: height ?? "100%",
1025
+ ...style
1026
+ },
1027
+ ...divProps
1028
+ }
1029
+ );
1030
+ }
1031
+ function createDiffuseTexture() {
1032
+ const size = 256;
1033
+ const data = new Uint8Array(size * size * 4);
1034
+ for (let y = 0; y < size; y += 1) {
1035
+ for (let x = 0; x < size; x += 1) {
1036
+ const i = (y * size + x) * 4;
1037
+ const stripe = Math.sin(y / size * Math.PI * 8) * 0.5 + 0.5;
1038
+ const tint = 0.25 + Math.sin(x / size * Math.PI * 2) * 0.15;
1039
+ const base = THREE5.MathUtils.clamp(stripe * 0.7 + tint, 0, 1);
1040
+ data[i] = base * 255;
1041
+ data[i + 1] = base * 200;
1042
+ data[i + 2] = base * 150;
1043
+ data[i + 3] = 255;
1044
+ }
1045
+ }
1046
+ const texture = new THREE5.DataTexture(data, size, size, THREE5.RGBAFormat);
1047
+ texture.needsUpdate = true;
1048
+ texture.colorSpace = THREE5.SRGBColorSpace;
1049
+ texture.wrapS = THREE5.RepeatWrapping;
1050
+ texture.wrapT = THREE5.RepeatWrapping;
1051
+ texture.magFilter = THREE5.LinearFilter;
1052
+ texture.minFilter = THREE5.LinearMipmapLinearFilter;
1053
+ texture.generateMipmaps = true;
1054
+ return texture;
1055
+ }
1056
+
1057
+ // src/components/EfectoAsciiEffect.tsx
1058
+ import {
1059
+ useCallback as useCallback5,
1060
+ useEffect as useEffect6,
1061
+ useMemo as useMemo3,
1062
+ useRef as useRef6
1063
+ } from "react";
1064
+ import {
1065
+ BlendFunction,
1066
+ Effect,
1067
+ EffectComposer,
1068
+ EffectPass,
1069
+ RenderPass
1070
+ } from "postprocessing";
1071
+ import * as THREE6 from "three";
1072
+ import { Uniform, Vector2 as Vector23 } from "three";
1073
+
1074
+ // src/utils/efectoMediaAdjustments.ts
1075
+ var EFECTO_MEDIA_ADJUSTMENT_DEFAULTS = {
1076
+ brightness: 1,
1077
+ contrast: 1,
1078
+ saturation: 1
1079
+ };
1080
+ var resolveEfectoMediaAdjustments = (adjustments) => ({
1081
+ brightness: typeof adjustments?.brightness === "number" ? adjustments.brightness : EFECTO_MEDIA_ADJUSTMENT_DEFAULTS.brightness,
1082
+ contrast: typeof adjustments?.contrast === "number" ? adjustments.contrast : EFECTO_MEDIA_ADJUSTMENT_DEFAULTS.contrast,
1083
+ saturation: typeof adjustments?.saturation === "number" ? adjustments.saturation : EFECTO_MEDIA_ADJUSTMENT_DEFAULTS.saturation
1084
+ });
1085
+
1086
+ // src/shaders/efecto-ascii-effect/fragment.ts
1087
+ var fragment_default5 = `
1088
+ uniform float cellSize;
1089
+ uniform bool invert;
1090
+ uniform bool colorMode;
1091
+ uniform int asciiStyle;
1092
+
1093
+ // PostFX uniforms
1094
+ uniform float time;
1095
+ uniform vec2 resolution;
1096
+ uniform vec2 mousePos;
1097
+
1098
+ // Tier 1 uniforms
1099
+ uniform float scanlineIntensity;
1100
+ uniform float scanlineCount;
1101
+ uniform float targetFPS;
1102
+ uniform float jitterIntensity;
1103
+ uniform float jitterSpeed;
1104
+ uniform bool mouseGlowEnabled;
1105
+ uniform float mouseGlowRadius;
1106
+ uniform float mouseGlowIntensity;
1107
+ uniform float vignetteIntensity;
1108
+ uniform float vignetteRadius;
1109
+ uniform int colorPalette;
1110
+
1111
+ // Tier 2 uniforms
1112
+ uniform float curvature;
1113
+ uniform float aberrationStrength;
1114
+ uniform float noiseIntensity;
1115
+ uniform float noiseScale;
1116
+ uniform float noiseSpeed;
1117
+ uniform float waveAmplitude;
1118
+ uniform float waveFrequency;
1119
+ uniform float waveSpeed;
1120
+ uniform float glitchIntensity;
1121
+ uniform float glitchFrequency;
1122
+ uniform float brightnessAdjust;
1123
+ uniform float contrastAdjust;
1124
+
1125
+ // =======================
1126
+ // HELPER FUNCTIONS
1127
+ // =======================
1128
+
1129
+ // Pseudo-random function
1130
+ float random(vec2 st) {
1131
+ return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453);
1132
+ }
1133
+
1134
+ // 2D Noise function
1135
+ float noise(vec2 st) {
1136
+ vec2 i = floor(st);
1137
+ vec2 f = fract(st);
1138
+ float a = random(i);
1139
+ float b = random(i + vec2(1.0, 0.0));
1140
+ float c = random(i + vec2(0.0, 1.0));
1141
+ float d = random(i + vec2(1.0, 1.0));
1142
+ vec2 u = f * f * (3.0 - 2.0 * f);
1143
+ return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
1144
+ }
1145
+
1146
+ // RGB to HSL conversion
1147
+ vec3 rgb2hsl(vec3 rgb) {
1148
+ float maxVal = max(max(rgb.r, rgb.g), rgb.b);
1149
+ float minVal = min(min(rgb.r, rgb.g), rgb.b);
1150
+ float delta = maxVal - minVal;
1151
+
1152
+ float h = 0.0;
1153
+ float s = 0.0;
1154
+ float l = (maxVal + minVal) * 0.5;
1155
+
1156
+ if (delta > 0.0001) {
1157
+ s = delta / (1.0 - abs(2.0 * l - 1.0));
1158
+
1159
+ if (maxVal == rgb.r) {
1160
+ h = mod((rgb.g - rgb.b) / delta, 6.0);
1161
+ } else if (maxVal == rgb.g) {
1162
+ h = (rgb.b - rgb.r) / delta + 2.0;
1163
+ } else {
1164
+ h = (rgb.r - rgb.g) / delta + 4.0;
1165
+ }
1166
+ h = h / 6.0;
1167
+ }
1168
+
1169
+ return vec3(h, s, l);
1170
+ }
1171
+
1172
+ // HSL to RGB conversion
1173
+ vec3 hsl2rgb(vec3 hsl) {
1174
+ float h = hsl.x;
1175
+ float s = hsl.y;
1176
+ float l = hsl.z;
1177
+
1178
+ float c = (1.0 - abs(2.0 * l - 1.0)) * s;
1179
+ float x = c * (1.0 - abs(mod(h * 6.0, 2.0) - 1.0));
1180
+ float m = l - c * 0.5;
1181
+
1182
+ vec3 rgb = vec3(0.0);
1183
+
1184
+ if (h < 1.0/6.0) {
1185
+ rgb = vec3(c, x, 0.0);
1186
+ } else if (h < 2.0/6.0) {
1187
+ rgb = vec3(x, c, 0.0);
1188
+ } else if (h < 3.0/6.0) {
1189
+ rgb = vec3(0.0, c, x);
1190
+ } else if (h < 4.0/6.0) {
1191
+ rgb = vec3(0.0, x, c);
1192
+ } else if (h < 5.0/6.0) {
1193
+ rgb = vec3(x, 0.0, c);
1194
+ } else {
1195
+ rgb = vec3(c, 0.0, x);
1196
+ }
1197
+
1198
+ return rgb + m;
1199
+ }
1200
+
1201
+ // Apply color palette
1202
+ vec3 applyColorPalette(vec3 color, float brightness, int palette) {
1203
+ if (palette == 0) return color; // Original
1204
+
1205
+ vec3 paletteColor = color;
1206
+
1207
+ if (palette == 1) { // Green phosphor
1208
+ paletteColor = vec3(0.0, brightness, 0.0) * 1.5;
1209
+ } else if (palette == 2) { // Amber
1210
+ paletteColor = vec3(brightness * 1.2, brightness * 0.7, 0.0);
1211
+ } else if (palette == 3) { // Cyan
1212
+ paletteColor = vec3(0.0, brightness * 0.9, brightness);
1213
+ } else if (palette == 4) { // Blue
1214
+ paletteColor = vec3(0.0, 0.0, brightness);
1215
+ }
1216
+
1217
+ return paletteColor;
1218
+ }
1219
+
1220
+ // Different character patterns based on style
1221
+ float getChar(float brightness, vec2 p, int style) {
1222
+ vec2 grid = floor(p * 4.0);
1223
+ float val = 0.0;
1224
+
1225
+ if (style == 0) {
1226
+ // Standard ASCII style
1227
+ if (brightness < 0.2) {
1228
+ val = (grid.x == 1.0 && grid.y == 1.0) ? 0.3 : 0.0;
1229
+ } else if (brightness < 0.35) {
1230
+ val = (grid.x == 1.0 || grid.x == 2.0) && (grid.y == 1.0 || grid.y == 2.0) ? 1.0 : 0.0;
1231
+ } else if (brightness < 0.5) {
1232
+ val = (grid.y == 1.0 || grid.y == 2.0) ? 1.0 : 0.0;
1233
+ } else if (brightness < 0.65) {
1234
+ val = (grid.y == 0.0 || grid.y == 3.0) ? 1.0 : (grid.y == 1.0 || grid.y == 2.0) ? 0.5 : 0.0;
1235
+ } else if (brightness < 0.8) {
1236
+ val = (grid.x == 0.0 || grid.x == 2.0 || grid.y == 0.0 || grid.y == 2.0) ? 1.0 : 0.3;
1237
+ } else {
1238
+ val = 1.0;
1239
+ }
1240
+ } else if (style == 1) {
1241
+ // Dense style
1242
+ if (brightness < 0.15) {
1243
+ val = 0.0;
1244
+ } else if (brightness < 0.3) {
1245
+ val = (grid.x >= 1.0 && grid.x <= 2.0 && grid.y >= 1.0 && grid.y <= 2.0) ? 0.6 : 0.0;
1246
+ } else if (brightness < 0.5) {
1247
+ val = (grid.y == 1.0 || grid.y == 2.0) ? 1.0 : 0.3;
1248
+ } else if (brightness < 0.7) {
1249
+ val = (grid.x == 0.0 || grid.x == 3.0 || grid.y == 0.0 || grid.y == 3.0) ? 1.0 : 0.6;
1250
+ } else {
1251
+ val = 1.0;
1252
+ }
1253
+ } else if (style == 2) {
1254
+ // Minimal style
1255
+ if (brightness < 0.25) {
1256
+ val = 0.0;
1257
+ } else if (brightness < 0.4) {
1258
+ val = (grid.x == 2.0 && grid.y == 2.0) ? 1.0 : 0.0;
1259
+ } else if (brightness < 0.6) {
1260
+ val = (grid.x == 1.0 || grid.x == 2.0) && grid.y == 2.0 ? 1.0 : 0.0;
1261
+ } else if (brightness < 0.8) {
1262
+ val = (grid.y == 1.0 || grid.y == 2.0) ? 1.0 : 0.0;
1263
+ } else {
1264
+ val = (grid.x <= 2.0 && grid.y <= 2.0) ? 1.0 : 0.3;
1265
+ }
1266
+ } else if (style == 3) {
1267
+ // Blocks style
1268
+ if (brightness < 0.2) {
1269
+ val = 0.0;
1270
+ } else if (brightness < 0.4) {
1271
+ val = (grid.x >= 1.0 && grid.x <= 2.0 && grid.y >= 1.0 && grid.y <= 2.0) ? 0.8 : 0.0;
1272
+ } else if (brightness < 0.6) {
1273
+ val = (grid.y <= 2.0) ? 0.9 : 0.0;
1274
+ } else if (brightness < 0.8) {
1275
+ val = (grid.x <= 2.0 || grid.y <= 2.0) ? 1.0 : 0.2;
1276
+ } else {
1277
+ val = 1.0;
1278
+ }
1279
+ }
1280
+
1281
+ return val;
1282
+ }
1283
+
1284
+ void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
1285
+ vec2 workingUV = uv;
1286
+
1287
+ // ===========================
1288
+ // TIER 2: PRE-PROCESSING
1289
+ // ===========================
1290
+
1291
+ // Screen curvature
1292
+ if (curvature > 0.0) {
1293
+ vec2 centered = workingUV * 2.0 - 1.0;
1294
+ float dist = dot(centered, centered);
1295
+ centered *= 1.0 + curvature * dist;
1296
+ workingUV = centered * 0.5 + 0.5;
1297
+
1298
+ // Make out-of-bounds transparent so parent background shows through
1299
+ if (workingUV.x < 0.0 || workingUV.x > 1.0 || workingUV.y < 0.0 || workingUV.y > 1.0) {
1300
+ outputColor = vec4(0.0, 0.0, 0.0, 0.0);
1301
+ return;
1302
+ }
1303
+ }
1304
+
1305
+ // Wave distortion
1306
+ if (waveAmplitude > 0.0) {
1307
+ workingUV.x += sin(workingUV.y * waveFrequency + time * waveSpeed) * waveAmplitude;
1308
+ workingUV.y += cos(workingUV.x * waveFrequency + time * waveSpeed) * waveAmplitude * 0.5;
1309
+ }
1310
+
1311
+ // ===========================
1312
+ // CORE ASCII RENDERING
1313
+ // ===========================
1314
+
1315
+ vec2 res = resolution;
1316
+ vec2 cellCount = res / cellSize;
1317
+ vec2 cellCoord = floor(workingUV * cellCount);
1318
+
1319
+ // Frame rate control
1320
+ if (targetFPS > 0.0) {
1321
+ float frameTime = 1.0 / targetFPS;
1322
+ float frameIndex = floor(time / frameTime);
1323
+ cellCoord = floor(workingUV * cellCount) + vec2(random(vec2(frameIndex)) * 0.5);
1324
+ }
1325
+
1326
+ vec2 cellUV = (cellCoord + 0.5) / cellCount;
1327
+
1328
+ // Chromatic aberration
1329
+ vec4 cellColor;
1330
+ if (aberrationStrength > 0.0) {
1331
+ float r = texture(inputBuffer, cellUV + vec2(aberrationStrength, 0.0)).r;
1332
+ float g = texture(inputBuffer, cellUV).g;
1333
+ float b = texture(inputBuffer, cellUV - vec2(aberrationStrength, 0.0)).b;
1334
+ cellColor = vec4(r, g, b, 1.0);
1335
+ } else {
1336
+ cellColor = texture(inputBuffer, cellUV);
1337
+ }
1338
+
1339
+ // Calculate brightness
1340
+ float brightness = dot(cellColor.rgb, vec3(0.299, 0.587, 0.114));
1341
+
1342
+ // Contrast and brightness adjustment
1343
+ brightness = (brightness - 0.5) * contrastAdjust + 0.5 + brightnessAdjust;
1344
+ brightness = clamp(brightness, 0.0, 1.0);
1345
+
1346
+ // Time-based noise
1347
+ if (noiseIntensity > 0.0) {
1348
+ float noiseVal = noise(workingUV * noiseScale * 100.0 + time * noiseSpeed);
1349
+ brightness = mix(brightness, noiseVal, noiseIntensity);
1350
+ }
1351
+
1352
+ // Jitter/fuzzy effect
1353
+ if (jitterIntensity > 0.0) {
1354
+ float jitter = random(cellCoord + floor(time * jitterSpeed) * 0.1) - 0.5;
1355
+ brightness += jitter * jitterIntensity;
1356
+ brightness = clamp(brightness, 0.0, 1.0);
1357
+ }
1358
+
1359
+ // RGB Glitch
1360
+ if (glitchIntensity > 0.0 && glitchFrequency > 0.0) {
1361
+ float glitchTrigger = random(vec2(time * glitchFrequency));
1362
+ if (glitchTrigger > 0.9) {
1363
+ float glitchOffset = (random(cellCoord + time) - 0.5) * glitchIntensity;
1364
+ cellColor.r = texture(inputBuffer, cellUV + vec2(glitchOffset, 0.0)).r;
1365
+ cellColor.b = texture(inputBuffer, cellUV - vec2(glitchOffset, 0.0)).b;
1366
+ }
1367
+ }
1368
+
1369
+ if (invert) {
1370
+ brightness = 1.0 - brightness;
1371
+ }
1372
+
1373
+ // Get local UV within the cell
1374
+ vec2 localUV = fract(workingUV * cellCount);
1375
+ float charValue = getChar(brightness, localUV, asciiStyle);
1376
+
1377
+ // ===========================
1378
+ // TIER 1: POST-PROCESSING
1379
+ // ===========================
1380
+
1381
+ vec3 finalColor;
1382
+
1383
+ if (colorMode) {
1384
+ finalColor = cellColor.rgb * charValue;
1385
+ } else {
1386
+ finalColor = vec3(brightness * charValue);
1387
+ }
1388
+
1389
+ // Color palette
1390
+ finalColor = applyColorPalette(finalColor, brightness, colorPalette);
1391
+
1392
+ // Mouse glow
1393
+ if (mouseGlowEnabled && mouseGlowRadius > 0.0) {
1394
+ vec2 pixelPos = workingUV * res;
1395
+ float dist = distance(pixelPos, mousePos);
1396
+ float glow = 1.0 - smoothstep(0.0, mouseGlowRadius, dist);
1397
+ glow = pow(glow, 2.0);
1398
+ finalColor *= 1.0 + glow * mouseGlowIntensity;
1399
+ }
1400
+
1401
+ // Scanlines
1402
+ if (scanlineIntensity > 0.0) {
1403
+ float scanline = sin(workingUV.y * scanlineCount * 3.14159) * 0.5 + 0.5;
1404
+ finalColor *= 1.0 - scanlineIntensity * (1.0 - scanline);
1405
+ }
1406
+
1407
+ // Vignette
1408
+ if (vignetteIntensity > 0.0) {
1409
+ vec2 centered = workingUV - 0.5;
1410
+ float dist = length(centered) / 0.707; // Normalize to corner distance
1411
+ float vignette = smoothstep(vignetteRadius, vignetteRadius - 0.5, dist);
1412
+ finalColor *= mix(1.0, vignette, vignetteIntensity);
1413
+ }
1414
+
1415
+ outputColor = vec4(finalColor, cellColor.a);
1416
+ }
1417
+ `;
1418
+
1419
+ // src/shaders/efecto-media-image/vertex.glsl
1420
+ var vertex_default5 = "uniform vec2 uvScale;\nuniform vec2 uvOffset;\nvarying vec2 vUv;\n\nvoid main() {\n // Apply texture scale and offset for cover-fill effect\n vUv = uv * uvScale + uvOffset;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}";
1421
+
1422
+ // src/shaders/efecto-media-image/fragment.glsl
1423
+ var fragment_default6 = "uniform sampler2D map;\nuniform float brightness;\nuniform float contrast;\nuniform float saturation;\nvarying vec2 vUv;\n\nvec3 adjustContrast(vec3 color, float contrastAmount) {\n return (color - 0.5) * contrastAmount + 0.5;\n}\n\nvec3 adjustSaturation(vec3 color, float saturationAmount) {\n float gray = dot(color, vec3(0.299, 0.587, 0.114));\n return mix(vec3(gray), color, saturationAmount);\n}\n\nvoid main() {\n vec4 texel = texture2D(map, vUv);\n vec3 color = texel.rgb;\n\n // Apply brightness\n color *= brightness;\n\n // Apply contrast\n color = adjustContrast(color, contrast);\n\n // Apply saturation\n color = adjustSaturation(color, saturation);\n\n gl_FragColor = vec4(color, texel.a);\n}";
1424
+
1425
+ // src/components/EfectoAsciiEffect.tsx
1426
+ import { jsx as jsx5 } from "react/jsx-runtime";
1427
+ var DEFAULT_RESOLUTION = new Vector23(1920, 1080);
1428
+ var DEFAULT_MOUSE_POSITION = new Vector23(0, 0);
1429
+ var PARALLAX_LERP = 0.12;
1430
+ var STYLE_MAP = {
1431
+ standard: 0,
1432
+ dense: 1,
1433
+ minimal: 2,
1434
+ blocks: 3
1435
+ };
1436
+ var COLOR_PALETTE_MAP = {
1437
+ original: 0,
1438
+ green: 1,
1439
+ amber: 2,
1440
+ cyan: 3,
1441
+ blue: 4
1442
+ };
1443
+ var EFECTO_ASCII_COMPONENT_DEFAULTS = {
1444
+ cellSize: 8,
1445
+ invert: false,
1446
+ colorMode: true,
1447
+ asciiStyle: "standard"
1448
+ };
1449
+ var EFECTO_ASCII_POST_PROCESSING_DEFAULTS = {
1450
+ scanlineIntensity: 0,
1451
+ scanlineCount: 200,
1452
+ targetFPS: 0,
1453
+ jitterIntensity: 0,
1454
+ jitterSpeed: 0,
1455
+ mouseGlowEnabled: false,
1456
+ mouseGlowRadius: 200,
1457
+ mouseGlowIntensity: 1.5,
1458
+ vignetteIntensity: 0,
1459
+ vignetteRadius: 0.8,
1460
+ colorPalette: COLOR_PALETTE_MAP.original,
1461
+ curvature: 0,
1462
+ aberrationStrength: 0,
1463
+ noiseIntensity: 0,
1464
+ noiseScale: 0.1,
1465
+ noiseSpeed: 0,
1466
+ waveAmplitude: 0,
1467
+ waveFrequency: 3,
1468
+ waveSpeed: 0.2,
1469
+ glitchIntensity: 0,
1470
+ glitchFrequency: 0,
1471
+ brightnessAdjust: 0,
1472
+ contrastAdjust: 1
1473
+ };
1474
+ var normalizePostProcessing = (overrides) => {
1475
+ const { colorPalette: overridePalette, ...otherOverrides } = overrides ?? {};
1476
+ const merged = {
1477
+ ...EFECTO_ASCII_POST_PROCESSING_DEFAULTS,
1478
+ ...otherOverrides
1479
+ };
1480
+ const paletteValue = overridePalette === void 0 ? merged.colorPalette : typeof overridePalette === "string" ? COLOR_PALETTE_MAP[overridePalette.toLowerCase()] ?? COLOR_PALETTE_MAP.original : overridePalette;
1481
+ return { ...merged, colorPalette: paletteValue };
1482
+ };
1483
+ var AsciiEffectImpl = class extends Effect {
1484
+ constructor(initialProps) {
1485
+ const uniformEntries = [
1486
+ ["cellSize", new Uniform(initialProps.cellSize)],
1487
+ ["invert", new Uniform(initialProps.invert)],
1488
+ ["colorMode", new Uniform(initialProps.colorMode)],
1489
+ ["asciiStyle", new Uniform(initialProps.asciiStyle)],
1490
+ ["time", new Uniform(0)],
1491
+ ["resolution", new Uniform(initialProps.resolution.clone())],
1492
+ ["mousePos", new Uniform(initialProps.mousePos.clone())],
1493
+ ["scanlineIntensity", new Uniform(initialProps.scanlineIntensity)],
1494
+ ["scanlineCount", new Uniform(initialProps.scanlineCount)],
1495
+ ["targetFPS", new Uniform(initialProps.targetFPS)],
1496
+ ["jitterIntensity", new Uniform(initialProps.jitterIntensity)],
1497
+ ["jitterSpeed", new Uniform(initialProps.jitterSpeed)],
1498
+ ["mouseGlowEnabled", new Uniform(initialProps.mouseGlowEnabled)],
1499
+ ["mouseGlowRadius", new Uniform(initialProps.mouseGlowRadius)],
1500
+ ["mouseGlowIntensity", new Uniform(initialProps.mouseGlowIntensity)],
1501
+ ["vignetteIntensity", new Uniform(initialProps.vignetteIntensity)],
1502
+ ["vignetteRadius", new Uniform(initialProps.vignetteRadius)],
1503
+ ["colorPalette", new Uniform(initialProps.colorPalette)],
1504
+ ["curvature", new Uniform(initialProps.curvature)],
1505
+ ["aberrationStrength", new Uniform(initialProps.aberrationStrength)],
1506
+ ["noiseIntensity", new Uniform(initialProps.noiseIntensity)],
1507
+ ["noiseScale", new Uniform(initialProps.noiseScale)],
1508
+ ["noiseSpeed", new Uniform(initialProps.noiseSpeed)],
1509
+ ["waveAmplitude", new Uniform(initialProps.waveAmplitude)],
1510
+ ["waveFrequency", new Uniform(initialProps.waveFrequency)],
1511
+ ["waveSpeed", new Uniform(initialProps.waveSpeed)],
1512
+ ["glitchIntensity", new Uniform(initialProps.glitchIntensity)],
1513
+ ["glitchFrequency", new Uniform(initialProps.glitchFrequency)],
1514
+ ["brightnessAdjust", new Uniform(initialProps.brightnessAdjust)],
1515
+ ["contrastAdjust", new Uniform(initialProps.contrastAdjust)]
1516
+ ];
1517
+ super("AsciiEffect", fragment_default5, {
1518
+ blendFunction: BlendFunction.SRC,
1519
+ uniforms: new Map(uniformEntries)
1520
+ });
1521
+ this.time = 0;
1522
+ this.deltaAccumulator = 0;
1523
+ }
1524
+ updateUniforms(nextProps) {
1525
+ if (nextProps.cellSize !== void 0) {
1526
+ this.uniforms.get("cellSize").value = nextProps.cellSize;
1527
+ }
1528
+ if (nextProps.invert !== void 0) {
1529
+ this.uniforms.get("invert").value = nextProps.invert;
1530
+ }
1531
+ if (nextProps.colorMode !== void 0) {
1532
+ this.uniforms.get("colorMode").value = nextProps.colorMode;
1533
+ }
1534
+ if (nextProps.asciiStyle !== void 0) {
1535
+ this.uniforms.get("asciiStyle").value = nextProps.asciiStyle;
1536
+ }
1537
+ if (nextProps.resolution) {
1538
+ this.setVector2Uniform("resolution", nextProps.resolution);
1539
+ }
1540
+ if (nextProps.mousePos) {
1541
+ this.setVector2Uniform("mousePos", nextProps.mousePos);
1542
+ }
1543
+ const uniformKeys = Object.keys(
1544
+ EFECTO_ASCII_POST_PROCESSING_DEFAULTS
1545
+ );
1546
+ for (const key of uniformKeys) {
1547
+ if (nextProps[key] !== void 0) {
1548
+ this.uniforms.get(key).value = nextProps[key];
1549
+ }
1550
+ }
1551
+ }
1552
+ setVector2Uniform(key, nextValue) {
1553
+ const uniform = this.uniforms.get(key);
1554
+ if (!uniform) {
1555
+ return;
1556
+ }
1557
+ if (uniform.value instanceof Vector23) {
1558
+ uniform.value.copy(nextValue);
1559
+ return;
1560
+ }
1561
+ uniform.value = nextValue.clone();
1562
+ }
1563
+ update(_renderer, _inputBuffer, deltaTime) {
1564
+ const targetFPS = this.uniforms.get("targetFPS").value;
1565
+ if (targetFPS > 0) {
1566
+ const frameDuration = 1 / targetFPS;
1567
+ this.deltaAccumulator += deltaTime;
1568
+ if (this.deltaAccumulator >= frameDuration) {
1569
+ this.time += frameDuration;
1570
+ this.deltaAccumulator = this.deltaAccumulator % frameDuration;
1571
+ }
1572
+ } else {
1573
+ this.time += deltaTime;
1574
+ }
1575
+ this.uniforms.get("time").value = this.time;
1576
+ }
1577
+ };
1578
+ function AsciiEffect({
1579
+ cellSize = EFECTO_ASCII_COMPONENT_DEFAULTS.cellSize,
1580
+ invert = EFECTO_ASCII_COMPONENT_DEFAULTS.invert,
1581
+ colorMode = EFECTO_ASCII_COMPONENT_DEFAULTS.colorMode,
1582
+ asciiStyle: asciiStyleProp = EFECTO_ASCII_COMPONENT_DEFAULTS.asciiStyle,
1583
+ imageUrl,
1584
+ resolution,
1585
+ mousePosition,
1586
+ postProcessing = {},
1587
+ mediaAdjustments,
1588
+ mouseParallax = false,
1589
+ parallaxIntensity = 0.5,
1590
+ className,
1591
+ style,
1592
+ width,
1593
+ height,
1594
+ ...divProps
1595
+ }) {
1596
+ const composerRef = useRef6(null);
1597
+ const effectRef = useRef6(null);
1598
+ const assetsRef = useRef6(null);
1599
+ const loadedTextureRef = useRef6(null);
1600
+ const cameraRef = useRef6(null);
1601
+ const mediaUniformsRef = useRef6({
1602
+ map: { value: null },
1603
+ brightness: { value: EFECTO_MEDIA_ADJUSTMENT_DEFAULTS.brightness },
1604
+ contrast: { value: EFECTO_MEDIA_ADJUSTMENT_DEFAULTS.contrast },
1605
+ saturation: { value: EFECTO_MEDIA_ADJUSTMENT_DEFAULTS.saturation },
1606
+ uvScale: { value: new Vector23(1, 1) },
1607
+ uvOffset: { value: new Vector23(0, 0) }
1608
+ });
1609
+ const pointerBoundsRef = useRef6({
1610
+ left: 0,
1611
+ top: 0,
1612
+ width: 1,
1613
+ height: 1
1614
+ });
1615
+ const manualResolutionRef = useRef6(
1616
+ resolution ? resolution.clone() : null
1617
+ );
1618
+ manualResolutionRef.current = resolution ? resolution.clone() : null;
1619
+ const resolutionRef = useRef6(
1620
+ manualResolutionRef.current ? manualResolutionRef.current.clone() : DEFAULT_RESOLUTION.clone()
1621
+ );
1622
+ const autoMouseRef = useRef6(DEFAULT_MOUSE_POSITION.clone());
1623
+ const usesExternalMouseRef = useRef6(Boolean(mousePosition));
1624
+ usesExternalMouseRef.current = Boolean(mousePosition);
1625
+ const viewportSizeRef = useRef6({ width: 1, height: 1 });
1626
+ const meshDisplaySizeRef = useRef6({ width: 1, height: 1 });
1627
+ const parallaxStateRef = useRef6({
1628
+ targetX: 0,
1629
+ targetY: 0,
1630
+ rotationX: 0,
1631
+ rotationY: 0
1632
+ });
1633
+ const parallaxIntensityRef = useRef6(
1634
+ THREE6.MathUtils.clamp(parallaxIntensity ?? 0, 0, 1)
1635
+ );
1636
+ parallaxIntensityRef.current = THREE6.MathUtils.clamp(
1637
+ parallaxIntensity ?? 0,
1638
+ 0,
1639
+ 1
1640
+ );
1641
+ const normalizedStyle = typeof asciiStyleProp === "string" ? asciiStyleProp.toLowerCase() ?? EFECTO_ASCII_COMPONENT_DEFAULTS.asciiStyle : EFECTO_ASCII_COMPONENT_DEFAULTS.asciiStyle;
1642
+ const asciiStyle = STYLE_MAP[normalizedStyle] ?? STYLE_MAP.standard;
1643
+ const resolvedPostProcessing = useMemo3(
1644
+ () => normalizePostProcessing(postProcessing),
1645
+ [postProcessing]
1646
+ );
1647
+ const resolvedMediaAdjustments = useMemo3(
1648
+ () => resolveEfectoMediaAdjustments(mediaAdjustments),
1649
+ [mediaAdjustments]
1650
+ );
1651
+ const initialEffectPropsRef = useRef6(null);
1652
+ if (!initialEffectPropsRef.current) {
1653
+ initialEffectPropsRef.current = {
1654
+ cellSize,
1655
+ invert,
1656
+ colorMode,
1657
+ asciiStyle,
1658
+ resolution: resolutionRef.current.clone(),
1659
+ mousePos: (mousePosition ?? DEFAULT_MOUSE_POSITION).clone(),
1660
+ ...resolvedPostProcessing
1661
+ };
1662
+ }
1663
+ const updateTextureUVTransform = useCallback5(() => {
1664
+ const texture = loadedTextureRef.current;
1665
+ if (!texture || !texture.image) {
1666
+ return;
1667
+ }
1668
+ const { width: planeWidth, height: planeHeight } = meshDisplaySizeRef.current;
1669
+ if (planeWidth <= 0 || planeHeight <= 0) {
1670
+ return;
1671
+ }
1672
+ const image = texture.image;
1673
+ if (!image.width || !image.height) {
1674
+ return;
1675
+ }
1676
+ const imageAspect = image.width / image.height;
1677
+ const planeAspect = planeWidth / planeHeight;
1678
+ let scaleX = 1;
1679
+ let scaleY = 1;
1680
+ if (imageAspect > planeAspect) {
1681
+ scaleX = imageAspect / planeAspect;
1682
+ } else {
1683
+ scaleY = planeAspect / imageAspect;
1684
+ }
1685
+ const uvScaleX = 1 / scaleX;
1686
+ const uvScaleY = 1 / scaleY;
1687
+ const offsetX = (1 - uvScaleX) * 0.5;
1688
+ const offsetY = (1 - uvScaleY) * 0.5;
1689
+ mediaUniformsRef.current.uvScale.value.set(uvScaleX, uvScaleY);
1690
+ mediaUniformsRef.current.uvOffset.value.set(offsetX, offsetY);
1691
+ const assets = assetsRef.current;
1692
+ if (assets) {
1693
+ assets.material.uniforms.uvScale.value.set(
1694
+ uvScaleX,
1695
+ uvScaleY
1696
+ );
1697
+ assets.material.uniforms.uvOffset.value.set(
1698
+ offsetX,
1699
+ offsetY
1700
+ );
1701
+ }
1702
+ }, []);
1703
+ const updateMeshScale = useCallback5(() => {
1704
+ const assets = assetsRef.current;
1705
+ if (!assets) return;
1706
+ const { width: width2, height: height2 } = viewportSizeRef.current;
1707
+ const camera = cameraRef.current;
1708
+ let viewWidth = width2;
1709
+ let viewHeight = height2;
1710
+ if (camera instanceof THREE6.PerspectiveCamera) {
1711
+ const distance = Math.abs(camera.position.z - assets.mesh.position.z);
1712
+ const verticalFov = THREE6.MathUtils.degToRad(camera.fov);
1713
+ viewHeight = 2 * Math.tan(verticalFov / 2) * distance;
1714
+ viewWidth = viewHeight * camera.aspect;
1715
+ }
1716
+ const texture = loadedTextureRef.current;
1717
+ if (!texture || !texture.image) {
1718
+ assets.mesh.scale.set(viewWidth, viewHeight, 1);
1719
+ meshDisplaySizeRef.current = { width: viewWidth, height: viewHeight };
1720
+ return;
1721
+ }
1722
+ const image = texture.image;
1723
+ const imageAspect = image.width && image.height ? image.width / Math.max(1, image.height) : 1;
1724
+ const viewportAspect = viewWidth / Math.max(1, viewHeight);
1725
+ let meshWidth = viewWidth;
1726
+ let meshHeight = viewHeight;
1727
+ if (imageAspect > viewportAspect) {
1728
+ meshHeight = viewHeight;
1729
+ meshWidth = viewHeight * imageAspect;
1730
+ } else {
1731
+ meshWidth = viewWidth;
1732
+ meshHeight = viewWidth / Math.max(1e-4, imageAspect);
1733
+ }
1734
+ assets.mesh.scale.set(meshWidth, meshHeight, 1);
1735
+ meshDisplaySizeRef.current = { width: meshWidth, height: meshHeight };
1736
+ updateTextureUVTransform();
1737
+ }, [updateTextureUVTransform]);
1738
+ const updatePointerBounds = useCallback5(
1739
+ (container = containerRef.current) => {
1740
+ if (!container) return;
1741
+ const bounds = container.getBoundingClientRect();
1742
+ pointerBoundsRef.current = {
1743
+ left: bounds.left,
1744
+ top: bounds.top,
1745
+ width: bounds.width || 1,
1746
+ height: bounds.height || 1
1747
+ };
1748
+ },
1749
+ []
1750
+ );
1751
+ const resetParallax = useCallback5(() => {
1752
+ parallaxStateRef.current.targetX = 0;
1753
+ parallaxStateRef.current.targetY = 0;
1754
+ parallaxStateRef.current.rotationX = 0;
1755
+ parallaxStateRef.current.rotationY = 0;
1756
+ const mesh = assetsRef.current?.mesh;
1757
+ if (mesh) {
1758
+ mesh.rotation.x = 0;
1759
+ mesh.rotation.y = 0;
1760
+ }
1761
+ }, []);
1762
+ const handleCreate = useCallback5(
1763
+ ({ scene, camera, renderer, size }) => {
1764
+ renderer.outputColorSpace = THREE6.SRGBColorSpace;
1765
+ renderer.toneMapping = THREE6.NoToneMapping;
1766
+ if (!manualResolutionRef.current) {
1767
+ resolutionRef.current.set(size.width, size.height);
1768
+ }
1769
+ viewportSizeRef.current = { width: size.width, height: size.height };
1770
+ const geometry = new THREE6.PlaneGeometry(1, 1);
1771
+ const material = new THREE6.ShaderMaterial({
1772
+ vertexShader: vertex_default5,
1773
+ fragmentShader: fragment_default6,
1774
+ uniforms: mediaUniformsRef.current,
1775
+ transparent: true,
1776
+ depthTest: false
1777
+ });
1778
+ material.toneMapped = false;
1779
+ const mesh = new THREE6.Mesh(geometry, material);
1780
+ scene.add(mesh);
1781
+ if (loadedTextureRef.current) {
1782
+ mediaUniformsRef.current.map.value = loadedTextureRef.current;
1783
+ material.uniforms.map.value = loadedTextureRef.current;
1784
+ }
1785
+ assetsRef.current = { mesh, geometry, material };
1786
+ cameraRef.current = camera;
1787
+ updateMeshScale();
1788
+ const composer = new EffectComposer(renderer);
1789
+ const renderPass = new RenderPass(scene, camera);
1790
+ const effect = new AsciiEffectImpl({
1791
+ ...initialEffectPropsRef.current,
1792
+ resolution: resolutionRef.current.clone()
1793
+ });
1794
+ const effectPass = new EffectPass(camera, effect);
1795
+ effectPass.renderToScreen = true;
1796
+ composer.addPass(renderPass);
1797
+ composer.addPass(effectPass);
1798
+ composer.setSize(size.width, size.height);
1799
+ composerRef.current = composer;
1800
+ effectRef.current = effect;
1801
+ return () => {
1802
+ composer.dispose();
1803
+ if (assetsRef.current) {
1804
+ scene.remove(assetsRef.current.mesh);
1805
+ assetsRef.current.geometry.dispose();
1806
+ assetsRef.current.material.dispose();
1807
+ assetsRef.current = null;
1808
+ }
1809
+ cameraRef.current = null;
1810
+ loadedTextureRef.current?.dispose();
1811
+ loadedTextureRef.current = null;
1812
+ composerRef.current = null;
1813
+ effectRef.current = null;
1814
+ };
1815
+ },
1816
+ [updateMeshScale]
1817
+ );
1818
+ const handleRender = useCallback5(
1819
+ (_context, delta, _elapsed) => {
1820
+ const assets = assetsRef.current;
1821
+ if (assets) {
1822
+ const enabled = mouseParallax && parallaxIntensityRef.current > 0 && assets.mesh;
1823
+ if (enabled) {
1824
+ parallaxStateRef.current.rotationX += (parallaxStateRef.current.targetX - parallaxStateRef.current.rotationX) * PARALLAX_LERP;
1825
+ parallaxStateRef.current.rotationY += (parallaxStateRef.current.targetY - parallaxStateRef.current.rotationY) * PARALLAX_LERP;
1826
+ assets.mesh.rotation.x = parallaxStateRef.current.rotationX;
1827
+ assets.mesh.rotation.y = parallaxStateRef.current.rotationY;
1828
+ } else if (Math.abs(assets.mesh.rotation.x) > 1e-3 || Math.abs(assets.mesh.rotation.y) > 1e-3) {
1829
+ assets.mesh.rotation.x *= 0.9;
1830
+ assets.mesh.rotation.y *= 0.9;
1831
+ }
1832
+ }
1833
+ composerRef.current?.render(delta);
1834
+ },
1835
+ [mouseParallax]
1836
+ );
1837
+ const handleResize = useCallback5(
1838
+ (_context, size) => {
1839
+ const composer = composerRef.current;
1840
+ composer?.setSize(size.width, size.height);
1841
+ viewportSizeRef.current = { width: size.width, height: size.height };
1842
+ updateMeshScale();
1843
+ updatePointerBounds(containerRef.current);
1844
+ if (manualResolutionRef.current) {
1845
+ resolutionRef.current.copy(manualResolutionRef.current);
1846
+ } else {
1847
+ resolutionRef.current.set(size.width, size.height);
1848
+ }
1849
+ effectRef.current?.updateUniforms({
1850
+ resolution: resolutionRef.current
1851
+ });
1852
+ if (!usesExternalMouseRef.current) {
1853
+ autoMouseRef.current.set(size.width / 2, size.height / 2);
1854
+ effectRef.current?.updateUniforms({ mousePos: autoMouseRef.current });
1855
+ }
1856
+ },
1857
+ [updateMeshScale, updatePointerBounds]
1858
+ );
1859
+ const { containerRef, contextRef } = useScene({
1860
+ camera: {
1861
+ type: "perspective",
1862
+ near: 0.1,
1863
+ far: 100,
1864
+ position: [0, 0, 10],
1865
+ fov: 50
1866
+ },
1867
+ onCreate: handleCreate,
1868
+ onRender: handleRender,
1869
+ onResize: handleResize,
1870
+ manualRender: true
1871
+ });
1872
+ useEffect6(() => {
1873
+ if (!mouseParallax) {
1874
+ resetParallax();
1875
+ }
1876
+ }, [mouseParallax, resetParallax]);
1877
+ useEffect6(() => {
1878
+ if (!imageUrl) return;
1879
+ let disposed = false;
1880
+ const loader = new THREE6.TextureLoader();
1881
+ loader.load(
1882
+ imageUrl,
1883
+ (texture) => {
1884
+ if (disposed) {
1885
+ texture.dispose();
1886
+ return;
1887
+ }
1888
+ texture.colorSpace = THREE6.SRGBColorSpace;
1889
+ texture.wrapS = THREE6.ClampToEdgeWrapping;
1890
+ texture.wrapT = THREE6.ClampToEdgeWrapping;
1891
+ texture.minFilter = THREE6.LinearFilter;
1892
+ texture.magFilter = THREE6.LinearFilter;
1893
+ loadedTextureRef.current?.dispose();
1894
+ loadedTextureRef.current = texture;
1895
+ mediaUniformsRef.current.map.value = texture;
1896
+ const assets = assetsRef.current;
1897
+ if (assets) {
1898
+ assets.material.uniforms.map.value = texture;
1899
+ }
1900
+ updateMeshScale();
1901
+ updateTextureUVTransform();
1902
+ },
1903
+ void 0,
1904
+ () => {
1905
+ if (disposed) return;
1906
+ loadedTextureRef.current?.dispose();
1907
+ loadedTextureRef.current = null;
1908
+ }
1909
+ );
1910
+ return () => {
1911
+ disposed = true;
1912
+ };
1913
+ }, [imageUrl, updateMeshScale, updateTextureUVTransform]);
1914
+ useEffect6(() => {
1915
+ const effect = effectRef.current;
1916
+ if (!effect) return;
1917
+ effect.updateUniforms({
1918
+ cellSize,
1919
+ invert,
1920
+ colorMode,
1921
+ asciiStyle
1922
+ });
1923
+ }, [asciiStyle, cellSize, colorMode, invert]);
1924
+ useEffect6(() => {
1925
+ if (!effectRef.current) return;
1926
+ effectRef.current.updateUniforms(resolvedPostProcessing);
1927
+ }, [resolvedPostProcessing]);
1928
+ useEffect6(() => {
1929
+ mediaUniformsRef.current.brightness.value = resolvedMediaAdjustments.brightness;
1930
+ mediaUniformsRef.current.contrast.value = resolvedMediaAdjustments.contrast;
1931
+ mediaUniformsRef.current.saturation.value = resolvedMediaAdjustments.saturation;
1932
+ const material = assetsRef.current?.material;
1933
+ if (material) {
1934
+ material.uniforms.brightness.value = resolvedMediaAdjustments.brightness;
1935
+ material.uniforms.contrast.value = resolvedMediaAdjustments.contrast;
1936
+ material.uniforms.saturation.value = resolvedMediaAdjustments.saturation;
1937
+ }
1938
+ }, [resolvedMediaAdjustments]);
1939
+ useEffect6(() => {
1940
+ if (!mousePosition || !effectRef.current) return;
1941
+ effectRef.current.updateUniforms({ mousePos: mousePosition });
1942
+ }, [mousePosition]);
1943
+ useEffect6(() => {
1944
+ if (!resolution || !effectRef.current) return;
1945
+ resolutionRef.current.copy(resolution);
1946
+ manualResolutionRef.current = resolution.clone();
1947
+ effectRef.current.updateUniforms({ resolution });
1948
+ }, [resolution]);
1949
+ useEffect6(() => {
1950
+ if (mousePosition) return;
1951
+ const container = containerRef.current;
1952
+ if (!container) return;
1953
+ updatePointerBounds(container);
1954
+ const updateFromEvent = (event) => {
1955
+ const bounds = pointerBoundsRef.current;
1956
+ autoMouseRef.current.set(
1957
+ event.clientX - bounds.left,
1958
+ bounds.height - (event.clientY - bounds.top)
1959
+ );
1960
+ effectRef.current?.updateUniforms({ mousePos: autoMouseRef.current });
1961
+ if (mouseParallax && bounds.width > 0 && bounds.height > 0) {
1962
+ const normalizedX = ((event.clientX - bounds.left) / bounds.width - 0.5) * 2;
1963
+ const normalizedY = ((event.clientY - bounds.top) / bounds.height - 0.5) * 2;
1964
+ const intensity = parallaxIntensityRef.current;
1965
+ parallaxStateRef.current.targetY = 0.3 * normalizedX * intensity;
1966
+ parallaxStateRef.current.targetX = -0.2 * normalizedY * intensity;
1967
+ }
1968
+ };
1969
+ const handlePointerEnter = () => {
1970
+ updatePointerBounds();
1971
+ };
1972
+ const resetToCenter = () => {
1973
+ const size = contextRef.current?.size ?? {
1974
+ width: container.clientWidth || 1,
1975
+ height: container.clientHeight || 1
1976
+ };
1977
+ autoMouseRef.current.set(size.width / 2, size.height / 2);
1978
+ effectRef.current?.updateUniforms({ mousePos: autoMouseRef.current });
1979
+ if (mouseParallax) {
1980
+ resetParallax();
1981
+ }
1982
+ };
1983
+ resetToCenter();
1984
+ container.addEventListener("pointermove", updateFromEvent);
1985
+ container.addEventListener("pointerenter", handlePointerEnter);
1986
+ container.addEventListener("pointerleave", resetToCenter);
1987
+ return () => {
1988
+ container.removeEventListener("pointermove", updateFromEvent);
1989
+ container.removeEventListener("pointerenter", handlePointerEnter);
1990
+ container.removeEventListener("pointerleave", resetToCenter);
1991
+ };
1992
+ }, [
1993
+ containerRef,
1994
+ contextRef,
1995
+ mouseParallax,
1996
+ mousePosition,
1997
+ resetParallax,
1998
+ updatePointerBounds
1999
+ ]);
2000
+ useEffect6(() => {
2001
+ parallaxIntensityRef.current = THREE6.MathUtils.clamp(
2002
+ parallaxIntensity ?? 0,
2003
+ 0,
2004
+ 1
2005
+ );
2006
+ }, [parallaxIntensity]);
2007
+ return /* @__PURE__ */ jsx5(
2008
+ "div",
2009
+ {
2010
+ ref: containerRef,
2011
+ className,
2012
+ style: {
2013
+ width: width ?? "100%",
2014
+ height: height ?? "100%",
2015
+ ...style
2016
+ },
2017
+ ...divProps
2018
+ }
2019
+ );
2020
+ }
2021
+
2022
+ // src/components/EfectoAsciiWrapper.tsx
2023
+ import { jsx as jsx6 } from "react/jsx-runtime";
2024
+ function EfectoAsciiWrapper({
2025
+ settings,
2026
+ imageUrl,
2027
+ mediaAdjustments,
2028
+ mouseParallax,
2029
+ parallaxIntensity,
2030
+ ...passThrough
2031
+ }) {
2032
+ return /* @__PURE__ */ jsx6(
2033
+ AsciiEffect,
2034
+ {
2035
+ asciiStyle: settings.asciiStyle,
2036
+ cellSize: settings.cellSize,
2037
+ invert: settings.invert,
2038
+ colorMode: settings.colorMode,
2039
+ postProcessing: settings.postProcessing,
2040
+ imageUrl,
2041
+ mediaAdjustments,
2042
+ mouseParallax,
2043
+ parallaxIntensity,
2044
+ ...passThrough
2045
+ }
2046
+ );
2047
+ }
2048
+
2049
+ // src/components/Efecto.tsx
2050
+ import { jsx as jsx7 } from "react/jsx-runtime";
2051
+ var ASCII_BASE_DEFAULTS = {
2052
+ cellSize: EFECTO_ASCII_COMPONENT_DEFAULTS.cellSize,
2053
+ invert: EFECTO_ASCII_COMPONENT_DEFAULTS.invert,
2054
+ colorMode: EFECTO_ASCII_COMPONENT_DEFAULTS.colorMode,
2055
+ asciiStyle: EFECTO_ASCII_COMPONENT_DEFAULTS.asciiStyle
2056
+ };
2057
+ function Efecto({
2058
+ postProcessing,
2059
+ src,
2060
+ mouseParallax = false,
2061
+ parallaxIntensity = 0.5,
2062
+ mediaAdjustments,
2063
+ cellSize,
2064
+ invert,
2065
+ colorMode,
2066
+ style,
2067
+ asciiStyle,
2068
+ ...wrapperProps
2069
+ }) {
2070
+ const resolvedStyle = style ?? asciiStyle ?? ASCII_BASE_DEFAULTS.asciiStyle;
2071
+ const baseAsciiProps = {
2072
+ cellSize: cellSize ?? ASCII_BASE_DEFAULTS.cellSize,
2073
+ invert: invert ?? ASCII_BASE_DEFAULTS.invert,
2074
+ colorMode: colorMode ?? ASCII_BASE_DEFAULTS.colorMode,
2075
+ asciiStyle: resolvedStyle
2076
+ };
2077
+ const asciiSettings = {
2078
+ ...baseAsciiProps,
2079
+ postProcessing
2080
+ };
2081
+ return /* @__PURE__ */ jsx7(
2082
+ EfectoAsciiWrapper,
2083
+ {
2084
+ settings: asciiSettings,
2085
+ imageUrl: src,
2086
+ mediaAdjustments,
2087
+ mouseParallax,
2088
+ parallaxIntensity,
2089
+ ...wrapperProps
2090
+ }
2091
+ );
2092
+ }
528
2093
  export {
2094
+ EFECTO_ASCII_COMPONENT_DEFAULTS,
2095
+ EFECTO_ASCII_POST_PROCESSING_DEFAULTS,
2096
+ Efecto,
529
2097
  FractalFlower,
2098
+ MenuGlitch,
530
2099
  OranoParticles,
531
2100
  ShaderArt
532
2101
  };