react-video-ascii 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # react-video-ascii
2
+
3
+ React component optimized for rendering videos as ASCII using WebGL2.
4
+
5
+ <p align="center">
6
+ <img width="1512" height="949" alt="demo" src="https://github.com/user-attachments/assets/2b41b0af-e864-430d-ade5-0249335d1aac" />
7
+ </p>
8
+
9
+ [Try it out!](https://video-ascii-demo.vercel.app/)
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install react-video-ascii
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ import { VideoAscii } from 'react-video-ascii';
21
+
22
+ // Basic usage
23
+ <VideoAscii src="/video.mp4" />
24
+
25
+ // Multiple videos (loop sequentially)
26
+ const sources = ['/video1.mp4', '/video2.mp4'];
27
+ <VideoAscii src={sources} />
28
+
29
+ // With options
30
+ <VideoAscii
31
+ src="/video.mp4"
32
+ videoMode={false}
33
+ numColsRaw={150}
34
+ brightnessRaw={1.2}
35
+ saturationRaw={1.0}
36
+ bgOpacityRaw={0.3}
37
+ chars=" `.',-_:!abcdef"
38
+ charMode="shape"
39
+ mouseEffect={{
40
+ style: 'brighten',
41
+ radius: 0.08,
42
+ duration: 1.0,
43
+ trailLen: 15,
44
+ trailDecay: 10,
45
+ brightness: 2.0,
46
+ }}
47
+ clickEffect={{
48
+ style: 'ripple',
49
+ brightness: 1.1,
50
+ speed: 2,
51
+ }}
52
+ revealEffect={{
53
+ type: 'random',
54
+ duration: 0.4,
55
+ }}
56
+ className="my-ascii"
57
+ />
58
+ ```
59
+
60
+ > **Note 1:** When passing an array to `src`, define it outside the component or in a `useMemo`/`useRef` (an inline array literal creates a new reference on every render and will cause the video to reload).
61
+
62
+ > **Note 2:** The component fills its parent container. Control size via the parent element or the `className` prop.
63
+
64
+ ## Props
65
+
66
+ | Prop | Type | Default | Range | Description |
67
+ |------|------|---------|-------|-------------|
68
+ | `src` | `string \| string[]` | — | — | Video source URL(s). Multiple URLs play sequentially. |
69
+ | `videoMode` | `boolean` | `false` | — | Show original video colors instead of ASCII. |
70
+ | `numColsRaw` | `number` | `250` | `20–350` | Number of character columns. |
71
+ | `brightnessRaw` | `number` | `1.0` | `0.0–2.0` | Brightness multiplier. |
72
+ | `saturationRaw` | `number` | `1.0` | `0.0–2.0` | Saturation multiplier. |
73
+ | `bgOpacityRaw` | `number` | `0.3` | `0.0–1.0` | Background opacity. |
74
+ | `chars` | `string` | *(standard)* | — | Character set, ordered dark to bright. |
75
+ | `charMode` | `'shape' \| 'luminance'` | `'shape'` | — | `shape` matches glyph silhouettes; `luminance` uses brightness. |
76
+ | `mouseEffect` | `boolean \| MouseEffectOptions` | `true` | — | Mouse hover effects. `true` uses defaults. |
77
+ | `clickEffect` | `boolean \| ClickEffectOptions` | `true` | — | Click effects. `true` uses defaults. |
78
+ | `revealEffect` | `boolean \| RevealEffectOptions` | `false` | — | Reveal animation on load. `true` uses defaults. |
79
+ | `className` | `string` | — | — | CSS class on the outer container. |
80
+
81
+ ---
82
+
83
+ ### `MouseEffectOptions`
84
+
85
+ Passed to `mouseEffect`. All fields optional.
86
+
87
+ | Field | Type | Default | Range | Description |
88
+ |-------|------|---------|-------|-------------|
89
+ | `style` | `'brighten' \| 'scatter'` | `'brighten'` | — | `brighten` highlights chars; `scatter` replaces them. |
90
+ | `radius` | `number` | `0.08` / `0.05` | `0.03–0.2` | Effect radius as a fraction of the smaller canvas dimension. |
91
+ | `duration` | `number` | `1.0` | `0.1–4` | Seconds the effect lingers after the cursor leaves. |
92
+ | `trailLen` | `number` | `15` | `0–30` | *(brighten)* Trail positions tracked. |
93
+ | `trailDecay` | `number` | `10` | `1–15` | *(brighten)* How fast older trail positions fade. |
94
+ | `brightness` | `number` | `2.0` | `0.2–5.0` | *(brighten)* Brightness boost at the cursor. |
95
+ | `scatterChars` | `string` | `'->o'` | — | *(scatter)* Chars substituted near the cursor. |
96
+
97
+ ---
98
+
99
+ ### `ClickEffectOptions`
100
+
101
+ Passed to `clickEffect`. All fields optional.
102
+
103
+ | Field | Type | Default | Range | Description |
104
+ |-------|------|---------|-------|-------------|
105
+ | `style` | `'ripple' \| 'spread'` | `'ripple'` | — | `ripple` sends a brightness ring outward; `spread` expands a scatter region. |
106
+ | `brightness` | `number` | `1.1` | `1.05–2.0` | *(ripple)* Brightness of the ripple ring. |
107
+ | `speed` | `number` | `2` | `0.5–4.0` | *(ripple)* Ripple expansion speed. |
108
+ | `spreadExpandDuration` | `number` | `1.5` | `0.5–5.0` | *(spread)* Seconds for the region to fully expand. |
109
+ | `spreadSpeed` | `number` | `7.5` | `0.5–10.0` | *(spread)* Speed of the spread wave front. |
110
+
111
+ ---
112
+
113
+ ### `RevealEffectOptions`
114
+
115
+ Passed to `revealEffect`. All fields optional.
116
+
117
+ | Field | Type | Default | Range | Description |
118
+ |-------|------|---------|-------|-------------|
119
+ | `type` | `'diagonal' \| 'radial' \| 'random'` | `'random'` | — | Pattern in which characters appear on load. |
120
+ | `duration` | `number` | `0.4` | `0.1–4` | Reveal animation duration in seconds. |
121
+
122
+ ---
123
+
124
+ ### `chars`
125
+
126
+ The default `chars` set is: `` `.',-_:!;|\"~+^lr[](\\/L)>t<v=Tz?icf1{sIxY*jJno}CZyVwmSXRqM$O%#9&NW0Q@``.
@@ -0,0 +1,3 @@
1
+ import { Props } from '../lib/ascii-props';
2
+ declare function VideoAscii({ src, videoMode, numColsRaw, brightnessRaw, saturationRaw, bgOpacityRaw, revealEffect, chars, mouseEffect, clickEffect, charMode, className, }: Props): import("react/jsx-runtime").JSX.Element;
3
+ export default VideoAscii;
@@ -0,0 +1,2 @@
1
+ export { default as VideoAscii } from './components/VideoAscii';
2
+ export type { Props } from './lib/ascii-props';
@@ -0,0 +1,62 @@
1
+ export declare const DEFAULT_CHARS = " `.',-_:!;|\"~+^lr[](\\/L)>t<v=Tz?icf1{sIxY*jJno}CZyVwmSXRqM$O%#9&NW0Q@";
2
+ export interface MouseEffectOptions {
3
+ style?: 'brighten' | 'scatter';
4
+ radius?: number;
5
+ duration?: number;
6
+ trailLen?: number;
7
+ trailDecay?: number;
8
+ brightness?: number;
9
+ scatterChars?: string;
10
+ }
11
+ export interface ClickEffectOptions {
12
+ style?: 'ripple' | 'spread';
13
+ brightness?: number;
14
+ speed?: number;
15
+ spreadExpandDuration?: number;
16
+ spreadSpeed?: number;
17
+ }
18
+ export interface RevealEffectOptions {
19
+ type?: 'diagonal' | 'radial' | 'random';
20
+ duration?: number;
21
+ }
22
+ export interface Props {
23
+ src: string | string[];
24
+ videoMode?: boolean;
25
+ numColsRaw?: number;
26
+ brightnessRaw?: number;
27
+ saturationRaw?: number;
28
+ bgOpacityRaw?: number;
29
+ revealEffect?: boolean | RevealEffectOptions;
30
+ chars?: string;
31
+ mouseEffect?: boolean | MouseEffectOptions;
32
+ clickEffect?: boolean | ClickEffectOptions;
33
+ charMode?: 'shape' | 'luminance';
34
+ className?: string;
35
+ }
36
+ export interface ParsedProps {
37
+ numCols: number;
38
+ brightness: number;
39
+ saturation: number;
40
+ bgOpacity: number;
41
+ mouseEnabled: boolean;
42
+ mouseStyle: 'brighten' | 'scatter';
43
+ brightenEnabled: boolean;
44
+ scatterEnabled: boolean;
45
+ scatterChars: string;
46
+ trailLen: number;
47
+ trailDecay: number;
48
+ duration: number;
49
+ mouseRadius: number;
50
+ mouseBrightness: number;
51
+ clickEnabled: boolean;
52
+ clickBrightness: number;
53
+ clickSpeed: number;
54
+ spreadEnabled: boolean;
55
+ spreadExpandDuration: number;
56
+ spreadSpeed: number;
57
+ revealEnabled: boolean;
58
+ revealType: string;
59
+ revealDuration: number;
60
+ revealEffectFlag: number;
61
+ }
62
+ export declare function parseProps(numColsRaw: number, brightnessRaw: number, saturationRaw: number, bgOpacityRaw: number, mouseEffect: boolean | MouseEffectOptions, clickEffect: boolean | ClickEffectOptions, revealEffect: boolean | RevealEffectOptions): ParsedProps;
@@ -0,0 +1,7 @@
1
+ declare const CIRCLES: number[][];
2
+ declare const SIMPLE_CIRCLES: number[][];
3
+ declare function computeShapeVectors(chars: string, charW: number, charH: number): {
4
+ char: string;
5
+ vector: number[];
6
+ }[];
7
+ export { computeShapeVectors, CIRCLES, SIMPLE_CIRCLES };
@@ -0,0 +1,19 @@
1
+ interface MouseTrailRefs {
2
+ brightenEnabledRef: {
3
+ current: boolean;
4
+ };
5
+ trailLenRef: {
6
+ current: number;
7
+ };
8
+ durationRef: {
9
+ current: number;
10
+ };
11
+ trailDecayRef: {
12
+ current: number;
13
+ };
14
+ }
15
+ export declare function createMouseTrail({ brightenEnabledRef, trailLenRef, durationRef, trailDecayRef, }: MouseTrailRefs): {
16
+ handleMouseMove(x: number, y: number, t: number): void;
17
+ tick(gl: WebGL2RenderingContext, mousePositionsLoc: WebGLUniformLocation | null, mouseLifeFracsLoc: WebGLUniformLocation | null): void;
18
+ };
19
+ export {};
@@ -0,0 +1,16 @@
1
+ interface ClickEffectRefs {
2
+ clickEnabledRef: {
3
+ current: boolean;
4
+ };
5
+ clickSpeedRef: {
6
+ current: number;
7
+ };
8
+ clickBrightnessRef: {
9
+ current: number;
10
+ };
11
+ }
12
+ export declare function createClickEffect({ clickEnabledRef, clickSpeedRef, clickBrightnessRef, }: ClickEffectRefs): {
13
+ handleClick(e: MouseEvent, canvas: HTMLCanvasElement): void;
14
+ tick(gl: WebGL2RenderingContext, canvas: HTMLCanvasElement, ripplePositionsLoc: WebGLUniformLocation | null, rippleRadiiLoc: WebGLUniformLocation | null, rippleBrightnessesLoc: WebGLUniformLocation | null): void;
15
+ };
16
+ export {};
@@ -0,0 +1,49 @@
1
+ export interface GLResources {
2
+ program: WebGLProgram;
3
+ pass1Program: WebGLProgram;
4
+ buffer: WebGLBuffer;
5
+ vertShader: WebGLShader;
6
+ fragShader: WebGLShader;
7
+ pass1FragShader: WebGLShader;
8
+ texture: WebGLTexture;
9
+ atlasTexture: WebGLTexture;
10
+ charVectorsTexture: WebGLTexture;
11
+ fboTexture: WebGLTexture;
12
+ fbo: WebGLFramebuffer;
13
+ scatterAtlasTexture: WebGLTexture;
14
+ scatterStateTexture: WebGLTexture;
15
+ spreadStateTexture: WebGLTexture;
16
+ p1ResLoc: WebGLUniformLocation | null;
17
+ p1CellsizeLoc: WebGLUniformLocation | null;
18
+ p1CircleNLoc: WebGLUniformLocation | null;
19
+ p1NumCharsLoc: WebGLUniformLocation | null;
20
+ p1ExponentLoc: WebGLUniformLocation | null;
21
+ p1CropOffsetLoc: WebGLUniformLocation | null;
22
+ p1CropScaleLoc: WebGLUniformLocation | null;
23
+ revealEffectFlagLoc: WebGLUniformLocation | null;
24
+ mouseEffectFlagLoc: WebGLUniformLocation | null;
25
+ clickEffectFlagLoc: WebGLUniformLocation | null;
26
+ shapeMatchingLoc: WebGLUniformLocation | null;
27
+ revealProgressLoc: WebGLUniformLocation | null;
28
+ brightnessLoc: WebGLUniformLocation | null;
29
+ saturationLoc: WebGLUniformLocation | null;
30
+ bgOpacityLoc: WebGLUniformLocation | null;
31
+ mouseBrightnessLoc: WebGLUniformLocation | null;
32
+ mousePositionsLoc: WebGLUniformLocation | null;
33
+ mouseLifeFracsLoc: WebGLUniformLocation | null;
34
+ mouseRadiusLoc: WebGLUniformLocation | null;
35
+ ripplePositionsLoc: WebGLUniformLocation | null;
36
+ rippleRadiiLoc: WebGLUniformLocation | null;
37
+ rippleBrightnessesLoc: WebGLUniformLocation | null;
38
+ scatterEffectFlagLoc: WebGLUniformLocation | null;
39
+ scatterNumCharsLoc: WebGLUniformLocation | null;
40
+ spreadEffectFlagLoc: WebGLUniformLocation | null;
41
+ videoModeLoc: WebGLUniformLocation | null;
42
+ resLoc: WebGLUniformLocation | null;
43
+ sizeLoc: WebGLUniformLocation | null;
44
+ numLoc: WebGLUniformLocation | null;
45
+ gridSizeLoc: WebGLUniformLocation | null;
46
+ cropOffsetLoc: WebGLUniformLocation | null;
47
+ cropScaleLoc: WebGLUniformLocation | null;
48
+ }
49
+ export declare function createGLResources(gl: WebGL2RenderingContext): GLResources | null;
@@ -0,0 +1,22 @@
1
+ interface ScatterRefs {
2
+ scatterEnabledRef: {
3
+ current: boolean;
4
+ };
5
+ mouseRadiusRef: {
6
+ current: number;
7
+ };
8
+ durationRef: {
9
+ current: number;
10
+ };
11
+ scatterCharsRef: {
12
+ current: string;
13
+ };
14
+ }
15
+ export declare function createScatterEffect({ scatterEnabledRef, mouseRadiusRef, durationRef, scatterCharsRef, }: ScatterRefs): {
16
+ setup(gl: WebGL2RenderingContext, cols: number, rows: number, cw: number, ch: number, scatterStateTexture: WebGLTexture): void;
17
+ handleMouseMove(x: number, y: number, t: number): void;
18
+ handleMouseLeave(): void;
19
+ reset(gl: WebGL2RenderingContext): void;
20
+ tick(gl: WebGL2RenderingContext, canvas: HTMLCanvasElement): void;
21
+ };
22
+ export {};
@@ -0,0 +1,20 @@
1
+ interface SpreadRefs {
2
+ spreadEnabledRef: {
3
+ current: boolean;
4
+ };
5
+ scatterCharsRef: {
6
+ current: string;
7
+ };
8
+ spreadExpandDurationRef: {
9
+ current: number;
10
+ };
11
+ spreadSpeedRef: {
12
+ current: number;
13
+ };
14
+ }
15
+ export declare function createSpreadEffect({ spreadEnabledRef, scatterCharsRef, spreadExpandDurationRef, spreadSpeedRef, }: SpreadRefs): {
16
+ setup(gl: WebGL2RenderingContext, cols: number, rows: number, cw: number, ch: number, spreadStateTexture: WebGLTexture): void;
17
+ handleClick(e: MouseEvent, canvas: HTMLCanvasElement): void;
18
+ tick(gl: WebGL2RenderingContext): void;
19
+ };
20
+ export {};
@@ -0,0 +1,3 @@
1
+ declare function createShader(gl: WebGLRenderingContext, type: number, src: string): WebGLShader | null;
2
+ declare function createProgram(gl: WebGLRenderingContext, vert: WebGLShader, frag: WebGLShader): WebGLProgram | null;
3
+ export { createProgram, createShader };
@@ -0,0 +1,513 @@
1
+ import { useEffect as e, useRef as t } from "react";
2
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
3
+ //#region src/lib/ascii-utils.ts
4
+ var i = [
5
+ [.25, .25],
6
+ [.75, .25],
7
+ [.25, .5],
8
+ [.75, .5],
9
+ [.25, .75],
10
+ [.75, .75]
11
+ ];
12
+ function a(e, t, n) {
13
+ let r = [], a = document.createElement("canvas");
14
+ a.width = t, a.height = n;
15
+ let o = a.getContext("2d", { willReadFrequently: !0 });
16
+ if (!o) return [];
17
+ o.font = `${n}px monospace`, o.textBaseline = "top";
18
+ let s = t / 5;
19
+ for (let a of e) {
20
+ o.fillStyle = "black", o.fillRect(0, 0, t, n), o.fillStyle = "white", o.fillText(a, 0, 0);
21
+ let e = o.getImageData(0, 0, t, n), c = [];
22
+ for (let [r, a] of i) {
23
+ let i = r * t, o = a * n, l = 0, u = 0;
24
+ for (let n = Math.floor(i - s); n <= Math.ceil(i + s); n += 1) for (let r = Math.floor(o - s); r <= Math.ceil(o + s); r += 1) {
25
+ let a = i - n, c = o - r;
26
+ if (a * a + c * c <= s * s) {
27
+ let i = (r * t + n) * 4, a = e.data[i];
28
+ l += a / 255, u++;
29
+ }
30
+ }
31
+ let d = u > 0 ? l / u : 0;
32
+ c.push(d);
33
+ }
34
+ r.push({
35
+ char: a,
36
+ vector: c
37
+ });
38
+ }
39
+ let c = [
40
+ ,
41
+ ,
42
+ ,
43
+ ,
44
+ ,
45
+ ,
46
+ ].fill(0);
47
+ for (let { vector: e } of r) for (let t = 0; t < 6; ++t) e[t] > c[t] && (c[t] = e[t]);
48
+ for (let { vector: e } of r) for (let t = 0; t < 6; ++t) e[t] /= c[t];
49
+ return r;
50
+ }
51
+ //#endregion
52
+ //#region src/lib/ascii-props.ts
53
+ var o = " `.',-_:!;|\"~+^lr[](\\/L)>t<v=Tz?icf1{sIxY*jJno}CZyVwmSXRqM$O%#9&NW0Q@";
54
+ function s(e, t, n, r, i, a, o) {
55
+ let s = !!i, c = typeof i == "object" ? i : {}, l = c.style ?? "brighten", u = s && l !== "scatter", d = s && l === "scatter", f = c.scatterChars ?? "->o", p = c.trailLen ?? 15, m = c.trailDecay ?? 10, h = c.duration ?? 1, g = c.radius ?? (l === "scatter" ? .05 : .08), _ = c.brightness ?? 2, v = !!a, y = typeof a == "object" ? a : {}, b = y.style ?? "ripple", x = v && b !== "spread", S = v && b === "spread", C = y.brightness ?? 1.1, w = y.speed ?? 2, T = y.spreadExpandDuration ?? 1.5, E = y.spreadSpeed ?? 7.5, D = !!o, O = typeof o == "object" ? o : {}, k = O.type ?? "random", A = O.duration ?? .4, j = Math.max(20, Math.min(350, Math.round(e))), M = Math.max(0, Math.min(2, t)), N = Math.max(0, Math.min(2, n)), P = Math.max(0, Math.min(1, r));
56
+ A = Math.max(.1, Math.min(4, A)), p = Math.max(0, Math.min(30, Math.round(p))), m = Math.max(1, Math.min(15, m)), h = Math.max(.1, Math.min(4, h)), g = Math.max(.03, Math.min(.2, g)), _ = Math.max(.2, Math.min(5, _)), C = Math.max(1.05, Math.min(2, C)), w = Math.max(.5, Math.min(4, w)), T = Math.max(.5, Math.min(5, T)), E = Math.max(.5, Math.min(10, E));
57
+ let F;
58
+ return F = D ? k === "diagonal" ? 1 : k === "radial" ? 2 : 3 : 0, {
59
+ numCols: j,
60
+ brightness: M,
61
+ saturation: N,
62
+ bgOpacity: P,
63
+ mouseEnabled: s,
64
+ mouseStyle: l,
65
+ brightenEnabled: u,
66
+ scatterEnabled: d,
67
+ scatterChars: f,
68
+ trailLen: p,
69
+ trailDecay: m,
70
+ duration: h,
71
+ mouseRadius: g,
72
+ mouseBrightness: _,
73
+ clickEnabled: x,
74
+ clickBrightness: C,
75
+ clickSpeed: w,
76
+ spreadEnabled: S,
77
+ spreadExpandDuration: T,
78
+ spreadSpeed: E,
79
+ revealEnabled: D,
80
+ revealType: k,
81
+ revealDuration: A,
82
+ revealEffectFlag: F
83
+ };
84
+ }
85
+ //#endregion
86
+ //#region src/lib/webgl-utils.ts
87
+ function c(e, t, n) {
88
+ let r = e.createShader(t);
89
+ if (!r) throw Error("Failed to create shader");
90
+ return e.shaderSource(r, n), e.compileShader(r), e.getShaderParameter(r, e.COMPILE_STATUS) ? r : (console.log(e.getShaderInfoLog(r)), e.deleteShader(r), null);
91
+ }
92
+ function l(e, t, n) {
93
+ let r = e.createProgram();
94
+ return e.attachShader(r, t), e.attachShader(r, n), e.linkProgram(r), e.getProgramParameter(r, e.LINK_STATUS) ? r : (console.log(e.getProgramInfoLog(r)), e.deleteProgram(r), null);
95
+ }
96
+ //#endregion
97
+ //#region src/shaders/vertex.glsl
98
+ var u = "#version 300 es\n\nin vec2 a_position;\n\nvoid main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n}", d = "#version 300 es\n\nprecision highp float;\nprecision highp usampler2D;\n\nuniform int u_revealEffectFlag;\nuniform bool u_videoMode;\n\nuniform float u_revealProgress;\nuniform float u_brightness;\nuniform float u_saturation;\nuniform float u_bgOpacity;\n\nuniform bool u_mouseEffect;\n#define MOUSE_TRAIL_LEN 30 \nuniform vec2 u_mousePositions[MOUSE_TRAIL_LEN];\nuniform float u_mouseLifeFracs[MOUSE_TRAIL_LEN]; \nuniform float u_mouseRadius;\nuniform float u_mouseBrightness;\n\nuniform bool u_clickEffect;\n#define MAX_RIPPLES 10\nuniform vec2 u_ripplePositions[MAX_RIPPLES];\nuniform float u_rippleRadii[MAX_RIPPLES]; \nuniform float u_rippleBrightnesses[MAX_RIPPLES]; \n\nuniform bool u_scatterEffect;\nuniform usampler2D u_scatterStateTexture; \nuniform sampler2D u_scatterAtlas; \nuniform float u_scatterNumChars;\n\nuniform bool u_spreadEffect;\nuniform usampler2D u_spreadStateTexture;\n\nuniform bool u_shapeMatching;\n\nuniform sampler2D u_texture;\nuniform sampler2D u_atlas;\nuniform usampler2D u_fboTexture;\nuniform vec2 u_resolution;\nuniform vec2 u_cellsize;\nuniform float u_numChars;\nuniform vec2 u_gridSize;\nuniform vec2 u_cropOffset;\nuniform vec2 u_cropScale;\n\nout vec4 fragColor;\n\nfloat hash(vec2 p) {\n vec3 p3 = fract(vec3(p.xyx) * 0.1031); \n p3 += dot(p3, p3.yzx + 33.33); \n return fract((p3.x + p3.y) * p3.z); \n}\n\nvec3 rgb2hsl(vec3 c) {\n float maxC = max(c.r, max(c.g, c.b));\n float minC = min(c.r, min(c.g, c.b));\n float l = (maxC + minC) * 0.5;\n float d = maxC - minC;\n float h = 0.0, s = 0.0;\n if (d > 0.0) {\n s = d / (1.0 - abs(2.0 * l - 1.0));\n if (maxC == c.r) h = mod((c.g - c.b) / d, 6.0) / 6.0;\n else if (maxC == c.g) h = ((c.b - c.r) / d + 2.0) / 6.0;\n else h = ((c.r - c.g) / d + 4.0) / 6.0;\n }\n return vec3(h, s, l);\n}\n\nvec3 hsl2rgb(vec3 c) {\n float h = c.x, s = c.y, l = c.z;\n float a = s * (1.0 - abs(2.0 * l - 1.0));\n vec3 rgb = clamp(abs(mod(h * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);\n return l + a * (rgb - 0.5);\n}\n\nvoid main() {\n vec2 fragCoord = vec2(gl_FragCoord.x, u_resolution.y - gl_FragCoord.y); \n vec2 cellCoord = floor(fragCoord / u_cellsize);\n\n if (u_revealEffectFlag == 1) {\n \n float revealThreshold = (cellCoord.x + cellCoord.y) / (u_gridSize.x + u_gridSize.y - 2.0);\n if (revealThreshold > u_revealProgress) {\n fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n return;\n }\n } else if (u_revealEffectFlag == 2) {\n float dist = length(cellCoord / u_gridSize - vec2(0.5));\n float revealThreshold = dist / 0.7071; \n if (revealThreshold > u_revealProgress) {\n fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n return;\n }\n } else if (u_revealEffectFlag == 3) {\n float revealThreshold = hash(cellCoord);\n if (revealThreshold > u_revealProgress) {\n fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n return;\n }\n }\n\n vec2 cellCenter = (cellCoord + 0.5) * u_cellsize; \n \n vec2 rawUV = u_videoMode ? (fragCoord / u_resolution) : (cellCenter / u_resolution);\n vec2 sampleUV = u_cropOffset + rawUV * u_cropScale;\n\n vec3 cellColor = texture(u_texture, sampleUV).rgb;\n float luminosity = dot(cellColor, vec3(0.299, 0.587, 0.114)); \n vec3 hsl = rgb2hsl(cellColor);\n hsl.z = clamp(hsl.z * u_brightness, 0.0, 1.0);\n hsl.y = clamp(hsl.y * u_saturation, 0.0, 1.0);\n cellColor = hsl2rgb(hsl);\n\n vec2 withinCellPos = fract(fragCoord / u_cellsize);\n\n \n float glyphMask = 0.0;\n if (!u_videoMode) {\n uint charInd;\n if (u_shapeMatching) {\n charInd = texelFetch(u_fboTexture, ivec2(cellCoord), 0).r;\n } else {\n charInd = uint(floor(luminosity * (u_numChars - 1.0)));\n }\n float atlasU = (float(charInd) + withinCellPos.x) / u_numChars;\n float atlasV = withinCellPos.y;\n glyphMask = texture(u_atlas, vec2(atlasU, atlasV)).r;\n }\n\n vec3 bgColor = cellColor * u_bgOpacity;\n\n bool scatterHit = false;\n vec3 scatterColor = vec3(0.0);\n if (u_scatterEffect) {\n uint state = texelFetch(u_scatterStateTexture, ivec2(cellCoord), 0).r;\n if (state > 0u) {\n int idx = int(state) - 1;\n float su = (float(idx) + withinCellPos.x) / u_scatterNumChars;\n float mask = texture(u_scatterAtlas, vec2(su, withinCellPos.y)).r;\n \n vec3 scatterBg = u_videoMode ? cellColor : bgColor;\n scatterColor = mix(scatterBg, vec3(1.0), mask);\n scatterHit = true;\n }\n }\n \n if (!scatterHit && u_spreadEffect) { \n uint state = texelFetch(u_spreadStateTexture, ivec2(cellCoord), 0).r;\n if (state > 0u) {\n int idx = int(state) - 1;\n float su = (float(idx) + withinCellPos.x) / u_scatterNumChars;\n float mask = texture(u_scatterAtlas, vec2(su, withinCellPos.y)).r;\n vec3 scatterBg = u_videoMode ? cellColor : bgColor;\n scatterColor = mix(scatterBg, vec3(1.0), mask);\n scatterHit = true;\n }\n }\n\n vec3 finalColor;\n if (scatterHit) {\n finalColor = scatterColor;\n } else {\n finalColor = u_videoMode ? cellColor : mix(bgColor, cellColor, glyphMask);\n\n if (u_mouseEffect) {\n bool inside = false;\n for (int i = 0; i < MOUSE_TRAIL_LEN; i++) {\n if (u_mouseLifeFracs[i] <= 0.0) {\n continue;\n }\n float r = u_mouseRadius * u_mouseLifeFracs[i];\n if (distance(cellCenter, u_mousePositions[i]) < r) {\n inside = true;\n break;\n }\n }\n if (inside) {\n finalColor = clamp(finalColor * u_mouseBrightness, 0.0, 1.0);\n }\n }\n\n if (u_clickEffect) {\n float boost = 1.0;\n for (int i = 0; i < MAX_RIPPLES; i++) {\n if (u_rippleBrightnesses[i] <= 1.0) {\n continue;\n }\n if (distance(cellCenter, u_ripplePositions[i]) < u_rippleRadii[i]) {\n boost *= u_rippleBrightnesses[i];\n }\n }\n if (boost > 1.0) {\n finalColor = clamp(finalColor * boost, 0.0, 1.0);\n }\n }\n }\n\n fragColor = vec4(finalColor, 1.0);\n}", f = "#version 300 es\n\nprecision highp float;\nprecision highp usampler2D;\n\nuniform sampler2D u_texture;\nuniform vec2 u_resolution;\nuniform vec2 u_cellsize;\nuniform int u_circleN;\nuniform sampler2D u_charVectors;\nuniform int u_numCharsInt;\nuniform float u_shapeExponent;\nuniform vec2 u_cropOffset;\nuniform vec2 u_cropScale;\n\nout uvec4 fragCharInd;\n\nvoid main() {\n \n ivec2 cellCoord = ivec2(floor(gl_FragCoord.xy));\n\n const vec2 CIRCLES[6] = vec2[6](\n vec2(0.25, 0.25), vec2(0.75, 0.25),\n vec2(0.25, 0.50), vec2(0.75, 0.50),\n vec2(0.25, 0.75), vec2(0.75, 0.75)\n );\n vec2 radiusUV = vec2(u_cellsize.x / 5.0) / u_resolution * u_cropScale; \n\n float sv[6];\n for (int ci = 0; ci < 6; ci++) {\n \n vec2 centerUV = u_cropOffset + (vec2(cellCoord) + CIRCLES[ci]) * u_cellsize / u_resolution * u_cropScale;\n float total = 0.0;\n int count = 0;\n for (int dx = -u_circleN; dx <= u_circleN; dx++) {\n for (int dy = -u_circleN; dy <= u_circleN; dy++) {\n if (dx*dx + dy*dy <= u_circleN*u_circleN) {\n vec2 sampleUV = centerUV + vec2(float(dx), float(dy)) * radiusUV / float(u_circleN);\n vec3 sc = texture(u_texture, sampleUV).rgb;\n total += dot(sc, vec3(0.299, 0.587, 0.114));\n count++;\n }\n }\n }\n \n sv[ci] = count > 0 ? total / float(count) : 0.0;\n }\n\n float maxVal = 0.0;\n for (int d = 0; d < 6; d++) maxVal = max(maxVal, sv[d]);\n if (maxVal > 0.0) {\n for (int d = 0; d < 6; d++) sv[d] = pow(sv[d] / maxVal, u_shapeExponent) * maxVal;\n }\n\n \n uint bestChar = 0u;\n float bestDist = 1e10;\n for (int i = 0; i < u_numCharsInt; i++) {\n vec4 r0 = texelFetch(u_charVectors, ivec2(i, 0), 0);\n vec4 r1 = texelFetch(u_charVectors, ivec2(i, 1), 0);\n float dist = 0.0;\n float diffs[6] = float[6](\n sv[0] - r0.r, sv[1] - r0.g, sv[2] - r0.b, sv[3] - r0.a,\n sv[4] - r1.r, sv[5] - r1.g\n );\n for (int d = 0; d < 6; d++) {\n dist += diffs[d] * diffs[d];\n }\n if (dist < bestDist) { \n bestDist = dist; bestChar = uint(i); \n }\n }\n\n fragCharInd = uvec4(bestChar, 0u, 0u, 0u);\n}";
99
+ //#endregion
100
+ //#region src/lib/create-gl-resources.ts
101
+ function p(e) {
102
+ let t = c(e, e.VERTEX_SHADER, u), n = c(e, e.FRAGMENT_SHADER, d);
103
+ if (!t || !n) return null;
104
+ let r = l(e, t, n);
105
+ if (!r) return null;
106
+ let i = new Float32Array([
107
+ 1,
108
+ 1,
109
+ -1,
110
+ 1,
111
+ 1,
112
+ -1,
113
+ -1,
114
+ -1,
115
+ -1,
116
+ 1,
117
+ 1,
118
+ -1
119
+ ]), a = e.createBuffer();
120
+ e.bindBuffer(e.ARRAY_BUFFER, a), e.bufferData(e.ARRAY_BUFFER, i, e.STATIC_DRAW);
121
+ let o = e.getAttribLocation(r, "a_position");
122
+ e.vertexAttribPointer(o, 2, e.FLOAT, !1, 0, 0), e.enableVertexAttribArray(o), e.useProgram(r);
123
+ let s = e.createTexture();
124
+ e.activeTexture(e.TEXTURE0), e.bindTexture(e.TEXTURE_2D, s), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.NEAREST), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.NEAREST);
125
+ let p = e.getUniformLocation(r, "u_texture");
126
+ e.uniform1i(p, 0);
127
+ let m = e.createTexture();
128
+ e.activeTexture(e.TEXTURE1), e.bindTexture(e.TEXTURE_2D, m), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.NEAREST), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.NEAREST);
129
+ let h = e.getUniformLocation(r, "u_atlas");
130
+ e.uniform1i(h, 1);
131
+ let g = e.createTexture();
132
+ e.activeTexture(e.TEXTURE2), e.bindTexture(e.TEXTURE_2D, g), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.NEAREST), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.NEAREST);
133
+ let _ = c(e, e.FRAGMENT_SHADER, f);
134
+ if (!_) return null;
135
+ let v = l(e, t, _);
136
+ if (!v) return null;
137
+ e.useProgram(v), e.uniform1i(e.getUniformLocation(v, "u_texture"), 0), e.uniform1i(e.getUniformLocation(v, "u_charVectors"), 2);
138
+ let y = e.getUniformLocation(v, "u_resolution"), b = e.getUniformLocation(v, "u_cellsize"), x = e.getUniformLocation(v, "u_circleN"), S = e.getUniformLocation(v, "u_numCharsInt"), C = e.getUniformLocation(v, "u_shapeExponent"), w = e.getUniformLocation(v, "u_cropOffset"), T = e.getUniformLocation(v, "u_cropScale"), E = e.createTexture();
139
+ e.activeTexture(e.TEXTURE3), e.bindTexture(e.TEXTURE_2D, E), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.NEAREST), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.NEAREST);
140
+ let D = e.createFramebuffer(), O = e.createTexture();
141
+ e.activeTexture(e.TEXTURE4), e.bindTexture(e.TEXTURE_2D, O), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.NEAREST), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.NEAREST);
142
+ let k = e.createTexture();
143
+ e.activeTexture(e.TEXTURE5), e.bindTexture(e.TEXTURE_2D, k), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.NEAREST), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.NEAREST);
144
+ let A = e.createTexture();
145
+ return e.activeTexture(e.TEXTURE6), e.bindTexture(e.TEXTURE_2D, A), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.NEAREST), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.NEAREST), e.useProgram(r), e.uniform1i(e.getUniformLocation(r, "u_fboTexture"), 3), e.uniform1i(e.getUniformLocation(r, "u_scatterAtlas"), 4), e.uniform1i(e.getUniformLocation(r, "u_scatterStateTexture"), 5), e.uniform1i(e.getUniformLocation(r, "u_spreadStateTexture"), 6), {
146
+ program: r,
147
+ pass1Program: v,
148
+ buffer: a,
149
+ vertShader: t,
150
+ fragShader: n,
151
+ pass1FragShader: _,
152
+ texture: s,
153
+ atlasTexture: m,
154
+ charVectorsTexture: g,
155
+ fboTexture: E,
156
+ fbo: D,
157
+ scatterAtlasTexture: O,
158
+ scatterStateTexture: k,
159
+ spreadStateTexture: A,
160
+ p1ResLoc: y,
161
+ p1CellsizeLoc: b,
162
+ p1CircleNLoc: x,
163
+ p1NumCharsLoc: S,
164
+ p1ExponentLoc: C,
165
+ p1CropOffsetLoc: w,
166
+ p1CropScaleLoc: T,
167
+ revealEffectFlagLoc: e.getUniformLocation(r, "u_revealEffectFlag"),
168
+ mouseEffectFlagLoc: e.getUniformLocation(r, "u_mouseEffect"),
169
+ clickEffectFlagLoc: e.getUniformLocation(r, "u_clickEffect"),
170
+ shapeMatchingLoc: e.getUniformLocation(r, "u_shapeMatching"),
171
+ revealProgressLoc: e.getUniformLocation(r, "u_revealProgress"),
172
+ brightnessLoc: e.getUniformLocation(r, "u_brightness"),
173
+ saturationLoc: e.getUniformLocation(r, "u_saturation"),
174
+ bgOpacityLoc: e.getUniformLocation(r, "u_bgOpacity"),
175
+ mouseBrightnessLoc: e.getUniformLocation(r, "u_mouseBrightness"),
176
+ mousePositionsLoc: e.getUniformLocation(r, "u_mousePositions"),
177
+ mouseLifeFracsLoc: e.getUniformLocation(r, "u_mouseLifeFracs"),
178
+ mouseRadiusLoc: e.getUniformLocation(r, "u_mouseRadius"),
179
+ ripplePositionsLoc: e.getUniformLocation(r, "u_ripplePositions"),
180
+ rippleRadiiLoc: e.getUniformLocation(r, "u_rippleRadii"),
181
+ rippleBrightnessesLoc: e.getUniformLocation(r, "u_rippleBrightnesses"),
182
+ scatterEffectFlagLoc: e.getUniformLocation(r, "u_scatterEffect"),
183
+ scatterNumCharsLoc: e.getUniformLocation(r, "u_scatterNumChars"),
184
+ spreadEffectFlagLoc: e.getUniformLocation(r, "u_spreadEffect"),
185
+ videoModeLoc: e.getUniformLocation(r, "u_videoMode"),
186
+ resLoc: e.getUniformLocation(r, "u_resolution"),
187
+ sizeLoc: e.getUniformLocation(r, "u_cellsize"),
188
+ numLoc: e.getUniformLocation(r, "u_numChars"),
189
+ gridSizeLoc: e.getUniformLocation(r, "u_gridSize"),
190
+ cropOffsetLoc: e.getUniformLocation(r, "u_cropOffset"),
191
+ cropScaleLoc: e.getUniformLocation(r, "u_cropScale")
192
+ };
193
+ }
194
+ //#endregion
195
+ //#region src/lib/scatter-effect.ts
196
+ function m({ scatterEnabledRef: e, mouseRadiusRef: t, durationRef: n, scatterCharsRef: r }) {
197
+ let i = new Uint8Array(), a = new Float32Array(), o = new Float32Array(), s = new Uint8Array(), c = null, l = null, u = 0, d = 0, f = -1, p = () => Math.floor(Math.random() * r.current.length), m = 0, h = 0, g = 1, _ = 1, v = null;
198
+ return {
199
+ setup(e, t, n, r, f, p) {
200
+ m = t, h = n, g = r, _ = f, v = p;
201
+ let y = t * n;
202
+ i = new Uint8Array(y), a = new Float32Array(y), o = new Float32Array(y), s = new Uint8Array(y), c = null, l = null, u = 0, d = 0, e.activeTexture(e.TEXTURE5), e.bindTexture(e.TEXTURE_2D, p), e.texImage2D(e.TEXTURE_2D, 0, e.R8UI, t, n, 0, e.RED_INTEGER, e.UNSIGNED_BYTE, i);
203
+ },
204
+ handleMouseMove(t, n, r) {
205
+ e.current && (l = c, c = {
206
+ x: t,
207
+ y: n,
208
+ t: r
209
+ });
210
+ },
211
+ handleMouseLeave() {
212
+ c = null, l = null, u = 0, d = 0;
213
+ },
214
+ reset(e) {
215
+ i.fill(0), v && m > 0 && (e.activeTexture(e.TEXTURE5), e.bindTexture(e.TEXTURE_2D, v), e.texSubImage2D(e.TEXTURE_2D, 0, 0, 0, m, h, e.RED_INTEGER, e.UNSIGNED_BYTE, i));
216
+ },
217
+ tick(r, y) {
218
+ if (!e.current || m === 0 || h === 0) return;
219
+ let b = performance.now(), x = f < 0 ? 0 : Math.min(.1, (b - f) / 1e3);
220
+ f = b;
221
+ let S = Math.min(y.width, y.height) * t.current, C = !1;
222
+ if (c && b - c.t < 80) {
223
+ if (l) {
224
+ let e = (c.t - l.t) / 1e3;
225
+ if (e > 0 && e < .2) {
226
+ let t = (c.x - l.x) / e, n = (c.y - l.y) / e;
227
+ u = u * .7 + t * .3, d = d * .7 + n * .3;
228
+ }
229
+ } else u = 0, d = 0;
230
+ Math.hypot(u, d) > g * 2 && (C = !0);
231
+ } else u *= .6, d *= .6;
232
+ if (s.fill(0), C && c && S > 0) {
233
+ let e = c.x, t = c.y, n = Math.max(0, Math.floor((e - S) / g)), r = Math.min(m - 1, Math.floor((e + S) / g)), l = Math.max(0, Math.floor((t - S) / _)), u = Math.min(h - 1, Math.floor((t + S) / _));
234
+ for (let c = l; c <= u; c++) for (let l = n; l <= r; l++) {
235
+ let n = (l + .5) * g, r = (c + .5) * _;
236
+ if (Math.hypot(n - e, r - t) > S) continue;
237
+ let u = c * m + l;
238
+ s[u] = 1, i[u] === 0 && (i[u] = 1 + p(), o[u] = .5 + Math.random() * 2), a[u] = 1;
239
+ }
240
+ }
241
+ let w = n.current;
242
+ if (x > 0) for (let e = 0; e < h; e++) for (let t = 0; t < m; t++) {
243
+ let n = e * m + t;
244
+ if (i[n] === 0 || s[n]) continue;
245
+ let r = t > 0 ? i[n - 1] !== 0 : !1, c = t < m - 1 ? i[n + 1] !== 0 : !1, l = e > 0 ? i[n - m] !== 0 : !1, u = e < h - 1 ? i[n + m] !== 0 : !1, d = (!r || !c || !l || !u ? 5 : 1) * o[n] / w;
246
+ a[n] -= d * x, a[n] <= 0 && (i[n] = 0);
247
+ }
248
+ r.activeTexture(r.TEXTURE5), r.bindTexture(r.TEXTURE_2D, v), r.texSubImage2D(r.TEXTURE_2D, 0, 0, 0, m, h, r.RED_INTEGER, r.UNSIGNED_BYTE, i);
249
+ }
250
+ };
251
+ }
252
+ //#endregion
253
+ //#region src/lib/brighten-effect.ts
254
+ function h({ brightenEnabledRef: e, trailLenRef: t, durationRef: n, trailDecayRef: r }) {
255
+ let i = [];
256
+ return {
257
+ handleMouseMove(n, r, a) {
258
+ e.current && (i.push({
259
+ x: n,
260
+ y: r,
261
+ t: a
262
+ }), i.length > t.current && i.shift());
263
+ },
264
+ tick(a, o, s) {
265
+ if (!e.current) return;
266
+ let c = performance.now(), l = new Float32Array(t.current * 2), u = new Float32Array(t.current);
267
+ for (let e = 0; e < i.length; e++) {
268
+ let t = c - i[e].t, a = Math.max(0, 1 - t / (n.current * 1e3)) ** r.current;
269
+ l[e * 2] = i[e].x, l[e * 2 + 1] = i[e].y, u[e] = a;
270
+ }
271
+ a.uniform2fv(o, l), a.uniform1fv(s, u);
272
+ }
273
+ };
274
+ }
275
+ //#endregion
276
+ //#region src/lib/click-effect.ts
277
+ var g = 10;
278
+ function _({ clickEnabledRef: e, clickSpeedRef: t, clickBrightnessRef: n }) {
279
+ let r = [];
280
+ return {
281
+ handleClick(t, n) {
282
+ if (!e.current) return;
283
+ let i = n.getBoundingClientRect(), a = (t.clientX - i.left) * (n.width / i.width), o = (t.clientY - i.top) * (n.height / i.height);
284
+ r.push({
285
+ x: a,
286
+ y: o,
287
+ t: performance.now()
288
+ }), r.length > g && r.shift();
289
+ },
290
+ tick(i, a, o, s, c) {
291
+ if (!e.current) return;
292
+ let l = performance.now(), u = Math.hypot(a.width, a.height), d = new Float32Array(g * 2), f = new Float32Array(g), p = new Float32Array(g);
293
+ for (; r.length > 0 && (l - r[0].t) * t.current >= u;) r.shift();
294
+ for (let e = 0; e < r.length; e++) {
295
+ let i = (l - r[e].t) * t.current, a = i / u, o = 1 + (n.current - 1) * (1 - a ** 2);
296
+ d[e * 2] = r[e].x, d[e * 2 + 1] = r[e].y, f[e] = i, p[e] = o;
297
+ }
298
+ i.uniform2fv(o, d), i.uniform1fv(s, f), i.uniform1fv(c, p);
299
+ }
300
+ };
301
+ }
302
+ //#endregion
303
+ //#region src/lib/spread-effect.ts
304
+ function v({ spreadEnabledRef: e, scatterCharsRef: t, spreadExpandDurationRef: n, spreadSpeedRef: r }) {
305
+ let i = new Uint8Array(), a = new Uint8Array(), o = 0, s = 0, c = 1, l = 1, u = null, d = -1, f = -1, p = () => Math.floor(Math.random() * t.current.length), m = [
306
+ [-1, 0],
307
+ [1, 0],
308
+ [0, -1],
309
+ [0, 1]
310
+ ];
311
+ return {
312
+ setup(e, t, n, r, p, m) {
313
+ o = t, s = n, c = r, l = p, u = m;
314
+ let h = t * n;
315
+ i = new Uint8Array(h), a = new Uint8Array(h), f = -1, d = -1, e.activeTexture(e.TEXTURE6), e.bindTexture(e.TEXTURE_2D, m), e.texImage2D(e.TEXTURE_2D, 0, e.R8UI, t, n, 0, e.RED_INTEGER, e.UNSIGNED_BYTE, i);
316
+ },
317
+ handleClick(t, r) {
318
+ if (!e.current || o === 0) return;
319
+ let u = r.getBoundingClientRect(), d = (t.clientX - u.left) * (r.width / u.width), m = (t.clientY - u.top) * (r.height / u.height), h = Math.floor(d / c), g = Math.floor(m / l);
320
+ if (h < 0 || h >= o || g < 0 || g >= s) return;
321
+ let _ = g * o + h;
322
+ i[_] = 1 + p(), a[_] = 1, f = performance.now() + n.current * 1e3;
323
+ },
324
+ tick(t) {
325
+ if (!e.current || o === 0 || s === 0) return;
326
+ let n = performance.now(), c = d < 0 ? 0 : Math.min(.1, (n - d) / 1e3);
327
+ d = n;
328
+ let l = f > 0 && n < f;
329
+ if (!l && f > 0) {
330
+ for (let e = 0; e < i.length; e++) a[e] === 1 && (a[e] = 2);
331
+ f = -1;
332
+ }
333
+ if (c > 0) for (let e = 0; e < s; e++) for (let t = 0; t < o; t++) {
334
+ let n = e * o + t;
335
+ if (a[n] !== 0) {
336
+ if (l && a[n] === 1) for (let [n, l] of m) {
337
+ let u = e + n, d = t + l;
338
+ if (u < 0 || u >= s || d < 0 || d >= o) continue;
339
+ let f = u * o + d;
340
+ a[f] === 0 && Math.random() < r.current * c && (i[f] = 1 + p(), a[f] = 1);
341
+ }
342
+ else if (a[n] === 2) for (let [l, u] of m) {
343
+ let d = e + l, f = t + u;
344
+ if (d < 0 || d >= s || f < 0 || f >= o || i[d * o + f] === 0) {
345
+ Math.random() < r.current * c && (i[n] = 0, a[n] = 0);
346
+ break;
347
+ }
348
+ }
349
+ }
350
+ }
351
+ t.activeTexture(t.TEXTURE6), t.bindTexture(t.TEXTURE_2D, u), t.texSubImage2D(t.TEXTURE_2D, 0, 0, 0, o, s, t.RED_INTEGER, t.UNSIGNED_BYTE, i);
352
+ }
353
+ };
354
+ }
355
+ //#endregion
356
+ //#region src/components/VideoAscii.tsx
357
+ function y({ src: i, videoMode: c = !1, numColsRaw: l = 250, brightnessRaw: u = 1, saturationRaw: d = 1, bgOpacityRaw: f = .3, revealEffect: g = !1, chars: y = o, mouseEffect: b = !0, clickEffect: x = !0, charMode: S = "shape", className: C }) {
358
+ let w = t(null), T = t(null), E = t(null), D = t(null), O = t(null), { numCols: k, brightness: A, saturation: j, bgOpacity: M, mouseEnabled: N, mouseStyle: P, brightenEnabled: F, scatterEnabled: I, scatterChars: L, trailLen: R, trailDecay: z, duration: B, mouseRadius: V, mouseBrightness: H, clickEnabled: U, clickBrightness: W, clickSpeed: G, spreadEnabled: K, spreadExpandDuration: q, spreadSpeed: ee, revealEnabled: te, revealDuration: ne, revealEffectFlag: re } = s(l, u, d, f, b, x, g), ie = t(A), ae = t(j), oe = t(M), se = t(N), ce = t(P), J = t(F), Y = t(I), le = t(H), X = t(V), ue = t(R), de = t(z), Z = t(B), Q = t(L), fe = t(U), pe = t(W), me = t(G), he = t(K), ge = t(q), _e = t(ee), ve = t(k), ye = t(c);
359
+ e(() => {
360
+ ie.current = A, ae.current = j, oe.current = M, se.current = N, ce.current = P, J.current = F, Y.current = I, le.current = H, X.current = V, ue.current = R, de.current = z, Z.current = B, Q.current !== L && $.current ? (Q.current = L, xe.current?.()) : Q.current = L, fe.current = U, pe.current = W, me.current = G, he.current = K, ge.current = q, _e.current = ee, ve.current = k, ye.current = c;
361
+ }, [
362
+ A,
363
+ j,
364
+ M,
365
+ N,
366
+ P,
367
+ F,
368
+ I,
369
+ H,
370
+ V,
371
+ R,
372
+ z,
373
+ B,
374
+ L,
375
+ U,
376
+ W,
377
+ G,
378
+ K,
379
+ q,
380
+ ee,
381
+ k,
382
+ c
383
+ ]);
384
+ let be = t(null), xe = t(null), $ = t(!1);
385
+ return e(() => {
386
+ $.current && be.current?.(k);
387
+ }, [k]), e(() => {
388
+ $.current = !1;
389
+ let e = [], t = 0, n = 0, r = 1, o = 1, s = w.current;
390
+ if (!s) return;
391
+ let c = T.current;
392
+ if (!c) return;
393
+ let l = s.getContext("webgl2");
394
+ if (!l) return;
395
+ l.pixelStorei(l.UNPACK_ALIGNMENT, 1);
396
+ let u = p(l);
397
+ if (!u) return;
398
+ let { program: d, pass1Program: f } = u;
399
+ D.current = u.atlasTexture, O.current = u.scatterAtlasTexture, l.uniform1i(u.shapeMatchingLoc, +(S === "shape")), l.uniform1i(u.revealEffectFlagLoc, re), l.uniform1f(u.revealProgressLoc, 0);
400
+ let g = m({
401
+ scatterEnabledRef: Y,
402
+ mouseRadiusRef: X,
403
+ durationRef: Z,
404
+ scatterCharsRef: Q
405
+ }), b = h({
406
+ brightenEnabledRef: J,
407
+ trailLenRef: ue,
408
+ durationRef: Z,
409
+ trailDecayRef: de
410
+ }), x = _({
411
+ clickEnabledRef: fe,
412
+ clickSpeedRef: me,
413
+ clickBrightnessRef: pe
414
+ }), C = v({
415
+ spreadEnabledRef: he,
416
+ scatterCharsRef: Q,
417
+ spreadExpandDurationRef: ge,
418
+ spreadSpeedRef: _e
419
+ }), k, A = -1, j = -1, M = 0, N = Array.isArray(i) ? i : [i], P = N.length > 1, F = document.createElement("canvas"), I = F.getContext("2d"), L = (i) => {
420
+ r = Math.max(1, Math.floor(s.width / i));
421
+ let c = r * 2;
422
+ if (I.font = `${c}px monospace`, o = Math.max(1, Math.round(c * r / I.measureText("M").width)), t = Math.floor(s.width / r), n = Math.floor(s.height / o), s.width = t * r, s.height = n * o, l.viewport(0, 0, s.width, s.height), l.useProgram(d), l.uniform2f(u.resLoc, s.width, s.height), l.useProgram(f), l.uniform2f(u.p1ResLoc, s.width, s.height), l.useProgram(d), g.setup(l, t, n, r, o, u.scatterStateTexture), C.setup(l, t, n, r, o, u.spreadStateTexture), S === "shape") {
423
+ e = a(y, r, o);
424
+ let i = e.length, s = new Float32Array(i * 8);
425
+ for (let t = 0; t < i; t++) {
426
+ let n = e[t].vector;
427
+ s[t * 4 + 0] = n[0], s[t * 4 + 1] = n[1], s[t * 4 + 2] = n[2], s[t * 4 + 3] = n[3], s[i * 4 + t * 4 + 0] = n[4], s[i * 4 + t * 4 + 1] = n[5];
428
+ }
429
+ l.activeTexture(l.TEXTURE2), l.bindTexture(l.TEXTURE_2D, u.charVectorsTexture), l.texImage2D(l.TEXTURE_2D, 0, l.RGBA32F, i, 2, 0, l.RGBA, l.FLOAT, s), l.activeTexture(l.TEXTURE3), l.bindTexture(l.TEXTURE_2D, u.fboTexture), l.texImage2D(l.TEXTURE_2D, 0, l.R8UI, t, n, 0, l.RED_INTEGER, l.UNSIGNED_BYTE, null), l.bindFramebuffer(l.FRAMEBUFFER, u.fbo), l.framebufferTexture2D(l.FRAMEBUFFER, l.COLOR_ATTACHMENT0, l.TEXTURE_2D, u.fboTexture, 0), l.bindFramebuffer(l.FRAMEBUFFER, null);
430
+ let c = Math.max(1, Math.round(r / 5));
431
+ l.useProgram(f), l.uniform2f(u.p1CellsizeLoc, r, o), l.uniform1i(u.p1CircleNLoc, c), l.uniform1i(u.p1NumCharsLoc, i), l.uniform1f(u.p1ExponentLoc, 2), l.useProgram(d);
432
+ }
433
+ l.uniform2f(u.gridSizeLoc, t, n), F.width = y.length * r, F.height = o, I.font = `${o}px monospace`, I.fillStyle = "black", I.fillRect(0, 0, y.length * r, o), I.fillStyle = "white", I.textBaseline = "top";
434
+ for (let e = 0; e < y.length; e++) I.fillText(y[e], e * r, 0);
435
+ l.activeTexture(l.TEXTURE1), l.bindTexture(l.TEXTURE_2D, D.current), l.texImage2D(l.TEXTURE_2D, 0, l.RGBA, l.RGBA, l.UNSIGNED_BYTE, F), l.uniform1f(u.numLoc, y.length), l.uniform2f(u.sizeLoc, r, o), R();
436
+ }, R = () => {
437
+ let e = Q.current;
438
+ F.width = e.length * r, F.height = o, I.fillStyle = "black", I.fillRect(0, 0, F.width, o), I.fillStyle = "white", I.textBaseline = "top", I.font = `${o}px monospace`;
439
+ for (let t = 0; t < e.length; t++) I.fillText(e[t], t * r, 0);
440
+ l.activeTexture(l.TEXTURE4), l.bindTexture(l.TEXTURE_2D, O.current), l.texImage2D(l.TEXTURE_2D, 0, l.RGBA, l.RGBA, l.UNSIGNED_BYTE, F), l.uniform1f(u.scatterNumCharsLoc, e.length), g.reset(l);
441
+ };
442
+ be.current = L, xe.current = R;
443
+ let z = (e, t) => {
444
+ s.width = Math.round(e), s.height = Math.round(t), L(ve.current);
445
+ let n = c.videoWidth / c.videoHeight, r = s.width / s.height, i = 1, a = 1, o = 0, p = 0;
446
+ r > n ? (a = n / r, p = (1 - a) / 2) : (i = r / n, o = (1 - i) / 2), l.uniform2f(u.cropOffsetLoc, o, p), l.uniform2f(u.cropScaleLoc, i, a), l.useProgram(f), l.uniform2f(u.p1CropOffsetLoc, o, p), l.uniform2f(u.p1CropScaleLoc, i, a), l.useProgram(d);
447
+ }, B = (e) => {
448
+ if (!se.current) return;
449
+ let t = s.getBoundingClientRect(), n = (e.clientX - t.left) * (s.width / t.width), r = (e.clientY - t.top) * (s.height / t.height), i = performance.now();
450
+ b.handleMouseMove(n, r, i), g.handleMouseMove(n, r, i);
451
+ };
452
+ s.addEventListener("mousemove", B);
453
+ let V = () => g.handleMouseLeave();
454
+ s.addEventListener("mouseleave", V);
455
+ let H = (e) => {
456
+ x.handleClick(e, s), C.handleClick(e, s);
457
+ };
458
+ s.addEventListener("click", H);
459
+ let U = () => {
460
+ if (l.uniform1i(u.videoModeLoc, +!!ye.current), l.uniform1f(u.brightnessLoc, ie.current), l.uniform1f(u.saturationLoc, ae.current), l.uniform1f(u.bgOpacityLoc, oe.current), l.uniform1i(u.mouseEffectFlagLoc, +!!J.current), l.uniform1i(u.scatterEffectFlagLoc, +!!Y.current), l.uniform1i(u.clickEffectFlagLoc, +!!fe.current), l.uniform1i(u.spreadEffectFlagLoc, +!!he.current), l.uniform1f(u.mouseBrightnessLoc, le.current), l.uniform1f(u.mouseRadiusLoc, Math.min(s.width, s.height) * X.current), te) {
461
+ let e = j < 0 ? 0 : Math.min(1, (performance.now() - j) / (ne * 1e3));
462
+ l.uniform1f(u.revealProgressLoc, e);
463
+ }
464
+ $.current && c.currentTime != A && c.readyState >= 2 && (l.activeTexture(l.TEXTURE0), l.bindTexture(l.TEXTURE_2D, u.texture), l.texSubImage2D(l.TEXTURE_2D, 0, 0, 0, l.RGBA, l.UNSIGNED_BYTE, c), A = c.currentTime), b.tick(l, u.mousePositionsLoc, u.mouseLifeFracsLoc), g.tick(l, s), x.tick(l, s, u.ripplePositionsLoc, u.rippleRadiiLoc, u.rippleBrightnessesLoc), C.tick(l), S === "shape" && (l.bindFramebuffer(l.FRAMEBUFFER, u.fbo), l.viewport(0, 0, t, n), l.useProgram(f), l.drawArrays(l.TRIANGLES, 0, 6), l.bindFramebuffer(l.FRAMEBUFFER, null), l.viewport(0, 0, s.width, s.height), l.useProgram(d)), l.drawArrays(l.TRIANGLES, 0, 6), k = requestAnimationFrame(U);
465
+ }, W = () => {
466
+ let e = E.current;
467
+ z(e.clientWidth || c.videoWidth, e.clientHeight || c.videoHeight), l.activeTexture(l.TEXTURE0), l.bindTexture(l.TEXTURE_2D, u.texture), l.texImage2D(l.TEXTURE_2D, 0, l.RGBA, l.RGBA, l.UNSIGNED_BYTE, c), c.play(), j = performance.now(), $.current = !0, k = requestAnimationFrame(U);
468
+ }, G = () => {
469
+ M = (M + 1) % N.length, c.src = N[M], c.load(), c.addEventListener("canplay", () => c.play(), { once: !0 });
470
+ }, K = new ResizeObserver((e) => {
471
+ let { width: t, height: n } = e[0].contentRect;
472
+ $.current && t > 0 && n > 0 && z(t, n);
473
+ });
474
+ return E.current && K.observe(E.current), c.addEventListener("loadeddata", W, { once: !0 }), P && c.addEventListener("ended", G), c.readyState >= 2 && c.currentSrc.endsWith(N[0]) ? W() : c.currentSrc.endsWith(N[0]) || c.load(), () => {
475
+ K.disconnect(), be.current = null, xe.current = null, $.current = !1, cancelAnimationFrame(k), c.removeEventListener("loadeddata", W), P && c.removeEventListener("ended", G), s.removeEventListener("mousemove", B), s.removeEventListener("mouseleave", V), s.removeEventListener("click", H), l.deleteTexture(u.texture), l.deleteTexture(u.charVectorsTexture), l.deleteFramebuffer(u.fbo), l.deleteTexture(u.fboTexture), l.deleteBuffer(u.buffer), l.deleteShader(u.vertShader), l.deleteShader(u.fragShader), l.deleteShader(u.pass1FragShader), l.deleteProgram(d), l.deleteProgram(f), l.deleteTexture(D.current), l.deleteTexture(O.current), l.deleteTexture(u.scatterStateTexture), l.deleteTexture(u.spreadStateTexture);
476
+ };
477
+ }, [
478
+ i,
479
+ S,
480
+ y,
481
+ re,
482
+ ne,
483
+ te
484
+ ]), /* @__PURE__ */ r("div", {
485
+ ref: E,
486
+ className: C,
487
+ style: {
488
+ height: "100%",
489
+ width: "100%"
490
+ },
491
+ children: [/* @__PURE__ */ n("video", {
492
+ ref: T,
493
+ muted: !0,
494
+ playsInline: !0,
495
+ autoPlay: !0,
496
+ loop: !Array.isArray(i) || i.length === 1,
497
+ style: { display: "none" },
498
+ children: /* @__PURE__ */ n("source", {
499
+ src: Array.isArray(i) ? i[0] : i,
500
+ type: "video/mp4"
501
+ })
502
+ }), /* @__PURE__ */ n("canvas", {
503
+ ref: w,
504
+ style: {
505
+ width: "100%",
506
+ height: "100%",
507
+ display: "block"
508
+ }
509
+ })]
510
+ });
511
+ }
512
+ //#endregion
513
+ export { y as VideoAscii };
@@ -0,0 +1,280 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`react`),require(`react/jsx-runtime`)):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ReactVideoAscii={},e.React,e.ReactJSXRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=[[.25,.25],[.75,.25],[.25,.5],[.75,.5],[.25,.75],[.75,.75]];function i(e,t,n){let i=[],a=document.createElement(`canvas`);a.width=t,a.height=n;let o=a.getContext(`2d`,{willReadFrequently:!0});if(!o)return[];o.font=`${n}px monospace`,o.textBaseline=`top`;let s=t/5;for(let a of e){o.fillStyle=`black`,o.fillRect(0,0,t,n),o.fillStyle=`white`,o.fillText(a,0,0);let e=o.getImageData(0,0,t,n),c=[];for(let[i,a]of r){let r=i*t,o=a*n,l=0,u=0;for(let n=Math.floor(r-s);n<=Math.ceil(r+s);n+=1)for(let i=Math.floor(o-s);i<=Math.ceil(o+s);i+=1){let a=r-n,c=o-i;if(a*a+c*c<=s*s){let r=(i*t+n)*4,a=e.data[r];l+=a/255,u++}}let d=u>0?l/u:0;c.push(d)}i.push({char:a,vector:c})}let c=[,,,,,,].fill(0);for(let{vector:e}of i)for(let t=0;t<6;++t)e[t]>c[t]&&(c[t]=e[t]);for(let{vector:e}of i)for(let t=0;t<6;++t)e[t]/=c[t];return i}var a=` \`.',-_:!;|"~+^lr[](\\/L)>t<v=Tz?icf1{sIxY*jJno}CZyVwmSXRqM$O%#9&NW0Q@`;function o(e,t,n,r,i,a,o){let s=!!i,c=typeof i==`object`?i:{},l=c.style??`brighten`,u=s&&l!==`scatter`,d=s&&l===`scatter`,f=c.scatterChars??`->o`,p=c.trailLen??15,m=c.trailDecay??10,h=c.duration??1,g=c.radius??(l===`scatter`?.05:.08),_=c.brightness??2,v=!!a,y=typeof a==`object`?a:{},b=y.style??`ripple`,x=v&&b!==`spread`,S=v&&b===`spread`,C=y.brightness??1.1,w=y.speed??2,T=y.spreadExpandDuration??1.5,E=y.spreadSpeed??7.5,D=!!o,O=typeof o==`object`?o:{},k=O.type??`random`,A=O.duration??.4,j=Math.max(20,Math.min(350,Math.round(e))),M=Math.max(0,Math.min(2,t)),N=Math.max(0,Math.min(2,n)),P=Math.max(0,Math.min(1,r));A=Math.max(.1,Math.min(4,A)),p=Math.max(0,Math.min(30,Math.round(p))),m=Math.max(1,Math.min(15,m)),h=Math.max(.1,Math.min(4,h)),g=Math.max(.03,Math.min(.2,g)),_=Math.max(.2,Math.min(5,_)),C=Math.max(1.05,Math.min(2,C)),w=Math.max(.5,Math.min(4,w)),T=Math.max(.5,Math.min(5,T)),E=Math.max(.5,Math.min(10,E));let F;return F=D?k===`diagonal`?1:k===`radial`?2:3:0,{numCols:j,brightness:M,saturation:N,bgOpacity:P,mouseEnabled:s,mouseStyle:l,brightenEnabled:u,scatterEnabled:d,scatterChars:f,trailLen:p,trailDecay:m,duration:h,mouseRadius:g,mouseBrightness:_,clickEnabled:x,clickBrightness:C,clickSpeed:w,spreadEnabled:S,spreadExpandDuration:T,spreadSpeed:E,revealEnabled:D,revealType:k,revealDuration:A,revealEffectFlag:F}}function s(e,t,n){let r=e.createShader(t);if(!r)throw Error(`Failed to create shader`);return e.shaderSource(r,n),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS)?r:(console.log(e.getShaderInfoLog(r)),e.deleteShader(r),null)}function c(e,t,n){let r=e.createProgram();return e.attachShader(r,t),e.attachShader(r,n),e.linkProgram(r),e.getProgramParameter(r,e.LINK_STATUS)?r:(console.log(e.getProgramInfoLog(r)),e.deleteProgram(r),null)}var l=`#version 300 es
2
+
3
+ in vec2 a_position;
4
+
5
+ void main() {
6
+ gl_Position = vec4(a_position, 0.0, 1.0);
7
+ }`,u=`#version 300 es
8
+
9
+ precision highp float;
10
+ precision highp usampler2D;
11
+
12
+ uniform int u_revealEffectFlag;
13
+ uniform bool u_videoMode;
14
+
15
+ uniform float u_revealProgress;
16
+ uniform float u_brightness;
17
+ uniform float u_saturation;
18
+ uniform float u_bgOpacity;
19
+
20
+ uniform bool u_mouseEffect;
21
+ #define MOUSE_TRAIL_LEN 30
22
+ uniform vec2 u_mousePositions[MOUSE_TRAIL_LEN];
23
+ uniform float u_mouseLifeFracs[MOUSE_TRAIL_LEN];
24
+ uniform float u_mouseRadius;
25
+ uniform float u_mouseBrightness;
26
+
27
+ uniform bool u_clickEffect;
28
+ #define MAX_RIPPLES 10
29
+ uniform vec2 u_ripplePositions[MAX_RIPPLES];
30
+ uniform float u_rippleRadii[MAX_RIPPLES];
31
+ uniform float u_rippleBrightnesses[MAX_RIPPLES];
32
+
33
+ uniform bool u_scatterEffect;
34
+ uniform usampler2D u_scatterStateTexture;
35
+ uniform sampler2D u_scatterAtlas;
36
+ uniform float u_scatterNumChars;
37
+
38
+ uniform bool u_spreadEffect;
39
+ uniform usampler2D u_spreadStateTexture;
40
+
41
+ uniform bool u_shapeMatching;
42
+
43
+ uniform sampler2D u_texture;
44
+ uniform sampler2D u_atlas;
45
+ uniform usampler2D u_fboTexture;
46
+ uniform vec2 u_resolution;
47
+ uniform vec2 u_cellsize;
48
+ uniform float u_numChars;
49
+ uniform vec2 u_gridSize;
50
+ uniform vec2 u_cropOffset;
51
+ uniform vec2 u_cropScale;
52
+
53
+ out vec4 fragColor;
54
+
55
+ float hash(vec2 p) {
56
+ vec3 p3 = fract(vec3(p.xyx) * 0.1031);
57
+ p3 += dot(p3, p3.yzx + 33.33);
58
+ return fract((p3.x + p3.y) * p3.z);
59
+ }
60
+
61
+ vec3 rgb2hsl(vec3 c) {
62
+ float maxC = max(c.r, max(c.g, c.b));
63
+ float minC = min(c.r, min(c.g, c.b));
64
+ float l = (maxC + minC) * 0.5;
65
+ float d = maxC - minC;
66
+ float h = 0.0, s = 0.0;
67
+ if (d > 0.0) {
68
+ s = d / (1.0 - abs(2.0 * l - 1.0));
69
+ if (maxC == c.r) h = mod((c.g - c.b) / d, 6.0) / 6.0;
70
+ else if (maxC == c.g) h = ((c.b - c.r) / d + 2.0) / 6.0;
71
+ else h = ((c.r - c.g) / d + 4.0) / 6.0;
72
+ }
73
+ return vec3(h, s, l);
74
+ }
75
+
76
+ vec3 hsl2rgb(vec3 c) {
77
+ float h = c.x, s = c.y, l = c.z;
78
+ float a = s * (1.0 - abs(2.0 * l - 1.0));
79
+ vec3 rgb = clamp(abs(mod(h * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
80
+ return l + a * (rgb - 0.5);
81
+ }
82
+
83
+ void main() {
84
+ vec2 fragCoord = vec2(gl_FragCoord.x, u_resolution.y - gl_FragCoord.y);
85
+ vec2 cellCoord = floor(fragCoord / u_cellsize);
86
+
87
+ if (u_revealEffectFlag == 1) {
88
+
89
+ float revealThreshold = (cellCoord.x + cellCoord.y) / (u_gridSize.x + u_gridSize.y - 2.0);
90
+ if (revealThreshold > u_revealProgress) {
91
+ fragColor = vec4(0.0, 0.0, 0.0, 1.0);
92
+ return;
93
+ }
94
+ } else if (u_revealEffectFlag == 2) {
95
+ float dist = length(cellCoord / u_gridSize - vec2(0.5));
96
+ float revealThreshold = dist / 0.7071;
97
+ if (revealThreshold > u_revealProgress) {
98
+ fragColor = vec4(0.0, 0.0, 0.0, 1.0);
99
+ return;
100
+ }
101
+ } else if (u_revealEffectFlag == 3) {
102
+ float revealThreshold = hash(cellCoord);
103
+ if (revealThreshold > u_revealProgress) {
104
+ fragColor = vec4(0.0, 0.0, 0.0, 1.0);
105
+ return;
106
+ }
107
+ }
108
+
109
+ vec2 cellCenter = (cellCoord + 0.5) * u_cellsize;
110
+
111
+ vec2 rawUV = u_videoMode ? (fragCoord / u_resolution) : (cellCenter / u_resolution);
112
+ vec2 sampleUV = u_cropOffset + rawUV * u_cropScale;
113
+
114
+ vec3 cellColor = texture(u_texture, sampleUV).rgb;
115
+ float luminosity = dot(cellColor, vec3(0.299, 0.587, 0.114));
116
+ vec3 hsl = rgb2hsl(cellColor);
117
+ hsl.z = clamp(hsl.z * u_brightness, 0.0, 1.0);
118
+ hsl.y = clamp(hsl.y * u_saturation, 0.0, 1.0);
119
+ cellColor = hsl2rgb(hsl);
120
+
121
+ vec2 withinCellPos = fract(fragCoord / u_cellsize);
122
+
123
+
124
+ float glyphMask = 0.0;
125
+ if (!u_videoMode) {
126
+ uint charInd;
127
+ if (u_shapeMatching) {
128
+ charInd = texelFetch(u_fboTexture, ivec2(cellCoord), 0).r;
129
+ } else {
130
+ charInd = uint(floor(luminosity * (u_numChars - 1.0)));
131
+ }
132
+ float atlasU = (float(charInd) + withinCellPos.x) / u_numChars;
133
+ float atlasV = withinCellPos.y;
134
+ glyphMask = texture(u_atlas, vec2(atlasU, atlasV)).r;
135
+ }
136
+
137
+ vec3 bgColor = cellColor * u_bgOpacity;
138
+
139
+ bool scatterHit = false;
140
+ vec3 scatterColor = vec3(0.0);
141
+ if (u_scatterEffect) {
142
+ uint state = texelFetch(u_scatterStateTexture, ivec2(cellCoord), 0).r;
143
+ if (state > 0u) {
144
+ int idx = int(state) - 1;
145
+ float su = (float(idx) + withinCellPos.x) / u_scatterNumChars;
146
+ float mask = texture(u_scatterAtlas, vec2(su, withinCellPos.y)).r;
147
+
148
+ vec3 scatterBg = u_videoMode ? cellColor : bgColor;
149
+ scatterColor = mix(scatterBg, vec3(1.0), mask);
150
+ scatterHit = true;
151
+ }
152
+ }
153
+
154
+ if (!scatterHit && u_spreadEffect) {
155
+ uint state = texelFetch(u_spreadStateTexture, ivec2(cellCoord), 0).r;
156
+ if (state > 0u) {
157
+ int idx = int(state) - 1;
158
+ float su = (float(idx) + withinCellPos.x) / u_scatterNumChars;
159
+ float mask = texture(u_scatterAtlas, vec2(su, withinCellPos.y)).r;
160
+ vec3 scatterBg = u_videoMode ? cellColor : bgColor;
161
+ scatterColor = mix(scatterBg, vec3(1.0), mask);
162
+ scatterHit = true;
163
+ }
164
+ }
165
+
166
+ vec3 finalColor;
167
+ if (scatterHit) {
168
+ finalColor = scatterColor;
169
+ } else {
170
+ finalColor = u_videoMode ? cellColor : mix(bgColor, cellColor, glyphMask);
171
+
172
+ if (u_mouseEffect) {
173
+ bool inside = false;
174
+ for (int i = 0; i < MOUSE_TRAIL_LEN; i++) {
175
+ if (u_mouseLifeFracs[i] <= 0.0) {
176
+ continue;
177
+ }
178
+ float r = u_mouseRadius * u_mouseLifeFracs[i];
179
+ if (distance(cellCenter, u_mousePositions[i]) < r) {
180
+ inside = true;
181
+ break;
182
+ }
183
+ }
184
+ if (inside) {
185
+ finalColor = clamp(finalColor * u_mouseBrightness, 0.0, 1.0);
186
+ }
187
+ }
188
+
189
+ if (u_clickEffect) {
190
+ float boost = 1.0;
191
+ for (int i = 0; i < MAX_RIPPLES; i++) {
192
+ if (u_rippleBrightnesses[i] <= 1.0) {
193
+ continue;
194
+ }
195
+ if (distance(cellCenter, u_ripplePositions[i]) < u_rippleRadii[i]) {
196
+ boost *= u_rippleBrightnesses[i];
197
+ }
198
+ }
199
+ if (boost > 1.0) {
200
+ finalColor = clamp(finalColor * boost, 0.0, 1.0);
201
+ }
202
+ }
203
+ }
204
+
205
+ fragColor = vec4(finalColor, 1.0);
206
+ }`,d=`#version 300 es
207
+
208
+ precision highp float;
209
+ precision highp usampler2D;
210
+
211
+ uniform sampler2D u_texture;
212
+ uniform vec2 u_resolution;
213
+ uniform vec2 u_cellsize;
214
+ uniform int u_circleN;
215
+ uniform sampler2D u_charVectors;
216
+ uniform int u_numCharsInt;
217
+ uniform float u_shapeExponent;
218
+ uniform vec2 u_cropOffset;
219
+ uniform vec2 u_cropScale;
220
+
221
+ out uvec4 fragCharInd;
222
+
223
+ void main() {
224
+
225
+ ivec2 cellCoord = ivec2(floor(gl_FragCoord.xy));
226
+
227
+ const vec2 CIRCLES[6] = vec2[6](
228
+ vec2(0.25, 0.25), vec2(0.75, 0.25),
229
+ vec2(0.25, 0.50), vec2(0.75, 0.50),
230
+ vec2(0.25, 0.75), vec2(0.75, 0.75)
231
+ );
232
+ vec2 radiusUV = vec2(u_cellsize.x / 5.0) / u_resolution * u_cropScale;
233
+
234
+ float sv[6];
235
+ for (int ci = 0; ci < 6; ci++) {
236
+
237
+ vec2 centerUV = u_cropOffset + (vec2(cellCoord) + CIRCLES[ci]) * u_cellsize / u_resolution * u_cropScale;
238
+ float total = 0.0;
239
+ int count = 0;
240
+ for (int dx = -u_circleN; dx <= u_circleN; dx++) {
241
+ for (int dy = -u_circleN; dy <= u_circleN; dy++) {
242
+ if (dx*dx + dy*dy <= u_circleN*u_circleN) {
243
+ vec2 sampleUV = centerUV + vec2(float(dx), float(dy)) * radiusUV / float(u_circleN);
244
+ vec3 sc = texture(u_texture, sampleUV).rgb;
245
+ total += dot(sc, vec3(0.299, 0.587, 0.114));
246
+ count++;
247
+ }
248
+ }
249
+ }
250
+
251
+ sv[ci] = count > 0 ? total / float(count) : 0.0;
252
+ }
253
+
254
+ float maxVal = 0.0;
255
+ for (int d = 0; d < 6; d++) maxVal = max(maxVal, sv[d]);
256
+ if (maxVal > 0.0) {
257
+ for (int d = 0; d < 6; d++) sv[d] = pow(sv[d] / maxVal, u_shapeExponent) * maxVal;
258
+ }
259
+
260
+
261
+ uint bestChar = 0u;
262
+ float bestDist = 1e10;
263
+ for (int i = 0; i < u_numCharsInt; i++) {
264
+ vec4 r0 = texelFetch(u_charVectors, ivec2(i, 0), 0);
265
+ vec4 r1 = texelFetch(u_charVectors, ivec2(i, 1), 0);
266
+ float dist = 0.0;
267
+ float diffs[6] = float[6](
268
+ sv[0] - r0.r, sv[1] - r0.g, sv[2] - r0.b, sv[3] - r0.a,
269
+ sv[4] - r1.r, sv[5] - r1.g
270
+ );
271
+ for (int d = 0; d < 6; d++) {
272
+ dist += diffs[d] * diffs[d];
273
+ }
274
+ if (dist < bestDist) {
275
+ bestDist = dist; bestChar = uint(i);
276
+ }
277
+ }
278
+
279
+ fragCharInd = uvec4(bestChar, 0u, 0u, 0u);
280
+ }`;function f(e){let t=s(e,e.VERTEX_SHADER,l),n=s(e,e.FRAGMENT_SHADER,u);if(!t||!n)return null;let r=c(e,t,n);if(!r)return null;let i=new Float32Array([1,1,-1,1,1,-1,-1,-1,-1,1,1,-1]),a=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,a),e.bufferData(e.ARRAY_BUFFER,i,e.STATIC_DRAW);let o=e.getAttribLocation(r,`a_position`);e.vertexAttribPointer(o,2,e.FLOAT,!1,0,0),e.enableVertexAttribArray(o),e.useProgram(r);let f=e.createTexture();e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,f),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST);let p=e.getUniformLocation(r,`u_texture`);e.uniform1i(p,0);let m=e.createTexture();e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,m),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST);let h=e.getUniformLocation(r,`u_atlas`);e.uniform1i(h,1);let g=e.createTexture();e.activeTexture(e.TEXTURE2),e.bindTexture(e.TEXTURE_2D,g),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST);let _=s(e,e.FRAGMENT_SHADER,d);if(!_)return null;let v=c(e,t,_);if(!v)return null;e.useProgram(v),e.uniform1i(e.getUniformLocation(v,`u_texture`),0),e.uniform1i(e.getUniformLocation(v,`u_charVectors`),2);let y=e.getUniformLocation(v,`u_resolution`),b=e.getUniformLocation(v,`u_cellsize`),x=e.getUniformLocation(v,`u_circleN`),S=e.getUniformLocation(v,`u_numCharsInt`),C=e.getUniformLocation(v,`u_shapeExponent`),w=e.getUniformLocation(v,`u_cropOffset`),T=e.getUniformLocation(v,`u_cropScale`),E=e.createTexture();e.activeTexture(e.TEXTURE3),e.bindTexture(e.TEXTURE_2D,E),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST);let D=e.createFramebuffer(),O=e.createTexture();e.activeTexture(e.TEXTURE4),e.bindTexture(e.TEXTURE_2D,O),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST);let k=e.createTexture();e.activeTexture(e.TEXTURE5),e.bindTexture(e.TEXTURE_2D,k),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST);let A=e.createTexture();return e.activeTexture(e.TEXTURE6),e.bindTexture(e.TEXTURE_2D,A),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST),e.useProgram(r),e.uniform1i(e.getUniformLocation(r,`u_fboTexture`),3),e.uniform1i(e.getUniformLocation(r,`u_scatterAtlas`),4),e.uniform1i(e.getUniformLocation(r,`u_scatterStateTexture`),5),e.uniform1i(e.getUniformLocation(r,`u_spreadStateTexture`),6),{program:r,pass1Program:v,buffer:a,vertShader:t,fragShader:n,pass1FragShader:_,texture:f,atlasTexture:m,charVectorsTexture:g,fboTexture:E,fbo:D,scatterAtlasTexture:O,scatterStateTexture:k,spreadStateTexture:A,p1ResLoc:y,p1CellsizeLoc:b,p1CircleNLoc:x,p1NumCharsLoc:S,p1ExponentLoc:C,p1CropOffsetLoc:w,p1CropScaleLoc:T,revealEffectFlagLoc:e.getUniformLocation(r,`u_revealEffectFlag`),mouseEffectFlagLoc:e.getUniformLocation(r,`u_mouseEffect`),clickEffectFlagLoc:e.getUniformLocation(r,`u_clickEffect`),shapeMatchingLoc:e.getUniformLocation(r,`u_shapeMatching`),revealProgressLoc:e.getUniformLocation(r,`u_revealProgress`),brightnessLoc:e.getUniformLocation(r,`u_brightness`),saturationLoc:e.getUniformLocation(r,`u_saturation`),bgOpacityLoc:e.getUniformLocation(r,`u_bgOpacity`),mouseBrightnessLoc:e.getUniformLocation(r,`u_mouseBrightness`),mousePositionsLoc:e.getUniformLocation(r,`u_mousePositions`),mouseLifeFracsLoc:e.getUniformLocation(r,`u_mouseLifeFracs`),mouseRadiusLoc:e.getUniformLocation(r,`u_mouseRadius`),ripplePositionsLoc:e.getUniformLocation(r,`u_ripplePositions`),rippleRadiiLoc:e.getUniformLocation(r,`u_rippleRadii`),rippleBrightnessesLoc:e.getUniformLocation(r,`u_rippleBrightnesses`),scatterEffectFlagLoc:e.getUniformLocation(r,`u_scatterEffect`),scatterNumCharsLoc:e.getUniformLocation(r,`u_scatterNumChars`),spreadEffectFlagLoc:e.getUniformLocation(r,`u_spreadEffect`),videoModeLoc:e.getUniformLocation(r,`u_videoMode`),resLoc:e.getUniformLocation(r,`u_resolution`),sizeLoc:e.getUniformLocation(r,`u_cellsize`),numLoc:e.getUniformLocation(r,`u_numChars`),gridSizeLoc:e.getUniformLocation(r,`u_gridSize`),cropOffsetLoc:e.getUniformLocation(r,`u_cropOffset`),cropScaleLoc:e.getUniformLocation(r,`u_cropScale`)}}function p({scatterEnabledRef:e,mouseRadiusRef:t,durationRef:n,scatterCharsRef:r}){let i=new Uint8Array,a=new Float32Array,o=new Float32Array,s=new Uint8Array,c=null,l=null,u=0,d=0,f=-1,p=()=>Math.floor(Math.random()*r.current.length),m=0,h=0,g=1,_=1,v=null;return{setup(e,t,n,r,f,p){m=t,h=n,g=r,_=f,v=p;let y=t*n;i=new Uint8Array(y),a=new Float32Array(y),o=new Float32Array(y),s=new Uint8Array(y),c=null,l=null,u=0,d=0,e.activeTexture(e.TEXTURE5),e.bindTexture(e.TEXTURE_2D,p),e.texImage2D(e.TEXTURE_2D,0,e.R8UI,t,n,0,e.RED_INTEGER,e.UNSIGNED_BYTE,i)},handleMouseMove(t,n,r){e.current&&(l=c,c={x:t,y:n,t:r})},handleMouseLeave(){c=null,l=null,u=0,d=0},reset(e){i.fill(0),v&&m>0&&(e.activeTexture(e.TEXTURE5),e.bindTexture(e.TEXTURE_2D,v),e.texSubImage2D(e.TEXTURE_2D,0,0,0,m,h,e.RED_INTEGER,e.UNSIGNED_BYTE,i))},tick(r,y){if(!e.current||m===0||h===0)return;let b=performance.now(),x=f<0?0:Math.min(.1,(b-f)/1e3);f=b;let S=Math.min(y.width,y.height)*t.current,C=!1;if(c&&b-c.t<80){if(l){let e=(c.t-l.t)/1e3;if(e>0&&e<.2){let t=(c.x-l.x)/e,n=(c.y-l.y)/e;u=u*.7+t*.3,d=d*.7+n*.3}}else u=0,d=0;Math.hypot(u,d)>g*2&&(C=!0)}else u*=.6,d*=.6;if(s.fill(0),C&&c&&S>0){let e=c.x,t=c.y,n=Math.max(0,Math.floor((e-S)/g)),r=Math.min(m-1,Math.floor((e+S)/g)),l=Math.max(0,Math.floor((t-S)/_)),u=Math.min(h-1,Math.floor((t+S)/_));for(let c=l;c<=u;c++)for(let l=n;l<=r;l++){let n=(l+.5)*g,r=(c+.5)*_;if(Math.hypot(n-e,r-t)>S)continue;let u=c*m+l;s[u]=1,i[u]===0&&(i[u]=1+p(),o[u]=.5+Math.random()*2),a[u]=1}}let w=n.current;if(x>0)for(let e=0;e<h;e++)for(let t=0;t<m;t++){let n=e*m+t;if(i[n]===0||s[n])continue;let r=t>0?i[n-1]!==0:!1,c=t<m-1?i[n+1]!==0:!1,l=e>0?i[n-m]!==0:!1,u=e<h-1?i[n+m]!==0:!1,d=(!r||!c||!l||!u?5:1)*o[n]/w;a[n]-=d*x,a[n]<=0&&(i[n]=0)}r.activeTexture(r.TEXTURE5),r.bindTexture(r.TEXTURE_2D,v),r.texSubImage2D(r.TEXTURE_2D,0,0,0,m,h,r.RED_INTEGER,r.UNSIGNED_BYTE,i)}}}function m({brightenEnabledRef:e,trailLenRef:t,durationRef:n,trailDecayRef:r}){let i=[];return{handleMouseMove(n,r,a){e.current&&(i.push({x:n,y:r,t:a}),i.length>t.current&&i.shift())},tick(a,o,s){if(!e.current)return;let c=performance.now(),l=new Float32Array(t.current*2),u=new Float32Array(t.current);for(let e=0;e<i.length;e++){let t=c-i[e].t,a=Math.max(0,1-t/(n.current*1e3))**r.current;l[e*2]=i[e].x,l[e*2+1]=i[e].y,u[e]=a}a.uniform2fv(o,l),a.uniform1fv(s,u)}}}var h=10;function g({clickEnabledRef:e,clickSpeedRef:t,clickBrightnessRef:n}){let r=[];return{handleClick(t,n){if(!e.current)return;let i=n.getBoundingClientRect(),a=(t.clientX-i.left)*(n.width/i.width),o=(t.clientY-i.top)*(n.height/i.height);r.push({x:a,y:o,t:performance.now()}),r.length>h&&r.shift()},tick(i,a,o,s,c){if(!e.current)return;let l=performance.now(),u=Math.hypot(a.width,a.height),d=new Float32Array(h*2),f=new Float32Array(h),p=new Float32Array(h);for(;r.length>0&&(l-r[0].t)*t.current>=u;)r.shift();for(let e=0;e<r.length;e++){let i=(l-r[e].t)*t.current,a=i/u,o=1+(n.current-1)*(1-a**2);d[e*2]=r[e].x,d[e*2+1]=r[e].y,f[e]=i,p[e]=o}i.uniform2fv(o,d),i.uniform1fv(s,f),i.uniform1fv(c,p)}}}function _({spreadEnabledRef:e,scatterCharsRef:t,spreadExpandDurationRef:n,spreadSpeedRef:r}){let i=new Uint8Array,a=new Uint8Array,o=0,s=0,c=1,l=1,u=null,d=-1,f=-1,p=()=>Math.floor(Math.random()*t.current.length),m=[[-1,0],[1,0],[0,-1],[0,1]];return{setup(e,t,n,r,p,m){o=t,s=n,c=r,l=p,u=m;let h=t*n;i=new Uint8Array(h),a=new Uint8Array(h),f=-1,d=-1,e.activeTexture(e.TEXTURE6),e.bindTexture(e.TEXTURE_2D,m),e.texImage2D(e.TEXTURE_2D,0,e.R8UI,t,n,0,e.RED_INTEGER,e.UNSIGNED_BYTE,i)},handleClick(t,r){if(!e.current||o===0)return;let u=r.getBoundingClientRect(),d=(t.clientX-u.left)*(r.width/u.width),m=(t.clientY-u.top)*(r.height/u.height),h=Math.floor(d/c),g=Math.floor(m/l);if(h<0||h>=o||g<0||g>=s)return;let _=g*o+h;i[_]=1+p(),a[_]=1,f=performance.now()+n.current*1e3},tick(t){if(!e.current||o===0||s===0)return;let n=performance.now(),c=d<0?0:Math.min(.1,(n-d)/1e3);d=n;let l=f>0&&n<f;if(!l&&f>0){for(let e=0;e<i.length;e++)a[e]===1&&(a[e]=2);f=-1}if(c>0)for(let e=0;e<s;e++)for(let t=0;t<o;t++){let n=e*o+t;if(a[n]!==0){if(l&&a[n]===1)for(let[n,l]of m){let u=e+n,d=t+l;if(u<0||u>=s||d<0||d>=o)continue;let f=u*o+d;a[f]===0&&Math.random()<r.current*c&&(i[f]=1+p(),a[f]=1)}else if(a[n]===2)for(let[l,u]of m){let d=e+l,f=t+u;if(d<0||d>=s||f<0||f>=o||i[d*o+f]===0){Math.random()<r.current*c&&(i[n]=0,a[n]=0);break}}}}t.activeTexture(t.TEXTURE6),t.bindTexture(t.TEXTURE_2D,u),t.texSubImage2D(t.TEXTURE_2D,0,0,0,o,s,t.RED_INTEGER,t.UNSIGNED_BYTE,i)}}}function v({src:e,videoMode:r=!1,numColsRaw:s=250,brightnessRaw:c=1,saturationRaw:l=1,bgOpacityRaw:u=.3,revealEffect:d=!1,chars:h=a,mouseEffect:v=!0,clickEffect:y=!0,charMode:b=`shape`,className:x}){let S=(0,t.useRef)(null),C=(0,t.useRef)(null),w=(0,t.useRef)(null),T=(0,t.useRef)(null),E=(0,t.useRef)(null),{numCols:D,brightness:O,saturation:k,bgOpacity:A,mouseEnabled:j,mouseStyle:M,brightenEnabled:N,scatterEnabled:P,scatterChars:F,trailLen:I,trailDecay:L,duration:R,mouseRadius:z,mouseBrightness:B,clickEnabled:V,clickBrightness:H,clickSpeed:U,spreadEnabled:W,spreadExpandDuration:G,spreadSpeed:K,revealEnabled:ee,revealDuration:te,revealEffectFlag:ne}=o(s,c,l,u,v,y,d),re=(0,t.useRef)(O),ie=(0,t.useRef)(k),ae=(0,t.useRef)(A),oe=(0,t.useRef)(j),se=(0,t.useRef)(M),q=(0,t.useRef)(N),J=(0,t.useRef)(P),ce=(0,t.useRef)(B),Y=(0,t.useRef)(z),le=(0,t.useRef)(I),ue=(0,t.useRef)(L),X=(0,t.useRef)(R),Z=(0,t.useRef)(F),Q=(0,t.useRef)(V),de=(0,t.useRef)(H),fe=(0,t.useRef)(U),pe=(0,t.useRef)(W),me=(0,t.useRef)(G),he=(0,t.useRef)(K),ge=(0,t.useRef)(D),_e=(0,t.useRef)(r);(0,t.useEffect)(()=>{re.current=O,ie.current=k,ae.current=A,oe.current=j,se.current=M,q.current=N,J.current=P,ce.current=B,Y.current=z,le.current=I,ue.current=L,X.current=R,Z.current!==F&&$.current?(Z.current=F,ye.current?.()):Z.current=F,Q.current=V,de.current=H,fe.current=U,pe.current=W,me.current=G,he.current=K,ge.current=D,_e.current=r},[O,k,A,j,M,N,P,B,z,I,L,R,F,V,H,U,W,G,K,D,r]);let ve=(0,t.useRef)(null),ye=(0,t.useRef)(null),$=(0,t.useRef)(!1);return(0,t.useEffect)(()=>{$.current&&ve.current?.(D)},[D]),(0,t.useEffect)(()=>{$.current=!1;let t=[],n=0,r=0,a=1,o=1,s=S.current;if(!s)return;let c=C.current;if(!c)return;let l=s.getContext(`webgl2`);if(!l)return;l.pixelStorei(l.UNPACK_ALIGNMENT,1);let u=f(l);if(!u)return;let{program:d,pass1Program:v}=u;T.current=u.atlasTexture,E.current=u.scatterAtlasTexture,l.uniform1i(u.shapeMatchingLoc,+(b===`shape`)),l.uniform1i(u.revealEffectFlagLoc,ne),l.uniform1f(u.revealProgressLoc,0);let y=p({scatterEnabledRef:J,mouseRadiusRef:Y,durationRef:X,scatterCharsRef:Z}),x=m({brightenEnabledRef:q,trailLenRef:le,durationRef:X,trailDecayRef:ue}),D=g({clickEnabledRef:Q,clickSpeedRef:fe,clickBrightnessRef:de}),O=_({spreadEnabledRef:pe,scatterCharsRef:Z,spreadExpandDurationRef:me,spreadSpeedRef:he}),k,A=-1,j=-1,M=0,N=Array.isArray(e)?e:[e],P=N.length>1,F=document.createElement(`canvas`),I=F.getContext(`2d`),L=e=>{a=Math.max(1,Math.floor(s.width/e));let c=a*2;if(I.font=`${c}px monospace`,o=Math.max(1,Math.round(c*a/I.measureText(`M`).width)),n=Math.floor(s.width/a),r=Math.floor(s.height/o),s.width=n*a,s.height=r*o,l.viewport(0,0,s.width,s.height),l.useProgram(d),l.uniform2f(u.resLoc,s.width,s.height),l.useProgram(v),l.uniform2f(u.p1ResLoc,s.width,s.height),l.useProgram(d),y.setup(l,n,r,a,o,u.scatterStateTexture),O.setup(l,n,r,a,o,u.spreadStateTexture),b===`shape`){t=i(h,a,o);let e=t.length,s=new Float32Array(e*8);for(let n=0;n<e;n++){let r=t[n].vector;s[n*4+0]=r[0],s[n*4+1]=r[1],s[n*4+2]=r[2],s[n*4+3]=r[3],s[e*4+n*4+0]=r[4],s[e*4+n*4+1]=r[5]}l.activeTexture(l.TEXTURE2),l.bindTexture(l.TEXTURE_2D,u.charVectorsTexture),l.texImage2D(l.TEXTURE_2D,0,l.RGBA32F,e,2,0,l.RGBA,l.FLOAT,s),l.activeTexture(l.TEXTURE3),l.bindTexture(l.TEXTURE_2D,u.fboTexture),l.texImage2D(l.TEXTURE_2D,0,l.R8UI,n,r,0,l.RED_INTEGER,l.UNSIGNED_BYTE,null),l.bindFramebuffer(l.FRAMEBUFFER,u.fbo),l.framebufferTexture2D(l.FRAMEBUFFER,l.COLOR_ATTACHMENT0,l.TEXTURE_2D,u.fboTexture,0),l.bindFramebuffer(l.FRAMEBUFFER,null);let c=Math.max(1,Math.round(a/5));l.useProgram(v),l.uniform2f(u.p1CellsizeLoc,a,o),l.uniform1i(u.p1CircleNLoc,c),l.uniform1i(u.p1NumCharsLoc,e),l.uniform1f(u.p1ExponentLoc,2),l.useProgram(d)}l.uniform2f(u.gridSizeLoc,n,r),F.width=h.length*a,F.height=o,I.font=`${o}px monospace`,I.fillStyle=`black`,I.fillRect(0,0,h.length*a,o),I.fillStyle=`white`,I.textBaseline=`top`;for(let e=0;e<h.length;e++)I.fillText(h[e],e*a,0);l.activeTexture(l.TEXTURE1),l.bindTexture(l.TEXTURE_2D,T.current),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,F),l.uniform1f(u.numLoc,h.length),l.uniform2f(u.sizeLoc,a,o),R()},R=()=>{let e=Z.current;F.width=e.length*a,F.height=o,I.fillStyle=`black`,I.fillRect(0,0,F.width,o),I.fillStyle=`white`,I.textBaseline=`top`,I.font=`${o}px monospace`;for(let t=0;t<e.length;t++)I.fillText(e[t],t*a,0);l.activeTexture(l.TEXTURE4),l.bindTexture(l.TEXTURE_2D,E.current),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,F),l.uniform1f(u.scatterNumCharsLoc,e.length),y.reset(l)};ve.current=L,ye.current=R;let z=(e,t)=>{s.width=Math.round(e),s.height=Math.round(t),L(ge.current);let n=c.videoWidth/c.videoHeight,r=s.width/s.height,i=1,a=1,o=0,f=0;r>n?(a=n/r,f=(1-a)/2):(i=r/n,o=(1-i)/2),l.uniform2f(u.cropOffsetLoc,o,f),l.uniform2f(u.cropScaleLoc,i,a),l.useProgram(v),l.uniform2f(u.p1CropOffsetLoc,o,f),l.uniform2f(u.p1CropScaleLoc,i,a),l.useProgram(d)},B=e=>{if(!oe.current)return;let t=s.getBoundingClientRect(),n=(e.clientX-t.left)*(s.width/t.width),r=(e.clientY-t.top)*(s.height/t.height),i=performance.now();x.handleMouseMove(n,r,i),y.handleMouseMove(n,r,i)};s.addEventListener(`mousemove`,B);let V=()=>y.handleMouseLeave();s.addEventListener(`mouseleave`,V);let H=e=>{D.handleClick(e,s),O.handleClick(e,s)};s.addEventListener(`click`,H);let U=()=>{if(l.uniform1i(u.videoModeLoc,+!!_e.current),l.uniform1f(u.brightnessLoc,re.current),l.uniform1f(u.saturationLoc,ie.current),l.uniform1f(u.bgOpacityLoc,ae.current),l.uniform1i(u.mouseEffectFlagLoc,+!!q.current),l.uniform1i(u.scatterEffectFlagLoc,+!!J.current),l.uniform1i(u.clickEffectFlagLoc,+!!Q.current),l.uniform1i(u.spreadEffectFlagLoc,+!!pe.current),l.uniform1f(u.mouseBrightnessLoc,ce.current),l.uniform1f(u.mouseRadiusLoc,Math.min(s.width,s.height)*Y.current),ee){let e=j<0?0:Math.min(1,(performance.now()-j)/(te*1e3));l.uniform1f(u.revealProgressLoc,e)}$.current&&c.currentTime!=A&&c.readyState>=2&&(l.activeTexture(l.TEXTURE0),l.bindTexture(l.TEXTURE_2D,u.texture),l.texSubImage2D(l.TEXTURE_2D,0,0,0,l.RGBA,l.UNSIGNED_BYTE,c),A=c.currentTime),x.tick(l,u.mousePositionsLoc,u.mouseLifeFracsLoc),y.tick(l,s),D.tick(l,s,u.ripplePositionsLoc,u.rippleRadiiLoc,u.rippleBrightnessesLoc),O.tick(l),b===`shape`&&(l.bindFramebuffer(l.FRAMEBUFFER,u.fbo),l.viewport(0,0,n,r),l.useProgram(v),l.drawArrays(l.TRIANGLES,0,6),l.bindFramebuffer(l.FRAMEBUFFER,null),l.viewport(0,0,s.width,s.height),l.useProgram(d)),l.drawArrays(l.TRIANGLES,0,6),k=requestAnimationFrame(U)},W=()=>{let e=w.current;z(e.clientWidth||c.videoWidth,e.clientHeight||c.videoHeight),l.activeTexture(l.TEXTURE0),l.bindTexture(l.TEXTURE_2D,u.texture),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,c),c.play(),j=performance.now(),$.current=!0,k=requestAnimationFrame(U)},G=()=>{M=(M+1)%N.length,c.src=N[M],c.load(),c.addEventListener(`canplay`,()=>c.play(),{once:!0})},K=new ResizeObserver(e=>{let{width:t,height:n}=e[0].contentRect;$.current&&t>0&&n>0&&z(t,n)});return w.current&&K.observe(w.current),c.addEventListener(`loadeddata`,W,{once:!0}),P&&c.addEventListener(`ended`,G),c.readyState>=2&&c.currentSrc.endsWith(N[0])?W():c.currentSrc.endsWith(N[0])||c.load(),()=>{K.disconnect(),ve.current=null,ye.current=null,$.current=!1,cancelAnimationFrame(k),c.removeEventListener(`loadeddata`,W),P&&c.removeEventListener(`ended`,G),s.removeEventListener(`mousemove`,B),s.removeEventListener(`mouseleave`,V),s.removeEventListener(`click`,H),l.deleteTexture(u.texture),l.deleteTexture(u.charVectorsTexture),l.deleteFramebuffer(u.fbo),l.deleteTexture(u.fboTexture),l.deleteBuffer(u.buffer),l.deleteShader(u.vertShader),l.deleteShader(u.fragShader),l.deleteShader(u.pass1FragShader),l.deleteProgram(d),l.deleteProgram(v),l.deleteTexture(T.current),l.deleteTexture(E.current),l.deleteTexture(u.scatterStateTexture),l.deleteTexture(u.spreadStateTexture)}},[e,b,h,ne,te,ee]),(0,n.jsxs)(`div`,{ref:w,className:x,style:{height:`100%`,width:`100%`},children:[(0,n.jsx)(`video`,{ref:C,muted:!0,playsInline:!0,autoPlay:!0,loop:!Array.isArray(e)||e.length===1,style:{display:`none`},children:(0,n.jsx)(`source`,{src:Array.isArray(e)?e[0]:e,type:`video/mp4`})}),(0,n.jsx)(`canvas`,{ref:S,style:{width:`100%`,height:`100%`,display:`block`}})]})}e.VideoAscii=v});
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "react-video-ascii",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "React component that renders videos as ASCII animations using WebGL2.",
6
+ "author": "Lucas Jin",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/LucasHJin/react-video-ascii.git"
10
+ },
11
+ "homepage": "https://github.com/LucasHJin/react-video-ascii",
12
+ "license": "MIT",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "main": "./dist/react-video-ascii.umd.cjs",
17
+ "module": "./dist/react-video-ascii.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/react-video-ascii.js",
23
+ "require": "./dist/react-video-ascii.umd.cjs"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "build": "tsc -b && vite build",
28
+ "dev": "vite build --watch",
29
+ "lint": "eslint ."
30
+ },
31
+ "peerDependencies": {
32
+ "react": ">=18",
33
+ "react-dom": ">=18"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/js": "^10.0.1",
37
+ "@types/node": "^24.0.0",
38
+ "@types/react": "^19.2.14",
39
+ "@types/react-dom": "^19.2.3",
40
+ "@vitejs/plugin-react": "^6.0.1",
41
+ "eslint": "^10.3.0",
42
+ "eslint-plugin-react-hooks": "^7.1.1",
43
+ "globals": "^17.6.0",
44
+ "typescript": "~6.0.3",
45
+ "typescript-eslint": "^8.59.1",
46
+ "vite": "^8.0.10",
47
+ "vite-plugin-dts": "^5.0.0",
48
+ "vite-plugin-glsl": "^1.6.0"
49
+ }
50
+ }