@fxshelf/glyph-rain 0.1.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.
@@ -0,0 +1,865 @@
1
+ "use strict";
2
+ var GlyphRain = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var src_exports = {};
23
+ __export(src_exports, {
24
+ DEFAULTS: () => DEFAULTS,
25
+ DEFAULT_CHARSET: () => DEFAULT_CHARSET,
26
+ GlyphRain: () => GlyphRain,
27
+ createGlyphRain: () => createGlyphRain,
28
+ supportsHtmlInCanvas: () => supportsHtmlInCanvas
29
+ });
30
+
31
+ // src/browser/react.ts
32
+ var globalReact = globalThis.React;
33
+ if (!globalReact) {
34
+ throw new Error(
35
+ '[glyph-rain] browser build: global "React" not found. Load a UMD build of React (react@18.3.x) before glyph-rain.'
36
+ );
37
+ }
38
+ var Fragment = globalReact.Fragment;
39
+ var createElement = globalReact.createElement;
40
+ var useEffect = globalReact.useEffect;
41
+ var useRef = globalReact.useRef;
42
+ var useState = globalReact.useState;
43
+ var useSyncExternalStore = globalReact.useSyncExternalStore;
44
+
45
+ // src/core/atlas.ts
46
+ function buildAtlas(charset) {
47
+ const glyphs = Array.from(new Set(Array.from(charset))).filter(
48
+ (g) => g.trim().length > 0
49
+ );
50
+ if (glyphs.length === 0) glyphs.push("0", "1");
51
+ const count = glyphs.length;
52
+ const grid = Math.max(Math.ceil(Math.sqrt(count)), 1);
53
+ const cellPx = 64;
54
+ const canvas = document.createElement("canvas");
55
+ canvas.width = grid * cellPx;
56
+ canvas.height = grid * cellPx;
57
+ const ctx = canvas.getContext("2d");
58
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
59
+ ctx.fillStyle = "#ffffff";
60
+ ctx.textAlign = "center";
61
+ ctx.textBaseline = "middle";
62
+ ctx.font = `600 ${Math.round(cellPx * 0.72)}px ui-monospace, SFMono-Regular, Menlo, monospace`;
63
+ for (let i = 0; i < count; i++) {
64
+ const x = (i % grid + 0.5) * cellPx;
65
+ const y = (Math.floor(i / grid) + 0.5) * cellPx;
66
+ ctx.fillText(glyphs[i], x, y);
67
+ }
68
+ return { canvas, count, grid };
69
+ }
70
+
71
+ // src/core/shaders.ts
72
+ var VERT = `#version 300 es
73
+ precision highp float;
74
+ layout(location = 0) in vec2 aPos;
75
+ out vec2 vUv;
76
+ void main () {
77
+ vUv = aPos * 0.5 + 0.5;
78
+ gl_Position = vec4(aPos, 0.0, 1.0);
79
+ }`;
80
+ var FRAG = `#version 300 es
81
+ precision highp float;
82
+ in vec2 vUv;
83
+ out vec4 outColor;
84
+ uniform sampler2D uContent;
85
+ uniform sampler2D uAtlas;
86
+ uniform sampler2D uWake;
87
+ uniform vec2 uResolution;
88
+ uniform float uTime;
89
+ uniform float uCell;
90
+ uniform float uGlyphCount;
91
+ uniform float uAtlasGrid;
92
+ uniform vec3 uColor;
93
+ uniform vec3 uHeadColor;
94
+ uniform float uSpeed;
95
+ uniform float uSpeedVar;
96
+ uniform float uDensity;
97
+ uniform float uTrail;
98
+ uniform float uGlow;
99
+ uniform float uMutate;
100
+ uniform float uFlicker;
101
+ uniform float uLayers;
102
+ uniform float uDim;
103
+ uniform float uLight;
104
+ uniform float uLightRadius;
105
+ uniform float uLightHeight;
106
+ uniform float uRelief;
107
+ uniform float uStir;
108
+ uniform float uScroll;
109
+ uniform float uPageLum;
110
+ uniform float uHasContent;
111
+
112
+ float hash11(float p) {
113
+ p = fract(p * 0.1031);
114
+ p *= p + 33.33;
115
+ p *= p + p;
116
+ return fract(p);
117
+ }
118
+
119
+ float hash21(vec2 p) {
120
+ vec3 q = fract(vec3(p.xyx) * 0.1031);
121
+ q += dot(q, q.yzx + 33.33);
122
+ return fract((q.x + q.y) * q.z);
123
+ }
124
+
125
+ float glyphMask(vec2 px, float cell, float seed) {
126
+ vec2 id = floor(px / cell);
127
+ vec2 f = fract(px / cell);
128
+ f = f * 0.74 + 0.13;
129
+ f.x = 1.0 - f.x;
130
+ float tick = floor(uTime * uMutate * 1.6 + hash21(id + seed) * 9.0);
131
+ float idx = floor(
132
+ hash21(id * 1.71 + vec2(seed + tick * 7.31, tick * 0.613)) * uGlyphCount
133
+ );
134
+ float gx = mod(idx, uAtlasGrid);
135
+ float gy = floor(idx / uAtlasGrid);
136
+ vec2 auv = (vec2(gx, gy) + f) / uAtlasGrid;
137
+ return texture(uAtlas, auv).a;
138
+ }
139
+
140
+ float colSpeed(float col, float seed) {
141
+ float variance = mix(0.35, 1.0, hash11(col * 0.37 + seed + 3.1));
142
+ return uSpeed * mix(1.0, variance, uSpeedVar) * 0.5;
143
+ }
144
+
145
+ float colOffset(float col, float seed) {
146
+ return hash11(col * 1.713 + seed) * 9.0;
147
+ }
148
+
149
+ vec2 wakeAt(float xpx) {
150
+ float u = clamp(xpx / max(uResolution.x, 1.0), 0.0, 1.0);
151
+ return texture(uWake, vec2(u, 0.5)).rg;
152
+ }
153
+
154
+ float lum(vec3 c) {
155
+ return dot(c, vec3(0.299, 0.587, 0.114));
156
+ }
157
+
158
+ void main () {
159
+ vec2 frag = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y + uScroll);
160
+ vec2 cuv = vec2(vUv.x, 1.0 - vUv.y);
161
+ float yn = 1.0 - frag.y / uResolution.y;
162
+
163
+ const float scales[3] = float[3](1.0, 1.5, 2.2);
164
+ const float weights[3] = float[3](1.0, 0.45, 0.22);
165
+ const float seeds[3] = float[3](0.0, 19.7, 41.3);
166
+
167
+ float g = 0.0;
168
+ float headG = 0.0;
169
+ for (int l = 0; l < 3; l++) {
170
+ if (float(l) >= uLayers) break;
171
+ float cell = uCell * scales[l];
172
+ float col = floor(frag.x / cell);
173
+ float sp = colSpeed(col, seeds[l]);
174
+ float off = colOffset(col, seeds[l]);
175
+ vec2 wk = uStir > 0.0 ? wakeAt((col + 0.5) * cell) : vec2(0.0);
176
+ float exc = uStir * wk.y;
177
+ float T = uTime * sp + off + sp * wk.x;
178
+ float phase = fract(yn + T);
179
+ float cyc = floor(yn + T);
180
+ float gate = step(hash21(vec2(col, cyc) + seeds[l]), uDensity);
181
+ float b = clamp(uTrail / (phase * 22.0), 0.0, 1.3) - 0.04;
182
+ if (b <= 0.0 || gate < 0.5) continue;
183
+ float flick = 1.0 + uFlicker * 0.6 *
184
+ sin(uTime * 14.0 + hash21(vec2(col, cyc)) * 40.0 + phase * 30.0);
185
+ float m = glyphMask(frag, cell, seeds[l] + cyc * 0.173);
186
+ float cellYn = cell / uResolution.y;
187
+ float head = 1.0 - smoothstep(0.0, cellYn * 1.2, phase);
188
+ g += m * b * flick * weights[l] * (1.0 + head * uGlow * 1.4) *
189
+ (1.0 + exc * 1.6);
190
+ headG += m * head * weights[l] * uGlow * (1.0 + exc * 1.1);
191
+ }
192
+ g = max(g, 0.0);
193
+
194
+ if (uHasContent < 0.5) {
195
+ vec3 rainCol = mix(uColor, uHeadColor, clamp(headG, 0.0, 1.0));
196
+ float a = clamp(g, 0.0, 1.0);
197
+ outColor = vec4(rainCol * a, a);
198
+ return;
199
+ }
200
+
201
+ vec2 e = vec2(3.0, 0.0) / uResolution;
202
+ vec4 content = texture(uContent, cuv);
203
+ float lC = lum(content.rgb);
204
+ float lX1 = lum(texture(uContent, clamp(cuv - e.xy, 0.0, 1.0)).rgb);
205
+ float lX2 = lum(texture(uContent, clamp(cuv + e.xy, 0.0, 1.0)).rgb);
206
+ float lY1 = lum(texture(uContent, clamp(cuv - e.yx, 0.0, 1.0)).rgb);
207
+ float lY2 = lum(texture(uContent, clamp(cuv + e.yx, 0.0, 1.0)).rgb);
208
+ vec3 N = normalize(vec3(
209
+ -(lX2 - lX1) * uRelief * 4.0,
210
+ -(lY2 - lY1) * uRelief * 4.0,
211
+ 1.0
212
+ ));
213
+ float reliefMix = clamp(uRelief, 0.0, 1.0);
214
+ vec2 e2 = vec2(30.0, 0.0) / uResolution;
215
+ float bgL = (lC
216
+ + lum(texture(uContent, clamp(cuv - e2.xy, 0.0, 1.0)).rgb)
217
+ + lum(texture(uContent, clamp(cuv + e2.xy, 0.0, 1.0)).rgb)
218
+ + lum(texture(uContent, clamp(cuv - e2.yx, 0.0, 1.0)).rgb)
219
+ + lum(texture(uContent, clamp(cuv + e2.yx, 0.0, 1.0)).rgb)) * 0.2;
220
+ float bright = smoothstep(0.55, 0.8, uPageLum) * smoothstep(0.2, 0.45, bgL);
221
+
222
+ float lightSum = 0.0;
223
+ float sigma2 = uLightRadius * uLightRadius * 0.5;
224
+ float reach = uLightRadius * 1.6;
225
+ float stride = max(1.0, ceil((uLightRadius * 1.7) / (uCell * 12.0)));
226
+ float baseCol = floor(floor(frag.x / uCell) / stride);
227
+ for (int o = -12; o <= 12; o++) {
228
+ float c = (baseCol + float(o)) * stride;
229
+ if (c < 0.0) continue;
230
+ float dx = (c + 0.5) * uCell - frag.x;
231
+ float wx = 1.0 - smoothstep(reach * 0.7, reach, abs(dx));
232
+ if (wx <= 0.0) continue;
233
+ float sp = colSpeed(c, 0.0);
234
+ float off = colOffset(c, 0.0);
235
+ vec2 wk = uStir > 0.0 ? wakeAt((c + 0.5) * uCell) : vec2(0.0);
236
+ float lampBoost = 1.0 + uStir * wk.y * 1.4;
237
+ float T = uTime * sp + off + sp * wk.x;
238
+ float s = 1.0 - frag.y / uResolution.y + T;
239
+ float k0 = floor(s);
240
+ for (int h = 0; h < 2; h++) {
241
+ float k = k0 + float(h);
242
+ float gate = step(hash21(vec2(c, k)), uDensity);
243
+ if (gate < 0.5) continue;
244
+ float lamp = 0.6 + 0.4 * hash11(c * 3.97 + k * 0.713);
245
+ float headDocY = (1.0 - (k - T)) * uResolution.y;
246
+ vec3 dv = vec3(dx, headDocY - frag.y, uLightHeight);
247
+ float d2 = dot(dv, dv);
248
+ float att = exp(-d2 / sigma2);
249
+ vec3 L = dv * inversesqrt(max(d2, 1.0));
250
+ float dif = mix(1.0, 0.25 + 0.75 * max(dot(N, L), 0.0), reliefMix);
251
+ lightSum += att * dif * wx * lamp * lampBoost;
252
+ }
253
+ }
254
+ float ls = lightSum * uLight * (0.6 + 0.4 * uGlow);
255
+ float lit = 2.2 * ls / (ls + 1.1);
256
+
257
+ float dimEff = uDim * (1.0 - bright);
258
+ float shade = mix(
259
+ clamp(1.0 - dimEff, 0.0, 1.0),
260
+ 1.0,
261
+ smoothstep(0.0, 1.0, lit)
262
+ );
263
+ vec3 col = content.rgb * shade;
264
+ col += uColor * lit * 0.14 * (1.0 - lC * 0.75) * (1.0 - bright);
265
+ col += uColor * clamp(lit - 1.0, 0.0, 1.0) * 0.1 * (1.0 - bright);
266
+
267
+ vec3 glyphCol = mix(uColor, uColor * 0.24 + vec3(0.02), lC * (1.0 - bright));
268
+ glyphCol = mix(glyphCol, uHeadColor, clamp(headG, 0.0, 1.0));
269
+ glyphCol = mix(glyphCol, vec3(1.0), bright * clamp(headG - 0.6, 0.0, 0.4));
270
+ float gA = clamp(g, 0.0, 1.0);
271
+ float knock = gA * mix(mix(0.3, 0.88, lC), 1.0, bright);
272
+ float paint = min(g, 1.5) * mix(1.0, mix(0.92, 1.0, bright), lC);
273
+ col = col * (1.0 - knock) + glyphCol * paint;
274
+
275
+ float alpha = max(content.a, gA);
276
+ col = clamp(col, vec3(0.0), vec3(alpha));
277
+ outColor = vec4(col, alpha);
278
+ }`;
279
+
280
+ // src/core/supports.ts
281
+ function supportsHtmlInCanvas() {
282
+ if (typeof document === "undefined") return false;
283
+ const probe = document.createElement("canvas");
284
+ const ctx = probe.getContext("2d");
285
+ return Boolean(
286
+ ctx && typeof ctx.drawElementImage === "function" && typeof probe.requestPaint === "function"
287
+ );
288
+ }
289
+
290
+ // src/core/types.ts
291
+ var DEFAULT_CHARSET = "\uFF71\uFF72\uFF73\uFF74\uFF75\uFF76\uFF77\uFF78\uFF79\uFF7A\uFF7B\uFF7C\uFF7D\uFF7E\uFF7F\uFF80\uFF81\uFF82\uFF83\uFF84\uFF85\uFF86\uFF87\uFF88\uFF89\uFF8A\uFF8B\uFF8C\uFF8D\uFF8E\uFF8F\uFF90\uFF91\uFF92\uFF93\uFF94\uFF95\uFF96\uFF97\uFF98\uFF99\uFF9A\uFF9B\uFF9C\uFF9D0123456789Z*+-<>\xA6=:.";
292
+ var DEFAULTS = {
293
+ charset: DEFAULT_CHARSET,
294
+ cell: 15,
295
+ color: [0.267, 0.455, 1],
296
+ headColor: [0.169, 0.416, 1],
297
+ speed: 0.2,
298
+ speedVariance: 0.5,
299
+ density: 0.15,
300
+ trail: 0.65,
301
+ glow: 1.75,
302
+ mutate: 0,
303
+ flicker: 0,
304
+ layers: 2,
305
+ dim: 0.5,
306
+ light: 2.8,
307
+ lightRadius: 240,
308
+ lightHeight: 172,
309
+ relief: 0.05,
310
+ stir: 0.7,
311
+ stirRadius: 260,
312
+ settle: 0.9
313
+ };
314
+
315
+ // src/core/createGlyphRain.ts
316
+ function createGlyphRain(elements, options = {}) {
317
+ const config = { ...DEFAULTS, ...options };
318
+ const { source, content, output } = elements;
319
+ const maybeGl = output.getContext("webgl2", {
320
+ alpha: true,
321
+ depth: false,
322
+ stencil: false,
323
+ antialias: false,
324
+ premultipliedAlpha: true
325
+ });
326
+ if (!maybeGl || maybeGl.isContextLost()) return null;
327
+ const gl = maybeGl;
328
+ const sourceCtx = source.getContext("2d");
329
+ const paintable = source;
330
+ const htmlInCanvas = Boolean(
331
+ sourceCtx && typeof sourceCtx.drawElementImage === "function" && typeof paintable.requestPaint === "function"
332
+ );
333
+ let contentDirty = false;
334
+ let pageLum = 0;
335
+ let wake = () => {
336
+ };
337
+ function readPageLum() {
338
+ try {
339
+ const probe = document.createElement("canvas");
340
+ probe.width = probe.height = 1;
341
+ const pctx = probe.getContext("2d", { willReadFrequently: true });
342
+ if (!pctx) return 0;
343
+ let el = content;
344
+ while (el instanceof Element) {
345
+ const bgColor = getComputedStyle(el).backgroundColor;
346
+ if (bgColor && bgColor !== "transparent") {
347
+ pctx.clearRect(0, 0, 1, 1);
348
+ pctx.fillStyle = bgColor;
349
+ pctx.fillRect(0, 0, 1, 1);
350
+ const d = pctx.getImageData(0, 0, 1, 1).data;
351
+ if (d[3] > 128) {
352
+ return (0.2126 * d[0] + 0.7152 * d[1] + 0.0722 * d[2]) / 255;
353
+ }
354
+ }
355
+ el = el.parentElement;
356
+ }
357
+ } catch {
358
+ }
359
+ return 0;
360
+ }
361
+ if (htmlInCanvas) {
362
+ paintable.onpaint = () => {
363
+ try {
364
+ sourceCtx.reset();
365
+ sourceCtx.drawElementImage(content, 0, 0);
366
+ contentDirty = true;
367
+ wake();
368
+ } catch {
369
+ }
370
+ };
371
+ }
372
+ function compile(type, text) {
373
+ const shader = gl.createShader(type);
374
+ gl.shaderSource(shader, text);
375
+ gl.compileShader(shader);
376
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
377
+ console.error("GlyphRain shader error:", gl.getShaderInfoLog(shader));
378
+ }
379
+ return shader;
380
+ }
381
+ const vertexShader = compile(gl.VERTEX_SHADER, VERT);
382
+ const fragmentShader = compile(gl.FRAGMENT_SHADER, FRAG);
383
+ const program = gl.createProgram();
384
+ gl.attachShader(program, vertexShader);
385
+ gl.attachShader(program, fragmentShader);
386
+ gl.linkProgram(program);
387
+ const uniforms = {};
388
+ const uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
389
+ for (let i = 0; i < uniformCount; i++) {
390
+ const info = gl.getActiveUniform(program, i);
391
+ uniforms[info.name] = gl.getUniformLocation(program, info.name);
392
+ }
393
+ const quad = gl.createBuffer();
394
+ gl.bindBuffer(gl.ARRAY_BUFFER, quad);
395
+ gl.bufferData(
396
+ gl.ARRAY_BUFFER,
397
+ new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
398
+ gl.STATIC_DRAW
399
+ );
400
+ gl.enableVertexAttribArray(0);
401
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
402
+ const contentTexture = gl.createTexture();
403
+ gl.bindTexture(gl.TEXTURE_2D, contentTexture);
404
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
405
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
406
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
407
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
408
+ gl.texImage2D(
409
+ gl.TEXTURE_2D,
410
+ 0,
411
+ gl.RGBA,
412
+ 1,
413
+ 1,
414
+ 0,
415
+ gl.RGBA,
416
+ gl.UNSIGNED_BYTE,
417
+ new Uint8Array([0, 0, 0, 0])
418
+ );
419
+ const atlasTexture = gl.createTexture();
420
+ let atlasCount = 1;
421
+ let atlasGrid = 1;
422
+ let atlasCharset = "";
423
+ function syncAtlas() {
424
+ if (config.charset === atlasCharset) return;
425
+ atlasCharset = config.charset;
426
+ const atlas = buildAtlas(config.charset);
427
+ atlasCount = atlas.count;
428
+ atlasGrid = atlas.grid;
429
+ gl.bindTexture(gl.TEXTURE_2D, atlasTexture);
430
+ gl.texParameteri(
431
+ gl.TEXTURE_2D,
432
+ gl.TEXTURE_MIN_FILTER,
433
+ gl.LINEAR_MIPMAP_LINEAR
434
+ );
435
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
436
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
437
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
438
+ gl.texImage2D(
439
+ gl.TEXTURE_2D,
440
+ 0,
441
+ gl.RGBA,
442
+ gl.RGBA,
443
+ gl.UNSIGNED_BYTE,
444
+ atlas.canvas
445
+ );
446
+ gl.generateMipmap(gl.TEXTURE_2D);
447
+ }
448
+ syncAtlas();
449
+ let dpr = 1;
450
+ function syncCanvasSize() {
451
+ dpr = Math.min(window.devicePixelRatio || 1, 2);
452
+ const width = Math.max(1, Math.round(output.clientWidth * dpr));
453
+ const height = Math.max(1, Math.round(output.clientHeight * dpr));
454
+ if (output.width !== width || output.height !== height) {
455
+ output.width = width;
456
+ output.height = height;
457
+ }
458
+ if (htmlInCanvas) {
459
+ const cssWidth = Math.max(1, Math.round(source.clientWidth));
460
+ const cssHeight = Math.max(1, Math.round(source.clientHeight));
461
+ if (source.width !== cssWidth * dpr || source.height !== cssHeight * dpr) {
462
+ source.width = cssWidth * dpr;
463
+ source.height = cssHeight * dpr;
464
+ }
465
+ paintable.requestPaint();
466
+ }
467
+ }
468
+ syncCanvasSize();
469
+ function uploadContent() {
470
+ if (!htmlInCanvas || !contentDirty) return;
471
+ contentDirty = false;
472
+ pageLum = readPageLum();
473
+ gl.bindTexture(gl.TEXTURE_2D, contentTexture);
474
+ gl.texImage2D(
475
+ gl.TEXTURE_2D,
476
+ 0,
477
+ gl.RGBA,
478
+ gl.RGBA,
479
+ gl.UNSIGNED_BYTE,
480
+ source
481
+ );
482
+ sourceCtx.clearRect(0, 0, source.width, source.height);
483
+ }
484
+ let time = 7.3;
485
+ const WAKE_RES = 256;
486
+ const wakeCharge = new Float32Array(WAKE_RES);
487
+ const wakeField = new Float32Array(WAKE_RES * 2);
488
+ let wakeLive = false;
489
+ let wakeTouched = false;
490
+ let pointerX = 0;
491
+ let tracking = false;
492
+ const wakeTexture = gl.createTexture();
493
+ gl.bindTexture(gl.TEXTURE_2D, wakeTexture);
494
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
495
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
496
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
497
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
498
+ gl.texImage2D(
499
+ gl.TEXTURE_2D,
500
+ 0,
501
+ gl.RG32F,
502
+ WAKE_RES,
503
+ 1,
504
+ 0,
505
+ gl.RG,
506
+ gl.FLOAT,
507
+ wakeField
508
+ );
509
+ function stirAmount() {
510
+ return Math.min(Math.max(config.stir, 0), 1);
511
+ }
512
+ function wakeSpan() {
513
+ const width = Math.max(output.clientWidth, 1);
514
+ const px = Math.min(Math.max(config.stirRadius, 8), 2e3);
515
+ return Math.max(px / width, 1 / WAKE_RES);
516
+ }
517
+ function stepWake(delta) {
518
+ const stir = stirAmount();
519
+ const settleT = Math.min(Math.max(config.settle, 0.05), 8);
520
+ const decay = Math.exp(-delta / settleT);
521
+ const span = wakeSpan();
522
+ const drive = stir > 1e-3 && !reducedMotion;
523
+ const track = drive && tracking;
524
+ let live = false;
525
+ for (let i = 0; i < WAKE_RES; i++) {
526
+ let charge = wakeCharge[i] * decay;
527
+ if (track) {
528
+ const d = Math.abs((i + 0.5) / WAKE_RES - pointerX) / span;
529
+ if (d < 1) {
530
+ const t = 1 - d;
531
+ const target = t * t * (3 - 2 * t);
532
+ if (target > charge) charge = target;
533
+ }
534
+ }
535
+ if (charge < 1e-4) charge = 0;
536
+ wakeCharge[i] = charge;
537
+ if (charge > 0) {
538
+ live = true;
539
+ if (drive) {
540
+ wakeField[i * 2] += delta * stir * 2.2 * charge;
541
+ wakeTouched = true;
542
+ }
543
+ }
544
+ wakeField[i * 2 + 1] = charge;
545
+ }
546
+ if (!live && !wakeLive) return;
547
+ wakeLive = live;
548
+ gl.bindTexture(gl.TEXTURE_2D, wakeTexture);
549
+ gl.texSubImage2D(
550
+ gl.TEXTURE_2D,
551
+ 0,
552
+ 0,
553
+ 0,
554
+ WAKE_RES,
555
+ 1,
556
+ gl.RG,
557
+ gl.FLOAT,
558
+ wakeField
559
+ );
560
+ }
561
+ function render() {
562
+ uploadContent();
563
+ gl.useProgram(program);
564
+ gl.activeTexture(gl.TEXTURE0);
565
+ gl.bindTexture(gl.TEXTURE_2D, contentTexture);
566
+ gl.uniform1i(uniforms.uContent, 0);
567
+ gl.activeTexture(gl.TEXTURE1);
568
+ gl.bindTexture(gl.TEXTURE_2D, atlasTexture);
569
+ gl.uniform1i(uniforms.uAtlas, 1);
570
+ gl.activeTexture(gl.TEXTURE2);
571
+ gl.bindTexture(gl.TEXTURE_2D, wakeTexture);
572
+ gl.uniform1i(uniforms.uWake, 2);
573
+ gl.uniform2f(uniforms.uResolution, output.width, output.height);
574
+ gl.uniform1f(uniforms.uTime, time);
575
+ gl.uniform1f(uniforms.uCell, Math.min(Math.max(config.cell, 8), 64) * dpr);
576
+ gl.uniform1f(uniforms.uGlyphCount, atlasCount);
577
+ gl.uniform1f(uniforms.uAtlasGrid, atlasGrid);
578
+ gl.uniform3f(
579
+ uniforms.uColor,
580
+ config.color[0],
581
+ config.color[1],
582
+ config.color[2]
583
+ );
584
+ gl.uniform3f(
585
+ uniforms.uHeadColor,
586
+ config.headColor[0],
587
+ config.headColor[1],
588
+ config.headColor[2]
589
+ );
590
+ gl.uniform1f(uniforms.uSpeed, Math.min(Math.max(config.speed, 0.05), 3));
591
+ gl.uniform1f(
592
+ uniforms.uSpeedVar,
593
+ Math.min(Math.max(config.speedVariance, 0), 1)
594
+ );
595
+ gl.uniform1f(uniforms.uDensity, Math.min(Math.max(config.density, 0), 1));
596
+ gl.uniform1f(uniforms.uTrail, Math.min(Math.max(config.trail, 0.2), 3));
597
+ gl.uniform1f(uniforms.uGlow, Math.min(Math.max(config.glow, 0), 3));
598
+ gl.uniform1f(uniforms.uMutate, Math.min(Math.max(config.mutate, 0), 4));
599
+ gl.uniform1f(uniforms.uFlicker, Math.min(Math.max(config.flicker, 0), 1));
600
+ gl.uniform1f(
601
+ uniforms.uLayers,
602
+ Math.round(Math.min(Math.max(config.layers, 1), 3))
603
+ );
604
+ gl.uniform1f(uniforms.uDim, Math.min(Math.max(config.dim, 0), 1));
605
+ gl.uniform1f(uniforms.uLight, Math.min(Math.max(config.light, 0), 3));
606
+ gl.uniform1f(
607
+ uniforms.uLightRadius,
608
+ Math.min(Math.max(config.lightRadius, 20), 600) * dpr
609
+ );
610
+ gl.uniform1f(uniforms.uLightHeight, Math.max(config.lightHeight, 4) * dpr);
611
+ gl.uniform1f(uniforms.uRelief, Math.min(Math.max(config.relief, 0), 2));
612
+ gl.uniform1f(uniforms.uStir, wakeTouched ? stirAmount() : 0);
613
+ gl.uniform1f(uniforms.uScroll, content.scrollTop * dpr);
614
+ gl.uniform1f(uniforms.uPageLum, pageLum);
615
+ gl.uniform1f(uniforms.uHasContent, htmlInCanvas ? 1 : 0);
616
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
617
+ gl.viewport(0, 0, output.width, output.height);
618
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
619
+ }
620
+ let raf = 0;
621
+ let lastTime = performance.now();
622
+ let destroyed = false;
623
+ let running = false;
624
+ let visible = true;
625
+ const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
626
+ let reducedMotion = motionQuery.matches;
627
+ function frame(now) {
628
+ if (destroyed) return;
629
+ if (!visible) {
630
+ running = false;
631
+ return;
632
+ }
633
+ const delta = Math.min((now - lastTime) / 1e3, 1 / 30);
634
+ lastTime = now;
635
+ if (!reducedMotion) time += delta;
636
+ stepWake(delta);
637
+ render();
638
+ if (reducedMotion && !contentDirty) {
639
+ running = false;
640
+ return;
641
+ }
642
+ raf = requestAnimationFrame(frame);
643
+ }
644
+ function start() {
645
+ if (destroyed || running || !visible) return;
646
+ running = true;
647
+ lastTime = performance.now();
648
+ raf = requestAnimationFrame(frame);
649
+ }
650
+ wake = start;
651
+ start();
652
+ function onMotionChange() {
653
+ reducedMotion = motionQuery.matches;
654
+ if (reducedMotion) {
655
+ tracking = false;
656
+ wakeCharge.fill(0);
657
+ for (let i = 0; i < WAKE_RES; i++) wakeField[i * 2 + 1] = 0;
658
+ wakeLive = true;
659
+ }
660
+ start();
661
+ }
662
+ motionQuery.addEventListener("change", onMotionChange);
663
+ content.addEventListener("scroll", start, { passive: true });
664
+ const pointerHost = output.parentElement ?? output;
665
+ function pointerNorm(event) {
666
+ const box = output.getBoundingClientRect();
667
+ if (box.width < 1) return -1;
668
+ return (event.clientX - box.left) / box.width;
669
+ }
670
+ function onPointerMove(event) {
671
+ if (reducedMotion) return;
672
+ const x = pointerNorm(event);
673
+ if (x < 0) return;
674
+ pointerX = x;
675
+ tracking = true;
676
+ start();
677
+ }
678
+ function onPointerLeave() {
679
+ tracking = false;
680
+ }
681
+ function onPointerDown(event) {
682
+ if (reducedMotion || stirAmount() <= 1e-3) return;
683
+ const x = pointerNorm(event);
684
+ if (x < 0) return;
685
+ pointerX = x;
686
+ tracking = true;
687
+ const span = wakeSpan() * 1.8;
688
+ for (let i = 0; i < WAKE_RES; i++) {
689
+ const d = Math.abs((i + 0.5) / WAKE_RES - x) / span;
690
+ if (d >= 1) continue;
691
+ const t = 1 - d;
692
+ const burst = t * t * (3 - 2 * t);
693
+ if (burst > wakeCharge[i]) wakeCharge[i] = burst;
694
+ }
695
+ start();
696
+ }
697
+ pointerHost.addEventListener("pointermove", onPointerMove, { passive: true });
698
+ pointerHost.addEventListener("pointerleave", onPointerLeave, { passive: true });
699
+ pointerHost.addEventListener("pointercancel", onPointerLeave, { passive: true });
700
+ pointerHost.addEventListener("pointerdown", onPointerDown, { passive: true });
701
+ const observer = new ResizeObserver(() => {
702
+ syncCanvasSize();
703
+ start();
704
+ });
705
+ observer.observe(output);
706
+ observer.observe(content);
707
+ const intersection = new IntersectionObserver((entries) => {
708
+ visible = entries[entries.length - 1]?.isIntersecting ?? true;
709
+ if (visible) start();
710
+ });
711
+ intersection.observe(output);
712
+ return {
713
+ setOptions(next) {
714
+ let changed = false;
715
+ for (const [key, value] of Object.entries(next)) {
716
+ const prev = config[key];
717
+ if (Array.isArray(value) && Array.isArray(prev)) {
718
+ if (value.length !== prev.length || value.some((item, i) => item !== prev[i])) {
719
+ changed = true;
720
+ break;
721
+ }
722
+ } else if (prev !== value) {
723
+ changed = true;
724
+ break;
725
+ }
726
+ }
727
+ Object.assign(config, next);
728
+ if (!changed) return;
729
+ syncAtlas();
730
+ syncCanvasSize();
731
+ start();
732
+ },
733
+ resize() {
734
+ syncCanvasSize();
735
+ start();
736
+ },
737
+ destroy() {
738
+ destroyed = true;
739
+ cancelAnimationFrame(raf);
740
+ observer.disconnect();
741
+ intersection.disconnect();
742
+ motionQuery.removeEventListener("change", onMotionChange);
743
+ content.removeEventListener("scroll", start);
744
+ pointerHost.removeEventListener("pointermove", onPointerMove);
745
+ pointerHost.removeEventListener("pointerleave", onPointerLeave);
746
+ pointerHost.removeEventListener("pointercancel", onPointerLeave);
747
+ pointerHost.removeEventListener("pointerdown", onPointerDown);
748
+ gl.deleteTexture(contentTexture);
749
+ gl.deleteTexture(atlasTexture);
750
+ gl.deleteTexture(wakeTexture);
751
+ gl.deleteProgram(program);
752
+ gl.deleteShader(vertexShader);
753
+ gl.deleteShader(fragmentShader);
754
+ gl.deleteBuffer(quad);
755
+ if (htmlInCanvas) paintable.onpaint = null;
756
+ }
757
+ };
758
+ }
759
+
760
+ // src/browser/jsx-runtime.ts
761
+ function jsx(type, props, key) {
762
+ const create = createElement;
763
+ return key === void 0 ? create(type, props) : create(type, { ...props, key });
764
+ }
765
+ var jsxs = jsx;
766
+
767
+ // src/react/GlyphRain.tsx
768
+ var emptySubscribe = () => () => {
769
+ };
770
+ function GlyphRain({
771
+ children,
772
+ className,
773
+ style,
774
+ ...options
775
+ }) {
776
+ const sourceRef = useRef(null);
777
+ const contentRef = useRef(null);
778
+ const outputRef = useRef(null);
779
+ const instanceRef = useRef(null);
780
+ const [initialOptions] = useState(options);
781
+ const [failed, setFailed] = useState(false);
782
+ const supported = useSyncExternalStore(
783
+ emptySubscribe,
784
+ supportsHtmlInCanvas,
785
+ () => false
786
+ );
787
+ const native = supported && !failed;
788
+ useEffect(() => {
789
+ const source = sourceRef.current;
790
+ const content = contentRef.current;
791
+ const output = outputRef.current;
792
+ if (!source || !content || !output) return;
793
+ instanceRef.current = createGlyphRain(
794
+ { source, content, output },
795
+ initialOptions
796
+ );
797
+ if (native && !instanceRef.current) setFailed(true);
798
+ return () => {
799
+ instanceRef.current?.destroy();
800
+ instanceRef.current = null;
801
+ };
802
+ }, [initialOptions, native]);
803
+ useEffect(() => {
804
+ instanceRef.current?.setOptions(options);
805
+ });
806
+ return /* @__PURE__ */ jsxs("div", { className, style: { position: "relative", ...style }, children: [
807
+ /* @__PURE__ */ jsx(
808
+ "canvas",
809
+ {
810
+ ref: sourceRef,
811
+ layoutsubtree: "true",
812
+ suppressHydrationWarning: true,
813
+ style: native ? {
814
+ position: "absolute",
815
+ inset: 0,
816
+ width: "100%",
817
+ height: "100%"
818
+ } : { display: "none" },
819
+ children: native ? /* @__PURE__ */ jsx(
820
+ "div",
821
+ {
822
+ ref: contentRef,
823
+ style: {
824
+ position: "relative",
825
+ width: "100%",
826
+ height: "100%",
827
+ overflow: "auto"
828
+ },
829
+ children
830
+ }
831
+ ) : null
832
+ }
833
+ ),
834
+ !native ? /* @__PURE__ */ jsx(
835
+ "div",
836
+ {
837
+ ref: contentRef,
838
+ style: {
839
+ position: "relative",
840
+ width: "100%",
841
+ height: "100%",
842
+ overflow: "auto"
843
+ },
844
+ children
845
+ }
846
+ ) : null,
847
+ /* @__PURE__ */ jsx(
848
+ "canvas",
849
+ {
850
+ ref: outputRef,
851
+ "aria-hidden": true,
852
+ style: {
853
+ position: "absolute",
854
+ inset: 0,
855
+ width: "100%",
856
+ height: "100%",
857
+ pointerEvents: "none"
858
+ }
859
+ }
860
+ )
861
+ ] });
862
+ }
863
+ return __toCommonJS(src_exports);
864
+ })();
865
+ //# sourceMappingURL=glyph-rain.iife.js.map